zydx-plus 1.35.630 → 1.35.632

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zydx-plus",
3
- "version": "1.35.630",
3
+ "version": "1.35.632",
4
4
  "description": "Vue.js",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -1,29 +1,34 @@
1
1
  <template>
2
+ <!-- 试题展示组件:支持单选/多选、材料附件、自定义按钮、答题状态反馈 -->
2
3
  <div :class="{paper: borderShow}">
3
4
  <div class="parer-cont" ref="parer">
5
+ <!-- 题目迭代 -->
4
6
  <div class="paper-choice" :class="{'array-ch': !styleText(item.testKey)}" v-for="(item,index) in value">
5
7
  <div class="subject">
8
+ <!-- 题目标题区域:有题号展示题号,否则展示序号 -->
6
9
  <div class="subject-title" :class="{'padd': item.top !== 0}" v-if="item.subject">
7
- <input v-if="multipleShow" type="checkbox" :checked="item.checked" @change="checkboxChange($event,index)"
10
+ <!-- 多选模式下的勾选框 -->
11
+ <input v-if="multipleShow" type="checkbox" :disabled="item.disabled" :checked="item.checked" @change="checkboxChange($event,index)"
8
12
  name="check"/>
13
+ <!-- 有题号展示题号 -->
9
14
  <span v-if="item.quesNumber" :style="{'font-weight': titleCrude?700:'normal'}">{{ item.quesNumber }}<em
10
15
  v-html="item.question"></em><em v-if="scoreShow">({{ item.score }}分)</em>
16
+ <!-- 自定义操作按钮 -->
11
17
  <i class="subject-but" ref="subject">
12
- <!-- <b :style="{right: -item.right + 'px', top: item.top + 'px'}" ref="b"> -->
13
- <button class="sees-but" :disabled="it.disabled" v-for="(it) in item.customBut" :class="{'buts-actives theme-text': it.active&&it.butStart,'buts-disabled': it.disabled}" @click="customButTap(it,item)">{{ it.active? it.nameActive: it.name }}</button>
14
- <!-- </b> -->
18
+ <button class="sees-but" :disabled="it.disabled" v-for="(it) in item.customBut" :class="{'buts-actives theme-text': it.active&&it.butStart,'buts-disabled': it.disabled}" @click="customButTap(it,item)">{{ it.active? it.nameActive: it.name }}</button>
15
19
  </i>
16
20
  </span>
21
+ <!-- 无题号则展示自增序号 -->
17
22
  <span v-else :style="{'font-weight': titleCrude?700:'normal'}">{{ item.order ? item.order : index + 1 }}{{ item.order === ' ' ? '' : '.' }}<em
18
23
  v-html="item.question"></em><em v-if="scoreShow">({{ item.score }}分)</em>
24
+ <!-- 自定义操作按钮(同上有题号版本) -->
19
25
  <i class="subject-but" ref="subject">
20
- <!-- <b :style="{right: -item.right + 'px', top: item.top + 'px'}" ref="b"> -->
21
- <button class="sees-but" :disabled="it.disabled" v-for="(it) in item.customBut" :class="{'buts-actives theme-text': it.active&&it.butStart,'buts-disabled': it.disabled}" @click="customButTap(it,item)">{{ it.active? it.nameActive: it.name }}</button>
22
- <!-- </b> -->
26
+ <button class="sees-but" :disabled="it.disabled" v-for="(it) in item.customBut" :class="{'buts-actives theme-text': it.active&&it.butStart,'buts-disabled': it.disabled}" @click="customButTap(it,item)">{{ it.active? it.nameActive: it.name }}</button>
23
27
  </i>
24
28
  </span>
25
29
  </div>
26
30
  </div>
31
+ <!-- 材料附件展示区 -->
27
32
  <div v-if="item.alist" class="subject-html" v-show="item.open">
28
33
  <div v-for="(it,ind) in item.alist">
29
34
  <div v-if="it.annexType === '0'">
@@ -32,13 +37,14 @@
32
37
  </div>
33
38
  </div>
34
39
  </div>
40
+ <!-- 选项展示区:单选/多选 -->
35
41
  <div class="choice" v-if="item.testKey" v-show="item.open && isLook">
36
42
  <div class="choice-box" v-for="(ts,ind) in item.testKey">
37
43
  <label>
38
44
  <input class="choice-input" style="display: none;" :disabled="disabled" :checked="ts.checked"
39
45
  @change="answerChange($event,item,`${id}${index}`,ts)" :type="ques(item.type)? 'radio': 'checkbox'"
40
46
  :name="`${id}${index}`"/>
41
- <!-- <svgs v-if="chooseAnswer" :radio="ques(item.type)" :select="ts.checked" :disabled="disabled" :state="stateFun(ts,item)"></svgs>-->
47
+ <!-- 选项图标:false表示始终使用复选框样式 -->
42
48
  <svgs v-if="chooseAnswer" :radio="false" :select="ts.checked" :disabled="disabled" :state="stateFun(ts,item)"></svgs>
43
49
  <span :class="{'choice-span': chooseAnswer}">{{
44
50
  ts.index
@@ -46,6 +52,7 @@
46
52
  </label>
47
53
  </div>
48
54
  </div>
55
+ <!-- 外部自定义内容插槽 -->
49
56
  <slot name="content" :row="item"></slot>
50
57
  </div>
51
58
  </div>
@@ -53,203 +60,286 @@
53
60
  </template>
54
61
 
55
62
  <script>
56
- import {html2json, json2html} from '../../utils/html2json.js'
63
+ /**
64
+ * seek/word.vue - 试题展示组件
65
+ * 功能:展示试题(单选题/多选题)、材料附件、自定义按钮操作、答题状态反馈
66
+ * 适用场景:题库资源中查看/编辑试题
67
+ */
68
+ import { json2html } from '../../utils/html2json.js'
57
69
  import svgs from './icon.vue'
58
70
 
59
71
  export default {
60
72
  name: "zydx-seek2",
61
- components: {svgs},
73
+ components: { svgs },
74
+
75
+ /** 内部状态 */
62
76
  data() {
63
77
  return {
64
- value: [],
65
- multipleShow: false,
66
- id: null
78
+ value: [], // 处理后的试题数据列表
79
+ multipleShow: false, // 是否显示多选勾选框
80
+ id: null // 组件实例唯一标识,用于name分组
67
81
  }
68
82
  },
83
+
84
+ /** 外部参数 */
69
85
  props: {
70
86
  data: {
71
87
  type: Array,
72
- default: []
88
+ default: () => [] // 试题数据源
73
89
  },
74
90
  disabled: {
75
91
  type: Boolean,
76
- default: false
92
+ default: false // 是否禁用交互
77
93
  },
78
94
  multiple: {
79
95
  type: Boolean,
80
- default: false
96
+ default: false // 是否开启多选模式
81
97
  },
82
98
  borderShow: {
83
99
  type: Boolean,
84
- default: true
100
+ default: true // 是否显示外边框
85
101
  },
86
102
  titleText: {
87
103
  type: String,
88
- default: '材料'
104
+ default: '材料' // 材料标题前缀
89
105
  },
90
106
  chooseAnswer: {
91
107
  type: Boolean,
92
- default: true
108
+ default: true // 是否显示选项图标(对/错标记)
93
109
  },
94
110
  scoreShow: {
95
111
  type: Boolean,
96
- default: false
112
+ default: false // 是否显示分值
97
113
  },
98
114
  titleCrude: {
99
115
  type: Boolean,
100
- default: false
116
+ default: false // 标题是否加粗
101
117
  },
102
- isLook: { // 题库资源中,查看试题true,创建试题false(新版题库类资源-选择题编辑状态时,试题选项只在编辑框中显示此时isLook: false)
118
+ isLook: {
103
119
  type: Boolean,
104
- default: true
120
+ default: true // 查看模式:true=查看试题,false=编辑试题(创建试题时选项仅在编辑框显示)
105
121
  }
106
122
  },
123
+
124
+ /** 数据监听 */
107
125
  watch: {
108
126
  data: {
109
- handler: function (val, oldVal) {
127
+ handler: function (val) {
110
128
  this.init()
111
129
  },
112
130
  immediate: true,
113
131
  deep: true,
114
132
  },
115
133
  multiple: {
116
- handler: function (val, oldVal) {
134
+ handler: function (val) {
117
135
  this.multipleShow = val
118
136
  },
119
- immediate: true,
120
- deep: true
121
- },
122
- replace: {
123
- handler: function (val, oldVal) {
124
- this.replaceShow = val
125
- },
126
- deep: true
137
+ immediate: true
127
138
  }
128
139
  },
140
+
141
+ /** 生命周期 */
129
142
  mounted() {
130
143
  this.id = this.randomId()
131
144
  },
145
+
146
+ /** 对外事件 */
132
147
  emits: ['change', 'butChange', 'changeAll', 'judgeAnswer'],
148
+
133
149
  methods: {
134
- stateFun(ts,item) {
135
- if(this.disabled&&ts.checked&&!item.error&&!this.nothing) return 'correct'
136
- if(this.disabled&&ts.checked&&item.error&&!this.nothing) return 'error'
150
+ /**
151
+ * 获取选项的状态样式
152
+ * @param {Object} ts - 选项数据
153
+ * @param {Object} item - 所属试题
154
+ * @returns {string} 'correct' | 'error' | 'default'
155
+ */
156
+ stateFun(ts, item) {
157
+ if (this.disabled && ts.checked && !item.error) return 'correct'
158
+ if (this.disabled && ts.checked && item.error) return 'error'
137
159
  return 'default'
138
160
  },
139
- customButTap(e,d) {
140
- if(e.key === 'option') d.open = !d.open
141
- if(e.single) {
142
- if(!e.active) {
161
+
162
+ /**
163
+ * 自定义按钮点击处理
164
+ * @param {Object} button - 按钮配置
165
+ * @param {Object} item - 所属试题
166
+ */
167
+ customButTap(button, item) {
168
+ // option键:切换材料附件的展开/收起
169
+ if (button.key === 'option') item.open = !item.open
170
+
171
+ if (button.single) {
172
+ // 单选模式按钮:取消其他同组单选按钮的选中状态
173
+ if (!button.active) {
143
174
  this.value.forEach(x => {
144
- // 把x.customBut里single是true的active设置成false
145
175
  const singleBut = x.customBut.filter(y => y.single)
146
- if(singleBut) singleBut.forEach(y => y.active = false)
176
+ if (singleBut) singleBut.forEach(y => y.active = false)
147
177
  })
148
178
  }
149
- e.active = !e.active
150
- }else {
151
- if(e.butStart) e.active = !e.active
179
+ button.active = !button.active
180
+ } else {
181
+ // 非单选模式:带butStart样式的按钮才能切换
182
+ if (button.butStart) button.active = !button.active
152
183
  }
153
- if(e.active) {
154
- if(e.key === 'disabled') {
184
+
185
+ // disabled键特殊逻辑:切换全局禁用状态
186
+ if (button.active) {
187
+ if (button.key === 'disabled') {
155
188
  this.disabledFun(true)
156
- e.disabled = false
189
+ button.disabled = false
157
190
  }
158
- }else {
191
+ } else {
159
192
  this.disabledFun(false)
160
193
  }
161
- this.$emit('butChange',e,d)
194
+
195
+ this.$emit('butChange', button, item)
162
196
  },
163
- disabledFun(e) {
197
+
198
+ /**
199
+ * 设置所有试题中 disabled 按钮的禁用状态
200
+ * @param {boolean} state - 目标禁用状态
201
+ */
202
+ disabledFun(state) {
164
203
  this.value.forEach(x => {
165
204
  x.customBut.forEach(y => {
166
- if(y.key === 'disabled') y.disabled = e
205
+ if (y.key === 'disabled') y.disabled = state
167
206
  })
168
207
  })
169
208
  },
170
- annexHtml(x) {
171
- return json2html({node: "root", child: typeof x === 'string' ? JSON.parse(x) : x})
209
+
210
+ /**
211
+ * 将附件内容转换为HTML
212
+ * @param {Object|string} data - 附件JSON数据
213
+ * @returns {string} HTML字符串
214
+ */
215
+ annexHtml(data) {
216
+ return json2html({ node: "root", child: typeof data === 'string' ? JSON.parse(data) : data })
217
+ },
218
+
219
+ /**
220
+ * 解析试题的判题答案
221
+ * @param {Object} item - 试题数据
222
+ * @returns {*} 单选返回字符串,多选返回数组
223
+ */
224
+ resolveJudgeAnswer(item) {
225
+ if (item.stuAnswer) return item.stuAnswer?.answer
226
+ return item?.judgeAnswer
172
227
  },
228
+
229
+ /**
230
+ * 根据判题答案回填选项的选中状态
231
+ * @param {Object} item - 试题数据(含已解析的testKey)
232
+ */
233
+ markCheckedOptions(item) {
234
+ const judge = this.resolveJudgeAnswer(item)
235
+ const isSingle = this.ques(item.type)
236
+
237
+ item.testKey = item.testKey.map(x => {
238
+ if (isSingle) {
239
+ // 单选题:匹配单个答案
240
+ x.checked = x.index === judge
241
+ } else {
242
+ // 多选题:匹配答案数组中的任一值
243
+ x.checked = Array.isArray(judge) ? judge.includes(x.index) : false
244
+ }
245
+ return x
246
+ })
247
+ },
248
+
249
+ /**
250
+ * 恢复已缓存试题的打开状态(数据更新时保留用户操作)
251
+ * @param {Array} cachedList - 更新前的value副本
252
+ */
253
+ restoreOpenState(cachedList) {
254
+ if (!cachedList.length || !this.value.length) return
255
+
256
+ const subjectiveTypes = ['101', '102', '102_1', '102_2']
257
+
258
+ cachedList.forEach((item, index) => {
259
+ if (!this.value[index]) return
260
+ if (item.questionId !== this.value[index].questionId) return
261
+
262
+ this.value[index].open = item.open
263
+
264
+ // 主观题:恢复"试题设置"按钮状态
265
+ if (subjectiveTypes.includes(item.type) && this.isLook) {
266
+ if (this.value[index].customBut) {
267
+ this.value[index].customBut[0].active = item.open
268
+ }
269
+ }
270
+ })
271
+ },
272
+
273
+ /**
274
+ * 核心初始化方法:深拷贝data → 解析testKey → 回填答案 → 恢复状态
275
+ */
173
276
  init() {
174
277
  const val = JSON.parse(JSON.stringify(this.data))
175
- let copy = this.value.length?this.value:[]
176
- this.value = val.map((item, index) => { // 转换html
177
- item.choice = true
178
- item.right = 0
179
- item.top = 0
278
+ const cachedList = this.value.length ? this.value : []
279
+
280
+ this.value = val.map((item) => {
281
+ // 设置默认值
180
282
  item.open = item.open !== undefined ? item.open : true
181
283
  item.error = item.error !== undefined ? item.error : false
182
284
  item.subject = item.subject !== undefined ? item.subject : true
285
+
286
+ // 解析选项并回填答案
183
287
  if (item.testKey) {
184
288
  item.testKey = typeof item.testKey === 'string' ? JSON.parse(item.testKey) : item.testKey
185
- let judge = item?.judgeAnswer
186
- if (item.stuAnswer) judge = item.stuAnswer?.answer
187
- item.testKey = item.testKey.map(x => {
188
- if (this.ques(item.type)) { // 判断是否单选
189
- x.checked = x.index === judge; //答案
190
- } else {
191
- for (let i = 0; i < judge?.length; i++) {
192
- if (x.index === judge[i]) {
193
- x.checked = true
194
- break
195
- } else {
196
- x.checked = false
197
- }
198
- }
199
- }
200
- return x
201
- })
289
+ this.markCheckedOptions(item)
202
290
  }
291
+
203
292
  return item
204
293
  })
205
- if( copy.length && this.value.length) {
206
- copy.forEach((item, index) => {
207
- if(this.value[index]){
208
- if(item.questionId===this.value[index].questionId){
209
- this.value[index].open = item.open
210
- if (item.type === '101' || item.type === '102' || item.type === '102_1' || item.type === '102_2') { // 解决主观题点击完成按钮‘试题设置’按钮收起问题
211
- if (this.isLook) {
212
- if(this.value[index].customBut) this.value[index].customBut[0].active = item.open
213
- }
214
- }
215
- }
216
- }
217
- })
218
- }
219
- this.$nextTick(() => { // 保证所有的数据都渲染完毕
220
- const w = this.$refs.parer.offsetWidth // 获取父级宽度
221
- for (let i = 0; i < this.$refs?.subject?.length; i++) {
222
- this.value[i].right = w - this.$refs.subject[i].offsetLeft
223
- if (this.$refs.b && this.$refs.b.length > 0) {
224
- if (this.value[i].right < this.$refs.b[i].offsetWidth) this.value[i].top = 25 // 判断是否换行
225
- }
226
- }
227
- })
294
+
295
+ // 恢复之前缓存的开合状态
296
+ this.restoreOpenState(cachedList)
228
297
  },
229
- randomId() { // 生成随机id
230
- const id = Math.random().toString(36).substr(2)
298
+
299
+ /**
300
+ * 生成不包含数字的随机ID(用于input的name分组)
301
+ * @returns {string} 纯字母随机ID
302
+ */
303
+ randomId() {
304
+ const id = Math.random().toString(36).substring(2)
231
305
  return id.replace(/[0-9]/g, '')
232
306
  },
233
- answerChange(e, v, id, check) {
234
- if(this.ques(v.type)) {
235
- v.testKey.map(x => {
236
- x.checked = false
237
- return x
238
- })
307
+
308
+ /**
309
+ * 选项变更回调
310
+ * @param {Event} e - change事件
311
+ * @param {Object} item - 当前试题
312
+ * @param {string} name - input的name标识
313
+ * @param {Object} check - 被操作的选项
314
+ */
315
+ answerChange(e, item, name, check) {
316
+ // 单选题:先清空所有选项
317
+ if (this.ques(item.type)) {
318
+ item.testKey.forEach(x => { x.checked = false })
239
319
  }
320
+
240
321
  check.checked = e.target.checked
241
- let answerArr = []
242
- const ids = document.getElementsByName(id)
322
+
323
+ // 收集当前选中的答案
324
+ const answerArr = []
325
+ const ids = document.getElementsByName(name)
243
326
  ids.forEach((x, index) => {
244
327
  if (x.checked) {
245
- answerArr.push(v.testKey[index].index)
328
+ answerArr.push(item.testKey[index].index)
246
329
  }
247
330
  })
331
+
248
332
  this.$emit('judgeAnswer', {
249
- ...v,
250
- judgeAnswer: this.ques(v.type) ? answerArr[0] : answerArr
333
+ ...item,
334
+ judgeAnswer: this.ques(item.type) ? answerArr[0] : answerArr
251
335
  })
252
336
  },
337
+
338
+ /**
339
+ * 多选模式勾选框变更
340
+ * @param {Event} e - change事件
341
+ * @param {number} index - 试题在value中的索引
342
+ */
253
343
  checkboxChange(e, index) {
254
344
  this.value[index].checked = e.target.checked
255
345
  this.$emit('change', {
@@ -258,15 +348,25 @@ export default {
258
348
  })
259
349
  this.$emit('changeAll', this.value)
260
350
  },
261
- ques(v) {
262
- return v?.indexOf('101') > -1
351
+
352
+ /**
353
+ * 判断是否为单选题
354
+ * @param {string} type - 试题类型编码
355
+ * @returns {boolean} 是否为单选题
356
+ */
357
+ ques(type) {
358
+ return type?.includes('101')
263
359
  },
264
- styleText(v) {
265
- if (!v || v === 'null') return
266
- for (let i = 0; i < v.length; i++) {
267
- if (v[i].content[0]?.length > 10) {
268
- return true
269
- }
360
+
361
+ /**
362
+ * 判断选项文本是否超长(超过10字需要改为纵向排列)
363
+ * @param {Array} options - 选项数组
364
+ * @returns {boolean}
365
+ */
366
+ styleText(options) {
367
+ if (!options || options === 'null') return false
368
+ for (let i = 0; i < options.length; i++) {
369
+ if (options[i].content[0]?.length > 10) return true
270
370
  }
271
371
  return false
272
372
  },
package/src/index.js CHANGED
@@ -87,7 +87,7 @@ function install(app) {
87
87
  }
88
88
 
89
89
  export default {
90
- version: '1.35.630',
90
+ version: '1.35.632',
91
91
  install,
92
92
  Calendar,
93
93
  Message,