vue3.2后台管理系统搭建学习笔记

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)

const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')

module.exports = {
intOnSave:false,
  devServer:{
    open:true,
    port:9000,
  },
  configureWebpack: {
    plugins: [
      AutoImport({
        resolvers: [ElementPlusResolver()],
      }),
      Components({
        resolvers: [ElementPlusResolver()],
      }),
    ]
  },
}

4、使用

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

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

2、使用element-plus Icon图标 

1、安装

cnpm install @element-plus/icons-vue

2、注册全局组件

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

 3、使用(左侧菜单icon)

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

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

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

 2、在main.js中引入

import '@/styles/index.scss'

3、配置webpack

css: {
    loaderOptions: {
      sass: {
        // 8版本用prependData:
        prependData: `
          @import "@/styles/variables.scss";  // scss文件地址
          @import "@/styles/mixin.scss";     // scss文件地址
        `
      }
    }
  }

4、使用svg,全局注册

1、svg图标导入到项目

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

<template>
  <svg class="svg-icon" aria-hidden="true">
    <use :xlink:href="iconName"></use>
  </svg>
</template>

<script setup>
import { defineProps, computed } from 'vue'
const props = defineProps({
  icon: {
    type: String,
    required: true
  }
})

const iconName = computed(() => {
  return `#icon-${props.icon}`
})
</script>

<style lang="scss" scoped>
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
}
</style>

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

import SvgIcon from '@/components/SvgIcon'

const svgRequired = require.context('./svg', false, /\.svg$/)
svgRequired.keys().forEach((item) => svgRequired(item))

export default (app) => {
  app.component('svg-icon', SvgIcon)
}

5、在main.js中引入

import SvgIcon from '@/icons'
const app=createApp(App)
SvgIcon(app)

6、安装依赖

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

7、配置webpack

const path = require('path')
function resolve(dir) {
  return path.join(__dirname, dir)
}
const webpack = require('webpack')

module.exports = {
chainWebpack(config) {
    // 设置 svg-sprite-loader
    // config 为 webpack 配置对象
    // config.module 表示创建一个具名规则,以后用来修改规则
    config.module
      // 规则
      .rule('svg')
      // 忽略
      .exclude.add(resolve('src/icons'))
      // 结束
      .end()
    // config.module 表示创建一个具名规则,以后用来修改规则
    config.module
      // 规则
      .rule('icons')
      // 正则,解析 .svg 格式文件
      .test(/\.svg$/)
      // 解析的文件
      .include.add(resolve('src/icons'))
      // 结束
      .end()
      // 新增了一个解析的loader
      .use('svg-sprite-loader')
      // 具体的loader
      .loader('svg-sprite-loader')
      // loader 的配置
      .options({
        symbolId: 'icon-[name]'
      })
      // 结束
      .end()
    config
      .plugin('ignore')
      .use(
        new webpack.ContextReplacementPlugin(/moment[/\\]locale$/, /zh-cn$/)
      )
    config.module
      .rule('icons')
      .test(/\.svg$/)
      .include.add(resolve('src/icons'))
      .end()
      .use('svg-sprite-loader')
      .loader('svg-sprite-loader')
      .options({
        symbolId: 'icon-[name]'
      })
      .end()
  },
}

8、使用

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

5、请求封装

1、安装axios 

cnpm i axios --save

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

const TOKEN_TIME = 'tokenTime'

const TOKEN_TIME_VALUE = 2 * 60 * 60 * 1000

// 登录时设置时间
export const setTokenTime = () => {
  localStorage.setItem(TOKEN_TIME, Date.now())
}

// 获取
export const getTokenTime = () => {
  return localStorage.getItem(TOKEN_TIME)
}

// 是否已经过期
export const diffTokenTime = () => {
  const currentTime = Date.now()
  const tokenTime = getTokenTime()
  return currentTime - tokenTime > TOKEN_TIME_VALUE
}

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

import axios from 'axios'
import { ElMessage } from 'element-plus'
import { diffTokenTime } from '@/utils/auth'
import store from '@/store'
const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
})

service.interceptors.request.use(
  (config) => {
    if (localStorage.getItem('token')) {
      if (diffTokenTime()) {
        store.dispatch('app/logout')
        return Promise.reject(new Error('token 失效了'))
      }
    }
    config.headers.Authorization = localStorage.getItem('token')
    return config
  },
  (error) => {
    return Promise.reject(new Error(error))
  }
)

service.interceptors.response.use(
  (response) => {
    const { data, meta } = response.data
    if (meta.status === 200 || meta.status === 201) {
      return data
    } else {
      ElMessage.error(meta.msg)
      return Promise.reject(new Error(meta.msg))
    }
  },
  (error) => {
    console.log(error.response)
    error.response && ElMessage.error(error.response.data)
    return Promise.reject(new Error(error.response.data))
  }
)
export default service

4、本地跨域配置

  devServer: {
    https: false,
    hotOnly: false,
    open:true,
    port:9000,
    proxy: {
      '/api': {
        target: 'https://*******/api/private/v1/',
        changeOrigin: true,
        pathRewrite: {
          '^/api': ''
        }
      }
    }
  },

5、全局坏境变量定义

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

ENV = 'development'

VUE_APP_BASE_API = '/api'
ENV = 'production'

VUE_APP_BASE_API = '/prod-api'

6、全局导航守卫 

import { createRouter, createWebHashHistory } from 'vue-router'

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/login/index.vue')
  },
  {
    path: '/',
    name: '/',
    component: () => import('../layout'),
    redirect: '/users',
    children: [
      {
        path: 'users',
        name: 'users',
        component: () => import('@/views/users/index.vue')
      },
      {
        path: 'categories',
        name: 'categories',
        component: () => import('@/views/categories/index.vue')
      },
      {
        path: 'goods',
        name: 'goods',
        component: () => import('@/views/goods/index.vue')
      },
      {
        path: 'orders',
        name: 'orders',
        component: () => import('@/views/orders/index.vue')
      },
      {
        path: 'params',
        name: 'params',
        component: () => import('@/views/params/index.vue')
      },
      {
        path: 'reports',
        name: 'reports',
        component: () => import('@/views/reports/index.vue')
      },
      {
        path: 'rights',
        name: 'rights',
        component: () => import('@/views/rights/index.vue')
      },
      {
        path: 'roles',
        name: 'roles',
        component: () => import('@/views/roles/index.vue')
      }
    ]
  }
]


const router = createRouter({
  history: createWebHashHistory(),
  routes
})

export default router

1、新建router/permission.js

import router from './index'
import store from '@/store'

const whiteList = ['/login']
router.beforeEach((to, from, next) => {
  if (store.getters.token) {
    if (to.path === '/login') {
      next('/')
    } else {
      next()
    }
  } else {
    if (whiteList.includes(to.path)) {
      next()
    } else {
      next('/login')
    }
  }
})

2、main.js引入 

import '@/router/permission'

5、登录 

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

<template>
  <div class="login-container">
    <el-form ref="loginFormRef" :model="loginForm" class="login-form" :rules="rules">
      <div class="title-container">
        <h3 class="title">后台管理系统</h3>
      </div>
      <el-form-item prop="username">
        <svg-icon icon="user" class="svg-container"></svg-icon>
        <el-input v-model="loginForm.username"></el-input>
      </el-form-item>
      <el-form-item prop="password">
        <svg-icon icon="password" class="svg-container"></svg-icon>
        <el-input v-model="loginForm.password" :type="passwordType"></el-input>
        <svg-icon
          class="svg-container"
          :icon="passwordType === 'password' ? 'eye' : 'eye-open'"
          @click="changeType"
        ></svg-icon>
      </el-form-item>
      <el-button type="primary" class="login-button" @click="handleLogin">登录</el-button>
    </el-form>
  </div>
</template>

<script setup>
import { ref,reactive} from 'vue'
import { useStore } from 'vuex'
const store = useStore()
const loginForm = reactive({
  username: 'admin',
  password: '123456'
})

const rules = reactive({
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
  ],
  password: [
    {
      required: true,
      message: '请输入密码',
      trigger: 'blur'
    }
  ]
})

const loginFormRef = ref(null)
const handleLogin = () => {
  loginFormRef.value.validate(async (valid) => {
    if (valid) {
      store.dispatch('app/login', loginForm)
    } else {
      console.log('error submit!!')
      return false
    }
  })
}

const passwordType = ref('password')
const changeType = () => {
  if (passwordType.value === 'password') {
    passwordType.value = 'text'
  } else {
    passwordType.value = 'password'
  }
}
</script>

<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
$cursor: #fff;

.login-container {
  min-height: 100%;
  width: 100%;
  background-color: $bg;
  overflow: hidden;

  .login-form {
    position: relative;
    width: 520px;
    max-width: 100%;
    padding: 160px 35px 0;
    margin: 0 auto;
    overflow: hidden;

    :deep(.el-form-item) {
      border: 1px solid rgba(255, 255, 255, 0.1);
      background: rgba(0, 0, 0, 0.1);
      border-radius: 5px;
      color: #454545;
      height: 50px;
      .el-input{
        flex:1;
        .el-input__wrapper{
          background-color:transparent;
          box-shadow: none;
          .el-input__inner{
             color: #fff;
          }
        }
      }
    }
    .login-button {
      width: 100%;
      box-sizing: border-box;
    }
  }

  .tips {
    font-size: 16px;
    line-height: 28px;
    color: #fff;
    margin-bottom: 10px;

    span {
      &:first-of-type {
        margin-right: 16px;
      }
    }
  }

  .svg-container {
    padding: 6px 5px 6px 15px;
    color: $dark_gray;
    vertical-align: middle;
    display: inline-block;
  }

  .title-container {
    position: relative;

    .title {
      font-size: 26px;
      color: $light_gray;
      margin: 0px auto 40px auto;
      text-align: center;
      font-weight: bold;
    }

    :deep(.lang-select) {
      position: absolute;
      top: 4px;
      right: 0;
      background-color: white;
      font-size: 22px;
      padding: 4px;
      border-radius: 4px;
      cursor: pointer;
    }
  }

  .show-pwd {
    // position: absolute;
    // right: 10px;
    // top: 7px;
    font-size: 16px;
    color: $dark_gray;
    cursor: pointer;
    user-select: none;
  }
}
</style>

2、新建api/login.js

import request from './request'

export const login = (data) => {
  return request({
    url: '/login',
    method: 'POST',
    data
  })
}

3、新建store/modules/app.js

import { login as loginApi } from '@/api/login'
import router from '@/router'
import { setTokenTime } from '@/utils/auth'
export default {
  namespaced: true,
  state: () => ({
    token: localStorage.getItem('token') || '',
    siderType: true,
  }),
  mutations: {
    setToken(state, token) {
      state.token = token
      localStorage.setItem('token', token)
    },
    changeSiderType(state) {
      state.siderType = !state.siderType
    },
  },
  actions: {
    login({ commit }, userInfo) {
      return new Promise((resolve, reject) => {
        loginApi(userInfo)
          .then((res) => {
            console.log(res)
            commit('setToken', res.token)
            setTokenTime()
            router.replace('/')
            resolve()
          })
          .catch((err) => {
            reject(err)
          })
      })
    },
    // 退出
    logout({ commit }) {
      commit('setToken', '')
      localStorage.clear()
      router.replace('/login')
    }
  }
}

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

export default {
  token: (state) => state.app.token,
  siderType: (state) => state.app.siderType,
}
import { createStore } from 'vuex'
import app from './modules/app'
import getters from './getters'
export default createStore({
  modules: {
    app
  },
  getters
})

3、Layout 布局

<template>
    <el-container class="content_wrapper">
      <el-aside :width="asideWidth" class="sidebar-container">
        <Menu />
      </el-aside>
      <el-container
      class="right_container"
      :class="{ hidderContainer: !$store.getters.siderType }"
    >
      <el-header><Headers /></el-header>
      <el-main>
        <router-view />
      </el-main>
    </el-container>
    </el-container>
</template>


<script setup>
import Menu from './Menu'
import Headers from './headers'
import { computed } from 'vue'
import variables from '@/styles/variables.scss'
import { useStore } from 'vuex'
const store = useStore()
// const asideWidth = ref(variables.sideBarWidth)
const asideWidth = computed(() => {
  return store.getters.siderType
    ? variables.sideBarWidth
    : variables.hideSideBarWidth
})
</script>

<style lang="scss" scoped>

  .content_wrapper{
     width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    :deep(.sidebar-container){
      flex-shrink: 0;
    }
    :deep(.right_container){
      flex:1;
      height: 100%;
    }
  }

</style>

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

<template>
  <el-menu
    active-text-color="#ffd04b"
    :background-color="variables.menuBg"
    class="el-menu-vertical-demo"
    :default-active="defaultActive"
    text-color="#fff"
    router
    unique-opened
    :collapse="!$store.getters.siderType"
  >
    <el-sub-menu
      :index="item.id+''"
      v-for="(item, index) in menusList"
      :key="item.id"
    >
      <template #title>
        <el-icon>
          <component :is="iconList[index]"></component>
        </el-icon>
        <span>{{ item.authName }}</span>
      </template>
      <el-menu-item
        :index="'/' + it.path"
        v-for="it in item.children"
        :key="it.id"
        @click="savePath(it.path)"
      >
        <template #title>
          <el-icon>
            <component :is="icon"></component>
          </el-icon>
          <span>{{  it.authName  }}</span>
        </template>
      </el-menu-item>
    </el-sub-menu>
  </el-menu>
</template>

<script setup>
import { menuList } from '@/api/menu'
import { ref } from 'vue'
import variables from '@/styles/variables.scss'

const iconList = ref(['user', 'setting', 'shop', 'tickets', 'pie-chart'])
const icon = ref('menu')

const defaultActive = ref(sessionStorage.getItem('path') || '/users')
const menusList = ref([])
const initMenusList = async () => {
  menusList.value = await menuList()
}
initMenusList()

const savePath = (path) => {
  sessionStorage.setItem('path', `/${path}`)
}
</script>

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

menu数据 

{
  "data": [
    {
      "id": 125,
      "authName": "用户管理",
      "path": "users",
      "children": [
        {
          "id": 110,
          "authName": "用户列表",
          "path": "users",
          "children": [
            
          ],
          "order": null
        }
      ],
      "order": 1
    },
    {
      "id": 103,
      "authName": "权限管理",
      "path": "rights",
      "children": [
        {
          "id": 111,
          "authName": "角色列表",
          "path": "roles",
          "children": [
            
          ],
          "order": null
        },
        {
          "id": 112,
          "authName": "权限列表",
          "path": "rights",
          "children": [
            
          ],
          "order": null
        }
      ],
      "order": 2
    },
    {
      "id": 101,
      "authName": "商品管理",
      "path": "goods",
      "children": [
        {
          "id": 104,
          "authName": "商品列表",
          "path": "goods",
          "children": [
            
          ],
          "order": 1
        },
        {
          "id": 115,
          "authName": "分类参数",
          "path": "params",
          "children": [
            
          ],
          "order": 2
        },
        {
          "id": 121,
          "authName": "商品分类",
          "path": "categories",
          "children": [
            
          ],
          "order": 3
        }
      ],
      "order": 3
    },
    {
      "id": 102,
      "authName": "订单管理",
      "path": "orders",
      "children": [
        {
          "id": 107,
          "authName": "订单列表",
          "path": "orders",
          "children": [
            
          ],
          "order": null
        }
      ],
      "order": 4
    },
    {
      "id": 145,
      "authName": "数据统计",
      "path": "reports",
      "children": [
        {
          "id": 146,
          "authName": "数据报表",
          "path": "reports",
          "children": [
            
          ],
          "order": null
        }
      ],
      "order": 5
    }
  ],
  "meta": {
    "msg": "获取菜单列表成功",
    "status": 200
  }
}

2、右侧头部

<template>
  <div class="navbar">
    <Hamburger />
    <Breadcrumb />
    <div class="navbar-right">
      <Driver class="navbar-item" />
      <screen-full class="navbar-item" />
      <Avatar class="navbar-item" />
    </div>
  </div>
</template>

<script setup>
import Hamburger from './components/hamburger.vue'
import Breadcrumb from './components/breadcrumb.vue'
import Avatar from './components/avatar.vue'
import ScreenFull from './components/screenFull.vue'
import Driver from './components/driver'
</script>

<style lang="scss" scoped>
.navbar {
  width: 100%;
  height: 60px;
  overflow: hidden;
  background-color: #fff;
  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
  padding: 0 16px;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  position: relative;
  .navbar-right {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    ::v-deep .navbar-item {
      display: inline-block;
      margin-left: 18px;
      font-size: 22px;
      color: #5a5e66;
      box-sizing: border-box;
      cursor: pointer;
    }
  }
}
</style>

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

<template>
  <div class="hamburger-container" @click="toggleClick" id="hamburger">
    <svg-icon :icon="icon"></svg-icon>
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
const store = useStore()
const toggleClick = () => {
  store.commit('app/changeSiderType')
}
const icon = computed(() => {
  return store.getters.siderType ? 'hamburger-opened' : 'hamburger-closed'
})
</script>

<style lang="scss" scoped>
.hamburger-container {
  margin-right: 16px;
  box-sizing: border-box;
  cursor: pointer;
}
</style>

2、breadcrumb.vue 面包屑导航

<template>
  <el-breadcrumb separator="/">
    <el-breadcrumb-item v-for="(item, index) in breadcrumbList" :key="index">
      <span class="no-redirect" v-if="index === breadcrumbList.length - 1">{{
        item.name
      }}</span>
      <span class="redirect" v-else @click="handleRedirect(item.path)">{{
      item.name
      }}</span>
    </el-breadcrumb-item>
  </el-breadcrumb>
</template>

<script setup>
import { watch, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()

const breadcrumbList = ref([])

const initBreadcrumbList = () => {
  breadcrumbList.value = route.matched
  console.log(route.matched)
}
const handleRedirect = (path) => {
  router.push(path)
}

watch(
  route,
  () => {
    initBreadcrumbList()
  },
  { deep: true, immediate: true }
)
</script>

<style lang="scss" scoped>
.no-redirect {
  color: #97a8be;
  cursor: text;
}
.redirect {
  color: #666;
  font-weight: 600;
  cursor: pointer;
  &:hover {
    color: $menuBg;
  }
}
</style>

3、screenFull.vue 全屏组件

1、安装

cnpm i screenfull --save

2、使用

<template>
  <div @click="handleFullScreen" id="screenFul">
    <svg-icon :icon="icon ? 'exit-fullscreen' : 'fullscreen'"></svg-icon>
  </div>
</template>

<script setup>
import screenfull from 'screenfull'
import { ref, onMounted, onBeforeMount } from 'vue'

const icon = ref(screenfull.isFullscreen)
const handleFullScreen = () => {
  if (screenfull.isEnabled) {
    screenfull.toggle()
  }
}

const changeIcon = () => {
  icon.value = screenfull.isFullscreen
}

onMounted(() => {
  screenfull.on('change', changeIcon)
})

onBeforeMount(() => {
  screenfull.off('change')
})
</script>

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

4、driver引导组件

1、安装

cnpm i driver.js --save

2、使用

<template>
  <div id="guide" @click.prevent.stop="handleGuide">
    <svg-icon icon="guide"></svg-icon>
  </div>
</template>

<script setup>
import Driver from 'driver.js'
import 'driver.js/dist/driver.min.css'
import { onMounted } from 'vue'
const steps=[
  {
    element: '#guide',
    popover: {
      title: 'guideBtn',
      description: 'Body of the popover',
      position: 'left'
    }
  },
  {
    element: '#hamburger',
    popover: {
      title: 'hamburgerBtn',
      description: 'Body of the popover',
      position: 'bottom'
    }
  },
  {
    element: '#screenFul',
    popover: {
      title: 'fullScreen',
      description: 'Body of the popover',
      position: 'left'
    }
  }
]

let driver
onMounted(() => {
  initDriver()
})
const initDriver = () => {
  driver = new Driver({
    animate: false, // Whether to animate or not
    opacity: 0.75, // Background opacity (0 means only popovers and without overlay)
    padding: 10, // Distance of element from around the edges
    allowClose: true, // Whether the click on overlay should close or not
    overlayClickNext: false, // Whether the click on overlay should move next
    doneBtnText: 'doneBtnText', // Text on the final button
    closeBtnText: 'closeBtnText', // Text on the close button for this step
    stageBackground: '#ffffff', // Background color for the staged behind highlighted element
    nextBtnText: 'nextBtnText', // Next button text for this step
    prevBtnText: 'prevBtnText' // Previous button text for this step
  })
}


const handleGuide = () => {
  driver.defineSteps(steps)
  driver.start()
}
</script>

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

 5、头像下拉 退出

<template>
  <el-dropdown>
    <span class="el-dropdown-link">
      <el-avatar shape="square" :size="40" :src="squareUrl"></el-avatar>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item @click="logout">退出</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
</template>

<script setup>
import { ref } from 'vue'
import { useStore } from 'vuex'
const store = useStore()

const squareUrl = ref(
  'https://img0.baidu.com/it/u=1056811702,4111096278&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500'
)

const logout = () => {
  store.dispatch('app/logout')
}
</script>

<style lang="scss" scoped>
::v-deep .el-dropdown-menu__item {
  white-space: nowrap;
}
</style>

4、用户管理开发

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

<template>
  <el-card class="users">
    <div class="top">
        <div class="search_box">
           <el-input
              v-model="queryForm.query"
              clearable
              placeholder="请输入用户名"
            />
        </div>
          <el-button type="primary" :icon="Search" @click="getUsersTableData">查询</el-button>
          <el-button type="primary" :icon="Plus" @click="handleDialogValue()">新增用户</el-button>
    </div>
    <div class="bottom">
    <el-table class="users_table" :data="tableData" border style="width: 100%">
      <el-table-column  align="center" :key="index" v-for="(item,index) in options" :prop="item.prop" :label="item.label" :width="item.width">
        <template v-slot="{row}" v-if="item.prop=='mg_state'">
          <el-switch v-model="row.mg_state" @change='changeState(row)' />
        </template>
        <template v-slot="{row}" v-else-if="item.prop=='create_time'">
          {{$filters.filterTimes(row.create_time)}}
        </template>
        <template #default="{row}"  v-else-if="item.prop=='action'">
          <el-button size="small" :icon="Edit" type="primary" @click="handleDialogValue(row)"></el-button>
          <el-button size="small" :icon="Setting" type="warning"></el-button>
          <el-button size="small" :icon="Delete" type="danger" @click="delUser(row)"></el-button>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      background
      v-model:currentPage="queryForm.pagenum"
      v-model:page-size="queryForm.pagesize"
      :page-sizes="[1, 2, 5, 10]"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
    </div>
  </el-card>
  <Dialog v-model="dialogVisible" :dialogTableValue="dialogTableValue" @initUserList="getUsersTableData" :dialogTitle="dialogTitle" v-if="dialogVisible" />
</template>

<script setup>
import {  Search,Edit,Setting,Delete,Plus } from '@element-plus/icons-vue'
import { ref,reactive,onMounted } from 'vue'
import { getUsersList,changeUserState,deleteUser } from '@/api/users'
import { ElMessage,ElMessageBox} from 'element-plus'
import { isNull } from '@/utils/filters'
import Dialog from './components/dialog'
const options = [
  {
    label:'username',
    prop:'username'
  },
  {
    label:'email',
    prop:'email'
  },
  {
    label:'mobile',
    prop:'mobile'
  },
  {
    label:'role_name',
    prop:'role_name'
  },
  {
    label:'mg_state',
    prop:'mg_state'
  },
  {
    label:'create_time',
    prop:'create_time'
  },
  {
    label:'action',
    prop:'action',
    width:'200px'
  },
]

  let dialogVisible = ref(false)
  let dialogTitle = ref('新增用户')
  let queryForm = reactive({
    query:'',
    pagenum:1,
    pagesize:10
  })
  const total = ref(0)
  let tableData = ref([])
  let dialogTableValue= ref({})
  const getUsersTableData= async ()=>{
    let res = await getUsersList(queryForm)
    tableData.value=res.users
    total.value=res.total
  }
  getUsersTableData()
 const handleSizeChange = (pagesize)=>{
   queryForm.pagesize=pagesize;
     getUsersTableData()
 }
 const handleCurrentChange = (pagenum)=>{
    queryForm.pagenum=pagenum;
     getUsersTableData()
 }
 const changeState= async (row)=>{
   let {id,mg_state} = row
   let res = await changeUserState(id,mg_state)
   console.log(res,'更改状态')
    ElMessage({
    message: '更新成功',
    type: 'success',
  })
 }
 const handleDialogValue = (row)=>{
   if(isNull(row)){
     dialogTitle.value='新增用户'
     dialogTableValue.value={}
   }else{
     dialogTitle.value='编辑用户'
     dialogTableValue.value=JSON.parse(JSON.stringify(row))
   }
   dialogVisible.value=true
 }
 const delUser = (row) =>{
    ElMessageBox.confirm(
    '您确定要删除改数据吗?',
    '删除',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
       draggable: true,
    }
  )
    .then(async () => {
      await deleteUser(row.id)
      getUsersTableData()
      ElMessage({
        type: 'success',
        message: '删除成功!',
      })
    })
    .catch(() => {
      ElMessage({
        type: 'info',
        message: '取消删除!',
      })
    })
 }
</script>

<style lang="scss" scoped>
.users{
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  :deep(.el-card__body){
    display: flex;
    flex-direction: column;
    width: 100%;
    height: 100%;
    .top{
    flex-shrink: 0;
    width: 100%;
     height: 60px;
    display: flex;
    align-items: center;
    .search_box{
      width: 300px;
      margin-right: 15px;
    }
  }
  .bottom{
    width: 100%;
    flex:1;
    display: flex;
    flex-direction: column;
    .users_table{
       flex:1;
    }
    .el-pagination{
      justify-content: flex-end;
    }
  }
  }

}
</style>

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

<template>
<el-dialog
    :model-value="dialogVisible"
    :title="dialogTitle"
    width="30%"
    @close="handleClose"
  >
     <el-form ref="formRef" :model="form" label-width="70px" :rules="rules">
    <el-form-item label="用户名" prop="username">
      <el-input v-model="form.username" />
    </el-form-item>
    <el-form-item label="密码" prop="password" v-if="dialogTitle==='新增用户'">
      <el-input type="password" v-model="form.password" />
    </el-form-item>
    <el-form-item label="邮箱" prop="email">
      <el-input  v-model="form.email" />
    </el-form-item>
     <el-form-item label="手机号" prop="mobile">
      <el-input type="number"  v-model="form.mobile" />
    </el-form-item>
     </el-form>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="handleClose">取消</el-button>
        <el-button type="primary" @click="handleConfirm"
          >保存</el-button
        >
      </span>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref,reactive,defineEmits,defineProps,watch} from  'vue'
import { addUser,editUser } from "@/api/users"
import { ElMessage } from 'element-plus'
const formRef = ref(null)
let form = reactive({
  username:'',
  password:'',
  email:'',
  mobile:''
})
const rules = reactive({
  username:[
    { required: true, message: '请输入用户名', trigger: 'blur' },
  ],
  password:[
    { required: true, message: '请输入密码', trigger: 'blur' },
  ],
  email:[
    { required: true, message: '请输入邮箱', trigger: 'blur' },
  ],
  mobile:[
    { required: true, message: '请输入手机号', trigger: 'blur' },
  ],
})
const props = defineProps({
  dialogTitle:{
    type:String,
    default:'',
    required:true
  },
  dialogTableValue:{
    type:Object,
    default:()=>{}
  }
})

const emits= defineEmits(['update:modelValue','initUserList'])
const handleClose =()=>{
  emits('update:modelValue',false)
}
const handleConfirm = ()=>{
  formRef.value.validate(async (valid) => {
    if (valid) {
      props.dialogTitle==='新增用户'?await addUser(form): await editUser(form)
      props.dialogTitle==='新增用户'? ElMessage({
        message: '创建成功',
        type: 'success',
      }):ElMessage({
        message: '更新成功',
        type: 'success',
      })

      emits('initUserList')
       handleClose()
    } else {
      console.log('error submit!!')
      return false
    }
  })
}
watch(()=>props.dialogTableValue,()=>{
  console.log('1',props.dialogTableValue);
  form=props.dialogTableValue
},{deep:true,immediate:true})
</script>
<style lang='scss' scoped>
</style>

3、新建全局时间过滤器

1、安装dayjs

cnpm i dayjs --save

2、新建utils/filters.js


import dayjs from 'dayjs'

const filterTimes = (val,format='YYYY-MM-DD') =>{
  if(!isNull(val)){
    val = parseInt(val)*1000
    return dayjs(val).format(format)
  }else{
    return '--'
  }
}

export const isNull = (val)=>{
  if(!val) return true
  if(JSON.stringify(val)==="{}") return true
  if(JSON.stringify(val)==="[]") return true
}

export default (app)=>{
  app.config.globalProperties.$filters={
    filterTimes
  }
}

3、main.js传入app

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

4、使用

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

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
关于Vue 3.2后台管理系统,您可以使用Vue.js框架来构建一个功能强大的后台管理系统Vue 3.2版本带来了许多新的特性和改进,使得开发后台管理系统更加高效和易用。 首先,您需要安装Vue CLI,这是一个官方提供的脚手架工具,可以帮助您快速搭建和管理Vue项目。您可以使用以下命令进行安装: ``` npm install -g @vue/cli ``` 接下来,您可以使用Vue CLI创建一个新的Vue项目。在命令行中运行以下命令: ``` vue create my-admin-system ``` 然后,您可以选择手动配置项目的特性,或者选择默认配置。建议选择默认配置,以便快速开始。 一旦项目创建完成,进入项目目录并启动开发服务器: ``` cd my-admin-system npm run serve ``` 现在,您可以开始开发您的后台管理系统了。下面是一些常用的技术和功能,可以帮助您构建一个功能完善的系统: 1. 使用Vue Router进行路由管理:Vue Router允许您通过定义路由来管理不同页面之间的导航。 2. 使用Vuex进行状态管理:Vuex是Vue的官方状态管理库,可以帮助您管理应用程序的共享状态。 3. 使用Element Plus或Ant Design Vue等UI框架:这些UI框架提供了丰富的组件和样式,可以帮助您快速构建漂亮的用户界面。 4. 使用axios或Fetch进行数据请求:这些库可以帮助您与后端API进行通信,从而获取和提交数据。 5. 使用ESLint和Prettier进行代码规范和格式化:这些工具可以帮助您保持代码的一致性和可读性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值