feat(admin): 添加用户管理相关文件

添加用户管理视图、API和状态管理文件
This commit is contained in:
张翔
2026-03-28 14:37:29 +08:00
commit 08ea5fbe98
1643 changed files with 255646 additions and 0 deletions
@@ -0,0 +1,19 @@
"""
Admin端页面对象模型
提供后台管理系统的页面对象封装。
"""
from .login_page import LoginPage
from .dashboard_page import DashboardPage
from .user_management_page import UserManagementPage
from .role_management_page import RoleManagementPage
from .menu_management_page import MenuManagementPage
__all__ = [
"LoginPage",
"DashboardPage",
"UserManagementPage",
"RoleManagementPage",
"MenuManagementPage",
]
@@ -0,0 +1,98 @@
"""
仪表盘页面
Admin后台仪表盘页面的页面对象模型。
"""
from playwright.sync_api import Page
from ..base_page import BasePage
class DashboardPage(BasePage):
"""仪表盘页面"""
# 页面路径
PATH = "/dashboard"
# 元素定位器
LOCATORS = {
"page_title": ".dashboard h1, .dashboard-title",
"sidebar_menu": ".el-menu, .sidebar",
"menu_items": ".el-menu-item",
"user_info": ".user-info, .user-avatar",
"logout_button": ".user-info",
"stat_cards": ".stat-card, .el-card",
"charts": ".chart-container",
}
def __init__(self, page: Page, base_url: str = ""):
super().__init__(page, base_url)
def navigate(self, path: str = "") -> None:
"""导航到仪表盘页面"""
target_path = path if path else self.PATH
self.page.goto(f"{self.base_url}{target_path}")
self.wait_for_load()
def is_loaded(self) -> bool:
"""检查页面是否加载完成"""
return self.is_element_visible(self.LOCATORS["stat_cards"], timeout=5000)
def get_page_title(self) -> str:
"""获取页面标题"""
return self.get_text(self.LOCATORS["page_title"])
def get_menu_items(self) -> list:
"""获取菜单项列表"""
items = self.page.locator(self.LOCATORS["menu_items"]).all()
return [item.text_content() for item in items]
def click_menu_item(self, menu_text: str) -> None:
"""点击菜单项"""
self.page.locator(self.LOCATORS["menu_items"]).filter(
has_text=menu_text
).click()
def is_menu_item_visible(self, menu_text: str) -> bool:
"""检查菜单项是否可见"""
try:
self.page.locator(self.LOCATORS["menu_items"]).filter(
has_text=menu_text
).wait_for(timeout=5000)
return True
except:
return False
def click_logout(self) -> None:
"""点击登出按钮"""
try:
user_info = self.page.locator(self.LOCATORS["user_info"])
if user_info.is_visible():
user_info.click()
self.wait_for_timeout(500)
logout_option = self.page.locator('.el-dropdown-menu__item:has-text("退出"), .el-dropdown-menu__item:has-text("logout"), .el-dropdown-menu__item:has-text("登出")')
if logout_option.count() > 0:
logout_option.first.click()
return
self.page.evaluate('localStorage.clear(); sessionStorage.clear();')
self.page.goto(f"{self.base_url}/login")
except Exception as e:
self.page.evaluate('localStorage.clear(); sessionStorage.clear();')
self.page.goto(f"{self.base_url}/login")
def get_stat_cards_count(self) -> int:
"""获取统计卡片数量"""
return self.get_elements_count(self.LOCATORS["stat_cards"])
def get_charts_count(self) -> int:
"""获取图表数量"""
return self.get_elements_count(self.LOCATORS["charts"])
def wait_for_data_load(self, timeout: int = 10000) -> None:
"""等待数据加载完成"""
self.wait_for_timeout(2000) # 等待数据加载动画
def refresh_data(self) -> None:
"""刷新数据"""
self.reload()
self.wait_for_load()
@@ -0,0 +1,91 @@
"""
登录页面
Admin后台登录页面的页面对象模型。
"""
from playwright.sync_api import Page
from ..base_page import BasePage
class LoginPage(BasePage):
"""登录页面"""
# 页面路径
PATH = "/login"
# 元素定位器
LOCATORS = {
"username_input": '[data-testid="username-input"] input, input[data-testid="username-input"]',
"password_input": '[data-testid="password-input"] input, input[data-testid="password-input"]',
"submit_button": '[data-testid="login-button"], button[type="submit"]',
"error_message": ".el-message--error .el-message__content, .el-message--error, .el-notification__content, .error-message",
"page_title": ".login-title, h1",
}
def __init__(self, page: Page, base_url: str = ""):
super().__init__(page, base_url)
def navigate(self, path: str = "") -> None:
"""导航到登录页面"""
target_path = path if path else self.PATH
self.page.goto(f"{self.base_url}{target_path}")
self.wait_for_load()
def is_loaded(self) -> bool:
"""检查页面是否加载完成"""
return self.is_element_visible(self.LOCATORS["username_input"])
def login(self, username: str, password: str) -> None:
"""
执行登录操作
Args:
username: 用户名
password: 密码
"""
self.fill_username(username)
self.fill_password(password)
self.click_submit()
def fill_username(self, username: str) -> None:
"""填写用户名"""
self.fill_input(self.LOCATORS["username_input"], username)
def fill_password(self, password: str) -> None:
"""填写密码"""
self.fill_input(self.LOCATORS["password_input"], password)
def click_submit(self) -> None:
"""点击登录按钮"""
self.click_element(self.LOCATORS["submit_button"])
def get_error_message(self) -> str:
"""获取错误消息"""
if self.is_element_visible(self.LOCATORS["error_message"], timeout=3000):
return self.get_text(self.LOCATORS["error_message"])
return ""
def has_error_message(self) -> bool:
"""检查是否有错误消息"""
self.wait_for_timeout(1000)
return self.is_element_visible(
self.LOCATORS["error_message"], timeout=5000
)
def clear_form(self) -> None:
"""清空表单"""
self.page.locator(self.LOCATORS["username_input"]).fill('')
self.page.locator(self.LOCATORS["password_input"]).fill('')
def is_submit_enabled(self) -> bool:
"""检查提交按钮是否可用"""
return self.is_element_enabled(self.LOCATORS["submit_button"])
def get_page_title(self) -> str:
"""获取页面标题"""
return self.get_text(self.LOCATORS["page_title"])
def wait_for_redirect(self, timeout: int = 10000) -> None:
"""等待页面跳转"""
self.page.wait_for_url("**/dashboard**", timeout=timeout)
@@ -0,0 +1,148 @@
"""
菜单管理页面
Admin后台菜单管理页面的页面对象模型。
"""
from playwright.sync_api import Page
from ..base_page import BasePage
class MenuManagementPage(BasePage):
"""菜单管理页面"""
# 页面路径
PATH = "/system/menu"
# 元素定位器
LOCATORS = {
"page_title": ".page-title, h1",
"create_button": ".el-button--primary, .el-button:has-text('新增')",
"menu_tree": ".el-tree, .tree-container",
"tree_nodes": ".el-tree-node, .tree-node",
"dialog": ".el-dialog",
"dialog_title": ".el-dialog__title",
"form_name": 'input[name="name"], input[placeholder*="名称"]',
"form_path": 'input[name="path"], input[placeholder*="路径"]',
"form_component": 'input[name="component"], input[placeholder*="组件"]',
"form_icon": 'input[name="icon"], input[placeholder*="图标"]',
"form_sort": 'input[name="sort"], input[placeholder*="排序"]',
"form_type": '.el-radio-group',
"form_parent": '.el-select',
"form_submit": '.el-dialog__footer .el-button--primary',
"form_cancel": '.el-dialog__footer .el-button--default',
"delete_confirm": ".el-message-box__btns .el-button--primary",
"success_message": ".el-message--success",
"error_message": ".el-message--error",
}
def __init__(self, page: Page, base_url: str = ""):
super().__init__(page, base_url)
def navigate(self, path: str = "") -> None:
"""导航到菜单管理页面"""
target_path = path if path else self.PATH
self.page.goto(f"{self.base_url}{target_path}")
self.wait_for_load()
def is_loaded(self) -> bool:
"""检查页面是否加载完成"""
return self.is_element_visible(self.LOCATORS["menu_tree"])
def click_create_button(self) -> None:
"""点击新建按钮"""
self.click_element(self.LOCATORS["create_button"])
def expand_tree_node(self, node_name: str) -> None:
"""展开树节点"""
node = self.page.locator(self.LOCATORS["menu_tree"]).locator(
".el-tree-node__label"
).filter(has_text=node_name).locator("..").locator("..").locator(
".el-tree-node__expand-icon"
).first
node.click()
def click_node_edit(self, node_name: str) -> None:
"""点击节点编辑"""
node = self.page.locator(self.LOCATORS["menu_tree"]).locator(
".el-tree-node__label"
).filter(has_text=node_name).locator("..").locator("..")
edit_btn = node.locator(".el-button--primary").first
edit_btn.click()
def click_node_delete(self, node_name: str) -> None:
"""点击节点删除"""
node = self.page.locator(self.LOCATORS["menu_tree"]).locator(
".el-tree-node__label"
).filter(has_text=node_name).locator("..").locator("..")
delete_btn = node.locator(".el-button--danger").first
delete_btn.click()
def confirm_delete(self) -> None:
"""确认删除"""
self.click_element(self.LOCATORS["delete_confirm"])
def fill_form_name(self, name: str) -> None:
"""填写表单菜单名称"""
self.fill_input(self.LOCATORS["form_name"], name)
def fill_form_path(self, path: str) -> None:
"""填写表单菜单路径"""
self.fill_input(self.LOCATORS["form_path"], path)
def fill_form_component(self, component: str) -> None:
"""填写表单组件"""
self.fill_input(self.LOCATORS["form_component"], component)
def fill_form_icon(self, icon: str) -> None:
"""填写表单图标"""
self.fill_input(self.LOCATORS["form_icon"], icon)
def fill_form_sort(self, sort: int) -> None:
"""填写表单排序"""
self.fill_input(self.LOCATORS["form_sort"], str(sort))
def select_form_type(self, menu_type: str) -> None:
"""选择表单菜单类型"""
self.page.locator(self.LOCATORS["form_type"]).locator(
".el-radio"
).filter(has_text=menu_type).click()
def select_form_parent(self, parent_name: str) -> None:
"""选择表单父级菜单"""
self.click_element(self.LOCATORS["form_parent"])
self.page.locator(".el-select-dropdown__item").filter(
has_text=parent_name
).click()
def click_form_submit(self) -> None:
"""点击表单提交按钮"""
self.click_element(self.LOCATORS["form_submit"])
def click_form_cancel(self) -> None:
"""点击表单取消按钮"""
self.click_element(self.LOCATORS["form_cancel"])
def is_dialog_visible(self) -> bool:
"""检查对话框是否可见"""
return self.is_element_visible(self.LOCATORS["dialog"])
def get_dialog_title(self) -> str:
"""获取对话框标题"""
return self.get_text(self.LOCATORS["dialog_title"])
def has_success_message(self) -> bool:
"""检查是否有成功消息"""
return self.is_element_visible(
self.LOCATORS["success_message"], timeout=3000
)
def has_error_message(self) -> bool:
"""检查是否有错误消息"""
return self.is_element_visible(
self.LOCATORS["error_message"], timeout=3000
)
def get_tree_nodes_count(self) -> int:
"""获取树节点数量"""
return self.get_elements_count(self.LOCATORS["tree_nodes"])
@@ -0,0 +1,141 @@
"""
角色管理页面
Admin后台角色管理页面的页面对象模型。
"""
from playwright.sync_api import Page
from ..base_page import BasePage
class RoleManagementPage(BasePage):
"""角色管理页面"""
# 页面路径
PATH = "/system/role"
# 元素定位器
LOCATORS = {
"page_title": ".page-title, h1",
"create_button": ".el-button--primary",
"search_input": ".search-input input",
"search_button": ".search-btn",
"table": ".el-table, table.el-table",
"table_rows": ".el-table__row, tr.el-table__row",
"dialog": ".el-dialog",
"dialog_title": ".el-dialog__title",
"form_name": 'input[name="name"], input[placeholder*="名称"]',
"form_code": 'input[name="code"], input[placeholder*="编码"]',
"form_description": 'textarea[name="description"], textarea[placeholder*="描述"]',
"form_status": '.el-select',
"form_submit": '.el-dialog__footer .el-button--primary',
"form_cancel": '.el-dialog__footer .el-button--default',
"permission_tree": ".el-tree",
"delete_confirm": ".el-message-box__btns .el-button--primary",
"success_message": ".el-message--success",
"error_message": ".el-message--error",
}
def __init__(self, page: Page, base_url: str = ""):
super().__init__(page, base_url)
def navigate(self, path: str = "") -> None:
"""导航到角色管理页面"""
target_path = path if path else self.PATH
self.page.goto(f"{self.base_url}{target_path}")
self.wait_for_load()
def is_loaded(self) -> bool:
"""检查页面是否加载完成"""
return self.is_element_visible(self.LOCATORS["table"])
def click_create_button(self) -> None:
"""点击新建按钮"""
self.click_element(self.LOCATORS["create_button"])
def fill_search(self, keyword: str) -> None:
"""填写搜索关键词"""
self.fill_input(self.LOCATORS["search_input"], keyword)
def click_search(self) -> None:
"""点击搜索按钮"""
self.click_element(self.LOCATORS["search_button"])
def get_table_rows_count(self) -> int:
"""获取表格行数"""
return self.get_elements_count(self.LOCATORS["table_rows"])
def click_row_edit(self, row_index: int = 0) -> None:
"""点击行编辑按钮"""
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
edit_btn = row.locator(".el-button--primary").first
edit_btn.click()
def click_row_delete(self, row_index: int = 0) -> None:
"""点击行删除按钮"""
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
delete_btn = row.locator(".el-button--danger").first
delete_btn.click()
def confirm_delete(self) -> None:
"""确认删除"""
self.click_element(self.LOCATORS["delete_confirm"])
def fill_form_name(self, name: str) -> None:
"""填写表单角色名称"""
self.fill_input(self.LOCATORS["form_name"], name)
def fill_form_code(self, code: str) -> None:
"""填写表单角色编码"""
self.fill_input(self.LOCATORS["form_code"], code)
def fill_form_description(self, description: str) -> None:
"""填写表单角色描述"""
self.fill_input(self.LOCATORS["form_description"], description)
def select_form_status(self, status: str) -> None:
"""选择表单状态"""
self.click_element(self.LOCATORS["form_status"])
self.page.locator(".el-select-dropdown__item").filter(
has_text=status
).click()
def click_form_submit(self) -> None:
"""点击表单提交按钮"""
self.click_element(self.LOCATORS["form_submit"])
def click_form_cancel(self) -> None:
"""点击表单取消按钮"""
self.click_element(self.LOCATORS["form_cancel"])
def is_dialog_visible(self) -> bool:
"""检查对话框是否可见"""
return self.is_element_visible(self.LOCATORS["dialog"])
def get_dialog_title(self) -> str:
"""获取对话框标题"""
return self.get_text(self.LOCATORS["dialog_title"])
def check_permission(self, permission_name: str) -> None:
"""勾选权限"""
self.page.locator(self.LOCATORS["permission_tree"]).locator(
".el-tree-node__label"
).filter(has_text=permission_name).locator("..").locator(
".el-checkbox__input"
).first.click()
def has_success_message(self) -> bool:
"""检查是否有成功消息"""
return self.is_element_visible(
self.LOCATORS["success_message"], timeout=3000
)
def has_error_message(self) -> bool:
"""检查是否有错误消息"""
return self.is_element_visible(
self.LOCATORS["error_message"], timeout=3000
)
def wait_for_table_load(self, timeout: int = 10000) -> None:
"""等待表格加载完成"""
self.wait_for_selector(self.LOCATORS["table_rows"], timeout=timeout)
@@ -0,0 +1,173 @@
"""
用户管理页面
Admin后台用户管理页面的页面对象模型。
"""
from playwright.sync_api import Page
from ..base_page import BasePage
class UserManagementPage(BasePage):
"""用户管理页面"""
# 页面路径
PATH = "/users"
# 元素定位器
LOCATORS = {
"page_title": ".user-management h1, .page-title",
"create_button": ".card-header .el-button--primary, .el-button:has-text('新增用户')",
"search_input": ".search-card input[v-model='queryParams.username'], .search-card .el-input input",
"search_button": ".search-card .el-button--primary, .el-button:has-text('搜索')",
"reset_button": ".search-card .el-button:not(.el-button--primary), .el-button:has-text('重置')",
"table": ".table-card .el-table, .el-table",
"table_rows": ".el-table__row",
"pagination": ".el-pagination",
"dialog": ".el-dialog",
"dialog_title": ".el-dialog__title",
"form_username": '.el-dialog input[v-model="formState.username"], .el-dialog input[placeholder*="用户名"]',
"form_nickname": '.el-dialog input[v-model="formState.nickname"], .el-dialog input[placeholder*="昵称"]',
"form_email": '.el-dialog input[v-model="formState.email"], .el-dialog input[placeholder*="邮箱"]',
"form_phone": '.el-dialog input[v-model="formState.phone"], .el-dialog input[placeholder*="手机"]',
"form_status": '.el-dialog .el-select',
"form_submit": '.el-dialog__footer .el-button--primary',
"form_cancel": '.el-dialog__footer .el-button:not(.el-button--primary)',
"delete_confirm": ".el-message-box__btns .el-button--primary, .el-popconfirm .el-button--primary",
"success_message": ".el-message--success .el-message__content, .el-message--success",
"error_message": ".el-message--error .el-message__content, .el-message--error",
}
def __init__(self, page: Page, base_url: str = ""):
super().__init__(page, base_url)
def navigate(self, path: str = "") -> None:
"""导航到用户管理页面"""
target_path = path if path else self.PATH
self.page.goto(f"{self.base_url}{target_path}")
self.wait_for_load()
def is_loaded(self) -> bool:
"""检查页面是否加载完成"""
try:
self.wait_for_timeout(2000)
return self.is_element_visible(self.LOCATORS["table"], timeout=10000)
except:
return False
def click_create_button(self) -> None:
"""点击新建按钮"""
self.click_element(self.LOCATORS["create_button"])
def fill_search(self, keyword: str) -> None:
"""填写搜索关键词"""
self.fill_input(self.LOCATORS["search_input"], keyword)
def click_search(self) -> None:
"""点击搜索按钮"""
self.click_element(self.LOCATORS["search_button"])
def click_reset(self) -> None:
"""点击重置按钮"""
self.click_element(self.LOCATORS["reset_button"])
def get_table_rows_count(self) -> int:
"""获取表格行数"""
return self.get_elements_count(self.LOCATORS["table_rows"])
def get_first_row_text(self) -> str:
"""获取第一行文本"""
rows = self.page.locator(self.LOCATORS["table_rows"]).all()
if rows:
return rows[0].text_content() or ""
return ""
def click_row_edit(self, row_index: int = 0) -> None:
"""点击行编辑按钮"""
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
edit_btn = row.locator(".el-button--primary").first
edit_btn.click()
def click_row_delete(self, row_index: int = 0) -> None:
"""点击行删除按钮"""
row = self.page.locator(self.LOCATORS["table_rows"]).nth(row_index)
delete_btn = row.locator(".el-button--danger").first
delete_btn.click()
def confirm_delete(self) -> None:
"""确认删除"""
self.click_element(self.LOCATORS["delete_confirm"])
def fill_form_username(self, username: str) -> None:
"""填写表单用户名"""
self.fill_input(self.LOCATORS["form_username"], username)
def fill_form_nickname(self, nickname: str) -> None:
"""填写表单昵称"""
self.fill_input(self.LOCATORS["form_nickname"], nickname)
def fill_form_email(self, email: str) -> None:
"""填写表单邮箱"""
self.fill_input(self.LOCATORS["form_email"], email)
def fill_form_phone(self, phone: str) -> None:
"""填写表单电话"""
self.fill_input(self.LOCATORS["form_phone"], phone)
def select_form_status(self, status: str) -> None:
"""选择表单状态"""
self.click_element(self.LOCATORS["form_status"])
self.page.locator(".el-select-dropdown__item").filter(
has_text=status
).click()
def click_form_submit(self) -> None:
"""点击表单提交按钮"""
self.click_element(self.LOCATORS["form_submit"])
def click_form_cancel(self) -> None:
"""点击表单取消按钮"""
self.click_element(self.LOCATORS["form_cancel"])
def is_dialog_visible(self) -> bool:
"""检查对话框是否可见"""
try:
dialog = self.page.locator(self.LOCATORS["dialog"])
if dialog.count() == 0:
return False
is_visible = dialog.is_visible()
if not is_visible:
return False
return True
except:
return False
def get_dialog_title(self) -> str:
"""获取对话框标题"""
return self.get_text(self.LOCATORS["dialog_title"])
def has_success_message(self) -> bool:
"""检查是否有成功消息"""
return self.is_element_visible(
self.LOCATORS["success_message"], timeout=3000
)
def has_error_message(self) -> bool:
"""检查是否有错误消息"""
return self.is_element_visible(
self.LOCATORS["error_message"], timeout=3000
)
def wait_for_table_load(self, timeout: int = 10000) -> None:
"""等待表格加载完成"""
try:
self.wait_for_timeout(2000)
self.wait_for_selector(self.LOCATORS["table_rows"], timeout=timeout)
except:
pass
def search_and_wait(self, keyword: str, timeout: int = 10000) -> None:
"""搜索并等待结果"""
self.fill_search(keyword)
self.click_search()
self.wait_for_table_load(timeout)