Appearance
题目组件
@qxs-bns/components-wc 提供了一套完整的题目 Web Components,涵盖题型选择、题目列表容器及各类题目编辑/预览组件。
题目内核建议使用 @qxs-bns/components-wc/core 引入;如需题干富文本,另外再引入 @qxs-bns/components-wc/editor。
引入与使用
ts
import '@qxs-bns/components-wc/core'vue
<qxs-subject-list
:subject-list.prop="subjectList"
:show-add.prop="false"
@change="handleChange"
/>在 Vue 3 中,给 Web Components 传数组、对象、函数或显式的布尔值时,建议直接使用连字符属性名配合 .prop,例如 :subject-list.prop="subjectList"、:show-add.prop="false"。不要再使用 .camel.prop。
组件演示
loading
当前推荐用法
当前这套题目组件,推荐把 qxs-subject-list 作为主入口使用。
推荐组合
| 场景 | 推荐组合 | 说明 |
|---|---|---|
| 完整出题页 | qxs-subject-type + qxs-subject-list | 推荐默认方案。题型选择、题目增删、保存导出、业务事件都在列表层汇总 |
| 只编辑某一题 | qxs-subject-single / qxs-blank-fill / qxs-text-fill / qxs-scale | 适合自定义外层容器或只嵌入某一个题型 |
| 只做预览 | qxs-subject-list + is-preview | 统一走列表容器,隐藏编辑控件 |
宿主层职责
- 引入
@qxs-bns/components-wc/core注册题目核心组件(列表、题型组件、通用动作组件)。 - 如需题干富文本能力,再引入
@qxs-bns/components-wc/editor注册qxs-blocksuite-editor。 - 通过
subject-list传入题目数组,并在change事件里把最新列表同步回宿主状态。 - 把标签、分类、AI 推荐答案、题库搜索、跳题、关联检查这些业务能力接到自己的接口或弹窗上。
- 提交前优先调用
validate(),最终提交调用toJSON()。
提示:如果你使用 Vue 3,给 Web Components 传复杂类型时,建议直接使用连字符属性名配合
.prop,例如:subject-list.prop="subjectList"、:category-list.prop="categoryList"、:search-handler.prop="searchHandler"。原生 JS 请使用el['subject-list'] = value这类写法。
Vue 3 快速开始
vue
<script setup lang="ts">
import { ref } from 'vue'
import '@qxs-bns/components-wc/core'
import '@qxs-bns/components-wc/editor'
const listRef = ref<any>(null)
const subjectList = ref<any[]>([])
const categoryList = ref([
{ categoryId: 'c1', title: '慢病管理' },
{ categoryId: 'c2', title: '健康行为' },
])
function handleSelect(e: CustomEvent) {
const { type, canSet } = e.detail
listRef.value?.addSubject(type, undefined, canSet ? 1 : 0)
}
function handleChange(e: CustomEvent) {
subjectList.value = [...e.detail]
}
async function handleSubmit() {
const result = await listRef.value?.validate()
if (!result?.valid) {
console.error('校验失败:', result.errors[0])
return
}
const data = await listRef.value?.toJSON()
console.log('提交数据:', data)
}
</script>
<template>
<qxs-subject-type @select="handleSelect" />
<qxs-subject-list
ref="listRef"
:subject-list.prop="subjectList"
:category-list.prop="categoryList"
:show-add.prop="false"
show-tag
show-category
show-ai
@change="handleChange"
/>
<button type="button" @click="handleSubmit">提交</button>
</template>常用流程
- 页面初始化时,宿主层准备
subjectList、categoryList、searchHandler等输入数据。 - 用户通过
qxs-subject-type选择题型后,调用listRef.addSubject()新增题目。 - 题目列表内部发生编辑、删除、新增、标签回填等变化时,监听
change同步外部subjectList。 - 宿主层按需监听
choose-tag、title-select、jump、set-relation这些业务事件,打开自己的弹窗或调用自己的接口。 - 提交前先调用
validate();通过后再调用toJSON()获取可直接落库的数据。
qxs-subject-type 题型选择器
题型选择器组件,提供可用题型列表供用户选择。
基本用法
html
<qxs-subject-type></qxs-subject-type>事件
| 事件名 | Detail | 说明 |
|---|---|---|
select | { type: string, canSet: boolean } | 选择题型时触发 |
支持题型
| type 值 | 题型名称 | 支持结果项 |
|---|---|---|
single | 单选题 | ✅ |
multiple | 多选题 | ❌ |
sort | 排序题 | ❌ |
blank_fill | 填空题 | ❌ |
text_fill | 问答题 | ❌ |
scale | 量表题 | ❌ |
page_end | 分页符 | ❌ |
示例
vue
<script setup>
function handleSelect(e) {
const { type, canSet } = e.detail
const examAnswerRelationType = canSet ? 1 : 0
subjectListRef.value?.addSubject(type, null, examAnswerRelationType)
}
</script>
<template>
<qxs-subject-type @select="handleSelect" />
</template>qxs-subject-list 题目列表
题目列表容器组件,支持拖拽排序和批量操作。
基本用法
html
<qxs-subject-list
:subject-list.prop="subjectList"
:category-list.prop="categoryList"
:show-add.prop="false"
@change="handleChange"
></qxs-subject-list>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
subject-list | any[] | [] | 题目列表数据(JS property 赋值) |
is-preview | boolean | false | 预览模式(隐藏编辑控件) |
category-list | any[] | [] | 分类选项列表,透传给单/多选/排序题 |
show-tag | boolean | false | 是否显示标签/关键信息区域,仅作用于单/多选/排序题 |
show-category | boolean | false | 是否显示分类区域,仅作用于单/多选/排序题 |
show-ai | boolean | false | 是否显示 AI 推荐答案区域,仅作用于单/多选/排序题 |
show-resource | boolean | false | 是否显示资源区域,仅作用于单/多选/排序题 |
show-jump | boolean | false | 是否显示跳题逻辑入口,仅作用于单/多选/排序题 |
show-add | boolean | true | 是否显示“在此题后加入新题”入口 |
show-answer-setting | boolean | false | 是否显示答题设置,仅作用于单/多选/排序和填空/问答的设置区 |
show-key | boolean | false | 是否显示核心题入口,仅作用于单/多选/排序题 |
search-api | string | '' | 题库搜索接口地址;未传 search-handler 时作为搜索兜底 |
search-handler | (query: string, answerType: number) => Promise<any[]> | - | 自定义题库搜索方法,优先于 search-api |
upload-image | (file: File) => Promise<string> | base64 | 富文本图片上传方法(应用于所有题型) |
提示:
subject-list、category-list、search-handler、upload-image这类数组、对象、函数类型,推荐通过 JS property 或框架的.prop绑定传入。
事件
| 事件名 | Detail | 说明 |
|---|---|---|
change | any[] | 题目列表变更时触发(增删改排序后) |
title-select | { id, customId, title, value, ... } | 题库搜索下拉选中题目时触发 |
choose-tag | { customId, examId, value, item } | 点击标签“选择”入口时触发 |
jump | { customId, examId, answerType, item } | 点击跳题逻辑入口时触发 |
set-relation | { customId, examId, answerIndex, answer, item } | 点击关联检查入口时触发 |
方法
| 方法 | 签名 | 说明 |
|---|---|---|
addSubject | (type: string, index?: number, examAnswerRelationType?: number) | 在指定位置添加新题目 |
addExam | (items: any[]) | 批量导入题目(含分页支持) |
uploadExcel | (list: any[]) | 从 Excel 导入题目 |
setAnswerRelation | (answerRelations: any, customId: string, customAnswerId: string) | 设置选项关联关系 |
setTagList | (tagList: any[], customId: string) | 回填指定题目的标签/关键信息,并触发 change |
currentList | getter | 获取当前题目列表 |
validate | () => Promise<{ valid: boolean, errors: SubjectError[] }> | 校验整份题目列表,不抛异常 |
toJSON | () => Promise<any[]> | 获取所有题目 JSON 数据;返回结果不包含 page_end,真实题目会带 pageIndex(校验失败抛出 SubjectError[]) |
示例
javascript
const list = document.querySelector('qxs-subject-list')
// 末尾添加单选题
list.addSubject('single')
// 在索引 0 后添加多选题
list.addSubject('multiple', 0)
// 监听变更
list.addEventListener('change', (e) => {
console.log('题目列表变更:', e.detail)
})
// 提交所有题目数据(返回 Promise)
async function submitQuestions() {
try {
const data = await list.toJSON()
console.log('提交数据:', data) // 每题都会带 pageIndex,分页符不会单独出现在结果里
// 发送数据到服务器
} catch (err) {
const firstError = Array.isArray(err) ? err[0] : err
if (firstError instanceof SubjectError) {
console.error(`错误码: ${firstError.code}, 字段: ${firstError.field}, 信息: ${firstError.message}`)
}
}
}qxs-subject-single 单选/多选/排序题
单选题、多选题、排序题的编辑/预览组件。
基本用法
html
<qxs-subject-single
question-type="single"
:is-edit="true"
:order-index="0"
></qxs-subject-single>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
order-index | number | 0 | 题目序号(从 0 开始) |
type | 'single' | 'multiple' | 'sort' | 'single' | 题型 |
is-edit | boolean | false | 是否编辑模式 |
is-save | boolean | false | 是否已保存(禁用输入) |
is-set | boolean | false | 是否已设置 |
is-key | boolean | false | 是否核心题 |
show-action | boolean | true | 是否显示操作栏 |
show-add | boolean | true | 是否显示“在此题后加入新题”入口 |
show-answer-setting | boolean | false | 是否显示答题设置入口与只读展示 |
show-key | boolean | false | 是否显示核心题入口与核心题标识 |
show-analysis | boolean | false | 是否显示解析输入框 |
answer-list | Array | [] | 选项列表 |
tag-list | TagItem[] | [] | 已选标签/关键信息列表 |
category-list | Category[] | [] | 分类选项列表 |
ai-answer | string | '' | AI 推荐答案文本 |
resource-list | Resource[] | [] | 题目资源列表 |
show-tag | boolean | false | 是否显示标签/关键信息区域 |
show-category | boolean | false | 是否显示分类区域 |
show-ai | boolean | false | 是否显示 AI 推荐答案区域 |
show-resource | boolean | false | 是否显示资源区域 |
show-jump | boolean | false | 是否显示跳题逻辑入口 |
has-jump | boolean | false | 当前题目是否已设置跳题逻辑 |
rich-text-content | string | '' | 富文本内容 |
analysis | string | '' | 题目解析 |
answer-check-type | number | 1 | 答题设置类型 |
exam-answer-relation-type | number | 0 | 关联类型(0=无,1=结果项,2=关联检查) |
least-answer-count | number | 0 | 多选题或排序题的至少选择项数,0 表示未设置 |
search-api | string | '' | 题库搜索接口地址;未传 search-handler 时作为搜索兜底 |
search-handler | (query: string, answerType: number) => Promise<SearchResult[]> | - | 自定义题库搜索方法,优先于 search-api |
custom-id | string | '' | 自定义 ID |
提示:
tag-list、category-list、resource-list、search-handler这类数组、对象、函数类型,推荐通过 JS property 或框架的.prop绑定传入。
事件
| 事件名 | Detail | 说明 |
|---|---|---|
save | { title, answers, analysis, examExpand, isSetCorrectAnswer, ... } | 保存题目(验证通过后触发) |
edit | - | 点击编辑按钮 |
delete | - | 点击删除按钮 |
move | 'up' | 'down' | 题目位置移动事件;当前默认不展示对应按钮 |
add | string | 点击“在此题后加入新题”后触发,值为题型标识 |
set-key | { value: boolean } | 设置/取消核心题 |
set-answer-setting | { value: number } | 修改答题设置 |
title-select | { id, customId, title, value, ... } | 题库搜索下拉选中题目时触发 |
choose-tag | { customId, examId, value } | 点击标签“选择”入口时触发 |
tag-change | { customId, examId, value } | 标签删除或调整后触发 |
category-change | { customId, examId, value } | 分类变更时触发 |
jump | { customId, examId, answerType } | 点击跳题逻辑入口时触发 |
set-relation | { customId, examId, answerIndex, answer } | 点击关联检查入口时触发,仅 exam-answer-relation-type=2 时生效 |
Answer 数据结构
typescript
interface Answer {
title: string // 选项文本
isCorrect: boolean // 是否正确答案
customAnswerId?: string // 自定义选项 ID
answerId?: string // 选项 ID
resultItem?: string // 结果项内容
orderIndex?: number // 排序索引
answerRelations?: any[] // 关联关系
}业务接入契约(单/多选/排序)
以下契约以当前 components-wc 实现为准。
- 组件内核负责:通用展示、基础校验、序列化、事件抛出、AI 一键勾选。
- 业务宿主负责:真实业务接口、标签选择器、AI 推荐答案生成、跳题/关联检查配置弹窗。
- 如果使用
qxs-subject-list,推荐优先在列表层对接业务事件;列表会负责把核心业务配置透传给qxs-subject-single。
| 能力 | 业务方输入 | 组件行为 | 组件输出事件 / 回调 | 业务方职责 |
|---|---|---|---|---|
| 标签/关键信息 | show-tag、tag-list,或列表项中的 tagInfos/memberTagInfo | 展示已选标签,支持删除,保存时在开启状态下执行必填校验,并序列化到 tagInfos/memberTagInfo | 单题层:choose-tag、tag-change;列表层:choose-tag;列表方法:setTagList() | 提供标签选择器、标签树/接口,并在选择完成后回填标签 |
| AI 推荐答案 | show-ai、ai-answer,或列表项中的 aiAnswer | 展示 AI 推荐答案文本,支持“一键勾选”,保存时原样带回 aiAnswer | 无强制事件;通过 save/toJSON 返回最终结果 | 生成或下发 aiAnswer 内容,并写入题目数据 |
| 题库搜索 | searchHandler 或 search-api | 在题目标题输入时触发搜索,展示下拉候选,选中后自动回填标题 | 单题层:title-select;列表层:title-select | 提供搜索接口或回调,并决定选中题库题目后的后续业务逻辑 |
| 分类 | show-category、category-list、category-id | 展示分类下拉并保存 categoryId | 单题层:category-change;列表层内部自动写回当前题目数据 | 提供分类选项数据;如直接使用单题组件,则自行监听分类变更 |
| 资源展示 | show-resource、resource-list,或列表项中的 resourceList/examResourceBOList | 展示图片/视频资源入口,支持预览,并在保存时原样序列化资源数据 | 无 | 提供资源数据本身,以及资源上传/管理链路 |
| 跳题逻辑 | show-jump、题目数据中的 hasSet/is-set | 展示跳题入口和“已设置跳题逻辑”提示 | 单题层:jump;列表层:jump | 打开跳题配置弹窗、保存跳题结果,并回写题目状态 |
| 关联检查 / 结果项 | exam-answer-relation-type、题目选项中的 answerRelations/resultItem | 1 时组件内编辑结果项;2 时展示关联检查入口和关联数量 | set-relation 仅在 exam-answer-relation-type=2 时触发;结果项通过 save/toJSON 返回 | 关联检查模式下提供关联选择器,并在选择完成后调用 setAnswerRelation() 或直接回写题目数据 |
| 题目新增入口 | show-add | 控制“在此题后加入新题”入口显隐 | 单题层:add;列表层默认已内建处理新增逻辑 | 如直接使用单题组件,则自行接管新增题型的处理 |
最小业务接入示例
下面示例演示一个 Vue 宿主如何在列表层接入标签、AI 推荐答案和题库搜索。示例只描述最短接法,不依赖任何厂家端专有组件。
vue
<script setup lang="ts">
import '@qxs-bns/components-wc/core'
import { ref } from 'vue'
const listRef = ref<any>(null)
const subjectList = ref([
{
customId: 'q1',
answerType: 'single',
title: '患者是否规律服药?',
aiAnswer: '1. 是',
tagInfos: [],
answers: [
{ title: '是', isCorrect: false },
{ title: '否', isCorrect: false },
],
},
])
async function searchHandler(query: string, answerType: number) {
const response = await fetch(`/api/question-search?q=${encodeURIComponent(query)}&answerType=${answerType}`)
const data = await response.json()
return (data?.list || []).map((item: any) => ({
...item,
value: item.title,
}))
}
async function openTagDialog() {
// 这里替换成你自己的标签弹窗或标签选择器
return [
{ tagId: 't1', tagName: '慢病管理' },
{ tagId: 't2', tagName: '依从性' },
]
}
async function handleChooseTag(e: CustomEvent) {
const { customId } = e.detail
const selected = await openTagDialog()
listRef.value?.setTagList(selected, customId)
}
function handleChange(e: CustomEvent) {
subjectList.value = [...e.detail]
}
function handleTitleSelect(e: CustomEvent) {
console.log('题库题目已选中:', e.detail)
}
</script>
<template>
<qxs-subject-list
ref="listRef"
:subject-list.prop="subjectList"
show-tag
show-ai
:search-handler.prop="searchHandler"
@change="handleChange"
@choose-tag="handleChooseTag"
@title-select="handleTitleSelect"
/>
</template>qxs-blank-fill 填空题
填空题组件,支持多个填空答案。
基本用法
html
<qxs-blank-fill
:is-edit="true"
:order-index="0"
></qxs-blank-fill>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
order-index | number | 0 | 题目序号 |
is-edit | boolean | false | 是否编辑模式 |
is-save | boolean | false | 是否已保存 |
show-add | boolean | true | 是否显示“在此题后加入新题”入口 |
show-answer-setting | boolean | false | 是否显示填空题答题设置区域 |
title | string | '' | 题目标题 |
answer-list | Array | [] | 答案列表 |
exam-answer-setting | object | {} | 答题设置 |
rich-text-content | string | '' | 富文本内容 |
analysis | string | '' | 题目解析 |
事件
| 事件名 | Detail | 说明 |
|---|---|---|
save | { title, answers, analysis, ... } | 保存题目 |
edit | - | 点击编辑按钮 |
delete | - | 点击删除按钮 |
move | 'up' | 'down' | 移动题目位置 |
add | { type, canSet } | 添加新题目 |
qxs-text-fill 问答题
问答题组件,支持文本输入答案。
基本用法
html
<qxs-text-fill
:is-edit="true"
:order-index="0"
></qxs-text-fill>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
order-index | number | 0 | 题目序号 |
is-edit | boolean | false | 是否编辑模式 |
is-save | boolean | false | 是否已保存 |
show-add | boolean | true | 是否显示“在此题后加入新题”入口 |
show-answer-setting | boolean | false | 是否显示问答题答题设置区域 |
title | string | '' | 题目标题 |
answer-list | Array | [] | 关键词列表,兼容 title / answer 两种字段名 |
exam-answer-setting | object | {} | 答题设置 |
exam-expand | string | '' | 完整推荐/正确答案 |
rich-text-content | string | '' | 富文本内容 |
analysis | string | '' | 题目解析 |
事件
| 事件名 | Detail | 说明 |
|---|---|---|
save | { title, answers, analysis, ... } | 保存题目 |
edit | - | 点击编辑按钮 |
delete | - | 点击删除按钮 |
move | 'up' | 'down' | 移动题目位置 |
add | { type, canSet } | 添加新题目 |
问答题导出时遵循厂家端口径:
answers:关键词数组examExpand:完整推荐/正确答案examAnswerSettingBO:关键词命中规则
qxs-scale 量表题
量表题组件,支持矩阵式评分。
基本用法
html
<qxs-scale
:is-edit="true"
:order-index="0"
></qxs-scale>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
order-index | number | 0 | 题目序号 |
is-edit | boolean | false | 是否编辑模式 |
is-save | boolean | false | 是否已保存 |
title | string | '' | 题目标题 |
answer-list | Array | [] | 量表选项(列) |
scale-questions | Array | [] | 量表题目列表(行) |
rich-text-content | string | '' | 富文本内容 |
analysis | string | '' | 题目解析 |
事件
| 事件名 | Detail | 说明 |
|---|---|---|
save | { title, answers, scaleQuestions, analysis, ... } | 保存题目 |
edit | - | 点击编辑按钮 |
delete | - | 点击删除按钮 |
move | 'up' | 'down' | 移动题目位置 |
add | { type, canSet } | 添加新题目 |
qxs-subject-rich-text 富文本块
富文本块组件,用于在题目列表中展示和编辑富文本内容。
基本用法
html
<qxs-subject-rich-text
:is-edit="true"
:rich-text-content="content"
></qxs-subject-rich-text>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
order-index | number | 0 | 序号 |
is-edit | boolean | false | 是否编辑模式 |
is-save | boolean | false | 是否已保存 |
show-action | boolean | true | 是否显示操作栏 |
is-set | boolean | false | 是否已设置 |
rich-text-content | string | '' | 富文本内容 |
upload-image | (file: File) => Promise<string> | base64 | 图片上传方法 |
exam-answer-relation-type | number | 0 | 关联类型 |
事件
| 事件名 | Detail | 说明 |
|---|---|---|
save | { richTextContent: string } | 保存富文本内容 |
edit | - | 点击编辑按钮 |
delete | - | 点击删除按钮 |
add | { type, canSet } | 添加新题目 |
qxs-page-end 分页符
分页符组件,用于在题目列表中插入分页。
注意:
qxs-page-end只作为编辑态分隔符存在。通过qxs-subject-list.toJSON()导出时,分页符不会单独出现在结果里;组件会把分页位置换算成每道题的pageIndex。
基本用法
html
<qxs-page-end
:order-index="0"
></qxs-page-end>属性
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
order-index | number | 0 | 序号 |
is-edit | boolean | false | 是否编辑模式 |
show-action | boolean | true | 是否显示操作栏 |
事件
| 事件名 | Detail | 说明 |
|---|---|---|
delete | - | 点击删除按钮 |
add | { type, canSet } | 添加新题目 |