SpringCloud项目开发完整流程
一、新建前端Vue项目(管理后台)
先下载node.js Node官网 然后win+r,输入cmd打开命令行窗口,输入命令node -v,检测node是否安装成功
全局安装cnpm (mac需要加上sudo),npm可以理解为前端的maven专门管理前端的js的,cnpm是国内的阿里的镜像库 npm install cnpm -g --registry=https://registry.npmmirror.com(后面地址可以省略) 成功后使用cnpm install安装,版本为3.10.0的Vue cnpm install -g @vue/cli@3.10.0 然后就可以在某个磁盘的文件下创建项目了,将文件目录切换到你要创建的文件夹下,输入vue create 文件名(不能有大写) 进行vue文件的创建 接着就进入了下面的界面中 不选第一个默认,选择下方的自选,自己选择这个项目包含着哪儿些包
接着选择Babel、Router(路由)、Vuex(状态管理也就是管理全局参数的)、CSS PRe-processors(CSS编辑器,CSS预处理)、Linter/Formatter(一般不选,这是格式化检查,选了会很自闭) 按回车,然后输入Y,把路由变成history模式 实际应用 然后CSS预编译选择第三个Less,这个比较简单 实际应用 如果之前选了Linter/Formatter,这时候就要选第一个 还是第一个 接着出现问你,你更喜欢把这些配置放在哪儿?选那个都可以 最后问你是否将其保存为未来项目的预设?选择N 在项目构建完只后,cd 项目名,切换到你的项目中,然后添加element(UI框架) 是否全引入?选择第一个选项全引入 问你是否要加CSS预编译,因为之前已经选了Less预编译这里就输入N 语言选择中文 添加axios(vue版的ajax用于进行异步交互),vue add axios
到这里就结束了,接着就是打开HbulidX,然后左上角打开我们刚刚创建的项目目录 然后右键项目文件,使用命令行窗口打开所在目录,输入npm run serve即可运行前端项目(ctrl+c可以终止退出) 详解
1、Vue的运行过程
Vue为单页开发,只有public下的一个index.html页面,主要显示也是显示在这儿,Vue运行时会加载public下的一个index.html页面和src下的main.js文件 将App对象通过"#"id选择器,加载到public下index.html中的div id="app"中 App.vue 为了在Vue这种单页开发的模式下实现多个页面的显示,就得用到路由,将其他页面的一部分或者整个页面做成一个组件,然后根据不同的地址显示不同的组件,需要通过"router-view"标签来实现,它的作用就是路由匹配到的组件将显示在这里
通过代码可以看到,我们把src/views/index.vue组件显示到了App.vue中router-view所在的位置,然后App.vue又被引入main.js中,最后在main.js中被加载到了public下的一个index.html的id为app的div中 品牌子页面BrandIndex 通过router的子路由 显示到了views下的index.vue中的右侧内容框内
2、axios异步请求(显示数据)
上面简述了为什么会显示这些页面,这些页面这么来的,接下来就说页面中的内容数据是怎么来的。
首先是template这个标签中必须只有一个子标签,所以先写一个div,至于div中有多少其他标签就不管了,其次是新建表格什么的,先把框架搭好 上面的图是完整实现后的,下方是研发中的,需要表格中显示数据 就得让表格中:tableData(要加:不然后面的" "就不是动态数据而是文本)有对应的数据,只是这数据还是死数据
要想动态的显示数据就需要拿到对应的后台数据,就得了解axios后异步请求了。
首先要了解一下怎么拿到axios中的数据,在axios.js中一个prototype自定义函数
Vue.prototype.aa= abcd
那么就可以在其他的页面中通过this.aa()来调用
<script>
export default {
name:'BrandIndex',
created(){
console.log(this.aa)
}
}
</script>
了解后,我们在axios中,自定义属性prototype
Vue.prototype.axios = _axios
这样在brand页面中,再用this.axios.request()发起异步请求,拿到后台返回的数据再赋值给tableData即可。
.then(response =>{})是当你请求成功后你要那返回值response做什么? 当然这时候运行是肯定会报错的,报错的原因跨域拦截(不允许其他项目访问当前项目),就是你前端是80端口,但是访问的后端是8081端口,所以就报错 这时候就需要修改后端代码允许跨域请求,允许其他项目访问后台项目,定义一个过滤器即可,在product的config下新建一个ProductConfig类
于是我们就拿到了返回值,只是返回值刚好在data里面 我们把返回的数据的response.dat赋值给tableData,就可以动态显示数据了 可以在axios中配置baseURL 第一次修改 当然为了方便,我们可以把请求方法给封装一下,封装到axios中,url传入的地址,method传入的是get还是post方法,callback传入的函数(方法) 在这里要插入一个概念
response => {
this.tableDate = response.data
}
function(response){
this.tableDate = response.data
}
function(response){
callback(response.data)
}
我们再修改brand中的代码 因为封装函数中是直接把response.data传入的,所以我们也就只要把response赋值给tableData即可
function(response){
this.tableDate = response
}
第二次修改 将get,post固定下来 这样brand页面只需要this.get即可,同时把这个函数写成一个方法,让created直接调用更加整洁方便。 当然以上只有get请求的方法,没有post的,实际上post请求传入的参数更加麻烦所以要特地交代,如果是get请求传递数据就直接用params属性,如果是post请求传递数据则要用data属性 并且不能传json方法,需要传递的是formData对象,该对象需要自己创建,然后调用append方法,传两个参数一个是数据名,一个是数据值,然后一个一个依次传入formData对象,传入后将formData对象赋值给data属性,由data传递给后台
fornData.append('name','张三')
再给post加上通知组件,来提示用户是否操作成功
3、引入加载组件Loading
需要先引入
import { Loading,Notification } from 'element-ui';
然后再在加载数据的代码下方写上加载时显示的文字,也就是请求之前 最后在数据被加载出来后,或者加载失败后将加载组件关闭
4、引入通知组件Notification
import { Loading,Notification } from 'element-ui';
在后台返回了ResultJson数据后,判断code是正确信息还是错误信息,最后通过组件Notification 显示给用户看 当然请求失败也需要显示
5、引入分页组件
引入分页组件后,需要将pageNo当前页,size每页显示条数传入给后台,这就涉及到参数传递了,前端的传参其实也就是params参数,params其实也就是个JSON,这里面的数据名会和后台@GetMapping注解的方法的参数进行匹配,如果对应的上就将值转到后台并赋值 后台返回的数据是对象时,前端在拿值需要看清楚对应的参数是什么 因为records才是真正的数据,因此应该绑定为tableData.records,而不是原来的tableData 在理清楚后,去Element-UI找到分页组件并引入,引入后绑定上数据即可
6、引入查询组件
去Element-UI找到查询组件并引入 通过品牌名称对数据进行查询,那么就得从前端把名称传入到后台,输入框input有个v-model可以进行双向绑定,在原传入后台的json数组query中加入name属性 点击查询按钮,查询按钮绑定了find事件,然后就通过条件去后台查,查到就从第一页开始显示 最后再给搜索框配个全局样式
7、Brand页面的增删改查功能
①查询
加载页面时需要将表格中数据加载出来,点击查询按钮能通过品牌首字母进行模糊查询 之前增加查询组件和分页组件的时候有详细描述,这里也就不细说了
②新增
在查询右边新增添加按钮,点击弹出新增通知栏 引入通知弹窗组件 弹窗默认是不显示 当按钮被点击时触发add事件,这时候显示弹窗 添加表单(实际保存新增数据) 弹窗的内容有点多,所以需要新增一个组件来专门做edit新增编辑页面 其实着页面也就是个表单页面,输入各种数据最后被提交,有关post请求数据请查看之前的axios异步请求 提交前需要进行表单验证,也就是rules属性 新增页面做好后通过组件的引入注册,最后通过标签把所有内容显示到通知栏中(也可以使用动态标签来引入) 在提交后,添加操作成功时会有个小bug,那就是表单中有验证不通过的,但是也进行了数据的传递,因此我们需要在最后发送数据前,进行二次验证,给表单加上ref="txform"这样就可以在vue中,获取到vue版的dom以此来操作表单对象 通过this.$refs[‘txform’]来获取vue版的dom对象,用来完成二次验证,二次验证的方法名称为validate,如果验证都通过了,才能允许提交,如果有一个没通过都不能提交 在通知组件内容提交后应该要将组件关闭(下次打开是空白状态),且表格内容要刷新,但是控制关闭属性的brandDialog.show是在父组件中,子组件没办法修改,要想修改就必须先把父组件的关闭显示属性传到子组件中
:show.sync="brandDialog.show"
同理函数也是,只是函数要加上@符号
@getTable="getTable"
v-if表示有没有如果是ture那么这个组件就有
v-if="brandDialog.show"
子组件想要修改父组件的值或者使用父组件的方法就必须用
$emit()
至此在新增了数据后,通知弹窗消失,表格内容刷新
③删除
在表格样式中新增一列加入删除按钮 新增删除方法 因为删除方法比较危险,所以我们要给人提示,新增MessageBos弹框,在确定要删除后我们只需要传入id,把ative的值变成0就好了,实现逻辑上的删除 很明显在,删除完数据后,删除和修改按钮是不能再继续使用的,因此在删除操作后,我们将这两个按钮变成“恢复数据”按钮,将active由0再变为1
④修改
在表格样式中新增一列加入修改按钮,如果要修改就得让后台知道你要修改的是那儿一列,因此需要把id传入后台,通过scope.row.id获取到id并传入修改方法 新增修改方法,拿到传入的id并赋值给this.brandDialog.id 通过自定义属性传入id,:id="brandDialog.id"自定义个id属性传入到edit组件后,用props接收 edit组件中通过props接收传入的id值 因为和添加共用一个组件,所以需要在程序开始就要运行的方法created中先判断一下id是否存在,如果存在就是修改方法,需要把该列的信息显示到通知组件中,如果不存在就还是原来那样一片空白
修改,需要查询单条数据,且将原来的url替换为修改的url地址,好让后台接收到正确的修改数据 查询单条数据,只需要将id传入后台即可查询到,在拿到后台返回的数据后需要对拿到的数据进行处理,name和firstLetter通过for循环正常循环赋值即可,但是file文件不能如此,它需要借助el-upload组件下的file-list属性帮忙,这个属性能够显示初始的文件,因为需要一个JSON 数组因此就新建了个flielist来传递数据 this.form.id=response.id加上是为了最后更新的时候好用id进行数据修改
之后需要将filelist的值传到组件中 在one.vue组件中用props下的fileList接收,然后在data中定义一个list数组(这样组件中其他的方法等等才能调用这个数据)将刚刚传入的flieList赋值给this.list 最后再赋值给file-list属性就可以显示了 在修改完数据后点击通知栏的保存按钮,进行数据的更新,并关闭通知栏,刷新列表数据 补全一下原来的添加逻辑
8、MinIo图片服务器(上传图片)
引入文件上传组件el-upload,然后配置el-upload的属性 因为on-change是一个函数,在文件添加时会传入file参数或者filelist参数,因此绑定了一个函数fileChange(file),将传入的函数赋值给form中的file,这样就能通过post请求传入后台了,后台需要用MultipartFile来接收,名字必须和前端传入的文件参数名一致file 在数据传入后台之后,后台将传入的文件上传到MinIo服务器,并将文件重命名返回给前端,这时候拿到数据需要思考如何将上传的图片文件在表格中显示出来,我们需要用到template自定义列内容,然后植入插槽slot-scop=“scope”,slot-scope=“scope” 是用来取得作用域插槽:data绑定的数据的,scope可以随便替换其他名称,只是定义对象来代表取得的data数据,便于使用,scope.row是获得该行的数据所有消息对象 通过:src绑定url就可以显示图片了,但是scope.row.img拿到的只是图片名称,缺少完整地址,这时候需要在main.js定义一个全局函数img()来方便图片文件的显示 自此图片是完全能显示出来了,但是发现一个很不方便的地方,因为图片不支持v-mode也就是双向绑定 ,每次上传文件都要写函数写很多东西,因此我们可以把这个图片上传封装成一个v-model的上传组件one.vue 因为很多地方都要用到文件上传,在封装好后就在main.js中进行全局注册 最后在调用的时候使用注册的标签,v-model绑定数据即可
9、user页面的增删改查功能
将brand的代码整个拷贝过来,修改里面的关键字已经新增一些字段即可 每个字段都要进行验证,邮箱有邮箱的验证规则,手机号也有手机号的验证规则,只是手机号需要自己写正则表达式进行验证
①增删改查
和brand页面的一样,只是改写参数我也就不多写了
10、新商品分类子项目
点击菜单栏中的分类管理,右边主窗口跳转到分类管理页面 菜单栏是被做成了组件加载到主页中的,右边的main也就是主窗口想要动态显示各个子页面,就要配置router路由 主路由配置的是主页面,子路由就是各个子页面 主页面的路由被加载到App.vue中
最后再配置分类管理子路由,就能在菜单栏点击分类管理后,右侧主窗口挑转到分类管理页面
①查询
因为是分类项目,所以分页组件,查询组件这些都不需要,只留下新增按钮、表格、通知弹框组织,当然为了分类列表能更好的看出层级关系,我们在table列表上加了row-key属性,然后指定唯一的id属性来作为它的对象,这样通过后台查到所有数据返回后,表格就会根据不同的id进行分层显示 当然返回的数据中必须要有children字段,也就是存放子集的字段
增加了两个按钮能跳转到sku列表和attr列表,以及添加下级按钮 要想显示数据只需要发送请求,通过后台查到数据,然后在后台返回后,赋值给this.tableDate = response就能显示出数据了 效果图
②新增
在点击右上角添加按钮的时候,新增的属性默认就是顶级,parent_id因此需要传入0,第二个参数为层级level,所以为1级 因为要往子组件传递id、parentId、level这三个属性,挨个写比较麻烦,于是加了个params属性,传递时赋值给v-bind就行 v-bind=“brandDialog.params” v-bind="brandDialog.params"和下面的写法都是一个意思 将属性传到子组件后,可以用props进行接收,当然也可以用this.$attrs进行数据接收,但是它只接收props没有接收过的参数,如果props已经接收了就不会在这边重复接收 其实分类中新增只是新增一个属性那就是name分类名称 所以验证也只需要验证它一个 如果是新增,要把传入的父级和层级属性进行赋值 在输入name分类名称的数据后,点击保存按钮保存,前端将参数信息发送到后台,后台处理后发回处理后的JSON信息 添加下级 添加下级明显再用这个添加按钮去添加就不合适了,我们应该在每一行中加入个添加下级按钮,它传入的参数也就是,当前行的id以级当前层级的id+1毕竟是在该行下添加下级,层级level肯定要加一的 其他的步骤也就是和新增一样了
③删除
删除也和其他项目类似,传入要删除行的id 删除后,刷新一下列表即可 只是后台还是需要判断一下,删除的层级下面还有没有下级,如果有是不允许删除的,能删除的必须是没有下级的
④修改
和新增一样只是修改在从主页面传入数据时只需要传入id属性即可 然后在created中对id进行判断,如果有传入id就是修改需要调用getOne()并将save进行重新赋值为修改的地址,没有就是新增 如果是修改的话,就得需要将id传入getOne(),通过id查询单条数据,在拿到后台的返回值后把该数据进行赋值,在组件中进行显示 在你修改了名称后,点击保存,系统将参数传入后台进行数据更新,然后你拿到返回的数据,进行修改组件的关闭和表格的刷新显示
⑤属性列表
每个商品都有自己的属性,同一类的商品的属性名相同,只是对应的值不同,新建attr属性列表页面
在商品分类中新增属性列表按钮 点击的时候将该行关联的id转入到attr组件就好 当然这里的属性按钮点击如果还是跳出弹框就不太好了,因为这个属性列表中也是要进行增删改查的,现在点击出现弹框,等一下属性列表中增删改查又出现弹框明显不大好,所以这里点击按钮后不仅仅要把该行的id传入,还要进行跳页 这样跳页也就是整个页面都跳到了新的页面,我们需要的是跳页后显示在菜单栏的右边主窗口,这就要配置router,在子组件配置router后就显示在主窗口中了 id的传入是通过url的方式 在attr属性列表页面要想拿到,由浏览器传入的categoryId属性值,需要在created()中,用this.$route在.vue文件中获取当前的路由信息,然后进行赋值 属性列表下的增删改查
①查询
通过gettable()将query这个Json数据传到后台,后台接收后,通过query中的categoryId查到属性列表list数据再返回给前端,拿到返回的数据后,给tableDate赋值就可以显示出数据了
②新增、删除、修改
和brand页面一样没有什么新东西
⑥SKU列表
我们买东西买的就是商品的SKU属性,SKU就是商品的最小销售单位,新建SKU属性列表页面
在商品分类中新增SKU列表按钮 点击的时候将该行关联的id转入到SKU组件就好 当然这里的属性按钮点击如果还是跳出弹框就不太好了,因为这个属性列表中也是要进行增删改查的,现在点击出现弹框,等一下属性列表中增删改查又出现弹框明显不大好,所以这里点击按钮后不仅仅要把该行的id传入,还要进行跳页
这样跳页也就是整个页面都跳到了新的页面,我们需要的是跳页后显示在菜单栏的右边主窗口,这就要配置router,在子组件配置router后就显示在主窗口中了
id的传入是通过url的方式
在SKU属性列表页面要想拿到,由浏览器传入的categoryId属性值,需要在created()中,用this.$route在.vue文件中获取当前的路由信息,然后进行赋值 SKU列表下的增删改查
②查询、删除、新增、修改
这几个和attr属性列表的逻辑也大致相同,就是修改和新增的通知栏显示有点不一样,新增了一个单选框组件 依旧是是在sku主页中,点击按钮将当前行的id传入通知栏,通知栏子组件在那到id后根据id进行数据更新操作,如果点击了按钮却没有id传入,那就是点击了新增按钮,新增一条数据。
因为值列表用的是多行文本框,在输入的时候都是输入一个值进行一次回车”\n“,所以在新增的时候要多加一步,把值列表绑定的this.form.inputList进行一次值的替换,将回车”\n“替换成”,“
如果不用逗号分隔,只用回车,那么这个数据存到数据库后会全部挤到一起 既然新增的时候需要,那么自然在点击修改时,通过id查询到单行数据进行数据的回显需要将”,“重新替换成”\n“
11、商品模块设计
新增加一个模块无外乎就是把其他项目的拿过来修修改改,然后router配置一下当前模块的路由,好让这个商品模块也显示在右边的主窗口中 新模块中几乎所有之前用到的功能都存在,甚至还有新加的一些
按关键字模糊查询、新增按钮 表格,是否上架滑块按钮 是否热卖滑块按钮,图片属性 是否有效,删除、修改、恢复数据按钮 分页组件,添加功能的通知栏组件 修改功能的通知栏组件 商品模块的增删改查功能
①查询
将query传入后台,后台拿到值后,判断是否有传入name,没有就查询所有数据,有就用传入的name属性进行模糊查询 最后返回给前端,前端将数据赋值给tableDate进行显示,显示的内容见上方
②新增
因为新增的属性实在是太多了,所以要把新增页面做成分段式的新增,用新组件Steps步骤条 v-for=“(item,index) in panels” 挨个循环panels中的值,step步骤从0开始 v-show="step===0"如果和上面的setp匹配上了,就会显示要添加的属性是哪些,用v-show就是为了显示和隐藏,而不是v-if的有或者没有,因为会有上一步这样的操作,如果是用v-if的有或者没有,上一步的数据就不会显示了 添加完属性后,点击下一步按钮触发事件 进行整个表单二次验证,如果全部验证通过,flag变为ture,进入if判断,让步骤step加一,就进入了第二步,所有的新增除了最后一个保存外都需要验证通过才能进行下一步 这个新增的属性总共分为五步,‘基础资料’,‘商品图片’,‘商品属性’,‘商品SKU’,‘SKU库存’,‘商品详情’
基础资料
是一个form表单 商品名称就是一个平常的input输入框,关联品牌要用到select选择器,毕竟关联的品牌应该是我们之前在品牌管理中输入的,这时候选择品牌就好 想用品牌管理选择器来选择,就得拿到品牌id关联的值才能继续显示出来,通过get请求去查询数据,url地址为getData,查到数据后将数据赋值给brandList 最后通过一个v-for循环将brandList中的数据都显示出来 然后就是商品分类和2个input输入框标准价格、关键字 商品分类用的是Cascader 级联选择器,options绑定后台传过来的数据源进行显示,props负责属性配置,同时在你选择后id会绑定到categoryId上 通过get请求去查询数据,url地址为getData,查到数据后将数据赋值给categoryList 但是这样显示会有个问题,就是我们最后一级的选项是有children属性的,但是却没有值,所以当我们点到最后一级时会显示暂无数据,我们应该要把这个暂无数据去掉
新建一个checkChildren方法,判断一下,如果children下面没有值就将children属性删除,有值的话就递归调用,再遍历一遍,直到所有的JSON包括子集的children中都有值为止 这样最后就不会在显示再无数据了,如果有注意可以在发现,在新建这个项目的数据表的时候我们是将关联类别设置成了文本类型的 因为商品类别在输出时是直接输出一个数组,关联了多少层,每一层 都会做个记录
商品图片
接着我们来看商品图片 效果图
其中商品图片是上传单个照片作为封面来用的,因为全局注册了当个文件上传类 所以在代码中直接引入就可以用了 简单回忆一下,当文件上传发生改变的时候调用fileChange方法 然后在方法中用input把文件信息再向外输出,实现双向绑定,然后我们在引用这个组件时使用v-model="form.file"将数据进行绑定即可 而商品相册却是上传多个图片,我们封装一个上传多个文件的组件 首先将limit最小上传数量改成能允许自己传的 默认为五个 新建一个arr数组专门存放传入的图片信息,用for循环将所有的图片都传入arr这个数组,最后将这个arr数组给输出出去
当然tx-uploadMore也是进行了全局注册的,直接使用就好
商品属性
效果图 商品属性将查到的商品属性值进行for循环将每个name都赋值给label,v-model进行双向绑定,绑定item.value,ruls规则也直接写在el-form-item中 商品属性的值,是根据之前基础资料中的商品分类选择来的,同时也拿到了商品SKU 我们知道传入的{categoryIds: categoryIds}是一个数组(因为商品分类有各个子集,所以一般会有多个categoryIds),如果数组直接进行get方法的传递,那么会出现链接的参数后面带[]的情况
传入的数组
[{categoryIds: 3},{categoryIds: 5},{categoryIds: 1}]
get请求的url链接
http://localhost:8090/pms-product/getAttrs?categoryIds[]=3&categoryIds[]=5&categoryIds[]=11
这样的传入显然是有问题的,因此我们要在axios中的过滤器来进行修改转换,需要引入qs包,qs包就是专门用来转换JSON的
在商品分类选择的时候传入的分类id就查出了,商品属性和商品SKU
商品SKU
效果图 商品SKU应该是在前面选择商品分类后得到的属性,这些属性应该是可以多选的,这里用多选框Checkbox 多选框,这多选框的内容也是通过v-for循环出来的,循环的对象是用splir分割成数组的inputList 为了达预期的显示效果(也就是一个多选框一个属性),我们还要把后台返回的数据,添加上value属性且要变成数组类型,这样el-checkbox-group才能用v-model进行绑定显示 同时我们还要判断addType的属性是否为1,如果是1的话就让属性可以进行动态添加,这里的v-model绑定的是新加的属性addvalue 如果addType是1,那么在点击添加按钮时就会触发方法addsku,将添加的item传入方法,先判断输入框的addvalue是否有值,如果有值就将值添加到inputList中,最后再清空addvalue,修改的数据都是sku的属性,最后的时候传入这个属性就能进行新增
SKU库存
效果图
SKU的库存是根据前面商品SKU中选择的数据来的,通过笛卡尔积来获得每一种的数据 为了能实现上述效果,需要将JSON放入数组中,然后再套一个大数组,在商品SKU的最后点击下一步会触发createsku()这个事件函数,因此我们来先看看这个函数对skus的处理
[[{key:颜色, value:黑色}],[{key:尺码, value:S}],[{key:季节,value:夏}]]
首先是组合第一层的JSON
第二乃至后面的组合方法写到了appendsku方法中 在appendsku方法中 最后再接收并清空一次数据,将sku得每一个对象赋值给一个skulist属性,并添加store库存,和price单价属性,数据都处理好后放入this.form.skus中,并在验证后进入下一个步骤 最后将数据绑定到表格中显示,主要修改的也就是form.skus属性,最后将这个属性通过save方法发生到后台新增就好
商品详情
效果图 商品详情其实就是个富文本编辑器,因为大多数的使用者都是不懂代码不懂排版的,如果想给字加粗变颜色等等,如果不懂肯定是不会的,所以要用富文本编辑器。 这里直接使用了全局注册的tx-editor来实现富文本编辑器,详情可以看附10富文本编辑器的实现
保存数据
最后就是点击保存按钮将上述的所有新增的属性this.form传入后台进行新增即可 当然在这之前要处理一些数据 首先是将attrs放入this.form.attrs中,请求传入过程中会将数组转换为字符串,就会让它的值变成[object Object],[object Object]因此我们要将数组的每一个值拿出来先转换为String类型再传入即可
因为是post请求,所以直接传入数组需要进行特殊处理,需要将数组中的值取出来一个一个传 最后在数据新增后,将组件关闭,刷新页面即可
③删除
添加删除按钮进行逻辑删除,删除和修改按钮变成恢复数据按钮 有效变成无效 传入id和active的值到后台,后台通过id对active的值进行更新,实现逻辑上的删除
④修改
新建一个修改组件update.vue 然后需要引入、注册到product的index.vue中 点击修改按钮的时候,触发update事件并传入当前行的id 然后显示修改通知栏,并通过组件下的:id="updateDialog.id"将值传入update组件中 created,在组件开始的时候加载三个方法来查询数据让修改组件出现内容 接收开始从index页面传进来的id this.getOne()
拿到productId后,用getOne把数据查到并将返回数据赋值给baseform、imglist、picfiles、piclist、detailHtml,也需要给baseform加上id属性,并把response的id赋值给baseform.id,这样修改时就能根据id进行修改了,
this.getData()
this.getData()获取到brandList和没有chukdren属性的categoryList this.getAttrsAndSkus()
通过productId查到对应的attrs和skus
在修改组件中修改的属性也得分段来修改,新增是因为数据太多要一段段新增,那么修改自然也是需要一段一段修改
基础资料
效果图 通过gatOne()方法给baseform所有参数赋值 修改的话用标签页tabs比较好,将基础资料的数据绑定为baseform,修改完后直接进行保存 在上述中的关联品牌循环的是brandList,数据都是由this.getData()t来的,只是el-select选中的属性是baseform.brandId,显示什么内容都是根据baseform.brandId来的,baseform.brandId可以实现双向绑定 在基础资料的数据修改完毕后,点击保存按钮会触发updateBase()事件
在form表单验证通过后将baseform数据传给后台,并在拿到后台返回值后刷新列表
商品图片
效果图 通过gatOne()方法给imgform所有参数赋值 ,还有两个默认显示图片的list 在修改完图片信息后,点击保存触发updateImg()事件 在updateImg()事件中,其实商品图片的修改很简单只需要往后台传入this.imgform.file的值就好,后台可以判断file这个值是否为空、是否有内容,如果有再进行上传就好,但是商品相册的修改就比较麻烦了 因为商品相册如果被修改了然后直接保存,那么剩下的图片的值会变成undefined未定义 会出现这样undefined未定义,是因为在我们原来封装的UploadMore组件中,当图片发生改变时,对外输出的直接就是该文件的raw,也就是说对外v-model绑定的是当前图片的raw 然而老的图片的fileList中却并没有raw,只有新上传的图片才有,所以当我们去掉了一个商品相册的图片后想保存,输出的都是undefined未定义,因为他压根就没有这个属性 上面是之前的图片信息,下面的是新的图片信息包含raw 所以我们要修改UploadMore组件,在change绑定的fileChange事件中判断一下图片是不是新上传的(判断fileList中有没有raw属性),如果是新上传的就对外输出raw,不是就对外输出name(也就是图片的名称一般后台返回的一个字符串类型的路径) 有raw输出raw,没有输出name 然后循环一下this.imgform.picfiles,把里面的值拿出来判断一下是不是Stirng类型的,如果是那么肯定就是UploadMore组件中输出的fileList[i].name,说明是老的数据,将老的数据存入names中,如果不是就是新的数据,新的数据存入files,最后再把id也传入,通过post请求将url为this.url.updateImg发送到后台,拿到返回数据 ,进行页面刷新就好了
商品属性
效果图 通过getAttrsAndSkus()方法给attrform所有参数赋值
在修改完attr商品属性信息后,点击保存触发updateAttr()事件 将attrs数据转换为String后存入attrs,通过post将url为this.url.updateAttr,参数为attrs的请求发送给后台
SKU库存
效果图 通过getAttrsAndSkus()方法给attrform所有参数赋值
sku商品库存因为修改的数据太多了用的是表格显示,为了更好的让skulist循环,我们应该要将其转换为数组,同时为了标题的更好显示,可以将第一条数据的key取出来全部存入skukey
在修改完sku商品库存信息后,点击保存触发updateSku()事件 将skus数据转换为String后存入skus数组,通过post将url为this.url.updateSku,参数为skus的请求发送给后台
商品详情
效果图 通过gatOne()方法给detailHtm参数赋值
因为商品详情是用富文本编辑器进行显示的,将detailHtm数据通过v-model传给富文本编辑器的tx-editor全局组件后,会由其组件中的prop下的value进行接收,在经过富文本编辑器的处理后会返回处理后的图片路径等等 在修改完商品详情的信息后,点击保存触发saveDetail()事件 通过post将url为this.url.saveDetail,参数为detailHtm和id的请求发送给后台
⑤是否上架、是否热买的修改
当点击滑块的时候,触发@change绑定的事件changePublishStatus将id和修改后的publishStatus值传入,后台根据id进行更新数据
12、新增login登陆子项目
新建一个login文件下的index.vue,然后配置路由,这次的login页面不会显示在菜单右边的主页面中,而是和整个view下的index.vue页面平行 页面框架设计,大体是底部从白到天蓝的渐变色,中间一个白色的登陆框 引用动态组件,将loginForm引入内容放在白色的登陆框中 编写登陆框中的内容 效果图 样式 显示代码 当用户点击登陆按钮时触发login事件,将form表单中的数据传入后台 在拿到返回的加密token后,将数据通过cmmint设置到全局域中存入Session Storage(浏览器不关闭就一直在)浏览器的本地存储,然后跳转到首页(根目录下)
总结一下 当前端发起登陆请求后,后端控制器类会返回一个通过JWT加密的token,前端会把这个token放到全局域Session Storage中,每次发送请求时前端都会过滤如果是"/login"或者带有token就放行,并且会将token放到参数头重在每一次请求时传到后台,因为所有请求都会先到gateway中由gateway来进行分配,所以我们在gataway也新建一个过滤器,来判断请求中是否有token,如果没有或者token不合法,都会向前端输出错误信息,前端在axios中接收到错误信息并显示到页面上,token合法的话就会放行通过
附1、npm是什么?
可以看做是前端的maven,也就是管理引用包的工具
附2、运行和停止VUE项目
打开HbuilderX,导入项目,在项目上右键选择"使用命令行窗口打开所在目录",然后在下方输入"npm run serve":运行该项目,“Ctrll+c”:停止运行 找到package.json后,可以看到实际执行的命令是什么,我们输入的是"npm run serve"实际执行的是"vue-cli-service serve",在这行命令后面加上"–port 80",可以改变端口号
附3、将Vue的外部终端改为内置终端
点击上方工具——>设置——>运行配置(下滑即可找到)
附4、router的两种引入方法
router下的index.js,在这个文件中通过routes匹配到的组件都会显示在"router-view"标签中 第一种引入方法 第二种引入方法
附5、JS6语法import可以拿到export的值
let i=100
export default {
i: i
}
import a from './a.js'
console.log(a.i)
附6、ajax的封装版是axios
附7、新增自定义模板
vue在新建.vue项目的时候可以选择模板,因此我们自定义一个模板可以事半功倍。 点击自定义模板即可去自己定义
附8、静态引入组件和动态引入组件
静态的写法
<template>
<div>
<el-dialog
width="450px"
:close-on-click-modal="false"
:title="brandDialog.title"
:visible.sync="brandDialog.show">
<!-- 这是静态的写法 -->
<BreadEdit></BreadEdit>
</el-dialog>
</div>
</template>
<script>
import BreadEdit from './edit'
export default {
name:'BrandIndex',
components:{
BreadEdit
}
}
</script>
动态的写法
<template>
<div>
<el-dialog
width="450px"
:close-on-click-modal="false"
:title="brandDialog.title"
:visible.sync="brandDialog.show">
<!-- 这是动态的写法 is是我们用的组件对象这里就是下方动态引入的brandDialog.component -->
<!-- v-if表示有没有,如果是ture那么这个组件就有 -->
<!-- show.sync为了在edit组件中修改index组件的brandDialog.show属性,名字自己取,需要将show的值传递过去,
加上sync是为了同步两边数据,这样子组件中修改后index组件这边的show属性就会跟着更改 -->
<!-- @getTable="getTable"为了在edit组件中使用index组件的函数,名字自己取但是需要用@开头,传的具体的函数名称 -->
<!-- :id="brandDialog.id"自定义个id属性传入到edit组件后,用props接收 -->
<component
v-if="brandDialog.show"
:is="brandDialog.component"
:show.sync="brandDialog.show"
:id="brandDialog.id"
@getTable="getTable"
></component>
</el-dialog>
</div>
</template>
<script>
export default {
name:'BrandIndex',
data(){
return{
brandDialog:{
show: false,
title: '',
component: () => import('./edit'),
id: null
}
}
}
</script>
附9、Vue进入调试模式
在代码中加入debugger进入调试模式
附10、引入富文本编辑器组件
几乎所有的富文本编辑器都是基于html的,基于vue组件的用vue2Editor,还有一个npm网站(类似于后端的maven,maven引jar包依赖,npm引前端依赖、js的)
如果要用需要先install安装,再导入 安装,记得安装好后要重启一下 新建一个tx-editor组件,引入富文本编辑器,并用components进行注册
通过图中我们可以看到,在我们对输入的文字进行加粗、斜体、排序等一系列操作后,我们对其保存时,保存的其实是一堆html标签 并且当我们传入图片时,富文本编辑器会直接转换为字节流,这样并不好,因为存在数据库中太大了 这时候我们同样需要再用到minio文件服务器来进行存储,当我们在VueEditor标签中加上useCustomImageHandler属性后,也就是使用客户的上传方式(也就是我们自己的上传方法),用我们自己的方法进行上传返回一个图片的url路径,将图片存储到minio中明显会更好些,添加一个添加图片的事件@image-added=“handleImageAdded” 然后上传图片的事件对应的函数就得我们自己写了,这个事件会往函数中传入四个参数file文件对象、Editor富文本编辑器对象、cursorLocation图片现在所在的位置,以及resetUploader一个函数代表重新加载,因为是用Editor.insertEmbed添加了一个image标签,所以同时也会将文件新的文件值赋值给content 在图片的路径重新加载和显示后,需要加一个监听器,因为刚刚的上传图片事件中导致了v-model绑定的content的属性变化,但是却并没有对外做输出,也就是说组件外的v-model并没有发生改变,所以这里要加个监听器,来监听content的,如果content发生了变化,就将改变的值对外输出,同时也要给value加上一个监听器,这样修改的时候,把值赋给v-model后组件也就会显示了,不会因为组件先加载而导致赋值没附上,显示不出,要修改的值
至此富文本编辑器完毕
最后将tx-editor组件全局注册到main.js中,这样商品详情中就可以直接通过TXEditor使用了
附11、Vue项目的部署(nginx)
需要构建它(也就是打包)npm run build, 打包好后会多出一个dist文件 然后右键当前项目然后选择在外部资源管理器打开,通过Xftp把这个dist文件放入Linux服务器 和后端的部署一样也是需要构建服务器,因为这是纯静态页,这里用一个静态服务器就好了,用静态服务器的话nginx比较合适,nginx的镜像是不带配置文件的,所以我们还要自己加个配置文件上去
当然配置文件得根据实际情况进行修改
#nginx静态部署配置文件
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
#端口80可以不改
listen 80;
server_name localhost;
#首页
index index.html;
#dist所在的路径要根据实际情况修改
root /app/front/dist/;
#charset koi8-r;
#access_log logs/host.access.log main;
location ~ ^/favicon.ico$ {
root /app/front/dist/;
}
location / {
try_files $uri $uri/ @fallback;
index index.html;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location @fallback {
rewrite ^.*$ /index.html break;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
在Linux中下载nginx镜像 docker pull nginx:1.18.0 在docker Hub上找到配置nginx运行的方法
docker --name tx-nginx -p 80:80 -v /app/front/nginx-vue.conf:/etc/nginx-vue.conf -v /app/front/dist:/app/front/dist -d nginx:1.18.0
运行完后通过日志查看是否启动 进入nginx容器中,查看刚刚映射的文件是否存在 接着尝试访问192.168.149.131:80 页面显示成功,说明前端vue的部署也是成功的
附12、README
附13、Vuex状态管理(设置全局数据)
前端拿到了后台的返回的加密token后,又面临一个新的问题,要将token存到哪儿去?因为后面每一次请求这个token都要传到后端,存在当前页面的data中肯定不行,因为离开了当前页面也就访问不到了,因此就是要将数据存到 Vuex状态管理中(从建项目时添加了但是就没用过)也就全局域中,对应的文件是store下的index.js
那什么叫Vuex状态管理呢? 首先Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。这个状态管理说白了就是一个data,不过它不是一个页面中的data,而是整项目的data(全局data),也可以看做是数据域,数据管理
在mutations中定义了一个设置token的方法其他页面要将数据设置成全局数据就用它,在getters中定义了一个拿到全局数据token的方法
因为store中把Vuex作为一个Vue对象的插件,那么在通过this点对象的时候就会有$store,拿到可以直接用getters,但是设置token是要用commit 设置全局域
附14、Vuex的持久化存储
全局数据设置好了后,也有新问题,就是当我页面刷新的时候,这个全局数据token就消失了变成了空字符串,因为我们在Vuex中存的数据是不能刷新的,刷新浏览器意味着main.js要重新加载,那么main.js下的所有组件就要重新引入,重新创建vue对象(有给new Vue当中包含store),那么一切都会初始化,store中的toekn也会重新变成空字符串。
这就需要Vuex的持久化了 Local Storage(不主动删除就会一直在,即使第二天再打开浏览器也一样)和Session Storage(浏览器不关闭就一直在)都是浏览器的本地存储,一般用Session Storage,如果Local Storage就怕某天你电脑给别人用了,人家就直接登陆进去了(token毕竟就是存储登陆的认证信息的) 先安装npm install vuex-persistedstate --save 然后引入 再植入一个插件就好了,默认是存入Local Storage中的,配置一个JSON{storage:window.sessionStorage}, 就能存入Session Storage中了
附15、router过滤器
如果除登陆页面外的其他页面中,没有token的存在,可以看做是没有登陆,这样直接访问能访问到显然是不行的,要强制跳转到登陆页面进行登陆。
需要在router中的index.js下定义一个过滤器,每一次跳页都要经过router中过滤看它是否有登陆,没有登陆就跳转到登录页面进行登陆 在前端也结束router过滤器中,我们只能通过store.getters.GET_TOKEN有没有这个token,但是不能判断这个token的合法性,可以直接在浏览器的Session Storage中随便伪造一个,然后也就可以绕过登陆正常访问页面了,为了验证这个token就只能到后端的gateway再新建一个过滤器进行认证了
附16、token认证前端
axios中也有过滤器,发请求前你要做什么可以在这儿完成,在请求头中加入‘token’,将store.getters.GET_TOKEN的值赋值给这个‘token’ 传入token后,在后台GatewayFilter进行token认证,如果是正确的token会直接被后端的过滤器过滤通过,只有错误的会返回,在axios中接收错误的返回信息,然后将页面跳转到登陆页
|