From b24483b03bdf4727288eecced07ae10586e141ec Mon Sep 17 00:00:00 2001 From: cat_shark <1716967236@qq.com> Date: Fri, 3 Jul 2026 08:54:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=A4=8D=E4=B9=A0)=EF=BC=9A=E5=9B=9E?= =?UTF-8?q?=E5=BF=86=E5=A4=8D=E4=B9=A0=E9=A1=B5=E9=9D=A2=E4=B8=8E=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ReviewRecall.vue 回忆复习页面(双栏布局) - 左栏:回忆大纲输入区、对比结果展示(✅❌着色树) - 右栏:标准导图(默认折叠)、编辑、重新生成 - 遗漏项列表,可点击回看原文 - 回忆历史记录查询 - api/standardMindMap.ts:标准导图与回忆对比 API - router:新增 /review/recall/:taskNum 路由 - Review.vue 行操作中增加回忆复习按钮 - ReviewDetail.vue 思维导图区增加回忆复习入口 --- src/__tests__/api/review.spec.ts | 30 -- src/__tests__/api/tasks.spec.ts | 51 +++ src/api/review.ts | 41 -- src/api/standardMindMap.ts | 63 +++ src/api/tasks.ts | 44 ++ src/components/Review.vue | 9 +- src/components/ReviewDetail.vue | 192 +-------- src/components/ReviewRecall.vue | 663 +++++++++++++++++++++++++++++++ src/components/Study.vue | 135 +++++++ src/components/TaskForm.vue | 232 +++++++++++ src/router/index.ts | 5 + 11 files changed, 1205 insertions(+), 260 deletions(-) create mode 100644 src/__tests__/api/tasks.spec.ts create mode 100644 src/api/standardMindMap.ts create mode 100644 src/api/tasks.ts create mode 100644 src/components/ReviewRecall.vue diff --git a/src/__tests__/api/review.spec.ts b/src/__tests__/api/review.spec.ts index 572be5c..9d90465 100644 --- a/src/__tests__/api/review.spec.ts +++ b/src/__tests__/api/review.spec.ts @@ -11,17 +11,13 @@ vi.mock('@/utils/request', () => ({ import request from '@/utils/request' import { - createReviewApplication, - deleteReviewApplication, getFragmentDetail, getReportDetail, - getReviewApplications, getReviewFeed, getReviewMindMap, getReviewTaskStats, getReviewTaskStatsByTask, getTaskReview, - updateReviewApplication, upsertReviewMindMap, uploadReviewMindMap, } from '@/api/review' @@ -83,32 +79,6 @@ describe('review API', () => { expect(mockedRequest.get).toHaveBeenCalledWith('/review/task/T001') }) - it('review applications API calls expected endpoints', async () => { - mockedRequest.get.mockResolvedValue({ code: 200, data: [] }) - mockedRequest.post.mockResolvedValue({ code: 200, data: { id: 1 } }) - mockedRequest.put.mockResolvedValue({ code: 200, data: { id: 1 } }) - mockedRequest.del.mockResolvedValue({ code: 200 }) - - await getReviewApplications('T001') - expect(mockedRequest.get).toHaveBeenCalledWith('/review/applications/T001') - - await createReviewApplication({ taskNum: 'T001', title: 'Demo', status: 'TODO' }) - expect(mockedRequest.post).toHaveBeenCalledWith('/review/applications', { - taskNum: 'T001', - title: 'Demo', - status: 'TODO', - }) - - await updateReviewApplication(1, { title: 'Demo 2', status: 'DOING' }) - expect(mockedRequest.put).toHaveBeenCalledWith('/review/applications/1', { - title: 'Demo 2', - status: 'DOING', - }) - - await deleteReviewApplication(1) - expect(mockedRequest.del).toHaveBeenCalledWith('/review/applications/1') - }) - it('review mind map API calls expected endpoints', async () => { mockedRequest.get.mockResolvedValue({ code: 200, data: null }) mockedRequest.put.mockResolvedValue({ code: 200, data: { id: 1 } }) diff --git a/src/__tests__/api/tasks.spec.ts b/src/__tests__/api/tasks.spec.ts new file mode 100644 index 0000000..87cb289 --- /dev/null +++ b/src/__tests__/api/tasks.spec.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' + +vi.mock('@/utils/request', () => ({ + default: { + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + del: vi.fn(), + }, +})) + +import request from '@/utils/request' +import { + createTaskApplication, + deleteTaskApplication, + getTaskApplications, + updateTaskApplication, +} from '@/api/tasks' + +const mockedRequest = vi.mocked(request) + +describe('tasks API', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('task applications API calls expected endpoints', async () => { + mockedRequest.get.mockResolvedValue({ code: 200, data: [] }) + mockedRequest.post.mockResolvedValue({ code: 200, data: { id: 1 } }) + mockedRequest.put.mockResolvedValue({ code: 200, data: { id: 1 } }) + mockedRequest.del.mockResolvedValue({ code: 200 }) + + await getTaskApplications('T001') + expect(mockedRequest.get).toHaveBeenCalledWith('/tasks/T001/applications') + + await createTaskApplication('T001', { title: 'Demo', status: 'TODO' }) + expect(mockedRequest.post).toHaveBeenCalledWith('/tasks/T001/applications', { + title: 'Demo', + status: 'TODO', + }) + + await updateTaskApplication(1, { title: 'Demo 2', status: 'DOING' }) + expect(mockedRequest.put).toHaveBeenCalledWith('/tasks/applications/1', { + title: 'Demo 2', + status: 'DOING', + }) + + await deleteTaskApplication(1) + expect(mockedRequest.del).toHaveBeenCalledWith('/tasks/applications/1') + }) +}) diff --git a/src/api/review.ts b/src/api/review.ts index b462957..36e007f 100644 --- a/src/api/review.ts +++ b/src/api/review.ts @@ -23,17 +23,6 @@ export interface ReviewTaskStats { avgEffectivenessRatio: number; } -export interface ReviewApplication { - id: number; - taskNum: string; - title: string; - description?: string; - resourceUrl?: string; - status: "TODO" | "DOING" | "DONE"; - createdTime?: string; - lastModifiedTime?: string; -} - export interface ReviewMindMap { id: number; taskNum: string; @@ -77,36 +66,6 @@ export const getTaskReview = (taskNum: string) => { return request.get(`/review/task/${taskNum}`); }; -export const getReviewApplications = (taskNum: string) => { - return request.get(`/review/applications/${taskNum}`); -}; - -export const createReviewApplication = (payload: { - taskNum: string; - title: string; - description?: string; - resourceUrl?: string; - status?: ReviewApplication["status"]; -}) => { - return request.post("/review/applications", payload); -}; - -export const updateReviewApplication = ( - id: number, - payload: { - title: string; - description?: string; - resourceUrl?: string; - status?: ReviewApplication["status"]; - }, -) => { - return request.put(`/review/applications/${id}`, payload); -}; - -export const deleteReviewApplication = (id: number) => { - return request.del(`/review/applications/${id}`); -}; - export const getReviewMindMap = (taskNum: string) => { return request.get(`/review/mind-map/${taskNum}`); }; diff --git a/src/api/standardMindMap.ts b/src/api/standardMindMap.ts new file mode 100644 index 0000000..275a8fe --- /dev/null +++ b/src/api/standardMindMap.ts @@ -0,0 +1,63 @@ +import request from "@/utils/request"; +import type { ReviewMindMap } from "./review"; + +// ============ 标准思维导图 ============ + +export interface StandardMindMap { + id: number; + taskNum: string; + title: string; + content: string; + outline: string; + summary: string; + generator: "BUILTIN" | "AI" | "USER"; + generatorVersion?: string; + sourceReportCount: number; + sourceFragmentCount: number; + generatedTime?: string; + createdTime?: string; + lastModifiedTime?: string; +} + +export interface RecallRecord { + id: number; + taskNum: string; + standardMapId: number; + recallContent: string; + compareResult: string; + recallRatio: number; + matchedCount: number; + missedCount: number; + extraCount: number; + createdTime: string; +} + +/** 获取/自动生成标准思维导图 */ +export const getStandardMindMap = (taskNum: string) => { + return request.get(`/review/standard-mind-map/${taskNum}`); +}; + +/** 强制重新生成标准思维导图 */ +export const regenerateStandardMindMap = (taskNum: string) => { + return request.post(`/review/standard-mind-map/${taskNum}/regenerate`); +}; + +/** 用户编辑标准思维导图(大纲文本) */ +export const updateStandardMindMap = (taskNum: string, outline: string) => { + return request.put(`/review/standard-mind-map/${taskNum}`, { outline }); +}; + +/** 用户提交回忆大纲,与标准导图对比 */ +export const recallCompare = (taskNum: string, recallOutline: string) => { + return request.post(`/review/standard-mind-map/${taskNum}/recall`, { recallOutline }); +}; + +/** 获取该任务的所有回忆对比记录 */ +export const listRecallRecords = (taskNum: string) => { + return request.get(`/review/standard-mind-map/${taskNum}/recall-records`); +}; + +/** 获取单条回忆对比记录 */ +export const getRecallRecord = (recordId: number) => { + return request.get(`/review/standard-mind-map/recall-records/${recordId}`); +}; diff --git a/src/api/tasks.ts b/src/api/tasks.ts new file mode 100644 index 0000000..b4af7ef --- /dev/null +++ b/src/api/tasks.ts @@ -0,0 +1,44 @@ +import request from "@/utils/request"; + +export interface TaskApplication { + id: number; + taskNum: string; + title: string; + description?: string; + resourceUrl?: string; + status: "TODO" | "DOING" | "DONE"; + createdTime?: string; + lastModifiedTime?: string; +} + +export const getTaskApplications = (taskNum: string) => { + return request.get(`/tasks/${taskNum}/applications`); +}; + +export const createTaskApplication = ( + taskNum: string, + payload: { + title: string; + description?: string; + resourceUrl?: string; + status?: TaskApplication["status"]; + }, +) => { + return request.post(`/tasks/${taskNum}/applications`, payload); +}; + +export const updateTaskApplication = ( + id: number, + payload: { + title: string; + description?: string; + resourceUrl?: string; + status?: TaskApplication["status"]; + }, +) => { + return request.put(`/tasks/applications/${id}`, payload); +}; + +export const deleteTaskApplication = (id: number) => { + return request.del(`/tasks/applications/${id}`); +}; diff --git a/src/components/Review.vue b/src/components/Review.vue index 162e927..1af2157 100644 --- a/src/components/Review.vue +++ b/src/components/Review.vue @@ -94,7 +94,7 @@ onMounted(() => { - + diff --git a/src/components/ReviewDetail.vue b/src/components/ReviewDetail.vue index 0c18a0e..8ad3c7d 100644 --- a/src/components/ReviewDetail.vue +++ b/src/components/ReviewDetail.vue @@ -2,16 +2,11 @@ import { computed, ref, watch } from "vue"; import { useRoute, useRouter } from "vue-router"; import { - createReviewApplication, - deleteReviewApplication, getFragmentDetail, getReportDetail, - getReviewApplications, getReviewMindMap, getTaskReview, - updateReviewApplication, uploadReviewMindMap, - type ReviewApplication, type ReviewFeedItem, type ReviewMindMap, } from "@/api/review"; @@ -34,21 +29,6 @@ const isEditing = ref(false); const editContent = ref(""); const saving = ref(false); -const applications = ref([]); -const applicationSaving = ref(false); -const editingApplicationId = ref(null); -const applicationForm = ref<{ - title: string; - description: string; - resourceUrl: string; - status: ReviewApplication["status"]; -}>({ - title: "", - description: "", - resourceUrl: "", - status: "TODO", -}); - const mindMap = ref(null); const mindMapUploading = ref(false); @@ -68,12 +48,6 @@ const taskSessions = ref<{ fragments: ReviewFeedItem[]; }[]>([]); -const statusText: Record = { - TODO: "待应用", - DOING: "进行中", - DONE: "已完成", -}; - type MindMapNode = { title?: string; notes?: string; @@ -123,16 +97,6 @@ const formatRatio = (ratio?: number): string => { return `${(ratio * 100).toFixed(1)}%`; }; -const resetApplicationForm = () => { - editingApplicationId.value = null; - applicationForm.value = { - title: "", - description: "", - resourceUrl: "", - status: "TODO", - }; -}; - const loadTaskHistory = async (tn: string) => { try { const res = await getTaskReview(tn); @@ -165,9 +129,6 @@ const loadTaskHistory = async (tn: string) => { }; const loadReviewExtensions = async (tn: string) => { - const applicationsRes = await getReviewApplications(tn).catch(() => null); - applications.value = applicationsRes?.data || []; - const mindMapRes = await getReviewMindMap(tn).catch(() => null); const loadedMindMap: ReviewMindMap | null = mindMapRes?.data || null; mindMap.value = loadedMindMap; @@ -182,7 +143,6 @@ const loadDetail = async () => { taskNum.value = ""; sessionInfo.value = null; taskSessions.value = []; - applications.value = []; mindMap.value = null; try { let res; @@ -260,58 +220,6 @@ const saveEdit = async () => { } }; -const saveApplication = async () => { - if (!taskNum.value) { - ElMessage.warning("未找到关联任务"); - return; - } - if (!applicationForm.value.title.trim()) { - ElMessage.warning("应用项目标题不能为空"); - return; - } - applicationSaving.value = true; - try { - const payload = { - title: applicationForm.value.title, - description: applicationForm.value.description, - resourceUrl: applicationForm.value.resourceUrl, - status: applicationForm.value.status, - }; - if (editingApplicationId.value) { - await updateReviewApplication(editingApplicationId.value, payload); - ElMessage.success("应用场景已更新"); - } else { - await createReviewApplication({ taskNum: taskNum.value, ...payload }); - ElMessage.success("应用场景已添加"); - } - resetApplicationForm(); - await loadReviewExtensions(taskNum.value); - } finally { - applicationSaving.value = false; - } -}; - -const editApplication = (item: ReviewApplication) => { - editingApplicationId.value = item.id; - applicationForm.value = { - title: item.title || "", - description: item.description || "", - resourceUrl: item.resourceUrl || "", - status: item.status || "TODO", - }; -}; - -const removeApplication = async (item: ReviewApplication) => { - await deleteReviewApplication(item.id); - ElMessage.success("应用场景已删除"); - if (editingApplicationId.value === item.id) { - resetApplicationForm(); - } - if (taskNum.value) { - await loadReviewExtensions(taskNum.value); - } -}; - const handleMindMapFileChange = async (uploadFile: any) => { if (!taskNum.value) { ElMessage.warning("未找到关联任务"); @@ -402,53 +310,6 @@ watch(
-
-
-

应用场景

- {{ applications.length }} -
- -
-
-
- {{ item.title }} - {{ statusText[item.status] || item.status }} -
-

{{ item.description }}

- - {{ item.resourceUrl }} - -
- 编辑 - 删除 -
-
-
- - -
- - - -
- - - - - - - {{ editingApplicationId ? "更新" : "添加" }} - - 取消 -
-
-
-

思维导图

@@ -467,6 +328,9 @@ watch( > 上传导图文件 + + 回忆复习 + {{ mindMap.fileFormat }} @@ -605,7 +469,7 @@ watch( .review-extension-grid { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr); gap: 14px; } @@ -627,48 +491,6 @@ watch( font-size: 16px; } -.application-list { - display: flex; - flex-direction: column; - gap: 10px; - margin-bottom: 14px; -} - -.application-item { - padding: 12px; - border: 1px solid var(--border-soft); - border-radius: 8px; - background: #fbfefc; -} - -.application-main { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} - -.application-main strong { - min-width: 0; - color: var(--text-primary); - word-break: break-word; -} - -.application-item p { - margin: 8px 0 0; - color: var(--text-secondary); - font-size: 13px; - word-break: break-word; -} - -.item-actions { - display: flex; - justify-content: flex-end; - gap: 4px; - margin-top: 8px; -} - -.application-form, .mind-map-form { display: flex; flex-direction: column; @@ -740,12 +562,6 @@ watch( color: var(--text-primary); } -.form-row { - display: flex; - align-items: center; - gap: 10px; -} - /* 历史记录卡片 */ .history-card { padding: 20px 22px; diff --git a/src/components/ReviewRecall.vue b/src/components/ReviewRecall.vue new file mode 100644 index 0000000..b9e7b29 --- /dev/null +++ b/src/components/ReviewRecall.vue @@ -0,0 +1,663 @@ + + + + + diff --git a/src/components/Study.vue b/src/components/Study.vue index c83a656..aa5fdc3 100644 --- a/src/components/Study.vue +++ b/src/components/Study.vue @@ -4,6 +4,11 @@ import request from "@/utils/request"; import router from "@/router"; import { ElMessage } from "element-plus"; import { getReviewTaskStatsByTask } from "@/api/review"; +import { + getTaskApplications, + updateTaskApplication, + type TaskApplication, +} from "@/api/tasks"; interface TaskItem { title: string; @@ -27,6 +32,14 @@ interface TaskItem { const tasks = ref([]); const selectedTaskId = ref(null); const loading = ref(false); +const taskApplications = ref([]); +const applicationsLoading = ref(false); + +const applicationStatusOptions: { label: string; value: TaskApplication["status"] }[] = [ + { label: "待应用", value: "TODO" }, + { label: "进行中", value: "DOING" }, + { label: "已完成", value: "DONE" }, +]; const selectedTask = computed(() => tasks.value.find((item) => item.taskId === selectedTaskId.value) || null @@ -62,6 +75,36 @@ async function loadTaskStats(taskNum: string) { } } +async function loadTaskApplications(taskNum: string) { + applicationsLoading.value = true; + try { + const res = await getTaskApplications(taskNum); + taskApplications.value = res?.data || []; + } catch (error: any) { + taskApplications.value = []; + ElMessage.error(error?.message || "应用场景加载失败"); + } finally { + applicationsLoading.value = false; + } +} + +const changeApplicationStatus = async (item: TaskApplication) => { + try { + await updateTaskApplication(item.id, { + title: item.title, + description: item.description, + resourceUrl: item.resourceUrl, + status: item.status, + }); + ElMessage.success("应用场景状态已更新"); + } catch (error: any) { + ElMessage.error(error?.message || "状态更新失败"); + if (selectedTask.value) { + await loadTaskApplications(selectedTask.value.taskNum); + } + } +}; + const fetchTasks = async () => { loading.value = true; try { @@ -91,10 +134,12 @@ const fetchTasks = async () => { selectedTaskId.value = nextSelectedTaskId; if (targetTask && shouldLoadStats) { await loadTaskStats(targetTask.taskNum); + await loadTaskApplications(targetTask.taskNum); } } catch (error: any) { tasks.value = []; selectedTaskId.value = null; + taskApplications.value = []; ElMessage.error(error?.message || "任务列表加载失败,请重试"); } finally { loading.value = false; @@ -105,6 +150,9 @@ watch(selectedTaskId, (newId) => { const task = tasks.value.find(t => t.taskId === newId); if (task) { loadTaskStats(task.taskNum); + loadTaskApplications(task.taskNum); + } else { + taskApplications.value = []; } }); @@ -229,6 +277,39 @@ onMounted(() => {
+ +
+

应用场景

+
+
+
+ {{ item.title }} + + + +
+

{{ item.description }}

+ + {{ item.resourceUrl }} + +
+
+

暂未设置应用场景

+
@@ -365,6 +446,51 @@ onMounted(() => { color: var(--text-primary); } +.application-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.application-item { + padding: 12px 0; + border-bottom: 1px solid var(--border-soft); +} + +.application-item:first-child { + padding-top: 0; +} + +.application-item:last-child { + border-bottom: 0; + padding-bottom: 0; +} + +.application-main { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +.application-main strong { + min-width: 0; + color: var(--text-primary); + word-break: break-word; +} + +.application-status { + width: 112px; + flex: 0 0 auto; +} + +.application-item p { + margin: 8px 0 0; + color: var(--text-secondary); + font-size: 13px; + word-break: break-word; +} + .action-card { padding: 16px; display: flex; @@ -404,5 +530,14 @@ onMounted(() => { .action-card { grid-template-columns: 1fr; } + + .application-main { + align-items: flex-start; + flex-direction: column; + } + + .application-status { + width: 100%; + } } diff --git a/src/components/TaskForm.vue b/src/components/TaskForm.vue index 57cc9e8..c3efc70 100644 --- a/src/components/TaskForm.vue +++ b/src/components/TaskForm.vue @@ -3,13 +3,22 @@ import { computed, onMounted, onUnmounted, ref } from "vue"; import request from "@/utils/request"; import { ElMessage } from "element-plus"; import { useRoute, useRouter } from "vue-router"; +import { + createTaskApplication, + deleteTaskApplication, + getTaskApplications, + updateTaskApplication, + type TaskApplication, +} from "@/api/tasks"; const router = useRouter(); const route = useRoute(); const taskId = route.params.taskId ? Number(route.params.taskId) : null; const buttonName = route.meta.buttonText as string; +const isUpdateMode = computed(() => route.meta.action === "update"); +const taskNum = ref(""); const taskName = ref(""); const taskDescription = ref(""); const materialURL = ref(""); @@ -22,6 +31,102 @@ const priority = ref({ }); const priorityOptions = [0, 1, 2, 3, 4, 5]; +const applicationStatusOptions: { label: string; value: TaskApplication["status"] }[] = [ + { label: "待应用", value: "TODO" }, + { label: "进行中", value: "DOING" }, + { label: "已完成", value: "DONE" }, +]; + +const applications = ref([]); +const applicationLoading = ref(false); +const applicationSaving = ref(false); +const editingApplicationId = ref(null); +const applicationForm = ref<{ + title: string; + description: string; + resourceUrl: string; + status: TaskApplication["status"]; +}>({ + title: "", + description: "", + resourceUrl: "", + status: "TODO", +}); + +const resetApplicationForm = () => { + editingApplicationId.value = null; + applicationForm.value = { + title: "", + description: "", + resourceUrl: "", + status: "TODO", + }; +}; + +const loadApplications = async (targetTaskNum: string) => { + applicationLoading.value = true; + try { + const res = await getTaskApplications(targetTaskNum); + applications.value = res?.data || []; + } catch (error: any) { + applications.value = []; + ElMessage.error(error?.message || "应用场景加载失败"); + } finally { + applicationLoading.value = false; + } +}; + +const saveApplication = async () => { + if (!taskNum.value) { + ElMessage.warning("任务编号未加载,暂不能维护应用场景"); + return; + } + if (!applicationForm.value.title.trim()) { + ElMessage.warning("应用项目标题不能为空"); + return; + } + applicationSaving.value = true; + try { + const payload = { + title: applicationForm.value.title, + description: applicationForm.value.description, + resourceUrl: applicationForm.value.resourceUrl, + status: applicationForm.value.status, + }; + if (editingApplicationId.value) { + await updateTaskApplication(editingApplicationId.value, payload); + ElMessage.success("应用场景已更新"); + } else { + await createTaskApplication(taskNum.value, payload); + ElMessage.success("应用场景已添加"); + } + resetApplicationForm(); + await loadApplications(taskNum.value); + } finally { + applicationSaving.value = false; + } +}; + +const editApplication = (item: TaskApplication) => { + editingApplicationId.value = item.id; + applicationForm.value = { + title: item.title || "", + description: item.description || "", + resourceUrl: item.resourceUrl || "", + status: item.status || "TODO", + }; +}; + +const removeApplication = async (item: TaskApplication) => { + await deleteTaskApplication(item.id); + ElMessage.success("应用场景已删除"); + if (editingApplicationId.value === item.id) { + resetApplicationForm(); + } + if (taskNum.value) { + await loadApplications(taskNum.value); + } +}; const createTask = () => { request @@ -46,6 +151,7 @@ const loadTask = () => { request.get(`/tasks/${taskId}`, {}).then((result) => { if (result.code === 200) { const data = result.data; + taskNum.value = data.taskNum || ""; taskName.value = data.taskName; taskDescription.value = data.taskDescription; materialURL.value = data.materialURL || data.materialUrl || ""; @@ -56,6 +162,9 @@ const loadTask = () => { futureValue: data.futureValue, subjectivePriority: data.subjectivePriority, }; + if (taskNum.value) { + loadApplications(taskNum.value); + } } else { ElMessage.error(`任务加载失败:${result.message}`); } @@ -169,6 +278,54 @@ const isMobile = computed(() => screenWidth.value <= 900); +
+

应用场景

+
+
+
+ {{ item.title }} + + {{ applicationStatusOptions.find(option => option.value === item.status)?.label || item.status }} + +
+

{{ item.description }}

+ + {{ item.resourceUrl }} + +
+ 编辑 + 删除 +
+
+
+

暂未设置应用场景

+ +
+ + + +
+ + + + + {{ editingApplicationId ? "更新" : "添加" }} + + 取消 +
+
+
+
{{ buttonName }} 取消 @@ -207,6 +364,67 @@ const isMobile = computed(() => screenWidth.value <= 900); grid-template-columns: 1fr; } +.application-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 14px; +} + +.application-item { + padding: 12px; + border: 1px solid var(--border-soft); + border-radius: 8px; + background: #fbfefc; +} + +.application-main { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} + +.application-main strong { + min-width: 0; + color: var(--text-primary); + word-break: break-word; +} + +.application-item p { + margin: 8px 0 0; + color: var(--text-secondary); + font-size: 13px; + word-break: break-word; +} + +.application-actions { + display: flex; + justify-content: flex-end; + gap: 4px; + margin-top: 8px; +} + +.application-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.application-form-row { + display: grid; + grid-template-columns: minmax(160px, 220px) auto auto; + gap: 10px; + align-items: center; + justify-content: start; +} + +.empty-text { + margin: 0 0 12px; + color: var(--text-secondary); + font-size: 13px; +} + .action-row { margin-top: 8px; display: flex; @@ -226,5 +444,19 @@ const isMobile = computed(() => screenWidth.value <= 900); width: 100%; margin: 0; } + + .application-main { + align-items: flex-start; + flex-direction: column; + } + + .application-form-row { + grid-template-columns: 1fr; + } + + .application-form-row .el-button { + width: 100%; + margin: 0; + } } diff --git a/src/router/index.ts b/src/router/index.ts index c8d27b5..144d744 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -32,6 +32,11 @@ const routes: RouteRecordRaw[] = [ component: () => import("@/components/ReviewDetail.vue"), meta: { requiresAuth: true, title: "复习详情" }, }, + { + path: "/review/recall/:taskNum", + component: () => import("@/components/ReviewRecall.vue"), + meta: { requiresAuth: true, title: "回忆复习" }, + }, { path: "/start-task/:taskNum", name: "start-task",