uview-plus 3.8.127 → 3.8.129

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 (70) hide show
  1. package/README.md +13 -0
  2. package/changelog.md +38 -0
  3. package/components/u-barcode/u-barcode.vue +1 -1
  4. package/components/u-calendar/month.vue +71 -35
  5. package/components/u-calendar/u-calendar.vue +67 -31
  6. package/components/u-car-keyboard/u-car-keyboard.vue +21 -8
  7. package/components/u-cate-tab/u-cate-tab.vue +56 -11
  8. package/components/u-cell/u-cell.vue +20 -16
  9. package/components/u-collapse-item/u-collapse-item.vue +34 -11
  10. package/components/u-datetime-picker/u-datetime-picker.vue +16 -0
  11. package/components/u-dropdown/u-dropdown.vue +15 -0
  12. package/components/u-grid/u-grid.vue +9 -0
  13. package/components/u-grid-item/u-grid-item.vue +8 -0
  14. package/components/u-icon/util.js +16 -1
  15. package/components/u-list/u-list.vue +59 -8
  16. package/components/u-list-item/u-list-item.vue +9 -3
  17. package/components/u-navbar/navbar.js +2 -1
  18. package/components/u-navbar/props.js +6 -1
  19. package/components/u-navbar/u-navbar.vue +20 -3
  20. package/components/u-number-keyboard/u-number-keyboard.vue +18 -6
  21. package/components/u-parse/parser.js +4 -3
  22. package/components/u-picker/u-picker.vue +6 -3
  23. package/components/u-search/u-search.vue +15 -5
  24. package/components/u-select/u-select.vue +206 -31
  25. package/components/u-slider/u-slider.vue +21 -12
  26. package/components/u-tabs/u-tabs.vue +5 -1
  27. package/components/u-text/u-text.vue +34 -32
  28. package/components/u-textarea/props.js +5 -0
  29. package/components/u-textarea/textarea.js +4 -1
  30. package/components/u-textarea/u-textarea.vue +3 -1
  31. package/components/u-upload/u-upload.vue +15 -4
  32. package/components/u-upload/utils.js +4 -1
  33. package/components/u-video/danmaku.js +97 -0
  34. package/components/u-video/props.js +205 -0
  35. package/components/u-video/u-video.vue +1427 -0
  36. package/components/u-video/video-danmaku.vue +273 -0
  37. package/components/u-video/video-slider.vue +179 -0
  38. package/components/u-video/video-utils.js +98 -0
  39. package/components/u-video/video.js +72 -0
  40. package/libs/config/props.js +2 -1
  41. package/libs/function/test.js +2 -0
  42. package/libs/i18n/locales/de.js +14 -1
  43. package/libs/i18n/locales/en.js +14 -1
  44. package/libs/i18n/locales/es.js +14 -1
  45. package/libs/i18n/locales/fr.js +14 -1
  46. package/libs/i18n/locales/ja.js +14 -1
  47. package/libs/i18n/locales/ko.js +14 -1
  48. package/libs/i18n/locales/ru.js +14 -1
  49. package/libs/i18n/locales/th.js +14 -1
  50. package/libs/i18n/locales/zh-Hans.js +14 -1
  51. package/libs/i18n/locales/zh-Hant.js +14 -1
  52. package/libs/mixin/mixin.js +11 -6
  53. package/package.json +1 -1
  54. package/skills/README.md +66 -0
  55. package/skills/uview-plus/SKILL.md +95 -0
  56. package/skills/uview-plus/references/api.md +105 -0
  57. package/skills/uview-plus/references/components.md +156 -0
  58. package/skills/uview-plus/references/icons.md +223 -0
  59. package/skills/uview-plus/references/pitfalls.md +77 -0
  60. package/skills/uview-plus/references/setup.md +168 -0
  61. package/types/comps/form.d.ts +44 -8
  62. package/types/comps/list.d.ts +1 -1
  63. package/types/comps/listItem.d.ts +1 -1
  64. package/types/comps/navbar.d.ts +6 -0
  65. package/types/comps/numberBox.d.ts +85 -21
  66. package/types/comps/pagination.d.ts +96 -0
  67. package/types/comps/swipeActionItem.d.ts +3 -4
  68. package/types/comps/textarea.d.ts +9 -3
  69. package/types/comps.d.ts +1 -0
  70. package/types/index.d.ts +5 -0
@@ -11,12 +11,18 @@
11
11
  :custom-style="iconStyle"
12
12
  :size="size === 'large' ? 22 : 18"></up-icon>
13
13
  </view>
14
+ <!-- 必填星号用真实节点渲染,绝对定位的伪元素在nvue和小程序下不显示,且通栏布局时会跑到屏幕外 -->
15
+ <text
16
+ v-if="required"
17
+ class="u-cell__required"
18
+ :style="[cellRequiredDynamicStyle]"
19
+ >*</text>
14
20
  <view class="u-cell__title">
15
21
  <!-- 将slot与默认内容用if/else分开主要是因为微信小程序不支持slot嵌套传递,这样才能解决collapse组件的slot不失效问题,label暂时未用到。 -->
16
22
  <slot name="title" v-if="$slots.title || !title">
17
23
  </slot>
18
24
  <text v-else class="u-cell__title-text" :style="[cellTitleDynamicStyle, titleTextStyle]"
19
- :class="[required && 'u-cell--required', disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
25
+ :class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
20
26
  <slot name="label">
21
27
  <text class="u-cell__label" v-if="label"
22
28
  :style="[cellLabelDynamicStyle]"
@@ -102,6 +108,11 @@
102
108
  cellDisabledColor() {
103
109
  return this.upThemeVar('--up-disabled-color', '#c8c9cc')
104
110
  },
111
+ cellRequiredDynamicStyle() {
112
+ return {
113
+ color: this.upThemeVar('--up-error', '#f56c6c')
114
+ }
115
+ },
105
116
  cellTitleDynamicStyle() {
106
117
  return {
107
118
  color: this.upThemeVar('--up-main-color', '#303133')
@@ -150,6 +161,8 @@
150
161
  $u-cell-title-font-size: 15px !default;
151
162
  $u-cell-title-line-height: 22px !default;
152
163
  $u-cell-title-color: $u-main-color !default;
164
+ $u-cell-required-font-size: 14px !default;
165
+ $u-cell-required-color: $u-error !default;
153
166
  $u-cell-label-font-size: 12px !default;
154
167
  $u-cell-label-color: $u-tips-color !default;
155
168
  $u-cell-label-line-height: 18px !default;
@@ -258,23 +271,14 @@
258
271
  }
259
272
  }
260
273
 
261
- &--required {
262
- /* #ifndef APP-NVUE */
263
- overflow: visible;
264
- /* #endif */
265
- @include flex;
266
- align-items: center;
267
- }
268
-
269
- &--required:before {
270
- position: absolute;
274
+ &__required {
271
275
  /* #ifndef APP-NVUE */
272
- content: '*';
276
+ align-self: flex-start;
273
277
  /* #endif */
274
- left: -8px;
275
- margin-top: 4rpx;
276
- font-size: 14px;
277
- color: $u-error;
278
+ margin-right: 2px;
279
+ font-size: $u-cell-required-font-size;
280
+ line-height: $u-cell-title-line-height;
281
+ color: $u-cell-required-color;
278
282
  }
279
283
 
280
284
  &--clickable {
@@ -45,6 +45,7 @@
45
45
  <view
46
46
  class="u-collapse-item__content"
47
47
  :animation="animationData"
48
+ :style="[contentStyle]"
48
49
  ref="animation"
49
50
  >
50
51
  <view
@@ -93,6 +94,8 @@
93
94
  elId: guid(),
94
95
  // uni.createAnimation的导出数据
95
96
  animationData: {},
97
+ // 展开动画结束后接管面板高度,交还给内容,避免内容后续变高被裁切
98
+ contentStyle: {},
96
99
  // 是否展开状态
97
100
  expanded: false,
98
101
  // 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
@@ -134,16 +137,20 @@
134
137
  children = []
135
138
  } = this.parent
136
139
 
137
- if (accordion) {
138
- if (test.array(value)) {
139
- return error('手风琴模式下,u-collapse组件的value参数不能为数组')
140
+ // value为null(默认值)时面板并非受控,展开状态只存在于子组件自身的expanded上,
141
+ // 此时按value反推会把用户点开的面板强行收起,init()便只剩重算内容高度这一职责
142
+ if (value !== null && value !== undefined) {
143
+ if (accordion) {
144
+ if (test.array(value)) {
145
+ return error('手风琴模式下,u-collapse组件的value参数不能为数组')
146
+ }
147
+ this.expanded = this.name == value
148
+ } else {
149
+ if (!test.array(value)) {
150
+ return error('非手风琴模式下,u-collapse组件的value参数必须为数组')
151
+ }
152
+ this.expanded = value.some(item => item == this.name)
140
153
  }
141
- this.expanded = this.name == value
142
- } else {
143
- if (!test.array(value) && value !== null) {
144
- return error('非手风琴模式下,u-collapse组件的value参数必须为数组')
145
- }
146
- this.expanded = (value || []).some(item => item == this.name)
147
154
  }
148
155
  // 设置组件的展开或收起状态
149
156
  await nextTick()
@@ -158,6 +165,15 @@
158
165
  // 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
159
166
  const rect = await this.queryRect()
160
167
  const height = this.expanded ? rect.height : 0
168
+ // #ifndef APP-NVUE
169
+ // 展开状态下面板高度是auto,收起前先固定成当前实际高度,否则浏览器无法从auto过渡到0
170
+ if (!this.expanded && this.contentStyle.height === 'auto') {
171
+ this.contentStyle = { height: `${rect.height}px` }
172
+ await nextTick()
173
+ // 再查询一次节点,借这次布局读取确保上面的固定高度已经生效,收起动画才有起点
174
+ await this.queryRect()
175
+ }
176
+ // #endif
161
177
  this.animating = true
162
178
  // #ifdef APP-NVUE
163
179
  const ref = this.$refs['animation'].ref
@@ -178,17 +194,24 @@
178
194
  const animation = uni.createAnimation({
179
195
  timingFunction: 'ease-in-out',
180
196
  });
197
+ // 只保留一个step:uni的step会累积之前的animates,多余的空step会在动画结束时
198
+ // 把同一个像素高度再写一遍,把下面交还给内容的auto高度盖掉
181
199
  animation
182
200
  .height(height)
183
201
  .step({
184
202
  duration: this.duration,
185
203
  })
186
- .step()
187
204
  // 导出动画数据给面板的animationData值
188
205
  this.animationData = animation.export()
189
- // 标识动画结束
206
+ // 标识动画结束。快速连点时只认最后一次动画的回调,否则旧回调会打断新动画
207
+ const animateToken = (this.animateToken || 0) + 1
208
+ this.animateToken = animateToken
190
209
  sleep(this.duration).then(() => {
210
+ if (this.animateToken !== animateToken) return
191
211
  this.animating = false
212
+ // 展开动画结束后把高度交还给内容:内容里的图片、异步数据等在测量之后才变高时,
213
+ // 面板不会再停留在过时的测量值上被overflow:hidden裁掉一截
214
+ this.contentStyle = this.expanded ? { height: 'auto' } : {}
192
215
  })
193
216
  // #endif
194
217
  },
@@ -197,6 +197,10 @@
197
197
  emits: ['close', 'closed', 'cancel', 'confirm', 'change', 'update:modelValue'],
198
198
  // #endif
199
199
  methods: {
200
+ // 是否为“未选择/已清空”的空值。空值必须原样透传,不能被夹取成边界值
201
+ isEmptyValue(value) {
202
+ return value === '' || value === null || value === undefined
203
+ },
200
204
  toInt(value, fallback = 0) {
201
205
  const num = parseInt(value, 10)
202
206
  return Number.isFinite(num) ? num : fallback
@@ -331,6 +335,14 @@
331
335
  },
332
336
  // 点击工具栏的确定按钮
333
337
  confirm() {
338
+ // 值为空(从未选择或被外部清空)时,用户看到的是各列当前停留的位置,
339
+ // 直接上抛空值会“所见非所得”,故先按各列当前显示值取一次真实值
340
+ if (this.isEmptyValue(this.innerValue)) {
341
+ this.change({
342
+ indexs: this.innerDefaultIndex,
343
+ values: this.columns
344
+ })
345
+ }
334
346
  // #ifdef VUE3
335
347
  this.$emit('update:modelValue', this.innerValue)
336
348
  // #endif
@@ -564,6 +576,10 @@
564
576
  },
565
577
  // 得出合法的时间
566
578
  correctValue(value) {
579
+ // 空值代表“未选择/已清空”,直接返回空串。
580
+ // 否则日期模式会被夹取成minDate、时间模式会被夹取成minHour:minMinute,
581
+ // 导致外部清空绑定值后输入框仍然显示最小日期/最小时间
582
+ if (this.isEmptyValue(value)) return ''
567
583
  const isDateMode = !['time', 'timesecond'].includes(this.mode)
568
584
  if (isDateMode) {
569
585
  // 日期类型统一解析成毫秒时间戳,没有设置合法的当前时间时才使用最小时间。
@@ -203,7 +203,11 @@
203
203
 
204
204
  .u-dropdown {
205
205
  flex: 1;
206
+ // nvue(weex)不支持百分比长度,width: 100%会被编译成width: 100(100px),
207
+ // 使整个下拉菜单被压缩到屏幕左侧一条窄列,nvue下交由flex拉伸撑满父级宽度
208
+ /* #ifndef APP-NVUE */
206
209
  width: 100%;
210
+ /* #endif */
207
211
  position: relative;
208
212
 
209
213
  &__menu {
@@ -243,8 +247,14 @@
243
247
  &__content {
244
248
  position: absolute;
245
249
  z-index: 8;
250
+ // 绝对定位元素在nvue下用left+right撑满父级宽度,替代无效的width: 100%
251
+ /* #ifndef APP-NVUE */
246
252
  width: 100%;
253
+ /* #endif */
247
254
  left: 0px;
255
+ /* #ifdef APP-NVUE */
256
+ right: 0px;
257
+ /* #endif */
248
258
  bottom: 0;
249
259
  overflow: hidden;
250
260
 
@@ -253,8 +263,13 @@
253
263
  position: absolute;
254
264
  z-index: 9;
255
265
  background: rgba(0, 0, 0, .3);
266
+ /* #ifndef APP-NVUE */
256
267
  width: 100%;
268
+ /* #endif */
257
269
  left: 0;
270
+ /* #ifdef APP-NVUE */
271
+ right: 0;
272
+ /* #endif */
258
273
  top: 0;
259
274
  bottom: 0;
260
275
  }
@@ -109,5 +109,14 @@
109
109
  grid-gap: v-bind(gap);
110
110
  grid-template-columns: repeat(v-bind(col), minmax(0, 1fr));
111
111
  /* #endif */
112
+ // 安卓7自带的WebView是Chromium 52/55,CSS Grid要到Chromium 57才可用,上面那条display:grid会被整条丢弃,
113
+ // 这里显式退回上面的flex换行,并把列数透出给u-grid-item,让宫格自己按列数取宽度;
114
+ // 这类老内核同样不支持flex的gap,所以gap在兜底布局下画不出来。
115
+ /* #ifndef APP-NVUE || MP */
116
+ @supports not (display: grid) {
117
+ display: flex !important;
118
+ --up-grid-col: v-bind(col);
119
+ }
120
+ /* #endif */
112
121
  }
113
122
  </style>
@@ -197,6 +197,14 @@
197
197
  margin-top:$u-grid-item-margin-top;
198
198
  /* #endif */
199
199
 
200
+ // 老WebView(安卓7自带的是Chromium 52/55)没有CSS Grid,u-grid会退回flex换行,
201
+ // 内联的width:100%此时等于整行宽度,会让每个宫格独占一行,所以按u-grid透出的列数重新分配。
202
+ /* #ifndef APP-NVUE || MP */
203
+ @supports not (display: grid) {
204
+ width: calc(100% / var(--up-grid-col, 1)) !important;
205
+ }
206
+ /* #endif */
207
+
200
208
  &--hover-class {
201
209
  opacity:$u-grid-item-hover-class-opcatiy;
202
210
  }
@@ -6,6 +6,13 @@ const appIconFontUrl = '_www/static/app-plus/uview-plus/upicon.ttf';
6
6
  // 项目没有本地字体(未启用本地字体构建插件、或没拷贝 static/app-plus/uview-plus/upicon.ttf)时:
7
7
  // App-Vue 在 uni.loadFontFace 失败后回退 config.iconUrl,App-nvue 先探测字体文件是否存在再决定字体地址。
8
8
  const useAppStaticIconFont = true;
9
+ // 字体加载失败后的重试上限与重试基础间隔。重试只能由定时器驱动,不能由“新建u-icon”驱动:
10
+ // u-search、u-input的清除按钮仅在输入框聚焦时才创建,若在那一刻补发字体请求,
11
+ // 微信开发者工具里正在聚焦的input会立即失去焦点,表现为输入内容后再也无法聚焦、无法删除内容。
12
+ // https://github.com/ijry/uview-plus/issues/844
13
+ const maxFontRetryTimes = 3;
14
+ const fontRetryDelay = 2000;
15
+ let fontRetryTimes = 0;
9
16
 
10
17
  let params = {
11
18
  loaded: false,
@@ -176,7 +183,15 @@ const loadFont = () => {
176
183
  // console.log('内置字体图标加载成功');
177
184
  },
178
185
  fail() {
179
- params.loading = false;
186
+ // 失败后不立刻释放请求锁,改由定时器重试有限次数,
187
+ // 避免此后每创建一个u-icon都立刻补发一次字体请求。
188
+ if (fontRetryTimes < maxFontRetryTimes) {
189
+ fontRetryTimes++;
190
+ setTimeout(() => {
191
+ params.loading = false;
192
+ loadFont();
193
+ }, fontRetryDelay * fontRetryTimes);
194
+ }
180
195
  // console.error('内置字体图标加载出错');
181
196
  }
182
197
  });
@@ -16,7 +16,7 @@
16
16
  <!-- #ifndef APP-NVUE -->
17
17
  <scroll-view
18
18
  class="u-list"
19
- :scroll-into-view="scrollIntoView"
19
+ :scroll-into-view="innerScrollIntoView"
20
20
  :style="[listStyle]"
21
21
  :scroll-y="scrollable"
22
22
  :scroll-top="Number(scrollTop)"
@@ -66,7 +66,7 @@
66
66
  * @property {Boolean} enableFlex 启用 flexbox 布局。开启后,当前节点声明了display: flex就会成为flex container,并作用于其孩子节点,仅微信小程序有效(默认 false )
67
67
  * @property {Boolean} pagingEnabled 是否按分页模式显示List,(默认 false )
68
68
  * @property {Boolean} scrollable 是否允许List滚动(默认 true )
69
- * @property {String} scrollIntoView 值应为某子元素id(id不能以数字开头)
69
+ * @property {String} scrollIntoView 滚动到指定位置,值为u-list-item的anchor,或item内某个子元素的id(id不能以数字开头)
70
70
  * @property {Boolean} scrollWithAnimation 在设置滚动条位置时使用动画过渡 (默认 false )
71
71
  * @property {Boolean} enableBackToTop iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只对微信小程序有效 (默认 false )
72
72
  * @property {String | Number} height 列表的高度 (默认 0 )
@@ -88,6 +88,8 @@
88
88
  return {
89
89
  // 记录内部滚动的距离
90
90
  innerScrollTop: 0,
91
+ // 非nvue下真正传给scroll-view的scroll-into-view值
92
+ innerScrollIntoView: '',
91
93
  // vue下,scroll-view在上拉加载时的偏移值
92
94
  offset: 0,
93
95
  sys: getWindowInfo()
@@ -109,11 +111,17 @@
109
111
  }
110
112
  },
111
113
  created() {
112
- this.refs = []
113
114
  this.children = []
114
115
  this.anchors = []
116
+ // 最后一次请求滚动的目标,非响应式,仅用于丢弃过期的重试
117
+ this.scrollIntoViewTarget = ''
118
+ },
119
+ mounted() {
120
+ // 初始就设置了scroll-into-view时,子组件已挂载完成,此时才能匹配到anchor
121
+ if (this.scrollIntoView) {
122
+ this.scrollIntoViewById(this.scrollIntoView)
123
+ }
115
124
  },
116
- mounted() {},
117
125
  emits: ["scroll", "scrolltolower", "scroll-to-lower", "scrolltoupper", "scroll-to-upper",
118
126
  "refresherpulling", "refresherrefresh", "refresherrestore", "refresherabort"],
119
127
  methods: {
@@ -131,15 +139,58 @@
131
139
  this.innerScrollTop = scrollTop
132
140
  this.$emit('scroll', scrollTop)
133
141
  },
134
- scrollIntoViewById(id) {
142
+ // 根据id找到设置了对应anchor的u-list-item,anchor与u-list-item-${anchor}两种写法都支持
143
+ getAnchorChild(id) {
144
+ const anchorId = String(id)
145
+ return this.children.find(child => {
146
+ if (child.anchor === '' || child.anchor === null || child.anchor === undefined) return false
147
+ const anchor = String(child.anchor)
148
+ return anchor === anchorId || `u-list-item-${anchor}` === anchorId
149
+ })
150
+ },
151
+ scrollIntoViewById(id, retry = true) {
152
+ if (id === '' || id === null || id === undefined) return
153
+ // 记录最后一次请求,避免过期的重试覆盖掉新值
154
+ this.scrollIntoViewTarget = id
155
+ // 根据id参数,找到所有u-list-item中匹配的节点
156
+ const child = this.getAnchorChild(id)
157
+ const scheduleRetry = () => {
158
+ if (!retry) return
159
+ this.$nextTick(() => {
160
+ if (this.scrollIntoViewTarget === id) this.scrollIntoViewById(id, false)
161
+ })
162
+ }
163
+ // #ifndef APP-NVUE
164
+ // 命中anchor时滚动到item的id;未命中时仍按原语义透传用户自己的子元素id
165
+ const target = child ? 'u-list-item-' + child.anchor : id
166
+ // scroll-into-view需要有一个值变动的过程,重复滚动同一节点时先置空
167
+ if (retry && this.innerScrollIntoView === target) {
168
+ this.innerScrollIntoView = ''
169
+ this.$nextTick(() => {
170
+ if (this.scrollIntoViewTarget === id) this.innerScrollIntoView = target
171
+ })
172
+ } else {
173
+ this.innerScrollIntoView = target
174
+ }
175
+ // #endif
135
176
  // #ifdef APP-NVUE
136
- // 根据id参数,找到所有u-list-item中匹配的节点,再通过dom模块滚动到对应的位置
137
- const item = this.refs.find(item => item.$refs[id] ? true : false)
138
- dom.scrollToElement(item.$refs[id], {
177
+ // nvue下通过children和带前缀的ref查找节点,也兼容用户直接传入子节点ref名
178
+ const ref = child && child.$refs && child.$refs['u-list-item-' + child.anchor]
179
+ const matchedItem = ref ? null : this.children.find(item => item.$refs && item.$refs[id])
180
+ const targetRef = ref || (matchedItem && matchedItem.$refs[id])
181
+ if (!targetRef) {
182
+ scheduleRetry()
183
+ return
184
+ }
185
+ dom.scrollToElement(targetRef, {
139
186
  // 是否需要滚动动画
140
187
  animated: this.scrollWithAnimation
141
188
  })
142
189
  // #endif
190
+ // #ifndef APP-NVUE
191
+ // anchor可能和列表数据同一帧生成,未命中时下一帧再尝试一次
192
+ if (!child) scheduleRetry()
193
+ // #endif
143
194
  },
144
195
  // 滚动到底部触发事件
145
196
  scrolltolower(e) {
@@ -4,6 +4,7 @@
4
4
  <!-- #endif -->
5
5
  <view
6
6
  class="u-list-item"
7
+ :id="anchorId"
7
8
  :ref="`u-list-item-${anchor}`"
8
9
  :anchor="`u-list-item-${anchor}`"
9
10
  :class="[`u-list-item-${anchor}`]"
@@ -27,8 +28,8 @@
27
28
  * List 列表
28
29
  * @description 该组件为高性能列表组件
29
30
  * @tutorial https://uview-plus.jiangruyi.com/components/list.html
30
- * @property {String | Number} anchor 用于滚动到指定item
31
- * @example <u-list-ite v-for="(item, index) in indexList" :key="index" ></u-list-item>
31
+ * @property {String | Number} anchor item的锚点,将u-list的scroll-into-view设置为该值即可滚动到此item
32
+ * @example <u-list-item v-for="(item, index) in indexList" :key="index" :anchor="`item-${index}`"></u-list-item>
32
33
  */
33
34
  export default {
34
35
  name: 'u-list-item',
@@ -43,7 +44,12 @@
43
44
  }
44
45
  },
45
46
  computed: {
46
-
47
+ // scroll-view的scroll-into-view只认节点id,故由anchor加前缀生成;
48
+ // 未设置anchor时输出null,避免列表里的每一项都得到同一个空id。
49
+ anchorId() {
50
+ if (this.anchor === '' || this.anchor === null || this.anchor === undefined) return null
51
+ return 'u-list-item-' + this.anchor
52
+ }
47
53
  },
48
54
  inject: ['uList'],
49
55
  watch: {
@@ -28,7 +28,8 @@ export default {
28
28
  autoBack: false,
29
29
  titleStyle: '',
30
30
  mode: 'default',
31
- scrollTop: 0
31
+ scrollTop: 0,
32
+ avoidCapsule: true
32
33
  }
33
34
 
34
35
  }
@@ -104,6 +104,11 @@ export const props = defineMixin({
104
104
  scrollTop: {
105
105
  type: [String, Number],
106
106
  default: () => defProps.navbar.scrollTop
107
- }
107
+ },
108
+ // 微信小程序端右侧区域是否避让右上角原生胶囊按钮
109
+ avoidCapsule: {
110
+ type: Boolean,
111
+ default: () => defProps.navbar.avoidCapsule
112
+ },
108
113
  }
109
114
  })
@@ -85,6 +85,7 @@
85
85
  </view>
86
86
  <view
87
87
  class="u-navbar__content__right"
88
+ :style="[navbarRightStyle]"
88
89
  v-if="$slots.right || rightIcon || rightText"
89
90
  @tap="rightClick"
90
91
  >
@@ -143,6 +144,7 @@
143
144
  * @property {Object | String} titleStyle 标题的样式,对象或字符串
144
145
  * @property {String} mode 导航栏模式,default-常规,ios-大标题磨砂模式(默认 'default' )
145
146
  * @property {String | Number} scrollTop 页面滚动距离,仅 ios 模式使用,由页面 onPageScroll 传入(默认 0 )
147
+ * @property {Boolean} avoidCapsule 微信小程序端右侧区域是否避让右上角原生胶囊按钮(默认 true )
146
148
  * @event {Function} leftClick 点击左侧区域
147
149
  * @event {Function} rightClick 点击右侧区域
148
150
  * @example <u-navbar title="剑未配妥,出门已是江湖" left-text="返回" right-text="帮助" @click-left="onClickBack" @click-right="onClickRight"></u-navbar>
@@ -221,9 +223,24 @@
221
223
  if (this.leftIconColor) return this.leftIconColor
222
224
  return this.upThemeVar('--up-main-color', this.$u.color.mainColor)
223
225
  },
224
- navbarRightColor() {
225
- return this.upThemeVar('--up-main-color', this.$u.color.mainColor)
226
- },
226
+ navbarRightColor() {
227
+ return this.upThemeVar('--up-main-color', this.$u.color.mainColor)
228
+ },
229
+ // 微信小程序端右侧区域避让右上角原生胶囊按钮:
230
+ // 将容器右边缘从屏幕右缘移到胶囊左缘,内容随容器自带 padding 自然留出间距。
231
+ navbarRightStyle() {
232
+ // #ifdef MP-WEIXIN
233
+ if (!this.avoidCapsule) return {}
234
+ const rect = uni.getMenuButtonBoundingClientRect()
235
+ const windowWidth = getWindowInfo().windowWidth
236
+ // 胶囊信息不可用(部分调试环境返回全 0)时维持原有布局
237
+ if (!rect || !rect.left || !windowWidth) return {}
238
+ return { right: `${windowWidth - rect.left}px` }
239
+ // #endif
240
+ // #ifndef MP-WEIXIN
241
+ return {}
242
+ // #endif
243
+ },
227
244
  navbarInnerStyle() {
228
245
  const style = {}
229
246
  style.background = this.navbarBgColor
@@ -25,7 +25,8 @@
25
25
  class="u-keyboard__button-wrapper__button u-keyboard__button-wrapper__button--gray"
26
26
  hover-class="u-hover-class"
27
27
  :hover-stay-time="200"
28
- @touchstart.stop="backspaceClick"
28
+ @tap="backspaceClick"
29
+ @touchstart.stop="backspaceTouchStart"
29
30
  @touchend="clearTimer"
30
31
  >
31
32
  <up-icon
@@ -110,17 +111,28 @@
110
111
  },
111
112
  emits: ["backspace", "change"],
112
113
  methods: {
113
- // 点击退格键
114
+ // 点击退格键,PC端浏览器(如电脑版微信内置浏览器)只有鼠标事件不触发touch,故由tap触发删除
114
115
  backspaceClick() {
115
116
  this.$emit('backspace');
116
- clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
117
- this.timer = null;
118
- this.timer = setInterval(() => {
117
+ },
118
+ // 触摸退格键,一定时间后进入长按连续删除状态,短按时会在touchend前被清除
119
+ backspaceTouchStart() {
120
+ this.clearTimer(); //再次清空定时器,防止重复注册定时器
121
+ this.timer = setTimeout(() => {
122
+ this.$emit('backspace');
123
+ this.backspaceLongPress();
124
+ }, 600);
125
+ },
126
+ // 长按状态下每隔一段时间删除一次
127
+ backspaceLongPress() {
128
+ this.clearTimer();
129
+ this.timer = setTimeout(() => {
119
130
  this.$emit('backspace');
131
+ this.backspaceLongPress();
120
132
  }, 250);
121
133
  },
122
134
  clearTimer() {
123
- clearInterval(this.timer);
135
+ clearTimeout(this.timer);
124
136
  this.timer = null;
125
137
  },
126
138
  // 获取键盘显示的内容
@@ -783,9 +783,10 @@ Parser.prototype.popNode = function () {
783
783
  }
784
784
  // #endif
785
785
 
786
- // 一些编辑器的自带 class
787
- if ((attrs.class || '').includes('align-center')) {
788
- styleObj['text-align'] = 'center'
786
+ // 一些编辑器的自带 class(quill 等编辑器用 class 而非行内样式表示对齐,如 ql-align-right)
787
+ const alignClass = ['left', 'center', 'right', 'justify'].find(align => (attrs.class || '').includes('align-' + align))
788
+ if (alignClass) {
789
+ styleObj['text-align'] = alignClass
789
790
  }
790
791
 
791
792
  Object.assign(styleObj, this.parseStyle(node))
@@ -334,9 +334,13 @@ export default {
334
334
  //记录用户选中但是还没确认的值
335
335
  this.currentActiveValue = value;
336
336
  // 通过对比前后两次的列索引,得出当前变化的是哪一列
337
+ // lastIndex记录的是"上一次确认"的值,滚动但未点确定时并不会更新,
338
+ // 用它做对比会导致滚动过上级列之后,columnIndex一直停留在上级列,
339
+ // 所以这里用innerIndex,即picker-view上一次所处的位置
340
+ const prevIndex = this.innerIndex
337
341
  for (let i = 0; i < value.length; i++) {
338
342
  let item = value[i]
339
- if (item !== undefined && item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0
343
+ if (item !== undefined && item !== (prevIndex[i] || 0)) { // 把undefined转为合法假值0
340
344
  // 设置columnIndex为当前变化列的索引
341
345
  columnIndex = i
342
346
  // index则为变化列中的变化项的索引
@@ -346,8 +350,7 @@ export default {
346
350
  }
347
351
  this.columnIndex = columnIndex
348
352
  const values = this.innerColumns
349
- // 将当前的各项变化索引,设置为"上一次"的索引变化值
350
- // this.setLastIndex(value)
353
+ // 记录picker-view当前的各列位置,作为下一次变化对比的基准
351
354
  this.setIndexs(value)
352
355
  //如果是非自带输入框才会在change时候触发v-model绑值的变化
353
356
  //否则会非常的奇怪,用户未确认,值就变了
@@ -13,6 +13,7 @@
13
13
  backgroundColor: resolvedBgColor,
14
14
  borderRadius: shape == 'round' ? '100px' : '4px',
15
15
  borderColor: resolvedBorderColor,
16
+ '--up-search-placeholder-color': resolvedPlaceholderColor,
16
17
  }"
17
18
  >
18
19
  <template v-if="$slots.label || label !== null">
@@ -268,6 +269,20 @@
268
269
  }
269
270
  </script>
270
271
 
272
+ <style lang="scss">
273
+ /**
274
+ * placeholder-class 指定的类不能写在 scoped 中:小程序端 placeholder 节点由 input 内部渲染,
275
+ * 不会带上 scoped 的 data-v- 类,编译出的 .xxx--placeholder.data-v-xxx 永远匹配不到它,
276
+ * placeholderColor 就只剩内联 placeholder-style 一条通路,而真机上首次渲染并不总会应用它,
277
+ * 表现为"输入内容后颜色才生效"。颜色统一由 --up-search-placeholder-color 变量传入。
278
+ */
279
+ $u-search-input-placeholder-color: $u-tips-color !default;
280
+
281
+ .u-search__content__input--placeholder {
282
+ color: var(--up-search-placeholder-color, #{$u-search-input-placeholder-color});
283
+ }
284
+ </style>
285
+
271
286
  <style lang="scss" scoped>
272
287
  $u-search-content-padding: 0 10px !default;
273
288
  $u-search-label-color: $u-main-color !default;
@@ -280,7 +295,6 @@ $u-search-close-transform: scale(0.82) !default;
280
295
  $u-search-input-font-size: 14px !default;
281
296
  $u-search-input-margin: 0 5px !default;
282
297
  $u-search-input-color: $u-main-color !default;
283
- $u-search-input-placeholder-color: $u-tips-color !default;
284
298
  $u-search-action-font-size: 14px !default;
285
299
  $u-search-action-color: $u-main-color !default;
286
300
  $u-search-action-width: 0 !default;
@@ -341,10 +355,6 @@ $u-search-action-margin-left: 5px !default;
341
355
  line-height: 1;
342
356
  margin: $u-search-input-margin;
343
357
  color: $u-search-input-color;
344
-
345
- &--placeholder {
346
- color: $u-search-input-placeholder-color;
347
- }
348
358
  }
349
359
  }
350
360