vueProject_尚品汇

vueProject_尚品汇

项目源码

项目配置

+ 浏览器自动打开
  - app/package.json
    ```
    "scripts": {
    "serve": "vue-cli-service serve --open",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
    },
    ```

+ 关闭eslint校验工具
  - app/vue.config.js
    ```
    module.exports = {
    //关闭eslint
    lintOnSave: false
    }
    ```

+ src别名
+ 在css中写的时候要写成~@
  - app/jsconfig.json
    ```
    {
      "compilerOptions": {
          "baseUrl": "./",
              "paths": {
              "@/*": [
                  "src/*"
              ]
          }
      },

      "exclude": [
          "node_modules",
          "dist"
      ]
    }
    ```

+ 安装less-loader
+ npm install --save less less-loader@5

组件通信

+ 组件通信方式
  + props
    + 父子通信
    + 书写方式:['todo'] {type:Array} {type:Array,default:[]}
  + 自定义事件
    + 子组件给父组件传递数据
    + $on $emit
  + 全局事件总线
    + 万能
    + vue.prototype.$bus = this;
  + pubsub-js 
    + 万能
    + react中使用较多
  + vuex
    + 万能
  + 插槽
    + 父子组件通信...(一般结构)
    + 默认插槽
    + 具名插槽
    + 作用域插槽

+ 原生DOM(如button)可以触发click事件
+ 对于自定义标签(如组件标签)上的@click,相当于自定义事件,一般是不可以触发的点击事件的,需要添加修饰符.native,进行修饰

+ v-model
  + 原理是通过 :value + @input 实现的
  + 在子组件上可以通过v-model实现父子数据同步 
    ```
    //父组件
    <child :value="msg" @input="msg = $event" />
    //v-model简写
    <child v-model="msg" />
    
    //子组件
    <input :value="value" @input="$emit('input',$event.target.value)" >
    
    //...javascript
    props:["value"],
    
    ```
+ sync
  + 原理是通过 :value + @update:value 实现的
  + 子组件可以通过sync修饰符实现父子数据同步
    ```
    //父组件
    <child :value="data" @update:value="data=$event" />
    //sync简写
    <child :data.sync="data"/>
    
    //子组件
    <button @click="$emit('update:value',data-=100)" ></button>
    
    //...javascript
    props:['data']
    ```
+ $attr and $listeners
  + $attr $listeners 都属于组件的属性
  + 可以获取从父组件传递过来的数据
    ```
    //二次封装elementUI中的组件例如button
    <myButton type="success" icon="el-icon-delete" size="mini" title="提示按钮" @click="handel"></myButton>
    
    //子组件
    <el-button v-bind="$attr" v-on="$listeners" ></el-button>
    //v-on不可用@替换
    ```
+ $children and $parent
  + 每个组件都有$children $parent两个属性,该属性返回当前组件的所有组件信息

跨域问题

+ 同源策略,协议 端口 域名 不同导致
+ 解决:jsonp cors 代理

+ 代理
  - app/vue.config.js
    ```
    module.exports = {
      //关闭eslint
      lintOnSave: false,
      devServer: {
        proxy: {
          '/api': {
            target: 'http://39.98.123.211'
          }
        },
      }
    }
    ```

vueRouter

+ 安装vue-router
+ npm install --save vue-router

+ 路由组件
  - @/pages/xxx/index.vue

+ 路由配置
  - @/router/index.js
    ```
    import Vue from "vue";
    import VueRouter from 'vue-router';

    Vue.use(VueRouter);

    import Home from '@/pages/Home'
    import Login from '@/pages/Login'
    import Search from '@/pages/Search'
    import Register from '@/pages/Register'

    export default new VueRouter({
      routes: [
        {
          path: "/home",
          component: Home,
          meta: { show: true }
        },
        {
          path: "/search/:keyword?",
          component: Search,
          meta: { show: true },
          name: "search",
          props: ($route) => ({keyword:$route.query.keyword})
        },
        {
          path: "/login",
          component: Login,
          meta: { show: false }
        },
        {
          path: "/register",
          component: Register,
          meta: { show: false }
        },
        {
          path: "*",
          redirect: "/home"
        }
      ]
    })
    ```

+ 注册路由
  - app/main.js
    ```
    import router from '@/router';

    new Vue({
      render: h => h(App),
      router,
    }).$mount('#app')
    ```

+ 所有的路由和非路由组件身上都会拥有$router $route属性
  + $router:一般进行编程式导航进行路由跳转
  + $route: 一般获取路由信息(name path params等)

+ 路由跳转方式
+ 声明式导航router-link标签 ,可以把router-link理解为一个a标签,它 也可以加class修饰
+ 编程式导航 :声明式导航能做的编程式都能做,而且还可以处理一些业务
  + push replace

+ 路由传参
  + query 
    + 不属于路径当中的一部分,类似于get请求,地址栏表现为 /search?k1=v1&k2=v2
    + 对应的路由信息 path: "/search" 
  + params
    + 属于路径当中的一部分,需要注意,在配置路由的时候,需要占位 ,地址栏表现为 /search/v1/v2
    + 对应的路由信息要修改为path: "/search/:keyword" 这里的/:keyword就是一个params参数的占位符
    + 占位符后加?可表示改参数可不传
    + 加?后若传递空串,通过undefined解决
    + this.$router.push({name:"Search",query:{keyword:this.keyword},params:{keyword:''||undefined}})

多次执行相同的push问题,控制台会出现警告(重写push,replace)

+ 多次push警告问题
+ push是一个promise,promise需要传递成功和失败两个参数,我们的push中没有传递。
  - @router/index.js
    ```
    let originPush = VueRouter.prototype.push
    let originReplace = VueRouter.prototype.replace

    VueRouter.prototype.push = function(location,resole,reject){
      if(resole && reject){
        originPush.call(this,location,resole,reject)
      }else{
        originPush.call(this,location,()=>{},()=>{})
      }
    }

    VueRouter.prototype.replace = function(location,resole,reject){
      if(resole && reject){
        originReplace.call(this,location,resole,reject)
      }else{
        originReplace.call(this,location,()=>{},()=>{})
      }
    }
    ```

封装axios

+ 安装axios
+ cnpm install --save axios
+ https://www.axios-http.cn/

+ 封装axios
  - @/api/ajax.js
    ```
    import axios from "axios";
    import nprogress from "nprogress";
    import "nprogress/nprogress.css";

    const requests = axios.create({
      baseURL: "/api",
      timeout: 5000
    })

    //请求拦截器
    requests.interceptors.request.use((config)=>{
      nprogress.start()
      return config
    })

    //响应拦截器
    requests.interceptors.response.use((res)=>{
      nprogress.done()
      return res.data
    },(error)=>{
      console.log(error)
      return Promise.reject(new Error("faild"))
    })

    export default requests;
    ```
    
+ 接口统一封装
  - @/api/index.js
    ```
    import requests from "./ajax";
    export const reqCategoryList = () => requests({url:'/product/getBaseCategoryList',method:'get'})
    //export const reqCategoryList = () => requests.get('/product/getBaseCategoryList')
    ```

+ usage
	```
	import {reqCateGoryList} from './api'
	reqCateGoryList(); 
  	```

nprogress进度条

+ 安装nprogress
+ cnpm install --save nprogress
+ https://www.npmjs.com/package/nprogress

- @/api/request.js
	```
  import nprogress from "nprogress";
  import "nprogress/nprogress.css";

  nprogress.start();
  nprogress.done();
	```

vuex

+ 安装vuex
+ npm install --save vuex
+ vue组件数据仓库
+ https://vuex.vuejs.org/zh/

+ store
  - @/store/home/index.js
    ```
    const state = {};
    const mutations = {};
    const actions = {};
    const getters = {};

    export default {
      state,mutations,actions,getters
    }
    ```

  - @/store/index.js
    ```
    import Vue from "vue";
    import Vuex from "vuex";

    Vue.use(Vuex);

    import home from './home';
    import search from './search'

    export default new Vuex.Store({
      //仓库模块化
      modules:{
        home,search
      }
    })
    ```

    - app/main.js
      ```
      import store from './store';
      new Vue({
        render: h => h(App),
        router,
        store
      }).$mount('#app')
      ```

  + usage
    - @/xxx.vue
      ```
      <script>
      import { mapState } from "vuex";
      export default {
        name: "",

        data() {
          return {};
        },

        mounted() {
          this.$store.dispatch('getBannerList');
        },

        methods: {},
        
        computed: {
          ...mapState({
            bannerList: (state) => state.home.bannerList,
          }),
        }
      };
      </script>
      ```

async await

+ 使用封装后的axios,进程异步问题
+ async 标识函数体中await标识的部分优先执行
+ 方法加上async,返回的一定是一个Promise
  - @/store/home/index.js
    ```
    const actions = {
      async categoryList({commit}){
        let res = await reqCategoryList();
        if(res.code == 200){
          commit("CATEGORYLIST",res.data)
        }
      }
    };
    ```

loadsh防抖和节流

+ lodash.js
+ https://www.lodashjs.com/

+ 防抖 用户操作很频繁,但是只执行一次,减少业务负担。
  _.debounce(func, [wait=0], [options=])
+ 节流 用户操作很频繁,但是把频繁的操作变为少量的操作,使浏览器有充分时间解析代码
  _.throttle(func, [wait=0], [options=])

+ 事件高频触发,性能下降,卡顿
  - @/components/TypeNav/index.vue
    ```
    methods: {
      changeIndex: _.throttle(function (index) {
        this.currentIndex = index;
      }, 50),
    }
    ```

编程式导航+事件委托

+ 三级标签列表有很多,每一个标签都是一个页面链接,我们要实现通过点击表现进行路由跳转
  对于导航式路由,我们有多少个a标签就会生成多少个router-link标签,这样当我们频繁操作时会出现卡顿现象
  对于编程式路由,我们是通过触发点击事件实现路由跳转。同理有多少个a标签就会有多少个触发函数。虽然不会出现卡顿,但是也会影响性能
  件委派即把子节点的触发事件都委托给父节点。这样只需要一个回调函数goSearch就可以解决,数据由自定义数据传递

  - @/components/TypeNav/index.js
    ```
    <div class="all-sort-list2" @click="goSearch">
      <!-- 一级 -->
      <a :data-categoryName="c1.categoryName" :data-category1Id="c1.categoryId">{{ c1.categoryName }}</a>
      <!-- 二级 -->
      <a :data-categoryName="c2.categoryName" :data-category2Id="c2.categoryId">{{ c2.categoryName }}</a>
      <!-- 三级 -->
      <a :data-categoryName="c3.categoryName" :data-category3Id="c3.categoryId">{{ c3.categoryName }}</a>
    </div>

    methods: {
      goSearch(event) {
        let element = event.target;
        let { categoryname, category1id, category2id, category3id } = element.dataset;

        if (categoryname) {
          let location = { name: "search" };
          let query = { categoryName: categoryname };

          if (category1id) {
            query.category1Id = category1id;
          } else if (category2id) {
            query.category2Id = category2id;
          } else {
            query.category3Id = category3id;
          }

          location.query = query;
          this.$router.push(location);
        }
      },
    }
    ```

mock

+ mockjs数据模拟
+ cnpm install --save mockjs
  - @/mock/banner.json
    ```
    [
      {
        "id": "1",
        "imgUrl": "/images/banner1.jpg"
      },
      {
        "id": "2",
        "imgUrl": "/images/banner2.jpg"
      },
      {
        "id": "3",
        "imgUrl": "/images/banner3.jpg"
      },
      {
        "id": "4",
        "imgUrl": "/images/banner4.jpg"
      }
    ]
    ```

  - @/mock/mockServer.js
    ```
    import Mock from 'mockjs';

    import banner from './banner.json';
    import floor from './floor.json';

    Mock.mock('/mock/banner',{code: 200, data: banner});
    Mock.mock('/mock/floor',{code: 200, data: floor});
    ```

  + 由于这里的数据请求的地址为mock路径下的,所以前面封装的axios要加一个新的,修改路径
  - @/api/mockAjax.js
    ```
    import axios from "axios";
    import nprogress from "nprogress";
    import "nprogress/nprogress.css";

    const requests = axios.create({
      baseURL: "/mock",
      timeout: 5000
    })

    //请求拦截器
    requests.interceptors.request.use((config)=>{
      nprogress.start()
      return config
    })

    //响应拦截器
    requests.interceptors.response.use((res)=>{
      nprogress.done()
      return res.data
    },(error)=>{
      console.log(error)
      return Promise.reject(new Error("faild"))
    })

    export default requests;
    ```

    + 将新的请求函数reqBannerList()暴露导出,使用参考'封装axios.md'
    - @/api/index.js
      ```
      import mockRequests from "./mockAjax";

      //pages/Home/ListContainer/ 轮播数据 mock 模拟
      export const reqBannerList = () => mockRequests({url:'/banner',method:'get'});
      ```

swiper

+ 将swiper封装成全局组件,数据通过props传递
  - @\components\Carousel\index.vue
    ```
    <template>
      <div class="swiper-container" ref="cur">
        <div class="swiper-wrapper">
          <div
            class="swiper-slide"
            v-for="carousel in carouselList"
            :key="carousel.id"
          >
            <img :src="carousel.imgUrl" />
          </div>
        </div>
        <div class="swiper-pagination"></div>
        <div class="swiper-button-prev"></div>
        <div class="swiper-button-next"></div>
      </div>
    </template>

    <script>
    import Swiper from "swiper";
    import "swiper/css/swiper.css";

    export default {
      name: "Carousel",
      props: ["carouselList"],
      data() {
        return {};
      },

      mounted() {},

      methods: {},

      watch: {
        carouselList: {
          immediate: true,
          handler() {
            this.$nextTick(() => {
              let mySwiper = new Swiper(this.$refs.cur, {
                loop: true, // 循环模式选项
                pagination: {
                  el: ".swiper-pagination",
                  clickable: true,
                },
                navigation: {
                  nextEl: ".swiper-button-next",
                  prevEl: ".swiper-button-prev",
                },
              });
            });
          },
        },
      },
    };
    </script>

    <style lang="scss" scoped></style>
    ```

+ usage
	```
    <Carousel :carouselList="bannerList"/>
	```

swiper+nexTick

+ swiper
+ npm install --save swiper@5
+ https://www.swiper.com.cn/usage/index.html
+ 由于在创建swiper实例时需要传入dom元素,官方给出的是一个固定的class,但当页面出现多个轮播图时,会导致多个轮播图使用同一数据,所以我们采用ref来标识
  因数据是通过ajax进行异步获取的,而dom元素是根据获取的数据进行生成的,固然不能直接在mounted中创建实例,所以我们通过watch监测所获取的属性并需要结合nexTick(将回调延迟到下一次DOM更新循环之后执行)来完成
+ usage
  - @\pages\Home\ListContainer\index.vue
    ```
    import Swiper from "swiper";
    import "swiper/css/swiper.css";

    watch: {
      bannerList(newValue, oldValue) {
        this.$nextTick(() => {
          let mySwiper = new Swiper(this.$refs.cur, {
            loop: true, // 循环模式选项
            pagination: {
              el: ".swiper-pagination",
              clickable: true,
            },
            navigation: {
              nextEl: ".swiper-button-next",
              prevEl: ".swiper-button-prev",
            },
          });
        });
      },
    }
    ```

Object.asign

+ object.assign
+ Object.assign(this.searchParams,this.$route.query,this.$route.params);
+ Object.assign({},this.searchParams,this.$route.query,this.$route.params);

面包屑

+ searchSelector中相关面包屑标签,点击进行分类搜索
+ 点击修改整合搜索参数searchParams,进行搜索
+ 围绕searchParams中的参数,进行修改删除再重新获取数据,xxxInfo(),removexxxInfo(),getDate()
+ 父子间通信,采用自定义事件进行传递参数
  - @/pages/Search/SearchSelector/index.vue
    ```
    <li v-for="trademark in trademarkList" :key="trademark.tmId" @click="trademarkInfo(trademark)">{{trademark.tmName}}</li>
    ....
    <li v-for="(attrValue,index) in attr.attrValueList" :key="index" @click="attrInfo(attr,attrValue)">
      <a>{{attrValue}}</a>
    </li>
    ```

  - @/pages/Search/index.vue
    ```
    <SearchSelector @trademarkInfo="trademarkInfo" @attrInfo="attrInfo" />

    <script>
    import { mapGetters } from "vuex";
    import SearchSelector from "./SearchSelector";
    export default {
      name: "Search",
      data() {
        return {
          searchParams: {
            //分类
            category1Id: "",
            category2Id: "",
            category3Id: "",
            categoryName: "",
            //关键字
            keyword: "",
            //排序
            order: "",
            pageNo: 1,
            pageSize: 10,
            //平台售卖所带的属性
            props: [],
            //品牌
            trademark: "",
          },
        };
      },
      components: {
        SearchSelector,
      },
      beforeMount() {
        Object.assign(this.searchParams, this.$route.query, this.$route.params);
      },
      mounted() {
        //search
        this.getDate();
      },
      methods: {
        //ajax获取数据
        getDate() {
          this.$store.dispatch("getSearchInfo", this.searchParams);
        },
        //面包屑
        removeCategoryName() {
          this.searchParams.categoryName = undefined;
          this.searchParams.category1Id = undefined;
          this.searchParams.category2Id = undefined;
          this.searchParams.category3Id = undefined;
          if (this.$route.params) {
            this.$router.push({ name: "search", params: this.$route.params });
          }
        },
        removeKeyword() {
          this.searchParams.keyword = undefined;
          this.$bus.$emit("clear");
          if (this.$route.query) {
            this.$router.push({ name: "search", query: this.$route.query });
          }
        },
        trademarkInfo(trademark) {
          this.searchParams.trademark = `${trademark.tmId}:${trademark.tmName}`;
          this.getDate();
        },
        removeTrademark() {
          this.searchParams.trademark = "";
          this.getDate();
        },
        attrInfo(attr, attrValue) {
          let props = `${attr.attrId}:${attrValue}:${attr.attrName}`;
          //props 去重
          if (this.searchParams.props.indexOf(props) === -1) {
            this.searchParams.props.push(props);
            this.getDate();
          }
        },
        removeAttrInfo(index) {
          this.searchParams.props.splice(index, 1);
          this.getDate();
        },
      },
      computed: {
        ...mapGetters(["goodsList", "pageInfo"]),
        pageLength() {
          let pageInfo = this.$store.getters.pageInfo;
          let len = pageInfo.total / pageInfo.pageSize;
          return Math.ceil(len);
        },
      },
      watch: {
        $route(newVaule, oldValue) {
          //整合params
          Object.assign(this.searchParams, this.$route.query, this.$route.params);
          this.getDate();
          //重置
          this.searchParams.category1Id = undefined;
          this.searchParams.category2Id = undefined;
          this.searchParams.category3Id = undefined;
        },
      },
    };
    </script>
    ```

商品排序

+ 修改searchParams中order属性即可
  - app\src\pages\Search\index.vue
    ```
    <li :class="{ active: isOne }" @click="changeOrder('1')">
      <a
        >综合<span v-show="isOne"
          ><i
            class="iconfont"
            :class="{
              'icon-jiantou_qiehuanxiangxia_o': isDesc,
              'icon-jiantou_qiehuanxiangshang_o': isAsc,
            }"
          ></i></span
      ></a>
    </li>
    <li :class="{ active: isTwo }" @click="changeOrder('2')">
      <a
        >价格<span v-show="isTwo"
          ><i
            class="iconfont"
            :class="{
              'icon-jiantou_qiehuanxiangxia_o': isDesc,
              'icon-jiantou_qiehuanxiangshang_o': isAsc,
            }"
          ></i></span
      ></a>
    </li>

    //computed
    isOne() {
      return this.searchParams.order.indexOf("1") !== -1;
    },
    isTwo() {
      return this.searchParams.order.indexOf("2") !== -1;
    },
    isDesc() {
      return this.searchParams.order.indexOf("desc") !== -1;
    },
    isAsc() {
      return this.searchParams.order.indexOf("asc") !== -1;
    },

    //methods
    changeOrder(flag) {
      let newOrder = this.searchParams.order;
      let Order_flag = newOrder.split(":")[0];
      let Order_sc = newOrder.split(":")[1];

      if (Order_flag === flag) {
        newOrder = `${flag}:${Order_sc === "desc" ? "asc" : "desc"}`;
      } else {
        newOrder = `${flag}:desc`;
      }
      this.searchParams.order = newOrder;
      this.getDate();
    },
    ```

分页器

+ 封装成全局组件pagination
+ 数据通过props,从父组件传入pageInfo
  + 通过pageInfo.pageNo
    + 确定class-active的绑定,当前页的样式(蓝底)
    + 确定<上一页><下一页>的disabled状态
  + 通过pageInfo.pageNo pageInfo.totalPages确定中间页centerPage的{arr,start,end}
    + 当arr长度不足5时,通过pageInfo.totalPages再次修改start,end
  + 通过自定义事件进行传递修改后的pageNo,在父组件进行修改并再次获取数据

  - app\src\components\Pagination\index.vue
    ```
    <template>
      <div class="pagination">
        <button @click="pageDec()" :disabled="pageInfo.pageNo===1">上一页</button>
        <button
          v-show="centerPage.start > 1"
          :class="{ active: pageInfo.pageNo === 1 }"
          @click="pageNum(1)"
        >
          1
        </button>
        <button v-show="centerPage.start - 1 > 1">···</button>

        <button
          v-for="(item, index) in centerPage.arr"
          :key="index"
          :class="{ active: pageInfo.pageNo === item }"
          @click="pageNum(item)"
        >
          {{ item }}
        </button>

        <button v-show="pageInfo.totalPages - centerPage.end > 1">···</button>
        <button
          @click="pageNum(pageInfo.totalPages)"
          v-show="centerPage.end < pageInfo.totalPages"
        >
          {{ pageInfo.totalPages }}
        </button>
        <button @click="pageAdd()" :disabled="pageInfo.pageNo===pageInfo.totalPages">下一页</button>

        <button style="margin-left: 30px">共 {{ pageInfo.total }} 条</button>
      </div>
    </template>

    <script>
    export default {
      name: "Pagination",
      props: ["pageInfo"],
      data() {
        return {};
      },
      methods: {
        pageAdd() {
          this.pageInfo.pageNo++;
          this.$emit("changePageNo", this.pageInfo.pageNo);
        },
        pageDec() {
          this.pageInfo.pageNo--;
          this.$emit("changePageNo", this.pageInfo.pageNo);
        },
        pageNum(pageNo) {
          this.pageInfo.pageNo = pageNo;
          this.$emit("changePageNo", this.pageInfo.pageNo);
        },
      },
      computed: {
        centerPage() {
          let arr = new Array();
          let start = this.pageInfo.pageNo - 2;
          let end = this.pageInfo.pageNo + 2;
          start = start > 0 ? start : 1;
          start = start > this.pageInfo.totalPages-5 ? this.pageInfo.totalPages-4 : start;
          end = end > 4 ? end : 5;
          end = end > this.pageInfo.totalPages ? this.pageInfo.totalPages : end;
          for (let i = start > 0 ? start : 1; i <= end; i++) {
            arr.push(i);
          }
          return { arr, start, end };
        },
      },
    };
    </script>
    ```

vue滚动行为

+ https://router.vuejs.org/zh/guide/advanced/scroll-behavior.html
+ 使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样
+ useage
  - app\src\router\index.js
    ```
    export default new VueRouter({
      routes,
      scrollBehavior (to, from, savedPosition) {
        if (savedPosition) {
          return savedPosition
        } else {
          return { x: 0, y: 0 }
        }
      }
    })
    ```

购物车组件

+ 静态组件
+ 配置请求
  - app\src\api\index.js
    ```
    // shopCar 
    // cartList /api/cart/cartList get 
    export const reqCarList = () => requests({
      url: '/cart/cartList', method: 'get'
    });
    // checkCart /api/cart/checkCart/{skuID}/{isChecked} get
    export const reqCheckCart = (skuID, isChecked) => requests({
      url: `/cart/checkCart/${skuID}/${isChecked}`, method: 'get'
    });
    // deleteCart /api/cart/deleteCart/{skuId} delete
    export const reqDeleteCart = (skuId) => requests({
      url: `/cart/deleteCart/${skuId}`, method: 'delete'
    })
    ```

+ 配置仓库
  - app\src\store\shopCar\index.js
    ```
    import { reqCarList, reqCheckCart, reqAddShopCar, reqDeleteCart } from '@/api';
    const state = {
      cartInfoList: []
    };
    const mutations = {
      GETCARLIST(state, cartInfoList) {
        state.cartInfoList = cartInfoList;
      }
    };
    const actions = {
      async getCarList({ commit }) {
        let res = await reqCarList();
        if (res.code == 200) {
          commit('GETCARLIST', res.data[0].cartInfoList);
        }
      },
      async updateSkuNum({ commit }, { skuId, skuNum }) {
        let res = await reqAddShopCar(skuId, skuNum);
        if (res.code === 200) {
          return 'ok';
        } else {
          return Promise.reject(new Error('faile'));
        }
      },
      async updateIsChecked({ commit }, { skuId, isChecked }) {
        let res = await reqCheckCart(skuId, isChecked);
        if (res.code === 200) {
          return 'ok';
        } else {
          return Promise.reject(new Error('faile'));
        }
      },
      async deleteCart({ commit }, { skuId }) {
        let res = await reqDeleteCart(skuId);
        if (res.code === 200) {
          return 'ok';
        } else {
          return Promise.reject(new Error('faile'));
        }
      }
    };
    const getters = {
      cartInfoList(state) {
        return state.cartInfoList || [];
      }
    };
    export default {
      state, mutations, actions, getters
    }
    ```
+ 单选框(单选或全选)
  - app\src\pages\ShopCart\index.vue
    ```
    //computed
    //全选是否选中,通过every遍历判定
    isAllChecked() {
      return this.cartInfoList.every((item) => item.isChecked == 1);
    },
    //选中几件商品,通过filter过滤
    isCheckedNum() {
      return this.cartInfoList.filter((item) => item.isChecked == 1).length;
    },

    //methods
    //store派发请求
    async updateIsChecked(skuId, isChecked) {
      await this.$store.dispatch("updateIsChecked", { skuId, isChecked });
    },
    //单选框click事件
    async changeIsChecked(skuId, isChecked) {
      isChecked = isChecked == 1 ? 0 : 1;
      try {
        await this.updateIsChecked(skuId, isChecked);
        await this.getCarList();
      } catch (error) {
        alert(error.message);
      }
    },
    //全选单选框click事件
    async checkAll(event) {
      let isChecked = event.target.checked ? 1 : 0;
      for (let cart of this.cartInfoList) {
        let skuId = cart.skuId;
        try {
          await this.updateIsChecked(skuId, isChecked);
        } catch (error) {
          alert(error.message);
        }
      }
      await this.getCarList();
    },
    ```
+ 删除以及删除所选
  - app\src\pages\ShopCart\index.vue
    ```
    //methods
    //store派发
    deleteSku(skuId) {
      this.$store.dispatch("deleteCart", { skuId });
    },
    //删除按钮click事件
    async deleteCart(skuId) {
      try {
        await this.deleteSku(skuId);
        await this.getCarList();
      } catch (error) {
        alert(error.message);
      }
    },
    //删除所选全部,通过filter过滤后逐个删除
    async deleteIscheckedCart() {
      let cartInfoList = this.cartInfoList.filter(
        (item) => item.isChecked == 1
      );
      for (let cart of cartInfoList) {
        let skuId = cart.skuId;
        try {
          await this.deleteSku(skuId);
        } catch (error) {
          alert(error.message);
        }
      }
      await this.getCarList();
    },
    ```
+ 修改商品数量
  - app\src\pages\ShopCart\index.vue
    ```
    //methods
    //添加节流防止多次点击异步问题
    updateNum: _.throttle(async function (cart, flag, disNum) {
      switch (flag) {
        case "add":
          disNum = 1;
          break;
        case "minus":
          if (cart.skuNum - 1 < 1) {
            disNum = 0;
          } else {
            disNum = -1;
          }
          break;
      }
      try {
        await this.$store.dispatch("updateSkuNum", {
          skuId: cart.skuId,
          skuNum: disNum,
        });
        //再次获取新的数据
        this.getCarList();
      } catch (error) {
        alert(error.message);
      }
    }, 500),
    ```

加入购物车成功

+ 导入静态AddCartSuccess组件

+ 注册router
  - app\src\router\routes.js
    ```
    ...
    import AddCartSuccess from "@/pages/AddCartSuccess";

    export default [
      {
        path: "/addCartSuccess",
        name: "addCartSuccess",
        component: AddCartSuccess,
        meta: { show: false },
      },
      ...
    ]
    ```

+ 注册请求req_reqAddShopCar()
  - app\src\api\index.js
    ```
    // detail addShopCar  /api/cart/addToCart/{ skuId }/{ skuNum } post
    export const reqAddShopCar = (skuId, skuNum) => requests({ url: `/cart/addToCart/${skuId}/${skuNum}`, method: 'post' });
    ```

+ store-actions,调用reqAddShopCar()
  - app\src\store\detail\index.js
    ```
    const actions = {
      async AddShopCar({commit},{skuId,skuNum}){
        let res = await reqAddShopCar(skuId,skuNum);
        if(res.code === 200){
          return 'ok';
        }else{
          return Promise.reject(new Error('faile'));
        }
      }
    };
    ```

+ @click事件,派发dispath(),判断req结果,编程式导航
  - app\src\pages\Detail\index.vue
    ```
    ...
    <a @click="addShopCar">加入购物车</a>
    ...
    
    methods: {
      async addShopCar() {
        try {
          await this.$store.dispatch("AddShopCar", {
            skuId: this.$route.params.skuId,
            skuNum: this.count,
          });
          this.$router.push({
            name: "addCartSuccess",
            query: { 'skuNum': this.count },
          });
        } catch (error) {
          alert(error.message);
        }
        //一些简单的数据,比如skuNum通过query传过去
        //复杂的数据通过session存储,
        //sessionStorage、localStorage只能存储字符串        sessionStorage.setItem("SKUINFO",JSON.stringify(this.skuInfo))
      }
    },
    ```

ES6 const新用法

data:{
  id: "",
  name: ""
}

let {id,name} = this

uuid

+ 生成随机id_token
+ npm install --save uuid
+ https://www.npmjs.com/package/uuid
  - usage
    ```
    import { v4 as uuidv4 } from 'uuid';

    uuidv4();
    ```

token问题

+ 对于完成登录后,刷新页面,自动登出问题的解决
+ 首先,我们在登录的时候将获取到的token通过localStorage存储在内存中,并通过mutation修改存储于vuex
+ 但一旦在home页刷新后,token不再提交到mutation中,固然token变成none了,所以我们需要在state中直接获取localStorage中的token并赋值
+ 然后分别在Header Home Login中派发getUserInfo获取登陆信息(后面可以通过路由守卫进行派发,不用一个一个的写)
+ 对于getUserInfo中的请求,需要加上特殊的请求头,也就是token,获取方法有两种,一种是在state中获取,一种是直接在localStorage中获取
  - app\src\api\ajax.js
    ```
    //请求拦截器
    requests.interceptors.request.use((config) => {
      let uuid_token = store.state.detail.uuid_token;
      let token = localStorage.getItem("TOKEN") || store.state.user.token; //单用前面一个好像也可以
      console.log(token);
      if (token) {
        config.headers.token = token;
      } else if (uuid_token) {
        config.headers.userTempId = uuid_token;
      }

      nprogress.start()
      return config
    })
    ```

sessionStorage

+ 本地存储
+ 存储对象是要通过JSON转化后再存储
+ sessionStorage.setItem("skuInfo",JSON.stringify(this.skuInfo));
+ JSON.parse(sessionStorage.getItem("skuInfo"));

表单验证

+ https://element.eleme.cn/#/zh-CN/component/form
+ elementUI示例

路由懒加载

+ 当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。
+ 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。
+ https://router.vuejs.org/zh/guide/advanced/lazy-loading.html
  - @/router/routes.js
    ```
    // 将
    // import UserDetails from './views/UserDetails'
    // 替换成
    const UserDetails = () => import('./views/UserDetails')

    const router = createRouter({
      // ...
      routes: [{ path: '/users/:id', component: UserDetails }],
    })
    ```

图片懒加载

+ https://www.npmjs.com/package/vue-lazyload
+ cnpm i vue-lazyload -S
+ usage
  - @/main.js
    ```
    import VueLazyload from 'vue-lazyload';
    Vue.use(VueLazyload);

    <img v-lazy="img.src">
    ```

打包部署

+ npm run build
+ 购买服务器 阿里云/腾讯云
+ 设置安全组,让服务器打开一些端口
+ Xshell 登录管理服务器
  + linux 常用指令
    + cd 跳转目录
    + ls 查看目录
    + mkdir 新建目录
  + 用到的文件夹
    + root(根目录) etc(nginx配置)
+ Xftp 向远程服务器上传管理文件
+ nginx配置
  + 进入etc目录,没有就新建
  + 安装nginx yum install nginx
  + vim nginx.conf 编辑配置
    + 默认访问location
      ```
      location / {
        root /root/***/dist;
        index index.html;
        try_files $uri $uri/ /index.html;
      }
      ```
    + 默认api location
      ```
      location /api {
        proxy_pass http://39.98.123.211;
      }
      ```
  + 启动nginx   service nginx start
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值