BlogVue
创建新项目
npm create vite
基于Vue3、Vite4、TypeScript
#安装依赖包
npm i
类型声明
TypeScript类型检查和自动补全功能,以增加代码的可读性和可维护性
vite-env.d.ts文件
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
有了上述文件 TypeScript 就能够正确地识别和检查 Vue.js 组件的 props、事件和组件实例等类型信息。
配置解析路径
配置模块路径别名
安装@types/node作为开发者依赖关系它是 Node.js 官方发布的类型声明文件,包含了 Node.js 标准库和常用模块的类型声明
npm install @types/node --save-dev
#实现方法
import path from 'path'
export const getRootPath = () => {
return path.resolve(process.cwd())
}
export const getSrcPath = (srcName = 'src') => {
return path.resolve(getRootPath(), srcName)
}
vite.config.ts文件配置模块路径别名
resolve: {
alias: {
'@': getSrcPath(),
'~': getRootPath()
}
},
解析路径
tsconfig.json文件中 在compilerOptions对象加入
"baseUrl": "./", //指定了解析非相对路径模块时使用的基本目录路径
"paths": { //将路径缩写映射为具体的路径
"@/*": ["src/*"],
"~/*": ["./*"]
},
安装Tailwind CSS框架
安装 Tailwind 以及其它依赖项:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
创建配置文件
npx tailwindcss init -p
tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], //制定优化性能,扫描文件提取样式
darkMode: 'class', //class手动 media跟随浏览器
theme: { //用于指定自定义主题
extend: {},
},
plugins: [], //用于指定一些插件
}
引入Tailwind
自定义一个样式文件
@tailwind base;
@tailwind components;
@tailwind utilities;
在main.ts引入
安装NaiveUI 前端框架
npm i -D naive-ui
全局引入NaiveUI
在main.ts引入
import naive from 'naive-ui'
app.use(naive)
NaiveUI全局挂载组件
挂载
组件包括 对话框、消息框,方便使用
在App.vue中包裹。
示例:
<template>
<n-config-provider :theme="appStore.theme==='dark' ? darkTheme : undefined" :locale="locale" class="h-full">
<n-loading-bar-provider>
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<NaiveProviderContent></NaiveProviderContent>
<router-view></router-view>
</n-notification-provider>
</n-dialog-provider>
</n-message-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup lang="ts">
import {useDialog, useLoadingBar, useMessage, useNotification} from "naive-ui";
import {defineComponent, h, ref} from "vue";
import {darkTheme, zhCN} from 'naive-ui'
import {useAppStore} from "@/store";
const appStore = useAppStore()
const locale = ref(zhCN)
//定义新组建 实现挂载
const NaiveProviderContent = defineComponent({
setup() {
window.$message = useMessage();
window.$dialog = useDialog();
window.$notification = useNotification()
window.$loadingBar = useLoadingBar()
},
render() {
return h('div')
},
})
</script>
<style scoped>
</style>
在模板中定义了一个名为 NaiveProviderContent 的组件,并在其中使用了 useDialog、useLoadingBar、useMessage 和 useNotification 四个 hook。
这四个 hook 返回了 Dialog、LoadingBar、Message 和 Notification 组件的实例,可以通过将其挂载在全局的 window 对象上,在全局范围内使用。
同时修改了 NaiveUI 的用户的语言环境为中文
类型声明
创建一个类型声明文件。只要是.d.ts结尾文件都可以
声明全局变量 $message、$dialog、$notification 和 $loadingBar 的类型
在声明这些全局变量之后,就可以在项目中直接使用它们,而无需在每个需要使用它们的文件中都进行导入。
import {MessageApiInjection} from "naive-ui/es/message/src/MessageProvider";
import {DialogApiInjection} from "naive-ui/es/dialog/src/DialogProvider";
import {NotificationApiInjection} from "naive-ui/es/notification/src/NotificationProvider";
import {LoadingBarApiInjection} from "naive-ui/es/loading-bar/src/LoadingBarProvider";
/**
* ts 添加定义 识别
*/
declare global{
interface Window{
$message:MessageApiInjection,
$dialog:DialogApiInjection,
$notification:NotificationApiInjection
$loadingBar:LoadingBarApiInjection
}
}
安装状态管理Pinia及持久化插件pinia-plugin-persistedstate
npm i pinia
npm i pinia-plugin-persistedstate
引入pinia及插件
在main.ts
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
创建相应store
export const useAppStore = defineStore('app', {
persist: true, //使用持久化插件
state: () => { //是一个函数,返回一个对象,用于存储状态
return {
collapsed: true,
theme: 'dark',
}
},
getters: {}, //用于存储计算属性
actions: { //用于封装业务逻辑
switchCollapsed() {
this.collapsed = !this.collapsed
},
switchTheme() {
if (this.theme === 'light') this.theme = 'dark'
else this.theme = 'light'
}
},
})
安装axios
npm i axios
请求拦截器
//设置请求拦截器
axios.interceptors.request.use((config) => {
// 携带上token
let token = userStore.token
token && (config.headers.Authorization = token)
return config
}, error => {
return Promise.reject(error)
})
响应拦截器
//响应拦截器
axios.interceptors.response.use(
(response) => {
//未登录
if(response.data.code===405){
router.push({
path:'/auth'
})
}
if (response.data.code !== 0) {
window.$message.error(response.data.msg)
return Promise.reject(response.data.msg); // 返回拒绝状态的 Promise 对象,将错误信息传递给后续的 Promise 处理函数
}
window.$message.success(response.data.msg)
return response;
}, (error => {
// 请求错误时做些事
let res = "";
if (error.request) {
res = error.request;
} else if (error.response) {
res = error.response;
}
if (res) {
//@ts-ignore
const data = JSON.parse(res.response)
window.$message.error(data.msg)
return Promise.reject(data.msg); // 返回拒绝状态的 Promise 对象,将错误信息传递给后续的 Promise 处理函数
} else {
window.$message.error(`链接服务器失败`)
return Promise.reject(error); // 返回拒绝状态的 Promise 对象,将错误信息传递给后续的 Promise 处理函数
}
})
)
安装vue-router
npm i vue-router
创建router文件
import {createRouter, createWebHistory} from "vue-router";
const routes = [
path: '/',
name: 'redis',
component: () => import('@/layout/admin/index.vue'),
]
const routerOptions = {
history: createWebHistory('/redis'),
routes: routes,
}
const router = createRouter(routerOptions)
export default router
在main.ts文件引入
import router from "@/router";
app.use(router)
安装 jsencrypt
对数据进行加密解密
npm i jsencrypt
BlogAPI
数据库设计
-- 用户表
CREATETABLE`blog_user`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`username`VARCHAR(50)NOTNULLCOMMENT'用户名',
`password`VARCHAR(255)NOTNULLCOMMENT'密码',
`email`VARCHAR(50)NOTNULLCOMMENT'电子邮件',
`role`VARCHAR(20)NOTNULLCOMMENT'角色',
`avatar`VARCHAR(255)NULLCOMMENT'头像地址',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='用户表';
-- 文章表
CREATETABLE`blog_post`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`title`VARCHAR(255)NOTNULLCOMMENT'文章标题',
`content`TEXTNOTNULLCOMMENT'文章内容',
`user_id`INT(11)NOTNULLCOMMENT'作者id',
`category_id`INT(11)NOTNULLCOMMENT'分类id',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`),
FOREIGNKEY(`user_id`)REFERENCES`blog_user`(`id`),
FOREIGNKEY(`category_id`)REFERENCES`blog_category`(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='文章表';
-- 分类表
CREATETABLE`blog_category`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`name`VARCHAR(100)NOTNULLCOMMENT'分类名称',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='分类表';
-- 评论表
CREATETABLE`blog_comment`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`content`TEXTNOTNULLCOMMENT'评论内容',
`user_id`INT(11)NOTNULLCOMMENT'评论者id',
`post_id`INT(11)NOTNULLCOMMENT'文章id',
`parent_id`INT(11)DEFAULTNULLCOMMENT'父级评论id,表示当前评论是对哪个评论的回复。如果不是回复,则为null',
`reply_to`INT(11)DEFAULTNULLCOMMENT'回复到哪条评论,表示该回复是针对哪个用户的回复。如果不是回复,为null',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`),
FOREIGNKEY(`user_id`)REFERENCES`blog_user`(`id`),
FOREIGNKEY(`post_id`)REFERENCES`blog_post`(`id`),
FOREIGNKEY(`parent_id`)REFERENCES`blog_comment`(`id`),
FOREIGNKEY(`reply_to`)REFERENCES`blog_user`(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='评论表';
CREATETABLE`blog_reply`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`content`TEXTNOTNULLCOMMENT'回复内容',
`user_id`INT(11)NOTNULLCOMMENT'回复者id',
`comment_id`INT(11)NOTNULLCOMMENT'评论id,表示回复是哪条评论的回复',
`parent_id`INT(11)DEFAULTNULLCOMMENT'父级回复id,表示当前回复是对哪个回复的回复。如果不是回复,则为null',
`reply_to`INT(11)DEFAULTNULLCOMMENT'回复到哪条评论,表示该回复是针对哪个用户的回复。如果不是回复,为null',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`),
FOREIGNKEY(`user_id`)REFERENCES`blog_user`(`id`),
FOREIGNKEY(`comment_id`)REFERENCES`blog_comment`(`id`),
FOREIGNKEY(`parent_id`)REFERENCES`blog_reply`(`id`),
FOREIGNKEY(`reply_to`)REFERENCES`blog_user`(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='回复表';
创建新项目
npm create vite
基于Vue3、Vite4、TypeScript
#安装依赖包
npm i
类型声明
TypeScript类型检查和自动补全功能,以增加代码的可读性和可维护性
vite-env.d.ts文件
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
有了上述文件 TypeScript 就能够正确地识别和检查 Vue.js 组件的 props、事件和组件实例等类型信息。
配置解析路径
配置模块路径别名
安装@types/node作为开发者依赖关系它是 Node.js 官方发布的类型声明文件,包含了 Node.js 标准库和常用模块的类型声明
npm install @types/node --save-dev
#实现方法
import path from 'path'
export const getRootPath = () => {
return path.resolve(process.cwd())
}
export const getSrcPath = (srcName = 'src') => {
return path.resolve(getRootPath(), srcName)
}
vite.config.ts文件配置模块路径别名
resolve: {
alias: {
'@': getSrcPath(),
'~': getRootPath()
}
},
解析路径
tsconfig.json文件中 在compilerOptions对象加入
"baseUrl": "./", //指定了解析非相对路径模块时使用的基本目录路径
"paths": { //将路径缩写映射为具体的路径
"@/*": ["src/*"],
"~/*": ["./*"]
},
安装Tailwind CSS框架
安装 Tailwind 以及其它依赖项:
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
创建配置文件
npx tailwindcss init -p
tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], //制定优化性能,扫描文件提取样式
darkMode: 'class', //class手动 media跟随浏览器
theme: { //用于指定自定义主题
extend: {},
},
plugins: [], //用于指定一些插件
}
引入Tailwind
自定义一个样式文件
@tailwind base;
@tailwind components;
@tailwind utilities;
在main.ts引入
安装NaiveUI 前端框架
npm i -D naive-ui
全局引入NaiveUI
在main.ts引入
import naive from 'naive-ui'
app.use(naive)
NaiveUI全局挂载组件
挂载
组件包括 对话框、消息框,方便使用
在App.vue中包裹。
示例:
<template>
<n-config-provider :theme="appStore.theme==='dark' ? darkTheme : undefined" :locale="locale" class="h-full">
<n-loading-bar-provider>
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<NaiveProviderContent></NaiveProviderContent>
<router-view></router-view>
</n-notification-provider>
</n-dialog-provider>
</n-message-provider>
</n-loading-bar-provider>
</n-config-provider>
</template>
<script setup lang="ts">
import {useDialog, useLoadingBar, useMessage, useNotification} from "naive-ui";
import {defineComponent, h, ref} from "vue";
import {darkTheme, zhCN} from 'naive-ui'
import {useAppStore} from "@/store";
const appStore = useAppStore()
const locale = ref(zhCN)
//定义新组建 实现挂载
const NaiveProviderContent = defineComponent({
setup() {
window.$message = useMessage();
window.$dialog = useDialog();
window.$notification = useNotification()
window.$loadingBar = useLoadingBar()
},
render() {
return h('div')
},
})
</script>
<style scoped>
</style>
在模板中定义了一个名为 NaiveProviderContent 的组件,并在其中使用了 useDialog、useLoadingBar、useMessage 和 useNotification 四个 hook。
这四个 hook 返回了 Dialog、LoadingBar、Message 和 Notification 组件的实例,可以通过将其挂载在全局的 window 对象上,在全局范围内使用。
同时修改了 NaiveUI 的用户的语言环境为中文
类型声明
创建一个类型声明文件。只要是.d.ts结尾文件都可以
声明全局变量 $message、$dialog、$notification 和 $loadingBar 的类型
在声明这些全局变量之后,就可以在项目中直接使用它们,而无需在每个需要使用它们的文件中都进行导入。
import {MessageApiInjection} from "naive-ui/es/message/src/MessageProvider";
import {DialogApiInjection} from "naive-ui/es/dialog/src/DialogProvider";
import {NotificationApiInjection} from "naive-ui/es/notification/src/NotificationProvider";
import {LoadingBarApiInjection} from "naive-ui/es/loading-bar/src/LoadingBarProvider";
/**
* ts 添加定义 识别
*/
declare global{
interface Window{
$message:MessageApiInjection,
$dialog:DialogApiInjection,
$notification:NotificationApiInjection
$loadingBar:LoadingBarApiInjection
}
}
安装状态管理Pinia及持久化插件pinia-plugin-persistedstate
npm i pinia
npm i pinia-plugin-persistedstate
引入pinia及插件
在main.ts
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
创建相应store
export const useAppStore = defineStore('app', {
persist: true, //使用持久化插件
state: () => { //是一个函数,返回一个对象,用于存储状态
return {
collapsed: true,
theme: 'dark',
}
},
getters: {}, //用于存储计算属性
actions: { //用于封装业务逻辑
switchCollapsed() {
this.collapsed = !this.collapsed
},
switchTheme() {
if (this.theme === 'light') this.theme = 'dark'
else this.theme = 'light'
}
},
})
安装axios
npm i axios
请求拦截器
//设置请求拦截器
axios.interceptors.request.use((config) => {
// 携带上token
let token = userStore.token
token && (config.headers.Authorization = token)
return config
}, error => {
return Promise.reject(error)
})
响应拦截器
//响应拦截器
axios.interceptors.response.use(
(response) => {
//未登录
if(response.data.code===405){
router.push({
path:'/auth'
})
}
if (response.data.code !== 0) {
window.$message.error(response.data.msg)
return Promise.reject(response.data.msg); // 返回拒绝状态的 Promise 对象,将错误信息传递给后续的 Promise 处理函数
}
window.$message.success(response.data.msg)
return response;
}, (error => {
// 请求错误时做些事
let res = "";
if (error.request) {
res = error.request;
} else if (error.response) {
res = error.response;
}
if (res) {
//@ts-ignore
const data = JSON.parse(res.response)
window.$message.error(data.msg)
return Promise.reject(data.msg); // 返回拒绝状态的 Promise 对象,将错误信息传递给后续的 Promise 处理函数
} else {
window.$message.error(`链接服务器失败`)
return Promise.reject(error); // 返回拒绝状态的 Promise 对象,将错误信息传递给后续的 Promise 处理函数
}
})
)
安装vue-router
npm i vue-router
创建router文件
import {createRouter, createWebHistory} from "vue-router";
const routes = [
path: '/',
name: 'redis',
component: () => import('@/layout/admin/index.vue'),
]
const routerOptions = {
history: createWebHistory('/redis'),
routes: routes,
}
const router = createRouter(routerOptions)
export default router
在main.ts文件引入
import router from "@/router";
app.use(router)
安装 jsencrypt
对数据进行加密解密
npm i jsencrypt
BlogAPI
数据库设计
-- 用户表
CREATETABLE`blog_user`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`username`VARCHAR(50)NOTNULLCOMMENT'用户名',
`password`VARCHAR(255)NOTNULLCOMMENT'密码',
`email`VARCHAR(50)NOTNULLCOMMENT'电子邮件',
`role`VARCHAR(20)NOTNULLCOMMENT'角色',
`avatar`VARCHAR(255)NULLCOMMENT'头像地址',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='用户表';
-- 文章表
CREATETABLE`blog_post`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`title`VARCHAR(255)NOTNULLCOMMENT'文章标题',
`content`TEXTNOTNULLCOMMENT'文章内容',
`user_id`INT(11)NOTNULLCOMMENT'作者id',
`category_id`INT(11)NOTNULLCOMMENT'分类id',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`),
FOREIGNKEY(`user_id`)REFERENCES`blog_user`(`id`),
FOREIGNKEY(`category_id`)REFERENCES`blog_category`(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='文章表';
-- 分类表
CREATETABLE`blog_category`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`name`VARCHAR(100)NOTNULLCOMMENT'分类名称',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='分类表';
-- 评论表
CREATETABLE`blog_comment`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`content`TEXTNOTNULLCOMMENT'评论内容',
`user_id`INT(11)NOTNULLCOMMENT'评论者id',
`post_id`INT(11)NOTNULLCOMMENT'文章id',
`parent_id`INT(11)DEFAULTNULLCOMMENT'父级评论id,表示当前评论是对哪个评论的回复。如果不是回复,则为null',
`reply_to`INT(11)DEFAULTNULLCOMMENT'回复到哪条评论,表示该回复是针对哪个用户的回复。如果不是回复,为null',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`),
FOREIGNKEY(`user_id`)REFERENCES`blog_user`(`id`),
FOREIGNKEY(`post_id`)REFERENCES`blog_post`(`id`),
FOREIGNKEY(`parent_id`)REFERENCES`blog_comment`(`id`),
FOREIGNKEY(`reply_to`)REFERENCES`blog_user`(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='评论表';
CREATETABLE`blog_reply`(
`id`INT(11)NOTNULLAUTO_INCREMENTCOMMENT'主键',
`content`TEXTNOTNULLCOMMENT'回复内容',
`user_id`INT(11)NOTNULLCOMMENT'回复者id',
`comment_id`INT(11)NOTNULLCOMMENT'评论id,表示回复是哪条评论的回复',
`parent_id`INT(11)DEFAULTNULLCOMMENT'父级回复id,表示当前回复是对哪个回复的回复。如果不是回复,则为null',
`reply_to`INT(11)DEFAULTNULLCOMMENT'回复到哪条评论,表示该回复是针对哪个用户的回复。如果不是回复,为null',
`created_at`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',
PRIMARYKEY(`id`),
FOREIGNKEY(`user_id`)REFERENCES`blog_user`(`id`),
FOREIGNKEY(`comment_id`)REFERENCES`blog_comment`(`id`),
FOREIGNKEY(`parent_id`)REFERENCES`blog_reply`(`id`),
FOREIGNKEY(`reply_to`)REFERENCES`blog_user`(`id`)
)ENGINE=INNODBDEFAULTCHARSET=utf8COMMENT='回复表';