QuestionConfig.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. <!-- 题目配置 -->
  2. <template>
  3. <div class="test_question_config">
  4. <Form
  5. :v-model:formRef="formRefDom"
  6. :form-item-list="finalFormItemList"
  7. :default-form-data="questionFormData"
  8. @form-item-change="HandleFormItemChange"
  9. labelWidth="92px"
  10. >
  11. <template #questionType>
  12. <div class="question_type">
  13. <!-- 题目类型下拉 -->
  14. <el-select
  15. v-model="questionFormData.questionType"
  16. class="type_select"
  17. @change="(val)=>HandleFormItemChange({key:'question_type',val,formData:questionFormData})"
  18. placeholder="请选择">
  19. <el-option
  20. v-for="selectItem in questionTypeOptions"
  21. :key="selectItem.value"
  22. :label="selectItem.label"
  23. :value="selectItem.value"
  24. />
  25. </el-select>
  26. <!-- 题目额外 展示配置 -->
  27. <el-checkbox-group
  28. class="type_cofig_check"
  29. v-model="questionFormData.extraConfig"
  30. >
  31. <el-checkbox
  32. v-for="checkItem in questionConfig"
  33. :key="checkItem.value"
  34. :label="checkItem.label"
  35. :value="checkItem.value"
  36. />
  37. </el-checkbox-group>
  38. </div>
  39. </template>
  40. <template #evaluationType="{formData}">
  41. <div class="score_reviewType">
  42. <el-radio-group v-model="questionFormData.evaluationType">
  43. <el-radio v-for="radioItem in scoreReviewTypeList" :key="radioItem.value" :value="radioItem.value">{{ radioItem.label }}</el-radio>
  44. </el-radio-group>
  45. <ul class="num_score" v-if="Number(formData.evaluationType) === 0 && Number(formData.questionScore)">
  46. <li v-for="value in Number(formData.questionScore)">
  47. {{ value }}分
  48. </li>
  49. </ul>
  50. <div class="star_score" v-else-if="Number(formData.evaluationType) === 1 && Number(formData.questionScore)">
  51. 非常不满意
  52. <el-rate size="large"
  53. :model-value="0"
  54. :disabled="true"
  55. disabled-void-color="#c6d1de"
  56. disabled-void-icon="Star"
  57. :max="Number(formData.questionScore)"
  58. clearable />
  59. 非常满意
  60. </div>
  61. <div class="null_score" v-else>
  62. 未查询到结果,请至题目分值处设置分值
  63. </div>
  64. </div>
  65. </template>
  66. <template #options="{ formData }">
  67. <div class="options_list_container">
  68. <div v-for="(option,index) in formData.options" :key="option.id" class="option_item">
  69. <div class="input_option">
  70. <el-input
  71. v-model="option.content"
  72. placeholder="请输入选项内容"
  73. />
  74. <!-- 统计题 -->
  75. <template v-if="displayMode === 'statistic'">
  76. <el-icon v-if="Number(questionFormData.questionType) === 0 && Number(index) > 1 ||
  77. Number(questionFormData.questionType) === 1 && Number(index) > 2 "
  78. class="delete_icon"
  79. @click="removeOption(option.id)"
  80. >
  81. <Close/>
  82. </el-icon>
  83. <div class="blank_icon" v-else></div>
  84. </template>
  85. </div>
  86. <!-- 得分题 -->
  87. <div v-if="displayMode === 'scored'" class="score_option">
  88. <span class="row_label">选项分值:</span>
  89. <el-input
  90. type="number"
  91. v-model="option.score"
  92. placeholder="请输入分值"
  93. />
  94. <el-icon v-if="Number(questionFormData.questionType) === 3 && Number(index) > 1"
  95. class="delete_icon"
  96. @click="removeOption(option.id)"
  97. >
  98. <Close/>
  99. </el-icon>
  100. <div class="blank_icon" v-else></div>
  101. </div>
  102. </div>
  103. </div>
  104. <div class="add_option_area">
  105. <el-button type="primary" link @click="addOption">
  106. <el-icon><CirclePlus /></el-icon>
  107. 添加选项
  108. </el-button>
  109. </div>
  110. </template>
  111. <template #submit="{formRef,formData}">
  112. <div class="action_bar">
  113. <el-button class="button_background" @click="HandleAddConfirm({formRef,formData})">确定</el-button>
  114. <el-button class="button_refresh">取消</el-button>
  115. </div>
  116. </template>
  117. </Form>
  118. </div>
  119. </template>
  120. <script setup lang="ts">
  121. import { ref, computed, watch } from 'vue'
  122. import { Close, CirclePlus } from '@element-plus/icons-vue'
  123. import Form from '@/baseComponents/Form.vue'
  124. import { questionFormItemList } from './form'
  125. import {
  126. statisticQuestionType, scoredQuestionType
  127. } from './questionTypeList'
  128. type DisplayMode = 'scored' | 'statistic'
  129. interface Option {
  130. content: String
  131. score?: String
  132. id?: Number
  133. }
  134. interface FormData {
  135. questionTitleName: String //题目名称
  136. questionScore?: String //题目分值
  137. questionType: String //题目类型
  138. extraConfig: String[] // 是否必答 required, 是否横排 isHorizontal
  139. options: Option[], //单选/多选 的 选项配置
  140. evaluationType?:Number, // 评价题 评分的展示形式
  141. }
  142. const props = defineProps({
  143. displayMode: { // 展示形式 带分值 scored 不带分值 statistic
  144. type: String as () => DisplayMode,
  145. default: 'statistic',
  146. required:true,
  147. },
  148. defaultFormData:{
  149. type:Object as ()=> FormData,
  150. }
  151. })
  152. const emit = defineEmits(['confirm', 'cancel', 'formChange'])
  153. const formRefDom = ref()
  154. // 试题题目信息
  155. const questionFormData = ref<FormData>({
  156. questionTitleName: '',
  157. questionScore: '',
  158. questionType: '',
  159. extraConfig: [], //默认勾选必答题
  160. options: [],
  161. evaluationType:0 //默认展示 数字式
  162. })
  163. /*
  164. 监听 props.displayMode 展示 不同的 题型配置 和 试卷配置
  165. */
  166. // 题型选项配置 监听 动态切换
  167. const questionTypeOptions = computed(() => {
  168. if (props.displayMode === 'scored') {
  169. return [...scoredQuestionType.value]
  170. } else {
  171. return [... statisticQuestionType.value]
  172. }
  173. })
  174. // 表单试题 展示项配置 监听 表单元素 动态显隐
  175. const finalFormItemList = computed(() => {
  176. return questionFormItemList.value.map(item => {
  177. const newItem = { ...item }
  178. // 题目分值仅在 scored 模式显示
  179. if (item.key === 'questionScore') {
  180. newItem.hidden = props.displayMode !== 'scored'
  181. }else if(item.key == 'options'){
  182. // 评价题 和 问答题 不展示 题目子选项配置
  183. newItem.hidden = [2,5].includes(Number(questionFormData.value.questionType))
  184. }else if(item.key == 'evaluationType'){
  185. // 评价题 展示评分的格式
  186. newItem.hidden = Number(questionFormData.value.questionType) !== 5
  187. }
  188. return newItem
  189. })
  190. })
  191. // 试题额外配置项 // 评价题 和 问答题 展示不同的配置项
  192. const questionConfig = computed(()=>{
  193. // 评价题 和 问答题
  194. if([2,5].includes(Number(questionFormData.value.questionType)) ){
  195. return [
  196. { label: '必答题', value: 'required' },
  197. ]
  198. }else{
  199. return [
  200. { label: '必答题', value: 'required' },
  201. { label: '选项横排', value: 'isHorizontal' }
  202. ]
  203. }
  204. })
  205. // 评价题 评分形式 列表
  206. const scoreReviewTypeList = ref([
  207. {label:'数字式',value:0},
  208. {label:'星星式', value:1},
  209. ])
  210. // 表单内部某些变化 触发 的操作
  211. const HandleFormItemChange = (params: any) => {
  212. const { key, val, formData: data } = params
  213. // console.log('表单项目变化了--',formData)
  214. emit('formChange', params)
  215. }
  216. // 添加题目选项配置
  217. const addOption = () => {
  218. questionFormData.value.options.push({ content: '', score: '',id:(Number(questionFormData.value.options[questionFormData.value.options.length - 1]?.id || 0) + 1) })
  219. }
  220. // 删除题目选项配置
  221. const removeOption = (id: number) => {
  222. questionFormData.value.options = questionFormData.value.options.filter((opt)=>{
  223. return opt.id !== id
  224. })
  225. }
  226. // 题目信息校验 并传递信息给父级
  227. const HandleAddConfirm = async (params:any)=>{
  228. const {formRef,formData} = params
  229. if (!formRef) return
  230. await formRef.validate(async(valid) => {
  231. if (valid) {
  232. emit('confirm', {formData:formData,formRef})
  233. }
  234. }).catch(err=>{
  235. console.log('报错信息',err)
  236. })
  237. }
  238. // 同步监听 1
  239. watch(()=>props.defaultFormData,(newVal)=>{
  240. // 赋值除了类型之外的其他值
  241. console.log('监听到赋值了',newVal)
  242. if (newVal) {
  243. // 避免首次赋值选项置空的情况 _ 待完善 _ 可能发生
  244. questionFormData.value = newVal
  245. }
  246. },{deep:true, immediate:true})
  247. </script>
  248. <style lang="scss" scoped>
  249. .test_question_config {
  250. padding: 20px;
  251. background-color: #F8FAFE;
  252. }
  253. .question_type{
  254. display: flex;
  255. gap:32px;
  256. align-items: center;
  257. width: 100%;
  258. .type_select{
  259. width: 200px;
  260. }
  261. }
  262. .score_reviewType{
  263. display: flex;
  264. flex-direction: column;
  265. .num_score,
  266. .star_score,
  267. .null_score
  268. {
  269. display: flex;
  270. align-items: center;
  271. gap:16px;
  272. color: #CCCCCC;
  273. line-height: 1;
  274. }
  275. .num_score{
  276. flex-wrap: wrap;
  277. li{
  278. display: flex;
  279. justify-content: center;
  280. align-items: center;
  281. border-radius: 4px ;
  282. border: 1px solid #CCCCCC;
  283. width: 44px;
  284. height: 32px;
  285. font-size: 14px;
  286. background-color: white;
  287. }
  288. }
  289. .star_score{
  290. word-break: keep-all;
  291. .el-rate{
  292. display: flex;
  293. flex-wrap: wrap;
  294. gap:10px 20px;
  295. height: auto;
  296. }
  297. }
  298. }
  299. // 选项列表样式
  300. .options_list_container {
  301. width:100% ;
  302. display: flex;
  303. flex-direction: column;
  304. gap: 16px;
  305. margin-bottom: 16px;
  306. .option_item {
  307. display: flex;
  308. justify-content: space-between;
  309. align-items: center;
  310. gap: 16px;
  311. .input_option{
  312. width:60% ;
  313. display: flex;
  314. align-items: center;
  315. padding-left: 92px;
  316. .blank_icon{
  317. margin-left: 20px;
  318. width: 20px !important;
  319. height: 20px;
  320. }
  321. }
  322. .score_option{
  323. width: 35%;
  324. display: flex;
  325. align-items: center;
  326. .row_label {
  327. color: #999999;
  328. font-size: 14px;
  329. flex-shrink: 0;
  330. }
  331. .blank_icon{
  332. margin-left: calc(20px + 6px);
  333. width: 20px !important;
  334. height: 20px;
  335. }
  336. }
  337. .input_option,
  338. .score_option{
  339. .delete_icon {
  340. font-size: 20px;
  341. color: #c0c4cc;
  342. cursor: pointer;
  343. margin-left: 20px;
  344. width: 20px !important;
  345. height: 20px;
  346. &:hover {
  347. color: #f56c6c;
  348. }
  349. }
  350. }
  351. }
  352. }
  353. // 添加选项按钮
  354. .add_option_area {
  355. margin-left: 92px;
  356. }
  357. // 底部操作栏
  358. .action_bar {
  359. margin-left: 75px;
  360. display: flex;
  361. gap:20px;
  362. .el-button {
  363. margin:0;
  364. padding:10px 36px;
  365. height: 40px;
  366. }
  367. }
  368. // 表单项样式
  369. :deep(.el-form) {
  370. display: flex;
  371. flex-wrap: wrap;
  372. justify-content: space-between;
  373. gap: 16px 0;
  374. .el-form-item {
  375. margin: 0px;
  376. &.custom_itm {
  377. width: 100%;
  378. .el-form-item__content{
  379. display: flex;
  380. flex-direction: column;
  381. align-items: flex-start;
  382. }
  383. }
  384. &.percent_70 {
  385. width: 60%;
  386. }
  387. &.percent_30 {
  388. width: 35%;
  389. }
  390. }
  391. }
  392. </style>