From 6347d1e5aad0572aa52e985348c788b6b613a302 Mon Sep 17 00:00:00 2001 From: LoveChengke Date: Sun, 18 Oct 2026 17:39:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E8=AF=BE=E8=A1=A8?= =?UTF-8?q?=E6=A1=A3=E6=A1=88=E3=80=81=E8=AF=BE=E4=BB=B6=E5=BD=92=E6=A1=A3?= =?UTF-8?q?=E3=80=81=E6=8F=90=E9=86=92=E4=B8=8E=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B9=B6=E5=8F=91=E5=B8=83=201.4.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 重写课表档案工作区并合并时间表、课表与完整档案导入导出 - 修复课前/上下课提醒遮罩与通知收回冲突 - 增加课件文件夹分类、教师归档路径和客户端本机归档 - 打通客户端下载/安装/完成状态上报并在管理端展示更新过程 - 修复侧栏折叠裁切、课时多选与上传控件样式 - 统一版本到 1.4.1 --- deploy/docker-compose.sqlite.yml | 2 +- deploy/docker-compose.yml | 2 +- deploy/package.runtime.json | 2 +- docs/app/courseware.md | 13 +- docs/app/imports.md | 2 +- docs/app/schedules.md | 6 +- docs/dev/api.md | 3 + docs/dev/data-model.md | 1 + docs/management/upgrade.md | 2 + docs/screenshots/plan-editor-workspace.png | Bin 0 -> 110265 bytes package.json | 2 +- packages/desktop-client/package.json | 2 +- .../desktop-client/src/island/IslandApp.vue | 2 +- .../src/island/LessonReminderContent.vue | 22 +- .../desktop-client/src/main/auto-update.ts | 170 ++- .../desktop-client/src/main/client-agent.ts | 8 +- .../desktop-client/src/main/courseware.ts | 70 +- packages/desktop-client/src/main/ipc.ts | 13 +- packages/desktop-client/src/main/island.ts | 13 +- .../src/main/lesson-reminder.ts | 25 +- .../src/main/smoke-courseware.ts | 16 +- .../desktop-client/src/main/smoke-resident.ts | 10 +- packages/desktop-client/src/preload/index.ts | 6 +- .../src/renderer/views/CoursewareView.vue | 55 +- .../desktop-client/src/types/desktop.d.ts | 1 + packages/server/package.json | 2 +- .../migration.sql | 10 + packages/server/prisma/schema.prisma | 13 + .../scripts/apply-column-migrations.cjs | 18 + packages/server/scripts/verify-courseware.ts | 127 +- packages/server/scripts/verify-e2e.mjs | 1 + packages/server/scripts/verify-v14.ts | 21 + packages/server/src/lib/snapshot.ts | 1 + .../modules/class-plans/class-plans.module.ts | 4 + .../class-plans/profile-export.service.ts | 106 ++ .../class-plans/profile-import.service.ts | 13 + .../src/modules/clients/clients.module.ts | 10 + .../src/modules/clients/clients.schemas.ts | 19 + .../src/modules/clients/clients.service.ts | 64 + .../modules/courseware/courseware.module.ts | 28 +- .../modules/courseware/courseware.schemas.ts | 22 + .../modules/courseware/courseware.service.ts | 44 +- packages/shared/package.json | 2 +- packages/shared/src/class-plan.ts | 5 + packages/shared/src/courseware.ts | 31 + packages/shared/src/types.ts | 3 + packages/web-admin/package.json | 2 +- .../src/components/ClientUpdateStatus.vue | 70 + .../src/components/ManagementMenu.vue | 18 +- packages/web-admin/src/config/navigation.ts | 2 +- packages/web-admin/src/router/index.ts | 16 +- packages/web-admin/src/styles/index.css | 30 + packages/web-admin/src/views/ClientsView.vue | 23 +- .../web-admin/src/views/CoursewareView.vue | 154 ++- .../web-admin/src/views/PlanEditorView.vue | 1231 +++++++++++++---- scripts/ui-smoke/main.cjs | 247 +--- scripts/verify-client-agent.mjs | 42 +- website/download.html | 2 +- website/index.html | 6 +- 59 files changed, 2251 insertions(+), 584 deletions(-) create mode 100644 docs/screenshots/plan-editor-workspace.png create mode 100644 packages/server/prisma/migrations/20261018120000_courseware_folders_update_progress/migration.sql create mode 100644 packages/server/src/modules/class-plans/profile-export.service.ts create mode 100644 packages/web-admin/src/components/ClientUpdateStatus.vue diff --git a/deploy/docker-compose.sqlite.yml b/deploy/docker-compose.sqlite.yml index e9545c8..ee9956e 100644 --- a/deploy/docker-compose.sqlite.yml +++ b/deploy/docker-compose.sqlite.yml @@ -23,7 +23,7 @@ services: context: .. dockerfile: deploy/Dockerfile target: runtime - image: classhelper-server:${APP_VERSION:-1.4.0} + image: classhelper-server:${APP_VERSION:-1.4.1} restart: unless-stopped environment: NODE_ENV: production diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index b2cbd47..4aaaed0 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -60,7 +60,7 @@ services: context: .. dockerfile: deploy/Dockerfile target: runtime - image: classhelper-server:${APP_VERSION:-1.4.0} + image: classhelper-server:${APP_VERSION:-1.4.1} restart: unless-stopped environment: NODE_ENV: production diff --git a/deploy/package.runtime.json b/deploy/package.runtime.json index bfec0a2..13e4a30 100644 --- a/deploy/package.runtime.json +++ b/deploy/package.runtime.json @@ -1,6 +1,6 @@ { "name": "classhelper-server-runtime", - "version": "1.4.0", + "version": "1.4.1", "private": true, "type": "module", "description": "ClassHelper 服务端运行时(安装包 / 容器内置)", diff --git a/docs/app/courseware.md b/docs/app/courseware.md index bc73bfd..7e9f72d 100644 --- a/docs/app/courseware.md +++ b/docs/app/courseware.md @@ -6,7 +6,8 @@ 1. 选择本地文件。 2. 选择可以查看课件的班级。候选项只包含本人担任任课教师的在读班级;仅担任班主任、级长或部长不会额外获得上传到这些班级的权限。 -3. 选择「仅保存云端」或「保存后推送所选班级」,点击上传。 +3. 可新建分类文件夹,使用 `/` 分隔多层目录,例如 `地理/必修一`;不选文件夹时直接按日期归档。 +4. 选择「仅保存云端」或「保存后推送所选班级」,点击上传。 不选择班级时,课件仅自己可见。仅保存云端并指定可见班级后,这些班级可在客户端自主下载。推送模式会先把文件完整保存到服务端,再给所选班级创建下载任务;不会从教师电脑直接传到教室电脑。 @@ -14,10 +15,14 @@ 「查看与下载课件」和「上传、设置可见范围与推送自己的课件」为独立权限,可在权限组中分配。默认教师与管理员、内置教师与班主任岗位包含这两项权限;升级时自定义权限组需管理员按需授权。 +「日期归档」按教师、年、月、日和上传时间选择目录;「文件夹分类」按自建目录筛选,已有文件可随时调整分类。文件夹按教师隔离,分类本身不授予班级下载权限。 + ## 班级客户端 客户端侧栏新增「课件」,仅列出本班可见的文件,显示文件名、类型、上传教师、日期与大小。点击「下载」将课件保存到本机,点击「打开文件夹」定位已下载文件。客户端不会自动运行文件。 +「本机归档」独立列出已经接收的课件,离线时也可按教师和日期目录查找。每个服务端与班级会话有独立归档根目录,文件路径采用 `Teachers\老师名字\上传年份\上传月\上传日\上传时间\文件名`,例如 `Teachers\Wanghong\2026\10\18\15-30-42\大气环流.pptx`。目录时间取上传时间,重复推送会复用已校验的文件;同秒同名的不同课件会添加编号,避免覆盖。 + 收到教师推送后,客户端自动下载;离线时任务保留在服务端,上线后的心跳会补拉,每台设备独立回执。实时通道正常时会立即触发检查;断线时通过心跳继续补收。传输失败会显示原因,推送任务在后续心跳中重试,不会影响远控和在线状态上报。 下载期间,固定岛和自定义常驻岛都会显示文件名、文件类型、传输教师、接收字节数和进度条。下载完成后校验 SHA256,再保存正式文件并回执。断网时可以查看已缓存的课件目录与已经下载的本地文件;重新联网后同步当前可见范围。 @@ -26,11 +31,11 @@ ## 服务端归档与备份 -默认文件实体目录为 `<服务端根目录的上一级>/data/courseware/YYYY-MM-DD/<类型>/<随机编号>`,展示名称与扩展名单独保存在数据库中。可通过环境变量 `COURSEWARE_DIR` 指定文件目录。上传使用流式写入,未完成文件使用 `.part` 临时文件;失败或上传中断时清理。只有完整收到文件并完成摘要计算后才记录元数据和创建推送任务。 +默认文件实体目录为 `<服务端根目录的上一级>/data/courseware/<随机编号>/Teachers/<教师>/<年>/<月>/<日>/<时间>/<文件名>`,随机编号隔离同名教师和同秒上传,旧课件保留原存储位置。可通过环境变量 `COURSEWARE_DIR` 指定文件目录。上传使用流式写入,未完成文件使用 `.part` 临时文件;失败或上传中断时清理。只有完整收到文件并完成摘要计算后才记录元数据和创建推送任务。 -数据库迁移 `20261018000000_add_courseware` 新增课件、可见班级、推送和设备回执四张表。SQLite 安装部署按现有自动迁移流程更新;源码开发部署执行 `pnpm --filter @classhelper/server db:deploy`。MySQL 按现有换库流程基于更新后的 schema 建模。 +数据库迁移新增课件、可见班级、推送、设备回执和分类目录,并记录上传归档路径。SQLite 安装部署按现有自动迁移流程更新;源码开发部署执行 `pnpm --filter @classhelper/server db:deploy`。MySQL 按现有换库流程基于更新后的 schema 建模。 -数据库 JSON 备份包含这四张表,但**不包含二进制文件本身**。完整备份必须同时备份 `data/courseware`(自定义目录时备份对应目录)。SQLite Docker 的现有数据卷覆盖此目录;MySQL Docker 配置增加 `server-data:/app/data` 数据卷。复制到新机器时需同时恢复数据库和文件目录。 +数据库 JSON 备份包含课件元数据和分类目录,但**不包含二进制文件本身**。完整备份必须同时备份 `data/courseware`(自定义目录时备份对应目录)。SQLite Docker 的现有数据卷覆盖此目录;MySQL Docker 配置增加 `server-data:/app/data` 数据卷。复制到新机器时需同时恢复数据库和文件目录。 使用反向代理时,需允许课件上传大小和足够的读写超时。仓库的 Nginx 样例单独为 `/api/courseware/` 配置 512 MB 和 1800 秒超时,其他接口维持原有大小限制。 diff --git a/docs/app/imports.md b/docs/app/imports.md index 76c0971..8698a20 100644 --- a/docs/app/imports.md +++ b/docs/app/imports.md @@ -22,7 +22,7 @@ description: 表格预览、名单与成绩校验,以及完整离线档案导 ## 完整 ClassIsland 档案 -在“课表管理 → 导入 ClassIsland 课程表”选择或粘贴 JSON,先预览再导入。 +在“课表档案 → 导入档案”选择或粘贴 JSON,先预览再导入。 完整输入包含 TimeLayouts、ClassPlans、Subjects 字典,可包含 ClassPlanGroups 和 OrderedSchedules 等配置。 JSON 只作为离线文件导入,不依赖在线联动插件。 diff --git a/docs/app/schedules.md b/docs/app/schedules.md index 653ff4a..7890ac4 100644 --- a/docs/app/schedules.md +++ b/docs/app/schedules.md @@ -5,9 +5,13 @@ description: 日课表、共享作息、轮换、临时换课和教学周。 # 课表 -Web 管理端可查看周课表并编辑时间表与日课表,客户端提供“今天”时间轴和“本周”视图。 +Web 管理端统一从「教学事务 → 课表档案」查看周课表并编辑时间表与日课表,客户端提供“今天”时间轴和“本周”视图。 上课状态、灵动岛课程提醒和紧急通知确认共用服务端的课表判定。 +编辑工作区参照 [CsesWebEditor](https://github.com/SmartTeachCN/CsesWebEditor) 的资源列表、设置卡片、课时列表与快速选科布局实现。左侧集中切换课程表、时间表、科目、群组与预定、整周预览和文件预览。旧管理页地址 `/schedules` 自动进入同一工作区。 + +点击课时后可用右侧科目按钮快速填充;拖动课时交换课程,拖动时间点调整时间表顺序,完成后保存。时间表支持任意分钟输入;设置的课程/课间分钟数会用于新建时间点的默认起止时间。完整档案可在页头导入或导出 JSON,保留轮换、时间点类型、空课、停用、群组和预定。 + ## 日课表与时间表 时间表保存时间点:上课、课间、分割线、行动;日课表引用时间表,每节课程通过外键关联时间点。 diff --git a/docs/dev/api.md b/docs/dev/api.md index 4a065d0..0adeafb 100644 --- a/docs/dev/api.md +++ b/docs/dev/api.md @@ -44,8 +44,10 @@ API 前缀 /api,JWT 使用 Authorization: Bearer。成功/失败结构以 `lib | /imports/template、/table/preview、/table/commit | 表格导入,按 kind 再检查角色、导入权限、班级和科目 | | /imports/class-plan/preview、/imports/class-plan | 旧预览与完整档案提交;提交走关系化导入 | | /imports/time-layout[/:id]、/time-layout/preview | 时间配置兼容入口,写入/删除需 timetable:manage | +| GET /class-plans/profile?classId= | 完整档案导出,按班级范围读取,保留时间点与课表关联 | | GET /courseware、/courseware/:id、/:id/download | 课件目录/详情/下载;员工需 courseware:read,班级端按可见范围 | | POST /courseware/upload、/:id/push;PATCH /courseware/:id/visibility;DELETE /courseware/:id | 写入 courseware:write + 员工身份;本人文件、任课班级与可见范围限制 | +| GET/POST /courseware/folders;PATCH /courseware/:id/folder | 员工 courseware:write;目录与课件均限本人 | | GET /courseware/transfers;POST /courseware/transfers/:id/ack | 班级端补领、逐设备回执 | ## 设备、系统与运维 @@ -53,6 +55,7 @@ API 前缀 /api,JWT 使用 Authorization: Bearer。成功/失败结构以 `lib | 路径/动作 | 行为及边界 | | ------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | POST /clients/heartbeat、/clients/commands/:id/ack | 登录设备心跳/回执,校验 deviceKey 和归属 | +| POST /clients/update-progress | 设备上传下载、校验、等待、安装和失败状态,校验设备班级归属 | | GET /clients/devices;DELETE /clients/devices/:id | 设备读取/维护,分别检查功能键和范围 | | POST /clients/commands | remote:control + 对应范围,白名单动作 | | POST /clients/updates | client:update,安装策略 immediate/after-school/idle | diff --git a/docs/dev/data-model.md b/docs/dev/data-model.md index 04f0a61..76abaf9 100644 --- a/docs/dev/data-model.md +++ b/docs/dev/data-model.md @@ -42,6 +42,7 @@ description: 现行模型职责、迁移与快照边界。 | `ScheduledClassPlan` | 按本地日期预定日课表 | | `PromotionRun` | 年度升级幂等与审计 | | `Courseware` | 文件元数据、上传教师、存储路径与 SHA256 | +| `CoursewareFolder` | 教师自建分类目录,按上传者隔离 | | `CoursewareVisibility` | 课件可见班级 | | `CoursewarePush` | 按班级的推送任务 | | `CoursewareReceipt` | 按设备的课件接收回执 | diff --git a/docs/management/upgrade.md b/docs/management/upgrade.md index ef29086..15ebf05 100644 --- a/docs/management/upgrade.md +++ b/docs/management/upgrade.md @@ -109,6 +109,8 @@ docker compose up -d ### 客户端(ClassHelper 班级端) +Web「客户端」可缓存安装包并推送目标版本,客户端从本服务下载并校验 SHA256,按立即、放学后或空闲策略安装。管理页显示下载字节与百分比、校验中、等待安装、安装中和失败原因。安装前先上报「正在更新,将会短暂离线」,新版上线后的心跳确认「已完成更新,版本 xxxx」;仅离线不会被判为更新成功。关闭自动更新的客户端会显示原因。 + 从 Releases 取新的 `setup.exe` 覆盖安装即可,配置在 `%APPDATA%`,不受影响。 > 如果你是在**自己维护的机器**上只替换了 `releases/` 里的产物、而没有同步到安装目录, diff --git a/docs/screenshots/plan-editor-workspace.png b/docs/screenshots/plan-editor-workspace.png new file mode 100644 index 0000000000000000000000000000000000000000..d169aadb4b9f13666493a2278e33e2760c7bb276 GIT binary patch literal 110265 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 c2PGmz+bL$R6k-oB4k00g)tTduEiT6V4M?M4 ({ mode.value === 'hidden' && !appearance.value.idleSliver && !state.value.lessonReminder, 'ci-lesson-card': !!state.value.lessonReminder, 'message-surface': !!state.value.active && mode.value !== 'hidden' && !state.value.lessonReminder, - sliver: mode.value === 'hidden' && appearance.value.idleSliver, + sliver: mode.value === 'hidden' && appearance.value.idleSliver && !state.value.lessonReminder, urgent: isUrgent.value, call: isCall.value, homework: isHomework.value, diff --git a/packages/desktop-client/src/island/LessonReminderContent.vue b/packages/desktop-client/src/island/LessonReminderContent.vue index 6c7247e..65ac88c 100644 --- a/packages/desktop-client/src/island/LessonReminderContent.vue +++ b/packages/desktop-client/src/island/LessonReminderContent.vue @@ -58,6 +58,7 @@ const time = computed(() => ); const regions = ref([0, 0, 0, 0, 0]); let frame = 0; +let fallback: ReturnType | undefined; // ClassIsland SlantedMaskControl:5 个斜切区域、30°偏移、两侧向中央打开,中央向两侧关闭。 const polygons = computed(() => { const offset = Math.tan(Math.PI / 6) * 40; @@ -71,12 +72,21 @@ const polygons = computed(() => { }); function animateMask() { cancelAnimationFrame(frame); + clearTimeout(fallback); const target = mask.value ? 1 : 0; if (!props.motion) { regions.value = [target, target, target, target, target]; return; } const from = [...regions.value]; + // 隐藏窗口恢复或系统合成暂停时 rAF 可能停住,遮罩仍须在有限时间内成为可见实体。 + fallback = setTimeout( + () => { + cancelAnimationFrame(frame); + regions.value = [target, target, target, target, target]; + }, + 600 / Math.max(0.25, props.appearance.speed), + ); const started = performance.now(); const delays = mask.value ? [0, 90, 120, 90, 0] : [120, 90, 0, 90, 120]; const update = (at: number) => { @@ -109,6 +119,7 @@ onMounted(() => { onUnmounted(() => { observer?.disconnect(); cancelAnimationFrame(frame); + clearTimeout(fallback); }); @@ -137,7 +148,7 @@ onUnmounted(() => { {{ leftSeconds }} 秒 - 课间休息 + {{ reminder.kind === 'on-class' ? '开始上课' : '课间休息' }} {{ @@ -146,8 +157,8 @@ onUnmounted(() => { : '下节课是:' }} - {{ course }} - {{ time }} + {{ course || '今日课程已结束' }} + {{ time }}
{ display: flex; align-items: center; justify-content: center; - gap: 24px; - padding: 0 28px; + gap: 12px; + padding: 0 14px; white-space: nowrap; transition: opacity var(--ci-duration) ease-in; } @@ -222,6 +233,7 @@ onUnmounted(() => { text-overflow: ellipsis; } .ci-lesson-course small { + flex-shrink: 0; font-size: 14px; font-variant-numeric: tabular-nums; } diff --git a/packages/desktop-client/src/main/auto-update.ts b/packages/desktop-client/src/main/auto-update.ts index 2527c2f..37f62c2 100644 --- a/packages/desktop-client/src/main/auto-update.ts +++ b/packages/desktop-client/src/main/auto-update.ts @@ -14,6 +14,26 @@ import { import { getConfig, saveConfig } from './config.js'; import { island } from './island.js'; import { logger } from './logger.js'; +import { connectionEvents } from './config-events.js'; + +let updateEpoch = 0; +let downloadAbort = new AbortController(); +connectionEvents.on('changed', () => { + updateEpoch++; + downloadAbort.abort(); + downloadAbort = new AbortController(); + policy = null; + installationRetryAt = 0; + installResultChecked = false; + updateProgress = { + version: '', + stage: 'idle', + downloadedBytes: 0, + totalBytes: 0, + percent: null, + error: '', + }; +}); /** * 服务端统一下发的自动更新(下载 + 空闲时静默安装)。 @@ -28,15 +48,24 @@ import { logger } from './logger.js'; * 否则会与"安装器正在覆盖自己"撞车 —— Windows 上正在运行的程序文件是删不掉的)。 */ -let policy: Pick | null = - null; +let policy: + | (Pick & { + serverUrl: string; + username: string; + }) + | null = null; function policyPath(): string { return path.join(pendingUpdateDir(), 'policy.json'); } function loadPolicy(): typeof policy { - if (policy) return policy; + if (policy) + return policy.serverUrl === getConfig().serverUrl && policy.username === getConfig().username + ? policy + : null; try { policy = JSON.parse(fs.readFileSync(policyPath(), 'utf8')); + if (policy?.serverUrl !== getConfig().serverUrl || policy?.username !== getConfig().username) + policy = null; } catch { /* 尚无下发策略 */ } @@ -58,7 +87,23 @@ function removeManagedInstaller(file: string): void { /** 每次成功心跳先同步策略;取消或更换目标后旧包不得自行安装。 */ export function syncUpdatePolicy(next: ClientHeartbeatResponse): void { + if (policy && policy.updateTargetVersion !== next.updateTargetVersion) { + installationRetryAt = 0; + updateEpoch++; + downloadAbort.abort(); + downloadAbort = new AbortController(); + updateProgress = { + version: next.updateTargetVersion || '', + stage: 'idle', + downloadedBytes: 0, + totalBytes: 0, + percent: null, + error: '', + }; + } policy = { + serverUrl: getConfig().serverUrl, + username: getConfig().username, updateTargetVersion: next.updateTargetVersion, updateMode: next.updateMode ?? 'idle', updateNotBefore: next.updateNotBefore ?? null, @@ -86,6 +131,8 @@ export function pendingUpdateDir(): string { let mainWindowHiddenAt: number | null = null; /** 本进程是否已经发起过安装(防止心跳与 hide 事件重复触发) */ let installing = false; +let installationRetryAt = 0; +let installResultChecked = false; let updateProgress: ContractReleaseProgressDto = { version: '', stage: 'idle', @@ -95,12 +142,69 @@ let updateProgress: ContractReleaseProgressDto = { error: '', }; export function getUpdateProgress() { + if (!installResultChecked) { + installResultChecked = true; + const resultFile = path.join(pendingUpdateDir(), 'install-result.json'); + try { + const result = JSON.parse(fs.readFileSync(resultFile, 'utf8').replace(/^\uFEFF/, '')); + if ( + result.serverUrl === getConfig().serverUrl && + result.username === getConfig().username && + typeof result.version === 'string' && + result.version === getConfig().pendingUpdateVersion && + isNewerVersion(app.getVersion(), result.version) && + typeof result.error === 'string' && + result.error + ) { + updateProgress = { + ...updateProgress, + version: result.version, + stage: 'failed', + error: result.error.slice(0, 1000), + }; + installationRetryAt = Date.now() + 5 * 60_000; + fs.rmSync(resultFile, { force: true }); + } + } catch { + /* 尚未执行安装助手,或上一次已成功。 */ + } + } return { ...updateProgress }; } let progressPublishedAt = 0; +let reportAt = 0; +let reporting: Promise = Promise.resolve(true); +/** 与心跳分离,按顺序上报;安装前等待该队列完成再退出。 */ +function reportProgress(): Promise { + const config = getConfig(), + snapshot = getUpdateProgress(), + epoch = updateEpoch; + if (!config.token || process.env.ELECTRON_SMOKE_TEST === '1') return Promise.resolve(true); + reporting = reporting + .catch(() => false) + .then(async () => { + if (epoch !== updateEpoch) return false; + try { + const response = await fetch(`${config.serverUrl}/api/clients/update-progress`, { + method: 'POST', + headers: { authorization: `Bearer ${config.token}`, 'content-type': 'application/json' }, + body: JSON.stringify({ deviceKey: config.deviceKey, progress: snapshot }), + signal: AbortSignal.timeout(5000), + redirect: 'error', + }); + if (!response.ok) logger.warn(`更新进度上报失败:HTTP ${response.status}`); + return response.ok; + } catch (error) { + logger.warn(`更新进度上报失败:${String(error)}`); + return false; + } + }); + return reporting; +} function publishProgress(patch: Partial) { const previousStage = updateProgress.stage; updateProgress = { ...updateProgress, ...patch }; + updateProgress.error = updateProgress.error.slice(0, 1000); updateProgress.percent = updateProgress.totalBytes ? Math.min(100, Math.round((updateProgress.downloadedBytes / updateProgress.totalBytes) * 100)) : null; @@ -108,6 +212,10 @@ function publishProgress(patch: Partial) { progressPublishedAt = Date.now(); for (const win of BrowserWindow.getAllWindows()) if (!win.isDestroyed()) win.webContents.send('client:update-progress', getUpdateProgress()); + if (previousStage !== updateProgress.stage || Date.now() - reportAt >= 2000) { + reportAt = Date.now(); + void reportProgress(); + } } /** * 退出本进程的回调(由 index.ts 注册成既有的 `quitApp()`)。 @@ -175,6 +283,7 @@ export function shouldConsider(version: string): boolean { * - 同一版本已经下载过(`pendingUpdateVersion` 命中且文件还在):跳过下载,直接尝试安装。 */ export async function considerUpdate(info: ClientUpdateInfoDto, targetVersion: string): Promise { + const epoch = updateEpoch; const version = targetVersion.trim() || info.version.trim(); if (!shouldConsider(version)) { logger.info(`忽略服务端下发的更新 ${version || '(未指定版本)'}:自动更新已在设置中关闭`); @@ -189,6 +298,13 @@ export async function considerUpdate(info: ClientUpdateInfoDto, targetVersion: s if (config.pendingUpdateVersion === version) { const existing = config.pendingUpdateFile; if (existing && fs.existsSync(existing)) { + publishProgress({ + version, + stage: 'waiting', + downloadedBytes: info.size, + totalBytes: info.size, + error: '', + }); logger.info(`更新 ${version} 早已下载完成,直接尝试安装`); await maybeInstallPendingUpdate(); return; @@ -222,7 +338,8 @@ export async function considerUpdate(info: ClientUpdateInfoDto, targetVersion: s logger.info(`开始下载更新 ${version}(${info.fileName},${formatSize(info.size)})`); const response = await fetch(url, { headers: { authorization: `Bearer ${token}` }, - signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), + signal: AbortSignal.any([AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS), downloadAbort.signal]), + redirect: 'error', }); if (!response.ok) { throw new Error(`下载失败:HTTP ${response.status}`); @@ -235,13 +352,20 @@ export async function considerUpdate(info: ClientUpdateInfoDto, targetVersion: s const handle = await fsp.open(temp, 'w'); try { for await (const chunk of response.body as unknown as AsyncIterable) { - await handle.write(chunk); + let offset = 0; + while (offset < chunk.byteLength) { + const { bytesWritten } = await handle.write(chunk, offset, chunk.byteLength - offset); + if (!bytesWritten) throw new Error('更新包写入失败'); + offset += bytesWritten; + } publishProgress({ downloadedBytes: updateProgress.downloadedBytes + chunk.byteLength }); } } finally { await handle.close(); } + if (epoch !== updateEpoch) return; + if (info.size > 0 && updateProgress.downloadedBytes !== info.size) throw new Error('更新包长度不匹配'); publishProgress({ stage: 'verifying' }); const digest = await sha256File(temp); const expected = info.sha256.trim().toLowerCase(); @@ -264,7 +388,7 @@ export async function considerUpdate(info: ClientUpdateInfoDto, targetVersion: s await fsp.rm(target, { force: true }); await fsp.rename(temp, target); publishProgress({ stage: 'waiting' }); - if (loadPolicy()?.updateTargetVersion !== version) { + if (epoch !== updateEpoch || loadPolicy()?.updateTargetVersion !== version) { removeManagedInstaller(target); return; } @@ -277,6 +401,7 @@ export async function considerUpdate(info: ClientUpdateInfoDto, targetVersion: s await maybeInstallPendingUpdate(); } catch (error) { await fsp.rm(temp, { force: true }).catch(() => undefined); + if (epoch !== updateEpoch) return; publishProgress({ stage: 'failed', error: String(error) }); logger.error(`下载更新 ${version} 失败:${error instanceof Error ? error.message : String(error)}`); } finally { @@ -319,6 +444,7 @@ export async function maybeInstallPendingUpdate(): Promise { const courseware = island.getCoursewareTransfer(); if (courseware && ['downloading', 'verifying'].includes(courseware.stage)) return; if (installing) return; + if (Date.now() < installationRetryAt) return; if (process.env.ELECTRON_SMOKE_TEST === '1') return; const config = getConfig(); @@ -375,15 +501,36 @@ export async function maybeInstallPendingUpdate(): Promise { } installing = true; - publishProgress({ stage: 'installing' }); + const installEpoch = updateEpoch; + publishProgress({ version, stage: 'installing', error: '' }); + const reported = await reporting; + if (!reported) { + installing = false; + publishProgress({ stage: 'waiting', error: '安装前状态尚未送达服务端,等待网络恢复后重试' }); + return; + } + if ( + installEpoch !== updateEpoch || + loadPolicy()?.updateTargetVersion !== version || + getConfig().autoUpdate === false + ) { + installing = false; + return; + } logger.info(`开始静默安装更新 ${version}:${installer}`); try { - const exe = app.getPath('exe'); + const exe = process.env.PORTABLE_EXECUTABLE_FILE || app.getPath('exe'); const quote = (value: string): string => "'" + value.replaceAll("'", "''") + "'"; + const config = getConfig(); + const resultFile = path.join(pendingUpdateDir(), 'install-result.json'); + // 上一次失败的结果不能冒充这一次:安装开始时先清掉,只有本轮失败才重新写入。 + fs.rmSync(resultFile, { force: true }); const script = - `$ErrorActionPreference = 'Stop'; Wait-Process -Id ${process.pid} -ErrorAction SilentlyContinue; ` + - `$installer = Start-Process -FilePath ${quote(installer)} -ArgumentList '/S','--updated','--force-run' -Wait -PassThru; ` + - `if ($installer.ExitCode -ne 0 -and (Test-Path -LiteralPath ${quote(exe)})) { Start-Process -FilePath ${quote(exe)} }`; + `$ErrorActionPreference = 'Stop'; try { Wait-Process -Id ${process.pid} -ErrorAction SilentlyContinue; ` + + `$installer = Start-Process -FilePath ${quote(installer)} -ArgumentList '/S','--updated','--force-run' -WindowStyle Hidden -Wait -PassThru; ` + + `if ($installer.ExitCode -ne 0) { throw ('安装器退出码:' + $installer.ExitCode) } ` + + `} catch { @{ version=${quote(version)}; serverUrl=${quote(config.serverUrl)}; username=${quote(config.username)}; error=$_.Exception.Message } | ConvertTo-Json -Compress | Set-Content -LiteralPath ${quote(resultFile)} -Encoding UTF8; ` + + `if (Test-Path -LiteralPath ${quote(exe)}) { Start-Process -FilePath ${quote(exe)} -WindowStyle Hidden } }`; const encoded = Buffer.from(script, 'utf16le').toString('base64'); const child = spawn( 'powershell.exe', @@ -408,6 +555,7 @@ export async function maybeInstallPendingUpdate(): Promise { } } catch (error) { installing = false; + publishProgress({ stage: 'failed', error: String(error).slice(0, 1000) }); logger.error( `启动更新安装失败(将在下次满足安装条件时重试):${error instanceof Error ? error.message : String(error)}`, ); diff --git a/packages/desktop-client/src/main/client-agent.ts b/packages/desktop-client/src/main/client-agent.ts index c1854ac..db57d30 100644 --- a/packages/desktop-client/src/main/client-agent.ts +++ b/packages/desktop-client/src/main/client-agent.ts @@ -15,7 +15,12 @@ import { type ClientHeartbeatResponse, type ClientUpdateInfoDto, } from '@classhelper/shared'; -import { maybeInstallPendingUpdate, considerUpdate, syncUpdatePolicy } from './auto-update.js'; +import { + maybeInstallPendingUpdate, + considerUpdate, + syncUpdatePolicy, + getUpdateProgress, +} from './auto-update.js'; import { connectionEvents, type ConnectionEvent } from './config-events.js'; import { getConfig } from './config.js'; import { island } from './island.js'; @@ -178,6 +183,7 @@ async function beat(): Promise { autoUpdate: config.autoUpdate !== false, pendingVersion: pendingVersionOrNull(), stateNote: currentStateNote(), + updateProgress: getUpdateProgress(), }; const response = await fetch(`${config.serverUrl}/api/clients/heartbeat`, { method: 'POST', diff --git a/packages/desktop-client/src/main/courseware.ts b/packages/desktop-client/src/main/courseware.ts index b248cd7..24fd4b1 100644 --- a/packages/desktop-client/src/main/courseware.ts +++ b/packages/desktop-client/src/main/courseware.ts @@ -9,6 +9,8 @@ import { app, BrowserWindow, shell } from 'electron'; import { COURSEWARE_MAX_BYTES, COURSEWARE_TRANSFER_TIMEOUT_MS, + coursewareArchivePath, + coursewarePathSegment, type ApiResponse, type CoursewareDto, type CoursewarePushDto, @@ -74,6 +76,34 @@ async function digest(file: string) { for await (const chunk of fs.createReadStream(file)) hash.update(chunk); return hash.digest('hex'); } +interface LocalCourseware { + item: CoursewareDto; + relativePath: string; +} +function localFile(root: string, relative: string) { + const file = path.resolve(root, relative); + if (!file.startsWith(path.resolve(root) + path.sep)) throw new Error('课件路径无效'); + return file; +} +async function localRecord(id: string, root = scopeDirectory()): Promise { + try { + return JSON.parse(await fsp.readFile(path.join(root, '.index', `${id}.json`), 'utf8')); + } catch { + return null; + } +} +export async function listLocalCourseware(): Promise { + if (!getConfig().token) return []; + const root = scopeDirectory(); + const startedEpoch = epoch; + const names = await fsp.readdir(path.join(root, '.index')).catch(() => []); + const result: CoursewareDto[] = []; + for (const name of names.filter((name) => /^[a-z0-9-]+\.json$/i.test(name))) { + const record = await localRecord(name.slice(0, -5), root); + if (record && fs.existsSync(localFile(root, record.relativePath))) result.push(record.item); + } + return startedEpoch === epoch ? result.sort((a, b) => b.createdAt.localeCompare(a.createdAt)) : []; +} export function downloadCourseware(id: string): Promise { checkId(id); const startedEpoch = epoch, @@ -99,10 +129,22 @@ export function downloadCourseware(id: string): Promise { !/^[a-f0-9]{64}$/.test(item.sha256) ) throw new Error('课件文件信息无效'); - const safeName = - `_${item.name.replace(/[<>:"/\\|?*\u0000-\u001f]/g, '_').replace(/[. ]+$/, '')}`.slice(0, 182); - const dir = path.join(scopeDirectory(), id), - file = path.join(dir, safeName); + const root = scopeDirectory(); + const stored = await localRecord(id, root); + let relativePath = + stored?.relativePath || + (item.archivePath || coursewareArchivePath(item.teacher.name, new Date(item.createdAt), item.name)) + .split('/') + .map(coursewarePathSegment) + .join(path.sep); + let file = localFile(root, relativePath); + // 同秒、同名文件不得覆盖先前课件;重试通过本地索引始终定位同一文件。 + if (!stored && fs.existsSync(file)) { + const ext = path.extname(file); + relativePath = relativePath.slice(0, relativePath.length - ext.length) + ` (${id})` + ext; + file = localFile(root, relativePath); + } + const dir = path.dirname(file); partial = `${file}.part`; const base: CoursewareTransfer = { id, @@ -164,6 +206,17 @@ export function downloadCourseware(id: string): Promise { await fsp.rename(partial, file); } if (startedEpoch !== epoch) throw new Error('登录会话已切换'); + await fsp.mkdir(path.join(root, '.index'), { recursive: true }); + const indexFile = path.join(root, '.index', `${id}.json`); + await fsp.writeFile( + `${indexFile}.part`, + JSON.stringify({ + item: { ...item, archivePath: relativePath.split(path.sep).join('/') }, + relativePath, + }), + 'utf8', + ); + await fsp.rename(`${indexFile}.part`, indexFile); publish({ ...base, receivedBytes: item.size, progress: 100, stage: 'completed' }); } catch (error) { if (partial) await fsp.rm(partial, { force: true }).catch(() => undefined); @@ -232,6 +285,15 @@ export async function pollCourseware() { } export async function revealCourseware(id: string) { checkId(id); + if (!getConfig().token) throw new Error('请先登录班级账号'); + const stored = await localRecord(id); + if (stored) { + const file = localFile(scopeDirectory(), stored.relativePath); + if (fs.existsSync(file)) { + shell.showItemInFolder(file); + return; + } + } const dir = path.join(scopeDirectory(), id); const names = await fsp.readdir(dir).catch(() => []); const file = names.find((name) => !name.endsWith('.part')); diff --git a/packages/desktop-client/src/main/ipc.ts b/packages/desktop-client/src/main/ipc.ts index eea0b53..47d879e 100644 --- a/packages/desktop-client/src/main/ipc.ts +++ b/packages/desktop-client/src/main/ipc.ts @@ -1,4 +1,10 @@ -import { downloadCourseware, getCoursewareTransfer, pollCourseware, revealCourseware } from './courseware.js'; +import { + downloadCourseware, + getCoursewareTransfer, + pollCourseware, + revealCourseware, + listLocalCourseware, +} from './courseware.js'; import { island } from './island.js'; import { getUpdateProgress } from './auto-update.js'; import { app, BrowserWindow, ipcMain, shell } from 'electron'; @@ -17,7 +23,10 @@ export { IPC_CHANNELS }; export function registerIpcHandlers(): void { ipcMain.handle('courseware:download', (_event, id: string) => downloadCourseware(id)); ipcMain.handle('courseware:reveal', (_event, id: string) => revealCourseware(id)); - ipcMain.handle('courseware:sync', () => { void pollCourseware(); }); + ipcMain.handle('courseware:local-list', () => listLocalCourseware()); + ipcMain.handle('courseware:sync', () => { + void pollCourseware(); + }); ipcMain.handle('courseware:get-progress', () => getCoursewareTransfer()); ipcMain.handle('client:get-update-progress', () => getUpdateProgress()); ipcMain.handle(IPC_CHANNELS.getConfig, (): DesktopStoredConfig => getConfig()); diff --git a/packages/desktop-client/src/main/island.ts b/packages/desktop-client/src/main/island.ts index 75407d7..722bf1f 100644 --- a/packages/desktop-client/src/main/island.ts +++ b/packages/desktop-client/src/main/island.ts @@ -195,7 +195,14 @@ class IslandController { if (JSON.stringify(next) === JSON.stringify(this.lessonCue)) return; this.lessonCue = next; // 触摸窗口先放得下新横条,再下发内容;混合宽高变化也只能增大画布。 - if (this.lessonShowing) this.syncTouchLayout(); + if (this.lessonShowing) { + // 通知收回的延迟回调不能在课表提醒出现后再隐藏同一扇原生窗口。 + if (this.closeTimer) clearTimeout(this.closeTimer); + this.closeTimer = null; + this.cancelFade(); + this.syncResidentShape(true); + this.syncTouchLayout(); + } this.emit(); this.syncWindow(); } @@ -1106,6 +1113,10 @@ class IslandController { * 所以"窗口最终会隐藏"的既有断言(含轮询写法)不受影响。 */ private finishClose(): void { + if (this.lessonShowing || this.coursewareShowing) { + this.setState({ mode: 'hidden', reason: null }); + return; + } // 动画期间又来了新消息 / 被显式唤起:这次收回归属于旧的一批,放弃收尾 if (this.state.mode !== 'pill' || this.state.reason !== 'closing') return; if (this.state.active || this.state.queued.length > 0) return; diff --git a/packages/desktop-client/src/main/lesson-reminder.ts b/packages/desktop-client/src/main/lesson-reminder.ts index f84dc8a..9f4dc43 100644 --- a/packages/desktop-client/src/main/lesson-reminder.ts +++ b/packages/desktop-client/src/main/lesson-reminder.ts @@ -1,6 +1,5 @@ import { dayKeyLocal, - resolveClassStatus, DEFAULT_LESSON_REMINDERS, type LessonReminderSettings, type IslandLessonReminder, @@ -23,16 +22,18 @@ export class LessonReminderTracker { const at = now.getTime(); const keyOf = (s: ScheduleDto, kind: IslandLessonReminder['kind']) => `${dayKeyLocal(now)}:${s.id}:${s.startTime}:${s.endTime}:${kind}`; - const status = resolveClassStatus(schedules, now); const timeAt = (time: string) => { const d = new Date(now); - const [h, m] = time.split(':').map(Number); - d.setHours(h!, m!, 0, 0); + const [h, m, s = 0] = time.split(':').map(Number); + d.setHours(h!, m!, s, 0); return d.getTime(); }; - const today = schedules.filter((s) => s.dayOfWeek === (now.getDay() || 7)); - const current = today.find((s) => s.id === status.current?.scheduleId); - const next = today.find((s) => s.id === status.next?.scheduleId); + const today = schedules + .filter((s) => s.dayOfWeek === (now.getDay() || 7)) + .sort((a, b) => timeAt(a.startTime) - timeAt(b.startTime)); + // 已解析日课表只按准确时间比较,避免带秒的课时提前触发或错过上课遮罩。 + const current = today.find((s) => at >= timeAt(s.startTime) && at < timeAt(s.endTime)); + const next = today.find((s) => timeAt(s.startTime) > at); const ended = today .filter((s) => at >= timeAt(s.endTime) && at - timeAt(s.endTime) < 12_000) .sort((a, b) => b.endTime.localeCompare(a.endTime))[0]; @@ -74,12 +75,14 @@ export class LessonReminderTracker { const entered = this.current?.id === id && at >= this.current.startedAt ? this.current.startedAt : at; const start = timeAt(lesson.startTime); const end = timeAt(lesson.endTime); + // 导入的 ClassIsland 时间点可能带秒;提醒条只显示到分钟,宽度保持稳定。 + const clock = (value: string) => value.slice(0, 5); return (this.current = { id, kind, courseName: lesson.course?.name || '课程', - startTime: lesson.startTime, - endTime: lesson.endTime, + startTime: clock(lesson.startTime), + endTime: clock(lesson.endTime), startedAt: entered, maskUntil: kind === 'ending' || kind === 'in-class' @@ -100,8 +103,8 @@ export class LessonReminderTracker { lessonStartsAt: start, lessonEndsAt: end, nextCourseName: kind === 'break' ? next?.course?.name : undefined, - nextStartTime: kind === 'break' ? next?.startTime : undefined, - nextEndTime: kind === 'break' ? next?.endTime : undefined, + nextStartTime: kind === 'break' && next ? clock(next.startTime) : undefined, + nextEndTime: kind === 'break' && next ? clock(next.endTime) : undefined, }); } } diff --git a/packages/desktop-client/src/main/smoke-courseware.ts b/packages/desktop-client/src/main/smoke-courseware.ts index 38a01c1..eaefcee 100644 --- a/packages/desktop-client/src/main/smoke-courseware.ts +++ b/packages/desktop-client/src/main/smoke-courseware.ts @@ -8,7 +8,7 @@ import { app } from 'electron'; import type { CoursewareDto } from '@classhelper/shared'; import { defaultIslandLayouts } from '@classhelper/shared'; import { getConfig, saveConfig } from './config.js'; -import { downloadCourseware, getCoursewareTransfer } from './courseware.js'; +import { downloadCourseware, getCoursewareTransfer, listLocalCourseware } from './courseware.js'; import { island } from './island.js'; export async function verifyCoursewareSmoke( check: (name: string, action: () => Promise | boolean) => Promise, @@ -93,7 +93,7 @@ export async function verifyCoursewareSmoke( }); await job; await check('课件校验完成后保存正式文件并显示完成', async () => { - const files = await localFiles(); + const files = (await localFiles()).filter((file) => file.endsWith('.pptx')); return ( getCoursewareTransfer()?.stage === 'completed' && getCoursewareTransfer()?.progress === 100 && @@ -101,6 +101,18 @@ export async function verifyCoursewareSmoke( (await fs.readFile(files[0]!)).equals(bytes) ); }); + await check('本机归档索引支持离线浏览且路径按教师年月日时间分类', async () => { + const saved = await listLocalCourseware(); + const files = (await localFiles()).filter((file) => file.endsWith('.pptx')); + return ( + saved.some((local) => local.id === item.id) && + files.some((file) => + /Teachers[\\/]临时课件教师[\\/]\d{4}[\\/]\d{2}[\\/]\d{2}[\\/]\d{2}-\d{2}-\d{2}[\\/]课件接收验证\.pptx$/.test( + file, + ), + ) + ); + }); await check('已完整接收的相同课件不重复传输', async () => { await downloadCourseware(item.id); return downloads === 1; diff --git a/packages/desktop-client/src/main/smoke-resident.ts b/packages/desktop-client/src/main/smoke-resident.ts index 8af9dc8..bcc5d5d 100644 --- a/packages/desktop-client/src/main/smoke-resident.ts +++ b/packages/desktop-client/src/main/smoke-resident.ts @@ -558,7 +558,15 @@ export async function verifyResidentSmoke( await pause(800); if (island.getState().lessonReminder?.kind !== 'prepare') return false; const mask = await win.webContents.executeJavaScript( - `(() => {const cue=document.querySelector('${layout === 'custom' ? '.resident-lesson-page' : '.ci-fixed-lesson'} .ci-mask')||document.querySelector('.ci-fixed-lesson .ci-mask');return Boolean(cue&&cue.querySelectorAll('polygon').length===5);})()`, + `(() => { + const cue=document.querySelector('${layout === 'custom' ? '.resident-lesson-page' : '.ci-fixed-lesson'} .ci-mask')||document.querySelector('.ci-fixed-lesson .ci-mask'); + if (!cue || cue.querySelectorAll('polygon').length !== 5) return false; + const card=cue.closest('.ci-lesson'), title=card?.querySelector('.ci-mask-title'); + const bounds=card?.getBoundingClientRect(), painted=cue.getBBox(); + let opacity=1, node=card; + while(node&&node!==document.body){const style=getComputedStyle(node);if(style.visibility==='hidden'||style.display==='none')return false;opacity*=Number(style.opacity);node=node.parentElement;} + return Boolean(bounds&&bounds.width>=290&&bounds.height>=39&&painted.width>=bounds.width*.9&&title&&Number(getComputedStyle(title).opacity)>.8&&opacity>.8); + })()`, ); if (!mask) return false; await shot('lesson-prepare-mask-' + layout); diff --git a/packages/desktop-client/src/preload/index.ts b/packages/desktop-client/src/preload/index.ts index d3fcf95..f807e4d 100644 --- a/packages/desktop-client/src/preload/index.ts +++ b/packages/desktop-client/src/preload/index.ts @@ -1,4 +1,7 @@ -import type { ReleaseProgressDto as ContractReleaseProgressDto, CoursewareTransfer } from '@classhelper/shared'; +import type { + ReleaseProgressDto as ContractReleaseProgressDto, + CoursewareTransfer, +} from '@classhelper/shared'; import { contextBridge, ipcRenderer } from 'electron'; import { IPC_CHANNELS, UPDATE_AVAILABLE_CHANNEL } from '../main/channels.js'; import type { ClientAgentStatus } from '../main/client-agent.js'; @@ -22,6 +25,7 @@ import type { IslandNotification, UpdateInfo } from '@classhelper/shared'; const bridge: DesktopBridge = { downloadCourseware: (id) => ipcRenderer.invoke('courseware:download', id), revealCourseware: (id) => ipcRenderer.invoke('courseware:reveal', id), + listLocalCourseware: () => ipcRenderer.invoke('courseware:local-list'), syncCourseware: () => ipcRenderer.invoke('courseware:sync'), getCoursewareTransfer: () => ipcRenderer.invoke('courseware:get-progress'), onCoursewareTransfer: (handler) => { diff --git a/packages/desktop-client/src/renderer/views/CoursewareView.vue b/packages/desktop-client/src/renderer/views/CoursewareView.vue index 9cc41b0..cd5866b 100644 --- a/packages/desktop-client/src/renderer/views/CoursewareView.vue +++ b/packages/desktop-client/src/renderer/views/CoursewareView.vue @@ -1,5 +1,5 @@ + + diff --git a/packages/web-admin/src/components/ManagementMenu.vue b/packages/web-admin/src/components/ManagementMenu.vue index 5d9a226..0e499a9 100644 --- a/packages/web-admin/src/components/ManagementMenu.vue +++ b/packages/web-admin/src/components/ManagementMenu.vue @@ -103,7 +103,23 @@ watch( width: 0; } .management-menu.menu-rail :deep(.el-sub-menu__title) { - padding: 0 14px !important; + padding: 0 !important; + margin: 4px 0 !important; + width: 48px; + height: 44px; + justify-content: center; +} +.management-menu.menu-rail .management-group-title { + justify-content: center; + gap: 0; + min-width: 20px; +} +.management-menu.menu-rail .management-group-title span { + flex: 0 0 0; + overflow: hidden; +} +.management-menu.menu-rail :deep(.el-sub-menu__icon-arrow) { + visibility: hidden; } .management-menu.menu-rail { overflow-x: hidden; diff --git a/packages/web-admin/src/config/navigation.ts b/packages/web-admin/src/config/navigation.ts index 4ebfd5c..08e3007 100644 --- a/packages/web-admin/src/config/navigation.ts +++ b/packages/web-admin/src/config/navigation.ts @@ -5,7 +5,7 @@ export const MANAGEMENT_GROUPS = [ id: 'teaching', title: '教学事务', icon: 'Notebook', - paths: ['/schedules', '/plan-editor', '/homeworks', '/notifications', '/courseware', '/grades'], + paths: ['/plan-editor', '/homeworks', '/notifications', '/courseware', '/grades'], }, { id: 'school', diff --git a/packages/web-admin/src/router/index.ts b/packages/web-admin/src/router/index.ts index 04baa92..0ff28fd 100644 --- a/packages/web-admin/src/router/index.ts +++ b/packages/web-admin/src/router/index.ts @@ -14,13 +14,23 @@ const routes: RouteRecordRaw[] = [ component: () => import('@/layouts/AdminLayout.vue'), redirect: '/dashboard', children: [ - { path: 'courseware', name: 'courseware', component: () => import('@/views/CoursewareView.vue'), meta: { title: '课件', icon: 'FolderOpened', roles: ['ADMIN', 'TEACHER'], permission: 'courseware:read' } }, + { + path: 'courseware', + name: 'courseware', + component: () => import('@/views/CoursewareView.vue'), + meta: { + title: '课件', + icon: 'FolderOpened', + roles: ['ADMIN', 'TEACHER'], + permission: 'courseware:read', + }, + }, { path: 'plan-editor', name: 'plan-editor', component: () => import('@/views/PlanEditorView.vue'), meta: { - title: '时间表与课表编辑', + title: '课表档案', icon: 'Calendar', roles: ['ADMIN', 'TEACHER'], permission: 'schedule:read', @@ -79,7 +89,7 @@ const routes: RouteRecordRaw[] = [ { path: 'schedules', name: 'schedules', - component: () => import('@/views/SchedulesView.vue'), + redirect: '/plan-editor', meta: { title: '课表管理', icon: 'Calendar' }, }, { diff --git a/packages/web-admin/src/styles/index.css b/packages/web-admin/src/styles/index.css index 1ee6c1a..cf34c3b 100644 --- a/packages/web-admin/src/styles/index.css +++ b/packages/web-admin/src/styles/index.css @@ -907,6 +907,36 @@ body { } } +/* 原生文件选择保留键盘与读屏能力,按钮外观与管理端控件保持一致。 */ +input[type='file']:not([hidden]) { + max-width: 100%; + min-width: 0; + font: inherit; + color: var(--el-text-color-regular); +} +input[type='file']::file-selector-button { + padding: 8px 16px; + margin-right: 12px; + border: 1px solid var(--el-border-color); + border-radius: 8px; + background: var(--el-color-primary-light-9); + color: var(--el-color-primary); + font: inherit; + cursor: pointer; +} +.toolbar { + row-gap: 12px; +} +.toolbar > .el-button + .el-button { + margin-left: 0; +} +.el-form--inline .el-form-item { + max-width: 100%; +} +.el-form-item__content { + min-width: 0; +} + /* * 抽屉(el-drawer)。和弹窗不同,抽屉的位移由 `.el-drawer` **自身**的 * `transition: all var(--el-transition-duration)` 承载(`.el-drawer-fade-*` 那组类 diff --git a/packages/web-admin/src/views/ClientsView.vue b/packages/web-admin/src/views/ClientsView.vue index 362e533..b616418 100644 --- a/packages/web-admin/src/views/ClientsView.vue +++ b/packages/web-admin/src/views/ClientsView.vue @@ -18,13 +18,13 @@ * **禁用 + tooltip 说明**而不是直接隐藏:让老师知道"有这件事、需要权限",而不是以为系统没有。 */ import ReleaseProgress from '@/components/ReleaseProgress.vue'; +import ClientUpdateStatus from '@/components/ClientUpdateStatus.vue'; import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'; import { ElMessage, ElMessageBox } from 'element-plus'; import { CLIENT_COMMAND_EXPIRE_MS, CLIENT_COMMAND_KINDS, CLIENT_COMMAND_LABELS, - CLIENT_HEARTBEAT_INTERVAL_MS, clientReleaseFileName, compareVersions, normalizeVersion, @@ -47,7 +47,7 @@ const { isMobile } = useResponsive(); const auth = useAuthStore(); /** 自动刷新间隔:跟客户端心跳同频,刷太快没有新数据 */ -const AUTO_REFRESH_MS = CLIENT_HEARTBEAT_INTERVAL_MS; +const AUTO_REFRESH_MS = 3000; function can(key: PermissionKey): boolean { return auth.can(key); @@ -214,9 +214,6 @@ function classLabel(device: ClientDeviceDto): string { } /** 「已指定 <版本>」:只显示版本号,避免与「已下载待安装」挤在一起 */ -function targetVersionLabel(device: ClientDeviceDto): string { - return normalizeVersion(device.updateTargetVersion) || device.updateTargetVersion; -} /* -------------------------------------------------------------- 下发更新 */ @@ -814,20 +811,8 @@ const deviceEmptyText = computed(() => — - - + + diff --git a/packages/web-admin/src/views/CoursewareView.vue b/packages/web-admin/src/views/CoursewareView.vue index d1fdcc6..c28d19f 100644 --- a/packages/web-admin/src/views/CoursewareView.vue +++ b/packages/web-admin/src/views/CoursewareView.vue @@ -8,6 +8,7 @@ import { type CoursewareDto, type CoursewareClassDto, type CoursewareUploadRequest, + type CoursewareFolderDto, } from '@classhelper/shared'; import { api } from '@/api/http'; import { useAuthStore } from '@/stores/auth'; @@ -19,6 +20,7 @@ const items = ref([]), const file = ref(null), classIds = ref([]), mode = ref<'cloud' | 'push'>('cloud'); +const fileInput = ref(); const loading = ref(false), uploading = ref(false), progress = ref(0), @@ -30,11 +32,51 @@ const scopeItem = ref(null), scopeMode = ref<'visibility' | 'push'>('visibility'), saving = ref(false); const canWrite = computed(() => auth.can('courseware:write')); +const folders = ref([]), + folderPath = ref(''), + folderFilter = ref(''); +const grouping = ref<'date' | 'folder'>('date'); +const dateFolder = ref(''); +const dateFolders = computed(() => + [ + ...new Set( + items.value.map((item) => item.archivePath?.split('/').slice(0, -1).join('/') || item.archiveDate), + ), + ] + .sort() + .reverse(), +); +async function newFolder() { + try { + const { value } = await ElMessageBox.prompt( + '输入文件夹名称,可用 / 创建多层分类,如 地理/必修一', + '新建文件夹', + ); + const path = String(value ?? '').trim(); + if (!path) return; + await api.post('/courseware/folders', { path }); + await refresh(); + folderPath.value = path; + } catch (error) { + if (error !== 'cancel' && error !== 'close') throw error; + } +} +async function move(item: CoursewareDto, target: string) { + await api.patch(`/courseware/${item.id}/folder`, { folderPath: target }); + await refresh(); +} const filtered = computed(() => items.value.filter( (item) => (!query.value || `${item.name} ${item.teacher.name}`.includes(query.value)) && (!category.value || item.category === category.value) && + (grouping.value !== 'folder' || + !folderFilter.value || + item.folderPath === folderFilter.value || + item.folderPath?.startsWith(folderFilter.value + '/')) && + (grouping.value !== 'date' || + !dateFolder.value || + (item.archivePath?.startsWith(dateFolder.value + '/') ?? item.archiveDate === dateFolder.value)) && (!date.value || item.archiveDate === date.value), ), ); @@ -42,13 +84,25 @@ function size(bytes: number) { return `${(bytes / 1024 / 1024).toFixed(2)} MB`; } function choose(event: Event) { - file.value = (event.target as HTMLInputElement).files?.[0] ?? null; + const picked = (event.target as HTMLInputElement).files?.[0]; + // 取消选择时不丢掉上一次选中的文件。 + if (picked) file.value = picked; +} +function pickFile() { + if (!fileInput.value) return; + // 允许重新选择同一个文件:清空后再打开选择器。 + fileInput.value.value = ''; + fileInput.value.click(); } async function refresh() { loading.value = true; try { items.value = await api.get('/courseware'); - if (canWrite.value) classes.value = await api.get('/courseware/classes'); + if (canWrite.value) + [classes.value, folders.value] = await Promise.all([ + api.get('/courseware/classes'), + api.get('/courseware/folders'), + ]); } finally { loading.value = false; } @@ -64,6 +118,7 @@ async function upload() { size: file.value.size, classIds: [...classIds.value], mode: mode.value, + folderPath: folderPath.value, }; uploading.value = true; progress.value = 0; @@ -133,7 +188,7 @@ onMounted(() => void refresh()); @@ -141,7 +196,34 @@ onMounted(() => void refresh());

上传课件

- +
+ + {{ file ? '重新选择文件' : '选择文件' }} + + {{ file.name }} · {{ size(file.size) }} + 未选择文件 + +
+
+ +
+ + + + 新建文件夹 +
void refresh());
+ + 日期归档 + 文件夹分类 + + + + + + + @@ -184,6 +276,26 @@ onMounted(() => void refresh());
+ + +