本文带领大家开发一套通用的官网开发模板,名字叫 nuxt3-best
,名字不重要,不是best也不重要,好用才最重要。
前言
pnpm dlx nuxi@latest init nuxt3-best
接着选择包管理器,有 npm
pnpm
yarn
bun
,我们选择 pnpm
。
接着会问我们是否初始化 git
仓库,选择 yes
就行了。
刚创建出来的项目目录结构非常简单,可以说只有一个 App.vue
,其他都是配置文件,如下图。
默认界面如下:
接下来开始开整项目,大概有以下几个步骤:
- 一、基本配置
prettier
、src目录
、.npmrc
等 - 二、引入
nuxt-ui
(可选) - 三、引入
tailwindcss/unocss
- 四、整理布局
- 五、响应式布局
- 六、表单验证
- 七、路由和导航
- 八、数据获取
- 九、引入其他常用
moduls
- 十、打包部署
鉴于
unibest
太强的代码格式配置,招到很多程序员的不适应,这个nuxt3-best
项目就温和一点了。另外,官网作为短时间就完成的项目(且大概率是一个人完成)来说,我们不做过多限制,只配个prettier
就行了。
一、基本配置 prettier
、src目录
等
-
.prettierrc.cjs
文件编写如下代码
// @see https://prettier.io/docs/en/options
module.exports = {
singleQuote: true,
tabWidth: 2,
printWidth: 100,
useTabs: false,
semi: false,
trailingComma: 'all',
endOfLine: 'auto',
htmlWhitespaceSensitivity: 'ignore',
overrides: [
{
files: '*.json',
options: {
trailingComma: 'none',
},
},
],
}
-
src目录
因为 Nuxt3
项目默认的文件夹都在顶层,就像 HBuilderX
创建的 Uniapp
项目那种的,全部在顶层,感觉很乱,所以推荐把所有的源代码都放到 src
里面,并增加 1个
配置即可。
上图是全部在顶层的图,把这些统统放到 src
里面,包括 App.vue
也要放进去。接着,在 nuxt.config.ts
中加上 srcDir
的配置:
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
+ // https://nuxt.com/docs/api/nuxt-config#srcdir
+ srcDir: 'src/',
})
- 3.
.npmrc
文件编写如下代码
这样就可以使用 淘宝源
和使用 pnpm
安装依赖了。
shamefully-hoist=true
strict-peer-dependencies=false
registry=https://registry.npmmirror.com/
引入 nuxt-ui
( 可选 )
大部分官网开发,不需要引入第三方 UI 库
,页面内容基本都是自己手写,包括组件。但是耐不住时不时有下拉框、表单和表单验证等功能出现,这个时候自己手写就太花时间了,还是引入第三方 UI库
吧。
我司官网有一个页面,需要用户留下信息,需要表单,于是我就引入了 nuxt-ui
,主要是用它的表单。为啥不选其他 UI 库,2个原因:1)够用就行,2)nuxt
官方维护的。
怎么引入呢?
在 有很多官方维护的 modules
,可以非常方便地集成,如下图:
选好 ui
那个卡片,点击进去,进入 nuxt-ui
文档,如下图只需要 2步
就引入了:
经过我的实际操作,这里使用 npx
安装会有问题,所以不能执行 npx nuxi@latest module add ui
,要改为 pnpm add @nuxt/ui
。
# npx nuxi@latest module add ui 不可以,因为原来是 pnpm 安装的,与npx 不兼容。
pnpm add @nuxt/ui
接着把 @nuxt/ui
加到 modules
,如下:
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
// https://nuxt.com/docs/api/nuxt-config#srcdir
serverDir: 'src/',
+ modules: ['@nuxt/ui'],
})
注意,nuxt-ui
内部已经引入了 tailwindcss
,所以无需另外引入 原子化CSS
了,你看下面的效果:
三、引入 tailwindcss/unocss
如果引入了
nuxt-ui
则本节可以跳过,nuxt-ui
自带tailwindcss
。
如果需要引入 原子化CSS
,则我推荐 UnoCSS
,modules
找到 UnoCSS
,如下图:
引入方式如下:
对应代码如下:
pnpm add -D @unocss/nuxt
nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@unocss/nuxt',
],
})
uno.config.ts
// uno.config.ts
import { defineConfig } from 'unocss'
export default defineConfig({
// ...UnoCSS options
})
四、整理布局
开发 layouts
和 pages
,并对 layouts/default.vue
进行布局,要的效果就是:
- 顶部栏固定在顶部
- 中间是内容区域
- footbar 一直在”底部“,当内容不足一屏的时候,固定在底部,超过一屏的时候,跟随内容在最底部。
layouts/default.vue
代码如下:
<template>
<div class="flex flex-col h-screen">
<div class="leading-10 bg-slate-500 fixed w-full z-20 h-10">topbar</div>
<div class="flex-1 pt-10 bg-gray-100">
<slot />
</div>
<div class="h-10 leading-10 bg-slate-500">footer</div>
</div>
</template>
五、响应式布局
-
- 内置的
.container
- 内置的
不管是古老的 BootStrap
还是现代的 tailwindcss/unocss
,都对 .container
进行了响应式处理,官网开发经常用得到。
举个例子,官网经常在最上面来个左右通屏的图片,下面内容又是左右居中的内容区域,我通常会这样处理:
<template>
<div>
<img class="w-full" src="~/assets/images/pretty-girl.png" />
<div class="container m-auto bg-yellow-100 p-4">
<fg-content :line="40" />
</div>
</div>
</template>
效果如下:
-
- 自己写响应式,如
grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-4
,这个比较直观,就不演示了。
- 自己写响应式,如
六、表单验证
talk is cheap, show me the code
下面用到了 zod
,记得 pnpm add zod
一下。
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '#ui/types'
const options = [
{ label: 'Option 1', value: 'option-1' },
{ label: 'Option 2', value: 'option-2' },
{ label: 'Option 3', value: 'option-3' },
]
const state = reactive({
input: undefined,
inputMenu: undefined,
textarea: undefined,
select: undefined,
selectMenu: undefined,
checkbox: undefined,
toggle: undefined,
radio: undefined,
radioGroup: undefined,
switch: undefined,
range: undefined,
})
const schema = z.object({
input: z.string().min(10),
inputMenu: z.any().refine((option) => option?.value === 'option-2', {
message: 'Select Option 2',
}),
textarea: z.string().min(10),
select: z.string().refine((value) => value === 'option-2', {
message: 'Select Option 2',
}),
selectMenu: z.any().refine((option) => option?.value === 'option-2', {
message: 'Select Option 2',
}),
toggle: z.boolean().refine((value) => value === true, {
message: 'Toggle me',
}),
checkbox: z.boolean().refine((value) => value === true, {
message: 'Check me',
}),
radio: z.string().refine((value) => value === 'option-2', {
message: 'Select Option 2',
}),
radioGroup: z.string().refine((value) => value === 'option-2', {
message: 'Select Option 2',
}),
range: z.number().max(20, { message: 'Must be less than 20' }),
})
type Schema = z.infer<typeof schema>
const form = ref()
async function onSubmit(event: FormSubmitEvent<Schema>) {
// Do something with event.data
console.log(event.data)
}
</script>
<template>
<UForm
ref="form"
:schema="schema"
:state="state"
class="space-y-4"
@submit="onSubmit"
>
<UFormGroup name="input" label="Input">
<UInput v-model="state.input" />
</UFormGroup>
<UFormGroup name="inputMenu" label="Input Menu">
<UInputMenu v-model="state.inputMenu" :options="options" />
</UFormGroup>
<UFormGroup name="textarea" label="Textarea">
<UTextarea v-model="state.textarea" />
</UFormGroup>
<UFormGroup name="select" label="Select">
<USelect
v-model="state.select"
placeholder="Select..."
:options="options"
/>
</UFormGroup>
<UFormGroup name="selectMenu" label="Select Menu">
<USelectMenu
v-model="state.selectMenu"
placeholder="Select..."
:options="options"
/>
</UFormGroup>
<UFormGroup name="toggle" label="Toggle">
<UToggle v-model="state.toggle" />
</UFormGroup>
<UFormGroup name="checkbox" label="Checkbox">
<UCheckbox v-model="state.checkbox" label="Check me" />
</UFormGroup>
<UFormGroup name="radioGroup" label="Radio Group">
<URadioGroup v-model="state.radioGroup" :options="options" />
</UFormGroup>
<UFormGroup name="radio" label="Radio">
<URadio
v-for="option in options"
:key="option.value"
v-model="state.radio"
v-bind="option"
>
{{ option.label }}
</URadio>
</UFormGroup>
<UFormGroup name="range" label="Range">
<URange v-model="state.range" />
</UFormGroup>
<UButton type="submit">Submit</UButton>
<UButton variant="outline" class="ml-2" @click="form.clear()">
Clear
</UButton>
</UForm>
</template>
效果如下:(提交不满足规范,会报红)
如果一行要放2个表单怎么办?
+<div class="grid grid-cols-2 gap-4">
<UFormGroup name="input" label="Input">
<UInput v-model="state.input" />
</UFormGroup>
<UFormGroup name="inputMenu" label="Input Menu">
<UInputMenu v-model="state.inputMenu" :options="options" />
</UFormGroup>
+</div>
效果如下:
七、路由和导航
首先申明,
常用的API都是自动导入的,无需引入。
约定的几个文件夹里面的东西都是可以直接使用的,无需引入。
可以直接通过 const route = useRoute()
拿到 route
页面路由信息,通过它可以获取路由 query
和 params
。
- 1)
http://localhost:3000?name=fg&age=30
const { name, age } = route.query
可以拿到 name
和 age
信息。
- 2)
http://localhost:3000/product/5
某个路由导航到这里,那么就可以命中 src/pages/product/[pId].vue
页面,页面里面可以通过如下代码获取到 pId
:
const { pId } = route.params
可以拿到 pId
信息。
-
路由导航,有2种方式,如下
<NuxtLink to="/product/5" > 进入详情 </NuxtLink
- 使用编程方式导航,
const router = useRouter()
拿到路由示例,然后router.push("/product/5")
八、数据获取
这个直接看官网就行了,。
主要有三种方式:useFetch
, useAsyncData
and $fetch
。
九、引入其他常用 moduls
大家可以在 找自己想要的
很多人官网不需要用户登录,所以 pinia
不是通用的,vueuse
倒是比较通用,还有 image
也不错,所以我就安装了这2个
:
pnpm add @vueuse/nuxt @nuxt/image
然后加到 nuxt.config.ts
的 modules
配置里,到此已经有 3个
了: modules: ['@nuxt/ui', '@vueuse/nuxt', '@nuxt/image'],
.
十、打包部署
执行 pnpm build
即可完成打包,然后在服务器上运行如下命令 node .output/server/index.mjs
即可,如下图:
为了保证程序运行更加稳健,我们通常会使用 PM2
,。
需要配置 ecosystem.confit.cjs
,内容如下:
module.exports = {
apps: [
{
name: 'NuxtAppName',
port: '3000',
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs'
}
]
}
在服务器上,安装 PM2
: npm i -g pm2
,
然后运行 PM2
:pm2 start ecosystem.config.cjs
其他常用命令如:pm2 stop|delete <pm2-app-name>
。
至此,完结~
总结
总结一下本文,主要描述了如何生成一个 nuxt3
项目模板,有以下 十个
步骤:
- 一、基本配置
prettier
、src目录
、.npmrc
等 - 二、引入
nuxt-ui
(可选) - 三、引入
tailwindcss/unocss
- 四、整理布局
- 五、响应式布局
- 六、表单验证
- 七、路由和导航
- 八、数据获取
- 九、引入其他常用
moduls
- 十、打包部署
模板地址:
全文完~