uview-pro 0.3.1 → 0.3.3

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,40 @@
1
+ ## 0.3.3(2025-10-16)
2
+
3
+ ### 🐛 Bug Fixes | Bug 修复
4
+
5
+ - **u-read-more:** 修复 init 方法无法在外部调用的问题 ([415d401](https://github.com/anyup/uView-Pro/commit/415d401883a3567653ab1f311b28b075b7bb5603))
6
+ - **u-button:** 修复 hover-class 属性被忽略的问题 ([b919c58](https://github.com/anyup/uView-Pro/commit/b919c58cea048f9e559a6448cebe5abbf1490acf))
7
+
8
+ ### ♻️ Code Refactoring | 代码重构
9
+
10
+ - **component-relation:** 重构组件关系逻辑并添加新功能 ([85d0cd2](https://github.com/anyup/uView-Pro/commit/85d0cd20db839a61733887f82825d47de0a1b1a6))
11
+ - **u-talbe:** 重构u-td和u-th组件,增强u-table的兼容性 ([3fbbc52](https://github.com/anyup/uView-Pro/commit/3fbbc5233bd41b91ca829f9a65cf95ee3b599e36))
12
+ - 修改 uView Pro 日志配置 ([6b9bb68](https://github.com/anyup/uView-Pro/commit/6b9bb6852af3eb24f109207f864145771c3e9c79))
13
+ - **clipboard:** add clipboard function ([efdaa58](https://github.com/anyup/uView-Pro/commit/efdaa58dda923b281d9b764a82a7492f36717ac4))
14
+
15
+ ### 👥 Contributors
16
+
17
+ <a href="https://github.com/koboshi"><img src="https://github.com/koboshi.png?size=40" width="40" height="40" alt="koboshi" title="koboshi"/></a> <a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a> <a href="https://github.com/lonelyflyer"><img src="https://github.com/lonelyflyer.png?size=40" width="40" height="40" alt="Lonelyflyer" title="Lonelyflyer"/></a>
18
+
19
+ ## 0.3.2(2025-10-15)
20
+
21
+ ### 📝 Documentation | 文档
22
+
23
+ - **changelog:** 更新 CHANGELOG.md 生成配置 ([27a2609](https://github.com/anyup/uView-Pro/commit/27a26095b92cf8cbc6477c563a68b1557f9fb045))
24
+ - 更新交流群图片链接 ([832815d](https://github.com/anyup/uView-Pro/commit/832815deb63f8b144f525591d16e3ccf900b8632))
25
+
26
+ ### ⚡ Performance Improvements | 性能优化
27
+
28
+ - **component-relation:** 增强组件间通信功能;修改 broadcast 方法,支持定向广播;移除对子组件名称的强制要求,允许匿名组件 ([28ea814](https://github.com/anyup/uView-Pro/commit/28ea814810055aa4c99b53d676b16da337fdf7d5))
29
+
30
+ ### 🐛 Bug Fixes | Bug 修复
31
+
32
+ - **u-index-list:** 重构索引锚点组件,兼容多端,修复IndexList索引列表在微信小程序没有吸顶效果 ([ce6a7a3](https://github.com/anyup/uView-Pro/commit/ce6a7a3c01b622cad268779098390c0d593f75bc))
33
+
34
+ ### 👥 Contributors
35
+
36
+ <a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
37
+
1
38
  ## 0.3.1(2025-10-14)
2
39
 
3
40
  ### 📝 Documentation | 文档
@@ -151,7 +151,9 @@ const waveActive = ref(false); // 激活水波纹
151
151
  */
152
152
  const getHoverClass = computed(() => {
153
153
  // 如果开启水波纹效果,则不启用hover-class效果
154
- if (props.loading || props.disabled || props.ripple || props.hoverClass) return '';
154
+ if (props.loading || props.disabled || props.ripple) return '';
155
+ // 如果用户传了 hoverClass,优先使用用户的
156
+ if (props.hoverClass) return props.hoverClass;
155
157
  let hoverClass = '';
156
158
  hoverClass = props.plain ? 'u-' + props.type + '-plain-hover' : 'u-' + props.type + '-hover';
157
159
  return hoverClass;
@@ -31,8 +31,8 @@ export default {
31
31
 
32
32
  <script setup lang="ts">
33
33
  import { IndexAnchorProps } from './types';
34
- import { ref, computed, onMounted, getCurrentInstance } from 'vue';
35
- import { $u } from '../..';
34
+ import { ref, onMounted } from 'vue';
35
+ import { $u, useChildren } from '../..';
36
36
 
37
37
  /**
38
38
  * indexAnchor 索引列表锚点
@@ -46,28 +46,54 @@ import { $u } from '../..';
46
46
  */
47
47
 
48
48
  const props = defineProps(IndexAnchorProps);
49
+ const { parentExposed } = useChildren('u-index-anchor', 'u-index-list');
49
50
 
50
51
  // 响应式变量
51
52
  const active = ref(false);
52
53
  const wrapperStyle = ref<Record<string, any>>({});
53
54
  const anchorStyle = ref<Record<string, any>>({});
54
- let parent: any = null;
55
-
56
- const instance = getCurrentInstance();
55
+ const top = ref(0);
56
+ const height = ref(0);
57
57
 
58
58
  // 挂载时查找父组件并注册
59
59
  onMounted(() => {
60
- parent = $u.$parent('u-index-list', instance);
61
- if (parent) {
62
- parent.exposed?.children.push(instance);
63
- parent.exposed?.updateData();
60
+ if (parentExposed) {
61
+ parentExposed?.value?.updateData();
64
62
  }
65
63
  });
64
+
65
+ function setTop(val) {
66
+ top.value = val;
67
+ }
68
+
69
+ function setHeight(val) {
70
+ height.value = val;
71
+ }
72
+
73
+ function setActive(val) {
74
+ active.value = val;
75
+ }
76
+
77
+ function setAnchorStyle(val) {
78
+ anchorStyle.value = val;
79
+ }
80
+
81
+ function setWrapperStyle(val) {
82
+ wrapperStyle.value = val;
83
+ }
84
+
66
85
  defineExpose({
86
+ props,
87
+ top,
88
+ height,
67
89
  active,
68
90
  wrapperStyle,
69
91
  anchorStyle,
70
- props
92
+ setTop,
93
+ setHeight,
94
+ setActive,
95
+ setAnchorStyle,
96
+ setWrapperStyle
71
97
  });
72
98
  </script>
73
99
 
@@ -49,7 +49,7 @@ export default {
49
49
 
50
50
  <script setup lang="ts">
51
51
  import { ref, reactive, computed, watch, onMounted, getCurrentInstance } from 'vue';
52
- import { $u } from '../..';
52
+ import { $u, useParent } from '../..';
53
53
  import { IndexListProps } from './types';
54
54
 
55
55
  /**
@@ -84,8 +84,6 @@ const activeAnchorIndex = ref(0);
84
84
  const showSidebar = ref(true);
85
85
  const touchmove = ref(false);
86
86
  const touchmoveIndex = ref(0);
87
- // 孩子锚点组件
88
- const children = reactive<any[]>([]);
89
87
  const sidebar = reactive<{ height: number; top: number }>({ height: 0, top: 0 });
90
88
  const scrollToAnchorIndex = ref<number | null>(null);
91
89
  const timer = ref<any>(null);
@@ -101,8 +99,7 @@ const indexList = computed(() => props.indexList ?? getIndexList()).value;
101
99
  const zIndex = computed(() => props.zIndex).value;
102
100
  const activeColor = computed(() => props.activeColor).value;
103
101
 
104
- // 只能在created生命周期定义children,如果在data定义,会因为循环引用而报错
105
- children.length = 0;
102
+ const { children, broadcast } = useParent('u-index-anchor');
106
103
 
107
104
  // 兼容 H5/非H5 stickyOffsetTop
108
105
  onMounted(() => {
@@ -146,11 +143,9 @@ function setRect() {
146
143
  function setAnchorsRect() {
147
144
  return Promise.all(
148
145
  children.map((anchor, index) => {
149
- $u.getRect('.u-index-anchor-wrapper', anchor).then((rect: any) => {
150
- Object.assign(anchor, {
151
- height: rect.height,
152
- top: rect.top
153
- });
146
+ $u.getRect('.u-index-anchor-wrapper', anchor.getInstance()).then((rect: any) => {
147
+ broadcast('setTop', rect.top, anchor.id);
148
+ broadcast('setHeight', rect.height, anchor.id);
154
149
  });
155
150
  })
156
151
  );
@@ -182,9 +177,9 @@ function setSiderbarRect() {
182
177
  function getActiveAnchorIndex() {
183
178
  const sticky = props.sticky;
184
179
  for (let i = children.length - 1; i >= 0; i--) {
185
- const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
180
+ const preAnchorHeight = i > 0 ? children[i - 1].getExposed().height.value : 0;
186
181
  const reachTop = sticky ? preAnchorHeight : 0;
187
- if (reachTop >= children[i].top) {
182
+ if (reachTop >= children[i].getExposed().top.value) {
188
183
  return i;
189
184
  }
190
185
  }
@@ -203,7 +198,7 @@ function onScroll() {
203
198
  if (sticky) {
204
199
  let isActiveAnchorSticky = false;
205
200
  if (active !== -1) {
206
- isActiveAnchorSticky = children[active].top <= 0;
201
+ isActiveAnchorSticky = children[active].getExposed().top.value <= 0;
207
202
  }
208
203
  children.forEach((item, index) => {
209
204
  if (index === active) {
@@ -212,7 +207,7 @@ function onScroll() {
212
207
  color: `${activeColor}`
213
208
  };
214
209
  if (isActiveAnchorSticky) {
215
- wrapperStyle = { height: `${children[index].height}px` };
210
+ wrapperStyle = { height: `${children[index].getExposed().height.value}px` };
216
211
  anchorStyle = {
217
212
  position: 'fixed',
218
213
  top: `${stickyOffsetTop.value}px`,
@@ -220,31 +215,30 @@ function onScroll() {
220
215
  color: `${activeColor}`
221
216
  };
222
217
  }
223
- item.active = active;
224
- item.wrapperStyle = wrapperStyle;
225
- item.anchorStyle = anchorStyle;
218
+ broadcast('setActive', active, item.id);
219
+ broadcast('setAnchorStyle', anchorStyle, item.id);
220
+ broadcast('setWrapperStyle', wrapperStyle, item.id);
226
221
  } else if (index === active - 1) {
227
222
  const currentAnchor = children[index];
228
- const currentOffsetTop = currentAnchor.top;
229
- const targetOffsetTop = index === children.length - 1 ? top.value : children[index + 1].top;
223
+ const currentOffsetTop = currentAnchor.getExposed().top.value;
224
+ const targetOffsetTop =
225
+ index === children.length - 1 ? top.value : children[index + 1].getExposed().top.value;
230
226
  const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
231
- const translateY = parentOffsetHeight - currentAnchor.height;
227
+ const translateY = parentOffsetHeight - currentAnchor.getExposed().height.value;
232
228
  const anchorStyle = {
233
229
  position: 'relative',
234
230
  transform: `translate3d(0, ${translateY}px, 0)`,
235
231
  zIndex: `${zIndex ? zIndex : $u.zIndex.indexListSticky}`,
236
232
  color: `${activeColor}`
237
233
  };
238
- item.active = active;
239
- item.anchorStyle = anchorStyle;
234
+ broadcast('setActive', active, currentAnchor.id);
235
+ broadcast('setAnchorStyle', anchorStyle, currentAnchor.id);
236
+ broadcast('setWrapperStyle', '', item.id);
240
237
  } else {
241
- item.active = false;
242
- item.anchorStyle = '';
243
- item.wrapperStyle = '';
238
+ broadcast('setActive', false, item.id);
239
+ broadcast('setAnchorStyle', '', item.id);
240
+ broadcast('setWrapperStyle', '', item.id);
244
241
  }
245
- item.exposed.active = item.active;
246
- item.exposed.anchorStyle = item.anchorStyle;
247
- item.exposed.wrapperStyle = item.wrapperStyle;
248
242
  });
249
243
  }
250
244
  }
@@ -284,12 +278,12 @@ function scrollToAnchor(index: number) {
284
278
  return;
285
279
  }
286
280
  scrollToAnchorIndex.value = index;
287
- const anchor = children.find(item => item.props.index === indexList[index]);
281
+ const anchor = children.find(item => item.getExposed().props.index === indexList[index]);
288
282
  if (anchor) {
289
- emit('select', anchor.props.index);
283
+ emit('select', anchor.getExposed().props.index);
290
284
  uni.pageScrollTo({
291
285
  duration: 0,
292
- scrollTop: anchor.top + Number(props.scrollTop)
286
+ scrollTop: anchor.getExposed().top.value + Number(props.scrollTop)
293
287
  });
294
288
  }
295
289
  }
@@ -119,6 +119,10 @@ function init() {
119
119
  }
120
120
  });
121
121
  }
122
+ // 显式暴露init方法
123
+ defineExpose({
124
+ init
125
+ });
122
126
 
123
127
  /**
124
128
  * 展开或者收起内容
@@ -18,8 +18,8 @@ export default {
18
18
  </script>
19
19
 
20
20
  <script setup lang="ts">
21
- import { ref, onMounted, getCurrentInstance, watch } from 'vue';
22
- import { $u } from '../..';
21
+ import { computed } from 'vue';
22
+ import { $u, useChildren } from '../..';
23
23
  import { TdProps } from './types';
24
24
 
25
25
  /**
@@ -32,52 +32,19 @@ import { TdProps } from './types';
32
32
 
33
33
  const props = defineProps(TdProps);
34
34
 
35
- /**
36
- * 组合式API变量声明
37
- * 保留所有说明注释
38
- */
39
- const tdStyle = ref<Record<string, any>>({}); // 单元格样式
40
- let parent: any = null; // 父组件实例
41
-
42
- /**
43
- * 更新单元格样式
44
- */
45
- function updateStyle() {
46
- if (!parent) return;
35
+ const { parentExposed } = useChildren('u-td', 'u-table');
47
36
 
37
+ const tdStyle = computed(() => {
48
38
  const style: Record<string, any> = {};
49
39
  if (props.width && props.width !== 'auto') style.width = props.width;
50
40
  else style.flex = '1';
51
- style.textAlign = parent.props.align;
52
- style.fontSize = parent.props.fontSize + 'rpx';
53
- style.padding = parent.props.padding;
54
- style.borderBottom = `solid 1px ${parent.props.borderColor}`;
55
- style.borderRight = `solid 1px ${parent.props.borderColor}`;
56
- style.color = parent.props.color;
57
- tdStyle.value = style;
58
- }
59
-
60
- /**
61
- * 组件挂载时查找父组件u-table并合并样式
62
- */
63
- onMounted(() => {
64
- // 查找父组件u-table
65
- const instance = getCurrentInstance();
66
- if (instance) {
67
- parent = $u.$parent('u-table');
68
- if (parent) {
69
- updateStyle();
70
-
71
- // 监听父组件属性变化
72
- watch(
73
- () => parent.props,
74
- () => {
75
- updateStyle();
76
- },
77
- { deep: true }
78
- );
79
- }
80
- }
41
+ style.textAlign = parentExposed.value?.props?.align;
42
+ style.fontSize = parentExposed.value?.props?.fontSize + 'rpx';
43
+ style.padding = parentExposed.value?.props?.padding;
44
+ style.borderBottom = `solid 1px ${parentExposed.value?.props?.borderColor}`;
45
+ style.borderRight = `solid 1px ${parentExposed.value?.props?.borderColor}`;
46
+ style.color = parentExposed.value?.props?.color;
47
+ return style;
81
48
  });
82
49
  </script>
83
50
 
@@ -18,8 +18,8 @@ export default {
18
18
  </script>
19
19
 
20
20
  <script setup lang="ts">
21
- import { ref, onMounted, getCurrentInstance, watch } from 'vue';
22
- import { $u } from '../..';
21
+ import { computed } from 'vue';
22
+ import { $u, useChildren } from '../..';
23
23
  import { ThProps } from './types';
24
24
 
25
25
  /**
@@ -32,47 +32,18 @@ import { ThProps } from './types';
32
32
 
33
33
  const props = defineProps(ThProps);
34
34
 
35
- const thStyle = ref<Record<string, any>>({}); // 标题单元格样式
36
- let parent: any = null; // 父组件实例
37
-
38
- /**
39
- * 更新标题单元格样式
40
- */
41
- function updateStyle() {
42
- if (!parent) return;
35
+ const { parentExposed } = useChildren('u-th', 'u-table');
43
36
 
37
+ const thStyle = computed(() => {
44
38
  const style: Record<string, any> = {};
45
39
  if (props.width && props.width !== 'auto') style.width = props.width;
46
40
  else style.flex = '1';
47
- style.textAlign = parent.props.align;
48
- style.padding = parent.props.padding;
49
- style.borderBottom = `solid 1px ${parent.props.borderColor}`;
50
- style.borderRight = `solid 1px ${parent.props.borderColor}`;
51
- Object.assign(style, parent.props.thStyle);
52
- thStyle.value = style;
53
- }
54
-
55
- /**
56
- * 组件挂载时查找父组件u-table并合并样式
57
- */
58
- onMounted(() => {
59
- // 查找父组件u-table
60
- const instance = getCurrentInstance();
61
- if (instance) {
62
- parent = $u.$parent('u-table');
63
- if (parent) {
64
- updateStyle();
65
-
66
- // 监听父组件属性变化
67
- watch(
68
- () => parent.props,
69
- () => {
70
- updateStyle();
71
- },
72
- { deep: true }
73
- );
74
- }
75
- }
41
+ style.textAlign = parentExposed.value?.props?.align;
42
+ style.padding = parentExposed.value?.props?.padding;
43
+ style.borderBottom = `solid 1px ${parentExposed.value?.props?.borderColor}`;
44
+ style.borderRight = `solid 1px ${parentExposed.value?.props?.borderColor}`;
45
+ Object.assign(style, parentExposed.value?.props?.thStyle);
46
+ return style;
76
47
  });
77
48
  </script>
78
49
 
@@ -0,0 +1,78 @@
1
+ function H5Copy(text: string, config: TClipboardOptions) {
2
+ const success = (result: string) => {
3
+ if (config.showToast) {
4
+ uni.showToast({
5
+ title: '复制成功',
6
+ icon: 'none'
7
+ });
8
+ }
9
+ config.success(result);
10
+ config.complete(result);
11
+ };
12
+ const fail = (err: string) => {
13
+ if (config.showToast) {
14
+ uni.showToast({
15
+ title: '复制失败',
16
+ icon: 'none'
17
+ });
18
+ }
19
+ config.fail(err);
20
+ config.complete(err);
21
+ };
22
+
23
+ const textarea = document.createElement('textarea');
24
+ textarea.value = text;
25
+ textarea.readOnly = true;
26
+ textarea.style.position = 'absolute';
27
+ textarea.style.left = '-9999px';
28
+ document.body.appendChild(textarea);
29
+
30
+ textarea.select();
31
+ textarea.setSelectionRange(0, text.length);
32
+
33
+ try {
34
+ const result = document.execCommand('copy');
35
+ if (result) {
36
+ success('复制成功');
37
+ } else {
38
+ // console.error(`复制失败,可能不是用户主动触发点击的方式调用,因web安全性,不能js直接调用!`);
39
+ fail('复制失败,可能不是用户主动触发点击的方式调用,因browser安全性,不能js直接调用!');
40
+ }
41
+ } catch (err) {
42
+ // console.error('【Clipboard Error】:', err);
43
+ fail(err);
44
+ } finally {
45
+ document.body.removeChild(textarea);
46
+ }
47
+ }
48
+
49
+ function UniCopy(text: string, config: TClipboardOptions) {
50
+ const opt = Object.assign({ data: text }, config);
51
+ console.log(opt);
52
+ uni.setClipboardData(opt);
53
+ }
54
+
55
+ type TClipboardOptions = Omit<UniNamespace.SetClipboardDataOptions, 'data'>;
56
+
57
+ export function clipboard(content: string, options?: TClipboardOptions) {
58
+ const text = String(content);
59
+ const showToast = typeof options.showToast === 'boolean' ? options.showToast : true;
60
+ const copySuccessCb = typeof options.success === 'function' ? options.success : () => {};
61
+ const copyFailCb = typeof options.success === 'function' ? options.fail : () => {};
62
+ const copyCompleteCb = typeof options.complete === 'function' ? options.complete : () => {};
63
+
64
+ let config: TClipboardOptions = {
65
+ showToast,
66
+ success: copySuccessCb,
67
+ fail: copyFailCb,
68
+ complete: copyCompleteCb
69
+ };
70
+
71
+ // #ifdef H5
72
+ H5Copy(text, config);
73
+ // #endif
74
+
75
+ // #ifndef H5
76
+ UniCopy(text, config);
77
+ // #endif
78
+ }
@@ -1,5 +1,14 @@
1
1
  // utils/useComponent.ts
2
- import { ref, reactive, getCurrentInstance, onUnmounted, nextTick, computed, watch, onMounted } from 'vue';
2
+ import {
3
+ ref,
4
+ reactive,
5
+ getCurrentInstance,
6
+ onUnmounted,
7
+ nextTick,
8
+ computed,
9
+ onMounted,
10
+ type ComponentInternalInstance
11
+ } from 'vue';
3
12
  import { logger } from '../util/logger';
4
13
 
5
14
  // 类型定义
@@ -7,11 +16,13 @@ interface ParentContext {
7
16
  name: string;
8
17
  addChild: (child: ChildContext) => void;
9
18
  removeChild: (childId: string) => void;
10
- broadcast: (event: string, data?: any) => void;
19
+ broadcast: (event: string, data?: any, childIds?: string | string[]) => void;
20
+ broadcastToChildren: (componentName: string, event: string, data?: any) => void;
11
21
  getChildren: () => ChildContext[];
12
22
  getExposed: () => Record<string, any>;
13
23
  getChildExposed: (childId: string) => Record<string, any>;
14
24
  getChildrenExposed: () => Array<{ id: string; name: string; exposed: Record<string, any> }>;
25
+ getInstance: () => any;
15
26
  }
16
27
 
17
28
  interface ChildContext {
@@ -25,7 +36,6 @@ interface ChildContext {
25
36
 
26
37
  // 符号定义
27
38
  const PARENT_CONTEXT_SYMBOL = Symbol('parent_context');
28
- const CHILDREN_CONTEXT_SYMBOL = Symbol('children_context');
29
39
 
30
40
  /**
31
41
  * 生成实例唯一ID
@@ -56,10 +66,36 @@ function findParentInstance(name: string, instance: any): any {
56
66
  */
57
67
  function getParentContext(name: string, instance: any): ParentContext | null {
58
68
  const parentInstance = findParentInstance(name, instance);
59
- if (parentInstance) {
60
- return parentInstance.proxy?.[PARENT_CONTEXT_SYMBOL] || null;
69
+ return parentInstance?.proxy?.[PARENT_CONTEXT_SYMBOL] || null;
70
+ }
71
+
72
+ /**
73
+ * 递归查找所有指定名称的子组件
74
+ */
75
+ function findAllChildComponents(componentName: string, instance: any): any[] {
76
+ const components: any[] = [];
77
+
78
+ function traverse(currentInstance: any) {
79
+ if (!currentInstance?.subTree) return;
80
+
81
+ const subTree = currentInstance.subTree?.children || [];
82
+ const children = Array.isArray(subTree) ? subTree : [subTree];
83
+
84
+ children.forEach((vnode: any) => {
85
+ const child = vnode.component;
86
+ if (!child) return;
87
+
88
+ const name = child.type?.name || child.type?.__name;
89
+ if (name === componentName) {
90
+ components.push(child);
91
+ }
92
+ traverse(child);
93
+ });
61
94
  }
62
- return null;
95
+
96
+ traverse(instance);
97
+ logger.log(`Found ${components.length} ${componentName} components`);
98
+ return components;
63
99
  }
64
100
 
65
101
  /**
@@ -71,96 +107,103 @@ export function useParent(componentName?: string) {
71
107
  throw new Error('useParent must be called within setup function');
72
108
  }
73
109
 
74
- // 使用组件名称作为默认名称
75
110
  const name = componentName || instance.type.name || instance.type.__name;
76
111
  if (!name) {
77
- throw new Error('Component name is required for useParent. Either provide a name or set component name.');
112
+ throw new Error('Component name is required for useParent');
78
113
  }
79
114
 
80
115
  const children = reactive<ChildContext[]>([]);
81
116
  const childrenMap = new Map<string, ChildContext>();
82
117
 
83
- // 父组件上下文
118
+ const broadcast = (event: string, data?: any, childIds?: string | string[]) => {
119
+ const targetChildren = childIds
120
+ ? ((Array.isArray(childIds) ? childIds : [childIds])
121
+ .map(id => childrenMap.get(id))
122
+ .filter(Boolean) as ChildContext[])
123
+ : Array.from(childrenMap.values());
124
+
125
+ logger.log(`Parent ${name} broadcasting event: ${event} to ${targetChildren.length} children`);
126
+
127
+ targetChildren.forEach(child => {
128
+ const exposed = child.getExposed();
129
+ if (exposed && typeof exposed[event] === 'function') {
130
+ try {
131
+ exposed[event](data);
132
+ } catch (error) {
133
+ logger.warn(`Error calling child method ${event}:`, error);
134
+ }
135
+ }
136
+ });
137
+ };
138
+
139
+ const broadcastToChildren = (componentName: string, event: string, data?: any) => {
140
+ logger.log(`Parent ${name} broadcasting event: ${event} to all ${componentName} components`);
141
+
142
+ const childComponents = findAllChildComponents(componentName, instance);
143
+ let successCount = 0;
144
+
145
+ childComponents.forEach(childComponent => {
146
+ const exposed = childComponent.exposed || childComponent.proxy;
147
+ if (exposed && typeof exposed[event] === 'function') {
148
+ try {
149
+ exposed[event](data);
150
+ successCount++;
151
+ } catch (error) {
152
+ logger.warn(`Error calling ${componentName} method ${event}:`, error);
153
+ }
154
+ }
155
+ });
156
+
157
+ logger.log(
158
+ `Parent ${name} successfully called ${successCount} of ${childComponents.length} ${componentName} components`
159
+ );
160
+ };
161
+
84
162
  const parentContext: ParentContext = {
85
163
  name,
86
-
87
164
  addChild(child: ChildContext) {
88
165
  if (!childrenMap.has(child.id)) {
89
166
  childrenMap.set(child.id, child);
90
167
  children.push(child);
91
- logger.log(`Parent ${name} added child: ${child.name} (${child.id})`);
168
+ logger.log(`Parent ${name} added child: ${child.name}`);
92
169
  }
93
170
  },
94
-
95
171
  removeChild(childId: string) {
96
172
  if (childrenMap.has(childId)) {
97
173
  const child = childrenMap.get(childId)!;
98
174
  childrenMap.delete(childId);
99
175
  const index = children.findIndex(c => c.id === childId);
100
- if (index > -1) {
101
- children.splice(index, 1);
102
- }
176
+ if (index > -1) children.splice(index, 1);
103
177
  logger.log(`Parent ${name} removed child: ${childId}`);
104
178
  }
105
179
  },
106
-
107
- broadcast(event: string, data?: any) {
108
- logger.log(`Parent ${name} broadcasting event: ${event} to ${childrenMap.size} children`);
109
- childrenMap.forEach(child => {
110
- const exposed = child.getExposed();
111
- if (exposed && typeof exposed[event] === 'function') {
112
- try {
113
- exposed[event](data);
114
- } catch (error) {
115
- logger.warn(`Error calling child method ${event}:`, error);
116
- }
117
- }
118
- });
119
- },
120
-
121
- getChildren() {
122
- return Array.from(childrenMap.values());
123
- },
124
-
125
- getExposed() {
126
- return instance.exposed || {};
127
- },
128
-
180
+ broadcast,
181
+ broadcastToChildren,
182
+ getChildren: () => Array.from(childrenMap.values()),
183
+ getExposed: () => instance.exposed || {},
129
184
  getChildExposed(childId: string) {
130
185
  const child = childrenMap.get(childId);
131
- if (child && child.getExposed) {
132
- return child.getExposed();
133
- }
134
- logger.warn(`Child ${childId} not found or does not have getExposed method`);
135
- return {};
186
+ return child?.getExposed?.() || {};
136
187
  },
137
-
138
188
  getChildrenExposed() {
139
189
  return Array.from(childrenMap.values())
140
190
  .filter(child => child.getExposed)
141
- .map(child => {
142
- const exposed = child.getExposed();
143
- return {
144
- id: child.id,
145
- name: child.name,
146
- exposed: exposed
147
- };
148
- })
191
+ .map(child => ({
192
+ id: child.id,
193
+ name: child.name,
194
+ exposed: child.getExposed()
195
+ }))
149
196
  .filter(item => Object.keys(item.exposed).length > 0);
150
- }
197
+ },
198
+ getInstance: () => instance
151
199
  };
152
200
 
153
- // 在组件实例上存储父组件上下文
154
201
  if (instance.proxy) {
155
202
  instance.proxy[PARENT_CONTEXT_SYMBOL] = parentContext;
156
203
  }
157
204
 
158
- // 组件卸载时清理
159
205
  onUnmounted(() => {
160
- // 清理所有子组件引用
161
- childrenMap.forEach((child, childId) => {
162
- parentContext.removeChild(childId);
163
- });
206
+ childrenMap.forEach((_, childId) => parentContext.removeChild(childId));
164
207
  if (instance.proxy) {
165
208
  delete instance.proxy[PARENT_CONTEXT_SYMBOL];
166
209
  }
@@ -170,11 +213,13 @@ export function useParent(componentName?: string) {
170
213
  return {
171
214
  parentName: name,
172
215
  children,
173
- broadcast: parentContext.broadcast,
216
+ broadcast,
217
+ broadcastToChildren,
174
218
  getChildren: parentContext.getChildren,
175
219
  getChildExposed: parentContext.getChildExposed,
176
220
  getChildrenExposed: parentContext.getChildrenExposed,
177
- getExposed: parentContext.getExposed
221
+ getExposed: parentContext.getExposed,
222
+ getInstance: parentContext.getInstance
178
223
  };
179
224
  }
180
225
 
@@ -187,17 +232,24 @@ export function useChildren(componentName?: string, parentName?: string) {
187
232
  throw new Error('useChildren must be called within setup function');
188
233
  }
189
234
 
190
- // 使用组件名称作为默认名称
191
235
  const name = componentName || instance.type.name || instance.type.__name;
192
- if (!name) {
193
- throw new Error('Component name is required for useChildren. Either provide a name or set component name.');
194
- }
195
-
196
- const instanceId = generateInstanceId(name);
197
- const parentRef = ref<ParentContext | null>(null);
236
+ const instanceId = generateInstanceId(name || 'anonymous');
237
+ const parentRef = ref<any | null>(null);
198
238
  const parentExposed = ref<Record<string, any>>({});
199
239
 
200
- // 获取父组件暴露内容
240
+ const createSimulatedParentContext = (parentInstance: any): ParentContext => ({
241
+ name: parentInstance?.type?.name || parentInstance?.type?.__name || 'unknown',
242
+ addChild: () => logger.log('Simulated parent added child'),
243
+ removeChild: () => logger.log('Simulated parent removed child'),
244
+ broadcast: () => logger.log('Simulated parent broadcasting'),
245
+ broadcastToChildren: () => logger.log('Simulated parent broadcasting to children'),
246
+ getChildren: () => [],
247
+ getExposed: () => parentInstance?.exposed || {},
248
+ getChildExposed: () => ({}),
249
+ getChildrenExposed: () => [],
250
+ getInstance: () => parentInstance
251
+ });
252
+
201
253
  const getParentExposed = (): Record<string, any> => {
202
254
  if (parentRef.value) {
203
255
  const exposed = parentRef.value.getExposed();
@@ -207,99 +259,98 @@ export function useChildren(componentName?: string, parentName?: string) {
207
259
  return {};
208
260
  };
209
261
 
210
- // 获取子组件exposed内容
211
- const getExposed = (): Record<string, any> => {
212
- return instance.exposed || {};
213
- };
262
+ const getExposed = (): Record<string, any> => instance.exposed || {};
214
263
 
215
- // 查找父组件
216
264
  const findParent = (): ParentContext | null => {
217
- // 如果指定了父组件名称,使用精确查找
218
265
  if (parentName) {
219
- return getParentContext(parentName, instance);
266
+ const parentContext = getParentContext(parentName, instance);
267
+ if (parentContext) {
268
+ if (!parentContext.getInstance) {
269
+ parentContext.getInstance = () => findParentInstance(parentName, instance);
270
+ }
271
+ return parentContext;
272
+ }
273
+
274
+ const parentInstance = findParentInstance(parentName, instance);
275
+ if (parentInstance) {
276
+ return createSimulatedParentContext(parentInstance);
277
+ }
220
278
  }
221
279
 
222
- // 否则查找最近的父组件上下文
223
280
  let current = instance.parent;
224
281
  while (current) {
225
282
  const context = current.proxy?.[PARENT_CONTEXT_SYMBOL];
226
283
  if (context) {
284
+ if (!context.getInstance) {
285
+ context.getInstance = () => current;
286
+ }
227
287
  return context;
228
288
  }
229
289
  current = current.parent;
230
290
  }
231
- return null;
291
+
292
+ return instance.parent ? createSimulatedParentContext(instance.parent) : null;
232
293
  };
233
294
 
234
- // 链接到父组件
235
295
  const linkParent = (): boolean => {
236
296
  const parent = findParent();
237
297
  if (parent) {
238
298
  parentRef.value = parent;
239
- parent.addChild(childContext);
299
+ if (parent.addChild && childContext) {
300
+ parent.addChild(childContext);
301
+ }
240
302
  getParentExposed();
241
- logger.log(`Child ${name} linked to parent ${parent.name}`);
303
+ logger.log(`Child ${name || 'anonymous'} linked to parent ${parent.name}`);
242
304
  return true;
243
305
  }
306
+ logger.log(`Child ${name || 'anonymous'} no parent found, working in standalone mode`);
244
307
  return false;
245
308
  };
246
309
 
247
- // 向父组件发送事件
248
310
  const emitToParent = (event: string, data?: any) => {
249
311
  if (parentRef.value) {
250
- const exposed = parentRef.value.getExposed();
312
+ const exposed = getParentExposed();
251
313
  if (exposed && typeof exposed[event] === 'function') {
252
314
  try {
253
315
  exposed[event](data, instanceId, name);
254
316
  } catch (error) {
255
317
  logger.warn(`Error calling parent method ${event}:`, error);
256
318
  }
257
- } else {
258
- logger.warn(`Parent method ${event} not found or not a function`);
259
319
  }
260
- } else {
261
- logger.warn(`No parent found to emit event: ${event}`);
262
320
  }
263
321
  };
264
322
 
265
- // 子组件上下文
266
323
  const childContext: ChildContext = {
267
324
  id: instanceId,
268
- name,
325
+ name: name || 'anonymous',
269
326
  emitToParent,
270
327
  getParentExposed,
271
- getInstance() {
272
- return instance;
273
- },
328
+ getInstance: () => instance,
274
329
  getExposed
275
330
  };
276
331
 
277
- logger.log(`Child ${name} registered, looking for parent`);
332
+ logger.log(`Child ${name || 'anonymous'} registered, looking for parent`);
278
333
 
279
334
  onMounted(() => {
280
- // 立即尝试连接父组件
281
335
  let connected = linkParent();
282
336
  nextTick(() => {
283
- // 如果未连接成功,500ms后重试一次
337
+ connected = linkParent();
284
338
  if (!connected) {
285
- setTimeout(() => {
286
- linkParent();
287
- }, 500);
339
+ setTimeout(linkParent, 500);
288
340
  }
289
341
  });
290
342
  });
291
343
 
292
- // 组件卸载时清理
293
344
  onUnmounted(() => {
294
- if (parentRef.value) {
345
+ if (parentRef.value?.removeChild) {
295
346
  parentRef.value.removeChild(instanceId);
296
347
  }
297
- logger.log(`Child ${name} unmounted`);
348
+ logger.log(`Child ${name || 'anonymous'} unmounted`);
298
349
  });
299
350
 
300
351
  return {
301
352
  childId: instanceId,
302
- childName: name,
353
+ childName: name || 'anonymous',
303
354
  parent: parentRef,
304
355
  emitToParent,
305
356
  getParentExposed,
@@ -319,13 +370,9 @@ export function hasParent(parentName?: string): boolean {
319
370
  return getParentContext(parentName, instance) !== null;
320
371
  }
321
372
 
322
- // 查找最近的父组件上下文
323
373
  let current = instance.parent;
324
374
  while (current) {
325
- const context = current.proxy?.[PARENT_CONTEXT_SYMBOL];
326
- if (context) {
327
- return true;
328
- }
375
+ if (current.proxy?.[PARENT_CONTEXT_SYMBOL]) return true;
329
376
  current = current.parent;
330
377
  }
331
378
  return false;
@@ -336,8 +383,7 @@ export function hasParent(parentName?: string): boolean {
336
383
  */
337
384
  export function getParentContextByName(parentName: string): ParentContext | null {
338
385
  const instance = getCurrentInstance();
339
- if (!instance) return null;
340
- return getParentContext(parentName, instance);
386
+ return instance ? getParentContext(parentName, instance) : null;
341
387
  }
342
388
 
343
389
  /**
@@ -345,7 +391,6 @@ export function getParentContextByName(parentName: string): ParentContext | null
345
391
  */
346
392
  export function cleanupComponentRelations(): void {
347
393
  logger.log('Cleaning up component relations for hot reload');
348
- // 由于使用组件实例存储,热更新时会自动重新建立关系
349
394
  }
350
395
 
351
396
  // 热更新处理
package/libs/index.ts CHANGED
@@ -46,6 +46,8 @@ import throttle from './function/throttle';
46
46
  import getRect from './function/getRect';
47
47
  // 获取父组件
48
48
  import { parentData, parent } from './function/parent';
49
+ // 剪贴板
50
+ import { clipboard } from './function/clipboard';
49
51
  // 配置信息
50
52
  import config from './config/config';
51
53
  // 各个需要fixed的地方的z-index配置文件
@@ -268,6 +270,7 @@ export {
268
270
  $parent,
269
271
  parent,
270
272
  parentData,
273
+ clipboard,
271
274
  dispatch,
272
275
  broadcast,
273
276
  config,
@@ -301,6 +304,7 @@ export const $u = {
301
304
  $parent,
302
305
  parent,
303
306
  parentData,
307
+ clipboard,
304
308
  addUnit,
305
309
  trim,
306
310
  type: ['primary', 'success', 'error', 'warning', 'info'],
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "id": "uview-pro",
3
3
  "name": "uview-pro",
4
4
  "displayName": "【Vue3重构版】uView Pro|基于Vue3+TS全面重构的70+精选UI组件库",
5
- "version": "0.3.1",
5
+ "version": "0.3.3",
6
6
  "description": "uView Pro,是全面支持Vue3的uni-app生态框架,70+精选组件已使用TypeScript重构,已全面支持uni-app Vue3.0",
7
7
  "main": "index.ts",
8
8
  "module": "index.ts",
package/readme.md CHANGED
@@ -73,7 +73,7 @@ pnpm dev
73
73
 
74
74
  <table class="table">
75
75
  <tr>
76
- <td><img src="https://ik.imagekit.io/anyup/images/social/weixin-chat.png?updatedAt=1760233880982" width="250" height="345" ></td>
76
+ <td><img src="https://ik.imagekit.io/anyup/images/social/weixin-chat-cl.png" width="250" height="345" ></td>
77
77
  <td><img src="https://ik.imagekit.io/anyup/images/social/qq-chat.png" width="250" height="345" ></td>
78
78
  </tr>
79
79
  <tr>
@@ -91,7 +91,9 @@ declare module 'vue' {
91
91
  uWaterfall: (typeof import('../components/u-waterfall/u-waterfall.vue'))['default'];
92
92
  uText: (typeof import('../components/u-text/u-text.vue'))['default'];
93
93
  uRootPortal: (typeof import('../components/u-root-portal/u-root-portal.vue'))['default'];
94
+ uStatusBar: (typeof import('../components/u-status-bar/u-status-bar.vue'))['default'];
95
+ uSafeBottom: (typeof import('../components/u-safe-bottom/u-safe-bottom.vue'))['default'];
94
96
  }
95
97
  }
96
98
 
97
- export {};
99
+ export { };