学校网站建设工作目标,杭州今天查出多少阳性,python制作网站开发,织梦网站首页怎么修改前端学习笔记 5#xff1a;大事件
本文将学习一个示例项目#xff08;大事件#xff09;的前端搭建过程。
1.准备工作
1.1.创建工程
创建一个名称为big-event的 Vue3 项目#xff0c;具体可以参考这篇文章。
1.2.安装插件
安装 ElementPlus#xff1a;
npm install…前端学习笔记 5大事件
本文将学习一个示例项目大事件的前端搭建过程。
1.准备工作
1.1.创建工程
创建一个名称为big-event的 Vue3 项目具体可以参考这篇文章。
1.2.安装插件
安装 ElementPlus
npm install element-plus --save安装好后还需要在main.js中导入相关模块和样式
import ./assets/main.scssimport { createApp } from vue
import ElementPlus from element-plus
import element-plus/dist/index.css
import App from ./App.vueconst app createApp(App)app.use(ElementPlus)
app.mount(#app)安装 Axios
npm install axios安装 sass
npm install sass -D这是一个 CSS 扩展。 1.3.调整目录 删除components目录下的内容 删除App.vue中的内容只保留script和template标签 在src下新建如下目录
api存放接口调用的js文件
utils存放工具js文件
拷贝request.js到util目录
views存放页面的.vue文件
删除assets目录中的内容 将资料中的静态资源文件全部拷贝到该目录下
2.注册
2.1.页面
在views下添加 Login.view 作为登录页并在 App.vue 中使用
script setup
import LoginVue from /views/Login.vue
/script
templateLoginVue/
/template为注册表单绑定响应式数据
script setup
// ...
// 注册表单的响应式数据
const registData ref({username: ,password: ,repassword:
})
/script
templateel-form ... :modelregistDatael-form-itemel-input ... v-modelregistData.username/el-input/el-form-itemel-form-itemel-input ... v-modelregistData.password/el-input/el-form-itemel-form-itemel-input ... v-modelregistData.repassword/el-input/el-form-item/el-form
/template为注册表单定义验证规则
// 注册表单的验证规则
const rules ref({username: [{ required: true, message: 请输入用户名, trigger: blur },{ min: 5, max: 16, message: 长度应该在5~16个字符, trigger: blur },],password: [{ required: true, message: 请输入密码, trigger: blur },{ min: 5, max: 16, message: 长度应该在5~16个字符, trigger: blur },],repassword: [{ validator: checkPass, trigger: blur }],
})其中再次输入密码repassword的验证规则是自定义验证规则
// 检查密码是否一致的验证函数
const checkPass (rule, value, callback) {if (value ) {callback(new Error(请输入密码))}else if(value ! registData.value.password){callback(new Error(密码不一致))}else{callback()}
}将验证规则绑定到表单
el-form ... :rulesrules!-- ... --el-form-item propusername!-- ... --/el-form-itemel-form-item proppassword!-- ... --/el-form-itemel-form-item proprepassword!-- ... --/el-form-item!-- ... --
/el-form2.2.接口调用
这里对应的服务端 jar 包是 big-event.jar对应的数据库表结构是 big-event.sql。 jar 包中登录数据库的帐号密码是root:1234如果不符需要手动自行修改。 可以通过以下命令运行服务端
java -jar .\big-event-1.0-SNAPSHOT.jar该服务端提供的接口可以查阅接口文档。
在/src/api/user.js下创建接口调用函数
import request from /utils/request.jsexport const registService (registData) {return request.post(/user/register, registData, {headers: {Content-Type: application/x-www-form-urlencoded}})
}需要注意的是这里通过 POST 方法传递的参数是以x-www-form-urlencoded方式传递的也就是在方法体中以 URL 编码方式传递并非 Axios 默认的 JSON 格式传递所以需要添加上额外参数指定请求头信息content-type。
除了上述方式也可以使用另一种方式传递 x-www-form-urlencoded 编码的 POST 参数
export const registService (registData) {var params new URLSearchParams()for(let key in registData){params.append(key, registData[key])}return request.post(/user/register, params)
}两者效果上是相同的。
在login.vue中使用该函数完成注册并绑定按钮点击事件
script setup
// 注册用户
import { registService } from /api/user.js
const regist async () {let result await registService(registData.value)if (result.code 0) {alert(注册成功)}else {alert(result.msg ? result.msg : 注册失败)}
}
/script
templateel-button classbutton typeprimary auto-insert-space clickregist注册/el-button
/template2.3.跨域
上边的示例实际上是有问题的运行后观察控制台会出现类似下面的报错信息
Access to XMLHttpRequest at http://localhost:8080/user/register from origin http://localhost:5173 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource.该信息说明了程序尝试从源 http://localhost:5173 进行 Ajax 请求到 http://localhost:8080/user/register这种请求被 CORS 规则所阻止。
实际上 CORS 是一种浏览器端的 AJAX 跨域访问的安全策略只要是对不同源协议\域名\端口任一不同的 AJAX 调用都会被浏览器阻止除非服务端允许跨域访问。
目前我们的前端程序Vue是运行在 Node.js 服务上的所以可以通过在 Node.js 服务Vue上添加代理让浏览器先访问 Node.js 服务再通过 Node.js 服务将请求转发到 Jar 包运行的服务这样就可以规避浏览器的跨域限制。
首先修改request.js让所有 AJAX 调用都以/api开头
// const baseURL http://localhost:8080;
const baseURL /api其次修改 Vue 配置文件vite.config.js
export default defineConfig({// ...// 代理配置server: {proxy: {/api: {target: http://localhost:8080, // 后端服务器地址changeOrigin: true, // 是否改变请求域名rewrite: (path) path.replace(/^\/api/, )//将原有请求路径中的api替换为}}}
})这里的正则/^\/api/的作用是将以/api开头的路径中的/api替换为空字符串。 3.登录
3.1.实现
在user.js中添加登录接口的调用函数
export const loginService (loginData) {return request.post(/user/login, loginData, {headers: {Content-Type: application/x-www-form-urlencoded}})
}在login.vue中添加登录逻辑
// 登录
const login async () {let result await loginService(registData.value)if (result.code 0) {alert(登录成功)}else {alert(result.message ? result.message : 登录失败)}
}给登录按钮绑定点击事件
el-button classbutton typeprimary auto-insert-space clicklogin登录/el-button为登录表单绑定响应式数据与注册表单的响应式数据复用
el-form ... :modelregistData :rulesrulesel-form-item propusernameel-input ... v-modelregistData.username/el-input/el-form-itemel-form-item proppasswordel-input ... v-modelregistData.password/el-input/el-form-item
/el-form这里对验证规则也进行了复用。 最后还需要在切换注册或登录页面时对响应式数据清空
// 重置注册登录数据
const resetRegistData (){registData.value {username: ,password: ,repassword: }
}el-link typeinfo :underlinefalse clickisRegister false; resetRegistData()← 返回
/el-link
!-- ... --
el-link typeinfo :underlinefalse clickisRegister true; resetRegistData()注册 →
/el-link3.2.拦截器优化
一般的接口都会用统一的返回标识符表示业务的成功和失败比如这里的result.data.code。可以利用这一点对响应拦截器进一步优化在响应拦截器中判断业务接口的成功和失败如果失败直接输出失败信息这样就不需要在前端的业务代码中对接口失败进行逐一处理。
修改request.js添加处理业务失败时的逻辑
instance.interceptors.response.use(result {if (result.data.code 0) {// 接口业务调用成功return result.data;}// 接口业务失败alert(result.data.message ? result.data.message : 业务接口调用出错)return Promise.reject(result)},err {alert(服务异常);return Promise.reject(err);//异步的状态转化成失败的状态}
)修改Login.vue调用接口后只需要显示成功信息
// 注册用户
import { registService, loginService } from /api/user.js
const regist async () {await registService(registData.value)alert(注册成功)
}// 登录
const login async () {await loginService(registData.value)alert(登录成功)
}3.3.优化消息提示
使用alert显示提示信息不够友好可以使用 ElementPlus 的消息提示组件
//添加响应拦截器
import { ElMessage } from element-plus
instance.interceptors.response.use(result {if (result.data.code 0) {// 接口业务调用成功return result.data;}// 接口业务失败let errorMsg result.data.message ? result.data.message : 业务接口调用出错ElMessage.error(errorMsg)return Promise.reject(result)},err {ElMessage.error(服务异常)return Promise.reject(err);//异步的状态转化成失败的状态}
)修改Login.vue
import { ElMessage } from element-plus
const regist async () {await registService(registData.value)ElMessage.success(注册成功)
}// 登录
const login async () {await loginService(registData.value)ElMessage.success(登录成功)
}4.主页面布局
添加 Layout.vue 到项目的views目录下。
修改App.vue以显示主页面
script setup
import LoginVue from /views/Login.vue
import LayoutVue from /views/Layout.vue
/script
template!-- LoginVue/ --LayoutVue/
/template关于主页面代码的结构说明可以观看这个视频。
4.1.路由
前端框架可以使用多种路由这里使用的是 Vue 的官方路由 VueRouter。
首先需要安装
npm install vue-router4创建文件src/router/index.js以添加路由定义
import { createRouter,createWebHistory } from vue-router
// 导入页面组件
import LoginVue from /views/Login.vue
import LayoutVue from /views/Layout.vue
// 定义路由规则
const routes [{ path: /login, component: LoginVue },{ path: /, component: LayoutVue }
]
// 创建路由实例
const router createRouter({history: createWebHistory(),routes: routes
})//导出路由实例
export default router在main.js中使用路由实例
// ...
import router from /router
const app createApp(App)
app.use(router)
app.mount(#app)在App.vue中使用路由标签展示内容
script setup
/script
templaterouter-view/router-view
/template修改Login.vue在登录成功时自动完成路径跳转
// 登录
import { useRouter } from vue-router
// 获取当前路由实例
const router useRouter()
const login async () {await loginService(registData.value)ElMessage.success(登录成功)//使用路由进行跳转router.push(/)
}现在访问 http://localhost:5173/login 就会展示登录页且登录后会自动跳转到主页面。
4.2.子路由
在主页面需要点击左侧菜单加载不同的内容这就需要用到子路由。
首先添加代表不同菜单内容的 Vue 文件 到 views 目录下。
接下来需要修改路由定义router/index.js为路径/添加子路由
import ArticleCategoryVue from /views/article/ArticleCategory.vue
import ArticleManageVue from /views/article/ArticleManage.vue
import UserAvatarVue from /views/user/UserAvatar.vue
import UserInfoVue from /views/user/UserInfo.vue
import UserResetPasswordVue from /views/user/UserResetPassword.vue
// 定义路由规则
const routes [{ path: /login, component: LoginVue },{path: /, component: LayoutVue, children: [{ path: /article/category, component: ArticleCategoryVue },{ path: /article/manage, component: ArticleManageVue },{ path: /user/avatar, component: UserAvatarVue },{ path: /user/info, component: UserInfoVue },{ path: /user/resetPassword, component: UserResetPasswordVue }]}
]子路由添加好后还需要在Layout.vue的菜单组件中使用这些子路由的路径
el-menu-item index/article/category!-- ... --
/el-menu-item
el-menu-item index/article/manage!-- ... --
/el-menu-item
!-- ... --最后还需要在内容展示区中使用router-view标签
el-mainrouter-view/router-view
/el-main现在点击主页面的菜单就可以完成不同内容的切换。
这里还有一个问题如果访问的是/路径默认不会加载任何内容因此还需要为其配置一个跳转
// 定义路由规则
const routes [{ path: /login, component: LoginVue },{path: /, component: LayoutVue, redirect:/article/manage, children: [// ...]}
]这里通过设置redirect属性添加自动跳转。现在如果访问/会自动跳转到/article/manage路径。
5.文章分类
5.1.列表页
基本的文章分类ArticleCategory.vue代码可以从这里获取。
添加api/article.js负责具体接口调用
import request from /utils/request.jsexport const getArticleCategoryList (){return request.get(/category);
}修改ArticleCategory.vue通过接口加载文章分类数据
script setup
// ...
import {getArticleCategoryList} from /api/article.js
const loadArticleCategoryList async () {result await getArticleCategoryList()categorys.value result
}
loadArticleCategoryList()
/script实际上这里调用接口会报错返回状态码是 401因为调用接口时没有传递 token这需要用到下面介绍的状态管理库 Pinia。 5.2.Pinia
Pinia是 Vue 的状态管理库我们可以利用它保存需要在全局使用和保存状态的变量。
首先需要安装 Pinia
npm install pinia修改main.js在 Vue 实例上使用 pinia
import { createPinia } from piniaconst app createApp(App)
const pinia createPinia()app.use(ElementPlus)
app.use(router)
app.use(pinia)
app.mount(#app)创建src/store/token.js添加存储库定义
import { defineStore } from pinia// pinia 中的 token 定义
export const useTokenStore defineStore(token, () {const token ref()const updateToken (newTokenVal) {token.value newTokenVal}const removeToken () {token.value }return { token, updateToken, removeToken }
})存储库定义通常都以use开头Store结尾。 修改Login.vue在登录后存储 token
import { useTokenStore } from /store/token.js
const tokenStore useTokenStore()
const login async () {let result await loginService(registData.value)// 写入 token 信息tokenStore.updateToken(result.data)ElMessage.success(登录成功)//使用路由进行跳转router.push(/)
}修改article.js调用接口时使用存储库中的 token 作为头信息
import request from /utils/request.js
import { useTokenStore } from /store/token.jsexport const getArticleCategoryList (){const tokenStore useTokenStore()return request.get(/category, {headers: {Authorization: tokenStore.token}});
}注意虽然tokenStore.token是一个响应式数据但这里不需要使用.value获取值Pinia 中的存储库定义都是如此。 5.3.请求拦截器
通常大多数接口都需要传递 token 作为身份认证信息如果每个接口调用都手动添加就会相当繁琐可以使用请求拦截器进行简化。
修改request.js使用 Axios 的请求拦截器统一传递 token 作为头信息
// 添加请求拦截器对所有请求都传递 token 作为头信息
import { useTokenStore } from /store/token.js
instance.interceptors.request.use(function (config) {// 在发送请求之前做些什么const tokenStore useTokenStore()if (tokenStore.token) {config.headers.Authorization tokenStore.token}return config;}, function (error) {// 对请求错误做些什么return Promise.reject(error);}
);这样就不需要在单独调用接口时添加 Token 作为请求头
import request from /utils/request.jsexport const getArticleCategoryList (){return request.get(/category);
}5.4.Pinia 持久化
默认情况下 Pinia 中的数据是保存在内存中的当浏览器页面刷新后会丢失所以需要借助 Pinia 插件实现持久化存储。
安装持久化插件 pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate修改main.js在 Pinia 实例上使用持久化插件
import piniaPluginPersistedstate from pinia-plugin-persistedstateconst app createApp(App)
const pinia createPinia()
pinia.use(piniaPluginPersistedstate)修改store/token.js在存储库定义中指定持久化属性persist
export const useTokenStore defineStore(token, () {const token ref()const updateToken (newTokenVal) {token.value newTokenVal}const removeToken () {token.value }return { token, updateToken, removeToken }
}, { persist: true })5.5.未登录状态处理
可以在响应拦截器中判断登录状态对于接口返回的状态码是401的视作未登录统一跳转到登录页。
修改request.js
import router from /router
instance.interceptors.response.use(result {if (result.data.code 0) {// 接口业务调用成功return result.data;}// 接口业务失败let errorMsg result.data.message ? result.data.message : 业务接口调用出错ElMessage.error(errorMsg)return Promise.reject(result)},err {if (err.response.status 401){//未登录跳转到登录页ElMessage.error(请登录)router.push(/login)}else{ElMessage.error(服务异常)}return Promise.reject(err);//异步的状态转化成失败的状态}
)需要注意的是因为模块加载顺序的关系在request.js中不能通过以下方式获取router
import { useRouter } from vue-router
const router useRouter()而是需要通过import router from /router的方式获取。
5.6.添加文章分类
在ArticleCategory.vue中添加弹窗组件
!-- 添加分类弹窗 --
el-dialog v-modeldialogVisible title添加弹层 width30%el-form :modelcategoryModel :rulesrules label-width100px stylepadding-right: 30pxel-form-item label分类名称 propcategoryNameel-input v-modelcategoryModel.categoryName minlength1 maxlength10/el-input/el-form-itemel-form-item label分类别名 propcategoryAliasel-input v-modelcategoryModel.categoryAlias minlength1 maxlength15/el-input/el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogVisible false取消/el-buttonel-button typeprimary 确认 /el-button/span/template
/el-dialog添加数据模型和表单验证规则
//控制添加分类弹窗
const dialogVisible ref(false)//添加分类数据模型
const categoryModel ref({categoryName: ,categoryAlias:
})
//添加分类表单校验
const rules {categoryName: [{ required: true, message: 请输入分类名称, trigger: blur },],categoryAlias: [{ required: true, message: 请输入分类别名, trigger: blur },]
}给添加按钮绑定单机事件以显示弹窗
el-button typeprimary clickdialogVisible true添加分类/el-button修改api/article.js添加新增分类接口调用
export const addArticleCategoryService (categoryData){return request.post(/category, categoryData)
}修改ArticleCategory.vue添加新增文章分类逻辑
// 添加文章分类
import { ElMessage } from element-plus
const addArticleCategory async () {let result await addArticleCategoryService(categoryModel.value)// 添加成功后显示提示信息ElMessage.success(result.message ? result.message : 添加成功)// 刷新列表信息loadArticleCategoryList()// 隐藏弹窗dialogVisible.value false// 清空数据模型categoryModel.value.categoryName categoryModel.value.categoryAlias
}绑定确认按钮的点击事件
el-button typeprimary clickaddArticleCategory 确认 /el-button5.7.编辑文章分类
编辑文章分类可以和添加文章分类共用弹窗通过绑定不同的 Title 进行区分
const dialogTitle ref()el-dialog v-modeldialogVisible :titledialogTitle width30%这样做的好处是可以复用响应式数据和表单校验规则。 定义不同的函数用于展示不同用途的弹窗
// 显示添加分类弹窗
const showAddDialog () {dialogTitle.value 添加分类// 清空数据模型categoryModel.value.categoryName categoryModel.value.categoryAlias // 显示弹窗dialogVisible.value true
}// 显示编辑分类弹窗
const showEditDialog (row) {dialogTitle.value 编辑分类// 加载已有信息categoryModel.value.categoryName row.categoryNamecategoryModel.value.categoryAlias row.categoryAliascategoryModel.value.id row.iddialogVisible.value true
}在显示编辑弹窗时需要加载所需的行数据用于展示现存信息可以通过表单上的 row 传入
template #default{ row }el-button :iconEdit circle plain typeprimary clickshowEditDialog(row) /el-buttonel-button :iconDelete circle plain typedanger/el-button
/template编辑时通常需要传入 id 用于服务端定位表数据行所以这里相比添加数据需要给数据模型新增一个id属性。 在点击弹窗的确认按钮时按照不同的 Title 决定不同的行为
el-button typeprimary clickdialogConfirmBtnClick 确认 /el-buttonconst dialogConfirmBtnClick () {if (dialogTitle.value 添加分类) {// 添加分类addArticleCategory()}else {// 编辑分类updateArticleCategory()}
}具体的编辑逻辑
// 编辑分类
const updateArticleCategory async () {let result await updateArticleCategoryService(categoryModel.value)ElMessage.success(result.message ? result.message : 编辑成功)// 更新分类信息loadArticleCategoryList()// 隐藏弹窗dialogVisible.value false
}对应的接口调用函数
export const updateArticleCategoryService (categoryData) {return request.put(/category, categoryData)
}5.8.删除文章
我们希望点击删除按钮后弹出一个消息提示框点击确认后删除该文章分类。
首先为删除按钮绑定点击事件
el-button :iconDelete circle plain typedanger clickbtnDeleteClick(row)/el-button通常删除时需要传递 id 给服务端所以这里传入row。
删除按钮点击后要弹出消息提示框
// 删除文章分类按钮点击事件
const btnDeleteClick (row) {// 弹出确认框ElMessageBox.confirm(确定是否要删除该文章分类,提示,{confirmButtonText: 确定,cancelButtonText: 取消,type: warning,}).then(async () {// 执行删除操作await deleteArticleCategoryService(row.id)// 重新加载列表页loadArticleCategoryList()ElMessage({type: success,message: 成功删除,})}).catch(() {ElMessage({type: info,message: 取消删除,})})
}具体的接口调用
export const deleteArticleCategoryService (id) {return request.delete(/category?id id)
}6.文章管理
6.1.文章列表
ArticleManage.vue的初始代码见这里。
分页条是英文显示需要使用中文语言包。修改main.js
import zhCn from element-plus/dist/locale/zh-cn.mjsconst app createApp(App)
const pinia createPinia()
pinia.use(piniaPluginPersistedstate)app.use(ElementPlus, {locale: zhCn})文章分类需要从后端加载
// 加载文章分类
import { getArticleCategoryList } from /api/article.js
const loadCategorys async () {let result await getArticleCategoryList()categorys.value result.data
}
loadCategorys()加载文章列表
// 根据文章分类id 获取分类
const getCategoryNameById (id) {for (let i 0; i categorys.value.length; i) {let category categorys.value[i]if (category.id id) {return category.categoryName}}return
}// 加载文章列表
const loadArticles async () {let params {pageNum: pageNum.value,pageSize: pageSize.value,categoryId: categoryId.value ? categoryId.value : null,state: state.value ? state.value : null}let result await getArticlesService(params)articles.value result.data.itemstotal.value result.data.totalfor (let i 0; i articles.value.length; i) {let article articles.value[i]article.categoryName getCategoryNameById(article.categoryId)}
}
loadArticles()因为接口返回的文章列表中只有 categoryId没有 categoryName所以需要循环遍历进行处理。 接口调用
export const getArticlesService (params) {return request.get(/article, { params: params })
}为搜索按钮绑定点击事件
el-button typeprimary clickloadArticles搜索/el-button为重置按钮设置点击事件
el-button clickcategoryId ; state 重置/el-button当分页信息发生变化时同样需要重新加载列表
//当每页条数发生了变化调用此函数
const onSizeChange (size) {pageSize.value sizeloadArticles()
}
//当前页码发生变化调用此函数
const onCurrentChange (num) {pageNum.value numloadArticles()
}6.2.添加文章
添加文章抽屉组件
import {Plus} from element-plus/icons-vue
//控制抽屉是否显示
const visibleDrawer ref(false)
//添加表单数据模型
const articleModel ref({title: ,categoryId: ,coverImg: ,content:,state:
})!-- 抽屉 --
el-drawer v-modelvisibleDrawer title添加文章 directionrtl size50%!-- 添加文章表单 --el-form :modelarticleModel label-width100px el-form-item label文章标题 el-input v-modelarticleModel.title placeholder请输入标题/el-input/el-form-itemel-form-item label文章分类el-select placeholder请选择 v-modelarticleModel.categoryIdel-option v-forc in categorys :keyc.id :labelc.categoryName :valuec.id/el-option/el-select/el-form-itemel-form-item label文章封面el-upload classavatar-uploader :auto-uploadfalse :show-file-listfalseimg v-ifarticleModel.coverImg :srcarticleModel.coverImg classavatar /el-icon v-else classavatar-uploader-iconPlus //el-icon/el-upload/el-form-itemel-form-item label文章内容div classeditor富文本编辑器/div/el-form-itemel-form-itemel-button typeprimary发布/el-buttonel-button typeinfo草稿/el-button/el-form-item/el-form
/el-drawer/* 抽屉样式 */
.avatar-uploader {:deep() {.avatar {width: 178px;height: 178px;display: block;}.el-upload {border: 1px dashed var(--el-border-color);border-radius: 6px;cursor: pointer;position: relative;overflow: hidden;transition: var(--el-transition-duration-fast);}.el-upload:hover {border-color: var(--el-color-primary);}.el-icon.avatar-uploader-icon {font-size: 28px;color: #8c939d;width: 178px;height: 178px;text-align: center;}}
}
.editor {width: 100%;:deep(.ql-editor) {min-height: 200px;}
}为添加文章按钮设置点击事件
el-button typeprimary clickvisibleDrawer true添加文章/el-button6.2.1.富文本编辑器
文章内容需要使用到富文本编辑器这里咱们使用一个开源的富文本编辑器 Quill
官网地址 https://vueup.github.io/vue-quill/
安装
npm install vueup/vue-quilllatest --save导入组件和样式
import { QuillEditor } from vueup/vue-quill
import vueup/vue-quill/dist/vue-quill.snow.css在页面使用富文本编辑器
el-form-item label文章内容div classeditorquill-editor themesnow v-model:contentarticleModel.content contentTypehtml/quill-editor/div
/el-form-item添加CSS样式
.editor {width: 100%;:deep(.ql-editor) {min-height: 200px;}
}6.2.2.图片上传
将来当点击图标选择本地图片后el-upload这个组件会自动发送请求把图片上传到指定的服务器上而不需要我们自己使用axios发送异步请求所以需要给el-upload标签添加一些属性控制请求的发送 auto-upload:是否自动上传 action: 服务器接口路径 name: 上传的文件字段名 headers: 设置上传的请求头 on-success: 上传成功的回调函数
el-upload classavatar-uploader :auto-uploadtrue :show-file-listfalse action/api/uploadnamefile :headers{Authorization:tokenStore.token} on-successuploadSuccessimg v-ifarticleModel.coverImg :srcarticleModel.coverImg classavatar /el-icon v-else classavatar-uploader-iconPlus //el-icon
/el-upload需要注意的是这里的headers属性前要用:因为其属性值是对象。
上传文件时需要附带 token
import { useTokenStore } from /store/token.js
const tokenStore useTokenStore();
const uploadSuccess (result) {articleModel.value.coverImg result.data
}6.2.3.添加文章
import { ElMessage } from element-plus
const addArticle async (state) {articleModel.value.state statelet result await addArticleService(articleModel.value)// 显示提示信息ElMessage.success(result.message ? result.message : 添加成功)// 隐藏抽屉visibleDrawer.value false// 重新加载列表loadArticles()
}el-button typeprimary clickaddArticle(已发布)发布/el-button
el-button typeinfo clickaddArticle(草稿)草稿/el-buttonexport const addArticleService (articleData) {return request.post(/article, articleData)
}服务端有限制图片地址不能为空这里可以先设置一个假的图片地址用于添加文章。 7.顶部导航栏
7.1.个人信息
顶部导航栏需要展示个人信息比如昵称。这些个人信息需要从接口获取并保存到全局Pinia 存储库。
先定义个人信息的存储库
import { defineStore } from pinia
import { ref } from vue// pinia 中的 userInfo 定义
export const useUserInfoStore defineStore(userInfo, () {const userInfo ref()const updateUserInfo (newUserInfo) {userInfo.value newUserInfo}const removeUserInfo () {userInfo.value {}}return { userInfo, updateUserInfo, removeUserInfo }
}, { persist: true })在Layout.vue中加载个人信息并保存到存储库
import { useUserInfoStore } from /store/userInfo.js
const userInfoStore useUserInfoStore()
import { getUserInfoService } from /api/user.js
const loadUserInfo async () {let result await getUserInfoService()userInfoStore.updateUserInfo(result.data)
}
loadUserInfo()将存储库中的内容渲染到页面
div黑马程序员strong{{ userInfoStore.userInfo.nickname }}/strong/div7.2.下拉菜单
ElementPlus 中的下拉菜单组件是el-dropdown标签
el-dropdown placementbottom-endspan classel-dropdown__boxel-avatar :srcavatar /el-iconCaretBottom //el-icon/spantemplate #dropdown
el-dropdown-menuel-dropdown-item commandprofile :iconUser基本资料/el-dropdown-itemel-dropdown-item commandavatar :iconCrop更换头像/el-dropdown-itemel-dropdown-item commandpassword :iconEditPen重置密码/el-dropdown-itemel-dropdown-item commandlogout :iconSwitchButton退出登录/el-dropdown-item/el-dropdown-menu/template
/el-dropdown可以为下拉菜单绑定一个command事件该事件在点击下拉菜单中的选项时会被触发且会传入菜单的command属性作为参数
el-dropdown placementbottom-end commandhandleCommandspan classel-dropdown__boxel-avatar :srcavatar /el-iconCaretBottom //el-icon/spantemplate #dropdown
el-dropdown-menuel-dropdown-item commandinfo :iconUser基本资料/el-dropdown-itemel-dropdown-item commandavatar :iconCrop更换头像/el-dropdown-itemel-dropdown-item commandresetPassword :iconEditPen重置密码/el-dropdown-itemel-dropdown-item commandlogout :iconSwitchButton退出登录/el-dropdown-item/el-dropdown-menu/template
/el-dropdown// 点击下拉菜单
import { useRouter } from vue-router
const router useRouter()
import { useTokenStore } from /store/token.js
const tokenStore useTokenStore()
import { ElMessage, ElMessageBox } from element-plus;
const handleCommand (command) {if (command logout) {// 退出登录// 消息提示框ElMessageBox.confirm(确定是否要退出登录,提示,{confirmButtonText: 确定,cancelButtonText: 取消,type: warning,}).then(async () {// 删除存储库中的 token 和个人信息tokenStore.removeToken()userInfoStore.removeUserInfo()// 跳转到登录页router.push(/login)ElMessage({type: success,message: 成功退出,})}).catch(() {ElMessage({type: info,message: 取消退出,})})}else {// 跳转到对应页面router.push(/user/ command)}
}8.用户信息修改
初始的用户信息修改页面代码可以见 UserInfo.vue。
个人信息保存在存储库中因此可以从存储库中读取个人信息修改后同样要保存回存储库
// 从存储库中读取个人信息
import { useUserInfoStore } from /store/userInfo.js
const userInfoStore useUserInfoStore()
const userInfo ref({ ...userInfoStore.userInfo })
// 修改个人信息
import { updateUserInfoService } from /api/user.js
import { ElMessage } from element-plus
const btnCommitClick async () {let result await updateUserInfoService(userInfo.value)// 修改存储库中的个人信息userInfoStore.updateUserInfo({ ...userInfo.value })ElMessage.success(result.message ? result.message : 修改成功)
}要注意的是响应式数据模型userInfo从存储库读取数据时需要用{...userInfoStore.userInfo}的方式获取存储库对象的一个拷贝而非直接获取存储库对象的引用否则会有bug修改数据模型后存储库对象也会立即改变。同样的道理将修改后的数据模型回写到存储库时同样需要传递一个结构后的新对象而非直接传递。
9.修改头像
修改头像页面的基本代码见 UserAvatar.vue。
首先要从存储库中的用户信息加载头像
import { useUserInfoStore } from /store/userInfo.js;
const userInfoStore useUserInfoStore()
// 从用户信息加载头像
const loadAvatar (){imgUrl userInfoStore.userInfo.userPic
}
loadAvatar()设置图像自动上传
el-upload refuploadRef classavatar-uploader :show-file-listfalse :auto-uploadtrueaction/api/upload namefile :headers{ Authorization: tokenStore.token }:on-successuploadSuccessimg v-ifimgUrl :srcimgUrl classavatar /img v-else :srcavatar width278 /
/el-upload在图片成功更新后修改图片绑定的地址信息
//用户头像地址
let imgUrl avatar
const uploadSuccess (result) {imgUrl https://p.qqan.com/up/2020-12/16070652276806379.jpg
}这里使用假数据。 设置图片上传按钮的点击事件
const updateAvatar async () {let result await updateUserAvatarService(imgUrl)//更新成功后更新存储库中的头像userInfoStore.userInfo.userPic imgUrlElMessage.success(result.msg ? result.msg : 更新成功)
}el-button typesuccess :iconUpload sizelarge clickupdateAvatar上传头像
/el-button谢谢阅读本文的完整示例代码可以从这里获取。
10.参考资料
黑马程序员SpringBoot3Vue3全套视频教程