yqform-edit 1.0.0

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.
Files changed (45) hide show
  1. package/.prettierrc +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +27 -0
  4. package/dist/demo.html +10 -0
  5. package/dist/fonts/element-icons.535877f5.woff +0 -0
  6. package/dist/fonts/element-icons.732389de.ttf +0 -0
  7. package/dist/yqform-edit.common.js +73796 -0
  8. package/dist/yqform-edit.css +1 -0
  9. package/dist/yqform-edit.umd.js +73806 -0
  10. package/dist/yqform-edit.umd.min.js +23 -0
  11. package/package.json +59 -0
  12. package/src/App.vue +126 -0
  13. package/src/assets/global.scss +12 -0
  14. package/src/assets/images/content/add.png +0 -0
  15. package/src/assets/images/content/arrow-left.png +0 -0
  16. package/src/assets/images/content/arrow-right.png +0 -0
  17. package/src/assets/images/content/delete.png +0 -0
  18. package/src/assets/images/content/edit.png +0 -0
  19. package/src/assets/images/content/electricity.png +0 -0
  20. package/src/assets/images/content/item_plus.png +0 -0
  21. package/src/assets/images/content/radio_checked.png +0 -0
  22. package/src/assets/images/content/radio_uncheck.png +0 -0
  23. package/src/assets/images/content/slider_btn.png +0 -0
  24. package/src/assets/images/content/wifi.png +0 -0
  25. package/src/assets/logo.png +0 -0
  26. package/src/components/common/content/components/form-item/components/Picker.vue +144 -0
  27. package/src/components/common/content/components/form-item/form-item.vue +829 -0
  28. package/src/components/common/content/components/form-item/index.js +5 -0
  29. package/src/components/common/content/components/input-box/input-box.vue +29 -0
  30. package/src/components/common/content/content.vue +504 -0
  31. package/src/components/common/content/index.js +5 -0
  32. package/src/components/common/rightConfig/formConfig.vue +319 -0
  33. package/src/components/dynamic-form/components/component-content.vue +530 -0
  34. package/src/components/dynamic-form/components/component-options.vue +1112 -0
  35. package/src/components/dynamic-form/components/component-type.vue +588 -0
  36. package/src/components/dynamic-form/dynamic-form.vue +120 -0
  37. package/src/components/dynamic-form/index.js +40 -0
  38. package/src/index.js +69 -0
  39. package/src/main.js +62 -0
  40. package/src/store/e7ingForm.js +10 -0
  41. package/src/store/getters.js +22 -0
  42. package/src/store/index.js +10 -0
  43. package/src/store/mutations.js +106 -0
  44. package/src/store/state.js +321 -0
  45. package/src/utils/index.js +23 -0
@@ -0,0 +1,5 @@
1
+ import FormItem from './form-item.vue'
2
+
3
+ FormItem.install = (Vue) => {
4
+ Vue.component(FormItem.name, FormItem)
5
+ }
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div class="input-box" :class="{active:active}">
3
+ <slot/>
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+ export default {
9
+ name: 'InputBox',
10
+ props: {
11
+ active: {
12
+ type: Boolean,
13
+ default: false
14
+ }
15
+ }
16
+ }
17
+ </script>
18
+
19
+ <style scoped lang="scss">
20
+ .input-box {
21
+ padding: 15px 10px 10px 15px;
22
+ border-radius: 5px;
23
+ border: 1px solid rgba(202, 218, 237, 1);
24
+
25
+ &.active {
26
+ border: 1px solid rgba(42, 130, 228, 1);
27
+ }
28
+ }
29
+ </style>
@@ -0,0 +1,504 @@
1
+ <template>
2
+ <div>
3
+ <main class="main" id="form-content">
4
+ <div class="content-topimg">
5
+ <div class="header-img" v-if="form && form.spec.theme.header">
6
+ <van-image width="100%" :src="form.spec.theme.header" />
7
+ </div>
8
+ <div class="header" v-if="form">
9
+ <div class="title">{{ currentPage.spec.title }}</div>
10
+ <div class="page" v-show="totalPage > 1">
11
+ ( 第{{ pageIndex + 1 }}{{ unit }},共{{ totalPage }}{{ unit }} )
12
+ </div>
13
+ </div>
14
+ <div class="sub-header" v-if="form && currentPage.spec.subtitle">
15
+ {{ currentPage.spec.subtitle }}
16
+ </div>
17
+ <div>
18
+ <draggable
19
+ class="item-container"
20
+ style="width: 100%; min-height: 330px"
21
+ v-model="currentPage.items"
22
+ group="form"
23
+ @start="drag = true"
24
+ @change="draggableChange"
25
+ @end="drag = false"
26
+ v-bind="dragOptions">
27
+ <div class="item" v-for="(item, index) of currentPage.items" :key="item.key">
28
+ <div :id="item.key">
29
+ <FormItem
30
+ ref="formItem"
31
+ :item="item"
32
+ :indexTag="getindexTag(index)"
33
+ :itemIndex="index"
34
+ :editItem="canEdit"
35
+ :currentKey="currentKey"
36
+ :currentPageIndex="currentPageIndex"
37
+ :noAnswer="noAnswer"
38
+ :isEdit="isEdit"
39
+ :dependValues="dependValues"
40
+ :form="form"
41
+ @selectItem="selectItem" />
42
+ </div>
43
+ </div>
44
+ <div v-if="!notControl" class="tip" :class="!form.spec.theme.footer ? 'hasBottom' : ''">
45
+ <div class="plus-icon">
46
+ <img src="@/assets/images/content/item_plus.png" alt="" />
47
+ </div>
48
+ <div class="text">从左侧拖动类型到此,添加问卷题目</div>
49
+ </div>
50
+ <div v-else class="tip-shadow"></div>
51
+ </draggable>
52
+ </div>
53
+ </div>
54
+ <div class="footer-img" v-if="form && form.spec.theme.footer">
55
+ <van-image width="100%" :src="form.spec.theme.footer" />
56
+ </div>
57
+ </main>
58
+ </div>
59
+ </template>
60
+ <script>
61
+ import draggable from 'vuedraggable'
62
+ import FormItem from './components/form-item/form-item.vue'
63
+ export default {
64
+ name: 'Content',
65
+ emits: ['onSelect'],
66
+ components: { FormItem, draggable },
67
+ props: {
68
+ pageIndex: {
69
+ type: Number,
70
+ required: false,
71
+ default: 0,
72
+ },
73
+ currentKey: {
74
+ type: String,
75
+ required: false,
76
+ default: '',
77
+ },
78
+ // 是否可拖入题目
79
+ noControl: {
80
+ type: Boolean,
81
+ required: false,
82
+ default: false,
83
+ },
84
+ // 是否可编辑题目
85
+ editItem: {
86
+ type: Boolean,
87
+ required: false,
88
+ default: true,
89
+ },
90
+ isEdit: {
91
+ type: Boolean,
92
+ required: false,
93
+ default: true,
94
+ },
95
+ form: {
96
+ type: Object,
97
+ default: function () {
98
+ return {
99
+ spec: {
100
+ name: '',
101
+ theme: {
102
+ name: 'show',
103
+ color: '#07c160',
104
+ header: '',
105
+ footer: '',
106
+ },
107
+ unit: '页',
108
+ },
109
+ groups: [
110
+ {
111
+ gid: 'templeteData',
112
+ spec: {
113
+ title: '',
114
+ subtitle: '',
115
+ unit: '页',
116
+ },
117
+ items: [],
118
+ },
119
+ ],
120
+ }
121
+ },
122
+ },
123
+ },
124
+ data() {
125
+ return {
126
+ notControl: false,
127
+ dragOptions: {
128
+ handle: '.move',
129
+ ghostClass: 'ghost',
130
+ filter: '.tip',
131
+ },
132
+ currentPage: {},
133
+ item: {},
134
+ totalPage: 1,
135
+ noAnswer: '',
136
+ dependValues: [],
137
+ currentItemKey: '',
138
+ canEdit: false,
139
+ currentPageIndex: 0,
140
+ }
141
+ },
142
+ watch: {
143
+ isEdit: {
144
+ handler(value) {
145
+ if (!value) {
146
+ this.notControl = true
147
+ this.canEdit = false
148
+ } else {
149
+ this.notControl = this.noControl
150
+ this.canEdit = this.editItem
151
+ }
152
+ },
153
+ immediate: true,
154
+ },
155
+ pageIndex: {
156
+ handler(newIndex) {
157
+ if (this.form) {
158
+ this.currentPage = this.form.groups[newIndex]
159
+ }
160
+ if (this.currentPageIndex !== newIndex) {
161
+ this.currentPageIndex = newIndex
162
+ }
163
+ },
164
+ immediate: true,
165
+ },
166
+ currentKey: {
167
+ handler(key) {
168
+ if (key) {
169
+ this.currentItemKey = key
170
+ }
171
+ },
172
+ immediate: true,
173
+ },
174
+ form: {
175
+ handler(newForm) {
176
+ if (newForm) {
177
+ console.log(newForm.groups[0])
178
+ this.totalPage = newForm.groups.length
179
+ this.currentPage = newForm.groups[this.pageIndex]
180
+ }
181
+ },
182
+ immediate: true,
183
+ deep: true,
184
+ },
185
+ },
186
+ provide() {
187
+ return {
188
+ addFile: this.addFile,
189
+ deleteFile: this.deleteFile,
190
+ }
191
+ },
192
+ computed: {
193
+ unit() {
194
+ return this.form?.spec.unit
195
+ },
196
+ },
197
+ methods: {
198
+ selectItem(key) {
199
+ this.$emit('onSelect', key)
200
+ this.currentItemKey = key
201
+ },
202
+ draggableChange(data) {
203
+ if (data.added) {
204
+ this.$emit('onSelect', data.added.element.key)
205
+ this.currentItemKey = data.added.element.key
206
+ }
207
+ },
208
+ goPage(index) {
209
+ this.currentPageIndex = index
210
+ this.$emit('getPage', index)
211
+ },
212
+ // 计算题号
213
+ getindexTag(index) {
214
+ if (this.pageIndex === 0) {
215
+ return index + 1
216
+ } else {
217
+ const groups = this.form.groups
218
+ let count = 0
219
+ for (let i = 0; i < this.pageIndex; i++) {
220
+ count += groups[i].items.length
221
+ }
222
+ count += index + 1
223
+ return count
224
+ }
225
+ },
226
+ // 检查输入类题目是否符合要求
227
+ checkInputText(item, newText, page, index, key) {
228
+ let min, max
229
+ // 手机号没有最小最大值配置,需要单独判断
230
+ if (item.type !== 'phone') {
231
+ min = item.range.min
232
+ max = item.range.max
233
+ }
234
+ // 优先判断内容是否为空
235
+ if (newText.length === 0) {
236
+ if (item.required) {
237
+ this.goPage(page)
238
+ this.selectItem(key)
239
+ this.noAnswerKey = ''
240
+ setTimeout(() => {
241
+ this.noAnswerKey = key
242
+ })
243
+ this.itemPosition(key)
244
+ return false
245
+ } else {
246
+ // 非必填项对空数据不做判断
247
+ return true
248
+ }
249
+ } else if (item.type === 'text' || item.type === 'password') {
250
+ const len = newText.length
251
+ if ((min && len < min * 1) || (max && len > max * 1)) {
252
+ this.goPage(page)
253
+ this.selectItem(key)
254
+ this.itemPosition(key)
255
+ return false
256
+ } else {
257
+ return true
258
+ }
259
+ } else if (item.type === 'integer' || item.type === 'float') {
260
+ if ((min && newText * 1 < min * 1) || (max && newText * 1 > max * 1)) {
261
+ this.goPage(page)
262
+ this.selectItem(key)
263
+ this.itemPosition(key)
264
+ return false
265
+ } else {
266
+ return true
267
+ }
268
+ } else {
269
+ return true
270
+ }
271
+ },
272
+ // 题目不合法时定位到该题目
273
+ itemPosition(key) {
274
+ setTimeout(() => {
275
+ const pageDom = document.getElementById('form-content')
276
+ const itemDom = document.getElementById(key)
277
+ const offsetTop = itemDom.offsetTop // 题目上边所在的位置
278
+ const clientHeight = itemDom.clientHeight // 题目区域的高度
279
+ if (offsetTop < clientHeight) {
280
+ pageDom.scrollTo(0, offsetTop - 40)
281
+ } else if (offsetTop + clientHeight < pageDom.scrollHeight) {
282
+ pageDom.scrollTo(0, offsetTop)
283
+ } else {
284
+ pageDom.scrollTo(0, offsetTop + clientHeight)
285
+ }
286
+ }, 50)
287
+ },
288
+ // 检查问卷
289
+ getAnswer() {
290
+ console.log('this.dependValues', this.dependValues)
291
+ const answerObj = {}
292
+ const fileItem = []
293
+ const imgItem = []
294
+ // 遍历页面
295
+ for (let r = 0; r < this.form.groups.length; r++) {
296
+ let itemArr = this.form.groups[r].items
297
+ let gid = this.form.groups[r].gid
298
+ if (itemArr.length > 0) {
299
+ // 遍历该页题目
300
+ for (let i = 0; i < itemArr.length; i++) {
301
+ let hasAnswer = false
302
+ const key = itemArr[i].key
303
+ // 遍历答案数组
304
+ for (let j = 0; j < this.dependValues.length; j++) {
305
+ let answer = this.dependValues[j]
306
+ if (gid === answer.gid && itemArr[i].key === answer.key) {
307
+ // 如果答案中存在对应的页面gid和题号key,则保存该题目的答案
308
+ hasAnswer = true
309
+ if (this.showTitleLimit.includes(itemArr[i].type) || itemArr[i].type === 'phone') {
310
+ // 如果输入类题目内容不合法则中断提交
311
+ if (!this.checkInputText(itemArr[i], answer.value, r, i, key)) {
312
+ return
313
+ }
314
+ }
315
+ if (this.hasOptionTypes.includes(itemArr[i].type)) {
316
+ if (itemArr[i].type === 'radio' || itemArr[i].type === 'rate') {
317
+ console.log('answer.value', answer)
318
+ let option = itemArr[i].options.find((item) => item.id == answer.value)
319
+ answerObj[`${answer.key}`] = [{ id: option.id, content: option.content }]
320
+ } else if (itemArr[i].type === 'checkbox') {
321
+ let key = answer.key
322
+ answerObj[`${key}`] = []
323
+ answer.value.forEach((item) => {
324
+ let option = itemArr[i].options.find((it) => it.id == item)
325
+ answerObj[`${key}`].push({ id: option.id, content: option.content })
326
+ })
327
+ } else if (itemArr[i].type === 'switch') {
328
+ let option = itemArr[i].options.find((item) => item.content === answer.value)
329
+ answerObj[`${answer.key}`] = [{ id: option.id, content: option.content }]
330
+ } else if (itemArr[i].type === 'picker') {
331
+ let option = itemArr[i].options.find((item) => item.id === answer.value.id)
332
+ answerObj[`${answer.key}`] = [{ id: option.id, content: option.content }]
333
+ }
334
+ } else if (itemArr[i].type === 'area') {
335
+ answerObj[`${answer.key}`] = [{ id: answer.value.code, content: answer.value.value }]
336
+ } else if (itemArr[i].type === 'file') {
337
+ const urlArr = []
338
+ answer.value.forEach((item) => {
339
+ urlArr.push({ url: item.url, name: item.name })
340
+ })
341
+ fileItem.push(answer)
342
+ answerObj[`${answer.key}`] = [{ content: urlArr }]
343
+ } else if (itemArr[i].type === 'image') {
344
+ const urlArr = []
345
+ answer.value.forEach((item) => {
346
+ urlArr.push({ url: item.url })
347
+ })
348
+ imgItem.push(answer)
349
+ answerObj[`${answer.key}`] = [{ content: urlArr }]
350
+ } else {
351
+ answerObj[`${answer.key}`] = [{ content: answer.value }]
352
+ }
353
+ }
354
+ }
355
+ // 如果当前题目没有匹配到答案且该题目是必填项
356
+ if (!hasAnswer && itemArr[i].required) {
357
+ if (itemArr[i].depends.length > 0) {
358
+ // 如果该题目没有答案则判断其是否有依赖选项
359
+ let depends = itemArr[i].depends
360
+ for (let k = 0; k < depends.length; k++) {
361
+ let index = this.dependValues.findIndex(
362
+ (item) => item.key === depends[k].item && item.gid === depends[k].group,
363
+ )
364
+ // 找到所依赖的题目
365
+ if (index > -1) {
366
+ let hasId = false
367
+ let vals = depends[k].vals
368
+ // 遍历依赖的选项
369
+ for (let h = 0; h < vals.length; h++) {
370
+ console.log('this.dependValues[index]', this.dependValues[index].value)
371
+ // 判断答案中是否有所依赖的选项
372
+ if (typeof this.dependValues[index].value === 'string') {
373
+ // 处理单选题
374
+ if (this.dependValues[index].value === vals[h].id) {
375
+ hasId = true
376
+ break
377
+ }
378
+ // 处理多选题
379
+ } else if (this.dependValues[index].value.findIndex((item) => item === vals[h].id) > -1) {
380
+ hasId = true
381
+ break
382
+ }
383
+ }
384
+ if (hasId) {
385
+ this.goPage(r)
386
+ this.setCurrentItemKey(key)
387
+ this.noAnswer = ''
388
+ setTimeout(() => {
389
+ this.noAnswer = key
390
+ })
391
+ this.itemPosition(key)
392
+ return
393
+ }
394
+ }
395
+ }
396
+ hasAnswer = true
397
+ }
398
+ if (!hasAnswer) {
399
+ // 用户没有作答该题
400
+ this.goPage(r)
401
+ this.setCurrentItemKey(key)
402
+ // 每次都重置未作答题目的key值(因为两次可能会是同一题,导致第二次不会触发)
403
+ this.noAnswer = ''
404
+ // 异步更改key值,否则隔页不会触发key的更改
405
+ setTimeout(() => {
406
+ this.noAnswer = key
407
+ })
408
+ this.itemPosition(key)
409
+ return
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
415
+ return {
416
+ files: fileItem,
417
+ imgs: imgItem,
418
+ answer: answerObj,
419
+ }
420
+ },
421
+ },
422
+ }
423
+ </script>
424
+ <style scoped lang="scss">
425
+ ::-webkit-scrollbar {
426
+ width: 0;
427
+ height: 0;
428
+ }
429
+ .main {
430
+ flex: 1;
431
+ overflow: auto;
432
+ display: flex;
433
+ height: 100%;
434
+ flex-direction: column;
435
+ justify-content: space-between;
436
+ .content-topimg {
437
+ padding: 0 12px;
438
+ box-sizing: border-box;
439
+ .header {
440
+ display: flex;
441
+ justify-content: space-between;
442
+ flex-shrink: 0;
443
+ align-items: center;
444
+ line-height: 20px;
445
+ margin: 12px 0;
446
+
447
+ .title {
448
+ font-size: 14px;
449
+ font-weight: 500;
450
+ letter-spacing: 0px;
451
+ word-break: break-all;
452
+ color: rgba(24, 144, 255, 1);
453
+ }
454
+
455
+ .page {
456
+ text-align: right;
457
+ min-width: 40%;
458
+ font-size: 12px;
459
+ font-weight: 500;
460
+ letter-spacing: 0px;
461
+ color: rgba(153, 153, 153, 1);
462
+ }
463
+ }
464
+ .sub-header {
465
+ font-size: 12px;
466
+ font-weight: 500;
467
+ letter-spacing: 0px;
468
+ word-break: break-all;
469
+ color: rgba(153, 153, 153, 1);
470
+ }
471
+ .item-container {
472
+ padding: 5px;
473
+ box-sizing: border-box;
474
+ }
475
+ .tip {
476
+ height: 98px;
477
+ margin-top: 10px;
478
+ justify-content: center;
479
+ text-align: center;
480
+ font-size: 14px;
481
+ font-weight: 400;
482
+ letter-spacing: 0px;
483
+ line-height: 20px;
484
+ color: rgba(166, 166, 166, 1);
485
+ border: 1px dashed rgba(24, 144, 255, 1);
486
+ .plus-icon {
487
+ margin-top: 20px;
488
+ margin-bottom: 5px;
489
+ }
490
+ }
491
+ .tip-shadow {
492
+ height: 50px;
493
+ }
494
+ .hasBottom {
495
+ margin-bottom: 50px;
496
+ }
497
+ }
498
+ .footer-img {
499
+ margin-bottom: 50px;
500
+ display: flex;
501
+ flex-direction: column-reverse;
502
+ }
503
+ }
504
+ </style>
@@ -0,0 +1,5 @@
1
+ import FormItem from './form-item.vue'
2
+
3
+ FormItem.install = (Vue) => {
4
+ Vue.component(FormItem.name, FormItem)
5
+ }