1
头图

Nuxt UI 提供了一组使用 Tailwind CSS 和 Headless UI 构建的 Vue 组件和 Composables。它的目标是在构建 Nuxt 应用程序时提供与 UI 相关的所有内容,并且能完全样式化和可定制。

这里有几个先进的概念,是旧时代的前端开发所不知道的。

  • Headless UI

    "Bring your own UI". 组件库仅仅封装行为,无任何风格,看起来就像裸 HTML 组件,但是有更多种类和特性。例子有 Radix 和 HeadlessUI。

  • Composable

    在 Vue 应用里,Composable 是一类特殊的函数,它借助 Vue 的 Composition API 封装和复用有状态的逻辑什么是 Vue 里的 Composables

Nuxt 和 Nuxt UI 是践行这两个先进概念的全栈框架,而且做到了凭直觉能使用(Intuitive)的开发者体验。

先安装试试看。首先你需要一个 Nuxt 项目,我们以 一个发送邮件的 API 服务器 为例,试着为它添加一个发送邮件的表单界面。

pnpm nuxt module add ui

上面这个命令,会自动更改 package.json 增加 @nuxt/ui 这个依赖,同时在 nuxt.config.ts 里增加 modules 配置项。成功后,所有的 UI 组件立即可用了。

设计网站结构

我们将设计一个既通用又简单的结构。

  • 头部是一个顶级菜单,有三个模块

    • 首页,也是落地页,用来宣传这个服务
    • 浏览我的邮件,修改以及监控这个系统

      • 数据总览
      • 发送记录
      • 邮箱列表
      • 地址管理
    • 设置中心,用户信息及其它

Nuxt 有一个好处,就是它基于文件目录结构来路由页面。要实现上面的网站结构,很简单,只需要分好目录和文件就行了。每个文件 (以 vue 为扩展名) 是一个页面,而目录对应路径。

例如,下面这个界面

 ┌───────────────────────────────────────────────────┐
 │                     Top Menu                      │
 ├────────────────┬──────────────────────────────────┤
 │                │                                  │
 │                │                                  │
 │                │                                  │
 │                │                                  │
 │    Side Menu   │          Contents                │
 │                │                                  │
 │                │                                  │
 │                │                                  │
 │                │                                  │
 │                │                                  │
 ├────────────────┴──────────────────────────────────┤
 │                      Footer                       │
 └───────────────────────────────────────────────────┘

对应的页面文件目录为

pages
├── command-palette
│   ├── index.vue
│   ├── general-settings.vue
│   └── user-info.vue
├── materials
│   ├── index.vue
│   ├── address-book.vue
│   ├── mailbox-maintain.vue
│   ├── overview.vue
│   └── sending-records.vue
├── index.vue
├── command-palette.vue
└── materials.vue
app.vue

app.vue 是入口页面,同时管理顶极路由。一个最简单的 app.vue 内容如下

<script setup lang="ts">
const topMenus = [
  [{
    label: '邮件引擎 (Email Engine) ',
    icon: 'i-heroicons-home',
    to: '/',
  }],
  [{
    label: '浏览我的邮件',
    icon: 'i-heroicons-chart-bar',
    to: '/materials',
  }],
  [{
    label: '设置中心',
    icon: 'i-heroicons-cog',
    to: '/command-palette',
  }],
]
</script>

<template>
  <UContainer>
    <MyHeader :links="topMenus" />
    <NuxtPage />
  </UContainer>
</template>

它的内容现在只有两部分:

  • 顶级路由:模块名、图标和路径
  • 顶级布局:在 UContainer 里放一个 Header 顶部导航,NuxtPage 根据当前路由展示相应页面

pages/index.vue 文件将映射到 / 这个路由。我们将在这个文件里编写落地页。落地页可以采用比较自由的布局,没有定式。

pages/materials.vue 文件和 pages/materials 目录是在 Nuxt 中指定 Nested Routes 嵌套路由的约定。

有些应用程序的用户界面由多个层级的组件嵌套组成。在这种情况下,URL的各个部分通常与嵌套组件的结构相对应,例如:

/user/johnny/profile                   /user/johnny/posts
┌──────────────────┐                  ┌──────────────────┐
│ User             │                  │ User             │
│ ┌──────────────┐ │                  │ ┌──────────────┐ │
│ │ Profile      │ │  ●────────────▶  │ │ Posts        │ │
│ │              │ │                  │ │              │ │
│ └──────────────┘ │                  │ └──────────────┘ │
└──────────────────┘                  └──────────────────┘

外层固定不变的布局(左侧导航、页脚等)在 pages/materials.vue 文件中指定,该文件的内容如下,注意所有的 to 都用绝对路径。

<script setup lang="ts">
const menus = [
  {
    label: '数据总览',
    icon: 'i-heroicons-chart-pie',
    to: '/materials/overview',
  },
  {
    label: '发送记录',
    icon: 'i-heroicons-shopping-bag',
    to: '/materials/sending-records',
  },
  {
    label: '邮箱列表',
    icon: 'i-heroicons-clipboard-document-list',
    to: '/materials/ailbox-maintain',
  },
  {
    label: '地址管理',
    icon: 'i-heroicons-book-open',
    to: '/materials/address-book',
  },
]
</script>

<template>
  <div>
    <div class="flex flex-row">
      <MyAsideMenu :links="menus" />
      <NuxtPage />
    </div>
    <MyFooter />
  </div>
</template>

pages/materials 目录里放左侧导航每一个菜单项对应的页面。注意 pages/materials/index.vue 里有一个特殊处理

<script setup lang="ts">
await navigateTo('/materials/overview', { replace: true })
</script>

这个处理必不可少的原因有二

  • 点击顶部导航项目后,自动选中第一个左侧导航项目
  • 切换左侧导航项目时,保持顶级导航和左侧导航项目的激活态

风格调整

Nuxt UI 组件的风格可以在下面三个地方改写,优先级依次从低到高。

  • app.config.ts
  • ui 属性
  • class 属性

为 Nuxt UI 组件指定风格,需要使用最新版的 tailwindcss。无需任何额外配置,直接写。

自定义组件

自定义组件的用途

  • 复用界面元素
  • 封装冗长的 tailwindcss 属性

自定义组件用到了 Vue 中的属性透传(Fallthrough Attributes)特性。当组件渲染一个单一的根元素时,透传属性会自动添加到这个根元素的属性中。

举例来说,MyAsideMenu 封装了一个 Nuxt UI 里的 UVerticalNavigation 组件

<template>
  <UVerticalNavigation class="flex-none overflow-y-auto h-[calc(100vh-7rem)] sticky top-16 w-40" />
</template>

在用到 MyAsideMenu 的地方,通过

<MyAsideMenu :links="menus" />

就把 links 属性添加到 UVerticalNavigation 上,同时也带上了 MyAsideMenu 里预定义好的一大堆 tailwindcss 风格项。这个技巧常用来写出清晰的代码。

成品展示

image.png

https://www.bilibili.com/video/BV1G43ueiEvx/?page=1

限于篇幅,本文只完成了网站框架结构。后续将在对应的页面里添加表单,对应邮件引擎的每个功能点

  • 多租户:用户登录与认证
  • 邮箱配置管理
  • 发送记录
  • 数据总览

这就不仅仅是一个 UI 的项目,还包括数据的持久化。一个 SaaS 的雏形初见端倪。

作为参考,本文的示例项目能在这里获取到。

总结与回顾

自此,我们完成了一个极简的基于 Nuxt UI 创建的网站框架结构。虽然我们只用到 Nuxt UI 最少的配置,最基本的特性,但是由于 Nuxt UI 本身极高的质量和极其灵活的可定制性,使用它能快速出原型再后期微调。我们还剩下两个跟 UI 相关的主题没有尝试

  • Dark Mode 暗黑模式
  • Mobile Layout 移动端布局自适应

将在后续的文章里再做说明。


黄灏
4 声望0 粉丝

一个技术爱好者。


引用和评论

0 条评论