前言
通过 Nuxt 实战系列的上节《【Nuxt 实战】01-初始化项目、新增组件库、图标库》,我们完成对 Nuxt 项目的初始化,并增加 UI 组件库和图标库。
这一小节,我们在 Nuxt 项目中集成 Pinia 、增加错误页面 、新增导航栏 、实现暗黑效果 、增加一个全局插件。
集成 Pinia
安装 Pinia
在 Nuxt 项目中集成 Pinia 非常简单,直接在 Nuxt Modules 页面搜索 Pinia 找到下图中的两个模块,把它们添加到 nuxt.config.ts
文件中的 modules
模块中。
在项目命令行终端输入 pnpm add pinia @pinia/nuxt pinia-plugin-persistedstate
。
然后把它们添加到 nuxt.config.ts
文件中,关于更多 @pinia/nuxt
的配置,可以参考 Pinia 官网中针对 Nuxt 的配置说明。
ts
modules: [
[
'@pinia/nuxt',
{
autoImports: [
// 自动引入 `defineStore(), storeToRefs()`
'defineStore',
'storeToRefs'
]
}
],
'pinia-plugin-persistedstate/nuxt'
]
应用 Pinia
在项目根目录新建 store
目录,然后在其文件夹中新增 user.ts
文件,例如新增如下内容。
ts
export const useUser = defineStore('user', {
state: () => ({
isLogin: false,
hello: false,
userName: '小熊'
}),
actions: {
login() {
this.isLogin = true
},
logout() {
this.isLogin = false
this.hello = false
}
},
persist: true
})
接下来模拟登录和退出登录的效果,实现对 Pinia 的应用。
在项目根目录中创建 pages/login/index.vue
页面。
ts
<template>
<div @click="handleLogin">登录</div>
</template>
<script setup lang="ts">
import { useUser } from '~/store/user'
const router = useRouter()
const store = useUser()
const { isLogin } = storeToRefs(store)
const handleLogin = () => {
store.login()
router.push('/')
}
if (isLogin.value) {
router.push('/')
}
</script>
修改项目根目录中的 pages/index.vue
文件中的内容。
ts
<template>
<div>Index Page</div>
<NuxtLink to="/mine">个人中心</NuxtLink>
<div @click="handleExit">退出登录</div>
</template>
<script setup lang="ts">
import { ElNotification } from 'element-plus'
import { useUser } from '~/store/user'
const router = useRouter()
const store = useUser()
const { isLogin, hello, userName } = storeToRefs(store)
console.log('object', isLogin.value)
if (!isLogin.value) {
navigateTo('/login')
}
if (!hello.value) {
ElNotification({
title: '你好' + userName.value,
message: '欢迎光临,前端庄园技术社区'
})
hello.value = true
}
const handleExit = () => {
store.logout()
router.push('/login')
}
</script>
实现效果如下,右侧弹框中成功读取存储在 store
中的用户信息。
代码提交记录
增加错误页面
在项目根目录新建 error.vue
页面,当页面发生错误的时候,如找不到页面的 404 错误,或者服务端报错等信息,可以在此页面中展示相关信息。
ts
<template>
<div class="min-h-screen bg-gray-100 flex flex-col items-center justify-center p-4">
<div class="bg-white rounded-lg shadow-xl p-8 max-w-md w-full text-center">
<Icon name="lucide:circle-alert" class="text-red-500 mb-4" size="80" />
<h1 class="text-4xl font-bold text-gray-800 mb-2">{{ error?.statusCode }}</h1>
<p class="text-xl text-gray-600 mb-8">{{ error?.message }}</p>
<div>
<el-button type="warning" plain round @click="retry">重试</el-button>
<el-button round @click="handleError">返回首页</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
error: Object
})
console.log(props.error)
const retry = () => (window.location.href = props.error!.url)
const handleError = () => clearError({ redirect: '/' })
</script>
例如,在浏览器页面访问 http://localhost:3000/article 一个不存在的页面,此时就会显示相关的错误信息。
代码提交记录
新增导航栏
在项目根目录中的 components
文件夹下新建 NavBar.vue
导航栏文件。
ts
<template>
<div class="border-b-1 border-zinc-200">
<div class="flex justify-between items-center max-w-[1200px] m-auto h-[60px]">
<div class="h-[40px] flex items-center cursor-pointer">
<img src="/assets/images/logo.png" class="w-full h-full" />
</div>
<div class="pr-2 flex items-center">
<UButton icon="lucide:sun" size="xl" variant="ghost" class="cursor-pointer" color="neutral" @click="handleLight" />
<UButton icon="lucide:moon" size="xl" variant="ghost" class="cursor-pointer" color="neutral" @click="handleDark" />
<UButton icon="ion:extension-puzzle-outline" size="xl" variant="ghost" class="cursor-pointer" color="neutral" />
<UButton icon="ion:notifications" size="xl" variant="ghost" class="cursor-pointer" color="neutral" />
<UButton icon="logos:github-icon" size="xl" variant="ghost" class="cursor-pointer" color="neutral" to="https://www.baidu.com" target="_black" />
<UDropdownMenu :items="items" class="w-48">
<UAvatar src="https://github.com/benjamincanac.png" class="ml-3 cursor-pointer" />
</UDropdownMenu>
</div>
</div>
</div>
</template>
<script setup>
const colorMode = useColorMode()
const currentMode = ref('')
currentMode.value = colorMode.preference
// colorMode.value = 'light'
console.log('colorMode', colorMode)
const handleLight = () => {
colorMode.preference = 'light'
colorMode.value = 'light'
}
const handleDark = () => {
colorMode.preference = 'dark'
colorMode.value = 'dark'
}
const items = ref([
[
{
label: 'Profile',
icon: 'i-heroicons-user'
},
{
label: 'Billing',
icon: 'i-heroicons-credit-card'
},
{
label: 'Settings',
icon: 'i-heroicons-cog',
kbds: [',']
},
{
label: 'Keyboard shortcuts',
icon: 'i-heroicons-computer-desktop'
}
],
[
{
label: 'Team',
icon: 'i-heroicons-users'
},
{
label: 'Invite users',
icon: 'i-heroicons-user-plus',
children: [
[
{
label: 'Email',
icon: 'i-heroicons-envelope'
},
{
label: 'Message',
icon: 'i-heroicons-chat-bubble-left'
}
],
[
{
label: 'More',
icon: 'i-heroicons-plus-circle'
}
]
]
},
{
label: 'New team',
icon: 'i-heroicons-plus',
kbds: ['meta', 'n']
}
],
[
{
label: 'GitHub',
icon: 'i-simple-icons-github',
to: 'https://github.com/nuxt/ui',
target: '_blank'
},
{
label: 'Support',
icon: 'i-heroicons-lifebuoy',
to: '/components/dropdown-menu'
},
{
label: 'API',
icon: 'i-heroicons-cloud',
disabled: true
}
],
[
{
label: 'Logout',
icon: 'i-heroicons-arrow-right-on-rectangle',
kbds: ['shift', 'meta', 'q']
}
]
])
</script>
值得注意的是,其中 UDropdownMenu
组件使用的是 Nuxt UI 中的 DropdownMenu 组件。
而实现暗黑效果 使用的是 Nuxt Modules 中的 @nuxtjs/color-mode 模块,安装方法和上述介绍的安装 Nuxt 模块相同,参考文档安装即可。
图标使用的是 icones 官网中的图标,具体使用方法在上一节文章《【Nuxt 实战】01-初始化项目、新增组件库、图标库》中已经介绍过。
新增后的导航栏目前的效果是这样的,后期再继续优化。
代码提交记录
新增全局插件
封装插件
在项目根目录 plugins
文件夹内新增 toast.ts
文件,Nuxt 会自动识别这是一个插件,我们就可以在全局使用此插件实现对应的业务。
我们针对 Nuxt UI 中的 Toast 组件封装一个全局插件,用于在页面中使用。
ts
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('app:created', (vueApp) => {
vueApp.config.globalProperties.$toast = (title: string, description?: string, color: string = 'success') => {
const toast = useToast()
toast.add({ title, description, color: color })
}
})
})
使用插件
值得注意的是,使用 Nuxt UI 中的 Toast 组件,首先需要在 app.vue 文件中引用 App 组件,这样我们就可以配置 Toast 组件的位置。
ts
<template>
<UApp :toaster="{ position: 'top-right', duration: 3000 }">
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</UApp>
</template>
在 pages/index.vue
文件中使用插件。
ts
<template>
<div>Index Page</div>
<NuxtLink to="/mine" class="text-4xl font-bold text-yellow-700">个人中心</NuxtLink>
<div @click="handleExit">退出登录</div>
</template>
<script setup lang="ts">
useHead({
title: '首页'
})
const ins = getCurrentInstance()
const router = useRouter()
const store = useUser()
const { isLogin, hello, userName } = storeToRefs(store)
const appConfig = useAppConfig()
if (!isLogin.value) {
navigateTo('/login')
}
if (!hello.value) {
ins?.proxy?.$toast('你好' + userName.value, appConfig.helloMessage)
hello.value = true
}
const handleExit = () => {
store.logout()
router.push('/login')
ins?.proxy?.$toast('退出登录成功', '', 'info')
}
</script>
结束语
本节,我们实现了在项目中集成 Pinia、新增导航栏并实现暗黑模式,以及新建错误页面、新增插件并应用在项目中,接下来就是实现用户登录、新增 TipTap 编辑器、连接数据库等操作。