# NewsExpress **Repository Path**: Cililin/news-express ## Basic Information - **Project Name**: NewsExpress - **Description**: 新闻速递项目,涵盖前后端。技术栈是SpringBoot3+Vue3,使用Sass和Pinia。 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-08-11 - **Last Updated**: 2024-08-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 新闻速递项目-前端 ## 一、技术栈 1. Vue3 2. Vite 3. Router 4. Pina 5. Element-Plus ## 二、跨域问题 ### 1.跨域 浏览器同源策略限制,向不同源发送ajax请求失败 ### 2.配置代理 ```js //utils.request.js //定制请求的实例 //导入axios npm install axios import axios from 'axios'; //定义一个变量,记录公共的前缀 , baseURL //该处将baseURL设置为/api const baseURL = '/api'; const instance = axios.create({baseURL}) //添加响应拦截器 instance.interceptors.response.use( result=>{ return result.data; }, err=>{ alert('服务异常'); return Promise.reject(err);//异步的状态转化成失败的状态 } ) export default instance; ``` ```js //vite.config.js import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, //在该处进行配置 server:{ proxy:{ '/api':{ //获取路径中包含/api的请求 target:'http://localhost:9090', //后台服务所在的源 changeOrigin:true, //修改源 rewrite:path=>path.replace(/^\/api/,'') //将api替换成空字符串 } } } }) ``` ## 三、前端接口编写与调用 #### 1.用户接口 ```js //src.api.user.js //导入请求工具 import request from '@/utils/request.js' //提供调用注册接口的函数 export const userRegisterService = (registerData) => { //借助UrlSearchParams对象,将注册数据转换成URL格式 const params = new URLSearchParams() for (let key in registerData) { params.append(key, registerData[key]) } return request.post('/user/register', params) } //提供调用登录接口的函数 export const userLoginService = (loginData) => { //借助UrlSearchParams对象,将注册数据转换成URL格式 const params = new URLSearchParams() for (let key in loginData) { params.append(key, loginData[key]) } return request.post('/user/login', params) } //获取用户详细信息 export const userInfoService = () => { return request.get('/user/userInfo') } //修改个人信息 export const updateUserInfoService = (userInfoData) => { return request.put('/user/update', userInfoData) } //修改头像 export const updateUserAvatarService = (avatarUrl) => { let params = new URLSearchParams(); params.append('avatarUrl', avatarUrl) return request.patch('/user/updateAvatar', params) } //更新密码 export const updatePasswordService = (userPwdData) => { return request.patch('/user/updatePwd', userPwdData) } ``` #### 2.文章接口 ```js //src.api.article.js import request from '@/utils/request.js' //文章分类列表查询 export const getArticleCategoryListService = () => { return request.get('/category') } //文章分类添加 export const addArticleCategoryService = (categoryData) => { return request.post('/category', categoryData) } //文章分类更新 export const updateArticleCategoryService = (categoryData) => { return request.put('/category', categoryData) } //文章分类删除 export const deleteArticleCategoryService = (id) => { return request.delete('/category?id=' + id) } //文章列表查询 export const getArticleListService = (params) => { return request.get('/article', { params: params }) } //文章添加 export const addArticleService = (articleData) => { return request.post('/article', articleData) } //文章更新 export const updateArticleService = (articleData) => { return request.put('/article', articleData) } //文章分类删除 export const deleteArticleService = (id) => { return request.delete('/article?id=' + id) } ``` #### 3.接口调用 ```js //以注册为例 //view.Login.vue import { registerService} from '@/api/user.js' //用于注册的事件函数 const register = async () => { //console.log('注册...'); let result = await registerService(registerData.value); if (result.code == 0) { alert('注册成功!') } else { alert('注册失败!') } } ``` ## 四、优化axios响应截器 在接口调用的API中,我们都需要对业务响应的状态进行判断,从而给用户对应的提示,这个工作不难,但是每个接口的调用,都这样写代码,显然是比较繁琐的,我们可以在axios的相应拦截器中,如果服务器响应成功了,统一判断后台返回的业务状态码code,如果成功了,正常返回数据,如果失败了,则给出用户对应的提示即可 #### 1.**请求工具request.js** ```js //添加响应拦截器 instance.interceptors.response.use( result => { //如果业务状态码为0,代表本次操作成功 if (result.data.code == 0) { return result.data; } //代码走到这里,代表业务状态码不是0,本次操作失败 alert(result.data.message || '服务异常'); return Promise.reject(result.data);//异步的状态转化成失败的状态 }, err => { alert('服务异常'); return Promise.reject(err);//异步的状态转化成失败的状态 } ) ``` #### 2.**接口调用user.js** ```js //用于注册的事件函数 const register = async () => { //console.log('注册...'); await registerService(registerData.value); alert('注册成功!') } //用于登录的事件函数 const login = async () => { await loginService(registerData.value) alert('登录成功!') } ``` #### 3.**Element-Plus提示框的使用** ```js import { ElMessage } from 'element-plus' ElMessage.error('服务异常'); ElMessage.success('登录成功!') ``` ## 五、配置子路由 #### 1.在src/router/index.js中配置子路由 ```js //定义路由关系 const routes = [ { path: '/login', component: LoginVue }, { path: '/', component: LayoutVue, //重定向 redirect: '/article/manage', //子路由 children: [ { path: '/article/category', component: ArticleCategoryVue }, { path: '/article/manage', component: ArticleManageVue }, { path: '/user/info', component: UserInfoVue }, { path: '/user/avatar', component: UserAvatarVUe }, { path: '/user/password', component: UserResetPasswordVue }, ] } ] ``` #### 2. 在Layout.vue组件的右侧中间区域,添加router-view标签 ```html
``` #### 3. 菜单项设置点击后跳转的路由路径 el-menu-item 标签的index属性可以设置点击后的路由路径 ```html 文章分类 ``` ## 六、Pinia状态管理 Pinia是Vue的专属状态管理库,它允许你跨组件或页面共享状态 #### 1.安装 ```js npm install pinia ``` #### 2.使用Pinia 在main.js中,引入pinia,创建pinia实例,并调用vue应用实例的use方法使用pinia ```js import { createPinia } from 'pinia' const pinia = createPinia() app.use(pinia) ``` #### 3.定义Store 在src/stores目录下定义token.js ```js import { defineStore } from "pinia"; import {ref} from 'vue'; /* defineStore参数描述: 第一个参数:给状态起名,具有唯一性 第二个参数:函数,可以把定义该状态中拥有的内容 defineStore返回值描述: 返回的是一个函数,将来可以调用该函数,得到第二个参数中返回的内容 */ export const useTokenStore = defineStore('token',()=>{ //1.定义描述token const token = ref('') //2.定义修改token的方法 const setToken = (newToken)=>{ token.value = newToken } //3.定义移除token的方法 const removeToken = ()=>{ token.value='' } return { token,setToken,removeToken } }) ``` #### 4. 使用Store 在需要使用状态的地方,导入@/stores/*.js , 使用即可 在Login.vue中导入@/stores/token.js, 并且当用户登录成功后,将token保存pinia中 ```js //导入token状态 import { useTokenStore } from '@/stores/token.js' //调用useTokenStore得到状态 const tokenStore = useTokenStore(); //用于登录的事件函数 const login = async () => { let result = await loginService(registerData.value) //保存token tokenStore.setToken(result.data) ElMessage.success('登录成功!') router.push('/') } ``` 在article.js中导入@/stores/token.js, 从pinia中获取到存储的token,在发起查询文章分类列表的时候把token通过请求头的形式携带给服务器 ```js //导入@/stores/token.js import { useTokenStore } from '../stores/token' //文章分类列表查询 export const articleCategoryListService = () => { //获取token状态 const tokenStore = useTokenStore() //通过请求头Authorization携带token return request.get('/category', { headers: { 'Authorization': tokenStore.token } }) } ``` ## 七、Pinia持久化插件 默认情况下,由于pinia是内存存储,当你刷新页面的时候pinia中的数据会丢失,可以借助于persist插件解决这个问题,persist插件支持将pinia中的数据持久化到sessionStorage和localStorage中 #### 1.安装persist插件 ```js npm install pinia-persistedstate-plugin ``` #### 2. pinia中使用persist插件 在main.js中 ```js import { createPinia } from 'pinia' //导入持久化插件 import {createPersistedState} from'pinia-persistedstate-plugin' const pinia = createPinia() const persist = createPersistedState() //pinia使用持久化插件 pinia.use(persist) app.use(pinia) ``` #### 3. 在创建定义状态是配置持久化 在src/stores/token.js中 ```js export const useTokenStore = defineStore('token',()=>{ //1.定义描述token const token = ref('') //2.定义修改token的方法 const setToken = (newToken)=>{ token.value = newToken } //3.定义移除token的方法 const removeToken = ()=>{ token.value='' } return { token,setToken,removeToken } } , //参数持久化 { persist:true } ) ``` ## 八、axios请求拦截器 当进入主页后,将来要与后台交互,都需要携带token,如果每次请求都写这样的代码,将会比较繁琐,此时可以将携带token的代码通过请求拦截器统一处理 在 src/util/request.js中 ```js //导入token状态 import { useTokenStore } from '@/stores/token.js'; //添加请求拦截器 instance.interceptors.request.use( (config)=>{ //在发送请求之前做什么 let tokenStore = useTokenStore() //如果token中有值,在携带 if(tokenStore.token){ config.headers.Authorization=tokenStore.token } return config }, (err)=>{ //如果请求错误做什么 Promise.reject(err) } ) ``` ## 九、未登录统一处理 在后续访问接口时,如果没有登录,则前端不携带token,后台服务器会返回响应状态码401,代表未登录,此时可以在axios的响应拦截器中,统一对未登录的情况做处理 **request.js** ```js import router from '@/router' //添加响应拦截器 instance.interceptors.response.use( result => { //如果业务状态码为0,代表本次操作成功 if (result.data.code == 0) { return result.data; } //代码走到这里,代表业务状态码不是0,本次操作失败 ElMessage.error(result.data.message || '服务异常'); return Promise.reject(result.data);//异步的状态转化成失败的状态 }, err => { //如果响应状态码时401,代表未登录,给出对应的提示,并跳转到登录页 if(err.response.status===401){ ElMessage.error('请先登录!') router.push('/login') }else{ ElMessage.error('服务异常'); } return Promise.reject(err);//异步的状态转化成失败的状态 } ) ``` ## 十、el-dropdown中功能实现 在el-dropdown中有四个子条目,分别是: - 基本资料 - 更换头像 - 重置密码 - 退出登录 其中其三个起到路由功能,跟左侧菜单中【个人中心】下面的二级菜单是同样的功能,退出登录需要删除本地pinia中存储的token以及userInfo #### 1.**路由实现:** 在el-dropdown-item标签上添加command属性,属性值和路由表中/user/xxx保持一致 ```html 基本资料 更换头像 重置密码 退出登录 ``` 在el-dropdown标签上绑定command事件,当有条目被点击后,会触发这个事件 ```html ``` 提供handleCommand函数,参数为点击条目的command属性值 ```js //dropDown条目被点击后,回调的函数 import {useRouter} from 'vue-router' const router = useRouter() const handleCommand = (command)=>{ if(command==='logout'){ //退出登录 alert('退出登录') }else{ //路由 router.push('/user/'+command) } } ``` #### 2.**退出登录实现:** ```js import {ElMessage,ElMessageBox} from 'element-plus' import { useTokenStore } from '@/stores/token.js' const tokenStore = useTokenStore() const handleCommand = (command) => { if (command === 'logout') { //退出登录 ElMessageBox.confirm( '你确认退出登录码?', '温馨提示', { confirmButtonText: '确认', cancelButtonText: '取消', type: 'warning', } ) .then(async () => { //用户点击了确认 //清空pinia中的token和个人信息 userInfoStore.info={} tokenStore.token='' //跳转到登录页 router.push('/login') }) .catch(() => { //用户点击了取消 ElMessage({ type: 'info', message: '取消退出', }) }) } else { //路由 router.push('/user/' + command) } } ``` ## 十一、修改头像 #### 1. 修改头像页面组件 ```html ``` #### 2. 头像回显 从pinia中读取用户的头像数据 ```js //读取用户信息 import {ref} from 'vue' import {useUserInfoStore} from '@/stores/user.js' const userInfoStore = useUserInfoStore() const imgUrl=ref(userInfoStore.info.userPic) ``` img标签上绑定图片地址 ```html ``` #### 3.头像上传 为el-upload指定属性值,分别有: - ​ action: 服务器接口路径 - ​ headers: 设置请求头,需要携带token - ​ on-success: 上传成功的回调函数 - ​ name: 上传图片的字段名称 ```html ``` 提供上传成功的回调函数 ```js //读取token信息 import {useTokenStore} from '@/stores/token.js' const tokenStore = useTokenStore() //图片上传成功的回调 const uploadSuccess = (result)=>{ //回显图片 imgUrl.value = result.data } ``` 外部触发图片选择 ​ 需要获取到el-upload组件,然后再通过$el.querySelector('input')获取到el-upload对应的元素,触发click事件 ```vue //获取el-upload元素 const uploadRef = ref() 选择图片 ``` #### 4.接口调用 在user.js中提供修改头像的函数 ```js //修改头像 export const userAvatarUpdateService=(avatarUrl)=>{ let params = new URLSearchParams(); params.append('avatarUrl',avatarUrl) return request.patch('/user/updateAvatar',params) } ``` 为【上传头像】按钮绑定单击事件 ```html 上传头像 ``` 提供updateAvatar函数,完成头像更新 ```js //调用接口,更新头像url import {userAvatarUpdateService} from '@/api/user.js' import {ElMessage} from 'element-plus' const updateAvatar = async ()=>{ let result = await userAvatarUpdateService(imgUrl.value) ElMessage.success(result.message? result.message:'修改成功') //更新pinia中的数据 userInfoStore.info.userPic=imgUrl.value } ```