From 0ce8f58adbb18f7dc9aa63faa84fd6f840aaef9d Mon Sep 17 00:00:00 2001 From: wangziqi Date: Thu, 23 Jul 2026 01:26:41 +0000 Subject: [PATCH] chore: remove dead code and tune frontend chunks Remove unused production symbols, starter assets, and stale browser tooling; reduce frontend chunk fragmentation. --- apps/admin/src/App.css | 182 ------------------ apps/admin/src/assets/hero.png | Bin 13057 -> 0 bytes apps/admin/src/assets/react.svg | 1 - apps/admin/src/assets/vite.svg | 1 - .../classroom-fields.integration.test.ts | 16 -- .../src/pages/Classrooms/classroom-fields.ts | 9 - apps/admin/vite.config.ts | 4 +- .../attendance/attendance.controller.spec.ts | 12 +- .../src/attendance/dto/dingtalk-import.dto.ts | 5 - apps/server/src/bills/dto/bill.dto.ts | 9 +- .../src/classrooms/classroom-template.spec.ts | 10 +- .../src/classrooms/classroom-template.ts | 2 - .../entities/jinshuju-match-rule.entity.ts | 5 - .../src/integration/dingtalk.service.ts | 5 - apps/server/src/main.ts | 1 - scripts/browser-use-tests.py | 182 ------------------ 16 files changed, 17 insertions(+), 427 deletions(-) delete mode 100644 apps/admin/src/App.css delete mode 100644 apps/admin/src/assets/hero.png delete mode 100644 apps/admin/src/assets/react.svg delete mode 100644 apps/admin/src/assets/vite.svg delete mode 100644 apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts delete mode 100644 apps/admin/src/pages/Classrooms/classroom-fields.ts delete mode 100644 scripts/browser-use-tests.py diff --git a/apps/admin/src/App.css b/apps/admin/src/App.css deleted file mode 100644 index d6c7c9a..0000000 --- a/apps/admin/src/App.css +++ /dev/null @@ -1,182 +0,0 @@ -.counter { - font-size: 16px; - padding: 5px 10px; - border-radius: 5px; - color: var(--accent); - background: var(--accent-bg); - border: 2px solid transparent; - transition: border-color 0.3s; - margin-bottom: 24px; - - &:hover { - border-color: var(--accent-border); - } - &:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; - } -} - -.hero { - position: relative; - - .base, - .framework, - .vite { - inset-inline: 0; - margin: 0 auto; - } - - .base { - width: 170px; - position: relative; - z-index: 0; - } - - .framework, - .vite { - position: absolute; - } - - .framework { - z-index: 1; - top: 34px; - height: 28px; - transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) scale(1.4); - } - - .vite { - z-index: 0; - top: 107px; - height: 26px; - width: auto; - transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) scale(0.8); - } -} - -#center { - display: flex; - flex-direction: column; - gap: 25px; - place-content: center; - place-items: center; - flex-grow: 1; - - @media (max-width: 1024px) { - padding: 32px 20px 24px; - gap: 18px; - } -} - -#next-steps { - display: flex; - border-top: 1px solid var(--border); - text-align: left; - - & > div { - flex: 1 1 0; - padding: 32px; - @media (max-width: 1024px) { - padding: 24px 20px; - } - } - - .icon { - margin-bottom: 16px; - width: 22px; - height: 22px; - } - - @media (max-width: 1024px) { - flex-direction: column; - text-align: center; - } -} - -#docs { - border-right: 1px solid var(--border); - - @media (max-width: 1024px) { - border-right: none; - border-bottom: 1px solid var(--border); - } -} - -#next-steps ul { - list-style: none; - padding: 0; - display: flex; - gap: 8px; - margin: 32px 0 0; - - .logo { - height: 18px; - } - - a { - color: var(--text-h); - font-size: 16px; - border-radius: 6px; - background: var(--social-bg); - display: flex; - padding: 6px 12px; - align-items: center; - gap: 8px; - text-decoration: none; - transition: box-shadow 0.3s; - - &:hover { - box-shadow: var(--shadow); - } - .button-icon { - height: 18px; - width: 18px; - } - } - - @media (max-width: 1024px) { - margin-top: 20px; - flex-wrap: wrap; - justify-content: center; - - li { - flex: 1 1 calc(50% - 8px); - } - - a { - width: 100%; - justify-content: center; - box-sizing: border-box; - } - } -} - -#spacer { - height: 88px; - border-top: 1px solid var(--border); - @media (max-width: 1024px) { - height: 48px; - } -} - -.ticks { - position: relative; - width: 100%; - - &::before, - &::after { - content: ''; - position: absolute; - top: -4.5px; - border: 5px solid transparent; - } - - &::before { - left: 0; - border-left-color: var(--border); - } - &::after { - right: 0; - border-right-color: var(--border); - } -} diff --git a/apps/admin/src/assets/hero.png b/apps/admin/src/assets/hero.png deleted file mode 100644 index 02251f4b956c55af2d76fd0788124d7eee2b45eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13057 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 P00000NkvXXu0mjft=yBf diff --git a/apps/admin/src/assets/react.svg b/apps/admin/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/apps/admin/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/admin/src/assets/vite.svg b/apps/admin/src/assets/vite.svg deleted file mode 100644 index 5101b67..0000000 --- a/apps/admin/src/assets/vite.svg +++ /dev/null @@ -1 +0,0 @@ -Vite diff --git a/apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts b/apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts deleted file mode 100644 index f3843e5..0000000 --- a/apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { CLASSROOM_VISIBLE_FIELDS } from './classroom-fields'; - -describe('classroom visible fields', () => { - it('excludes obsolete course and supervisor metadata', () => { - expect(CLASSROOM_VISIBLE_FIELDS).toEqual([ - 'name', - 'building', - 'floor', - 'roomType', - 'capacity', - 'status', - 'notes', - ]); - }); -}); diff --git a/apps/admin/src/pages/Classrooms/classroom-fields.ts b/apps/admin/src/pages/Classrooms/classroom-fields.ts deleted file mode 100644 index 43fe469..0000000 --- a/apps/admin/src/pages/Classrooms/classroom-fields.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const CLASSROOM_VISIBLE_FIELDS = [ - 'name', - 'building', - 'floor', - 'roomType', - 'capacity', - 'status', - 'notes', -] as const; diff --git a/apps/admin/vite.config.ts b/apps/admin/vite.config.ts index c290e7c..4c60748 100644 --- a/apps/admin/vite.config.ts +++ b/apps/admin/vite.config.ts @@ -18,13 +18,13 @@ export default defineConfig({ name: 'echarts', test: /node_modules[\\/](echarts|zrender)[\\/]/, priority: 20, - maxSize: 300_000, + maxSize: 600_000, }, { name: 'antd', test: /node_modules[\\/](@ant-design|antd|rc-[^\\/]+)[\\/]/, priority: 10, - maxSize: 300_000, + maxSize: 600_000, }, ], }, diff --git a/apps/server/src/attendance/attendance.controller.spec.ts b/apps/server/src/attendance/attendance.controller.spec.ts index 83fdb1e..0f81373 100644 --- a/apps/server/src/attendance/attendance.controller.spec.ts +++ b/apps/server/src/attendance/attendance.controller.spec.ts @@ -63,7 +63,7 @@ describe('AttendanceController — DingTalk import scope', () => { attendanceService.getTeacherClassDingUserIds.mockResolvedValue(['ding-1', 'ding-2']); await controller.importFromDingTalk( - { start: '2026-07-01', end: '2026-07-02', classId: 8, autoMatch: true }, + { start: '2026-07-01', end: '2026-07-02', classId: 8 }, { user: { id: 21, username: 'teacher', permissions: [], isSuperAdmin: false } } as never, ); @@ -79,7 +79,7 @@ describe('AttendanceController — DingTalk import scope', () => { it('does not allow a teacher to supply arbitrary DingTalk user IDs', async () => { await expect( controller.importFromDingTalk( - { start: '2026-07-01', end: '2026-07-02', users: 'someone-else', autoMatch: true }, + { start: '2026-07-01', end: '2026-07-02', users: 'someone-else' }, { user: { id: 21, username: 'teacher', permissions: [], isSuperAdmin: false } } as never, ), ).rejects.toBeInstanceOf(ForbiddenException); @@ -89,7 +89,7 @@ describe('AttendanceController — DingTalk import scope', () => { it('requires teachers to select one of their classes', async () => { await expect( - controller.importFromDingTalk({ start: '2026-07-01', end: '2026-07-02', autoMatch: true }, { + controller.importFromDingTalk({ start: '2026-07-01', end: '2026-07-02' }, { user: { id: 21, username: 'teacher', permissions: [], isSuperAdmin: false }, } as never), ).rejects.toBeInstanceOf(BadRequestException); @@ -106,11 +106,11 @@ describe('AttendanceController — DingTalk import scope', () => { expect(attendanceService.getImportableClasses).toHaveBeenCalledWith(21, false); }); - it('always auto-matches class-scoped imports even if an old client sends autoMatch=false', async () => { + it('always auto-matches class-scoped imports', async () => { attendanceService.getTeacherClassDingUserIds.mockResolvedValue(['ding-1']); await controller.importFromDingTalk( - { start: '2026-07-01', end: '2026-07-02', classId: 8, autoMatch: false }, + { start: '2026-07-01', end: '2026-07-02', classId: 8 }, { user: { id: 21, username: 'teacher', permissions: [], isSuperAdmin: false } } as never, ); @@ -137,7 +137,7 @@ describe('AttendanceController — DingTalk import scope', () => { expect(attendanceService.getImportableClasses).toHaveBeenCalledWith(7, true); await controller.importFromDingTalk( - { start: '2026-07-01', end: '2026-07-02', users: 'ding-1,ding-2', autoMatch: true }, + { start: '2026-07-01', end: '2026-07-02', users: 'ding-1,ding-2' }, { user: { id: 7, diff --git a/apps/server/src/attendance/dto/dingtalk-import.dto.ts b/apps/server/src/attendance/dto/dingtalk-import.dto.ts index adaadc4..63244e7 100644 --- a/apps/server/src/attendance/dto/dingtalk-import.dto.ts +++ b/apps/server/src/attendance/dto/dingtalk-import.dto.ts @@ -27,11 +27,6 @@ export class DingTalkImportDto { @IsOptional() @IsString() users?: string; - - /** @deprecated Imports are always matched through DingTalk user mappings. */ - @IsOptional() - @Type(() => Boolean) - autoMatch?: boolean; } /** diff --git a/apps/server/src/bills/dto/bill.dto.ts b/apps/server/src/bills/dto/bill.dto.ts index 7d3d67d..55e7333 100644 --- a/apps/server/src/bills/dto/bill.dto.ts +++ b/apps/server/src/bills/dto/bill.dto.ts @@ -1,4 +1,4 @@ -import { ArrayNotEmpty, IsArray, IsIn, IsInt, IsNotEmpty, IsOptional, IsString, Matches, MaxLength } from 'class-validator'; +import { IsIn, IsNotEmpty, IsOptional, IsString, Matches, MaxLength } from 'class-validator'; export class GenerateBillsDto { @IsOptional() @@ -36,10 +36,3 @@ export class CancelBillDto { @MaxLength(300) reason: string; } - -export class BatchUpdateBillStatusDto extends UpdateBillStatusDto { - @IsArray() - @ArrayNotEmpty() - @IsInt({ each: true }) - ids: number[]; -} diff --git a/apps/server/src/classrooms/classroom-template.spec.ts b/apps/server/src/classrooms/classroom-template.spec.ts index 0444342..3ddbd24 100644 --- a/apps/server/src/classrooms/classroom-template.spec.ts +++ b/apps/server/src/classrooms/classroom-template.spec.ts @@ -1,7 +1,13 @@ -import { CLASSROOM_TEMPLATE_HEADERS } from './classroom-template'; +import { CLASSROOM_TEMPLATE_COLUMNS } from './classroom-template'; describe('classroom import template', () => { it('contains only classroom fields used by the product', () => { - expect(CLASSROOM_TEMPLATE_HEADERS).toEqual(['教室名', '楼栋', '楼层', '类型', '容量']); + expect(CLASSROOM_TEMPLATE_COLUMNS.map(({ header }) => header)).toEqual([ + '教室名', + '楼栋', + '楼层', + '类型', + '容量', + ]); }); }); diff --git a/apps/server/src/classrooms/classroom-template.ts b/apps/server/src/classrooms/classroom-template.ts index f4cc29d..5bfb004 100644 --- a/apps/server/src/classrooms/classroom-template.ts +++ b/apps/server/src/classrooms/classroom-template.ts @@ -5,5 +5,3 @@ export const CLASSROOM_TEMPLATE_COLUMNS = [ { header: '类型', key: 'roomType', width: 10 }, { header: '容量', key: 'capacity', width: 10 }, ]; - -export const CLASSROOM_TEMPLATE_HEADERS = CLASSROOM_TEMPLATE_COLUMNS.map(({ header }) => header); diff --git a/apps/server/src/entities/jinshuju-match-rule.entity.ts b/apps/server/src/entities/jinshuju-match-rule.entity.ts index bebb53a..5e01cb3 100644 --- a/apps/server/src/entities/jinshuju-match-rule.entity.ts +++ b/apps/server/src/entities/jinshuju-match-rule.entity.ts @@ -16,11 +16,6 @@ export interface JinshujuFieldMapping { studentNo?: string; } -export const DEFAULT_MAPPING: JinshujuFieldMapping = { - name: 'field_1', - phone: 'field_2', -}; - const mappingTransformer = { to(value: JinshujuFieldMapping): string { return JSON.stringify(value); diff --git a/apps/server/src/integration/dingtalk.service.ts b/apps/server/src/integration/dingtalk.service.ts index 7a39cba..cb2c3b2 100644 --- a/apps/server/src/integration/dingtalk.service.ts +++ b/apps/server/src/integration/dingtalk.service.ts @@ -915,11 +915,6 @@ export class DingTalkService { })); } - private delay(requestIndex: number): Promise { - const ms = requestIndex % 10 === 0 ? 200 : DingTalkService.MIN_INTERVAL; - return this.sleep(ms); - } - private sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 8a6ae5d..35946fd 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -1,5 +1,4 @@ import { NestFactory } from '@nestjs/core'; -import { ValidationPipe } from '@nestjs/common'; import { AppModule } from './app.module'; import { runMigrationsOnStartup } from './migration-runner'; diff --git a/scripts/browser-use-tests.py b/scripts/browser-use-tests.py deleted file mode 100644 index 9809182..0000000 --- a/scripts/browser-use-tests.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env python3 -""" -恭学基地管理系统 — 功能联动集成测试 -引擎: browser-use (本地 Playwright) + DeepSeek Chat - -用法: - export DEEPSEEK_API_KEY=sk-xxxx - # 先启动服务: npm run dev (admin=:3002, server=:3003) - pip install "browser-use>=0.2" playwright - playwright install chromium - python scripts/browser-use-tests.py - -说明: - 以下用例中的端口/菜单/字段/按钮文案均已核对当前代码库: - - 前端 admin: http://localhost:3002 (vite.config.ts) - - 后端 API : http://localhost:3003/api (main.ts setGlobalPrefix('api')) - - 默认账号 : admin / admin123 (rbac.service.ts 初始化) - 未实现的“课程管理 / 选课 / 学费应收”用例已剔除。 -""" - -import asyncio -import os -import sys -import time -from dataclasses import dataclass, field - -try: - from browser_use import Agent, BrowserProfile - from browser_use.llm import ChatDeepSeek -except ImportError as e: # pragma: no cover - print(f"❌ 缺少依赖: {e}\n 请先: pip install 'browser-use>=0.2' && playwright install chromium") - sys.exit(1) - -ADMIN = "http://localhost:3002" -API = "http://localhost:3003/api" - -# 每个用例的开场白:登录 admin 系统 -L = ( - f"打开 {ADMIN}/login ,在“用户名”输入框填 admin,在“密码”输入框填 admin123," - f"点“登录”按钮。登录成功后会跳转到“数据面板”。接着" -) - - -@dataclass -class Test: - name: str - task: str - expect: list[str] - - -TESTS: list[Test] = [ - # 1) 学生 → 入住:学生管理在“学员管理”分组;入住登记在“宿舍运营”→“入住管理” - Test( - name="创建学生→入住", - expect=["学生", "入住", "成功"], - task=( - f"{L}在左侧菜单点“学员管理”展开,点“学生管理”。点右上角“添加学生”按钮," - f"弹窗里“姓名”填“张三测试”,“学号”填“GX2026T01”,“性别”选“男”,点“保存”。" - f"若提示学号重复,改成 GX2026T02 再存。保存成功后,点左侧“宿舍运营”→“入住管理”," - f"点“入住登记”按钮,“选择学生”搜索并选“张三测试”,“选择宿舍”选任意一个未满的宿舍," - f"“入住日期”选今天,点“确认入住”。返回: 学生创建是否成功、入住登记是否成功。" - ), - ), - # 2) 宿舍费用 → 账单:费用录入在“财务管理”;账单周期是日期区间(RangePicker) - Test( - name="宿舍费用→生成账单", - expect=["费用", "账单", "成功"], - task=( - f"{L}点左侧“财务管理”→“费用录入”。默认在“宿舍费用”标签页,点“录入宿舍费用”按钮," - f"“宿舍”选任意一间,“费用类型”选“水费”,“金额”填 100,“账单周期”选本月第一天到最后一天" - f"(开始日期和结束日期都要选),点“确认录入”。然后点左侧“财务管理”→“账单管理”," - f"点右上角“生成账单”按钮,“账单周期”同样选本月起止两天,点“生成”。等待完成后查看账单列表。" - f"返回: 费用录入是否成功、账单是否生成成功、账单列表大概有几条记录。" - ), - ), - # 3) 班级创建:在“学员管理”→“班级管理”;班型只有 文化课/专业课/集训营/冲刺营;编码必填 - Test( - name="创建班级", - expect=["班级", "创建", "成功"], - task=( - f"{L}点左侧“学员管理”→“班级管理”。点右上角“创建班级”按钮," - f"“班级名称”填“测试班级-1班”,“班级编码”填“TEST-CLS-01”,“班型”选“文化课”," - f"“开班日期”选 2026-07-01,点弹窗的确定/保存按钮。" - f"返回: 班级是否创建成功,列表中能否看到“测试班级-1班”。" - ), - ), - # 4) 排课:注意——没有“新建排课”按钮,需点网格中某个空白单元格弹出新增表单 - Test( - name="课程排课", - expect=["排课", "成功"], - task=( - f"{L}点左侧“教务管理”→“排课管理”。页面是“教室 × 星期”的表格。" - f"在“周一”这一列,找任意一间教室对应的空白单元格(显示“—”)并点击它,弹出“新增排课”表单。" - f"“班级”选任意一个班级,“科目”填“语文”,“上课时段”选 08:00 到 10:00," - f"“日期范围”选 2026-07-01 到 2026-12-31,点“创建”。" - f"若提示需要先有教室或班级,请如实说明。返回: 排课是否创建成功。" - ), - ), - # 5) 仪表盘数据一致性 - Test( - name="仪表盘数据校验", - expect=["宿舍总数", "在读学生", "入住率"], - task=( - f"{L}停留在“数据面板”,等页面加载完。读出顶部这几个数字指标: " - f"“宿舍总数”、“在读学生”、“当前在住”、“入住率”。然后点“学员管理”→“学生管理”," - f"记录学生列表底部“共 N 人”的数字;再点“宿舍运营”→“入住管理”,记录“共 N 条”的数字。" - f"返回: 仪表盘的“在读学生”与学生列表条数是否一致、“当前在住”与在住入住记录数是否吻合。" - ), - ), - # 6) 权限边界:登录错误、未登录重定向、未授权 API 401 - Test( - name="权限边界", - expect=["错误", "登录", "401"], - task=( - f"做 3 个安全检查,逐条返回结果:\n" - f"1. 打开 {ADMIN}/login ,用户名 admin,密码 wrongpass,点“登录”,看是否提示错误(登录失败)。\n" - f"2. 新开标签直接访问 {ADMIN}/dashboard (不登录/清掉 token),看是否被重定向回 /login。\n" - f"3. 直接访问 {API}/students (不带登录态),看返回的内容里是否包含 401 或 Unauthorized。" - ), - ), -] - - -@dataclass -class Result: - name: str - passed: bool - hit: list[str] = field(default_factory=list) - miss: list[str] = field(default_factory=list) - out: str = "" - sec: float = 0.0 - err: str | None = None - - -async def run_one(t: Test, llm: ChatDeepSeek) -> Result: - start = time.monotonic() - try: - agent = Agent( - task=t.task, - llm=llm, - browser_profile=BrowserProfile(headless=True, window_size={"width": 1440, "height": 900}), - ) - history = await agent.run(max_steps=30) - out = history.final_result() or "" - hit = [k for k in t.expect if k in out] - miss = [k for k in t.expect if k not in out] - return Result(t.name, len(miss) == 0, hit, miss, out[:600], time.monotonic() - start) - except Exception as e: # noqa: BLE001 - return Result(t.name, False, [], t.expect, "", time.monotonic() - start, str(e)) - - -async def main() -> int: - key = os.environ.get("DEEPSEEK_API_KEY") - if not key: - print("❌ 未设置环境变量 DEEPSEEK_API_KEY") - return 1 - - bar = "=" * 64 - print(f"{bar}\n 恭学基地 集成测试 (browser-use + DeepSeek Chat)\n{bar}") - print(f" 前端 {ADMIN} 后端 {API}\n{bar}") - - llm = ChatDeepSeek(model="deepseek-chat", api_key=key) - results: list[Result] = [] - for i, t in enumerate(TESTS, 1): - print(f"\n[{i}/{len(TESTS)}] {t.name} …") - r = await run_one(t, llm) - results.append(r) - if r.passed: - print(f" ✅ 通过 ({r.sec:.0f}s)") - elif r.err: - print(f" ❌ 异常 ({r.sec:.0f}s): {r.err[:160]}") - else: - print(f" ❌ 失败 ({r.sec:.0f}s) 缺关键词: {','.join(r.miss)}\n 输出: {r.out[:180]}") - - passed = sum(1 for r in results if r.passed) - failed = len(results) - passed - print(f"\n{bar}\n ✅ {passed} / ❌ {failed} 通过率 {passed / len(results) * 100:.0f}%\n{bar}") - return 1 if failed else 0 - - -if __name__ == "__main__": - sys.exit(asyncio.run(main()))