vue3.2后台管理系统搭建学习笔记
1、创建项目更改版本1-1、创建项目vue create vue3-admin1-2、更改vue版本更改前更改后2、开发项目1、按需引入element-plus1、安装npm install element-plus --save2、按需导入(自动导入)cnpm install -D unplugin-vue-components unplugin-auto-import3、Webpack配置(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)
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>
更多推荐
已为社区贡献19条内容
所有评论(0)