根据不同的场景,电商系统一般都提供了PC端、移动 APP、移动 Web、微信小程序等多种终端访问方式。
电商后台 管理系统同用于管理用户账号、商品分类、商品信息、订单、数据统计等业务功能。
电商后台管理系统整体采用前后端分离的开发模式,其中前端项目是基于Vue技术栈的SPA项目。
① 安装vue脚手架
② 通过 vue 脚手架创建项目
③ 配置 vue 路由
④ 配置 Element-UI 组件库
⑤ 配置 axios 库
⑥ 初始化 git 远程仓库
⑦ 将本地项目托管到 Github 或码云中
① 安装MySQL数据库
② 安装Node.js 环境
③ 配置项目相关信息
④ 启动项目
⑤ 使用Postman 测试后台项目接口是否正常
① 在登录页面输入用户名和密码
② 调用后台接口进行验证
③ 通过验证之后,根据后台的响应状态跳转到项目主页
通过Element-UI组件实现布局
首先打开 vue-shop 项目
git status
login
分支git checkout -b login
git branch
这是一个 Vue 单文件组件
祖传 Vue 布局如下
我们需要在
标签中进行布局
在
标签中编写用户行为和数据定义
在
标签中定义样式
我用到了 ElementUI 库,主要是记录一下这个库的用法
先放上源码,我会对下面的代码进行解读
登录
重置
在
标签中添加
:model
属性,这是登录表单的数据绑定对象,Ta需要和标签中的
v-module
配合使用
:rules
是登录表单验证,Ta需要在 JS 区域的 data 中返回一个登录表单验证对象,其中的属性为需要验证的内容,如何使用?在el-form-item
标签中新增prop
属性,使它的值为刚刚在 data 中定义的属性值
标签中的
prefix-icon
属性是用来设置前置字体图标的,我引用的阿里矢量图库,也可以通过suffix-icon
属性在 input 组件的尾部增加显示图标。
表单中的
ref
属性是为当前表单注册一个实例对象,Ta的属性名就是这个实例对象的名称,比如说我自定义的名称为loginFormRef
,那我就可以通过this.$refs.loginFormRef
来访问到这个实例对象。
resetFields
可以通过this.$refs.loginFormRef.resetFields()
调用此方法,此方法的作用是:对整个表单进行重置,将所有字段值重置为初始值并移除校验结果。
validate
可以通过this.$refs.loginFormRef.validate()
调用此方法,此方法的作用是:对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise。Function(callback: Function(boolean, object))
如果用户没有登录,但是直接通过URL访问特定页面,需要重新导航到登录页面
// 为路由对象,添加 beforeEach 导航守卫
router.beforeEach((to, from, next) => {
// 如果用户访问的登录页,直接放行
if (to.path === "/login") return next()
// 从 sessionStorage 中获取到保存的 token 值
const tokenStr = window.sessionStorage.getItem("token")
// 没有 token ,强制跳转到登录页
if (!tokenStr) return next("/login")
next()
})
基于token 的方式实现退出比较简单,只需要销毁本地的token即可。这样,后续的请求就不会携带token,必须重新登录生成一个新的token之后才可以访问页面。
// 清空token
window.sessionStorage.clear()
// 跳转到登录页
this.$router.push('/login')
解决 eslintrc 格式化报错问题
在项目中新建 .prettierrc
文件,文件内容如下
{
"semi": false,
"singleQuote": true
}
“semi”: false 表示移除分号(
“singleQuote”: true 表示用单引号(’’)来表示字符串
最后在每个需要修改的文件中使用快捷键
Ctrl+Alt+\
进行格式化文档
在 .eslintrc.js
--> rules 下新建一条命令
"space-before-function-paren": 0
git status
此时提示你的是红色信息
它会提示你
Changes not staged for commit:
尚未提交
git add .
把所有的文件都添加到暂存区之后继续使用
git status
,提示信息就变成绿色了
git commit -m "完成了登录功能"
git branch
当前分支是 login ,我们需要把 login 中的所有代码合并到 master 主分支中
注意:你需要先切换到 master 分支之后再合并 login
git checkout master
出现以下提示说明成功
Switched to branch ‘master’
Your branch is up to date with ‘origin/master’.
- 如果没有成功删除
.gitignore
暂存文件,重新从第一步开始再使用
git branch
可以看到已经切换到了 master 主分支
我的 .gitignore
文件内容如下:
node_modules
git merge login
git push
但是我们发现码云中只要 master 一个分支,并没有 login 分支
所有我们需要将 login 也推送到码云中
git checkout login
git push -u origin login
-u 是指定推送 login 到码云
先上下划分,再左右划分
码小余后台管理
退出
Aside
Main
导航一
导航一
通过 axios 请求拦截器添加 token,保证拥有获取数据的权限。
// axios请求拦截
axios.interceptors.request.use((config) => {
// console.log(config);
config.headers.Authorization = window.sessionStorage.getItem("token");
return config;
});
码小余后台管理
退出
{{ item.authName }}
{{ subItem.authName }}
首页
用户管理
用户列表
添加用户
通过权限管理模块控制不同的用户可以进行哪些操作,具体可以通过角色的方式进行控制,即每个用户分配一个特定的角色,角色包括不同的功能权限。
比如说:王者荣耀,你充钱的玩家有皮肤,不充钱的玩家没有皮肤…
{{ item1.authName }}
{{ item2.authName }}
{{ item3.authName }}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njOQiVro-1605152538773)(…/upload/image-20201107144025650.png)]
一级
二级
三级
编辑
删除
没错,就是它
首先,它需要一个数组,数组中有很多对象,并且这个数组数据是从 后台接口 动态获取的,所以说它返回来的数据也是固定的,但是 这个Cascader 级联选择器,它里面需要接收必须有 value 和 label 的对象(value为分类id, label为分类名称),那么我应该考虑如何把后台返回来的列表变成符合要求的列表,我的解决方案是 把后台数据列表先转换成字符串,再使用替换方法,再转成列表返回,这样问题就解决了,但是它还是有bug,它的选择器的长度,超乎我的想象,于是我用F12找到对应的元素,进行修改,到此,完美结束
第一种方法:
export default {
data(){
return {
// 选择的分类id列表
selectedKeys: []
// 父级分类列表
parentCateList: [],
// options 列表
options: [],
}
}
}
// 更改 parentCateList 列表中属性名重新赋值给 options 列表,来供el-cascader组件中的 :options 使用
getOptionsList(){
this.options = JSON.parse(JSON.stringify(this.parentCateList).replace(/cat_id/g, "value").replace(/cat_name/g, "label").replace(/cat_deleted/g, "del").replace(/cat_level/g, "del").replace(/cat_pid/g, "del"))
this.options.forEach((item1)=>{
delete item1.del
item1.children.forEach((item2)=>{
delete item2.del
})
})
},
// 修改 cscader 默认的超长选择框的高度
.el-cascader-panel {
height: 300px;
}
第二种方法,老师的方法:
export default {
data(){
return {
// 选择的分类id列表
selectedKeys: []
// 父级分类列表
parentCateList: [],
// 指定级联选择器的配置对象
cascaderProps: {
value: "cat_id",
label: "cat_name",
children: "children"
},
}
}
}
基本信息
商品参数
商品属性
商品图片
商品内容
export default {
data(){
return {
activeIndex: '0'
}
}
}
首先和主要的就是 data 中的
activeIndex
的定义,首先,你应该知道它是双向绑定在标签和
标签中的,
标签是用
:active
绑定的,它的定义是:索引值(数字类型)为几,那么它就跳到第几步,标签是用
v-model
绑定的,它的定义是:你点的是哪一栏,v-model 的值(字符串)就是什么,关键是这两个的类型不同,所以我定义了activeIndex
的值默认是字符串的 ‘0’,需要数字的地方直接减 0 就行了
首先,你应该能够获取到这一行的数据,无论是小康的(在路径后面传入该商品对应的id,然后通过id查询商品列表),还是我的(把该行的数据存入sessonStorage 中,然后在需要的地方直接拿出来),这都能获取到该行的数据,关键是你怎么把数据重新渲染到编辑页面,我是直接给 editForm ,需要提交的表单进行重新赋值,让该商品的数据重新赋值给 editForm 中对应的属性上,但是,需要主要的是,商品分类是该商品没有给到的数据,那你就应该通过商品 id 来查询该商品,你在它的返回结果就可以找到 goods_cat(商品分类)这个数据,但是,它返回的这个 goods_cat 是一个字符串(这是因为我在Add.vue 中提交表单时作了处理,因为它最终只能提交字符串格式的数据),所以我需要把它先转换为一个数组,然后再重新赋值给 editForm,最后只需要改一下编辑的接口即可成功修改该商品。
List.vue
// 通过编程式导航跳转到编辑商品页面
goEditpage(row){
this.$router.push(`/goods/edit`)
const rowObj = JSON.stringify(row)
console.log(rowObj);
window.sessionStorage.setItem("edit", [rowObj])
}
Edit.vue
async created(){
this.getCateList()
const rowObj = JSON.parse(window.sessionStorage.getItem("edit"))
console.log(rowObj);
// 根据 ID 查询商品
const {data: res} = await this.$http.get("goods/" + rowObj.goods_id)
console.log(res.data);
this.goodsId = rowObj.goods_id
this.editForm.goods_cat = res.data.goods_cat.split(",").map(Number)
this.editForm.goods_name = rowObj.goods_name
this.editForm.goods_number = rowObj.goods_number
this.editForm.goods_price = rowObj.goods_price
this.editForm.goods_weight = rowObj.goods_weight
// console.log(this.editForm);
},
需要注意的是,把数字字符串转成数组之后它的每一项还是字符串,所以需要用 map(Number) 把其中的每一项都变成数字。
就是顶部一闪而过的进度条
npm install --save nprogress
// 导入 NProgress 包对应的JS和CSS
import NProgress from "nprogress";
import "nprogress/nprogress.css";
// 在 request 拦截器中,展示进度条 NProgress.start()
axios.interceptors.request.use((config) => {
// console.log(config);
NProgress.start();
config.headers.Authorization = window.sessionStorage.getItem("token");
return config;
});
// 在 resopnse 拦截器中,隐藏进度条 NProgress.done()
axios.interceptors.response.use((response) => {
NProgress.done();
return response;
});
使用 babel-plugin-transform-remove-console 插件移除
在开发依赖中安装
npm install babel-plugin-transform-remove-console --save-dev
按需使用 这个插件,在开发时不移除 console,在发布时移除 console
babel.config.js
// 这是项目发布阶段需要用到的 babel 插件
const prodPlugins = []
if(process.env.NODE_ENV === "production"){
prodPlugins.push("transform-remove-console")
}
module.exports = {
// 发布产品时候的插件数组
...prodPlugins
}
具体配置如下:
config.set ('externals', {
vue: 'vue',
'vue-router': 'vueRouter',axios: 'axios',
loaash: '_',
echarts: 'echarts', nprogress: 'NProgress',
'vue-quill-editor': 'vueQuillEditor'})
当打包构建项目时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
具体需要3步:
① 安装 @babel/plugin-syntax-dynamic-import
包。
② 在babel.config.js
配置文件中声明该插件。
③ 将路由改为按需加载的形式,示例代码如下;
const Foo = () => import(/* webpackChunkName: "group-foo" */ "./Foo.vue")
const Bar = () => import(/* webpackChunkName: "group-foo" */ "./Bar.vue")
const Baz = () => import(/* webpackChunkName: "group-boo" */ "./Baz.vue")
使用 gzip 可以减小文件体积,使传输速度更快。
② 可以通过服务器端使用Express做gzip 压缩。其配置如下:
//安装相应包
npm install compression -D
//导入包
const compression = require('compression');
//启用中间件
app.use(compression());
注意:先注册 gizp 压缩,再托管静态资源
pm2 可以使你的终端窗口被关闭后也可以正常的处于开启服务的状态
① 在服务器中安装pm2: npm i pm2 -g
② 启动项目: pm2 start脚本--name自定义名称
③ 查看运行项目: pm2 ls
④ 重启项目: pm2 restart 自定义名称
⑤ 停止项目: pm2 stop 自定义名称
== “production”){
proPlugins.push(“transform-remove-console”)
}
module.exports = {
// 发布产品时候的插件数组
…prodPlugins
}
### 项目优化 - 通过 externals 加载外部CDN资源
> 具体配置如下:
```javascript
config.set ('externals', {
vue: 'vue',
'vue-router': 'vueRouter',axios: 'axios',
loaash: '_',
echarts: 'echarts', nprogress: 'NProgress',
'vue-quill-editor': 'vueQuillEditor'})
当打包构建项目时,JavaScript包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
具体需要3步:
① 安装 @babel/plugin-syntax-dynamic-import
包。
② 在babel.config.js
配置文件中声明该插件。
③ 将路由改为按需加载的形式,示例代码如下;
const Foo = () => import(/* webpackChunkName: "group-foo" */ "./Foo.vue")
const Bar = () => import(/* webpackChunkName: "group-foo" */ "./Bar.vue")
const Baz = () => import(/* webpackChunkName: "group-boo" */ "./Baz.vue")
使用 gzip 可以减小文件体积,使传输速度更快。
② 可以通过服务器端使用Express做gzip 压缩。其配置如下:
//安装相应包
npm install compression -D
//导入包
const compression = require('compression');
//启用中间件
app.use(compression());
注意:先注册 gizp 压缩,再托管静态资源
pm2 可以使你的终端窗口被关闭后也可以正常的处于开启服务的状态
① 在服务器中安装pm2: npm i pm2 -g
② 启动项目: pm2 start脚本--name自定义名称
③ 查看运行项目: pm2 ls
④ 重启项目: pm2 restart 自定义名称
⑤ 停止项目: pm2 stop 自定义名称
⑥ 删除项目: pm2 delete 自定义名称