hep-mistake
v1.2.31
Published
习题课
Readme
hep-mistake 使用文档
hep-mistake 是基于 Vue 3、Element Plus 的习题练习组件包,提供答题页、自由练习、错题本、AI 助手与拖拽容器等组件。
安装与注册
npm install hep-mistakeimport { createApp } from 'vue'
import ElementPlus from 'element-plus'
import HepMistake from 'hep-mistake'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.use(HepMistake)
app.mount('#app')插件会全局注册以下组件:
| 组件名 | 说明 |
| --- | --- |
| ExerciseToPay | 答题、判分、交卷主组件 |
| Practice | 练习入口组件 |
| freePractice | 自由练习配置组件 |
| notebook | 错题本组件 |
| Intelligent | AI 助手组件 |
| DraggingSlot | 可拖拽插槽容器 |
ExerciseToPay
答题页主组件,负责题目渲染、上一题/下一题、检查答案、AI 判分、错题本、提交答题结果。
<template>
<ExerciseToPay
:optionsStyle="optionsStyle"
:questionUuidList="questionUuids"
:getQuestionInfo="getQuestionInfo"
:getStatic="getStatic"
:historyHandler="historyHandler"
:fitbAiScoreFn="fitbAiScoreFn"
:upLoadFileSuccess="upLoadFileSuccess"
:submitCorrectionFn="submitCorrectionFn"
:feedbackType="feedbackType"
@questionAddFn="questionAddFn"
@questionAllAddFn="questionAllAddFn"
@addMistakeBook="addMistakeBook"
/>
</template>Props
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| optionsStyle | ExerciseToPayStyle | 否 | 页面样式配置 |
| questionUuidList | Array<string \| Question> | 是 | 题目 UUID 列表;答题过程中会被替换为题目对象 |
| getQuestionInfo | (questionUuid: string) => Promise<Question> | 是 | 根据题目 UUID 获取题目详情 |
| getStatic | (questionUuid: string) => Promise<{ data: QuestionStatic }> | 是 | 获取单题统计 |
| historyHandler | (questionUuid: string) => Promise<HistoryAnswer> | 是 | 回显历史答题记录 |
| fitbAiScoreFn | (question: Question, onMessage?: (text: string) => void) => Promise<AiScoreResponse> | 是 | 填空题/问答题 AI 判分;问答题支持流式回调 |
| upLoadFileSuccess | (file: File) => Promise<string> | 是 | 编辑器图片/媒体上传 |
| submitCorrectionFn | (data: CorrectionForm) => Promise<any> | 是 | 提交纠错 |
| feedbackType | FeedbackType | 是 | 练习反馈模式、标题、倒计时等 |
Events
| 事件 | 参数 | 触发时机 |
| --- | --- | --- |
| questionAddFn | (data: QuestionSubmit, cardUpload?: Record<string, string>) | 单题检查答案后 |
| questionAllAddFn | (data: any, cardUpload?: Record<string, string>) | 全卷提交后 |
| addMistakeBook | (questionUuids: string[]) | 点击加入错题本 |
optionsStyle
interface ExerciseToPayStyle {
bodyBgColor?: string
width?: string | number
titleFontSize?: string | number
titleColor?: string
cardColor?: string
boxShadow?: string
titleFontWeight?: string | number
}feedbackType
interface FeedbackType {
feedbackWay: 1 | 2 | 3 | 4
paperTime?: number | null
workName: string
questionCard?: Record<string, string>
}feedbackWay 说明:
| 值 | 说明 |
| --- | --- |
| 1 | 单题模式,检查当前题后显示下一题 |
| 2 | 全题模式,可整卷提交 |
| 3 | 背题/只看答案模式 |
| 4 | 带答题卡回显模式 |
getQuestionInfo 返回结构
组件内部依赖题目对象中的以下字段。不同题型可以包含额外字段。
interface Question {
id: number | string
questionUuid: string
questionDbId: string
questionTypeBase:
| 'dan_xuan'
| 'duo_xuan'
| 'pan_duan'
| 'tian_kong'
| 'wen_da'
| 'lian_xian'
| 'pai_xu'
| 'wan_xing_tian_kong'
| 'zong_he'
| 'ting_li'
questionTypeName?: string
subjectEditorValue: string
answerEditorValue: string
explainEditorValue?: string
knowledge?: Array<{ knowledgeId: string; knowledgeName: string }>
options?: any[]
answers: QuestionAnswers
subQuestionList?: Question[]
attachObject?: any
questionStatic?: QuestionStatic
}
interface QuestionAnswers {
questionUuid?: string
answer: any
answerObj?: any
isAnswered?: boolean
isRight?: 'true' | 'false' | 'trueFalse' | 'none'
isRemake?: boolean
noIsMark?: boolean
useTime?: number
qAnswer?: any
upFileList?: Array<{ url: string; type: 'img' | 'media' | string; name?: string }>
student_answer_image?: string[]
AIMessage?: {
content: Array<{
text: {
value: string | object
}
}>
}
}getStatic 返回结构
interface QuestionStaticResponse {
data: {
allnums: number
allrights: number
}
}historyHandler 返回结构
返回历史答题记录。组件会将其合并到题目 answers 中,常见字段如下:
interface HistoryAnswer {
isRight?: 'true' | 'false' | 'trueFalse' | 'none'
isAnswered?: boolean
answer?: any
answerObj?: any
userAnswer?: string
useTime?: number
smallQuestionList?: any[]
}fitbAiScoreFn 接口
用于填空题和问答题自动判分。组件会在 questionTypeBase 为 tian_kong 或 wen_da 时调用。
后端调用填空题 AI 判分服务时,接口地址、请求结构、响应解包、结果校验和异常处理详见 AI 填空题判分接入文档。
type FitbAiScoreFn = (
question: Question,
onMessage?: (text: string) => void
) => Promise<AiScoreResponse>问答题流式输出时,onMessage 接收接口累计文本。页面展示时只读取 JSON 中的 reasoning 字段。
返回示例:
interface AiScoreResponse {
data?: {
contentList?: string[]
message?: {
content?: Array<{
text: {
value: string | AiScoreValue
}
}>
}
}
}
interface AiScoreValue {
score?: number
full_score?: number
grade?: string
reasoning?: string
results?: Array<{
score?: number
reasoning?: string
}>
}问答题判定规则:
| 条件 | answers.isRight |
| --- | --- |
| score 存在且等于 full_score | true |
| 未返回 score | false |
| score === 0 | false |
| 其它有分未满分 | trueFalse |
问答题流式 SSE 封装示例:
import { fetchEventSource } from '@microsoft/fetch-event-source'
export async function getImgAnswerScore(
data: {
prompt: string
questionDbId: string
questionUuid: string
},
onMessage?: (text: string) => void
) {
let fullText = ''
await fetchEventSource('/api/api/public/qa/getImgAnswerScore', {
method: 'POST',
headers: {
Accept: 'text/event-stream',
'Content-Type': 'application/json',
Authorization: 'Bearer <token>'
},
body: JSON.stringify(data),
onmessage(event) {
const eventData = JSON.parse(event.data)
const chunk =
eventData?.choices?.[0]?.delta?.content ??
eventData?.content ??
''
if (!chunk) return
fullText += chunk
onMessage?.(fullText)
}
})
return {
data: {
contentList: [fullText],
message: {
content: [
{
text: {
value: fullText
}
}
]
}
}
}
}开发环境中如果 Vite 代理配置会把浏览器侧 /api 前缀 rewrite 掉,浏览器请求 /api/api/public/qa/getImgAnswerScore 后,后端实际收到的是 /api/public/qa/getImgAnswerScore。
upLoadFileSuccess 接口
type UpLoadFileSuccess = (file: File) => Promise<string>返回上传后的文件 URL 或路径。编辑器会将该地址插入答题内容,并记录到 answers.upFileList。
submitCorrectionFn 接口
type SubmitCorrectionFn = (data: CorrectionForm) => Promise<any>
type CorrectionType = '题干有误' | '答案有误' | '解析有误' | '其他' | '选项有误'
interface CorrectionForm {
questionUuid: string
questionDbId: string
checkedLabelList: CorrectionType[]
remarks?: string
}freePractice
自由练习配置组件。组件根据题库知识点、难度、题型数量生成练习,并打开答题页。
<freePractice
:getList="getKnowledgeList"
:loadNode="loadKnowledgeNode"
:getDifficult="getDifficult"
:Guaher="getQuestionTypes"
:paotNum="getQuestionTypeCount"
:submit="createPracticeQuestions"
:practiceList="savePractice"
@paramFormNum="onParamForm"
@practiceForm="onPracticeForm"
/>Props 接口
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| getList | () => Promise<{ data: Knowledge[] }> | 获取知识点树根节点 |
| loadNode | (node: any, resolve: Function) => void \| Promise<void> | 懒加载知识点子节点 |
| getDifficult | () => Promise<{ data: Difficult[] }> | 获取难度列表 |
| Guaher | () => Promise<{ data: QuestionType[] }> | 获取题型列表;当前代码中保留但未启用 |
| paotNum | () => Promise<{ data: QuestionTypeCount[] }> | 获取所选条件下各题型可用数量 |
| submit | () => Promise<{ data: { questionUuids: string[] } }> | 按当前条件抽题 |
| practiceList | (form: PracticeCreateForm) => Promise<{ data: { testId: string } }> | 保存练习记录并返回 testId |
Events
| 事件 | 参数 | 说明 |
| --- | --- | --- |
| paramFormNum | PracticeParamForm | 查询题型数量、开始练习前抛出当前条件 |
| practiceForm | PracticeCreateForm | 保存练习前抛出最终练习表单 |
数据结构
interface Knowledge {
id: string | number
parentId?: string | number
name: string
children?: Knowledge[]
}
interface Difficult {
id: string | number
name: string
}
interface QuestionTypeCount {
questionTypeBase?: string
questionTypeName?: string
questionTypeNum: number
questionNum?: number | ''
}
interface PracticeParamForm {
workName: string
practiceType: number
feedbackWay: number
questionDbId: string
questionRange: number
knowledgeIds: Array<string | number>
basicDifficult: Array<string | number>
questions?: QuestionTypeCount[]
paperTime?: number
isTiming?: 0 | 1 | boolean
}
interface PracticeCreateForm extends PracticeParamForm {
questionUuids: string
client: 'pc' | string
}notebook
错题本组件。用于展示常错知识点、错题列表、按条件查询、删除错题、从错题生成练习。
<notebook
:getList="getWrongKnowledge"
:noteList="getWrongQuestionList"
:mistakesList="deleteWrongQuestions"
:mistakesType="getWrongQuestionTypes"
:practiceList="createWrongPractice"
:practionList="createKnowledgePractice"
@noteForm="onNoteQuery"
@practForm="onKnowledgePracticeForm"
@practiceForm="onPracticeForm"
@mistakesForm="onDeleteWrongQuestions"
/>Props 接口
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| getList | () => Promise<{ data: WrongKnowledge[] }> | 获取常错知识点 |
| noteList | (query: WrongQuestionQuery) => Promise<{ data: WrongQuestion[]; total: number }> | 查询错题列表 |
| mistakesList | (questionUuids: string[]) => Promise<any> | 删除错题 |
| mistakesType | () => Promise<{ data: QuestionType[] }> | 获取错题题型 |
| practiceList | (form: WrongPracticeForm) => Promise<{ data: { testId: string } }> | 从选中错题创建练习/背题 |
| practionList | (form: KnowledgePracticeForm) => Promise<{ data: { testId: string } }> | 从常错知识点创建单题练习 |
Events
| 事件 | 参数 | 说明 |
| --- | --- | --- |
| noteForm | WrongQuestionQuery | 查询错题列表前抛出查询条件 |
| practForm | KnowledgePracticeForm | 从知识点生成练习前抛出表单 |
| practiceForm | WrongPracticeForm | 从错题生成练习前抛出表单 |
| mistakesForm | string[] | 删除错题前抛出题目 UUID 列表 |
数据结构
interface WrongKnowledge {
knowledgeId: string
name: string
count?: number
}
interface WrongQuestionQuery {
questionTypeName?: string
inquire?: string
basicDifficultName?: string
timeOrder?: string
startTime?: string
endTime?: string
pageNum: number
pageSize: number
questionDbId: string
}
interface WrongQuestion {
questionUuid: string
questionJsonContent: string
questionTypeName?: string
basicDifficultName?: string
createTime?: string
checked?: boolean
}
interface WrongPracticeForm {
practiceType: 6
questionNum: number
questionUuids: string[]
questionDbId: string
exerciseTpye: '1' | '2' | string
feedbackWay: 1 | 3
client: 'pc'
}
interface KnowledgePracticeForm {
knowledgeId: string
workName: string
questionDbId?: string
}Intelligent
AI 助手组件。当前组件通过 props 接收外部接口函数,不直接绑定后端。
建议按以下形式接入:
<Intelligent
:currenOptions="{ questionDbId, questionUuid }"
:set_ai_answer_add="saveAiAnswer"
:get_img2latex="img2latex"
:get_has_ai_times="getHasAiTimes"
:set_model_dialog_add="saveModelDialog"
:eve_State="0"
:imgUser="imgUser"
@close="visible = false"
/>常用接口:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| get_has_ai_times | () => Promise<any> | 获取当前题 AI 可用次数 |
| set_model_dialog_add | (data: any) => Promise<any> | 保存模型对话 |
| set_ai_answer_add | (data: any) => Promise<any> | 保存 AI 回答 |
| get_img2latex | (data: any) => Promise<any> | 图片转 LaTeX |
DraggingSlot
可拖拽容器,需要通过 dragHeader 插槽绑定拖拽事件。
<DraggingSlot :windowSize="{ width: 80, height: 80 }">
<template #dragHeader="{ startDrag, onDragging }">
<div @mousedown="startDrag" @mousemove="onDragging">
拖拽我
</div>
</template>
</DraggingSlot>| 参数 | 类型 | 说明 |
| --- | --- | --- |
| windowSize | { width: number; height: number } | 拖拽元素尺寸 |
注意事项
- 组件依赖 Vue 3、Element Plus、Vue Router、MathJax/TinyMCE 相关运行环境。
- 题目详情字段需要与组件内部题型渲染结构一致,尤其是
questionTypeBase、answers、options。 - 问答题 AI 判分结果页面只展示
reasoning字段;最终判分依赖score与full_score。 - 开发环境使用 Vite 代理时,注意浏览器请求路径和后端真实路径可能不同。
- 组件内部部分页面会通过
window.open跳转到/paperOnline/#/hepMistake?testId=...,宿主项目需要提供对应路由。
