Files
yidaima_tools/step1.py

211 lines
8.4 KiB
Python
Raw Normal View History

2026-04-09 14:55:54 +08:00
from __future__ import annotations
import os
import re
import time
from typing import Optional, Callable
from config_loader import get_config
def _now_datestr() -> str:
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d")
def _safe_screenshot(page, save_path: str) -> None:
try:
page.screenshot(path=save_path, full_page=True)
except Exception:
pass
class Step1FeastCoding:
"""
Step 1: 访问 feast.yidaima.cn登录并点击 FeastCoding 按钮
参考: docs/step1.md
"""
def __init__(self, chrome_path: str, username: str, password: str, log_callback: Optional[Callable[[str], None]] = None):
self.chrome_path = chrome_path
self.username = username
self.password = password
self.url = "https://feast.yidaima.cn/login"
# self.url = "http://localhost/login"
self.project_name = "【A173】基于Springboot + vue3实现的学生交流互助平台"
self.feast_button = "FeastCoding"
self.browser = None
self.page = None
self.log_callback = log_callback
def _log(self, message: str):
"""输出日志,支持回调到 GUI"""
print(message)
if self.log_callback:
self.log_callback(message)
def run(self) -> bool:
"""执行 Step 1 流程,成功返回 True"""
from playwright.sync_api import sync_playwright
save_dir = os.path.join(".", "data", "screenshots", _now_datestr())
os.makedirs(save_dir, exist_ok=True)
err_path = os.path.join(save_dir, "step1_error.png")
try:
with sync_playwright() as p:
self.browser = p.chromium.launch(headless=False, executable_path=self.chrome_path)
context = self.browser.new_context(viewport={"width": 1280, "height": 800})
self.page = context.new_page()
# 1) 登录页
self._log("[Step1] 正在打开登录页...")
self.page.goto(self.url, wait_until="networkidle")
self.page.wait_for_timeout(2000)
# 等待输入框出现
self.page.wait_for_selector("input[type='text'], input[name*='user' i], input[placeholder*='' i]", timeout=15000)
# 查找用户名和密码输入框
user_candidates = [
self.page.locator("input[type='text']").first,
self.page.locator("input[name*='user' i]").first,
self.page.locator("input[name*='username' i]").first,
self.page.locator("input[placeholder*='' i]").first,
self.page.get_by_placeholder(re.compile(r"用户名|账号|登录名", re.I)).first,
]
pass_candidates = [
self.page.locator("input[type='password']").first,
self.page.locator("input[name*='pass' i]").first,
self.page.locator("input[placeholder*='' i]").first,
self.page.get_by_placeholder(re.compile(r"密码", re.I)).first,
]
user_loc = next((c for c in user_candidates if c.count() > 0 and c.is_visible()), None)
pass_loc = next((c for c in pass_candidates if c.count() > 0 and c.is_visible()), None)
if user_loc is None or pass_loc is None:
raise RuntimeError("未能定位到用户名/密码输入框。")
# 输入用户名密码
self._log("[Step1] 正在输入用户名密码...")
user_loc.click(timeout=5000, force=True)
user_loc.fill(self.username)
pass_loc.click(timeout=5000, force=True)
pass_loc.fill(self.password)
# 登录按钮
login_btn = self.page.get_by_role("button", name=re.compile(r"(登录|Log in|Sign in|立即登录)", re.I)).first
try:
if login_btn.count() > 0:
login_btn.click(timeout=10000, force=True)
else:
self.page.locator("button[type='submit']").first.click(timeout=10000, force=True)
except Exception:
self.page.keyboard.press("Enter")
# 等待菜单
self._log("[Step1] 等待登录完成...")
self.page.wait_for_selector("text=工作室运营", timeout=30000)
time.sleep(1)
# 2) 进入源码管理
self._log("[Step1] 进入源码管理...")
self.page.get_by_text("工作室运营", exact=False).first.click(timeout=10000, force=True)
time.sleep(0.8)
self.page.get_by_text("源码管理", exact=False).first.click(timeout=10000, force=True)
time.sleep(1)
# 3) 搜索项目
self._log("[Step1] 搜索项目...")
self.page.wait_for_timeout(1500)
search_input_candidates = [
self.page.locator("input[type='search']").first,
self.page.locator("input[placeholder*='搜索' i]").first,
self.page.get_by_placeholder("搜索").first,
self.page.locator(".el-input__inner").filter(has_text=re.compile(r"搜索", re.I)).first,
]
search_input = next((c for c in search_input_candidates if c.count() > 0 and c.is_visible()), None)
if search_input is None:
all_inputs = self.page.locator("input[type='text']").all()
for inp in all_inputs:
if inp.is_visible():
search_input = inp
break
if search_input is None:
raise RuntimeError("未能定位到搜索输入框。")
search_input.click(timeout=5000, force=True)
search_input.fill(self.project_name)
search_input.press("Enter")
# 从 project_name 提取项目编号前6个字符
project_code = self.project_name[:6] if len(self.project_name) >= 6 else self.project_name
# 等待列表渲染
self.page.wait_for_selector(f"text={project_code}", timeout=20000)
self.page.wait_for_timeout(1000)
# 查找项目行
row = self.page.locator("tr", has_text=self.project_name).first
if row.count() == 0:
row = self.page.locator("tr", has_text=project_code).first
# 点击"查看"按钮
view_btn = row.get_by_role("button", name=re.compile(r"(查看|View)", re.I)).first
if view_btn.count() == 0 or not view_btn.is_visible():
raise RuntimeError("未能定位到'查看'按钮。")
view_btn.click(timeout=10000)
# 4) 滚动到底并点击 FeastCoding
self._log("[Step1] 查找 FeastCoding 按钮...")
self.page.wait_for_timeout(1500)
feast_loc = self.page.get_by_text(self.feast_button, exact=False).first
feast_loc.wait_for(state="visible", timeout=15000)
self._log("[Step1] 已找到 FeastCoding 按钮")
feast_loc.scroll_into_view_if_needed(timeout=10000)
self.page.wait_for_timeout(800)
self._log("[Step1] 点击 FeastCoding 按钮...")
feast_loc.click(timeout=10000)
self.page.wait_for_timeout(3000)
self._log("[Step1] 已完成,内容已复制到剪贴板")
return True
except Exception as e:
if self.page is not None:
_safe_screenshot(self.page, err_path)
raise e
finally:
if self.browser is not None:
try:
self.browser.close()
except Exception:
pass
def main():
"""单独运行 Step1"""
config = get_config()
chrome_path = config.chrome_path
step1_config = config.step1_config
username = step1_config.get("username", "wangpeng")
password = step1_config.get("password", "Feastcoding@123")
step1 = Step1FeastCoding(chrome_path, username, password)
try:
step1.run()
print("[INFO] Step1 执行成功!")
except Exception as e:
print(f"[ERROR] Step1 执行失败: {e}")
if __name__ == "__main__":
main()