@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

katex-vue3

katex use vue3

2,037
vue3-math-editor

A semantic equation editor for Vue 3: WYSIWYG equations rendered with KaTeX, exp...

3,308 0
vue-katex

Vue plugin for KaTeX

30,224 128
vue-katex-auto-render

A KaTeX auto render library for vue directive.

4,869 6
@hsorby/vue3-katex

Vue 3 plugin for KaTeX

26,276 128

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