uview-pro 0.5.6 → 0.5.8

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,13 +1,58 @@
1
- ## 0.5.6(2026-02-04
1
+ ## 0.5.8(2026-02-10
2
+
3
+ ### 🚀 Chore | 构建/工程依赖/工具
4
+
5
+ - **deps:** 更新 uni-app 相关依赖包版本到 4.87,修复更新包后运行到 h5 报错问题 ([b00751b](https://github.com/anyup/uView-Pro/commit/b00751b2643bd582f2d1b9472e245d856352d4ed))
6
+
7
+ ### 📝 Documentation | 文档
8
+
9
+ - 添加npm下载量统计徽章 ([63971ee](https://github.com/anyup/uView-Pro/commit/63971eead3365d5efe29166b2252e58904c0306c))
2
10
 
3
11
  ### 🐛 Bug Fixes | Bug 修复
4
12
 
5
- - **vue-tsc:** 修复部分定时器ts类型定义错误问题(#124) ([dada764](https://github.com/anyup/uView-Pro/commit/dada764eaa6ea73402e8fa6d96a783ae2a68715a))
13
+ - **useToast:** 修复使用useToast全局提示会触发多次监听的问题(#130) ([ae56413](https://github.com/anyup/uView-Pro/commit/ae564132604d27c3d92de2f6ece5eaae75980aaa))
14
+ - **useModal:** 修复使用useModal全局弹窗会触发多次监听的问题(#130) ([50da10c](https://github.com/anyup/uView-Pro/commit/50da10c7c19d65885244df5c99634775be088824))
15
+ - **u-upload:** 修复u-upload组件中删除确认弹窗的“取消”和“确认”按钮国际化问题(#128) ([e48ab1d](https://github.com/anyup/uView-Pro/commit/e48ab1d23f50c850b1fd85c9b48860bd1d5105b0))
16
+
17
+ ### ✨ Features | 新功能
18
+
19
+ - **useToast:** 支持使用useToast页面级弹出时,toast可指定页面ID(#130) ([7d09ffe](https://github.com/anyup/uView-Pro/commit/7d09ffe5f8bbee5a3872dfc716dc7ba013f7e1bc))
20
+ - **useModal:** 支持使用useModal页面级弹出时,modal可指定页面ID(#130) ([63af409](https://github.com/anyup/uView-Pro/commit/63af409370f775bac7ceabe98db8b5225491baa8))
21
+ - **u-button:** 添加按钮文本属性支持 ([72fda47](https://github.com/anyup/uView-Pro/commit/72fda47673f1b099f12dcd88ca20570d20d3d5bc))
22
+ - **router:** 添加路由跳转hooks功能 ([cb5a687](https://github.com/anyup/uView-Pro/commit/cb5a687bd7ec8c863d237f69b5cb486e86016398))
23
+ - **demo:** 添加useModal和useToast示例页面 ([f42ca51](https://github.com/anyup/uView-Pro/commit/f42ca51839b1ebd3f02106da0310f4bbbfbb96cf))
6
24
 
7
25
  ### 👥 Contributors
8
26
 
9
27
  <a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
10
28
 
29
+ ## 0.5.7(2026-02-06)
30
+
31
+ ### 🐛 Bug Fixes | Bug 修复
32
+
33
+ - **u-tabs:** 修复u-tabs的scroll-view在不同平台会显示滚动条的问题,统一各平台的滚动条隐藏逻辑 ([6ba904b](https://github.com/anyup/uView-Pro/commit/6ba904b85fdc5be69ab8b430878e16fa74674c64))
34
+ - **demo:** 修复演示项目在钉钉小程序调用uni.setTabBarItem报错问题(#125) ([fd4ea39](https://github.com/anyup/uView-Pro/commit/fd4ea3987928039e9dcc3a53dc8bea42fce2b685))
35
+ - **u-input,u-field:** 修复输入框绑定值为undefined和null时的显示异常问题 ([4af659f](https://github.com/anyup/uView-Pro/commit/4af659fb365179e3ed7db26a1d8571c327497f7a))
36
+
37
+ ### ✨ Features | 新功能
38
+
39
+ - **demo-page:** 所有页面支持小程序分享功能 ([a055904](https://github.com/anyup/uView-Pro/commit/a05590443e898ed076d47a04cb8bfc74c2e73da8))
40
+ - **u-card:** u-card添加圆角配置功能并调整默认边框样式 ([e43c939](https://github.com/anyup/uView-Pro/commit/e43c9396a17fe485305e63895a7ea8d6edf1906b))
41
+
42
+ ### 👥 Contributors
43
+
44
+ <a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
45
+
46
+ ## 0.5.6(2026-02-04)
47
+
48
+ ### 🐛 Bug Fixes | Bug 修复
49
+
50
+ - **vue-tsc:** 修复部分定时器ts类型定义错误问题(#124) ([dada764](https://github.com/anyup/uView-Pro/commit/dada764eaa6ea73402e8fa6d96a783ae2a68715a))
51
+
52
+ ### 👥 Contributors
53
+
54
+ <a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
55
+
11
56
  ## 0.5.5(2026-02-02)
12
57
 
13
58
  ### ✨ Features | 新功能
@@ -9,6 +9,8 @@ import { baseProps } from '../common/props';
9
9
 
10
10
  export const ButtonProps = {
11
11
  ...baseProps,
12
+ /** 按钮文本 */
13
+ text: { type: String, default: '' },
12
14
  /** 是否细边框 */
13
15
  hairLine: { type: Boolean, default: true },
14
16
  /** 按钮的预置样式,default,primary,error,warning,success */
@@ -48,7 +48,7 @@
48
48
  :hover-class="getHoverClass"
49
49
  :loading="loading"
50
50
  >
51
- <slot></slot>
51
+ <slot>{{ props.text }}</slot>
52
52
  <view
53
53
  v-if="ripple"
54
54
  class="u-wave-ripple"
@@ -25,7 +25,7 @@ export const CardProps = {
25
25
  /** 副标题字体大小,单位rpx */
26
26
  subTitleSize: { type: [Number, String], default: '26' },
27
27
  /** 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时) */
28
- border: { type: Boolean, default: true },
28
+ border: { type: Boolean, default: false },
29
29
  /** 用于标识点击了第几个 */
30
30
  index: { type: [String, Number, Object] as PropType<CardIndex>, default: '' },
31
31
  /** 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx" */
@@ -36,7 +36,7 @@
36
36
  v-if="props.type === 'textarea'"
37
37
  class="u-flex-1 u-textarea-class"
38
38
  :style="$u.toStyle(props.fieldStyle)"
39
- :value="String(props.modelValue)"
39
+ :value="inputValue"
40
40
  :placeholder="String(props.placeholder)"
41
41
  :placeholderStyle="props.placeholderStyle"
42
42
  :disabled="props.disabled"
@@ -56,7 +56,7 @@
56
56
  class="u-flex-1 u-field__input-wrap"
57
57
  :style="$u.toStyle(props.fieldStyle)"
58
58
  :type="(props.type as any)"
59
- :value="String(props.modelValue)"
59
+ :value="inputValue"
60
60
  :password="props.password || props.type === 'password'"
61
61
  :placeholder="String(props.placeholder)"
62
62
  :placeholderStyle="props.placeholderStyle"
@@ -74,7 +74,7 @@
74
74
  <view v-if="props.disabled" class="u-field-disabled-overlay" @tap="fieldClick"></view>
75
75
  </view>
76
76
  <u-icon
77
- v-if="props.clearable && props.modelValue != '' && focused && !props.disabled"
77
+ v-if="props.clearable && inputValue !== '' && focused && !props.disabled"
78
78
  :size="props.clearSize"
79
79
  name="close-circle-fill"
80
80
  color="var(--u-light-color)"
@@ -160,44 +160,16 @@ import { $u } from '../..';
160
160
  * @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field>
161
161
  */
162
162
 
163
- const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'confirm', 'right-icon-click', 'click']);
163
+ const emit = defineEmits(['update:modelValue', 'input', 'focus', 'blur', 'confirm', 'right-icon-click', 'click']);
164
164
 
165
- /**
166
- * field 输入框
167
- * @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。
168
- * @property {string} icon label左边的图标,限uView的图标名称
169
- * @property {string} rightIcon 输入框右边的图标名称,限uView的图标名称(默认false)
170
- * @property {boolean} required 是否必填,左边显示红色"*"号(默认false)
171
- * @property {string} label 输入框左边的文字提示
172
- * @property {boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false)
173
- * @property {boolean} clearable 是否显示右侧清空内容的图标控件(默认true)
174
- * @property {number|string} labelWidth label的宽度,单位rpx(默认130)
175
- * @property {string} labelAlign label的文字对齐方式(默认left)
176
- * @property {string} inputAlign 输入框内容对齐方式(默认left)
177
- * @property {string} iconColor 左边通过icon配置的图标的颜色(默认var(--u-content-color))
178
- * @property {boolean} autoHeight 是否自动增高输入区域,type为textarea时有效(默认true)
179
- * @property {string|boolean} errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
180
- * @property {string} placeholder 输入框的提示文字
181
- * @property {string} placeholderStyle placeholder的样式(内联样式,字符串),如"color: var(--u-divider-color)"
182
- * @property {boolean} focus 是否自动获得焦点(默认false)
183
- * @property {boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
184
- * @property {boolean} disabled 是否不可输入(默认false)
185
- * @property {number|string} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
186
- * @property {string} confirmType 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
187
- * @property {string} labelPosition label位置(默认left)
188
- * @property {Record<string, any>} fieldStyle 自定义输入框的样式,对象形式
189
- * @property {number|string} clearSize 清除图标的大小,单位rpx(默认30)
190
- * @property {Record<string, any>} iconStyle 左侧图标样式
191
- * @property {boolean} borderTop 是否显示field的上边框(默认false)
192
- * @property {boolean} borderBottom 是否显示field的下边框(默认true)
193
- * @property {boolean} trim 是否自动去除输入内容首尾空格(默认true)
194
- * @property {string|number} value 输入框绑定值
195
- * @property {string} type 输入框类型(text/textarea/password等,默认text)
196
- */
197
165
  const props = defineProps(FieldProps);
198
166
 
199
167
  const focused = ref(false);
200
- const itemIndex = ref(0);
168
+
169
+ const inputValue = computed<string>(() => {
170
+ if (props.modelValue === undefined || props.modelValue === null) return '';
171
+ return String(props.modelValue);
172
+ });
201
173
 
202
174
  const inputWrapStyle = computed(() => {
203
175
  const style: Record<string, string> = {};
@@ -253,6 +225,7 @@ function onInput(event: any) {
253
225
  // 判断是否去除空格
254
226
  if (props.trim) value = $u.trim(value);
255
227
  emit('update:modelValue', value);
228
+ emit('input', value);
256
229
  }
257
230
 
258
231
  function onFocus(event: any) {
@@ -19,7 +19,7 @@
19
19
  v-if="type == 'textarea'"
20
20
  class="u-input__input u-input__textarea"
21
21
  :style="getStyle"
22
- :value="String(defaultValue)"
22
+ :value="inputValue"
23
23
  :placeholder="placeholder"
24
24
  :placeholderStyle="placeholderStyle"
25
25
  :disabled="disabled"
@@ -43,7 +43,7 @@
43
43
  class="u-input__input"
44
44
  :type="((type == 'password' ? 'text' : type) as any)"
45
45
  :style="getStyle"
46
- :value="String(defaultValue)"
46
+ :value="inputValue"
47
47
  :password="type == 'password' && !showPassword"
48
48
  :placeholder="placeholder"
49
49
  :placeholderStyle="placeholderStyle"
@@ -65,7 +65,7 @@
65
65
  <view class="u-input__right-icon u-flex">
66
66
  <view
67
67
  class="u-input__right-icon__clear u-input__right-icon__item"
68
- v-if="clearable && modelValue != '' && !disabled"
68
+ v-if="clearable && inputValue !== '' && !disabled"
69
69
  @click.stop="onClear"
70
70
  >
71
71
  <u-icon size="32" name="close-circle-fill" color="var(--u-light-color)" />
@@ -98,7 +98,7 @@
98
98
  }"
99
99
  v-if="props.type === 'textarea' && props.count"
100
100
  >
101
- {{ String(defaultValue).length }}/{{ props.maxlength }}
101
+ {{ inputValue.length }}/{{ props.maxlength }}
102
102
  </text>
103
103
  </view>
104
104
  </template>
@@ -126,7 +126,6 @@ const emit = defineEmits(['update:modelValue', 'input', 'blur', 'focus', 'confir
126
126
 
127
127
  const { emitToParent } = useChildren('u-input', 'u-form-item');
128
128
 
129
- const defaultValue = ref(props.modelValue);
130
129
  const inputHeight = 70; // input的高度
131
130
  const textareaHeight = 100; // textarea的高度
132
131
  const validateState = ref(props.validateState); // 当前input的验证状态,用于错误时,边框是否改为红色
@@ -134,11 +133,15 @@ const focused = ref(false); // 当前是否处于获得焦点的状态
134
133
  const showPassword = ref(false); // 是否预览密码
135
134
  const lastValue = ref(''); // 用于头条小程序,判断@input中,前后的值是否发生了变化
136
135
 
136
+ const inputValue = computed<string>(() => {
137
+ if (props.modelValue === undefined || props.modelValue === null) return '';
138
+ return String(props.modelValue);
139
+ });
140
+
137
141
  // 监听 value 变化
138
142
  watch(
139
- () => props.modelValue,
143
+ () => inputValue.value,
140
144
  (nVal, oVal) => {
141
- defaultValue.value = nVal;
142
145
  // 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
143
146
  if (nVal != oVal && props.type == 'select') handleInput({ detail: { value: nVal } });
144
147
  }
@@ -179,8 +182,6 @@ function handleInput(event: any) {
179
182
  let value = event.detail.value;
180
183
  // 判断是否去除空格
181
184
  if (props.trim) value = $u.trim(value);
182
- // 当前model 赋值
183
- defaultValue.value = value;
184
185
  emit('update:modelValue', value);
185
186
  emit('input', value);
186
187
  // 过一个生命周期再发送事件给u-form-item,否则this.$emit('update:modelValue')更新了父组件的值,但是微信小程序上
@@ -208,7 +209,7 @@ function handleBlur(event: any) {
208
209
  focused.value = false;
209
210
  }, 100);
210
211
  setTimeout(() => {
211
- let value = String(defaultValue.value);
212
+ let value = inputValue.value;
212
213
  emit('blur', value);
213
214
  // 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
214
215
  // #ifdef MP-TOUTIAO
@@ -8,15 +8,34 @@
8
8
  import type { ModalProps } from './types';
9
9
 
10
10
  // 普通(页面级)modal 事件
11
- export const U_MODAL_EVENT_SHOW = 'uview-pro:u-modal:show';
12
- export const U_MODAL_EVENT_HIDE = 'uview-pro:u-modal:hide';
13
- export const U_MODAL_EVENT_CLEAR_LOADING = 'uview-pro:u-modal:clear-loading';
11
+ export const U_MODAL_EVENT_SHOW = 'uview-pro:u-modal:show:';
12
+ export const U_MODAL_EVENT_HIDE = 'uview-pro:u-modal:hide:';
13
+ export const U_MODAL_EVENT_CLEAR_LOADING = 'uview-pro:u-modal:clear-loading:';
14
14
 
15
15
  // 全局(App 根部)modal 事件,供 useModal() 使用
16
16
  export const U_MODAL_GLOBAL_EVENT_SHOW = 'uview-pro:u-modal:global:show';
17
17
  export const U_MODAL_GLOBAL_EVENT_HIDE = 'uview-pro:u-modal:global:hide';
18
18
  export const U_MODAL_GLOBAL_EVENT_CLEAR_LOADING = 'uview-pro:u-modal:global:clear-loading';
19
19
 
20
+ // 根据当前页面获取事件名
21
+ export function getEventWithCurrentPage(event: string, page?: string | boolean) {
22
+ if (page && typeof page === 'string' && page !== '') {
23
+ return event + page;
24
+ }
25
+ return event + getCurrentPage();
26
+ }
27
+
28
+ // 获取当前页面路由
29
+ function getCurrentPage() {
30
+ try {
31
+ const pages = getCurrentPages();
32
+ const currentPage = pages[pages.length - 1].route as string;
33
+ return currentPage || '';
34
+ } catch (error) {
35
+ return '';
36
+ }
37
+ }
38
+
20
39
  /**
21
40
  * u-modal 函数式调用载荷类型
22
41
  * @description 完整覆盖 u-modal 的所有 props(除 modelValue 外)
@@ -122,7 +122,7 @@ export const ModalProps = {
122
122
  },
123
123
  /** 是否作为页面级 modal(通常放在页面中,给 useModal({ page: true }) 使用) */
124
124
  page: {
125
- type: Boolean,
125
+ type: [Boolean, String] as PropType<boolean | string>,
126
126
  default: false
127
127
  }
128
128
  };
@@ -26,7 +26,7 @@
26
26
  <slot />
27
27
  </view>
28
28
  <view v-else class="u-model__content__message" :style="$u.toStyle(effectiveConfig.contentStyle)">
29
- {{ effectiveConfig.content }}
29
+ <text>{{ effectiveConfig.content }}</text>
30
30
  </view>
31
31
  </view>
32
32
  <view
@@ -89,6 +89,7 @@ import {
89
89
  U_MODAL_GLOBAL_EVENT_CLEAR_LOADING,
90
90
  U_MODAL_GLOBAL_EVENT_HIDE,
91
91
  U_MODAL_GLOBAL_EVENT_SHOW,
92
+ getEventWithCurrentPage,
92
93
  type ModalPayload
93
94
  } from './service';
94
95
 
@@ -128,12 +129,28 @@ const slots = useSlots();
128
129
 
129
130
  // 确认按钮是否正在加载中
130
131
  const loading = ref(false);
131
- const isGlobal = computed(() => props.global);
132
- const isPage = computed(() => props.page);
133
- const showEvent = computed(() => (isGlobal.value ? U_MODAL_GLOBAL_EVENT_SHOW : isPage.value ? U_MODAL_EVENT_SHOW : ''));
134
- const hideEvent = computed(() => (isGlobal.value ? U_MODAL_GLOBAL_EVENT_HIDE : isPage.value ? U_MODAL_EVENT_HIDE : ''));
132
+ const isGlobal = computed(() => !!props.global);
133
+ const isPage = computed(() => !!props.page);
134
+ const showEvent = computed(() =>
135
+ isGlobal.value
136
+ ? U_MODAL_GLOBAL_EVENT_SHOW
137
+ : isPage.value
138
+ ? getEventWithCurrentPage(U_MODAL_EVENT_SHOW, props.page)
139
+ : ''
140
+ );
141
+ const hideEvent = computed(() =>
142
+ isGlobal.value
143
+ ? U_MODAL_GLOBAL_EVENT_HIDE
144
+ : isPage.value
145
+ ? getEventWithCurrentPage(U_MODAL_EVENT_HIDE, props.page)
146
+ : ''
147
+ );
135
148
  const clearLoadingEvent = computed(() =>
136
- isGlobal.value ? U_MODAL_GLOBAL_EVENT_CLEAR_LOADING : isPage.value ? U_MODAL_EVENT_CLEAR_LOADING : ''
149
+ isGlobal.value
150
+ ? U_MODAL_GLOBAL_EVENT_CLEAR_LOADING
151
+ : isPage.value
152
+ ? getEventWithCurrentPage(U_MODAL_EVENT_CLEAR_LOADING, props.page)
153
+ : ''
137
154
  );
138
155
 
139
156
  // 存储用户传入的回调函数
@@ -312,7 +329,12 @@ function resetTempConfig() {
312
329
  userOnCancel = null;
313
330
  }
314
331
 
315
- onMounted(() => {
332
+ // 开始监听事件
333
+ function startListeners() {
334
+ // 如果为全局 toast,则先移除所有事件监听,再重新监听
335
+ if (isGlobal.value) {
336
+ removeAllListeners();
337
+ }
316
338
  if (showEvent.value) {
317
339
  uni?.$on && uni.$on(showEvent.value, onServiceShow);
318
340
  }
@@ -322,9 +344,10 @@ onMounted(() => {
322
344
  if (clearLoadingEvent.value) {
323
345
  uni?.$on && uni.$on(clearLoadingEvent.value, clearLoading);
324
346
  }
325
- });
347
+ }
326
348
 
327
- onBeforeUnmount(() => {
349
+ // 停止监听事件
350
+ function stopListeners() {
328
351
  if (showEvent.value) {
329
352
  uni?.$off && uni.$off(showEvent.value, onServiceShow);
330
353
  }
@@ -334,6 +357,27 @@ onBeforeUnmount(() => {
334
357
  if (clearLoadingEvent.value) {
335
358
  uni?.$off && uni.$off(clearLoadingEvent.value, clearLoading);
336
359
  }
360
+ }
361
+
362
+ // 移除所有事件监听
363
+ function removeAllListeners() {
364
+ if (showEvent.value) {
365
+ uni?.$off && uni.$off(showEvent.value);
366
+ }
367
+ if (hideEvent.value) {
368
+ uni?.$off && uni.$off(hideEvent.value);
369
+ }
370
+ if (clearLoadingEvent.value) {
371
+ uni?.$off && uni.$off(clearLoadingEvent.value);
372
+ }
373
+ }
374
+
375
+ onMounted(() => {
376
+ startListeners();
377
+ });
378
+
379
+ onBeforeUnmount(() => {
380
+ stopListeners();
337
381
  });
338
382
 
339
383
  defineExpose({
@@ -2,7 +2,13 @@
2
2
  <view class="u-tabs" :style="$u.toStyle({ background: bgColor }, customStyle)" :class="customClass">
3
3
  <!-- $u.getRect()对组件根节点无效,因为写了.in(this),故这里获取内层接点尺寸 -->
4
4
  <view>
5
- <scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation>
5
+ <scroll-view
6
+ scroll-x
7
+ class="u-scroll-view"
8
+ :scroll-left="scrollLeft"
9
+ :show-scrollbar="false"
10
+ scroll-with-animation
11
+ >
6
12
  <view class="u-scroll-box" :id="id" :class="{ 'u-tabs-scroll-flex': !isScroll }">
7
13
  <view
8
14
  class="u-tab-item u-line-1"
@@ -246,9 +252,7 @@ scroll-view {
246
252
  box-sizing: border-box;
247
253
  }
248
254
 
249
- /* #ifndef APP-NVUE */
250
- ::-webkit-scrollbar,
251
- ::-webkit-scrollbar,
255
+ // 隐藏滚动条样式,支持App、H5、小程序等平台
252
256
  ::-webkit-scrollbar {
253
257
  display: none;
254
258
  width: 0 !important;
@@ -256,7 +260,6 @@ scroll-view {
256
260
  -webkit-appearance: none;
257
261
  background: transparent;
258
262
  }
259
- /* #endif */
260
263
 
261
264
  .u-scroll-box {
262
265
  position: relative;
@@ -276,6 +279,13 @@ scroll-view ::v-deep ::-webkit-scrollbar {
276
279
  }
277
280
  /* #endif */
278
281
 
282
+ // App-nvue 平台使用特殊的滚动条隐藏方式
283
+ /* #ifdef APP-NVUE */
284
+ .scroll-view-nvue {
285
+ -webkit-scrollbar: none;
286
+ }
287
+ /* #endif */
288
+
279
289
  .u-scroll-view {
280
290
  width: 100%;
281
291
  white-space: nowrap;
@@ -4,6 +4,7 @@
4
4
  scroll-x
5
5
  class="u-scroll-view"
6
6
  :scroll-left="scrollLeft"
7
+ :show-scrollbar="false"
7
8
  scroll-with-animation
8
9
  :style="{ zIndex: Number(zIndex) + 1 }"
9
10
  >
@@ -315,9 +316,6 @@ scroll-view {
315
316
  transition-property: background-color, color;
316
317
  }
317
318
 
318
- /* #ifndef APP-NVUE */
319
- ::-webkit-scrollbar,
320
- ::-webkit-scrollbar,
321
319
  ::-webkit-scrollbar {
322
320
  display: none;
323
321
  width: 0 !important;
@@ -325,7 +323,6 @@ scroll-view {
325
323
  -webkit-appearance: none;
326
324
  background: transparent;
327
325
  }
328
- /* #endif */
329
326
 
330
327
  /* #ifdef H5 */
331
328
  // 通过样式穿透,隐藏H5下,scroll-view下的滚动条
@@ -8,13 +8,32 @@
8
8
  import type { ToastProps } from './types';
9
9
 
10
10
  // 普通(页面级)toast 事件
11
- export const U_TOAST_EVENT_SHOW = 'uview-pro:u-toast:show';
12
- export const U_TOAST_EVENT_HIDE = 'uview-pro:u-toast:hide';
11
+ export const U_TOAST_EVENT_SHOW = 'uview-pro:u-toast:show:';
12
+ export const U_TOAST_EVENT_HIDE = 'uview-pro:u-toast:hide:';
13
13
 
14
14
  // 全局(App 根部)toast 事件,供 useToast() 使用
15
15
  export const U_TOAST_GLOBAL_EVENT_SHOW = 'uview-pro:u-toast:global:show';
16
16
  export const U_TOAST_GLOBAL_EVENT_HIDE = 'uview-pro:u-toast:global:hide';
17
17
 
18
+ // 根据当前页面获取事件名
19
+ export function getEventWithCurrentPage(event: string, page?: string | boolean) {
20
+ if (page && typeof page === 'string' && page !== '') {
21
+ return event + page;
22
+ }
23
+ return event + getCurrentPage();
24
+ }
25
+
26
+ // 获取当前页面路由
27
+ function getCurrentPage() {
28
+ try {
29
+ const pages = getCurrentPages();
30
+ const currentPage = pages[pages.length - 1].route as string;
31
+ return currentPage || '';
32
+ } catch (error) {
33
+ return '';
34
+ }
35
+ }
36
+
18
37
  export type ToastPayload = Partial<ToastProps> & {
19
38
  /** 文案(兼容 toast.show('xxx')) */
20
39
  title?: string;
@@ -32,7 +32,7 @@ export const ToastProps = {
32
32
  /** 是否作为全局根部 toast(通常放在 App.vue 中,给 useToast() 使用) */
33
33
  global: { type: Boolean, default: false },
34
34
  /** 是否作为页面级 toast(通常放在页面中,给 useToast({ page: true }) 使用) */
35
- page: { type: Boolean, default: false },
35
+ page: { type: [Boolean, String] as PropType<boolean | string>, default: false },
36
36
  /** 是否为loading “常驻” */
37
37
  loading: { type: Boolean, default: false }
38
38
  };
@@ -56,6 +56,7 @@ import {
56
56
  U_TOAST_EVENT_SHOW,
57
57
  U_TOAST_GLOBAL_EVENT_HIDE,
58
58
  U_TOAST_GLOBAL_EVENT_SHOW,
59
+ getEventWithCurrentPage,
59
60
  type ToastPayload
60
61
  } from './service';
61
62
 
@@ -197,31 +198,68 @@ function onServiceHide() {
197
198
  }
198
199
 
199
200
  // 是否为 App 根部的“全局 toast”
200
- const isGlobal = computed(() => props.global);
201
+ const isGlobal = computed(() => !!props.global);
201
202
  // 是否为页面级 toast
202
- const isPage = computed(() => props.page);
203
+ const isPage = computed(() => !!props.page);
203
204
 
204
205
  // 显示事件
205
- const showEvent = computed(() => (isGlobal.value ? U_TOAST_GLOBAL_EVENT_SHOW : isPage.value ? U_TOAST_EVENT_SHOW : ''));
206
+ const showEvent = computed(() =>
207
+ isGlobal.value
208
+ ? U_TOAST_GLOBAL_EVENT_SHOW
209
+ : isPage.value
210
+ ? getEventWithCurrentPage(U_TOAST_EVENT_SHOW, props.page)
211
+ : ''
212
+ );
213
+
206
214
  // 隐藏事件
207
- const hideEvent = computed(() => (isGlobal.value ? U_TOAST_GLOBAL_EVENT_HIDE : isPage.value ? U_TOAST_EVENT_HIDE : ''));
215
+ const hideEvent = computed(() =>
216
+ isGlobal.value
217
+ ? U_TOAST_GLOBAL_EVENT_HIDE
218
+ : isPage.value
219
+ ? getEventWithCurrentPage(U_TOAST_EVENT_HIDE, props.page)
220
+ : ''
221
+ );
208
222
 
209
- onMounted(() => {
223
+ // 开始监听事件
224
+ function startListeners() {
225
+ // 如果为全局 toast,则先移除所有事件监听,再重新监听
226
+ if (isGlobal.value) {
227
+ removeAllListeners();
228
+ }
210
229
  if (showEvent.value) {
211
230
  uni?.$on && uni.$on(showEvent.value, onServiceShow);
212
231
  }
213
232
  if (hideEvent.value) {
214
233
  uni?.$on && uni.$on(hideEvent.value, onServiceHide);
215
234
  }
216
- });
235
+ }
217
236
 
218
- onBeforeUnmount(() => {
237
+ // 停止监听事件
238
+ function stopListeners() {
219
239
  if (showEvent.value) {
220
240
  uni?.$off && uni.$off(showEvent.value, onServiceShow);
221
241
  }
222
242
  if (hideEvent.value) {
223
243
  uni?.$off && uni.$off(hideEvent.value, onServiceHide);
224
244
  }
245
+ }
246
+
247
+ // 移除所有事件监听
248
+ function removeAllListeners() {
249
+ if (showEvent.value) {
250
+ uni?.$off && uni.$off(showEvent.value);
251
+ }
252
+ if (hideEvent.value) {
253
+ uni?.$off && uni.$off(hideEvent.value);
254
+ }
255
+ }
256
+
257
+ onMounted(() => {
258
+ startListeners();
259
+ });
260
+
261
+ onBeforeUnmount(() => {
262
+ stopListeners();
225
263
  });
226
264
 
227
265
  defineExpose<ToastExpose>({
@@ -365,6 +365,8 @@ function deleteItem(index: number) {
365
365
  uni.showModal({
366
366
  title: t('uUpload.modalTitle'),
367
367
  content: t('uUpload.deleteConfirm'),
368
+ cancelText: t('uUpload.modalCancelText'),
369
+ confirmText: t('uUpload.modalConfirmText'),
368
370
  success: async (res: any) => {
369
371
  if (res.confirm) {
370
372
  // 先检查是否有定义before-remove移除前钩子
@@ -5,6 +5,7 @@ import {
5
5
  U_MODAL_GLOBAL_EVENT_SHOW,
6
6
  U_MODAL_GLOBAL_EVENT_HIDE,
7
7
  U_MODAL_GLOBAL_EVENT_CLEAR_LOADING,
8
+ getEventWithCurrentPage,
8
9
  type ModalPayload
9
10
  } from '../../components/u-modal/service';
10
11
 
@@ -32,8 +33,8 @@ export type UseModal = {
32
33
  export type UseModalOptions = {
33
34
  /** 是否使用全局根部 <u-modal global /> */
34
35
  global?: boolean;
35
- /** 是否使用页面级 <u-modal page /> */
36
- page?: boolean;
36
+ /** 是否使用页面级 <u-modal page /> 或某个页面的 <u-modal page="pageId" /> */
37
+ page?: boolean | string;
37
38
  };
38
39
 
39
40
  function normalize(contentOrOptions: string | UseModalShowOptions): UseModalShowOptions {
@@ -44,18 +45,43 @@ function normalize(contentOrOptions: string | UseModalShowOptions): UseModalShow
44
45
  return contentOrOptions || {};
45
46
  }
46
47
 
48
+ function getPage(optionsOrGlobal: UseModalOptions | boolean): string {
49
+ if (typeof optionsOrGlobal === 'boolean') {
50
+ return '';
51
+ }
52
+ if (optionsOrGlobal.page && typeof optionsOrGlobal.page === 'string' && optionsOrGlobal.page !== '') {
53
+ return optionsOrGlobal.page;
54
+ }
55
+ return '';
56
+ }
57
+
47
58
  /**
48
59
  * Modal 函数式调用
49
- * @description 需要页面/应用中至少存在一个 <u-modal global /> 或 <u-modal page /> 实例用于承接事件;不影响原调用方式。
50
- * 支持两种调用方式:应用级 useModal() / useModal({ global: true }) 页面级 useModal({ page: true }) / useModal(false)
60
+ * @description 需要页面/应用中至少存在一个 <u-modal global /> 或 <u-modal page /> 实例用于承接事件;不影响原 ref 调用方式。
61
+ *
62
+ * 支持两种调用方式:
63
+ * - 应用级 useModal() / useModal(true) / useModal({ global: true })
64
+ * - 页面级 useModal(false) / useModal({ page: true }) / useModal({ page: 'pageId' }) (pageId 应和页面中 <u-modal page="pageId" /> 的 page 属性一致)
51
65
  */
52
66
  export function useModal(optionsOrGlobal: UseModalOptions | boolean = true): UseModal {
53
- const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal !== false : optionsOrGlobal.global === true;
54
- const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : optionsOrGlobal.page === true;
67
+ const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === true : !!optionsOrGlobal.global;
68
+ const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : !!optionsOrGlobal.page;
55
69
 
56
- const showEvent = isGlobal ? U_MODAL_GLOBAL_EVENT_SHOW : isPage ? U_MODAL_EVENT_SHOW : '';
57
- const hideEvent = isGlobal ? U_MODAL_GLOBAL_EVENT_HIDE : isPage ? U_MODAL_EVENT_HIDE : '';
58
- const clearLoadingEvent = isGlobal ? U_MODAL_GLOBAL_EVENT_CLEAR_LOADING : isPage ? U_MODAL_EVENT_CLEAR_LOADING : '';
70
+ const showEvent = isGlobal
71
+ ? U_MODAL_GLOBAL_EVENT_SHOW
72
+ : isPage
73
+ ? getEventWithCurrentPage(U_MODAL_EVENT_SHOW, getPage(optionsOrGlobal))
74
+ : '';
75
+ const hideEvent = isGlobal
76
+ ? U_MODAL_GLOBAL_EVENT_HIDE
77
+ : isPage
78
+ ? getEventWithCurrentPage(U_MODAL_EVENT_HIDE, getPage(optionsOrGlobal))
79
+ : '';
80
+ const clearLoadingEvent = isGlobal
81
+ ? U_MODAL_GLOBAL_EVENT_CLEAR_LOADING
82
+ : isPage
83
+ ? getEventWithCurrentPage(U_MODAL_EVENT_CLEAR_LOADING, getPage(optionsOrGlobal))
84
+ : '';
59
85
 
60
86
  function emitShow(payload: UseModalShowOptions) {
61
87
  if (showEvent) {
@@ -0,0 +1,173 @@
1
+ /**
2
+ * 路由跳转 hooks,基于 route.ts 的 Router 类实现
3
+ * 提供 Vue Composition API 风格的路由跳转方法
4
+ */
5
+
6
+ import { ref } from 'vue';
7
+
8
+ interface RouterConfig {
9
+ type?: string;
10
+ url?: string;
11
+ delta?: number;
12
+ params?: Record<string, any>;
13
+ animationType?: string;
14
+ animationDuration?: number;
15
+ intercept?: boolean;
16
+ }
17
+
18
+ declare const uni: any;
19
+
20
+ const config: RouterConfig = {
21
+ type: 'navigateTo',
22
+ url: '',
23
+ delta: 1,
24
+ params: {},
25
+ animationType: 'pop-in',
26
+ animationDuration: 300,
27
+ intercept: false
28
+ };
29
+
30
+ // 响应式配置,支持动态修改
31
+ const configRef = ref({ ...config });
32
+
33
+ /**
34
+ * 判断 url 前面是否有 "/",如果没有则加上
35
+ */
36
+ const addRootPath = (url: string): string => {
37
+ return url[0] === '/' ? url : `/${url}`;
38
+ };
39
+
40
+ /**
41
+ * 整合路由参数
42
+ */
43
+ const mixinParam = (url: string, params: Record<string, any>): string => {
44
+ url = url && addRootPath(url);
45
+ let query = '';
46
+ if (/.*\/.*\?.*=.*/.test(url)) {
47
+ query = uni.$u.queryParams(params, false);
48
+ return url + '&' + query;
49
+ } else {
50
+ query = uni.$u.queryParams(params);
51
+ return url + query;
52
+ }
53
+ };
54
+
55
+ /**
56
+ * 执行路由跳转
57
+ */
58
+ const openPage = (cfg: RouterConfig): void => {
59
+ const { url = '', type = '', delta = 1, animationDuration = 300 } = cfg;
60
+ if (type === 'navigateTo' || type === 'to') {
61
+ uni.navigateTo({ url, animationDuration });
62
+ }
63
+ if (type === 'redirectTo' || type === 'redirect') {
64
+ uni.redirectTo({ url });
65
+ }
66
+ if (type === 'switchTab' || type === 'tab') {
67
+ uni.switchTab({ url });
68
+ }
69
+ if (type === 'reLaunch' || type === 'launch') {
70
+ uni.reLaunch({ url });
71
+ }
72
+ if (type === 'navigateBack' || type === 'back') {
73
+ uni.navigateBack({ delta });
74
+ }
75
+ };
76
+
77
+ /**
78
+ * 路由跳转主方法
79
+ */
80
+ const route = async (options: string | RouterConfig = {}, params: Record<string, any> = {}): Promise<void> => {
81
+ let mergeConfig: RouterConfig = {};
82
+
83
+ if (typeof options === 'string') {
84
+ mergeConfig.url = mixinParam(options, params);
85
+ mergeConfig.type = 'navigateTo';
86
+ } else {
87
+ mergeConfig = uni.$u.deepMerge(configRef.value, options);
88
+ mergeConfig.url = mixinParam(options.url || '', options.params || {});
89
+ }
90
+
91
+ if (params.intercept !== undefined) {
92
+ configRef.value.intercept = params.intercept;
93
+ }
94
+
95
+ mergeConfig.params = params;
96
+ mergeConfig = uni.$u.deepMerge(configRef.value, mergeConfig);
97
+
98
+ if (uni.$u.routeIntercept && typeof uni.$u.routeIntercept === 'function') {
99
+ const isNext = await new Promise<boolean>(resolve => {
100
+ uni.$u.routeIntercept(mergeConfig, resolve);
101
+ });
102
+ isNext && openPage(mergeConfig);
103
+ } else {
104
+ openPage(mergeConfig);
105
+ }
106
+ };
107
+
108
+ /**
109
+ * 跳转到指定页面
110
+ */
111
+ const to = (url: string, params?: Record<string, any>): Promise<void> => {
112
+ return route({ url, type: 'navigateTo' }, params || {});
113
+ };
114
+
115
+ /**
116
+ * 关闭当前页面,跳转到指定页面
117
+ */
118
+ const redirect = (url: string, params?: Record<string, any>): Promise<void> => {
119
+ return route({ url, type: 'redirectTo' }, params || {});
120
+ };
121
+
122
+ /**
123
+ * 跳转到 tabBar 页面
124
+ */
125
+ const tab = (url: string, params?: Record<string, any>): Promise<void> => {
126
+ return route({ url, type: 'switchTab' }, params || {});
127
+ };
128
+
129
+ /**
130
+ * 关闭所有页面,跳转到指定页面
131
+ */
132
+ const reLaunch = (url: string, params?: Record<string, any>): Promise<void> => {
133
+ return route({ url, type: 'reLaunch' }, params || {});
134
+ };
135
+
136
+ /**
137
+ * 返回上一页
138
+ */
139
+ const back = (delta: number = 1): Promise<void> => {
140
+ return route({ type: 'navigateBack', delta });
141
+ };
142
+
143
+ /**
144
+ * 设置默认路由配置
145
+ */
146
+ const setConfig = (newConfig: Partial<RouterConfig>): void => {
147
+ configRef.value = uni.$u.deepMerge(configRef.value, newConfig);
148
+ };
149
+
150
+ /**
151
+ * 获取当前路由配置
152
+ */
153
+ const getConfig = (): RouterConfig => {
154
+ return { ...configRef.value };
155
+ };
156
+
157
+ export function useRouter() {
158
+ return {
159
+ // 核心跳转方法
160
+ route,
161
+ // 便捷方法
162
+ to,
163
+ redirect,
164
+ tab,
165
+ reLaunch,
166
+ back,
167
+ // 配置方法
168
+ setConfig,
169
+ getConfig
170
+ };
171
+ }
172
+
173
+ export default useRouter;
@@ -3,6 +3,7 @@ import {
3
3
  U_TOAST_EVENT_SHOW,
4
4
  U_TOAST_GLOBAL_EVENT_HIDE,
5
5
  U_TOAST_GLOBAL_EVENT_SHOW,
6
+ getEventWithCurrentPage,
6
7
  type ToastPayload
7
8
  } from '../../components/u-toast/service';
8
9
  import type { ThemeType } from '../../types/global';
@@ -33,8 +34,8 @@ export type UseToast = {
33
34
  export type UseToastOptions = {
34
35
  /** 是否使用全局根部 <u-toast global />,默认 true;为 false 时走页面级 <u-toast /> */
35
36
  global?: boolean;
36
- /** 是否使用页面级 <u-toast page /> */
37
- page?: boolean;
37
+ /** 是否使用页面级 <u-toast page /> 或某个页面的 <u-toast page="pageId" /> */
38
+ page?: boolean | string;
38
39
  };
39
40
 
40
41
  function normalize(titleOrOptions: string | UseToastShowOptions): UseToastShowOptions {
@@ -42,16 +43,37 @@ function normalize(titleOrOptions: string | UseToastShowOptions): UseToastShowOp
42
43
  return titleOrOptions || {};
43
44
  }
44
45
 
46
+ function getPage(optionsOrGlobal: UseToastOptions | boolean): string {
47
+ if (typeof optionsOrGlobal === 'boolean') {
48
+ return '';
49
+ }
50
+ if (optionsOrGlobal.page && typeof optionsOrGlobal.page === 'string' && optionsOrGlobal.page !== '') {
51
+ return optionsOrGlobal.page;
52
+ }
53
+ return '';
54
+ }
55
+
45
56
  /**
46
57
  * Toast 函数式调用
47
58
  * @description 需要页面/应用中至少存在一个 <u-toast global /> 或 <u-toast page /> 实例用于承接事件;不影响原 ref 调用方式。
48
- * 支持两种调用方式:应用级 useToast() / useToast({ global: true }) 页面级 useToast({ page: true }) / useToast(false)
59
+ *
60
+ * 支持两种调用方式:
61
+ * - 应用级 useToast() / useToast(true) / useToast({ global: true })
62
+ * - 页面级 useToast(false) / useToast({ page: true }) / useToast({ page: 'pageId' }) (pageId 应和页面中 <u-toast page="pageId" /> 的 page 属性一致)
49
63
  */
50
64
  export function useToast(optionsOrGlobal: UseToastOptions | boolean = true): UseToast {
51
- const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal !== false : optionsOrGlobal.global === true;
52
- const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : optionsOrGlobal.page === true;
53
- const showEvent = isGlobal ? U_TOAST_GLOBAL_EVENT_SHOW : isPage ? U_TOAST_EVENT_SHOW : '';
54
- const hideEvent = isGlobal ? U_TOAST_GLOBAL_EVENT_HIDE : isPage ? U_TOAST_EVENT_HIDE : '';
65
+ const isGlobal = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === true : !!optionsOrGlobal.global;
66
+ const isPage = typeof optionsOrGlobal === 'boolean' ? optionsOrGlobal === false : !!optionsOrGlobal.page;
67
+ const showEvent = isGlobal
68
+ ? U_TOAST_GLOBAL_EVENT_SHOW
69
+ : isPage
70
+ ? getEventWithCurrentPage(U_TOAST_EVENT_SHOW, getPage(optionsOrGlobal))
71
+ : '';
72
+ const hideEvent = isGlobal
73
+ ? U_TOAST_GLOBAL_EVENT_HIDE
74
+ : isPage
75
+ ? getEventWithCurrentPage(U_TOAST_EVENT_HIDE, getPage(optionsOrGlobal))
76
+ : '';
55
77
 
56
78
  function emitShow(payload: UseToastShowOptions) {
57
79
  if (showEvent) {
@@ -13,6 +13,8 @@ export default {
13
13
  reUpload: 'Re-upload',
14
14
  uploadFailed: 'Upload failed, please try again',
15
15
  modalTitle: 'Notice',
16
+ modalCancelText: 'Cancel',
17
+ modalConfirmText: 'Confirm',
16
18
  deleteConfirm: 'Are you sure you want to delete this item?',
17
19
  terminatedRemove: 'Removal cancelled',
18
20
  removeSuccess: 'Removed successfully',
@@ -13,6 +13,8 @@ export default {
13
13
  reUpload: '重新上传',
14
14
  uploadFailed: '上传失败,请重试',
15
15
  modalTitle: '提示',
16
+ modalCancelText: '取消',
17
+ modalConfirmText: '确定',
16
18
  deleteConfirm: '您确定要删除此项吗?',
17
19
  terminatedRemove: '已终止移除',
18
20
  removeSuccess: '移除成功',
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-pro",
3
3
  "name": "uview-pro",
4
4
  "displayName": "【支持鸿蒙】uView Pro|基于Vue3+TS的高质量UI组件库,支持多主题、暗黑模式、多语言",
5
- "version": "0.5.6",
5
+ "version": "0.5.8",
6
6
  "description": "uView Pro是基于Vue3+TS的多平台UI框架,提供80+高质量组件、便捷工具和常用模板,支持多主题、暗黑模式、多语言,支持H5/APP/鸿蒙/小程序多端开发。已在鸿蒙应用商店上架,欢迎体验!",
7
7
  "main": "index.ts",
8
8
  "module": "index.ts",
package/readme.md CHANGED
@@ -9,6 +9,7 @@
9
9
  [![stars](https://img.shields.io/github/stars/anyup/uView-Pro?style=flat-square&logo=GitHub)](https://github.com/anyup/uView-Pro)
10
10
  [![forks](https://img.shields.io/github/forks/anyup/uView-Pro?style=flat-square&logo=GitHub)](https://github.com/anyup/uView-Pro)
11
11
  [![issues](https://img.shields.io/github/issues/anyup/uView-Pro?style=flat-square&logo=GitHub)](https://github.com/anyup/uView-Pro/issues)
12
+ [![downloads](https://img.shields.io/npm/dm/uview-pro?logo=npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fuview-pro&style=flat-square)](https://www.npmjs.com/package/uview-pro)
12
13
  [![npm version](https://img.shields.io/npm/v/uview-pro)](https://www.npmjs.com/package/uview-pro)
13
14
  [![Website](https://img.shields.io/badge/uView%20Pro-docs-blue?style=flat-square)](https://uviewpro.cn)
14
15
  [![node version](https://img.shields.io/badge/node-%3E%3D18-green)](https://nodejs.org/)