npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tiku-canpoint/qp-component-ui

v1.0.13

Published

试题组件库 - 基于 Vue3,提供试题卡片和无头试题组件

Readme

@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 | 选择填充/四选二 | 特殊选择题型 |