Skill: wosuiyu/banjixiaoguanjia (version 1.0.0)

--- name: 班级小管家作业截图 slug: banjixiaoguanjia version: 1.0.0 description: "自动化班级小管家作业截图工具。支持连接 Chrome 调试端口、进入指定课程、滚动查看学生列表、截取学生作业图片。专为小学数学老师设计，用于批量获取学生作业截图进行批改。" metadata: {"clawdbot":{"emoji":"📚","requires":{"bins":["node","npm"],"packages":["playwright"]},"os":["linux","darwin","win32"]}} --- # 班级小管家作业截图 自动化班级小管家 (banjixiaoguanjia.com) 作业截图工具，支持批量获取学生作业图片。 ## 功能特性 - 🔌 连接 Chrome 调试端口 (CDP) - 📖 自动进入指定课程 - 👨‍🎓 滚动查看所有学生 - 📸 批量截取学生作业图片 - 🤖 支持 AI 图像分析 (GLM/MiniMax) ## 编码规范 **重要**: 所有文件、路径、目录统一使用 **UTF-8 编码** - 脚本文件保存为 UTF-8 with BOM - 文件路径处理使用 UTF-8 编码 - 输出报告使用 UTF-8 编码 - 避免使用 GBK/GB2312 编码 ## 前置要求 1. **Chrome 浏览器** 已安装 2. **已登录** 班级小管家（首次使用需要手动登录） > 💡 **提示**: 使用 `capture-new-browser.js` 脚本时，无需手动启动 Chrome 调试模式，脚本会自动处理浏览器启动和复用。 ## 📖 脚本使用手册 **⚠️ 重要**: - 详细的脚本使用流程请查看 [`SCRIPT-GUIDE.md`](./SCRIPT-GUIDE.md) - **必须严格按照 SCRIPT-GUIDE.md 使用脚本**，不要随意尝试目录下其他脚本 - 本目录下脚本众多，部分脚本有bug或已过时 --- ## 快速开始 ### 🆕 推荐使用脚本: `capture-new-browser.js` (v1.9.0+) **最新版功能**: - ✅ **自动复用浏览器**: 检测已打开的班级小管家页面，有则复用，无则启动新浏览器（开启调试端口 9222） - ✅ **动态学生列表**: 自动获取学生列表，不固定学生名字，支持任意班级 - ✅ **智能截图**: 采用固定步长滚动（400像素），确保截图顺序正确 - ✅ **改正作业处理**: 自动检测"改正如下"标签，单独截图改正部分 - ✅ **浏览器保持打开**: 脚本结束时无限等待，防止浏览器被意外关闭 - ✅ **窗口大小**: 1280x1000（高度 1000） - ✅ **使用本地 Chrome**: 不是 Chrome for Testing **使用方法**: ```bash node capture-new-browser.js "课程名称" ``` **示例**: ```bash node capture-new-browser.js "二下(第38节)" ``` **输出位置**: `桌面/课程名称/` **浏览器复用逻辑**: 1. 第一次运行：启动新浏览器，自动开启调试端口 9222 2. 后续运行：检测并复用已打开的浏览器，无需重新扫码登录 3. 脚本完成：浏览器保持打开，可继续手动查看和批改 --- ### 旧版脚本（不推荐） **截图 + AI分析作业数量**: ```bash node capture-36-fixed.js "二下(第36节)" ``` > ⚠️ **注意**: 旧版脚本需要手动启动 Chrome 调试模式，且不会自动复用浏览器 --- ## 核心 API ### 连接到 Chrome ```javascript const { chromium } = require('playwright'); const browser = await chromium.connectOverCDP('http://localhost:9222'); ``` ### 进入课程 ```javascript // 启用辅助功能 await page.evaluate(() => { const el = document.querySelector('flt-semantics-placeholder'); if (el) el.click(); }); // 查找并点击课程 await page.evaluate((courseName) => { const elements = document.querySelectorAll('flt-semantics[aria-label]'); for (const el of elements) { const label = el.getAttribute('aria-label'); if (label && label.includes(courseName)) { el.dispatchEvent(new MouseEvent('click', { bubbles: true })); break; } } }, '三上(第11节)'); ``` ### 滚动查找课程（课程列表页面） > ⚠️ **关键发现**: Flutter 页面需要使用 `scrollIntoView` 滚动到已知课程来触发动态加载更多课程！ **正确方法**: ```javascript // 滚动到最后一个已知课程，触发 Flutter 加载更多 await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics[aria-label]'); let lastCourseEl = null; for (const el of elements) { const label = el.getAttribute('aria-label'); if (label && (label.includes('第') || label.includes('年级'))) { lastCourseEl = el; } } if (lastCourseEl) { lastCourseEl.scrollIntoView({ block: 'start' }); } }); await page.waitForTimeout(1500); // 等待加载完成 ``` **完整查找流程**: ```javascript async function findCourseWithScroll(courseName, maxScrolls = 20) { for (let i = 0; i < maxScrolls; i++) { // 1. 在当前视图查找目标课程 const found = await page.evaluate((name) => { const elements = document.querySelectorAll('flt-semantics[aria-label]'); for (const el of elements) { const label = el.getAttribute('aria-label'); if (label && label.includes(name)) { return { found: true, label }; } } return { found: false }; }, courseName); if (found.found) return found; // 2. 滚动到最后一个课程，触发加载更多 await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics[aria-label]'); let lastCourseEl = null; for (const el of elements) { const label = el.getAttribute('aria-label'); if (label && (label.includes('第') || label.includes('年级'))) { lastCourseEl = el; } } if (lastCourseEl) lastCourseEl.scrollIntoView({ block: 'start' }); }); await page.waitForTimeout(1500); } } ``` ### 滚动查看学生 ```javascript // 获取滚动容器 const scrollContainer = await page.evaluate(() => { return document.querySelector('[style*="overflow-y: scroll"]'); }); // 滚动到指定位置 await page.evaluate((scrollTop) => { const el = document.querySelector('[style*="overflow-y: scroll"]'); if (el) el.scrollTop = scrollTop; }, 100); ``` ### 截图学生作业 ```javascript await page.screenshot({ path: `screenshots/${studentName}.png`, fullPage: true }); ``` ## 完整示例 ```javascript const { chromium } = require('playwright'); async function captureHomework(courseName, outputDir = './screenshots') { const browser = await chromium.connectOverCDP('http://localhost:9222'); const context = browser.contexts()[0] || await browser.newContext(); const pages = context.pages(); let page = pages.find(p => p.url().includes('banjixiaoguanjia')); if (!page) { page = await context.newPage(); await page.goto('https://service.banjixiaoguanjia.com/appweb/'); } // 启用辅助功能 await page.evaluate(() => { const el = document.querySelector('flt-semantics-placeholder'); if (el) el.click(); }); await page.waitForTimeout(2000); // 进入课程 await page.evaluate((name) => { const elements = document.querySelectorAll('flt-semantics[aria-label]'); for (const el of elements) { const label = el.getAttribute('aria-label'); if (label && label.includes(name)) { el.dispatchEvent(new MouseEvent('click', { bubbles: true })); break; } } }, courseName); await page.waitForTimeout(4000); // 获取学生列表 const students = await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics[aria-label]'); const list = []; for (const el of elements) { const label = el.getAttribute('aria-label'); if (label && label.length > 20) { const lines = label.split('\n'); const name = lines[0].trim(); if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) { list.push(name); } } } return list; }); // 截图每个学生 for (const student of students) { await page.screenshot({ path: `${outputDir}/${student}.png`, fullPage: true }); console.log(`✓ 已截图: ${student}`); } await browser.close(); } ``` ## 技术要点 ### 课程详情页 vs 批改页面的区别 | 页面 | 学生信息格式 | 查找条件 | |------|-------------|----------| | **课程详情页** | `球球\n2026-03-21 14:20\n 1.听课顺序...` | `includes('2026-') && includes('课内')` | | **批改页面** | `球球的作业(1/11)` | 匹配正则 `/(.+?)的作业\((\d+)\/(\d+)\)/` | > ⚠️ **关键**: 两个页面的学生信息格式完全不同，不能用相同的查找条件！ ### Flutter 页面特殊处理 1. **启用辅助功能**: 必须先点击 `flt-semantics-placeholder` 2. **点击方式**: 使用 `dispatchEvent(new MouseEvent('click', { bubbles: true }))` 3. **滚动容器**: 查找 `[style*="overflow-y: scroll"]` 元素 4. **语义元素**: 通过 `flt-semantics[aria-label]` 定位 ### 【关键】滚动截图策略 > ⚠️ **重要发现**: 每次截图前必须先复位，否则 scrollTop 会累积导致截图错误！ **正确流程**: ```javascript // 1. 先复位 scrollTop = -1000（关键！） await page.evaluate(() => { const el = document.querySelector('[style*="overflow-y: scroll"]'); if (el) el.scrollTop = -1000; }); // 2. 等待复位完成 await page.waitForTimeout(500); // 3. 设置具体的 scrollTop 值 await page.evaluate((scrollTop) => { const el = document.querySelector('[style*="overflow-y: scroll"]'); if (el) el.scrollTop = scrollTop; }, 12); // 例如: Zoey 的位置 // 4. 等待滚动完成 await page.waitForTimeout(1000); // 5. 截图 await page.screenshot({ path: 'Zoey.png' }); ``` **二下第31节经验值**: | 学生 | scrollTop | 说明 | |------|-----------|------| | 灿灿 | -3 | 只看到灿灿 | | Zoey | 12 | 只看到Zoey和球球 | | 球球 | 15 | 到底部，显示"没有更多了" | **错误示范** (不复位): ```javascript // ❌ 错误：不复位会导致 scrollTop 累积 await page.evaluate(() => { el.scrollTop = 12; // 如果之前是 10，现在变成 22！ }); ``` ## 故障排除 ### 无法连接到 Chrome - 检查 Chrome 是否以 `--remote-debugging-port=9222` 启动 - 检查端口是否被占用: `curl http://localhost:9222/json/version` ### 找不到课程 - 确保已登录班级小管家 - 检查课程名称是否完全匹配（包含年级、班级信息） ### 点击无效 - 确保已启用辅助功能 - 使用 `dispatchEvent` 而非普通 `click()` ## Qwen3.5-Plus 图像分析 截图完成后，使用 **Qwen3.5-Plus** (通义千问) 分析学生作业提交情况。 ### 依赖安装 ```bash pip install openai ``` ### 环境变量设置 ```bash # Windows PowerShell $env:DASHSCOPE_API_KEY="sk-your-api-key" # Windows CMD set DASHSCOPE_API_KEY=sk-your-api-key # 永久设置（Windows） [Environment]::SetEnvironmentVariable("DASHSCOPE_API_KEY", "sk-your-api-key", "User") ``` ### 使用方法 ```javascript const { BanjixiaoguanjiaCapture } = require('./skills/banjixiaoguanjia'); const capture = new BanjixiaoguanjiaCapture(); // 截图并分析（自动使用 DASHSCOPE_API_KEY 环境变量） const result = await capture.captureAndAnalyze('一下(第9节)'); // 输出分析结果 for (const item of result.analysis) { console.log(`学生: ${item.student}`); console.log(`分析: ${item.analysis}`); } ``` ### 分析结果示例 ``` 学生姓名：KIKI 课内作业：6张 奥数作业：7张 ``` ### API Key 获取 1. 访问 https://bailian.console.aliyun.com 2. 注册/登录阿里云账号 3. 在控制台获取 API Key ## 作业图片下载完整流程 ### 流程概述 ``` 阶段1: AI分析（Qwen） ├── 截图所有学生作业 ├── 使用Qwen分析每张截图 └── 获取：学生姓名 + 课内数量 + 奥数数量 阶段2: 下载原图 ├── 进入课程 → 点击第一个学生图片 → 进入批改页面 ├── 下载当前学生作业（先课内，后奥数） ├── 核对：本地文件数量 vs 分析结果数量 ├── 点击左侧学生名字切换到下一个学生 ├── 重复下载流程 └── 直到所有学生下载完成 阶段3: 最终核对 ├── 检查每个学生文件夹 ├── 核对课内/奥数图片数量 └── 确认全部下载成功 ``` ### 关键要点 1. **刷新会回到首页** - 操作过程中不能刷新页面 2. **学生切换** - 在批改页面左侧点击学生名字（页面不刷新，自动回到第1张） 3. **查看原图按钮** - 元素索引[8]，位置(814,0)，尺寸40x56 4. **图片切换** - 使用 `ArrowRight` 键切换到下一张 5. **作业顺序** - 先课内，后奥数 6. **以分析结果为准** - 多余图片不下载 7. **核对步骤** - 每个学生下载完成后，必须核对本地文件数量与AI分析结果是否一致 ### 动态获取学生信息（关键修正） > ⚠️ **重要**: 页面顶部元素[7]显示"学生名字的作业(X/Y)"，**不包含"返回"前缀**！ > > 页面结构： > - 元素[6]: "返回" - 返回按钮 > - 元素[7]: "Zoey的作业(8/8)" - 学生名字和作业信息（**用这个！**） ```javascript // 获取当前学生名字和图片位置 const currentInfo = await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics'); // 优先检查元素[7]（页面顶部标题，不包含"返回"前缀） if (elements[7]) { const text = elements[7].textContent || elements[7].getAttribute('aria-label') || ''; const match = text.match(/(.+?)的作业\((\d+)\/(\d+)\)/); if (match) { return { student: match[1].trim(), // 学生名字（不含"返回"前缀） current: parseInt(match[2]), // 当前图片位置 total: parseInt(match[3]) // 总图片数 }; } } // 备用：遍历所有元素查找 for (let i = 0; i < elements.length; i++) { const text = elements[i].textContent || elements[i].getAttribute('aria-label') || ''; const match = text.match(/(.+?)的作业\((\d+)\/(\d+)\)/); if (match) { let studentName = match[1].trim(); // 去掉"返回"前缀（兼容旧版本） studentName = studentName.replace(/^返回/, ''); return { student: studentName, current: parseInt(match[2]), total: parseInt(match[3]) }; } } return null; }); // 获取左侧学生列表（v8 动态获取） const students = await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics'); const studentList = []; for (let i = 0; i < elements.length; i++) { const text = elements[i].textContent || elements[i].getAttribute('aria-label') || ''; // 匹配学生名字（短文本，排除各种UI元素） if (text.length > 0 && text.length < 20 && !text.includes('作业') && !text.includes('图片') && !text.includes('查看') && !text.includes('详情') && !text.includes('2026-') && !text.includes('课内') && !text.includes('奥数') && !text.includes('返回') && !text.includes('已点评') && !text.includes('待点评') && !text.includes('没有更多') && !text.includes('收起') && !text.includes('点评') && !text.includes('全部') && !text.includes('未点评') && !text.includes('帮助') && !text.includes('快捷键') && !text.includes('设置') && !text.includes('画笔') && !text.includes('旋转') && !text.includes('文字') && !text.includes('橡皮擦') && !text.includes('撤销') && !text.includes('放大') && !text.includes('缩小') && !text.includes('上一张') && !text.includes('下一张') && !text.includes('保存')) { studentList.push({ index: i, name: text.trim() }); } } return studentList; }); ``` ### 下载单个学生作业（含核对） ```javascript async function downloadStudentImages(page, studentName, keNeiCount, aoShuCount, outputDir) { const totalCount = keNeiCount + aoShuCount; // 创建目录 const keNeiDir = path.join(outputDir, studentName, '课内'); const aoShuDir = path.join(outputDir, studentName, '奥数'); if (!fs.existsSync(keNeiDir)) fs.mkdirSync(keNeiDir, { recursive: true }); if (!fs.existsSync(aoShuDir)) fs.mkdirSync(aoShuDir, { recursive: true }); // 下载课内作业（第1到keNeiCount张） for (let i = 1; i <= keNeiCount; i++) { const savePath = path.join(keNeiDir, `图片${i}.jpg`); // 点击"查看原图"按钮（元素[8]） await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics'); if (elements[8]) { elements[8].dispatchEvent(new MouseEvent('click', { bubbles: true })); } }); await page.waitForTimeout(3000); // 获取图片URL并下载 const context = page.context(); const pages = context.pages(); const newPage = pages.find(p => p !== page && p.url().includes('img.banjixiaoguanjia.com')); if (newPage) { const imageUrl = newPage.url(); execSync(`curl -L -o "${savePath}" "${imageUrl}"`, { timeout: 30000 }); await newPage.close(); await page.waitForTimeout(500); } // 切换到下一张 if (i < totalCount) { await page.keyboard.press('ArrowRight'); await page.waitForTimeout(1500); } } // 下载奥数作业（第1到aoShuCount张） for (let i = 1; i <= aoShuCount; i++) { const savePath = path.join(aoShuDir, `图片${i}.jpg`); // 点击"查看原图"按钮 await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics'); if (elements[8]) { elements[8].dispatchEvent(new MouseEvent('click', { bubbles: true })); } }); await page.waitForTimeout(3000); // 获取图片URL并下载 const context = page.context(); const pages = context.pages(); const newPage = pages.find(p => p !== page && p.url().includes('img.banjixiaoguanjia.com')); if (newPage) { const imageUrl = newPage.url(); execSync(`curl -L -o "${savePath}" "${imageUrl}"`, { timeout: 30000 }); await newPage.close(); await page.waitForTimeout(500); } // 切换到下一张 if (i < aoShuCount) { await page.keyboard.press('ArrowRight'); await page.waitForTimeout(1500); } } // ===== 核对步骤 ===== console.log(`\n核对 ${studentName} 的下载结果...`); // 核对课内作业 const keNeiFiles = fs.readdirSync(keNeiDir).filter(f => f.endsWith('.jpg')); console.log(` 课内作业: 预期 ${keNeiCount} 张, 实际 ${keNeiFiles.length} 张`); if (keNeiFiles.length !== keNeiCount) { console.log(` ⚠️ 课内作业数量不匹配!`); } else { console.log(` ✓ 课内作业核对通过`); } // 核对奥数作业 const aoShuFiles = fs.readdirSync(aoShuDir).filter(f => f.endsWith('.jpg')); console.log(` 奥数作业: 预期 ${aoShuCount} 张, 实际 ${aoShuFiles.length} 张`); if (aoShuFiles.length !== aoShuCount) { console.log(` ⚠️ 奥数作业数量不匹配!`); } else { console.log(` ✓ 奥数作业核对通过`); } // 核对总数 const totalFiles = keNeiFiles.length + aoShuFiles.length; console.log(` 总计: 预期 ${totalCount} 张, 实际 ${totalFiles} 张`); if (totalFiles !== totalCount) { console.log(` ⚠️ 总数不匹配!`); return false; // 返回失败状态 } else { console.log(` ✓ ${studentName} 全部核对通过`); return true; // 返回成功状态 } } ``` ### 切换到下一个学生（v8 动态获取 + 索引点击） > ⚠️ **重要**: 切换学生后必须验证是否成功，并确保回到第1张图片！ > > **v8 改进**: 动态获取学生列表，通过索引点击，支持任意课程 ```javascript // 动态获取学生列表并切换到目标学生 async function switchToStudent(page, targetStudentName) { console.log(`切换到学生: ${targetStudentName}`); // 1. 获取左侧学生列表 const students = await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics'); const studentList = []; for (let i = 0; i < elements.length; i++) { const text = elements[i].textContent || elements[i].getAttribute('aria-label') || ''; if (text.length > 0 && text.length < 20 && !text.includes('作业') && !text.includes('图片') && !text.includes('查看') && !text.includes('详情') && !text.includes('2026-') && !text.includes('课内') && !text.includes('奥数') && !text.includes('返回') && !text.includes('已点评') && !text.includes('待点评') && !text.includes('没有更多') && !text.includes('收起') && !text.includes('点评') && !text.includes('全部') && !text.includes('未点评') && !text.includes('帮助') && !text.includes('快捷键') && !text.includes('设置') && !text.includes('画笔') && !text.includes('旋转') && !text.includes('文字') && !text.includes('橡皮擦') && !text.includes('撤销') && !text.includes('放大') && !text.includes('缩小') && !text.includes('上一张') && !text.includes('下一张') && !text.includes('保存')) { studentList.push({ index: i, name: text.trim() }); } } return studentList; }); // 2. 查找目标学生 const targetInfo = students.find(s => s.name === targetStudentName); if (!targetInfo) { console.log(` ⚠️ 未在学生列表中找到 ${targetStudentName}`); return null; } // 3. 使用索引点击 await page.evaluate((index) => { const elements = document.querySelectorAll('flt-semantics'); if (elements[index]) { elements[index].dispatchEvent(new MouseEvent('click', { bubbles: true })); } }, targetInfo.index); // 4. 等待页面刷新（切换学生后会自动回到第1张图片） await page.waitForTimeout(4000); // 5. 验证切换是否成功（检查页面顶部元素[7]） const info = await page.evaluate(() => { const elements = document.querySelectorAll('flt-semantics'); if (elements[7]) { const text = elements[7].textContent || elements[7].getAttribute('aria-label') || ''; const match = text.match(/(.+?)的作业\((\d+)\/(\d+)\)/); if (match) { return { student: match[1].trim(), current: parseInt(match[2]), total: parseInt(match[3]) }; } } return null; }); if (info && info.student === targetStudentName) { console.log(`✓ 已切换到 ${targetStudentName}: 第${info.current}/${info.total}张`); return info; } else { console.log(` ⚠️ 切换后显示: ${info ? `${info.student} (第${info.current}/${info.total}张)` : '未知'}`); return null; } } ``` ## 作业分析功能 ### 使用 `analyze-homework.js` 进行作业批改 ```javascript const { HomeworkAnalyzer } = require('./analyze-homework'); const analyzer = new HomeworkAnalyzer(); // 分析单个学生作业 await analyzer.analyze('一下(第9节)', '大瑀', '课内'); // 输出： // - 一下(第9节)大瑀课内分析.txt // - 一下(第9节)大瑀课内分析.docx ``` ### 分析流程 ``` 阶段1: 准备 ├── 检查答案文件（桌面/答案/课程名称/课内答案.png） ├── 获取作业图片 └── 统计图片数量 阶段2: AI分析（Qwen模型） ├── 优先批量分析（答案+所有作业图片） ├── 如果失败，切换到逐一分析 └── 生成批改结果 阶段3: 生成报告 ├── 保存 TXT 文件 └── 生成 Word 文档（微软雅黑，优化排版） ``` ### 提示词模板 **有答案时：** ``` 第1张图片是XX答案.png，这是作业的标准答案，你需要认真参考答案进行作业批改。 帮我检查这些作业的对错... ``` **无答案时：** ``` 帮我检查这些作业的对错... ``` ### Word 排版格式（手机阅读优化版） **设计目标**：适合手机阅读，字体大、布局简洁、层次清晰 - **页面边距**：窄边距（0.5英寸），内容更宽适合手机屏幕 - **字体**：微软雅黑 - **大标题**：20磅，📚表情+蓝色加粗居中 - **副标题**：12磅，灰色居中 - **章节标题**：16磅，▶符号+蓝色加粗 - **小节标题**：15磅，加粗，深灰色 - **正文**：14磅（手机阅读用大字体） - **列表项**：数字用蓝色加粗，内容14磅 - **子项**：13磅，缩进显示 - **正确标记**：绿色 ✅ - **错误标记**：红色 ❌ - **页脚**：12磅，灰色居中 **排版特点**： - 流式布局，去掉表格 - 章节间空行分隔 - 颜色区分对错 - 层次用缩进和符号区分 ### 阶段5: 完成当前课程 ``` 阶段5: 完成当前课程 ├── 所有作业下载和分析完成 ├── 刷新班级小管家页面（自动回到首页） └── 等待下一次作业批改命令 ``` **操作：** ```javascript // 刷新页面回到首页 await page.goto('https://service.banjixiaoguanjia.com/appweb/'); console.log('已回到首页，等待下一次作业批改命令'); ``` ## 相关技能 - **qwen35-plus-image-analyze**: 使用 Qwen3.5-Plus 分析作业图片 - **playwright**: 浏览器自动化基础 ## 更新日志 ### 1.0.0 - 初始版本 - 支持 Chrome CDP 连接 - 支持课程进入和学生截图 ### 1.1.0 - 新增作业图片下载功能 - 使用 Qwen3.5-Plus 进行AI分析 - 支持动态获取学生列表 - 支持课内/奥数分类下载 ### 1.2.0 - 新增作业分析功能 - 支持批量/逐一分析 - 自动生成 Word 报告 - 优化排版格式（微软雅黑） ### 1.3.0 (2026-03-22) - **关键修正**: 页面顶部元素[7]显示"学生名字的作业(X/Y)"，不包含"返回"前缀 - **关键修正**: 切换学生后必须验证是否成功，通过检查页面顶部元素[7]核对"当前第几张/一共多少张" - **关键修正**: 学生名字在左侧列表中的索引通常是固定的（如：球球在索引16，Zoey在索引17） - **关键修正**: 课程详情页查找学生的条件应该是 `includes('2026-') && includes('课内')`，而不是 `includes('已点评')` - 新增 `download-homework.js` 完整下载脚本 - 新增 `analyze-homework-ai.js` AI分析脚本 - **新增**: `SCRIPT-GUIDE.md` 脚本使用手册 ### 1.4.0 (2026-03-22) - **v8 改进**: `download-homework.js` 使用动态获取学生列表 + 索引点击的方式切换学生 - **移除**: 固定学生名字的专用脚本（如 download-zoey.js） - **优势**: 支持任意课程，无需为每个课程创建专用脚本 - **新增**: `archived-scripts/` 脚本归档目录 - **规范**: 统一使用 UTF-8 编码处理文件、路径、目录 ### 1.5.0 (2026-03-22) - **文档更新**: 在 SCRIPT-GUIDE.md 和 SKILL.md 中明确标注推荐脚本 - **问题记录**: 标注 `capture-and-analyze-36.js` 有bug（学生数量判断死循环） - **推荐脚本**: `capture-36-fixed.js` 作为截图+AI分析的标准脚本 - **重要提示**: 强调必须按照文档使用脚本，不要随意尝试其他脚本 ### 1.6.0 (2026-03-23) - **逻辑调整**: `download-homework.js` 新增"已点评"检查 - 遍历学生列表时，检查每个学生是否有"已点评"标签 - 有"已点评"标签 → 跳过该学生的作业下载 - 无"已点评"标签 → 正常下载该学生作业 - 在 `getStudentList()` 方法中返回 `skip` 字段标记是否需要跳过 - 在 `switchToStudent()` 方法中检查 `skip` 字段并跳过已点评学生 ### 1.7.0 (2026-03-23) - **截图逻辑修复**: `capture-36-fixed.js` 修复截图定位问题 - 问题: 使用 `getBoundingClientRect().top` 定位不准确，导致截图错误（如Zoey截图显示为球球作业） - 解决: 收集学生列表后**重新进入页面**，按顺序使用固定滚动步长（400px）截图 - 关键: 不再依赖 `top` 值定位，而是按顺序滚动截图 - **"改正如下"处理**: `capture-36-fixed.js` 添加改正作业截图逻辑 - 检测"改正如下"标签（收集学生列表时检查 `fullLabel`） - 单独截图改正部分（保存为 `学生名_改正.png`） - AI分析时提示不统计改正部分 - **"已点评"检测修复**: `download-homework.js` 修复学生名字提取逻辑 - 问题: 左侧学生列表中的文本包含"已点评"、"良好"等标签，导致名字匹配失败 - 解决: 提取学生名字时去掉"已点评"、评级（优秀/良好/合格/待改进）、数字括号、换行符等 - 确保第一个学生也能正确检测"已点评"状态并跳过下载 ### 1.9.0 (2026-03-29) - **新版一键截图脚本**: `capture-new-browser.js` 正式发布 - 自动复用浏览器：检测已打开的班级小管家页面，有则复用，无则启动新浏览器 - 动态学生列表：自动获取学生列表，不固定学生名字，支持任意班级 - 智能截图逻辑：采用固定步长滚动（400像素），确保截图顺序正确 - 改正作业处理：检测"改正如下"标签，单独截图改正部分 - 浏览器保持打开：脚本结束时无限等待，防止浏览器被意外关闭 - 窗口大小：1280x1000，使用本地 Chrome ### 1.8.0 (2026-03-24) - **Word报告手机阅读优化**: `analyze-homework.js` 重构Word生成逻辑 - 页面边距缩小到0.5英寸，适合手机屏幕 - 正文字体从12pt增大到14pt，章节标题16pt，大标题20pt - 去掉表格布局，改用流式排版 - 添加📚表情符号和▶章节标记 - 正确内容显示绿色，错误内容显示红色 - 层次结构：大章节→小节→子项，用缩进和符号区分

path: SCRIPT-GUIDE.md
content: # 班级小管家作业批改 - 脚本使用手册

> **⚠️ 重要提示**: 本目录下脚本众多，请严格按照本文档推荐的脚本执行，不要使用其他未标注的脚本，以免出现意外错误。

## 🎯 完整作业流程

### 执行规则

**当用户说"检查某节课作业"时，自动完整执行以下流程，中途不需要确认：**

1. **截图收集** - 运行 `capture-new-browser.js` 收集所有学生作业截图
2. **下载原图** - 运行 `download-homework.js` 下载学生作业原图（自动跳过已点评学生）
3. **AI批改分析** - 运行 `analyze-homework.js` 对每个学生的作业进行逐题批改

**注意：** 如果学生已点评，下载脚本会自动跳过，此时AI批改阶段会提示无原图可分析。

---

### 🆕 新版推荐: 一键截图（自动浏览器管理）

**✅ 强烈推荐使用脚本**: `capture-new-browser.js`

**功能特点**:
- ✅ **自动复用浏览器**: 检测已打开的班级小管家页面，有则复用，无则启动新浏览器
- ✅ **动态学生列表**: 自动获取学生列表，不固定学生名字，支持任意班级
- ✅ **智能截图**: 列表第一个学生默认可见直接截图，其他学生滚动查找定位
- ✅ **窗口大小**: 1280x1000
- ✅ **使用本地 Chrome**: 不是 Chrome for Testing

**使用方法**:
```bash
node capture-new-browser.js "课程名称"
```

**示例**:
```bash
node capture-new-browser.js "二下(第38节)"
```

**输出位置**: `桌面/课程名称/`

**浏览器管理逻辑**:
1. 检查是否有已打开的 Chrome 调试端口
2. 如果有，查找是否有班级小管家的页面
   - 有 → 直接使用，导航到首页
   - 没有 → 关闭连接，启动新浏览器
3. 如果没有调试端口 → 启动新浏览器（开启调试端口 9222）

---

### 旧版流程（如需使用旧版脚本）

#### 前置阶段: 启动Chrome调试模式
**手动执行** (PowerShell):

```powershell
$tempProfile = "$env:TEMP\chrome-dev-profile"
New-Item -ItemType Directory -Path $tempProfile -Force
Start-Process "C:\Program Files\Google\Chrome\Application\chrome.exe" `
  -ArgumentList "--remote-debugging-port=9222", "--user-data-dir=$tempProfile", "--window-size=1280,800", "https://service.banjixiaoguanjia.com/appweb/"
```

#### 阶段1: 截图收集 + AI分析作业数量
**脚本**: `capture-36-fixed.js`

```bash
node capture-36-fixed.js "课程名称"
```

**功能**:
- 连接Chrome调试端口
- 进入指定课程
- 动态收集学生列表
- 截图所有学生作业
- AI分析作业数量

**⚠️ 注意事项**:
- 需要手动启动 Chrome 调试模式
- 不会自动复用浏览器

---

### 调试脚本: 单独进入课程
**使用脚本**: `enter-course.js`

```bash
node enter-course.js "课程名称"
```

**使用场景** (非必须):
- `capture-auto.js` 执行失败，需要人工调试
- 只想查看课程页面，不截图分析
- 测试页面元素定位

**注意**: 正常流程不需要运行此脚本，`capture-auto.js` 已包含进入课程功能

---

### 阶段2: 下载原图
**使用脚本**: `download-homework.js` (v8)

```bash
node download-homework.js "课程名称"
```

**功能**:
- 自动下载所有学生的作业
- 动态获取学生列表并切换
- 自动核对下载数量
- **自动跳过已点评学生**（检测"已点评"标签）

**实现原理**:
1. 动态获取左侧学生列表（过滤非学生元素）
2. 检查学生是否有"已点评"标签，有则跳过
3. 通过索引点击切换学生
4. 验证切换结果（检查页面顶部元素[7]）
5. 逐个下载所有学生作业

**⚠️ 重要**: 已点评的学生会自动跳过，不会重复下载

---

### 阶段3: AI批改分析
**使用脚本**: `analyze-homework.js`

```bash
node -e "
const { HomeworkAnalyzer } = require('./analyze-homework');
const analyzer = new HomeworkAnalyzer();
analyzer.analyze('课程名称', '学生姓名', '课内|奥数');
"
```

**功能**:
- 批量分析作业图片
- 逐题批改（正确/错误）
- 生成TXT报告
- 生成Word文档（手机阅读优化版）

**Word格式特点**:
- 窄边距（0.5英寸），适合手机屏幕
- 大字体（正文14pt，标题16-20pt）
- 📚表情符号 + ▶章节标记
- 正确显示绿色 ✅，错误显示红色 ❌
- 流式布局，无表格

**输出位置**: `C:\Users\Administrator\Desktop\课程名称\`

---

## 🚫 已弃用/有问题的脚本

**以下脚本请勿使用**：

| 脚本名 | 问题 | 说明 |
|--------|------|------|
| `capture-and-analyze-36.js` | ❌ 有bug - 学生数量判断死循环 | 期望收集10个学生，实际可能不足，导致无限滚动 |
| `capture-36.js` | ⚠️ 功能简陋 - 只支持2个固定学生 | 学生名单写死，不灵活 |
| `capture-auto.js` | ⚠️ 已过时 | 使用旧版逻辑，可能不稳定 |
| `capture-36-fixed.js` | ⚠️ 旧版推荐 | 需要手动启动 Chrome 调试模式，不会自动复用浏览器 |

**归档脚本** (已移动到 `archived-scripts/`):

| 脚本名 | 用途 | 归档日期 | 说明 |
|--------|------|---------|------|
| download-student-fixed-20260322.js | 切换学生下载作业（修正版） | 2026-03-22 | 已合并到 download-homework.js v8 |
| download-zoey.js | Zoey专用下载脚本 | 2026-03-22 | 已弃用，使用通用脚本 |

---

## ⚠️ 重要注意事项

1. **严格按照本文档使用脚本** - 不要随意尝试其他脚本
2. **推荐脚本经过测试**:
   - `capture-new-browser.js` - 新版一键截图（自动浏览器管理）✅ **强烈推荐**
   - `download-homework.js` - 下载原图 ✅
   - `analyze-homework.js` - AI批改 ✅
3. **不要移动作业目录** - 保持在桌面原位置
4. **切换学生验证** - 切换后必须检查页面顶部显示的学生名

---

## 🔧 故障处理

### 切换学生失败
- `download-homework.js` v8 已使用动态获取学生列表的方式
- 如仍失败，检查页面是否已登录、Chrome调试端口是否正常

### AI分析超时
- 自动切换到逐一分析模式
- 耐心等待完成

### 学生列表获取异常
- 检查班级小管家页面是否加载完成
- 尝试刷新页面后重新运行脚本

---

## 更新日志

### 2026-03-29
- **`capture-new-browser.js` 优化**
  - 修复浏览器复用问题：新浏览器启动时开启调试端口 9222，确保下次能正确复用
  - 修复截图错位问题：采用固定步长滚动（400像素）代替动态查找，确保截图顺序正确
  - 修复"改正如下"截图逻辑：改正部分单独滚动截图
  - 优化学生列表收集：最大滚动次数从50次减少到10次
  - 脚本结束时保持运行，防止浏览器被意外关闭
  - 复用浏览器时直接滚动到顶部，不重新导航，避免需要重新登录

### 2026-03-28
- **🆕 新增** `capture-new-browser.js` - 新版一键截图脚本
  - 自动复用浏览器（检测已打开的班级小管家页面）
  - 动态学生列表（不固定学生名字，支持任意班级）
  - 智能截图（第一个学生默认可见，其他滚动查找）
  - 窗口大小 1280x1000
  - 使用本地 Chrome（不是 Chrome for Testing）

### 2026-03-24
- **Word报告手机阅读优化** `analyze-homework.js`
  - 页面边距缩小到0.5英寸
  - 正文字体从12pt增大到14pt
  - 添加📚表情符号和▶章节标记
  - 正确内容绿色，错误内容红色
  - 流式布局，去掉表格

### 2026-03-22
- **v8** `download-homework.js` 使用动态获取学生列表的方式切换学生
- 移除固定学生名字的专用脚本（如 download-zoey.js）
- 统一使用索引点击方式，支持任意课程的学生切换

*最后更新: 2026-03-29*


path: analyze-homework.js
content: /**
 * 作业分析脚本 - 完整版
 * 支持批量分析和逐一分析（备用）
 * 自动生成 Word 报告（微软雅黑，优化排版）
 */

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

class HomeworkAnalyzer {
  constructor(options = {}) {
    // 直接从脚本中读取 API Key，不再依赖环境变量
    this.[REDACTED_SECRET] || '[REDACTED_SECRET]';
    this.baseUrl = 'https://dashscope.aliyuncs.com/compatible-mode/v1';
    this.model = 'qwen-vl-max-latest';
  }

  /**
   * 分析学生作业
   * @param {string} courseName - 课程名称
   * @param {string} studentName - 学生姓名
   * @param {string} homeworkType - 作业类型（课内/奥数）
   */
  async analyze(courseName, studentName, homeworkType) {
    console.log(`\n========================================`);
    console.log(`分析: ${studentName} - ${homeworkType}作业`);
    console.log(`========================================\n`);

    // 1. 准备路径
    const studentDir = path.join(require('os').homedir(), 'Desktop', courseName, studentName, homeworkType);
    const answerPath = path.join(require('os').homedir(), 'Desktop', '答案', courseName, `${homeworkType}答案.png`);
    const outputDir = path.join(require('os').homedir(), 'Desktop', courseName);

    // 2. 检查答案文件
    const hasAnswer = fs.existsSync(answerPath);
    console.log(`答案文件: ${hasAnswer ? '存在' : '不存在'}`);

    // 3. 获取作业图片
    const homeworkFiles = fs.readdirSync(studentDir)
      .filter(f => f.endsWith('.jpg'))
      .map(f => path.join(studentDir, f))
      .sort();
    console.log(`作业图片: ${homeworkFiles.length} 张\n`);

    if (homeworkFiles.length === 0) {
      console.log('未找到作业图片');
      return;
    }

    // 4. 尝试批量分析
    let result = null;
    try {
      result = await this.analyzeBatch(studentName, homeworkType, homeworkFiles, answerPath, hasAnswer);
      console.log('✓ 批量分析成功\n');
    } catch (error) {
      console.log('批量分析失败，切换到逐一分析...\n');
      result = await this.analyzeOneByOne(studentName, homeworkType, homeworkFiles, answerPath, hasAnswer);
    }

    // 5. 保存 TXT
    const txtPath = path.join(outputDir, `${courseName}${studentName}${homeworkType}分析.txt`);
    fs.writeFileSync(txtPath, result, 'utf-8');
    console.log(`✓ TXT 已保存: ${txtPath}`);

    // 6. 生成 Word
    console.log('\n正在生成 Word 文档...');
    await this.generateWord(courseName, studentName, homeworkType, result, outputDir);
    console.log(`✓ Word 已保存: ${courseName}${studentName}${homeworkType}分析.docx`);

    return result;
  }

  /**
   * 批量分析
   */
  async analyzeBatch(studentName, homeworkType, homeworkFiles, answerPath, hasAnswer) {
    const imagePaths = hasAnswer ? [answerPath, ...homeworkFiles] : homeworkFiles;
    
    const prompt = this.buildPrompt(studentName, homeworkType, homeworkFiles.length, hasAnswer);
    
    const pythonScript = this.buildPythonScript(imagePaths, prompt);
    
    const tempScript = path.join(homeworkFiles[0], '..', '_analyze_batch.py');
    fs.writeFileSync(tempScript, pythonScript, 'utf-8');

    try {
      const result = execSync(`python3 "${tempScript}"`, { 
        encoding: 'utf-8',
        timeout: 300000
      });
      fs.unlinkSync(tempScript);
      return result.trim();
    } catch (error) {
      if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
      throw error;
    }
  }

  /**
   * 逐一分析（备用）
   */
  async analyzeOneByOne(studentName, homeworkType, homeworkFiles, answerPath, hasAnswer) {
    const analysisResults = [];

    for (let i = 0; i < homeworkFiles.length; i++) {
      console.log(`分析第 ${i + 1}/${homeworkFiles.length} 张...`);
      
      const imagePaths = hasAnswer ? [answerPath, homeworkFiles[i]] : [homeworkFiles[i]];
      
      const prompt = `帮我检查这张作业的对错。

**重要**：
1.要严谨仔细理解题目意思，确保判改作业的正确性(重要)。
2.要检查应用题答题中是否缺少单位。
3.应用题的答案需要有计算过程。
4.对的划对勾，错的划错号，看不清楚的用问号，作业中的漏答题做漏题标注。
5.答题的位置写的算式是错误的，算式要写在相应的答题位置。
7.不要使用"识别"这种计算机技术用语。
8.不要使用繁体字。

请按以下格式输出：
第${i + 1}张作业：
题目分析：...
批改结果：...（正确/错误/看不清楚/漏题）
学生答案：...
批改说明：...`;

      const pythonScript = this.buildPythonScript(imagePaths, prompt);
      
      const tempScript = path.join(homeworkFiles[i], '..', `_analyze_${i}.py`);
      fs.writeFileSync(tempScript, pythonScript, 'utf-8');

      try {
        const result = execSync(`python3 "${tempScript}"`, { 
          encoding: 'utf-8',
          timeout: 120000
        });
        fs.unlinkSync(tempScript);
        analysisResults.push(result.trim());
        console.log(`  ✓ 完成`);
      } catch (error) {
        if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
        console.log(`  ✗ 失败`);
        analysisResults.push(`第${i + 1}张作业：分析失败`);
      }
    }

    // 汇总
    return this.summarizeResults(studentName, homeworkType, homeworkFiles.length, analysisResults);
  }

  /**
   * 构建提示词
   */
  buildPrompt(studentName, homeworkType, count, hasAnswer) {
    let prompt = '';
    
    if (hasAnswer) {
      prompt += `第1张图片是${homeworkType}答案.png，这是作业的标准答案，你需要认真参考答案进行作业批改。\n\n`;
    }
    
    prompt += `帮我检查这些作业的对错，并根据学生的对错分析出该学生对于这个章节理解的薄弱环节与作业总结。对于看不清楚学生答案的按照错误处理，如果发现较多看不清楚学生手写答案那就要强调学生在作业书写规范上要重点加强(字迹潦草不算，因为孩子年龄不大)。

**重要**：
1.要严谨仔细理解题目意思，确保判改作业的正确性(重要)。
2.要检查应用题答题中是否缺少单位。
3.应用题的答案需要有计算过程。
4.对的划对勾，错的划错号，看不清楚的用问号，作业中的漏答题做漏题标注。
5.答题的位置写的算式是错误的，算式要写在相应的答题位置。
6.总结内容要语气话一些，但是要正式，要多维度总结。
7.不要使用"识别"这种计算机技术用语。
8.不要使用繁体字。

请按以下格式输出：
学生姓名：${studentName}
作业类型：${homeworkType}
作业图片数量：${count}张

逐题批改：
第1题：...（正确/错误/看不清楚/漏题）
...

薄弱环节分析：
...

作业总结：
...`;

    return prompt;
  }

  /**
   * 构建 Python 脚本
   */
  buildPythonScript(imagePaths, prompt) {
    const imageEncodings = imagePaths.map(imgPath => {
      const base64 = fs.readFileSync(imgPath, { encoding: 'base64' });
      return `"data:image/jpeg;base64,${base64}"`;
    });

    return `
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from openai import OpenAI

client = OpenAI(
    [REDACTED_SECRET]",
    base_url="${this.baseUrl}"
)

response = client.chat.completions.create(
    model="${this.model}",
    messages=[
        {
            "role": "user",
            "content": [
                ${imageEncodings.map(img => `{
                    "type": "image_url",
                    "image_url": {"url": ${img}}
                }`).join(',\n                ')},
                {
                    "type": "text",
                    "text": """${prompt}"""
                }
            ]
        }
    ]
)

print(response.choices[0].message.content)
`;
  }

  /**
   * 汇总逐一分析结果
   */
  summarizeResults(studentName, homeworkType, count, results) {
    const allResults = results.join('\n\n');
    
    const summaryPrompt = `基于以下各张作业的批改结果，生成完整的作业分析报告。

各张作业批改结果：
${allResults}

请按以下格式输出完整报告：
学生姓名：${studentName}
作业类型：${homeworkType}
作业图片数量：${count}张

逐题批改：
（列出每道题的批改结果）

薄弱环节分析：
根据学生的错题情况，分析该学生对于章节理解的薄弱环节。

作业总结：
语气正式但亲切，多维度总结学生的作业情况。如果发现较多无法识别学生手写答案，要强调学生在作业书写规范上要重点加强(字迹潦草不算，因为孩子年龄不大)。

**重要**：
1.总结内容要语气话一些，但是要正式，要多维度总结。
2.不要使用"识别"这种计算机技术用语。
3.不要使用繁体字。`;

    const pythonScript = `
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from openai import OpenAI

client = OpenAI(
    [REDACTED_SECRET]",
    base_url="${this.baseUrl}"
)

response = client.chat.completions.create(
    model="${this.model}",
    messages=[
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": """${summaryPrompt}"""
            }
        }
    ]
)

print(response.choices[0].message.content)
`;

    const tempScript = path.join(require('os').homedir(), 'Desktop', '_summarize.py');
    fs.writeFileSync(tempScript, pythonScript, 'utf-8');

    try {
      const result = execSync(`python3 "${tempScript}"`, { 
        encoding: 'utf-8',
        timeout: 120000
      });
      fs.unlinkSync(tempScript);
      return result.trim();
    } catch (error) {
      if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
      return allResults; // 如果汇总失败，返回原始结果
    }
  }

  /**
   * 生成 Word 文档（手机阅读优化版）
   */
  async generateWord(courseName, studentName, homeworkType, content, outputDir) {
    const docxPath = path.join(outputDir, `${courseName}${studentName}${homeworkType}分析.docx`);
    
    const pythonScript = `
# -*- coding: utf-8 -*-
from docx import Document
from docx.shared import Pt, RGBColor, Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn

doc = Document()

# 设置窄边距，适合手机阅读
sections = doc.sections
for section in sections:
    section.top_margin = Inches(0.5)
    section.bottom_margin = Inches(0.5)
    section.left_margin = Inches(0.5)
    section.right_margin = Inches(0.5)

# 设置默认字体
doc.styles['Normal'].font.name = '微软雅黑'
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
doc.styles['Normal'].font.size = Pt(14)  # 手机阅读用大字体

# 标题
title = doc.add_heading('', level=0)
title_run = title.add_run('📚 ${studentName} ${homeworkType}作业分析')
title_run.font.name = '微软雅黑'
title_run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
title_run.font.size = Pt(20)  # 大标题
title_run.font.bold = True
title_run.font.color.rgb = RGBColor(0, 102, 204)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER

# 副标题
subtitle = doc.add_paragraph()
subtitle_run = subtitle.add_run('${courseName}')
subtitle_run.font.name = '微软雅黑'
subtitle_run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
subtitle_run.font.size = Pt(12)
subtitle_run.font.color.rgb = RGBColor(128, 128, 128)
subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER

doc.add_paragraph()

# 解析内容
content = """${content.replace(/"/g, '\\"').replace(/'/g, "\\'")}"""
lines = content.split('\\n')

# 跳过基本信息行
skip_lines = ['学生姓名：', '作业类型：', '作业图片数量：']

for line in lines:
    line = line.strip()
    if not line:
        continue
    if any(line.startswith(s) for s in skip_lines):
        continue
    if line == '---':
        doc.add_paragraph()
        continue
    
    # 大章节标题（如：逐题批改、薄弱环节分析）
    if line.startswith('###') or (line.startswith('**') and '批改' in line) or (line.startswith('**') and '分析' in line) or (line.startswith('**') and '总结' in line):
        heading_text = line.strip('*#').strip()
        doc.add_paragraph()  # 前面空一行
        heading = doc.add_heading('', level=1)
        run = heading.add_run('▶ ' + heading_text)
        run.font.name = '微软雅黑'
        run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
        run.font.size = Pt(16)  # 章节标题
        run.font.bold = True
        run.font.color.rgb = RGBColor(0, 102, 204)
        continue
    
    # 小节标题（如：第1题、第2课时）
    if (line.startswith('第') and ('题' in line or '课时' in line)) or line.startswith('**第'):
        clean_line = line.strip('*')
        doc.add_paragraph()  # 前面空一行
        p = doc.add_paragraph()
        
        # 提取题号和内容
        if '：' in clean_line or ':' in clean_line:
            sep = '：' if '：' in clean_line else ':'
            parts = clean_line.split(sep, 1)
            run1 = p.add_run(parts[0] + ' ')
            run1.font.name = '微软雅黑'
            run1._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
            run1.font.size = Pt(15)
            run1.font.bold = True
            run1.font.color.rgb = RGBColor(51, 51, 51)
            
            if len(parts) > 1:
                run2 = p.add_run(parts[1])
                run2.font.name = '微软雅黑'
                run2._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
                run2.font.size = Pt(14)
                # 根据内容设置颜色
                if '正确' in parts[1] or '✅' in parts[1]:
                    run2.font.color.rgb = RGBColor(0, 153, 0)
                elif '错误' in parts[1] or '❌' in parts[1]:
                    run2.font.color.rgb = RGBColor(204, 0, 0)
        else:
            run = p.add_run(clean_line)
            run.font.name = '微软雅黑'
            run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
            run.font.size = Pt(15)
            run.font.bold = True
        continue
    
    # 数字列表项（如：1. 2. 3.）
    if len(line) > 2 and line[0].isdigit() and line[1] in ['.', '、']:
        p = doc.add_paragraph()
        num_text = line[0] + '. '
        content_text = line[2:].strip()
        
        run1 = p.add_run(num_text)
        run1.font.name = '微软雅黑'
        run1._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
        run1.font.size = Pt(14)
        run1.font.bold = True
        run1.font.color.rgb = RGBColor(0, 102, 204)
        
        run2 = p.add_run(content_text)
        run2.font.name = '微软雅黑'
        run2._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
        run2.font.size = Pt(14)
        continue
    
    # 子项（以-或•开头）
    if line.startswith('-') or line.startswith('•') or line.startswith('→'):
        p = doc.add_paragraph()
        p.paragraph_format.left_indent = Inches(0.3)  # 缩进
        run = p.add_run('  ' + line)
        run.font.name = '微软雅黑'
        run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
        run.font.size = Pt(13)
        
        if '正确' in line or '✅' in line:
            run.font.color.rgb = RGBColor(0, 153, 0)
        elif '错误' in line or '❌' in line:
            run.font.color.rgb = RGBColor(204, 0, 0)
        continue
    
    # 普通段落
    p = doc.add_paragraph()
    run = p.add_run(line)
    run.font.name = '微软雅黑'
    run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
    run.font.size = Pt(14)  # 手机阅读用大字体

# 页脚
doc.add_paragraph()
doc.add_paragraph()
footer = doc.add_paragraph('— 分析报告结束 —')
footer.alignment = WD_ALIGN_PARAGRAPH.CENTER
for run in footer.runs:
    run.font.name = '微软雅黑'
    run._element.rPr.rFonts.set(qn('w:eastAsia'), '微软雅黑')
    run.font.size = Pt(12)
    run.font.color.rgb = RGBColor(153, 153, 153)

doc.save(r'${docxPath.replace(/\\/g, '\\\\')}')
print('Word文档已保存')
`;

    const tempScript = path.join(outputDir, '_generate_word.py');
    fs.writeFileSync(tempScript, pythonScript, 'utf-8');

    try {
      execSync(`python3 "${tempScript}"`, { encoding: 'utf-8', timeout: 60000 });
      fs.unlinkSync(tempScript);
    } catch (error) {
      if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
      throw error;
    }
  }
}

module.exports = { HomeworkAnalyzer };

// 如果直接运行
if (require.main === module) {
  const analyzer = new HomeworkAnalyzer();
  
  // 分析示例
  const courseName = process.argv[2] || '一下(第9节)';
  const studentName = process.argv[3] || '大瑀';
  const homeworkType = process.argv[4] || '课内';
  
  analyzer.analyze(courseName, studentName, homeworkType)
    .then(() => console.log('\n分析完成！'))
    .catch(console.error);
}


path: archived-scripts/analyze-homework-ai.js
content: const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

async function analyzeScreenshots(courseName) {
  console.log('\n' + '='.repeat(60));
  console.log(`  AI 分析作业数量: ${courseName}`);
  console.log('='.repeat(60) + '\n');

  const desktopDir = path.join(require('os').homedir(), 'Desktop');
  const courseDir = path.join(desktopDir, courseName);
  
  if (!fs.existsSync(courseDir)) {
    console.log(`❌ 未找到课程目录: ${courseDir}`);
    return;
  }

  // 获取所有截图文件
  const screenshots = fs.readdirSync(courseDir)
    .filter(f => f.endsWith('.png') && !f.startsWith('_'))
    .map(f => path.join(courseDir, f));

  if (screenshots.length === 0) {
    console.log('❌ 未找到截图文件');
    return;
  }

  console.log(`找到 ${screenshots.length} 个学生截图\n`);

  const [REDACTED_SECRET];
  if (!apiKey) {
    console.log('❌ 未设置 DASHSCOPE_API_KEY 环境变量');
    return;
  }

  const results = [];

  for (const screenshot of screenshots) {
    const studentName = path.basename(screenshot, '.png');
    console.log(`分析: ${studentName}...`);

    const prompt = `请分析这张截图中学生提交的作业数量。

截图中格式通常如下：
认真完成课内课后作业，并及时提交作业。
[图片][图片][图片]...（数量不固定）
认真完成奥数课后作业，并及时提交作业。
[图片][图片][图片]...（数量不固定）

请统计：
1. 课内作业的图片数量
2. 奥数作业的图片数量

用简洁的中文回答，格式如下：
学生姓名：XXX
课内作业：X张
奥数作业：X张`;

    const base64 = fs.readFileSync(screenshot, { encoding: 'base64' });

    const pythonScript = `
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from openai import OpenAI

client = OpenAI(
    [REDACTED_SECRET]",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

response = client.chat.completions.create(
    model="qwen-vl-max-latest",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {"url": "data:image/png;base64,${base64}"}
                },
                {
                    "type": "text",
                    "text": """${prompt}"""
                }
            ]
        }
    ]
)

print(response.choices[0].message.content)
`;

    const tempScript = path.join(courseDir, `_analyze_${studentName}.py`);
    fs.writeFileSync(tempScript, pythonScript, 'utf-8');

    try {
      const result = execSync(`python3 "${tempScript}"`, { 
        encoding: 'utf-8',
        timeout: 120000
      });
      fs.unlinkSync(tempScript);

      // 解析结果
      const keNeiMatch = result.match(/课内作业：(\d+)张/);
      const aoShuMatch = result.match(/奥数作业：(\d+)张/);

      const keNeiCount = keNeiMatch ? parseInt(keNeiMatch[1]) : 0;
      const aoShuCount = aoShuMatch ? parseInt(aoShuMatch[1]) : 0;

      results.push({
        student: studentName,
        keNeiCount,
        aoShuCount,
        rawAnalysis: result.trim()
      });

      console.log(`  ✅ 课内: ${keNeiCount}张, 奥数: ${aoShuCount}张\n`);
    } catch (error) {
      if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
      console.log(`  ❌ 分析失败: ${error.message}\n`);
      results.push({
        student: studentName,
        keNeiCount: 0,
        aoShuCount: 0,
        error: true
      });
    }
  }

  // 保存分析结果
  const analysisPath = path.join(courseDir, 'analysis-results.json');
  fs.writeFileSync(analysisPath, JSON.stringify(results, null, 2), 'utf-8');

  console.log('='.repeat(60));
  console.log('  分析结果汇总');
  console.log('='.repeat(60));
  
  for (const r of results) {
    console.log(`\n${r.student}:`);
    console.log(`  课内作业: ${r.keNeiCount}张`);
    console.log(`  奥数作业: ${r.aoShuCount}张`);
    if (r.rawAnalysis) {
      console.log(`  详细: ${r.rawAnalysis.replace(/\n/g, ' ')}`);
    }
  }

  console.log('\n' + '='.repeat(60));
  console.log('  ✅ AI 分析完成！');
  console.log('='.repeat(60) + '\n');

  return results;
}

// 主入口
const courseName = process.argv[2] || '二下(第36节)';
analyzeScreenshots(courseName).catch(console.error);


path: archived-scripts/analyze-screenshots.js
content: const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

async function analyzeScreenshots(courseName) {
  console.log('\n' + '='.repeat(60));
  console.log(`  AI 分析作业数量: ${courseName}`);
  console.log('='.repeat(60) + '\n');

  const desktopDir = path.join(require('os').homedir(), 'Desktop');
  const courseDir = path.join(desktopDir, courseName);
  
  if (!fs.existsSync(courseDir)) {
    console.log(`❌ 未找到课程目录: ${courseDir}`);
    return;
  }

  // 获取所有截图文件
  const screenshots = fs.readdirSync(courseDir)
    .filter(f => f.endsWith('.png') && !f.startsWith('_'))
    .map(f => path.join(courseDir, f));

  if (screenshots.length === 0) {
    console.log('❌ 未找到截图文件');
    return;
  }

  console.log(`找到 ${screenshots.length} 个学生截图\n`);

  const [REDACTED_SECRET];
  if (!apiKey) {
    console.log('❌ 未设置 DASHSCOPE_API_KEY 环境变量');
    return;
  }

  const results = [];

  for (const screenshot of screenshots) {
    const studentName = path.basename(screenshot, '.png');
    console.log(`分析: ${studentName}...`);

    const prompt = `请分析这张截图中学生提交的作业数量。

截图中格式通常如下：
认真完成课内课后作业，并及时提交作业。
[图片][图片][图片]...（数量不固定）
认真完成奥数课后作业，并及时提交作业。
[图片][图片][图片]...（数量不固定）

请统计：
1. 课内作业的图片数量
2. 奥数作业的图片数量

用简洁的中文回答，格式如下：
学生姓名：XXX
课内作业：X张
奥数作业：X张`;

    const base64 = fs.readFileSync(screenshot, { encoding: 'base64' });

    const pythonScript = `
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from openai import OpenAI

client = OpenAI(
    [REDACTED_SECRET]",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

response = client.chat.completions.create(
    model="qwen-vl-max-latest",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {"url": "data:image/png;base64,${base64}"}
                },
                {
                    "type": "text",
                    "text": """${prompt}"""
                }
            ]
        }
    ]
)

print(response.choices[0].message.content)
`;

    const tempScript = path.join(courseDir, `_analyze_${studentName}.py`);
    fs.writeFileSync(tempScript, pythonScript, 'utf-8');

    try {
      const result = execSync(`python3 "${tempScript}"`, { 
        encoding: 'utf-8',
        timeout: 120000
      });
      fs.unlinkSync(tempScript);

      // 解析结果
      const keNeiMatch = result.match(/课内作业：(\d+)张/);
      const aoShuMatch = result.match(/奥数作业：(\d+)张/);

      const keNeiCount = keNeiMatch ? parseInt(keNeiMatch[1]) : 0;
      const aoShuCount = aoShuMatch ? parseInt(aoShuMatch[1]) : 0;

      results.push({
        student: studentName,
        keNeiCount,
        aoShuCount,
        rawAnalysis: result.trim()
      });

      console.log(`  ✅ 课内: ${keNeiCount}张, 奥数: ${aoShuCount}张\n`);
    } catch (error) {
      if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
      console.log(`  ❌ 分析失败: ${error.message}\n`);
      results.push({
        student: studentName,
        keNeiCount: 0,
        aoShuCount: 0,
        error: true
      });
    }
  }

  // 保存分析结果
  const analysisPath = path.join(courseDir, 'analysis-results.json');
  fs.writeFileSync(analysisPath, JSON.stringify(results, null, 2), 'utf-8');

  console.log('='.repeat(60));
  console.log('  分析结果汇总');
  console.log('='.repeat(60));
  
  for (const r of results) {
    console.log(`\n${r.student}:`);
    console.log(`  课内作业: ${r.keNeiCount}张`);
    console.log(`  奥数作业: ${r.aoShuCount}张`);
    if (r.rawAnalysis) {
      console.log(`  详细: ${r.rawAnalysis.replace(/\n/g, ' ')}`);
    }
  }

  console.log('\n' + '='.repeat(60));
  console.log('  ✅ AI 分析完成！');
  console.log('='.repeat(60) + '\n');

  return results;
}

// 主入口
const courseName = process.argv[2] || '二下(第36节)';
analyzeScreenshots(courseName).catch(console.error);


path: archived-scripts/analyze-with-qwen.js
content: /**
 * 使用 Qwen 分析小米的课内作业
 */

const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

async function analyzeWithQwen() {
  const studentName = '小米';
  const homeworkType = '课内';
  const courseName = '一下(第9节)';
  
  // 学生作业目录
  const studentDir = path.join(require('os').homedir(), 'Desktop', courseName, studentName, homeworkType);
  
  // 答案文件
  const answerPath = path.join(require('os').homedir(), 'Desktop', '答案', courseName, '课内答案.png');
  
  console.log(`========================================`);
  console.log(`使用 Qwen 批改: ${studentName} - ${homeworkType}作业`);
  console.log(`========================================\n`);
  
  // 检查答案文件是否存在
  const hasAnswer = fs.existsSync(answerPath);
  console.log(`答案文件: ${hasAnswer ? '存在' : '不存在'}\n`);
  
  // 获取学生作业图片
  const homeworkFiles = fs.readdirSync(studentDir)
    .filter(f => f.endsWith('.jpg'))
    .map(f => path.join(studentDir, f))
    .sort();
  
  console.log(`作业图片: ${homeworkFiles.length} 张\n`);
  
  // 构建提示词
  let promptPrefix = '';
  if (hasAnswer) {
    promptPrefix = "第1张图片是课内答案.png，这是作业的标准答案，你需要认真参考答案进行作业批改。\n\n";
  }
  
  const prompt = promptPrefix + `帮我检查这些作业的对错，并根据学生的对错分析出该学生对于这个章节理解的薄弱环节与作业总结。对于看不清楚学生答案的按照错误处理，如果发现较多看不清楚学生手写答案那就要强调学生在作业书写规范上要重点加强(字迹潦草不算，因为孩子年龄不大)。

**重要**：
1.要严谨仔细理解题目意思，确保判改作业的正确性(重要)。
2.要检查应用题答题中是否缺少单位。
3.应用题的答案需要有计算过程。
4.对的划对勾，错的划错号，看不清楚的用问号，作业中的漏答题做漏题标注。
5.答题的位置写的算式是错误的，算式要写在相应的答题位置。
6.总结内容要语气话一些，但是要正式，要多维度总结。
7.不要使用"识别"这种计算机技术用语。
8.不要使用繁体字。

请按以下格式输出：
学生姓名：${studentName}
作业类型：${homeworkType}
作业图片数量：${homeworkFiles.length}张

逐题批改：
第1题：...（正确/错误/看不清楚/漏题）
...

薄弱环节分析：
...

作业总结：
...`;

  // 构建Python脚本 - 使用 Qwen
  const imagePaths = hasAnswer 
    ? [answerPath, ...homeworkFiles]
    : homeworkFiles;
  
  const imageEncodings = imagePaths.map(imgPath => {
    const base64 = fs.readFileSync(imgPath, { encoding: 'base64' });
    return `"data:image/jpeg;base64,${base64}"`;
  });

  const pythonScript = `
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from openai import OpenAI

client = OpenAI(
    [REDACTED_SECRET] || '[REDACTED_SECRET]'}",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

response = client.chat.completions.create(
    model="qwen-vl-max-latest",
    messages=[
        {
            "role": "user",
            "content": [
                ${imageEncodings.map(img => `{
                    "type": "image_url",
                    "image_url": {"url": ${img}}
                }`).join(',\n                ')},
                {
                    "type": "text",
                    "text": """${prompt}"""
                }
            ]
        }
    ]
)

print(response.choices[0].message.content)
`;

  const tempScript = path.join(studentDir, '_analyze_qwen.py');
  fs.writeFileSync(tempScript, pythonScript, 'utf-8');
  
  console.log('正在使用 Qwen 分析，请稍候...\n');
  
  try {
    const result = execSync(`python3 "${tempScript}"`, { 
      encoding: 'utf-8',
      timeout: 300000  // 5分钟超时
    });
    fs.unlinkSync(tempScript);
    
    console.log(result);
    
    // 保存结果到文件
    const resultPath = path.join(require('os').homedir(), 'Desktop', courseName, `${courseName}${studentName}${homeworkType}分析.txt`);
    fs.writeFileSync(resultPath, result, 'utf-8');
    console.log(`\n分析结果已保存: ${resultPath}`);
    
  } catch (error) {
    if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
    console.error('分析失败:', error.message);
  }
}

analyzeWithQwen().catch(console.error);


path: archived-scripts/capture-1.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureFirstStudent() {
  console.log('截图第一个学生（灿灿）\n');
  
  const outputDir = 'C:\\Users\\Administrator\\Desktop\\二下(第31节)';
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 截图第一个学生（灿灿）
  const filepath = path.join(outputDir, '灿灿.png');
  await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
  console.log(`✓ 已保存: 灿灿.png`);
  
  await browser.close();
  console.log('\n完成！');
}

captureFirstStudent().catch(console.error);


path: archived-scripts/capture-2.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureSecondStudent() {
  console.log('截图第二个学生（Zoey）\n');
  
  const outputDir = 'C:\\Users\\Administrator\\Desktop\\二下(第31节)';
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 截图第二个学生（Zoey）
  const filepath = path.join(outputDir, 'Zoey.png');
  await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
  console.log(`✓ 已保存: Zoey.png`);
  
  await browser.close();
  console.log('\n完成！');
}

captureSecondStudent().catch(console.error);


path: archived-scripts/capture-3.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureThirdStudent() {
  console.log('截图第三个学生（球球）\n');
  
  const outputDir = 'C:\\Users\\Administrator\\Desktop\\二下(第31节)';
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 截图第三个学生（球球）
  const filepath = path.join(outputDir, '球球.png');
  await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
  console.log(`✓ 已保存: 球球.png`);
  
  await browser.close();
  console.log('\n完成！');
}

captureThirdStudent().catch(console.error);


path: archived-scripts/capture-36.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureTwoStudents() {
  const courseName = '二下(第36节)';
  const outputDir = 'C:\\Users\\Administrator\\Desktop\\二下(第36节)';
  
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 学生列表（已确认只有2个）
  const studentList = [
    { name: '球球', scrollTop: 0 },
    { name: 'Zoey', scrollTop: 400 }
  ];
  
  console.log('学生列表:');
  studentList.forEach((s, i) => console.log(`  ${i + 1}. ${s.name}`));
  console.log();
  
  // 截图每个学生
  console.log('【截图】\n');
  
  const screenshots = [];
  
  for (let i = 0; i < studentList.length; i++) {
    const student = studentList[i];
    
    console.log(`[${i + 1}] ${student.name}`);
    
    // 设置滚动位置
    if (i > 0) {
      await page.evaluate((scrollTop) => {
        const container = document.querySelector('[style*="overflow-y: scroll"]');
        if (container) {
          container.scrollTop = scrollTop;
        }
      }, student.scrollTop);
      await page.waitForTimeout(1000);
    }
    
    // 截图
    const filepath = path.join(outputDir, `${student.name}.png`);
    await page.screenshot({ path: filepath, fullPage: false });
    console.log(`   ✓ 已保存: ${student.name}.png`);
    
    screenshots.push({ name: student.name, filepath });
  }
  
  console.log('\n========================================');
  console.log(`✓ 截图完成，共 ${screenshots.length} 个学生`);
  console.log(`输出目录: ${outputDir}\n`);
  
  await browser.close();
  
  return {
    course: courseName,
    screenshotCount: screenshots.length,
    students: screenshots.map(s => s.name),
    outputDir
  };
}

captureTwoStudents()
  .then(result => {
    console.log('========================================');
    console.log('  截图完成');
    console.log('========================================');
    console.log(`课程: ${result.course}`);
    console.log(`已截图: ${result.screenshotCount} 人`);
    console.log(`学生: ${result.students.join(', ')}`);
    console.log(`目录: ${result.outputDir}`);
    console.log('========================================');
  })
  .catch(console.error);


path: archived-scripts/capture-and-analyze-36.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

async function captureAndAnalyzeHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图与分析 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // ========== 第一阶段：进入课程并收集学生列表 ==========
  console.log('【第一阶段】收集学生列表\n');
  
  // 1. 进入课程
  console.log('1. 进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 20; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(5000);
  console.log('   ✓ 已进入课程详情页\n');
  
  // 2. 获取"已提交"数量
  console.log('2. 获取已提交数量...');
  const submittedCount = await page.evaluate(() => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes('已提交')) {
        const match = label.match(/(\d+)/);
        if (match) return parseInt(match[0]);
      }
    }
    return null;
  });
  
  if (submittedCount) {
    console.log(`   ✓ 已提交: ${submittedCount} 人\n`);
  } else {
    console.log('   ⚠️ 未获取到已提交数量\n');
  }
  
  const targetCount = submittedCount || 10;
  
  // 3. 收集学生列表
  console.log('3. 收集学生列表...');
  const studentList = [];
  const scrollStep = 400;
  
  while (studentList.length < targetCount) {
    // 获取当前可见学生
    const visibleStudents = await page.evaluate(() => {
      const list = [];
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20 && label.includes('2026-')) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            if (rect.top >= -50 && rect.top < window.innerHeight) {
              list.push({ name, top: rect.top, label });
            }
          }
        }
      }
      return list;
    });
    
    // 记录新学生
    for (const student of visibleStudents) {
      if (!studentList.find(s => s.name === student.name)) {
        studentList.push(student);
        console.log(`   [${studentList.length}] ${student.name}`);
      }
    }
    
    // 检查是否已收集足够学生
    if (studentList.length >= targetCount) {
      console.log(`   ✓ 已收集 ${studentList.length} 个学生\n`);
      break;
    }
    
    // 向下滚动
    console.log(`   滚动查找更多学生...`);
    await page.evaluate((step) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) {
        container.scrollTop += step;
      }
    }, scrollStep);
    
    await page.waitForTimeout(1500);
  }
  
  console.log('   学生列表:');
  studentList.forEach((s, i) => console.log(`     ${i + 1}. ${s.name}`));
  console.log();
  
  // ========== 第二阶段：截图 ==========
  console.log('【第二阶段】截图\n');
  console.log('========================================');
  
  // 4. 重新进入页面（从第一个学生开始）
  console.log('4. 重新进入页面...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 20; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(5000);
  console.log('   ✓ 已重新进入课程详情页\n');
  
  // 5. 截图每个学生
  console.log('5. 截图每个学生...\n');
  
  const screenshots = [];
  
  for (let i = 0; i < studentList.length; i++) {
    const studentName = studentList[i].name;
    
    console.log(`[${i + 1}] ${studentName}`);
    
    // 截图
    const filepath = path.join(outputDir, `${studentName}.png`);
    await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
    console.log(`   ✓ 已保存: ${studentName}.png`);
    
    screenshots.push({ name: studentName, filepath });
    
    // 如果不是最后一个学生，滚动到下一个
    if (i < studentList.length - 1) {
      console.log(`   滚动: scrollTop += ${scrollStep}`);
      await page.evaluate((step) => {
        const container = document.querySelector('[style*="overflow-y: scroll"]');
        if (container) {
          container.scrollTop += step;
        }
      }, scrollStep);
      
      await page.waitForTimeout(1500);
    }
  }
  
  console.log('\n========================================');
  console.log(`\n✓ 截图完成，共 ${screenshots.length} 个学生`);
  console.log(`输出目录: ${outputDir}\n`);
  
  await browser.close();
  
  // ========== 第三阶段：使用 Qwen3.5-Plus 分析 ==========
  console.log('【第三阶段】使用 Qwen3.5-Plus 分析作业\n');
  
  const analysisResults = [];
  
  for (const screenshot of screenshots) {
    console.log(`分析: ${screenshot.name}`);
    
    try {
      // 使用 qwen35-plus-image-analyze 技能分析图片
      const prompt = `请分析这张班级小管家作业截图，识别以下信息：
1. 学生姓名
2. 课内作业提交数量（图片中显示"课内"相关的数字）
3. 奥数作业提交数量（图片中显示"奥数"相关的数字）

请以以下格式输出：
学生姓名：XXX
课内作业：X张
奥数作业：X张`;
      
      const result = analyzeWithQwen(screenshot.filepath, prompt);
      
      analysisResults.push({
        name: screenshot.name,
        filepath: screenshot.filepath,
        analysis: result
      });
      
      console.log(`   ✓ 分析完成\n`);
    } catch (err) {
      console.log(`   ✗ 分析失败: ${err.message}\n`);
      analysisResults.push({
        name: screenshot.name,
        filepath: screenshot.filepath,
        analysis: '分析失败'
      });
    }
  }
  
  // ========== 第四阶段：生成报告 ==========
  console.log('【第四阶段】生成分析报告\n');
  
  const reportPath = path.join(outputDir, '分析报告.txt');
  let report = `========================================
  作业提交情况分析报告
  课程: ${courseName}
  时间: ${new Date().toLocaleString('zh-CN')}
========================================

`;
  
  // 统计信息
  let totalKeNei = 0;
  let totalAoShu = 0;
  const submittedStudents = [];
  const unsubmittedStudents = [];
  
  for (const result of analysisResults) {
    const analysis = result.analysis;
    
    // 尝试提取数量
    const keNeiMatch = analysis.match(/课内作业[:：]\s*(\d+)/);
    const aoShuMatch = analysis.match(/奥数作业[:：]\s*(\d+)/);
    
    const keNeiCount = keNeiMatch ? parseInt(keNeiMatch[1]) : 0;
    const aoShuCount = aoShuMatch ? parseInt(aoShuMatch[1]) : 0;
    
    totalKeNei += keNeiCount;
    totalAoShu += aoShuCount;
    
    if (keNeiCount > 0 || aoShuCount > 0) {
      submittedStudents.push({
        name: result.name,
        keNei: keNeiCount,
        aoShu: aoShuCount
      });
    } else {
      unsubmittedStudents.push(result.name);
    }
  }
  
  // 报告内容
  report += `【总体情况】\n`;
  report += `应提交人数: ${studentList.length} 人\n`;
  report += `已提交人数: ${submittedStudents.length} 人\n`;
  report += `未提交人数: ${unsubmittedStudents.length} 人\n`;
  report += `课内作业总计: ${totalKeNei} 张\n`;
  report += `奥数作业总计: ${totalAoShu} 张\n\n`;
  
  report += `【已提交学生详情】\n`;
  if (submittedStudents.length > 0) {
    submittedStudents.forEach((s, i) => {
      report += `${i + 1}. ${s.name}: 课内 ${s.keNei} 张, 奥数 ${s.aoShu} 张\n`;
    });
  } else {
    report += `无\n`;
  }
  report += `\n`;
  
  report += `【未提交学生】\n`;
  if (unsubmittedStudents.length > 0) {
    unsubmittedStudents.forEach((name, i) => {
      report += `${i + 1}. ${name}\n`;
    });
  } else {
    report += `无\n`;
  }
  report += `\n`;
  
  report += `【原始分析数据】\n`;
  analysisResults.forEach((result, i) => {
    report += `\n--- ${result.name} ---\n`;
    report += `${result.analysis}\n`;
  });
  
  fs.writeFileSync(reportPath, report, 'utf8');
  console.log(`✓ 分析报告已保存: ${reportPath}\n`);
  
  // 打印摘要
  console.log('========================================');
  console.log('  分析结果摘要');
  console.log('========================================');
  console.log(`课程: ${courseName}`);
  console.log(`已提交: ${submittedStudents.length}/${studentList.length} 人`);
  console.log(`课内作业: ${totalKeNei} 张`);
  console.log(`奥数作业: ${totalAoShu} 张`);
  console.log('========================================');
  
  return {
    course: courseName,
    submittedCount: submittedStudents.length,
    totalStudents: studentList.length,
    keNeiTotal: totalKeNei,
    aoShuTotal: totalAoShu,
    submittedStudents,
    unsubmittedStudents,
    outputDir,
    reportPath
  };
}

// 使用 Qwen3.5-Plus 分析图片
function analyzeWithQwen(imagePath, prompt) {
  const skillPath = path.join(process.env.USERPROFILE, '.openclaw', 'workspace', 'skills', 'qwen35-plus-image-analyze', 'scripts', 'analyze_image.py');
  
  try {
    const result = execSync(
      `python "${skillPath}" "${imagePath}" "${prompt}"`,
      { encoding: 'utf8', timeout: 60000 }
    );
    return result.trim();
  } catch (err) {
    throw new Error(`Qwen分析失败: ${err.message}`);
  }
}

// 运行
captureAndAnalyzeHomework('二下(第36节)', 'C:\\Users\\Administrator\\Desktop\\二下(第36节)')
  .then(result => {
    console.log('\n任务完成！');
    console.log(`报告位置: ${result.reportPath}`);
  })
  .catch(err => {
    console.error('任务失败:', err);
    process.exit(1);
  });


path: archived-scripts/capture-auto-start.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');

// 启动 Chrome 调试模式
async function startChromeDebugger() {
  console.log('🚀 正在启动 Chrome 调试模式...');
  
  const isWindows = process.platform === 'win32';
  const isMac = process.platform === 'darwin';
  
  let chromePath;
  let args;
  
  if (isWindows) {
    chromePath = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
    args = [
      '--remote-debugging-port=9222',
      '--window-size=1280,800',
      'https://service.banjixiaoguanjia.com/appweb/'
    ];
  } else if (isMac) {
    chromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
    args = [
      '--remote-debugging-port=9222',
      '--window-size=1280,800',
      'https://service.banjixiaoguanjia.com/appweb/'
    ];
  } else {
    // Linux
    chromePath = 'google-chrome';
    args = [
      '--remote-debugging-port=9222',
      '--window-size=1280,800',
      'https://service.banjixiaoguanjia.com/appweb/'
    ];
  }
  
  // 检查 Chrome 是否已经在运行
  try {
    const response = await fetch('http://localhost:9222/json/version');
    if (response.ok) {
      console.log('   ✅ Chrome 调试模式已在运行');
      return true;
    }
  } catch (e) {
    // Chrome 未启动，继续启动
  }
  
  // 启动 Chrome
  return new Promise((resolve, reject) => {
    const chrome = spawn(chromePath, args, {
      detached: true,
      stdio: 'ignore'
    });
    
    chrome.on('error', (err) => {
      console.error('❌ 启动 Chrome 失败:', err.message);
      reject(err);
    });
    
    // 等待 Chrome 启动完成
    let attempts = 0;
    const maxAttempts = 30;
    
    const checkConnection = setInterval(async () => {
      attempts++;
      try {
        const response = await fetch('http://localhost:9222/json/version');
        if (response.ok) {
          clearInterval(checkConnection);
          console.log('   ✅ Chrome 调试模式已启动 (端口 9222，窗口 1280x800)');
          resolve(true);
        }
      } catch (e) {
        if (attempts >= maxAttempts) {
          clearInterval(checkConnection);
          reject(new Error('Chrome 启动超时，请检查 Chrome 是否已安装'));
        }
      }
    }, 1000);
  });
}

// 等待函数
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function captureHomework(courseName) {
  console.log('\n' + '='.repeat(60));
  console.log(`  班级小管家作业截图`);
  console.log(`  课程: ${courseName}`);
  console.log('='.repeat(60) + '\n');

  // 步骤1: 启动 Chrome
  try {
    await startChromeDebugger();
    console.log('');
  } catch (err) {
    console.error('❌ 无法启动 Chrome:', err.message);
    console.log('\n请手动启动 Chrome 调试模式：');
    console.log('"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" --remote-debugging-port=9222 --window-size=1280,800');
    return;
  }

  const outputDir = path.join(require('os').homedir(), 'Desktop', courseName);
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  console.log('2. 连接 Chrome...');
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0];
  const pages = context.pages();
  
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  if (!page) {
    console.log('❌ 未找到班级小管家页面');
    await browser.close();
    return;
  }
  
  console.log('   ✅ 已连接\n');
  console.log('3. 导航到首页...');
  
  // 导航到首页
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await sleep(3000);
  
  console.log('   ✅ 已回到首页\n');
  console.log('4. 启用辅助功能...');
  
  // 启用辅助功能
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await sleep(2000);
  
  console.log('   ✅ 辅助功能已启用\n');
  console.log('5. 查找课程...');
  
  // 滚动查找课程
  let found = false;
  let scrollAttempts = 0;
  const maxScrollAttempts = 20;
  
  while (!found && scrollAttempts < maxScrollAttempts) {
    // 在当前视图查找课程
    const courseInfo = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) {
          return { found: true, label, hasCorrection: label.includes('改正如下') };
        }
      }
      return { found: false };
    }, courseName);
    
    if (courseInfo.found) {
      console.log(`   ✅ 找到课程: ${courseInfo.label}`);
      found = true;
      break;
    }
    
    // 滚动到最后一个课程，触发加载更多
    await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let lastCourseEl = null;
      
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) {
          lastCourseEl = el;
        }
      }
      
      if (lastCourseEl) {
        lastCourseEl.scrollIntoView({ block: 'start' });
      }
    });
    
    await sleep(1500);
    scrollAttempts++;
    process.stdout.write(`   滚动查找中... (${scrollAttempts}/${maxScrollAttempts})\r`);
  }
  
  if (!found) {
    console.log('\n❌ 未找到课程:', courseName);
    await browser.close();
    return;
  }
  
  console.log('\n6. 点击进入课程...');
  
  // 点击进入课程
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await sleep(4000);
  console.log('   ✅ 已进入课程\n');
  
  // 收集学生列表
  console.log('7. 收集学生列表...');
  
  const students = [];
  let hasMore = true;
  let scrollCount = 0;
  const maxScrolls = 50;
  
  while (hasMore && scrollCount < maxScrolls) {
    const result = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      const students = [];
      let hasMoreContent = false;
      
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes('2026-') && label.includes('课内')) {
          const lines = label.split('\n');
          const name = lines[0].trim();
          const hasCorrection = label.includes('改正如下');
          
          if (name && !students.find(s => s.name === name)) {
            students.push({ name, fullLabel: label, hasCorrection });
          }
        }
        if (label && label.includes('没有更多了')) {
          hasMoreContent = false;
        } else if (label && (label.includes('第') || label.includes('年级'))) {
          hasMoreContent = true;
        }
      }
      
      return { students, hasMoreContent };
    });
    
    // 添加新发现的学生
    for (const student of result.students) {
      if (!students.find(s => s.name === student.name)) {
        students.push(student);
      }
    }
    
    // 检查是否还有更多
    const hasNoMore = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes('没有更多了')) {
          return true;
        }
      }
      return false;
    });
    
    if (hasNoMore) {
      hasMore = false;
    } else {
      // 继续滚动
      await page.evaluate(() => {
        const container = document.querySelector('[style*="overflow-y"]');
        if (container) {
          container.scrollTop += 400;
        }
      });
      await sleep(1000);
      scrollCount++;
    }
  }
  
  console.log(`   ✅ 找到 ${students.length} 个学生`);
  students.forEach((s, i) => {
    const correctionMark = s.hasCorrection ? ' [有改正]' : '';
    console.log(`      ${i + 1}. ${s.name}${correctionMark}`);
  });
  console.log('');
  
  // 重新进入页面，按顺序截图
  console.log('8. 重新进入页面准备截图...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await sleep(3000);
  
  // 启用辅助功能
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await sleep(2000);
  
  // 点击进入课程
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await sleep(4000);
  console.log('   ✅ 已重新进入课程\n');
  
  // 截图每个学生
  console.log('9. 截图学生作业...');
  
  const screenshots = [];
  const scrollStep = 400;
  
  for (let i = 0; i < students.length; i++) {
    const student = students[i];
    
    // 复位滚动位置
    await page.evaluate(() => {
      const container = document.querySelector('[style*="overflow-y"]');
      if (container) {
        container.scrollTop = -1000;
      }
    });
    await sleep(500);
    
    // 滚动到指定位置
    const scrollTop = i * scrollStep;
    await page.evaluate((top) => {
      const container = document.querySelector('[style*="overflow-y"]');
      if (container) {
        container.scrollTop = top;
      }
    }, scrollTop);
    await sleep(1000);
    
    // 截图
    const screenshotPath = path.join(outputDir, `${student.name}.png`);
    await page.screenshot({ path: screenshotPath, fullPage: false });
    screenshots.push({ name: student.name, path: screenshotPath, hasCorrection: student.hasCorrection });
    
    console.log(`   ✅ [${i + 1}/${students.length}] ${student.name}`);
    
    // 如果有改正，额外截图改正部分
    if (student.hasCorrection) {
      await sleep(500);
      const correctionPath = path.join(outputDir, `${student.name}_改正.png`);
      await page.screenshot({ path: correctionPath, fullPage: false });
      console.log(`      📌 已截图改正部分: ${student.name}_改正.png`);
    }
  }
  
  console.log(`\n   ✅ 截图完成，共 ${screenshots.length} 个学生\n`);
  
  // AI 分析
  console.log('10. AI 分析作业数量...');
  
  const analysisResults = [];
  
  for (let i = 0; i < screenshots.length; i++) {
    const { name, path: screenshotPath, hasCorrection } = screenshots[i];
    process.stdout.write(`    分析中: ${name}...\r`);
    
    // 读取图片
    const imageBuffer = fs.readFileSync(screenshotPath);
    const base64Image = imageBuffer.toString('base64');
    
    // 调用 AI 分析
    try {
      const analysis = await analyzeHomework(base64Image, name, hasCorrection);
      analysisResults.push({
        student: name,
        keNei: analysis.keNei,
        aoShu: analysis.aoShu,
        hasCorrection: hasCorrection
      });
      console.log(`    ✅ ${name}: 课内${analysis.keNei}张, 奥数${analysis.aoShu}张${hasCorrection ? ' (有改正)' : ''}`);
    } catch (err) {
      console.log(`    ❌ ${name}: 分析失败 - ${err.message}`);
      analysisResults.push({
        student: name,
        keNei: 0,
        aoShu: 0,
        error: err.message
      });
    }
  }
  
  // 保存分析结果
  const resultPath = path.join(outputDir, 'analysis-results.json');
  fs.writeFileSync(resultPath, JSON.stringify(analysisResults, null, 2));
  
  console.log(`\n   ✅ 分析完成，结果已保存: ${resultPath}\n`);
  
  // 打印汇总
  console.log('='.repeat(60));
  console.log('  作业数量汇总');
  console.log('='.repeat(60));
  
  let totalKeNei = 0;
  let totalAoShu = 0;
  
  for (const result of analysisResults) {
    const correctionMark = result.hasCorrection ? ' [改]' : '';
    console.log(`  ${result.student.padEnd(10)} 课内: ${result.keNei.toString().padStart(2)}张  奥数: ${result.aoShu.toString().padStart(2)}张${correctionMark}`);
    totalKeNei += result.keNei;
    totalAoShu += result.aoShu;
  }
  
  console.log('-'.repeat(60));
  console.log(`  总计       课内: ${totalKeNei.toString().padStart(2)}张  奥数: ${totalAoShu.toString().padStart(2)}张`);
  console.log('='.repeat(60) + '\n');
  
  await browser.close();
  
  console.log('✅ 全部完成！');
  console.log(`📁 输出目录: ${outputDir}\n`);
}

// AI 分析函数
async function analyzeHomework(base64Image, studentName, hasCorrection) {
  const [REDACTED_SECRET];
  
  if (!apiKey) {
    throw new Error('未设置 DASHSCOPE_API_KEY 环境变量');
  }
  
  const correctionPrompt = hasCorrection 
    ? '注意：截图中包含"改正如下"的部分，这是学生补交的改正作业，在统计数量时不需要计入。' 
    : '';
  
  const response = await fetch('https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      model: 'qwen-vl-max-latest',
      messages: [
        {
          role: 'user',
          content: [
            {
              type: 'text',
              text: `请分析这张学生作业截图，统计课内作业和奥数作业的数量。

${correctionPrompt}

请按以下格式回复：
课内作业：X张
奥数作业：Y张

只需要返回这两行，不要其他内容。`
            },
            {
              type: 'image_url',
              image_url: { url: `data:image/png;base64,${base64Image}` }
            }
          ]
        }
      ]
    })
  });
  
  if (!response.ok) {
    throw new Error(`API 请求失败: ${response.status}`);
  }
  
  const data = await response.json();
  const content = data.choices[0].message.content;
  
  // 解析结果
  const keNeiMatch = content.match(/课内作业[:：]\s*(\d+)/);
  const aoShuMatch = content.match(/奥数作业[:：]\s*(\d+)/);
  
  return {
    keNei: keNeiMatch ? parseInt(keNeiMatch[1]) : 0,
    aoShu: aoShuMatch ? parseInt(aoShuMatch[1]) : 0
  };
}

// 主函数
const courseName = process.argv[2];

if (!courseName) {
  console.log('用法: node capture-auto-start.js "课程名称"');
  console.log('示例: node capture-auto-start.js "二下(第38节)"');
  process.exit(1);
}

captureHomework(courseName).catch(console.error);


path: archived-scripts/capture-auto-v2.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 1. 进入课程
  console.log('1. 进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/');
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(4000);
  console.log('   ✓ 已进入课程详情页\n');
  
  // 2. 截图每个学生
  console.log('2. 开始截图...\n');
  console.log('========================================');
  
  const screenshots = [];
  let prevTopStudent = null;
  let noChangeCount = 0;
  const maxNoChange = 5; // 增加容错次数
  const scrollStep = 500;
  const maxScrolls = 20; // 最大滚动次数
  
  for (let scrollCount = 0; scrollCount < maxScrolls; scrollCount++) {
    // 获取当前在视口顶部的学生
    const currentTopStudent = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let topStudent = null;
      let minTop = Infinity;
      
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            if (rect.top >= -50 && rect.top < minTop) {
              minTop = rect.top;
              topStudent = { name, top: rect.top.toFixed(0) };
            }
          }
        }
      }
      return topStudent;
    });
    
    if (!currentTopStudent) {
      console.log('   未找到学生，结束截图');
      break;
    }
    
    console.log(`   当前顶部: ${currentTopStudent.name} (top: ${currentTopStudent.top})`);
    
    // 检查顶部学生是否变化
    if (prevTopStudent && prevTopStudent.name === currentTopStudent.name) {
      noChangeCount++;
      console.log(`   滚动未变化 (${noChangeCount}/${maxNoChange})`);
      
      // 只有当滚动多次无变化且已经截图过这个学生时才结束
      if (noChangeCount >= maxNoChange && screenshots.find(s => s.name === currentTopStudent.name)) {
        console.log('   滚动多次无变化且已截图，结束截图\n');
        break;
      }
    } else {
      noChangeCount = 0;
    }
    
    // 检查是否已经截图过这个学生
    if (!screenshots.find(s => s.name === currentTopStudent.name)) {
      console.log(`\n[${screenshots.length + 1}] ${currentTopStudent.name}`);
      
      // 截图浏览器可见区域
      const filepath = path.join(outputDir, `${currentTopStudent.name}.png`);
      await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
      console.log(`   ✓ 已保存: ${currentTopStudent.name}.png`);
      
      screenshots.push({ name: currentTopStudent.name, filepath });
    }
    
    prevTopStudent = currentTopStudent;
    
    // 向下滚动
    console.log(`   滚动: scrollTop += ${scrollStep}`);
    await page.evaluate((step) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) {
        container.scrollTop += step;
      }
    }, scrollStep);
    
    await page.waitForTimeout(1500);
  }
  
  console.log('========================================');
  console.log(`\n✓ 截图完成，共 ${screenshots.length} 个学生:`);
  screenshots.forEach((s, i) => console.log(`   ${i + 1}. ${s.name}`));
  console.log(`\n输出目录: ${outputDir}\n`);
  
  await browser.close();
  
  return {
    course: courseName,
    screenshotCount: screenshots.length,
    students: screenshots.map(s => s.name),
    outputDir
  };
}

// 运行
captureHomework('二下(第31节)', 'C:\\Users\\Administrator\\Desktop\\二下(第31节)')
  .then(result => {
    console.log('========================================');
    console.log('  批改结果');
    console.log('========================================');
    console.log(`课程: ${result.course}`);
    console.log(`已截图: ${result.screenshotCount} 人`);
    console.log(`学生: ${result.students.join(', ')}`);
    console.log(`目录: ${result.outputDir}`);
    console.log('========================================');
  })
  .catch(console.error);


path: archived-scripts/capture-auto.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const { execSync, spawn } = require('child_process');
const os = require('os');

// 配置
const CONFIG = {
  chromePath: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
  debugPort: 9222,
  baseUrl: 'https://service.banjixiaoguanjia.com/appweb/',
  maxRetries: 3,
  timeouts: {
    navigation: 30000,
    element: 10000,
    scroll: 1500,
    click: 5000
  }
};

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// 检查 Chrome 是否已以调试模式运行
async function isChromeRunning() {
  try {
    const response = await fetch(`http://localhost:${CONFIG.debugPort}/json/version`);
    return response.ok;
  } catch {
    return false;
  }
}

// 启动 Chrome 调试模式
async function startChrome() {
  console.log('🚀 启动 Chrome 调试模式...');
  
  const tempProfile = path.join(os.tmpdir(), 'chrome-dev-profile');
  if (!fs.existsSync(tempProfile)) {
    fs.mkdirSync(tempProfile, { recursive: true });
  }
  
  // 启动 Chrome
  const chromeProcess = spawn(CONFIG.chromePath, [
    `--remote-debugging-port=${CONFIG.debugPort}`,
    `--user-data-dir=${tempProfile}`,
    CONFIG.baseUrl
  ], {
    detached: true,
    stdio: 'ignore'
  });
  
  chromeProcess.unref();
  
  // 等待 Chrome 启动
  let attempts = 0;
  while (attempts < 30) {
    if (await isChromeRunning()) {
      console.log('   ✅ Chrome 已启动\n');
      return true;
    }
    await sleep(1000);
    attempts++;
  }
  
  throw new Error('Chrome 启动超时');
}

// 连接 Chrome
async function connectBrowser() {
  console.log('1. 连接 Chrome...');
  
  // 如果 Chrome 未运行，自动启动
  if (!await isChromeRunning()) {
    await startChrome();
  }
  
  const browser = await chromium.connectOverCDP(`http://localhost:${CONFIG.debugPort}`);
  console.log('   ✅ 已连接\n');
  return browser;
}

// 获取班级小管家页面
async function getBanjixiaoguanjiaPage(browser) {
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  if (!page) {
    page = await context.newPage();
    await page.goto(CONFIG.baseUrl, { waitUntil: 'networkidle' });
  }
  
  return page;
}

// 导航到首页
async function navigateToHome(page) {
  console.log('2. 导航到首页...');
  await page.goto(CONFIG.baseUrl, { waitUntil: 'networkidle' });
  await sleep(3000);
  console.log('   ✅ 已回到首页\n');
}

// 启用辅助功能
async function enableAccessibility(page) {
  console.log('3. 启用辅助功能...');
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await sleep(2000);
  console.log('   ✅ 辅助功能已启用\n');
}

// 查找并进入课程
async function findAndEnterCourse(page, courseName) {
  console.log('4. 查找课程...');
  
  let found = false;
  for (let i = 0; i < 25; i++) {
    found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) {
          return true;
        }
      }
      return false;
    }, courseName);
    
    if (found) {
      console.log('   ✅ 找到课程');
      break;
    }
    
    // 滚动查找
    await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) {
          last = el;
        }
      }
      if (last) last.scrollIntoView({ block: 'start' });
    });
    
    await sleep(1500);
  }
  
  if (!found) {
    throw new Error(`未找到课程: ${courseName}`);
  }
  
  console.log('\n5. 进入课程...');
  
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await sleep(5000);
  console.log('   ✅ 已进入课程\n');
}

// 收集学生列表
async function collectStudents(page) {
  console.log('6. 收集学生列表...');
  const students = [];
  
  for (let scrollCount = 0; scrollCount < 30; scrollCount++) {
    const currentStudents = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      const list = [];
      
      for (const el of elements) {
        const text = el.getAttribute('aria-label') || '';
        if (text.includes('2026-') && text.includes('课内') && text.length > 50) {
          const lines = text.split('\n').filter(l => l.trim());
          if (lines.length >= 2) {
            const name = lines[0].trim();
            if (!list.find(s => s.name === name)) {
              list.push({ name, fullLabel: text });
            }
          }
        }
      }
      return list;
    });
    
    for (const s of currentStudents) {
      if (!students.find(cs => cs.name === s.name)) {
        students.push(s);
      }
    }
    
    // 滚动
    await page.evaluate(() => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) container.scrollTop += 400;
    });
    
    await sleep(1500);
    
    // 检查是否到底
    const hasMore = await page.evaluate(() => {
      const text = document.body.textContent;
      return !text.includes('没有更多了');
    });
    
    if (!hasMore && scrollCount > 5) break;
  }
  
  console.log(`   ✅ 收集到 ${students.length} 个学生\n`);
  return students;
}

// 截图学生作业
async function captureScreenshots(page, students, outputDir) {
  console.log('7. 截图所有学生作业...');
  
  for (let i = 0; i < students.length; i++) {
    const student = students[i];
    console.log(`   [${i + 1}/${students.length}] ${student.name}`);
    
    await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label') || '';
        if (label.includes(name) && label.includes('2026-')) {
          el.scrollIntoView({ block: 'center' });
          break;
        }
      }
    }, student.name);
    
    await sleep(1500);
    
    const filepath = path.join(outputDir, `${student.name}.png`);
    await page.screenshot({ path: filepath, fullPage: false });
  }
  
  console.log(`   ✅ 截图完成\n`);
}

// AI 分析作业
async function analyzeHomework(students, outputDir) {
  console.log('8. AI分析作业数量...');
  
  const [REDACTED_SECRET];
  if (!apiKey) {
    console.log('   ⚠️ 未设置 DASHSCOPE_API_KEY，跳过AI分析');
    return [];
  }
  
  const analysisResults = [];
  
  for (const student of students) {
    const screenshotPath = path.join(outputDir, `${student.name}.png`);
    
    if (!fs.existsSync(screenshotPath)) continue;
    
    console.log(`   分析: ${student.name}`);
    
    const prompt = `图片中格式：
认真完成课内课后作业，并及时提交作业。
[图片][图片][图片]...（数量不固定）
认真完成奥数课后作业，并及时提交作业。
[图片][图片][图片]...（数量不固定）

分析出不同作业类型下的缩略图的图片数量。

用简洁的中文回答，格式如下：
学生姓名：XXX
课内作业：X张
奥数作业：X张`;

    const base64 = fs.readFileSync(screenshotPath, { encoding: 'base64' });
    
    const pythonScript = `
# -*- coding: utf-8 -*-
import sys
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

from openai import OpenAI

client = OpenAI(
    [REDACTED_SECRET]",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

response = client.chat.completions.create(
    model="qwen-vl-max-latest",
    messages=[
        {
            "role": "user",
            "content": [
                {
                    "type": "image_url",
                    "image_url": {"url": "data:image/png;base64,${base64}"}
                },
                {
                    "type": "text",
                    "text": """${prompt}"""
                }
            ]
        }
    ]
)

print(response.choices[0].message.content)
`;

    const tempScript = path.join(outputDir, `_analyze_${student.name}.py`);
    fs.writeFileSync(tempScript, pythonScript, 'utf-8');
    
    try {
      const result = execSync(`python3 "${tempScript}"`, { 
        encoding: 'utf-8',
        timeout: 120000
      });
      fs.unlinkSync(tempScript);
      
      const keNeiMatch = result.match(/课内作业：(\d+)张/);
      const aoShuMatch = result.match(/奥数作业：(\d+)张/);
      
      analysisResults.push({
        student: student.name,
        keNeiCount: keNeiMatch ? parseInt(keNeiMatch[1]) : 0,
        aoShuCount: aoShuMatch ? parseInt(aoShuMatch[1]) : 0,
        rawAnalysis: result
      });
      
      console.log(`      ✅ 课内:${keNeiMatch?.[1] || 0}张, 奥数:${aoShuMatch?.[1] || 0}张`);
    } catch (error) {
      if (fs.existsSync(tempScript)) fs.unlinkSync(tempScript);
      console.log(`      ⚠️ 分析失败`);
      analysisResults.push({
        student: student.name,
        keNeiCount: 0,
        aoShuCount: 0,
        error: true
      });
    }
  }
  
  return analysisResults;
}

// 保存结果
async function saveResults(analysisResults, outputDir) {
  const analysisPath = path.join(outputDir, 'analysis-results.json');
  fs.writeFileSync(analysisPath, JSON.stringify(analysisResults, null, 2), 'utf-8');
  
  console.log('\n   ✅ AI分析完成\n');
  console.log('📊 分析结果汇总:');
  console.log('-'.repeat(40));
  for (const r of analysisResults) {
    console.log(`   ${r.student}: 课内${r.keNeiCount}张 | 奥数${r.aoShuCount}张`);
  }
  console.log('-'.repeat(40));
}

// 主流程
async function captureHomework(courseName) {
  console.log('\n' + '='.repeat(60));
  console.log(`  📚 班级小管家作业截图 - 全自动化`);
  console.log(`  课程: ${courseName}`);
  console.log('='.repeat(60) + '\n');

  const outputDir = path.join(os.homedir(), 'Desktop', courseName);
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  let browser = null;
  
  try {
    // 1. 连接浏览器（自动启动 Chrome）
    browser = await connectBrowser();
    
    // 2. 获取页面
    const page = await getBanjixiaoguanjiaPage(browser);
    
    // 3. 导航到首页
    await navigateToHome(page);
    
    // 4. 启用辅助功能
    await enableAccessibility(page);
    
    // 5. 查找并进入课程
    await findAndEnterCourse(page, courseName);
    
    // 6. 收集学生列表
    const students = await collectStudents(page);
    
    if (students.length === 0) {
      throw new Error('未找到学生');
    }
    
    // 7. 截图
    await captureScreenshots(page, students, outputDir);
    
    // 8. AI分析
    const analysisResults = await analyzeHomework(students, outputDir);
    
    // 9. 保存结果
    await saveResults(analysisResults, outputDir);
    
    console.log('\n' + '='.repeat(60));
    console.log('  ✅ 全自动化流程完成！');
    console.log(`  📁 结果保存: ${outputDir}`);
    console.log('='.repeat(60) + '\n');
    
    return {
      success: true,
      students: students.length,
      outputDir,
      analysisResults
    };
    
  } catch (error) {
    console.error('\n❌ 错误:', error.message);
    return {
      success: false,
      error: error.message
    };
  } finally {
    if (browser) {
      await browser.close();
    }
  }
}

// 命令行入口
const courseName = process.argv[2] || '二下(第36节)';
captureHomework(courseName)
  .then(result => {
    process.exit(result.success ? 0 : 1);
  })
  .catch(error => {
    console.error('致命错误:', error);
    process.exit(1);
  });

module.exports = { captureHomework };


path: archived-scripts/capture-complete.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // ========== 第一阶段：进入课程并收集学生列表 ==========
  console.log('【第一阶段】收集学生列表\n');
  
  // 1. 进入课程
  console.log('1. 进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(5000);
  console.log('   ✓ 已进入课程详情页\n');
  
  // 2. 获取"已提交"数量
  console.log('2. 获取已提交数量...');
  const submittedCount = await page.evaluate(() => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes('已提交')) {
        const match = label.match(/(\d+)/);
        if (match) return parseInt(match[0]);
      }
    }
    return null;
  });
  
  if (submittedCount) {
    console.log(`   ✓ 已提交: ${submittedCount} 人\n`);
  } else {
    console.log('   ⚠️ 未获取到已提交数量\n');
  }
  
  const targetCount = submittedCount || 3;
  
  // 3. 收集学生列表
  console.log('3. 收集学生列表...');
  const studentList = [];
  const scrollStep = 400;
  
  while (studentList.length < targetCount) {
    // 获取当前可见学生
    const visibleStudents = await page.evaluate(() => {
      const list = [];
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            if (rect.top >= -50 && rect.top < window.innerHeight) {
              list.push({ name, top: rect.top });
            }
          }
        }
      }
      return list;
    });
    
    // 记录新学生
    for (const student of visibleStudents) {
      if (!studentList.find(s => s.name === student.name)) {
        studentList.push(student);
        console.log(`   [${studentList.length}] ${student.name}`);
      }
    }
    
    // 检查是否已收集足够学生
    if (studentList.length >= targetCount) {
      console.log(`   ✓ 已收集 ${studentList.length} 个学生\n`);
      break;
    }
    
    // 向下滚动
    console.log(`   滚动查找更多学生...`);
    await page.evaluate((step) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) {
        container.scrollTop += step;
      }
    }, scrollStep);
    
    await page.waitForTimeout(1500);
  }
  
  console.log('   学生列表:');
  studentList.forEach((s, i) => console.log(`     ${i + 1}. ${s.name}`));
  console.log();
  
  // ========== 第二阶段：截图 ==========
  console.log('【第二阶段】截图\n');
  console.log('========================================');
  
  // 4. 重新进入页面（从第一个学生开始）
  console.log('4. 重新进入页面...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(5000);
  console.log('   ✓ 已重新进入课程详情页\n');
  
  // 5. 截图每个学生
  console.log('5. 截图每个学生...\n');
  
  const screenshots = [];
  
  for (let i = 0; i < studentList.length; i++) {
    const studentName = studentList[i].name;
    
    console.log(`[${i + 1}] ${studentName}`);
    
    // 截图
    const filepath = path.join(outputDir, `${studentName}.png`);
    await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
    console.log(`   ✓ 已保存: ${studentName}.png`);
    
    screenshots.push({ name: studentName, filepath });
    
    // 如果不是最后一个学生，滚动到下一个
    if (i < studentList.length - 1) {
      console.log(`   滚动: scrollTop += ${scrollStep}`);
      await page.evaluate((step) => {
        const container = document.querySelector('[style*="overflow-y: scroll"]');
        if (container) {
          container.scrollTop += step;
        }
      }, scrollStep);
      
      await page.waitForTimeout(1500);
    }
  }
  
  console.log('\n========================================');
  console.log(`\n✓ 截图完成，共 ${screenshots.length} 个学生:`);
  screenshots.forEach((s, i) => console.log(`   ${i + 1}. ${s.name}`));
  console.log(`\n输出目录: ${outputDir}\n`);
  
  await browser.close();
  
  return {
    course: courseName,
    submittedCount,
    screenshotCount: screenshots.length,
    students: screenshots.map(s => s.name),
    outputDir
  };
}

// 运行
captureHomework('二下(第36节)', 'C:\\Users\\Administrator\\Desktop\\二下(第36节)')
  .then(result => {
    console.log('========================================');
    console.log('  批改结果');
    console.log('========================================');
    console.log(`课程: ${result.course}`);
    console.log(`已提交: ${result.submittedCount} 人`);
    console.log(`已截图: ${result.screenshotCount} 人`);
    console.log(`学生: ${result.students.join(', ')}`);
    console.log(`目录: ${result.outputDir}`);
    console.log('========================================');
  })
  .catch(console.error);


path: archived-scripts/capture-final.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 1. 进入课程
  console.log('1. 进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/');
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(4000);
  console.log('   ✓ 已进入课程详情页\n');
  
  // 2. 截图每个学生
  console.log('2. 开始截图...\n');
  console.log('========================================');
  
  const screenshots = [];
  const scrollStep = 400;
  const maxScrolls = 20; // 最大滚动次数
  let scrollCount = 0;
  
  while (scrollCount < maxScrolls) {
    // 获取当前在视口顶部的学生
    const currentTopStudent = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let topStudent = null;
      let minTop = Infinity;
      
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            if (rect.top >= -50 && rect.top < minTop) {
              minTop = rect.top;
              topStudent = { name, top: rect.top.toFixed(0) };
            }
          }
        }
      }
      return topStudent;
    });
    
    if (!currentTopStudent) {
      console.log('   未找到学生，结束截图');
      break;
    }
    
    // 检查是否已经截图过这个学生
    if (screenshots.find(s => s.name === currentTopStudent.name)) {
      console.log(`\n   ${currentTopStudent.name} 已截图过，跳过`);
    } else {
      console.log(`\n[${screenshots.length + 1}] ${currentTopStudent.name}`);
      
      // 截图浏览器可见区域
      const filepath = path.join(outputDir, `${currentTopStudent.name}.png`);
      await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
      console.log(`   ✓ 已保存`);
      
      screenshots.push({ name: currentTopStudent.name, filepath });
    }
    
    // 向下滚动
    console.log(`   滚动: scrollTop += ${scrollStep}`);
    await page.evaluate((step) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) {
        container.scrollTop += step;
      }
    }, scrollStep);
    
    await page.waitForTimeout(1500);
    scrollCount++;
  }
  
  console.log('\n========================================');
  console.log(`\n✓ 截图完成，共 ${screenshots.length} 个学生:`);
  screenshots.forEach((s, i) => console.log(`   ${i + 1}. ${s.name}`));
  console.log(`\n输出目录: ${outputDir}\n`);
  
  await browser.close();
  
  return {
    course: courseName,
    screenshotCount: screenshots.length,
    students: screenshots.map(s => s.name),
    outputDir
  };
}

// 运行
captureHomework('二下(第31节)', 'C:\\Users\\Administrator\\Desktop\\二下(第31节)')
  .then(result => {
    console.log('========================================');
    console.log('  批改结果');
    console.log('========================================');
    console.log(`课程: ${result.course}`);
    console.log(`已截图: ${result.screenshotCount} 人`);
    console.log(`学生: ${result.students.join(', ')}`);
    console.log(`目录: ${result.outputDir}`);
    console.log('========================================');
  })
  .catch(console.error);


path: archived-scripts/capture-fix.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureCorrectly() {
  const courseName = '二下(第36节)';
  const outputDir = 'C:\\Users\\Administrator\\Desktop\\二下(第36节)';
  
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  console.log('当前页面URL:', page.url());
  console.log('\n开始截图...\n');
  
  // 先截图球球（第一个学生，不需要滚动）
  console.log('[1] 截图: 球球');
  await page.screenshot({ path: path.join(outputDir, '球球.png') });
  console.log('   ✓ 已保存: 球球.png');
  
  // 滚动到Zoey
  console.log('\n滚动到下一个学生...');
  await page.evaluate(() => {
    const container = document.querySelector('[style*="overflow-y: scroll"]');
    if (container) {
      container.scrollTop = 400;
    }
  });
  await page.waitForTimeout(1500);
  
  // 截图Zoey
  console.log('[2] 截图: Zoey');
  await page.screenshot({ path: path.join(outputDir, 'Zoey.png') });
  console.log('   ✓ 已保存: Zoey.png');
  
  console.log('\n========================================');
  console.log('✓ 截图完成！');
  console.log(`输出目录: ${outputDir}`);
  console.log('========================================');
  
  await browser.close();
}

captureCorrectly().catch(console.error);


path: archived-scripts/capture-fixed.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 1. 进入课程
  console.log('进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/');
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(4000);
  console.log('已进入课程详情页\n');
  
  // 2. 获取所有学生列表
  console.log('获取学生列表...');
  const allStudents = [];
  
  for (let i = 0; i < 10; i++) {
    const students = await page.evaluate(() => {
      const list = [];
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            list.push({ name });
          }
        }
      }
      return list;
    });
    
    let newCount = 0;
    for (const s of students) {
      if (!allStudents.find(existing => existing.name === s.name)) {
        allStudents.push(s);
        newCount++;
      }
    }
    
    if (newCount === 0 && i > 0) break;
    
    // 滚动到最后一个学生
    await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            last = el;
          }
        }
      }
      if (last) last.scrollIntoView({ block: 'start', behavior: 'instant' });
    });
    
    await page.waitForTimeout(1500);
  }
  
  console.log(`✓ 共 ${allStudents.length} 个学生: ${allStudents.map(s => s.name).join(', ')}\n`);
  
  // 3. 滚动回顶部
  console.log('滚动到顶部...');
  await page.evaluate(() => {
    const container = document.querySelector('[style*="overflow-y: scroll"]');
    if (container) container.scrollTop = 0;
  });
  await page.waitForTimeout(1500);
  console.log('');
  
  // 4. 截图每个学生
  console.log('========================================');
  console.log('  开始截图');
  console.log('========================================\n');
  
  for (let i = 0; i < allStudents.length; i++) {
    const student = allStudents[i];
    
    console.log(`[${i + 1}/${allStudents.length}] ${student.name}`);
    
    // 滚动到该学生
    await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.indexOf(name) === 0) {
          el.scrollIntoView({ block: 'start', behavior: 'instant' });
          break;
        }
      }
    }, student.name);
    await page.waitForTimeout(2000);
    
    // 获取当前学生位置
    const pos = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.indexOf(name) === 0) {
          const rect = el.getBoundingClientRect();
          return { top: rect.top };
        }
      }
      return null;
    }, student.name);
    
    if (!pos) {
      console.log(`  ⚠️ 未找到位置\n`);
      continue;
    }
    
    // 截图：从学生顶部开始，固定高度700px
    const clip = { 
      x: 0, 
      y: i === 0 ? -20 : Math.max(0, pos.top - 20), 
      width: 929, 
      height: 700
    };
    
    console.log(`  截图: ${student.name} (y: ${clip.y}, height: ${clip.height})`);
    
    const filepath = path.join(outputDir, `${student.name}.png`);
    await page.screenshot({ path: filepath, clip: clip });
    console.log(`  ✓ 已保存\n`);
  }
  
  console.log('========================================');
  console.log('  截图完成');
  console.log('========================================\n');
  
  await browser.close();
}

// 运行
captureHomework('二下(第31节)', 'C:\\Users\\Administrator\\Desktop\\二下(第31节)')
  .then(() => console.log('完成！'))
  .catch(console.error);


path: archived-scripts/capture-new.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 1. 进入课程
  console.log('1. 进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/', { waitUntil: 'networkidle' });
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(5000);
  console.log('   ✓ 已进入课程详情页\n');
  
  // 2. 获取"已提交"数量
  console.log('2. 获取已提交数量...');
  const submittedCount = await page.evaluate(() => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes('已提交')) {
        const match = label.match(/(\d+)/);
        if (match) return parseInt(match[0]);
      }
    }
    return null;
  });
  
  if (submittedCount) {
    console.log(`   ✓ 已提交: ${submittedCount} 人\n`);
  } else {
    console.log('   ⚠️ 未获取到已提交数量，默认截图3人\n');
  }
  
  const targetCount = submittedCount || 5;
  
  // 3. 截图每个学生
  console.log('3. 开始截图...\n');
  console.log('========================================');
  
  const screenshots = [];
  const scrollStep = 400;
  
  while (screenshots.length < targetCount) {
    // 获取当前在视口顶部的学生
    const currentTopStudent = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let topStudent = null;
      let minTop = Infinity;
      
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            if (rect.top >= -50 && rect.top < minTop) {
              minTop = rect.top;
              topStudent = { name, top: rect.top.toFixed(0) };
            }
          }
        }
      }
      return topStudent;
    });
    
    if (!currentTopStudent) {
      console.log('   未找到学生，结束截图');
      break;
    }
    
    // 【关键】每次滚动后都截图，使用序号命名避免覆盖
    // 这样确保不会遗漏任何学生的作业
    const screenshotName = `截图${screenshots.length + 1}_${currentTopStudent.name}`;
    console.log(`\n[${screenshots.length + 1}] ${screenshotName}`);
    
    // 截图浏览器可见区域
    const filepath = path.join(outputDir, `${screenshotName}.png`);
    await page.screenshot({ path: filepath, fullPage: false, timeout: 60000 });
    console.log(`   ✓ 已保存: ${screenshotName}.png`);
    
    screenshots.push({ name: screenshotName, filepath });
    
    // 检查是否已截图数量达到已提交数量
    if (screenshots.length >= targetCount) {
      console.log('\n   已截图数量达到已提交数量，结束\n');
      break;
    }
    
    // 向下滚动400
    console.log(`   滚动: scrollTop += ${scrollStep}`);
    await page.evaluate((step) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) {
        container.scrollTop += step;
      }
    }, scrollStep);
    
    await page.waitForTimeout(1500);
  }
  
  console.log('========================================');
  console.log(`\n✓ 截图完成，共 ${screenshots.length} 个学生:`);
  screenshots.forEach((s, i) => console.log(`   ${i + 1}. ${s.name}`));
  console.log(`\n输出目录: ${outputDir}\n`);
  
  await browser.close();
  
  return {
    course: courseName,
    submittedCount,
    screenshotCount: screenshots.length,
    students: screenshots.map(s => s.name),
    outputDir
  };
}

// 运行
captureHomework('二下(第31节)', 'C:\\Users\\Administrator\\Desktop\\二下(第31节)')
  .then(result => {
    console.log('========================================');
    console.log('  批改结果');
    console.log('========================================');
    console.log(`课程: ${result.course}`);
    console.log(`已提交: ${result.submittedCount} 人`);
    console.log(`已截图: ${result.screenshotCount} 人`);
    console.log(`学生: ${result.students.join(', ')}`);
    console.log(`目录: ${result.outputDir}`);
    console.log('========================================');
  })
  .catch(console.error);


path: archived-scripts/capture-scrolltop.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 1. 进入课程
  console.log('进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/');
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(4000);
  console.log('已进入课程详情页\n');
  
  // 2. 获取所有学生及其滚动位置
  console.log('获取学生列表...');
  const studentsWithPositions = [];
  let scrollTop = 0;
  const scrollStep = 100;
  const maxScrolls = 50;
  
  for (let i = 0; i < maxScrolls; i++) {
    // 设置 scrollTop
    await page.evaluate((top) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) container.scrollTop = top;
    }, scrollTop);
    await page.waitForTimeout(800);
    
    // 获取当前在视口顶部的学生
    const studentAtTop = await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let topStudent = null;
      let minTop = Infinity;
      
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            // 找到在视口内且最靠上的学生
            if (rect.top >= -50 && rect.top < minTop) {
              minTop = rect.top;
              topStudent = { name, top: rect.top };
            }
          }
        }
      }
      
      return topStudent;
    });
    
    if (studentAtTop) {
      // 检查是否已记录
      const existing = studentsWithPositions.find(s => s.name === studentAtTop.name);
      if (!existing) {
        studentsWithPositions.push({
          name: studentAtTop.name,
          scrollTop: scrollTop,
          viewTop: studentAtTop.top
        });
        console.log(`  发现: ${studentAtTop.name} (scrollTop: ${scrollTop})`);
      }
    }
    
    // 检查是否到底
    const isAtBottom = await page.evaluate(() => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) {
        return container.scrollTop + container.clientHeight >= container.scrollHeight - 10;
      }
      return false;
    });
    
    if (isAtBottom) break;
    
    scrollTop += scrollStep;
  }
  
  console.log(`✓ 共 ${studentsWithPositions.length} 个学生: ${studentsWithPositions.map(s => s.name).join(', ')}\n`);
  
  // 3. 截图每个学生
  console.log('========================================');
  console.log('  开始截图');
  console.log('========================================\n');
  
  for (let i = 0; i < studentsWithPositions.length; i++) {
    const student = studentsWithPositions[i];
    const nextStudent = studentsWithPositions[i + 1];
    
    console.log(`[${i + 1}/${studentsWithPositions.length}] ${student.name}`);
    
    // 滚动到该学生的位置
    await page.evaluate((top) => {
      const container = document.querySelector('[style*="overflow-y: scroll"]');
      if (container) container.scrollTop = top;
    }, student.scrollTop);
    await page.waitForTimeout(1500);
    
    // 获取当前学生位置
    const pos = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.indexOf(name) === 0) {
          const rect = el.getBoundingClientRect();
          return { top: rect.top };
        }
      }
      return null;
    }, student.name);
    
    if (!pos) {
      console.log(`  ⚠️ 未找到位置\n`);
      continue;
    }
    
    // 确定截图区域
    let clip;
    if (nextStudent) {
      // 滚动到下一个学生，获取其位置
      await page.evaluate((top) => {
        const container = document.querySelector('[style*="overflow-y: scroll"]');
        if (container) container.scrollTop = top;
      }, nextStudent.scrollTop);
      await page.waitForTimeout(1000);
      
      const nextPos = await page.evaluate((name) => {
        const elements = document.querySelectorAll('flt-semantics[aria-label]');
        for (const el of elements) {
          const label = el.getAttribute('aria-label');
          if (label && label.indexOf(name) === 0) {
            const rect = el.getBoundingClientRect();
            return { top: rect.top };
          }
        }
        return null;
      }, nextStudent.name);
      
      // 滚动回当前学生
      await page.evaluate((top) => {
        const container = document.querySelector('[style*="overflow-y: scroll"]');
        if (container) container.scrollTop = top;
      }, student.scrollTop);
      await page.waitForTimeout(1000);
      
      if (nextPos) {
        const height = nextPos.top - pos.top;
        clip = { 
          x: 0, 
          y: i === 0 ? -20 : pos.top, 
          width: 929, 
          height: i === 0 ? height + 20 : Math.max(100, height)
        };
        console.log(`  截图: ${student.name} → ${nextStudent.name} (${height.toFixed(0)}px)`);
      }
    }
    
    if (!clip) {
      clip = { 
        x: 0, 
        y: i === 0 ? -20 : pos.top, 
        width: 929, 
        height: i === 0 ? 820 : 800
      };
      console.log(`  截图: ${student.name} → 底部`);
    }
    
    const filepath = path.join(outputDir, `${student.name}.png`);
    await page.screenshot({ path: filepath, clip: clip });
    console.log(`  ✓ 已保存\n`);
  }
  
  console.log('========================================');
  console.log('  截图完成');
  console.log('========================================\n');
  
  await browser.close();
}

// 运行
captureHomework('二下(第31节)', 'C:\\Users\\Administrator\\Desktop\\二下(第31节)')
  .then(() => console.log('完成！'))
  .catch(console.error);


path: archived-scripts/capture-simple.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureHomework(courseName, outputDir) {
  console.log('========================================');
  console.log(`  作业截图 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 1. 进入课程
  console.log('进入课程...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/');
  await page.waitForTimeout(3000);
  
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 查找课程
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(4000);
  console.log('已进入课程详情页\n');
  
  // 2. 获取学生列表（滚动加载所有）
  console.log('获取学生列表...');
  const allStudents = new Map();
  
  for (let i = 0; i < 5; i++) {
    const students = await page.evaluate(() => {
      const list = [];
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            list.push({ name, top: rect.top });
          }
        }
      }
      return list;
    });
    
    let newCount = 0;
    for (const s of students) {
      if (!allStudents.has(s.name)) {
        allStudents.set(s.name, s);
        newCount++;
      }
    }
    
    if (newCount === 0 && i > 0) break;
    
    // 滚动到最后一个学生
    await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            last = el;
          }
        }
      }
      if (last) last.scrollIntoView({ block: 'start', behavior: 'instant' });
    });
    
    await page.waitForTimeout(1500);
  }
  
  const students = Array.from(allStudents.values());
  console.log(`✓ 共 ${students.length} 个学生: ${students.map(s => s.name).join(', ')}`);
  
  // 【关键】滚动回顶部，确保第一个学生在 DOM 中
  console.log('滚动到顶部...');
  await page.evaluate(() => {
    const container = document.querySelector('[style*="overflow-y: scroll"]');
    if (container) container.scrollTop = 0;
  });
  await page.waitForTimeout(1500);
  console.log('');
  
  // 3. 截图每个学生
  console.log('========================================');
  console.log('  开始截图');
  console.log('========================================\n');
  
  for (let i = 0; i < students.length; i++) {
    const student = students[i];
    const nextStudent = (i < students.length - 1) ? students[i + 1] : null;
    
    console.log(`[${i + 1}/${students.length}] ${student.name}`);
    
    // 第一个学生默认已在视图中，其他学生需要滚动
    if (i > 0) {
      await page.evaluate((name) => {
        const elements = document.querySelectorAll('flt-semantics[aria-label]');
        for (const el of elements) {
          const label = el.getAttribute('aria-label');
          if (label && label.indexOf(name) === 0) {
            el.scrollIntoView({ block: 'start', behavior: 'instant' });
            break;
          }
        }
      }, student.name);
      await page.waitForTimeout(1500);
    }
    
    // 获取当前位置
    const pos = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.indexOf(name) === 0) {
          const rect = el.getBoundingClientRect();
          return { top: rect.top };
        }
      }
      return null;
    }, student.name);
    
    if (!pos) {
      console.log(`  ⚠️ 未找到位置\n`);
      continue;
    }
    
    // 确定截图区域
    let clip;
    if (nextStudent) {
      const nextPos = await page.evaluate((name) => {
        const elements = document.querySelectorAll('flt-semantics[aria-label]');
        for (const el of elements) {
          const label = el.getAttribute('aria-label');
          if (label && label.indexOf(name) === 0) {
            const rect = el.getBoundingClientRect();
            return { top: rect.top };
          }
        }
        return null;
      }, nextStudent.name);
      
      if (nextPos) {
        const height = nextPos.top - pos.top;
        clip = { x: 0, y: pos.top, width: 929, height: Math.max(100, height) };
        console.log(`  截图: ${student.name} → ${nextStudent.name} (${height.toFixed(0)}px)`);
      }
    }
    
    if (!clip) {
      clip = { x: 0, y: pos.top, width: 929, height: 800 };
      console.log(`  截图: ${student.name} → 底部 (800px)`);
    }
    
    // 第一个学生从更上面开始
    if (i === 0) {
      clip.y = -20;
      clip.height += 20;
      console.log(`  调整: 第一个学生从 y=-20 开始`);
    }
    
    const filepath = path.join(outputDir, `${student.name}.png`);
    await page.screenshot({ path: filepath, clip: clip });
    console.log(`  ✓ 已保存\n`);
  }
  
  console.log('========================================');
  console.log('  截图完成');
  console.log('========================================\n');
  
  await browser.close();
}

// 运行
captureHomework('二下(第31节)', 'C:\\Users\\Administrator\\Desktop\\二下(第31节)')
  .then(() => console.log('完成！'))
  .catch(console.error);


path: archived-scripts/capture-v2.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureCourse(courseName, outputDir) {
  console.log('========================================');
  console.log(`  截图工具 - ${courseName}`);
  console.log('========================================\n');
  
  // 清理目录
  if (fs.existsSync(outputDir)) {
    fs.rmSync(outputDir, { recursive: true });
  }
  fs.mkdirSync(outputDir, { recursive: true });
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  if (!page) {
    console.log('未找到班级小管家页面');
    await browser.close();
    return;
  }
  
  // 1. 导航到主页
  console.log('导航到主页...');
  await page.goto('https://service.banjixiaoguanjia.com/appweb/');
  await page.waitForTimeout(3000);
  
  // 启用辅助功能
  await page.evaluate(() => {
    const el = document.querySelector('flt-semantics-placeholder');
    if (el) el.click();
  });
  await page.waitForTimeout(2000);
  
  // 2. 查找并进入课程
  console.log(`查找课程: ${courseName}`);
  for (let i = 0; i < 15; i++) {
    const found = await page.evaluate((name) => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.includes(name)) return true;
      }
      // 滚动加载更多
      const all = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of all) {
        const label = el.getAttribute('aria-label');
        if (label && (label.includes('第') || label.includes('年级'))) last = el;
      }
      if (last) last.scrollIntoView({ block: 'start' });
      return false;
    }, courseName);
    
    if (found) break;
    await page.waitForTimeout(1500);
  }
  
  // 点击进入
  await page.evaluate((name) => {
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.includes(name)) {
        el.dispatchEvent(new MouseEvent('click', { bubbles: true }));
        break;
      }
    }
  }, courseName);
  
  await page.waitForTimeout(4000);
  console.log('已进入课程详情页\n');
  
  // 3. 滚动加载所有学生
  console.log('加载学生列表...');
  const allStudents = new Map();
  
  for (let i = 0; i < 15; i++) {
    const students = await page.evaluate(() => {
      const list = [];
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            const rect = el.getBoundingClientRect();
            list.push({ name, top: rect.top });
          }
        }
      }
      return list;
    });
    
    let newCount = 0;
    for (const s of students) {
      if (!allStudents.has(s.name)) {
        allStudents.set(s.name, s);
        newCount++;
      }
    }
    
    if (newCount === 0 && i > 0) break;
    
    await page.evaluate(() => {
      const elements = document.querySelectorAll('flt-semantics[aria-label]');
      let last = null;
      for (const el of elements) {
        const label = el.getAttribute('aria-label');
        if (label && label.length > 20) {
          const lines = label.split(/\r?\n/);
          const name = lines[0].trim();
          if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
            last = el;
          }
        }
      }
      if (last) last.scrollIntoView({ block: 'start', behavior: 'instant' });
    });
    
    await page.waitForTimeout(1500);
  }
  
  const studentList = Array.from(allStudents.values());
  console.log(`✓ 共 ${studentList.length} 个学生: ${studentList.map(s => s.name).join(', ')}\n`);
  
  // 4. 截图前先滚动到顶部，确保所有学生都在 DOM 中
  console.log('滚动到页面顶部...');
  await page.evaluate(() => {
    // 【关键】使用 scrollTop = 0 滚动到顶部（scrollIntoView 在 Flutter 页面无效）
    const container = document.querySelector('[style*="overflow-y: scroll"]');
    if (container) {
      container.scrollTop = 0;
    }
    window.scrollTo(0, 0);
  });
  await page.waitForTimeout(2000);
  
  // 5. 截图每个学生
  console.log('========================================');
  console.log('  开始截图');
  console.log('========================================\n');
  
  // 【关键】使用滚动搜索来找到每个学生
  // 因为 scrollIntoView 在 Flutter 页面无效，我们需要通过滚动来定位学生
  
  for (let i = 0; i < studentList.length; i++) {
    const student = studentList[i];
    const nextStudent = (i < studentList.length - 1) ? studentList[i + 1] : null;
    
    console.log(`[${i + 1}/${studentList.length}] ${student.name}`);
    
    // 滚动搜索该学生
    let found = false;
    let scrollAttempts = 0;
    const maxScrollAttempts = 20;
    
    while (!found && scrollAttempts < maxScrollAttempts) {
      // 检查当前是否在视图中
      const checkResult = await page.evaluate((name) => {
        const elements = document.querySelectorAll('flt-semantics[aria-label]');
        for (const el of elements) {
          const label = el.getAttribute('aria-label');
          if (label && label.indexOf(name) === 0) {
            const rect = el.getBoundingClientRect();
            // 如果在视口顶部附近，认为找到了
            if (rect.top >= 0 && rect.top < 200) {
              return { found: true, top: rect.top, bottom: rect.bottom };
            }
          }
        }
        return { found: false };
      }, student.name);
      
      if (checkResult.found) {
        found = true;
        console.log(`  找到: top=${checkResult.top.toFixed(0)}`);
        
        // 确定截图区域
        let clip;
        if (nextStudent) {
          const nextPos = await page.evaluate((name) => {
            const elements = document.querySelectorAll('flt-semantics[aria-label]');
            for (const el of elements) {
              const label = el.getAttribute('aria-label');
              if (label && label.indexOf(name) === 0) {
                const rect = el.getBoundingClientRect();
                return { top: rect.top };
              }
            }
            return null;
          }, nextStudent.name);
          
          if (nextPos) {
            const height = nextPos.top - checkResult.top;
            clip = { x: 0, y: Math.max(0, checkResult.top), width: 929, height: Math.max(100, height) };
            console.log(`  截图: ${student.name} → ${nextStudent.name} (${height.toFixed(0)}px)`);
          }
        }
        
        if (!clip) {
          // 最后一个学生：截图到底部
          clip = { x: 0, y: Math.max(0, checkResult.top), width: 929, height: 800 };
          console.log(`  截图: ${student.name} → 底部 (800px)`);
        }
        
        // 【关键】第一个学生的截图从更上面开始（-20），确保包含完整内容
        if (i === 0) {
          clip.y = -20;
          clip.height = clip.height + 20;
          console.log(`  调整: 第一个学生从 y=-20 开始`);
        }
        
        const filepath = path.join(outputDir, `${student.name}.png`);
        await page.screenshot({ path: filepath, clip: clip });
        console.log(`  ✓ 已保存: ${path.basename(filepath)}\n`);
        break;
      }
      
      // 未找到，滚动查找
      scrollAttempts++;
      
      if (scrollAttempts === 1 && i === 0) {
        // 第一个学生，先滚动到顶部
        await page.evaluate(() => {
          const container = document.querySelector('[style*="overflow-y: scroll"]');
          if (container) container.scrollTop = 0;
        });
      } else {
        // 滚动到最后一个可见学生
        await page.evaluate(() => {
          const elements = document.querySelectorAll('flt-semantics[aria-label]');
          let last = null;
          for (const el of elements) {
            const label = el.getAttribute('aria-label');
            if (label && label.length > 20) {
              const lines = label.split(/\r?\n/);
              const name = lines[0].trim();
              if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
                const rect = el.getBoundingClientRect();
                if (rect.top > 0) {
                  last = el;
                }
              }
            }
          }
          if (last) last.scrollIntoView({ block: 'start', behavior: 'instant' });
        });
      }
      
      await page.waitForTimeout(1000);
    }
    
    if (!found) {
      console.log(`  ⚠️ 未找到 ${student.name}\n`);
    }
  }
  
  console.log('\n========================================');
  console.log('  截图完成');
  console.log('========================================\n');
  
  await browser.close();
  
  return {
    course: courseName,
    studentCount: studentList.length,
    students: studentList.map(s => s.name),
    outputDir
  };
}

// 运行
const courseName = '二下(第31节)';
const outputDir = `C:\\Users\\Administrator\\Desktop\\${courseName}`;

captureCourse(courseName, outputDir)
  .then(result => {
    console.log('批改结果:');
    console.log(`  课程: ${result.course}`);
    console.log(`  学生: ${result.studentCount}`);
    console.log(`  列表: ${result.students.join(', ')}`);
    console.log(`  目录: ${result.outputDir}`);
  })
  .catch(console.error);


path: archived-scripts/capture-visible.js
content: const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');

async function captureVisibleArea() {
  console.log('========================================');
  console.log('  截图浏览器可见区域');
  console.log('========================================\n');
  
  const outputDir = 'C:\\Users\\Administrator\\Desktop\\二下(第31节)';
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  const browser = await chromium.connectOverCDP('http://localhost:9222');
  const context = browser.contexts()[0] || await browser.newContext();
  const pages = context.pages();
  let page = pages.find(p => p.url().includes('banjixiaoguanjia'));
  
  // 获取当前可见学生
  const visibleStudents = await page.evaluate(() => {
    const list = [];
    const elements = document.querySelectorAll('flt-semantics[aria-label]');
    for (const el of elements) {
      const label = el.getAttribute('aria-label');
      if (label && label.length > 20) {
        const lines = label.split(/\r?\n/);
        const name = lines[0].trim();
        if (name && name.length < 15 && !name.includes('详情') && !name.includes('查看')) {
          const rect = el.getBoundingClientRect();
          if (rect.top >= -50 && rect.top < window.innerHeight) {
            list.push({ name, top: rect.top.toFixed(0) });
          }
        }
      }
    }
    return list;
  });
  
  console.log('当前可见学生:');
  visibleStudents.forEach((s, i) => {
    console.log(`  [${i}] ${s.name} (top: ${s.top})`);
  });
  
  // 截图浏览器可见区域（全屏截图）
  const filepath = path.join(outputDir, '截图1-可见区域.png');
  await page.screenshot({ path: filepath, fullPage: false });
  console.log(`\n✓ 已保存: ${filepath}`);
  console.log('  截图范围: 浏览器可见区域');
  
  await browser.close();
  console.log('\n完成！');
}

captureVisibleArea().catch(console.error);


path: archived-scripts/fix_docx.py
content: # -*- coding: utf-8 -*-
from docx import Document
from docx.enum.text import WD_ALIGN_PARAGRAPH

# 读取 txt 文件
with open(r'C:\Users\Administrator\Desktop\一下(第9节)\一下(第9节)小米课内分析.txt', 'r', encoding='utf-8') as f:
    content = f.read()

# 创建 Word 文档
doc = Document()

# 添加标题
title = doc.add_heading('一下(第9节) 小米 课内作业分析报告', 0)
title.alignment = WD_ALIGN_PARAGRAPH.CENTER

# 添加内容
for line in content.split('\n'):
    if line.strip():
        doc.add_paragraph(line)

# 保存
doc.save(r'C:\Users\Administrator\Desktop\一下(第9节)\一下(第9节)小米课内分析.docx')
print('Word文档已保存')


path: package.json
content: {
  "name": "banjixiaoguanjia-capture",
  "version": "1.0.0",
  "description": "班级小管家作业截图工具 - 自动化获取学生作业图片",
  "main": "index.js",
  "bin": {
    "bjxgj-capture": "./capture.js"
  },
  "scripts": {
    "capture": "node capture.js",
    "test": "node test.js"
  },
  "keywords": [
    "banjixiaoguanjia",
    "homework",
    "screenshot",
    "playwright",
    "education"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "playwright": "^1.40.0"
  }
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: stale | SkillSpector: suspicious (CRITICAL)