uview-pro 0.6.13 → 0.6.14
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 +22 -3
- package/index.ts +100 -100
- package/package.json +2 -11
- package/plugins/root/README.md +161 -161
- package/plugins/root/dist/page.cjs +29 -11
- package/plugins/root/dist/page.mjs +29 -11
- package/plugins/root/dist/root.cjs +10 -2
- package/plugins/root/dist/root.mjs +10 -2
- package/plugins/root/dist/utils.cjs +1 -1
- package/plugins/root/dist/utils.mjs +1 -1
- package/plugins/root/index.ts +123 -123
- package/plugins/root/page.ts +77 -54
- package/plugins/root/root.ts +53 -35
- package/plugins/root/utils.ts +137 -133
- package/readme.md +2 -2
- package/types/index.d.ts +14 -19
package/changelog.md
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
|
-
## 0.6.
|
|
1
|
+
## 0.6.14(2026-07-30)
|
|
2
2
|
|
|
3
3
|
### 🐛 Bug Fixes | Bug 修复
|
|
4
4
|
|
|
5
|
-
- **
|
|
6
|
-
- **
|
|
5
|
+
- **uni-root:** 适配HBuilderX环境,添加parser降级逻辑 ([f70749d](https://github.com/anyup/uView-Pro/commit/f70749d14d8e67f44ee44707c6c185b6a06e4b7e))
|
|
6
|
+
- **plugin:** 解决微信小程序平台Vite/Rollup的sourcemap不正确警告问题 ([8158f79](https://github.com/anyup/uView-Pro/commit/8158f79223a6d73b7a715e288c812432081c3b70))
|
|
7
|
+
|
|
8
|
+
### ♻️ Code Refactoring | 代码重构
|
|
9
|
+
|
|
10
|
+
- **types:** 优化uview-pro的类型声明和导出配置 ([f540d45](https://github.com/anyup/uView-Pro/commit/f540d45fc5bf1ba340138699872d8f97857656fb))
|
|
11
|
+
|
|
12
|
+
### ✨ Features | 新功能
|
|
13
|
+
|
|
14
|
+
- **root:** root根组件新增生命周期钩子和日志 ([d144b24](https://github.com/anyup/uView-Pro/commit/d144b2432402955e6973628d4dddaa2697f6f901))
|
|
7
15
|
|
|
8
16
|
### 👥 Contributors
|
|
9
17
|
|
|
10
18
|
<a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
|
|
11
19
|
|
|
20
|
+
## 0.6.13(2026-07-28)
|
|
21
|
+
|
|
22
|
+
### 🐛 Bug Fixes | Bug 修复
|
|
23
|
+
|
|
24
|
+
- **u-picker:** 修复抖音小程序等平台picker数据加载,导致弹窗失败问题(#174) ([ead3cdc](https://github.com/anyup/uView-Pro/commit/ead3cdc18fc5be05a3022acac4485fc3e3e75faa))
|
|
25
|
+
- **u-select:** 修复抖音小程序select弹出问题(#174) ([c7a4c28](https://github.com/anyup/uView-Pro/commit/c7a4c2856f9790747ec234b0a6cc1154b560d701))
|
|
26
|
+
|
|
27
|
+
### 👥 Contributors
|
|
28
|
+
|
|
29
|
+
<a href="https://github.com/anyup"><img src="https://github.com/anyup.png?size=40" width="40" height="40" alt="anyup" title="anyup"/></a>
|
|
30
|
+
|
|
12
31
|
## 0.6.12(2026-07-24)
|
|
13
32
|
|
|
14
33
|
### 🐛 Bug Fixes | Bug 修复
|
package/index.ts
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import { $u, type RequestOptions, initTheme, configProvider } from './libs';
|
|
2
|
-
import type { UViewProOptions, Theme } from './types/global';
|
|
3
|
-
import { defaultThemes } from './libs/config/theme-tokens';
|
|
4
|
-
import { setConfig } from './libs/config/config';
|
|
5
|
-
|
|
6
|
-
declare const uni: {
|
|
7
|
-
[key: string]: any;
|
|
8
|
-
$u?: typeof $u;
|
|
9
|
-
createSelectorQuery: () => any;
|
|
10
|
-
hideLoading: () => void;
|
|
11
|
-
showLoading: (options: any) => void;
|
|
12
|
-
request: (options: RequestOptions) => any;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// $u挂载到uni对象上
|
|
16
|
-
const install = (app: any, options?: UViewProOptions): void => {
|
|
17
|
-
try {
|
|
18
|
-
if (options) {
|
|
19
|
-
// 配置主题:统一使用 useTheme 的 initTheme 初始化,避免重复初始化
|
|
20
|
-
if (options?.theme) {
|
|
21
|
-
const optTheme = options.theme as any;
|
|
22
|
-
// 1.如果是数组,则为多主题配置
|
|
23
|
-
if (Array.isArray(optTheme)) {
|
|
24
|
-
initTheme(optTheme);
|
|
25
|
-
} else if (typeof optTheme === 'object' && optTheme.themes) {
|
|
26
|
-
// 2.如果为对象且有themes,则为多主题配置+设置默认主题
|
|
27
|
-
initTheme(
|
|
28
|
-
optTheme.themes,
|
|
29
|
-
{
|
|
30
|
-
defaultTheme: optTheme.defaultTheme,
|
|
31
|
-
defaultDarkMode: optTheme.defaultDarkMode
|
|
32
|
-
},
|
|
33
|
-
optTheme.isForce
|
|
34
|
-
);
|
|
35
|
-
} else {
|
|
36
|
-
// 3.兼容之前只有一套样式的情况,需要覆盖默认主题,默认系统主题(uviewpro)
|
|
37
|
-
const defaultTheme = defaultThemes[0];
|
|
38
|
-
if (defaultTheme) {
|
|
39
|
-
// 创建新主题对象,用用户的 theme 覆盖默认主题的 color
|
|
40
|
-
const mergedTheme: Theme = {
|
|
41
|
-
...defaultTheme,
|
|
42
|
-
color: {
|
|
43
|
-
...defaultTheme.color,
|
|
44
|
-
...optTheme
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
// 初始化主题(只包含覆盖后的默认主题)
|
|
48
|
-
initTheme([mergedTheme], defaultTheme.name);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
52
|
-
// 默认初始化系统主题
|
|
53
|
-
initTheme();
|
|
54
|
-
}
|
|
55
|
-
// 初始化国际化(如果提供 options.locale 或使用内置语言包)
|
|
56
|
-
try {
|
|
57
|
-
// options.locale 可以是 string(默认语言名) | any[](locale 列表) | { locales: any[], defaultLocale?: string }
|
|
58
|
-
if (options?.locale) {
|
|
59
|
-
const optLocale: any = options.locale as any;
|
|
60
|
-
if (typeof optLocale === 'string') {
|
|
61
|
-
configProvider.initLocales(undefined, optLocale);
|
|
62
|
-
} else if (Array.isArray(optLocale)) {
|
|
63
|
-
configProvider.initLocales(optLocale);
|
|
64
|
-
} else if (optLocale && typeof optLocale === 'object') {
|
|
65
|
-
configProvider.initLocales(optLocale.locales, optLocale.defaultLocale, optLocale.isForce);
|
|
66
|
-
} else {
|
|
67
|
-
configProvider.initLocales();
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
// 无 locale 配置,仍然初始化内置语言包以保证可用
|
|
71
|
-
configProvider.initLocales();
|
|
72
|
-
}
|
|
73
|
-
} catch (e) {
|
|
74
|
-
console.error('[install locales] Error:', e);
|
|
75
|
-
}
|
|
76
|
-
// 设置调试模式
|
|
77
|
-
setConfig({
|
|
78
|
-
debugMode: options?.debugMode ?? false
|
|
79
|
-
});
|
|
80
|
-
} else {
|
|
81
|
-
// 默认初始化系统主题
|
|
82
|
-
initTheme();
|
|
83
|
-
// 默认初始化内置语言包以保证可用
|
|
84
|
-
configProvider.initLocales();
|
|
85
|
-
}
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error('[install options] Error:', error);
|
|
88
|
-
}
|
|
89
|
-
uni.$u = $u;
|
|
90
|
-
// 可扩展更多配置项
|
|
91
|
-
app.config.globalProperties.$u = $u;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
export default {
|
|
95
|
-
install
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export * from './libs';
|
|
99
|
-
|
|
100
|
-
export type { UViewProOptions };
|
|
1
|
+
import { $u, type RequestOptions, initTheme, configProvider } from './libs';
|
|
2
|
+
import type { UViewProOptions, Theme } from './types/global';
|
|
3
|
+
import { defaultThemes } from './libs/config/theme-tokens';
|
|
4
|
+
import { setConfig } from './libs/config/config';
|
|
5
|
+
|
|
6
|
+
declare const uni: {
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
$u?: typeof $u;
|
|
9
|
+
createSelectorQuery: () => any;
|
|
10
|
+
hideLoading: () => void;
|
|
11
|
+
showLoading: (options: any) => void;
|
|
12
|
+
request: (options: RequestOptions) => any;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// $u挂载到uni对象上
|
|
16
|
+
const install = (app: any, options?: UViewProOptions): void => {
|
|
17
|
+
try {
|
|
18
|
+
if (options) {
|
|
19
|
+
// 配置主题:统一使用 useTheme 的 initTheme 初始化,避免重复初始化
|
|
20
|
+
if (options?.theme) {
|
|
21
|
+
const optTheme = options.theme as any;
|
|
22
|
+
// 1.如果是数组,则为多主题配置
|
|
23
|
+
if (Array.isArray(optTheme)) {
|
|
24
|
+
initTheme(optTheme);
|
|
25
|
+
} else if (typeof optTheme === 'object' && optTheme.themes) {
|
|
26
|
+
// 2.如果为对象且有themes,则为多主题配置+设置默认主题
|
|
27
|
+
initTheme(
|
|
28
|
+
optTheme.themes,
|
|
29
|
+
{
|
|
30
|
+
defaultTheme: optTheme.defaultTheme,
|
|
31
|
+
defaultDarkMode: optTheme.defaultDarkMode
|
|
32
|
+
},
|
|
33
|
+
optTheme.isForce
|
|
34
|
+
);
|
|
35
|
+
} else {
|
|
36
|
+
// 3.兼容之前只有一套样式的情况,需要覆盖默认主题,默认系统主题(uviewpro)
|
|
37
|
+
const defaultTheme = defaultThemes[0];
|
|
38
|
+
if (defaultTheme) {
|
|
39
|
+
// 创建新主题对象,用用户的 theme 覆盖默认主题的 color
|
|
40
|
+
const mergedTheme: Theme = {
|
|
41
|
+
...defaultTheme,
|
|
42
|
+
color: {
|
|
43
|
+
...defaultTheme.color,
|
|
44
|
+
...optTheme
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
// 初始化主题(只包含覆盖后的默认主题)
|
|
48
|
+
initTheme([mergedTheme], defaultTheme.name);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// 默认初始化系统主题
|
|
53
|
+
initTheme();
|
|
54
|
+
}
|
|
55
|
+
// 初始化国际化(如果提供 options.locale 或使用内置语言包)
|
|
56
|
+
try {
|
|
57
|
+
// options.locale 可以是 string(默认语言名) | any[](locale 列表) | { locales: any[], defaultLocale?: string }
|
|
58
|
+
if (options?.locale) {
|
|
59
|
+
const optLocale: any = options.locale as any;
|
|
60
|
+
if (typeof optLocale === 'string') {
|
|
61
|
+
configProvider.initLocales(undefined, optLocale);
|
|
62
|
+
} else if (Array.isArray(optLocale)) {
|
|
63
|
+
configProvider.initLocales(optLocale);
|
|
64
|
+
} else if (optLocale && typeof optLocale === 'object') {
|
|
65
|
+
configProvider.initLocales(optLocale.locales, optLocale.defaultLocale, optLocale.isForce);
|
|
66
|
+
} else {
|
|
67
|
+
configProvider.initLocales();
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
// 无 locale 配置,仍然初始化内置语言包以保证可用
|
|
71
|
+
configProvider.initLocales();
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.error('[install locales] Error:', e);
|
|
75
|
+
}
|
|
76
|
+
// 设置调试模式
|
|
77
|
+
setConfig({
|
|
78
|
+
debugMode: options?.debugMode ?? false
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
// 默认初始化系统主题
|
|
82
|
+
initTheme();
|
|
83
|
+
// 默认初始化内置语言包以保证可用
|
|
84
|
+
configProvider.initLocales();
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error('[install options] Error:', error);
|
|
88
|
+
}
|
|
89
|
+
uni.$u = $u;
|
|
90
|
+
// 可扩展更多配置项
|
|
91
|
+
app.config.globalProperties.$u = $u;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default {
|
|
95
|
+
install
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export * from './libs';
|
|
99
|
+
|
|
100
|
+
export type { UViewProOptions };
|
package/package.json
CHANGED
|
@@ -2,21 +2,12 @@
|
|
|
2
2
|
"id": "uview-pro",
|
|
3
3
|
"name": "uview-pro",
|
|
4
4
|
"displayName": "【支持鸿蒙】uView Pro|基于Vue3+TS的高质量UI组件库,支持多主题、暗黑模式、多语言",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.14",
|
|
6
6
|
"description": "uView Pro是基于Vue3+TS的多平台UI框架,提供80+高质量组件、便捷工具和常用模板,支持多主题、暗黑模式、多语言,支持H5/APP/鸿蒙/小程序多端开发。已在鸿蒙应用商店上架,欢迎体验!",
|
|
7
7
|
"main": "index.ts",
|
|
8
8
|
"module": "index.ts",
|
|
9
9
|
"browser": "index.ts",
|
|
10
|
-
"
|
|
11
|
-
".": "./index.ts",
|
|
12
|
-
"./plugins": {
|
|
13
|
-
"types": "./plugins/index.ts",
|
|
14
|
-
"import": "./plugins/index.mjs",
|
|
15
|
-
"require": "./plugins/index.cjs",
|
|
16
|
-
"default": "./plugins/index.mjs"
|
|
17
|
-
},
|
|
18
|
-
"./*": "./*"
|
|
19
|
-
},
|
|
10
|
+
"types": "index.ts",
|
|
20
11
|
"keywords": [
|
|
21
12
|
"uview-pro",
|
|
22
13
|
"vue3",
|
package/plugins/root/README.md
CHANGED
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
# vite-plugin-uni-root
|
|
2
|
-
|
|
3
|
-
UniApp 虚拟根组件 Vite 插件,零额外依赖,纯字符串操作实现。
|
|
4
|
-
|
|
5
|
-
## 解决的问题
|
|
6
|
-
|
|
7
|
-
UniApp 不支持原生的根组件包裹机制,无法在全局统一注入 `u-config-provider`、`u-toast`、`u-modal` 等需要在每个页面根部存在的组件。传统方案需要在每个页面手动添加,维护成本高。
|
|
8
|
-
|
|
9
|
-
本插件通过 Vite 构建时注入,自动为每个页面包裹 `<global-root-view>` 根组件,无需修改任何页面代码本身。
|
|
10
|
-
|
|
11
|
-
## 技术方案
|
|
12
|
-
|
|
13
|
-
### 架构
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
App.root.vue(源码) → 在 main.ts 中导入并注册为全局组件 global-root-view
|
|
17
|
-
→ 页面模板被包裹 <global-root-view> 页面内容 </global-root-view>
|
|
18
|
-
→ 渲染时页面内容填入 <slot />
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### 工作流程
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
|
|
25
|
-
│ main.ts │ → │ 注入 import │ → │ 全局注册组件 │
|
|
26
|
-
│ │ │ + 注册代码 │ │ global-root-view │
|
|
27
|
-
└─────────────┘ └──────────────┘ └─────────────────┘
|
|
28
|
-
│
|
|
29
|
-
┌─────────────┐ ┌──────────────┐ │
|
|
30
|
-
│ page.vue │ → │ 包裹模板 │ │
|
|
31
|
-
│ │ │ <global-...> │ ←─────────┘
|
|
32
|
-
└─────────────┘ └──────────────┘
|
|
33
|
-
│
|
|
34
|
-
┌─────────────┐ │
|
|
35
|
-
│ App.root.vue│ → <slot /> 接收页面内容
|
|
36
|
-
└─────────────┘
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 三步实现
|
|
40
|
-
|
|
41
|
-
1. **main.ts 注入**(`root.ts -> registerRootApp`)
|
|
42
|
-
- 在 main.ts 中注入 `import GlobalRootView from "./App.root.vue"`
|
|
43
|
-
- 注入 `app.component("global-root-view", GlobalRootView)` 注册为全局组件
|
|
44
|
-
- 注:`GlobalRootView` 仅为局部变量名,实际组件就是 `App.root.vue`
|
|
45
|
-
|
|
46
|
-
2. **页面模板包裹**(`page.ts -> transformPage`)
|
|
47
|
-
- 解析 SFC,定位 `<template>` 标签的起止位置
|
|
48
|
-
- 提取 PageMeta 节点(如有则放到包裹层外部)
|
|
49
|
-
- 将模板内容包裹在 `<global-root-view>...</global-root-view>` 中
|
|
50
|
-
|
|
51
|
-
3. **App.root.vue 加载**(`index.ts -> load`)
|
|
52
|
-
- 拦截 `App.root.vue` 的模块请求,从文件读取并返回
|
|
53
|
-
- 模板中的 `<slot />` 接收并渲染页面内容
|
|
54
|
-
|
|
55
|
-
### 跨端兼容
|
|
56
|
-
|
|
57
|
-
插件是纯 Vite 构建时插件,在 UniApp 编译之前完成源代码转换,不依赖任何平台特定 API,所有 UniApp 支持的平台均可正常工作:
|
|
58
|
-
|
|
59
|
-
| 平台 | 支持 |
|
|
60
|
-
|------|------|
|
|
61
|
-
| H5 | ✅ |
|
|
62
|
-
| 微信小程序 | ✅ |
|
|
63
|
-
| 支付宝小程序 | ✅ |
|
|
64
|
-
| 头条/抖音小程序 | ✅ |
|
|
65
|
-
| Android App | ✅ |
|
|
66
|
-
| iOS App | ✅ |
|
|
67
|
-
| 鸿蒙 App | ✅ |
|
|
68
|
-
|
|
69
|
-
## 使用方式
|
|
70
|
-
|
|
71
|
-
### 1. 创建 App.root.vue
|
|
72
|
-
|
|
73
|
-
在 `src/` 目录下创建 `App.root.vue` 文件:
|
|
74
|
-
|
|
75
|
-
```vue
|
|
76
|
-
<script setup lang="ts">
|
|
77
|
-
import { onLoad } from '@dcloudio/uni-app';
|
|
78
|
-
import { useTheme, useLocale } from 'uview-pro';
|
|
79
|
-
|
|
80
|
-
const { darkMode, themes, currentTheme } = useTheme();
|
|
81
|
-
const { currentLocale } = useLocale();
|
|
82
|
-
|
|
83
|
-
onLoad(() => {
|
|
84
|
-
console.log('darkMode->', darkMode.value);
|
|
85
|
-
console.log('theme->', currentTheme.value?.name);
|
|
86
|
-
console.log('locale->', currentLocale.value?.name);
|
|
87
|
-
});
|
|
88
|
-
</script>
|
|
89
|
-
|
|
90
|
-
<template>
|
|
91
|
-
<u-config-provider
|
|
92
|
-
:dark-mode="darkMode"
|
|
93
|
-
:themes="themes"
|
|
94
|
-
:current-theme="currentTheme?.name"
|
|
95
|
-
:current-locale="currentLocale?.name"
|
|
96
|
-
>
|
|
97
|
-
<slot />
|
|
98
|
-
<u-toast global></u-toast>
|
|
99
|
-
<u-modal global></u-modal>
|
|
100
|
-
</u-config-provider>
|
|
101
|
-
</template>
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
关键点:`<slot />` 是页面内容的渲染位置,必须保留。
|
|
105
|
-
|
|
106
|
-
### 2. 配置 vite.config.ts
|
|
107
|
-
|
|
108
|
-
```ts
|
|
109
|
-
// npm 安装
|
|
110
|
-
import { UniRoot } from 'uview-pro/plugins';
|
|
111
|
-
|
|
112
|
-
// uni_modules 安装
|
|
113
|
-
import { UniRoot } from './src/uni_modules/uview-pro/plugins';
|
|
114
|
-
|
|
115
|
-
export default defineConfig({
|
|
116
|
-
plugins: [
|
|
117
|
-
UniRoot(), // 放在其他插件之前
|
|
118
|
-
// ...其他插件
|
|
119
|
-
],
|
|
120
|
-
});
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### 3. 运行
|
|
124
|
-
|
|
125
|
-
```bash
|
|
126
|
-
npm run dev
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
不需要修改任何页面代码,所有页面自动被 `<global-root-view>` 包裹。
|
|
130
|
-
|
|
131
|
-
## 文件结构
|
|
132
|
-
|
|
133
|
-
```
|
|
134
|
-
src/uni_modules/uview-pro/plugins/root/
|
|
135
|
-
├── index.ts # 插件入口,Vite 插件生命周期
|
|
136
|
-
├── root.ts # main.ts 注入 + App.root.vue 处理
|
|
137
|
-
├── page.ts # 页面模板包裹转换
|
|
138
|
-
└── utils.ts # 工具函数(SFC 解析、pages.json 加载、AST 查找等)
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## 配置项
|
|
142
|
-
|
|
143
|
-
```ts
|
|
144
|
-
UniRoot({
|
|
145
|
-
rootFileName: 'App.root', // 根组件文件名(不含扩展名),默认 App.root
|
|
146
|
-
})
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## HMR 支持
|
|
150
|
-
|
|
151
|
-
修改 `pages.json` 后自动重载页面列表,无需手动重启开发服务器。
|
|
152
|
-
|
|
153
|
-
## 注意事项
|
|
154
|
-
|
|
155
|
-
- Vue 版本需 >= 3.2.13(使用 `vue/compiler-sfc` 的 `parse` API
|
|
156
|
-
- 页面模板中如有 `<page-meta>` 组件,会被提取到包裹层外部,确保微信小程序兼容
|
|
157
|
-
- 页面模板中如有嵌套 `<template #slot>` 具名插槽,不影响根模板的正确识别
|
|
158
|
-
|
|
159
|
-
## 致谢
|
|
160
|
-
|
|
161
|
-
本插件的实现参考了 [@uni-ku/root](https://github.com/uni-ku/root)(MIT License,作者 skiyee),核心思路受其启发并进行了重新实现。
|
|
1
|
+
# vite-plugin-uni-root
|
|
2
|
+
|
|
3
|
+
UniApp 虚拟根组件 Vite 插件,零额外依赖,纯字符串操作实现。
|
|
4
|
+
|
|
5
|
+
## 解决的问题
|
|
6
|
+
|
|
7
|
+
UniApp 不支持原生的根组件包裹机制,无法在全局统一注入 `u-config-provider`、`u-toast`、`u-modal` 等需要在每个页面根部存在的组件。传统方案需要在每个页面手动添加,维护成本高。
|
|
8
|
+
|
|
9
|
+
本插件通过 Vite 构建时注入,自动为每个页面包裹 `<global-root-view>` 根组件,无需修改任何页面代码本身。
|
|
10
|
+
|
|
11
|
+
## 技术方案
|
|
12
|
+
|
|
13
|
+
### 架构
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
App.root.vue(源码) → 在 main.ts 中导入并注册为全局组件 global-root-view
|
|
17
|
+
→ 页面模板被包裹 <global-root-view> 页面内容 </global-root-view>
|
|
18
|
+
→ 渲染时页面内容填入 <slot />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 工作流程
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐
|
|
25
|
+
│ main.ts │ → │ 注入 import │ → │ 全局注册组件 │
|
|
26
|
+
│ │ │ + 注册代码 │ │ global-root-view │
|
|
27
|
+
└─────────────┘ └──────────────┘ └─────────────────┘
|
|
28
|
+
│
|
|
29
|
+
┌─────────────┐ ┌──────────────┐ │
|
|
30
|
+
│ page.vue │ → │ 包裹模板 │ │
|
|
31
|
+
│ │ │ <global-...> │ ←─────────┘
|
|
32
|
+
└─────────────┘ └──────────────┘
|
|
33
|
+
│
|
|
34
|
+
┌─────────────┐ │
|
|
35
|
+
│ App.root.vue│ → <slot /> 接收页面内容
|
|
36
|
+
└─────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 三步实现
|
|
40
|
+
|
|
41
|
+
1. **main.ts 注入**(`root.ts -> registerRootApp`)
|
|
42
|
+
- 在 main.ts 中注入 `import GlobalRootView from "./App.root.vue"`
|
|
43
|
+
- 注入 `app.component("global-root-view", GlobalRootView)` 注册为全局组件
|
|
44
|
+
- 注:`GlobalRootView` 仅为局部变量名,实际组件就是 `App.root.vue`
|
|
45
|
+
|
|
46
|
+
2. **页面模板包裹**(`page.ts -> transformPage`)
|
|
47
|
+
- 解析 SFC,定位 `<template>` 标签的起止位置
|
|
48
|
+
- 提取 PageMeta 节点(如有则放到包裹层外部)
|
|
49
|
+
- 将模板内容包裹在 `<global-root-view>...</global-root-view>` 中
|
|
50
|
+
|
|
51
|
+
3. **App.root.vue 加载**(`index.ts -> load`)
|
|
52
|
+
- 拦截 `App.root.vue` 的模块请求,从文件读取并返回
|
|
53
|
+
- 模板中的 `<slot />` 接收并渲染页面内容
|
|
54
|
+
|
|
55
|
+
### 跨端兼容
|
|
56
|
+
|
|
57
|
+
插件是纯 Vite 构建时插件,在 UniApp 编译之前完成源代码转换,不依赖任何平台特定 API,所有 UniApp 支持的平台均可正常工作:
|
|
58
|
+
|
|
59
|
+
| 平台 | 支持 |
|
|
60
|
+
|------|------|
|
|
61
|
+
| H5 | ✅ |
|
|
62
|
+
| 微信小程序 | ✅ |
|
|
63
|
+
| 支付宝小程序 | ✅ |
|
|
64
|
+
| 头条/抖音小程序 | ✅ |
|
|
65
|
+
| Android App | ✅ |
|
|
66
|
+
| iOS App | ✅ |
|
|
67
|
+
| 鸿蒙 App | ✅ |
|
|
68
|
+
|
|
69
|
+
## 使用方式
|
|
70
|
+
|
|
71
|
+
### 1. 创建 App.root.vue
|
|
72
|
+
|
|
73
|
+
在 `src/` 目录下创建 `App.root.vue` 文件:
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
import { onLoad } from '@dcloudio/uni-app';
|
|
78
|
+
import { useTheme, useLocale } from 'uview-pro';
|
|
79
|
+
|
|
80
|
+
const { darkMode, themes, currentTheme } = useTheme();
|
|
81
|
+
const { currentLocale } = useLocale();
|
|
82
|
+
|
|
83
|
+
onLoad(() => {
|
|
84
|
+
console.log('darkMode->', darkMode.value);
|
|
85
|
+
console.log('theme->', currentTheme.value?.name);
|
|
86
|
+
console.log('locale->', currentLocale.value?.name);
|
|
87
|
+
});
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<u-config-provider
|
|
92
|
+
:dark-mode="darkMode"
|
|
93
|
+
:themes="themes"
|
|
94
|
+
:current-theme="currentTheme?.name"
|
|
95
|
+
:current-locale="currentLocale?.name"
|
|
96
|
+
>
|
|
97
|
+
<slot />
|
|
98
|
+
<u-toast global></u-toast>
|
|
99
|
+
<u-modal global></u-modal>
|
|
100
|
+
</u-config-provider>
|
|
101
|
+
</template>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
关键点:`<slot />` 是页面内容的渲染位置,必须保留。
|
|
105
|
+
|
|
106
|
+
### 2. 配置 vite.config.ts
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// npm 安装
|
|
110
|
+
import { UniRoot } from 'uview-pro/plugins';
|
|
111
|
+
|
|
112
|
+
// uni_modules 安装
|
|
113
|
+
import { UniRoot } from './src/uni_modules/uview-pro/plugins';
|
|
114
|
+
|
|
115
|
+
export default defineConfig({
|
|
116
|
+
plugins: [
|
|
117
|
+
UniRoot(), // 放在其他插件之前
|
|
118
|
+
// ...其他插件
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. 运行
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm run dev
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
不需要修改任何页面代码,所有页面自动被 `<global-root-view>` 包裹。
|
|
130
|
+
|
|
131
|
+
## 文件结构
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
src/uni_modules/uview-pro/plugins/root/
|
|
135
|
+
├── index.ts # 插件入口,Vite 插件生命周期
|
|
136
|
+
├── root.ts # main.ts 注入 + App.root.vue 处理
|
|
137
|
+
├── page.ts # 页面模板包裹转换
|
|
138
|
+
└── utils.ts # 工具函数(SFC 解析、pages.json 加载、AST 查找等)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 配置项
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
UniRoot({
|
|
145
|
+
rootFileName: 'App.root', // 根组件文件名(不含扩展名),默认 App.root
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## HMR 支持
|
|
150
|
+
|
|
151
|
+
修改 `pages.json` 后自动重载页面列表,无需手动重启开发服务器。
|
|
152
|
+
|
|
153
|
+
## 注意事项
|
|
154
|
+
|
|
155
|
+
- Vue 版本需 >= 3.2.13(使用 `vue/compiler-sfc` 的 `parse` API,HBuilderX 环境下自动降级为正则匹配)
|
|
156
|
+
- 页面模板中如有 `<page-meta>` 组件,会被提取到包裹层外部,确保微信小程序兼容
|
|
157
|
+
- 页面模板中如有嵌套 `<template #slot>` 具名插槽,不影响根模板的正确识别
|
|
158
|
+
|
|
159
|
+
## 致谢
|
|
160
|
+
|
|
161
|
+
本插件的实现参考了 [@uni-ku/root](https://github.com/uni-ku/root)(MIT License,作者 skiyee),核心思路受其启发并进行了重新实现。
|
|
@@ -23,8 +23,6 @@ module.exports = __toCommonJS(page_exports);
|
|
|
23
23
|
var import_utils = require("./utils.cjs");
|
|
24
24
|
async function transformPage(code) {
|
|
25
25
|
const sfc = await (0, import_utils.parseSFC)(code);
|
|
26
|
-
if (!sfc.template)
|
|
27
|
-
return null;
|
|
28
26
|
const openMatch = code.match(/<template\b[^>]*>/);
|
|
29
27
|
if (!openMatch)
|
|
30
28
|
return null;
|
|
@@ -33,23 +31,43 @@ async function transformPage(code) {
|
|
|
33
31
|
return null;
|
|
34
32
|
const openTagEnd = openMatch.index + openMatch[0].length;
|
|
35
33
|
const content = code.slice(openTagEnd, closeTagStart);
|
|
36
|
-
const pageMetaNode = (0, import_utils.findNode)(sfc, "PageMeta");
|
|
37
34
|
let pageMetaSource = "";
|
|
38
35
|
let newContent = content;
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
if (sfc == null ? void 0 : sfc.template) {
|
|
37
|
+
const pageMetaNode = (0, import_utils.findNode)(sfc, "PageMeta");
|
|
38
|
+
if (pageMetaNode) {
|
|
39
|
+
pageMetaSource = pageMetaNode.loc.source;
|
|
40
|
+
const metaStart = pageMetaNode.loc.start.offset;
|
|
41
|
+
const metaEnd = pageMetaNode.loc.end.offset;
|
|
42
|
+
const metaStartInContent = metaStart - openTagEnd;
|
|
43
|
+
const metaEndInContent = metaEnd - openTagEnd;
|
|
44
|
+
newContent = content.slice(0, metaStartInContent) + content.slice(metaEndInContent);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
const pageMetaMatch = content.match(/<page-meta\b[^>]*>[\s\S]*?<\/page-meta>/);
|
|
48
|
+
if (pageMetaMatch) {
|
|
49
|
+
pageMetaSource = pageMetaMatch[0];
|
|
50
|
+
newContent = content.replace(pageMetaMatch[0], "");
|
|
51
|
+
} else {
|
|
52
|
+
const selfClosingMatch = content.match(/<page-meta\b[^>]*\/>/);
|
|
53
|
+
if (selfClosingMatch) {
|
|
54
|
+
pageMetaSource = selfClosingMatch[0];
|
|
55
|
+
newContent = content.replace(selfClosingMatch[0], "");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
const wrappedContent = `
|
|
48
60
|
${pageMetaSource}
|
|
49
61
|
<global-root-view>${newContent}</global-root-view>
|
|
50
62
|
`;
|
|
51
63
|
return {
|
|
52
|
-
code: code.slice(0, openTagEnd) + wrappedContent + code.slice(closeTagStart)
|
|
64
|
+
code: code.slice(0, openTagEnd) + wrappedContent + code.slice(closeTagStart),
|
|
65
|
+
map: {
|
|
66
|
+
version: 3,
|
|
67
|
+
sources: [],
|
|
68
|
+
names: [],
|
|
69
|
+
mappings: ""
|
|
70
|
+
}
|
|
53
71
|
};
|
|
54
72
|
}
|
|
55
73
|
// Annotate the CommonJS export names for ESM import in node:
|