@tiku-canpoint/qp-component-ui
试题组件库 - 基于 Vue3,提供试题卡片和无头试题组件
1,814
| Install | |
|---|---|
npm install @tiku-canpoint/qp-component-ui |
|
| Latest Version: | 1.0.13 |
| Vue: | 3 |
| License: | MIT |
| Last Updated: | Sep 21, 2026 |
| Links: | npm |
Maintainer: canpoint-tiku
@tiku-canpoint/qp-component-ui
试题组件库 - 基于 Vue3,提供试题卡片和无头试题组件,支持 LaTeX 数学公式渲染。
安装
npm install @tiku-canpoint/qp-component-ui
# 或
pnpm add @tiku-canpoint/qp-component-ui
前置要求
- Vue 3.3.0+
完整引入
// main.ts
import { createApp } from 'vue'
import QpComponentUI from '@tiku-canpoint/qp-component-ui'
import '@tiku-canpoint/qp-component-ui/dist/style.css'
const app = createApp(App)
app.use(QpComponentUI)
app.mount('#app')
按需引入
<script setup>
import { QpQuestionCard } from '@tiku-canpoint/qp-component-ui'
</script>
组件
qp-question-card 试题卡片
试题卡片主容器组件,支持多种题型渲染、LaTeX 数学公式、答案解析显示、自定义插槽等。
功能特性
- ✅ 多题型支持:单选题、多选题、填空题、判断题、简答题、综合题、完形填空、听力题等
- ✅ LaTeX 公式渲染:自动加载 KaTeX,支持行内
$...$和块级$$...$$公式 - ✅ 答案解析:支持显示/隐藏答案、解析、考点、专题
- ✅ 子题支持:综合题自动渲染子题
- ✅ 自定义插槽:头部、底部、操作区域、标签区域均可自定义
- ✅ 响应式设计:适配不同屏幕尺寸
Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| questionData | Question |
- | 试题数据(必填) |
| questionNum | number |
1 |
题号 |
| showExplain | boolean |
false |
是否默认显示答案解析 |
| showKnowledge | boolean |
true |
是否显示考点 |
| showSpecialSubject | boolean |
true |
是否显示专题 |
| showTopInfo | boolean |
true |
是否显示顶部信息(试题 ID、题型、难度等) |
| showTags | boolean |
false |
是否显示标签 |
| showScore | boolean |
false |
是否显示分数 |
| showSource | boolean |
true |
是否显示试题来源 |
| metaInfoTop | MetaInfo[] |
- | 自定义顶部元信息 |
| className | string |
'' |
自定义类名 |
| onExpandExplain | (question: Question, isExpanded: boolean) => void | Promise<void> |
- | 展开答案解析时的回调函数,可用于异步加载试题详情 |
Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| toggle-explain | 切换答案解析时触发 | (visible: boolean) |
| click | 点击试题时触发 | (question: Question) |
Slots
| 插槽名 | 说明 | 插槽参数 |
|---|---|---|
| header | 自定义头部内容 | { question: Question } |
| footer | 自定义底部内容 | { question: Question } |
| bottomActionArea | 底部操作按钮区域 | { question: Question } |
| tagAction | 标签操作区域 | { question: Question, tags: TagInfo[] } |
基础用法
<template>
<qp-question-card
:questionData="questionData"
:questionNum="1"
:showExplain="false"
:showTopInfo="true"
/>
</template>
<script setup>
import { QpQuestionCard } from '@tiku-canpoint/qp-component-ui'
const questionData = {
questionId: 'Q001',
quesStruct: { code: 11, name: '单选题' },
quesType: { code: 11, name: '单选题' },
context: {
stem: '已知函数 $f(x) = x^2 - 2x + 1$,则 $f(2)$ 的值为( )',
options: ['$0$', '$1$', '$2$', '$4$'],
},
explain: [{
answers: [['B']],
analysis: '将 $x = 2$ 代入:$f(2) = 2^2 - 2 \\times 2 + 1 = 1$,故选 B。',
}],
knowledge: [{ code: 'K001', name: '函数求值' }],
difficulty: { code: 2, name: '中等' },
level: 2,
}
</script>
使用插槽
<template>
<qp-question-card :questionData="questionData">
<!-- 自定义头部 -->
<template #header="{ question }">
<span>难度:{{ question.difficulty?.name }}</span>
</template>
<!-- 自定义底部 -->
<template #footer="{ question }">
<span>试题 ID: {{ question.questionId }}</span>
</template>
<!-- 底部操作按钮 -->
<template #bottomActionArea="{ question }">
<button @click.stop="handleEdit(question)">编辑</button>
<button @click.stop="handleDelete(question)">删除</button>
</template>
</qp-question-card>
</template>
展开回调(异步加载详情)
当试题数据需要异步加载时,可以传入 onExpandExplain 回调函数,在点击展开答案解析时执行。常用于按需加载试题详情接口:
<template>
<qp-question-card
:questionData="questionData"
:questionNum="1"
:showExplain="false"
:onExpandExplain="handleExpandExplain"
/>
</template>
<script setup>
import { ref } from 'vue'
import { QpQuestionCard } from '@tiku-canpoint/qp-component-ui'
import { getQuestionDetail } from '@/api/question'
const questionData = ref({
questionId: '12345',
// ... 基础试题数据(可能不包含完整的答案解析)
})
// 展开答案解析时的回调 - 异步加载完整试题详情
const handleExpandExplain = async (question, isExpanded) => {
if (isExpanded && !question.explain) {
try {
// 调用接口获取完整的试题详情(包含答案解析)
const detail = await getQuestionDetail(question.questionId)
// 更新试题数据,组件会自动渲染答案解析
questionData.value = {
...questionData.value,
explain: detail.explain,
knowledge: detail.knowledge,
specialSubject: detail.specialSubject,
}
} catch (error) {
console.error('加载试题详情失败', error)
}
}
}
</script>
注意事项:
- 回调支持异步函数(Promise),组件会等待回调完成后再展开显示
- 如果回调执行失败(抛出异常),组件会捕获错误并继续展开
- 回调仅在展开时(isExpanded = true)触发,收起时不会触发
无头组件
useQuestion
无头试题组件,提供试题解析逻辑,不包含 UI 渲染。
功能特性
- ✅ 试题题干解析
- ✅ 答案解析生成
- ✅ 显示规则控制
- ✅ 数学公式渲染支持
- ✅ 展开回调(异步加载详情)
Options
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| question | Ref<Question | undefined> |
required |
试题数据(Ref 或 ComputedRef) |
| questionNum | number |
1 |
题号 |
| showExplain | boolean |
false |
是否默认显示答案解析 |
| showKnowledge | boolean |
true |
是否显示考点 |
| showSpecialSubject | boolean |
true |
是否显示专题 |
| showTopInfo | boolean |
true |
是否显示顶部信息 |
| showTags | boolean |
false |
是否显示标签 |
| showScore | boolean |
false |
是否显示分数 |
| showSource | boolean |
true |
是否显示试题来源 |
| metaInfoTop | MetaInfo[] |
- |
自定义顶部元信息 |
| onExpandExplain | (question: Question, isExpanded: boolean) => void | Promise<void> |
- |
展开答案解析时的回调函数,可用于异步加载试题详情 |
返回值
| 属性 | 类型 | 说明 |
|---|---|---|
| stem | UseQuestionStemReturn |
题干相关数据和计算属性 |
| answer | UseQuestionAnswerReturn |
答案相关数据和计算属性 |
| isShowExplain | Ref<boolean> |
是否显示答案解析 |
| isExpanding | Ref<boolean> |
是否正在展开(回调执行中) |
| toggleExplain | () => Promise<void> |
切换答案解析显示(支持异步回调) |
| katex | { isReady: Ref<boolean>; render: (el?) => Promise<void> } |
KaTeX 相关 |
| metaInfoList | ComputedRef<MetaInfo[]> |
元信息列表 |
| childrenList | ComputedRef<Question[]> |
子题列表 |
| showChildren | ComputedRef<boolean> |
是否显示子题 |
| levelLabel | ComputedRef<string> |
难度等级 |
stem 属性
| 属性 | 类型 | 说明 |
|---|---|---|
| stemHtml | string |
题干 HTML(含公式) |
| options | string[] |
选项列表 |
| showStem | boolean |
是否显示题干 |
| showOptions | boolean |
是否显示选项 |
| getOptionHtml | (option: string, index: number) => string |
获取选项 HTML |
answer 属性
| 属性 | 类型 | 说明 |
|---|---|---|
| answerList | string[] |
答案列表 |
| analysisList | string[] |
解析列表 |
| knowledgeList | Knowledge[] |
考点列表 |
| specialSubjectList | Knowledge[] |
专题列表 |
| commentList | string[] |
点评列表 |
用法
<template>
<div>
<!-- 题干 -->
<div v-if="stem.showStem" v-html="stem.stemHtml"></div>
<!-- 选项 -->
<div v-if="stem.showOptions">
<div v-for="(option, index) in stem.options" :key="index">
<span v-html="stem.getOptionHtml(option, index)"></span>
</div>
</div>
<!-- 答案 -->
<div>
<div v-for="(item, index) in answer.answerList" :key="index" v-html="item"></div>
</div>
<!-- 解析 -->
<div>
<div v-for="(item, index) in answer.analysisList" :key="index" v-html="item"></div>
</div>
<!-- 考点 -->
<div v-if="answer.knowledgeList.length">
【考点】
<span v-for="item in answer.knowledgeList" :key="item.code">{{ item.name }}</span>
</div>
</div>
</template>
<script setup>
import { useQuestion } from '@tiku-canpoint/qp-component-ui'
const questionData = {
questionId: 'Q001',
quesStruct: { code: 11, name: '单选题' },
context: {
stem: '已知函数 $f(x) = x^2$,则 $f(2)$ 的值为',
options: ['$0$', '$1$', '$2$', '$4$'],
},
explain: [{
answers: [['D']],
analysis: '将 $x = 2$ 代入:$f(2) = 2^2 = 4$',
}],
knowledge: [{ code: 'K001', name: '函数求值' }],
}
const { stem, answer } = useQuestion({ question: questionData })
</script>
展开回调(异步加载详情)
使用组合式函数时,可以传入 onExpandExplain 回调,在展开答案解析时执行异步操作:
<template>
<div ref="cardRef" class="question-card">
<!-- 题干 -->
<div v-if="stem.showStem" v-html="stem.stemHtml"></div>
<div v-if="stem.showOptions">
<div v-for="(option, index) in stem.options" :key="index">
<span v-html="stem.getOptionHtml(option, index)"></span>
</div>
</div>
<!-- 加载中提示 -->
<div v-if="isExpanding" class="loading">加载试题详情中...</div>
<!-- 答案解析 -->
<div v-if="isShowExplain">
<div v-for="item in answer.answerList" :key="item" v-html="item"></div>
</div>
<button @click="toggleExplain" :disabled="isExpanding">
{{ isExpanding ? '加载中...' : (isShowExplain ? '收起解析' : '展开解析') }}
</button>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useQuestion } from '@tiku-canpoint/qp-component-ui'
import { getQuestionDetail } from '@/api/question'
const cardRef = ref(null)
const questionData = ref({
questionId: '12345',
// ... 基础试题数据
})
const questionRef = computed(() => questionData.value)
// 展开回调 - 异步加载试题详情
const handleExpandExplain = async (question, isExpanded) => {
if (isExpanded && !question.explain) {
const detail = await getQuestionDetail(question.questionId)
questionData.value = {
...questionData.value,
explain: detail.explain,
knowledge: detail.knowledge,
specialSubject: detail.specialSubject,
}
}
}
const {
stem,
answer,
isShowExplain,
isExpanding, // 回调执行中为 true
toggleExplain,
katex,
} = useQuestion({
question: questionRef,
showExplain: false,
onExpandExplain: handleExpandExplain,
})
onMounted(() => {
katex.render(cardRef.value)
})
</script>
注意事项:
- 回调支持异步函数(Promise),组件会等待回调完成后再展开显示
- 如果回调执行失败(抛出异常),组件会捕获错误并继续展开
- 回调仅在展开时(isExpanded = true)触发,收起时不会触发
isExpanding属性可用于显示加载状态(回调执行中为 true)- 按钮可以使用
:disabled="isExpanding"防止重复点击
LaTeX 公式支持
组件自动渲染 LaTeX 数学公式,支持以下格式:
- 行内公式:
$...$或\(...\) - 块级公式:
$$...$$或\[...\]
预加载 KaTeX(可选)
使用 qp-katex-loader 组件提前加载 KaTeX,避免公式渲染延迟:
<template>
<qp-katex-loader>
<qp-question-card :questionData="questionData" />
</qp-katex-loader>
</template>
<script setup>
import { QpKatexLoader, QpQuestionCard } from '@tiku-canpoint/qp-component-ui'
</script>
自定义 CDN
<template>
<qp-katex-loader baseUrl="https://unpkg.com">
<qp-question-card :questionData="questionData" />
</qp-katex-loader>
</template>
使用本地 KaTeX
import katex from 'katex'
import renderMathInElement from 'katex/contrib/auto-render'
import { configure } from '@tiku-canpoint/qp-component-ui'
window.katex = katex
window.renderMathInElement = renderMathInElement
configure({ loadStrategy: 'none' })
数据类型
Question 试题数据
interface Question {
/** 试题 ID */
questionId: string;
/** 题型结构 */
quesStruct: {
code: number;
name: string;
};
/** 题型信息 */
quesType?: {
code: number;
name: string;
};
/** 试题上下文 */
context: {
/** 题干 */
stem: string;
/** 选项 */
options?: string[];
/** 听力原文 */
original_text?: string;
/** 音频 URL */
audioUrl?: string;
/** 音频名称 */
audioName?: string;
};
/** 解析 */
explain?: QuestionExplain[];
/** 考点 */
knowledge?: Knowledge[];
/** 专题 */
specialSubject?: Knowledge[];
/** 子题(综合题) */
children?: Question[];
/** 难度 */
difficulty?: {
code: number;
name: string;
};
/** 等级 */
level?: number;
/** 标签 */
tagPaper?: {
year?: number;
area?: string;
paperType?: string;
};
/** 来源 */
source?: string;
/** 分数 */
score?: number;
}
QuestionExplain 答案解析
interface QuestionExplain {
/** 答案(二维数组支持多选、多空) */
answers: string[][];
/** 解析(支持 HTML 和 LaTeX) */
analysis?: string;
/** 点评 */
comment?: string;
}
Knowledge 考点/专题
interface Knowledge {
code: string;
name: string;
}
MetaInfo 元信息
interface MetaInfo {
label: string;
value: string;
}
支持题型
| 题型代码 | 题型名称 | 说明 |
|---|---|---|
| 11 | 单选题 | 单个正确选项的选择题 |
| 12 | 多选题 | 多个正确选项的选择题 |
| 13 | 填空题 | 需要填写空白的题目 |
| 14 | 判断题 | 判断对错的题目 |
| 15 | 简答题 | 需要简要回答的题目 |
| 16 | 多题综合类 | 包含多个子题的综合题 |
| 17 | 多题综合类(同一知识点) | 同一知识点下的综合题 |
| 18 | 解答题 | 需要详细解答的题目 |
| 19 | 听力多题 | 包含听力材料的综合题 |
| 20-22 | 听力单题 | 听力单选/填空/判断 |
| 23 | 完形填空 | 完形填空类题目 |
| 24 | 综合类填空 | 阅读理解类填空 |
| 25 | 七选五 | 七选五类阅读理解 |
| 26 | 作文 | 作文类题目 |
| 27-28 | 选择填充/四选二 | 特殊选择题型 |
Related Packages
vue3-math-editor
A semantic equation editor for Vue 3: WYSIWYG equations rendered with KaTeX, exp...
3,308
0
Version History
| Version | Released | License |
|---|---|---|
| 1.0.13 | MIT | |
| 1.0.12 | MIT | |
| 1.0.11 | MIT | |
| 1.0.10 | MIT | |
| 1.0.9 | MIT | |
| 1.0.8 | MIT | |
| 1.0.7 | MIT | |
| 1.0.6 | MIT | |
| 1.0.2 | MIT | |
| 1.0.1 | MIT | |
| 1.0.0 | MIT |