zydx-plus 1.35.630 → 1.35.631

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.631",
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="multipleDisabled" :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,290 @@
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 // 是否禁用交互
93
+ },
94
+ multipleDisabled: {
95
+ type: Boolean,
96
+ default: false // 多选模式是否禁用交互
77
97
  },
78
98
  multiple: {
79
99
  type: Boolean,
80
- default: false
100
+ default: false // 是否开启多选模式
81
101
  },
82
102
  borderShow: {
83
103
  type: Boolean,
84
- default: true
104
+ default: true // 是否显示外边框
85
105
  },
86
106
  titleText: {
87
107
  type: String,
88
- default: '材料'
108
+ default: '材料' // 材料标题前缀
89
109
  },
90
110
  chooseAnswer: {
91
111
  type: Boolean,
92
- default: true
112
+ default: true // 是否显示选项图标(对/错标记)
93
113
  },
94
114
  scoreShow: {
95
115
  type: Boolean,
96
- default: false
116
+ default: false // 是否显示分值
97
117
  },
98
118
  titleCrude: {
99
119
  type: Boolean,
100
- default: false
120
+ default: false // 标题是否加粗
101
121
  },
102
- isLook: { // 题库资源中,查看试题true,创建试题false(新版题库类资源-选择题编辑状态时,试题选项只在编辑框中显示此时isLook: false)
122
+ isLook: {
103
123
  type: Boolean,
104
- default: true
124
+ default: true // 查看模式:true=查看试题,false=编辑试题(创建试题时选项仅在编辑框显示)
105
125
  }
106
126
  },
127
+
128
+ /** 数据监听 */
107
129
  watch: {
108
130
  data: {
109
- handler: function (val, oldVal) {
131
+ handler: function (val) {
110
132
  this.init()
111
133
  },
112
134
  immediate: true,
113
135
  deep: true,
114
136
  },
115
137
  multiple: {
116
- handler: function (val, oldVal) {
138
+ handler: function (val) {
117
139
  this.multipleShow = val
118
140
  },
119
- immediate: true,
120
- deep: true
121
- },
122
- replace: {
123
- handler: function (val, oldVal) {
124
- this.replaceShow = val
125
- },
126
- deep: true
141
+ immediate: true
127
142
  }
128
143
  },
144
+
145
+ /** 生命周期 */
129
146
  mounted() {
130
147
  this.id = this.randomId()
131
148
  },
149
+
150
+ /** 对外事件 */
132
151
  emits: ['change', 'butChange', 'changeAll', 'judgeAnswer'],
152
+
133
153
  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'
154
+ /**
155
+ * 获取选项的状态样式
156
+ * @param {Object} ts - 选项数据
157
+ * @param {Object} item - 所属试题
158
+ * @returns {string} 'correct' | 'error' | 'default'
159
+ */
160
+ stateFun(ts, item) {
161
+ if (this.disabled && ts.checked && !item.error) return 'correct'
162
+ if (this.disabled && ts.checked && item.error) return 'error'
137
163
  return 'default'
138
164
  },
139
- customButTap(e,d) {
140
- if(e.key === 'option') d.open = !d.open
141
- if(e.single) {
142
- if(!e.active) {
165
+
166
+ /**
167
+ * 自定义按钮点击处理
168
+ * @param {Object} button - 按钮配置
169
+ * @param {Object} item - 所属试题
170
+ */
171
+ customButTap(button, item) {
172
+ // option键:切换材料附件的展开/收起
173
+ if (button.key === 'option') item.open = !item.open
174
+
175
+ if (button.single) {
176
+ // 单选模式按钮:取消其他同组单选按钮的选中状态
177
+ if (!button.active) {
143
178
  this.value.forEach(x => {
144
- // 把x.customBut里single是true的active设置成false
145
179
  const singleBut = x.customBut.filter(y => y.single)
146
- if(singleBut) singleBut.forEach(y => y.active = false)
180
+ if (singleBut) singleBut.forEach(y => y.active = false)
147
181
  })
148
182
  }
149
- e.active = !e.active
150
- }else {
151
- if(e.butStart) e.active = !e.active
183
+ button.active = !button.active
184
+ } else {
185
+ // 非单选模式:带butStart样式的按钮才能切换
186
+ if (button.butStart) button.active = !button.active
152
187
  }
153
- if(e.active) {
154
- if(e.key === 'disabled') {
188
+
189
+ // disabled键特殊逻辑:切换全局禁用状态
190
+ if (button.active) {
191
+ if (button.key === 'disabled') {
155
192
  this.disabledFun(true)
156
- e.disabled = false
193
+ button.disabled = false
157
194
  }
158
- }else {
195
+ } else {
159
196
  this.disabledFun(false)
160
197
  }
161
- this.$emit('butChange',e,d)
198
+
199
+ this.$emit('butChange', button, item)
162
200
  },
163
- disabledFun(e) {
201
+
202
+ /**
203
+ * 设置所有试题中 disabled 按钮的禁用状态
204
+ * @param {boolean} state - 目标禁用状态
205
+ */
206
+ disabledFun(state) {
164
207
  this.value.forEach(x => {
165
208
  x.customBut.forEach(y => {
166
- if(y.key === 'disabled') y.disabled = e
209
+ if (y.key === 'disabled') y.disabled = state
167
210
  })
168
211
  })
169
212
  },
170
- annexHtml(x) {
171
- return json2html({node: "root", child: typeof x === 'string' ? JSON.parse(x) : x})
213
+
214
+ /**
215
+ * 将附件内容转换为HTML
216
+ * @param {Object|string} data - 附件JSON数据
217
+ * @returns {string} HTML字符串
218
+ */
219
+ annexHtml(data) {
220
+ return json2html({ node: "root", child: typeof data === 'string' ? JSON.parse(data) : data })
221
+ },
222
+
223
+ /**
224
+ * 解析试题的判题答案
225
+ * @param {Object} item - 试题数据
226
+ * @returns {*} 单选返回字符串,多选返回数组
227
+ */
228
+ resolveJudgeAnswer(item) {
229
+ if (item.stuAnswer) return item.stuAnswer?.answer
230
+ return item?.judgeAnswer
231
+ },
232
+
233
+ /**
234
+ * 根据判题答案回填选项的选中状态
235
+ * @param {Object} item - 试题数据(含已解析的testKey)
236
+ */
237
+ markCheckedOptions(item) {
238
+ const judge = this.resolveJudgeAnswer(item)
239
+ const isSingle = this.ques(item.type)
240
+
241
+ item.testKey = item.testKey.map(x => {
242
+ if (isSingle) {
243
+ // 单选题:匹配单个答案
244
+ x.checked = x.index === judge
245
+ } else {
246
+ // 多选题:匹配答案数组中的任一值
247
+ x.checked = Array.isArray(judge) ? judge.includes(x.index) : false
248
+ }
249
+ return x
250
+ })
172
251
  },
252
+
253
+ /**
254
+ * 恢复已缓存试题的打开状态(数据更新时保留用户操作)
255
+ * @param {Array} cachedList - 更新前的value副本
256
+ */
257
+ restoreOpenState(cachedList) {
258
+ if (!cachedList.length || !this.value.length) return
259
+
260
+ const subjectiveTypes = ['101', '102', '102_1', '102_2']
261
+
262
+ cachedList.forEach((item, index) => {
263
+ if (!this.value[index]) return
264
+ if (item.questionId !== this.value[index].questionId) return
265
+
266
+ this.value[index].open = item.open
267
+
268
+ // 主观题:恢复"试题设置"按钮状态
269
+ if (subjectiveTypes.includes(item.type) && this.isLook) {
270
+ if (this.value[index].customBut) {
271
+ this.value[index].customBut[0].active = item.open
272
+ }
273
+ }
274
+ })
275
+ },
276
+
277
+ /**
278
+ * 核心初始化方法:深拷贝data → 解析testKey → 回填答案 → 恢复状态
279
+ */
173
280
  init() {
174
281
  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
282
+ const cachedList = this.value.length ? this.value : []
283
+
284
+ this.value = val.map((item) => {
285
+ // 设置默认值
180
286
  item.open = item.open !== undefined ? item.open : true
181
287
  item.error = item.error !== undefined ? item.error : false
182
288
  item.subject = item.subject !== undefined ? item.subject : true
289
+
290
+ // 解析选项并回填答案
183
291
  if (item.testKey) {
184
292
  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
- })
293
+ this.markCheckedOptions(item)
202
294
  }
295
+
203
296
  return item
204
297
  })
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
- })
298
+
299
+ // 恢复之前缓存的开合状态
300
+ this.restoreOpenState(cachedList)
228
301
  },
229
- randomId() { // 生成随机id
230
- const id = Math.random().toString(36).substr(2)
302
+
303
+ /**
304
+ * 生成不包含数字的随机ID(用于input的name分组)
305
+ * @returns {string} 纯字母随机ID
306
+ */
307
+ randomId() {
308
+ const id = Math.random().toString(36).substring(2)
231
309
  return id.replace(/[0-9]/g, '')
232
310
  },
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
- })
311
+
312
+ /**
313
+ * 选项变更回调
314
+ * @param {Event} e - change事件
315
+ * @param {Object} item - 当前试题
316
+ * @param {string} name - input的name标识
317
+ * @param {Object} check - 被操作的选项
318
+ */
319
+ answerChange(e, item, name, check) {
320
+ // 单选题:先清空所有选项
321
+ if (this.ques(item.type)) {
322
+ item.testKey.forEach(x => { x.checked = false })
239
323
  }
324
+
240
325
  check.checked = e.target.checked
241
- let answerArr = []
242
- const ids = document.getElementsByName(id)
326
+
327
+ // 收集当前选中的答案
328
+ const answerArr = []
329
+ const ids = document.getElementsByName(name)
243
330
  ids.forEach((x, index) => {
244
331
  if (x.checked) {
245
- answerArr.push(v.testKey[index].index)
332
+ answerArr.push(item.testKey[index].index)
246
333
  }
247
334
  })
335
+
248
336
  this.$emit('judgeAnswer', {
249
- ...v,
250
- judgeAnswer: this.ques(v.type) ? answerArr[0] : answerArr
337
+ ...item,
338
+ judgeAnswer: this.ques(item.type) ? answerArr[0] : answerArr
251
339
  })
252
340
  },
341
+
342
+ /**
343
+ * 多选模式勾选框变更
344
+ * @param {Event} e - change事件
345
+ * @param {number} index - 试题在value中的索引
346
+ */
253
347
  checkboxChange(e, index) {
254
348
  this.value[index].checked = e.target.checked
255
349
  this.$emit('change', {
@@ -258,15 +352,25 @@ export default {
258
352
  })
259
353
  this.$emit('changeAll', this.value)
260
354
  },
261
- ques(v) {
262
- return v?.indexOf('101') > -1
355
+
356
+ /**
357
+ * 判断是否为单选题
358
+ * @param {string} type - 试题类型编码
359
+ * @returns {boolean} 是否为单选题
360
+ */
361
+ ques(type) {
362
+ return type?.includes('101')
263
363
  },
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
- }
364
+
365
+ /**
366
+ * 判断选项文本是否超长(超过10字需要改为纵向排列)
367
+ * @param {Array} options - 选项数组
368
+ * @returns {boolean}
369
+ */
370
+ styleText(options) {
371
+ if (!options || options === 'null') return false
372
+ for (let i = 0; i < options.length; i++) {
373
+ if (options[i].content[0]?.length > 10) return true
270
374
  }
271
375
  return false
272
376
  },
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.631',
91
91
  install,
92
92
  Calendar,
93
93
  Message,