前言
- 小程序开发定制这是一个适合初学Vue3的小白(比如本人,自学小白,目前大二,小程序开发定制过完暑假就大三了,小程序开发定制想要做一个项目,小程序开发定制复习一些所学的内容)小程序开发定制做的小型项目,小程序开发定制项目是后台管理系统。小程序开发定制所用涉及到的技术栈为Vue3、Vue-cli、Vue-router、Vuex、element-ui、echarts、axios、mysql、node.js其中***echarts、axios、mysql、node.js***小程序开发定制只是简单的使用,小程序开发定制不涉及复杂操作。***element-ui***小程序开发定制里面的表单等,小程序开发定制也没有进行相关检验,小程序开发定制总体功能只是想让初学小程序开发定制者有一个项目体验,小程序开发定制或多或少美中不足。小程序开发定制项目中的数据全是动态的,[若是没有node和mysql小程序开发定制相关基础请去B站观看相关视频]。备注(不是打广告,本人是自学,没怎么写过文章,不进行商用等)。其中mysql下载教学在P59,相关sql语句以及在node中的使用在P60-P70
- 图片链接在CSDN显示失败,请在掘金上观看效果 点击链接
Vue项目代码区
初始化项目
- vue create 项目名称(比如shop)
- 我们手动配置,选择***Manually select features***
- 选择Babel(默认自动选择) Roter Vuex CSS-processors 空格是可以选择或者取消选择的配置,此处我们不要选择Linter / Formatter
- 选择3.×
- Use history mode for router 选择 n
- Pick a CSS pre-processor 选择less
- Where do you prefer placing config for Babel, ESLint, etc.? 选择In dedicated config files
- Save this as a preset for future projects? 选择N
- 启动项目 进入创建的目录(比如shop)中 npm run serve
vue create 项目名称
- 1
下载相关包
//安装element-plus包npm install element-plus --save//axios包npm install axios --save// 安装vuex包npm install axios --save// 安装echarts包npm install echarts --save
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
初始化组件
首先将views目录下的About.vue文件以及HomeView.vue文件和components目录下的HelloWorld.vue文件删除。然后清除App.vue文件里面代码,放置如下代码:
App.vue
App.vue代码
//App.vue<template> <div> <router-view></router-view> </div></template><script></script><style lang="less">* { margin: 0; padding: 0;}</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在components目录下新建一个Layout的vue文件,即Layout.vue。
Layout进行页面整体布局,利用的Container布局容器以及Menu菜单。当用户点击退出时,会清除掉token,用户需要重新登录。为什么要在Layout.vue中就拿Map.vue中的数据而不是在Map.vue中去获取? 作者试过了,当我们启动这个项目时,第一次"表格展示"的"echarts"()时,展示不了图表,没有渲染上去(作者猜测,可能是使用了Vuex的原因)。当我们切换到"员工管理"的"员工列表后"我们再点击"表格展示"的"echarts"我们就会发现此时页面会出现表格,将数据渲染到DOM上了。为了避免这种情况,我们知道Vuex就是管理数据的,Vuex能够跨组件使用数据,所以我们就在Layout.vue中就请求数据,然后直接在Map.vue中进行使用。
Layout.vue
//Layout.vue<template> <div class="common-layout"> <el-container> <el-header> <div class="header"> <div class="header_left"> <div class="img"> <img src="../assets/logo.png" alt="" /> </div> <div class="title"> <div> <h1>最强节点人力资源管理系统</h1> </div> </div> </div> <div class="header_right"> <el-row> <el-button type="primary" round @click="logOut">退出</el-button> </el-row> </div> </div> </el-header> <el-container> <el-aside width="200px"> <el-menu default-active="2" class="el-menu-vertical-demo" @open="handleOpen" @close="handleClose" background-color="none" text-color="#fff" router > <el-sub-menu index="1"> <template #title> <el-icon><Avatar /></el-icon> <span>员工管理</span> </template> <el-menu-item index="/layout/usersList">员工列表</el-menu-item> </el-sub-menu> <el-sub-menu index="2"> <template #title> <el-icon><Collection /></el-icon> <span>表格展示</span> </template> <el-menu-item index="/layout/map">echarts</el-menu-item> </el-sub-menu> </el-menu> </el-aside> <el-main> <router-view></router-view> </el-main> </el-container> </el-container> </div></template><script>import { useRouter } from "vue-router";import { useStore } from "vuex";export default { setup() { // 此处 直接请求Map中的数据,不然第一次直接点击Map组件,将得不到数据展示。· // 因此就再点击map路由前,先获取到Vuex中的数据所以,Vuex本来就是用于跨组件使用数据,然后我们就可以在Map组件中可以使用 const store = useStore(); store.dispatch("asyncGetSex"); store.dispatch("asyncgetsalaryRate"); store.dispatch("asyncgetpersonalSalarytotals"); const router = useRouter(); function logOut() { // 清除本地储存的token localStorage.removeItem("token"); alert("退出成功"); // 跳转到登录页面 router.push("/login"); } return { logOut, }; },};</script><style lang="less" scoped>.el-header { height: 6rem; background-color: #242a31;}.el-aside { height: 100vh; background-color: #2d363f;}.el-header,.el-aside { color: #fff;}.header { display: flex; justify-content: space-between; align-items: center; .header_left { display: flex; .img { width: 20%; height: 30%; margin-right: 5rem; img { display: block; width: 100%; height: 100%; } } } .title { display: flex; justify-content: center; align-items: center; }}a { color: #fff; text-decoration: none;}.header_right { display: flex; .img { width: 20%; height: 30%; margin-right: 5rem; img { width: 100%; height: 100%; } }}</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
退出效果如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BWxtORGi-1659418267056)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f77210c3946f477ea92cd267dbef0ced~tplv-k3u1fbpfcp-.image?)]
在views目录下,新建三个vue文件,分别是Login.vue,Map.vue,Userlist.vue。
Login.vue是用于管理人员进行登录的。当管理人员登录成功后,我们生成相对应的token保存在localstrage中,这样子,管理人员哪怕是刷新都不会需要进行重新登陆、token是用了jsonwebtoken这个第三方包生成的动态数据。
Login.vue
//Login.vue<template> <div class="login-container"> <div class="center-form"> <el-form class="login-form"> <el-form-item prop="userNumber" label="登录账号"> <el-input v-model="userNumber" type="text" /> </el-form-item> <el-form-item prop="password" label="登录密码"> <el-input v-model="password" type="password" /> </el-form-item> <el-form-item> <el-button type="primary" @click="submit">登录</el-button> <el-button>忘记密码</el-button> <el-button>重置</el-button> </el-form-item> </el-form> </div> </div></template><script>import { reactive, toRefs, ref } from "vue";import { login } from "@/util/api/employee.js";import { useRouter } from "vue-router";export default { setup() { let data = reactive({ userNumber: "", password: "", }); let router = useRouter(); async function submit() { let res = await login("login", data.userNumber, data.password); console.log(router); console.log(res.data); if (res.data.status == 200) { alert(res.data.msg); localStorage.setItem("token", res.data.token); router.push("/"); } else { localStorage.removeItem("token"); } } return { ...toRefs(data), submit, }; },};</script><style lang="less" scoped>.login-container { width: 100%; height: 50rem; background-color: skyblue; position: relative; .center-form { position: absolute; top: 50%; left: 50%; padding: 1rem; transform: translate(-50%, -50%); border-radius: 0.5rem; background-color: aliceblue; .login-form { button { display: block; margin: 1rem; } } }}</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
Userlist.vue
Userlist.vue是用于展示员工数据,使用了的Pagination分页组件以及Table表格组件。Userlist.vue是这个项目的核心,它展示了增删改查功能,以及对拿到的数据进行分页。笔者在这里将Element Plus一些功能删除了,进行了简化。
Userlist.vue并没有使用Vuex,为什么? 首先笔者想要换一种写法,就在Userlist.vue中单纯进行异步请求,通过axios拿到数据。其次,数据并没有跨组件进行使用,只是单单在Userlist.vue中使用了,就没必要使用Vuex了。为什么作者要将// let showUserList = ref([]); // let dataShow = ref();注释? 我们知道ref和reactive都可以对值进行操作。但是reactive只能对引用型数据操作,而ref不仅可以对基本数据类型操作,还可以对引用型数据操作。笔者两种方式都试了,因此为了传统上的使用,笔者将涉及到showUserList和dataShowref的写法代码都注释了。如果大家有兴趣,不妨也可以使用。特别注意,如果大家使用ref的写法,那么需要将 el-table :data=“showData.dataShow” border style="width: 100%"改为 el-table :data=“dataShow” border style=“width: 100%” 将 :total="showData.showUserList.length"改为:total="showUserList.length"
//Userlist.vue<template> <div class="usersList-container"> <div class="sticky"> <el-form :inline="true" :model="formInline" class="demo-form-inline"> <div class="left-input"> <el-form-item label="查询员工"> <el-input v-model="formInline.user" placeholder="请输入员工号或者员工名" ></el-input> </el-form-item> </div> <el-form-item> <el-button type="primary" @click="onSubmit">查询</el-button> </el-form-item> <el-form-item label="添加员工"> <el-button type="primary" @click="dialogTableVisible">添加</el-button> </el-form-item> </el-form> </div> <div class="block"> <span class="demonstration">完整功能</span> <div class="tanchukuang" v-if="flag"> <form action="" class="form"> <div> <label for="">工号<input type="text" v-model="I" /> <br /></label> </div> <div> <label for="">姓名<input type="text" v-model="N" /> <br /></label> </div> <div> <label for="">电话<input type="text" v-model="P" /> <br /></label> </div> <div> <label for="">性别<input type="text" v-model="S" /> <br /></label> </div> <div> <label for="">薪资<input type="text" v-model="Sa" /> <br /></label> </div> <div> <label for="">职位<input type="text" v-model="Po" /> <br /></label> </div> <div class="btn-left"> <el-button @click="noSee">取消</el-button> <el-button type="primary" @click="submit">提交</el-button> </div> </form> </div> <el-table :data="showData.dataShow" border style="width: 100%"> <el-table-column fixed prop="empID" label="ID" width="150"> </el-table-column> <el-table-column prop="employeeName" label="姓名" width="120"> </el-table-column> <el-table-column prop="employeePhone" label="手机号" width="120"> </el-table-column> <el-table-column prop="employeeSex" label="性别" width="120"> </el-table-column> <el-table-column prop="employeeSalary" label="薪资" width="120"> </el-table-column> <el-table-column prop="employeePosition" label="职位" width="120"> </el-table-column> <el-table-column fixed="right" label="操作" width="250"> <template #default="scope"> <el-button>查看</el-button> <el-button type="primary" size="small" @click="handleEdit(scope.$index, scope.row)" >编辑</el-button > <el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)" >删除</el-button > </template> </el-table-column> </el-table> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-sizes="[2, 4, 6, 7, 8]" layout="total, sizes, prev, pager, next, jumper" :total="showData.showUserList.length" > </el-pagination> </div> <div class="update" v-if="up_flag"> <form action="" class="form"> <div> <label for="" >电话<input type="text" v-model="upPhone" /> <br /></label> </div> <div> <label for="" >薪资<input type="text" v-model="upSalary" /> <br /></label> </div> <div> <label for="" >职位<input type="text" v-model="upPosition" /> <br /></label> </div> <div class="btn-left"> <el-button @click="hiddenUpdate">取消</el-button> <el-button type="primary" @click="updateEmp">提交</el-button> </div> </form> </div> </div></template><script>import { reactive, ref, toRefs } from "vue";import { getEmployees, addEmployees, deleteEmployees, updateEmployees,} from "@/util/api/employee.js";export default { setup() { const showData = reactive({ showUserList: [], dataShow: [], }); // let showUserList = ref([]); // let dataShow = ref(); let data = getEmployees("employee"); data.then((res) => { // dataShow.value = showUserList.value = res.data; showData.dataShow = showData.showUserList = res.data; }); let flag = ref(false); let person = reactive({ I: "", N: "", P: "", S: "", Sa: "", Po: "", }); let formInline = reactive({ user: "", region: "", }); let dialogTableVisible = () => { flag.value = true; }; function submit() { addEmployees("add", person).then(() => { data = getEmployees("employee"); data.then((res) => { // dataShow.value = showUserList.value = res.data; showData.dataShow = showData.showUserList = res.data; console.log(dataShow.value); }); flag.value = false; }); } // 删除员工 function handleDelete(index, row) { deleteEmployees("delete", row.empID).then(() => { data = getEmployees("employee"); data.then((res) => { // dataShow.value = showUserList.value = res.data; showData.dataShow = showData.showUserList = res.data; console.log(dataShow.value); }); }); } // 更新员工信息 let up_flag = ref(false); function hiddenUpdate() { up_flag.value = false; } let getID = ref(); let upPhone = ref(); let upSalary = ref(); let upPosition = ref(); function updateEmp() { updateEmployees( "update", getID.value, upPhone.value, upSalary.value, upPosition.value ).then(() => { data = getEmployees("employee"); data.then((res) => { // dataShow.value = showUserList.value = res.data; showData.dataShow = showData.showUserList = res.data; console.log(dataShow.value); }); up_flag.value = false; }); } let page_number = ref(); const handleEdit = (index, row) => { up_flag.value = true; getID.value = row.empID; }; function handleSizeChange(val) { console.log(`每页 ${val} 条`); page_number.value = val; } function handleCurrentChange(val) { console.log(`这是第${val}页`); // dataShow.value = showUserList.value.slice( // (val - 1) * page_number.value, // val * page_number.value // ); showData.dataShow = showData.showUserList.slice( (val - 1) * page_number.value, val * page_number.value ); } function onSubmit() { console.log(); // dataShow.value = showUserList.value.filter( // (i) => i.empID == formInline.user || i.employeeName == formInline.user // ); showData.dataShow = showData.showUserList.filter( (i) => i.empID == formInline.user || i.employeeName == formInline.user ); console.log(dataShow.value); } function noSee() { flag.value = false; } return { // dataShow, // showUserList, showData, flag, up_flag, person, page_number, formInline, upPhone, upSalary, upPosition, handleEdit, handleDelete, ...toRefs(person), noSee, onSubmit, submit, dialogTableVisible, handleSizeChange, handleCurrentChange, hiddenUpdate, updateEmp, }; },};</script><style lang="less" scoped>.usersList-container { display: flex; justify-content: center; position: relative; overflow-wrap: wrap;}.margin { margin-bottom: 5rem;}.el-form-item__content { margin-right: 5rem;}.tanchukuang { z-index: 11; position: fixed; top: 30vh; left: 30vh;}.update { z-index: 12; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);}.form { display: flex; flex-direction: column; justify-content: space-between; align-content: center; background-color: skyblue; position: relative; div { padding: 1rem; label { color: #fff; } input { color: #000; font-size: 1rem; } } .btn-left { transform: translateX(30%); }}</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
- 288
- 289
- 290
- 291
- 292
- 293
- 294
- 295
- 296
- 297
- 298
- 299
- 300
- 301
- 302
- 303
- 304
- 305
- 306
- 307
- 308
- 309
- 310
- 311
- 312
- 313
Map.vue
Map.vue完全是echarts展示的可视化图,关于echarts的详细使用,请点击 为什么作者在这里使用echarts?作者主要是看了ehcarts相关教学,但是一直都没有使用过,正好做Vue3项目,配合node.js通过mysql拿到动态数据进行使用。此处需要注意的是,只有等待DOM渲染完毕,才能使用echarts,因此,echarts的相关操作,必须要在onMounted里面才行。关于为什么不在Map.vue中请求数据,笔者在Layout.vue中就已经详细阐述了,此处不再赘述。
//Map.vue<script>import * as echarts from "echarts";import { onMounted } from "vue";import { useStore } from "vuex";export default { setup(props) { const store = useStore(); onMounted(() => { console.log(store.state.sex_totals.man_totals); // 绘制图表 var myChart1 = echarts.init(document.querySelector("#left-main")); myChart1.setOption({ title: { text: "公司男女性别比", subtext: "加油一起干,都是好伙伴", left: "center", }, tooltip: { trigger: "item", }, legend: { orient: "vertical", left: "left", }, series: [ { name: "男女性别比例", type: "pie", radius: "50%", data: [ { value: `${store.state.sex_totals.man_totals}`, name: "男", }, { value: `${store.state.sex_totals.male_totals}`, name: "女", }, ], emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: "rgba(0, 0, 0, 0.5)", }, }, }, ], }); const arrSalaryRate = []; const axisX = [ ">=3k&&<4k", ">=4k&&<5k", ">=5k&&<6k", ">=6k&&<8k", ">=8k&&<10k", ">=10k&&<15k", ">=15k", ]; // 遍历store.state.salaryRate值,储存到arrSalaryRate数组中 Object.values(store.state.salaryRate).forEach((value, index) => { arrSalaryRate[index] = value; }); var myChart2 = echarts.init(document.querySelector("#center-main")); myChart2.setOption({ title: { text: "员工工资占比", subtext: "努力工作,加油升资", left: "center", }, tooltip: { trigger: "item", formatter: "{a} <br/>{b} : {c} ({d}%)", }, legend: { left: "center", top: "bottom", data: axisX, }, series: [ { name: "Area Mode", type: "pie", radius: [20, 140], center: ["50%", "50%"], roseType: "area", itemStyle: { borderRadius: 5, }, data: [ { value: arrSalaryRate[0], name: ">=3k&&<4k", }, { value: arrSalaryRate[1], name: ">=4k&&<5k", }, { value: arrSalaryRate[2], name: ">=5k&&<6k", }, { value: arrSalaryRate[3], name: ">=6k&&<8k", }, { value: arrSalaryRate[4], name: ">=8k&&<10k", }, { value: arrSalaryRate[5], name: ">=10k&&<15k", }, { value: arrSalaryRate[6], name: ">=15k", }, ], }, ], }); var months = []; // 存储每个月的工资 store.state.getPersonalSalaryTotal.arr.forEach((item, index) => { months[index] = [ `${item.Jan}`, `${item.Feb}`, `${item.Mar}`, `${item.Apr}`, `${item.May}`, `${item.Jun}`, `${item.Jul}`, `${item.Aug}`, `${item.Sep}`, `${item.Oct}`, `${item.Nov}`, `${item.Dec}`, ]; }); const myChart3_arr = []; store.state.getPersonalSalaryTotal.arr.forEach((item, index) => { myChart3_arr.push({ name: `${item.employeeName}`, type: "line", data: months[index], }); }); var myChart3 = echarts.init(document.querySelector("#right-main")); myChart3.setOption({ tooltip: { trigger: "axis", }, legend: { data: [ `${store.state.getPersonalSalaryTotal.arr[0].employeeName}`, `${store.state.getPersonalSalaryTotal.arr[1].employeeName}`, `${store.state.getPersonalSalaryTotal.arr[2].employeeName}`, `${store.state.getPersonalSalaryTotal.arr[3].employeeName}`, ], }, grid: { left: "3%", right: "4%", bottom: "3%", containLabel: true, }, xAxis: { type: "category", data: [ "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月", ], }, yAxis: { type: "value", }, series: myChart3_arr, }); }); },};</script><template> <div class="rolesList-container"> <div class="left-main" id="left-main"></div> <div class="center-main" id="center-main"></div> <div class="right-main" id="right-main"></div> </div></template><style lang="less" scoped>.rolesList-container { height: 100%; width: 100%; display: flex; .left-main { flex: 2; } .center-main { flex: 3; } .right-main { flex: 6; }}</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
首先将main.js中的代码清除,放置如下代码:
要引入import ElementPlus from “”;
import “element-plus/dist/index.css”;
以及注册全局组件
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
此外,还要注册ElementPlus,即app.use(ElementPlus)
//main.jsimport { createApp } from "vue";import App from "./App.vue";import router from "./router";import store from "./store";import ElementPlus from "element-plus";import "element-plus/dist/index.css";import * as ElementPlusIconsVue from "@element-plus/icons-vue";const app = createApp(App);for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component);}app.use(store).use(router).use(ElementPlus).mount("#app");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
在src目录下新建一个util目录,在util目录下新建一个api目录以及一个index.js文件
index.js中的代码
import axios from "axios";const service = axios.create({ baseURL: "http://localhost:80", timeout: 3000,});export default service;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
api下面新建echarts.js文件以及employee.js文件
employee.js中的代码
写关于employee的api接口
import service from "../index";// 登录export function login(url, userNumber, password) { return service({ method: "post", url: url, data: { userNumber, password, }, });}// 获取员工表export function getEmployees(url) { return service({ method: "get", url: url, });}// 添加员工export function addEmployees(url, values) { console.log(url, values); return service({ method: "post", url: url, data: { empID: values.I, employeeName: values.N, employeePhone: values.P, employeeSex: values.S, employeeSalary: values.Sa, employeePosition: values.Po, }, });}// 更新员工信息export function updateEmployees( url, empID, employeePhone, employeeSalary, employeePosition) { return service({ method: "post", url: url, data: { empID, employeePhone, employeeSalary, employeePosition, }, });}// 删除员工信息export function deleteEmployees(url, empID) { return service({ method: "delete", url: url, params: { empID, }, });}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
echarts.js中的代码
写关于echarts的api接口。因为请求的接口几乎一致,所以写一个就好了
import service from "../index";export function seemURL(url) { return service({ method: "get", url: url, });}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
首先将store目录下的index.js代码清除,然后放置如下代码:
import { createStore } from "vuex";import { seemURL } from "@/util/api/echarts";export default createStore({ state: { sex_totals: { male_totals: "", man_totals: "", }, salaryRate: { threeK: "", fourK: "", fiveK: "", sixK: "", eightK: "", tenK: "", firthK: "", }, getPersonalSalaryTotal: { arr: [], }, }, getters: {}, mutations: { getSex(state, value) { state.sex_totals.male_totals = value.male_total; state.sex_totals.man_totals = value.man_total; }, getsalaryRate(state, value) { state.salaryRate.threeK = value.threeK; state.salaryRate.fourK = value.fourK; state.salaryRate.fiveK = value.fiveK; state.salaryRate.sixK = value.sixK; state.salaryRate.eightK = value.eightK; state.salaryRate.tenK = value.tenK; state.salaryRate.firthK = value.firthK; }, getpersonalSalarytotals(state, value) { state.getPersonalSalaryTotal.arr = value; }, }, actions: { asyncGetSex(context) { const res = seemURL("/map_image/1"); res.then((res) => { context.commit("getSex", res.data); }); }, asyncgetsalaryRate(context) { const res = seemURL("/map_image/2"); res.then((res) => { context.commit("getsalaryRate", res.data); }); }, asyncgetpersonalSalarytotals(context) { const res = seemURL("/map_image/3"); res.then((res) => { context.commit("getpersonalSalarytotals", res.data); }); }, }, modules: {},});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
首先将router目录下的index.js代码清除,然后放置如下代码:
import { createRouter, createWebHashHistory } from "vue-router";const routes = [ { path: "/", name: "/", component: () => import("@/components/Layout.vue"), redirect: "/layout", }, { path: "/login", name: "login", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import("@/views/Login.vue"), }, { path: "/layout", name: "layout", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import("@/components/Layout.vue"), redirect: "/layout/userslist", children: [ { path: "userslist", name: "userslist", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import("@/views/UsersList.vue"), }, { path: "map", name: "map", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import("@/views/Map.vue"), }, ], },];const router = createRouter({ // 历史模式此处设置为 hash模式 history: createWebHashHistory(), routes,});router.beforeEach((to, from, next) => { // 如果跳转目标不是/login if (to.path !== "/login") { // 并且存在本地记录,则放行 if (localStorage.getItem("token")) { next(); } else { //如果不存在本地记录,则强制跳转到登录界面 next("/login"); } } else { // 如果是跳转目标是/login,需要放行 next(); }});export default router;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
启动项目 在项目名称下(比如shop)
npm run serve
- 1
node服务器代码区
新建一个目录(比如backstage)
新建一个js目录,目录下新建app.js以及router.js
初始化backstage
//生成package.josnnpm init -y
- 1
- 2
下载相关包
npm i cors --savenpm i express --savenpm i express-jwt --savenpm i josnwebtoken --savenpm i mysql --savenpm i nodemon -D
- 1
- 2
- 3
- 4
- 5
- 6
app.js中的代码
- 为什么使用app.use(cors()); ? 处理跨域请求的问题。
- 为什么使用app.use(express.json()); ? 因为axios发送的是josn格式的数据,而node解析表单是x-www-form-urlencoded;charset=UTF-8格式,因此我们可以在node中自己配置解析josn格式的数据。axios官方也有相关措施,详细请看
const express = require("express");const app = express();// 解析axios发送的josn数据,axios发送的data,默认是josn格式,而node默认是urlencoded格式的app.use(express.json());// 配置跨越请求const cors = require("cors");app.use(cors());// 解析表单app.use(express.urlencoded({ extended: false }));const router = require("./router.js");app.use("/", router);app.listen("80", function () { console.log("为您服务");});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
router.js中的代码
为什么使用promise代码?本人在不使用promise时,造成了回调地狱的问题,出于美观考虑,笔者还是决定使用promise,正好复习一些promise相关内容
const express = require("express");const secretKey = "strongest ^0^";const jwt = require("jsonwebtoken");// express-jwt 最新版本使用方式,用于解析生成的token// 解码后的 JWT 有效负载现在可用作 req.auth 而不是 req.user// 我们只是为了获取token,并不需要解析token,不配置这个中间件也行var { expressjwt: expressJWT } = require("express-jwt");express().use(expressJWT({ secret: secretKey, algorithms: ["HS256"] }));const router = express.Router();const mysql = require("mysql");const db = mysql.createPool({ host: "127.0.0.1", user: "root",//你自己的mysql登录用户名,一般都是root password: "123456",//你自己的mysql登录密码,比如你设置的123456 port: "3306", // 连接数据库,//hrmsystem是(database)库的名字 database: "hrmsystem",});// 员工查询操作router.get("/employee", function (req, res) { db.query("SELECT * FROM employee", function (err, result) { console.log(result); const employees = result; if (err) return console.log(err.message); res.send(employees); });});// 登录操作router.post("/login", function (req, res) { const tokenStr = jwt.sign({}, secretKey, { expiresIn: "10d", }); db.query("SELECT * FROM register", function (err, result) { if (err) return console.log(err.message); if (result.some((item) => item.regID == req.body.userNumber)) { if (result.some((item) => item.regPassword == req.body.password)) { //发送token res.send({ status: 200, msg: "登录成功", token: `Bearer ${tokenStr}` }); } else { res.send({ status: "error", msg: "密码错误" }); } } else { res.send({ status: "error", msg: "账号不存在" }); } });});// 添加操作router.post("/add", function (req, res) { db.query(`SELECT empID FROM employee `, function (err, result) { if (err) return console.log(err.message); if ( result.some((item) => { item.empID == req.body.empID; }) ) { res.send({ msg: "员工ID已经被注册过了,请更换ID" }); } else { const sql = `insert into employee set ?`; const loop = { empID: req.body.empID, employeeName: req.body.employeeName, employeePhone: req.body.employeePhone, employeeSex: req.body.employeeSex, employeeSalary: req.body.employeeSalary, employeePosition: req.body.employeePosition, }; db.query( sql, [ loop, loop.ID, loop.name, loop.phone, loop.sex, loop.salary, loop.position, ], function (err, result) { if (err) { return console.log(err.message); } if (result.affectedRows === 1) { res.send({ person: req.body, status: 200, msg: "添加员工成功", }); } } ); } });});// 更新操作router.post("/update", function (req, res) { db.query(`SELECT empID FROM employee `, function (err, result) { // if (err) return console.log(err.message); if (result.some((item) => item.empID == req.body.empID)) { const sql = "update employee set ? where empID=?"; // const sql = `update employee set employeePhone=${req.body.employeePhone} and employeeSalary=${req.body.employeeSalary} and employeePosition=${req.body.employeePosition} where empID=${req.body.empID}`; const updateData = { employeePhone: req.body.employeePhone, employeeSalary: req.body.employeeSalary, employeePosition: req.body.employeePosition, }; // db.query(sql, function (err, result) { db.query(sql, [updateData, req.body.empID], function (err, result) { if (err) return console.log(err.message); if (result.affectedRows === 1) { res.send({ status: 200, msg: "更改员工成功" }); } }); } else { res.send({ msg: "员工ID不存在" }); } });});// 删除员工router.delete("/delete", function (req, res) { db.query( `SELECT empID FROM employee where empID= ${req.query.empID}`, function (err, result) { console.log(result); console.log(req.query.empID); console.log(result.length > 0); if (result.length > 0) { const sql = `delete from employee where empID=?`; db.query(sql, (empID = req.query.empID), function (err, result) { if (err) return console.log(err.message); if (result.affectedRows === 1) { console.log(1); res.send({ status: 200, msg: "删除员工成功" }); } }); } else { res.send({ msg: "员工ID不存在" }); } } );});router.get("/map_image/1", function (req, res) { let arr = []; let p = new Promise(function (resolve, reject) { db.query( ` SELECT * FROM employee where employeeSex="男"`, function (err, result) { arr.push(result.length); if (err) return console.log(err.message); resolve(arr); } ); }); p.then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSex="女"`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }).then(function (result) { res.send({ man_total: result[0], male_total: result[1] }); }).catch((err) => { throw new Error(err.message); });;});router.get("/map_image/2", function (req, res) { let arr = []; let p = new Promise(function (resolve, reject) { db.query( ` SELECT * FROM employee where employeeSalary>=3000 and employeeSalary<4000`, function (err, result) { arr.push(result.length); if (err) return console.log(err.message); resolve(arr); } ); }); p.then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSalary>=4000 and employeeSalary<5000`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }) .then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSalary>=5000 and employeeSalary<6000`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }) .then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSalary>=6000 and employeeSalary<8000`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }) .then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSalary>=8000 and employeeSalary<10000`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }) .then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSalary>=10000 and employeeSalary<15000`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }) .then((result) => { return new Promise((resolve, reject) => { db.query( ` SELECT * FROM employee where employeeSalary>=15000`, function (err, result) { if (err) return console.log(err.message); arr.push(result.length); resolve(arr); } ); }); }) .then(function (result) { console.log(result); res.send({ threeK: result[0], fourK: result[1], fiveK: result[2], sixK: result[3], eightK: result[4], tenK: result[5], firthK: result[6], }); }).catch((err) => { throw new Error(err.message); });;});router.get("/map_image/3", function (req, res) { db.query(` SELECT * FROM personal_salary`, function (err, result) { if (err) return console.log(err.message); res.send(result); });});// db.query("SELECT * FROM employee", function (err, result) {// if (err) return console.log(err.message);// console.log(result);// });// db.query("SELECT * FROM register", function (err, result) {// if (err) return console.log(err.message);// console.log(result);// });module.exports = router;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
- 282
- 283
- 284
- 285
- 286
- 287
启动服务器 在js目录下
nodemon app.js
- 1
mysql相关内容
新建一个hrmsystem数据库
在hrmsystem数据库中新建三张表,分别是employee和personal_salary以及register
相关字段如下图所示
employee字段即empID,employeeName,employeePhone,employeeSex,employeeSalary,employeePosition
register字段 regID,regPassword,registerName
personal_salary字段 empID Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec employeeName
总结
对于一个小白(特别是自学者)写一个前后端结合的项目很不容易,特别是看别人的代码,没有注释那就更加难受了。这个前后端结合项目,总体功能比较简单,就是对拿到的数据进行增删改查操作。用了echarts进行可视化。希望这个项目能够对初学者能够有一定帮助。如果觉得有作用的话,不妨对作者进行一键三连(关注,收藏,加点赞),谢谢各位啦!