uview-plus 3.8.32 → 3.8.37

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,33 @@
1
+ ## 3.8.37(2026-05-22)
2
+ feat: 日历组件新增今天按钮并支持快速回到当月
3
+
4
+ 在日历头部增加今天按钮与禁用态,点击后可跳转到今天所在月份;为今天日期增加独立高亮样式且不覆盖用户当前已选日期。同时补充 showToday 配置、类型声明与多语言文案键,保证不同语言和模式下行为一致。
5
+
6
+ ## 3.8.36(2026-05-20)
7
+ fix: 修复 u-parse 链接点击在 iOS 端可能闪退的问题
8
+
9
+ - 在 u-parse 的两个链接点击入口统一增加 href 规范化与外链判断,避免空值或非法值传入 plus.runtime.openWeb/openURL。
10
+ - 同时将原生外链打开封装为受保护调用并添加异常兜底,降低 APP-PLUS 与 APP-HARMONY 下点击链接导致崩溃的风险。
11
+
12
+ ## 3.8.35(2026-05-19)
13
+ fix: 修复up-input在confirm阶段可能丢失扫码内容
14
+
15
+ - 问题背景:issue #1011 反馈外接扫码枪场景下,up-input 的 confirm 回调偶发丢码,连续0更明显。
16
+ - 原因分析:组件 onConfirm 直接返回 innerValue,当扫码输入与回车触发节奏接近时,confirm 可能早于最后一次 input 同步,导致取到旧值。
17
+ - 改动说明:onConfirm 优先使用 event.detail.value,若平台未提供该字段再回退到 innerValue,兼容原有行为并降低扫码场景丢码风险。
18
+ ## 3.8.34(2026-05-18)
19
+ fix: 仅在 TabBar 页面同步 TabBar 样式
20
+
21
+ - 在 libs/theme/runtime.js 新增 isTabBarPage 与 trySetTabBarStyle,统一封装 TabBar 页面判断与安全调用。
22
+ - 在 runtime/theme/common mixin 的原生主题同步路径中,全部改为调用 trySetTabBarStyle,不再直接调用 uni.setTabBarStyle。
23
+
24
+ ## 3.8.33(2026-05-17)
25
+ fix: 修复主题变量桥接与 nvue 按钮取色问题
26
+
27
+ - 调整主题 bridge 的导出位置,避免 legacy light 变量在样式注入后覆盖用户自定义 CSS 变量。
28
+ - 修复 up-button 在 nvue 下的主题取色与 info/plain 样式问题,补充运行时取色兜底,避免按钮颜色错误或主题方法缺失时报错。
29
+ - 同步更新示例接入、版本号与变更记录,保持主题改造后的行为与文档一致。
30
+
1
31
  ## 3.8.32(2026-05-14)
2
32
  feat: 新增二维码静区配置
3
33
 
@@ -117,6 +117,7 @@ import { mixin } from '../../libs/mixin/mixin';
117
117
  import { props } from "./props";
118
118
  import { addStyle } from '../../libs/function/index';
119
119
  import { throttle } from '../../libs/function/throttle';
120
+ import { getThemeVar } from '../../libs/theme/runtime.js';
120
121
  /**
121
122
  * button 按钮
122
123
  * @description Button 按钮
@@ -194,8 +195,43 @@ export default {
194
195
  isDarkTheme() {
195
196
  return this.$u.theme && this.$u.theme.mode === 'dark';
196
197
  },
198
+ nvueMainColor() {
199
+ return this.resolveNvueColor(this.$u.color.mainColor, this.isDarkTheme ? '#f5f5f5' : '#303133');
200
+ },
201
+ nvueBorderColor() {
202
+ return this.resolveNvueColor(this.$u.color.borderColor, this.isDarkTheme ? '#3a3a3c' : '#dadbde');
203
+ },
197
204
  themeTypeColor() {
198
- return this.$u.color[this.type] || this.$u.color.info;
205
+ const fallbackMap = {
206
+ primary: '#3c9cff',
207
+ success: '#5ac725',
208
+ warning: '#f9ae3d',
209
+ error: '#f56c6c',
210
+ info: '#909399',
211
+ };
212
+ const type = fallbackMap[this.type] ? this.type : 'info';
213
+ return this.resolveNvueColor(
214
+ this.upThemeVar(`--up-${type}`, fallbackMap[type]),
215
+ fallbackMap[type]
216
+ );
217
+ },
218
+ nvueInfoBackgroundColor() {
219
+ return this.upThemeVar(
220
+ '--up-button-info-background-color',
221
+ this.upThemeVar(
222
+ '--up-card-bg-color',
223
+ this.isDarkTheme ? '#1c1c1e' : '#ffffff'
224
+ )
225
+ );
226
+ },
227
+ nvuePlainBackgroundColor() {
228
+ return this.upThemeVar(
229
+ '--up-button-plain-background-color',
230
+ this.upThemeVar(
231
+ '--up-card-bg-color',
232
+ this.isDarkTheme ? '#1c1c1e' : '#ffffff'
233
+ )
234
+ );
199
235
  },
200
236
  loadingColor() {
201
237
  if (this.plain) {
@@ -212,13 +248,11 @@ export default {
212
248
  iconColorCom() {
213
249
  // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
214
250
  // up-icon的color能接受一个主题颜色的值
215
- if (this.iconColor) return this.iconColor;
251
+ if (this.iconColor) return this.iconColor;
216
252
  if (this.plain) {
217
253
  return this.color ? this.color : this.type;
218
254
  } else {
219
- return this.type === "info"
220
- ? (this.isDarkTheme ? "#f3f4f6" : "#000000")
221
- : "#ffffff";
255
+ return this.type === "info" ? this.nvueMainColor : "#ffffff";
222
256
  }
223
257
  },
224
258
  baseColor() {
@@ -249,26 +283,28 @@ export default {
249
283
  }
250
284
  return style;
251
285
  }
286
+ // #ifdef APP-NVUE
252
287
  const typeColor = this.themeTypeColor;
253
288
  if (this.plain) {
254
- style.color = this.type === 'info' ? this.$u.color.mainColor : typeColor;
255
- style.backgroundColor = this.isDarkTheme ? '#1c1c1e' : '#ffffff';
256
- style.borderColor = this.type === 'info' ? this.$u.color.borderColor : typeColor;
289
+ style.color = this.type === 'info' ? this.nvueMainColor : typeColor;
290
+ style.backgroundColor = this.nvuePlainBackgroundColor;
291
+ style.borderColor = this.type === 'info' ? this.nvueBorderColor : typeColor;
257
292
  style.borderWidth = this.hairline ? '0.5px' : '1px';
258
293
  style.borderStyle = 'solid';
259
294
  } else {
260
295
  style.borderWidth = this.hairline ? '0.5px' : '1px';
261
296
  style.borderStyle = 'solid';
262
297
  if (this.type === 'info') {
263
- style.color = this.$u.color.mainColor;
264
- style.backgroundColor = this.isDarkTheme ? '#2c2c2e' : '#ffffff';
265
- style.borderColor = this.$u.color.borderColor;
298
+ style.color = this.nvueMainColor;
299
+ style.backgroundColor = this.nvueInfoBackgroundColor;
300
+ style.borderColor = this.nvueBorderColor;
266
301
  } else {
267
302
  style.color = '#ffffff';
268
303
  style.backgroundColor = typeColor;
269
304
  style.borderColor = typeColor;
270
305
  }
271
306
  }
307
+ // #endif
272
308
  return style;
273
309
  },
274
310
  // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
@@ -276,7 +312,7 @@ export default {
276
312
  let style = {};
277
313
  // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
278
314
  if (this.type === "info") {
279
- style.color = this.$u.color.mainColor;
315
+ style.color = this.nvueMainColor;
280
316
  }
281
317
  if (this.color) {
282
318
  style.color = this.plain ? this.color : "white";
@@ -301,6 +337,14 @@ export default {
301
337
  'error', 'opensetting', 'launchapp', 'agreeprivacyauthorization'],
302
338
  methods: {
303
339
  addStyle,
340
+ resolveNvueColor(colorValue, fallbackColor) {
341
+ if (typeof colorValue !== 'string') return fallbackColor;
342
+ if (colorValue.indexOf('var(') > -1) return fallbackColor;
343
+ return colorValue;
344
+ },
345
+ upThemeVar(varName, fallbackColor) {
346
+ return getThemeVar(varName, fallbackColor, this.$u);
347
+ },
304
348
  clickHandler(e: any) {
305
349
  // 非禁止并且非加载中,才能点击
306
350
  if (!this.disabled && !this.loading) {
@@ -362,9 +406,9 @@ $u-button-mini-height: 22px !default;
362
406
  $u-button-mini-font-size: 10px !default;
363
407
  $u-button-mini-min-width: 50px !default;
364
408
  $u-button-disabled-opacity: 0.5 !default;
365
- $u-button-info-color: #323233 !default;
366
- $u-button-info-background-color: #fff !default;
367
- $u-button-info-border-color: #ebedf0 !default;
409
+ $u-button-info-color: $u-main-color !default;
410
+ $u-button-info-background-color: var(--up-button-info-background-color, var(--up-card-bg-color, #fff)) !default;
411
+ $u-button-info-border-color: $u-border-color !default;
368
412
  $u-button-info-border-width: 1px !default;
369
413
  $u-button-info-border-style: solid !default;
370
414
  $u-button-success-color: #fff !default;
@@ -397,7 +441,7 @@ $u-button-square-border-top-left-radius: 3px !default;
397
441
  $u-button-square-border-bottom-left-radius: 3px !default;
398
442
  $u-button-square-border-bottom-right-radius: 3px !default;
399
443
  $u-button-icon-min-width: 1em !default;
400
- $u-button-plain-background-color: #fff !default;
444
+ $u-button-plain-background-color: var(--up-button-plain-background-color, var(--up-card-bg-color, #fff)) !default;
401
445
  $u-button-hairline-border-width: 0.5px !default;
402
446
 
403
447
  .u-button {
@@ -38,9 +38,10 @@ export default {
38
38
  showRangePrompt: true,
39
39
  allowSameDay: false,
40
40
  round: 0,
41
- monthNum: 3,
42
- monthSwitch: false,
43
- weekText: [t("up.week.one"), t("up.week.two"), t("up.week.three"), t("up.week.four"), t("up.week.five"), t("up.week.six"), t("up.week.seven")],
41
+ monthNum: 3,
42
+ monthSwitch: false,
43
+ showToday: true,
44
+ weekText: [t("up.week.one"), t("up.week.two"), t("up.week.three"), t("up.week.four"), t("up.week.five"), t("up.week.six"), t("up.week.seven")],
44
45
  forbidDays: [],
45
46
  forbidDaysToast: t("up.calendar.disabled"),
46
47
  monthFormat: '',
@@ -4,10 +4,10 @@
4
4
  class="u-calendar-header__title"
5
5
  v-if="showTitle"
6
6
  >{{ title }}</text>
7
- <view
8
- class="u-calendar-header__subtitle-wrap"
9
- v-if="showSubtitle"
10
- >
7
+ <view
8
+ class="u-calendar-header__subtitle-wrap"
9
+ v-if="showSubtitle"
10
+ >
11
11
  <text
12
12
  v-if="showSwitch"
13
13
  class="u-calendar-header__switch"
@@ -27,13 +27,19 @@
27
27
  :class="{ 'u-calendar-header__switch--disabled': nextDisabled }"
28
28
  @click="next"
29
29
  >›</text>
30
- <text
31
- v-if="showSwitch"
32
- class="u-calendar-header__switch"
33
- :class="{ 'u-calendar-header__switch--disabled': nextYearDisabled }"
34
- @click="nextYear"
35
- >»</text>
36
- </view>
30
+ <text
31
+ v-if="showSwitch"
32
+ class="u-calendar-header__switch"
33
+ :class="{ 'u-calendar-header__switch--disabled': nextYearDisabled }"
34
+ @click="nextYear"
35
+ >»</text>
36
+ <text
37
+ v-if="showToday"
38
+ class="u-calendar-header__today"
39
+ :class="{ 'u-calendar-header__today--disabled': todayDisabled }"
40
+ @click="today"
41
+ >{{ todayText }}</text>
42
+ </view>
37
43
  <view class="u-calendar-header__weekdays">
38
44
  <text class="u-calendar-header__weekdays__weekday">{{ weekText[0] }}</text>
39
45
  <text class="u-calendar-header__weekdays__weekday">{{ weekText[1] }}</text>
@@ -100,12 +106,27 @@
100
106
  type: Boolean,
101
107
  default: false
102
108
  },
103
- // 下一年按钮是否禁用
104
- nextYearDisabled: {
105
- type: Boolean,
106
- default: false
107
- }
108
- },
109
+ // 下一年按钮是否禁用
110
+ nextYearDisabled: {
111
+ type: Boolean,
112
+ default: false
113
+ },
114
+ // 是否显示今天按钮
115
+ showToday: {
116
+ type: Boolean,
117
+ default: true
118
+ },
119
+ // 今天按钮文案
120
+ todayText: {
121
+ type: String,
122
+ default: ''
123
+ },
124
+ // 今天按钮是否禁用
125
+ todayDisabled: {
126
+ type: Boolean,
127
+ default: false
128
+ }
129
+ },
109
130
  data() {
110
131
  return {
111
132
 
@@ -127,13 +148,18 @@
127
148
  this.$emit('prevYear')
128
149
  }
129
150
  },
130
- nextYear() {
131
- if (!this.nextYearDisabled) {
132
- this.$emit('nextYear')
133
- }
134
- }
135
- },
136
- }
151
+ nextYear() {
152
+ if (!this.nextYearDisabled) {
153
+ this.$emit('nextYear')
154
+ }
155
+ },
156
+ today() {
157
+ if (!this.todayDisabled) {
158
+ this.$emit('today')
159
+ }
160
+ }
161
+ },
162
+ }
137
163
  </script>
138
164
 
139
165
  <style lang="scss" scoped>
@@ -182,17 +208,33 @@
182
208
  }
183
209
  }
184
210
 
185
- &__weekdays {
186
- @include flex;
187
- justify-content: space-between;
211
+ &__weekdays {
212
+ @include flex;
213
+ justify-content: space-between;
188
214
 
189
215
  &__weekday {
190
216
  font-size: 13px;
191
217
  color: var(--up-main-color, $u-main-color);
192
218
  line-height: 30px;
193
219
  flex: 1;
194
- text-align: center;
195
- }
196
- }
197
- }
198
- </style>
220
+ text-align: center;
221
+ }
222
+ }
223
+
224
+ &__today {
225
+ margin-left: 4px;
226
+ height: 28px;
227
+ line-height: 28px;
228
+ padding: 0 10px;
229
+ border-radius: 14px;
230
+ font-size: 13px;
231
+ color: var(--up-primary, $u-primary);
232
+ border: 1px solid var(--up-primary, $u-primary);
233
+ flex-shrink: 0;
234
+
235
+ &--disabled {
236
+ opacity: 0.35;
237
+ }
238
+ }
239
+ }
240
+ </style>
@@ -135,6 +135,11 @@
135
135
  forbidDaysToast: {
136
136
  type: String,
137
137
  default: ''
138
+ },
139
+ // 今天日期,用于独立高亮
140
+ todayDate: {
141
+ type: String,
142
+ default: ''
138
143
  }
139
144
  },
140
145
  data() {
@@ -193,6 +198,10 @@
193
198
  if (this.selected.some(item => this.dateSame(item, date))) {
194
199
  style.backgroundColor = this.color
195
200
  }
201
+ if (this.todayDate && this.dateSame(date, this.todayDate) && !this.selected.some(item => this.dateSame(item, date))) {
202
+ style.border = `1px solid ${this.color}`
203
+ style.boxSizing = 'border-box'
204
+ }
196
205
  if (this.mode === 'single') {
197
206
  if (date === this.selected[0]) {
198
207
  // 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
@@ -148,6 +148,11 @@ export const props = defineMixin({
148
148
  type: Boolean,
149
149
  default: () => defProps.calendar.monthSwitch
150
150
  },
151
+ // 是否显示今天按钮
152
+ showToday: {
153
+ type: Boolean,
154
+ default: () => defProps.calendar.showToday
155
+ },
151
156
  // 星期文案
152
157
  weekText: {
153
158
  type: Array,
@@ -16,6 +16,9 @@
16
16
  :showTitle="showTitle"
17
17
  :weekText="weekText"
18
18
  :showSwitch="monthSwitch"
19
+ :showToday="showToday"
20
+ :todayText="todayText"
21
+ :todayDisabled="todayDisabled"
19
22
  :prevDisabled="switchPrevDisabled"
20
23
  :nextDisabled="switchNextDisabled"
21
24
  :prevYearDisabled="switchPrevYearDisabled"
@@ -24,6 +27,7 @@
24
27
  @next="nextMonth"
25
28
  @prevYear="prevYear"
26
29
  @nextYear="nextYear"
30
+ @today="jumpToToday"
27
31
  ></uHeader>
28
32
  <scroll-view
29
33
  v-if="!monthSwitch"
@@ -56,6 +60,7 @@
56
60
  :forbidDays="forbidDays"
57
61
  :forbidDaysToast="forbidDaysToast"
58
62
  :monthFormat="monthFormat"
63
+ :todayDate="todayDate"
59
64
  ref="month"
60
65
  @monthSelected="monthSelected"
61
66
  @updateMonthTop="onUpdateMonthTop"
@@ -88,6 +93,7 @@
88
93
  :forbidDays="forbidDays"
89
94
  :forbidDaysToast="forbidDaysToast"
90
95
  :monthFormat="monthFormat"
96
+ :todayDate="todayDate"
91
97
  ref="month"
92
98
  @monthSelected="monthSelected"
93
99
  @updateMonthTop="onUpdateMonthTop"
@@ -121,6 +127,7 @@ import { mpMixin } from '../../libs/mixin/mpMixin.js'
121
127
  import { mixin } from '../../libs/mixin/mixin.js'
122
128
  import { addUnit, getPx, range, error, padZero } from '../../libs/function/index';
123
129
  import test from '../../libs/function/test';
130
+ import { t } from '../../libs/i18n'
124
131
  /**
125
132
  * Calendar 日历
126
133
  * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
@@ -224,6 +231,28 @@ export default {
224
231
  ? Number(this.minDate)
225
232
  : this.minDate
226
233
  },
234
+ todayDate() {
235
+ return dayjs().format('YYYY-MM-DD')
236
+ },
237
+ todayText() {
238
+ return t('up.calendar.today')
239
+ },
240
+ todayDisabled() {
241
+ const today = dayjs(this.todayDate)
242
+ const minDate = this.innerMinDate
243
+ ? dayjs(this.innerMinDate)
244
+ : null
245
+ const maxDate = this.innerMaxDate
246
+ ? dayjs(this.innerMaxDate)
247
+ : null
248
+ if (minDate && today.isBefore(minDate, 'day')) {
249
+ return true
250
+ }
251
+ if (maxDate && today.isAfter(maxDate, 'day')) {
252
+ return true
253
+ }
254
+ return false
255
+ },
227
256
  // 多个条件的变化,会引起选中日期的变化,这里统一管理监听
228
257
  selectedChange() {
229
258
  return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
@@ -449,6 +478,22 @@ export default {
449
478
  this.monthIndex += 12
450
479
  }
451
480
  },
481
+ jumpToToday() {
482
+ if (this.todayDisabled) {
483
+ return
484
+ }
485
+ const targetMonth = dayjs(this.todayDate).format('YYYY-MM')
486
+ if (this.monthSwitch) {
487
+ const todayMonthIndex = this.months.findIndex(({ year, month }) => {
488
+ return `${year}-${padZero(month)}` === targetMonth
489
+ })
490
+ if (todayMonthIndex !== -1) {
491
+ this.monthIndex = todayMonthIndex
492
+ }
493
+ return
494
+ }
495
+ this.scrollIntoDefaultMonth(targetMonth)
496
+ },
452
497
  // 滚动到默认设置的月份
453
498
  scrollIntoDefaultMonth(selected) {
454
499
  // 查询默认日期在可选列表的下标
@@ -462,6 +507,7 @@ export default {
462
507
  if (_index !== -1) {
463
508
  // #ifndef MP-WEIXIN
464
509
  this.$nextTick(() => {
510
+ this.scrollIntoView = ''
465
511
  this.scrollIntoView = `month-${_index}`
466
512
  this.scrollIntoViewScroll = this.scrollIntoView
467
513
  })
@@ -324,7 +324,9 @@ export default {
324
324
  },
325
325
  // 点击完成按钮时触发
326
326
  onConfirm(event) {
327
- this.$emit("confirm", this.innerValue);
327
+ const detail = event && event.detail ? event.detail : {};
328
+ const confirmValue = typeof detail.value !== "undefined" ? detail.value : this.innerValue;
329
+ this.$emit("confirm", confirmValue);
328
330
  },
329
331
  // 键盘高度发生变化的时候触发此事件
330
332
  // 兼容性:微信小程序2.7.0+、App 3.1.0+
@@ -185,6 +185,53 @@ export default {
185
185
  // #ifdef MP-WEIXIN
186
186
  toJSON () { return this },
187
187
  // #endif
188
+ /**
189
+ * @description 规范化链接地址
190
+ * @param {any} href
191
+ * @return {String}
192
+ */
193
+ normalizeHref (href) {
194
+ return typeof href === 'string' ? href.trim() : ''
195
+ },
196
+
197
+ /**
198
+ * @description 判断是否为外部链接
199
+ * @param {String} href
200
+ * @return {Boolean}
201
+ */
202
+ isExternalLink (href) {
203
+ return href.split('?')[0].includes('://')
204
+ },
205
+
206
+ /**
207
+ * @description 打开外部链接
208
+ * @param {String} href
209
+ */
210
+ openExternalLink (href) {
211
+ // #ifdef H5
212
+ window.open(href)
213
+ // #endif
214
+ // #ifdef MP
215
+ uni.setClipboardData({
216
+ data: href,
217
+ success: () =>
218
+ uni.showToast({
219
+ title: '链接已复制'
220
+ })
221
+ })
222
+ // #endif
223
+ // #ifdef APP-PLUS
224
+ try {
225
+ plus.runtime.openWeb(href)
226
+ } catch (e) { }
227
+ // #endif
228
+ // #ifdef APP-HARMONY
229
+ try {
230
+ plus.runtime.openURL(href)
231
+ } catch (e) { }
232
+ // #endif
233
+ },
234
+
188
235
  /**
189
236
  * @description 播放视频事件
190
237
  * @param {Event} e
@@ -341,7 +388,7 @@ export default {
341
388
  linkTap (e) {
342
389
  const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
343
390
  const attrs = node.attrs || e
344
- const href = attrs.href
391
+ const href = this.normalizeHref(attrs.href)
345
392
  this.root.$emit('linktap', Object.assign({
346
393
  innerText: this.root.getText(node.children || []) // 链接内的文本内容
347
394
  }, attrs))
@@ -349,27 +396,10 @@ export default {
349
396
  if (href[0] === '#') {
350
397
  // 跳转锚点
351
398
  this.root.navigateTo(href.substring(1)).catch(() => { })
352
- } else if (href.split('?')[0].includes('://')) {
399
+ } else if (this.isExternalLink(href)) {
353
400
  // 复制外部链接
354
401
  if (this.root.copyLink) {
355
- // #ifdef H5
356
- window.open(href)
357
- // #endif
358
- // #ifdef MP
359
- uni.setClipboardData({
360
- data: href,
361
- success: () =>
362
- uni.showToast({
363
- title: '链接已复制'
364
- })
365
- })
366
- // #endif
367
- // #ifdef APP-PLUS
368
- plus.runtime.openWeb(href)
369
- // #endif
370
- // #ifdef APP-HARMONY
371
- plus.runtime.openURL(href)
372
- // #endif
402
+ this.openExternalLink(href)
373
403
  }
374
404
  } else {
375
405
  // 跳转页面
@@ -130,6 +130,41 @@ export default {
130
130
  this._hook('onDetached')
131
131
  },
132
132
  methods: {
133
+ /**
134
+ * @description 规范化链接地址
135
+ * @param {any} href
136
+ * @return {String}
137
+ */
138
+ normalizeHref(href) {
139
+ return typeof href === 'string' ? href.trim() : ''
140
+ },
141
+
142
+ /**
143
+ * @description 判断是否为外部链接
144
+ * @param {String} href
145
+ * @return {Boolean}
146
+ */
147
+ isExternalLink(href) {
148
+ return href.split('?')[0].includes('://')
149
+ },
150
+
151
+ /**
152
+ * @description 打开外部链接
153
+ * @param {String} href
154
+ */
155
+ openExternalLink(href) {
156
+ // #ifdef APP-PLUS
157
+ try {
158
+ plus.runtime.openWeb(href)
159
+ } catch (e) { }
160
+ // #endif
161
+ // #ifdef APP-HARMONY
162
+ try {
163
+ plus.runtime.openURL(href)
164
+ } catch (e) { }
165
+ // #endif
166
+ },
167
+
133
168
  /**
134
169
  * @description 将锚点跳转的范围限定在一个 scroll-view 内
135
170
  * @param {Object} page scroll-view 所在页面的示例
@@ -420,7 +455,7 @@ export default {
420
455
  break
421
456
  // 链接点击
422
457
  case 'onLinkTap': {
423
- const href = message.attrs.href
458
+ const href = this.normalizeHref(message.attrs.href)
424
459
  this.$emit('linktap', message.attrs)
425
460
  if (href) {
426
461
  // 锚点跳转
@@ -430,15 +465,10 @@ export default {
430
465
  offset: message.offset
431
466
  })
432
467
  }
433
- } else if (href.includes('://')) {
468
+ } else if (this.isExternalLink(href)) {
434
469
  // 打开外链
435
470
  if (this.copyLink) {
436
- // #ifdef APP-PLUS
437
- plus.runtime.openWeb(href)
438
- // #endif
439
- // #ifdef APP-HARMONY
440
- plus.runtime.openURL(href)
441
- // #endif
471
+ this.openExternalLink(href)
442
472
  }
443
473
  } else {
444
474
  uni.navigateTo({
package/index.scss CHANGED
@@ -1,5 +1,6 @@
1
1
  /* #ifndef APP-NVUE */
2
2
  @import "./libs/css/theme-vars.scss";
3
+ @import "./libs/css/theme-legacy-bridge.scss";
3
4
  /* #endif */
4
5
 
5
6
  // 引入公共基础类
@@ -0,0 +1,6 @@
1
+ @import "../../theme.scss";
2
+
3
+ @include up-export-legacy-light-vars(':root');
4
+ @include up-export-legacy-light-vars('page');
5
+ @include up-export-legacy-light-vars('body');
6
+ @include up-export-legacy-light-vars("[data-up-theme='light']");
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "Datumsauswahl",
35
35
  "up.calendar.disabled": "Dieses Datum ist deaktiviert",
36
36
  "up.calendar.daysExceed": "Die Anzahl der ausgewählten Tage darf {days} Tage nicht überschreiten",
37
+ "up.calendar.today": "Heute",
37
38
  "up.cityLocate.locateCity": "Stadt lokalisieren",
38
39
  "up.cityLocate.fail": "Lokalisierung fehlgeschlagen, bitte klicken Sie zum Wiederholen.",
39
40
  "up.cityLocate.locating": "Lokalisierung läuft",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "Date selection",
35
35
  "up.calendar.disabled": "This date is disabled",
36
36
  "up.calendar.daysExceed": "The number of selected days cannot exceed {days} days",
37
+ "up.calendar.today": "Today",
37
38
  "up.cityLocate.locateCity": "Locate city",
38
39
  "up.cityLocate.fail": "Location failed, please click to retry.",
39
40
  "up.cityLocate.locating": "Locating",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "Selección de fecha",
35
35
  "up.calendar.disabled": "Esta fecha está deshabilitada",
36
36
  "up.calendar.daysExceed": "Los días seleccionados no pueden exceder {days} días",
37
+ "up.calendar.today": "Hoy",
37
38
  "up.cityLocate.locateCity": "Localizar ciudad",
38
39
  "up.cityLocate.fail": "Error de localización, haga clic para reintentar.",
39
40
  "up.cityLocate.locating": "Localizando",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "Sélection de dates",
35
35
  "up.calendar.disabled": "Cette date est désactivée",
36
36
  "up.calendar.daysExceed": "Le nombre de jours sélectionnés ne peut pas dépasser {days} jours",
37
+ "up.calendar.today": "Aujourd'hui",
37
38
  "up.cityLocate.locateCity": "Localiser la ville",
38
39
  "up.cityLocate.fail": "Échec de localisation, veuillez cliquer pour réessayer.",
39
40
  "up.cityLocate.locating": "Localisation en cours",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "日付選択",
35
35
  "up.calendar.disabled": "この日付は無効です",
36
36
  "up.calendar.daysExceed": "選択日数は{days}日を超えることはできません",
37
+ "up.calendar.today": "今日",
37
38
  "up.cityLocate.locateCity": "都市の位置を特定",
38
39
  "up.cityLocate.fail": "位置特定に失敗しました。再試行するにはクリックしてください。",
39
40
  "up.cityLocate.locating": "位置特定中",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "날짜 선택",
35
35
  "up.calendar.disabled": "해당 날짜는 사용할 수 없습니다",
36
36
  "up.calendar.daysExceed": "선택한 날짜 수가 {days}일을 초과할 수 없습니다",
37
+ "up.calendar.today": "오늘",
37
38
  "up.cityLocate.locateCity": "도시 위치 찾기",
38
39
  "up.cityLocate.fail": "위치 찾기 실패, 다시 시도하려면 클릭하세요.",
39
40
  "up.cityLocate.locating": "위치 찾는 중",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "Выбор даты",
35
35
  "up.calendar.disabled": "Эта дата отключена",
36
36
  "up.calendar.daysExceed": "Количество выбранных дней не может превышать {days} дней",
37
+ "up.calendar.today": "Сегодня",
37
38
  "up.cityLocate.locateCity": "Определение города",
38
39
  "up.cityLocate.fail": "Ошибка определения местоположения, нажмите для повтора.",
39
40
  "up.cityLocate.locating": "Определение местоположения",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "เลือกวันที่",
35
35
  "up.calendar.disabled": "วันที่นี้ถูกปิดการใช้งาน",
36
36
  "up.calendar.daysExceed": "จำนวนวันที่เลือกต้องไม่เกิน {days} วัน",
37
+ "up.calendar.today": "วันนี้",
37
38
  "up.cityLocate.locateCity": "ระบุตำแหน่งเมือง",
38
39
  "up.cityLocate.fail": "การระบุตำแหน่งล้มเหลว กรุณาคลิกเพื่อลองใหม่",
39
40
  "up.cityLocate.locating": "กำลังระบุตำแหน่ง",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "日期选择",
35
35
  "up.calendar.disabled": "该日期已禁用",
36
36
  "up.calendar.daysExceed": "选择天数不能超过{days}天",
37
+ "up.calendar.today": "今天",
37
38
  "up.cityLocate.locateCity": "定位城市",
38
39
  "up.cityLocate.fail": "定位失败,请点击重试。",
39
40
  "up.cityLocate.locating": "定位中",
@@ -34,6 +34,7 @@
34
34
  "up.calendar.chooseDates": "日期選擇",
35
35
  "up.calendar.disabled": "該日期已禁用",
36
36
  "up.calendar.daysExceed": "選擇天數不能超過{days}天",
37
+ "up.calendar.today": "今天",
37
38
  "up.cityLocate.locateCity": "定位城市",
38
39
  "up.cityLocate.fail": "定位失敗,請點擊重試。",
39
40
  "up.cityLocate.locating": "定位中",
@@ -133,6 +133,37 @@ function getRuntimeU(upU) {
133
133
  return null
134
134
  }
135
135
 
136
+ function normalizeRuntimeRoute(route) {
137
+ if (typeof route !== 'string') return ''
138
+ return route.replace(/^\//, '').split('?')[0]
139
+ }
140
+
141
+ function getCurrentRuntimeRoute() {
142
+ try {
143
+ if (typeof getCurrentPages !== 'function') return ''
144
+ const pages = getCurrentPages()
145
+ if (!Array.isArray(pages) || pages.length === 0) return ''
146
+ const page = pages[pages.length - 1] || {}
147
+ return normalizeRuntimeRoute(page.route || page.path || '')
148
+ } catch (e) {}
149
+ return ''
150
+ }
151
+
152
+ function getRuntimeTabBarRoutes() {
153
+ const routes = []
154
+ try {
155
+ const runtimeConfig = typeof __uniConfig !== 'undefined' ? __uniConfig : null
156
+ const tabBarList = runtimeConfig?.tabBar?.list
157
+ if (Array.isArray(tabBarList)) {
158
+ tabBarList.forEach((item) => {
159
+ const route = normalizeRuntimeRoute(item?.pagePath || '')
160
+ if (route) routes.push(route)
161
+ })
162
+ }
163
+ } catch (e) {}
164
+ return routes
165
+ }
166
+
136
167
  function hasActiveRuntimePage() {
137
168
  try {
138
169
  if (typeof getCurrentPages === 'function') {
@@ -154,6 +185,25 @@ function trySetNavigationBarColor(options) {
154
185
  } catch (e) {}
155
186
  }
156
187
 
188
+ export function isTabBarPage() {
189
+ const route = getCurrentRuntimeRoute()
190
+ if (!route) return false
191
+ const tabBarRoutes = getRuntimeTabBarRoutes()
192
+ if (!tabBarRoutes.length) return false
193
+ return tabBarRoutes.includes(route)
194
+ }
195
+
196
+ export function trySetTabBarStyle(options) {
197
+ if (typeof uni === 'undefined' || typeof uni.setTabBarStyle !== 'function') return
198
+ if (!isTabBarPage()) return
199
+ try {
200
+ const result = uni.setTabBarStyle(options)
201
+ if (result && typeof result.catch === 'function') {
202
+ result.catch(() => {})
203
+ }
204
+ } catch (e) {}
205
+ }
206
+
157
207
  function normalizeThemeMode(theme = 'light') {
158
208
  return theme === 'dark' ? 'dark' : 'light'
159
209
  }
@@ -377,9 +427,7 @@ export function applyNativeThemeUI(upU) {
377
427
  backgroundColorBottom: pageBg
378
428
  })
379
429
  }
380
- if (typeof uni.setTabBarStyle === 'function') {
381
- uni.setTabBarStyle(getThemeTabBarStyle(runtimeU))
382
- }
430
+ trySetTabBarStyle(getThemeTabBarStyle(runtimeU))
383
431
  }
384
432
 
385
433
  export function applyNativeThemeUIDeferred(upU, delay = 30) {
@@ -1,6 +1,7 @@
1
1
  import config from '../config/config.js'
2
2
  import color from '../config/color.js'
3
3
  import index from '../function/index.js'
4
+ import { trySetTabBarStyle } from './runtime.js'
4
5
 
5
6
  const DEFAULT_LIGHT_THEME_COLORS = Object.freeze({
6
7
  primary: '#3c9cff',
@@ -562,14 +563,12 @@ function applyNativeThemeUI(mode, themeColors, themeVars = {}) {
562
563
  backgroundColorBottom: pageBg
563
564
  })
564
565
  }
565
- if (typeof uni.setTabBarStyle === 'function') {
566
- uni.setTabBarStyle({
567
- color: isDark ? '#8e8e93' : '#909399',
568
- selectedColor: isDark ? '#f2f2f7' : '#303133',
569
- backgroundColor: isDark ? '#111111' : '#ffffff',
570
- borderStyle: isDark ? 'white' : 'black'
571
- })
572
- }
566
+ trySetTabBarStyle({
567
+ color: isDark ? '#8e8e93' : '#909399',
568
+ selectedColor: isDark ? '#f2f2f7' : '#303133',
569
+ backgroundColor: isDark ? '#111111' : '#ffffff',
570
+ borderStyle: isDark ? 'white' : 'black'
571
+ })
573
572
  }
574
573
 
575
574
  function applyTheme(mode = 'light') {
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.8.32",
5
+ "version": "3.8.37",
6
6
  "description": "零云®uview-plus已兼容vue3支持多语言,120+全面的组件和便捷的工具会让您信手拈来。近期新增拖动排序、条码、图片裁剪、下拉刷新、虚拟列表、签名、Markdown等。",
7
7
  "keywords": [
8
8
  "uview",
package/theme.scss CHANGED
@@ -111,11 +111,6 @@ $up-legacy-light-theme-map: (
111
111
  }
112
112
  }
113
113
 
114
- @include up-export-legacy-light-vars(':root');
115
- @include up-export-legacy-light-vars('page');
116
- @include up-export-legacy-light-vars('body');
117
- @include up-export-legacy-light-vars("[data-up-theme='light']");
118
-
119
114
  // scss混入,为了少写几行#ifndef
120
115
  @mixin flex($direction: row) {
121
116
  /* #ifndef APP-NVUE */
@@ -134,6 +134,11 @@ declare interface CalendarProps {
134
134
  * @default 3
135
135
  */
136
136
  monthNum?: string | number
137
+ /**
138
+ * 是否显示今天按钮
139
+ * @default true
140
+ */
141
+ showToday?: boolean
137
142
  /**
138
143
  * 日期选择完成后触发,若`show-confirm`为`true`,则点击确认按钮后触发
139
144
  */