From 8f1e5b4c463df52ceb5066a7121aa7b7d0ef383f Mon Sep 17 00:00:00 2001 From: karuo Date: Fri, 20 Mar 2026 21:45:42 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-03-20=2021:45=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8F=B0=20|=20=E6=8E=92=E9=99=A4=20>20MB:=2011=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 运营中枢/工作台/gitea_push_log.md | 1 + .../工作台/scripts/s2_admin_fullpage_capture.py | 77 +++++++++++++++++-- 运营中枢/工作台/代码管理.md | 1 + 3 files changed, 74 insertions(+), 5 deletions(-) diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 52f8e9a6..85a7257c 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -404,3 +404,4 @@ | 2026-03-20 12:22:19 | 🔄 卡若AI 同步 2026-03-20 12:22 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-20 13:39:57 | 🔄 卡若AI 同步 2026-03-20 12:40 | 更新:水桥平台对接 | 排除 >20MB: 11 个 | | 2026-03-20 16:08:50 | 🔄 卡若AI 同步 2026-03-20 16:08 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | +| 2026-03-20 21:38:30 | 🔄 卡若AI 同步 2026-03-20 21:38 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | diff --git a/运营中枢/工作台/scripts/s2_admin_fullpage_capture.py b/运营中枢/工作台/scripts/s2_admin_fullpage_capture.py index 8cb26411..4c7ef5cd 100644 --- a/运营中枢/工作台/scripts/s2_admin_fullpage_capture.py +++ b/运营中枢/工作台/scripts/s2_admin_fullpage_capture.py @@ -2,13 +2,18 @@ """ S2 私域管理后台:按路由批量全页截图(Playwright) -用法(需已登录态,任选其一): - 1) 先在本机 Chrome 登录后台,再用「用户数据目录」启动浏览器: +用法(登录态,任选其一): + 0) **账号密码自动登录(推荐)**:环境变量(勿提交仓库) + export S2_ADMIN_USER='你的账号' + export S2_ADMIN_PASS='你的密码' + python3 s2_admin_fullpage_capture.py --wait-ms 2500 + 登录成功后才进入路由截图;失败会抛错并中止。 + + 1) 本机 Chrome 已登录 +「用户数据目录」: python3 s2_admin_fullpage_capture.py --user-data-dir "$HOME/Library/Application Support/Google/Chrome" --channel chromium --headless (无 Google Chrome.app 时用 channel chromium;有则用 --channel chrome。建议先关 Chrome 避免配置锁。) - 2) 或使用已导出的 storage_state.json: - playwright install chromium + 2) 已导出的 storage_state.json: python3 s2_admin_fullpage_capture.py --storage-state /path/to/state.json 默认输出:卡若Ai 报告目录下 screenshots/fullpage_playwright/ @@ -17,6 +22,7 @@ from __future__ import annotations import argparse import asyncio +import os import re from pathlib import Path @@ -61,6 +67,38 @@ def parse_routes_from_app_js(text: str) -> list[str]: return uniq +async def login_if_needed(page, username: str, password: str, timeout_ms: int = 120000) -> None: + """若未登录则在 #/login 填写账号密码;已登录则跳过。""" + await page.goto(f"{ADMIN}/home", wait_until="domcontentloaded", timeout=timeout_ms) + await page.wait_for_timeout(2000) + acct = page.get_by_placeholder("账号") + if await acct.count() == 0 or not await acct.first.is_visible(): + await page.goto(f"{ADMIN}/login", wait_until="domcontentloaded", timeout=timeout_ms) + await page.wait_for_timeout(1500) + acct = page.get_by_placeholder("账号") + if await acct.count() == 0 or not await acct.first.is_visible(): + return + await acct.first.fill(username) + await page.get_by_placeholder("密码").fill(password) + await page.get_by_role("button", name="登录").click() + for _ in range(120): + await page.wait_for_timeout(500) + h = await page.evaluate("() => location.hash || ''") + if "/login" not in h: + break + else: + raise RuntimeError("登录失败:超时仍停留在登录页") + await page.wait_for_timeout(800) + errs = page.locator(".el-message--error") + if await errs.count() > 0: + e0 = errs.first + if await e0.is_visible(): + raise RuntimeError("登录失败:" + (await e0.inner_text()).strip()) + acct2 = page.get_by_placeholder("账号") + if await acct2.count() > 0 and await acct2.first.is_visible(): + raise RuntimeError("登录失败:提交后仍显示登录表单,请检查账号密码") + + async def main() -> None: ap = argparse.ArgumentParser() ap.add_argument("--dest", type=Path, default=DEST_DEFAULT) @@ -79,8 +117,26 @@ async def main() -> None: action="store_true", help="不在每页加载后按 Escape(默认按一次以尝试关闭 v-modal 遮罩)", ) + ap.add_argument( + "--login-user", + default=None, + help="登录账号(优先用环境变量 S2_ADMIN_USER,勿把密码写进 shell 历史可用 env 文件)", + ) + ap.add_argument( + "--login-password", + default=None, + help="登录密码(强烈建议仅用环境变量 S2_ADMIN_PASS)", + ) + ap.add_argument( + "--headed", + action="store_true", + help="有界面运行(调试用;默认无头)", + ) args = ap.parse_args() + login_user = (os.environ.get("S2_ADMIN_USER") or args.login_user or "").strip() + login_pass = (os.environ.get("S2_ADMIN_PASS") or args.login_password or "").strip() + from playwright.async_api import async_playwright async with httpx.AsyncClient(verify=False, timeout=60) as client: @@ -92,6 +148,8 @@ async def main() -> None: args.dest.mkdir(parents=True, exist_ok=True) async with async_playwright() as p: + context = None + browser = None if args.user_data_dir: browser = await p.chromium.launch_persistent_context( user_data_dir=str(args.user_data_dir), @@ -101,13 +159,20 @@ async def main() -> None: ) page = browser.pages[0] if browser.pages else await browser.new_page() else: - browser = await p.chromium.launch(headless=True) + browser = await p.chromium.launch(headless=not args.headed) context = await browser.new_context( viewport={"width": 1440, "height": 900}, storage_state=str(args.storage_state) if args.storage_state else None, ) page = await context.new_page() + if login_user and login_pass: + print("登录中…") + await login_if_needed(page, login_user, login_pass) + print("登录成功,开始截图") + elif not args.user_data_dir and not args.storage_state: + print("警告:未提供 S2_ADMIN_USER/S2_ADMIN_PASS、storage-state 或 user-data-dir,可能截到未登录页") + for i, route in enumerate(routes, 1): url = f"{ADMIN}{route}" name = route.strip("/").replace("/", "__") or "home" @@ -126,6 +191,8 @@ async def main() -> None: if args.user_data_dir: await browser.close() else: + if context: + await context.close() await browser.close() diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 1426a68a..5f291109 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -407,3 +407,4 @@ | 2026-03-20 12:22:19 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 12:22 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-20 13:39:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 12:40 | 更新:水桥平台对接 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-20 16:08:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 16:08 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-20 21:38:30 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-20 21:38 | 更新:水桥平台对接、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |