【Nuxt 实战】02-集成 Pinia、新增导航栏、暗黑模式

前言

通过 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 编辑器、连接数据库等操作。

相关推荐
m0_7482361121 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo61733 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489435 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-8 小时前
验证码机制
前端·后端
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js