VUE

vue-admin

1. 项目搭建

创建项目

​ 1.cd C:\Users\lenovo\Desktop\HZ-H5-2213\1213 (进入文件)

  1. npm init vue@latest (创建项目的命令)

    Need to install the following packages:
      create-vue@latest
    Ok to proceed? (y) y
    
  2. y 创建项目

√ Project name: ... vue-admin   项目名称
√ Add TypeScript? ... No / Yes   是否添加ts
√ Add JSX Support? ... No / Yes   是否添加 jsx
√ Add Vue Router for Single Page Application development? ... No / Yes 是否添加路由
√ Add Pinia for state management? ... No / Yes  是否添加 pinia
√ Add Vitest for Unit Testing? ... No / Yes   是否测试
√ Add an End-to-End Testing Solution? » No  是否测试
√ Add ESLint for code quality? ... No / Yes   是否设置编码风格(严格模式)

文件目录介绍

a vue-admin 项目目录

​ b .vscode vscode 中的自带文件

​ b node_modules 项目的依赖文件

​ b public 项目的资源公共文件

​ b .gitignore git忽略文件,不上传提交的文件

​ b index.html 项目中唯一的 html 文件,项目的入口

​ b package.json 项目中 node 的配置文件

​ b README.md 项目的说明文件

​ b vite.config.js 项目的配置文件

​ b src 写代码的主场

​ c assets 项目静态资源文件,会放图片或者样式用

​ c components 放自己封装的组件用的文件夹

​ c router 项目路由文件夹

​ c views (pages) 页面文件

​ c App.vue 项目的根组件

​ c main.js 项目入口 js 文件

vscode组件
vscode-styled-components
Vue 3 Snippets
Vue Language Features (Volar)
Vue VSCode Snippets

2. 项目路由基础

因为我们在创建项目的时候有选择 vue-router 所以我们无需自己手动安装

https://router.vuejs.org/zh/

  1. 导入 vue-router
  2. 创建 路由规则
  3. 创建路由实例
  4. 导出路由实例
  5. 在main.js导入并挂载路由

createWebHistory 路由模式 (History)

History 前面用的是 / 开头,一般是要后端配合使用,容易产生 404

createWebHashHistory 路由模式 (Hash)

Hash 前面用的是 # 开头,无需配合

// src/router/index.js
/*
  1. 先导入 vue-router
  createRouter 创建路由的方法
  createWebHistory 路由模式 (History)    
  History  前面用的是 / 开头,一般是要后端配合使用,容易产生 404
  createWebHashHistory 路由模式 (Hash)    
  Hash 前面用的是 # 开头,无需配合
*/
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
// 导入 home 组件
import HomeView from '../views/HomeView.vue'

// 路由规则配置
const routes = [
  {
    // 访问时的路径
    path: '/',
    // 命名路由,路由别名
    name: 'home',
    // 当访问到对应路由后需要展示的组件
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (About.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    // 路由懒加载,对于一些不确定立即需要显示的页面,可以使用懒加载
    component: () => import('../views/AboutView.vue')
  }
]


// 创建 router 实例
const router = createRouter({
  // 配置路由模式
  history: createWebHashHistory(import.meta.env.BASE_URL),
  // 配置路由规则
  routes
})

// 导出路由
export default router

// src/main.js
// 解构出 createApp
import { createApp } from 'vue'
// 导入根组件
import App from './App.vue'
// 导入路由配置
import router from './router'
// 导入全局样式
import './assets/main.css'
// 创建一个梗 app 实例
const app = createApp(App)
// 将路由挂载到 app 实例中
app.use(router)
// 将 app 实例挂载到 id 为 app 的 元素上
app.mount('#app')

3. 页面UI组件选择

https://element-plus.gitee.io/zh-CN/guide/design.html

安装

# 选择一个你喜欢的包管理器

# NPM
$ npm install element-plus --save

# Yarn
$ yarn add element-plus

# pnpm
$ pnpm install element-plus

配置自动导入

导入方式分 3 种

  1. 全部导入,方便开发,不需要考虑用到什么组件导入什么组件,弊端是打包后项目的体积会变大
  2. 按需导入,自动按需导入不需要考虑导入什么组件,而且打包后项目体积不会特别大
  3. 手动导入,比较麻烦,用什么就要导什么。打包体积小
cnpm install -D unplugin-vue-components unplugin-auto-import
// vite.config.js
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// ++++++++++
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    // ... +++++++++
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

格式化样式

因为我们开发项目阶段会使用到各种标签,有些标签会有一些自带的样式属性,我们可以通过格式化样式的方式将一些常见的不需要的样式去掉

npm install --save normalize.css

安装完成后在 main.js 中导入即可

// src/main.js
// 解构出 createApp
import { createApp } from 'vue'
// 导入根组件
import App from './App.vue'
// 导入路由配置
import router from './router'

// 导入格式化样式文件
import 'normalize.css/normalize.css' // ++++++++++++++++++

// 导入 element-plus 样式
// import 'element-plus/dist/index.css'



// 导入全局样式
// import './assets/main.css'
// 创建一个梗 app 实例
const app = createApp(App)
// 将路由挂载到 app 实例中
app.use(router)
// 将 app 实例挂载到 id 为 app 的 元素上
app.mount('#app')

添加预编译 CSS

因为在开发中写 css 有些时候不够便利,所以我们在开发中可以使用 less 或者 sass 来进行开发

sass: 安装命令 cnpm i sass

只需要安装不需要做其他多余的配置

在写样式的时候需要在 style 标签中添加一个 lang="scss"

icon 组件的安装

在项目开发过程中会用到很多小的图标,这些需要我们自己来安装使用

npm install @element-plus/icons-vue

在组件中如果想要使用 icon

  1. 导入对应需要使用的 icon
  2. 注册该 icon 组件
  3. 使用 icon
import { Fold, Expand } from "@element-plus/icons-vue";
components: {
    Fold,
    Expand
  }
<el-icon size="30">
    <Fold />
</el-icon>

4. 主体搭建

容器布局

https://element-plus.gitee.io/zh-CN/component/container.html#常见页面布局

编写 CSS 样式

<!-- lang="scss" 定义我们的样式是使用 sass 写的 -->
<style lang="scss" scoped>
.el-container {
  background-color: #f8f9fa;
  }

.el-aside {
    background-color: #d0ebff;
}

.el-header {
  background-color: #ced4da;
}
</style>

添加标题和点击收起展开

<script>
// 在项目中 万物皆模块
import zfb from "../assets/zfb.png";
import { Fold, Expand } from "@element-plus/icons-vue";

export default {
  data () {
    return {
      url: zfb,
      collapse: false
    }
  },
  computed: {
    headerLeft () {
      return this.collapse ? 'Expand' : 'Fold'
    },
    asideWidth () {
      return this.collapse ? '54px' : '200px'
    }
  },
  components: {
    Fold,
    Expand,
    AsideCom
  }
}

</script>

<template>
  <div class="common-layout">
    <el-container>

      <!-- 左侧侧边栏 -->
      <el-aside>
        <div class="logo-box">
          <el-image style="width: 32px; height: 32px" :src="url"></el-image>
          <h2 class="manage-title" v-show="!collapse"> 嗨购后台管理系统 </h2>
        </div>
      </el-aside>

      <!-- 右侧内容 -->
      <el-container>

        <!-- 头部 -->
        <el-header>
          <el-icon size="30" @click="collapse = !collapse">
            <component :is="headerLeft"></component>
          </el-icon>
        </el-header>

        <!-- 主体内容 -->
        <el-main>Main</el-main>

      </el-container>
    </el-container>
  </div>
</template>

<!-- lang="scss" 定义我们的样式是使用 sass 写的 -->
<style lang="scss" scoped>
.el-container {
  background-color: #f8f9fa;

  .el-aside {
    background-color: #d0ebff;
    /* 直接将计算属性的值拿过来 */
    width: v-bind(asideWidth);
    /* overflow: hidden; */
    transition: all 0.2s;

    .logo-box {
      height: 60px;
      display: flex;
      align-items: center;
      padding: 0 10px;
      box-sizing: border-box;

      .el-image {
        margin-right: 10px;
      }

      .manage-title {
        font-size: 16px;
      }
    }
  }
}
.el-header {
  background-color: #ced4da;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

侧边栏布局

创建一个组件 components/AsideCom.vue

找到组件

https://element-plus.gitee.io/zh-CN/component/menu.html#侧栏

<!-- src/components/AsideCom.vue  -->
<script>
import { Avatar } from "@element-plus/icons-vue";
export default {
    data () {
        return {

        }
    },
    components: {
        Avatar
    }
}
</script>
<template>
    <el-menu default-active="2" class="el-menu-vertical-demo">

        <el-menu-item> 首页 </el-menu-item>

        <el-sub-menu index="1">
            <template #title>
                <el-icon>
                    <Avatar />
                </el-icon>
                <span>账号管理</span>
            </template>
            <el-menu-item> 管理员列表 </el-menu-item>
            <el-menu-item> 用户列表 </el-menu-item>
        </el-sub-menu>

    </el-menu>
</template>

在 HomeView.vue 中导入组件,然后注册组件,使用组件

<script>
// 在项目中 万物皆模块
import zfb from "../assets/zfb.png";

import { Fold, Expand } from "@element-plus/icons-vue";

// @ 代表的是 src 路径
import AsideCom from "@/components/AsideCom.vue"; // +++++++++++++

export default {
  data () {
    return {
      url: zfb,
      collapse: false
    }
  },
  computed: {
    headerLeft () {
      return this.collapse ? 'Expand' : 'Fold'
    },
    asideWidth () {
      return this.collapse ? '54px' : '200px'
    }
  },
  components: {
    Fold,
    Expand,
    AsideCom // +++++++++++++++++
  }
}

</script>

<template>
  <div class="common-layout">
    <el-container>

      <!-- 左侧侧边栏 -->
      <el-aside>
        <div class="logo-box">
          <el-image style="width: 32px; height: 32px" :src="url"></el-image>
          <h2 class="manage-title" v-show="!collapse"> 嗨购后台管理系统 </h2>
        </div>

        <!-- 使用自定义组件 ++++++++++++++ -->
        <AsideCom /> 


      </el-aside>

      <!-- 右侧内容 -->
      <el-container>
			...
      </el-container>
    </el-container>
  </div>
</template>

<!-- lang="scss" 定义我们的样式是使用 sass 写的 -->
<style lang="scss" scoped>
...
</style>

5 登录页面

静态页面搭建

<script>

export default {
  name: 'LoginView',
  // 因为要让每个组件都有自身独立的数据
  data () {
    return {
      adminname: '',
      password: ''
    }
  }

}

</script>

<template>
  <div class="login-container">

    <div class="form-wrap">
      <h2 class="header">嗨购后台管理系统</h2>
      <el-input v-model="adminname" placeholder="请输入管理员账号" />
      <el-input class="psw" v-model="password" placeholder="请输入密码" />
      <el-button type="success">登录</el-button>
    </div>

  </div>
</template>

<style lang="scss" scoped>
.login-container {
  height: 100%;
  background-color: #dbe4ff;

  .form-wrap {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 360px;
    transform: translate(-50%, -50%);
    background-color: white;
    border-radius: 15px;
    padding: 32px 32px 20px;

    .header {
      text-align: center;
      margin-bottom: 32px;
    }

    .psw {
      margin-top: 20px;
    }

    .el-button {
      width: 100%;
      margin-top: 20px;
    }
  }
}
</style>

表单验证

如果需要使用到正则表达式,可以在 vscode 中安装一个 铁皮盒子 的插件,按 F1 进行选择我们要的正则表达式

  1. 先做表单验证
  2. 加密
  3. 提交到服务器
<script>
import { ElMessage } from 'element-plus'
export default {
  name: 'LoginView',
  // 因为要让每个组件都有自身独立的数据
  data () {
    return {
      adminname: '',
      password: ''
    }
  },
  methods: {
    submitForm () {
      console.log('被调用了', this.adminname, this.password);

      if (this.adminname.length < 5) {
        // 长度不够
        ElMessage.error('管理员账号要大于 5 位')
        // 让代码不在执行
        return
      }
      if (!/^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\W_!@#$%^&*`~()-+=]+$)(?![0-9\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\W_!@#$%^&*`~()-+=]/.test(this.password)) {
        ElMessage.error('密码一定要包含数字,大写字母和小写字母')
        return
      }
      // console.log(/^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\W_!@#$%^&*`~()-+=]+$)(?![0-9\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\W_!@#$%^&*`~()-+=]/.test(this.password));

    }
  }

}

</script>

<template>
  <div class="login-container">

    <div class="form-wrap">
      <h2 class="header">嗨购后台管理系统</h2>
      <el-input v-model="adminname" placeholder="请输入管理员账号" />
      <el-input class="psw" v-model="password" placeholder="请输入密码" />
      <el-button type="success" @click="submitForm">登录</el-button>
    </div>

  </div>
</template>

<style lang="scss" scoped>
...
</style>

MD5 加密

https://www.npmjs.com/package/md5

安装 md5: npm i md5

在需要使用加密的组件中导入 md5 函数

import md5 from "md5";
      // md5 是一个加密函数,我们可以将要加密的字符串传递进去,会返回一个加密好的字符串
      // 我们不能相信用户,因为我们无法阻止用户将密码设置为 123456 
      // 123 456       担心安全问题,所以我们要将用户输入的密码做一次处理
      // 注册和登录一定要使用 一样的加密方式
      console.log(md5('千峰123教育456前端'));

axios 数据请求

https://axios-http.com/zh/

安装命令: npm install axios

直接使用 axios

在需要数据请求的页面中先导入

发送数据请求

import axios from 'axios'
 axios.get('http://121.89.205.189:3000/api/pro/list')
        .then(res => {
          console.log(res);
        })
        .catch(err => {
          console.log(err);
        })

封装 axios

  1. 配置 baseURL
  2. 请求拦截器,就是在请求之前你还需要做什么配置
  3. 响应拦截器,就是服务器给出响应后,返回到前端前我们需要做什么操作, res.data
  4. 就是封装各种请求的方法如 get,post,put...
// src/utils/request.js
import axios from 'axios'

// 封装 baseUrl

// 开发环境(写代码的阶段)       生产环境(代码已经部署服务器了)      测试环境(代码写好阶段)
// development                  production                      production
// npm run dev                  npm run build                    npm run build

// 获取当前环境,判断当前环境是否为开发环境
// const isDev = process.env.NODE_ENV == 'development'
/*
    开发阶段:192.168.1.111
    生产阶段:http://121.89.205.189
*/

const request = axios.create({
    // isDev 为真
    // baseURL: isDev ? '开发环境' : '生产环境'
    // baseURL: isDev ? 'http://121.89.205.189:3000/admin' : 'http://121.89.205.189:3000/admin'
    // baseURL 是用来配置基础路径用的,以后数据请求的时候不需要每次都写对应的地址了
    baseURL: 'http://121.89.205.189:3000/admin',
    timeout: 60000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    // 在发送请求前我们可以将公用的属性设置上
    // 比如可以在这里配置对应的 token
    return config;
}, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
});

// 添加响应拦截器
request.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    // 10119 token失效 
    // 判断用户的登录状态
    // 因为服务器响应的时候有两个 data,所以我这里 return response.data;
    // 让页面中需要一次 .data 即可
    return response.data;
}, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
});

// 自定义各种数据请求的 axios
export default function ajax (config) {
    // 数据请求的时候我们需要什么参数
    // 1. 先获取到请求的一些必要参数
    const { url = '', method = 'GET', data = {}, headers = {} } = config

    // 2. 判断我们请求的类型 get  GET  GeT 
    switch (method.toUpperCase()) {
        case 'GET':
            return request.get(url, { params: data })
        case 'POST':
            // 1. 表单提交数据
            if (headers['content-type'] == 'application/x-www-form-url-encoded') {
                // 转换参数类型,格式化数据
                const obj = new URLSearchParams()
                for (const key in data) {
                    obj.append(key, data[key])
                }
                return request.post(url, obj, { headers })
            }

            // 2. 文件数据
            if (headers['content-type'] == 'multipart/form-data') {
                // 处理文件的对象
                const obj = new FormData()
                for (const key in data) {
                    obj.append(key, data[key])
                }
                return request.post(url, obj, { headers })
            }


            // 3. json 数据提交
            return request.post(url, data)

        case 'PUT':
            // 修改数据 --- 数据的更新

            return request.put(url, data)

        case 'DELETE':
            // 删除数据
            return request.delete(url, { data })

        case 'PATCH':
            // 更新局部资源
            return request.patch(url, data)
        default:
            // 如果前面全部都不是
            return request.request(config)
    }
}

登录功能的实现

创建一个 api/user.js

导入刚封装的 axios

封装请求接口 loginFn

// src/api/user.js
// 导入我们刚封装的 axios
import ajax from '@/utils/request'

// 管理系统登录接口的封装
export function loginFn (params) {

    return ajax({
        method: 'PoSt',
        url: '/admin/login',
        data: params
    })
}


在 login 页面中我们导入 api/user.js

然后发送数据请求

请求成功后直接跳转到首页

<script>
import { ElMessage } from 'element-plus'
import md5 from "md5";

import axios from 'axios'
// 导入数据请求的方法
import { loginFn } from '@/api/user'
export default {
  name: 'LoginView',
  // 因为要让每个组件都有自身独立的数据
  data () {
    return {
      adminname: '',
      password: ''
    }
  },
  methods: {
    submitForm () {
      // console.log('被调用了', this.adminname, this.password);

      // if (this.adminname.length < 5) {
      //   // 长度不够
      //   ElMessage.error('管理员账号要大于 5 位')
      //   // 让代码不在执行
      //   return
      // }


      // if (!/^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\W_!@#$%^&*`~()-+=]+$)(?![0-9\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\W_!@#$%^&*`~()-+=]/.test(this.password)) {

      //   ElMessage.error('密码一定要包含数字,大写字母和小写字母')

      //   return
      // }

      // console.log(/^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_!@#$%^&*`~()-+=]+$)(?![a-z0-9]+$)(?![a-z\W_!@#$%^&*`~()-+=]+$)(?![0-9\W_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9\W_!@#$%^&*`~()-+=]/.test(this.password));


      // md5 是一个加密函数,我们可以将要加密的字符串传递进去,会返回一个加密好的字符串
      // 我们不能相信用户,因为我们无法阻止用户将密码设置为 123456 
      // 123 456       担心安全问题,所以我们要将用户输入的密码做一次处理
      // 注册和登录一定要使用 一样的加密方式
      // console.log(md5('千峰123教育456前端'));

      // 发送数据请求
      // 直接发送请求是没有任何问题的,但是如果页面多请求多,服务器地址一旦
      // 发生变化,那么我就需要修改很多个地方,所以我们要将其封装起来
      // axios.get('http://121.89.205.189:3000/api/pro/list')
      //   .then(res => {
      //     console.log(res);
      //   })
      //   .catch(err => {
      //     console.log(err);
      //   })


        // ++++++++++++++++++++++++++++++++
      loginFn({
        adminname: this.adminname,
        password: this.password
      }).then(res => {
        console.log(res);
        if (res.code === '10005') {
          // 未注册
          ElMessage.error(res.message)
          return
        } else if (res.code === '10003') {
          // 密码错误
          ElMessage.error(res.message)
          return
        } else {

          // this.$router 是我们的路由对象
          // .push 需要进入到哪个路由中
          this.$router.push('/')
          // console.log(this.$router);

        }
      })
        // ++++++++++++++++++++++++++++++++

    }
  }

}

</script>

<template>
  <div class="login-container">

    <div class="form-wrap">
      <h2 class="header">嗨购后台管理系统</h2>
      <el-input v-model="adminname" placeholder="请输入管理员账号" />
      <el-input class="psw" v-model="password" placeholder="请输入密码" />
      <el-button type="success" @click="submitForm">登录</el-button>
    </div>

  </div>
</template>

<style lang="scss" scoped>
...
</style>

6.vuex 全局状态(数据)管理

https://vuex.vuejs.org/zh/

安装 vue: npm install vuex@next --save

在 src 中创建一个 store 的文件夹,然后在内部创建一个 index.js

导入 vuex 然后创建一个 store

将 store 导出去

在 main.js 中导入 sotre

将 store 挂载在 app 上

定义状态

// src/store/index.js
import { createStore } from "vuex";

// 定义全局状态的实例
const store = createStore({
    // 严格模式,在生产环境中不要打开
    strict: process.env.NODE_ENV !== 'production',
    state () {
        // 全局状态
        return {
            count: 100,
        }
    },
    // 唯一修改数据的方法
    mutations: {
        // 唯一修改数据的方法
        add (state) {
            // setTimeout(() => {
            state.count++
            // }, 5000)
        },
        addnum (state, value) {
            state.count += value
        }
    },
    // 异步修改数据的地方
    actions: {
        addActions (context) {
            console.log(context);
            // 所谓的异步也还要调用同步的方法来进行修改数据
            setTimeout(() => {
                context.commit('add')
            }, 5000)

        },
        addnumActions ({ commit }, value) {
            commit('addnum', value)
        }
    },
    // 是 vuex 中的计算属性
    getters: {

    },
    // 状态模块
    modules: {

    }
})

// 导出创建好的实例
export default store

在组件中使用

  1. 可以在模板中直接 $store.state.属性 即可
{{ $store.state.count }}
  1. 通过映射 mapState
import { mapState } from 'vuex'
export default{
 data () {
    return {
      adminname: '',
      password: ''
    }
  },
  computed: {
	//++++++++++++++++++
    ...mapState(['count'])
  }
}
//组件中使用
{{ count }}

组件中修改全局状态

先在全局状态中定义

mutations:vuex 中唯一修改状态的地方
actions:异步操作的地方

  1. 通过 this.$store.commit('') 触发 mutations。通过 this.$store.dispatch('')触发 Actions
  2. 通过 mapMutations 映射
import { mapState, mapMutations } from 'vuex'

export default {
  name: 'LoginView',
  // 因为要让每个组件都有自身独立的数据
  data () {
    return {
      adminname: '',
      password: ''
    }
  },
  computed: {
    ...mapState(['count'])
  },
  methods: {
    ...mapMutations(['updateUserInfo'])
  }
}

// 使用 
this.updateUserInfo(res.data)

7 登录成功之后的处理

  1. 在全局状态定义一个用户信息和修改用户信息的方法
  2. 在登录成功后将数据直接更新到全局状态中即可
// src/store/index.js
import { createStore } from "vuex";

// 定义全局状态的实例
const store = createStore({
    // 严格模式,在生产环境中不要打开
    strict: process.env.NODE_ENV !== 'production',
    state () {
        // 全局状态
        return {
            count: 100,
            // 放用户信息的对象
            userInfo: {} // ++++++
        }
    },
    // 唯一修改数据的方法
    mutations: {
       ...
        // 保存用户数据 +++++++++++
        updateUserInfo (state, value) {
            state.userInfo = value
        }
    },
   
})

// 导出创建好的实例
export default store

<script>
import { ElMessage } from 'element-plus'
import md5 from "md5";

import axios from 'axios'
// 导入数据请求的方法
import { loginFn } from '@/api/user'

import { mapState, mapMutations } from 'vuex' // ++++++++++

export default {
  name: 'LoginView',
  // 因为要让每个组件都有自身独立的数据
  data () {
    return {
      adminname: '',
      password: ''
    }
  },
  computed: {
    ...mapState(['count'])   // +++++++++++++++++++
  },
  methods: {
    ...mapMutations(['updateUserInfo']), // ++++++++++++++++++++
    submitForm () {
      


      loginFn({
        adminname: this.adminname,
        password: this.password
      }).then(res => {
        console.log(res);
        if (res.code === '10005') {
          // 未注册
          ElMessage.error(res.message)
          return
        } else if (res.code === '10003') {
          // 密码错误
          ElMessage.error(res.message)
          return
        } else {

          // 第一种修改数据的方法
          // this.$store.commit('updateUserInfo', res.data)
          // 第二种修改数据的方法
          this.updateUserInfo(res.data) // ++++++++++++++++++++++

          // this.$router 是我们的路由对象
          // .push 需要进入到哪个路由中
          this.$router.push('/')
          // console.log(this.$router);

        }
      })


    }
  }

}

</script>

<template>
  <div class="login-container">

    <div class="form-wrap">
      <h2 class="header"> {{ count }} 嗨购后台管理系统 {{ $store.state.count }}</h2>
      <el-input v-model="adminname" placeholder="请输入管理员账号" />
      <el-input class="psw" v-model="password" placeholder="请输入密码" />
      <el-button type="success" @click="submitForm">登录</el-button>
    </div>

  </div>
</template>

<style lang="scss" scoped>
...
</style>

vuex 中的数据持久化

解决 vuex 中状态刷新页面后会恢复初始值

  1. 通过自己手动存储需要持久化的数据
  2. 通过插件来进行自动持久化

安装插件: npm install --save vuex-persistedstate

使用过程:

1.先导入 import createPersistedState from 'vuex-persistedstate'

2.配置vuex插件 plugins:[]

3.配置需要持久化的数据

plugins: [
        // 创建持久化对象
        createPersistedState({
            // 配置需要持久化的数据
            reducer: state => {
                return {
                    // 需要持久化的数据
                    userInfo: state.userInfo
                }
            }
        })
    ]
// src/store/index.js
import { createStore } from "vuex";
// 导入数据持久化操作的创建函数
import createPersistedState from 'vuex-persistedstate'   // ++++++++++++++

// 定义全局状态的实例
const store = createStore({
    // 严格模式,在生产环境中不要打开
    strict: process.env.NODE_ENV !== 'production',
    state () {
        // 全局状态
        return {
            count: 100,
            // 放用户信息的对象
            userInfo: {

            }
        }
    },
    // 唯一修改数据的方法
    mutations: {
        // 唯一修改数据的方法
        add (state) {
            // setTimeout(() => {
            state.count++
            // }, 5000)
        },
        addnum (state, value) {
            state.count += value
        },
        // 保存用户数据
        updateUserInfo (state, value) {
            state.userInfo = value
        }
    },
    // 异步修改数据的地方
    actions: {
        addActions (context) {
            console.log(context);
            // 所谓的异步也还要调用同步的方法来进行修改数据

            setTimeout(() => {
                context.commit('add')
            }, 5000)

        },
        addnumActions ({ commit }, value) {
            commit('addnum', value)
        }
    },
    // 是 vuex 中的计算属性
    getters: {

    },
    // 状态模块
    modules: {

    },
    // vuex配置插件
    plugins: [
        // 创建持久化对象
        createPersistedState({
            // 配置需要持久化的数据
            reducer: state => {
                return {
                    // 需要持久化的数据
                    userInfo: state.userInfo
                }
            }
        })
    ]
})

// 导出创建好的实例
export default store

在首页验证用户是否登录

 mounted () {
    console.log(this.$store.state.userInfo.adminname);
    if (!this.$store.state.userInfo.adminname) {
      // 用户没有登录
      this.$router.push('/login')
    }
  }

退出登录

添加退出登录的按钮

<span> 欢迎 {{ $store.state.userInfo.adminname }}<button @click="logout">退出</button> </span>

 logout () {
      localStorage.clear()
      this.$store.commit('updateUserInfo', {})
      this.$router.push('/login')
    }

8. 管理员管理

先创建路由和页面

在 views 中创建一个 manager 的文件夹

在文件夹中创建 3 个页面

<!-- src/views/manager/ManagerIndexView.vue -->
<template>
    <router-view></router-view>
</template>
<!-- src/views/manager/ManagerListView.vue -->
<template>
    <div>
        管理员列表
    </div>
</template>
<!-- src/views/manager/UserListView.vue -->
<template>
    <div>
        用户列表
    </div>
</template>

路由创建


// src/router/index.js
/*
  1. 先导入 vue-router
  createRouter 创建路由的方法
  createWebHistory 路由模式 (History)    
  History  前面用的是 / 开头,一般是要后端配合使用,容易产生 404
  createWebHashHistory 路由模式 (Hash)    
  Hash 前面用的是 # 开头,无需配合
*/
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
// 导入 home 组件
import HomeView from '../views/HomeView.vue'

// 导入管路员和用户的组件
import MangerIndexView from "@/views/manager/MangerIndexView.vue";
import MangerListView from "@/views/manager/MangerListView.vue";
import UserListView from "@/views/manager/UserListView.vue";

// 导入轮播图管理相关组件
import BannerIndexView from '../views/banner/BannerIndexView.vue'
import BannerListView from '../views/banner/BannerListView.vue'
import AddBannerView from '../views/banner/AddBannerView.vue'

// 路由规则配置
const routes = [
  {
    // 访问时的路径
    path: '/',
    // 命名路由,路由别名
    name: 'home',
    // 当访问到对应路由后需要展示的组件
    component: HomeView,
    // 路由嵌套,配置二级路由
    children: [
      {
        path: 'manager',
        name: 'manager',
        label: '账号管理',
        component: MangerIndexView,
        // 配置三级路由
        children: [
          {
            path: 'managerlist',
            name: 'managerlist',
            label: '管理员列表',
            component: MangerListView
          },
          {
            path: 'userlist',
            name: 'userlist',
            label: '用户列表',
            component: UserListView
          }
        ]
      },
      {
        path: 'banner',
        name: 'banner',
        label: '轮播图管理',
        component: BannerIndexView,
        children: [
          {
            path: 'bannerlist',
            name: 'bannerlist',
            label: '轮播图列表',
            component: BannerListView,
          },
          {
            path: 'addbanner',
            name: 'addbanner',
            label: '添加轮播图',
            component: AddBannerView,
          }
        ]
      }
    ]
  },
  {
    path: '/login',
    name: 'login',
    // route level code-splitting
    // this generates a separate chunk (About.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    // 路由懒加载,对于一些不确定立即需要显示的页面,可以使用懒加载
    component: () => import('../views/LoginView.vue')
  }
]


// 创建 router 实例
const router = createRouter({
  // 配置路由模式
  history: createWebHashHistory(import.meta.env.BASE_URL),
  // 配置路由规则
  routes
})

// 导出路由
export default router

管理员列表

  1. 获取到所有管理员数据
  2. 渲染管理员列表
  3. 添加管理员的功能
  4. 修改管理员信息
  5. 删除管理员

渲染管理员列表

// src/api/user.js
// 导入我们刚封装的 axios
import ajax from '@/utils/request'


// 管理系统登录接口的封装
export function loginFn (params) {

    return ajax({
        method: 'PoSt',
        url: '/admin/login',
        data: params
    })

}

// 获取管理员列表
export function adminList () {
    return ajax({
        url: '/admin/list',
        method: 'get'
    })
}


<!-- src/views/manager/ManagerListView.vue -->
<script>
import { adminList } from '@/api/user'
export default {
    data () {
        return {
            tableData: []
        }
    },
    methods: {

    },
    mounted () {
        // 获取管理员列表
        adminList().then(res => {
            console.log(res.data);
            this.tableData = res.data
        })
    }
}
</script>
<template>
    <div>
        管理员列表

        <el-table :data="tableData" style="width: 100%">
            <!-- type="index" 标记当前为索引序号 -->
            <!-- label 列标题 -->
            <el-table-column type="index" label="序号" width="80" />
            <!-- prop 列的数据字段 -->
            <el-table-column prop="adminname" label="管理员名称" width="180" />

            <el-table-column prop="role" label="管理员权限">
                <template #default="scope">
                    <!-- <div> -->
                    <!-- {{ scope.row.role == 1 ? '管理员' : '超级管理员' }} -->
                    <el-tag :type="scope.row.role == 1 ? '' : 'success'">{{ scope.row.role == 1 ? '管理员' : '超级管理员'
                    }}</el-tag>
                    <!-- </div> -->
                </template>
            </el-table-column>


            <el-table-column prop="role" label="操作">
                <template #default>
                    <el-button size="small" type="primary">编辑</el-button>
                    <el-button size="small" type="success">删除</el-button>
                </template>
            </el-table-column>

        </el-table>
    </div>
</template>

分页处理

<!-- src/views/manager/ManagerListView.vue -->
<script>
import { adminList } from '@/api/user'
export default {
    data () {
        return {
            tableData: [],
            // 当前页
            currentPage: 1
        }
    },
    methods: {

    },
    mounted () {
        // 获取管理员列表
        adminList().then(res => {
            console.log(res.data);
            this.tableData = res.data
        })
    },
    computed: {
        // +++++++++++++++ 计算出当前页该显示什么数据 
        computedTableData () {
            return this.tableData.slice((this.currentPage - 1) * 10, (this.currentPage - 1) * 10 + 10)
        }
    }
}
</script>
<template>
    <div>
        管理员列表
        
        <el-table :data="computedTableData" style="width: 100%">
            <!-- type="index" 标记当前为索引序号 -->
            <!-- label 列标题 -->
            <el-table-column type="index" :index="(currentPage - 1) * 10 + 1" label="序号" width="80" />
            <!-- prop 列的数据字段 -->
            <el-table-column prop="adminname" label="管理员名称" width="180" />

            <el-table-column prop="role" label="管理员权限">
                <template #default="scope">
                    <!-- <div> -->
                    <!-- {{ scope.row.role == 1 ? '管理员' : '超级管理员' }} -->
                    <el-tag :type="scope.row.role == 1 ? '' : 'success'">{{ scope.row.role == 1 ? '管理员' : '超级管理员'
                    }}</el-tag>
                    <!-- </div> -->
                </template>
            </el-table-column>


            <el-table-column prop="role" label="操作">
                <template #default>
                    <el-button size="small" type="primary">编辑</el-button>
                    <el-button size="small" type="success">删除</el-button>
                </template>
            </el-table-column>


        </el-table>
        <!-- 用做分页显示 +++++++++++++++++++++++++++++++++ -->
        <el-pagination 
           background 
           v-model:current-page="currentPage" 
           layout="prev, pager, next"
           :total="tableData.length" />


    </div>
</template>

添加管理员

页面布局,添加一个抽屉效果

<div class="header">
     管理员列表
     <el-button type="success" @click="drawer = true">添加管理员</el-button>
</div>

 <!-- 抽屉效果  -->
        <el-drawer v-model="drawer" title="添加管理员">

            <el-form label-width="120px">
                <el-form-item label="管理员账号">
                    <el-input placeholder="请输入管理员账号" v-model="formData.adminname" />
                </el-form-item>

                <el-form-item label="管理员密码">
                    <el-input placeholder="请输入管理员密码" v-model="formData.password" />
                </el-form-item>

                <el-form-item label="管理员角色">
                    <el-select placeholder="请选择角色" v-model="formData.role">
                        <el-option label="管理员" value="管理员" />
                        <el-option label="超级管理员" value="超级管理员" />
                    </el-select>
                </el-form-item>

                <el-form-item>
                    <el-tree show-checkbox ref="treeRef" :data="menus" />
                </el-form-item>

            </el-form>

            <el-button @click="add" type="primary">添加</el-button>
        </el-drawer>

在添加按钮回调函数中获取我们用户输入的内容

将用户输入的内容发送给后端服务器

添加成功后关闭抽屉然后重新获取最新数据

// 获取树形结构中选中的值
        formatCheckedKeys () {
            // console.log(this.$refs.treeRef.getCheckedNodes(true));

            // 根据选中的值得到父级路由
            const list = this.$refs.treeRef.getCheckedNodes(true)

            // 用来放父级路由的 label 用的
            const tempList = []

            // 最终结果用的
            const result = []

            // list: 是我们选中的子路由
            list.forEach(item => {
                // parent:选中后子路的父级路由
                const parent = this.menus.find(mitem => {
                    return mitem.children.some(child => child.label == item.label)
                })

                // console.log('111111', parent);

                // 判断当前父级路由是否在 tempList 中已经有了
                // 2. 第二次遍历用户列表的时候,发现 tempList 中已经有了父级路由
                if (tempList.includes(parent.label)) {
                    // 就直接找到父级路由,在子路由中再添加一个子路由
                    result.find(item => item.label == parent.label).children.push(item)
                } else {
                    // 将对应的内容放在 tempList 中
                    // console.log('11111111', parent.label);
                    tempList.push(parent.label)

                    // 1. 第一次执行的时候讲管理员列表添加进去
                    result.push({
                        label: parent.label,
                        path: parent.path,
                        children: [item]
                    })
                }


                console.log(result);
                // 将路由权限添加到 formData 中
                this.formData.checkedKeys = result
            })

        },
        add () {
            // 将管理员数据发送到服务器
            // 1. 收集数据
            // console.log(this.formData);

            // 获取所有的权限路由
            this.formatCheckedKeys()

            // 2. 将数据发给服务器 
            // console.log(this.formData);
            this.formData.role = this.formData.role == '管理员' ? '1' : '2'


            // 将数据添加服务器中
            addAdmin(this.formData).then(res => {
                // console.log(res);
                if (res.code == '200') {
                    // 成功
                    ElMessage.success(res.message)

                    // 关闭抽屉效果
                    this.drawer = false

                    // 重新获取最新的数据
                    // 获取管理员列表
                    adminList().then(res => {
                        // console.log(res.data);
                        this.tableData = res.data
                    })
                } else {
                    ElMessage.error(res.message)
                }

            })
        }

所有代码

<!-- src/views/manager/ManagerListView.vue -->
<script>
import { adminList, addAdmin } from '@/api/user'

import { routes } from '@/router/index.js'
import { ElMessage } from 'element-plus'

export default {
    data () {
        return {
            tableData: [],
            // 当前页
            currentPage: 1,
            // 是否打开抽屉效果
            drawer: false,
            // 路由信息
            menus: routes[0].children,
            // 管理员数据
            formData: {
                adminname: '',
                password: '',
                role: '',
                checkedKeys: ''
            }
        }
    },
    methods: {
        // 获取树形结构中选中的值
        formatCheckedKeys () {
            // console.log(this.$refs.treeRef.getCheckedNodes(true));

            // 根据选中的值得到父级路由
            const list = this.$refs.treeRef.getCheckedNodes(true)

            // 用来放父级路由的 label 用的
            const tempList = []

            // 最终结果用的
            const result = []

            // list: 是我们选中的子路由
            list.forEach(item => {
                // parent:选中后子路的父级路由
                const parent = this.menus.find(mitem => {
                    return mitem.children.some(child => child.label == item.label)
                })

                // console.log('111111', parent);

                // 判断当前父级路由是否在 tempList 中已经有了
                // 2. 第二次遍历用户列表的时候,发现 tempList 中已经有了父级路由
                if (tempList.includes(parent.label)) {
                    // 就直接找到父级路由,在子路由中再添加一个子路由
                    result.find(item => item.label == parent.label).children.push(item)
                } else {
                    // 将对应的内容放在 tempList 中
                    // console.log('11111111', parent.label);
                    tempList.push(parent.label)

                    // 1. 第一次执行的时候讲管理员列表添加进去
                    result.push({
                        label: parent.label,
                        path: parent.path,
                        children: [item]
                    })
                }


                console.log(result);
                // 将路由权限添加到 formData 中
                this.formData.checkedKeys = result
            })

        },
        add () {
            // 将管理员数据发送到服务器
            // 1. 收集数据
            // console.log(this.formData);

            // 获取所有的权限路由
            this.formatCheckedKeys()

            // 2. 将数据发给服务器 
            // console.log(this.formData);
            this.formData.role = this.formData.role == '管理员' ? '1' : '2'


            // 将数据添加服务器中
            addAdmin(this.formData).then(res => {
                // console.log(res);
                if (res.code == '200') {
                    // 成功
                    ElMessage.success(res.message)

                    // 关闭抽屉效果
                    this.drawer = false

                    // 重新获取最新的数据
                    // 获取管理员列表
                    adminList().then(res => {
                        // console.log(res.data);
                        this.tableData = res.data
                    })
                } else {
                    ElMessage.error(res.message)
                }

            })
        }
    },
    mounted () {
        // 获取管理员列表
        adminList().then(res => {
            // console.log(res.data);
            this.tableData = res.data
        })
    },
    computed: {
        computedTableData () {
            return this.tableData.slice((this.currentPage - 1) * 10, (this.currentPage - 1) * 10 + 10)
        }
    }
}
</script>
<template>
    <div>
        <div class="header">
            管理员列表
            <el-button type="success" @click="drawer = true">添加管理员</el-button>
        </div>
        <el-table :data="computedTableData" style="width: 100%">
            <!-- type="index" 标记当前为索引序号 -->
            <!-- label 列标题 -->
            <el-table-column type="index" :index="(currentPage - 1) * 10 + 1" label="序号" width="80" />
            <!-- prop 列的数据字段 -->
            <el-table-column prop="adminname" label="管理员名称" width="180" />

            <el-table-column prop="role" label="管理员权限">
                <template #default="scope">
                    <!-- <div> -->
                    <!-- {{ scope.row.role == 1 ? '管理员' : '超级管理员' }} -->
                    <el-tag :type="scope.row.role == 1 ? '' : 'success'">{{ scope.row.role == 1 ? '管理员' : '超级管理员'
                    }}</el-tag>
                    <!-- </div> -->
                </template>
            </el-table-column>


            <el-table-column prop="role" label="操作">
                <template #default>
                    <el-button size="small" type="primary">编辑</el-button>
                    <el-button size="small" type="success">删除</el-button>
                </template>
            </el-table-column>


        </el-table>
        <!-- 用做分页显示 -->
        <el-pagination background v-model:current-page="currentPage" layout="prev, pager, next"
            :total="tableData.length" />



        <!-- 抽屉效果  -->
        <el-drawer v-model="drawer" title="添加管理员">

            <el-form label-width="120px">
                <el-form-item label="管理员账号">
                    <el-input placeholder="请输入管理员账号" v-model="formData.adminname" />
                </el-form-item>

                <el-form-item label="管理员密码">
                    <el-input placeholder="请输入管理员密码" v-model="formData.password" />
                </el-form-item>

                <el-form-item label="管理员角色">
                    <el-select placeholder="请选择角色" v-model="formData.role">
                        <el-option label="管理员" value="管理员" />
                        <el-option label="超级管理员" value="超级管理员" />
                    </el-select>
                </el-form-item>

                <el-form-item>
                    <el-tree show-checkbox ref="treeRef" :data="menus" />
                </el-form-item>

            </el-form>

            <el-button @click="add" type="primary">添加</el-button>
        </el-drawer>
    </div>
</template>
<style lang="scss" scoped>
.header {
    margin-bottom: 16px;
}
</style>

修改管理员信息

添加一个属性 interfaceType 用来判断我们是添加还是编辑管理员

打开抽屉,将当前用户数据渲染在抽屉中

请求前获取所有的用户数据

将用户数据发送给后端

重新加载最新的数据

<!-- src/views/manager/ManagerListView.vue -->
<script>
import { adminList, addAdmin, updateAdmin, deleteAdmin } from '@/api/user'

import { routes } from '@/router/index.js'
import { ElMessage } from 'element-plus'

export default {
    data () {
        return {
            tableData: [],
            // 当前页
            currentPage: 1,
            // 是否打开抽屉效果
            drawer: false,
            // 路由信息
            menus: routes[0].children,
            // 管理员数据
            formData: {
                adminname: '',
                password: '',
                role: '',
                checkedKeys: ''
            },
            // 默认选中项
            defaultCheckedKeys: [],
            interfaceType: '10086', // 10086  添加管理员   10010 编辑管理员
        }
    },
    methods: {
       
        // 获取树形结构中选中的值
        formatCheckedKeys () {
            // console.log(this.$refs.treeRef.getCheckedNodes(true));

            // 根据选中的值得到父级路由
            const list = this.$refs.treeRef.getCheckedNodes(true)

            // 用来放父级路由的 label 用的
            const tempList = []

            // 最终结果用的
            const result = []

            // list: 是我们选中的子路由
            list.forEach(item => {
                // parent:选中后子路的父级路由
                const parent = this.menus.find(mitem => {
                    return mitem.children.some(child => child.label == item.label)
                })

                // console.log('111111', parent);

                // 判断当前父级路由是否在 tempList 中已经有了
                // 2. 第二次遍历用户列表的时候,发现 tempList 中已经有了父级路由
                if (tempList.includes(parent.label)) {
                    // 就直接找到父级路由,在子路由中再添加一个子路由
                    result.find(item => item.label == parent.label).children.push(item)
                } else {
                    // 将对应的内容放在 tempList 中
                    // console.log('11111111', parent.label);
                    tempList.push(parent.label)

                    // 1. 第一次执行的时候讲管理员列表添加进去
                    result.push({
                        label: parent.label,
                        path: parent.path,
                        children: [item]
                    })
                }


                console.log(result);
                // 将路由权限添加到 formData 中
                this.formData.checkedKeys = result
            })

        },
   
        editClick (row) {

            this.interfaceType = '10010'
            // 打开抽屉
            this.drawer = true
            // 获取当前的一行数据
            // console.log(row);


            this.formData.adminname = row.adminname
            this.formData.password = ''
            this.formData.role = row.role == '1' ? '管理员' : '超级管理员'


            const checkedKeys = []
            row.checkedKeys.forEach(parent => {
                parent.children.forEach(item => {
                    checkedKeys.push(item.path)
                })
            })

            // console.log(checkedKeys);
            this.defaultCheckedKeys = checkedKeys
        },
        close () {
            this.formData = {}
            this.defaultCheckedKeys = []

            if (this.$refs.treeRef) {
                this.$refs.treeRef.setCheckedKeys([])
            }
        },
        update () {
            // 获取表单数据
            console.log(this.formData);

            if (this.formData.adminname == '' || this.formData.password == '') {
                ElMessage.error('管理员名称和密码不能为空')
                return
            }

            this.formData.role = this.formData.role == '管理员' ? '1' : '2'

            // 获取当前用户选中的权限
            this.formatCheckedKeys()


            // 提交修改信息了
            updateAdmin(this.formData).then(res => {
                // console.log(res);
                // 关闭抽屉效果
                this.drawer = false

                // 重新获取最新的数据
                // 获取管理员列表
                adminList().then(res => {
                    // console.log(res.data);
                    this.tableData = res.data
                })
            })

        },
        
    },
    mounted () {
        // 获取管理员列表
        adminList().then(res => {
            // console.log(res.data);
            this.tableData = res.data
        })
    },
    computed: {
        computedTableData () {
            return this.tableData.slice((this.currentPage - 1) * 10, (this.currentPage - 1) * 10 + 10)
        }
    }
}
</script>
<template>
    <div>
        ...
        <!-- 用做分页显示 -->
        <el-pagination background v-model:current-page="currentPage" layout="prev, pager, next"
            :total="tableData.length" />



        <!-- 抽屉效果  -->
        <el-drawer @close="close" v-model="drawer">

            <template #header>
                <h4> {{ interfaceType == '10086' ? '添加管理员' : '编辑管理员' }}</h4>
            </template>


            <el-form label-width="120px">
                <el-form-item label="管理员账号">
                    <el-input placeholder="请输入管理员账号" v-model="formData.adminname" />
                </el-form-item>

                <el-form-item label="管理员密码">
                    <el-input placeholder="请输入管理员密码" v-model="formData.password" />
                </el-form-item>

                <el-form-item label="管理员角色">
                    <el-select placeholder="请选择角色" v-model="formData.role">
                        <el-option label="管理员" value="管理员" />
                        <el-option label="超级管理员" value="超级管理员" />
                    </el-select>
                </el-form-item>

                <el-form-item>
                    <!-- :default-checked-keys="" -->
                    <el-tree :default-checked-keys="defaultCheckedKeys" :default-expand-all="true" show-checkbox
                        ref="treeRef" :data="menus" node-key="path" />
                </el-form-item>

            </el-form>

            <el-button v-if="interfaceType == '10086'" @click="add" type="primary">添加</el-button>
<!-- +++++++++++++++++++++++++ -->
            <el-button v-else type="primary" @click="update">修改</el-button>

        </el-drawer>
    </div>
</template>
<style lang="scss" scoped>
.header {
    margin-bottom: 16px;
}
</style>

删除管理员

第一步先给按钮添加一个点击事件,同时将当前数据传递过去

第二步发送数据请求将 adminid 传递服务器中然后进行删除

deleteClick (row) {
            // console.log(row);
            deleteAdmin({ adminid: row.adminid }).then(res => {
                if (res.code == 200) {
                    // 重新获取最新的数据
                    // 获取管理员列表
                    adminList().then(res => {
                        // console.log(res.data);
                        this.tableData = res.data
                    })
                } else {
                    ElMessage.error('删除失败...')
                }
            })
        }

9.面包屑

创建一个面包屑组件

将路由导入然后格式化为 map 对象 key - value

将当前路由的 key 和 value 获取到然后存入 list 数组中

遍历 list 数组,渲染内容

<script>

import { routes } from '@/router/index'

export default {
    data () {

        // 用来记录所有路由 name 对应的 label
        this.routesMap = new Map()
        // 用来处理将路由处理成 map
        this.initRoutesMap(routes)

        console.log(this.routesMap);

        return {
            list: []
        }
    },
    methods: {
		/*
			0: {"home" => "首页"}
            1: {"manager" => "账号管理"}
            2: {"managerlist" => "管理员列表"}
            3: {"userlist" => "用户列表"}
            4: {"banner" => "轮播图管理"}
            5: {"bannerlist" => "轮播图列表"}
            6: {"addbanner" => "添加轮播图"}
            7: {"login" => undefined}
		*/
        initRoutesMap (routes) {
            routes.forEach(route => {
                this.routesMap.set(route.name, route.label)
                // 如果有子路由
                if (route.children) {
                    this.initRoutesMap(route.children)
                }
            })
        }
    },
    mounted () {
        // 带 r 就是用来做页面路由跳转用的
        // this.$router.push()

        // 不带 r 的是用来做路由信息查询用的
        // this.$route
        // console.log(this.$route);
    },
    watch: {
        $route: {
            immediate: true,
            handler (route) {
                // 处理路径

                this.list = route.matched.map(item => {
                    /*
                        [
                            {
                                name: 'home',
                                label: '首页'
                            },
                            {
                                name: 'manager',
                                label: '账号管理'
                            }
                        ]
                    
                    */
                    return {
                        name: item.name,
                        label: this.routesMap.get(item.name)
                    }
                })

            }
        }
    }
}

</script>
<template>
    <el-breadcrumb separator=">">

        <el-breadcrumb-item v-for="item in list" :key="item.name" :to="{ name: item.name }">{{ item.label
        }}</el-breadcrumb-item>


        <!-- <el-breadcrumb-item :to="{ path: '/' }">homepage</el-breadcrumb-item>
        <el-breadcrumb-item><a href="/">promotion management</a></el-breadcrumb-item>
        <el-breadcrumb-item>promotion list</el-breadcrumb-item>
        <el-breadcrumb-item>promotion detail</el-breadcrumb-item> -->
    </el-breadcrumb>
</template>

<style lang="scss" scoped>
.el-breadcrumb {
    margin-bottom: 20px;
}
</style>

10 用户列表

先在 api/user.js 文件中先写一个获取数据的方法

在 UserListView.vue 文件中调用该方法

拿到数据并渲染数据

<!-- src/views/manager/UserListView.vue -->

<script>
import { getUserList } from '@/api/user'
export default {
    data () {
        return {
            userlist: [],
            currentPage: 1
        }
    },
    mounted () {
        getUserList().then(res => {
            console.log(res.data);
            this.userlist = res.data
        })
    },
    computed: {
        computedTableData () {
            return this.userlist.slice((this.currentPage - 1) * 10, (this.currentPage - 1) * 10 + 10)
        }
    }
}
</script>
<template>
    <div>
        <div>
            用户列表
        </div>

        <el-table :data="computedTableData" style="width: 100%">
            <el-table-column prop="telcode" label="用户名" />
            <el-table-column prop="tel" label="手机号" />
            <el-table-column prop="email" label="邮箱" />
            <el-table-column prop="userid" label="用户 id" />

        </el-table>

        <!-- 用做分页显示 -->
        <el-pagination background v-model:current-page="currentPage" layout="prev, pager, next"
            :total="userlist.length" />
    </div>
</template>

# 11轮播图管理

添加轮播图

先获取到用户输入和选择的图片

将用户的内容上传到服务器

上传成功后跳转到轮播图列表页面

<script>
import { Plus } from '@element-plus/icons-vue'
import { addBanner } from '@/api/banner'
export default {
    data () {
        return {
            imageUrl: '',
            formData: {
                alt: '',
                link: '',
                img: ''
            }
        }
    },
    components: {
        Plus
    },
    methods: {
        httpRequest (data) {
            console.log(data.file);

            // 根据文件生成一个图片的 url 地址,该地址是用于展示的临时的
            // this.imageUrl = URL.createObjectURL(data.file)

            /*
                1. 先获取用户选择的图片
                2. 将用户选择的图片上传到存放图片的服务器,该服务器会给你返回一个图片地址
                3. 将图片地址发送给自己的后端做存储
            */

            // 创建一个文件加载器
            let reader = new FileReader()
            // 指定加载其开始加载文件
            reader.readAsDataURL(data.file)
            // 加载完成后的回调函数
            reader.onload = () => {
                // 将文件加载为 base64 的格式
                // console.log(reader.result);
                this.imageUrl = reader.result
                this.formData.img = reader.result
            }

        },
        addBanner () {
            // console.log(this.formData);
            addBanner(this.formData).then(res => {
                // console.log(res);
                if (res.code == 200) {
                    // 提交成功后进入轮播图列表查看
                    this.$router.push('/banner/bannerlist')
                }
            })
        }
    }

}
</script>
<template>
    <div>
        添加轮播图

        <br>

        <el-form>
            <el-form-item label="请输入 alt">
                <el-input v-model="formData.alt" placeholder="请输入 alt" />
            </el-form-item>

            <el-form-item label="请输入 link">
                <el-input v-model="formData.link" placeholder="请输入 link" />
            </el-form-item>

            <el-form-item>
                <el-upload :http-request="httpRequest" class="avatar-uploader" :show-file-list="false">
                    <img v-if="imageUrl" :src="imageUrl" class="avatar" />
                    <el-icon v-else class="avatar-uploader-icon">
                        <Plus />
                    </el-icon>
                </el-upload>
            </el-form-item>

            <el-button @click="addBanner"> 添加 </el-button>
        </el-form>
    </div>
</template>
<style lang="scss" scoped>
.avatar-uploader .avatar {
    width: 178px;
    height: 178px;
    display: block;
}
</style>
<style>
.avatar-uploader .el-upload {
    border: 1px dashed var(--el-border-color);
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition: var(--el-transition-duration-fast);
}

.avatar-uploader .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;
}
</style>

轮播图列表

请求获取到轮播图列表数据

将该数据展示出来

<script>
import { getBannerList } from '@/api/banner'
export default {
    data () {
        return {
            bannerList: []
        }
    },
    mounted () {
        getBannerList().then(res => {
            // console.log(res);
            this.bannerList = res.data
        })
    }
}

</script>
<template>
    <el-table :data="bannerList" style="width: 100%">
        <el-table-column type="index" label="序号" />
        <el-table-column prop="img" label="图片">
            <template #default="scope">
                <div style="display: flex; align-items: center">
                    <el-image :src="scope.row.img" />
                </div>
            </template>
        </el-table-column>
        <el-table-column prop="alt" label="提示信息" />
        <el-table-column prop="link" label="链接" />
    </el-table>
</template>
<style lang="scss" scoped>
.el-image {
    width: 80px;
}
</style>

12. echarts

https://echarts.apache.org/zh/index.html

  1. 安装 npm install echarts --save
  2. 导入图表
  3. 将图表绑定在指定元素上
  4. 设置一个 option
<script>
import * as echarts from 'echarts';

export default {
    data () {
        return {
            myChart: null
        }
    },
    methods: {
        changeType (type, x, y) {
            this.myChart.setOption({
                title: {
                    text: 'ECharts 入门示例'
                },
                tooltip: {},
                toolbox: {
                    feature: {
                        saveAsImage: {}
                    }
                },
                xAxis: {
                    data: x || ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
                },
                yAxis: {},
                series: [
                    {
                        name: '销量',
                        type: type || 'pie',
                        data: y || [5, 20, 36, 10, 10, 20]
                    }
                ]
            });
        }
    },
    mounted () {

        // 基于准备好的dom,初始化echarts实例
        this.myChart = echarts.init(document.getElementById('main'));
        // 绘制图表
        this.changeType('pie')
    }
}

</script>
<template>
    <div>
        <el-button @click="changeType('line')">折线图</el-button>
        <el-button type="primary" @click="changeType('bar')">柱状图</el-button>
        <el-button type="success" @click="changeType('pie')">饼图</el-button>
        <el-button type="info" @click="changeType('scatter')">散点图</el-button>

        <el-button type="warning"
            @click="changeType('line', ['冰箱', '彩电', '洗衣机', '空调', 'VCD', 'DVD'], ['1000', '150', '5888', '3500', '50', '550'])">电器销量</el-button>
        <el-button type="danger"
            @click="changeType('', ['草莓', '榴莲', '柠檬', '橙子', '苹果', '圣女果'], ['100', '80', '200', '500', '99', '666'])">水果销量</el-button>
    </div>
    <div id="main"></div>
</template>

<style lang="scss" scoped>
#main {
    height: 600px;
    background-color: white;
}
</style>

13 文件导入导出

文件导入

安装插件 : npm install xlsx

选择文件

将文件读成数据流

将数据流读为对象

将对象中工作表1 中的内容读取出来

然后将读取的内容转换成 json

将数据渲染出来

<script>
import * as XLSX from 'xlsx'
export default {
    data () {
        return {
            tableData: []
        }
    },
    methods: {
        importClick () {
            // 选择文件按钮的点击事件
            this.$refs.inp.click()
        },
        importChange (event) {
            // 选中文件后的回调函数
            console.log(event.target.files[0]);
            // 1. 获取到我们选中的文件
            const file = event.target.files[0]
            // 创建文件的加载器
            const reader = new FileReader()
            // 将文件读取成数据流
            reader.readAsBinaryString(file)
            reader.onload = () => {
                // console.log(reader.result);

                // 将我们的数据流转换成 js 对象
                const boxx = XLSX.read(reader.result, { type: 'binary' })

                // 将 工作表1 中的数据提出来
                let res = boxx.Sheets['工作表1']

                // 将 工作表1 中的内容转换成 json  数据
                res = XLSX.utils.sheet_to_json(res)

                console.log(res);
                // 
                this.tableData = res
            }
        }
    }
}

</script>
<template>
    <div>
        <el-button type="success" @click="importClick"> 选择文件 </el-button>

        <input ref="inp" hidden type="file" @change="importChange">


        <el-table :data="tableData" style="width: 100%">
            <el-table-column type="index" label="序号" />
            <el-table-column label="图片">
                <template #default="scope">
                    <div style="display: flex; align-items: center">
                        <el-image :src="scope.row.img1" />
                    </div>
                </template>

            </el-table-column>
            <el-table-column prop="category" label="分类" />
            <el-table-column prop="proname" label="名称" />



        </el-table>
    </div>
</template>

文件导出

  1. 直接通过 a 标签进行文件的下载
  2. 通过 js 进行导出文件

先安装: cnpm install js-export-excel

创建一个文件导出的配置对象

该对象中需要配置文件的名称

配置需要导出的数据

将对象生成为表格然后进行保存

<script>
import { proList } from '@/api/pro'
// 1. 导入
import ExportJsonExcel from 'js-export-excel'
export default {
    data () {
        return {
            tableData: []
        }
    },
    mounted () {
        proList().then(res => {
            // console.log(res);
            this.tableData = res.data
        })
    },
    methods: {
        exportClick () {
            // 2. 创建导出对象

            // 文件配置
            let option = {}
            // 导出文件的名字
            option.fileName = 'proList'

            // 需要导出的内容
            option.datas = [
                {
                    // 需要导出的数据
                    sheetData: this.tableData,
                    // 工作表名称
                    sheetName: "工作表1",
                    sheetFilter: ["brand", "category", "desc", 'proname', 'img1', 'originprice'],
                    sheetHeader: ["品牌", "分类", '描述', '商品名称', '图片地址', '产品价格'],
                    columnWidths: [5, 5, 10, 20, 30, 40], // 导出后的宽度
                }
            ];

            // 3. 实现具体导出
            let toExcel = new ExportJsonExcel(option)
            toExcel.saveExcel()

        }
    }
}
</script>
<template>
    <div>
        <!-- 
            文件导出分两种方法
            第一种:直接一个 a 标签即可
            <a href="https://code.jquery.com/jquery-3.6.2.min.js" download="">Download the compressed, production jQuery 3.6.2</a>
            第二种:自己将数据处理后然后本地保存
        -->
        <el-button @click="exportClick">文件导出</el-button>
        <el-table :data="tableData" style="width: 100%">
            <el-table-column type="index" label="序号" />
            <el-table-column label="图片">
                <template #default="scope">
                    <div style="display: flex; align-items: center">
                        <el-image :src="scope.row.img1" />
                    </div>
                </template>

            </el-table-column>
            <el-table-column prop="category" label="分类" />
            <el-table-column prop="proname" label="名称" />
        </el-table>
    </div>
</template>

14富文本编辑器

将富文本编辑器的文件夹复制到 public 中

在 index.html 中添加对应导入

在组件中使用

<script>
export default {
    data () {
        return {

        }
    },
    methods: {
        submit () {
            // 获取内容
            console.log(tinymce.activeEditor.getContent());
        }
    },
    mounted () {
        // console.log(tinymce);
        tinymce.init({
            selector: 'textarea#default-editor',
            height: 500,
            plugins: 'advlist link image lists paste', // 使用插件
            paste_data_images: true, // 支持图片粘贴
            branding: false, // 取消右下角默认提示
            auto_focus: true, // 自动获取焦点
        })
    }
}
</script>
<template>
    <div>
        <textarea id="default-editor"></textarea>
        <el-button @click="submit"> 点击获取输入内容 </el-button>
    </div>
</template>

15 百度地图

先注册为百度的开发者

申请秘钥

创建一个应用,获取到秘钥

在项目的入口 html 中导入百度地图的 api

在组件中直接使用即可

<script>
export default {
    data () {
        return {
            map: null
        }
    },
    methods: {
        search () {
            // console.log(event.target.value);

            var options = {
                onSearchComplete: function (results) {
                    // 判断状态是否正确
                    if (local.getStatus() == BMAP_STATUS_SUCCESS) {
                        var s = [];
                        for (var i = 0; i < results.getCurrentNumPois(); i++) {
                            s.push(results.getPoi(i).title + ", " + results.getPoi(i).address);
                        }
                        document.getElementById("r-result").innerHTML = s.join("<br/>");
                    }
                }
            };
            var local = new BMapGL.LocalSearch(this.map, options);
            local.search(event.target.value);


        }
    },
    mounted () {
        this.map = new BMapGL.Map("container");
        var point = new BMapGL.Point(120.270922, 30.311587);
        // 设置地图显示的中心位置和地图缩放比例
        this.map.centerAndZoom(point, 15);

        this.map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放

        // map.setHeading(64.5);   //设置地图旋转角度
        // map.setTilt(73);       //设置地图的倾斜角度

        // map.setMapType(BMAP_EARTH_MAP);      // 设置地图类型为地球模式
        // console.log(window);


        var scaleCtrl = new BMapGL.ScaleControl({
            anchor: BMAP_ANCHOR_TOP_RIGHT
        });  // 添加比例尺控件
        this.map.addControl(scaleCtrl);
        var zoomCtrl = new BMapGL.ZoomControl();  // 添加缩放控件
        this.map.addControl(zoomCtrl);
        var cityCtrl = new BMapGL.CityListControl();  // 添加城市列表控件
        this.map.addControl(cityCtrl);

        // 更改为自定义的地图模式
        // map.setMapStyleV2({
        //     styleId: '3786c052e48e145feb2d93e2f8f9f14f'
        // });


        var marker = new BMapGL.Marker(point);        // 创建标注   
        this.map.addOverlay(marker);                     // 将标注添加到地图中


        var polyline = new BMapGL.Polyline([
            new BMapGL.Point(116.399, 39.910),
            new BMapGL.Point(116.405, 39.920),
            new BMapGL.Point(116.425, 39.900),
            new BMapGL.Point(116.525, 40.000)
        ], { strokeColor: "blue", strokeWeight: 2, strokeOpacity: 0.5 });
        this.map.addOverlay(polyline);



        var geolocation = new BMapGL.Geolocation();

        const _this = this

        // geolocation.getCurrentPosition(function (r) {
        //     console.log(r);
        //     if (this.getStatus() == BMAP_STATUS_SUCCESS) {
        //         var mk = new BMapGL.Marker(r.point);
        //         _this.map.addOverlay(mk);
        //         _this.map.panTo(r.point);




        //         alert('您的位置:' + r.point.lng + ',' + r.point.lat);
        //     }
        //     else {
        //         alert('failed' + this.getStatus());
        //     }
        // });



    }
}
</script>
<template>
    <h3>
        地图搜索:<input type="text" @keyup.enter="search">
    </h3>
    <div id="container"></div>
    <section id="r-result"></section>
</template>
<style lang="scss" scoped>
#container {
    height: 450px;
}
</style>
posted @ 2023-11-08 22:09  yifanSJ  阅读(20)  评论(0编辑  收藏  举报