项目地址:https://github.com/2019083310/vue-shop
一,准备阶段
1.电商后台管理需要服务端提供相应的接口,先在vue-shop-serve中启动服务端(node app.js),启动之前要把相应的mysql文件导入数据库,并配置好mysql的用户名密码实现连接,在启动服务端。
2.此项目是前后端模式开发:
前端技术栈:
Vue全家桶:core+vue-router+vuex+axios
第三方框架:element-UI
可视化:Echarts
后端技术栈:
node+express+mysql+Jwt(状态保持的第三方插件)+Sequelize(操作mysql的第三方插件)
二,Login组件
1.怎么实现状态保持的
由于存在跨域问题:前端与服务器之间使用token实现状态保持,即登录前输入账号密码-->服务器验证后返回token给客户端保存起来,后续的所有请求都会使用token携带发送数据请求-->服务器验证token是否通过。
//不存在跨越问题时,客户端通过cookie记录状态,服务端使用session记录状态
//http是无状态的
2.Login组件的实现
1.使用position:absolut;和transform:translate(-50%,-50%)样式居中。
2.element-ui表单控件的使用:
npm install element-ui
全局导入:
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
接下来在element-ui官网查找相应的组件from复制粘贴到相应的位置
3.图标字体的使用:
element-ui的图标字体很有限,因此需要引入其他的图标字体库(这里用的是阿里图标字体库),复制fonts文件夹到assets目录下,在mian.js中引入.css文件,vue中使用图标格式:
前置图标prefix-icon='iconfont xxx'
4.表单的数据绑定:
在<el-from :model=''>通过model属性给表单绑定数据,在子表单项中通过v-model=''等于form绑定的数据中的值,这样就实现了表单的数据绑定,绑定的值就可以在表单中显示。
5.表单的数据验证:
在表单中<el-from :rules=''>通过绑定rules属性给表单添加验证规则,在校验规则对象中通过<el-from-item prop=''>prop属性绑定相应的rules中的属性值,在某个条件触发时,实现数据校验,trigger属性为什么条件触发,trigger='blur'为在失去焦点时触发校验规则。
6.表单的重置和预验证:(resetFileds()和validate())
在element-ui表单的最下面有相关的表单的属性和方法,包括数据绑定,数据校验等属性以及表单数据重置等方法,看相应的API做出相应的处理。
这里登录是涉及到了表单提交到服务器,因此要用axios网络请求,通过post携带data向服务器发送请求,服务器判断data中的数据作出相应返回给客户端status以及response.
7.登录成功后的行为:
sessionStroge:只在打开网站期间生效,会话机制
localStroge:网站持久化,任何期间都会生效,关闭页面也不会忘记数据
在登陆时,通过axios向服务器发送了登录请求,请求成功会返回给客户端一个token,通过-----window.sessionStroge.setItem('token',res.data.token)来保存得到的token,以后的每次服务器请求都要携带token向服务器发送请求来验证身份.
保存成功后跳转到Home页面,this.$router.push('/home')
防止别人直接访问`login`以外的路由,而需要调用路由的导航守卫使用 `beforeEach` 离开之前,检查是否有token的存在如果没有就直接跳转到 login 页面
`router.beforeEach((to, from, next) => {})` to 即将跳转到哪里(到那里去) from 在哪里跳转(从哪里来) next 放行枷(给不给走)
退出登录: 在home页面设置按钮 清空token并且跳转到 login
3.Home组件的实现
主页的布局主要分为三个部分,通过element-ui组件实现,分别是头部header,左侧边栏aside,中间内容main。
//element-ui 提供的组件,每个组件名都是它自己的类名
1.header布局:
<el-header>组件实现header布局,用flex实现水平分布,在加入内容即可。
2.aside布局:
<el-aside>组件实现侧边栏布局,主要是一级菜单和二级菜单即可.右侧菜单(二级可折叠) `el-menu`(最外层包裹菜单) ` `一级菜单 ` ` 二级菜单(里层) `` 菜单的模板(icon/span).
***左侧列表的数据请求,通过axios向服务器端发起数据请求,接口'/menus',得到的值处理为this.menulist=res.data,然后通过v-for循环对menulist进行数据循环,得到一级菜单列表,在对menulist的item进行v-for循环,得到二级菜单,这样左侧列表的数据请求和绘制就完成了。
element-ui框架很好用,很多东西都已经帮我们封装好了,比如菜单的折叠只想折叠一个的效果,可以通过element-ui相应组件的属性来完成,给`el-menu` 添加 `unique-opened` 属性(1) 为 `true` | 折叠属性(2): `collapse` | 关闭过渡动画属性(3): `:collapse-transition="false"` ,这样就完成了很好看的效果,element-ui很好用!!!!
//左侧导航激活的高亮`:default-active="activePath"`: 点击导航-> 使用sessionStorage来保存激活的路径 并赋值给高亮的变量-> 当离开再回来created时得到 sessionStorage 的路径 赋值给 高亮变量 (导航守卫.beforeEach)
4.Main中的用户管理模块
1.导航的名称: `el-breadcrumb` 面包屑 : 首页 > 用户管理 > 用户列表
2.卡片搜索框的使用: `el-card` 配合 栅栏布局 使用 input复合框 : 样式配合 Row 和 Col的栅栏配合.
3.使用get获取用户数据 参数为 params { params : {name:'LHJ'} }
4.表格数据: ` ` ` `
显示按钮使用作用预插槽: 在` ` 添加template模板再使用`v-slot`属性拿到当前槽作用域的布尔值 Boolean 再通过Switch组件显示 而在 ` ` 使用了作用域插槽会覆盖当前层的prop所以可以删除prop 按钮使用时需要 插槽作用域
5.分页: `pagination`: `page-sizes` 每页显示个数选择器的选项设置 `page-size` 每页显示条目个数,支持 .sync 修饰符 number `layout`: 显示那些组件 监听改变事件 页码的修改 显示个数的修改 `handleSizeChange(newValue)` 监听显示页数的改变自带参数 是 新的值 `handleCurrentChange`监听页码的改变
6.按钮状态的修改: 通过Switch的chang改变事件触发回调函数
7.搜索功能: 给搜索框双向绑定到 `queryInfo.query` 因为搜索时根据它来的 再搜索按钮绑定点击事件发送用户数据请求,根据query返回对应的参数 , 清空搜索框并清空搜索的内容 element-ui的搜索框有自带的clear事件,点击清楚时再次发送用户数据请求,此时因为query已经清空所以返回的是默认的数据
8.点击添加用户弹出 `:visible.sync = DialogVisble ` 为true显示反之隐藏
添加 **el-form** 项 :model="绑定要显示数据的对象" :rules="绑定校验规则的对象" ref="重置表单数据素要的方法"
手机和邮箱验证规则: 在data里面与return同级 用变量接收一个箭头函数(ruel, value, callback) 里面时校验的正则表达式 通过直接callback() 不通过则callback(new Error(''))
使用变量名的方法: `{ validator: checkEmail, trigger: 'blur' }` 在验证规则里面写下`validator: 变量名` 就可以调用正则表达式来验证邮箱或手机号码
* 调用form的validate属性判断数据是否合法, 值是true就发送网络请求添加用户 否则直接返回结束函数 post 对象 if(res.meta.status) 201为创建成功
对话框的代码可以放到外面,只需要使用点击事件来变换 布尔值 就可以做到显示和隐藏
9.修改用户信息: 点击按钮使用作用插槽传id值,再发送axios.get请求获取当前id的用户 并将数据保存到起来.点击确认按钮: 表单预登录验证 如果成功就发送 put请求并将保存的数据作为参数修改(因为双向绑定)
10.删除用户: 点击删除按钮 弹出对话框 -> 确认是否删除; 需要使用ui的 MessageBox 且要全局挂载在Vue.prototype.confirm = MessageBox .confirm
5.Main中的权限管理
1.添加两个 home 的子路由 rights/roles
2.权限列表: 使用卡片再用过table绑定请求来的网络数据
3.角色列表的实现:
其table第一个数据是展开项: 需要给el-table-column 绑定一个 expand 属性 此属性是展开卡片 index 索引
Dialog 对话框的关闭 表单的清空
点击按钮显示框 并获取数据 .点击确认按钮验证表单的 `rules ` 规则是否全通过返回 true 就发送相应的 修改/删除等操作
Dialog有关闭事件可以清空表单操作
el-table-column type="expand">
<template v-slot="scope">
<el-row
:class="['bdtop', i1 === 0 ? 'bdbottom' : '', 'vcenter']"
v-for="(item1, i1) in scope.row.children"
:key="item1.id"
>
<!-- 渲染一级权限 -->
<el-col :span="5">
<el-tag
closable
@close="removeRightById(scope.row, item1.id)"
>{{ item1.authName }}</el-tag
>
<i class="el-icon-caret-right"></i>
</el-col>
<!-- 渲染二和三级权限 -->
<el-col :span="19">
<!-- 通过 for 嵌套 渲染二级权限 -->
<el-row
v-for="(item2, i2) in item1.children"
:key="item2.id"
:class="[i2 === 0 ? '' : 'bdtop', 'vcenter']"
>
<el-col :span="6">
<el-tag
type="success"
closable
@close="removeRightById(scope.row, item2.id)"
>
{{ item2.authName }}
</el-tag>
<i class="el-icon-caret-right"></i>
</el-col>
<el-col :span="18">
<el-tag
type="warning"
:class="[i3 === 0 ? '' : 'bdtop']"
v-for="(item3, i3) in item2.children"
:key="item3.id"
closable
@close="removeRightById(scope.row, item3.id)"
>
{{ item3.authName }}
</el-tag>
</el-col>
</el-row>
</el-col>
</el-row>
<!-- <pre>
{{ scope.row }}
</pre> -->
</template>
</el-table-column>
4.展开的设置: 使用栅栏布局嵌套 for 循环遍历children ,el-row -> el-col**2 -> el-row -> el-col*2 ,栅栏row套栅栏会重置成24块样
5.Tree 树形结构 : :data 要绑定的数据源 :prop展示的属性名字 `show-checkbox` 以复选框的形式 `node-key="id"`绑定id
`:default-checked-keys="defKeys"` 默认选中的 使用递归推送到数组中 没有子了直接推进数组 否则重再调用自己
递归的形式: 结束的条件 自己调用自己
6.main中的商品分类
树形表格:由于 element-ui 没有 树形的表格要借助于第三方的 vue-table-with-tree-grid
github地址
分页: 由于获取用户的方法关系,可以每次修改 页码 或 页数 时直接重新发送获取用户请求
分类的添加:
<!-- 添加分类的对话框 -->
<el-dialog
title="添加分类"
:visible.sync="addCateDialogVisible"
width="50%"
@close="addCateDialogClosed"
>
<el-form
:model="addCateForm"
:rules="addCateFormRules"
ref="addCateFormRef"
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="父级分类:">
<!-- options 用来指定数据源 -->
<!-- props 用来指定配置对象 expandTrigger: 触发方式 value(id) label(显示的文字) children(下层元素) -->
<!-- v-model 双向绑定keys -->
<el-cascader
v-model="selectdKeys"
:options="parentCateList"
:props="{
expandTrigger: 'hover',
...cascaderProps,
checkStrictly: 'true'
}"
@change="parentCateChanged"
clearable
></el-cascader>
</el-form-item>
</el-form>
<tree-table
:data="cateList"
:expand-type="false"
:selection-type="false"
show-index
class="treetable"
index-text="#"
stripe
border
:show-row-hover="false"
:columns="columns"
>
<!-- 是否有效 -->
<!-- <template slot='isok' v-slot="scope"> -->
<template v-slot:isok="scope">
<i
class="el-icon-success"
v-if="scope.row.cat_deleted === false"
style="color: lightgreen"
></i>
<i class="el-icon-error" style="color: red" v-else></i>
</template>
<!-- 排序 -->
<template v-slot:order="scope">
<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>
</template>
<template v-slot:opt="scope">
<el-button
type="primary"
@click="showeditCateDialog(scope.row)"
icon="el-icon-edit"
size="mini"
>编辑</el-button
>
<el-button
type="danger"
@click="removeCate(scope.row.cat_id)"
icon="el-icon-delete"
size="mini"
>删除</el-button
>
</template>
</tree-table>
级联选择框
<el-cascader
v-model="addForm.goods_cat" 选择出来的keys
:options="cateList" 绑定的数据元
:props="{ expandTrigger: 'hover', ...cateProps (要显示的东西) }"
@change="handleChange" 选择项发生变化
></el-cascader>
cateProps: {
label: 'cat_name', 显示的名字
value: 'cat_id', 双向绑定的id
children: 'children' 显示的子项
}
<!-- 渲染表单的item项 -->
<el-form-item
:label="item.attr_name"
v-for="item in manyTableData"
:key="item.attr_id"
>
<el-checkbox-group v-model="item.attr_vals">
<el-checkbox
border
:label="item"
v-for="(item, i) in item.attr_vals"
:key="i"
>{{ item }}</el-checkbox
>
</el-checkbox-group>
</el-form-item>
图片的上传
使用 element-ui的upload 删除操作
// 处理移除图片的操作
handleRemove(file) {
// 1. 获取将要删除的图片的临时路径
const filePath = file.response.data.tmp_path
// 2. 从 pics 数组中找到这个图片的对应的索引值
const index = this.addForm.pics.findIndex(x => x.pic === filePath)
// 3. 调用数组的splice方法,把图片信息对象,从pics数组中移除
this.addForm.pics.splice(index, 1)
console.log('移除图片', file, this.addForm)
}
富文本: vue-quill-editor