questionId: Ref
+ examId?: Ref
+ subjectId?: Ref
isLandscape?: Ref
+ taskType?: Ref
}
function createMarkingData(options: UseMarkingDataOptions) {
- const { taskId, questionId, isLandscape } = options
+ const { taskId, questionId, examId, subjectId, isLandscape, taskType } = options
// 基础数据
const questionData = ref([])
@@ -33,6 +37,8 @@ function createMarkingData(options: UseMarkingDataOptions) {
const markingStartTime = ref(0)
const mode = ref<'single' | 'multi'>('single')
+ const queryClient = useQueryClient()
+
// 当前分数
const firstNotScoredIndex = computed(() => {
return 0
@@ -59,7 +65,23 @@ function createMarkingData(options: UseMarkingDataOptions) {
queryKey: computed(() => ['marking-question', taskId.value]),
queryFn: async () => examMarkingTaskApi.questionDetail(taskId.value),
enabled: computed(() => !!taskId.value),
+ gcTime: 0,
})
+
+ // 获取题目列表数据(用于Layout显示)
+ const {
+ data: questionsListData,
+ refetch: refetchQuestionsList,
+ } = useQuery({
+ queryKey: computed(() => ['marking-questions-list', examId?.value, subjectId?.value]),
+ queryFn: async () => examMarkingTaskApi.byQuestionList({
+ exam_id: examId!.value!,
+ exam_subject_id: subjectId!.value!,
+ }) as unknown as Promise,
+ enabled: computed(() => !!examId?.value && !!subjectId?.value),
+ placeholderData: [],
+ })
+
// 获取平均分对比
const {
data: avgScoreData,
@@ -101,6 +123,24 @@ function createMarkingData(options: UseMarkingDataOptions) {
}
}
+ // 当前任务信息(用于获取总数)
+ const currentTaskInfo = computed(() => {
+ if (!questionId.value || !taskId.value || !questionsListData.value.length)
+ return null
+
+ // 找到当前题目
+ const currentQuestion = questionsListData.value.find(q => q.question_id === questionId.value)
+ if (!currentQuestion)
+ return null
+
+ return currentQuestion.tasks?.[taskType.value]
+ })
+
+ // 总题目数量(从任务中获取)
+ const totalQuestions = computed(() => {
+ return currentTaskInfo.value?.task_quantity || 0
+ })
+
// 监听数据变化
watch(() => questionResponse.value, processQuestionData, { immediate: true })
@@ -132,6 +172,7 @@ function createMarkingData(options: UseMarkingDataOptions) {
if (response) {
isSubmitted.value = true
// 重新获取下一题
+ questionData.value = []
await refetchQuestion()
processQuestionData()
return response
@@ -162,7 +203,9 @@ function createMarkingData(options: UseMarkingDataOptions) {
if (response) {
isSubmitted.value = true
+ currentTaskInfo.value!.marked_quantity = (currentTaskInfo.value!.marked_quantity || 0) + 1
// 重新获取下一题
+ questionData.value = []
await refetchQuestion()
processQuestionData()
return response
@@ -184,15 +227,19 @@ function createMarkingData(options: UseMarkingDataOptions) {
await Promise.all([
refetchQuestion(),
refetchAvgScore(),
+ refetchQuestionsList(),
])
}
return {
// 数据状态
questionData,
+ questionsList: computed(() => questionsListData.value || []), // 题目列表数据
currentMarkingSubmitData,
currentScore,
firstNotScoredIndex,
+ totalQuestions, // 总题目数量
+ currentTaskInfo,
isLoading: computed(() => isQuestionLoading.value || isLoading.value),
isSubmitted: readonly(isSubmitted),
mode,
@@ -211,6 +258,7 @@ function createMarkingData(options: UseMarkingDataOptions) {
reload,
refetchQuestion,
refetchAvgScore,
+ refetchQuestionsList,
}
}
diff --git a/src/components/marking/composables/useMarkingSettings.ts b/src/components/marking/composables/useMarkingSettings.ts
index 2864b56..78983a5 100644
--- a/src/components/marking/composables/useMarkingSettings.ts
+++ b/src/components/marking/composables/useMarkingSettings.ts
@@ -124,7 +124,7 @@ export function generateOnekeyScores(stepSize: number, fullScore: number): numbe
export function getSelectedOnekeyScores(
stepSize: number,
fullScore: number,
- selections: Record,
+ selections: Record = {},
): number[] {
const allScores = generateOnekeyScores(stepSize, fullScore)
const selectionKey = `${stepSize}_${fullScore}`
diff --git a/src/components/marking/review/ReviewHeader.vue b/src/components/marking/review/ReviewHeader.vue
new file mode 100644
index 0000000..5603525
--- /dev/null
+++ b/src/components/marking/review/ReviewHeader.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
diff --git a/src/components/marking/review/ReviewImageRenderer.vue b/src/components/marking/review/ReviewImageRenderer.vue
new file mode 100644
index 0000000..ffa9097
--- /dev/null
+++ b/src/components/marking/review/ReviewImageRenderer.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 点击编辑
+
+
+
+
+
+
+
+
+
+
+ 暂无图片
+
+
+
+
+
+
diff --git a/src/components/marking/review/ScoreEditDialog.vue b/src/components/marking/review/ScoreEditDialog.vue
new file mode 100644
index 0000000..a77bbcc
--- /dev/null
+++ b/src/components/marking/review/ScoreEditDialog.vue
@@ -0,0 +1,499 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ editScore }}
+ /
+ {{ fullScore }}
+
+ 当前分数
+
+
+
+
+
+
+ -1
+
+
+
+
+
+
+
+ +1
+
+
+
+
+
+ 快捷分数
+
+
+ {{ score }}
+
+
+
+
+
+
+
+ 取消
+
+
+ 确定修改
+
+
+
+
+
+
+
diff --git a/src/pages.json b/src/pages.json
index 08f8ad3..a6cd48f 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -154,6 +154,13 @@
"style": {
"navigationBarTitleText": "阅卷监控"
}
+ },
+ {
+ "path": "pages/marking/review",
+ "type": "page",
+ "style": {
+ "navigationBarTitleText": "回评"
+ }
}
],
"subPackages": []
diff --git a/src/pages/marking/detail.vue b/src/pages/marking/detail.vue
index 11c1c9d..8a36f22 100644
--- a/src/pages/marking/detail.vue
+++ b/src/pages/marking/detail.vue
@@ -103,7 +103,7 @@ async function handleStartMarking(task: any) {
// 跳转到阅卷界面
uni.navigateTo({
- url: `/pages/marking/grading?examId=${examId.value}&subjectId=${subjectId.value}&questionId=${task.question_id}&taskId=${task.id}&type=${evaluationType}`,
+ url: `/pages/marking/grading?examId=${examId.value}&subjectId=${subjectId.value}&questionId=${task.question_id}&taskId=${task.id}&type=${evaluationType}&taskType=${task.task_type}`,
})
}
catch (error) {
@@ -115,32 +115,11 @@ async function handleStartMarking(task: any) {
}
}
-// 回评
-async function handleRemark(task: any) {
- try {
- await uni.showModal({
- title: '确认回评',
- content: '回评将清除已阅记录,是否继续?',
- })
-
- uni.showToast({
- title: '回评功能开发中',
- icon: 'none',
- })
-
- // TODO: 实现回评接口调用
- // 刷新数据
- refetch()
- }
- catch (error: any) {
- if (error.errMsg !== 'showModal:fail cancel') {
- console.error('回评失败:', error)
- uni.showToast({
- title: '回评失败',
- icon: 'none',
- })
- }
- }
+// 查看回评
+async function handleViewReview(task: any) {
+ uni.navigateTo({
+ url: `/pages/marking/review?examId=${examId.value}&subjectId=${subjectId.value}&questionId=${task.question_id}&taskId=${task.id}`,
+ })
}
// 刷新数据
@@ -217,12 +196,12 @@ function handleRefresh() {
-
+
回评
diff --git a/src/pages/marking/grading.vue b/src/pages/marking/grading.vue
index 5be823e..8f95ef8 100644
--- a/src/pages/marking/grading.vue
+++ b/src/pages/marking/grading.vue
@@ -8,6 +8,8 @@
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+ 加载失败
+
+ 重试
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 暂无图片
+
+
+
+
+
+
+
+ 加载更多
+
+
+
+
+
+ 没有更多数据了
+
+
+
+
+
+
+
+ 暂无历史记录
+ 请调整筛选条件
+
+
+
+
+
+
+
+
+
+
diff --git a/src/utils/http.ts b/src/utils/http.ts
index 2e30615..179a281 100644
--- a/src/utils/http.ts
+++ b/src/utils/http.ts
@@ -1,3 +1,4 @@
+/* eslint-disable style/operator-linebreak */
/**
* 桌面端 HTTP 客户端
* 基于现有的 uni.request 架构,自动注入 baseURL: api.xianglexue.com
@@ -10,12 +11,44 @@ import { http, httpDelete, httpGet, httpPost, httpPut } from '@/http/http'
// 桌面端 baseURL
const DESKTOP_BASE_URL = 'http://api.xianglexue.com/api/v1'
+interface ControllerResponse {
+ code?: number
+ data?: T
+}
+
+// 重写 ModelPageResponse 以支持泛型,避免 any[] 覆盖问题
+interface TypedModelPageResponse {
+ /** 是否有更多数据 */
+ has_more?: boolean
+ /** 当前页数据列表 */
+ list?: T[]
+ /** 当前页 */
+ page?: number
+ /** 每页数量 */
+ page_size?: number
+ /** 总记录数 */
+ total?: number
+}
+
+// 检测是否为分页响应类型
+type IsPageResponse = T extends { list?: any[], page?: number, total?: number } ? true : false
+
+// 优化的类型解压,智能处理分页响应
+type ExtractData = T extends ControllerResponse
+ ? IsPageResponse extends true
+ ? // 如果是分页响应,保持原始结构但替换 ModelPageResponse 为 TypedModelPageResponse
+ U extends { list?: infer L }
+ ? Omit & { list?: L }
+ : U
+ : U
+ : T
+
/**
* 桌面端 GET 请求
*/
function desktopHttpGet(url: string, query?: Record, header?: Record, options?: Partial) {
const desktopUrl = url.startsWith('http') ? url : `${DESKTOP_BASE_URL}${url}`
- return httpGet(desktopUrl, query, header, options)
+ return httpGet>(desktopUrl, query, header, options)
}
/**
@@ -23,7 +56,7 @@ function desktopHttpGet(url: string, query?: Record, header?: Re
*/
function desktopHttpPost(url: string, data?: Record, query?: Record, header?: Record, options?: Partial) {
const desktopUrl = url.startsWith('http') ? url : `${DESKTOP_BASE_URL}${url}`
- return httpPost(desktopUrl, data, query, header, options)
+ return httpPost>(desktopUrl, data, query, header, options)
}
/**
@@ -31,7 +64,7 @@ function desktopHttpPost(url: string, data?: Record, query?: Rec
*/
function desktopHttpPut(url: string, data?: Record, query?: Record, header?: Record, options?: Partial) {
const desktopUrl = url.startsWith('http') ? url : `${DESKTOP_BASE_URL}${url}`
- return httpPut(desktopUrl, data, query, header, options)
+ return httpPut>(desktopUrl, data, query, header, options)
}
/**
@@ -39,7 +72,7 @@ function desktopHttpPut(url: string, data?: Record, query?: Reco
*/
function desktopHttpDelete(url: string, query?: Record, header?: Record, options?: Partial) {
const desktopUrl = url.startsWith('http') ? url : `${DESKTOP_BASE_URL}${url}`
- return httpDelete(desktopUrl, query, header, options)
+ return httpDelete>(desktopUrl, query, header, options)
}
/**
@@ -189,4 +222,7 @@ class DesktopHttpClient {
// 创建桌面端 HTTP 客户端实例
const httpClient = new DesktopHttpClient()
+// 导出类型和客户端
+export { TypedModelPageResponse }
+export type { ExtractData }
export default httpClient