QuestionConfig.vue 13 KB

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