Vue电商管理系统笔记

429 阅读8分钟

一、准备工作

0. 给el-container设置最小宽度,防止挤压变形
0. 给目录配置别名
// vue.config.js文件中

const path = require('path')
// 处理路径函数
function resolve (dir) {
  console.log(__dirname)
  return path.join(__dirname, dir)
}


module.exports = {
  lintOnSave: true, // 启用eslint
  // 解决[WDS] Disconnected! 报错
  devServer: {
    headers: {
      'Access-Control-Allow-Origin': '*'
    },
    hotOnly: true,
    disableHostCheck: true
  },
  // 打包时去console插件
  configureWebpack: config => {
    if (process.env.NODE_ENV === 'production') {
      config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
    }
  },
  // 配置别名
  chainWebpack (config) {
  config.resolve.alias.set('views',resolve('src/views')).set('@', resolve('src'))
  }
}

1. 登录逻辑以及token
1. 前端输入用户名密码进行登录
2. 后端服务器验证登录成功,会生成一个token字段,返回给前端
3. 前端将token存储在sessionStorage中
4. 以后的每次请求都要携带此token
2. API接口授权

除了登录接口,其他需要授权的 API ,必须在请求头中使用 Authorization 字段提供 token 令牌

// 利用axios请求拦截器
axios.interceptors.request.use(config=>{
  //为请求头对象,添加token验证的Authorization字段
  config.headers.Authorization = window.sessionStorage.getItem("token")
  return config
})
3. 解决左侧菜单刷新后默认高亮
  • 方法一:

    sessionStorage保存每次点击子菜单选项绑定的唯一index , 刷新以后created里面同步到data中定义的activePath ,el-menu的default-active绑定此activePath
    
    注意:不能是mounted生命周期中同步,具体见各个生命周期会进行的事件
    
    https://zhuanlan.zhihu.com/p/71958016
    

    vue生命周期图解

  • 方法二:el-menu的default-active直接绑定 $route.path

二、用户管理

4. 栅格布局
5. Table设置索引列
 <!-- 设置索引列 -->
        <el-table-column
          label="#"
          type="index"
          width="60"
          align="center"
        >
        </el-table-column>
6. 作用域插槽实现switch开关状态
7. trycatch用法
8. 表单绑定以及验证
1. 先data中定义表单数据formData ,表单校验规则formRules 。 对象里有多个属性
2. 不光 el-input 要利用v-model绑定到formData的各个属性上
3. el-form也要用 :model 绑定到表单数据formData上
4. el-form-item 还要用 prop绑定formRules里的具体对应的校验规则,属性值可以是数组,里面包含多个规则(见9中的 addUserFormRules)
9. 邮箱和手机号自定义校验规则
1. 先在data中定义利用回调函数自定义校验规则,注意不是在return2. 再在prop绑定的校验规则中用validator指向定义的该回调,trigger:blur失焦时候触发
// 定义在data中的回调 
// 验证邮箱的规则
    var checkEmail = (rule, value, cb) => {
      // 验证邮箱的正则表达式
      const regEmail = /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/;

      if (regEmail.test(value)) {
        // 合法的邮箱
        return cb();
      }

      cb(new Error("请输入合法的邮箱"));
    };

    // 验证手机号的规则
    var checkMobile = (rule, value, cb) => {
      // 验证手机号的正则表达式
      const regMobile = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/;

      if (regMobile.test(value)) {
        return cb();
      }

      cb(new Error("请输入合法的手机号"));
    };
// 定义在data中的校验规则 
// 添加表单的验证规则对象
      addUserFormRules: {
        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, trigger: "blur" }
        ],
        mobile: [
          { required: true, message: "请输入手机号", trigger: "blur" },
          { validator: checkMobile, trigger: "blur" }
        ]
      }
10. 表单提交之前要做预校验

预校验通过的话就再做表单提交的网络请求

// el-form-item 要绑定prop的验证规则字段,用v-model绑定到输入框的值 ,el-form还要用 :rule 绑定到验证规则 , 用 :model 绑定到表单数据
11. 侧边el-aside底部出现滚动条
将el-aside的宽度设置小于200px , 在点击aside内部的导航栏时,底部出现横向滚动条。
经过简单测试,宽度>=200px的时候不会有

解决办法:在el-aside的内部el-menu的外部套一个el-scrollbar组件,在style属性中设置width:100% 。 类似情形如果出现纵向滚动,就设为高度100%
12. 列表分页的问题
12-1. 刷新以后分页的当前页码和每页条数被重置
思路:
1. pagination组件的current-page和page-size 绑定data中定义的初始值pagenum和pagesize
2. 点击更改页码或者容量时,回掉函数内部把当前的pagesize和pagenum同步到sessionStorage中 
3. created生命周期里重新获取并赋值给初始值 , 再请求列表数据
注意: current-page和page-size要绑定的是Number类型 , 要利用parseInt()转换类型
created() {
    if (window.sessionStorage.getItem("currentPage")) {
      this.params.pagenum = parseInt(
        window.sessionStorage.getItem("currentPage")
      );
    }
    if (window.sessionStorage.getItem("currentPageSize")) {
      this.params.pagesize = parseInt(
        window.sessionStorage.getItem("currentPageSize")
      );
    }
    this.getUsers();
  }
12-2. 分页中删除最后一页数据自动跳到上一页
 // 解决分页删除当前页不跳转,先判断当前应该处于的页数 , 再给pagenum重新赋值
	//  因为usersTotal总数需要在重新请求后才能同步最新数据,所以这里计算需要手动减1
      let totalPage = Math.ceil((this.usersTotal - 1) / this.params.pagesize);
      let currentPage = this.params.pagenum > totalPage ? totalPage : this.params.pagenum;
      this.params.pagenum = currentPage < 1 ? 1 : currentPage;
 // 同步sessionStorage中的currentPage
 window.sessionStorage.setItem("currentPage", currentPage);
// 分页器的代码如下:
      <el-pagination
        :background="true"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="params.pagenum"
        :page-sizes="[2, 3, 4, 5]"
        :page-size="params.pagesize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="usersTotal"
      >
      </el-pagination>

// usersTotal是data中定义的用户总数 , params是用户请求的参数
      params: {
        pagesize: 4,
        pagenum: 1,
        query: "" // 用户搜索关键字 , 绑定到搜索输入框
      },
12-3. 删除最后一页数据再刷新显示列表数据为空
因为删除最后一页时候,没有同步sessionStorage中的currentPage,所以再刷新请求利用的还是最后一页的currentPage值,故请求的仍是原尾页的数据,为空 
 window.sessionStorage.setItem("currentPage", currentPage);

三、权限管理

13. 作用域插槽显示不同等级的标签

应用场景:接口返回的等级是0/1/2 , 需要根据等级分别渲染成不同标签到表格里

作用域插槽配合 v-if 条件判断实现

<el-table-column prop="level" label="权限等级" align="center">
          <template slot-scope="level">
            <el-tag v-if="level.row.level === '0'" type="success">等级一</el-tag>
            <el-tag v-else-if="level.row.level === '1'" type="warning">等级二</el-tag>
            <el-tag v-else type="danger">等级三</el-tag>
          </template>
</el-table-column>
另外思路: 可能还可以用过滤器filters配合switch实现(???)暂时没试过
14. 删除权限标签视图刷新以后展开的权限列表又关闭了

是因为删除以后发起请求是请求所有角色的权限数据,所以页面会重新渲染,可以不重新请求所有数据,只更新赋值当前角色的children里面所有权限数据

15. 利用递归获取当前角色的所有三级权限id , el-tree默认选中
 // 递归获取已有三级权限的id
    getDefaultKeys(data, arr) {
 /* 
思路:1. 点击分配权限,传递当前role的数据,获取当前角色的所有已有三级权限id , 组成数组 , 赋值给defaultExpandedArray
     2. 遍历roleData, 判断当前数据是否有children , 没有就把当前对象的id push到数组 
     3. 有就遍历该children数组 , 然后对每一项元素继续调用this.getDefaultKeys() 进行判断
*/
      if (!data.children) {
        arr.push(data.id);
        return false;
      } else {
        data.children.forEach(i => this.getDefaultKeys(i, arr));
      }
    },
16. 给上一个角色分配权限,再点下一个角色,默认选中了上一个角色有的权限
// 在关闭对话框时候,要把默认选中的权限 defaultCheckedKeysArray数据清空,不然点击下一个角色的分配权限,会默认选中上一个角色的权限

 // 关闭el-tree分配权限对话框
    closeSetRightsDialog() {
      /* 在关闭对话框时候,要把默认选中的权限 defaultCheckedKeysArray数据清空 
      不然点击下一个角色的分配权限,会默认选中上一个角色的权限
      */
      this.defaultCheckedKeysArray = [];
    }
17. vue-table-with-tree-grid实现树形table表格

vue-table-with-tree-grid

四、商品管理

18. moment.js格式化时间
  • 先定义全局或者局部过滤器

    import moment from 'moment'
    
    // 定义全局的时间过滤器
    Vue.filter('dateFilter', (time) => {
      return moment(time).format('YYYY-MM-DD   HH:mm:ss') // 格式:1970-01-18 20:39:05
    })
    
    
  • 引用

      <el-table-column
              header-align="center"
              align="center"
              width="250"
              label="创建时间">
              <template slot-scope="{row}">
                {{ row.add_time | dateFilter }}
              </template>
      </el-table-column>
    
19. el-tab标签栏和el-steps步骤条联动
https://i.postimg.cc/xTxK7ZMy/Screenshot-2.png
20. el-tab和el-form的层级关系

必须是 el-form包裹el-tab , el-tab-pane 里面再包裹 el-form-item

21. 利用before-leave在切换tab标签之前做判断

添加商品时,从第一步跳转到第二步的时候要检查是否选中了商品分类,否则提示并阻止跳转

1. 要判断当前步骤是不是由 1 到 2
2. this.queryInfo.goods_cat.length !== 3 时 返回 false
22. upload图片上传,发请求显示无效token
// 说明upload上传没用的axios发请求 
// 利用headers属性,设置请求头

  uploadUrl: 'http://127.0.0.1:8888/api/private/v1/upload', // 图片上传服务器地址
      headerObj: {
        // upload设置请求头
        Authorization: window.sessionStorage.getItem('token')
      }
23. 图片上传成功,但是缩略图不能正常显示,是白色方块
// 因为el-upload中:file-list绑定了提交的表单信息里的pics , 导致每次pics数组里push的对象如下:
[{"pic":"tmp_uploads\\6dc05d2211b648cefe5d4e593c952f8e.jpeg","uid":1618827444464,"status":"success"}]

// 正常应该是只有pic属性

24. vue-quill-editor富文本编辑器使用
25. lodash的_.cloneDeep深拷贝

五. 数据统计

1. e-charts的使用
  • 在mounted生命周期中初始化

六. 项目优化上线

blog.csdn.net/weixin_4438…

1. 顶部进度条效果 nprogress.js
  • 在main.js中利用axios拦截器实现
import Nprogress from 'nprogress'
import 'nprogress/nprogress.css'


// 请求拦截器中显示进度条效果
axios.interceptors.request.use(config => {
  Nprogress.start()
  // 为请求头对象,添加token验证的Authorization字段
  config.headers.Authorization = window.sessionStorage.getItem('token')
  return config
})
// 响应拦截器中停止进度条效果
axios.interceptors.response.use(config => {
  Nprogress.done()
  return config
})
  • 利用路有守卫实现
router.beforeEach((to, from, next) => {
if (to.path == '/login') {
    sessionStorage.removeItem('username');
  }
let user = sessionStorage.getItem('username');
if (!user && to.path != '/login') {
    next({path: '/login'})
  } else {
    NProgress.start();
    next()
  }
});

router.afterEach(transition => {
  NProgress.done();
});
2. build期间去掉所有的console
babel-plugin-transform-remove-console 插件
// 注意需要只在生产阶段去console 要先做环境的判断 process.env.NODE_ENV === 'production'
3. 生成打包报告

image-20210421022015340.png

4. 为开发模式和发布模式指定不同的打包入口

image-20210421022941046.png

image-20210421023558153.png

// main.js复制两份分别命名main-dev.js / main.prod.js
// vue.config.js中使用chainWebpack
module.exports = {
    // 利用when判断环境,指定不同的打包入口文件
    // 发布模式
    config.when(process.env.NODE_ENV === 'production', config => {
      // entry找到默认的打包入口,调用clear则是删除默认的打包入口
      // add添加新的打包入口
      config.entry('app').clear().add('./src/main-prod.js')
    })
    // 开发模式
    config.when(process.env.NODE_ENV === 'development', config => {
      config.entry('app').clear().add('./src/main-dev.js')
    })
}

5. 通过externals加载外部的CDN资源

image-20210421025131662.png

  • 配置如下 :

image-20210421025313698.png

6. 优化打包element-ui

image-20210421034739933.png

7. 自定义首页标题内容

image-20210421035031790.png

image-20210421035103607.png

8. 路由懒加载
  • 使用babel的话,先安装@babel/plugin-syntax-dynamic-import

  • babel.config.js中声明一下插件

image-20210422131024190.png

  • 路由中改造组件导入方式

image-20210422131217315.png

9. 开启gzip压缩

一般是后端的范围 , 需要服务器是node写的,可能需要前端做