参考文档
- Nuxt 英文文档
- Nuxt 中文文档
- 图标库 Heroicons
- Element-plus中文文档
- @nuxt/element-plus
- @nuxtjs/tailwindcss
- Tailwind CSS 英文文档
- Tailwind CSS 中文文档
- tailwindcss的简化版本windicss
初始化项目
创建新的项目:npx nuxi@latest init 01-nestjs-front
修改项目端口号, 修改文件nuxt.config.ts
export default defineNuxtConfig({
+ devServer: { port: 8010 },
})
修改app.vue
<template>
<NuxtPage />
</template>
创建pages/index.vue
文件, 安装依赖npm i sass -D
<template>
<div class="test-div">{{ hello }}</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const hello = ref('hello world')
</script>
<style lang="scss" scoped>
.test-div {
font-size: 40px;
}
</style>
命令行运行项目npm run dev
, 在浏览器访问http://localhost:8010/
使用组件库
使用tailwindcss
安装组件库:npm install --save-dev @nuxtjs/tailwindcss
在nuxt.config.ts
添加配置
export default defineNuxtConfig({
+ modules: ['@nuxtjs/tailwindcss']
})
解决没有class样式提示的问题
npx tailwindcss init
使用Icon
安装图标库:npm install @heroicons/vue @headlessui/vue
使用
</template>
<ArrowPathIcon/>
<ChevronDownIcon/>
<template>
<script setup lang="ts">
import { ArrowPathIcon} from '@heroicons/vue/24/outline'
import { ChevronDownIcon} from '@heroicons/vue/20/solid'
</script>
Element-plus
安装插件:npm i element-plus @element-plus/nuxt -D
在nuxt.config.ts
中配置
export default defineNuxtConfig({
modules: [ '@element-plus/nuxt' ],
elementPlus: { /** Options */ }
})
修改默认主题
创建文件\assets\styles\element\theme.scss
:root {
--el-color-primary: red;
--el-bg-color-overlay: transparent;
}
在nuxt.config.ts
中增加配置
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
scss: { additionalData: `@use "~/assets/styles/element/theme.scss" as element;` },
},
},
},
elementPlus: {
// icon: 'ElIcon',
importStyle: 'scss', // themes: ['dark'],
},
})
使用默认布局
改造app.vue
<template>
+ <NuxtLayout>
<NuxtPage />
+ </NuxtLayout>
</template>
创建layouts/default.vue
<template>
<div class="default-layout">
<div class="default-header">
header
</div>
<div class="default-body">
<slot />
</div>
<div class="default-footer">
footer
</div>
</div>
</template>
<script lang="ts" setup>
</script>
<style lang="scss" scoped>
</style>
状态管理pinia
安装插件:npm i pinia @pinia/nuxt
修改配置文件nuxt.config.ts
modules: ['@pinia/nuxt'],
创建目录和文件:stores/counter.ts
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
在项目中使用
<template>
<div class="py-3">
<div color="primary" size="xs" variant="solid" @click="counterStore.increment">+</div>
<p class="text-teal-400 font-bold my-2">count: {{ counterStore.count }}</p>
</div>
</template>
<script lang="ts" setup>
import { useCounterStore } from '../stores/counter'
const counterStore = useCounterStore()
</script>
状态持久化
在pinia
中有个下载量较多的插件pinia-plugin-persistedstate
,这是插件文档
安装插件:npm i pinia-plugin-persistedstate @pinia-plugin-persistedstate/nuxt
然后配置nuxt.config.ts
export default defineNuxtConfig({
devServer: {
port: 8010,
},
devtools: { enabled: true },
modules: ['@nuxt/ui', '@pinia/nuxt', '@pinia-plugin-persistedstate/nuxt'],
// 全局设置pinia持久化存储策略
piniaPersistedstate: {
cookieOptions: { sameSite: 'strict' },
// 1. sessionStorage和localStorage服务端渲染不会持久化, cookies客户端和服务端都可以持久化
storage: 'cookies', // 可选值sessionStorage|localStorage|cookies
},
})
编辑文件:stores/counter.ts
export const useCounterStore = defineStore(
'counter',
() => {
...
},
// 如果不想缓存就不要配置
{
// 方案1: 如果想使用cookies缓存(客户端和服务端都可以持久化)
+ persist: true,
// 方案2: 如果想配置sessionStorage|localStorage缓存(只有客户端可以持久化)
+ persist: process.client ? {
+ key: 'aaaa', // 自定义缓存的键名, 默认使用store的名称,本例中默认是'counter'
+ storage: localStorage, // localStorage|sessionStorage
+ }: false
},
)
请求接口
axios
安装插件:npm i axios
,中文文档
创建api/common/request.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'
const instance = axios.create({
baseURL: '/api',
timeout: 80000,
})
// 添加请求拦截器
instance.interceptors.request.use(
function (config) {
if (process.env.NODE_ENV === 'development') {
// 本地开发环境,baseURL修正(服务端没法做本地代理,需要完整的请求路径)
config.baseURL = `${process.server ? process.env.VITE_API_BASEURL : ''}${config.baseURL}`
}
// 在发送请求之前做些什么
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
},
)
// 添加响应拦截器
instance.interceptors.response.use(
function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
const resData = response.data
if (resData) {
if (resData.code !== 0) {
if (process.client && resData.msg) {
ElMessage({ showClose: true, message: resData.msg, type: 'error' }) // 错误提示
}
}
return resData //成功返回数据
} else {
if (process.client) {
ElMessage({ showClose: true, message: response.data.message, type: 'error' }) // 错误提示
}
return Promise.reject(response)
}
},
function (error) {
if (process.client) {
ElMessage({ showClose: true, message: error.message, type: 'error' }) // 错误提示
}
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
},
)
export default instance
配置tsconfig.json
, 解决axios引用错误
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/stores/*": ["./stores/*"],
+ "@/utils/*": ["./utils/*"]
}
}
}
在nuxt.config.ts
配置本地接口请求跨域
export default defineNuxtConfig({
nitro: {
// 配置请求跨域
devProxy: {
'/api': {
target: 'http://127.0.0.1:9010/api',
changeOrigin: true,
},
},
},
})
在.vue
文件中请求接口
<template>
<div @click="handleRequest">Login</div>
</template>
<script setup lang="ts">
import axios from '@/api/common/request'
const requestUserById = (id: number): Promise<Record<string, any>> => {
return request.get(`/user/${id}`)
}
const handleRequest = async () => {
const res = await requestUserById(1)
console.log(res)
}
</script>
<style scoped></style>
设置环境变量
我们会在项目中配置两个变量
VITE_MODE_ENV
、VITE_API_BASEURL
调用方式
<script setup lang="ts">
// 方式1:在访问不到nuxt实例的地方不可用
const config = useRuntimeConfig()
console.log(config.public.VITE_MODE_ENV) // 服务端和客户端都可以获取
console.log(config.VITE_MODE_ENV) // 只有服务端才能获取到
// 方式2:处处可用
console.log(process.env.VITE_MODE_ENV)
</script>
- 创建
.env.development
,.env.test
,.env.production
等配置文件,按需调整配置
# .env.development
VITE_MODE_ENV = development # 开发环境
VITE_API_BASEURL = http://127.0.0.1:9010 # 接口请求地址
# .env.test
VITE_MODE_ENV = test # 开发环境
VITE_API_BASEURL = http://127.0.0.1:9010 # 接口请求地址
# .env.production
VITE_MODE_ENV = production # 开发环境
VITE_API_BASEURL = http://127.0.0.1:9010 # 接口请求地址
- 创建类型
typings/env.d.ts
, 支持process.env.XXX
的代码提示
declare namespace NodeJS {
// process.env.XXX 的类型提示
interface ProcessEnv {
NODE_ENV: string
VITE_MODE_ENV: 'development' | 'test' | 'production'
VITE_API_BASEURL: string
}
}
- 修改
package.json
script脚本,指向对应的配置文件
"dev": "nuxt dev --dotenv .env.development",
"dev:test": "nuxt dev --dotenv .env.test",
"dev:prod": "nuxt dev --dotenv .env.production",
"build": "nuxt build --dotenv .env.development",
"build:test": "nuxt build --dotenv .env.test",
"build:prod": "nuxt build --dotenv .env.production",
- 修改配置文件
nuxt.config.ts
import { loadEnv } from 'vite'
// 通过启动命令区分环境`development|test|production`
const envScript = (process.env as any).npm_lifecycle_script.split(' ')
// 通过启动命令区分环境
const envName = envScript[envScript.length - 1] || '.env.development'
// 拿到了.env文件配置的信息
const envData = loadEnv(envName, 'env') as unknown as NodeJS.ProcessEnv
// 定义变量,能通过`process.env.XXX`获取值
const defineEnv: Record<string, string> = {}
Object.keys(envData).forEach((key) => (defineEnv[`process.env.${key}`] = JSON.stringify((envData as any)[key])))
// 配置接口代理地址
const proxyApiTarget = `${envData.VITE_API_BASEURL}/api`
console.log('环境变量 ======= process.env.XXXX:', defineEnv)
console.log('运行变量 ======= useRuntimeConfig:', envData)
export default defineNuxtConfig({
...
vite: {
// 指定env文件夹
envDir: '~',
// 这定义的变量通过`process.env.VITE_MODE_ENV`获取到值
define: {
// 如:'process.env.VITE_MODE_ENV': JSON.stringify(envData.VITE_MODE_ENV),
...defineEnv,
},
}
runtimeConfig: {
...envData,
// 把env放入这个里面,通过useRuntimeConfig获取
public: { ...envData },
},
nitro: {
// 配置客户端跨域,用于客户端转发
devProxy: {
'/api': { target: proxyApiTarget, changeOrigin: true },
},
},
})
修改api/common/request.ts
// 在请求拦截器中,用配置数据修正baseURL
// 本地开发环境,baseURL修正(服务端没法做本地代理,需要完整的请求路径)
- config.baseURL = `${process.server ? 'http://localhost:9010' : ''}${config.baseURL}`
+ config.baseURL = `${process.server ? process.env.VITE_API_BASEURL : ''}${config.baseURL}`
用户登录
在src/api/user.ts
中添加接口
import type { IUserInfo } from '@/stores/user'
import request from './common/request'
import type { IResPromise } from './common/request.d'
/** 用户登录, 假设存在接口`/auth/signin.ts`, */
export const requestSingin = (data: { username: string; password: string }): IResPromise<{ access_token: string; userInfo: IUserInfo }> => {
return request.post('/auth/signin', data)
}
创建文件src/stores/user.ts
因为
pinia
使用了持久化缓存插件,所以userStore会把accessToken
和userInfo
保存在cookie中
import { requestSingin } from '@/api/user'
/** 登录的用户信息 */
export interface IUserInfo {
id?: number
username?: string
password?: string
profile?: {
id: number
gender: 1 | 2
photo: string
address: string
}
}
export const useUserStore = defineStore(
'userStore',
() => {
// 因为`pinia`使用了持久化缓存插件,所以userStore会把`accessToken`和`userInfo`保存在cookie中
const accessToken = ref<string>('')
const userInfo = ref<IUserInfo>({})
// 用户是否登录
const isLogin = computed(() => !!accessToken.value)
// 用户登录
const handleLoginIn = async (params: { username: string; password: string; [p: string]: any }) => {
const { username, password } = params
const res = await requestSingin({ username, password })
if (res?.code === 0 && res.data) {
const { access_token, userInfo: info } = res.data
accessToken.value = access_token
userInfo.value = info
return info
}
}
// 退出登录,清空缓存
const handleLoginOut = () => {
accessToken.value = ''
userInfo.value = {}
}
return { accessToken, userInfo, isLogin, handleLoginIn, handleLoginOut }
},
// 配置持久化缓存
{ persist: true },
)
编辑src/api/common/request.ts
, 配置Authorization
头信息
每次接口请求都会携带
Authorization
头信息,服务端会自动读取Authorization
并做用户权限校验
+ import { useUserStore } from '@/stores/user'
// 添加请求拦截器
instance.interceptors.request.use(
function (config) {
// 添加Authorization头信息
+ const userStore = useUserStore()
+ if (userStore.accessToken) {
+ config.headers.Authorization = `Bearer ${userStore.accessToken}`
+ }
...
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
},
)
在centos系统中部署
在centos系统安装docker
,配置gitee的密钥(参考之前的文章)
在nuxt3项目中创建文件:.dockerignore
*.md
node_modules/
.git/
.DS_Store
.vscode/
.dockerignore
在nuxt3项目中创建文件:Dockerfile
# build stage
FROM node:18.0-alpine3.14 as build-stage
WORKDIR /app
COPY package.json .
RUN npm config set registry https://registry.npmmirror.com/
RUN npm install
COPY . .
#3、参数,node的环境为生产环境
ENV NODE_ENV=production
#4、任意ip
ENV HOST 0.0.0.0
# 执行生产打包
RUN npm run build:prod
# production stage
FROM node:18.0-alpine3.14 as production-stage
COPY --from=build-stage /app/.output /app/.output
COPY --from=build-stage /app/package.json /app/package.json
WORKDIR /app
#3、参数,node的环境为生产环境
ENV NODE_ENV=production
#4、任意ip
ENV HOST 0.0.0.0
RUN npm config set registry https://registry.npmmirror.com/
RUN npm install --production
RUN npm install pm2 -g
EXPOSE 3000
# CMD ["node", "/app/.output/server/index.mjs"]
CMD ["pm2-runtime", "/app/.output/server/index.mjs"]
在nuxt3项目中创建文件:deploy.sh
#!/bin/bash
# 在centos系统中的项目路径
WORK_PATH='/usr/__project__/englist-novel-front'
cd $WORK_PATH
echo "清除老代码-----------------------------"
git reset --hard origin/master
git clean -f
echo "拉取新代码-----------------------------"
git pull origin master
echo "停止旧容器并删除旧容器------------------"
# docker stop englist-novel-front
# docker rm englist-novel-front
echo "开始执行构建新容器---------------------------"
docker build -t englist-novel-front .
# 构建Docker镜像:
echo "构建Docker镜像-----------------------------"
docker build -t englist-novel-front .
# 运行Docker容器:
echo "运行Docker容器-----------------------------"
docker run -p 8010:3000 englist-novel-front
# 阿里云发布 ---------------------------------
# - 执行部署:sh deploy.sh
在阿里云centos系统中创建目录/usr/__project__
,用git通过ssh方式拉取项目englist-novel-front
仓库,执行shell脚本部署:sh deploy.sh