# 后台管理项目 **Repository Path**: RanD0mi2e/myAdmin ## Basic Information - **Project Name**: 后台管理项目 - **Description**: 这个项目是根据花裤衩大大的vue-admin-template作为模板,再结合自己的需求配合element-UI搭建的后台管理系统,可以用来控制商城前台项目的商品展示与否 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-02-21 - **Last Updated**: 2022-07-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 后台管理系统 ## 项目介绍 这个后台管理系统是基于花裤衩大大的[vue-admin-template](https://github.com/PanJiaChen/vue-admin-template )模板进行后台功能的开发,主要用到的技术为vue全家桶(vue2+vuex+vue-router)+axios+vue-cli和element-UI+基于ECharts自己封装的vue组件。 ## 后台数据服务器 通过后端提供的swagger文档设置api获取相应的数据,文档地址为: http://39.98.123.211:8170/swagger-ui.html http://39.98.123.211:8216/swagger-ui.html ## 功能 ### 登陆功能 1.使用默认的静态组件 2.把login中的promise的异步请求重构为async/await方法 3.utils文件夹下request文件对axios二次封装,将原来的mock数据改为真实接口需要: axios请求拦截的请求头注意修改token; 后台服务器返回的状态码验证为200/20000; 真实接口代理跨域问题(在vue.config.js中修改devServer,修改后切记重启!!!); 错误21002,json解析异常(一般是获取用户信息时发送的token异常)。 ### 商品管理模块 > 下面的图解和代码是在做项目的时候随笔写的,主要是为了记录做这个项目时遇到的一些问题和解决思路,本打算写完后梳理一遍,但是回头发现自己写得一团乱麻的ORZ... #### 品牌管理 ##### 添加/修改品牌 新添加的品牌不需要给后台传递id只需要传递url和name值。修改品牌则需要id url name三个值,通过判断有无id来确定添加/修改品牌: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/1644661367(1).jpg) 表单数据收集:利用v-model收集表单数据form ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/1644661809(1).jpg) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/Snipaste_2022-02-12_18-31-57.png) #### SPU管理页 SPU——在这个后台管理系统中指的就是产品种类(例如手机种类:苹果、安卓) **静态模块分析**:SPU主页布局为两个,第一个card内部使用之前封装好的全局组件; ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215151651.png) 由于Attrs,Spu,Sku组件都需要用到三级列表,并且都需要从三级列表获取到各级列表的categoryId,为了提高复用性,封装一个productMixin.js文件用来存放混入函数 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220214190208.png) 获取三级列表各级id的方法: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220214190259.png) 第二个card中有三种切换模式,分别为0:展示spu数据列表;1:添加/修改spu;2:添加sku,并根据data中的scene的数值进行切换。 1.默认spu展示页: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/Snipaste_2022-02-14_18-41-35.png) 2.添加/修改SPU页: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215135108.png) 添加/修改SPU使用的时同一张列表,结构相同,区别在于传入数据的不同。 组件结构: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215135250.png) SPU的table表单中的操作按钮使用之前写前台时封装的hintButton组件: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215105220.png) 组件的模板: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215112351.png) 其中$attrs接收父组件的传递的参数,$listeners访问传递给组件的事件监听 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215112554.png) 3.sku静态组件页 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216211736.png) **spuForm业务分析**: 需要用到的接口: ​ (1). GET /admin/product/baseTrademark/getTrademarkList:获取品牌数据 ​ (2). GET /admin/product/baseSaleAttrList:获取基础售卖属性 ​ (3). GET /admin/product/getSpuById/{spuId}:获得某一个spu信息 ​ (4).GET /admin/product/spuImageList/{spuId}:获取spu照片 由于spuForm子组件使用v-show进行显示隐藏,因此$mouted只挂载一次,不能把获取spu信息的函数放置在子组件的$mouted中。 解决方法:在父组件中利用$refs获取到子组件,直接调用子组件的方法(见下图) spu父组件 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215161000.png) spuForm子组件 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215190832.png) **照片墙模块**: spu图片的使用file-list属性上传一整个图片数组,但是这里会遇到一个问题,那就是**获取的spu数组和要上传的格式不一样**。 el-upload的file-list属性接收的数组属性分别为url和name,但是我们的spuImageList拿到的数据如下: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215184154.png) 拿不到正确的url和name属性所以图片无法显示。 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215184205.png) 所以对请求返回的spuImageList数据进行处理,添加name和url属性 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215190545.png) 图片的收集: ​ (1). on-preview,点击预览图片时的回调函数,通常通过这个函数收集dialogImageUrl参数 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215222626.png) ​ (2). on-remove,点击删除图片时的回调函数,通过这个函数更改spuImageList里的数据(这时候的操作和获取spuImageList数据时相反,需要删除数组每个元素的url和name并返回新数组) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215222550.png) ​ (3). on-success,图片上传成功后对调函数,可以用来修改spuImageList的数据(注意,在success钩子中必须替换本地图片url为服务器返回的图片url,否则会导致前台图片无法显示) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215222513.png) **销售属性模块**: 后台销售属性共有三个:颜色、尺码、版本 对于属性值名称这一栏,由于每一个属性都应该有自己的标签和输入框,因此不能在spuForm父组件中定义inputVisible和inputValue(在父组件中使用同一份数据会导致修改一个标签所有标签都会被修改的情况)。 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215204646.png) 剩余未选择销售属性的计算: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215205541.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220215212736.png) 类似于利用两个for循环筛选出合适的结果,分别对比两个属性列表数组中是否存在相同属性名,并返回一个新的未被添加的属性数组。 添加属性功能的实现: 添加一个销售属性需要(baseSaleAttrId,saleAttrName,spuSaleAttrValueList三个属性),点击【添加销售属性】按钮后触发回调函数,把这些参数传入spu数据中: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216104117.png) 添加属性值功能的实现: 在添加新的属性名后,需要往对应的属性值栏里添加新的属性值,因此需要给【+ 属性值按钮添加一个回调】 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216111045.png) 注意:后添加的响应式数据需要用到vm.set进行添加 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216112140.png) 最后给【属性值】失去焦点/回车设定一个事件,保证在属性值添加完成创建一个新tag标签并隐藏input输入框。 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216123840.png) 输入框失去焦点后出现的问题:**输入属性值回车后事件触发两次,生成两个相同的标签**。造成这种现象的原因在于blur和keyup.enter事件冲突,触发回车事件的同时也会触发blur事件! ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216115819.png) 为了修复这个问题,将上述代码改为: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216123411.png) 这样就可以规避enter触发两次事件啦! 删除属性/属性值功能: 从spuSaleAttrList数组中删除指定元素实现属性删除功能,从spuSaleAttrValueList数组删除指定元素实现删除属性值功能,二者都用到数组的splice方法。 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216151142.png) 通过作用域插槽中的$index获取到spuSaleAttrList数组的索引值,spuSaleAttrValueList数组索引值获取同理,之后使用splice($index,1)完成删除功能。 新增spu功能存在的问题: 由于新增/修改spu用的同一个spuForm页面,所以页面切换后清除原有数据十分重要,在组件点击保存或取消按钮回到spu列表页前,调用自定义的clear函数清理数据 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220216182440.png) Vue中提供了$options,可以通过$options获取到组件初始化时的数据,利用object.assign( )将初始化时的空数据替换掉更改后的数据即可完成清理页面的效果。 **skuForm业务分析** 需要用到的接口: 1.获取图片列表接口 /admin/product/spuImageList/{spuId} GET 2.获取spu销售属性接口 /admin/product/spuSaleAttrList/{spuId} GET 3.获取平台属性接口 /admin/product/attrInfoList/{category1Id}/{category2Id}/{category3Id} GET skuForm数据的展示与收集: skuForm需要收集skuInfo对象作为提交数据,提交数据格式如下: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217141627.png) 数据项多要仔细检查,避免出错!! **查看sku列表时的loading效果**: 发送请求前,中loading属性设置为true; ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217185133.png) 当获取到skuList数据后,将使用到的loading属性变为false ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217185902.png) #### SKU管理页 **sku的上架与下架:**通过skuList中选中的数据对象的isSale属性决定(0为下架状态,1为上架状态),通过点击按钮切换isSale属性值从而实现这个功能。 上架状态: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217225434.png) 此时可以从前台看到我们的产品: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217225551.png) 点击下架后: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217225634.png) 下架后前台的商品搜索不到‘小鳄鱼’ ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220217225715.png) **sku详情页展示:** ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218110324.png) 点击按钮后,会从右往左打开抽屉,抽屉的内部使用elementUI中的layout布局: layout的栅格布局本质上是百分比布局: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218111254.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218111323.png) 遇到的问题:**修改element-ui轮播图样式发现不起作用** ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218115725.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218115821.png) 解决方案:使用深度选择器deep。 1.在Vue模板中,style标签如果加上scoped属性,那么在这个组件中的所有结构都会被添加一个data-v-xxx自定义属性,在这模板中定义的样式都会通过属性选择器匹配上相应的节点添加上,例如: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218123642.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218123650.png) 2.如果子组件的根标签(例如子组件根标签为h3)在父组件中已经书写了样式,那么子组件也会被添加上相应的样式(子组件也会被标成红色) 3.深度选择器可以实现样式穿透,在不同的语言中格式略有不同: ​ 在原生CSS,使用'>>>' ​ 在less中,使用'/deep/' ​ 在scss中,使用'::v-deep' ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218125004.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218125014.png) 使用了深度穿透后,就可以实现子组件中使用父组件的样式,轮播图的问题就迎刃而解。 #### 数据可视化 **canvas的使用:** 1. canvas标签默认宽高为300*150,浏览器把canvas标签当成一张图片(png格式); 2. 给canvas加子节点没有意义; 3. 通过canvas标签的标签属性width/height设置大小,不要用样式去设置(会引发坐标系的改变); 4. canvas操作需要通过js实现; 5. canvas.getContext( )获取canvas上下文的环境 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220218162112.png) **svg的使用:** 1. svg与canvas的区别:svg标签不是一张图片,svg在标签内绘制图形(canvas则是利用js代码进行图像绘制),默认宽高300*150,它的宽高可以通过样式修改(canvas不允许) 2. IE8以下不兼容 **ECharts:** ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220219120038.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220219120115.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220219120148.png) **后台项目首页可视化布局:** 整个首页内容划分成三部分,第一部分为四个由组成的可视化数据图,由于结构相似,因此封装成一个大的、单独的组件Card,里面再划分成四块Detail小组件,数据/结构则通过插槽传入。 这一部分由于没有后台数据,所以使用的是mockjs获取自己写的‘假数据’。 父组件要插入子组件的模板: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220219190659.png) 子组件内部结构: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220219190724.png) 将数据传入后完成效果: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220219190916.png) 第二部分是销售柱状图属性: 第三部分为线上热门搜索/销售类别饼图组件组成: 对于热门搜索模块,将他的折线图封装成一个子组件,便于复用: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220220141808.png) 组件的结构和需要传入的数据为: ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220220141851.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220220141947.png) ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220220201906.png) #### ### 权限模块: 权限模块用来实现权限、角色等业务逻辑,根据所赋予帐号的不同角色,来决定他们能够有权限使用后台的哪些功能。 该后台的权限模块分为三个部分:用户管理、角色管理、菜单管理 **问题:**如何实现菜单的权限,保证不同用户能查看的菜单不一样? 方法:根据不同账户,登陆时向服务器发请求,服务器会返回用户相应的菜单权限信息返回给我们,根据服务器返回的权限信息,再动态的设置路由,这样就可以实现不同账户,不同菜单了。 ![](https://gitee.com/RanD0mi2e/my-picture-library/raw/master/my-picture-library/20220221101531.png) 菜单权限:当用户获取用户信息时,服务器把用户拥有的菜单权限信息返回,根据用户身份对比,根据结果得出这个用户需要展示哪些菜单 按钮权限:格局用户按钮选择不同的显示能否编辑/删除 **问题:**element-ui中的树形控件获取不到父节点ID实例,可以通过修改源代码的方式实现功能 **后台一个严重的BUG:初次登陆时路由可以正常跳转,但是在页面刷新后,界面变成空白页**(比如登陆后点击`/book/list`菜单没问题,刷新一下页面白屏了,然后把链接改成`/book/create`又出来了,再刷新又白屏了。) 解决方案:受到[CSDN文章](https://blog.csdn.net/qq_41912398/article/details/109231418)的启发在permission.js中38行的next()改为next(to.path)后即可解决刷新后跳转空白的问题。 ## Build Setup ```bash # clone the project git clone https://github.com/PanJiaChen/vue-admin-template.git # enter the project directory cd vue-admin-template # install dependency npm install # develop npm run dev ``` 项目所使用的本地路径: http://localhost:9528 ## Build ```bash # 用于测试环境 npm run build:stage # 用于生产环境 npm run build:prod ```