当前位置:   article > 正文

vue3.2后台管理系统搭建学习笔记_module '"d:/works/wuwei-admin-vue/src/modules/base

module '"d:/works/wuwei-admin-vue/src/modules/base/layout/components/amenu.v

1、创建项目更改版本

1-1、创建项目

vue create vue3-admin

1-2、更改vue版本

更改前

更改后

 

2、开发项目

1、按需引入element-plus

1、安装

npm install element-plus --save

2、按需导入(自动导入)

cnpm install -D unplugin-vue-components unplugin-auto-import

3、Webpack配置(vue.config.js)

  1. const AutoImport = require('unplugin-auto-import/webpack')
  2. const Components = require('unplugin-vue-components/webpack')
  3. const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
  4. module.exports = {
  5. intOnSave:false,
  6. devServer:{
  7. open:true,
  8. port:9000,
  9. },
  10. configureWebpack: {
  11. plugins: [
  12. AutoImport({
  13. resolvers: [ElementPlusResolver()],
  14. }),
  15. Components({
  16. resolvers: [ElementPlusResolver()],
  17. }),
  18. ]
  19. },
  20. }

4、使用

tips:在main.js中引入样式(避免弹出框等组件样式错乱)

import 'element-plus/dist/index.css'

2、使用element-plus Icon图标 

1、安装

cnpm install @element-plus/icons-vue

2、注册全局组件

  1. //main.js
  2. import * as ELIcons from '@element-plus/icons-vue'
  3. for (const iconName in ELIcons) {
  4. app.component(iconName, ELIcons[iconName])
  5. }

 3、使用(左侧菜单icon)

  1. <template #title>
  2. <el-icon>
  3. <component :is="iconList[index]"></component>
  4. </el-icon>
  5. <span>{{ item.authName }}</span>
  6. </template>

3、样式初始化,使用scss变量

1、导入初始样式,导出变量值

 2、在main.js中引入

import '@/styles/index.scss'

3、配置webpack

  1. css: {
  2. loaderOptions: {
  3. sass: {
  4. // 8版本用prependData:
  5. prependData: `
  6. @import "@/styles/variables.scss"; // scss文件地址
  7. @import "@/styles/mixin.scss"; // scss文件地址
  8. `
  9. }
  10. }
  11. }

4、使用svg,全局注册

1、svg图标导入到项目

 2、在components文件夹下创建SvgIcon组件

  1. <template>
  2. <svg class="svg-icon" aria-hidden="true">
  3. <use :xlink:href="iconName"></use>
  4. </svg>
  5. </template>
  6. <script setup>
  7. import { defineProps, computed } from 'vue'
  8. const props = defineProps({
  9. icon: {
  10. type: String,
  11. required: true
  12. }
  13. })
  14. const iconName = computed(() => {
  15. return `#icon-${props.icon}`
  16. })
  17. </script>
  18. <style lang="scss" scoped>
  19. .svg-icon {
  20. width: 1em;
  21. height: 1em;
  22. vertical-align: -0.15em;
  23. fill: currentColor;
  24. overflow: hidden;
  25. }
  26. </style>

4、在icons 文件夹下新建index.js

  1. import SvgIcon from '@/components/SvgIcon'
  2. const svgRequired = require.context('./svg', false, /\.svg$/)
  3. svgRequired.keys().forEach((item) => svgRequired(item))
  4. export default (app) => {
  5. app.component('svg-icon', SvgIcon)
  6. }

5、在main.js中引入

  1. import SvgIcon from '@/icons'
  2. const app=createApp(App)
  3. SvgIcon(app)

6、安装依赖

cnpm i --save-dev svg-sprite-loader@6.0.9

7、配置webpack

  1. const path = require('path')
  2. function resolve(dir) {
  3. return path.join(__dirname, dir)
  4. }
  5. const webpack = require('webpack')
  6. module.exports = {
  7. chainWebpack(config) {
  8. // 设置 svg-sprite-loader
  9. // config 为 webpack 配置对象
  10. // config.module 表示创建一个具名规则,以后用来修改规则
  11. config.module
  12. // 规则
  13. .rule('svg')
  14. // 忽略
  15. .exclude.add(resolve('src/icons'))
  16. // 结束
  17. .end()
  18. // config.module 表示创建一个具名规则,以后用来修改规则
  19. config.module
  20. // 规则
  21. .rule('icons')
  22. // 正则,解析 .svg 格式文件
  23. .test(/\.svg$/)
  24. // 解析的文件
  25. .include.add(resolve('src/icons'))
  26. // 结束
  27. .end()
  28. // 新增了一个解析的loader
  29. .use('svg-sprite-loader')
  30. // 具体的loader
  31. .loader('svg-sprite-loader')
  32. // loader 的配置
  33. .options({
  34. symbolId: 'icon-[name]'
  35. })
  36. // 结束
  37. .end()
  38. config
  39. .plugin('ignore')
  40. .use(
  41. new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/)
  42. )
  43. config.module
  44. .rule('icons')
  45. .test(/\.svg$/)
  46. .include.add(resolve('src/icons'))
  47. .end()
  48. .use('svg-sprite-loader')
  49. .loader('svg-sprite-loader')
  50. .options({
  51. symbolId: 'icon-[name]'
  52. })
  53. .end()
  54. },
  55. }

8、使用

<svg-icon icon="user" class="svg-container"></svg-icon>

5、请求封装

1、安装axios 

cnpm i axios --save

 2、新建utils文件夹,新建auth.js设置过期时间

  1. const TOKEN_TIME = 'tokenTime'
  2. const TOKEN_TIME_VALUE = 2 * 60 * 60 * 1000
  3. // 登录时设置时间
  4. export const setTokenTime = () => {
  5. localStorage.setItem(TOKEN_TIME, Date.now())
  6. }
  7. // 获取
  8. export const getTokenTime = () => {
  9. return localStorage.getItem(TOKEN_TIME)
  10. }
  11. // 是否已经过期
  12. export const diffTokenTime = () => {
  13. const currentTime = Date.now()
  14. const tokenTime = getTokenTime()
  15. return currentTime - tokenTime > TOKEN_TIME_VALUE
  16. }

3、新建api文件夹,新建request.js文件

  1. import axios from 'axios'
  2. import { ElMessage } from 'element-plus'
  3. import { diffTokenTime } from '@/utils/auth'
  4. import store from '@/store'
  5. const service = axios.create({
  6. baseURL: process.env.VUE_APP_BASE_API,
  7. timeout: 5000
  8. })
  9. service.interceptors.request.use(
  10. (config) => {
  11. if (localStorage.getItem('token')) {
  12. if (diffTokenTime()) {
  13. store.dispatch('app/logout')
  14. return Promise.reject(new Error('token 失效了'))
  15. }
  16. }
  17. config.headers.Authorization = localStorage.getItem('token')
  18. return config
  19. },
  20. (error) => {
  21. return Promise.reject(new Error(error))
  22. }
  23. )
  24. service.interceptors.response.use(
  25. (response) => {
  26. const { data, meta } = response.data
  27. if (meta.status === 200 || meta.status === 201) {
  28. return data
  29. } else {
  30. ElMessage.error(meta.msg)
  31. return Promise.reject(new Error(meta.msg))
  32. }
  33. },
  34. (error) => {
  35. console.log(error.response)
  36. error.response && ElMessage.error(error.response.data)
  37. return Promise.reject(new Error(error.response.data))
  38. }
  39. )
  40. export default service

4、本地跨域配置

  1. devServer: {
  2. https: false,
  3. hotOnly: false,
  4. open:true,
  5. port:9000,
  6. proxy: {
  7. '/api': {
  8. target: 'https://*******/api/private/v1/',
  9. changeOrigin: true,
  10. pathRewrite: {
  11. '^/api': ''
  12. }
  13. }
  14. }
  15. },

5、全局坏境变量定义

 新建 .env.development、.env.production文件

  1. ENV = 'development'
  2. VUE_APP_BASE_API = '/api'
  1. ENV = 'production'
  2. VUE_APP_BASE_API = '/prod-api'

6、全局导航守卫 

  1. import { createRouter, createWebHashHistory } from 'vue-router'
  2. const routes = [
  3. {
  4. path: '/login',
  5. name: 'Login',
  6. component: () => import('../views/login/index.vue')
  7. },
  8. {
  9. path: '/',
  10. name: '/',
  11. component: () => import('../layout'),
  12. redirect: '/users',
  13. children: [
  14. {
  15. path: 'users',
  16. name: 'users',
  17. component: () => import('@/views/users/index.vue')
  18. },
  19. {
  20. path: 'categories',
  21. name: 'categories',
  22. component: () => import('@/views/categories/index.vue')
  23. },
  24. {
  25. path: 'goods',
  26. name: 'goods',
  27. component: () => import('@/views/goods/index.vue')
  28. },
  29. {
  30. path: 'orders',
  31. name: 'orders',
  32. component: () => import('@/views/orders/index.vue')
  33. },
  34. {
  35. path: 'params',
  36. name: 'params',
  37. component: () => import('@/views/params/index.vue')
  38. },
  39. {
  40. path: 'reports',
  41. name: 'reports',
  42. component: () => import('@/views/reports/index.vue')
  43. },
  44. {
  45. path: 'rights',
  46. name: 'rights',
  47. component: () => import('@/views/rights/index.vue')
  48. },
  49. {
  50. path: 'roles',
  51. name: 'roles',
  52. component: () => import('@/views/roles/index.vue')
  53. }
  54. ]
  55. }
  56. ]
  57. const router = createRouter({
  58. history: createWebHashHistory(),
  59. routes
  60. })
  61. export default router

1、新建router/permission.js

  1. import router from './index'
  2. import store from '@/store'
  3. const whiteList = ['/login']
  4. router.beforeEach((to, from, next) => {
  5. if (store.getters.token) {
  6. if (to.path === '/login') {
  7. next('/')
  8. } else {
  9. next()
  10. }
  11. } else {
  12. if (whiteList.includes(to.path)) {
  13. next()
  14. } else {
  15. next('/login')
  16. }
  17. }
  18. })

2、main.js引入 

import '@/router/permission'

5、登录 

1、新增登录界面login/index.vue

  1. <template>
  2. <div class="login-container">
  3. <el-form ref="loginFormRef" :model="loginForm" class="login-form" :rules="rules">
  4. <div class="title-container">
  5. <h3 class="title">后台管理系统</h3>
  6. </div>
  7. <el-form-item prop="username">
  8. <svg-icon icon="user" class="svg-container"></svg-icon>
  9. <el-input v-model="loginForm.username"></el-input>
  10. </el-form-item>
  11. <el-form-item prop="password">
  12. <svg-icon icon="password" class="svg-container"></svg-icon>
  13. <el-input v-model="loginForm.password" :type="passwordType"></el-input>
  14. <svg-icon
  15. class="svg-container"
  16. :icon="passwordType === 'password' ? 'eye' : 'eye-open'"
  17. @click="changeType"
  18. ></svg-icon>
  19. </el-form-item>
  20. <el-button type="primary" class="login-button" @click="handleLogin">登录</el-button>
  21. </el-form>
  22. </div>
  23. </template>
  24. <script setup>
  25. import { ref,reactive} from 'vue'
  26. import { useStore } from 'vuex'
  27. const store = useStore()
  28. const loginForm = reactive({
  29. username: 'admin',
  30. password: '123456'
  31. })
  32. const rules = reactive({
  33. username: [
  34. { required: true, message: '请输入用户名', trigger: 'blur' },
  35. ],
  36. password: [
  37. {
  38. required: true,
  39. message: '请输入密码',
  40. trigger: 'blur'
  41. }
  42. ]
  43. })
  44. const loginFormRef = ref(null)
  45. const handleLogin = () => {
  46. loginFormRef.value.validate(async (valid) => {
  47. if (valid) {
  48. store.dispatch('app/login', loginForm)
  49. } else {
  50. console.log('error submit!!')
  51. return false
  52. }
  53. })
  54. }
  55. const passwordType = ref('password')
  56. const changeType = () => {
  57. if (passwordType.value === 'password') {
  58. passwordType.value = 'text'
  59. } else {
  60. passwordType.value = 'password'
  61. }
  62. }
  63. </script>
  64. <style lang="scss" scoped>
  65. $bg: #2d3a4b;
  66. $dark_gray: #889aa4;
  67. $light_gray: #eee;
  68. $cursor: #fff;
  69. .login-container {
  70. min-height: 100%;
  71. width: 100%;
  72. background-color: $bg;
  73. overflow: hidden;
  74. .login-form {
  75. position: relative;
  76. width: 520px;
  77. max-width: 100%;
  78. padding: 160px 35px 0;
  79. margin: 0 auto;
  80. overflow: hidden;
  81. :deep(.el-form-item) {
  82. border: 1px solid rgba(255, 255, 255, 0.1);
  83. background: rgba(0, 0, 0, 0.1);
  84. border-radius: 5px;
  85. color: #454545;
  86. height: 50px;
  87. .el-input{
  88. flex:1;
  89. .el-input__wrapper{
  90. background-color:transparent;
  91. box-shadow: none;
  92. .el-input__inner{
  93. color: #fff;
  94. }
  95. }
  96. }
  97. }
  98. .login-button {
  99. width: 100%;
  100. box-sizing: border-box;
  101. }
  102. }
  103. .tips {
  104. font-size: 16px;
  105. line-height: 28px;
  106. color: #fff;
  107. margin-bottom: 10px;
  108. span {
  109. &:first-of-type {
  110. margin-right: 16px;
  111. }
  112. }
  113. }
  114. .svg-container {
  115. padding: 6px 5px 6px 15px;
  116. color: $dark_gray;
  117. vertical-align: middle;
  118. display: inline-block;
  119. }
  120. .title-container {
  121. position: relative;
  122. .title {
  123. font-size: 26px;
  124. color: $light_gray;
  125. margin: 0px auto 40px auto;
  126. text-align: center;
  127. font-weight: bold;
  128. }
  129. :deep(.lang-select) {
  130. position: absolute;
  131. top: 4px;
  132. right: 0;
  133. background-color: white;
  134. font-size: 22px;
  135. padding: 4px;
  136. border-radius: 4px;
  137. cursor: pointer;
  138. }
  139. }
  140. .show-pwd {
  141. // position: absolute;
  142. // right: 10px;
  143. // top: 7px;
  144. font-size: 16px;
  145. color: $dark_gray;
  146. cursor: pointer;
  147. user-select: none;
  148. }
  149. }
  150. </style>

2、新建api/login.js

  1. import request from './request'
  2. export const login = (data) => {
  3. return request({
  4. url: '/login',
  5. method: 'POST',
  6. data
  7. })
  8. }

3、新建store/modules/app.js

  1. import { login as loginApi } from '@/api/login'
  2. import router from '@/router'
  3. import { setTokenTime } from '@/utils/auth'
  4. export default {
  5. namespaced: true,
  6. state: () => ({
  7. token: localStorage.getItem('token') || '',
  8. siderType: true,
  9. }),
  10. mutations: {
  11. setToken(state, token) {
  12. state.token = token
  13. localStorage.setItem('token', token)
  14. },
  15. changeSiderType(state) {
  16. state.siderType = !state.siderType
  17. },
  18. },
  19. actions: {
  20. login({ commit }, userInfo) {
  21. return new Promise((resolve, reject) => {
  22. loginApi(userInfo)
  23. .then((res) => {
  24. console.log(res)
  25. commit('setToken', res.token)
  26. setTokenTime()
  27. router.replace('/')
  28. resolve()
  29. })
  30. .catch((err) => {
  31. reject(err)
  32. })
  33. })
  34. },
  35. // 退出
  36. logout({ commit }) {
  37. commit('setToken', '')
  38. localStorage.clear()
  39. router.replace('/login')
  40. }
  41. }
  42. }

4、store/getters.js、store/index.js

  1. export default {
  2. token: (state) => state.app.token,
  3. siderType: (state) => state.app.siderType,
  4. }
  1. import { createStore } from 'vuex'
  2. import app from './modules/app'
  3. import getters from './getters'
  4. export default createStore({
  5. modules: {
  6. app
  7. },
  8. getters
  9. })

3、Layout 布局

  1. <template>
  2. <el-container class="content_wrapper">
  3. <el-aside :width="asideWidth" class="sidebar-container">
  4. <Menu />
  5. </el-aside>
  6. <el-container
  7. class="right_container"
  8. :class="{ hidderContainer: !$store.getters.siderType }"
  9. >
  10. <el-header><Headers /></el-header>
  11. <el-main>
  12. <router-view />
  13. </el-main>
  14. </el-container>
  15. </el-container>
  16. </template>
  17. <script setup>
  18. import Menu from './Menu'
  19. import Headers from './headers'
  20. import { computed } from 'vue'
  21. import variables from '@/styles/variables.scss'
  22. import { useStore } from 'vuex'
  23. const store = useStore()
  24. // const asideWidth = ref(variables.sideBarWidth)
  25. const asideWidth = computed(() => {
  26. return store.getters.siderType
  27. ? variables.sideBarWidth
  28. : variables.hideSideBarWidth
  29. })
  30. </script>
  31. <style lang="scss" scoped>
  32. .content_wrapper{
  33. width: 100%;
  34. height: 100%;
  35. display: flex;
  36. align-items: center;
  37. :deep(.sidebar-container){
  38. flex-shrink: 0;
  39. }
  40. :deep(.right_container){
  41. flex:1;
  42. height: 100%;
  43. }
  44. }
  45. </style>

1、左侧菜单 新建layout/Menu/index.vue

  1. <template>
  2. <el-menu
  3. active-text-color="#ffd04b"
  4. :background-color="variables.menuBg"
  5. class="el-menu-vertical-demo"
  6. :default-active="defaultActive"
  7. text-color="#fff"
  8. router
  9. unique-opened
  10. :collapse="!$store.getters.siderType"
  11. >
  12. <el-sub-menu
  13. :index="item.id+''"
  14. v-for="(item, index) in menusList"
  15. :key="item.id"
  16. >
  17. <template #title>
  18. <el-icon>
  19. <component :is="iconList[index]"></component>
  20. </el-icon>
  21. <span>{{ item.authName }}</span>
  22. </template>
  23. <el-menu-item
  24. :index="'/' + it.path"
  25. v-for="it in item.children"
  26. :key="it.id"
  27. @click="savePath(it.path)"
  28. >
  29. <template #title>
  30. <el-icon>
  31. <component :is="icon"></component>
  32. </el-icon>
  33. <span>{{ it.authName }}</span>
  34. </template>
  35. </el-menu-item>
  36. </el-sub-menu>
  37. </el-menu>
  38. </template>
  39. <script setup>
  40. import { menuList } from '@/api/menu'
  41. import { ref } from 'vue'
  42. import variables from '@/styles/variables.scss'
  43. const iconList = ref(['user', 'setting', 'shop', 'tickets', 'pie-chart'])
  44. const icon = ref('menu')
  45. const defaultActive = ref(sessionStorage.getItem('path') || '/users')
  46. const menusList = ref([])
  47. const initMenusList = async () => {
  48. menusList.value = await menuList()
  49. }
  50. initMenusList()
  51. const savePath = (path) => {
  52. sessionStorage.setItem('path', `/${path}`)
  53. }
  54. </script>
  55. <style lang="scss" scoped></style>

menu数据 

  1. {
  2. "data": [
  3. {
  4. "id": 125,
  5. "authName": "用户管理",
  6. "path": "users",
  7. "children": [
  8. {
  9. "id": 110,
  10. "authName": "用户列表",
  11. "path": "users",
  12. "children": [
  13. ],
  14. "order": null
  15. }
  16. ],
  17. "order": 1
  18. },
  19. {
  20. "id": 103,
  21. "authName": "权限管理",
  22. "path": "rights",
  23. "children": [
  24. {
  25. "id": 111,
  26. "authName": "角色列表",
  27. "path": "roles",
  28. "children": [
  29. ],
  30. "order": null
  31. },
  32. {
  33. "id": 112,
  34. "authName": "权限列表",
  35. "path": "rights",
  36. "children": [
  37. ],
  38. "order": null
  39. }
  40. ],
  41. "order": 2
  42. },
  43. {
  44. "id": 101,
  45. "authName": "商品管理",
  46. "path": "goods",
  47. "children": [
  48. {
  49. "id": 104,
  50. "authName": "商品列表",
  51. "path": "goods",
  52. "children": [
  53. ],
  54. "order": 1
  55. },
  56. {
  57. "id": 115,
  58. "authName": "分类参数",
  59. "path": "params",
  60. "children": [
  61. ],
  62. "order": 2
  63. },
  64. {
  65. "id": 121,
  66. "authName": "商品分类",
  67. "path": "categories",
  68. "children": [
  69. ],
  70. "order": 3
  71. }
  72. ],
  73. "order": 3
  74. },
  75. {
  76. "id": 102,
  77. "authName": "订单管理",
  78. "path": "orders",
  79. "children": [
  80. {
  81. "id": 107,
  82. "authName": "订单列表",
  83. "path": "orders",
  84. "children": [
  85. ],
  86. "order": null
  87. }
  88. ],
  89. "order": 4
  90. },
  91. {
  92. "id": 145,
  93. "authName": "数据统计",
  94. "path": "reports",
  95. "children": [
  96. {
  97. "id": 146,
  98. "authName": "数据报表",
  99. "path": "reports",
  100. "children": [
  101. ],
  102. "order": null
  103. }
  104. ],
  105. "order": 5
  106. }
  107. ],
  108. "meta": {
  109. "msg": "获取菜单列表成功",
  110. "status": 200
  111. }
  112. }

2、右侧头部

  1. <template>
  2. <div class="navbar">
  3. <Hamburger />
  4. <Breadcrumb />
  5. <div class="navbar-right">
  6. <Driver class="navbar-item" />
  7. <screen-full class="navbar-item" />
  8. <Avatar class="navbar-item" />
  9. </div>
  10. </div>
  11. </template>
  12. <script setup>
  13. import Hamburger from './components/hamburger.vue'
  14. import Breadcrumb from './components/breadcrumb.vue'
  15. import Avatar from './components/avatar.vue'
  16. import ScreenFull from './components/screenFull.vue'
  17. import Driver from './components/driver'
  18. </script>
  19. <style lang="scss" scoped>
  20. .navbar {
  21. width: 100%;
  22. height: 60px;
  23. overflow: hidden;
  24. background-color: #fff;
  25. box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  26. padding: 0 16px;
  27. display: flex;
  28. align-items: center;
  29. box-sizing: border-box;
  30. position: relative;
  31. .navbar-right {
  32. flex: 1;
  33. display: flex;
  34. align-items: center;
  35. justify-content: flex-end;
  36. ::v-deep .navbar-item {
  37. display: inline-block;
  38. margin-left: 18px;
  39. font-size: 22px;
  40. color: #5a5e66;
  41. box-sizing: border-box;
  42. cursor: pointer;
  43. }
  44. }
  45. }
  46. </style>

1、hamburger.vue 控制左侧菜单折叠展开

  1. <template>
  2. <div class="hamburger-container" @click="toggleClick" id="hamburger">
  3. <svg-icon :icon="icon"></svg-icon>
  4. </div>
  5. </template>
  6. <script setup>
  7. import { useStore } from 'vuex'
  8. import { computed } from 'vue'
  9. const store = useStore()
  10. const toggleClick = () => {
  11. store.commit('app/changeSiderType')
  12. }
  13. const icon = computed(() => {
  14. return store.getters.siderType ? 'hamburger-opened' : 'hamburger-closed'
  15. })
  16. </script>
  17. <style lang="scss" scoped>
  18. .hamburger-container {
  19. margin-right: 16px;
  20. box-sizing: border-box;
  21. cursor: pointer;
  22. }
  23. </style>

2、breadcrumb.vue 面包屑导航

  1. <template>
  2. <el-breadcrumb separator="/">
  3. <el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
  4. <span class="no-redirect" v-if="index === breadcrumbList.length - 1">{{
  5. item.name
  6. }}</span>
  7. <span class="redirect" v-else @click="handleRedirect(item.path)">{{
  8. item.name
  9. }}</span>
  10. </el-breadcrumb-item>
  11. </el-breadcrumb>
  12. </template>
  13. <script setup>
  14. import { watch, ref } from 'vue'
  15. import { useRoute, useRouter } from 'vue-router'
  16. const route = useRoute()
  17. const router = useRouter()
  18. const breadcrumbList = ref([])
  19. const initBreadcrumbList = () => {
  20. breadcrumbList.value = route.matched
  21. console.log(route.matched)
  22. }
  23. const handleRedirect = (path) => {
  24. router.push(path)
  25. }
  26. watch(
  27. route,
  28. () => {
  29. initBreadcrumbList()
  30. },
  31. { deep: true, immediate: true }
  32. )
  33. </script>
  34. <style lang="scss" scoped>
  35. .no-redirect {
  36. color: #97a8be;
  37. cursor: text;
  38. }
  39. .redirect {
  40. color: #666;
  41. font-weight: 600;
  42. cursor: pointer;
  43. &:hover {
  44. color: $menuBg;
  45. }
  46. }
  47. </style>

3、screenFull.vue 全屏组件

1、安装

cnpm i screenfull --save

2、使用

  1. <template>
  2. <div @click="handleFullScreen" id="screenFul">
  3. <svg-icon :icon="icon ? 'exit-fullscreen' : 'fullscreen'"></svg-icon>
  4. </div>
  5. </template>
  6. <script setup>
  7. import screenfull from 'screenfull'
  8. import { ref, onMounted, onBeforeMount } from 'vue'
  9. const icon = ref(screenfull.isFullscreen)
  10. const handleFullScreen = () => {
  11. if (screenfull.isEnabled) {
  12. screenfull.toggle()
  13. }
  14. }
  15. const changeIcon = () => {
  16. icon.value = screenfull.isFullscreen
  17. }
  18. onMounted(() => {
  19. screenfull.on('change', changeIcon)
  20. })
  21. onBeforeMount(() => {
  22. screenfull.off('change')
  23. })
  24. </script>
  25. <style lang="scss" scoped></style>

4、driver引导组件

1、安装

cnpm i driver.js --save

2、使用

  1. <template>
  2. <div id="guide" @click.prevent.stop="handleGuide">
  3. <svg-icon icon="guide"></svg-icon>
  4. </div>
  5. </template>
  6. <script setup>
  7. import Driver from 'driver.js'
  8. import 'driver.js/dist/driver.min.css'
  9. import { onMounted } from 'vue'
  10. const steps=[
  11. {
  12. element: '#guide',
  13. popover: {
  14. title: 'guideBtn',
  15. description: 'Body of the popover',
  16. position: 'left'
  17. }
  18. },
  19. {
  20. element: '#hamburger',
  21. popover: {
  22. title: 'hamburgerBtn',
  23. description: 'Body of the popover',
  24. position: 'bottom'
  25. }
  26. },
  27. {
  28. element: '#screenFul',
  29. popover: {
  30. title: 'fullScreen',
  31. description: 'Body of the popover',
  32. position: 'left'
  33. }
  34. }
  35. ]
  36. let driver
  37. onMounted(() => {
  38. initDriver()
  39. })
  40. const initDriver = () => {
  41. driver = new Driver({
  42. animate: false, // Whether to animate or not
  43. opacity: 0.75, // Background opacity (0 means only popovers and without overlay)
  44. padding: 10, // Distance of element from around the edges
  45. allowClose: true, // Whether the click on overlay should close or not
  46. overlayClickNext: false, // Whether the click on overlay should move next
  47. doneBtnText: 'doneBtnText', // Text on the final button
  48. closeBtnText: 'closeBtnText', // Text on the close button for this step
  49. stageBackground: '#ffffff', // Background color for the staged behind highlighted element
  50. nextBtnText: 'nextBtnText', // Next button text for this step
  51. prevBtnText: 'prevBtnText' // Previous button text for this step
  52. })
  53. }
  54. const handleGuide = () => {
  55. driver.defineSteps(steps)
  56. driver.start()
  57. }
  58. </script>
  59. <style lang="scss" scoped></style>

 5、头像下拉 退出

  1. <template>
  2. <el-dropdown>
  3. <span class="el-dropdown-link">
  4. <el-avatar shape="square" :size="40" :src="squareUrl"></el-avatar>
  5. </span>
  6. <template #dropdown>
  7. <el-dropdown-menu>
  8. <el-dropdown-item @click="logout">退出</el-dropdown-item>
  9. </el-dropdown-menu>
  10. </template>
  11. </el-dropdown>
  12. </template>
  13. <script setup>
  14. import { ref } from 'vue'
  15. import { useStore } from 'vuex'
  16. const store = useStore()
  17. const squareUrl = ref(
  18. 'https://img0.baidu.com/it/u=1056811702,4111096278&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500'
  19. )
  20. const logout = () => {
  21. store.dispatch('app/logout')
  22. }
  23. </script>
  24. <style lang="scss" scoped>
  25. ::v-deep .el-dropdown-menu__item {
  26. white-space: nowrap;
  27. }
  28. </style>

4、用户管理开发

1、搜索功能 、结果列表展示

  1. <template>
  2. <el-card class="users">
  3. <div class="top">
  4. <div class="search_box">
  5. <el-input
  6. v-model="queryForm.query"
  7. clearable
  8. placeholder="请输入用户名"
  9. />
  10. </div>
  11. <el-button type="primary" :icon="Search" @click="getUsersTableData">查询</el-button>
  12. <el-button type="primary" :icon="Plus" @click="handleDialogValue()">新增用户</el-button>
  13. </div>
  14. <div class="bottom">
  15. <el-table class="users_table" :data="tableData" border style="width: 100%">
  16. <el-table-column align="center" :key="index" v-for="(item,index) in options" :prop="item.prop" :label="item.label" :width="item.width">
  17. <template v-slot="{row}" v-if="item.prop=='mg_state'">
  18. <el-switch v-model="row.mg_state" @change='changeState(row)' />
  19. </template>
  20. <template v-slot="{row}" v-else-if="item.prop=='create_time'">
  21. {{$filters.filterTimes(row.create_time)}}
  22. </template>
  23. <template #default="{row}" v-else-if="item.prop=='action'">
  24. <el-button size="small" :icon="Edit" type="primary" @click="handleDialogValue(row)"></el-button>
  25. <el-button size="small" :icon="Setting" type="warning"></el-button>
  26. <el-button size="small" :icon="Delete" type="danger" @click="delUser(row)"></el-button>
  27. </template>
  28. </el-table-column>
  29. </el-table>
  30. <el-pagination
  31. background
  32. v-model:currentPage="queryForm.pagenum"
  33. v-model:page-size="queryForm.pagesize"
  34. :page-sizes="[1, 2, 5, 10]"
  35. layout="total, sizes, prev, pager, next, jumper"
  36. :total="total"
  37. @size-change="handleSizeChange"
  38. @current-change="handleCurrentChange"
  39. />
  40. </div>
  41. </el-card>
  42. <Dialog v-model="dialogVisible" :dialogTableValue="dialogTableValue" @initUserList="getUsersTableData" :dialogTitle="dialogTitle" v-if="dialogVisible" />
  43. </template>
  44. <script setup>
  45. import { Search,Edit,Setting,Delete,Plus } from '@element-plus/icons-vue'
  46. import { ref,reactive,onMounted } from 'vue'
  47. import { getUsersList,changeUserState,deleteUser } from '@/api/users'
  48. import { ElMessage,ElMessageBox} from 'element-plus'
  49. import { isNull } from '@/utils/filters'
  50. import Dialog from './components/dialog'
  51. const options = [
  52. {
  53. label:'username',
  54. prop:'username'
  55. },
  56. {
  57. label:'email',
  58. prop:'email'
  59. },
  60. {
  61. label:'mobile',
  62. prop:'mobile'
  63. },
  64. {
  65. label:'role_name',
  66. prop:'role_name'
  67. },
  68. {
  69. label:'mg_state',
  70. prop:'mg_state'
  71. },
  72. {
  73. label:'create_time',
  74. prop:'create_time'
  75. },
  76. {
  77. label:'action',
  78. prop:'action',
  79. width:'200px'
  80. },
  81. ]
  82. let dialogVisible = ref(false)
  83. let dialogTitle = ref('新增用户')
  84. let queryForm = reactive({
  85. query:'',
  86. pagenum:1,
  87. pagesize:10
  88. })
  89. const total = ref(0)
  90. let tableData = ref([])
  91. let dialogTableValue= ref({})
  92. const getUsersTableData= async ()=>{
  93. let res = await getUsersList(queryForm)
  94. tableData.value=res.users
  95. total.value=res.total
  96. }
  97. getUsersTableData()
  98. const handleSizeChange = (pagesize)=>{
  99. queryForm.pagesize=pagesize;
  100. getUsersTableData()
  101. }
  102. const handleCurrentChange = (pagenum)=>{
  103. queryForm.pagenum=pagenum;
  104. getUsersTableData()
  105. }
  106. const changeState= async (row)=>{
  107. let {id,mg_state} = row
  108. let res = await changeUserState(id,mg_state)
  109. console.log(res,'更改状态')
  110. ElMessage({
  111. message: '更新成功',
  112. type: 'success',
  113. })
  114. }
  115. const handleDialogValue = (row)=>{
  116. if(isNull(row)){
  117. dialogTitle.value='新增用户'
  118. dialogTableValue.value={}
  119. }else{
  120. dialogTitle.value='编辑用户'
  121. dialogTableValue.value=JSON.parse(JSON.stringify(row))
  122. }
  123. dialogVisible.value=true
  124. }
  125. const delUser = (row) =>{
  126. ElMessageBox.confirm(
  127. '您确定要删除改数据吗?',
  128. '删除',
  129. {
  130. confirmButtonText: '确定',
  131. cancelButtonText: '取消',
  132. type: 'warning',
  133. draggable: true,
  134. }
  135. )
  136. .then(async () => {
  137. await deleteUser(row.id)
  138. getUsersTableData()
  139. ElMessage({
  140. type: 'success',
  141. message: '删除成功!',
  142. })
  143. })
  144. .catch(() => {
  145. ElMessage({
  146. type: 'info',
  147. message: '取消删除!',
  148. })
  149. })
  150. }
  151. </script>
  152. <style lang="scss" scoped>
  153. .users{
  154. width: 100%;
  155. height: 100%;
  156. box-sizing: border-box;
  157. :deep(.el-card__body){
  158. display: flex;
  159. flex-direction: column;
  160. width: 100%;
  161. height: 100%;
  162. .top{
  163. flex-shrink: 0;
  164. width: 100%;
  165. height: 60px;
  166. display: flex;
  167. align-items: center;
  168. .search_box{
  169. width: 300px;
  170. margin-right: 15px;
  171. }
  172. }
  173. .bottom{
  174. width: 100%;
  175. flex:1;
  176. display: flex;
  177. flex-direction: column;
  178. .users_table{
  179. flex:1;
  180. }
  181. .el-pagination{
  182. justify-content: flex-end;
  183. }
  184. }
  185. }
  186. }
  187. </style>

2、新建components/dialog.vue 新增、编辑用户弹框组件

  1. <template>
  2. <el-dialog
  3. :model-value="dialogVisible"
  4. :title="dialogTitle"
  5. width="30%"
  6. @close="handleClose"
  7. >
  8. <el-form ref="formRef" :model="form" label-width="70px" :rules="rules">
  9. <el-form-item label="用户名" prop="username">
  10. <el-input v-model="form.username" />
  11. </el-form-item>
  12. <el-form-item label="密码" prop="password" v-if="dialogTitle==='新增用户'">
  13. <el-input type="password" v-model="form.password" />
  14. </el-form-item>
  15. <el-form-item label="邮箱" prop="email">
  16. <el-input v-model="form.email" />
  17. </el-form-item>
  18. <el-form-item label="手机号" prop="mobile">
  19. <el-input type="number" v-model="form.mobile" />
  20. </el-form-item>
  21. </el-form>
  22. <template #footer>
  23. <span class="dialog-footer">
  24. <el-button @click="handleClose">取消</el-button>
  25. <el-button type="primary" @click="handleConfirm"
  26. >保存</el-button
  27. >
  28. </span>
  29. </template>
  30. </el-dialog>
  31. </template>
  32. <script setup>
  33. import { ref,reactive,defineEmits,defineProps,watch} from 'vue'
  34. import { addUser,editUser } from "@/api/users"
  35. import { ElMessage } from 'element-plus'
  36. const formRef = ref(null)
  37. let form = reactive({
  38. username:'',
  39. password:'',
  40. email:'',
  41. mobile:''
  42. })
  43. const rules = reactive({
  44. username:[
  45. { required: true, message: '请输入用户名', trigger: 'blur' },
  46. ],
  47. password:[
  48. { required: true, message: '请输入密码', trigger: 'blur' },
  49. ],
  50. email:[
  51. { required: true, message: '请输入邮箱', trigger: 'blur' },
  52. ],
  53. mobile:[
  54. { required: true, message: '请输入手机号', trigger: 'blur' },
  55. ],
  56. })
  57. const props = defineProps({
  58. dialogTitle:{
  59. type:String,
  60. default:'',
  61. required:true
  62. },
  63. dialogTableValue:{
  64. type:Object,
  65. default:()=>{}
  66. }
  67. })
  68. const emits= defineEmits(['update:modelValue','initUserList'])
  69. const handleClose =()=>{
  70. emits('update:modelValue',false)
  71. }
  72. const handleConfirm = ()=>{
  73. formRef.value.validate(async (valid) => {
  74. if (valid) {
  75. props.dialogTitle==='新增用户'?await addUser(form): await editUser(form)
  76. props.dialogTitle==='新增用户'? ElMessage({
  77. message: '创建成功',
  78. type: 'success',
  79. }):ElMessage({
  80. message: '更新成功',
  81. type: 'success',
  82. })
  83. emits('initUserList')
  84. handleClose()
  85. } else {
  86. console.log('error submit!!')
  87. return false
  88. }
  89. })
  90. }
  91. watch(()=>props.dialogTableValue,()=>{
  92. console.log('1',props.dialogTableValue);
  93. form=props.dialogTableValue
  94. },{deep:true,immediate:true})
  95. </script>
  96. <style lang='scss' scoped>
  97. </style>

3、新建全局时间过滤器

1、安装dayjs

cnpm i dayjs --save

2、新建utils/filters.js

  1. import dayjs from 'dayjs'
  2. const filterTimes = (val,format='YYYY-MM-DD') =>{
  3. if(!isNull(val)){
  4. val = parseInt(val)*1000
  5. return dayjs(val).format(format)
  6. }else{
  7. return '--'
  8. }
  9. }
  10. export const isNull = (val)=>{
  11. if(!val) return true
  12. if(JSON.stringify(val)==="{}") return true
  13. if(JSON.stringify(val)==="[]") return true
  14. }
  15. export default (app)=>{
  16. app.config.globalProperties.$filters={
  17. filterTimes
  18. }
  19. }

3、main.js传入app

  1. import filters from './utils/filters'
  2. filters(app)

4、使用

  1. <template v-slot="{row}" v-else-if="item.prop=='create_time'">
  2. {{$filters.filterTimes(row.create_time)}}
  3. </template>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/58837
推荐阅读
相关标签
  

闽ICP备14008679号