今天出现了生产环境clickhouse 清理业务表数据磁盘并未下降的问题 搜索相关文档后得知system表内会继续各种操作日志 一、查询各个表占用空间信息select concat(database, '.', table) as table, formatReadableSize(sum(bytes)) as size, sum(rows) as rows, max(modification_time) as latest_modification, sum(bytes) as bytes_size, any(engine) as engine, formatReadableSize(sum(primary_key_bytes_in_memory)) as primary_keys_size from system.parts where active group by database, table order by bytes_size desc limit 10; 二、清理system 系统自带的日志表数据2.1 根据指定数据库清理SQLalter table system.query_thread_log delete where current_database ='xxx' ; 2.2 根据指定数据库和时间清理SQLalter table system.query_thread_log delete where current_database ='xxx' and event_date='2023-02-05';
A.新建分支goods_cate新建分支goods_cate并推送到码云git checkout -b goods_categit push -u origin goods_cateB.创建子级路由创建categories子级路由组件并设置路由规则import Cate from './components/goods/Cate.vue' path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate } ] C.添加组件基本布局在Cate.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮 商品分类 <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>商品管理</el-breadcrumb-item> <el-breadcrumb-item>商品分类</el-breadcrumb-item> </el-breadcrumb> <el-card> <el-row> <el-col> <el-button type="primary">添加分类</el-button> </el-col> </el-row> </el-card> D.请求分类数据请求分类数据并将数据保存在data中 E.使用插件展示数据使用第三方插件vue-table-with-tree-grid展示分类数据1).在vue 控制台中点击依赖->安装依赖->运行依赖->输入vue-table-with-tree-gird->点击安装2).打开main.js，导入vue-table-with-tree-gridimport TreeTable from 'vue-table-with-tree-grid'.....Vue.config.productionTip = false//全局注册组件 Vue.component('tree-table', TreeTable) 3).使用组件展示分类数据 <tree-table :data="cateList" :columns="columns" :selection-type="false" :expand-type="false" show-index index-text="#" border :show-row-hover="false"> </tree-table> 在数据中添加columns: columns: [ {label:'分类名称',prop:'cat_name'} ] F.自定义数据列使用vue-table-with-tree-grid定义模板列并添加自定义列//先在columns中添加一个列 columns: [ {label:'分类名称',prop:'cat_name'}, //type:'template'(将该列设置为模板列)，template:'isok'(设置该列模板的名称为isok) {label:'是否有效',prop:'',type:'template',template:'isok'}, {label:'排序',prop:'',type:'template',template:'order'}, {label:'操作',prop:'',type:'template',template:'opt'} ] <el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag> <el-tag size="mini" type="success" v-else-if="scope.row.cat_level===1">二级</el-tag> <el-tag size="mini" type="warning" v-else>三级</el-tag> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> G.完成分页功能 <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> //添加对应的事件函数 methods:{ ....... handleSizeChange(newSize){ //当pagesize发生改变时触发 this.queryInfo.pagesize = newSize; this.getCateList(); }, handleCurrentChange(newPage){ //当pagenum发生改变时触发 this.queryInfo.pagenum = newPage; this.getCateList(); } } H.完成添加分类...... <el-row> <el-col> <el-button type="primary" @click="showAddCateDialog">添加分类</el-button> </el-col> </el-row> ...... <el-dialog title="添加分类" :visible.sync="addCateDialogVisible" width="50%" @close="addCateDialogClosed"> <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateFormRuleForm" label-width="100px"> <el-form-item label="分类名称" prop="cat_name"> <el-input v-model="addCateForm.cat_name"></el-input> </el-form-item> <el-form-item label="父级分类" prop="cat_pid"> </el-form-item> </el-form> <el-button @click="addCateDialogVisible = false">取 消</el-button> <el-button type="primary" @click="addCate">确 定</el-button> </el-dialog> //用来显示或隐藏添加分类对话框 addCateDialogVisible: false, //添加分类的表单数据对象 addCateForm:{ //分类名称 cat_name:'', //添加分类的父级id，0则表示父级为0.添加一级分类 cat_pid:0, //添加分类的等级，0则表示添加一级分类 cat_level:0 }, //添加分类校验规则 addCateFormRules:{ //验证规则 cat_name:[ {required:true , message:'请输入分类名称',trigger:'blur'} ] }, //保存1,2级父级分类的列表 parentCateList:[] ....... showAddCateDialog() { //调用getParentCateList获取分类列表 this.getParentCateList() //显示添加分类对话框 this.addCateDialogVisible = true }, async getParentCateList(){ //获取父级分类数据列表 const { data: res } = await this.$http.get('categories', { params: {type:2} }) if (res.meta.status !== 200) { return this.$message.error('获取商品分类列表数据失败') } this.parentCateList = res.data } 添加级联菜单显示父级分类先导入Cascader组件，并注册然后添加使用级联菜单组件：<el-form-item label="父级分类" prop="cat_pid"> <el-cascader expandTrigger='hover' v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChange" clearable change-on-select></el-cascader> </el-form-item> 添加数据 //配置级联菜单中数据如何展示 cascaderProps:{ value:'cat_id', label:'cat_name', children:'children', expandTrigger:'hover' }, //绑定用户选择的分类值 selectedKeys:[] ..... methods:{ ..... parentCateChange(){ //级联菜单中选择项发生变化时触发 console.log(this.selectedKeys) //如果用户选择了父级分类 if(this.selectedKeys.length > 0){ //则将数组中的最后一项设置为父级分类 this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1] //level也要跟着发生变化 this.addCateForm.cat_level = this.selectedKeys.length return }else{ this.addCateForm.cat_pid = 0 this.addCateForm.cat_level = 0 return } }, addCateDialogClosed(){ //当关闭添加分类对话框时，重置表单 this.$refs.addCateFormRef.resetFields() this.selectedKeys = []; this.addCateForm.cat_pid = 0 this.addCateForm.cat_level = 0 }, addCate() { //点击确定，完成添加分类 console.log(this.addCateForm) this.$refs.addCateFormRef.validate(async valid => { if (!valid) return //发送请求完成添加分类 const { data: res } = await this.$http.post( 'categories', this.addCateForm ) if (res.meta.status !== 201) { return this.$message.error('添加分类失败') } this.$message.success('添加分类成功') this.getCateList() this.addCateDialogVisible = false }) } } I.推送代码制作完添加分类之后，将代码提交到仓库，推送到码云,将goods_cate分支合并到mastergit add .git commit -m '完成商品分类'git pushgit checkout mastergit merge goods_cate2.参数管理只允许给三级分类内容设置参数，参数分为动态参数和静态参数属性A.添加子级组件添加Params.vue子组件，并在router.js中引入该组件并设置路由规则import Params from './components/goods/Params.vue' ...... path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles }, { path: "/categories", component: Cate }, { path: "/params", component: Params } ] B.完成组件基本布局完成Params.vue组件的基本布局其中警告提示信息使用了el-alert，在element.js引入该组件并注册 分类参数 <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>商品管理</el-breadcrumb-item> <el-breadcrumb-item>分类参数</el-breadcrumb-item> </el-breadcrumb> <el-card> <el-alert title="注意：只允许为第三级分类设置相关参数" type="warning" :closable="false" show-icon> </el-alert> <el-row class="cat_opt"> <el-col> 选择商品分类： </el-col> <el-col></el-col> </el-row> </el-card> C.完成级联选择框完成商品分类级联选择框 <el-row class="cat_opt"> <el-col> 选择商品分类： <el-cascader expandTrigger='hover' v-model="selectedCateKeys" :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader> </el-col> <el-col></el-col> </el-row> ...... D.展示参数展示动态参数数据以及静态属性数据 <el-tabs v-model="activeName" @tab-click="handleTabClick"> <el-tab-pane label="动态参数" name="many"> <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加参数</el-button> <el-table :data="manyTableData" border stripe> <el-table-column type="expand"></el-table-column> <el-table-column type="index"></el-table-column> <el-table-column label="参数名称" prop="attr_name"></el-table-column> <el-table-column label="操作"> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> </el-table-column> </el-table> </el-tab-pane> <el-tab-pane label="静态属性" name="only"> <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加属性</el-button> <el-table :data="onlyTableData" border stripe> <el-table-column type="expand"></el-table-column> <el-table-column type="index"></el-table-column> <el-table-column label="属性名称" prop="attr_name"></el-table-column> <el-table-column label="操作"> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> </el-table-column> </el-table> </el-tab-pane> </el-tabs> export default { data() { return { ...... //tab页签激活显示的页签项 activeName: 'many', //用来保存动态参数数据 manyTableData: [], //用来保存静态属性数据 onlyTableData: [] } methods: { ....... async handleChange() { //当用户在级联菜单中选择内容改变时触发 console.log(this.selectedCateKeys) //发送请求，根据用户选择的三级分类和面板获取参数数据 const { data: res } = await this.$http.get( `categories/${this.cateId}/attributes`, { params: { sel: this.activeName } } ) if (res.meta.status !== 200) { return this.$message.error('获取参数列表数据失败') } console.log(res.data) if (this.activeName === 'many') { //获取的是动态参数 this.manyTableData = res.data } else if (this.activeName === 'only') { //获取的是静态属性 this.onlyTableData = res.data } }, handleTabClick() { console.log(this.activeName) this.handleChange() } }, computed: { //添加计算属性用来获取按钮禁用与否 isButtonDisabled() { return this.selectedCateKeys.length !== 3 }, //获取选中的三级分类id cateId() { if (this.selectedCateKeys.length === 3) { return this.selectedCateKeys[this.selectedCateKeys.length - 1] } return null } } E.添加参数完成添加参数或属性 <el-dialog :title="'添加'+titleText" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed"> <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px"> <el-form-item :label="titleText" prop="attr_name"> <el-input v-model="addForm.attr_name"></el-input> </el-form-item> </el-form> <el-button @click="addDialogVisible = false">取 消</el-button> <el-button type="primary" @click="addParams">确 定</el-button> </el-dialog> export default { data() { return { ....... //控制添加参数.属性对话框的显示或隐藏 addDialogVisible: false, //添加参数的表单数据对象 addForm: { attr_name: '' }, //添加表单验证规则 addFormRules: { attr_name: [{ required: true, message: '请输入名称', trigger: 'blur' }] } } },methods: { ....... addParams() { //当用户点击对话框中的确定时，校验表单 this.$refs.addFormRef.validate(async valid => { //校验不通过，return if (!valid) return //校验通过，发送请求完成添加参数或者属性 const { data: res } = this.$http.post(`categories/${this.cateId}/attributes`, { attr_name: this.addForm.attr_name, attr_sel: this.activeName, attr_vals: "a,b,c" } ) console.log(res) if (res.meta.status !== 201) { return this.$message.error('添加' + this.titleText + '数据失败') } this.$message.success('添加' + this.titleText + '数据成功') this.addDialogVisible = false this.getCateList() }) } } F.编辑参数完成编辑参数或属性 <el-dialog :title="'修改'+titleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed"> <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px"> <el-form-item :label="titleText" prop="attr_name"> <el-input v-model="editForm.attr_name"></el-input> </el-form-item> </el-form> <el-button @click="editDialogVisible = false">取 消</el-button> <el-button type="primary" @click="editParams">确 定</el-button> </el-dialog> export default { data() { return { ....... //控制修改参数.属性对话框的显示或隐藏 editDialogVisible:false, //修改参数.属性对话框中的表单 editForm:{ attr_name:'' }, //修改表单的验证规则 editFormRules:{ attr_name:[ { required: true, message: '请输入名称', trigger: 'blur' } ] } } },methods: { ....... async showEditDialog(attr_id){ //发起请求获取需要修改的那个参数数据 const {data:res} = await this.$http.get(`categories/${this.cateId}/attributes/${attr_id}`, {params:{ attr_sel:this.activeName }}) if (res.meta.status !== 200) { return this.$message.error('获取参数数据失败') } this.editForm = res.data; //显示修改参数.属性对话框 this.editDialogVisible = true; }, editDialogClosed(){ //当关闭修改参数.属性对话框时 this.$refs.editFormRef.resetFields() }, editParams(){ //验证表单 this.$refs.editFormRef.validate(async valid => { if(!valid) return; //发送请求完成修改 const {data:res} = await this.$http.put(`categories/${this.cateId}/attributes/${this.editForm.attr_id}`, {attr_name:this.editForm.attr_name,attr_sel:this.activeName}) if (res.meta.status !== 200) { return this.$message.error('获取参数数据失败') } this.$message.success('修改' + this.titleText + '数据成功') this.editDialogVisible = false this.handleChange(); }) } } G.删除参数删除参数或属性给两个删除按钮添加事件 <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button> <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button> 添加对应的事件处理函数 async removeParams(attr_id){ //根据id删除对应的参数或属性 //弹窗提示用户是否要删除 const confirmResult = await this.$confirm( '请问是否要删除该'+this.titleText, '删除提示', { confirmButtonText: '确认删除', cancelButtonText: '取消', type: 'warning' } ).catch(err => err) //如果用户点击确认，则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if (confirmResult != 'confirm') { return this.$message.info('已经取消删除') } //没有取消就是要删除，发送请求完成删除 const {data:res} = await this.$http.delete(`categories/${this.cateId}/attributes/${attr_id}`) if (res.meta.status !== 200) { return this.$message.error('删除参数数据失败') } this.$message.success('删除' + this.titleText + '数据成功') this.handleChange() }
1.修改用户信息A.为用户列表中的修改按钮绑定点击事件B.在页面中添加修改用户对话框，并修改对话框的属性C.根据id查询需要修改的用户数据//展示编辑用户的对话框 async showEditDialog(id) { //发送请求根据id获取用户信息 const { data: res } = await this.$http.get('users/' + id) //判断如果添加失败，就做提示 if (res.meta.status !== 200) return this.$message.error('获取用户信息失败') //将获取到的数据保存到数据editForm中 this.editForm = res.data //显示弹出窗 this.editDialogVisible = true } D.在弹出窗中添加修改用户信息的表单并做响应的数据绑定以及数据验证 <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="70px"> <el-form-item label="用户名"> <el-input v-model="editForm.username" disabled></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="editForm.email"></el-input> </el-form-item> <el-form-item label="电话" prop="mobile"> <el-input v-model="editForm.mobile"></el-input> </el-form-item> </el-form> 数据绑定以及验证：//控制修改用户对话框的显示与否 editDialogVisible: false, //修改用户的表单数据 editForm: { username: '', email: '', mobile: '' }, //修改表单的验证规则对象 editFormRules: { email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { validator: checkEmail, message: '邮箱格式不正确，请重新输入', trigger: 'blur' } ], mobile: [ { required: true, message: '请输入手机号码', trigger: 'blur' }, { validator: checkMobile, message: '手机号码不正确，请重新输入', trigger: 'blur' } ] } E.监听对话框关闭事件，在对话框关闭之后，重置表单<el-dialog title="修改用户" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed"> editDialogClosed(){ //对话框关闭之后，重置表达 this.$refs.editFormRef.resetFields() } F.在用户点击确定按钮的时候，验证数据成功之后发送请求完成修改editUser() { //用户点击修改表单中的确定按钮之后，验证表单数据 this.$refs.editFormRef.validate(async valid => { if (!valid) return this.$message.error('请填写完整用户信息') //发送请求完成修改用户的操作 const { data: res } = await this.$http.put( 'users/' + this.editForm.id, this.editForm ) //判断如果修改失败，就做提示 if (res.meta.status !== 200) return this.$message.error('修改用户失败') //修改成功的提示 this.$message.success('修改用户成功') //关闭对话框 this.editDialogVisible = false //重新请求最新的数据 this.getUserList() }) } 2.删除用户在点击删除按钮的时候，我们应该跳出提示信息框，让用户确认要进行删除操作。如果想要使用确认取消提示框，我们需要先将提示信息框挂载到vue中。A.导入MessageBox组件，并将MessageBox组件挂载到实例。Vue.prototype.$confirm = MessageBox.confirmB.给用户列表中的删除按钮添加事件，并在事件处理函数中弹出确定取消窗,最后再根据id发送删除用户的请求async removeUserById(id){ //弹出确定取消框，是否删除用户 const confirmResult = await this.$confirm('请问是否要永久删除该用户','删除提示',{ confirmButtonText:'确认删除', cancelButtonText:'取消', type:'warning' }).catch(err=>err) //如果用户点击确认，则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if(confirmResult != "confirm"){ return this.$message.info("已经取消删除") } //发送请求根据id完成删除操作 const {data:res} = await this.$http.delete('users/'+id) //判断如果删除失败，就做提示 if (res.meta.status !== 200) return this.$message.error('删除用户失败') //修改成功的提示 this.$message.success('删除用户成功') //重新请求最新的数据 this.getUserList() } 3.推送代码创建user子分支，并将代码推送到码云A.创建user子分支 git checkout -b userB.将代码添加到暂存区 git add .C.将代码提交并注释 git commit -m '添加完成用户列表功能'D.将本地的user分支推送到码云 git push -u origin userE.将user分支代码合并到master:切换到master git checkout master合并user git merge userF.将本地master分支的代码推送到码云 git push创建rights子分支A.创建rights子分支 git checkout -b rightsB.将本地的rights分支推送到码云 git push -u origin rights4.权限列表A.添加权限列表路由创建权限管理组件（Rights.vue），并在router.js添加对应的路由规则import Rights from './components/power/Rights.vue' ...... path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights } ] ...... B.添加面包屑导航在Rights.vue中添加面包屑组件展示导航路径C.显示数据在data中添加一个rightsList数据，在methods中提供一个getRightsList方法发送请求获取权限列表数据，在created中调用这个方法获取数据<el-table :data="rightsList" stripe> <el-table-column type="index"></el-table-column> <el-table-column label="权限名称" prop="authName"></el-table-column> <el-table-column label="路径" prop="path"></el-table-column> <el-table-column label="权限等级" prop="level"> <el-tag v-if="scope.row.level === 0">一级权限</el-tag> <el-tag v-if="scope.row.level === 1" type="success">二级权限</el-tag> <el-tag v-if="scope.row.level === 2" type="warning">三级权限</el-tag> </el-table-column> </el-table> 5.角色列表A.添加角色列表路由添加角色列表子组件（power/Roles.vue），并添加对应的规则path: '/home', component: Home, redirect: '/welcome', children: [ { path: "/welcome", component: Welcome }, { path: "/users", component: Users }, { path: "/rights", component: Rights }, { path: "/roles", component: Roles } ] B.添加面包屑导航在Roles.vue中添加面包屑组件展示导航路径C.显示数据在data中添加一个roleList数据，在methods中提供一个getRoleList方法发送请求获取权限列表数据，在created中调用这个方法获取数据 <el-table row-key="id" :data="roleList" border> <el-table-column type="expand"></el-table-column> <el-table-column type="index"></el-table-column> <el-table-column label="角色名称" prop="roleName"></el-table-column> <el-table-column label="角色描述" prop="roleDesc"></el-table-column> <el-table-column label="操作" width="300px"> <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button> <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button> <el-button size="mini" type="warning" icon="el-icon-setting">分配权限</el-button> </el-table-column> </el-table> D.补充说明之前学习过类似的添加角色，删除角色，编辑角色请参照之前编写过的代码还有接口文档完成效果。E.生成权限列表使用三重嵌套for循环生成权限下拉列表 <el-table-column type="expand"> <el-row :class="['bdbottom',i1===0?'bdtop':'']" v-for="(item1,i1) in scope.row.children" :key="item1.id"> <el-col :span="5"> <el-tag> {{item1.authName}} </el-tag> </el-col> <el-col :span="19"> <el-row :class="[i2===0?'':'bdtop' ]" v-for="(item2,i2) in item1.children" :key="item2.id"> <el-col :span="6"> <el-tag type="success">{{item2.authName}}</el-tag> </el-col> <el-col :span="18"> <el-tag type="warning" v-for="(item3) in item2.children" :key="item3.id"> {{item3.authName}} </el-tag> </el-col> </el-row> </el-col> </el-row> </el-table-column> F.美化样式通过设置global.css中的#app样式min-width:1366px 解决三级权限换行的问题，通过给一级权限el-row添加display:flex,align-items:center的方式解决一级权限垂直居中的问题，二级权限也类似添加，因为需要给多个内容添加，可以将这个样式设置为一个.vcenter{display:flex;align-items:center}G.添加权限删除功能给每一个权限的el-tag添加closable属性，是的权限右侧出现“X”图标再给el-tag添加绑定close事件处理函数removeRightById(scope.row,item1.id)removeRightById(scope.row,item2.id)removeRightById(scope.row,item3.id)async removeRightById(role,rightId){ //弹窗提示用户是否要删除 const confirmResult = await this.$confirm('请问是否要删除该权限','删除提示',{ confirmButtonText:'确认删除', cancelButtonText:'取消', type:'warning' }).catch(err=>err) //如果用户点击确认，则confirmResult 为'confirm' //如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel' if(confirmResult != "confirm"){ return this.$message.info("已经取消删除") } //用户点击了确定表示真的要删除 //当发送delete请求之后，返回的数据就是最新的角色权限信息 const {data:res} = await this.$http.delete(`roles/${role.id}/rights/${rightId}`) if (res.meta.status !== 200) return this.$message.error('删除角色权限失败') //无需再重新加载所有权限 //只需要对现有的角色权限进行更新即可 role.children = res.data // this.getRoleList(); } H.完成权限分配功能先给分配权限按钮添加事件<el-button size="mini" type="warning" icon="el-icon-setting" @click="showSetRightDialog">分配权限在showSetRightDialog函数中请求权限树数据并显示对话框async showSetRightDialog() { //当点击分配权限按钮时，展示对应的对话框 this.setRightDialogVisible = true; //获取所有权限的数据 const {data:res} = await this.$http.get('rights/tree') //如果返回状态为异常状态则报错并返回 if (res.meta.status !== 200) return this.$message.error('获取权限树失败') //如果返回状态正常，将请求的数据保存在data中 this.rightsList = res.data } 添加分配权限对话框，并添加绑定数据setRightDialogVisible 这是一段信息 取 消 确 定 I.完成树形结构弹窗在element.js中引入Tree，注册Tree <el-dialog title="分配权限" :visible.sync="setRightDialogVisible" width="50%" @close="setRightDialogClose"> <el-tree :data="rightsList" :props="treeProps" show-checkbox node-key="id" default-expand-all :default-checked-keys="defKeys" ref="treeRef"></el-tree> <el-button @click="setRightDialogVisible = false">取 消</el-button> <el-button type="primary" @click="allotRights">确 定</el-button> </el-dialog> 6.分配角色打开Users.vue，完成分配角色的功能A.添加分配角色对话框 <el-dialog title="分配角色" :visible.sync="setRoleDialogVisible" width="50%"> 当前的用户:{{userInfo.username}} 当前的角色:{{userInfo.role_name}} 分配新角色: <el-button @click="setRoleDialogVisible = false">取 消</el-button> <el-button type="primary" @click="setRoleDialogVisible = false">确 定</el-button> </el-dialog> B.给分配角色按钮添加点击事件，点击之后弹出一个对话框进行角色分配 <el-tooltip class="item" effect="dark" content="分配角色" placement="top" :enterable="false"> <el-button type="warning" icon="el-icon-setting" size='mini' @click="setRole(scope.row)"></el-button> </el-tooltip> data(){ ...... //控制显示分配角色对话框 setRoleDialogVisible:false, //保存正在操作的那个用户信息 userInfo:{}, //保存所有的角色信息 rolesList:[], //保存用户选中的角色id selectedRoleId:'' }, methods:{ ...... async setRole( userInfo ){ //保存起来以供后续使用 this.userInfo = userInfo; //获取所有的角色信息，以备下拉列表使用 //发送请求根据id完成删除操作 const { data: res } = await this.$http.get('roles') //判断如果删除失败，就做提示 if (res.meta.status !== 200) return this.$message.error('获取角色列表失败') this.rolesList = res.data; //展示分配角色对话框 this.setRoleDialogVisible = true; } } C.在element.js中引入Select，Option，注册Select，Option <el-select v-model="selectedRoleId" placeholder="请选择角色"> <el-option v-for="item in rolesList" :key="item.id" :label="item.roleName" :value="item.id"> </el-option> </el-select> D.当用户点击对话框中的确定之后，完成分配角色的操作 <el-dialog title="分配角色" :visible.sync="setRoleDialogVisible" width="50%" @close="setRoleDialogClosed"> 当前的用户:{{userInfo.username}} 当前的角色:{{userInfo.role_name}} 分配新角色: <el-select v-model="selectedRoleId" placeholder="请选择角色"> <el-option v-for="item in rolesList" :key="item.id" :label="item.roleName" :value="item.id"> </el-option> </el-select> <el-button @click="setRoleDialogVisible = false">取 消</el-button> <el-button type="primary" @click="saveRoleInfo">确 定</el-button> </el-dialog> methods:{ ....... async saveRoleInfo(){ //当用户点击确定按钮之后 //判断用户是否选择了需要分配的角色 if(!this.selectedRoleId){ return this.$message.error('请选择需要分配的角色') } //发送请求完成分配角色的操作 const {data:res} = await this.$http.put(`users/${this.userInfo.id}/role`,{rid:this.selectedRoleId}) //判断如果删除失败，就做提示 if (res.meta.status !== 200) return this.$message.error('分配角色失败') this.$message.success('分配角色成功') this.getUserList(); //关闭对话框 this.setRoleDialogVisible = false }, setRoleDialogClosed(){ //当关闭对话框的时候，重置下拉框中的内容 this.selectedRoleId = '' this.userInfo = {} } } 7.将代码推送到码云A.将代码推送到暂存区 git add .B.将代码提交到仓库 git commit -m '完成了权限功能开发'C.将rights分支代码推送到码云 git pushD.将代码合并到mastergit checkout mastergit merge rightsE.将master代码推送到码云git push
打开Home.vue组件，进行布局：<el-container class="home-container"> <el-header>Header<el-button type="info" @click="logout"> 退出 </el-button></el-header> <el-container> <el-aside width="200px">Aside</el-aside> <el-main>Main</el-main> </el-container> </el-container> 默认情况下，跟element-ui组件同名的类名可以帮助我们快速的给对应的组件添加样式，如：.home-container { height: 100%; } .el-header{ background-color:#373D41; } .el-aside{ background-color:#333744; } .el-main{ background-color:#eaedf1; } 2.顶部布局，侧边栏布局 <el-container class="home-container"> <el-header> 电商后台管理系统 <el-button type="info" @click="logout"> 退出 </el-button> </el-header> <el-container> <el-aside width="200px"> <el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b"> <el-submenu index="1"> 导航一 <el-menu-item index="1-4-1"> 子菜单一 </el-menu-item> </el-submenu> </el-menu> </el-aside> <el-main>Main</el-main> </el-container> </el-container> 3.axios请求拦截器后台除了登录接口之外，都需要token权限验证，我们可以通过添加axios请求拦截器来添加token，以保证拥有获取数据的权限在main.js中添加代码，在将axios挂载到vue原型之前添加下面的代码//请求在到达服务器之前，先会调用use中的这个回调函数来添加请求头信息 axios.interceptors.request.use(config=>{ //为请求头对象，添加token验证的Authorization字段 config.headers.Authorization = window.sessionStorage.getItem("token") return config }) 4.请求侧边栏数据 通过v-for双重循环渲染左侧菜单<el-menu background-color="#333744" text-color="#fff" active-text-color="#ffd04b"> <el-submenu :index="item.id+''" v-for="item in menuList" :key="item.id"> {{item.authName}} <el-menu-item :index="subItem.id+''" v-for="subItem in item.children" :key="subItem.id"> {{subItem.authName}} </el-menu-item> </el-submenu> </el-menu> 5.设置激活子菜单样式通过更改el-menu的active-text-color属性可以设置侧边栏菜单中点击的激活项的文字颜色通过更改菜单项模板（template）中的i标签的类名，可以将左侧菜单栏的图标进行设置，我们需要在项目中使用第三方字体图标在数据中添加一个iconsObj：iconsObj: { '125':'iconfont icon-user', '103':'iconfont icon-tijikongjian', '101':'iconfont icon-shangpin', '102':'iconfont icon-danju', '145':'iconfont icon-baobiao' } 然后将图标类名进行数据绑定，绑定iconsObj中的数据：为了保持左侧菜单每次只能打开一个，显示其中的子菜单，我们可以在el-menu中添加一个属性unique-opened或者也可以数据绑定进行设置(此时true认为是一个bool值，而不是字符串) :unique-opened="true"6.制作侧边菜单栏的伸缩功能在菜单栏上方添加一个div <el-aside :width="isCollapse ? '64px':'200px'"> ||| <el-menu :collapse="isCollapse" :collapse-transition="false" ...... 然后给div添加样式，给div添加事件：|||7.在后台首页添加子级路由新增子级路由组件Welcome.vue在router.js中导入子级路由组件，并设置路由规则以及子级路由的默认重定向打开Home.vue，在main的主体结构中添加一个路由占位符制作好了Welcome子级路由之后，我们需要将所有的侧边栏二级菜单都改造成子级路由链接我们只需要将el-menu的router属性设置为true就可以了，此时当我们点击二级菜单的时候，就会根据菜单的index属性进行路由跳转,如: /110,使用index id来作为跳转的路径不合适，我们可以重新绑定index的值为 :index="'/'+subItem.path"8.完成用户列表主体区域新建用户列表组件 user/Users.vue在router.js中导入子级路由组件Users.vue，并设置路由规则当点击二级菜单的时候，被点击的二级子菜单并没有高亮，我们需要正在被使用的功能高亮显示我们可以通过设置el-menu的default-active属性来设置当前激活菜单的index但是default-active属性也不能写死，固定为某个菜单值所以我们可以先给所有的二级菜单添加点击事件,并将path值作为方法的参数@click="saveNavState('/'+subItem.path)"在saveNavState方法中将path保存到sessionStorage中saveNavState( path ){//点击二级菜单的时候保存被点击的二级菜单信息window.sessionStorage.setItem("activePath",path);this.activePath = path;}然后在数据中添加一个activePath绑定数据，并将el-menu的default-active属性设置为activePath最后在created中将sessionStorage中的数据赋值给activePaththis.activePath = window.sessionStorage.getItem("activePath")9.绘制用户列表基本结构A.使用element-ui面包屑组件完成顶部导航路径(复制面包屑代码，在element.js中导入组件Breadcrumb,BreadcrumbItem)B.使用element-ui卡片组件完成主体表格(复制卡片组件代码，在element.js中导入组件Card)，再使用element-ui输入框完成搜索框及搜索按钮，此时我们需要使用栅格布局来划分结构(复制卡片组件代码，在element.js中导入组件Row，Col)，然后再使用el-button制作添加用户按钮 用户列表组件 <el-breadcrumb separator="/"> <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item> <el-breadcrumb-item>用户管理</el-breadcrumb-item> <el-breadcrumb-item>用户列表</el-breadcrumb-item> </el-breadcrumb> <el-card> <el-row :gutter="20"> <el-col :span="7"> <el-input placeholder="请输入内容"> <el-button slot="append" icon="el-icon-search"></el-button> </el-input> </el-col> <el-col :span="4"> <el-button type="primary">添加用户</el-button> </el-col> </el-row> </el-card> 10.请求用户列表数据 11.将用户列表数据展示使用表格来展示用户列表数据，使用element-ui表格组件完成列表展示数据(复制表格代码，在element.js中导入组件Table,TableColumn)在渲染展示状态时，会使用作用域插槽获取每一行的数据再使用switch开关组件展示状态信息(复制开关组件代码，在element.js中导入组件Switch)而渲染操作列时，也是使用作用域插槽来进行渲染的，在操作列中包含了修改，删除，分配角色按钮，当我们把鼠标放到分配角色按钮上时希望能有一些文字提示，此时我们需要使用文字提示组件(复制文字提示组件代码，在element.js中导入组件Tooltip),将分配角色按钮包含代码结构如下： <el-table :data="userList" border stripe> <el-table-column type="index"></el-table-column> <el-table-column label="姓名" prop="username"></el-table-column> <el-table-column label="邮箱" prop="email"></el-table-column> <el-table-column label="电话" prop="mobile"></el-table-column> <el-table-column label="角色" prop="role_name"></el-table-column> <el-table-column label="状态"> <el-switch v-model="scope.row.mg_state"></el-switch> </el-table-column> <el-table-column label="操作" width="180px"> <el-button type="primary" icon="el-icon-edit" size='mini'></el-button> <el-button type="danger" icon="el-icon-delete" size='mini'></el-button> <el-tooltip class="item" effect="dark" content="分配角色" placement="top" :enterable="false"> <el-button type="warning" icon="el-icon-setting" size='mini'></el-button> </el-tooltip> </el-table-column> </el-table> 12.实现用户列表分页A.使用表格来展示用户列表数据，可以使用分页组件完成列表分页展示数据(复制分页组件代码，在element.js中导入组件Pagination)B.更改组件中的绑定数据 <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[1, 2, 5, 10]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> C.添加两个事件的事件处理函数@size-change，@current-changehandleSizeChange(newSize) { //pagesize改变时触发，当pagesize发生改变的时候，我们应该 //以最新的pagesize来请求数据并展示数据 // console.log(newSize) this.queryInfo.pagesize = newSize; //重新按照pagesize发送请求，请求最新的数据 this.getUserList(); }, handleCurrentChange( current ) { //页码发生改变时触发当current发生改变的时候，我们应该 //以最新的current页码来请求数据并展示数据 // console.log(current) this.queryInfo.pagenum = current; //重新按照pagenum发送请求，请求最新的数据 this.getUserList(); } 13.实现更新用户状态当用户点击列表中的switch组件时，用户的状态应该跟随发生改变。A.首先监听用户点击switch组件的事件，并将作用域插槽的数据当做事件参数进行传递<el-switch v-model="scope.row.mg_state" @change="userStateChanged(scope.row)"></el-switch> B.在事件中发送请求完成状态的更改async userStateChanged(row) { //发送请求进行状态修改 const { data: res } = await this.$http.put( `users/${row.id}/state/${row.mg_state}` ) //如果返回状态为异常状态则报错并返回 if (res.meta.status !== 200) { row.mg_state = !row.mg_state return this.$message.error('修改状态失败') } this.$message.success('更新状态成功') }, 14.实现搜索功能添加数据绑定，添加搜索按钮的点击事件(当用户点击搜索按钮的时候，调用getUserList方法根据文本框内容重新请求用户列表数据)当我们在输入框中输入内容并点击搜索之后，会按照搜索关键字搜索，我们希望能够提供一个X删除搜索关键字并重新获取所有的用户列表数据，只需要给文本框添加clearable属性并添加clear事件，在clear事件中重新请求数据即可<el-col :span="7"> <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="getUserList"> <el-button slot="append" icon="el-icon-search" @click="getUserList"></el-button> </el-input> </el-col> 15.实现添加用户A.当我们点击添加用户按钮的时候，弹出一个对话框来实现添加用户的功能，首先我们需要复制对话框组件的代码并在element.js文件中引入Dialog组件B.接下来我们要为“添加用户”按钮添加点击事件，在事件中将addDialogVisible设置为true，即显示对话框C.更改Dialog组件中的内容 <el-dialog title="添加用户" :visible.sync="addDialogVisible" width="50%"> <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="70px"> <el-form-item label="用户名" prop="username"> <el-input v-model="addForm.username"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input v-model="addForm.password"></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="addForm.email"></el-input> </el-form-item> <el-form-item label="电话" prop="mobile"> <el-input v-model="addForm.mobile"></el-input> </el-form-item> </el-form> <el-button @click="addDialogVisible = false">取 消</el-button> <el-button type="primary" @click="addDialogVisible = false">确 定</el-button> </el-dialog> D.添加数据绑定和校验规则：data() { //验证邮箱的规则 var checkEmail = (rule, value, cb) => { const regEmail = /^\w+@\w+(\.\w+)+$/ if (regEmail.test(value)) { return cb() } //返回一个错误提示 cb(new Error('请输入合法的邮箱')) } //验证手机号码的规则 var checkMobile = (rule, value, cb) => { const regMobile = /^1[34578]\d{9}$/ if (regMobile.test(value)) { return cb() } //返回一个错误提示 cb(new Error('请输入合法的手机号码')) } return { //获取查询用户信息的参数 queryInfo: { // 查询的条件 query: '', // 当前的页数，即页码 pagenum: 1, // 每页显示的数据条数 pagesize: 2 }, //保存请求回来的用户列表数据 userList: [], total: 0, //是否显示添加用户弹出窗 addDialogVisible: false, // 添加用户的表单数据 addForm: { username: '', password: '', email: '', mobile: '' }, // 添加表单的验证规则对象 addFormRules: { username: [ { required: true, message: '请输入用户名称', trigger: 'blur' }, { min: 3, max: 10, message: '用户名在3~10个字符之间', trigger: 'blur' } ], password: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, max: 15, message: '用户名在6~15个字符之间', trigger: 'blur' } ], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { validator:checkEmail, message: '邮箱格式不正确，请重新输入', trigger: 'blur'} ], mobile: [ { required: true, message: '请输入手机号码', trigger: 'blur' }, { validator:checkMobile, message: '手机号码不正确，请重新输入', trigger: 'blur'} ] } } } E.当关闭对话框时，重置表单给el-dialog添加@close事件，在事件中添加重置表单的代码methods:{ .... addDialogClosed(){ //对话框关闭之后，重置表达 this.$refs.addFormRef.resetFields(); } } F.点击对话框中的确定按钮，发送请求完成添加用户的操作首先给确定按钮添加点击事件，在点击事件中完成业务逻辑代码methods:{ .... addUser(){ //点击确定按钮，添加新用户 //调用validate进行表单验证 this.$refs.addFormRef.validate( async valid => { if(!valid) return this.$message.error("请填写完整用户信息"); //发送请求完成添加用户的操作 const {data:res} = await this.$http.post("users",this.addForm) //判断如果添加失败，就做提示 if (res.meta.status !== 200) return this.$message.error('添加用户失败') //添加成功的提示 this.$message.success("添加用户成功") //关闭对话框 this.addDialogVisible = false //重新请求最新的数据 this.getUserList() }) } }
客户使用的业务服务：PC端，小程序，移动web，移动app管理员使用的业务服务：PC后台管理端。PC后台管理端的功能：管理用户账号（登录，退出，用户管理，权限管理），商品管理（商品分类，分类参数，商品信息，订单），数据统计电商后台管理系统采用前后端分离的开发模式前端项目是基于Vue的SPA（单页应用程序）项目前端技术栈:Vue,Vue-Router,Element-UI,Axios,Echarts后端技术栈：Node.js,Express,Jwt(模拟session),Mysql,Sequelize(操作数据库的框架)2.项目初始化A.安装Vue脚手架B.通过脚手架创建项目C.配置路由D.配置Element-UI:在插件中安装，搜索vue-cli-plugin-elementE.配置Axios：在依赖中安装,搜索axios(运行依赖)F.初始化git仓库G.将本地项目托管到github或者码云中3.码云相关操作A.注册登录码云账号B.安装git在Windows上使用Git，可以从Git官网直接下载安装程序进行安装。测试命令：git --versionC.点击网站右上角“登录”，登录码云，并进行账号设置D.在本地创建公钥：在终端运行：ssh-keygen -t rsa -C "xxx@xxx.com"E.找到公钥地址：Your identification has been saved in /c/Users/My/.ssh/id_rsa.Your public key has been saved in /c/Users/My/.ssh/id_rsa.pub.当我们创建公钥完毕之后，请注意打印出来的信息“Your public key has been saved in”/c/Users/My/.ssh/id_rsa.pub : c盘下面的Users下面的My下面的.ssh下面的id_rsa.pub就是我们创建好的公钥了E.打开id_rsa.pub文件，复制文件中的所有代码，点击码云中的SSH公钥，将生成的公钥复制到公钥中G.测试公钥：打开终端，输入命令ssh -T git@gitee.comH.将本地代码托管到码云中点击码云右上角的+号->新建仓库I.进行git配置：打开项目所在位置的终端，进行git仓库关联4.配置后台项目A.安装phpStudy并导入mysql数据库数据B.安装nodeJS，配置后台项目,从终端打开后台项目vue_api_server然后在终端中输入命令安装项目依赖包：npm installC.使用postman测试api接口5.实现登录功能A.登录状态保持如果服务器和客户端同源，建议可以使用cookie或者session来保持登录状态如果客户端和服务器跨域了，建议使用token进行维持登录状态。B.登录逻辑：在登录页面输入账号和密码进行登录，将数据发送给服务器服务器返回登录的结果，登录成功则返回数据中带有token客户端得到token并进行保存，后续的请求都需要将此token发送给服务器，服务器会验证token以保证用户身份。C.添加新分支login，在login分支中开发当前项目vue_shop：打开vue_shop终端，使用git status确定当前项目状态。确定当前工作目录是干净的之后，创建一个分支进行开发，开发完毕之后将其合并到mastergit checkout -b login然后查看新创建的分支：git branch确定我们正在使用login分支进行开发然后执行vue ui命令打开ui界面，然后运行serve，运行app查看当前项目效果发现现在是一个默认页面，我们需要进行更改，打开项目的src目录，点击main.js文件（入口文件）import Vue from 'vue' import App from './App.vue' import router from './router' import './plugins/element.js' Vue.config.productionTip = false new Vue({ router, render: h => h(App) }).$mount('#app') 再打开App.vue(根组件)，将根组件的内容进行操作梳理(template中留下根节点，script中留下默认导出，去掉组件，style中去掉所有样式) <router-view></router-view> 再打开router.js(路由)，将routes数组中的路由规则清除，然后将views删除，将components中的helloworld.vue删除import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ ] }) 在components文件夹中新建Login.vue组件,添加template，script，style标签,style标签中的scoped可以防止组件之间的样式冲突，没有scoped则样式是全局的 在router.js中导入组件并设置规则在App.vue中添加路由占位符const router = new Router({ routes: [ { path: '/', redirect: '/login' }, { path: '/login', component: Login } ] }) 当我们给Login.vue中的内容添加样式的时候，会报错“缺少less-loader”，需要配置less加载器（开发依赖），安装less(开发依赖)然后需要添加公共样式，在assets文件夹下面添加css文件夹，创建global.css文件,添加全局样式/* 全局样式表 */ html,body,#app{ width: 100%; height: 100%; margin: 0; padding: 0; } 在main.js中导入global.css，使得全局样式生效 import "./assets/css/global.css"然后Login.vue中的根元素也需要设置撑满全屏（height:100%）最终Login.vue文件中的代码如下 <el-form :model="loginForm" ref="LoginFormRef" :rules="loginFormRules" label-width="0px" class="login_form"> <el-form-item prop="username"> <el-input v-model="loginForm.username" prefix-icon="iconfont icon-user" ></el-input> </el-form-item> <el-form-item prop="password"> <el-input type="password" v-model="loginForm.password" prefix-icon="iconfont icon-3702mima"></el-input> </el-form-item> <el-form-item class="btns"> <el-button type="primary" @click="login">登录</el-button> <el-button type="info" @click="resetLoginForm">重置</el-button> </el-form-item> </el-form> 其中我们有用到一下内容，需要进行进一步处理：A.添加element-ui的表单组件在plugins文件夹中打开element.js文件，进行elementui的按需导入import Vue from 'vue'import { Button } from 'element-ui'import { Form, FormItem } from 'element-ui'import { Input } from 'element-ui'Vue.use(Button)Vue.use(Form)Vue.use(FormItem)Vue.use(Input)B.添加第三方字体复制素材中的fonts文件夹到assets中,在入口文件main.js中导入import './assets/fonts/iconfont.css'然后直接在 接着添加登录盒子C.添加表单验证的步骤1).给添加属性:rules="rules"，rules是一堆验证规则，定义在script、中2).在script中添加rules：export default{ data(){return{......, rules: {name: [{ required: true, message: '请输入活动名称', trigger: 'blur' },{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }],region: [{ required: true, message: '请选择活动区域', trigger: 'change' }]}......3).通过的prop属性设置验证规则4.导入axios以发送ajax请求打开main.js，import axios from 'axios';设置请求的根路径：axios.defaults.baseURL = 'http://127.0.0.1:8888/api/private/v1/';挂载axios：Vue.prototype.$http = axios;5.配置弹窗提示：在plugins文件夹中打开element.js文件，进行elementui的按需导入import {Message} from 'element-ui'进行全局挂载：Vue.prototype.$message = Message;在login.vue组件中编写弹窗代码：this.$message.error('登录失败')6.登录成功之后的操作A.登录成功之后，需要将后台返回的token保存到sessionStorage中操作完毕之后，需要跳转到/homelogin() { //点击登录的时候先调用validate方法验证表单内容是否有误 this.$refs.LoginFormRef.validate(async valid => { console.log(this.loginFormRules) //如果valid参数为true则验证通过 if (!valid) { return } //发送请求进行登录 const { data: res } = await this.$http.post('login', this.loginForm) // console.log(res); if (res.meta.status !== 200) { return this.$message.error('登录失败:' + res.meta.msg) //console.log("登录失败:"+res.meta.msg) } this.$message.success('登录成功') console.log(res) //保存token window.sessionStorage.setItem('token', res.data.token) // 导航至/home this.$router.push('/home') }) } 添加一个组件Home.vue，并为之添加规则 this is home <el-button type="info" @click="logout"> 退出 </el-button> 添加路由规则const router = new Router({ routes: [ { path: '/', redirect: '/login' }, { path: '/login', component: Login }, { path: '/home', component: Home } ] }) 添加路由守卫如果用户没有登录，不能访问/home,如果用户通过url地址直接访问，则强制跳转到登录页面打开router.jsimport Vue from 'vue' import Router from 'vue-router' import Login from './components/Login.vue' import Home from './components/Home.vue' Vue.use(Router) const router = new Router({ routes: [ { path:'/', redirect:'/login'}, { path:'/login' , component:Login }, { path:'/home' , component:Home} ] }) //挂载路由导航守卫,to表示将要访问的路径，from表示从哪里来，next是下一个要做的操作 router.beforeEach((to,from,next)=>{ if(to.path === '/login') return next(); //获取token const tokenStr = window.sessionStorage.getItem('token'); if(!tokenStr) return next('/login'); next(); }) export default router 实现退出功能在Home组件中添加一个退出功能按钮,给退出按钮添加点击事件，添加事件处理代码如下：export default { methods:{ logout(){ window.sessionStorage.clear(); this.$router.push('/login'); } } } 补充A.处理ESLint警告打开脚手架面板，查看警告信息[图片]默认情况下，ESLint和vscode格式化工具有冲突，需要添加配置文件解决冲突。在项目根目录添加 .prettierrc 文件{ "semi":false, "singleQuote":true } 打开.eslintrc.js文件，禁用对 space-before-function-paren 的检查: rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 'space-before-function-paren' : 0 }, B.合并按需导入的element-uiimport Vue from 'vue' import { Button, Form, FormItem, Input, Message } from 'element-ui' Vue.use(Button) Vue.use(Form) Vue.use(FormItem) Vue.use(Input) // 进行全局挂载： Vue.prototype.$message = Message C.将代码提交到码云新建一个项目终端,输入命令‘git status’查看修改过的与新增的文件内容将所有文件添加到暂存区：git add .将所有代码提交到本地仓库：git commit -m "添加登录功能以及/home的基本结构"查看分支： git branch 发现所有代码都被提交到了login分支将login分支代码合并到master主分支，先切换到master：git checkout master在master分支进行代码合并：git merge login将本地的master推送到远端的码云：git push推送本地的子分支到码云，先切换到子分支：git checkout 分支名然后推送到码云：git push -u origin 远端分支名
A.浏览器端的模块化 1).AMD(Asynchronous Module Definition,异步模块定义) 代表产品为：Require.js 2).CMD(Common Module Definition,通用模块定义) 代表产品为：Sea.js B.服务器端的模块化 服务器端的模块化规范是使用CommonJS规范： 1).使用require引入其他模块或者包 2).使用exports或者module.exports导出模块成员 3).一个文件就是一个模块，都拥有独立的作用域 C.ES6模块化 ES6模块化规范中定义： 1).每一个js文件都是独立的模块 2).导入模块成员使用import关键字 3).暴露模块成员使用export关键字 小结：推荐使用ES6模块化，因为AMD，CMD局限使用与浏览器端，而CommonJS在服务器端使用。ES6模块化是浏览器端和服务器端通用的规范.2.在NodeJS中安装babelA.安装babel打开终端，输入命令：npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node 安装完毕之后，再次输入命令安装：npm install --save @babel/polyfill B.创建babel.config.js在项目目录中创建babel.config.js文件。 编辑js文件中的代码如下： const presets = [ ["@babel/env",{ targets:{ edge:"17", firefox:"60", chrome:"67", safari:"11.1" } }] ] //暴露 module.exports = { presets } C.创建index.js文件在项目目录中创建index.js文件作为入口文件 在index.js中输入需要执行的js代码，例如： console.log("ok"); D.使用npx执行文件打开终端，输入命令：npx babel-node ./index.js 3.设置默认导入/导出A.默认导出export default { 成员A, 成员B, ....... },如下： let num = 100; export default{ num } B.默认导入import 接收名称 from "模块标识符"，如下： import test from "./test.js" 注意：在一个模块中，只允许使用export default向外默认暴露一次成员，千万不要写多个export default。如果在一个模块中没有向外暴露成员，其他模块引入该模块时将会得到一个空对象4.设置按需导入/导出A.按需导出export let num = 998; export let myName = "jack"; export function fn = function(){ console.log("fn") } B.按需导入import { num,fn as printFn ,myName } from "./test.js" //同时导入默认导出的成员以及按需导入的成员 import test,{ num,fn as printFn ,myName } from "./test.js" 注意：一个模块中既可以按需导入也可以默认导入，一个模块中既可以按需导出也可以默认导出5.直接导入并执行代码import "./test2.js"; 6.webpack的概念webpack是一个流行的前端项目构建工具，可以解决目前web开发的困境。webpack提供了模块化支持，代码压缩混淆，解决js兼容问题，性能优化等特性，提高了开发效率和项目的可维护性7.webpack的基本使用A.创建项目目录并初始化创建项目，并打开项目所在目录的终端，输入命令： npm init -y B.创建首页及js文件在项目目录中创建index.html页面，并初始化页面结构：在页面中摆放一个ul，ul里面放置几个li 在项目目录中创建js文件夹，并在文件夹中创建index.js文件 C.安装jQuery打开项目目录终端，输入命令: npm install jQuery -S D.导入jQuery打开index.js文件，编写代码导入jQuery并实现功能： import $ from "jquery"; $(function(){ $("li:odd").css("background","cyan"); $("li:odd").css("background","pink"); }) 注意：此时项目运行会有错误，因为import $ from "jquery";这句代码属于ES6的新语法代码，在浏览器中可能会存在兼容性问题所以我们需要webpack来帮助我们解决这个问题。E.安装webpack1).打开项目目录终端，输入命令: npm install webpack webpack-cli -D 2).然后在项目根目录中，创建一个 webpack.config.js 的配置文件用来配置webpack 在 webpack.config.js 文件中编写代码进行webpack配置，如下： module.exports = { mode:"development"//可以设置为development(开发模式)，production(发布模式) } 补充：mode设置的是项目的编译模式。 如果设置为development则表示项目处于开发阶段，不会进行压缩和混淆，打包速度会快一些 如果设置为production则表示项目处于上线发布阶段，会进行压缩和混淆，打包速度会慢一些 3).修改项目中的package.json文件添加运行脚本dev，如下： "scripts":{ "dev":"webpack" } 注意：scripts节点下的脚本，可以通过 npm run 运行，如： 运行终端命令：npm run dev 将会启动webpack进行项目打包 4).运行dev命令进行项目打包，并在页面中引入项目打包生成的js文件 打开项目目录终端，输入命令: npm run dev 等待webpack打包完毕之后，找到默认的dist路径中生成的main.js文件，将其引入到html页面中。 浏览页面查看效果。 8.设置webpack的打包入口/出口在webpack 4.x中，默认会将src/index.js 作为默认的打包入口js文件 默认会将dist/main.js 作为默认的打包输出js文件 如果不想使用默认的入口/出口js文件，我们可以通过改变 webpack.config.js 来设置入口/出口的js文件，如下： const path = require("path"); module.exports = { mode:"development", //设置入口文件路径 entry: path.join(__dirname,"./src/xx.js"), //设置出口文件 output:{ //设置路径 path:path.join(__dirname,"./dist"), //设置文件名 filename:"res.js" } } 9.设置webpack的自动打包默认情况下，我们更改入口js文件的代码，需要重新运行命令打包webpack，才能生成出口的js文件 那么每次都要重新执行命令打包，这是一个非常繁琐的事情，那么，自动打包可以解决这样繁琐的操作。 实现自动打包功能的步骤如下： A.安装自动打包功能的包:webpack-dev-server npm install webpack-dev-server -D B.修改package.json中的dev指令如下： "scripts":{ "dev":"webpack-dev-server" } C.将引入的js文件路径更改为： D.运行npm run dev，进行打包 E.打开网址查看效果：http://localhost:8080 注意：webpack-dev-server自动打包的输出文件，默认放到了服务器的根目录中. 补充：在自动打包完毕之后，默认打开服务器网页，实现方式就是打开package.json文件，修改dev命令："dev": "webpack-dev-server --open --host 127.0.0.1 --port 9999"10.配置html-webpack-plugin使用html-webpack-plugin 可以生成一个预览页面。 因为当我们访问默认的 http://localhost:8080/的时候，看到的是一些文件和文件夹，想要查看我们的页面 还需要点击文件夹点击文件才能查看，那么我们希望默认就能看到一个页面，而不是看到文件夹或者目录。 实现默认预览页面功能的步骤如下： A.安装默认预览功能的包:html-webpack-plugin npm install html-webpack-plugin -D B.修改webpack.config.js文件，如下： //导入包 const HtmlWebpackPlugin = require("html-webpack-plugin"); //创建对象 const htmlPlugin = new HtmlWebpackPlugin({ //设置生成预览页面的模板文件 template:"./src/index.html", //设置生成的预览页面名称 filename:"index.html" }) C.继续修改webpack.config.js文件，添加plugins信息： module.exports = { ...... plugins:[ htmlPlugin ] } 11.webpack中的加载器通过loader打包非js模块：默认情况下，webpack只能打包js文件，如果想要打包非js文件，需要调用loader加载器才能打包 loader加载器包含： 1).less-loader 2).sass-loader 3).url-loader:打包处理css中与url路径有关的文件 4).babel-loader:处理高级js语法的加载器 5).postcss-loader 6).css-loader,style-loader 注意：指定多个loader时的顺序是固定的，而调用loader的顺序是从后向前进行调用 A.安装style-loader,css-loader来处理样式文件 1).安装包 npm install style-loader css-loader -D 2).配置规则：更改webpack.config.js的module中的rules数组 module.exports = { ...... plugins:[ htmlPlugin ], module : { rules:[ { //test设置需要匹配的文件类型，支持正则 test:/\.css$/, //use表示该文件类型需要调用的loader use:['style-loader','css-loader'] } ] } } B.安装less,less-loader处理less文件 1).安装包 npm install less-loader less -D 2).配置规则：更改webpack.config.js的module中的rules数组 module.exports = { ...... plugins:[ htmlPlugin ], module : { rules:[ { //test设置需要匹配的文件类型，支持正则 test:/\.css$/, //use表示该文件类型需要调用的loader use:['style-loader','css-loader'] }, { test:/\.less$/, use:['style-loader','css-loader','less-loader'] } ] } } C.安装sass-loader,node-sass处理less文件 1).安装包 npm install sass-loader node-sass -D 2).配置规则：更改webpack.config.js的module中的rules数组 module.exports = { ...... plugins:[ htmlPlugin ], module : { rules:[ { //test设置需要匹配的文件类型，支持正则 test:/\.css$/, //use表示该文件类型需要调用的loader use:['style-loader','css-loader'] }, { test:/\.less$/, use:['style-loader','css-loader','less-loader'] }, { test:/\.scss$/, use:['style-loader','css-loader','sass-loader'] } ] } } 补充：安装sass-loader失败时，大部分情况是因为网络原因，详情参考： https://segmentfault.com/a/1190000010984731?utm_source=tag-newest D.安装post-css自动添加css的兼容性前缀（-ie-,-webkit-）1).安装包 npm install postcss-loader autoprefixer -D 2).在项目根目录创建并配置postcss.config.js文件 const autoprefixer = require("autoprefixer"); module.exports = { plugins:[ autoprefixer ] } 3).配置规则：更改webpack.config.js的module中的rules数组 module.exports = { ...... plugins:[ htmlPlugin ], module : { rules:[ { //test设置需要匹配的文件类型，支持正则 test:/\.css$/, //use表示该文件类型需要调用的loader use:['style-loader','css-loader','postcss-loader'] }, { test:/\.less$/, use:['style-loader','css-loader','less-loader'] }, { test:/\.scss$/, use:['style-loader','css-loader','sass-loader'] } ] } } E.打包样式表中的图片以及字体文件在样式表css中有时候会设置背景图片和设置字体文件，一样需要loader进行处理 使用url-loader和file-loader来处理打包图片文件以及字体文件 1).安装包 npm install url-loader file-loader -D 2).配置规则：更改webpack.config.js的module中的rules数组 module.exports = { ...... plugins:[ htmlPlugin ], module : { rules:[ { //test设置需要匹配的文件类型，支持正则 test:/\.css$/, //use表示该文件类型需要调用的loader use:['style-loader','css-loader'] }, { test:/\.less$/, use:['style-loader','css-loader','less-loader'] }, { test:/\.scss$/, use:['style-loader','css-loader','sass-loader'] },{ test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/, //limit用来设置字节数，只有小于limit值的图片，才会转换 //为base64图片 use:"url-loader?limit=16940" } ] } } F.打包js文件中的高级语法：在编写js的时候，有时候我们会使用高版本的js语法有可能这些高版本的语法不被兼容，我们需要将之打包为兼容性的js代码 我们需要安装babel系列的包 A.安装babel转换器 npm install babel-loader @babel/core @babel/runtime -D B.安装babel语法插件包 npm install @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-class-properties -D C.在项目根目录创建并配置babel.config.js文件 module.exports = { presets:["@babel/preset-env"], plugins:[ "@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties" ] } D.配置规则：更改webpack.config.js的module中的rules数组module.exports = { ...... plugins:[ htmlPlugin ], module : { rules:[ { //test设置需要匹配的文件类型，支持正则 test:/\.css$/, //use表示该文件类型需要调用的loader use:['style-loader','css-loader'] }, { test:/\.less$/, use:['style-loader','css-loader','less-loader'] }, { test:/\.scss$/, use:['style-loader','css-loader','sass-loader'] },{ test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/, //limit用来设置字节数，只有小于limit值的图片，才会转换 //为base64图片 use:"url-loader?limit=16940" },{ test:/\.js$/, use:"babel-loader", //exclude为排除项，意思是不要处理node_modules中的js文件 exclude:/node_modules/ } ] } } 12.Vue单文件组件传统Vue组件的缺陷：全局定义的组件不能重名，字符串模板缺乏语法高亮，不支持css(当html和js组件化时，css没有参与其中)没有构建步骤限制，只能使用H5和ES5，不能使用预处理器（babel）解决方案：使用Vue单文件组件，每个单文件组件的后缀名都是.vue每一个Vue单文件组件都由三部分组成1).template组件组成的模板区域2).script组成的业务逻辑区域3).style样式区域代码如下： 组件代码区域 补充：安装Vetur插件可以使得.vue文件中的代码高亮配置.vue文件的加载器A.安装vue组件的加载器npm install vue-loader vue-template-compiler -D B.配置规则：更改webpack.config.js的module中的rules数组const VueLoaderPlugin = require("vue-loader/lib/plugin"); const vuePlugin = new VueLoaderPlugin(); module.exports = { ...... plugins:[ htmlPlugin, vuePlugin ], module : { rules:[ ...//其他规则 { test:/\.vue$/, loader:"vue-loader", } ] } } 13.在webpack中使用vue上一节我们安装处理了vue单文件组件的加载器，想要让vue单文件组件能够使用，我们必须要安装vue并使用vue来引用vue单文件组件。A.安装Vuenpm install vue -S B.在index.js中引入vue：import Vue from "vue"C.创建Vue实例对象并指定el，最后使用render函数渲染单文件组件const vm = new Vue({el:"#first",render:h=>h(app)})14.使用webpack打包发布项目在项目上线之前，我们需要将整个项目打包并发布。A.配置package.json"scripts":{ "dev":"webpack-dev-server", "build":"webpack -p" } B.在项目打包之前，可以将dist目录删除，生成全新的dist目录15.Vue脚手架Vue脚手架可以快速生成Vue项目基础的架构。A.安装3.x版本的Vue脚手架：npm install -g @vue/cli B.基于3.x版本的脚手架创建Vue项目：1).使用命令创建Vue项目 命令：vue create my-project 选择Manually select features(选择特性以创建项目) 勾选特性可以用空格进行勾选。 是否选用历史模式的路由：n ESLint选择：ESLint + Standard config 何时进行ESLint语法校验：Lint on save babel，postcss等配置文件如何放置：In dedicated config files(单独使用文件进行配置) 是否保存为模板：n 使用哪个工具安装包：npm 2).基于ui界面创建Vue项目 命令：vue ui 在自动打开的创建项目网页中配置项目信息。 3).基于2.x的旧模板，创建Vue项目 npm install -g @vue/cli-init vue init webpack my-project C.分析Vue脚手架生成的项目结构node_modules:依赖包目录 public：静态资源目录 src：源码目录 src/assets:资源目录 src/components：组件目录 src/views:视图组件目录 src/App.vue:根组件 src/main.js:入口js src/router.js:路由js babel.config.js:babel配置文件 .eslintrc.js: 16.Vue脚手架的自定义配置A.通过 package.json 进行配置 [不推荐使用] "vue":{ "devServer":{ "port":"9990", "open":true } } B.通过单独的配置文件进行配置，创建vue.config.js module.exports = { devServer:{ port:8888, open:true } } 17.Element-UI的基本使用Element-UI:一套基于2.0的桌面端组件库官网地址：http://element-cn.eleme.io/#/zh-CNA.安装：npm install element-ui -S B.导入使用：import ElementUI from "element-ui"; import "element-ui/lib/theme-chalk/index.css"; Vue.use(ElementUI)
路由的本质就是一种对应关系，比如说我们在url地址中输入我们要访问的url地址之后，浏览器要去请求这个url地址对应的资源。 那么url地址和真实的资源之间就有一种对应的关系，就是路由 路由分为前端路由和后端路由1).后端路由是由服务器端进行实现，并完成资源的分发2).前端路由是依靠hash值(锚链接)的变化进行实现后端路由性能相对前端路由来说较低，所以，我们接下来主要学习的是前端路由前端路由的基本概念：根据不同的事件来显示不同的页面内容，即事件与事件处理函数之间的对应关系前端路由主要做的事情就是监听事件并分发执行事件处理函数2.前端路由的初体验前端路由是基于hash值的变化进行实现的（比如点击页面中的菜单或者按钮改变URL的hash值，根据hash值的变化来控制组件的切换）核心实现依靠一个事件，即监听hash值变化的事件window.onhashchange = function(){ //location.hash可以获取到最新的hash值 location.hash } 前端路由实现tab栏切换： Document 主页 科技 财经 娱乐 <component :is="comName"></component> 案例效果图：点击每个超链接之后，会进行相应的内容切换，如下：核心思路：在页面中有一个vue实例对象，vue实例对象中有四个组件，分别是tab栏切换需要显示的组件内容在页面中有四个超链接，如下：主页 科技 财经 娱乐 当我们点击这些超链接的时候，就会改变url地址中的hash值，当hash值被改变时，就会触发onhashchange事件在触发onhashchange事件的时候，我们根据hash值来让不同的组件进行显示：window.onhashchange = function() { // 通过 location.hash 获取到最新的 hash 值 console.log(location.hash); switch(location.hash.slice(1)){ case '/zhuye': //通过更改数据comName来指定显示的组件 //因为 <component :is="comName"></component> ，组件已经绑定了comName vm.comName = 'zhuye' break case '/keji': vm.comName = 'keji' break case '/caijing': vm.comName = 'caijing' break case '/yule': vm.comName = 'yule' break } } 3.Vue Router简介它是一个Vue.js官方提供的路由管理器。是一个功能更加强大的前端路由器，推荐使用。Vue Router和Vue.js非常契合，可以一起方便的实现SPA(single page web application,单页应用程序)应用程序的开发。Vue Router依赖于Vue，所以需要先引入Vue，再引入Vue RouterVue Router的特性：支持H5历史模式或者hash模式支持嵌套路由支持路由参数支持编程式路由支持命名路由支持路由导航守卫支持路由过渡动画特效支持路由懒加载支持路由滚动行为4.Vue Router的使用步骤(★★★)A.导入js文件B.添加路由链接:是路由中提供的标签，默认会被渲染为a标签，to属性默认被渲染为href属性，to属性的值会被渲染为#开头的hash地址UserLoginC.添加路由填充位（路由占位符）D.定义路由组件var User = { template:"This is User" }var Login = { template:"This is Login" }E.配置路由规则并创建路由实例var myRouter = new VueRouter({//routes是路由规则数组routes:[//每一个路由规则都是一个对象，对象中至少包含path和component两个属性//path表示 路由匹配的hash地址，component表示路由规则对应要展示的组件对象{path:"/user",component:User},{path:"/login",component:Login}]})F.将路由挂载到Vue实例中new Vue({el:"#app",//通过router属性挂载路由对象router:myRouter})小结：Vue Router的使用步骤还是比较清晰的，按照步骤一步一步就能完成路由操作A.导入js文件B.添加路由链接C.添加路由占位符(最后路由展示的组件就会在占位符的位置显示)D.定义路由组件E.配置路由规则并创建路由实例F.将路由挂载到Vue实例中补充：路由重定向：可以通过路由重定向为页面设置默认展示的组件在路由规则中添加一条路由规则即可，如下：var myRouter = new VueRouter({//routes是路由规则数组routes: [//path设置为/表示页面最初始的地址 / ,redirect表示要被重定向的新地址，设置为一个路由即可{ path:"/",redirect:"/user"},{ path: "/user", component: User },{ path: "/login", component: Login }]})5.嵌套路由，动态路由的实现方式A.嵌套路由的概念(★★★)当我们进行路由的时候显示的组件中还有新的子级路由链接以及内容。嵌套路由最关键的代码在于理解子级路由的概念：比如我们有一个/login的路由那么/login下面还可以添加子级路由，如:/login/account/login/phone参考代码如下：var User = { template: " This is User " } //Login组件中的模板代码里面包含了子级路由链接以及子级路由的占位符 var Login = { template: ` This is Login <router-link to="/login/account">账号密码登录</router-link> <router-link to="/login/phone">扫码登录</router-link> <router-view></router-view> ` } //定义两个子级路由组件 var account = { template:" 账号： 密码： "}; var phone = { template:" 扫我二维码 "}; var myRouter = new VueRouter({ //routes是路由规则数组 routes: [ { path:"/",redirect:"/user"}, { path: "/user", component: User }, { path: "/login", component: Login, //通过children属性为/login添加子路由规则 children:[ { path: "/login/account", component: account }, { path: "/login/phone", component: phone }, ] } ] }) var vm = new Vue({ el: '#app', data: {}, methods: {}, router:myRouter }); 页面效果大致如下：B.动态路由匹配(★★★)var User = { template:"用户：{{$route.params.id}}"}var myRouter = new VueRouter({//routes是路由规则数组routes: [//通过/:参数名 的形式传递参数{ path: "/user/:id", component: User },] })补充：如果使用$route.params.id来获取路径传参的数据不够灵活。1.我们可以通过props来接收参数var User = {props:["id"],template:"用户：{{id}}"}var myRouter = new VueRouter({//routes是路由规则数组routes: [//通过/:参数名 的形式传递参数//如果props设置为true，route.params将会被设置为组件属性{ path: "/user/:id", component: User,props:true },] })2.还有一种情况，我们可以将props设置为对象，那么就直接将对象的数据传递给组件进行使用var User = {props:["username","pwd"],template:"用户：{{username}}---{{pwd}}"}var myRouter = new VueRouter({//routes是路由规则数组routes: [//通过/:参数名 的形式传递参数//如果props设置为对象，则传递的是对象中的数据给组件{ path: "/user/:id", component: User,props:{username:"jack",pwd:123} },] })3.如果想要获取传递的参数值还想要获取传递的对象数据，那么props应该设置为函数形式。var User = {props:["username","pwd","id"],template:"用户：{{id}} -> {{username}}---{{pwd}}"}var myRouter = new VueRouter({//routes是路由规则数组routes: [//通过/:参数名 的形式传递参数//如果props设置为函数，则通过函数的第一个参数获取路由对象//并可以通过路由对象的params属性获取传递的参数//{ path: "/user/:id", component: User,props:(route)=>{return {username:"jack",pwd:123,id:route.params.id}}},] })7.命名路由以及编程式导航A.命名路由：给路由取别名案例：var myRouter = new VueRouter({//routes是路由规则数组routes: [//通过name属性为路由添加一个别名{ path: "/user/:id", component: User, name:"user"},] })//添加了别名之后，可以使用别名进行跳转UserUser//还可以编程式导航myRouter.push( { name:'user' , params: {id:123} } )####B.编程式导航(★★★)页面导航的两种方式：A.声明式导航：通过点击链接的方式实现的导航B.编程式导航：调用js的api方法实现导航Vue-Router中常见的导航方式：this.$router.push("hash地址");this.$router.push("/login");this.$router.push({ name:'user' , params: {id:123} });this.$router.push({ path:"/login" });this.$router.push({ path:"/login",query:{username:"jack"} });this.$router.go( n );//n为数字，参考history.gothis.$router.go( -1 );8.实现后台管理案例(★★★)案例效果：点击左侧的"用户管理","权限管理","商品管理","订单管理","系统设置"都会出现对应的组件并展示内容其中"用户管理"组件展示的效果如上图所示，在用户管理区域中的详情链接也是可以点击的，点击之后将会显示用户详情信息。案例思路：1).先将素材文件夹中的11.基于vue-router的案例.html复制到我们自己的文件夹中。看一下这个文件中的代码编写了一些什么内容，这个页面已经把后台管理页面的基本布局实现了2).在页面中引入vue，vue-router3).创建Vue实例对象，准备开始编写代码实现功能4).希望是通过组件的形式展示页面的主体内容，而不是写死页面结构，所以我们可以定义一个根组件：//只需要把原本页面中的html代码设置为组件中的模板内容即可 const app = { template:` 传智后台管理系统 用户管理 权限管理 商品管理 订单管理 系统设置 添加用户表单 版权信息 ` } 5).当我们访问页面的时候，默认需要展示刚刚创建的app根组件，我们可以创建一个路由对象来完成这个事情,然后将路由挂载到Vue实例对象中即可const myRouter = new VueRouter({ routes:[ {path:"/",component:app} ] }) const vm = new Vue({ el:"#app", data:{}, methods:{}, router:myRouter }) 补充：到此为止，基本的js代码都处理完毕了，我们还需要设置一个路由占位符 <router-view></router-view> 6).此时我们打开页面应该就可以得到一个VueRouter路由出来的根组件了我们需要在这个根组件中继续路由实现其他的功能子组件先让我们更改根组件中的模板：更改左侧li为子级路由链接，并在右侧内容区域添加子级组件占位符const app = { template:` ........ <router-link to="/users">用户管理</router-link> <router-link to="/accesses">权限管理</router-link> <router-link to="/goods">商品管理</router-link> <router-link to="/orders">订单管理</router-link> <router-link to="/systems">系统设置</router-link> <router-view></router-view> ....... ` } 然后，我们要为子级路由创建并设置需要显示的子级组件//建议创建的组件首字母大写，和其他内容区分 const Users = {template:` 用户管理 `} const Access = {template:` 权限管理 `} const Goods = {template:` 商品管理 `} const Orders = {template:` 订单管理 `} const Systems = {template:` 系统管理 `} //添加子组件的路由规则 const myRouter = new VueRouter({ routes:[ {path:"/",component:app , children:[ { path:"/users",component:Users }, { path:"/accesses",component:Access }, { path:"/goods",component:Goods }, { path:"/orders",component:Orders }, { path:"/systems",component:Systems }, ]} ] }) const vm = new Vue({ el:"#app", data:{}, methods:{}, router:myRouter }) 7).展示用户信息列表：A.为Users组件添加私有数据,并在模板中循环展示私有数据​ const Users = { data(){ return { userList:[ {id:1,name:"zs",age:18}, {id:2,name:"ls",age:19}, {id:3,name:"wang",age:20}, {id:4,name:"jack",age:21}, ] } }, template:` 用户管理 编号 姓名 年龄 操作 {{item.id}} {{item.name}} {{item.age}} 详情 `} ​8.当用户列表展示完毕之后，我们可以点击列表中的详情来显示用户详情信息，首先我们需要创建一个组件，用来展示详情信息const UserInfo = { props:["id"], template:` 用户详情 查看 {{id}} 号用户信息 返回用户详情页 `, methods:{ goBack(){ //当用户点击按钮，后退一页 this.$router.go(-1); } } } 然后我们需要设置这个组件的路由规则const myRouter = new VueRouter({ routes:[ {path:"/",component:app , children:[ { path:"/users",component:Users }, //添加一个/userinfo的路由规则 { path:"/userinfo/:id",component:UserInfo,props:true}, { path:"/accesses",component:Access }, { path:"/goods",component:Goods }, { path:"/orders",component:Orders }, { path:"/systems",component:Systems }, ]} ] }) const vm = new Vue({ el:"#app", data:{}, methods:{}, router:myRouter }) 再接着给用户列表中的详情a连接添加事件const Users = { data(){ return { userList:[ {id:1,name:"zs",age:18}, {id:2,name:"ls",age:19}, {id:3,name:"wang",age:20}, {id:4,name:"jack",age:21}, ] } }, template:` 用户管理 编号 姓名 年龄 操作 {{item.id}} {{item.name}} {{item.age}} 详情 `, methods:{ goDetail(id){ this.$router.push("/userinfo/"+id); } } }
原生ajax 基于jQuery的ajax fetch axios 异步 JavaScript的执行环境是「单线程」 所谓单线程，是指JS引擎中负责解释和执行JavaScript代码的线程只有一个，也就是一次只能完成一项任务，这个任务执行完后才能执行下一个，它会「阻塞」其他任务。这个任务可称为主线程 异步模式可以一起执行多个任务 JS中常见的异步调用 定时任何 ajax 事件函数 promise 主要解决异步深层嵌套的问题 promise 提供了简洁的API 使得异步操作更加容易 基于Promise发送Ajax请求 Promise 基本API实例方法.then() 得到异步任务正确的结果 .catch() 获取异常信息 .finally() 成功与否都会执行（不是正式标准） 静态方法.all() Promise.all方法接受一个数组作参数，数组中的对象（p1、p2、p3）均为promise实例（如果不是一个promise，该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定 .race() Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变（变为fulfilled或rejected），p的状态就跟着改变。并把第一个改变状态的promise的返回值，传给p的回调函数 ​ fetch Fetch API是新的ajax解决方案 Fetch会返回Promise fetch不是ajax的进一步封装，而是原生js，没有使用XMLHttpRequest对象。 fetch(url, options).then(） fetch API 中的 HTTP 请求 fetch(url, options).then(） HTTP协议，它给我们提供了很多的方法，如POST，GET，DELETE，UPDATE，PATCH和PUT 默认的是 GET 请求 需要在 options 对象中 指定对应的 method method:请求使用的方法 post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body fetchAPI 中 响应格式 用fetch来获取数据，如果响应正常返回，我们首先看到的是一个response对象，其中包括返回的一堆原始字节，这些字节需要在收到后，需要我们通过调用方法将其转换为相应格式的数据，比如JSON，BLOB或者TEXT等等 /* Fetch响应结果的数据格式 */ fetch('http://localhost:3000/json').then(function(data){ // return data.json(); // 将获取到的数据使用 json 转换对象 return data.text(); // // 将获取到的数据 转换成字符串 }).then(function(data){ // console.log(data.uname) // console.log(typeof data) var obj = JSON.parse(data); console.log(obj.uname,obj.age,obj.gender) }) axios 基于promise用于浏览器和node.js的http客户端 支持浏览器和node.js 支持promise 能拦截请求和响应 自动转换JSON数据 能转换请求和响应数据 axios基础用法 get和 delete请求传递参数 通过传统的url 以 ? 的形式传递参数 restful 形式传递参数 通过params 形式传递参数 post 和 put 请求传递参数 通过选项传递参数 通过 URLSearchParams 传递参数 # 1. 发送get 请求 axios.get('http://localhost:3000/adata').then(function(ret){ # 拿到 ret 是一个对象 所有的对象都存在 ret 的data 属性里面 // 注意data属性是固定的用法，用于获取后台的实际数据 // console.log(ret.data) console.log(ret) }) # 2. get 请求传递参数 # 2.1 通过传统的url 以 ? 的形式传递参数 axios.get('http://localhost:3000/axios?id=123').then(function(ret){ console.log(ret.data) }) # 2.2 restful 形式传递参数 axios.get('http://localhost:3000/axios/123').then(function(ret){ console.log(ret.data) }) # 2.3 通过params 形式传递参数 axios.get('http://localhost:3000/axios', { params: { id: 789 } }).then(function(ret){ console.log(ret.data) }) #3 axios delete 请求传参 传参的形式和 get 请求一样 axios.delete('http://localhost:3000/axios', { params: { id: 111 } }).then(function(ret){ console.log(ret.data) }) # 4 axios 的 post 请求 # 4.1 通过选项传递参数 axios.post('http://localhost:3000/axios', { uname: 'lisi', pwd: 123 }).then(function(ret){ console.log(ret.data) }) # 4.2 通过 URLSearchParams 传递参数 var params = new URLSearchParams(); params.append('uname', 'zhangsan'); params.append('pwd', '111'); axios.post('http://localhost:3000/axios', params).then(function(ret){ console.log(ret.data) }) #5 axios put 请求传参 和 post 请求一样 axios.put('http://localhost:3000/axios/123', { uname: 'lisi', pwd: 123 }).then(function(ret){ console.log(ret.data) }) axios 全局配置# 配置公共的请求头 axios.defaults.baseURL = 'https://api.example.com'; # 配置 超时时间 axios.defaults.timeout = 2500; # 配置公共的请求头 axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; # 配置公共的 post 的 Content-Type axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; axios 拦截器 请求拦截器 请求拦截器的作用是在请求发送前进行一些操作 例如在每个请求体里加上token，统一做了处理如果以后要改也非常容易 响应拦截器 响应拦截器的作用是在接收到响应后进行一些操作 例如在服务器返回登录状态失效，需要重新登录的时候，跳转到登录页 # 1. 请求拦截器 axios.interceptors.request.use(function(config) { console.log(config.url) # 1.1 任何请求都会经过这一步 在发送请求之前做些什么 config.headers.mytoken = 'nihao'; # 1.2 这里一定要return 否则配置不成功 return config; }, function(err){ #1.3 对请求错误做点什么 console.log(err) }) #2. 响应拦截器 axios.interceptors.response.use(function(res) { #2.1 在接收响应做些什么 var data = res.data; return data; }, function(err){ #2.2 对响应错误做点什么 console.log(err) }) async 和 await async作为一个关键字放到函数前面 任何一个async函数都会隐式返回一个promise await关键字只能在使用async定义的函数中使用 ​ await后面可以直接跟一个 Promise实例对象 ​ await函数不能单独使用 async/await 让异步代码看起来、表现起来更像同步代码 # 1. async 基础用法 # 1.1 async作为一个关键字放到函数前面 async function queryData() { # 1.2 await关键字只能在使用async定义的函数中使用 await后面可以直接跟一个 Promise实例对象 var ret = await new Promise(function(resolve, reject){ setTimeout(function(){ resolve('nihao') },1000); }) // console.log(ret.data) return ret; } # 1.3 任何一个async函数都会隐式返回一个promise 我们可以使用then 进行链式编程 queryData().then(function(data){ console.log(data) }) #2. async 函数处理多个异步函数 axios.defaults.baseURL = 'http://localhost:3000'; async function queryData() { # 2.1 添加await之后 当前的await 返回结果之后才会执行后面的代码 var info = await axios.get('async1'); #2.2 让异步代码看起来、表现起来更像同步代码 var ret = await axios.get('async2?info=' + info.data); return ret.data; } queryData().then(function(data){ console.log(data) }) 图书列表案例1. 基于接口案例-获取图书列表 导入axios 用来发送ajax 把获取到的数据渲染到页面上 编号 名称 时间 操作 {{item.id}} {{item.name}} {{item.date }} 修改 | 删除 1. 导入axios 2 添加图书 获取用户输入的数据 发送到后台 渲染最新的数据到页面上 methods: { handle: async function(){ if(this.flag) { // 编辑图书 // 就是根据当前的ID去更新数组中对应的数据 this.books.some((item) => { if(item.id == this.id) { item.name = this.name; // 完成更新操作之后，需要终止循环 return true; } }); this.flag = false; }else{ # 1.1 在前面封装好的 handle 方法中 发送ajax请求 # 1.2 使用async 和 await 简化操作 需要在 function 前面添加 async var ret = await axios.post('books', { name: this.name }) # 1.3 根据后台返回的状态码判断是否加载数据 if(ret.status == 200) { # 1.4 调用 queryData 这个方法 渲染最新的数据 this.queryData(); } } // 清空表单 this.id = ''; this.name = ''; }, } 3 验证图书名称是否存在 添加图书之前发送请求验证图示是否已经存在 如果不存在 往后台里面添加图书名称 图书存在与否只需要修改submitFlag的值即可 watch: { name: async function(val) { // 验证图书名称是否已经存在 // var flag = this.books.some(function(item){ // return item.name == val; // }); var ret = await axios.get('/books/book/' + this.name); if(ret.status == 1) { // 图书名称存在 this.submitFlag = true; }else{ // 图书名称不存在 this.submitFlag = false; } } }, 4. 编辑图书 根据当前书的id 查询需要编辑的书籍 需要根据状态位判断是添加还是编辑 methods: { handle: async function(){ if(this.flag) { #4.3 编辑图书 把用户输入的信息提交到后台 var ret = await axios.put('books/' + this.id, { name: this.name }); if(ret.status == 200){ #4.4 完成添加后 重新加载列表数据 this.queryData(); } this.flag = false; }else{ // 添加图书 var ret = await axios.post('books', { name: this.name }) if(ret.status == 200) { // 重新加载列表数据 this.queryData(); } } // 清空表单 this.id = ''; this.name = ''; }, toEdit: async function(id){ #4.1 flag状态位用于区分编辑和添加操作 this.flag = true; #4.2 根据id查询出对应的图书信息 页面中可以加载出来最新的信息 # 调用接口发送ajax 请求 var ret = await axios.get('books/' + id); this.id = ret.id; this.name = ret.name; }, 5 删除图书 把需要删除的id书籍 通过参数的形式传递到后台 deleteBook: async function(id){ // 删除图书 var ret = await axios.delete('books/' + id); if(ret.status == 200) { // 重新加载列表数据 this.queryData(); } }
组件 (Component) 是 Vue.js 最强大的功能之一 组件可以扩展 HTML 元素，封装可重用的代 组件注册全局注册 Vue.component('组件名称', { }) 第1个参数是标签名称，第2个参数是一个选项对象 全局组件注册后，任何vue实例都可以用 组件基础用 <my-component></my-component> 组件注意事项 组件参数的data值必须是函数同时这个函数要求返回一个对象 组件模板必须是单个根元素 组件模板的内容可以是模板字符串 <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> <hello-world></hello-world> 局部注册 只能在当前注册它的vue实例中使用 <my-component></my-component> Vue 调试工具Vue组件之间传值父组件向子组件传值 父组件发送的形式是以属性的形式绑定值到子组件身上。 然后子组件用属性props接收 在props中使用驼峰形式，模板中需要使用短横线的形式字符串形式的模板中没有这个限制 {{pmsg}} <menu-item title='来自父组件的值'></menu-item> <menu-item :title='ptitle' content='hello'></menu-item> 子组件向父组件传值 子组件用$emit()触发事件 $emit() 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据 父组件用v-on 监听子组件的事件 {{pmsg}} <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item> 兄弟之间的传递 兄弟之间传递数据需要借助于事件中心，通过事件中心传递数据 提供事件中心 var hub = new Vue() 传递数据方，通过一个事件触发hub.$emit(方法名，传递的数据) 接收数据方，通过mounted(){} 钩子中 触发hub.$on()方法名 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据 父组件 销毁事件 <test-tom></test-tom> <test-jerry></test-jerry> 组件插槽 组件的最大特性就是复用性，而用好插槽能大大提高组件的可复用能力 匿名插槽 <alert-box>有bug发生</alert-box> <alert-box>有一个警告</alert-box> <alert-box></alert-box> 具名插槽 具有名字的插槽 使用 中的 "name" 属性绑定元素 <base-layout> 标题信息 主要内容1 主要内容2 底部信息信息 </base-layout> <base-layout> 标题信息1 标题信息2 主要内容1 主要内容2 底部信息信息1 底部信息信息2 </base-layout> 作用域插槽 父组件对子组件加工处理 既可以复用子组件的slot，又可以使slot内容不一致 <fruit-list :list='list'> {{slotProps.info.name}} {{slotProps.info.name}} </fruit-list> 购物车案例1. 实现组件化布局 把静态页面转换成组件化模式 把组件渲染到页面上 <my-cart></my-cart> 2、实现 标题和结算功能组件 标题组件实现动态渲染 从父组件把标题数据传递过来 即 父向子组件传值 把传递过来的数据渲染到页面上 结算功能组件 从父组件把商品列表list 数据传递过来 即 父向子组件传值 把传递过来的数据计算最终价格渲染到页面上 <my-cart></my-cart> 3. 实现列表组件删除功能 从父组件把商品列表list 数据传递过来 即 父向子组件传值 把传递过来的数据渲染到页面上 点击删除按钮的时候删除对应的数据 给按钮添加点击事件把需要删除的id传递过来 子组件中不推荐操作父组件的数据有可能多个子组件使用父组件的数据 我们需要把数据传递给父组件让父组件操作数据 父组件删除对应的数据 <my-cart></my-cart> 4. 实现组件更新数据功能 上 将输入框中的默认数据动态渲染出来 输入框失去焦点的时候 更改商品的数量 子组件中不推荐操作数据 把这些数据传递给父组件 让父组件处理这些数据 父组件中接收子组件传递过来的数据并处理 <my-cart></my-cart> 5. 实现组件更新数据功能 下 子组件通过一个标识符来标记对用的用户点击 + - 或者输入框输入的内容 父组件拿到标识符更新对应的组件 Document <my-cart></my-cart>