黑马大事故前端资料(附源代码)

打印 上一主题 下一主题

主题 899|帖子 899|积分 2697

黑马大事故前端

前端最终源代码见本文末
视频链接:黑马程序员SpringBoot3+Vue3全套视频教程
一、情况预备

1.1 创建vue工程(big-event)

  1. npm init vue@latest
  2. cd big-event
  3. npm install
复制代码
1.2 安装插件


  • 安装element-plus
    1. 1.1 执行命令: npm install element-plus --save
    2. 1.2 在main.js中做如下配置
    3.         import ElementPlus from 'element-plus'
    4.         import 'element-plus/dist/index.css'
    5.         app.use(ElementPlus)
    复制代码
  • 安装axios
    1. npm install axios
    复制代码
  • 安装sass依靠
    1. npm install sass -D
    复制代码
1.3 目录调解


  • 删除components目录下的内容
  • 删除App.vue中的内容,只保留script和template标签
  • 新建如下目录:
​ api:存放接口调用的js文件
​ utils:存放工具js文件
​ 拷贝request.js到util目录
​ views:存放页面的.vue文件

  • 删除assets目录中的内容, 将资料中的静态资源文件全部拷贝到该目录下
二、注册

2.1 页面搭建

  1. <script setup>
  2. import { User, Lock } from '@element-plus/icons-vue'
  3. import { ref } from 'vue'
  4. //控制注册与登录表单的显示, 默认显示注册
  5. const isRegister = ref(false)
  6. </script>
  7. <template>
  8.     <el-row class="login-page">
  9.         <el-col :span="12" class="bg"></el-col>
  10.         <el-col :span="6" :offset="3" class="form">
  11.             <!-- 注册表单 -->
  12.             <el-form ref="form" size="large" autocomplete="off" v-if="isRegister">
  13.                 <el-form-item>
  14.                     <h1>注册</h1>
  15.                 </el-form-item>
  16.                 <el-form-item>
  17.                     <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
  18.                 </el-form-item>
  19.                 <el-form-item>
  20.                     <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input>
  21.                 </el-form-item>
  22.                 <el-form-item>
  23.                     <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"></el-input>
  24.                 </el-form-item>
  25.                 <!-- 注册按钮 -->
  26.                 <el-form-item>
  27.                     <el-button class="button" type="primary" auto-insert-space>
  28.                         注册
  29.                     </el-button>
  30.                 </el-form-item>
  31.                 <el-form-item class="flex">
  32.                     <el-link type="info" :underline="false" @click="isRegister = false">
  33.                         ← 返回
  34.                     </el-link>
  35.                 </el-form-item>
  36.             </el-form>
  37.             <!-- 登录表单 -->
  38.             <el-form ref="form" size="large" autocomplete="off" v-else>
  39.                 <el-form-item>
  40.                     <h1>登录</h1>
  41.                 </el-form-item>
  42.                 <el-form-item>
  43.                     <el-input :prefix-icon="User" placeholder="请输入用户名"></el-input>
  44.                 </el-form-item>
  45.                 <el-form-item>
  46.                     <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"></el-input>
  47.                 </el-form-item>
  48.                 <el-form-item class="flex">
  49.                     <div class="flex">
  50.                         <el-checkbox>记住我</el-checkbox>
  51.                         <el-link type="primary" :underline="false">忘记密码?</el-link>
  52.                     </div>
  53.                 </el-form-item>
  54.                 <!-- 登录按钮 -->
  55.                 <el-form-item>
  56.                     <el-button class="button" type="primary" auto-insert-space>登录</el-button>
  57.                 </el-form-item>
  58.                 <el-form-item class="flex">
  59.                     <el-link type="info" :underline="false" @click="isRegister = true">
  60.                         注册 →
  61.                     </el-link>
  62.                 </el-form-item>
  63.             </el-form>
  64.         </el-col>
  65.     </el-row>
  66. </template>
  67. <style lang="scss" scoped>
  68. /* 样式 */
  69. .login-page {
  70.     height: 100vh;
  71.     background-color: #fff;
  72.     .bg {
  73.         background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,
  74.             url('@/assets/login_bg.jpg') no-repeat center / cover;
  75.         border-radius: 0 20px 20px 0;
  76.     }
  77.     .form {
  78.         display: flex;
  79.         flex-direction: column;
  80.         justify-content: center;
  81.         user-select: none;
  82.         .title {
  83.             margin: 0 auto;
  84.         }
  85.         .button {
  86.             width: 100%;
  87.         }
  88.         .flex {
  89.             width: 100%;
  90.             display: flex;
  91.             justify-content: space-between;
  92.         }
  93.     }
  94. }
  95. </style>
复制代码
2.2 页面数据绑定与事故绑定

2.2.1 数据绑定

  1. //用于注册的数据模型
  2. const registerData = ref({
  3.     username: '',
  4.     password: '',
  5.     rePassword: ''
  6. })
复制代码
2.2.2 表单校验

  1. //自定义确认密码的校验函数
  2. const rePasswordValid = (rule, value, callback) => {
  3.     if (value == null || value === '') {
  4.         return callback(new Error('请再次确认密码'))
  5.     }
  6.     if (registerData.password !== value) {
  7.         return callback(new Error('两次输入密码不一致'))
  8.     }
  9. }
  10. //用于注册的表单校验模型
  11. const registerDataRules = ref({
  12.     username: [
  13.         { required: true, message: '请输入用户名', trigger: 'blur' },
  14.         { min: 5, max: 16, message: '用户名的长度必须为5~16位', trigger: 'blur' }
  15.     ],
  16.     password: [
  17.         { required: true, message: '请输入密码', trigger: 'blur' },
  18.         { min: 5, max: 16, message: '密码长度必须为5~16位', trigger: 'blur' }
  19.     ],
  20.     rePassword: [
  21.         { validator: rePasswordValid, trigger: 'blur' }
  22.     ]
  23. })
复制代码
2.2.3 事故绑定

  1. //用于注册的事件函数
  2. const register = () => {
  3.     console.log('注册...');
  4. }
复制代码
2.3 接口调用

2.3.1 在src/api/user.js中提供访问注册接口的函数

  1. //注册
  2. export const registerService = (registerData) => {
  3.     var params = new URLSearchParams()
  4.     for (let key in registerData) {
  5.         params.append(key, registerData[key])
  6.     }
  7.     return request.post('/user/register', params)
  8. }
复制代码
2.3.2 在Login.vue中完成注册接口调用

  1. import { registerService} from '@/api/user.js'
  2. //用于注册的事件函数
  3. const register = async () => {
  4.     //console.log('注册...');
  5.     let result = await registerService(registerData.value);
  6.     if (result.code == 0) {
  7.         alert('注册成功!')
  8.     } else {
  9.         alert('注册失败!')
  10.     }
  11. }
复制代码
2.4 处理跨域问题

由于发起ajax请求的域为http://localhost:5173, 而后台服务器的域为 http://localhost:8080, 所以欣赏器会限定该请求的发送, 这种问题称为跨域问题, 跨域问题可以在服务器端办理,也可以在欣赏器端办理, 咱们这一块通过设置署理的方式办理
request.js中设置统一前缀 /api
  1. //定制请求的实例//导入axios  npm install axios
  2. import axios from 'axios';//定义一个变量,记录公共的前缀  ,  baseURLconst baseURL = '/api';const instance = axios.create({baseURL})//添加相应拦截器instance.interceptors.response.use(    result=>{        return result.data;    },    err=>{        alert('服务异常');        return Promise.reject(err);//异步的状态转化成失败的状态    })export default instance;
复制代码
vie.config.js中设置署理
  1. import { fileURLToPath, URL } from 'node:url'
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. // https://vitejs.dev/config/
  5. export default defineConfig({
  6.   plugins: [
  7.     vue(),
  8.   ],
  9.   resolve: {
  10.     alias: {
  11.       '@': fileURLToPath(new URL('./src', import.meta.url))
  12.     }
  13.   },
  14.   //配置代理
  15.   server: {
  16.     proxy: {
  17.       '/api': {
  18.         target: 'http://localhost:8080', // 后端服务器地址
  19.         changeOrigin: true, // 是否改变请求域名
  20.         rewrite: (path) => path.replace(/^\/api/, '')//将原有请求路径中的api替换为''
  21.       }
  22.     }
  23.   }
  24. })
复制代码
三、登录

3.1页面数据绑定与事故绑定

3.1.1 绑定数据

  1. //复用注册表单的数据模型
  2. const registerData = ref({
  3.     username: '',
  4.     password: '',
  5.     rePassword: ''
  6. })
复制代码
3.1.2 每次点击注册或者登录,共用数据模型中的数据

  1. //清空数据模型的数据
  2. const clearRegisterData = () => {
  3.     registerData.value = {
  4.         username: '',
  5.         password: '',
  6.         rePassword: ''
  7.     }
  8. }
复制代码
3.1.3 事故绑定

  1. const login =  () => {
  2.    
  3. }
复制代码
3.2 接口调用

3.2.1 在src/api/user.js中提供访问注册接口的函数

  1. //登录
  2. export const loginService = (loginData)=>{
  3.     var params = new URLSearchParams()
  4.     for(let key in loginData){
  5.         params.append(key,loginData[key])
  6.     }
  7.     return request.post('/user/login',params)
  8. }
复制代码
3.2.2 在Login.vue中完成登录接口调用

  1. import { registerService, loginService } from '@/api/user.js'
  2. //用于登录的事件函数
  3. const login = async () => {
  4.     let result = await loginService(registerData.value)
  5.     if(result.code==0){
  6.         alert('登录成功!')
  7.     }else{
  8.         alert('登录失败!')
  9.     }
  10. }
复制代码
四、优化axios相应截器

在接口调用的API中,我们都需要对业务相应的状态举行判断,从而给用户对应的提示,这个工作不难,但是每个接口的调用,都如许写代码,显然是比力繁琐的,我们可以在axios的相应拦截器中,如果服务器相应乐成了,统一判断后台返回的业务状态码code,如果乐成了,正常返回数据,如果失败了,则给出用户对应的提示即可
请求工具request.js
  1. //添加响应拦截器
  2. instance.interceptors.response.use(
  3.     result => {
  4.         //如果业务状态码为0,代表本次操作成功
  5.         if (result.data.code == 0) {
  6.             return result.data;
  7.         }
  8.         //代码走到这里,代表业务状态码不是0,本次操作失败
  9.         alert(result.data.message || '服务异常');
  10.         return Promise.reject(result.data);//异步的状态转化成失败的状态
  11.     },
  12.     err => {
  13.         alert('服务异常');
  14.         return Promise.reject(err);//异步的状态转化成失败的状态
  15.     }
  16. )
复制代码
接口调用user.js
  1. //用于注册的事件函数
  2. const register = async () => {
  3.     //console.log('注册...');
  4.     await registerService(registerData.value);
  5.     alert('注册成功!')
  6. }
  7. //用于登录的事件函数
  8. const login = async () => {
  9.     await loginService(registerData.value)
  10.     alert('登录成功!')
  11.    
  12. }
复制代码
Element-Plus提示框的使用
  1. import { ElMessage } from 'element-plus'
  2. ElMessage.error('服务异常');
  3. ElMessage.success('登录成功!')
复制代码
五、主页面布局


  1. <script setup>
  2. import {
  3.     Management,
  4.     Promotion,
  5.     UserFilled,
  6.     User,
  7.     Crop,
  8.     EditPen,
  9.     SwitchButton,
  10.     CaretBottom
  11. } from '@element-plus/icons-vue'
  12. import avatar from '@/assets/default.png'
  13. </script>
  14. <template>
  15.     <el-container class="layout-container">
  16.         <!-- 左侧菜单 -->
  17.         <el-aside width="200px">
  18.             <div class="el-aside__logo"></div>
  19.             <el-menu active-text-color="#ffd04b" background-color="#232323"  text-color="#fff"
  20.                 router>
  21.                 <el-menu-item >
  22.                     <el-icon>
  23.                         <Management />
  24.                     </el-icon>
  25.                     <span>文章分类</span>
  26.                 </el-menu-item>
  27.                 <el-menu-item >
  28.                     <el-icon>
  29.                         <Promotion />
  30.                     </el-icon>
  31.                     <span>文章管理</span>
  32.                 </el-menu-item>
  33.                 <el-sub-menu >
  34.                     <template #title>
  35.                         <el-icon>
  36.                             <UserFilled />
  37.                         </el-icon>
  38.                         <span>个人中心</span>
  39.                     </template>
  40.                     <el-menu-item >
  41.                         <el-icon>
  42.                             <User />
  43.                         </el-icon>
  44.                         <span>基本资料</span>
  45.                     </el-menu-item>
  46.                     <el-menu-item >
  47.                         <el-icon>
  48.                             <Crop />
  49.                         </el-icon>
  50.                         <span>更换头像</span>
  51.                     </el-menu-item>
  52.                     <el-menu-item >
  53.                         <el-icon>
  54.                             <EditPen />
  55.                         </el-icon>
  56.                         <span>重置密码</span>
  57.                     </el-menu-item>
  58.                 </el-sub-menu>
  59.             </el-menu>
  60.         </el-aside>
  61.         <!-- 右侧主区域 -->
  62.         <el-container>
  63.             <!-- 头部区域 -->
  64.             <el-header>
  65.                 <div>黑马程序员:<strong>东哥</strong></div>
  66.                 <el-dropdown placement="bottom-end">
  67.                     <span class="el-dropdown__box">
  68.                         <el-avatar :src="avatar" />
  69.                         <el-icon>
  70.                             <CaretBottom />
  71.                         </el-icon>
  72.                     </span>
  73.                     <template #dropdown>
  74.                         <el-dropdown-menu>
  75.                             <el-dropdown-item command="profile" :icon="User">基本资料</el-dropdown-item>
  76.                             <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
  77.                             <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
  78.                             <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
  79.                         </el-dropdown-menu>
  80.                     </template>
  81.                 </el-dropdown>
  82.             </el-header>
  83.             <!-- 中间区域 -->
  84.             <el-main>
  85.                 <div style="width: 1290px; height: 570px;border: 1px solid red;">
  86.                     内容展示区
  87.                 </div>
  88.             </el-main>
  89.             <!-- 底部区域 -->
  90.             <el-footer>大事件 ©2023 Created by 黑马程序员</el-footer>
  91.         </el-container>
  92.     </el-container>
  93. </template>
  94. <style lang="scss" scoped>
  95. .layout-container {
  96.     height: 100vh;
  97.     .el-aside {
  98.         background-color: #232323;
  99.         &__logo {
  100.             height: 120px;
  101.             background: url('@/assets/logo.png') no-repeat center / 120px auto;
  102.         }
  103.         .el-menu {
  104.             border-right: none;
  105.         }
  106.     }
  107.     .el-header {
  108.         background-color: #fff;
  109.         display: flex;
  110.         align-items: center;
  111.         justify-content: space-between;
  112.         .el-dropdown__box {
  113.             display: flex;
  114.             align-items: center;
  115.             .el-icon {
  116.                 color: #999;
  117.                 margin-left: 10px;
  118.             }
  119.             &:active,
  120.             &:focus {
  121.                 outline: none;
  122.             }
  123.         }
  124.     }
  125.     .el-footer {
  126.         display: flex;
  127.         align-items: center;
  128.         justify-content: center;
  129.         font-size: 14px;
  130.         color: #666;
  131.     }
  132. }
  133. </style>
复制代码
六、路由

在App.vue中,不能同时展示Login.vue和Layout.vue,实际的需求是用户第一次访问程序,先展示登录页面,当用户登录乐成后,再展示主页面,如果要达成这个需求,需要用到vue提供的路由相干的知识
路由,从出发点到尽头时,决定从出发点到尽头的路径的进程,在前端工程中,路由指的是根据不同的访问路径,展示不同组件的内容。Vue Router是Vue.js的官方路由,它与Vue.js深度集成,让Vue.js构建单页面应用变得更加十拿九稳
6.1安装路由

  1. npm install vue-router@4
复制代码
6.2创建路由器,并导出

在src/router目录下,定义一个js文件,起名为index.js。如许名字的js文件在导入时,可以不写文件名,只要定位到文件所在的文件夹即可,使用起来很方便
  1. //导入vue-router
  2. import { createRouter, createWebHistory } from 'vue-router'
  3. //导入组件
  4. import LoginVue from '@/views/Login.vue'
  5. import LayoutVue from '@/views/Layout.vue'
  6. //定义路由关系
  7. const routes = [
  8.     { path: '/login', component: LoginVue },
  9.     { path: '/', component: LayoutVue }
  10. ]
  11. //创建路由器
  12. const router = createRouter({
  13.     history: createWebHistory(),
  14.     routes: routes
  15. });
  16. export default router
复制代码
6.3在vue应用实例中使用router

在main.js中导入创建应用气力的js文件,并调用实例的use方法使用路由器
  1. import router from '@/router'
  2. app.use(router)
复制代码
6.4定义展示路由组件的地方

在App.vue文件的template标签中,定义router-view标签
  1. <template>
  2.    <router-view></router-view>
  3. </template>
复制代码
将来不管根据路由匹配到的组件内容,会在router-view标签内举行展示
6.5 测试

在欣赏器地址栏分别访问:http://localhost:5173/ 和 http://localhost:5173/login
6.6 路由API

在登录乐成后,需要通过代码的方式将页面切换到首页,此时就需要调用路由器相干的API
获取路由器
  1. import { useRouter } from 'vue-router'
  2. const router = useRouter();
复制代码
调用API
  1. router.push('/')
复制代码
七、子路由

在咱们的主页面中,当用户点击左侧的菜单时,右侧主地区的内容需要发生变化,将来每切换一个菜单,右侧需要加载对应组件的内容举行展示,像如许的场景咱们也需要使用路由来完成
由于这些组件都需要在Layout.vue中展示, 而Layout.vue本身已经参与了路由,因此我们需要在Layout.vue中通过子路由的方式来完成组件的切换
7.1提供菜单对应的组件

可以复制资料中的文件,也可以自己创建:


  • ArticleCategory.vue
  • ArticleManage.vue
  • UserInfo.vue
  • UserAvatar.vue
  • UserResetPassword.vue
7.2设置子路由

在src/router/index.js中设置子路由
  1. //定义路由关系
  2. const routes = [
  3.     { path: '/login', component: LoginVue },
  4.     {
  5.         path: '/',
  6.         component: LayoutVue,
  7.         //重定向
  8.         redirect: '/article/manage',
  9.         //子路由
  10.         children: [
  11.             { path: '/article/category', component: ArticleCategoryVue },
  12.             { path: '/article/manage', component: ArticleManageVue },
  13.             { path: '/user/info', component: UserInfoVue },
  14.             { path: '/user/avatar', component: UserAvatarVUe },
  15.             { path: '/user/password', component: UserResetPasswordVue },
  16.         ]
  17.     }
  18. ]
复制代码
7.3 在Layout.vue组件的右侧中间地区,添加router-view标签

  1. <!-- 中间区域 -->
  2. <el-main>
  3.     <div style="width: 1290px; height: 570px;border: 1px solid red;">
  4.         <router-view></router-view>
  5.     </div>
  6. </el-main>
复制代码
7.4 菜单项设置点击后跳转的路由路径

el-menu-item 标签的index属性可以设置点击后的路由路径
  1. <el-menu-item index="/article/category">
  2.     <el-icon>
  3.         <Management />
  4.     </el-icon>
  5.     <span>文章分类</span>
  6. </el-menu-item>
复制代码
八、文章分类列表

8.1 文章分类组件

  1. <script setup>
  2. import {
  3.     Edit,
  4.     Delete
  5. } from '@element-plus/icons-vue'
  6. import { ref } from 'vue'
  7. const categorys = ref([
  8.     {
  9.         "id": 3,
  10.         "categoryName": "美食",
  11.         "categoryAlias": "my",
  12.         "createTime": "2023-09-02 12:06:59",
  13.         "updateTime": "2023-09-02 12:06:59"
  14.     },
  15.     {
  16.         "id": 4,
  17.         "categoryName": "娱乐",
  18.         "categoryAlias": "yl",
  19.         "createTime": "2023-09-02 12:08:16",
  20.         "updateTime": "2023-09-02 12:08:16"
  21.     },
  22.     {
  23.         "id": 5,
  24.         "categoryName": "军事",
  25.         "categoryAlias": "js",
  26.         "createTime": "2023-09-02 12:08:33",
  27.         "updateTime": "2023-09-02 12:08:33"
  28.     }
  29. ])
  30. </script>
  31. <template>
  32.     <el-card class="page-container">
  33.         <template #header>
  34.             <div class="header">
  35.                 <span>文章分类</span>
  36.                 <div class="extra">
  37.                     <el-button type="primary">添加分类</el-button>
  38.                 </div>
  39.             </div>
  40.         </template>
  41.         <el-table :data="categorys" style="width: 100%">
  42.             <el-table-column label="序号" width="100" type="index"> </el-table-column>
  43.             <el-table-column label="分类名称" prop="categoryName"></el-table-column>
  44.             <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>
  45.             <el-table-column label="操作" width="100">
  46.                 <template #default="{ row }">
  47.                     <el-button :icon="Edit" circle plain type="primary" ></el-button>
  48.                     <el-button :icon="Delete" circle plain type="danger"></el-button>
  49.                 </template>
  50.             </el-table-column>
  51.             <template #empty>
  52.                 <el-empty description="没有数据" />
  53.             </template>
  54.         </el-table>
  55.     </el-card>
  56. </template>
  57. <style lang="scss" scoped>
  58. .page-container {
  59.     min-height: 100%;
  60.     box-sizing: border-box;
  61.     .header {
  62.         display: flex;
  63.         align-items: center;
  64.         justify-content: space-between;
  65.     }
  66. }
  67. </style>
复制代码
8.2 列表接口调用

src/api/article.js
  1. //导入请求工具类
  2. import request from '@/utils/request.js'
  3. //文章分类列表查询
  4. export const articleCategoryListService = ()=>{
  5.     return request.get('/category')
  6. }
复制代码
ArticleCategory.vue
  1. //获取所有文章分类数据
  2. import { articleCategoryListService } from '@/api/article.js'
  3. const getAllCategory = async () => {
  4.     let result = await articleCategoryListService();
  5.     categorys.value = result.data;
  6. }
  7. getAllCategory();
复制代码
但是上述的代码并不能真正的获取到所有文章分类数据,服务器相应状态码为401,由于目前请求头中并没有携带token
九、Pinia状态管理库

Pinia是Vue的专属状态管理库,它答应你跨组件或页面共享状态
9.1安装

  1. npm install pinia
复制代码
9.2使用Pinia

在main.js中,引入pinia,创建pinia实例,并调用vue应用实例的use方法使用pinia
  1. import { createPinia } from 'pinia'
  2. const pinia = createPinia()
  3. app.use(pinia)
复制代码
9.3 定义Store

在src/stores目录下定义token.js
  1. import { defineStore } from "pinia";
  2. import {ref} from 'vue';
  3. /*
  4.     defineStore参数描述:
  5.         第一个参数:给状态起名,具有唯一性
  6.         第二个参数:函数,可以把定义该状态中拥有的内容
  7.     defineStore返回值描述:
  8.         返回的是一个函数,将来可以调用该函数,得到第二个参数中返回的内容
  9. */
  10. export const useTokenStore = defineStore('token',()=>{
  11.     //1.定义描述token
  12.     const token = ref('')
  13.     //2.定义修改token的方法
  14.     const setToken = (newToken)=>{
  15.         token.value = newToken
  16.     }
  17.     //3.定义移除token的方法
  18.     const removeToken = ()=>{
  19.         token.value=''
  20.     }
  21.     return {
  22.         token,setToken,removeToken
  23.     }
  24. })
复制代码
9.4 使用Store

在需要使用状态的地方,导入@/stores/*.js , 使用即可
在Login.vue中导入@/stores/token.js, 并且当用户登录乐成后,将token生存pinia中
  1. //导入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('/')
  2. }
复制代码
在article.js中导入@/stores/token.js, 从pinia中获取到存储的token,在发起查询文章分类列表的时候把token通过请求头的形式携带给服务器
  1. //导入@/stores/token.js
  2. import { useTokenStore } from '../stores/token'
  3. //文章分类列表查询
  4. export const articleCategoryListService = () => {
  5.     //获取token状态
  6.     const tokenStore = useTokenStore()
  7.     //通过请求头Authorization携带token
  8.     return request.get('/category', { headers: { 'Authorization': tokenStore.token } })
  9. }
复制代码
十、axios请求拦截器

当进入主页后,将来要与后台交互,都需要携带token,如果每次请求都写如许的代码,将会比力繁琐,此时可以将携带token的代码通过请求拦截器统一处理
在 src/util/request.js中
  1. //导入token状态
  2. import { useTokenStore } from '@/stores/token.js';
  3. //添加请求拦截器
  4. instance.interceptors.request.use(
  5.     (config)=>{
  6.         //在发送请求之前做什么
  7.         let tokenStore = useTokenStore()
  8.         //如果token中有值,在携带
  9.         if(tokenStore.token){
  10.             config.headers.Authorization=tokenStore.token
  11.         }
  12.         return config
  13.     },
  14.     (err)=>{
  15.         //如果请求错误做什么
  16.         Promise.reject(err)
  17.     }
  18. )
复制代码
十一、Pinia长期化插件

默认情况下,由于pinia是内存存储,当你刷新页面的时候pinia中的数据会丢失,可以借助于persist插件办理这个问题,persist插件支持将pinia中的数据长期化到sessionStorage和localStorage中
11.1 安装persist插件

  1. npm install pinia
  2. -persistedstate-plugin
复制代码
11.2 pinia中使用persist插件

在main.js中
  1. import { createPinia } from 'pinia'
  2. //导入持久化插件
  3. import {createPersistedState} from'pinia-persistedstate-plugin'
  4. const pinia = createPinia()
  5. const persist = createPersistedState()
  6. //pinia使用持久化插件
  7. pinia.use(persist)
  8. app.use(pinia)
复制代码
11.3 在创建定义状态是设置长期化

在src/stores/token.js中
  1. export const useTokenStore = defineStore('token',()=>{
  2.     //1.定义描述token
  3.     const token = ref('')
  4.     //2.定义修改token的方法
  5.     const setToken = (newToken)=>{
  6.         token.value = newToken
  7.     }
  8.     //3.定义移除token的方法
  9.     const removeToken = ()=>{
  10.         token.value=''
  11.     }
  12.     return {
  13.         token,setToken,removeToken
  14.     }
  15. }
  16. ,
  17. //参数持久化
  18. {
  19.     persist:true
  20. }
  21. )
复制代码
十二、未登录统一处理

在后续访问接口时,如果没有登录,则前端不携带token,后台服务器会返回相应状态码401,代表未登录,此时可以在axios的相应拦截器中,统一对未登录的情况做处理
request.js
  1. import router from '@/router'
  2. //添加响应拦截器
  3. instance.interceptors.response.use(
  4.     result => {
  5.         //如果业务状态码为0,代表本次操作成功
  6.         if (result.data.code == 0) {
  7.             return result.data;
  8.         }
  9.         //代码走到这里,代表业务状态码不是0,本次操作失败
  10.         ElMessage.error(result.data.message || '服务异常');
  11.         return Promise.reject(result.data);//异步的状态转化成失败的状态
  12.     },
  13.     err => {
  14.         //如果响应状态码时401,代表未登录,给出对应的提示,并跳转到登录页
  15.         if(err.response.status===401){
  16.             ElMessage.error('请先登录!')
  17.             router.push('/login')
  18.         }else{
  19.             ElMessage.error('服务异常');
  20.         }
  21.         return Promise.reject(err);//异步的状态转化成失败的状态
  22.     }
  23. )
复制代码
十三、添加文章分类

13.1 添加分类弹窗页面

  1. <!-- 添加分类弹窗 -->
  2. <el-dialog v-model="dialogVisible" title="添加弹层" width="30%">
  3.     <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">
  4.         <el-form-item label="分类名称" prop="categoryName">
  5.             <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>
  6.         </el-form-item>
  7.         <el-form-item label="分类别名" prop="categoryAlias">
  8.             <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>
  9.         </el-form-item>
  10.     </el-form>
  11.     <template #footer>
  12.         <span class="dialog-footer">
  13.             <el-button @click="dialogVisible = false">取消</el-button>
  14.             <el-button type="primary"> 确认 </el-button>
  15.         </span>
  16.     </template>
  17. </el-dialog>
复制代码
13.2 数据模型和校验规则

  1. //控制添加分类弹窗
  2. const dialogVisible = ref(false)
  3. //添加分类数据模型
  4. const categoryModel = ref({
  5.     categoryName: '',
  6.     categoryAlias: ''
  7. })
  8. //添加分类表单校验
  9. const rules = {
  10.     categoryName: [
  11.         { required: true, message: '请输入分类名称', trigger: 'blur' },
  12.     ],
  13.     categoryAlias: [
  14.         { required: true, message: '请输入分类别名', trigger: 'blur' },
  15.     ]
  16. }
复制代码
13.3 添加分类按钮单击事故

  1. <el-button type="primary" @click="dialogVisible = true">添加分类</el-button>
复制代码
13.4 接口调用

在article.js中提供添加分类的函数
  1. //添加文章分类
  2. export const articleCategoryAddService = (categoryModel) => {
  3.     return request.post('/category', categoryModel)
  4. }
复制代码
在页面中调用接口
  1. //访问后台,添加文章分类
  2. const addCategory = async ()=>{
  3.     let result = await articleCategoryAddService(categoryModel.value);
  4.     ElMessage.success(result.message? result.message:'添加成功')
  5.     //隐藏弹窗
  6.     dialogVisible.value = false
  7.     //再次访问后台接口,查询所有分类
  8.     getAllCategory()
  9. }
复制代码
  1. <el-button type="primary" @click="addCategory"> 确认 </el-button>
复制代码
十四、修改文章分类

14.1 修改分类弹窗页面

修改分类弹窗和新增文章分类弹窗长的一样,所以可以服用添加分类的弹窗
弹窗标题显示
定义标题
  1. //弹窗标题
  2. const title=ref('')
复制代码
在弹窗上绑定标题
  1. <el-dialog v-model="dialogVisible" :title="title" width="30%">
复制代码
为添加分类按钮绑定事故
  1. <el-button type="primary" @click="title='添加分类';dialogVisible = true">添加分类</el-button>
复制代码
为修改分类按钮绑定事故
  1. <el-button :icon="Edit" circle plain type="primary" @click="title='修改分类';dialogVisible=true"></el-button>
复制代码
14.2 数据回显

当点击修改分类按钮时,需要把当前这一条数据的详细信息显示到修改分类的弹窗上,这个叫回显
通过插槽的方式得到被点击按钮所在行的数据
  1. <template #default="{ row }">
  2.                     <el-button :icon="Edit" circle plain type="primary" @click="updateCategoryEcho(row)"></el-button>
  3.                     <el-button :icon="Delete" circle plain type="danger"></el-button>
  4.                 </template>
复制代码
回显函数
  1. //修改分类回显
  2. const updateCategoryEcho = (row) => {
  3.     title.value = '修改分类'
  4.     dialogVisible.value = true
  5.     //将row中的数据赋值给categoryModel
  6.     categoryModel.value.categoryName=row.categoryName
  7.     categoryModel.value.categoryAlias=row.categoryAlias
  8.     //修改的时候必须传递分类的id,所以扩展一个id属性
  9.     categoryModel.value.id=row.id
  10. }
复制代码
14.3 接口调用

article.js中提供修改分类的函数
  1. //修改分类
  2. export const articleCategoryUpdateService = (categoryModel)=>{
  3.     return request.put('/category',categoryModel)
  4. }
复制代码
修改确定按钮的绑定事故
  1. <span class="dialog-footer">
  2.                 <el-button @click="dialogVisible = false">取消</el-button>
  3.                 <el-button type="primary" @click="title==='添加分类'? addCategory():updateCategory()"> 确认 </el-button>
  4.             </span>
复制代码
调用接口完成修改的函数
  1. //修改分类
  2. const updateCategory=async ()=>{
  3.     let result = await articleCategoryUpdateService(categoryModel.value)
  4.     ElMessage.success(result.message? result.message:'修改成功')
  5.     //隐藏弹窗
  6.     dialogVisible.value=false
  7.     //再次访问后台接口,查询所有分类
  8.     getAllCategory()
  9. }
复制代码
由于现在修改和新增共用了一个数据模型,所以在点击添加分类后,偶然候会显示数据,此时可以将categoryModel中的数据清空
  1. //清空模型数据
  2. const clearCategoryModel = ()=>{
  3.     categoryModel.value.categoryName='',
  4.     categoryModel.value.categoryAlias=''
  5. }
复制代码
修改添加按钮的点击事故
  1. <el-button type="primary" @click="title = '添加分类'; dialogVisible = true;clearCategoryModel()">添加分类</el-button>
复制代码
十五、删除分类(后台需要补齐)

15.1 确认框

  1. //删除分类  给删除按钮绑定事件
  2. const deleteCategory = (row) => {
  3.     ElMessageBox.confirm(
  4.         '你确认删除该分类信息吗?',
  5.         '温馨提示',
  6.         {
  7.             confirmButtonText: '确认',
  8.             cancelButtonText: '取消',
  9.             type: 'warning',
  10.         }
  11.     )
  12.         .then(() => {
  13.             //用户点击了确认
  14.             ElMessage({
  15.                 type: 'success',
  16.                 message: '删除成功',
  17.             })
  18.         })
  19.         .catch(() => {
  20.             //用户点击了取消
  21.             ElMessage({
  22.                 type: 'info',
  23.                 message: '取消删除',
  24.             })
  25.         })
  26. }
复制代码
15.2 接口调用

article.js中提供删除分类的函数
  1. //删除分类
  2. export const articleCategoryDeleteService = (id) => {
  3.     return request.delete('/category?id='+id)
  4. }
复制代码
当用户点击确认后,调用接口删除分类
  1. //删除分类
  2. const deleteCategory = (row) => {
  3.     ElMessageBox.confirm(
  4.         '你确认删除该分类信息吗?',
  5.         '温馨提示',
  6.         {
  7.             confirmButtonText: '确认',
  8.             cancelButtonText: '取消',
  9.             type: 'warning',
  10.         }
  11.     )
  12.         .then(async () => {
  13.             //用户点击了确认
  14.             let result = await articleCategoryDeleteService(row.id)
  15.             ElMessage.success(result.message?result.message:'删除成功')
  16.             //再次调用getAllCategory,获取所有文章分类
  17.             getAllCategory()
  18.         })
  19.         .catch(() => {
  20.             //用户点击了取消
  21.             ElMessage({
  22.                 type: 'info',
  23.                 message: '取消删除',
  24.             })
  25.         })
  26. }
复制代码
十六、文章列表

16.1 文章列表页面组件

  1. <script setup>
  2. import {
  3.     Edit,
  4.     Delete
  5. } from '@element-plus/icons-vue'
  6. import { ref } from 'vue'
  7. //文章分类数据模型
  8. const categorys = ref([
  9.     {
  10.         "id": 3,
  11.         "categoryName": "美食",
  12.         "categoryAlias": "my",
  13.         "createTime": "2023-09-02 12:06:59",
  14.         "updateTime": "2023-09-02 12:06:59"
  15.     },
  16.     {
  17.         "id": 4,
  18.         "categoryName": "娱乐",
  19.         "categoryAlias": "yl",
  20.         "createTime": "2023-09-02 12:08:16",
  21.         "updateTime": "2023-09-02 12:08:16"
  22.     },
  23.     {
  24.         "id": 5,
  25.         "categoryName": "军事",
  26.         "categoryAlias": "js",
  27.         "createTime": "2023-09-02 12:08:33",
  28.         "updateTime": "2023-09-02 12:08:33"
  29.     }
  30. ])
  31. //用户搜索时选中的分类id
  32. const categoryId=ref('')
  33. //用户搜索时选中的发布状态
  34. const state=ref('')
  35. //文章列表数据模型
  36. const articles = ref([
  37.     {
  38.         "id": 5,
  39.         "title": "陕西旅游攻略",
  40.         "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
  41.         "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
  42.         "state": "草稿",
  43.         "categoryId": 2,
  44.         "createTime": "2023-09-03 11:55:30",
  45.         "updateTime": "2023-09-03 11:55:30"
  46.     },
  47.     {
  48.         "id": 5,
  49.         "title": "陕西旅游攻略",
  50.         "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
  51.         "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
  52.         "state": "草稿",
  53.         "categoryId": 2,
  54.         "createTime": "2023-09-03 11:55:30",
  55.         "updateTime": "2023-09-03 11:55:30"
  56.     },
  57.     {
  58.         "id": 5,
  59.         "title": "陕西旅游攻略",
  60.         "content": "兵马俑,华清池,法门寺,华山...爱去哪去哪...",
  61.         "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
  62.         "state": "草稿",
  63.         "categoryId": 2,
  64.         "createTime": "2023-09-03 11:55:30",
  65.         "updateTime": "2023-09-03 11:55:30"
  66.     },
  67. ])
  68. //分页条数据模型
  69. const pageNum = ref(1)//当前页
  70. const total = ref(20)//总条数
  71. const pageSize = ref(3)//每页条数
  72. //当每页条数发生了变化,调用此函数
  73. const onSizeChange = (size) => {
  74.     pageSize.value = size
  75. }
  76. //当前页码发生变化,调用此函数
  77. const onCurrentChange = (num) => {
  78.     pageNum.value = num
  79. }
  80. </script>
  81. <template>
  82.     <el-card class="page-container">
  83.         <template #header>
  84.             <div class="header">
  85.                 <span>文章管理</span>
  86.                 <div class="extra">
  87.                     <el-button type="primary">添加文章</el-button>
  88.                 </div>
  89.             </div>
  90.         </template>
  91.         <!-- 搜索表单 -->
  92.         <el-form inline>
  93.             <el-form-item label="文章分类:">
  94.                 <el-select placeholder="请选择" v-model="categoryId">
  95.                     <el-option
  96.                         v-for="c in categorys"
  97.                         :key="c.id"
  98.                         :label="c.categoryName"
  99.                         :value="c.id">
  100.                     </el-option>
  101.                 </el-select>
  102.             </el-form-item>
  103.             <el-form-item label="发布状态:">
  104.                 <el-select placeholder="请选择" v-model="state">
  105.                     <el-option label="已发布" value="已发布"></el-option>
  106.                     <el-option label="草稿" value="草稿"></el-option>
  107.                 </el-select>
  108.             </el-form-item>
  109.             <el-form-item>
  110.                 <el-button type="primary">搜索</el-button>
  111.                 <el-button>重置</el-button>
  112.             </el-form-item>
  113.         </el-form>
  114.         <!-- 文章列表 -->
  115.         <el-table :data="articles" style="width: 100%">
  116.             <el-table-column label="文章标题" width="400" prop="title"></el-table-column>
  117.             <el-table-column label="分类" prop="categoryId"></el-table-column>
  118.             <el-table-column label="发表时间" prop="createTime"> </el-table-column>
  119.             <el-table-column label="状态" prop="state"></el-table-column>
  120.             <el-table-column label="操作" width="100">
  121.                 <template #default="{ row }">
  122.                     <el-button :icon="Edit" circle plain type="primary"></el-button>
  123.                     <el-button :icon="Delete" circle plain type="danger"></el-button>
  124.                 </template>
  125.             </el-table-column>
  126.             <template #empty>
  127.                 <el-empty description="没有数据" />
  128.             </template>
  129.         </el-table>
  130.         <!-- 分页条 -->
  131.         <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5 ,10, 15]"
  132.             layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"
  133.             @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />
  134.     </el-card>
  135. </template>
  136. <style lang="scss" scoped>
  137. .page-container {
  138.     min-height: 100%;
  139.     box-sizing: border-box;
  140.     .header {
  141.         display: flex;
  142.         align-items: center;
  143.         justify-content: space-between;
  144.     }
  145. }
  146. </style>
复制代码
使用中文语言包,办理分页条中文问题, 在main.js中完成
  1. import locale from 'element-plus/dist/locale/zh-cn.js'
  2. app.use(ElementPlus,{locale})
复制代码
16.2、文章分类数据回显

ArticleMange.vue
  1. //文章列表查询
  2. import { articleCategoryListService } from '@/api/article.js'
  3. const getArticleCategoryList = async () => {
  4.     //获取所有分类
  5.     let resultC = await articleCategoryListService();
  6.     categorys.value = resultC.data
  7. }
  8. getArticleCategoryList();
复制代码


十七、 文章列表接口调用

article.js中提供获取文章列表数据的函数
  1. //文章列表查询
  2. export const articleListService = (params) => {
  3.     return request.get('/article', { params: params })
  4. }
复制代码
ArticleManage.vue中,调用接口获取数据
  1. //文章列表查询
  2. import { articleListService } from '@/api/article.js'
  3. const getArticles = async () => {
  4.     let params = {
  5.         pageNum: pageNum.value,
  6.         pageSize: pageSize.value,
  7.         categoryId: categoryId.value ? categoryId.value : null,
  8.         state: state.value ? state.value : null
  9.     }
  10.     let result = await articleListService(params);
  11.     //渲染列表数据
  12.     articles.value = result.data.items
  13.     //为列表中添加categoryName属性
  14.     for(let i=0;i<articles.value.length;i++){
  15.         let article = articles.value[i];
  16.         for(let j=0;j<categorys.value.length;j++){
  17.             if(article.categoryId===categorys.value[j].id){
  18.                 article.categoryName=categorys.value[j].categoryName
  19.             }
  20.         }
  21.     }
  22.     //渲染总条数
  23.     total.value=result.data.total
  24. }
  25. getArticles()
复制代码
当分页条的当前页和每页条数发生变化,重新发送请求获取数据
  1. //当每页条数发生了变化,调用此函数
  2. const onSizeChange = (size) => {
  3.     pageSize.value = size
  4.     getArticles()
  5. }
  6. //当前页码发生变化,调用此函数
  7. const onCurrentChange = (num) => {
  8.     pageNum.value = num
  9.     getArticles()
  10. }
复制代码
十八、搜刮和重置

为搜刮按钮绑定单击事故,调用getArticles函数即可
  1. <el-button type="primary" @click="getArticles">搜索</el-button>
复制代码
为重置按钮绑定单击事故,清除categoryId和state的之即可
  1. <el-button @click="categoryId='';state=''">重置</el-button>
复制代码
十九、添加文章

19.1 添加文章抽屉组件

  1. import {Plus} from '@element-plus/icons-vue'
  2. //控制抽屉是否显示
  3. const visibleDrawer = ref(false)
  4. //添加表单数据模型
  5. const articleModel = ref({
  6.     title: '',
  7.     categoryId: '',
  8.     coverImg: '',
  9.     content:'',
  10.     state:''
  11. })
复制代码
  1. <!-- 抽屉 -->
  2.         <el-drawer v-model="visibleDrawer" title="添加文章" direction="rtl" size="50%">
  3.             <!-- 添加文章表单 -->
  4.             <el-form :model="articleModel" label-width="100px" >
  5.                 <el-form-item label="文章标题" >
  6.                     <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>
  7.                 </el-form-item>
  8.                 <el-form-item label="文章分类">
  9.                     <el-select placeholder="请选择" v-model="articleModel.categoryId">
  10.                         <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">
  11.                         </el-option>
  12.                     </el-select>
  13.                 </el-form-item>
  14.                 <el-form-item label="文章封面">
  15.                     <el-upload class="avatar-uploader" :auto-upload="false" :show-file-list="false">
  16.                         <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
  17.                         <el-icon v-else class="avatar-uploader-icon">
  18.                             <Plus />
  19.                         </el-icon>
  20.                     </el-upload>
  21.                 </el-form-item>
  22.                 <el-form-item label="文章内容">
  23.                     <div class="editor">富文本编辑器</div>
  24.                 </el-form-item>
  25.                 <el-form-item>
  26.                     <el-button type="primary">发布</el-button>
  27.                     <el-button type="info">草稿</el-button>
  28.                 </el-form-item>
  29.             </el-form>
  30.         </el-drawer>
复制代码
  1. /* 抽屉样式 */
  2. .avatar-uploader {
  3.     :deep() {
  4.         .avatar {
  5.             width: 178px;
  6.             height: 178px;
  7.             display: block;
  8.         }
  9.         .el-upload {
  10.             border: 1px dashed var(--el-border-color);
  11.             border-radius: 6px;
  12.             cursor: pointer;
  13.             position: relative;
  14.             overflow: hidden;
  15.             transition: var(--el-transition-duration-fast);
  16.         }
  17.         .el-upload:hover {
  18.             border-color: var(--el-color-primary);
  19.         }
  20.         .el-icon.avatar-uploader-icon {
  21.             font-size: 28px;
  22.             color: #8c939d;
  23.             width: 178px;
  24.             height: 178px;
  25.             text-align: center;
  26.         }
  27.     }
  28. }
  29. .editor {
  30.   width: 100%;
  31.   :deep(.ql-editor) {
  32.     min-height: 200px;
  33.   }
  34. }
复制代码
为添加文章按钮添加单击事故,展示抽屉
  1. <el-button type="primary" @click="visibleDrawer = true">添加文章</el-button>
复制代码
19.2 富文本编辑器

文章内容需要使用到富文本编辑器,这里咱们使用一个开源的富文本编辑器 Quill
官网地址: https://vueup.github.io/vue-quill/
安装:
  1. npm install @vueup/vue-quill@latest --save
复制代码
导入组件和样式:
  1. import { QuillEditor } from '@vueup/vue-quill'
  2. import '@vueup/vue-quill/dist/vue-quill.snow.css'
复制代码
页面长使用quill组件:
  1. <quill-editor
  2.               theme="snow"
  3.               v-model:content="articleModel.content"
  4.               contentType="html"
  5.               >
  6. </quill-editor>
复制代码
样式美化:
  1. .editor {
  2.   width: 100%;
  3.   :deep(.ql-editor) {
  4.     min-height: 200px;
  5.   }
  6. }
复制代码
19.3 文章封面图片上传

将来当点击+图标,选择本地图片后,el-upload这个组件会主动发送请求,把图片上传到指定的服务器上,而不需要我们自己使用axios发送异步请求,所以需要给el-upload标签添加一些属性,控制请求的发送
auto-upload:是否主动上传
action: 服务器接口路径
name: 上传的文件字段名
headers: 设置上传的请求头
on-success: 上传乐成的回调函数
  1. import {
  2.     Plus
  3. } from '@element-plus/icons-vue'
  4. <el-form-item label="文章封面">
  5.     <el-upload class="avatar-uploader"
  6.                :show-file-list="false"
  7.                >
  8.         <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />
  9.         <el-icon v-else class="avatar-uploader-icon">
  10.             <Plus />
  11.         </el-icon>
  12.     </el-upload>
  13. </el-form-item>
复制代码
注意:

  • 由于这个请求时el-upload主动发送的异步请求,并没有使用咱们的request.js请求工具,所以在请求的路ing上,需要加上/api, 这个时候请求署理才能拦截到这个请求,转发到后台服务器上
  • 要携带请求头,还需要导入pinia状态才可以使用
    1. import { useTokenStore } from '@/stores/token.js'
    2. const tokenStore = useTokenStore();
    复制代码
  • 在乐成的回调函数中,可以拿到服务器相应的数据,其中有一个属性为data,对应的就是图片在阿里云oss上存储的访问地址,需要把它赋值给articleModel的coverImg属性,如许img标签就能显示这张图片了,由于img标签上通过src属性绑定了articleModel.coverImg
    1. //上传图片成功回调
    2. const uploadSuccess = (img) => {
    3.     //img就是后台响应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}
    4.     articleModel.value.coverImg=img.data
    5. }
    复制代码
19.4 添加文章接口调用

article.js中提供添加文章函数
  1. //添加文章
  2. export const articleAddService = (articleModel)=>{
  3.     return request.post('/article',articleModel)
  4. }
复制代码
为已发布和草稿按钮绑定事故
  1. <el-form-item>
  2.     <el-button type="primary" @click="addArticle('已发布')">发布</el-button>
  3.     <el-button type="info" @click="addArticle('草稿')">草稿</el-button>
  4. </el-form-item>
复制代码
ArticleManage.vue中提供addArticle函数完成添加文章接口的调用
  1. //添加文章
  2. const addArticle=async (state)=>{
  3.     articleModel.value.state = state
  4.     let result = await articleAddService(articleModel.value);
  5.     ElMessage.success(result.message? result.message:'添加成功')
  6.     //再次调用getArticles,获取文章
  7.     getArticles()
  8.     //隐藏抽屉
  9.     visibleDrawer.value=false
  10. }
复制代码
二十、顶部导航栏个人信息显示

在Layout.vue中,页面加载完就发送请求,获取个人信息展示,并存储到pinia中,由于将来在个人中心中修改信息的时候还需要使用
user.js中提供获取个人信息的函数
  1. //获取个人信息
  2. export const userInfoGetService = ()=>{
  3.     return request.get('/user/userInfo');
  4. }
复制代码
src/stores/user.js中,定义个人中心状态
  1. import { defineStore } from "pinia"
  2. import {ref} from 'vue'
  3. export const useUserInfoStore = defineStore('userInfo',()=>{
  4.     //1.定义用户信息
  5.     const info = ref({})
  6.     //2.定义修改用户信息的方法
  7.     const setInfo = (newInfo)=>{
  8.         info.value = newInfo
  9.     }
  10.     //3.定义清空用户信息的方法
  11.     const removeInfo = ()=>{
  12.         info.value={}
  13.     }
  14.     return{info,setInfo,removeInfo}
  15. },{
  16.     persist:true
  17. })
复制代码
Layout.vue中获取个人信息,并存储到pinia中
  1. //导入接口函数
  2. import {userInfoGetService} from '@/api/user.js'
  3. //导入pinia
  4. import {useUserInfoStore} from '@/stores/user.js'
  5. const userInfoStore = useUserInfoStore();
  6. import {ref} from 'vue'
  7. //获取个人信息
  8. const getUserInf = async ()=>{
  9.     let result = await userInfoGetService();
  10.     //存储pinia
  11.     userInfoStore.info =result.data;
  12. }
  13. getUserInf()
复制代码
Layout.vue的顶部导航栏中,展示昵称和头像
  1. <div>黑马程序员:<strong>{{ userInfoStore.info.nickname ? userInfoStore.info.nickname : userInfoStore.info.usrename }}</strong></div>
  2. <el-avatar :src="userInfoStore.info.userPic ? userInfoStore.info.userPic : avatar" />
复制代码
二十一、el-dropdown中功能实现

在el-dropdown中有四个子条目,分别是:


  • 基本资料
  • 更换头像
  • 重置密码
  • 退出登录
其中其三个起到路由功能,跟左侧菜单中【个人中心】下面的二级菜单是同样的功能,退出登录需要删除本地pinia中存储的token以及userInfo
路由实现:
在el-dropdown-item标签上添加command属性,属性值和路由表中/user/xxx保持一致
  1. <el-dropdown-menu>
  2.     <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>
  3.     <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>
  4.     <el-dropdown-item command="password" :icon="EditPen">重置密码</el-dropdown-item>
  5.     <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>
  6. </el-dropdown-menu>
复制代码
在el-dropdown标签上绑定command事故,当有条目被点击后,会触发这个事故
  1. <el-dropdown placement="bottom-end" @command="handleCommand">
复制代码
提供handleCommand函数,参数为点击条目标command属性值
  1. //dropDown条目被点击后,回调的函数
  2. import {useRouter} from 'vue-router'
  3. const router = useRouter()
  4. const handleCommand = (command)=>{
  5.     if(command==='logout'){
  6.         //退出登录
  7.         alert('退出登录')
  8.     }else{
  9.         //路由
  10.         router.push('/user/'+command)
  11.     }
  12. }
复制代码
退出登录实现:
  1. import {ElMessage,ElMessageBox} from 'element-plus'
  2. import { useTokenStore } from '@/stores/token.js'
  3. const tokenStore = useTokenStore()
  4. const handleCommand = (command) => {
  5.     if (command === 'logout') {
  6.         //退出登录
  7.         ElMessageBox.confirm(
  8.             '你确认退出登录码?',
  9.             '温馨提示',
  10.             {
  11.                 confirmButtonText: '确认',
  12.                 cancelButtonText: '取消',
  13.                 type: 'warning',
  14.             }
  15.         )
  16.             .then(async () => {
  17.                 //用户点击了确认
  18.                 //清空pinia中的token和个人信息
  19.                 userInfoStore.info={}
  20.                 tokenStore.token=''
  21.                 //跳转到登录页
  22.                 router.push('/login')
  23.             })
  24.             .catch(() => {
  25.                 //用户点击了取消
  26.                 ElMessage({
  27.                     type: 'info',
  28.                     message: '取消退出',
  29.                 })
  30.             })
  31.     } else {
  32.         //路由
  33.         router.push('/user/' + command)
  34.     }
  35. }
复制代码
二十二、基本资料修改

22.1 基本资料页面组件

  1. <script setup>
  2. import { ref } from 'vue'
  3. const userInfo = ref({
  4.     id: 0,
  5.     username: 'zhangsan',
  6.     nickname: 'zs',
  7.     email: 'zs@163.com',
  8. })
  9. const rules = {
  10.     nickname: [
  11.         { required: true, message: '请输入用户昵称', trigger: 'blur' },
  12.         {
  13.             pattern: /^\S{2,10}$/,
  14.             message: '昵称必须是2-10位的非空字符串',
  15.             trigger: 'blur'
  16.         }
  17.     ],
  18.     email: [
  19.         { required: true, message: '请输入用户邮箱', trigger: 'blur' },
  20.         { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
  21.     ]
  22. }
  23. </script>
  24. <template>
  25.     <el-card class="page-container">
  26.         <template #header>
  27.             <div class="header">
  28.                 <span>基本资料</span>
  29.             </div>
  30.         </template>
  31.         <el-row>
  32.             <el-col :span="12">
  33.                 <el-form :model="userInfo" :rules="rules" label-width="100px" size="large">
  34.                     <el-form-item label="登录名称">
  35.                         <el-input v-model="userInfo.username" disabled></el-input>
  36.                     </el-form-item>
  37.                     <el-form-item label="用户昵称" prop="nickname">
  38.                         <el-input v-model="userInfo.nickname"></el-input>
  39.                     </el-form-item>
  40.                     <el-form-item label="用户邮箱" prop="email">
  41.                         <el-input v-model="userInfo.email"></el-input>
  42.                     </el-form-item>
  43.                     <el-form-item>
  44.                         <el-button type="primary">提交修改</el-button>
  45.                     </el-form-item>
  46.                 </el-form>
  47.             </el-col>
  48.         </el-row>
  49.     </el-card>
  50. </template>
复制代码
22.2 表单数据回显

个人信息之前已经存储到了pinia中,只需要从pinia中获取个人信息,替换模板数据即可
  1. import { useUserInfoStore } from '@/stores/user.js';
  2. const userInfoStore = useUserInfoStore()
  3. const userInfo = ref({...userInfoStore.info})
复制代码
22.3 接口调用

在src/api/user.js中提供修改基本资料的函数
  1. //修改个人信息
  2. export const userInfoUpdateService = (userInfo)=>{
  3.     return request.put('/user/update',userInfo)
  4. }
复制代码
为修改按钮绑定单击事故
  1.   <el-button type="primary" @click="updateUserInfo">提交修改</el-button>
复制代码
提供updateUserInfo函数
  1. //修改用户信息
  2. import {userInfoUpdateService} from '@/api/user.js'
  3. import { ElMessage } from 'element-plus';
  4. const updateUserInfo = async ()=>{
  5.     let result = await userInfoUpdateService(userInfo.value)
  6.     ElMessage.success(result.message? result.message:'修改成功')
  7.     //更新pinia中的数据
  8.     userInfoStore.info.nickname=userInfo.value.nickname
  9.     userInfoStore.info.email = userInfo.value.email
  10. }
复制代码
二十三、修改头像

23.1 修改头像页面组件

  1. <script setup>
  2. import { Plus, Upload } from '@element-plus/icons-vue'
  3. import {ref} from 'vue'
  4. import avatar from '@/assets/default.png'
  5. const uploadRef = ref()
  6. //用户头像地址
  7. const imgUrl= avatar
  8. </script>
  9. <template>
  10.     <el-card class="page-container">
  11.         <template #header>
  12.             <div class="header">
  13.                 <span>更换头像</span>
  14.             </div>
  15.         </template>
  16.         <el-row>
  17.             <el-col :span="12">
  18.                 <el-upload
  19.                     ref="uploadRef"
  20.                     class="avatar-uploader"
  21.                     :show-file-list="false"
  22.                     >
  23.                     <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  24.                     <img v-else src="avatar" width="278" />
  25.                 </el-upload>
  26.                 <br />
  27.                 <el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">
  28.                     选择图片
  29.                 </el-button>
  30.                 <el-button type="success" :icon="Upload" size="large">
  31.                     上传头像
  32.                 </el-button>
  33.             </el-col>
  34.         </el-row>
  35.     </el-card>
  36. </template>
  37. <style lang="scss" scoped>
  38. .avatar-uploader {
  39.     :deep() {
  40.         .avatar {
  41.             width: 278px;
  42.             height: 278px;
  43.             display: block;
  44.         }
  45.         .el-upload {
  46.             border: 1px dashed var(--el-border-color);
  47.             border-radius: 6px;
  48.             cursor: pointer;
  49.             position: relative;
  50.             overflow: hidden;
  51.             transition: var(--el-transition-duration-fast);
  52.         }
  53.         .el-upload:hover {
  54.             border-color: var(--el-color-primary);
  55.         }
  56.         .el-icon.avatar-uploader-icon {
  57.             font-size: 28px;
  58.             color: #8c939d;
  59.             width: 278px;
  60.             height: 278px;
  61.             text-align: center;
  62.         }
  63.     }
  64. }
  65. </style>
复制代码
23.2 头像回显

从pinia中读取用户的头像数据
  1. //读取用户信息
  2. import {ref} from 'vue'
  3. import {useUserInfoStore} from '@/stores/user.js'
  4. const userInfoStore = useUserInfoStore()
  5. const imgUrl=ref(userInfoStore.info.userPic)
复制代码
img标签上绑定图片地址
  1. <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  2. <img v-else src="@/assets/avatar.jpg" width="278" />
复制代码
23.3 头像上传

为el-upload指定属性值,分别有:


  • ​ action: 服务器接口路径
  • ​ headers: 设置请求头,需要携带token
  • ​ on-success: 上传乐成的回调函数
  • ​ name: 上传图片的字段名称
  1. <el-upload
  2.            class="avatar-uploader"
  3.            :show-file-list="false"
  4.            :auto-upload="true"
  5.            action="/api/upload"
  6.            name="file"
  7.            :headers="{'Authorization':tokenStore.token}"
  8.            :on-success="uploadSuccess"
  9.            >
  10.     <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  11.     <img v-else src="@/assets/avatar.jpg" width="278" />
  12. </el-upload>
复制代码
提供上传乐成的回调函数
  1. //读取token信息
  2. import {useTokenStore} from '@/stores/token.js'
  3. const tokenStore = useTokenStore()
  4. //图片上传成功的回调
  5. const uploadSuccess = (result)=>{
  6.     //回显图片
  7.     imgUrl.value = result.data
  8. }
复制代码
外部触发图片选择
​ 需要获取到el-upload组件,然后再通过$el.querySelector(‘input’)获取到el-upload对应的元素,触发click事故
  1. //获取el-upload元素
  2. const uploadRef = ref()
  3. <el-button type="primary" :icon="Plus" size="large"  @click="uploadRef.$el.querySelector('input').click()">
  4.     选择图片
  5. </el-button>
复制代码
24.4 接口调用

在user.js中提供修改头像的函数
  1. //修改头像
  2. export const userAvatarUpdateService=(avatarUrl)=>{
  3.     let params = new URLSearchParams();
  4.     params.append('avatarUrl',avatarUrl)
  5.     return request.patch('/user/updateAvatar',params)
  6. }
复制代码
为【上传头像】按钮绑定单击事故
  1. <el-button type="success" :icon="Upload" size="large" @click="updateAvatar">
  2.     上传头像
  3. </el-button>
复制代码
提供updateAvatar函数,完成头像更新
  1. //调用接口,更新头像url
  2. import {userAvatarUpdateService} from '@/api/user.js'
  3. import {ElMessage} from 'element-plus'
  4. const updateAvatar = async ()=>{
  5.     let result = await userAvatarUpdateService(imgUrl.value)
  6.     ElMessage.success(result.message? result.message:'修改成功')
  7.     //更新pinia中的数据
  8.     userInfoStore.info.userPic=imgUrl.value
  9. }
复制代码
前端源代码


一. api

article.js

  1. import request from '@/utils/request.js'//导入@/stores/token.jsimport { useTokenStore } from '@/stores/token.js'export const articleCategoryListService = () => {    //获取token状态    // const tokenStore = useTokenStore()    // //通过请求头Authorization携带token    // return request.get('/category', { headers: { 'Authorization': tokenStore.token } })    return request.get('/category')}//添加文章分类
  2. export const articleCategoryAddService = (categoryModel) => {
  3.     return request.post('/category', categoryModel)
  4. }
  5. //修改分类export const articleCategoryUpdateService = (categoryModel) => {    return request.put('/category', categoryModel)}//删除分类export const articleCategoryDeleteService = (id) => {    return request.delete('/category?id=' + id)}//文章列表查询
  6. export const articleListService = (params) => {
  7.     return request.get('/article', { params: params })
  8. }
  9. //添加文章export const articleAddService = (articleModel) => {    return request.post('/article', articleModel)}//修改文章export const articleEditService = (articleModel) => {    return request.put('/article', articleModel)}//删除文章export const articleDeleteService = (id) => {    return request.delete('/article?id=' + id)}
复制代码
user.js

  1. import request from '@/utils/request.js'
  2. export const userRegisterService = (registerData)=>{
  3.     const params = new URLSearchParams()
  4.     for (let key in registerData) {
  5.         params.append(key, registerData[key])
  6.     }
  7.    return  request.post('/user/register',params)
  8. }
  9. export const userLoginService = (loginData)=>{
  10.     const params = new URLSearchParams()
  11.     for (let key in loginData) {
  12.         params.append(key, loginData[key])
  13.     }
  14.    return  request.post('/user/login',params)
  15. }
  16. export const userInfoService = () => {
  17.     return request.get('/user/userInfo')
  18. }
  19. //修改个人信息
  20. export const userInfoUpdateService = (userInfo) => {
  21.     return request.put('/user/update', userInfo)
  22. }
  23. //修改头像
  24. export const userAvatarUpdateService = (avatarUrl) => {
  25.     let params = new URLSearchParams();
  26.     params.append('avatarUrl', avatarUrl)
  27.     return request.patch('/user/updateAvatar', params)
  28. }
  29. //修改密码
  30. export const userUpdatePasswordService = (pwdModel) => {
  31.     const params = {
  32.         old_pwd: pwdModel.oldPwd,
  33.         new_pwd: pwdModel.newPwd,
  34.         re_pwd: pwdModel.rePwd
  35.     };
  36.     return request.patch('/user/updatePwd', params)
  37. }
复制代码
二. router

index.js

  1. //导入vue-router
  2. import { createRouter, createWebHistory } from 'vue-router'
  3. //导入组件
  4. import LoginVue from '@/views/Login.vue'
  5. import LayoutVue from '@/views/Layout.vue'
  6. import ArticleCategoryVue from '@/views/article/ArticleCategory.vue'
  7. import ArticleManageVue from '@/views/article/ArticleManage.vue'
  8. import UserAvatarVUe from '@/views/user/UserAvatar.vue'
  9. import UserInfoVue from '@/views/user/UserInfo.vue'
  10. import UserResetPasswordVue from '@/views/user/UserResetPassword.vue'
  11. //定义路由关系
  12. const routes = [
  13.     { path: '/login', component: LoginVue },
  14.     {
  15.         path: '/', component: LayoutVue, children:[
  16.      {path: '/article/category', component: ArticleCategoryVue },
  17.     { path: '/article/manage', component: ArticleManageVue },
  18.     { path: '/user/info', component: UserInfoVue },
  19.     { path: '/user/avatar', component: UserAvatarVUe },
  20.     { path: '/user/resetpassword', component: UserResetPasswordVue },
  21.         ]
  22.     }
  23. ]
  24. //创建路由器
  25. const router = createRouter({
  26.     history: createWebHistory(),
  27.     routes: routes
  28. });
  29. export default router
复制代码
三. stores

token.js

  1. import { defineStore } from "pinia";
  2. import { ref } from 'vue';
  3. /*
  4.     defineStore参数描述:
  5.         第一个参数:给状态起名,具有唯一性
  6.         第二个参数:函数,可以把定义该状态中拥有的内容
  7.     defineStore返回值描述:
  8.         返回的是一个函数,将来可以调用该函数,得到第二个参数中返回的内容
  9. */
  10. export const useTokenStore = defineStore('token', () => {
  11.     //1.定义描述token
  12.     const token = ref('')
  13.     //2.定义修改token的方法
  14.     const setToken = (newToken) => {
  15.         token.value = newToken
  16.     }
  17.     //3.定义移除token的方法
  18.     const removeToken = () => {
  19.         token.value = ''
  20.     }
  21.     return {
  22.         token, setToken, removeToken
  23.     }
  24. }
  25.     ,
  26.     {
  27.         persist: true
  28.     }
  29. )
复制代码
userInfo.js

  1. import { defineStore } from "pinia"
  2. import { ref } from 'vue'
  3. export const useUserInfoStore = defineStore('userInfo', () => {
  4.     //1.定义用户信息
  5.     const info = ref({})
  6.     //2.定义修改用户信息的方法
  7.     const setInfo = (newInfo) => {
  8.         info.value = newInfo
  9.     }
  10.     //3.定义清空用户信息的方法
  11.     const removeInfo = () => {
  12.         info.value = {}
  13.     }
  14.     return { info, setInfo, removeInfo }
  15. }, {
  16.     persist: true
  17. })
复制代码
四. utils

request.js

  1. //定制请求的实例//导入axios  npm install axios
  2. import axios from 'axios';import { ElMessage } from 'element-plus';//定义一个变量,记录公共的前缀  ,  baseURL// const baseURL = 'http://localhost:8080';const baseURL = '/api';const instance = axios.create({baseURL})//导入token状态import { useTokenStore } from '@/stores/token.js';import router from '@/router/index.js';//添加请求拦截器instance.interceptors.request.use(    (config) => {        //在发送请求之前做什么        let tokenStore = useTokenStore()        //如果token中有值,在携带        if (tokenStore.token) {            config.headers.Authorization = tokenStore.token        }        return config    },    (err) => {        //如果请求错误做什么        Promise.reject(err)    })//添加相应拦截器instance.interceptors.response.use(    result => {        if (result.data.code === 0) {            return result.data;        }        // alert(result.msg?result.msg:'服务异常')        ElMessage.error(result.data.message ? 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);//异步的状态转化成失败的状态    })export default instance;
复制代码
五. views/article

ArticleCategory.vue

  1. <script setup>import {    Edit,    Delete} from '@element-plus/icons-vue'import { ref } from 'vue'const categorys = ref([    {        "id": 3,        "categoryName": "美食",        "categoryAlias": "my",        "createTime": "2023-09-02 12:06:59",        "updateTime": "2023-09-02 12:06:59"    },    {        "id": 4,        "categoryName": "娱乐",        "categoryAlias": "yl",        "createTime": "2023-09-02 12:08:16",        "updateTime": "2023-09-02 12:08:16"    },    {        "id": 5,        "categoryName": "军事",        "categoryAlias": "js",        "createTime": "2023-09-02 12:08:33",        "updateTime": "2023-09-02 12:08:33"    }])import {articleCategoryListService, articleCategoryAddService, articleCategoryUpdateService,articleCategoryDeleteService} from '@/api/article.js'const articleCategoryList = async() => {    let result = await articleCategoryListService();    categorys.value = result.data;}articleCategoryList();//控制添加分类弹窗
  2. const dialogVisible = ref(false)
  3. //添加分类数据模型
  4. const categoryModel = ref({
  5.     categoryName: '',
  6.     categoryAlias: ''
  7. })
  8. //添加分类表单校验
  9. const rules = {
  10.     categoryName: [
  11.         { required: true, message: '请输入分类名称', trigger: 'blur' },
  12.     ],
  13.     categoryAlias: [
  14.         { required: true, message: '请输入分类别名', trigger: 'blur' },
  15.     ]
  16. }
  17. import { ElMessage } from 'element-plus'const addCategory = async() => {      let result = await articleCategoryAddService(categoryModel.value);      ElMessage.success(result.message?result.message:'添加乐成');        articleCategoryList();        dialogVisible.value = false;}const title = ref('')const showDialog = (row) => {    dialogVisible.value = true;    title.value = '编辑分类'    categoryModel.value.categoryName = row.categoryName    categoryModel.value.categoryAlias = row.categoryAlias    categoryModel.value.id = row.id}//修改分类const updateCategory = async () => {    let result = await articleCategoryUpdateService(categoryModel.value)    ElMessage.success(result.message ? result.message : '修改乐成')    //隐藏弹窗    dialogVisible.value = false    //再次访问后台接口,查询所有分类    articleCategoryList()}//清空模型数据const clearCategoryModel = () => {    categoryModel.value.categoryName = '',        categoryModel.value.categoryAlias = ''}//删除分类  给删除按钮绑定事故import { ElMessageBox } from 'element-plus'//删除分类const deleteCategory = (row) => {    ElMessageBox.confirm(        '你确认删除该分类信息吗?',        '温馨提示',        {            confirmButtonText: '确认',            cancelButtonText: '取消',            type: 'warning',        }    )        .then(async () => {            //用户点击了确认            let result = await articleCategoryDeleteService(row.id)            ElMessage.success(result.message ? result.message : '删除乐成')            //再次调用getAllCategory,获取所有文章分类            articleCategoryList()                })        .catch(() => {            //用户点击了取消            ElMessage({                type: 'info',                message: '取消删除',            })        })}</script><template>    <el-card class="page-container">        <template #header>            <div class="header">                <span>文章分类</span>                <div class="extra">                    <el-button type="primary"                        @click="title = '添加分类'; dialogVisible = true; clearCategoryModel()">添加分类</el-button>                </div>            </div>        </template>        <el-table :data="categorys" style="width: 100%">            <el-table-column label="序号" width="100" type="index"> </el-table-column>            <el-table-column label="分类名称" prop="categoryName"></el-table-column>            <el-table-column label="分类别名" prop="categoryAlias"></el-table-column>            <el-table-column label="操作" width="100">                <template #default="{ row }">                    <el-button :icon="Edit" circle plain type="primary" @click="showDialog(row)"></el-button>                    <el-button :icon="Delete" circle plain type="danger" @click="deleteCategory(row)"></el-button>                </template>            </el-table-column>            <template #empty>                <el-empty description="没有数据" />            </template>        </el-table>        <!-- 添加分类弹窗 -->        <el-dialog v-model="dialogVisible" :title="title" width="30%">
  18.             <el-form :model="categoryModel" :rules="rules" label-width="100px" style="padding-right: 30px">                <el-form-item label="分类名称" prop="categoryName">                    <el-input v-model="categoryModel.categoryName" minlength="1" maxlength="10"></el-input>                </el-form-item>                <el-form-item label="分类别名" prop="categoryAlias">                    <el-input v-model="categoryModel.categoryAlias" minlength="1" maxlength="15"></el-input>                </el-form-item>            </el-form>            <template #footer>                <span class="dialog-footer">                    <el-button @click="dialogVisible = false">取消</el-button>                    <el-button type="primary" @click="title==='添加分类'?addCategory():updateCategory()"> 确认 </el-button>                </span>            </template>        </el-dialog>    </el-card></template><style lang="scss" scoped>.page-container {    min-height: 100%;    box-sizing: border-box;    .header {        display: flex;        align-items: center;        justify-content: space-between;    }}</style>
复制代码
ArticleManage.vue

  1. <script setup>import {    Edit,    Delete} from '@element-plus/icons-vue'import { ref } from 'vue'//文章分类数据模型const categorys = ref([    {        "id": 3,        "categoryName": "美食",        "categoryAlias": "my",        "createTime": "2023-09-02 12:06:59",        "updateTime": "2023-09-02 12:06:59"    },    {        "id": 4,        "categoryName": "娱乐",        "categoryAlias": "yl",        "createTime": "2023-09-02 12:08:16",        "updateTime": "2023-09-02 12:08:16"    },    {        "id": 5,        "categoryName": "军事",        "categoryAlias": "js",        "createTime": "2023-09-02 12:08:33",        "updateTime": "2023-09-02 12:08:33"    }])//用户搜刮时选中的分类idconst categoryId = ref('')//用户搜刮时选中的发布状态const state = ref('')//文章列表数据模型const articles = ref([    {        "id": 5,        "title": "陕西旅游攻略",        "content": "戎马俑,华清池,法门寺,西岳...爱去哪去哪...",        "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",        "state": "草稿",        "categoryId": 2,        "createTime": "2023-09-03 11:55:30",        "updateTime": "2023-09-03 11:55:30"    },    {        "id": 5,        "title": "陕西旅游攻略",        "content": "戎马俑,华清池,法门寺,西岳...爱去哪去哪...",        "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",        "state": "草稿",        "categoryId": 2,        "createTime": "2023-09-03 11:55:30",        "updateTime": "2023-09-03 11:55:30"    },    {        "id": 5,        "title": "陕西旅游攻略",        "content": "戎马俑,华清池,法门寺,西岳...爱去哪去哪...",        "coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",        "state": "草稿",        "categoryId": 2,        "createTime": "2023-09-03 11:55:30",        "updateTime": "2023-09-03 11:55:30"    },])//分页条数据模型const pageNum = ref(1)//当前页const total = ref(20)//总条数const pageSize = ref(3)//每页条数//当每页条数发生了变化,调用此函数const onSizeChange = (size) => {    pageSize.value = size    articleList()}//当前页码发生变化,调用此函数const onCurrentChange = (num) => {    pageNum.value = num    articleList()}//文章列表查询import { articleCategoryListService, articleListService, articleAddService, articleEditService, articleDeleteService } from '@/api/article.js'const articleCategoryList = async () => {    //获取所有分类    let resultC = await articleCategoryListService();    categorys.value = resultC.data}const articleList = async () => {    let params = {        pageNum: pageNum.value,        pageSize: pageSize.value,        categoryId: categoryId.value ? categoryId.value : null,        state: state.value ? state.value : null    }    let result = await articleListService(params);    //渲染总条数    total.value = result.data.total    //渲染列表数据    articles.value = result.data.items    //为列表中添加categoryName属性    for (let i = 0; i < articles.value.length; i++) {        let article = articles.value[i];        for (let j = 0; j < categorys.value.length; j++) {            if (article.categoryId === categorys.value[j].id) {                article.categoryName = categorys.value[j].categoryName            }        }    }}articleCategoryList();articleList();import { QuillEditor } from '@vueup/vue-quill'
  2. import '@vueup/vue-quill/dist/vue-quill.snow.css'
  3. import { Plus } from '@element-plus/icons-vue'//控制抽屉是否显示const visibleDrawer = ref(false)//添加表单数据模型const articleModel = ref({    title: '',    categoryId: '',    coverImg: '',    content: '',    state: ''})import { useTokenStore } from '@/stores/token.js';const tokenStore = useTokenStore();//上传图片乐成回调const uploadSuccess = (result) => {    //img就是后台相应的数据,格式为:{code:状态码,message:提示信息,data: 图片的存储地址}    articleModel.value.coverImg = result.data    console.log(result.data)}const title = ref('新增文章')//添加文章import { ElMessage } from 'element-plus'const addArticle = async (state) => {    articleModel.value.state = state    let result = await articleAddService(articleModel.value);    ElMessage.success(result.message ? result.message : '添加乐成')    //隐藏抽屉    visibleDrawer.value = false    //再次调用articleList方法获取文章    articleList();}const showForm = (row) => {    title.value = '编辑文章'    //将文章数据渲染到表单中    articleModel.value.title = row.title    articleModel.value.categoryId = row.categoryId    articleModel.value.coverImg = row.coverImg    articleModel.value.content = row.content    articleModel.value.state = row.state    // 确保编辑器的内容也渲染    articleModel.value.id = row.id}const editArticle = async (state) => {    articleModel.value.state = state    let result = await articleEditService(articleModel.value)    ElMessage.success('修改乐成')    //隐藏抽屉    visibleDrawer.value = false    //再次调用articleList方法获取文章    articleList();}const clearArticelForm = () => {    title.value = '新增文章'    articleModel.value.title = '';    articleModel.value.categoryId = '';    articleModel.value.coverImg = '';    articleModel.value.content = '';    articleModel.value.state = '';    // 确保编辑器的内容也清空    if (document.querySelector('.ql-editor')) {        document.querySelector('.ql-editor').innerHTML = ''; // 清空编辑器的内容    }}const deleteArticle = async (row) => {    let result = await articleDeleteService(row.id)    ElMessage.success(result.message ? result.message : '删除乐成')    articleList()}</script><template>    <el-card class="page-container">        <template #header>            <div class="header">                <span>文章管理</span>                <div class="extra">                    <el-button type="primary" @click="visibleDrawer = true; clearArticelForm()">添加文章</el-button>                </div>            </div>        </template>        <!-- 搜刮表单 -->        <el-form inline>            <el-form-item label=" 文章分类:">                <el-select placeholder="请选择" v-model="categoryId" style="width: 200px;">                    <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">                    </el-option>                </el-select>            </el-form-item>            <el-form-item label="发布状态:">                <el-select placeholder="请选择" v-model="state" style="width: 200px;">                    <el-option label=" 已发布" value="已发布"></el-option>                    <el-option label="草稿" value="草稿"></el-option>                </el-select>            </el-form-item>            <el-form-item>                <el-button type="primary" @click="articleList">搜刮</el-button>                <el-button @click="categoryId = ''; state = ''; articleList()">重置</el-button>            </el-form-item>        </el-form>        <!-- 文章列表 -->        <el-table :data="articles" style="width: 100%">            <el-table-column label="文章标题" width="400" prop="title"></el-table-column>            <el-table-column label="分类" prop="categoryName"></el-table-column>            <el-table-column label="发表时间" prop="createTime"> </el-table-column>            <el-table-column label="状态" prop="state"></el-table-column>            <el-table-column label="操作" width="100">                <template #default="{ row }">                    <el-button :icon="Edit" circle plain type="primary"                        @click="visibleDrawer = true; showForm(row);"></el-button>                    <el-button :icon="Delete" circle plain type="danger" @click="deleteArticle(row)"></el-button>                </template>            </el-table-column>            <template #empty>                <el-empty description="没有数据" />            </template>        </el-table>        <!-- 分页条 -->        <el-pagination v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[3, 5, 10, 15]"            layout="jumper, total, sizes, prev, pager, next" background :total="total" @size-change="onSizeChange"            @current-change="onCurrentChange" style="margin-top: 20px; justify-content: flex-end" />        <!-- 抽屉 -->        <el-drawer v-model="visibleDrawer" :title="title" direction="rtl" size="50%">            <!-- 添加文章表单 -->            <el-form :model="articleModel" label-width="100px">                <el-form-item label="文章标题">                    <el-input v-model="articleModel.title" placeholder="请输入标题"></el-input>                </el-form-item>                <el-form-item label="文章分类">                    <el-select placeholder="请选择" v-model="articleModel.categoryId">                        <el-option v-for="c in categorys" :key="c.id" :label="c.categoryName" :value="c.id">                        </el-option>                    </el-select>                </el-form-item>                <el-form-item label="文章封面">                    <el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false" action="/api/upload"                        name="file" :headers="{ 'Authorization': tokenStore.token }" :on-success="uploadSuccess">                        <img v-if="articleModel.coverImg" :src="articleModel.coverImg" class="avatar" />                        <el-icon v-else class="avatar-uploader-icon">                            <Plus />                        </el-icon>                    </el-upload>                </el-form-item>                <el-form-item label="文章内容">                    <div class="editor">                        <quill-editor theme="snow" v-model:content="articleModel.content" contentType="html">                        </quill-editor>                    </div>                </el-form-item>                <el-form-item>                    <el-button type="primary"                        @click="title === '编辑文章' ? editArticle('已发布') : addArticle('已发布')">发布</el-button>                    <el-button type="info"                        @click="title === '编辑文章' ? editArticle('草稿') : addArticle('草稿')">草稿</el-button>                </el-form-item>            </el-form>        </el-drawer>    </el-card></template><style lang="scss" scoped>.page-container {    min-height: 100%;    box-sizing: border-box;    .header {        display: flex;        align-items: center;        justify-content: space-between;    }}/* 抽屉样式 */.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;    }}</style>
复制代码
六. views/user

UserAvatar.vue

  1. <script setup>
  2. import { Plus, Upload } from '@element-plus/icons-vue'
  3. import { ref } from 'vue'
  4. import avatar from '@/assets/default.png'
  5. const uploadRef = ref()
  6. import { useTokenStore } from '@/stores/token.js'
  7. const tokenStore = useTokenStore()
  8. import { useUserInfoStore } from '@/stores/userInfo.js'
  9. const userInfoStore = useUserInfoStore()
  10. //用户头像地址
  11. const imgUrl = ref(userInfoStore.info.userPic)
  12. const uploadSuccess = (result) => {
  13.    imgUrl.value = result.data
  14. }
  15. //调用接口,更新头像url
  16. import { userAvatarUpdateService } from '@/api/user.js'
  17. import { ElMessage } from 'element-plus'
  18. const updateAvatar = async () => {
  19.     let result = await userAvatarUpdateService(imgUrl.value)
  20.     ElMessage.success(result.message ? result.message : '修改成功')
  21.     //更新pinia中的数据
  22.     userInfoStore.info.userPic = imgUrl.value
  23. }
  24. </script>
  25. <template>
  26.     <el-card class="page-container">
  27.         <template #header>
  28.             <div class="header">
  29.                 <span>更换头像</span>
  30.             </div>
  31.         </template>
  32.         <el-row>
  33.             <el-col :span="12">
  34.                 <el-upload ref="uploadRef" class="avatar-uploader" :show-file-list="false" :auto-upload="true"
  35.                     action="/api/upload" name="file" :headers="{'Authorization': tokenStore.token}"
  36.                     :on-success="uploadSuccess">
  37.                     <img v-if="imgUrl" :src="imgUrl" class="avatar" />
  38.                     <img v-else :src="avatar" width="278" />
  39.                 </el-upload>
  40.                 <br />
  41.                 <el-button type="primary" :icon="Plus" size="large"
  42.                     @click="uploadRef.$el.querySelector('input').click()">
  43.                     选择图片
  44.                 </el-button>
  45.                 <el-button type="success" :icon="Upload" size="large" @click="updateAvatar">
  46.                     上传头像
  47.                 </el-button>
  48.             </el-col>
  49.         </el-row>
  50.     </el-card>
  51. </template>
  52. <style lang="scss" scoped>
  53. .avatar-uploader {
  54.     :deep() {
  55.         .avatar {
  56.             width: 278px;
  57.             height: 278px;
  58.             display: block;
  59.         }
  60.         .el-upload {
  61.             border: 1px dashed var(--el-border-color);
  62.             border-radius: 6px;
  63.             cursor: pointer;
  64.             position: relative;
  65.             overflow: hidden;
  66.             transition: var(--el-transition-duration-fast);
  67.         }
  68.         .el-upload:hover {
  69.             border-color: var(--el-color-primary);
  70.         }
  71.         .el-icon.avatar-uploader-icon {
  72.             font-size: 28px;
  73.             color: #8c939d;
  74.             width: 278px;
  75.             height: 278px;
  76.             text-align: center;
  77.         }
  78.     }
  79. }
  80. </style>
复制代码
UserInfo.vue

  1. <script setup>import { ref } from 'vue'import { useUserInfoStore } from '@/stores/userInfo.js';const userInfoStore = useUserInfoStore()const userInfo = ref({ ...userInfoStore.info })const rules = {    nickname: [        { required: true, message: '请输入用户昵称', trigger: 'blur' },        {            pattern: /^\S{2,10}$/,            message: '昵称必须是2-10位的非空字符串',            trigger: 'blur'        }    ],    email: [        { required: true, message: '请输入用户邮箱', trigger: 'blur' },        { type: 'email', message: '邮箱格式不精确', trigger: 'blur' }    ]}import { userInfoUpdateService } from '@/api/user.js'import { ElMessage } from 'element-plus'//修改个人信息const updateUserInfo = async() => {    let result = await userInfoUpdateService(userInfo.value)    ElMessage.success(result.message ? result.message : '修改乐成')    //修改pinia用户信息    userInfoStore.setInfo(userInfo.value)}</script><template>    <el-card class="page-container">        <template #header>            <div class="header">                <span>基本资料</span>            </div>        </template>        <el-row>            <el-col :span="12">                <el-form :model="userInfo" :rules="rules" label-width="100px" size="large">                    <el-form-item label="登录名称">                        <el-input v-model="userInfo.username" disabled></el-input>                    </el-form-item>                    <el-form-item label="用户昵称" prop="nickname">                        <el-input v-model="userInfo.nickname"></el-input>                    </el-form-item>                    <el-form-item label="用户邮箱" prop="email">                        <el-input v-model="userInfo.email"></el-input>                    </el-form-item>                    <el-form-item>                        <el-button type="primary" @click="updateUserInfo">提交修改</el-button>
  2.                     </el-form-item>                </el-form>            </el-col>        </el-row>    </el-card></template>
复制代码
UserResetPassword.vue

  1. <template>
  2.         <el-card >
  3.             <el-form style="max-width: 600px" :model="pwdModel" status-icon :rules="rules" label-width="auto"
  4.                 class="demo-ruleForm">
  5.                 <el-form-item label="原密码" prop="oldPwd">
  6.                     <el-input v-model="pwdModel.oldPwd" type="password" autocomplete="off" />
  7.                 </el-form-item>
  8.                 <el-form-item label="新密码" prop="newPwd">
  9.                     <el-input v-model="pwdModel.newPwd" type="password" autocomplete="off" />
  10.                 </el-form-item>
  11.                 <el-form-item label="确认新密码" prop="rePwd">
  12.                     <el-input v-model="pwdModel.rePwd" type="password" autocomplete="off" />
  13.                 </el-form-item>
  14.                 <el-form-item>
  15.                     <el-button type="primary" @click="submitForm()" style="margin-left: 30%;">
  16.                         确认
  17.                     </el-button>
  18.                     <el-button @click="resetForm()">重置</el-button>
  19.                 </el-form-item>
  20.             </el-form>
  21.         </el-card>
  22. </template>
  23. <script lang="ts" setup>
  24. import {ref } from 'vue'
  25. const validateRePwd = (rule, value, callback) => {
  26.     if (value === '') {
  27.         callback(new Error('请再次输入新密码'))
  28.     } else if (value !== pwdModel.value.newPwd) {
  29.         callback(new Error("确认密码与新密码不匹配"))
  30.     } else {
  31.         callback()
  32.     }
  33. }
  34. const pwdModel= ref({
  35.     oldPwd: '',
  36.     newPwd: '',
  37.     rePwd: '',
  38. })
  39. const rules = {
  40.     oldPwd: [{ required: true, message: '请输入原密码', trigger: 'blur' },
  41.         { pattern: /^\S{6,16}$/, message: '密码长度必须是6-16位的非空字符串', trigger: 'blur' }
  42.       
  43.     ],
  44.     newPwd: [{ required: true, message: '请输入新密码', trigger: 'blur' },
  45.         { pattern: /^\S{6,16}$/, message: '密码长度必须是6-16位的非空字符串', trigger: 'blur' }
  46.     ],
  47.     rePwd: [{ validator: validateRePwd, trigger: 'blur' },
  48.     { pattern: /^\S{6,16}$/, message: '密码长度必须是6-16位的非空字符串', trigger: 'blur' }
  49.     ],
  50. }
  51. import { ElMessage } from 'element-plus'
  52. import { useUserInfoStore } from '@/stores/userInfo.js'
  53. import { useRouter } from 'vue-router';
  54. const router = useRouter()
  55. const userInfoStore = useUserInfoStore()
  56. import { userUpdatePasswordService } from '@/api/user.js'
  57. const submitForm = async () => {
  58.    
  59.     let result = await userUpdatePasswordService(pwdModel.value)
  60.     ElMessage.success('修改成功,即将跳转登录页')
  61.     //修改pinia用户信息
  62.     userInfoStore.setInfo(pwdModel.value)
  63.     resetForm()
  64.     setTimeout(() => {
  65.         router.push('/login')
  66.     }, 2000);
  67. }
  68. const resetForm = () => {
  69.     pwdModel.value = {
  70.         oldPwd: '',
  71.         newPwd: '',
  72.         rePwd: ''
  73.     }
  74. }
  75. </script>
复制代码
七. Layout.vue

  1. <script setup>import {    Management,    Promotion,    UserFilled,    User,    Crop,    EditPen,    SwitchButton,    CaretBottom} from '@element-plus/icons-vue'import avatar from '@/assets/default.png'import { userInfoService } from '@/api/user.js'import { useUserInfoStore } from '@/stores/userInfo';import { useTokenStore } from '@/stores/token';const TokenStore= useTokenStore();const userInfoStore = useUserInfoStore();//获取用户详细信息const getUserInfo = async() => {    let result = await userInfoService();    userInfoStore.setInfo(result.data);}getUserInfo();import { ElMessageBox, ElMessage } from 'element-plus';import { useRouter } from 'vue-router'
  2. const router = useRouter();
  3. const handleCommand = (command) => {    if (command === 'logout') {        ElMessageBox.confirm(            '你确认退出登录吗?',            '温馨提示',            {                confirmButtonText: '确认',                cancelButtonText: '取消',                type: 'warning',            }        )            .then(async () => {                TokenStore.removeToken();                userInfoStore.removeInfo();                router.push('/login');                //用户点击了确认                ElMessage.success('退出登录乐成')            })            .catch(() => {                //用户点击了取消                ElMessage({                    type: 'info',                    message: '取消登录',                })            })           } else {        router.push(`/user/${command}`);    }}</script><template>    <el-container class="layout-container">        <!-- 左侧菜单 -->        <el-aside width="200px">            <div class="el-aside__logo"></div>            <el-menu active-text-color="#ffd04b" background-color="#232323" text-color="#fff" router>                <el-menu-item index="/article/category">                    <el-icon>                        <Management />                    </el-icon>                    <span>文章分类</span>                </el-menu-item>                <el-menu-item index="/article/manage">                    <el-icon>                        <Promotion />                    </el-icon>                    <span>文章管理</span>                </el-menu-item>                <el-sub-menu>                    <template #title>                        <el-icon>                            <UserFilled />                        </el-icon>                        <span>个人中心</span>                    </template>                    <el-menu-item index="/user/info">                        <el-icon>                            <User />                        </el-icon>                        <span>基本资料</span>                    </el-menu-item>                    <el-menu-item index="/user/avatar">                        <el-icon>                            <Crop />                        </el-icon>                        <span>更换头像</span>                    </el-menu-item>                    <el-menu-item index="/user/resetpassword">                        <el-icon>                            <EditPen />                        </el-icon>                        <span>重置密码</span>                    </el-menu-item>                </el-sub-menu>            </el-menu>        </el-aside>        <!-- 右侧主地区 -->        <el-container>            <!-- 头部地区 -->            <el-header>                <div>黑马程序员:<strong>{{ userInfoStore.info.nickname }}</strong></div>                <el-dropdown placement="bottom-end" @command="handleCommand">
  4.                     <span class="el-dropdown__box">                        <el-avatar :src="userInfoStore.info.userPic? userInfoStore.info.userPic : avatar" />                        <el-icon>                            <CaretBottom />                        </el-icon>                    </span>                    <template #dropdown>                        <el-dropdown-menu>                            <el-dropdown-item command="info" :icon="User">基本资料</el-dropdown-item>                            <el-dropdown-item command="avatar" :icon="Crop">更换头像</el-dropdown-item>                            <el-dropdown-item command="resetpassword" :icon="EditPen">重置密码</el-dropdown-item>                            <el-dropdown-item command="logout" :icon="SwitchButton">退出登录</el-dropdown-item>                        </el-dropdown-menu>                    </template>                </el-dropdown>            </el-header>            <!-- 中间地区 -->            <el-main>                <!-- <div style="width: 1290px; height: 570px;border: 1px solid red;">                    内容展示区                </div> -->                <router-view></router-view>            </el-main>            <!-- 底部地区 -->            <el-footer>大事故 ©2023 Created by 黑马程序员</el-footer>        </el-container>    </el-container></template><style lang="scss" scoped>.layout-container {    height: 100vh;    .el-aside {        background-color: #232323;        &__logo {            height: 120px;            background: url('@/assets/logo.png') no-repeat center / 120px auto;        }        .el-menu {            border-right: none;        }    }    .el-header {        background-color: #fff;        display: flex;        align-items: center;        justify-content: space-between;        .el-dropdown__box {            display: flex;            align-items: center;            .el-icon {                color: #999;                margin-left: 10px;            }            &:active,            &:focus {                outline: none;            }        }    }    .el-footer {        display: flex;        align-items: center;        justify-content: center;        font-size: 14px;        color: #666;    }}</style>
复制代码
八. Login,vue

  1. <script setup>import { User, Lock } from '@element-plus/icons-vue'import { ref } from 'vue'//控制注册与登录表单的显示, 默认显示注册const isRegister = ref(false)const registerData = ref({    username: '',    password: '',    rePassword: ''})//自定义确认密码的校验函数const rePasswordValid = (rule, value, callback) => {    if (value == null || value === '') {        return callback(new Error('请再次确认密码'))    }    if (registerData.value.password !== value) {        return callback(new Error('两次输入密码不一致'))    }}//用于注册的表单校验模型const registerDataRules = {    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: rePasswordValid, trigger: 'blur' }    ]}//用于注册的事故函数import { userRegisterService, userLoginService } from '@/api/user.js';import { ElMessage } from 'element-plus';const register = async () => {    let result = await userRegisterService(registerData.value)    // if (result.code === 0) {    //     alert(result.msg?result.msg:'注册乐成')    // }else{    //     alert(result.msg?result.msg:'注册失败')    // }    // alert(result.msg ? result.msg : '注册乐成')    ElMessage.success(result.message ? result.message : '注册乐成')  }import { useTokenStore } from '@/stores/token.js'import { useRouter } from 'vue-router'//调用useTokenStore得到状态const tokenStore = useTokenStore();const router = useRouter();//用于登陆事故函数const login = async () => {    let result = await userLoginService(registerData.value)    //生存token    tokenStore.setToken(result.data)    // if (result.code === 0) {    //     alert(result.msg?result.msg:'登录乐成')    // }else{    //     alert(result.msg?result.msg:'登录失败')    // }    // alert(result.msg ? result.msg : '登录乐成')    ElMessage.success('登录乐成')    router.push('/')
  2. }const clearRegisterData = () => {    registerData.value = {        username: '',        password: '',        rePassword: ''    }}</script><template>    <el-row class="login-page">        <el-col :span="12" class="bg"></el-col>        <el-col :span="6" :offset="3" class="form">            <!-- 注册表单 -->            <el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData"                :rules="registerDataRules">                <el-form-item>                    <h1>注册</h1>                </el-form-item>                <el-form-item prop="username">                    <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>                </el-form-item>                <el-form-item prop="password">                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码"                        v-model="registerData.password"></el-input>                </el-form-item>                <el-form-item prop="rePassword">                    <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码"                        v-model="registerData.rePassword"></el-input>                </el-form-item>                <!-- 注册按钮 -->                <el-form-item>                    <el-button @click="register(registerData)" class="button" type="primary" auto-insert-space>                        注册                    </el-button>                </el-form-item>                <el-form-item class="flex">                    <el-link type="info" :underline="false" @click="isRegister = false; clearRegisterData()">                        ← 返回                    </el-link>                </el-form-item>            </el-form>            <!-- 登录表单 -->            <el-form ref="form" size="large" autocomplete="off" v-else :model="registerData" :rules="registerDataRules">                <el-form-item>                    <h1>登录</h1>                </el-form-item>                <el-form-item prop="username">                    <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>                </el-form-item>                <el-form-item prop="password">                    <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码"                        v-model="registerData.password"></el-input>                </el-form-item>                <el-form-item class="flex">                    <div class="flex">                        <el-checkbox>记着我</el-checkbox>                        <el-link type="primary" :underline="false">忘记密码?</el-link>                    </div>                </el-form-item>                <!-- 登录按钮 -->                <el-form-item>                    <el-button class="button" type="primary" auto-insert-space                        @click="login(registerData)">登录</el-button>                </el-form-item>                <el-form-item class="flex">                    <el-link type="info" :underline="false" @click="isRegister = true; clearRegisterData()">                        注册 →                    </el-link>                </el-form-item>            </el-form>        </el-col>    </el-row></template><style lang="scss" scoped>/* 样式 */.login-page {    height: 100vh;    background-color: #fff;    .bg {        background: url('@/assets/logo2.png') no-repeat 60% center / 240px auto,            url('@/assets/login_bg.jpg') no-repeat center / cover;        border-radius: 0 20px 20px 0;    }    .form {        display: flex;        flex-direction: column;        justify-content: center;        user-select: none;        .title {            margin: 0 auto;        }        .button {            width: 100%;        }        .flex {            width: 100%;            display: flex;            justify-content: space-between;        }    }}</style>
复制代码
九. App.vue

  1. <script setup>
  2. </script>
  3. <template>
  4. <RouterView></RouterView>
  5. </template>
复制代码
十. main.js

  1. import { createApp } from 'vue'
  2. import ElementPlus from 'element-plus'
  3. import 'element-plus/dist/index.css'
  4. import locale from 'element-plus/dist/locale/zh-cn.js'
  5. import App from './App.vue'
  6. import router from '@/router'
  7. import { createPinia } from 'pinia'
  8. //导入持久化插件
  9. import { createPersistedState } from 'pinia-persistedstate-plugin'
  10. const app = createApp(App)
  11. const pinia = createPinia()
  12. const persist = createPersistedState()
  13. //pinia使用持久化插件
  14. pinia.use(persist)
  15. app.use(pinia)
  16. app.use(ElementPlus, { locale })
  17. app.use(router)
  18. app.mount('#app')
复制代码
前端源代码GitHub堆栈:点击检察前端源代码

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大号在练葵花宝典

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表