uview-plus 3.3.9 → 3.3.11

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/changelog.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 3.3.11(2024-08-05)
2
+ feat: 新增支持upload组件的deletable/maxCount/accept变更监听 #333
3
+
4
+ feat: 新增支持tabs在swiper中使用
5
+
6
+ feat: 新增FormItem支持独立设置验证规则rules
7
+
8
+ fix: 修复index-list未设置$slots.header时索引高亮失效
9
+
10
+ ## 3.3.10(2024-08-02)
11
+ fix: 修复index-list偶发的滑动最后一个索引报错top不存在
12
+
13
+ fix: 修复gird在QQ、抖音小程序下布局
14
+
15
+ feat: 优化step支持自定义样式prop
16
+
17
+ feat: action-sheet组件支持v-model:show双向绑定
18
+
19
+ fix: 小程序下steps和grid都统一采用grid布局
20
+
21
+ fix: 修复支付宝小程序下input类型为数字时双向绑定失效
22
+
23
+ feat : form 表单 validate 校验不通过后 error增加字段prop信息 #304
24
+
25
+ fix: form组件异步校异常验问题 #393
26
+
1
27
  ## 3.3.9(2024-08-01)
2
28
  fix: 优化获取nvue元素
3
29
 
@@ -166,16 +166,18 @@
166
166
  }
167
167
  },
168
168
  },
169
- emits: ["close", "select"],
169
+ emits: ["close", "select", "update:show"],
170
170
  methods: {
171
171
  closeHandler() {
172
172
  // 允许点击遮罩关闭时,才发出close事件
173
173
  if(this.closeOnClickOverlay) {
174
+ this.$emit('update:show')
174
175
  this.$emit('close')
175
176
  }
176
177
  },
177
178
  // 点击取消按钮
178
179
  cancel() {
180
+ this.$emit('update:show')
179
181
  this.$emit('close')
180
182
  },
181
183
  selectHandler(index) {
@@ -183,6 +185,7 @@
183
185
  if (item && !item.disabled && !item.loading) {
184
186
  this.$emit('select', item)
185
187
  if (this.closeOnClickAction) {
188
+ this.$emit('update:show')
186
189
  this.$emit('close')
187
190
  }
188
191
  }
@@ -2,6 +2,7 @@
2
2
  <view v-if="hasInput" class="u-datetime-picker">
3
3
  <u-input
4
4
  :placeholder="placeholder"
5
+ :readonly="!!showByClickInput"
5
6
  border="surround"
6
7
  v-model="inputValue"
7
8
  @click="showByClickInput = !showByClickInput"
@@ -134,54 +134,87 @@
134
134
  // 如果为字符串,转为数组
135
135
  value = [].concat(value);
136
136
  // 历遍children所有子form-item
137
- this.children.map((child) => {
138
- // 用于存放form-item的错误信息
139
- const childErrors = [];
140
- if (value.includes(child.prop)) {
141
- // 获取对应的属性,通过类似'a.b.c'的形式
142
- const propertyVal = getProperty(
143
- this.model,
144
- child.prop
145
- );
146
- // 属性链数组
147
- const propertyChain = child.prop.split(".");
148
- const propertyName =
149
- propertyChain[propertyChain.length - 1];
137
+ let promises = this.children.map(child => {
138
+ return new Promise((resolve, reject) => {
139
+ // 用于存放form-item的错误信息
140
+ const childErrors = [];
141
+ if (value.includes(child.prop)) {
142
+ // 获取对应的属性,通过类似'a.b.c'的形式
143
+ const propertyVal = getProperty(
144
+ this.model,
145
+ child.prop
146
+ );
147
+ // 属性链数组
148
+ const propertyChain = child.prop.split(".");
149
+ const propertyName =
150
+ propertyChain[propertyChain.length - 1];
150
151
 
151
- const rule = this.formRules[child.rule || child.prop];
152
- // 如果不存在对应的规则,直接返回,否则校验器会报错
153
- if (!rule) return;
154
- // rule规则可为数组形式,也可为对象形式,此处拼接成为数组
155
- const rules = [].concat(rule);
152
+ let rule = []
153
+ if (child.itemRules && child.itemRules.length > 0) {
154
+ rule = child.itemRules
155
+ } else {
156
+ rule = this.formRules[child.prop];
157
+ }
158
+ // 如果不存在对应的规则,直接返回,否则校验器会报错
159
+ if (!rule) {
160
+ resolve()
161
+ return;
162
+ }
163
+ // rule规则可为数组形式,也可为对象形式,此处拼接成为数组
164
+ const rules = [].concat(rule);
156
165
 
157
- // 对rules数组进行校验
158
- for (let i = 0; i < rules.length; i++) {
159
- const ruleItem = rules[i];
160
- // 将u-form-item的触发器转为数组形式
161
- const trigger = [].concat(ruleItem?.trigger);
162
- // 如果是有传入触发事件,但是此form-item却没有配置此触发器的话,不执行校验操作
163
- if (event && !trigger.includes(event)) continue;
164
- // 实例化校验对象,传入构造规则
165
- const validator = new Schema({
166
- [propertyName]: ruleItem,
167
- });
168
- validator.validate({
166
+ // 对rules数组进行校验
167
+ if (!rules.length) {
168
+ resolve()
169
+ }
170
+ for (let i = 0; i < rules.length; i++) {
171
+ const ruleItem = rules[i];
172
+ // 将u-form-item的触发器转为数组形式
173
+ const trigger = [].concat(ruleItem?.trigger);
174
+ // 如果是有传入触发事件,但是此form-item却没有配置此触发器的话,不执行校验操作
175
+ if (event && !trigger.includes(event)) {
176
+ resolve()
177
+ continue;
178
+ }
179
+ // 实例化校验对象,传入构造规则
180
+ const validator = new Schema({
181
+ [propertyName]: ruleItem,
182
+ });
183
+ validator.validate({
169
184
  [propertyName]: propertyVal,
170
185
  },
171
- (errors, fields) => {
172
- if (test.array(errors)) {
173
- errorsRes.push(...errors);
174
- childErrors.push(...errors);
186
+ (errors, fields) => {
187
+ if (test.array(errors)) {
188
+ errors.forEach(element => {
189
+ element.prop = child.prop;
190
+ });
191
+ errorsRes.push(...errors);
192
+ childErrors.push(...errors);
193
+ }
194
+ child.message =
195
+ childErrors[0]?.message ? childErrors[0].message : null;
196
+
197
+ if (i == (rules.length - 1)) {
198
+ resolve(errorsRes)
199
+ }
175
200
  }
176
- child.message =
177
- childErrors[0]?.message ? childErrors[0].message : null;
178
- }
179
- );
201
+ )
202
+ }
203
+ } else {
204
+ resolve({})
180
205
  }
181
- }
206
+ });
182
207
  });
183
- // 执行回调函数
184
- typeof callback === "function" && callback(errorsRes);
208
+
209
+ // 使用Promise.all来等待所有Promise完成
210
+ Promise.all(promises)
211
+ .then(results => {
212
+ // 执行回调函数
213
+ typeof callback === "function" && callback(errorsRes);
214
+ })
215
+ .catch(error => {
216
+ console.error('An error occurred:', error);
217
+ });
185
218
  });
186
219
  },
187
220
  // 校验全部数据
@@ -198,6 +231,7 @@
198
231
  const formItemProps = this.children.map(
199
232
  (item) => item.prop
200
233
  );
234
+ // console.log(formItemProps)
201
235
  this.validateField(formItemProps, (errors) => {
202
236
  if(errors.length) {
203
237
  // 如果错误提示方式为toast,则进行提示
@@ -13,9 +13,9 @@ export const props = defineMixin({
13
13
  default: () => defProps.formItem.prop
14
14
  },
15
15
  // 绑定的规则
16
- rule: {
17
- type: String,
18
- default: () => defProps.formItem.rule
16
+ rules: {
17
+ type: Array,
18
+ default: () => defProps.formItem.rules
19
19
  },
20
20
  // 是否显示表单域的下划线边框
21
21
  borderBottom: {
@@ -87,12 +87,12 @@
87
87
  * @tutorial https://ijry.github.io/uview-plus/components/form.html
88
88
  * @property {String} label input的label提示语
89
89
  * @property {String} prop 绑定的值
90
- * @property {String} rule 绑定的规则
90
+ * @property {Array} rules 绑定的规则
91
91
  * @property {String | Boolean} borderBottom 是否显示表单域的下划线边框
92
92
  * @property {String | Number} labelWidth label的宽度,单位px
93
93
  * @property {String} rightIcon 右侧图标
94
94
  * @property {String} leftIcon 左侧图标
95
- * @property {String | Object} leftIconStyle 左侧图标的样式
95
+ * @property {String | Object} leftIconStyle 左侧图标的样式
96
96
  * @property {Boolean} required 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 (默认 false )
97
97
  *
98
98
  * @example <u-form-item label="姓名" prop="userInfo.name" borderBottom ref="item1"></u-form-item>
@@ -116,7 +116,8 @@
116
116
  // 错误提示方式
117
117
  errorType: 'message'
118
118
  },
119
- color: color
119
+ color: color,
120
+ itemRules: []
120
121
  }
121
122
  },
122
123
  // 组件创建完成时,将当前实例保存到u-form中
@@ -129,6 +130,15 @@
129
130
  this.init()
130
131
  },
131
132
  emits: ["click"],
133
+ watch: {
134
+ // 监听规则的变化
135
+ rules: {
136
+ immediate: true,
137
+ handler(n) {
138
+ this.setRules(n);
139
+ },
140
+ },
141
+ },
132
142
  methods: {
133
143
  addStyle,
134
144
  addUnit,
@@ -139,6 +149,12 @@
139
149
  error('u-form-item需要结合u-form组件使用')
140
150
  }
141
151
  },
152
+ // 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
153
+ setRules(rules) {
154
+ // 判断是否有规则
155
+ if (rules.length === 0) return;
156
+ this.itemRules = rules;
157
+ },
142
158
  // 获取父组件的参数
143
159
  updateParentData() {
144
160
  // 此方法写在mixin中
@@ -105,7 +105,7 @@
105
105
  align-items: center;
106
106
  // 在uni-app中应尽量避免使用flex布局以外的方式,因为nvue/uvue等方案都支持flex布局
107
107
  // 这里使用grid布局使用为目前20240409uni-app在抖音小程序开启virtualHost时有bug,存在事件失效问题。
108
- /* #ifdef MP-TOUTIAO */
108
+ /* #ifdef MP */
109
109
  display: grid;
110
110
  grid-template-columns: repeat(v-bind(col), 1fr);
111
111
  /* #endif */
@@ -82,7 +82,12 @@
82
82
  itemStyle() {
83
83
  const style = {
84
84
  background: this.bgColor,
85
+ // #ifndef MP
85
86
  width: this.width
87
+ // #endif
88
+ // #ifdef MP
89
+ width: '100%'
90
+ // #endif
86
91
  }
87
92
  return deepMerge(style, addStyle(this.customStyle))
88
93
  }
@@ -361,16 +361,16 @@
361
361
  // pageY += sys().windowTop
362
362
  // #endif
363
363
  // 对第一和最后一个字母做边界处理,因为用户可能在字母列表上触摸到两端的尽头后依然继续滑动
364
- // console.log('top1', top)
365
- // console.log('height', height)
364
+ console.log('top1', top)
365
+ console.log('height', height)
366
366
  top = top - (height / 2) // 减去transfrom的translateY值导致的高度
367
367
  pageY = pageY - this.topOffset
368
368
  // if (this.safeBottomFix) {
369
369
  // pageY = pageY + 34
370
370
  // }
371
- // console.log('topOffset', this.topOffset)
372
- // console.log('pageY', pageY)
373
- // console.log('top2', top)
371
+ console.log('topOffset', this.topOffset)
372
+ console.log('pageY', pageY)
373
+ console.log('top2', top)
374
374
  if (pageY < top) {
375
375
  index = 0
376
376
  } else if (pageY >= top + height) {
@@ -418,7 +418,9 @@
418
418
  return child
419
419
  })
420
420
  // console.log('this.children[currentIndex].top', children[currentIndex].top)
421
- this.scrollTop = children[currentIndex].top - getPx(customNavHeight)
421
+ if (children[currentIndex]?.top) {
422
+ this.scrollTop = children[currentIndex].top - getPx(customNavHeight)
423
+ }
422
424
  // #endif
423
425
 
424
426
  // #ifdef APP-NVUE
@@ -434,6 +436,13 @@
434
436
  getHeaderRect() {
435
437
  // 获取header slot的高度,因为list组件中获取元素的尺寸是没有top值的
436
438
  return new Promise(resolve => {
439
+ if (!this.$slots.header) {
440
+ resolve({
441
+ width: 0,
442
+ height: 0
443
+ })
444
+ }
445
+
437
446
  // #ifndef APP-NVUE
438
447
  this.$uGetRect('.u-index-list__header').then(size => {
439
448
  resolve(size)
@@ -452,7 +461,6 @@
452
461
  resolve(res.size)
453
462
  })
454
463
  // #endif
455
-
456
464
  })
457
465
  },
458
466
  // scroll-view的滚动事件
@@ -91,7 +91,7 @@ import { addStyle, addUnit, deepMerge, formValidate, $parent, sleep, os } from '
91
91
  * @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa' )
92
92
  * @property {Boolean} clearable 是否显示清除控件 ( 默认 false )
93
93
  * @property {Boolean} password 是否密码类型 ( 默认 false )
94
- * @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
94
+ * @property {Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
95
95
  * @property {String} placeholder 输入框为空时的占位符
96
96
  * @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
97
97
  * @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
@@ -102,7 +102,7 @@ import { addStyle, addUnit, deepMerge, formValidate, $parent, sleep, os } from '
102
102
  * @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
103
103
  * @property {Boolean} autoBlur 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
104
104
  * @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
105
- * @property {String | Number} cursor 指定focus时光标的位置( 默认 -1
105
+ * @property {String | Number} cursor 指定focus时光标的位置( 默认 140
106
106
  * @property {String | Number} cursorSpacing 输入框聚焦时底部与键盘的距离 ( 默认 30 )
107
107
  * @property {String | Number} selectionStart 光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
108
108
  * @property {String | Number} selectionEnd 光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
@@ -234,6 +234,7 @@ export default {
234
234
  onInput(e) {
235
235
  let { value = "" } = e.detail || {};
236
236
  // 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
237
+ // console.log('onInput', value, this.innerValue)
237
238
  this.innerValue = value;
238
239
  this.$nextTick(() => {
239
240
  let formatValue = this.innerFormatter(value);
@@ -22,6 +22,7 @@
22
22
  >
23
23
  </view>
24
24
  <view
25
+ @click="onClick"
25
26
  class="u-slider__gap"
26
27
  :style="[
27
28
  barStyle,
@@ -61,6 +61,9 @@
61
61
  },
62
62
  created() {
63
63
  this.children = []
64
+ },
65
+ options: {
66
+ virtualHost: false
64
67
  }
65
68
  }
66
69
  </script>
@@ -78,6 +81,10 @@
78
81
  &--row {
79
82
  flex-direction: row;
80
83
  flex: 1;
84
+ /* #ifdef MP */
85
+ display: grid;
86
+ grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
87
+ /* #endif */
81
88
  }
82
89
  }
83
90
  </style>
@@ -21,6 +21,11 @@ export const props = defineMixin({
21
21
  error: {
22
22
  type: Boolean,
23
23
  default: () => defProps.stepsItem.error
24
- }
24
+ },
25
+ // 自定义样式
26
+ itemStyle: {
27
+ type: [Object],
28
+ default: {}
29
+ },
25
30
  }
26
31
  })
@@ -3,7 +3,8 @@
3
3
  <view class="u-steps-item__line" v-if="index + 1 < childLength"
4
4
  :class="[`u-steps-item__line--${parentData.direction}`]" :style="[lineStyle]"></view>
5
5
  <view class="u-steps-item__wrapper"
6
- :class="[`u-steps-item__wrapper--${parentData.direction}`, parentData.dot && `u-steps-item__wrapper--${parentData.direction}--dot`]">
6
+ :class="[`u-steps-item__wrapper--${parentData.direction}`, parentData.dot && `u-steps-item__wrapper--${parentData.direction}--dot`]"
7
+ :style="[itemStyle]">
7
8
  <slot name="icon">
8
9
  <view class="u-steps-item__wrapper__dot" v-if="parentData.dot" :style="{
9
10
  backgroundColor: statusColor
@@ -95,6 +96,11 @@
95
96
  created() {
96
97
  this.init()
97
98
  },
99
+ // #ifdef MP-TOUTIAO
100
+ options: {
101
+ virtualHost: false
102
+ },
103
+ // #endif
98
104
  computed: {
99
105
  lineStyle() {
100
106
  const style = {}
@@ -111,6 +117,11 @@
111
117
  .current ? this.parentData.activeColor : this.parentData.inactiveColor
112
118
  return style
113
119
  },
120
+ itemStyle() {
121
+ return {
122
+ ...this.itemStyle
123
+ }
124
+ },
114
125
  statusClass() {
115
126
  const {
116
127
  index,
@@ -231,10 +242,11 @@
231
242
  align-items: center;
232
243
  position: relative;
233
244
  background-color: #fff;
245
+ border-radius: 50px;
234
246
 
235
247
  &--column {
236
248
  width: 20px;
237
- height: 32px;
249
+ height: 20px;
238
250
 
239
251
  &--dot {
240
252
  height: 20px;
@@ -243,7 +255,7 @@
243
255
  }
244
256
 
245
257
  &--row {
246
- width: 32px;
258
+ width: 20px;
247
259
  height: 20px;
248
260
 
249
261
  &--dot {
@@ -178,6 +178,7 @@
178
178
  // 获取下划线的数值px表示法
179
179
  const lineWidth = getPx(this.lineWidth);
180
180
  this.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2
181
+ console.log(lineOffsetLeft)
181
182
  // #ifdef APP-NVUE
182
183
  // 第一次移动滑块,无需过渡时间
183
184
  this.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration))
@@ -257,6 +258,12 @@
257
258
  return
258
259
  }
259
260
  Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {
261
+ // 兼容在swiper组件中使用
262
+ if (tabsRect.left > tabsRect.width) {
263
+ tabsRect.right = tabsRect.right - Math.floor(tabsRect.left / tabsRect.width) * tabsRect.width
264
+ tabsRect.left = tabsRect.left % tabsRect.width
265
+ }
266
+ // console.log(tabsRect)
260
267
  this.tabsRect = tabsRect
261
268
  this.scrollViewWidth = 0
262
269
  itemRect.map((item, index) => {
@@ -196,6 +196,15 @@
196
196
  immediate: true,
197
197
  deep: true,
198
198
  },
199
+ deletable(newVal) {
200
+ this.formatFileList()
201
+ },
202
+ maxCount(newVal) {
203
+ this.formatFileList()
204
+ },
205
+ accept(newVal) {
206
+ this.formatFileList()
207
+ }
199
208
  },
200
209
  // #ifdef VUE3
201
210
  emits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview'],
@@ -226,6 +226,8 @@
226
226
  }
227
227
 
228
228
  .u-image {
229
+ /* #ifndef APP-NVUE */
229
230
  max-width: 100%;
231
+ /* #endif */
230
232
  }
231
233
  </style>
@@ -12,7 +12,7 @@ export default {
12
12
  formItem: {
13
13
  label: '',
14
14
  prop: '',
15
- rule: '',
15
+ rules: [],
16
16
  borderBottom: '',
17
17
  labelPosition: '',
18
18
  labelWidth: '',
@@ -17,7 +17,7 @@ export default {
17
17
  disabledColor: '#f5f7fa',
18
18
  clearable: false,
19
19
  password: false,
20
- maxlength: -1,
20
+ maxlength: 140,
21
21
  placeholder: null,
22
22
  placeholderClass: 'input-placeholder',
23
23
  placeholderStyle: 'color: #c0c4cc',
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-plus",
3
3
  "name": "uview-plus",
4
4
  "displayName": "uview-plus3.0重磅发布,全面的Vue3移动组件库。",
5
- "version": "3.3.9",
5
+ "version": "3.3.11",
6
6
  "description": "uview-plus已兼容vue3,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
7
7
  "keywords": [
8
8
  "uview",