最近做的uniapp项目中,发现uniapp的App.vue并没有template作为根目录,按照uniapp官网的说法,app.vue并不是页面,而是用来监听应用的生命周期、做一些全局样式和一些全局的变量(globalData),所以你在这里写template是没有用的。
那么问题来了,没有根目录那就没办法写全局组件了,在uniapp这种项目中,就得每个页面都去引入组件,那这样的话就会有重复的代码一直复制粘贴了。为了解决这个问题,我找到了两种办法,一种是使用插件在每个组件中都导入一遍对应的全局组件,一种是通过插件虚拟出根组件来,然后将全局会用到的组件放进来,使之成为一个真正的全局组件。
现在着重讲一下第二种办法,我们将会用到@uni-ku/root。(背景,本项目是由cli创建)
首先进行插件依赖的安装
bash
npm install @uni-ku/root -D
然后,修改 vite.config.js 的配置
javascript
import { defineConfig } from 'vite'
import uni from '@dcloudio/vite-plugin-uni'
import AutoImport from 'unplugin-auto-import/vite'
import UniKuRoot from '@uni-ku/root'
// https://vitejs.dev/config/
export default defineConfig({
...
plugins: [
UniKuRoot(), // 用来"虚拟"出一个根组件,让 uni-app 也能像 Vue 一样使用 App.vue,也就是能够用来做一些全局的组件挂载使用等操作
uni(),
AutoImport({
imports: ['vue', 'pinia'],
}),
],
....
})
然后在 src 下创建App.ku.vue,内容如下:
html
<template>
<KuRootView />
<!-- 以下会组件被全局插入 -->
<!-- Toast轻提示,需要引入@/components/toast/useToast.js然后再使用showToast打开 -->
<Toast />
</template>
<script setup>
import Toast from '@/components/toast/index.vue'
</script>
<KuRootView /> 类似vuerouter的routerview标签,但是在uniapp中这个并不一样,比如vuerouter里面有各种生命周期等等。
toast.vue代码如下
html
<template>
<view v-if="visible" class="toast-mask" :class="{ 'can-penetrate': maskCanPenetrate }">
<view class="toast-box" @tap.stop>
<!-- 图标 -->
<view v-if="iconType" class="toast-icon-box">
<svg v-if="iconType === 'success'" class="success-icon toast-icon"><use href="#SelectCircleFilled"></use></svg>
<svg v-else-if="iconType === 'fail'" class="fail-icon toast-icon"><use href="#CloseCircleFilled"></use></svg>
</view>
<!-- 文案 -->
<text class="toast-title">{{ title }}</text>
</view>
</view>
</template>
<script setup>
import { useToast } from './useToast'
const { visible, title, iconType, maskCanPenetrate, showToast, hideToast } = useToast()
/* 导出给外部调用 */
defineExpose({ showToast, hideToast })
</script>
<style lang="scss" scoped>
.toast-mask {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 9999;
overflow: hidden;
.can-penetrate {
pointer-events: none;
}
}
.toast-box {
background-color: #fff;
box-shadow: 0 8rpx 32rpx 0 rgba(0, 0, 0, 0.1);
border-radius: 1998rpx 1998rpx 1998rpx 1998rpx;
text-align: center;
position: absolute;
padding: 24rpx 48rpx;
top: 112rpx;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
.toast-icon-box {
display: flex;
align-items: center;
.toast-icon {
margin-right: 16rpx;
width: 32rpx;
height: 32rpx;
&.success-icon {
color: $G6;
}
&.fail-icon {
color: $R6;
}
}
}
}
.toast-title {
font-size: 32rpx;
line-height: 32rpx;
color: $T10;
}
</style>
useToast.js
javascript
const visible = ref(false) // toast开关
const title = ref('') // 内容
const iconType = ref('fail') // icon: success / fail / ''
const toastDuration = ref(2000) // 默认延迟2000ms关闭
const maskCanPenetrate = ref(true) // 是否可以点击穿透(有宽高100%的蒙层存在),默认可以
let timer = null
export const useToast = () => {
// 打开toast
const showToast = ({ type = 'success', text = '', duration = 2000, canPenetrate = true } = {}) => {
if (timer) clearTimeout(timer)
visible.value = true
iconType.value = type
title.value = text
toastDuration.value = duration
maskCanPenetrate.value = canPenetrate
if (toastDuration.value > 0) {
timer = setTimeout(() => {
hideToast()
}, toastDuration.value)
}
}
// 关闭toast
const hideToast = () => {
visible.value = false
console.log('hideToast')
if (timer) clearTimeout(timer)
timer = null
}
return {
visible,
title,
iconType,
toastDuration,
maskCanPenetrate,
showToast,
hideToast,
}
}
onUnmounted(() => clearTimeout(timer))
现在toast就变成了全局的,我们只需要在需要使用到toast的页面引入useToast.js,然后操作里面的方法就可以打开了
html
<template>
<button @click="showToast">打开toast</button>
</template>
<script setup >
import { useToast } from '@/components/toast/useToast.js'
const { showToast } = useToast()
</script>
<style lang="scss" scoped>
// ....
</style>
这样就解决了在uniapp开发过程中一些全局组件的重复引入的问题了,代码也美观一些,更容易理解。