电商管理系统
1 项目需求
现在有功能完善的一套电商后端系统数据,并有配套的接口文档,要求根据接口文档和项目实际需求编写前端界面,沟通后端数据实现总体功能。
使用技术栈:Vue+VueRouter+Axios
UI:Element UI
编译工具:VSCode
前端效果预览:
2 项目设计与实现
2.1 后端
2.1.1 下载依赖
后端数据文件夹为vue_api_server,功能完善,首先下载项目的依赖包。
npm install
2.1.2 配置数据库
添加MySQL数据库,导入sql文件,在这里使用的是小皮面板代替。
- 出现问题:连接数据库时显示错误,提示找不到服务。
- 解决方案:把电脑自身的MySQL服务关掉,按照小皮面板提示重新安装一个集成版本的数据库,安装之后就显示成功了。接着就继续创建一个新的库,并导入sql文件。
2.1.3 运行后端服务
使用node命令运行后端数据服务。
node ./app.js
2.2 前端
2.2.1 新建项目
使用vue命令新建一个项目
vue create vue_new_shop
2.2.2 项目结构
src文件夹为全部的源码。
api文件夹里面的index.js配置baseURL和请求拦截器等。
assets文进家里面的文件用来对项目的公共样式进行配置。
components文件夹来存放所有的组件,components里面又分为common、goods、orders、Permission、reports、user各个功能模块要用到的组件。
router文件夹里面的index.js用来配置路由。
utils文件夹里面的check.js用来配置所有的表单验证规则。
views文件夹里面的文件是整个项目的登录界面和整体页面等。
main.js是项目的入口文件。
2.2.3 运行项目
使用命令运行整个项目。
npm run serve
3 项目重点
3.1 配置路由
在router文件夹下面的index.js文件中进行路由的配置。
- 引入文件组件
- 配置当前路由信息的path路径和组件名
3.2 单文件组件
一个文件就是一个组件,一个组件就是一个文件。单文件组件模板主要分三部分
<template>
<div>单文件组件</div>
</template>
<script>
export default{
}
</script>
<style lang="less" scope>
</style>
- template标签内为文件的主要显示内容
- script标签内用来引入其他组件和编写各种业务逻辑处理数据,其中包含data、methods、computed和生命周期钩子等等。
- style标签内主要编写当前组件页面的css样式。
3.3 父子通信
父子组件之间的通信一般使用props
和$emit()
来实现。
例如父组件userlist.vue与子组件addUser.vue之间的数据通信。
-
要求实现目的:点击父组件的“编辑修改用户”按钮,弹出一个对话框,填写完对话框内的表单信息之后,点击“确定”按钮,完成膝盖当前用户信息的操作。
-
具体实现步骤:
- 第一步:点击“编辑修改用户”按钮,弹出对话框,渲染对话框内的表单。
- 第二步:输入数据,验证表单合法性。
- 第三步:点击“确定”按钮,对话框消失;用户数据重新渲染,显示当前被修改用户的新信息。
-
代码实现:
代码太长,看完太费劲,不如以一言语之。假设一个情景剧,父组件是一名数学老师A,子组件作为学生B,是数学课代表。
话说这天是周末,A老师仰头一看,豁,天气晴朗,空气清新,好安逸啊。想着不能辜负了这大好天气,于是给班级学生划了新的知识点,布置了家庭作业。让课代表B同学给班级学生传达一下,到了周一收作业,还要检查作业完成情况。
接下来要解决问题就是:A老师怎么在周末这天联系B?周末休息,不像在学校里面直接去班级就能找得到。
所以A老师拿出电话簿
(组件实例占位符)
找到了B的手机号码(ref)
,B同学此时正在家里打游戏呢,接到A老师的电话也是一脸懵,结果是告知自己留新作业!同学当场就站了起来,咬了咬牙也不敢言语,只能应和着说一定会通知到班级学生完成家庭作业的。挂了电话,B同学顿时口吐芬芳,唱起了国粹,wc,sb...但还是不得不拿出了自己的课本,找到了老师留的作业
(props)
,此时又是一口国粹。良久,酝酿好了情绪,来到了班级的群里,告知了大家这一件不幸的事。B同学表示自己是占同学们这一边的,一曲大合唱之后,大家接受了这个事实,虽不时有余音袅袅,但却奈何不得,只得作罢。到了周一,课代表B同学抱着大家完成的作业踉踉跄跄迈进了办公室的大门,放在A老师的办公桌上,请老师批改作业。A老师也非常人。一边跟B同学聊着理想一边批改作业,蹭蹭蹭就批改完了,就让B同学把作业抱回去给同学们分发一下,还鼓励同学们继续加油。
现在回顾整个情景剧。父组价A老师,子组件B同学。A老师翻电话簿找B同学手机号码。
<!-- 父组件 --> <template> <div> <template slot-scope="scope"> <el-button type="primary" @click="showAddDialog(scope.row.id)"></el-button> </template> <add-user :user="currentUser" ref="addUser" @updatelist="getUserList"></add-user> </div> </template> <script> import addUser from './addUser.vue' export default{ data(){ return{ currentUser: {}, userlist: [] } } components: { addUser }, methods:{ async showAddDialog(uid) { const { data: res } = await this.$http.get(`users/${uid}`) this.currentUser = res.data this.$refs.addUser.showDialog() }, } } </script>
-
import引入组件
addUser
,注册addUser,并用<addUser></addUser>
标签实例化组件。ref="addUser"
存的就是子组件B同学的电话号码,使用:user="currentUser"
传递的就是布置的作业。 -
点击编辑按钮,触发
showAddDialog(uid)
方法并传递过去当前项的id
,方法发起数据请求,用currentUser
接收获得的数据对象。 -
给B同学打电话告知留的作业在哪,要写些什么。使用
this.$refs.addUser
就是打电话,电话打通之后,this.$refs.addUser.showDialog()
就是让B同学通知学生们写作业。
继续回顾:B同学通知学生做作业,写完作业后,B同学抱着作业去找A老师批改。
<template> <el-dialog title="编辑用户信息" :visible.sync="dialogVisible" width="50%"> <el-form :model="addUserForm" :rules="addUserRules" ref="addUserRef" label-width="100px"> <el-form-item label="用户名" prop="username"> <el-input v-model="addUserForm.username" disabled></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="addUserForm.email"></el-input> </el-form-item> <el-form-item label="手机号" prop="mobile"> <el-input v-model="addUserForm.mobile"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="hideDialog">取 消</el-button> <el-button type="primary" @click="saveUserInfo">确 定</el-button> </span> </el-dialog> </template> <script> export default{ props: ['user'], computed: { addUserForm() { return this.user } }, data() { return { dialogVisible: false, addUserRules: { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 10, message: '请输入3到10个字符', trigger: 'blur' } ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { validator: checkObj.checkeEmail, trigger: 'blur' } ], mobile: [ { required: true, message: '请输入手机号', trigger: 'blur' }, { validator: checkObj.checkeMobile, trigger: 'blur' } ] } } }, methods: { showDialog() { this.dialogVisible = true }, hideDialog() { this.dialogVisible = false }, saveUserInfo() { this.$refs.addUserRef.validate(async valid => { if (valid) { var flag = true for (var key in this.addUserForm) { if (this.addUserForm[key] !== this.user[key]) { flag = false if (flag) { this.$message.warning('您的内容没有进行修改,请修改之后再提交') return null } } } // 校验成功,请求保存 const { data: res } = await this.$http.put(`users/${this.user.id}`, this.addUserForm) if (res.meta.status === 200) { this.$message.success('更新成功') this.$emit('updatelist') this.hideDialog() } else { this.$message.error('更新失败') } } else { this.$message.error('校验失败') } }) } } } </script>
-
对话框的
:visible.sync="dialogVisible"
指定的dialogVisible默认为false,表示对话框是隐藏不显示的。由于之前父组件调用了子组件的showDialog方法,改变dialogVisible为true,将对话框显示出来,渲染了对话框中的表单项和按钮。 -
父组件A老师使用
ref
打电话,用:user="currentUser"
传递给子组件B同学的数据,子组件使用props: ['user']
接收过来,并且使用计算属性把接收到的数据对象保存到了addUserForm
上面。 -
使用
:model="addUserForm"
把数据赋给了表单。 -
在表单的每一项中使用
v-model="addUserForm.username"
双向绑定了从父组件传递过来的数据对象的对应属性值。 -
Element UI为表单提供有表单验证功能,在表单中使用
prop
属性设置需要校验的字段名,然后在:rules="addUserRules"
指定的addUserRules中配置对应的校验规则。 -
输入信息后,经校验符合规则后点击确定按钮,触发
saveUserInfo
方法,this.$refs.addUserRef.validate
对整个表单进行校验,校验无误后发起保存数据请求,请求成功后由子组件使用$emit()
向父组件发送一个自定义事件,this.$emit('updatelist')
,在父组件里面,有个自定义事件@updatelist="getUserList"
,将触发父组件中的getUserList
方法,由父组件重新获取最新的用户数据列表并渲染出来。隐藏对话框。
回顾continuing:到此为止,老师留了作业,学生做完,老师也给批改好了。
再简单整体回顾一下父子组件之间通信的过程:
1、点击父组件的编辑按钮,父组件找到子组件的ref属性值,通过它调用了子组件的showDialog方法,渲染出了对话框。
2、点击子组件的确定按钮,使用prop指定的对应校验规则进行校验,无误后发起数据请求。子组件使用$emit()触发自定义事件向父组件通信,调用父组件中的getUserList方法重新获取并渲染用户数据列表。隐藏对话框。
-
-
完整源码如下
-
父组件userlist.vue
<template> <div> <template slot-scope="scope"> <el-button type="primary" icon="el-icon-edit" @click="showAddDialog(scope.row.id)"></el-button> </template> <!-- 编辑用户组件 --> <add-user :user="currentUser" ref="addUser" @updatelist="getUserList"></add-user> </div> </template> <script> // 添加用户组件 import addUser from './addUser.vue' export default{ components: { addUser }, data() { return { currentUser: {}, userlist: [], total:0 } }, methods:{ // 1、发起请求 请求用户数据 async getUserList() { const { data: res } = await this.$http.get('users', { params: this.queryInfo }) if (res.meta.status === 200) { this.userlist = res.data.users this.total = res.data.total } else { this.$message.error('获取用户数据失败') } }, // 2、显示要修改的当前用户信息 async showAddDialog(uid) { const { data: res } = await this.$http.get(`users/${uid}`) this.currentUser = res.data this.$refs.addUser.showDialog() }, } } </script> <style lang="less" scope> </style>
-
子组件addUser.vue
<template> <el-dialog title="编辑用户信息" :visible.sync="dialogVisible" width="50%"> <el-form :model="addUserForm" :rules="addUserRules" ref="addUserRef" label-width="100px" class="demo-ruleForm"> <el-form-item label="用户名" prop="username"> <el-input v-model="addUserForm.username" disabled></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="addUserForm.email"></el-input> </el-form-item> <el-form-item label="手机号" prop="mobile"> <el-input v-model="addUserForm.mobile"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="hideDialog">取 消</el-button> <el-button type="primary" @click="saveUserInfo">确 定</el-button> </span> </el-dialog> </template> <script> import checkObj from '@/utils/check.js' export default{ props: ['user'], computed: { addUserForm() { return this.user } }, data() { return { dialogVisible: false, addUserRules: { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, { min: 3, max: 10, message: '请输入3到10个字符', trigger: 'blur' } ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { validator: checkObj.checkeEmail, trigger: 'blur' } ], mobile: [ { required: true, message: '请输入手机号', trigger: 'blur' }, { validator: checkObj.checkeMobile, trigger: 'blur' } ] } } }, methods: { showDialog() { this.dialogVisible = true }, hideDialog() { this.dialogVisible = false }, saveUserInfo() { this.$refs.addUserRef.validate(async valid => { if (valid) { var flag = true for (var key in this.addUserForm) { if (this.addUserForm[key] !== this.user[key]) { flag = false if (flag) { this.$message.warning('您的内容没有进行修改,请修改之后再提交') return null } } } // 校验成功,请求保存 const { data: res } = await this.$http.put(`users/${this.user.id}`, this.addUserForm) if (res.meta.status === 200) { this.$message.success('更新成功') this.$emit('updatelist') this.hideDialog() } else { this.$message.error('更新失败') } } else { this.$message.error('校验失败') } }) } } } </script> <style lang="less" scope> </style>
-
4 项目总结
此电商管理系统是使用Vue+VueRouter+Axios+Element UI开发,通过本次项目实战,对于Vue跟Element UI的使用有了进一步的认知和掌握。
其中使用较频繁的就是组件之间的通信,此外还有其它重点操作,比如组件之间的引入,也就是模块化规范,使用的是ES6模块化规范。
- ES6模块化规范
- 组件间通信
- 数据双向绑定
- Element UI的使用
- Flex布局