diff --git a/CLAUDE.md b/CLAUDE.md
index 6c84c85..2b1158d 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -32,7 +32,7 @@ The backend was rewritten from Django in 2026-10 to cut memory use (~25 MB vs. 1
- `src/icons.ts` — `` renders bundled `lucide-vue-next` icons (works offline); register new names in its map. Fonts are bundled too (`@fontsource/jetbrains-mono`)
- `components/workbench/Workbench.vue` — shared challenge / creation page: left = brief + AI chat (`PromptPanel`), right = preview / code (`CodeTabs`) + `VersionBar`. Every assistant message with code is a version; hand edits and pasted external-AI code are saved as versions via `POST /prompt/versions` (`source` `edit` / `manual`, stored as a user+assistant pair). Challenges are submitted only when the student clicks “交这一版” (`POST /submission/` with `message_id`), never automatically. Editor code is per task (`composables/useTaskCode.ts`, localStorage `web-code::`); the tutorial page edits the shared `store/editors.ts` refs but `Workspace.vue` saves them under the same per-task key and `TutorialContent` restores them (example code only the first time)
- `pages/ChallengeHome.vue` — challenge list page; `pages/TaskEditor.vue` (prop `kind`) is the single tutorial / challenge authoring page
- - `pages/Review.vue` (“批改”, default teaching page) + `components/review/ReviewPanel.vue` (also opened from the gradebook) — opens as a large preview (wall hidden, Esc returns; “小面板” switches to a side panel, remembered); keys: J/K next/prev student, 1–5 rate, P present; “全部班级” groups the wall by class; `components/review/PeersDrawer.vue` is the students' “同学作品” (本班 / 全部班级)
+ - `pages/Review.vue` (“批改”, default teaching page) + `components/review/ReviewPanel.vue` (also opened from the gradebook) — opens as a large preview (wall hidden, Esc returns; “小面板” switches to a side panel, remembered); keys: J/K previous/next student (by key position), 1–5 rate, P present; “全部班级” groups the wall by class; `components/review/PeersDrawer.vue` is the students' “同学作品” (本班 / 全部班级)
- `components/layout/AppShell.vue` — top bar for every page; `pages/Dashboard.vue` is the teaching sidebar (“教学”) under `/dashboard`
- `src/store/` — Reactive state (plain Vue refs, not Vuex/Pinia); `prompt.ts` / `guidance.ts` own the WebSockets
- `src/pages/`, `src/components/` — user management uses `components/dashboard/UserFormModal.vue` (there is no Django Admin anymore)
diff --git a/client/src/components/editor/Preview.vue b/client/src/components/editor/Preview.vue
index a03e480..b6d546c 100644
--- a/client/src/components/editor/Preview.vue
+++ b/client/src/components/editor/Preview.vue
@@ -46,6 +46,7 @@
ref="frame"
class="iframe"
:sandbox="PREVIEW_SANDBOX"
+ :key="frameKey"
:srcdoc="previewContent"
>
@@ -141,8 +142,12 @@ function getContent(forDownload = false) {
})
}
+// 每次内容变了换一个新的 iframe:在同一个 iframe 里改 srcdoc 会进浏览器历史,
+// 按“后退”就成了在预览里倒退,而不是离开页面
+const frameKey = ref(0)
function preview() {
previewContent.value = getContent()
+ frameKey.value++
}
function download() {
diff --git a/client/src/components/review/PeersDrawer.vue b/client/src/components/review/PeersDrawer.vue
index 45feee3..1712373 100644
--- a/client/src/components/review/PeersDrawer.vue
+++ b/client/src/components/review/PeersDrawer.vue
@@ -61,7 +61,7 @@
@update:show="(v: boolean) => !v && (large = null)"
>
-
+
diff --git a/client/src/components/review/ReviewPanel.vue b/client/src/components/review/ReviewPanel.vue
index 0c578cb..8ffb0b2 100644
--- a/client/src/components/review/ReviewPanel.vue
+++ b/client/src/components/review/ReviewPanel.vue
@@ -13,7 +13,7 @@
- 上一个学生(K)
+ 上一个学生(J)
@@ -21,7 +21,7 @@
- 下一个学生(J)
+ 下一个学生(K)
-
+
+
diff --git a/client/src/components/workbench/RatingCard.vue b/client/src/components/workbench/RatingCard.vue
index c8ce1f5..9dc984d 100644
--- a/client/src/components/workbench/RatingCard.vue
+++ b/client/src/components/workbench/RatingCard.vue
@@ -10,6 +10,7 @@