示例项目
通过实际示例学习如何使用 Root
本文档通过实际示例展示如何在不同场景下使用 Root 解决 Uniapp 开发中的问题。每个示例都包含完整的代码实现和详细说明,帮助您快速掌握 Root 的使用方法。
示例项目结构
所有示例项目都遵循以下基本结构:
example-project/
├── src/
│ ├── App.ku.vue # 虚拟根组件
│ ├── main.ts # 应用入口文件
│ ├── pages/ # 页面目录
│ │ └── index.vue # 首页
│ ├── components/ # 组件目录
│ │ └── GlobalToast.vue # 全局组件示例
│ └── composables/ # 组合式 API 目录
│ └── useToast.ts # Toast 组合式 API
├── vite.config.ts # Vite 配置文件
└── package.json # 项目依赖
示例一:全局共享 Toast 组件
Toast 是移动应用中常见的 UI 组件,用于显示简短的消息提示。通过 Root,我们可以轻松实现一个全局共享的 Toast 组件,在应用的任何页面中调用。
实现目标
- 创建一个全局 Toast 组件
- 在任何页面中显示和隐藏 Toast
- 支持自定义 Toast 内容和样式
实现步骤
1. 创建 Toast 组件
首先,创建一个 Toast 组件:
<!-- src/components/GlobalToast.vue -->
<script setup lang="ts">
import { useToast } from '@/composables/useToast'
const { toastState, toastMessage, hideToast } = useToast()
</script>
<template>
<transition name="toast-fade">
<div v-if="toastState" class="toast-overlay" @click="hideToast">
<div class="toast-container" @click.stop>
<div class="toast-icon">ℹ️</div>
<div class="toast-message">{{ toastMessage }}</div>
</div>
</div>
</transition>
</template>
<style scoped>
.toast-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.toast-container {
background: white;
border-radius: 8px;
padding: 16px 24px;
max-width: 80%;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
animation: toast-slide-in 0.3s ease-out;
}
.toast-icon {
font-size: 24px;
margin-right: 12px;
}
.toast-message {
font-size: 16px;
color: #333;
line-height: 1.4;
}
.toast-fade-enter-active,
.toast-fade-leave-active {
transition: opacity 0.3s;
}
.toast-fade-enter-from,
.toast-fade-leave-to {
opacity: 0;
}
@keyframes toast-slide-in {
from {
transform: translateY(-20px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
</style>
2. 实现 Toast 组合式 API
创建一个组合式 API 来管理 Toast 的状态和行为:
// src/composables/useToast.ts
import { ref } from 'vue'
const toastState = ref(false)
const toastMessage = ref('')
export function useToast() {
function showToast(message: string, duration: number = 3000) {
toastMessage.value = message
toastState.value = true
// 自动隐藏
setTimeout(() => {
hideToast()
}, duration)
}
function hideToast() {
toastState.value = false
}
return {
toastState,
toastMessage,
showToast,
hideToast
}
}
3. 挂载至 App.ku.vue
在虚拟根组件中引入并使用 Toast 组件:
<!-- src/App.ku.vue -->
<script setup lang="ts">
import GlobalToast from '@/components/GlobalToast.vue'
</script>
<template>
<!-- 页面内容将渲染在这里 -->
<KuRootView />
<!-- 全局 Toast 组件 -->
<GlobalToast />
</template>
4. 在页面中调用 Toast
现在,您可以在任何页面中使用 Toast:
<!-- src/pages/index.vue -->
<script setup lang="ts">
import { useToast } from '@/composables/useToast'
const { showToast } = useToast()
function showSuccessToast() {
showToast('操作成功!', 2000)
}
function showErrorToast() {
showToast('操作失败,请重试', 3000)
}
function showCustomToast() {
showToast('这是一条自定义时长的提示消息', 5000)
}
</script>
<template>
<view class="container">
<text>Toast 示例</text>
<button @click="showSuccessToast">显示成功提示</button>
<button @click="showErrorToast">显示错误提示</button>
<button @click="showCustomToast">显示自定义时长提示</button>
</view>
</template>
<style scoped>
.container {
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
button {
padding: 10px 16px;
background-color: #007aff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
}
</style>
扩展功能
您可以根据需要扩展 Toast 组件的功能:
1. 支持不同类型的 Toast
// src/composables/useToast.ts
type ToastType = 'info' | 'success' | 'warning' | 'error'
const toastType = ref<ToastType>('info')
export function useToast() {
function showToast(message: string, type: ToastType = 'info', duration: number = 3000) {
toastMessage.value = message
toastType.value = type
toastState.value = true
setTimeout(() => {
hideToast()
}, duration)
}
// ... 其他代码
}
2. 在组件中根据类型显示不同样式
<!-- src/components/GlobalToast.vue -->
<script setup lang="ts">
import { useToast } from '@/composables/useToast'
const { toastState, toastMessage, toastType, hideToast } = useToast()
// 根据类型获取图标
const getIcon = (type: string) => {
switch (type) {
case 'success': return '✅'
case 'warning': return '⚠️'
case 'error': return '❌'
default: return 'ℹ️'
}
}
</script>
<template>
<transition name="toast-fade">
<div v-if="toastState" class="toast-overlay" @click="hideToast">
<div class="toast-container" :class="`toast-${toastType}`" @click.stop>
<div class="toast-icon">{{ getIcon(toastType) }}</div>
<div class="toast-message">{{ toastMessage }}</div>
</div>
</div>
</transition>
</template>
<style scoped>
/* ... 原有样式 ... */
.toast-success .toast-container {
border-left: 4px solid #4caf50;
}
.toast-warning .toast-container {
border-left: 4px solid #ff9800;
}
.toast-error .toast-container {
border-left: 4px solid #f44336;
}
</style>
示例二:全局共享 ConfigProvider
ConfigProvider 是一种常见的设计模式,用于为整个应用提供全局配置,如主题、语言、组件默认值等。通过 Root,我们可以轻松实现一个全局的 ConfigProvider。
实现目标
- 创建一个全局 ConfigProvider 组件
- 支持主题切换功能
- 在任何页面中访问和修改主题配置
实现步骤
1. 定义主题类型和配置
首先,定义主题相关的类型和默认配置:
// src/types/theme.ts
export type ThemeMode = 'light' | 'dark'
export interface ThemeVars {
primaryColor?: string
secondaryColor?: string
backgroundColor?: string
textColor?: string
borderColor?: string
borderRadius?: string
}
export interface ThemeConfig {
mode: ThemeMode
vars: ThemeVars
}
2. 实现主题组合式 API
创建一个管理主题的组合式 API:
// src/composables/useTheme.ts
import { ref, computed, watch } from 'vue'
import type { ThemeMode, ThemeVars, ThemeConfig } from '@/types/theme'
// 默认主题变量
const defaultThemeVars: ThemeVars = {
primaryColor: '#007aff',
secondaryColor: '#5ac8fa',
backgroundColor: '#ffffff',
textColor: '#000000',
borderColor: '#e5e5e5',
borderRadius: '8px'
}
// 暗色主题变量
const darkThemeVars: ThemeVars = {
primaryColor: '#0a84ff',
secondaryColor: '#64d2ff',
backgroundColor: '#000000',
textColor: '#ffffff',
borderColor: '#38383a',
borderRadius: '8px'
}
const themeMode = ref<ThemeMode>('light')
const themeVars = ref<ThemeVars>({ ...defaultThemeVars })
// 计算当前主题配置
const currentTheme = computed<ThemeConfig>(() => ({
mode: themeMode.value,
vars: themeVars.value
}))
// 应用主题变量到 CSS 变量
function applyThemeVars(vars: ThemeVars) {
const root = document.documentElement
Object.entries(vars).forEach(([key, value]) => {
if (value !== undefined) {
const cssVarName = `--theme-${kebabCase(key)}`
root.style.setProperty(cssVarName, value)
}
})
}
// 转换为 kebab-case
function kebabCase(str: string) {
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}
// 切换主题模式
function toggleTheme(mode?: ThemeMode) {
const newMode = mode || (themeMode.value === 'light' ? 'dark' : 'light')
themeMode.value = newMode
// 更新主题变量
if (newMode === 'dark') {
themeVars.value = { ...darkThemeVars, ...themeVars.value }
} else {
themeVars.value = { ...defaultThemeVars, ...themeVars.value }
}
// 应用主题变量
applyThemeVars(themeVars.value)
// 保存到本地存储
localStorage.setItem('theme-mode', newMode)
}
// 更新主题变量
function updateThemeVars(vars: Partial<ThemeVars>) {
themeVars.value = { ...themeVars.value, ...vars }
applyThemeVars(themeVars.value)
// 保存到本地存储
localStorage.setItem('theme-vars', JSON.stringify(themeVars.value))
}
// 初始化主题
function initTheme() {
// 从本地存储恢复主题设置
const savedMode = localStorage.getItem('theme-mode') as ThemeMode
const savedVars = localStorage.getItem('theme-vars')
if (savedMode) {
themeMode.value = savedMode
}
if (savedVars) {
try {
const parsedVars = JSON.parse(savedVars)
themeVars.value = { ...themeVars.value, ...parsedVars }
} catch (e) {
console.error('Failed to parse theme vars from localStorage', e)
}
}
// 应用主题变量
applyThemeVars(themeVars.value)
}
// 监听主题模式变化
watch(themeMode, (newMode) => {
if (newMode === 'dark') {
themeVars.value = { ...darkThemeVars, ...themeVars.value }
} else {
themeVars.value = { ...defaultThemeVars, ...themeVars.value }
}
applyThemeVars(themeVars.value)
})
export function useTheme(initialVars?: ThemeVars) {
// 初始化主题变量
if (initialVars) {
updateThemeVars(initialVars)
}
return {
themeMode,
themeVars,
currentTheme,
toggleTheme,
updateThemeVars,
initTheme
}
}
3. 创建 ConfigProvider 组件
创建一个 ConfigProvider 组件来包裹整个应用:
<!-- src/components/ConfigProvider.vue -->
<script setup lang="ts">
import { provide, onMounted } from 'vue'
import { useTheme } from '@/composables/useTheme'
import type { ThemeConfig } from '@/types/theme'
// 初始化主题
const { currentTheme, initTheme } = useTheme()
// 提供主题配置给子组件
provide('themeConfig', currentTheme)
// 组件挂载时初始化主题
onMounted(() => {
initTheme()
})
</script>
<template>
<div class="config-provider" :class="`theme-${currentTheme.mode}`">
<slot />
</div>
</template>
<style>
/* 全局主题变量 */
:root {
--theme-primary-color: #007aff;
--theme-secondary-color: #5ac8fa;
--theme-background-color: #ffffff;
--theme-text-color: #000000;
--theme-border-color: #e5e5e5;
--theme-border-radius: 8px;
}
/* 暗色主题 */
.theme-dark {
color: var(--theme-text-color);
background-color: var(--theme-background-color);
}
</style>
4. 挂载至 App.ku.vue
在虚拟根组件中使用 ConfigProvider:
<!-- src/App.ku.vue -->
<script setup lang="ts">
import { useTheme } from '@/composables/useTheme'
import ConfigProvider from '@/components/ConfigProvider.vue'
// 初始化主题
const { themeMode, toggleTheme } = useTheme({
primaryColor: '#07c160'
})
</script>
<template>
<ConfigProvider>
<div class="app-root" :class="`theme-${themeMode}`">
<div class="app-header">
<h1>Root 主题示例</h1>
<button @click="toggleTheme()">
切换主题 (当前: {{ themeMode }})
</button>
</div>
<!-- 页面内容将渲染在这里 -->
<KuRootView />
</div>
</ConfigProvider>
</template>
<style scoped>
.app-root {
min-height: 100vh;
transition: background-color 0.3s, color 0.3s;
}
.app-header {
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--theme-border-color);
}
button {
padding: 8px 16px;
background-color: var(--theme-primary-color);
color: white;
border: none;
border-radius: var(--theme-border-radius);
cursor: pointer;
}
</style>
5. 在页面中使用主题
现在,您可以在任何页面中使用和修改主题:
<!-- src/pages/theme-demo.vue -->
<script setup lang="ts">
import { inject, ref } from 'vue'
import type { ThemeConfig } from '@/types/theme'
// 注入主题配置
const themeConfig = inject('themeConfig') as ThemeConfig
// 自定义主题变量
const customPrimaryColor = ref('#07c160')
function applyCustomColor() {
// 通过根组件实例修改主题
const pagesStack = getCurrentPages()
if (pagesStack.length > 0) {
const rootInstance = pagesStack[pagesStack.length - 1].$vm
if (rootInstance && rootInstance.$refs.uniKuRoot) {
rootInstance.$refs.uniKuRoot.updateThemeVars({
primaryColor: customPrimaryColor.value
})
}
}
}
</script>
<template>
<view class="theme-demo">
<text>当前主题: {{ themeConfig.mode }}</text>
<text>主色调: {{ themeConfig.vars.primaryColor }}</text>
<view class="color-picker">
<text>自定义主色调:</text>
<input v-model="customPrimaryColor" type="color" />
<button @click="applyCustomColor">应用</button>
</view>
<view class="demo-components">
<button class="primary-btn">主要按钮</button>
<button class="secondary-btn">次要按钮</button>
</view>
</view>
</template>
<style scoped>
.theme-demo {
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.color-picker {
display: flex;
align-items: center;
gap: 8px;
}
.demo-components {
display: flex;
gap: 12px;
}
.primary-btn {
padding: 10px 16px;
background-color: var(--theme-primary-color);
color: white;
border: none;
border-radius: var(--theme-border-radius);
}
.secondary-btn {
padding: 10px 16px;
background-color: var(--theme-secondary-color);
color: white;
border: none;
border-radius: var(--theme-border-radius);
}
</style>
示例三:使用 Wot 组件库的 Toast 和 Notify
Wot Design Uni 是一个流行的 Uniapp 组件库,它提供了丰富的 UI 组件。通过 Root,我们可以更好地集成和使用这些组件,特别是那些需要全局挂载的组件。
实现目标
- 集成 Wot Design Uni 组件库
- 全局挂载 Toast 和 Notify 组件
- 在页面中方便地调用这些组件
实现步骤
1. 安装 Wot Design Uni
首先,安装 Wot Design Uni:
# 使用 npm
npm install wot-design-uni
# 使用 yarn
yarn add wot-design-uni
# 使用 pnpm
pnpm add wot-design-uni
2. 配置 Wot Design Uni
在 main.ts
中配置 Wot Design Uni:
// src/main.ts
import { createSSRApp } from 'vue'
import App from './App.vue'
import WotDesignUni from 'wot-design-uni'
export function createApp() {
const app = createSSRApp(App)
app.use(WotDesignUni)
return {
app
}
}
3. 挂载 Wot 组件至 App.ku.vue
在虚拟根组件中挂载 Wot 的 Toast 和 Notify 组件:
<!-- src/App.ku.vue -->
<script setup lang="ts">
// 可以在这里添加一些全局逻辑
</script>
<template>
<!-- 页面内容将渲染在这里 -->
<KuRootView />
<!-- 全局挂载 Wot 组件 -->
<wd-toast />
<wd-notify />
</template>
4. 在页面中使用 Wot 组件
现在,您可以在任何页面中使用 Wot 的 Toast 和 Notify 组件:
<!-- src/pages/wot-demo.vue -->
<script setup lang="ts">
import { useToast, useNotify } from 'wot-design-uni'
const toast = useToast()
const notify = useNotify()
function showToast() {
toast.show('这是一条 Toast 消息')
}
function showSuccessToast() {
toast.success('操作成功')
}
function showErrorToast() {
toast.error('操作失败')
}
function showNotify() {
notify({
message: '这是一条通知消息',
type: 'success',
duration: 3000
})
}
function showWarningNotify() {
notify({
message: '这是一条警告通知',
type: 'warning'
})
}
function showErrorNotify() {
notify({
message: '这是一条错误通知',
type: 'error'
})
}
</script>
<template>
<view class="wot-demo">
<text>Wot Design Uni 组件示例</text>
<view class="section">
<text>Toast 示例</text>
<button @click="showToast">显示 Toast</button>
<button @click="showSuccessToast">显示成功 Toast</button>
<button @click="showErrorToast">显示错误 Toast</button>
</view>
<view class="section">
<text>Notify 示例</text>
<button @click="showNotify">显示通知</button>
<button @click="showWarningNotify">显示警告通知</button>
<button @click="showErrorNotify">显示错误通知</button>
</view>
</view>
</template>
<style scoped>
.wot-demo {
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
}
.section {
display: flex;
flex-direction: column;
gap: 12px;
}
text {
font-size: 16px;
font-weight: bold;
}
button {
padding: 10px 16px;
background-color: #1989fa;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
}
</style>
高级用法
1. 封装全局方法
您可以在根组件中封装一些全局方法,方便在页面中调用:
<!-- src/App.ku.vue -->
<script setup lang="ts">
import { ref } from 'vue'
// 封装全局方法
function showGlobalToast(message: string, type = 'info') {
// 这里可以使用任何 Toast 实现
console.log(`Global Toast: ${message} (${type})`)
// 如果使用 Wot 的 Toast
// const toast = useToast()
// toast[type](message)
}
function showGlobalNotify(message: string, type = 'info') {
// 这里可以使用任何 Notify 实现
console.log(`Global Notify: ${message} (${type})`)
// 如果使用 Wot 的 Notify
// const notify = useNotify()
// notify({ message, type })
}
// 暴露给页面使用
defineExpose({
showGlobalToast,
showGlobalNotify
})
</script>
<template>
<KuRootView />
<wd-toast />
<wd-notify />
</template>
2. 在页面中调用全局方法
<!-- src/pages/global-methods.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const rootInstance = ref()
onMounted(() => {
// 获取根组件实例
const pagesStack = getCurrentPages()
if (pagesStack.length > 0) {
rootInstance.value = pagesStack[pagesStack.length - 1].$vm.$refs.uniKuRoot
}
})
function callGlobalToast() {
if (rootInstance.value) {
rootInstance.value.showGlobalToast('这是全局 Toast', 'success')
}
}
function callGlobalNotify() {
if (rootInstance.value) {
rootInstance.value.showGlobalNotify('这是全局通知', 'warning')
}
}
</script>
<template root="rootInstance">
<view class="global-methods">
<text>全局方法调用示例</text>
<button @click="callGlobalToast">调用全局 Toast</button>
<button @click="callGlobalNotify">调用全局 Notify</button>
</view>
</template>
<style scoped>
.global-methods {
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
button {
padding: 10px 16px;
background-color: #07c160;
color: white;
border: none;
border-radius: 4px;
}
</style>
示例总结
通过以上三个示例,我们展示了 Root 在不同场景下的应用:
- 全局共享 Toast 组件:展示了如何创建和使用全局共享的 UI 组件,解决了 Uniapp 中无法在根级别挂载组件的问题。
- 全局共享 ConfigProvider:展示了如何实现全局主题管理,包括主题切换、主题变量自定义等功能,为应用提供了一致的外观和体验。
- 使用 Wot 组件库的 Toast 和 Notify:展示了如何与第三方组件库集成,充分利用 Root 的虚拟根组件特性,更好地使用这些组件。
这些示例涵盖了 Root 的主要功能和使用场景,您可以根据实际项目需求进行调整和扩展。通过合理使用 Root,您可以构建更加灵活、可维护的 Uniapp 应用。
更多示例和最佳实践,请参考 GitHub 仓库 中的示例项目。