zydx-plus 1.35.638 → 1.35.640
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,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TipBox 提示弹窗组件入口文件
|
|
3
|
+
* 动态渲染提示弹窗,支持多种类型:文本提示(text)、图片(img)、单选框(radio)、复选框(checkbox)、输入表单(input)、日历选择(calender)等
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { createVNode, render } from 'vue'
|
|
2
7
|
import XtxConfirm from './src/main.vue'
|
|
3
8
|
|
|
@@ -8,12 +13,14 @@ render(divNode, document.body)
|
|
|
8
13
|
const container = divNode.el
|
|
9
14
|
|
|
10
15
|
// 导出函数可通过调用 Confirm() 函数动态创建 XtxConfirm 组件
|
|
16
|
+
// 参数说明:type(弹窗类型)、title(标题)、promptContent(提示内容)、cancelShow(是否显示取消按钮)、inputArr(输入项数组)、countdown(倒计时)等
|
|
11
17
|
const Confirm = ({ className, type, url ,title,radioArr,checkboxArr,submitText,cancelText,closeShow, promptContent, middle, cancelShow, inputArr, countdown, components, slotProps }) => {
|
|
12
18
|
// 返回 Promise 对象
|
|
13
19
|
return new Promise((resolve, reject) => {
|
|
14
20
|
// 2. 点击确认按钮,触发resolve同时销毁组件
|
|
15
21
|
const submit = () => {
|
|
16
22
|
let val = []
|
|
23
|
+
// 收集输入框类型的值
|
|
17
24
|
if(type === 'input') {
|
|
18
25
|
for(let i=0; i< inputArr.length; i++) {
|
|
19
26
|
if(inputArr[i].type === 'select') {
|
|
@@ -26,6 +33,7 @@ const Confirm = ({ className, type, url ,title,radioArr,checkboxArr,submitText,c
|
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
35
|
}
|
|
36
|
+
// 收集单选项的值
|
|
29
37
|
if(type === 'radio') {
|
|
30
38
|
for(let i=0; i< radioArr.length; i++) {
|
|
31
39
|
if(radioArr[i].checked) {
|
|
@@ -33,6 +41,7 @@ const Confirm = ({ className, type, url ,title,radioArr,checkboxArr,submitText,c
|
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
}
|
|
44
|
+
// 收集多选项的值
|
|
36
45
|
if(type === 'checkbox') {
|
|
37
46
|
for(let i=0; i< checkboxArr.length; i++) {
|
|
38
47
|
if(checkboxArr[i].checked) {
|
|
@@ -48,6 +57,7 @@ const Confirm = ({ className, type, url ,title,radioArr,checkboxArr,submitText,c
|
|
|
48
57
|
render(null, container)
|
|
49
58
|
reject('点击取消')
|
|
50
59
|
}
|
|
60
|
+
// 关闭弹窗
|
|
51
61
|
const close = () => {
|
|
52
62
|
render(null, container)
|
|
53
63
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TipBox 提示弹窗组件主文件
|
|
3
|
+
* 致用大学 - 支持多种弹窗类型:文本提示、图片展示、单选框、复选框、表单输入、日历选择、年份选择、文件上传、封面图等
|
|
4
|
+
* 通过入口文件动态创建该组件实现函数式调用弹窗
|
|
5
|
+
*/
|
|
6
|
+
|
|
1
7
|
<template>
|
|
2
8
|
<div class="hello">
|
|
9
|
+
<!-- 弹窗遮罩层 -->
|
|
3
10
|
<div class="tip-popupWindowSearch">
|
|
4
11
|
<div class="tip-box" :class="className">
|
|
5
12
|
<img v-if="closeShow" @click="close" class="close"
|
|
@@ -10,13 +17,16 @@
|
|
|
10
17
|
</div>
|
|
11
18
|
<div class="tip-boxContent" ref="tip-boxContent" :style="overflowShow?'overflow-y: scroll':''">
|
|
12
19
|
<div class="tap-p" ref="tapP">
|
|
20
|
+
<!-- 插槽类型:渲染自定义组件 -->
|
|
13
21
|
<div v-if="type === 'slot'">
|
|
14
22
|
<component :is="components" v-bind="slotProps"></component>
|
|
15
23
|
</div>
|
|
24
|
+
<!-- 文本类型提示内容 -->
|
|
16
25
|
<div class="tip-title2" ref="tipTitle" v-if="type === 'text'"
|
|
17
26
|
:style="{'text-align': (middles)? 'center': 'left', 'text-indent': indent? '2em': '0' }">
|
|
18
27
|
{{ promptContent }} {{ countdown === 0 ? '' : `(${timeo}s)` }}
|
|
19
28
|
</div>
|
|
29
|
+
<!-- 图片类型提示内容 -->
|
|
20
30
|
<div class="tip-img" v-if="type === 'img'">
|
|
21
31
|
<img :src="url" alt=""/>
|
|
22
32
|
</div>
|
|
@@ -41,9 +51,11 @@
|
|
|
41
51
|
</div>
|
|
42
52
|
</div>
|
|
43
53
|
<div class="tip-img" v-if="type === 'input'">
|
|
44
|
-
<div class="tip-input" v-for="(item,index) in inputArr" :key="index"
|
|
54
|
+
<div class="tip-input" v-for="(item,index) in inputArr" :key="index"
|
|
55
|
+
ref="inputRefs"
|
|
56
|
+
:class="{'is-error': errorIndex.includes(index), 'shake': errorIndex.includes(index) && shaking}">
|
|
45
57
|
<span v-if="item.name"><em>{{ item.name.replace(/:/g, "").replace(/:/g, "") }}</em><b>:</b><i
|
|
46
|
-
v-if="item.required">*</i></span>
|
|
58
|
+
v-if="item.required || required">*</i></span>
|
|
47
59
|
<div v-if="item.type === 'file'" class="file-cont">
|
|
48
60
|
<em>{{ fileName }}</em>
|
|
49
61
|
<label>
|
|
@@ -56,17 +68,17 @@
|
|
|
56
68
|
<input type="number" @keypress="isNumberKey($event,index)"
|
|
57
69
|
:placeholder="item.placeholder"
|
|
58
70
|
:maxlength="item.maxlength"
|
|
59
|
-
v-model="item.value" :disabled="item.disabled"/>
|
|
71
|
+
v-model="item.value" :disabled="item.disabled" @input="clearError(index)"/>
|
|
60
72
|
</div>
|
|
61
73
|
<input v-else type="text" :placeholder="item.placeholder" :maxlength="item.maxlength"
|
|
62
|
-
v-model="item.value" :disabled="item.disabled"/>
|
|
74
|
+
v-model="item.value" :disabled="item.disabled" @input="clearError(index)"/>
|
|
63
75
|
<b>{{ item.tail }}</b>
|
|
64
76
|
</div>
|
|
65
77
|
<div class="input-name" v-if="item.type === 'query'">
|
|
66
78
|
<div class="number-input">
|
|
67
79
|
<input type="text"
|
|
68
80
|
:placeholder="item.placeholder"
|
|
69
|
-
v-model="item.value" @input="queryDrop($event,item)" />
|
|
81
|
+
v-model="item.value" @input="queryDrop($event,item); clearError(index)" />
|
|
70
82
|
</div>
|
|
71
83
|
<div class="drop-down" v-if="dropData.length > 0">
|
|
72
84
|
<p @click="dropTap(item,item3)" v-for="item3 in dropData">{{ item3[item.column] }}</p>
|
|
@@ -86,7 +98,7 @@
|
|
|
86
98
|
</div>
|
|
87
99
|
</div>
|
|
88
100
|
<div v-if="item.type === 'select'" style="display: inline-block;width: 300px;">
|
|
89
|
-
<Select :options="item.option" :readonly="item.disabled" @select="item.select"
|
|
101
|
+
<Select :options="item.option" :readonly="item.disabled" @select="(...args) => { item.select && item.select(...args); clearError(index) }"
|
|
90
102
|
v-model:value="item.selectValue" :placeholder="item.placeholder"></Select>
|
|
91
103
|
</div>
|
|
92
104
|
<div class="cal" ref="calenderRefId" v-if="item.type === 'calender'">
|
|
@@ -135,8 +147,9 @@
|
|
|
135
147
|
</div>
|
|
136
148
|
</div>
|
|
137
149
|
</div>
|
|
150
|
+
<!-- 按钮区域:确定/取消 -->
|
|
138
151
|
<div class="tip-btnBox">
|
|
139
|
-
<div @click="
|
|
152
|
+
<div @click="submitTap" v-html="submitText"></div>
|
|
140
153
|
<div v-if="cancelShow" @click="cancel" v-html="cancelText"></div>
|
|
141
154
|
</div>
|
|
142
155
|
</div>
|
|
@@ -145,10 +158,12 @@
|
|
|
145
158
|
</template>
|
|
146
159
|
|
|
147
160
|
<script>
|
|
161
|
+
// 引入子组件
|
|
148
162
|
import Select from '../../select/src/select.vue'
|
|
149
163
|
import Calendar from '../../calendar/src/Calendar'
|
|
150
164
|
import Year from '../../year/src/year'
|
|
151
165
|
import zydxTextarea from '../../textarea'
|
|
166
|
+
// 定时器和实例引用
|
|
152
167
|
let timeStop = null
|
|
153
168
|
let _that = null
|
|
154
169
|
export default {
|
|
@@ -156,14 +171,16 @@ export default {
|
|
|
156
171
|
components: {Select, Calendar, Year,zydxTextarea},
|
|
157
172
|
data() {
|
|
158
173
|
return {
|
|
159
|
-
tip: false,
|
|
160
|
-
tipYear: false,
|
|
161
|
-
fileName: '',
|
|
162
|
-
middles: this.middle,
|
|
163
|
-
indent: false,
|
|
164
|
-
overflowShow: false,
|
|
165
|
-
timeo: 0,
|
|
166
|
-
dropData: []
|
|
174
|
+
tip: false, // 日历显示状态
|
|
175
|
+
tipYear: false, // 年份选择显示状态
|
|
176
|
+
fileName: '', // 文件名
|
|
177
|
+
middles: this.middle, // 文字居中
|
|
178
|
+
indent: false, // 文字缩进
|
|
179
|
+
overflowShow: false, // 是否显示滚动条
|
|
180
|
+
timeo: 0, // 倒计时剩余秒数
|
|
181
|
+
dropData: [], // 下拉数据
|
|
182
|
+
errorIndex: [], // 必填校验未通过的项索引集合
|
|
183
|
+
shaking: false // 是否触发抖动动画
|
|
167
184
|
}
|
|
168
185
|
},
|
|
169
186
|
props: {
|
|
@@ -207,6 +224,10 @@ export default {
|
|
|
207
224
|
type: Array,
|
|
208
225
|
default: []
|
|
209
226
|
},
|
|
227
|
+
required: { // 是否开启必填校验:为 true 时所有输入项均视为必填;单项也可通过 item.required 单独控制
|
|
228
|
+
type: Boolean,
|
|
229
|
+
default: false
|
|
230
|
+
},
|
|
210
231
|
radioArr: { // 单选数据
|
|
211
232
|
type: Array,
|
|
212
233
|
default: []
|
|
@@ -251,25 +272,85 @@ export default {
|
|
|
251
272
|
mounted() {
|
|
252
273
|
_that = this
|
|
253
274
|
this.$nextTick(() => {
|
|
275
|
+
// 文本内容过长时取消居中,启用缩进
|
|
254
276
|
if (this.type === 'text') {
|
|
255
277
|
if (this.$refs.tapP.offsetHeight > 25) {
|
|
256
278
|
this.middles = false
|
|
257
279
|
this.indent = true
|
|
258
280
|
}
|
|
259
281
|
}
|
|
282
|
+
// 启动倒计时
|
|
260
283
|
if (this.countdown > 0) {
|
|
261
284
|
this.timeo = this.countdown
|
|
262
285
|
this.countdownFun()
|
|
263
286
|
}
|
|
287
|
+
// 内容超过600px显示滚动条
|
|
264
288
|
this.overflowShow = this.$refs['tip-boxContent'].scrollHeight>600
|
|
265
289
|
})
|
|
266
290
|
},
|
|
267
291
|
beforeUnmount() {
|
|
268
|
-
clearInterval(timeStop)
|
|
292
|
+
clearInterval(timeStop) // 清理倒计时定时器
|
|
269
293
|
},
|
|
270
294
|
methods: {
|
|
295
|
+
// 点击确认:先校验必填项,全部通过才执行外部 submit
|
|
296
|
+
submitTap() {
|
|
297
|
+
if (!this.validateRequired()) return
|
|
298
|
+
this.submit()
|
|
299
|
+
},
|
|
300
|
+
// 判断某项是否必填(组件级 required 或单项 item.required)
|
|
301
|
+
isRequired(item) {
|
|
302
|
+
return !!(item.required || this.required)
|
|
303
|
+
},
|
|
304
|
+
// 判断某项的值是否为空(不同类型取值字段不同)
|
|
305
|
+
isItemEmpty(item) {
|
|
306
|
+
let val
|
|
307
|
+
switch (item.type) {
|
|
308
|
+
case 'select': val = item.selectValue; break
|
|
309
|
+
case 'cover': val = item.url; break
|
|
310
|
+
default: val = item.value
|
|
311
|
+
}
|
|
312
|
+
return val === undefined || val === null || val === '' ||
|
|
313
|
+
(Array.isArray(val) && val.length === 0)
|
|
314
|
+
},
|
|
315
|
+
// 校验必填项,返回是否通过;不通过时记录错误项并触发抖动
|
|
316
|
+
validateRequired() {
|
|
317
|
+
const errors = []
|
|
318
|
+
if (this.type === 'input') {
|
|
319
|
+
this.inputArr.forEach((item, index) => {
|
|
320
|
+
if (this.isRequired(item) && this.isItemEmpty(item)) {
|
|
321
|
+
errors.push(index)
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
this.errorIndex = errors
|
|
326
|
+
if (errors.length > 0) {
|
|
327
|
+
// 先移除抖动类,强制 reflow 后再加回,确保每次点击都能重新播放动画
|
|
328
|
+
this.shaking = false
|
|
329
|
+
this.$nextTick(() => {
|
|
330
|
+
// 读取布局属性触发强制重排,使浏览器提交"移除 shake"的中间态,
|
|
331
|
+
// 否则 nextTick 微任务内移除又加回,动画不会重新触发
|
|
332
|
+
const refs = this.$refs.inputRefs
|
|
333
|
+
if (Array.isArray(refs)) refs.forEach(el => el && el.offsetWidth)
|
|
334
|
+
else if (refs) refs.offsetWidth
|
|
335
|
+
this.shaking = true
|
|
336
|
+
})
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
this.shaking = false
|
|
340
|
+
return true
|
|
341
|
+
},
|
|
342
|
+
// 清空指定索引的错误状态(用户开始输入时调用)
|
|
343
|
+
clearError(index) {
|
|
344
|
+
const i = this.errorIndex.indexOf(index)
|
|
345
|
+
if (i > -1) this.errorIndex.splice(i, 1)
|
|
346
|
+
},
|
|
347
|
+
// 按项引用清空错误状态
|
|
348
|
+
clearErrorByItem(item) {
|
|
349
|
+
const index = this.inputArr.indexOf(item)
|
|
350
|
+
if (index > -1) this.clearError(index)
|
|
351
|
+
},
|
|
352
|
+
// 封面上传:动态创建input选择图片
|
|
271
353
|
uploadImg(index){
|
|
272
|
-
let that = this
|
|
273
354
|
const inputUpload = document.createElement('input')
|
|
274
355
|
inputUpload.type = 'file'
|
|
275
356
|
inputUpload.multiple = false
|
|
@@ -279,6 +360,7 @@ export default {
|
|
|
279
360
|
that.inputArr[index].value = files[0]
|
|
280
361
|
that.inputArr[index].url = URL.createObjectURL(files[0])
|
|
281
362
|
console.log('inputArr',that.inputArr[index])
|
|
363
|
+
that.clearError(index)
|
|
282
364
|
that.$forceUpdate()
|
|
283
365
|
})
|
|
284
366
|
inputUpload.click()
|
|
@@ -317,6 +399,7 @@ export default {
|
|
|
317
399
|
let file = event.target.files[0]
|
|
318
400
|
this.fileName = file.name
|
|
319
401
|
this.inputArr[index].value = file
|
|
402
|
+
this.clearError(index)
|
|
320
403
|
},
|
|
321
404
|
isNumberKey(evt, index) {
|
|
322
405
|
const charCode = (evt.which) ? evt.which : evt.keyCode;
|
|
@@ -344,6 +427,7 @@ export default {
|
|
|
344
427
|
},
|
|
345
428
|
radioTao2(v,n) {
|
|
346
429
|
v.value = n.value
|
|
430
|
+
this.clearErrorByItem(v)
|
|
347
431
|
},
|
|
348
432
|
radioTao(index) {
|
|
349
433
|
this.radioArr.forEach(item => {
|
|
@@ -357,6 +441,7 @@ export default {
|
|
|
357
441
|
confirmYear(e, data) {
|
|
358
442
|
data.value = e
|
|
359
443
|
this.tipYear = false
|
|
444
|
+
this.clearErrorByItem(data)
|
|
360
445
|
},
|
|
361
446
|
cancelYear() {
|
|
362
447
|
this.tipYear = false
|
|
@@ -369,6 +454,7 @@ export default {
|
|
|
369
454
|
console.log('e',data)
|
|
370
455
|
data.value = e.start
|
|
371
456
|
this.tip = false
|
|
457
|
+
this.clearErrorByItem(data)
|
|
372
458
|
},
|
|
373
459
|
emptyCal(e,data) {
|
|
374
460
|
data.value = ''
|
|
@@ -436,6 +522,33 @@ export default {
|
|
|
436
522
|
max-height: 200px !important;
|
|
437
523
|
overflow-y: scroll !important;
|
|
438
524
|
}
|
|
525
|
+
/* 必填项未填写:输入框/选择框变红 */
|
|
526
|
+
.tip-input.is-error .input-name > input,
|
|
527
|
+
.tip-input.is-error .cal > input,
|
|
528
|
+
.tip-input.is-error .number-input > input,
|
|
529
|
+
.tip-input.is-error :deep(.select-css),
|
|
530
|
+
.tip-input.is-error .file-cont > em,
|
|
531
|
+
.tip-input.is-error .cover-img {
|
|
532
|
+
border-color: #f56c6c !important;
|
|
533
|
+
}
|
|
534
|
+
.tip-input.is-error .input-radio {
|
|
535
|
+
outline: 1px solid #f56c6c;
|
|
536
|
+
outline-offset: 2px;
|
|
537
|
+
border-radius: 3px;
|
|
538
|
+
}
|
|
539
|
+
/* 必填项校验失败时的轻微抖动动画 */
|
|
540
|
+
@keyframes tipShake {
|
|
541
|
+
0%, 100% { transform: translateX(0); }
|
|
542
|
+
15% { transform: translateX(-6px); }
|
|
543
|
+
30% { transform: translateX(6px); }
|
|
544
|
+
45% { transform: translateX(-5px); }
|
|
545
|
+
60% { transform: translateX(5px); }
|
|
546
|
+
75% { transform: translateX(-3px); }
|
|
547
|
+
90% { transform: translateX(3px); }
|
|
548
|
+
}
|
|
549
|
+
.tip-input.shake {
|
|
550
|
+
animation: tipShake 0.4s ease;
|
|
551
|
+
}
|
|
439
552
|
</style>
|
|
440
553
|
|
|
441
554
|
<style scoped src="./zydxStyle.css">
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
cursor: pointer;
|
|
29
29
|
}
|
|
30
30
|
.tip-box {
|
|
31
|
-
width:
|
|
31
|
+
width: 610px;
|
|
32
32
|
padding: 60px 100px 40px 100px;
|
|
33
33
|
background-color: white;
|
|
34
34
|
border-radius: 5px;
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
|
|
43
43
|
.tip-boxContent {
|
|
44
44
|
max-height: 600px;
|
|
45
|
+
padding-left: 10px;
|
|
46
|
+
box-sizing: border-box;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
.tip-boxContent:hover {
|