【Vue】后台管理系统

news2024/11/15 3:23:55

O 项目说明

1.脚手架

  • vite
  • vue-cli ==》 webpack

2.vite脚手架使用

官网:https://vitejs.cn/

Vue3 vite官网:https://cn.vitejs.dev/

Vite下一代的前端工具链,为开发者提供急速响应

# 安装
$ cnpm i vite -g
$ vite -v
vite/4.0.3 darwin-x64 node-v16.13.1

windows注意处理 ****/vite.psl文件

Vite(法语意为 “快速的”,发音 /vit/,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:

  • 一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR)。
  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。

2.1 如何搭建vue3项目

  • 使用npm
# 如果选择npm创建项目再执行
$ npm create vite@latest
  • 使用yarn,如果电脑没有安装yarn cnpm i yarn -g
$ yarn create vite
  • 使用pnpm:如果电脑尚未安装 pnpm cnpm i pnpm -g
$ pnpm create vite
对比yarnnpmpnpm
初始化yarn initnpm init利用硬链接和符号连接来避免复制所有本地缓存资源文件
安装依赖yarn install 或者yarnnpm install 或 npm ipnpm install
新增依赖yarn add vantnpm i vant -Spnpm i vant
删除依赖yarn remove vantnpm uninstall vant -S
删除devDependencies 依赖npm uninstall vant -D
更新依赖yarn upgradenpm updatepnpm update
全局安装或者删除yarn global remove vue-clinpm uninstall vue-cli -g
同时下载多个yarn add axios vue-axiosnpm i axis vue-axios -S

1.创建项目

$ npm init vue@latest

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cSvR3gWm-1672888918411)(assets/image-20221226093812541.png)]

|- vue3-admin-app  # 项目名称
	|- node_modules  # 项目依赖包
  |- public
  	favicon.ico 	 # 网页图标
  |- src					 # 写代码的主场
  	|- assets			 # 资源文件
  		base.css     # 基础样式
  		logo.svg     # logo
  		main.css     # 项目样式
		|- components  # 自定义组件
    	|- icons     # 图标组件
  		HelloWorld.vue # 自定义组件
  		TheWelcome.vue # 自定义组件
  		welcomeItem.vue # 自定义组件
  	|-router       # 路由文件夹
  		index.ts     # 路由配置
  	|- stores      # 状态管理器文件夹
  		counter.ts   # 状态管理器模块
  	|- views       # 项目页面组件
  		AboutView.vue # 页面
  		HomeView.vue  # 页面
  	App.vue         # 项目跟组件
  	main.ts         # 项目入口文件
  .eslintrc.cjs     # 代码格式化说明
  .gitignore        # git上传忽略文件
  .prettierrc.json  # 格式化配置
  env.d.ts 					# 环境配置声明文件 - ts 中
  index.html        # 页面模版
  package.json      # 项目依赖说明以及运行命令
  README.md 				# 说明文档
  tsconfig.config.json # ts的配置文件说明 - 本项目部分
  tsconfig.json				 # ts的配置文件说明 - 公共部分
  vite.config.ts			 # vite的配置文件

1.1 Vue3 单文件组件 SFC

一个 Vue 单文件组件 (SFC),通常使用 *.vue 作为文件扩展名,它是一种使用了类似 HTML 语法的自定义文件格式,用于定义 Vue 组件。一个 Vue 单文件组件在语法上是兼容 HTML 的。

每一个 *.vue 文件都由三种顶层语言块构成:<template><script><style>

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

打开src/mian.ts发现在引入 App.vue中画红线,说明项目中没有.vue的声明文件,需要自行补充一下

// env.d.ts
/// <reference types="vite/client" />

// 简单版本
// declare module '*.vue'

// 推荐
declare module '*.vue' {
  // 引入vue模块中ts的方法
  import type { DefineComponent } from 'vue'
  // 定义vue组件以及类型注解
  const component: DefineComponent<{}, {}, any>
  export default component
}

再次打开 src/main.ts发现红线丢失

复制src文件夹,保留App.vue以及main.ts基本内容

// src/main.ts
import { createApp } from 'vue'

import App from './App.vue'

const app = createApp(App)

app.mount('#app')

如何使用选项式API

<!-- src/App.vue -->
<template>
  <div class="example">
    {{  msg  }} - {{ count }}
    <button @click="count++">加1</button>
  </div>
</template>

<script lang="ts">
  import { defineComponent} from 'vue'
  export default defineComponent({
    data () {
      return {
        msg: 'hello sfc',
        count: 10
      }
    }
  })
</script>
<!-- 如果样式只针对当前组件是有效的,只要给style 添加 scoped 属性即可 -->
<style>
  .example {
    color: #f66
  }
</style>

如果使用组合式API

<!-- src/App.vue -->
<template>
  <div class="example">
    {{  msg  }} - {{ count }}
    <button @click="count++">1</button>
  </div>
</template>

<script lang="ts">
  import { defineComponent, ref } from 'vue'
  export default defineComponent({
    setup () {
      const msg = ref('hello sfc!!!!')
      const count = ref(10)

      return {
        msg, count
      }
    }
  })
</script>
<!-- 如果样式只针对当前组件是有效的,只要给style 添加 scoped 属性即可 -->
<style>
  .example {
    color: #f66
  }
</style>
  • 组合式API简写形式
<!-- src/App.vue -->
<template>
  <div class="example">
    {{  msg  }} - {{ count }}
    <button @click="count++">加1</button>
  </div>
</template>

<!-- 采用简写形式 -->
<script lang="ts" setup>

  import { ref } from 'vue'

  const msg = ref('hello sfc!!!!')
  const count = ref(10)

</script>
<!-- 如果样式只针对当前组件是有效的,只要给style 添加 scoped 属性即可 -->
<style>
  .example {
    color: #f66
  }
</style>

预习:https://cn.vuejs.org/guide/typescript/composition-api.html

2.准备工作

?样式处理 css、sass、less 、stylus

sass、less、stylus 为 css 预处理器

.contianer {}
.container .box {}
.container .box .header {}
.container .box .content {}
.container .box .footer {}
// scss
.contianer {
  width: 100%
	.box {
    width: 100%
		.header {
      width: 100%
    }
		.content {
    	width: 100%  
    }
		.footer {
      width: 100%
    }
	}
}
// .less
.contianer {
  width: 100%
	.box {
    width: 100%
		.header {
      width: 100%
    }
		.content {
    	width: 100%  
    }
		.footer {
      width: 100%
    }
	}
}
// stylus
.container
  width 100%
  .box
    width 100%
    .header
      width 100%
    .content
      width 100%
    .footer
      width 100%

?? 本项目用什么,建议大家先选择UI组件库(element-plus / ant-design-vue)

  • 样式 css - sass - 可以重新创建项目

    # 需要安装
    $ cnpm i sass -D
    

    使用重置样式表

    # 需要安装
    $ cnpm i normalize.css -D
    
    // src/main.ts
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    
    import App from './App.vue'
    import router from './router'
    
    // 重置样式表
    import 'normalize.css/normalize.css'
    
    // import './assets/main.css' // 不使用默认样式
    
    const app = createApp(App)
    
    app.use(createPinia())
    app.use(router)
    
    app.mount('#app')
    
    
  • 路由 vue-router

    # 创建项目时已经选择了,此处不需要安装
    $ cnpm i vue-router -S
    
  • 状态管理 pinia / vuex

    # 创建项目时已经选择了,此处不需要安装
    $ cnpm i pinia -S
    
  • 数据请求方案 axios / fetch

    # 需要安装
    $ cnpm i axios -S
    
  • 组件库 element plus

    # 需要安装(如果安装失败, 更新cnpm)  
    # npm install -g cnpm --registry=https://registry.npmmirror.com
    $ cnpm i element-plus -S
    

    Element-plus 默认使用英文,如果需要使用中文包,如下操作

    // src/main.ts
    import { createApp } from 'vue'
    import { createPinia } from 'pinia'
    
    // 引入UI库 ++++++
    import ElementPlus from 'element-plus'
    import 'element-plus/dist/index.css'
    import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
    
    import App from './App.vue'
    import router from './router'
    
    // 重置样式表
    import 'normalize.css/normalize.css'
    
    // import './assets/main.css' // 不使用默认样式
    
    const app = createApp(App)
    
    // 配置UI库  ++++++
    app.use(ElementPlus, { locale: zhCn })
    app.use(createPinia())
    app.use(router)
    
    app.mount('#app')
    
    
    // env.d.ts
    /// <reference types="vite/client" />
    
    // declare module '*.vue'
    declare module '*.vue' {
    import type { DefineComponent, defineComponent } from 'vue'
    const component: DefineComponent<{}, {}, any>
    export default component
    }
    
    declare module 'element-plus/dist/locale/zh-cn.mjs'
    
  • 本地存储 localStorage / store2 /

    # 安装
    $ cnpm i store2 -S
    
  • 数据可视化 ECharts

    # 安装
    $ cnpm i echarts -S
    

3.创建主布局文件

https://element-plus.gitee.io/zh-CN/component/container.html#%E5%B8%B8%E8%A7%81%E9%A1%B5%E9%9D%A2%E5%B8%83%E5%B1%80

<!-- src/App.vue -->
<script lang="ts" setup>
</script>

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>Main</el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

<style lang="scss">
/* 审查元素从而设定样式 */
html, body, #app, .common-layout, .el-container {
  height: 100%;
}
.common-layout {
  .el-container {
    background-color: #efefef;
    .el-aside {
      background-color: #001529;
    }
    .el-container {
      .el-header {
        background-color: #fff;
      }
      .el-main {
        background-color: #fff;
        margin: 16px;
      }
      .el-footer {
        background-color: #fff;
      }
    }
  }
}
</style>

4.对于整个系统的设计思路

4.1 系统的默认设置

https://panjiachen.gitee.io/vue-element-admin/#/login?redirect=%2Fdashboard

// src/settings.ts
// 参考项目链接
// https://panjiachen.gitee.io/vue-element-admin/#/login?redirect=%2Fdashboard
export default {

  title: '嗨购管理系统',

  // 主题等的设置
  showSetting: true,

  // 快捷导航的提示
  tagsView: true,

  // 头部是否固定
  fixedHeader: true,

  // 左侧菜单logo显示
  sideBarLogo: true
}

4.2 设置页面的标题

// src/utils/get-page-title.ts
import defaultSettings from '../settings'

const title = defaultSettings.title || '嗨购后台管理系统'

// 系统首页 - 嗨购后台管理系统
// 添加轮播图 - 嗨购后台管理系统
export default function getPageTitle (pageTitle: string) {
  if (pageTitle) {
    // 当前页面标识 + 应用的标题
    return `${ pageTitle } - ${ title }`
  } else {
    return `${ title }`
  }
}


页面标题的修改需要配合路由使用

5.设置路由

5.1 设置基本布局组件

<!-- src/layout/index.vue -->
<!-- 后台管理系统两种布局:登录布局 + 其他布局 -->
<script lang="ts" setup>
</script>

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>Main</el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>
<!-- src/App.vue -->
<!-- 组合式API中如何使用 组件 -->
<script lang="ts" setup>
  import Layout from './layout/index.vue'
</script>

<template>
  <Layout />
</template>

<style lang="scss">
/* 审查元素从而设定样式 */
html, body, #app, .common-layout, .el-container {
  height: 100%;
}
.common-layout {
  .el-container {
    background-color: #efefef;
    .el-aside {
      background-color: #001529;
    }
    .el-container {
      .el-header {
        background-color: #fff;
      }
      .el-main {
        background-color: #fff;
        margin: 16px;
      }
      .el-footer {
        background-color: #fff;
      }
    }
  }
}
</style>

5.2 设置基本页面

5.2.1 系统首页

<!-- src/views/home/index.vue -->
<script lang="ts" setup></script>

<template>
  <div>系统首页</div>
</template>

5.2.2 轮播图管理

  • 轮播图列表

    • <!-- src/views/banner/list.vue -->
      <script lang="ts" setup>
      </script>
      
      <template>
        <div>轮播图列表</div>
      </template>
      
      
    • <!-- src/views/banner/components/home.vue -->
      <script lang="ts" setup>
      </script>
      
      <template>
        <div>首页轮播图列表</div>
      </template>
      
      
    • <!-- src/views/banner/components/kind.vue -->
      <script lang="ts" setup>
      </script>
      
      <template>
        <div>分类轮播图列表</div>
      </template>
      
      
  • 添加轮播图

    <!-- src/views/banner/add.vue -->
    <script lang="ts" setup>
    </script>
    
    <template>
      <div>添加轮播图</div>
    </template>
    
    

5.2.3 产品管理

<!-- src/views/pro/list.vue -->
<script lang="ts" setup>
</script>

<template>
  <div>产品列表</div>
</template>

<!-- src/views/pro/search.vue -->
<script lang="ts" setup>
</script>

<template>
  <div>筛选列表</div>
</template>


5.2.4 账户管理

<!-- src/views/account/admin.vue -->
<script lang="ts" setup>
</script>

<template>
  <div>管理员列表</div>
</template>

<!-- src/views/account/user.vue -->
<script lang="ts" setup>
</script>

<template>
  <div>用户列表</div>
</template>

5.2.5 登录页面

<!-- src/views/login/index.vue -->
<script lang="ts" setup>
</script>

<template>
  <div>登录页面</div>
</template>

5.3 设置路由模块

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

左侧菜单渲染结构(这些页面应该都是主界面结构)

|- 系统首页            # 只有一级路由
|- 轮播图管理    			# 三级路由
	|- 轮播图列表
		|- 首页轮播图
		|- 分类页轮播图
	|- 添加轮播图
|- 产品管理						# 二级路由
	|- 产品列表
	|- 筛选列表
|- 账户管理						# 二级路由
	|- 管理员列表
	|- 用户列表

真实项目开发时,可能 轮播图 产品 账户是不同的人开发以及维护的,所以建议将这几个路由的配置分离开来

然后再统一整合管理即可

5.3.1 账户管理路由

// src/router/modules/account.ts
// @符号代表 src 目录结构
import Layout from '@/layout/index.vue'
import User from '@/views/account/user.vue'
import Admin from '@/views/account/Admin.vue'
export default {
  path: '/account', // 地址栏显示的路径
  redirect: '/account/user', // 当用户输入 /account 路由时,自动跳转到 /account/user 路由
  component: Layout, // 账户管理使用主界面布局结构
  name: 'account', // 给路由起名字 - 命名路由 - 具有唯一性 - 后期会使用
  meta: {
    title: '账户管理', // 左侧菜单栏显示的名字
    icon: 'User' // 左侧菜单栏显示的图标
  },
  children: [ // 代表账户管理下有二级路由
    {
      path: '/account/user',
      component: User,
      meta: {
        title: '用户列表'
      }
    },
    {
      path: '/account/admin',
      component: Admin,
      meta: {
        title: '管理员列表'
      }
    }
  ]
}

5.3.2 轮播图路由

未命名文件(25)
// src/router/modules/banner.ts
import Layout from '@/layout/index.vue'
import BannerAdd from '@/views/banner/add.vue'
import BannerList from '@/views/banner/list.vue'
import BannerHomeList from '@/views/banner/components/home.vue'
import BannerKindList from '@/views/banner/components/kind.vue'
export default {
  path: '/banner',
  name: 'banner',
  redirect: '/banner/list',
  component: Layout,
  meta: {
    title: '轮播图管理',
    icon: 'PictureFilled'
  },
  children: [
    {
      path: '/banner/list',
      component: BannerList,
      redirect: '/banner/list/home',
      meta: {
        title: '轮播图列表'
      },
      children: [
        {
          path: '/banner/list/home',
          component: BannerHomeList,
          meta: {
            title: '首页轮播图'
          },
        },
        {
          path: '/banner/list/kind',
          component: BannerKindList,
          meta: {
            title: '分类页轮播图'
          },
        }
      ]
    },
    {
      path: '/banner/add',
      component: BannerAdd,
      meta: {
        title: '添加轮播图'
      }
    }
  ]
}

5.3.3 产品管理路由

// src/router/modules/pro.ts
import Layout from '@/layout/index.vue'
import ProList from '@/views/pro/list.vue'
import ProSearch from '@/views/pro/search.vue'
export default {
  path: '/pro',
  redirect: '/pro/list',
  component: Layout,
  meta: {
    title: '产品管理',
    icon: 'Fries'
  },
  children: [
    {
      path: '/pro/list',
      component: ProList,
      meta: {
        title: '产品列表'
      }
    },
    {
      path: '/pro/search',
      component: ProSearch,
      meta: {
        title: '筛选列表'
      }
    }
  ]
}

5.3.4 整合路由

image-20221227104118491 image-20221227104226230
// src/router/index.ts

import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
// 1.引入自定义分模块的路由
import bannerRoutes from './modules/banner'
import proRoutes from './modules/pro'
import accountRoutes from './modules/account'

// 2.引入主界面布局,生成 首页和登录的路由 -- 常规路由 -- 任何人都可以访问路由
import Layout from '@/layout/index.vue'
import Home from '@/views/home/index.vue'
import Login from '@/views/login/index.vue'

// 3.定义常规路由-任何人可以访问的路由
export const constantRoutes: RouteRecordRaw[] = [
  { // 地址栏输入 /login, 整个页面展示登录页面
    path: '/login',
    component: Login
  },
  {
    path: '/',
    redirect: '/home',
    component: Layout,
    children: [
      {
        path: '/home',
        component: Home,
        meta: {
          title: '系统首页',
          icon: 'HomeFilled'
        }
      }
    ]
  }
]

// 4.整合模块路由
export const asyncRoutes = [
  bannerRoutes,
  proRoutes,
  accountRoutes
]

// 5.合并路由
const router = createRouter({
  // createWebHistory http://localhost:5173/home
  // createWebHashHistory   http://localhost:5173/#/home
  history: createWebHistory(import.meta.env.BASE_URL),
  // routes: constantRoutes.concat(asyncRoutes)
  routes: [...constantRoutes, ...asyncRoutes]
})

export default router

5.3.5 入口文件配置路由

// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'

// 引入UI库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

import App from './App.vue'
import router from './router'

// 重置样式表
import 'normalize.css/normalize.css'

// import './assets/main.css' // 不使用默认样式

const app = createApp(App)

// 配置UI库
app.use(ElementPlus, { locale: zhCn })
app.use(createPinia())
app.use(router)

app.mount('#app')

5.3.6 App.vue使用路由

<!-- src/App.vue -->
<!-- 组合式API中如何使用 组件 -->
<script lang="ts" setup>
</script>

<template>
  <!-- 路由映射的组件  
    /login   Login
    /pro/list  Layout
  -->
  <RouterView/>
</template>

<style lang="scss">
/* 审查元素从而设定样式 */
html, body, #app, .common-layout, .el-container {
  height: 100%;
}
.common-layout {
  .el-container {
    background-color: #efefef;
    .el-aside {
      background-color: #001529;
    }
    .el-container {
      .el-header {
        background-color: #fff;
      }
      .el-main {
        background-color: #fff;
        margin: 16px;
      }
      .el-footer {
        background-color: #fff;
      }
    }
  }
}
</style>

5.3.7 主界面设置路由视图

<!-- src/layout/index.vue -->
<!-- 后台管理系统两种布局:登录布局 + 其他布局 -->
<script lang="ts" setup>
</script>

<template>
  <div class="common-layout">
    <el-container>
      <el-aside width="200px">Aside</el-aside>
      <el-container>
        <el-header>Header</el-header>
        <el-main>
          <!--  内容区域变化 -->
          <RouterView />
        </el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

5.3.8 轮播图列表视图

<!-- src/views/banner/list.vue -->
<script lang="ts" setup></script>

<template>
  <div>轮播图列表</div>
  <RouterView />
</template>

6.渲染左侧菜单栏

6.1 重新设置路由表

页面展示靠路由,但是左侧菜单并不见得所有的路由都得出现

将不需要出现在左侧菜单栏的数据 做一个标识hidden: true

扩展了类型注解

// src/router/index.ts
// type RouteRecordRaw 定义路由规则的变量的类型注解
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
// 1.引入自定义分模块的路由
import bannerRoutes from './modules/banner'
import proRoutes from './modules/pro'
import accountRoutes from './modules/account'

// 2.引入主界面布局,生成 首页和登录的路由 -- 常规路由 -- 任何人都可以访问路由
import Layout from '@/layout/index.vue'
import Home from '@/views/home/index.vue'
import Login from '@/views/login/index.vue'

// 扩展 类型注解
type MyRouteRecordRaw = RouteRecordRaw & {
  hidden?: boolean
}

// 3.定义常规路由-任何人可以访问的路由
export const constantRoutes: MyRouteRecordRaw[] = [
  { // 地址栏输入 /login, 整个页面展示登录页面
    path: '/login',
    component: Login,
    hidden: true
  },
  {
    path: '/',
    redirect: '/home',
    component: Layout,
    children: [
      {
        path: '/home',
        component: Home,
        meta: {
          title: '系统首页',
          icon: 'HomeFilled'
        }
      }
    ]
  }
]

// 4.整合模块路由
export const asyncRoutes: MyRouteRecordRaw[] = [
  bannerRoutes,
  proRoutes,
  accountRoutes
]

// 5.合并路由
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: constantRoutes.concat(asyncRoutes)
})

export default router

// src/router/modules/banner.ts
import Layout from '@/layout/index.vue'
import BannerAdd from '@/views/banner/add.vue'
import BannerList from '@/views/banner/list.vue'
import BannerHomeList from '@/views/banner/components/home.vue'
import BannerKindList from '@/views/banner/components/kind.vue'
export default {
  path: '/banner',
  name: 'banner',
  redirect: '/banner/list',
  component: Layout,
  meta: {
    title: '轮播图管理',
    icon: 'PictureFilled'
  },
  children: [
    {
      path: '/banner/list',
      component: BannerList,
      redirect: '/banner/list/home',
      meta: {
        title: '轮播图列表'
      },
      children: [
        {
          path: '/banner/list/home',
          component: BannerHomeList,
          meta: {
            title: '首页轮播图'
          },
        },
        {
          path: '/banner/list/kind',
          component: BannerKindList,
          meta: {
            title: '分类页轮播图'
          },
        }
      ]
    },
    {
      path: '/banner/add',
      component: BannerAdd,
      meta: {
        title: '添加轮播图'
      },
      hidden: true
    }
  ]
}

6.2 构建左侧菜单的组件

<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup></script>
<template>
  <el-aside width="200px">
    <div class="logo"></div>
    <div>左侧菜单</div>
  </el-aside>
</template>
<!-- src/layout/index.vue -->
<!-- 后台管理系统两种布局:登录布局 + 其他布局 -->
<script lang="ts" setup>
import SideBar from './components/Sidebar/index.vue'
</script>

<template>
  <div class="common-layout">
    <el-container>
      <!-- <el-aside width="200px">Aside</el-aside> -->
      <SideBar />
      <el-container>
        <el-header>Header</el-header>
        <el-main>
          <!--  内容区域变化 -->
          <RouterView />
        </el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

左侧菜单对应的每个数据的菜单项

<!-- src/layout/components/Sidebar/SidebarItem.vue -->
<script lang="ts" setup></script>

<template>
  <div>左侧菜单选项</div>
</template>
<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
  import SidebarItem from './SidebarItem.vue'

  // 整合路由
  import { constantRoutes, asyncRoutes } from '@/router'

  const menuList = constantRoutes.concat(asyncRoutes)
  console.log(menuList)
</script>
<template>
  <el-aside width="200px">
    <div class="logo"></div>
    <!-- 菜单组件
    https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
    -->
    <!-- <div>左侧菜单</div> -->
    <el-menu>
      <!-- 路由中的每一个选项都需要单独去渲染菜单项
      有的不需要出现在左侧的菜单栏
      有的只有一个菜单项
      有的有二级菜单和三级菜单
      建议把每个菜单项单独封装成一个组件  SidebarItem
      登录  系统首页   产品管理   账户管理  轮播图管理
      将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
      -->
      <SidebarItem 
        v-for="item of menuList"
        :key="item.path"
        :item = "item"
      ></SidebarItem>
    </el-menu>
  </el-aside>
</template>

渲染子组件

分析数据结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-itlMnTal-1672888918413)(assets/image-20221227160845689.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8CRy9Iq-1672888918414)(assets/image-20221227160905511.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LQBUyTgw-1672888918414)(assets/image-20221227161017853.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ryAgzwzg-1672888918415)(assets/image-20221227161042917.png)]

6.3 渲染左侧菜单的数据

说明:需要使用图标

$ cnpm install @element-plus/icons-vue
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'

// 引入UI库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

// 引入图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

import App from './App.vue'
import router from './router'

// 重置样式表
import 'normalize.css/normalize.css'

// import './assets/main.css' // 不使用默认样式

const app = createApp(App)
// 配置图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

// 配置UI库
app.use(ElementPlus, { locale: zhCn })
app.use(createPinia())
app.use(router)

app.mount('#app')

<!-- src/layout/components/Sidebar/SidebarItem.vue -->
<script lang="ts" setup>
  // 1.接收父组件所传递的数据 item 并且加以验证
  // https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props
  const props = defineProps({
    // item 为父组件传递给子组件的标识
    item: { type: Object, required: true }
  })
</script>
<!-- 
  遇到 类似登录的路由含有 hidden:true  不希望出现左侧菜单栏
 -->
<template>
  <!-- 过滤了 含有 hidden:true 的数据 -->
  <div v-if="!item.hidden">
    <!-- 系统首页的路由 children 的长度只为1,认为只需要写一个一级路由即可 
    index属性代表唯一标志 -->
    <el-menu-item :index="item.path" v-if="item.children && item.children.length === 1">
      <el-icon>
        <!-- 动态组件 -->
        <component :is="item.children[0].meta.icon"></component>
      </el-icon>
      <span>{{ item.children[0].meta.title }}</span>
    </el-menu-item>
    <!-- 路由选项既没有 children 也没有 hidden 时候
      {
        path: '/setting',
        component: Setting,
        meta: {
          title: '设置',
          icon: 'Setting'
        }
      },
      { // 二级菜单子菜单
        path: '/account/user',
        component: User,
        meta: {
          title: '用户列表'
        }
      },
    -->
    <el-menu-item :index="item.path" v-else-if="!item.children">
      <el-icon>
        <!-- 动态组件 -->
        <component :is="item.meta.icon"></component>
      </el-icon>
      <span>{{ item.meta.title }}</span>
    </el-menu-item>
    <!-- 剩余的包含有多级菜单的数据
      函数 - 函数递归
      组件 - 组件递归
        组合式API的组件递归,自己调用自己的组件,必须确保组件的名字和组件的文件名字保持一致
        假设组件文件名为 TestCom.vue ,那么递归调用时   <test-com></text-com>
        递归目的:自动渲染多级菜单

    -->
    <el-sub-menu :index="item.path" v-else>
      <!-- 插槽实现一级菜单 -->
      <template #title>
        <el-icon>
          <!-- 动态组件 -->
          <component :is="item.meta.icon"></component>
        </el-icon>
        <span>{{ item.meta.title }}</span>
      </template>
      <!-- 递归组件 -->
      <sidebar-item v-for="itm of item.children" :key="item.path" :item="itm"></sidebar-item>
    </el-sub-menu>
  </div>
</template>

6.4 点击左侧菜单切换路由

<!-- src/layout/components/Sidebar/SidebarItem.vue -->
<script lang="ts" setup>
  import { useRouter } from 'vue-router';

  const props = defineProps({
    item: { type: Object, required: true }
  })
  // https://router.vuejs.org/zh/guide/advanced/composition-api.html
  // router.push()       从A push 到B,从B push 到C,C可以返回到B,B可以返回到A
  // router.replace()    从A push 到B,从B replace 到C, C返回的实际上是 A
  // router.back()       返回
  // router.go(num)       num为正,前进几步,num为负,后退几步
  // 选项式API  this.$router.push()  .replace()
  const router = useRouter()
  const changeUrl = (path: string) => {
    console.log(path)
    router.push(path) // js 跳转页面 ---- 编程式导航跳转
  }
</script>
<template>
  <div v-if="!item.hidden">
    <el-menu-item @click="changeUrl(item.path)" :index="item.path" v-if="item.children && item.children.length === 1">
      <el-icon>
        <component :is="item.children[0].meta.icon"></component>
      </el-icon>
      <span>{{ item.children[0].meta.title }}</span>
    </el-menu-item>
    <el-menu-item @click="changeUrl(item.path)" :index="item.path" v-else-if="!item.children">
      <el-icon>
        <component :is="item.meta.icon"></component>
      </el-icon>
      <span>{{ item.meta.title }}</span>
    </el-menu-item>
    <el-sub-menu :index="item.path" v-else>
      <template #title>
        <el-icon>
          <component :is="item.meta.icon"></component>
        </el-icon>
        <span>{{ item.meta.title }}</span>
      </template>
      <sidebar-item v-for="itm of item.children" :key="item.path" :item="itm"></sidebar-item>
    </el-sub-menu>
  </div>
</template>

6.5 只展开一个以及刷新选中

<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
  import SidebarItem from './SidebarItem.vue'

  // 整合路由
  import { constantRoutes, asyncRoutes } from '@/router'
  import { useRoute } from 'vue-router';

  const menuList = constantRoutes.concat(asyncRoutes)
  console.log(menuList)

  // 获取路由地址
  // 选项式API  this.$route
  const route = useRoute()
  console.log(route)
</script>
<template>
  <el-aside width="200px">
    <div class="logo"></div>
    <!-- 菜单组件
    https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
    -->
    <!-- <div>左侧菜单</div> -->
    <el-menu
      :unique-opened="true"
      :default-active="route.path"
      background-color="#001529"
      text-color="#fff"
      :collapse-transition="false"
    >
      <!-- 路由中的每一个选项都需要单独去渲染菜单项
      有的不需要出现在左侧的菜单栏
      有的只有一个菜单项
      有的有二级菜单和三级菜单
      建议把每个菜单项单独封装成一个组件  SidebarItem
      登录  系统首页   产品管理   账户管理  轮播图管理
      将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
      -->
      <SidebarItem 
        v-for="item of menuList"
        :key="item.path"
        :item = "item"
      ></SidebarItem>
    </el-menu>
  </el-aside>
</template>

6.6 logo

<!-- src/layout/components/logo/index.vue -->
<script lang="ts" setup>
  import { computed } from 'vue'
  import logo from '@/assets/logo.svg'
  import settings from '@/settings'

  const title = computed(() => settings.title)
</script>

<template>
  <div class="logo">
    <el-image style="width: 40px; height: 40px" :src="logo" fit="fill" />
    <span>{{  title  }}</span>
  </div>
</template>

<style lang="scss">
.logo {
  width: 100%;
  height: 60px;
  overflow: hidden;
  padding: 10px;
  box-sizing: border-box;
  color: #fff;
  display: flex;
  align-items: center;
}
</style>
<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
  import SidebarItem from './SidebarItem.vue'
  import Logo from './../logo/index.vue'

  // 整合路由
  import { constantRoutes, asyncRoutes } from '@/router'
  import { useRoute } from 'vue-router';

  const menuList = constantRoutes.concat(asyncRoutes)
  console.log(menuList)

  // 获取路由地址
  // 选项式API  this.$route
  const route = useRoute()
  console.log(route) // route.path.value
</script>
<template>
  <el-aside width="200px">
    <!-- <div class="logo"></div> -->
    <Logo />
    <!-- 菜单组件
    https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
    -->
    <!-- <div>左侧菜单</div> -->
    <el-menu
      :unique-opened="true"
      :default-active="route.path"
    >
      <!-- 路由中的每一个选项都需要单独去渲染菜单项
      有的不需要出现在左侧的菜单栏
      有的只有一个菜单项
      有的有二级菜单和三级菜单
      建议把每个菜单项单独封装成一个组件  SidebarItem
      登录  系统首页   产品管理   账户管理  轮播图管理
      将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
      -->
      <SidebarItem 
        v-for="item of menuList"
        :key="item.path"
        :item = "item"
      ></SidebarItem>
    </el-menu>
  </el-aside>
</template>

6.7 左侧菜单的收缩

logo组件接收变量,控制文件的显示

<!-- src/layout/components/logo/index.vue -->
<script lang="ts" setup>
  import { computed } from 'vue'
  import logo from '@/assets/logo.svg'
  import settings from '@/settings'

  const props = defineProps({
    collapse: { type: Boolean, required: true }
  })

  const title = computed(() => settings.title)
</script>

<template>
  <div class="logo">
    <el-image style="width: 40px; height: 40px" :src="logo" fit="fill" />
    <span v-if="!collapse">{{  title  }}</span>
  </div>
</template>

<style lang="scss">
.logo {
  width: 100%;
  height: 60px;
  overflow: hidden;
  padding: 10px;
  box-sizing: border-box;
  color: #fff;
  display: flex;
  align-items: center;
}
</style>
<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
  import { ref } from 'vue'
  import SidebarItem from './SidebarItem.vue'
  import Logo from './../logo/index.vue'

  // 整合路由
  import { constantRoutes, asyncRoutes } from '@/router'
  import { useRoute } from 'vue-router';

  const menuList = constantRoutes.concat(asyncRoutes)
  console.log(menuList)

  // 获取路由地址
  // 选项式API  this.$route
  const route = useRoute()
  console.log(route) // route.path.value

  const collapse = ref(false)
</script>
<template>
  <el-aside :width="collapse ? '50px' : '200px'">
    <!-- <div class="logo"></div> -->
    <Logo :collapse="collapse"/>
    <!-- 菜单组件
    https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
    -->
    <!-- <div>左侧菜单</div> -->
    <el-menu
      :collapse="collapse"
      :unique-opened="true"
      :default-active="route.path"
    >
      <!-- 路由中的每一个选项都需要单独去渲染菜单项
      有的不需要出现在左侧的菜单栏
      有的只有一个菜单项
      有的有二级菜单和三级菜单
      建议把每个菜单项单独封装成一个组件  SidebarItem
      登录  系统首页   产品管理   账户管理  轮播图管理
      将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
      -->
      <SidebarItem 
        v-for="item of menuList"
        :key="item.path"
        :item = "item"
      ></SidebarItem>
    </el-menu>
    <div class="changeIcon" :style="{ width: collapse ? '50px' : '200px'}">
      <el-icon color="#fff" v-if="collapse" @click="collapse = !collapse"><ArrowRight /></el-icon>
      <el-icon color="#fff" v-else @click="collapse=!collapse"><ArrowLeft /></el-icon>
    </div>
  </el-aside>
</template>

<style lang="scss">
.changeIcon {
  position: absolute;
  bottom: 10px;
  display: flex;
  justify-content: center;
}
</style>

6.8 面包屑

<!-- src/layout/components/breadcrumb/index.vue -->
<script lang="ts" setup></script>

<template>
  <div>面包屑</div>
</template>
// src/router/index.ts
// type RouteRecordRaw 定义路由规则的变量的类型注解
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
// 1.引入自定义分模块的路由
import bannerRoutes from './modules/banner'
import proRoutes from './modules/pro'
import accountRoutes from './modules/account'

// 2.引入主界面布局,生成 首页和登录的路由 -- 常规路由 -- 任何人都可以访问路由
import Layout from '@/layout/index.vue'
import Home from '@/views/home/index.vue'
import Login from '@/views/login/index.vue'

// 扩展 类型注解(interface) 暴露出去,供需要的使用 ++++++++++++++++++++++++++++++++++++++++
// export declare 暴露类型
export declare type MyRouteRecordRaw = RouteRecordRaw & {
  hidden?: boolean
}

// 3.定义常规路由-任何人可以访问的路由
export const constantRoutes: MyRouteRecordRaw[] = [
  { // 地址栏输入 /login, 整个页面展示登录页面
    path: '/login',
    component: Login,
    hidden: true
  },
  // {
  //   path: '/setting',
  //   component: Setting,
  //   meta: {
  //     title: '设置',
  //     icon: 'Setting'
  //   }
  // },
  {
    path: '/',
    redirect: '/home',
    component: Layout,
    children: [
      {
        path: '/home',
        component: Home,
        meta: {
          title: '系统首页',
          icon: 'HomeFilled'
        }
      }
    ]
  }
]

// 4.整合模块路由
export const asyncRoutes: MyRouteRecordRaw[] = [
  bannerRoutes,
  proRoutes,
  accountRoutes
]

// 5.合并路由
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: constantRoutes.concat(asyncRoutes)
})

export default router

获取需要的对象数据

<!-- src/layout/components/breadcrumb/index.vue -->
<script lang="ts" setup>
  // 准备数据
  // const breadcrumbNameMap = {
  //   '/home': '系统首页',
  //   '/banner': '轮播图管理',
  //   '/banner/list': '轮播图列表',
  //   '/banner/list/home': '首页轮播图',
  //   '/banner/list/kind': '分类轮播图',
  //   '/banner/add': '添加轮播图',
  //   '/pro': '产品管理',
  //   '/pro/list': '产品列表',
  //   '/pro/search': '筛选列表',
  //   '/account': '账户管理',
  //   '/account/user': '用户列表',
  //   '/account/admin': '管理员列表' 
  // }
  import { constantRoutes, asyncRoutes, type MyRouteRecordRaw } from '@/router'
  
  const menuList = [...constantRoutes, ...asyncRoutes]

  let breadcrumbNameMap: Record<string, string> = {}

  function getBreadcrumbNameMap (menuList: MyRouteRecordRaw[]) {
    menuList.forEach(item => {
      if (item.path !== '/login') {
        if (item.children) {
          if (item.children.length === 1) {
            // 如果遇到 ** 可能未定义 使用 !解决
            // 因为路由meta 的属性的类型注解 不明确,此处上面定义为 string,使用类型推断为string即可
            breadcrumbNameMap[item.children[0].path] = item.children[0].meta!.title as string
          } else {
            breadcrumbNameMap[item.path] = item.meta!.title as string
            getBreadcrumbNameMap(item.children)
          }
        } else {
          breadcrumbNameMap[item.path] = item.meta!.title as string
        }
      }
    })
  }
  getBreadcrumbNameMap(menuList)
  console.log(breadcrumbNameMap) // 需要准备的数据

  // /banner/list/kind 
  // breadcrumbNameMap['/banner']  breadcrumbNameMap['/banner/list'] breadcrumbNameMap['/banenr/list/kind']
  // 轮播图管理 轮播图列表 分类轮播图
</script>

<template>
  <div>面包屑</div>
</template>

根据地址栏获取路由

<!-- src/layout/components/breadcrumb/index.vue -->
<script lang="ts" setup>
  // 准备数据
  // const breadcrumbNameMap = {
  //   '/home': '系统首页',
  //   '/banner': '轮播图管理',
  //   '/banner/list': '轮播图列表',
  //   '/banner/list/home': '首页轮播图',
  //   '/banner/list/kind': '分类轮播图',
  //   '/banner/add': '添加轮播图',
  //   '/pro': '产品管理',
  //   '/pro/list': '产品列表',
  //   '/pro/search': '筛选列表',
  //   '/account': '账户管理',
  //   '/account/user': '用户列表',
  //   '/account/admin': '管理员列表' 
  // }
  import { constantRoutes, asyncRoutes, type MyRouteRecordRaw } from '@/router'
  import { watchEffect, ref } from 'vue';
  import { useRoute } from 'vue-router';
  
  const menuList = [...constantRoutes, ...asyncRoutes]

  let breadcrumbNameMap: Record<string, string> = {}

  function getBreadcrumbNameMap (menuList: MyRouteRecordRaw[]) {
    menuList.forEach(item => {
      if (item.path !== '/login') {
        if (item.children) {
          if (item.children.length === 1) {
            // 如果遇到 ** 可能未定义 使用 !解决
            // 因为路由meta 的属性的类型注解 不明确,此处上面定义为 string,使用类型推断为string即可
            breadcrumbNameMap[item.children[0].path] = item.children[0].meta!.title as string
          } else {
            breadcrumbNameMap[item.path] = item.meta!.title as string
            getBreadcrumbNameMap(item.children)
          }
        } else {
          breadcrumbNameMap[item.path] = item.meta!.title as string
        }
      }
    })
  }
  getBreadcrumbNameMap(menuList)
  // console.log(breadcrumbNameMap) // 需要准备的数据

  // /banner/list/kind 
  // breadcrumbNameMap['/banner']  breadcrumbNameMap['/banner/list'] breadcrumbNameMap['/banenr/list/kind']
  // 轮播图管理 轮播图列表 分类轮播图

  const route = useRoute()
  // const url = route.path
  // console.log(url) // /banner/list/kind 

  function getData () {
    const arr = route.path.split('/') // ['', 'banner', 'list', 'kind']
    arr.shift() // ['banner', 'list', 'kind']
    // ['/banner', '/banner/list', '/banner/list/kind']

    const newArr: string[] = []
    arr.map((_, index) => {
      // join('/') 拼接时使用 / 拼接,默认是 ,
      const newUrl = '/' + arr.slice(0, index + 1).join('/')
      console.log(newUrl)
      newArr.push(newUrl)
    })
    // console.log(newArr)
    return newArr
  }
  let breadcrumbArr = ref<string[]>([])

  watchEffect(() => { // 路由变化时 重新获取面包屑 ---- 保证数据的双向绑定
    console.log(11111)
    breadcrumbArr.value = getData() // 修改使用 value 保证响应式
    console.log('66', breadcrumbArr)
  })
</script>

<template>
  <el-breadcrumb separator="/">
    <el-breadcrumb-item :to="{ path: '/' }">系统首页</el-breadcrumb-item>
    <el-breadcrumb-item v-for="(item, index) of breadcrumbArr" :key="index">
      <a :href="item">{{ breadcrumbNameMap[item] }}</a>
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

垂直居中显示面包屑

<!-- src/layout/index.vue -->
<!-- 后台管理系统两种布局:登录布局 + 其他布局 -->
<script lang="ts" setup>
import SideBar from './components/Sidebar/index.vue'
import Breadcrumb from './components/breadcrumb/index.vue'
</script>

<template>
  <div class="common-layout">
    <el-container>
      <!-- <el-aside width="200px">Aside</el-aside> -->
      <SideBar />
      <el-container>
        <el-header :style="{ display: 'flex', alignItems: 'center'}">
          <Breadcrumb />
        </el-header>
        <el-main>
          <!--  内容区域变化 -->
          <RouterView />
        </el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

7.封装数据请求

axios https://www.axios-http.cn/docs/intro

// restful api
// get
// post
// put    更新
// patch  更新
// delete 删除

// get
fetch('url?a=1&b=2').then(res => res.json()).then(res => { console.log(res) })
axios.get('url?a=1&b=2').then(res => { console.log(res) })
axios.get('url', {  params: { a: 1, b: 2} }).then(res => { console.log(res) })
axios({
  url: 'url?a=1&b=2',
  method: 'get'
}).then(res => { console.log(res) })
axios({
  url: 'url',
  method: 'get',
  params: { a: 1, b: 2}
}).then(res => { console.log(res) })

// post
axios.post('url', { a: 1, b: 2 }).then(res => { console.log(res) })
axios({
  url: 'url',
  method: 'post',
  data: { a: 1, b: 2}
}).then(res => { console.log(res) })

// axios 拦截器
// 请求拦截器
// 响应拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
  	// 显示loading动画效果
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
  	// loading动画消失
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

一般项目中都需要重新定义axios,需要对axios进行配置

// 创建实例时配置默认值
const instance = axios.create({
  baseURL: 'https://api.example.com'
});

// 创建实例后修改默认值
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// https://api.example.com/api/pro/list
instance.get('/api/pro/list')

接口文档:http://121.89.205.189:3000/admindoc/

// src/utils/request.ts
// 1.引入axios
import axios from 'axios'
// 因为router 是单独暴露的,此处可以直接引入使用
import router from '@/router'
// 2.自定义axios 
// 接口文档: http://121.89.205.189:3000/admindoc/
const ins = axios.create({
  baseURL: 'http://121.89.205.189:3000/admin'
})

// 3.配置拦截器
// 请求拦截器
ins.interceptors.request.use((config) => {
  // 请求之前干什么
  // 后台管理系统所有的页面都需要从服务器获取数据
  // 思考:要数据就必须是登录才可以
  // 请求数据时需要将 是否 登录的标识传递给 服务器,服务器判断登录标识 是否可用,
  // 如果可以使用,返回你要的数据,如果不可以使用,告诉用户,登录失效,用户重新登录
  // 登录标识:token 登录以后后端返回给前端,前端一般会将其存入到本地存储
  // 如何将 token 传递给服务器,一般从本地存储提取,然后在 头信息(请求头) 中传递

  config.headers!.token = localStorage.getItem('token') || ''

  return config
}, (error) => {
  return Promise.reject(error)
})

// 响应拦截器
ins.interceptors.response.use((response) => {
  // 判断登录标识 是否有效,如果无效跳转至登录页面,如果有效,不做操作
  if (response.data.code === '10119') {
    // 没有传递token 或者 token过期
    // 跳转到登录页面 重新登录
    router.push('/login')

  } 
  return response
}, (error) => {
  return Promise.reject(error)
})

// 暴露自定义的 axios
export default ins

所有的数据请求都要先登录,登录状态在非常多的页面都需要使用

  • 多个视图需要依赖同一个状态
  • 来自不同视图的行为需要变更同一个状态
  • 首选状态管理器

8.pinia状态管理器

https://pinia.vuejs.org/zh/

8.1 入口注册pinia

// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'

// 引入UI库
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

// 引入图标
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

import App from './App.vue'
import router from './router'

// 重置样式表
import 'normalize.css/normalize.css'

// import './assets/main.css' // 不使用默认样式

const app = createApp(App)
// 配置图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}

// 配置UI库
app.use(ElementPlus, { locale: zhCn })
app.use(createPinia())
app.use(router)

app.mount('#app')

8.2 定义状态管理器模块

// src/store/counter.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

// 选项式API
// export const useCounterStore = defineStore('counter', {
//   state () { // 初始化数据 -- data
//     return {
//       count: 99
//     }
//   },
//   getters: { // 计算属性
//     doubleCount: state => { // state 即为 当前状态管理器中的 所有的状态
//       return state.count * 2
//     } 
//   },
//   actions: { // 事件  ----  methods
//     increment () {
//       this.count += 10
//     },
//     decrement () {
//       this.count -= 10
//     }
//   }

// })

// 组合式API
export const useCounterStore = defineStore('counter', () => {
  // 初始化
  const count = ref(0)
  // 计算属性
  const doubleCount = computed(() => count.value * 2)
  // 方法
  function increment() {
    count.value += 10
  }
  function decrement() {
    count.value -= 10
  }
  // 返回˙状态和方法
  return { count, doubleCount, increment, decrement }
})

8.3 组件中使用

8.3.1 模版中使用对象渲染

<!-- src/views/home/index.vue -->
<script lang="ts" setup>
  import { computed } from 'vue'
  import { useCounterStore } from '@/stores/counter'

  const counter = useCounterStore()

  // 提取模版中需要使用的数据
  const count = computed(() => counter.count)
  const doubleCount = computed(() => counter.doubleCount)

  const reduce = () => {
    counter.decrement()
  }
  const add = () => {
    counter.increment()
  }
</script>

<template>
  <div>系统首页</div>
  <button @click="reduce">-10</button> 
  {{ count }} - {{ counter.count }}  - {{ counter.doubleCount }} - {{  doubleCount }}
  <button @click="add">+10</button>
</template>
<!-- src/views/account/user.vue -->
<script lang="ts" setup>
  import { useCounterStore } from '@/stores/counter'

  const counter = useCounterStore()

  const reduce = () => {
    counter.decrement()
  }
  const add = () => {
    counter.increment()
  }
</script>

<template>
  <div>用户列表</div>
  <button @click="reduce">-10</button> 
  {{ counter.count }} - {{ counter.doubleCount }}
  <button @click="add">+10</button>
</template>

9登录功能实现

9.1 构建登录页面

<!-- src/views/login/index.vue -->
<script lang="ts" setup>
  import { ref, computed } from 'vue';

  const adminname = ref('')
  const password = ref('')

  // 按钮是否可点
  const flag = computed(() => { return adminname.value === '' || password.value === ''})
</script>

<template>
  <div class="loginBox">
    <div class="loginForm">
      <div class="loginTitle">系统登录</div>
      <div class="loginInput">
        <el-input v-model="adminname" placeholder="管理员账户" clearable prefix-icon="User"/>
      </div>
      <div class="loginInput">
        <el-input type="password" v-model="password" clearable placeholder="管理员密码" show-password prefix-icon="Lock"/>
      </div>
      <el-button type="primary" :disabled="flag" class="loginBtn">登录</el-button>

      <span>默认账户名:admin 默认密码:123456</span>
    </div>
  </div>
</template>

<style lang="scss">
  .loginBox {
    width: 100%;
    height: 100%;
    background-color: #2b2b2b;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #fff;
    .loginForm {
      width: 500px;
      min-height: 300px;
      /* background-color: #fff; */
      .loginTitle {
        text-align: center;
        font-size: 24px;
        font-weight: bold;
      }
      .loginInput {
        margin: 20px 0;
        .el-input {
          height: 40px;
          .el-input__wrapper {
            background-color: #2b2b2b;
            .el-input__inner {
              background-color: #2b2b2b;
            }
          }
          
        }
      }
      .loginBtn {
        width: 100%;
        height: 36px;
        margin-bottom: 20px;
      }
    }
  }
</style>

9.2 封装管理员登录的数据请求

// src/api/admin.ts
import request from '@/utils/request'

// interface IAdminLoginParams {
//   adminname: string
//   password: string
// }

type TAdminLoginParams = {
  adminname: string
  password: string
}

export function adminLogin (params: TAdminLoginParams) {
  return request.post('/admin/login', params)
}

9.3 登录页面调用登录接口

<!-- src/views/login/index.vue -->
<script lang="ts" setup>
  import { ref, computed } from 'vue';
  import { ElMessage } from 'element-plus'
  import { adminLogin } from '@/api/admin'
  import { useRouter } from 'vue-router';
  const adminname = ref('')
  const password = ref('')

  // 按钮是否可点
  const flag = computed(() => { return adminname.value === '' || password.value === ''})

  const router = useRouter()
  const login = () => {
    adminLogin({
      adminname: adminname.value,
      password: password.value
    }).then(res => {
      console.log(res)
      if (res.data.code === '10005') {
        console.log('账户未注册')
        ElMessage.error('账户未注册.')
      } else if (res.data.code === '10003') {
        ElMessage.warning('密码错误')
      } else {
        ElMessage.success('登录成功')

        // 将登陆信息保存到本地
        localStorage.setItem('adminname', res.data.data.adminname)
        localStorage.setItem('checkedkeys', res.data.data.checkedkeys)
        localStorage.setItem('role', res.data.data.role)
        localStorage.setItem('token', res.data.data.token)

        router.replace('/')
      }
    })
  }
</script>

<template>
  <div class="loginBox">
    <div class="loginForm">
      <div class="loginTitle">系统登录</div>
      <div class="loginInput">
        <el-input v-model="adminname" placeholder="管理员账户" clearable prefix-icon="User"/>
      </div>
      <div class="loginInput">
        <el-input type="password" v-model="password" clearable placeholder="管理员密码" show-password prefix-icon="Lock"/>
      </div>
      <el-button type="primary" @click="login" :disabled="flag" class="loginBtn">登录</el-button>

      <span>默认账户名:admin 默认密码:123456</span>
    </div>
  </div>
</template>

<style lang="scss">
  .loginBox {
    width: 100%;
    height: 100%;
    background-color: #2b2b2b;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #fff;
    .loginForm {
      width: 500px;
      min-height: 300px;
      /* background-color: #fff; */
      .loginTitle {
        text-align: center;
        font-size: 24px;
        font-weight: bold;
      }
      .loginInput {
        margin: 20px 0;
        .el-input {
          height: 40px;
          .el-input__wrapper {
            background-color: #2b2b2b;
            .el-input__inner {
              background-color: #2b2b2b;
            }
          }
          
        }
      }
      .loginBtn {
        width: 100%;
        height: 36px;
        margin-bottom: 20px;
      }
    }
  }
</style>

9.4 退出登录

<!-- src/layout/index.vue -->
<!-- 后台管理系统两种布局:登录布局 + 其他布局 -->
<script lang="ts" setup>
  import SideBar from './components/Sidebar/index.vue'
  import Breadcrumb from './components/breadcrumb/index.vue'
  import { ArrowDown } from '@element-plus/icons-vue'
  import { useRouter } from 'vue-router';

  const router = useRouter()
  const adminname = localStorage.getItem('adminname')

  const logout = () => {
    localStorage.removeItem('adminname')
    localStorage.removeItem('token')
    localStorage.removeItem('checkedkeys')
    localStorage.removeItem('role')

    router.push('/login')
  }
</script>

<template>
  <div class="common-layout">
    <el-container>
      <!-- <el-aside width="200px">Aside</el-aside> -->
      <SideBar />
      <el-container>
        <el-header :style="{ display: 'flex', alignItems: 'center'}">
          <Breadcrumb />
          <el-dropdown :style="{ position: 'absolute', right: '16px'}">
            <span class="el-dropdown-link">
              欢迎您,{{  adminname  }}
              <el-icon class="el-icon--right">
                <arrow-down />
              </el-icon>
            </span>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>个人中心</el-dropdown-item>
                <el-dropdown-item divided @click="logout">退出</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </el-header>
        <el-main>
          <!--  内容区域变化 -->
          <RouterView />
        </el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

9.5 登录信息状态管理

// src/store/admins.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

// 组合式API
export const useAdminStore = defineStore('admin', () => {
  // 初始化
  const adminname = ref(localStorage.getItem('adminname') || '')
  const token = ref(localStorage.getItem('token') || '')
  
  const changeAdminname = (val: string) => {
    adminname.value = val
  }

  const changeToken = (val: string) => {
    token.value = val
  }

  // 返回˙状态和方法
  return { adminname, token, changeAdminname, changeToken }
})

9.6 登录页面修改 状态以及布局页面使用状态

<!-- src/views/login/index.vue -->
<script lang="ts" setup>
  import { ref, computed } from 'vue';
  import { ElMessage } from 'element-plus'
  import { adminLogin } from '@/api/admin'
  import { useRouter } from 'vue-router';
  import { useAdminStore } from '@/stores/admins';
  const adminname = ref('admin')
  const password = ref('123456')

  const admin = useAdminStore()

  // 按钮是否可点
  const flag = computed(() => { return adminname.value === '' || password.value === ''})

  const router = useRouter()
  const login = () => {
    // 正则验证
    adminLogin({
      adminname: adminname.value,
      password: password.value
    }).then(res => {
      // console.log(res)
      if (res.data.code === '10005') {
        console.log('账户未注册')
        ElMessage.error('账户未注册.')
      } else if (res.data.code === '10003') {
        ElMessage.warning('密码错误')
      } else {
        ElMessage.success('登录成功')

        // 将登陆信息保存到本地
        localStorage.setItem('adminname', res.data.data.adminname)
        localStorage.setItem('checkedkeys', res.data.data.checkedkeys)
        localStorage.setItem('role', res.data.data.role)
        localStorage.setItem('token', res.data.data.token)

        // 可以将数据保存到状态管理器
        admin.changeAdminname(res.data.data.adminname)
        admin.changeToken(res.data.data.token)

        router.replace('/')
      }
    })
  }
</script>

<template>
  <div class="loginBox">
    <div class="loginForm">
      <div class="loginTitle">系统登录</div>
      <div class="loginInput">
        <el-input v-model="adminname" placeholder="管理员账户" clearable prefix-icon="User"/>
      </div>
      <div class="loginInput">
        <el-input type="password" v-model="password" clearable placeholder="管理员密码" show-password prefix-icon="Lock"/>
      </div>
      <el-button type="primary" @click="login" :disabled="flag" class="loginBtn">登录</el-button>

      <span>默认账户名:admin 默认密码:123456</span>
    </div>
  </div>
</template>

<style lang="scss">
  .loginBox {
    width: 100%;
    height: 100%;
    background-color: #2b2b2b;
    display: flex;
    justify-content: center;
    align-items: center;
    color: #fff;
    .loginForm {
      width: 500px;
      min-height: 300px;
      /* background-color: #fff; */
      .loginTitle {
        text-align: center;
        font-size: 24px;
        font-weight: bold;
      }
      .loginInput {
        margin: 20px 0;
        .el-input {
          height: 40px;
          .el-input__wrapper {
            background-color: #2b2b2b;
            .el-input__inner {
              background-color: #2b2b2b;
            }
          }
          
        }
      }
      .loginBtn {
        width: 100%;
        height: 36px;
        margin-bottom: 20px;
      }
    }
  }
</style>
<!-- src/layout/index.vue -->
<!-- 后台管理系统两种布局:登录布局 + 其他布局 -->
<script lang="ts" setup>
  import SideBar from './components/Sidebar/index.vue'
  import Breadcrumb from './components/breadcrumb/index.vue'
  import { ArrowDown } from '@element-plus/icons-vue'
  import { useRouter } from 'vue-router';
  import { useAdminStore } from '@/stores/admins'
  import { computed } from 'vue';

  const router = useRouter()
  // const adminname = localStorage.getItem('adminname')
  const admin = useAdminStore()
  const adminname = computed(() => admin.adminname)

  const logout = () => {
    localStorage.removeItem('adminname')
    localStorage.removeItem('token')
    localStorage.removeItem('checkedkeys')
    localStorage.removeItem('role')

    router.push('/login')
  }
</script>

<template>
  <div class="common-layout">
    <el-container>
      <!-- <el-aside width="200px">Aside</el-aside> -->
      <SideBar />
      <el-container>
        <el-header :style="{ display: 'flex', alignItems: 'center'}">
          <Breadcrumb />
          <el-dropdown :style="{ position: 'absolute', right: '16px'}">
            <span class="el-dropdown-link">
              欢迎您1,{{  adminname  }}
              <el-icon class="el-icon--right">
                <arrow-down />
              </el-icon>
            </span>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>个人中心</el-dropdown-item>
                <el-dropdown-item divided @click="logout">退出</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </el-header>
        <el-main>
          <!--  内容区域变化 -->
          <RouterView />
        </el-main>
        <el-footer>Footer</el-footer>
      </el-container>
    </el-container>
  </div>
</template>

10 管理员的操作

10.1 渲染管理员列表

封装获取管理员信息的接口

// src/api/admin.ts
import request from '@/utils/request'

// interface IAdminLoginParams {
//   adminname: string
//   password: string
// }

type TAdminLoginParams = {
  adminname: string
  password: string
}

export function adminLogin (params: TAdminLoginParams) {
  return request.post('/admin/login', params)
}

export function getAdminList () {
  return request.get('/admin/list')
}
<!-- src/views/account/Admin.vue -->
<script lang="ts" setup>
  import { ref, onMounted } from 'vue';
  import { getAdminList } from '@/api/admin'

  interface IAdminInfo {
    adminid: string
    adminname: string
    role: number
  }

  const adminList = ref<IAdminInfo[]>([])

  onMounted(() => {
    getAdminList().then(res => {
      console.log('admin', res.data)
      adminList.value = res.data.data
    })
  })
</script>

<template>
  <el-table :data="adminList" style="width: 100%">
    <el-table-column label="序号">
      <template #default="scope">
        <!-- scope.$index 代表索引值 -->
        <span>{{ scope.$index + 1 }}</span>
      </template>
    </el-table-column>
    <el-table-column prop="adminname" label="管理员账户" width="180" />
    <el-table-column prop="role" label="角色" width="180" >
      <template #default="scope">
        <!-- scope.row 代表一条数据 -->
        <el-tag v-if="scope.row.role === 2" class="ml-2" type="success">超级管理员</el-tag>
        <el-tag v-else class="ml-2" type="info">管理员</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template #default="scope">
        <el-button size="small"
          >编辑</el-button
        >
        <el-button
          size="small"
          type="danger"
          >删除</el-button
        >
      </template>
    </el-table-column>
  </el-table>
</template>

10.2 分页实现

<!-- src/views/account/Admin.vue -->
<script lang="ts" setup>
  import { ref, onMounted, computed } from 'vue';
  import { getAdminList } from '@/api/admin'

  interface IAdminInfo {
    adminid: string
    adminname: string
    role: number
  }

  // 将展示数据adminList 变为了 计算属性的数据
  // 将依据原始数据 originalList 以及 页码currentPage  和每页显示个数pageSize 一起计算出来显示的数据adminList
  // const adminList = ref<IAdminInfo[]>([])
  const currentPage = ref(1)
  const pageSize = ref(10)
  const originalList = ref<IAdminInfo[]>([])
  const changeCurrentPage = (val: number) => { // 页码改变函数
    currentPage.value = val
  }
  const changeSize = (val: number) => { // 每页显示个数改变的函数
    pageSize.value = val
  }

  const adminList = computed(() => {
    // 截取数组的某一些项
    // 深拷贝数据作为备份
    const arr = JSON.parse(JSON.stringify(originalList.value))
    const newArr = arr.splice((currentPage.value - 1) * pageSize.value, pageSize.value)
    // console.log(originalList.value)
    return newArr
  })

  onMounted(() => {
    getAdminList().then(res => {
      // console.log('admin', res.data)
      originalList.value = res.data.data
    })
  })


</script>

<template>
  <el-table :data="adminList" style="width: 100%">
    <el-table-column label="序号">
      <template #default="scope">
        <!-- scope.$index 代表索引值 -->
        <span>{{ scope.$index + 1 }}</span>
      </template>
    </el-table-column>
    <el-table-column prop="adminname" label="管理员账户" width="180" />
    <el-table-column prop="role" label="角色" width="180" >
      <template #default="scope">
        <!-- scope.row 代表一条数据 -->
        <el-tag v-if="scope.row.role === 2" class="ml-2" type="success">超级管理员</el-tag>
        <el-tag v-else class="ml-2" type="info">管理员</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template #default="scope">
        <el-button size="small"
          >编辑</el-button
        >
        <el-button
          size="small"
          type="danger"
          >删除</el-button
        >
      </template>
    </el-table-column>
  </el-table>
  <el-pagination 
    background 
    layout="sizes, prev, pager, next" 
    :page-sizes="[5, 10, 20, 30]"
    :total="originalList.length" 
    @current-change="changeCurrentPage"
    @size-change="changeSize"
    />
</template>

filter处理数据

const adminList = computed(() => {
    const newArr = originalList.value.filter((item, index) => {
      return index >= (currentPage.value - 1) * pageSize.value && index < (currentPage.value - 1) * pageSize.value + pageSize.value
    })
    return newArr
  })

10.3 删除管理员

// src/api/admin.ts
import request from '@/utils/request'

// interface IAdminLoginParams {
//   adminname: string
//   password: string
// }

type TAdminLoginParams = {
  adminname: string
  password: string
}

export function adminLogin (params: TAdminLoginParams) {
  return request.post('/admin/login', params)
}

export function getAdminList () {
  return request.get('/admin/list')
}

export function deleteAdmin (params: { adminid: string }) {
  return request.post('/admin/delete', params)
}
<!-- src/views/account/Admin.vue -->
<script lang="ts" setup>
  import { ref, onMounted, computed } from 'vue';
  import { getAdminList, deleteAdmin } from '@/api/admin'

  interface IAdminInfo {
    adminid: string
    adminname: string
    role: number
  }

  // 将展示数据adminList 变为了 计算属性的数据
  // 将依据原始数据 originalList 以及 页码currentPage  和每页显示个数pageSize 一起计算出来显示的数据adminList
  // const adminList = ref<IAdminInfo[]>([])
  const currentPage = ref(1)
  const pageSize = ref(10)
  const originalList = ref<IAdminInfo[]>([])
  const changeCurrentPage = (val: number) => { // 页码改变函数
    currentPage.value = val
  }
  const changeSize = (val: number) => { // 每页显示个数改变的函数
    pageSize.value = val
  }

  const adminList = computed(() => {
    // 截取数组的某一些项
    // 深拷贝数据作为备份
    // const arr = JSON.parse(JSON.stringify(originalList.value))
    // const newArr = arr.splice((currentPage.value - 1) * pageSize.value, pageSize.value)
    const newArr = originalList.value.filter((item, index) => {
      return index >= (currentPage.value - 1) * pageSize.value && index < (currentPage.value - 1) * pageSize.value + pageSize.value
    })
    // console.log(originalList.value)
    return newArr
  })
  const getAdminListData = () => {
    getAdminList().then(res => {
      // console.log('admin', res.data)
      originalList.value = res.data.data
    })
  }
  onMounted(() => {
    getAdminListData()
  })

  const removeItem = (adminid: string) => {
    deleteAdmin({ adminid }).then(() => {
      getAdminListData()
    })
  }
</script>

<template>
  <el-table :data="adminList" style="width: 100%">
    <el-table-column label="序号">
      <template #default="scope">
        <!-- scope.$index 代表索引值 -->
        <span>{{ (currentPage - 1) * pageSize + scope.$index + 1 }}</span>
      </template>
    </el-table-column>
    <el-table-column prop="adminname" label="管理员账户" width="180" />
    <el-table-column prop="role" label="角色" width="180" >
      <template #default="scope">
        <!-- scope.row 代表一条数据 -->
        <el-tag v-if="scope.row.role === 2" class="ml-2" type="success">超级管理员</el-tag>
        <el-tag v-else class="ml-2" type="info">管理员</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template #default="scope">
        <el-button size="small"
          >编辑</el-button
        >
        <el-popconfirm title="确定删除吗?" @confirm="removeItem(scope.row.adminid)">
          <template #reference>
            <el-button
              size="small"
              type="danger"
              >删除</el-button
            >
          </template>
        </el-popconfirm>
        
      </template>
    </el-table-column>
  </el-table>
  <el-pagination 
    background 
    layout="sizes, prev, pager, next" 
    :page-sizes="[5, 10, 20, 30]"
    :total="originalList.length" 
    @current-change="changeCurrentPage"
    @size-change="changeSize"
    />
</template>

10.4 添加管理员

10.4.1 封装添加管理员接口

// src/api/admin.ts
import request from '@/utils/request'

// interface IAdminLoginParams {
//   adminname: string
//   password: string
// }

type TAdminLoginParams = {
  adminname: string
  password: string
}

export function adminLogin (params: TAdminLoginParams) {
  return request.post('/admin/login', params)
}

export function getAdminList () {
  return request.get('/admin/list')
}

export function deleteAdmin (params: { adminid: string }) {
  return request.post('/admin/delete', params)
}

interface IAdminAddParams {
  adminname: string
  password: string
  role: number
  checkedKeys: any  // 因为开放,索引设置为any
}
export function addAdmin (params: IAdminAddParams) {
  return request.post('/admin/add', params)
}

10.4.2 封装获取树形控件数据方法

// src/utils/get-routes.ts

import type { MyRouteRecordRaw } from "@/router";

interface Tree {
  id: string
  label: string
  children?: Tree[]
}

export function getRoutes (menuList: MyRouteRecordRaw[]) {
  const arr: Tree[] = []
  menuList.forEach((item) => {
    let obj: Tree = {
      id: '',
      label: ''
    }
    if (item.meta) {
      if (item.children) { // 动态那些数据
        obj = {
          id: item.path,
          label: String(item.meta.title),
          children: getRoutes(item.children)
        }
      } else {
        obj = {
          id: item.path,
          label: String(item.meta.title)
        }
      }
    } else {
      if (item.children) { // 系统首页
        obj = {
          id: item.children[0].path,
          label: String(item.children[0].meta!.title)
        }
      }
    }
    // arr.push(obj)
    obj.id !== '' && arr.push(obj)
  });
  return arr
}

// getRoutes(menuList)

10.3.4 添加管理员

<!-- src/views/account/Admin.vue -->
<script lang="ts" setup>
  import { ref, onMounted, computed } from 'vue';
  import { getAdminList, deleteAdmin, addAdmin } from '@/api/admin'
  import { constantRoutes, asyncRoutes } from '@/router'
  import { getRoutes } from '@/utils/get-routes'
  interface IAdminInfo {
    adminid: string
    adminname: string
    role: number
  }

  // 将展示数据adminList 变为了 计算属性的数据
  // 将依据原始数据 originalList 以及 页码currentPage  和每页显示个数pageSize 一起计算出来显示的数据adminList
  // const adminList = ref<IAdminInfo[]>([])
  const currentPage = ref(1)
  const pageSize = ref(10)
  const originalList = ref<IAdminInfo[]>([])
  const changeCurrentPage = (val: number) => { // 页码改变函数
    currentPage.value = val
  }
  const changeSize = (val: number) => { // 每页显示个数改变的函数
    pageSize.value = val
  }

  const adminList = computed(() => {
    // 截取数组的某一些项
    // 深拷贝数据作为备份
    // const arr = JSON.parse(JSON.stringify(originalList.value))
    // const newArr = arr.splice((currentPage.value - 1) * pageSize.value, pageSize.value)
    const newArr = originalList.value.filter((item, index) => {
      return index >= (currentPage.value - 1) * pageSize.value && index < (currentPage.value - 1) * pageSize.value + pageSize.value
    })
    // console.log(originalList.value)
    return newArr
  })
  const getAdminListData = () => {
    getAdminList().then(res => {
      // console.log('admin', res.data)
      originalList.value = res.data.data
    })
  }
  onMounted(() => {
    getAdminListData()
  })

  const removeItem = (adminid: string) => {
    deleteAdmin({ adminid }).then(() => {
      getAdminListData()
    })
  }

  // 添加管理员
  const drawer = ref(false)
  const adminname = ref('')
  const password = ref('')
  const role = ref(1)
  const checkedKeys = ref<string[]>([])
  const menuList = [...constantRoutes, ...asyncRoutes]
  const treeData = getRoutes(menuList)
  const onTreeCheck = (_: any, obj: { halfCheckedKeys: string[]; checkedKeys: string[]
  }) => {
    checkedKeys.value = [...obj.halfCheckedKeys, ...obj.checkedKeys].sort()
  }
  const addAdminFn = () => {
    addAdmin({
      adminname: adminname.value,
      password: password.value,
      role: role.value,
      checkedKeys: checkedKeys.value
    }).then(() => {
      // 重置表单状态
      adminname.value = ''
      password.value = ''
      role.value = 1
      checkedKeys.value = []
      // 关闭抽屉效果
      drawer.value = false
      getAdminListData()
    })
  }
  const closeDrawer = () => {
    // 重置表单状态
    adminname.value = ''
    password.value = ''
    role.value = 1
    checkedKeys.value = []
    // 关闭抽屉效果
    drawer.value = false
  }
</script>

<template>
  <el-button type="primary" @click="drawer=true">添加管理员</el-button>
  <el-table :data="adminList" style="width: 100%">
    <el-table-column label="序号">
      <template #default="scope">
        <!-- scope.$index 代表索引值 -->
        <span>{{ (currentPage - 1) * pageSize + scope.$index + 1 }}</span>
      </template>
    </el-table-column>
    <el-table-column prop="adminname" label="管理员账户" width="180" />
    <el-table-column prop="role" label="角色" width="180" >
      <template #default="scope">
        <!-- scope.row 代表一条数据 -->
        <el-tag v-if="scope.row.role === 2" class="ml-2" type="success">超级管理员</el-tag>
        <el-tag v-else class="ml-2" type="info">管理员</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template #default="scope">
        <el-button size="small"
          >编辑</el-button
        >
        <el-popconfirm title="确定删除吗?" @confirm="removeItem(scope.row.adminid)">
          <template #reference>
            <el-button
              size="small"
              type="danger"
              >删除</el-button
            >
          </template>
        </el-popconfirm>
        
      </template>
    </el-table-column>
  </el-table>
  <el-pagination 
    background 
    layout="sizes, prev, pager, next" 
    :page-sizes="[5, 10, 20, 30]"
    :total="originalList.length" 
    @current-change="changeCurrentPage"
    @size-change="changeSize"
    />
    <!-- 添加管理员  抽屉效果 -->
    <el-drawer v-model="drawer" title="I am the title" :with-header="false" @close="closeDrawer">
      <h1>添加管理员</h1>
      <el-input v-model="adminname" placeholder="管理员账户" clearable/>
      <el-input v-model="password" placeholder="密码" clearable />
      <el-select v-model="role" placeholder="管理员角色" >
        <el-option
          label="管理员"
          :value="1"
        />
        <el-option
          label="超级管理员"
          :value="2"
        />
      </el-select>
      <el-tree
        ref="treeRef"
        :data="treeData"
        show-checkbox
        node-key="id"
        @check="onTreeCheck"
      />
      <el-button type="primary" @click="addAdminFn">添加</el-button>
    </el-drawer>
</template>

10.5 编辑管理员

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Py3yNhxo-1672888918417)(assets/image-20221230153508095.png)]

// src/api/admin.ts
import request from '@/utils/request'

// interface IAdminLoginParams {
//   adminname: string
//   password: string
// }

type TAdminLoginParams = {
  adminname: string
  password: string
}

export function adminLogin (params: TAdminLoginParams) {
  return request.post('/admin/login', params)
}

export function getAdminList () {
  return request.get('/admin/list')
}

export function deleteAdmin (params: { adminid: string }) {
  return request.post('/admin/delete', params)
}

interface IAdminAddParams {
  adminname: string
  password: string
  role: number
  checkedKeys: any  // 因为开放,索引设置为any
}
export function addAdmin (params: IAdminAddParams) {
  return request.post('/admin/add', params)
}

interface IAdminUpdateParams {
  adminname: string
  role: number
  checkedKeys: any  // 因为开放,索引设置为any
}
export function updateAdmin (params: IAdminUpdateParams) {
  return request.post('/admin/update', params)
}t
<!-- src/views/account/Admin.vue -->
<script lang="ts" setup>
  import { ref, onMounted, computed } from 'vue';
  import { getAdminList, deleteAdmin, addAdmin, updateAdmin } from '@/api/admin'
  import { constantRoutes, asyncRoutes } from '@/router'
  import { ElTree } from 'element-plus'
  import { getRoutes } from '@/utils/get-routes'
  interface IAdminInfo {
    adminid: string
    adminname: string
    role: number
  }

  // 将展示数据adminList 变为了 计算属性的数据
  // 将依据原始数据 originalList 以及 页码currentPage  和每页显示个数pageSize 一起计算出来显示的数据adminList
  // const adminList = ref<IAdminInfo[]>([])
  const currentPage = ref(1)
  const pageSize = ref(10)
  const originalList = ref<IAdminInfo[]>([])
  const changeCurrentPage = (val: number) => { // 页码改变函数
    currentPage.value = val
  }
  const changeSize = (val: number) => { // 每页显示个数改变的函数
    pageSize.value = val
  }

  const adminList = computed(() => {
    // 截取数组的某一些项
    // 深拷贝数据作为备份
    // const arr = JSON.parse(JSON.stringify(originalList.value))
    // const newArr = arr.splice((currentPage.value - 1) * pageSize.value, pageSize.value)
    const newArr = originalList.value.filter((item, index) => {
      return index >= (currentPage.value - 1) * pageSize.value && index < (currentPage.value - 1) * pageSize.value + pageSize.value
    })
    // console.log(originalList.value)
    return newArr
  })
  const getAdminListData = () => {
    getAdminList().then(res => {
      console.log('admin', res.data)
      originalList.value = res.data.data
    })
  }
  onMounted(() => {
    getAdminListData()
  })

  const removeItem = (adminid: string) => {
    deleteAdmin({ adminid }).then(() => {
      getAdminListData()
    })
  }

  // 添加管理员
  const drawer = ref(false)
  const adminname = ref('')
  const password = ref('')
  const role = ref(1)
  const checkedKeys = ref<string[]>([])
  const menuList = [...constantRoutes, ...asyncRoutes]
  const treeData = getRoutes(menuList)
  const onTreeCheck = (_: any, obj: { halfCheckedKeys: string[]; checkedKeys: string[]
  }) => {
    checkedKeys.value = [...obj.halfCheckedKeys, ...obj.checkedKeys].sort()
  }
  const addAdminFn = () => {
    addAdmin({
      adminname: adminname.value,
      password: password.value,
      role: role.value,
      checkedKeys: checkedKeys.value
    }).then(() => {
      // 重置表单状态
      adminname.value = ''
      password.value = ''
      role.value = 1
      checkedKeys.value = []
      // 关闭抽屉效果
      drawer.value = false
      getAdminListData()
    })
  }
  const closeDrawer = () => {
    // 重置表单状态
    adminname.value = ''
    role.value = 1
    checkedKeys.value = []
    // 关闭抽屉效果
    drawer.value = false
  }

  // 修改管理员
  const treeUpdateRef = ref<InstanceType<typeof ElTree>>()
  const dialog = ref(false)
  const updateAdminInfo = (item: any) => {
    dialog.value = true
    adminname.value = item.adminname
    role.value = item.role
    // console.log(item)
    // checkedKeys.value = item.checkedKeys
    // 添加延时器确保DOM加载完毕, 否则显示 setCheckedKeys 未定义
    setTimeout(() => {
      treeUpdateRef.value!.setCheckedKeys(item.checkedKeys, false)
    }, 0)
  }
  const updateAdminInfoFn = () => {
    // console.log('111', {
    //   adminname: adminname.value,
    //   role: role.value,
    //   checkedKeys: checkedKeys.value
    // })
    updateAdmin({
      adminname: adminname.value,
      role: role.value,
      checkedKeys: checkedKeys.value
    }).then((res) => {
      // console.log('22222', res.data)
      // 重置表单状态
      adminname.value = ''
      role.value = 1
      checkedKeys.value = []
      // 关闭抽屉效果
      dialog.value = false
      getAdminListData()
    })
  }
</script>

<template>
  <el-button type="primary" @click="drawer=true">添加管理员</el-button>
  <el-table :data="adminList" style="width: 100%">
    <el-table-column label="序号">
      <template #default="scope">
        <!-- scope.$index 代表索引值 -->
        <span>{{ (currentPage - 1) * pageSize + scope.$index + 1 }}</span>
      </template>
    </el-table-column>
    <el-table-column prop="adminname" label="管理员账户" width="180" />
    <el-table-column prop="role" label="角色" width="180" >
      <template #default="scope">
        <!-- scope.row 代表一条数据 -->
        <el-tag v-if="scope.row.role === 2" class="ml-2" type="success">超级管理员</el-tag>
        <el-tag v-else class="ml-2" type="info">管理员</el-tag>
      </template>
    </el-table-column>
    <el-table-column label="操作">
      <template #default="scope">
        <el-button size="small" @click="updateAdminInfo(scope.row)">编辑</el-button>
        <el-popconfirm title="确定删除吗?" @confirm="removeItem(scope.row.adminid)">
          <template #reference>
            <el-button
              size="small"
              type="danger"
              >删除</el-button
            >
          </template>
        </el-popconfirm>
        
      </template>
    </el-table-column>
  </el-table>
  <el-pagination 
    background 
    layout="sizes, prev, pager, next" 
    :page-sizes="[5, 10, 20, 30]"
    :total="originalList.length" 
    @current-change="changeCurrentPage"
    @size-change="changeSize"
    />
    <!-- 添加管理员  抽屉效果 -->
    <el-drawer v-model="drawer" title="I am the title" :with-header="false" @close="closeDrawer">
      <h1>添加管理员</h1>
      <el-input v-model="adminname" placeholder="管理员账户" clearable/>
      <el-input v-model="password" placeholder="密码" clearable />
      <el-select v-model="role" placeholder="管理员角色" >
        <el-option
          label="管理员"
          :value="1"
        />
        <el-option
          label="超级管理员"
          :value="2"
        />
      </el-select>
      <el-tree
        ref="treeAddRef"
        :data="treeData"
        show-checkbox
        node-key="id"
        @check="onTreeCheck"
      />
      <el-button type="primary" @click="addAdminFn">添加</el-button>
    </el-drawer>
    <!-- 编辑管理员 -->
    <el-dialog v-model="dialog" title="编辑管理员信息">
      <el-input v-model="adminname" readonly placeholder="管理员账户" clearable/>
      <el-select v-model="role" placeholder="管理员角色" >
        <el-option
          label="管理员"
          :value="1"
        />
        <el-option
          label="超级管理员"
          :value="2"
        />
      </el-select>
      <el-tree
        ref="treeUpdateRef"
        :data="treeData"
        show-checkbox
        node-key="id"
        @check="onTreeCheck"
      />
      <template #footer>
        <span class="dialog-footer">
          <el-button type="primary" @click="updateAdminInfoFn">
            修改
          </el-button>
        </span>
      </template>
    </el-dialog>
</template>

11.左侧菜单栏动态渲染

11.1 处理左侧菜单数据

此算法建议直接拿来使用

// src/utils/get-menu-list.ts
import type { MyRouteRecordRaw } from "@/router";

export function getMenuList (menus: MyRouteRecordRaw[], checkedkeys: string[]) {
  if (checkedkeys.length === 0) { // 如果没有设置权限,那么即将拥有所有的权限
    checkedkeys = ['/home', '/banner', '/banner/list', '/banner/list/home', '/banner/list/kind', '/banner/add', '/pro', '/pro/list', '/pro/search', '/account', '/account/user', '/account/admin']
  }
  let result = getResult(menus)

  function getResult(menus: MyRouteRecordRaw[]) {
    let arr: MyRouteRecordRaw[]= []
    menus.forEach(item => {
      if (item.meta) {
        if (checkedkeys.includes(item.path)) {
          arr.push({...item})
          
          if (item.children) {
            arr[arr.length - 1].children = getResult(item.children)
          }
        }
      } else { // 系统首页
        arr.push(item)
      }
    });
    return arr 
  }
  return result
} 

11.2 动态渲染左侧菜单

<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
  import { ref } from 'vue'
  import SidebarItem from './SidebarItem.vue'
  import Logo from './../logo/index.vue'
  import { getMenuList } from '@/utils/get-menu-list'

  // 整合路由
  import { constantRoutes, asyncRoutes } from '@/router'
  import { useRoute } from 'vue-router';
  // /banner,/banner/add,/banner/list,/banner/list/home,/banner/list/kind,/home
  const checkedkeys = localStorage.getItem('adminname') !== 'admin' ? localStorage.getItem('checkedkeys')!.split(',') : []
  console.log('checkedkeys', checkedkeys)
  // const menuList = constantRoutes.concat(asyncRoutes) // 所有数据
  const menuList = getMenuList(constantRoutes.concat(asyncRoutes), checkedkeys) // 真实数据
  // console.log(menuList)

  // 获取路由地址
  // 选项式API  this.$route
  const route = useRoute()
  // console.log(route) // route.path.value

  const collapse = ref(false)
</script>
<template>
  <el-aside :width="collapse ? '50px' : '200px'">
    <!-- <div class="logo"></div> -->
    <Logo :collapse="collapse"/>
    <!-- 菜单组件
    https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
    -->
    <!-- <div>左侧菜单</div> -->
    <el-menu
      :collapse="collapse"
      :unique-opened="true"
      :default-active="route.path"
      background-color="#001529"
      text-color="#fff"
      :collapse-transition="false"
    >
      <!-- 路由中的每一个选项都需要单独去渲染菜单项
      有的不需要出现在左侧的菜单栏
      有的只有一个菜单项
      有的有二级菜单和三级菜单
      建议把每个菜单项单独封装成一个组件  SidebarItem
      登录  系统首页   产品管理   账户管理  轮播图管理
      将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
      -->
      <SidebarItem 
        v-for="item of menuList"
        :key="item.path"
        :item = "item"
      ></SidebarItem>
    </el-menu>
    <div class="changeIcon" :style="{ width: collapse ? '50px' : '200px'}">
      <el-icon color="#fff" v-if="collapse" @click="collapse = !collapse"><ArrowRight /></el-icon>
      <el-icon color="#fff" v-else @click="collapse=!collapse"><ArrowLeft /></el-icon>
    </div>
  </el-aside>
</template>

<style lang="scss">
.changeIcon {
  position: absolute;
  bottom: 10px;
  display: flex;
  justify-content: center;
}
</style>

ckbox
node-key=“id”
@check=“onTreeCheck”
/>
<template #footer>

<el-button type=“primary” @click=“updateAdminInfoFn”>
修改







# 11.左侧菜单栏动态渲染

## 11.1 处理左侧菜单数据

此算法建议直接拿来使用

```ts
// src/utils/get-menu-list.ts
import type { MyRouteRecordRaw } from "@/router";

export function getMenuList (menus: MyRouteRecordRaw[], checkedkeys: string[]) {
  if (checkedkeys.length === 0) { // 如果没有设置权限,那么即将拥有所有的权限
    checkedkeys = ['/home', '/banner', '/banner/list', '/banner/list/home', '/banner/list/kind', '/banner/add', '/pro', '/pro/list', '/pro/search', '/account', '/account/user', '/account/admin']
  }
  let result = getResult(menus)

  function getResult(menus: MyRouteRecordRaw[]) {
    let arr: MyRouteRecordRaw[]= []
    menus.forEach(item => {
      if (item.meta) {
        if (checkedkeys.includes(item.path)) {
          arr.push({...item})
          
          if (item.children) {
            arr[arr.length - 1].children = getResult(item.children)
          }
        }
      } else { // 系统首页
        arr.push(item)
      }
    });
    return arr 
  }
  return result
} 

11.2 动态渲染左侧菜单

<!-- src/layout/components/Sidebar/index.vue -->
<script lang="ts" setup>
  import { ref } from 'vue'
  import SidebarItem from './SidebarItem.vue'
  import Logo from './../logo/index.vue'
  import { getMenuList } from '@/utils/get-menu-list'

  // 整合路由
  import { constantRoutes, asyncRoutes } from '@/router'
  import { useRoute } from 'vue-router';
  // /banner,/banner/add,/banner/list,/banner/list/home,/banner/list/kind,/home
  const checkedkeys = localStorage.getItem('adminname') !== 'admin' ? localStorage.getItem('checkedkeys')!.split(',') : []
  console.log('checkedkeys', checkedkeys)
  // const menuList = constantRoutes.concat(asyncRoutes) // 所有数据
  const menuList = getMenuList(constantRoutes.concat(asyncRoutes), checkedkeys) // 真实数据
  // console.log(menuList)

  // 获取路由地址
  // 选项式API  this.$route
  const route = useRoute()
  // console.log(route) // route.path.value

  const collapse = ref(false)
</script>
<template>
  <el-aside :width="collapse ? '50px' : '200px'">
    <!-- <div class="logo"></div> -->
    <Logo :collapse="collapse"/>
    <!-- 菜单组件
    https://element-plus.gitee.io/zh-CN/component/menu.html#%E4%BE%A7%E6%A0%8F
    -->
    <!-- <div>左侧菜单</div> -->
    <el-menu
      :collapse="collapse"
      :unique-opened="true"
      :default-active="route.path"
      background-color="#001529"
      text-color="#fff"
      :collapse-transition="false"
    >
      <!-- 路由中的每一个选项都需要单独去渲染菜单项
      有的不需要出现在左侧的菜单栏
      有的只有一个菜单项
      有的有二级菜单和三级菜单
      建议把每个菜单项单独封装成一个组件  SidebarItem
      登录  系统首页   产品管理   账户管理  轮播图管理
      将每一项数据 item 传输到子组件,子组件接收数据,并且渲染
      -->
      <SidebarItem 
        v-for="item of menuList"
        :key="item.path"
        :item = "item"
      ></SidebarItem>
    </el-menu>
    <div class="changeIcon" :style="{ width: collapse ? '50px' : '200px'}">
      <el-icon color="#fff" v-if="collapse" @click="collapse = !collapse"><ArrowRight /></el-icon>
      <el-icon color="#fff" v-else @click="collapse=!collapse"><ArrowLeft /></el-icon>
    </div>
  </el-aside>
</template>

<style lang="scss">
.changeIcon {
  position: absolute;
  bottom: 10px;
  display: flex;
  justify-content: center;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/141426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sentinel + Redis + Mysql + RabbitMQ 秒杀功能设计及后端代码实现

文章目录前言数据一致性高性能动静分离静态资源缓存流控缓存数据库消息队列RabbitMQ的优点高并发分布式锁后端代码实现中间件表结构添加依赖公共常量实体类Redission配置定时任务Controller下单接口付款接口接收通道消息完整代码前言 在开发秒杀系统功能的时候&#xff0c;需要…

MyBatis讲解,批量添加

一、批量添加 1.书写BookMapper 1.1先在navicat的新建查询里书写条件查询的sql语句 条件查询的sql语句 insert into book(book_name) values(三体); 1.2将sql语句复制到BookMapper里 用到foreach标签&#xff1b; collection&#xff1a;可以放数组&#xff0c;也可以放list集…

数据结构与算法-算法分析(2)

算法和算法分析 对于同一个问题可能由不同的算法。究竟来如何评价这些算法 一个算法首先要具备正确性&#xff0c;健壮性&#xff0c;可读性和有穷性&#xff0c;然后我们再比较其算法的效率&#xff0c;来评判算法的优劣。 主要从时间和空间上的效率进行评价算法&#xff0c…

对JSON的理解

什么是JSON? JSON全名是JavaSpript Object Notation。 JSON是轻量级的文本数据交换格式。 JSON是存储和交换文本信息的语法&#xff0c;类似XML,比XML更小&#xff0c;更快&#xff0c;更易解析。 JSON可以将Java对象转换为特殊格式的字符串&#xff08;JSON串&#xff09…

矿井水深度除总氮

工艺原理 选择性去除硝酸盐氮 项目背景 近年来高矿化度和含特殊组分矿井水逐年增多&#xff0c;以及环保政策的趋严给矿井水处理带来新挑战。 随着《水污染防治行动计划》 &#xff08;水十 条&#xff09;的深入开展和新的煤矿环境影响评价制度的执行&#xff0c;山西、陕…

CSS权威指南(三)特指度

文章目录1.特指度的定义2.继承3.层叠1.特指度的定义 ​ 我们都知道&#xff0c;当同一元素被设置了两个相同属性的时候&#xff0c;只会生效其中的一个属性值。至于到底生效哪一个属性值&#xff0c;自然是有一套计算规则的。在CSS中&#xff0c;选择符的特指度由选择符本身的…

模板学堂丨数据大屏配色设计指南

DataEase开源数据可视化分析平台于2022年6月正式发布模板市场&#xff08;https://dataease.io/templates/&#xff09;。模板市场旨在为DataEase用户提供专业、美观、拿来即用的仪表板模板&#xff0c;方便用户根据自身的业务需求和使用场景选择对应的仪表板模板&#xff0c;并…

[MySQL]-双主+keepalived实现高可用

[MySQL]-双主keepalived实现高可用 梁森 | 2023年1月 本文旨在记录学习主从时的拓展内容&#xff0c;怎么借助keepalived实现简单的高可用。 一、环境介绍 1.1 keepalived keepalived的作用是检测服务器的状态&#xff0c;若某一台服务器宕机&#xff0c;会通过VIP&#xff08;…

【人工智能】基于五笔字型规范和人工神经网络的简中汉字识别【六】

识别网络训练与测试 一、配置文件的修改二、修改训练模型参数三、训练自己的识别模型四、测试识别模型一、配置文件的修改 前期工作铺垫了这么久,终于可以正式进正题了。 训练目标检测模型需要修改几个文件,我们这里为了不破坏原本项目结构,采用在相同目录下复制一份不同名文…

linux下后台运行python脚本

这几天工作中遇到一个问题&#xff0c;后台运行python脚本&#xff0c;存储输出日志到linux系统中&#xff0c;因为在脚本中用了大量的print&#xff0c;导致输出很多信息&#xff0c;服务器内存占满了光是log就有120G&#xff0c;因此写下这篇博客&#xff0c;记录后台运行pyt…

数据防篡改之主机加固篇

​ 随着物联网技术和互联网技术的日益发展&#xff0c;勒索病毒、工控安全、产线作业都面领着极大的威胁。智慧互联正在成为各个行业未来的发展方向&#xff0c;智慧互联包括物联网、万物互联&#xff0c;机器与机器&#xff0c;工业控制体系&#xff0c;信息化&#xff0c;也…

Redis之乱七八糟

redis过期时间 注意事项 DEL/SET/GETSET等命令会清除过期时间   在使用 DEL、SET、GETSET 等会覆盖key对应value的命令操作一个设置了过期时间的key的时候&#xff0c;会导致对应的key的过期时间被清除。 INCR/LPUSH/HSET等命令则不会清除过期时间   而在使用 INCR/LPUSH…

【Git】 常用命令速查

一、 Git 常用命令速查git branch 查看本地所有分支git status 查看当前状态 git commit 提交 git branch -a 查看所有的分支git branch -r 查看远程所有分支git commit -am "init" 提交并且加注释 git remote add origin git192.168.1.119:ndshowgit push origin ma…

人工智能 - 朴素贝叶斯、案例:文本情感分析

朴素贝叶斯&#xff1a;用概率去预测 1、朴素贝叶斯介绍 朴素&#xff1a;指的是&#xff0c;特征之间相互独立 拉普拉斯平滑系数&#xff0c;每个种类都加k&#xff0c;避免条件概率出现0 区分情书与作业的例子&#xff0c;用关键词&#xff1a; 是情书的概率更高&#xf…

基于Transformer的多变量风电功率预测TF2

Transformer目前大火&#xff0c;作为一个合格的算法搬运工自然要跟上潮流&#xff0c;本文基于tensorflow2框架&#xff0c;构建transformer模型&#xff0c;并将其用于多变量的风电功率负荷预测。 实验结果表明&#xff0c;相比与传统的LSTM&#xff0c;该方法精度更高&…

干货 | 背熟这些 Docker 命令,面试再也不怕啦~

我们下载 Docker 镜像的时候&#xff0c;默认会访问 Docker 网站&#xff0c;而 Docker 网站是在国外部署的&#xff0c;距离比较远下载速度特别慢。我们可以通过设置加速器的方式来加速 Docker 镜像下载的速度。下面将描述一下使用加速器的步骤&#xff1a;1.我们这里选择的是…

ORB-SLAM2 --- MapPoint::Replace函数

目录 1.函数作用 2.code 3.函数解析 1.函数作用 替换地图点&#xff0c;更新观测关系。 2.code void MapPoint::Replace(MapPoint* pMP) {// 同一个地图点则跳过if(pMP->mnIdthis->mnId)return;//要替换当前地图点,有两个工作:// 1. 将当前地图点的观测数据等其他数…

数据报告:[数字健康]如何引发美国医疗深度变革

本文由前嗅数据研究院出品 在美国&#xff0c;全球疫情的不断发展扩大&#xff0c;促进了其医疗行业的变革与创新&#xff0c;以“Digital Health”&#xff08;数字健康&#xff09;为关键词的医疗领域正在发⽣⾰命性的变化。本文着重介绍“Digital Health”的特点及其在各领域…

【博学谷学习记录】超强总结,用心分享|kafka如何保证数据不丢失

文章目录数据在Kafka中的流转阶段一:生产者如何保证数据不丢失ACK机制阶段二:Broker端如何保证数据不丢失磁盘副本阶段三:消费者如何保证数据不丢失消费者提交偏移量数据在Kafka中的流转 阶段一:生产者如何保证数据不丢失 ACK机制 生产者将数据生产到Broker后,Broker需要给一个…

学习python之——python入门

欢迎来到 Python 入门的学习之旅&#xff01; Python 是一种高级编程语言&#xff0c;它是一种解释型语言&#xff0c;有着丰富的库和大量的第三方模块&#xff0c;能够用于许多不同的编程任务。无论你是想要学习 Python 进行 Web 开发&#xff0c;还是想用它来进行数据分析和…