vue后台管理系统 vue3+vite+pinia+elementui+axios下

news2024/11/14 2:47:27

这篇文章来完成用户组件 也就是增删改查表格

用户页面信息页面由头部,表格,和弹框组成
<template>

<div class="user-header">

<el-button type="primary" @click="handleAdd">新增</el-button>

<el-form :inline="true" :model="formInline">

<el-form-item label="请输入">

<el-input placeholder="请输入用户名" v-model="formInline.keyWord">

</el-input>

</el-form-item>

<el-form-item>

<el-button type="primary" @click="handleSearch">搜索</el-button>

</el-form-item>

</el-form>

</div>

<div class="table">

<el-table :data="tableData" style="width:100%">

<el-table-column

v-for="item in tableLabel"

:key="item.prop"

:width="item.width ? item.width :125"

:prop="item.prop"

:label="item.label"

></el-table-column>

<el-table-column fixed="right" label="Operations" min-width="150">

<template #="scope">

<el-button type="primary" size="small" @click="handleEdit(scope.row)">

编辑

</el-button>

<el-button type="danger" size="small" @click="handleDelete(scope.row)">

删除

</el-button>

</template>

</el-table-column>

</el-table>

<el-pagination

class="pager"

background

layout="prev, pager,next"

size="small"

:total="config.total"

@current-change="handleChange"

>

</el-pagination>

</div>

<el-dialog

v-model="dialogVisible"

:title="action === 'add' ? '新增用户' : '编辑用户'"

width="35%"

:before-close="handleClose"

>

<!--需要注意的是设置了:inline="true",

会对el-select的样式造成影响,我们通过给他设置一个class=select-clearn

在css进行处理-->

<el-form :inline="true" :model="formUser" :rules="rules" ref="userForm">

<el-row>

<el-col :span="12">

<el-form-item label="姓名" prop="name">

<el-input v-model="formUser.name" placeholder="请输入姓名" />

</el-form-item>

</el-col>

<el-col :span="12">

<el-form-item label="年龄" prop="age">

<el-input v-model.number="formUser.age" placeholder="请输入年龄" />

</el-form-item>

</el-col>

</el-row>

<el-row>

<el-col :span="12">

<el-form-item class="select-clearn" label="性别" prop="sex">

<el-select v-model="formUser.sex" placeholder="请选择">

<el-option label="男" value="1" />

<el-option label="女" value="0" />

</el-select>

</el-form-item>

</el-col>

<el-col :span="12">

<el-form-item label="出生日期" prop="birth">

<el-date-picker

v-model="formUser.birth"

type="date"

placeholder="请输入"

style="width: 100%"

/>

</el-form-item>

</el-col>

</el-row>

<el-row>

<el-form-item

label="地址"

prop="addr"

>

<el-input v-model="formUser.addr" placeholder="请输入地址" />

</el-form-item>

</el-row>

<el-row style="justify-content: flex-end">

<el-form-item>

<el-button type="primary" @click="handleCancel">取消</el-button>

<el-button type="primary" @click="onSubmit">确定</el-button>

</el-form-item>

</el-row>

</el-form>

</el-dialog>

</template>


<style lang="scss" scoped>

.user-header{

display:flex;

justify-content: space-between;

}

.table{

position:relative;

height:520px;

.pager{

position:absolute;

right:10px;

bottom:30px;

}

.el-table{

width:100%;

height:500px;

}

}

</style>
在api文件夹中api.js文件加入
getUserData(data){

return request({

url:"/home/getUserData",

method:'get',

data

})

},

deleteUser(data){

return request({

url:"/home/deleteUser",

method:'get',

data

})

},

addUser(params) {

return request({

url: '/user/addUser',

method: 'post',

data: params

})

},

editUser(params) {

return request({

url: '/user/editUser',

method: 'post',

data: params

})

},
在mock.js中加入
Mock.mock(/api\/home\/getUserData/,"get",userApi.getUserList)

Mock.mock(/api\/home\/deleteUser/,"get",userApi.deleteUser)

Mock.mock(/api\/user\/addUser/,"post", userApi.createUser)

Mock.mock(/api\/user\/editUser/, "post",userApi.updateUser)
mockData文件夹中加入user.js模拟数据
import Mock from 'mockjs'

  

// get请求从config.url获取参数,post从config.body中获取参数

function param2Obj(url) {

const search = url.split('?')[1]

if (!search) {

return {}

}

return JSON.parse(

'{"' +

decodeURIComponent(search)

.replace(/"/g, '\\"')

.replace(/&/g, '","')

.replace(/=/g, '":"') +

'"}'

)

}

  

let List = []

const count = 200

//模拟200条用户数据

for (let i = 0; i < count; i++) {

List.push(

Mock.mock({

id: Mock.Random.guid(),

name: Mock.Random.cname(),

addr: Mock.mock('@county(true)'),

'age|18-60': 1,

birth: Mock.Random.date(),

sex: Mock.Random.integer(0, 1)

})

)

}

  
  

export default {

/**

* 获取列表

* 要带参数 name, page, limt; name可以不填, page,limit有默认值。

* @param name, page, limit

* @return {{code: number, count: number, data: *[]}}

*/

getUserList: config => {

//limit默认是10,因为分页器默认也是一页10个

const { name, page = 1, limit = 10 } = param2Obj(config.url)

const mockList = List.filter(user => {

//如果name存在会,根据name筛选数据

if (name && user.name.indexOf(name) === -1) return false

return true

})

//分页

const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))

return {

code: 200,

data: {

list: pageList,

count: mockList.length, //数据总条数需要返回

}

}

},

//在原来的export default 中添加

  

/**

* 删除用户

* @param id

* @return {*}

*/

deleteUser: config => {

const { id } = param2Obj(config.url)

  

if (!id) {

return {

code: -999,

message: '参数不正确'

}

} else {

List = List.filter(u => u.id !== id)

return {

code: 200,

message: '删除成功'

}

}

},

/**

* 增加用户

* @param name, addr, age, birth, sex

* @return {{code: number, data: {message: string}}}

*/

createUser: config => {

const { name, addr, age, birth, sex } = JSON.parse(config.body)

List.unshift({

id: Mock.Random.guid(),

name: name,

addr: addr,

age: age,

birth: birth,

sex: sex

})

return {

code: 200,

data: {

message: '添加成功'

}

}

},

/**

* 修改用户

* @param id, name, addr, age, birth, sex

* @return {{code: number, data: {message: string}}}

*/

updateUser: config => {

const { id, name, addr, age, birth, sex } = JSON.parse(config.body)

const sex_num = parseInt(sex)

List.some(u => {

if (u.id === id) {

u.name = name

u.addr = addr

u.age = age

u.birth = birth

u.sex = sex_num

return true

}

})

return {

code: 200,

data: {

message: '编辑成功'

}

}

}

}
有了相应的请求数据 就可以实现增删改差以及相应的功能
<script setup>
//
import {ref,getCurrentInstance,onMounted,reactive,nextTick} from 'vue'

const tableLabel = reactive([

{

prop:'name',

label:'姓名'

},

{

prop:'age',

label:'年龄'

},

{

prop:'sexLabel',

label:'性别'

},

{

prop:'birth',

label:'出生日期',

width:200

},

{

prop:'addr',

label:'地址',

width:400

},

])

const tableData =ref([])

const {proxy} = getCurrentInstance()

const getUserData = async () => {

let data = await proxy.$api.getUserData(config)

// console.log(data)

tableData.value = data.list.map(item =>({

...item,

sexLabel: item.sex === 1 ? '男' : '女'

}))

config.total = data.count

}

const formInline = reactive({

keyWord:''

})

const config =reactive({

name:'',

total:0,

page:1

})

const handleSearch=() => {

config.name = formInline.keyWord

getUserData()

}

const handleChange = (page) => {

config.page = page

getUserData()

}

const handleDelete =(val) => {

ElMessageBox.confirm("你确定要删除吗?").then(async () => {

await proxy.$api.deleteUser({id:val.id})

ElMessage({

showClose:true,

message:'删除成功',

type:'success'

})

getUserData()

}

)

}

const action =ref('add')

const dialogVisible = ref(false)

const formUser = reactive({

sex:"1"

})

const rules = reactive({

name: [{ required: true, message: "姓名是必填项", trigger: "blur" }],

age: [

{ required: true, message: "年龄是必填项", trigger: "blur" },

{ type: "number", message: "年龄必须是数字" },

],

sex: [{ required: true, message: "性别是必选项", trigger: "change" }],

birth: [{ required: true, message: "出生日期是必选项" }],

addr:[{ required: true, message: '地址是必填项' }]

})

  

//这个方法之前定义过

const handleAdd = () => {

action.value="add"

//打开对话窗

dialogVisible.value=true

}

  

//对话框右上角的关闭事件

const handleClose = () => {

//获取到表单dom,执行resetFields重置表单

// proxy.$refs["userForm"].resetFields()

//关闭对话框

dialogVisible.value=false

}

  

//对话框右下角的取消事件

const handleCancel = () => {

// proxy.$refs["userForm"].resetFields()

dialogVisible.value=false

}

//格式化日期,格式化为:1997-01-02这种

const timeFormat = (time) => {

var time = new Date(time);

var year = time.getFullYear();

var month = time.getMonth() + 1;

var date = time.getDate();

function add(m) {

return m < 10 ? "0" + m : m;

}

return year + "-" + add(month) + "-" + add(date);

}

  

const onSubmit = () => {

//执行userForm表单的validate进行规则校验,传入一个回调函数,回调函数会接受到一个是否校验通过的变量

proxy.$refs["userForm"].validate(async (valid)=>{

//如果校验成功

if (valid) {

//res用于接收添加用户或者编辑用户接口的返回值

let res=null

//这里无论是新增或者是编辑,我们都要对这个日期进行一个格式化

//如果不是1997-01-02这种格式,使用timeFormat方法进行格式化

formUser.birth=/^\d{4}-\d{2}-\d{2}$/.test(formUser.birth) ? formUser.birth : timeFormat(formUser.birth)

//如果当前的操作是新增,则调用新增接口

if (action.value == "add") {

res = await proxy.$api.addUser(formUser);

}else if(action.value == "edit"){

res = await proxy.$api.editUser(formUser)

}

//如果接口调用成功

if(res){

//关闭对话框,重置表单,重新请求用户数据

dialogVisible.value = false;

proxy.$refs["userForm"].resetFields()

getUserData()

}

//如果校验失败

}else {

ElMessage({

showClose: true,

message: "请输入正确的内容",

type: "error",

})

}

})

}

const handleEdit = (val) => {

action.value="edit"

dialogVisible.value=true

nextTick(()=>{

//因为在第一次显示弹窗的时候form组件没有加载出来,如果直接对formUser赋值,这个值会作为form表单的初始值

//所以使用nextTick,赋值的操作在一个微任务中,这样就可以避免在from表单加载之前赋值

Object.assign(formUser,{...val,sex:""+val.sex})

//这里需要改变sex数据类型,是因为el-option的value有类型的校验

})

}


onMounted(() => {

getUserData()

})

</script>

用户表格实现完成
请添加图片描述

接下来实现面包屑组件

<template>

<div class="tags">

<el-tag

v-for="(tag,index) in tags"

:key="tag.name"

:closable="tag.name !== 'home'"

:effect = "route.name === tag.name ? 'dark': 'plain'"

@click="handleMenu(tag)"

@close="handleClose(tag,index)"

>{{tag.label}}

</el-tag>

</div>

</template>

  

<script setup>

import {ref,computed} from 'vue'

import {useRoute,useRouter} from 'vue-router'

import {useAllDataStore} from '../stores/index.js'

const router = useRouter()

const store = useAllDataStore()

const tags = computed(()=>store.$state.tags)

const route = useRoute()

const handleMenu = (tag) => {

router.push(tag.name)

store.selectMenu(tag)

}

const handleClose = (tag,index) => {

store.updateMenu(tag)

  

if(index === store.$state.tags.length){

store.selectMenu(tags.value[index-1])

router.push(tags.value[index-1].name)

}else{

store.selectMenu(tags.value[index])

router.push(tags.value[index].name)

}

}

</script>

  

<style lang="scss" scoped>

.tags{

margin:20px 0 0 20px;

}

el-tag{

margin-right:10px;

}

</style>
在store/index.js中添加内容
export const useAllDataStore = defineStore('allData', {

state:() => {

return {

isCollapse: true,

tags:[

{

path:'/home',

name:'home',

label:'首页',

icon:'home',

}

],

currentMenu:null,

}

},

actions: {

selectMenu(val){

if(val.name === "home"){

this.currentMenu =null;

}else{

this.currentMenu = val

// console.log()

let index = this.tags.findIndex((item)=> item.name === val.name);

  

index === -1 ? this.tags.push(val) : "";

}

},

updateMenu(tag){

let index =this.tags.findIndex((item) => item.name === tag.name)

this.tags.splice(index,1)

},
搭建登陆页面 动态添加路由 有两个账号 账号一admin密码123456与账号二oner密码123456

请添加图片描述

请添加图片描述

<template>

<div class="body-login">

<el-form :model="loginForm" class="login-container">

<h1>欢迎登陆</h1>

<el-form-item>

<el-input type="input" placeholder="请输入账号"

v-model="loginForm.username"></el-input>

</el-form-item>

<el-form-item>

<el-input type="input" placeholder="请输入密码"

v-model="loginForm.password"></el-input>

</el-form-item>

<el-form-item>

<el-button type="primary" @click="handleLogin"> 登陆</el-button>

</el-form-item>

</el-form>

</div>

</template>

  

<script setup>

import {reactive,getCurrentInstance} from 'vue'

import {useAllDataStore} from '../stores/index.js'

import {useRouter} from 'vue-router'

  

const loginForm = reactive({

username:'',

password:'',

})

const {proxy} = getCurrentInstance()

const store = useAllDataStore()

const router = useRouter()

const handleLogin = async () => {

const res = await proxy.$api.getMenu(loginForm)

store.updateMenuList(res.menuList)

store.$state.token = res.token

store.addMenu(router)

router.push('/home')

}

</script>

  

<style lang="scss" scoped>

.body-login{

width:100%;

height:100%;

background-image:url("../assets/images/background.png");

background-size:100%;

overflow:hidden;

}

.login-container{

width:400px;

background-color:#fff;

border:1px solid #eaeaea;

border-radius:15px;

padding:35px 35px 15px 35px;

box-shadow:0 0 25px #cacaca;

margin:250px auto;

h1{

text-align:center;

margin-bottom:20px;

color:#505450;

}

:deep(.el-form-item__content){

justify-content:center;

}

}

</style>

更新store/index.js代码

import { defineStore } from 'pinia'
export const useAllDataStore = defineStore('allData', {

state:() => {

return {

isCollapse: true,

tags:[

{

path:'/home',

name:'home',

label:'首页',

icon:'home',

}

],

currentMenu:null,

menuList:[],

token:"",

routerList:[],

}

},

actions: {

selectMenu(val){

if(val.name === "home"){

this.currentMenu =null;

}else{

this.currentMenu = val

// console.log()

let index = this.tags.findIndex((item)=> item.name === val.name);

  

index === -1 ? this.tags.push(val) : "";

}

},

updateMenu(tag){

let index =this.tags.findIndex((item) => item.name === tag.name)

this.tags.splice(index,1)

},

updateMenuList(val){

this.menuList =val;

},

//动态添加路由

  

addMenu(router,type){

const menu = this.menuList

const module = import.meta.glob("../views/**/*.vue");

const routeArr = [];

menu.forEach((item)=>{

if(item.children){

item.children.forEach((val) => {

let url = `../views/${val.url}.vue`;

val.component = module[url];

routeArr.push(...item.children);

})

}else{

let url = `../views/${item.url}.vue`;

item.component = module[url];

routeArr.push(item)

}

})

this.routerList= [];

let routers = router.getRoutes()

routers.map((item) => {

if(item.name === 'Main' || item.name ==="Login"){

return ;

}else{

router.removeRoute(item.name)

}

})

routeArr.forEach(

(item) => {

this.routerList.push(router.addRoute("Main",item))

}

)

// this.routerList= [];

// let routers = router.getRoutes()

// routers.map((item) => {

// if(item.name === 'main' || item.name ==="login"){ return ;}else{

// router.removeRoute(item.name)

// }


// })

},

clean(){

this.routerList.forEach((item) => {

if(item) item()

})

this.$reset()

}

},

})

项目到此就结束了
请添加图片描述

请添加图片描述

源码地址:https://github.com/zhaimou/vue3-admin

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

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

相关文章

(2024,LlamaGen,Llama,自回归下一token预测,模型扩展)自回归模型优于扩散:Llama 用于可扩展图像生成

Autoregressive Model Beats Diffusion: Llama for Scalable Image Generation 目录 0. 摘要 1. 引言 2. 自回归模型在图像生成中的应用 2.1 概述 2.2 图像 tokenizer 2.3 自回归模型生成图像 2.4 规模扩展 2.5 服务 3. 实验 5. 结论 0. 摘要 我们介绍 LlamaGen&…

使用mid360从0开始搭建实物机器人入门级导航系统,基于Fast_Lio,Move_Base

Introduction 本文原本只是自己在拿到mid360后&#xff0c;开始进行开发过程的一些问题和学习的记录。毕竟实物和仿真还是有很多不同&#xff0c;且由于碰到的问题也比较多&#xff0c;READEME也越来越详细&#xff0c;所以就干脆整合起来&#xff0c;做成了一篇使用mid360的搭…

嵌入式初学-C语言-十一

#接嵌入式初学-C语言-十,以及部分例题# 循环结构 break和continue break 功能&#xff1a; 1. 用在switch中&#xff0c;用来跳出switch的case语句&#xff1b;如果case没有break&#xff0c;可能会产生case穿透。 2. 用在循环中&#xff08;while、do..while、for..&#…

Linux压缩/解压缩工具:tar命令详解

目录 一、概述 二、基本概念 三、tar命令的基本语法 1、基本语法 2、常用选项 3、帮助获取 四、用法和示例 1. 创建归档文件 &#xff08;1&#xff09;用法 &#xff08;2&#xff09;示例 2. 解压缩归档文件 &#xff08;1&#xff09;用法 &#xff08;2&#…

经典非比较排序—计数排序的Java实现方式

目录 1.具体思路&#xff1a; 2.代码实现&#xff1a; 3.代码分析 4.示例测试&#xff1a; 测试源码&#xff1a; 测试结果&#xff1a; 计数排序&#xff0c;又被称为鸽巢原理&#xff0c;属于桶排序的一种&#xff0c;其本质是通过哈希映射思想&#xff0c;设定计数数组输入以…

音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现

音视频入门基础&#xff1a;WAV专题系列文章&#xff1a; 音视频入门基础&#xff1a;WAV专题&#xff08;1&#xff09;——使用FFmpeg命令生成WAV音频文件 音视频入门基础&#xff1a;WAV专题&#xff08;2&#xff09;——WAV格式简介 音视频入门基础&#xff1a;WAV专题…

论文翻译:Large Language Models in Education: Vision and Opportunities

Large Language Models in Education: Vision and Opportunities 文章目录 教育中的大型语言模型&#xff1a;愿景与机遇摘要1 引言2. 教育与LLMsA. 教育背景B. LLMs背景C. 智能教育D. 教育中的LLMs 3. EduLLMs的关键技术4. LLM赋能教育A. LLMs在教育中的应用B. LLMs下教育的特…

免费分享:全国传统村落空间分布数据(附下载方法)

数据简介 本数据是在中国传统村落名录的基础上&#xff0c;通过地理编码&#xff0c;制作成具有空间坐标信息的矢量数据。 数据属性 数据名称&#xff1a;全国传统村落空间分布数据数据时间&#xff1a;2012年至今&#xff0c;更新至第五批空间位置&#xff1a;全国数据格式&…

opencascade AIS_TrihedronOwner源码学习对象的实体所有者用于选择管理

opencascade AIS_TrihedronOwner 前言 AIS_Trihedron对象的实体所有者用于选择管理。 在OpenCascade的AIS&#xff08;交互对象框架&#xff09;中&#xff0c;管理类似AIS_Trihedron的对象的选择涉及理解如何处理实体&#xff08;或所有者&#xff09;以进行选择。 方法 1…

【单片机毕业设计选题24095】-基于手机端的电池电压采集系统

系统功能: 系统上电后&#xff0c;OLED显示三组18650锂电池电压。 第一行显示第一组锂电池电压 第二行显示第二组锂电池电压 第三行显示第三组锂电池电压 第四行显示电压设定阈值 短按B4按键增加电压设定阈值 短按B5按键减小电压设定阈值 如果任意一组电池电压小于电压…

红酒与季节:品味四季的风情

四季轮转&#xff0c;岁月更迭&#xff0c;每个季节都有其不同的韵味与风情。当定制红酒洒派红酒&#xff08;Bold & Generous&#xff09;与四季相遇&#xff0c;它们共同编织出一幅幅美丽的味觉画卷&#xff0c;让我们在品味中感受四季的风情。 一、春之序曲&#xff1a…

【ESP01开发实例】-ESP-01驱动DHT11和DH22传感器

ESP-01驱动DHT11和DH22传感器 文章目录 ESP-01驱动DHT11和DH22传感器1、DHT11/DHT22传感器介绍2、LCD1602介绍3、硬件准备与接线4、代码实现本主题介绍如何使用 DHT11 和 DHT22 相对湿度和温度传感器与 ESP8266 ESP-01 Wi-Fi 模块,将相对湿度和温度的测量值显示在 162 LCD 屏幕…

C++回顾——多态

一、定义 ①从广义上说,多态性是指&#xff1a;一段程序能够处理多种类型对象的能力。在C语言中,这种多态性可以通过包含多态4种形式来实现。强制多态、重载多态、类型参数化多态、包含多态。 ②从实现上来说&#xff0c;多态的分类&#xff1a;静态多态、动态多态。 二、 广…

云原生安全检测工具(容器安全、trivy、veinmind-tools)

目录 trivy 功能 扫描方式&目标 安装 漏洞库下载及更新 漏洞库扫描原理 官方漏洞库生成 使用 扫描镜像 扫描 wazuh-daemonset 所有漏洞 扫描 wazuh-daemonset 高危、严重漏洞(漏洞过滤) 扫描文件系统 Git 存储库漏洞扫描 报告输出格式 veinmind-tools 1.…

Vue的学习(一)

目录 一、Vue的介绍 二、指令 1.v-text 2.v-html 3.v-show 4.v-if 1&#xff09;v-if与v-show的区别 5.v-else 6.v-else-if 7.v-for 1)v-for 与v-if的优先级 8.v-on&#xff1a;click 9.v-bind 10.v-model 三、表单修饰符 1. .lazy 修饰符 2. .number修饰符…

算法通关:014_2:用队列实现栈

文章目录 题目思路问题总结导包有问题&#xff0c;java提供的有关队列的方法不能调用。queue.add()和queue.offer(n)&#xff0c;这两个方法有什么区别什么叫用LinkedList实现队列 代码结果 题目 LeetCode 225. 用队列实现栈 思路 问题总结 导包有问题&#xff0c;java提供的…

银行业数据分析的关键应用场景探索

“ 随着数字经济的蓬勃发展&#xff0c;银行业正经历着前所未有的变革。从传统的存贷款业务到如今的数字化服务&#xff0c;数据分析在银行中扮演着核心角色。本文将探讨银行业数据分析的主要场景&#xff0c;以及选型适配银行业分析需求的数据库技术。” 国内的商业化银行大体…

二维码直达App,Xinstall为你打通运营任督二脉

在移动互联网时代&#xff0c;App的推广和运营显得尤为重要。然而&#xff0c;许多企业在投入大量资源进行App推广和运营时&#xff0c;总会遇到一些棘手的问题&#xff0c;如用户转化率低、数据分析困难等。今天&#xff0c;我们要为大家揭秘一个神奇的助手——Xinstall&#…

RocketMQ5.0 生产者

生产者消息类型&#xff1a; 延迟队列的生产者 package mainimport ("context""fmt""github.com/apache/rocketmq-clients/golang/v5""github.com/apache/rocketmq-clients/golang/v5/credentials"errgroup2 "golang.org/x/sync…