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 风格项。这个技巧常用来写出清晰的代码。
成品展示
https://www.bilibili.com/video/BV1G43ueiEvx/?page=1
限于篇幅,本文只完成了网站框架结构。后续将在对应的页面里添加表单,对应邮件引擎的每个功能点
- 多租户:用户登录与认证
- 邮箱配置管理
- 发送记录
- 数据总览
这就不仅仅是一个 UI 的项目,还包括数据的持久化。一个 SaaS 的雏形初见端倪。
作为参考,本文的示例项目能在这里获取到。
总结与回顾
自此,我们完成了一个极简的基于 Nuxt UI 创建的网站框架结构。虽然我们只用到 Nuxt UI 最少的配置,最基本的特性,但是由于 Nuxt UI 本身极高的质量和极其灵活的可定制性,使用它能快速出原型再后期微调。我们还剩下两个跟 UI 相关的主题没有尝试
- Dark Mode 暗黑模式
- Mobile Layout 移动端布局自适应
将在后续的文章里再做说明。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。