123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- <template>
- <view ref="questionEl" class="mx-question-content flex flex-col gap-40 text-content">
- <view v-html="titleWithSeq"/>
- <view class="mx-question-content-opt" :class="{'pl-30': deep>0}">
- <template v-if="question.options?.length">
- <slot name="options" v-bind="{options: optionsWithCode, deep, disabled}">
- <component :is="optGroupComponent" :modelValue="modelValue" :disabled="disabled" placement="column"
- :class="groupClass" @change="emits('update:modelValue', $event)">
- <component :is="optComponent" v-for="op in optionsWithCode" :name="op.code"
- :class="calcOptionClass(op)">
- <view v-html="op.option" :class="textClass"/>
- </component>
- </component>
- </slot>
- </template>
- <template v-if="question.subQuestions?.length">
- <mx-question-content v-for="sub in question.subQuestions" :question="sub" :deep="deep+1"
- :disabled="disabled">
- <template v-for="(fn, name) in $slots" #[name]="scope">
- <slot :name="name" v-bind="scope"/>
- </template>
- </mx-question-content>
- </template>
- </view>
- <mx-question-subjective v-if="useSubjectiveStyle" :modelValue="modelValue" :attachments="attachments"
- :disabled="disabled" @update:modelValue="emits('update:modelValue', $event)"
- @update:attachments="emits('update:attachments', $event)"/>
- </view>
- </template>
- <script setup>
- import {computed, onMounted, ref, watch} from 'vue'
- import {createPropDefine} from "@/utils";
- import _ from 'lodash';
- import UvCheckboxGroup from "@/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue";
- import UvRadioGroup from "@/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue";
- import MxQuestionPlainOptionGroup from "@/components/mx-question-content/components/mx-question-plain-option-group.vue";
- import UvRadio from "@/uni_modules/uv-radio/components/uv-radio/uv-radio.vue";
- import UvCheckbox from "@/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue";
- import MxQuestionPlainOption from "@/components/mx-question-content/components/mx-question-plain-option.vue";
- import mxConst from "@/common/mxConst";
- import MxQuestionSubjective from "@/components/mx-question-content/components/mx-question-subjective.vue";
- import {useMathJaxService} from "@/hooks/useMathJaxService";
- import {useInjectQuestionOptionFormatter} from "@/components/mx-question-content/useQuestionOptionInjection";
- import {useInjectMathJaxSwitch} from "@/components/mx-question-content/useMathJaxSwitchInjection";
- import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
- /*NOTE:递归调用目前没有做事件穿透,所以子题还不支持作答
- NOTE: mx-question-content 支持MathJax
- * */
- const props = defineProps({
- question: createPropDefine({}, Object),
- deep: createPropDefine(0, Number),
- disabled: createPropDefine(false, Boolean),
- modelValue: createPropDefine('', [String, Number, Array]),
- attachments: createPropDefine([], [String, Array]),
- sysAnswer: createPropDefine('', [String, Array]),
- readonly: createPropDefine(false, Boolean)
- })
- const emits = defineEmits(['update:modelValue', 'update:attachments'])
- const questionEl = ref(null)
- const {updateMathJax} = useMathJaxService()
- const {formatter, optionClass} = useInjectQuestionOptionFormatter()
- const {disabled: disabledMathJax} = useInjectMathJaxSwitch()
- const titleWithSeq = computed(() => {
- const {title, seq, type, questionId} = props.question
- const arr = []
- if (seq) arr.push(`(${seq})`)
- if (type) arr.push(`[${type}]`)
- if (questionId) arr.push(`[${questionId}]`)
- arr.push(title)
- return arr.join('')
- })
- const optionsWithCode = computed(() => {
- const {options} = props.question
- return formatter(options)
- })
- const useMultipleStyle = computed(() => {
- const {deep, readonly, question: {typeId}} = props
- return deep == 0 && !readonly && mxConst.question.isCheckbox(typeId)
- })
- const useRadioStyle = computed(() => {
- // 子题都算主观题, 学生手写作答
- const {deep, readonly, question: {typeId}} = props
- return deep == 0 && !readonly && mxConst.question.isRadio(typeId)
- })
- const useSubjectiveStyle = computed(() => {
- // 一级题的主观题渲染输入框
- const {deep, readonly, question: {typeId}} = props
- return deep == 0 && !readonly && !mxConst.question.isObjective(typeId)
- })
- const optGroupComponent = computed(() => {
- if (useRadioStyle.value) return UvRadioGroup
- if (useMultipleStyle.value) return UvCheckboxGroup
- return MxQuestionPlainOptionGroup // 为了对齐UvCheckboxGroup/UvRadioGroup
- })
- const optComponent = computed(() => {
- if (useRadioStyle.value) return UvRadio
- if (useMultipleStyle.value) return UvCheckbox
- return MxQuestionPlainOption // 为了对齐UvCheckbox/UvRadio
- })
- const groupClass = computed(() => {
- return ['gap-20']
- })
- const textClass = computed(() => {
- return []
- })
- onMounted(() => updateMathJaxIfNeeded())
- watch(() => props.question, () => updateMathJaxIfNeeded())
- const updateMathJaxIfNeeded = () => {
- if (disabledMathJax) return
- updateMathJax(questionEl.value?.$el)
- }
- const calcOptionClass = function (op) {
- const defaultClass = 'px-15 py-20 border border-solid rounded'
- const injectClass = func(optionClass) ? optionClass(props.question) : optionClass
- const classes = [injectClass === undefined ? defaultClass : injectClass]
- if (props.sysAnswer) {
- // 如果有传入标准答案,则突出显示正确错误
- if (op.code == props.modelValue || props.modelValue.includes(op.code)) {
- // classes.push('border-primary')
- if (op.code != props.sysAnswer && !props.sysAnswer.includes(op.code)) {
- // 答错了
- classes.push('bg-[#f9e6e6] text-[#571515] border-[#e6c3c3]')
- }
- }
- if (op.code == props.sysAnswer || props.sysAnswer.includes(op.code)) {
- // 正确答案
- classes.push('bg-[#e0f9e6] text-[#155724] border-[#c3e6cb]')
- }
- } else {
- classes.push('border-border')
- }
- return classes
- }
- const tryRandomAnswer = () => {
- if (useRadioStyle.value) {
- // 随机选1个
- const rd = _.sample(optionsWithCode.value)
- emits('update:modelValue', rd.code)
- } else if (useMultipleStyle.value) {
- // 随机选N个
- const num = _.random(1, optionsWithCode.value.length + 1)
- const rd = _.sampleSize(optionsWithCode.value, num)
- // 与原选项保持顺序并提取code
- const codes = optionsWithCode.value.filter(i => rd.includes(i)).map(i => i.code)
- emits('update:modelValue', codes)
- }
- }
- defineExpose({tryRandomAnswer})
- </script>
- <style scoped lang="scss">
- .mx-question-content + .mx-question-content {
- margin-top: 40rpx;
- }
- </style>
|