加载中...
  • vue-mall 商城项目 loading

    vue-mall项目地址

    简介

    项目以Vue全家桶作为主要的技术体系,开发网页和交互功能。

    1
    vue create 'xxx' // 初始化项目

    前端跨域解决方案

    跨域是浏览器为了安全做出的限制策略。

    浏览器必须请求同源策略:同域名、同协议、同端口

    CORS跨域

    服务端设置,前端直接调用

    说明:后台允许前端某个站点进行访问

    1
    2
    3
    App.vue
    <script>
    import axios from 'axios'

    Access-Control-Allow-Credentials: true // 前端跨域发送cookie

    JSONP跨域

    jsonp跨域,前端适配,后台配合 // 前端发callback

    说明:前后台同时改造

    1
    2
    3
    4
    5
    6
    7
    jsonp (url, () => {}) // js 文件
    mounted() {
    let url = 'https://www.imooc.com/search/hotwords';
    jsonp(url, (err,res) => {
    this.data = res
    })
    },

    代理跨域

    接口代理-通过修改nginx服务器配置来实现

    说明:前端修改,后台不动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    devServer: {
    host:'localhost',
    port: 8082,
    proxy: {
    // /search/hotwords
    '/api': {
    target: 'https://www.imooc.com/',
    changeOrigin: true,
    pathRewrite: {
    '/api': '' // 将接口设置为空
    }
    }
    }
    }

    需求梳理

    熟悉文档,查看原型、读懂需求

    了解前端设计稿-设计前端业务架构

    了解后台接口文档-指定相关对接规范

    协调资源

    搭建前端框架

    搭建

    建议大图片放在public, 小图片放在assets (base64)

    路由封装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const routes = [
    {
    path: '/',
    name: 'home',
    component: Home,
    redirect: '/index',
    children: [
    {
    path: 'index',
    name: 'index',
    component: Index
    },
    {
    path: 'product/:id',
    name: 'product',
    component: Product
    },
    {
    path: 'detail/:id',
    name: 'detail',
    component: Detail
    }
    ]
    },

    Storage 封装

    Cookie、localStorage、sessionStorage 三者区别?

    都是缓存技术

    存储大小:Cookie 4K、Storage 5M

    有效期:Cookie 拥有有效期、Storage 永久存储 (localStorage)

    Cookie 会发送到服务端,存储在内存中,Storage 只存储在浏览器端

    路径:Cookie 有路径限制,Storage 只存储在域名下

    API:Cookie 没有特定的API,Storage 有特定的API

    document.cookie setItem() localStorag.getItem

    为什么要封装Storage,本身已经是API?

    Storage 本身有API,但是只是简单的key/value 形式

    Storage 只存储字符串,需要人工传换成json对象

    Storage 只能一次性清空,不能单个清空

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // storage 封装

    const STORAGE_KEY = 'mall'
    export default {
    // 存储值
    setItem (key, value, module_name) {
    if (module_name) {
    let val = this.getItem(module_name)
    val[key] = value
    this.setItem(module_name, val)
    }
    let val = this.getStorage();
    val[key] = value
    window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(val))
    },
    // 获取某一个模块下面的属性
    getItem (key, module_name) {
    if (module_name) {
    let val = this.getItem(module_name)
    if (val) return val[key]
    }
    return this.getStorage()[key]
    },
    // 获取整个数据
    getStorage () {
    return JSON.parse(window.sessionStorage.getItem(STORAGE_KEY) || '{}')
    },

    // 清空
    clear (key, module_name) {
    let val = this.getStorage()
    if (module_name) {
    delete val[module_name][key]
    } else {
    delete val[key]
    }
    window.sessionStorage.setItem(STORAGE_KEY, JSON.stringify(val))
    }
    }

    接口错误拦截

    统一报错

    未登录统一拦截

    请求值、返回值统一处理

    1
    2
    3
    axios 传参 get 加params post 不加

    同时发多个请求? axios.all([]).then()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    main.js
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    import axios from 'axios' // 每个页面都要去写,导入
    import VueAxios from 'vue-axios' // 作用域对象挂载在vue实例上

    // 根据前端的跨域方式做调整 (接口代理除外)
    axios.defaults.baseURL = '/api'

    axios.defaults.timeout = 8000; // 超时时间

    // 接口错误拦截
    axios.interceptors.response.use(function (response) {
    let res = response.data;
    if (res.status == 0) { // 成功
    return res.data
    } else if (res.status == 10) { // 未登录 自定义状态码
    window.location.href = '/#/login'
    // 路由是挂载在vue实例中 这里是js文件 不能用路由跳转
    } else {
    alert (res.msg)
    }
    })

    Vue.use(VueAxios, axios)
    Vue.config.productionTip = false

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

    接口环境配置

    开发上线的不同阶段,需要不同的配置

    不同的跨域方式,配置不同

    打包的时候统一注入环境参数,统一管理环境,输出不同的版本包

    1
    2
    3
    "serve": "vue-cli-service serve --mode=development", // 注入参数,把环境变量传给mode
    "build": "vue-cli-service build --mode=production",
    "test": "vue-cli-service test --mode=test",
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    env.js
    let baseURL; // 用cors 和 jsons 使用
    switch (process.env.NODE_ENV) { // nodejs 的环境变量
    case 'development':
    baseURL = 'http://dev-mall-pre.springboot.cn/api'
    break;
    case 'test': // 不能随便书写 需要写一个.env.(name) 文件 配置NODE_ENV = 'name'
    baseURL = 'http://test-mall-pre.springboot.cn/api'
    break;
    case 'production':
    baseURL = 'http://mall-pre.springboot.cn/api'
    break;
    default:
    baseURL = 'http://mall-pre.springboot.cn/api'
    break

    }

    export default {
    baseURL
    }

    Mock 设置

    开发阶段,为了高效率,需要提前Mock

    减少代码冗余,灵活拔插

    减少沟通,减少接口联调时间

    三种方案:

    1、本地创建json

    2、easy-mock 平台

    3、集成Mock API

    1
    2
    3
    4
    5
    6
    7
    json
    mounted() {
    // 本地加载请求静态json
    this.axios.get('/mock/user/login.json').then(res => {
    this.res = res // localhost:8080 => 默认在 public 下导入 根目录
    })
    },
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    集成Mock API
    main.js
    const mock = true;// 希望mock开关打开时被拦截,用require而不是import
    // import 预编译加载 require 执行加载,如果未需要,不加载
    if (mock) {require('./mock/api')}

    api.js
    import Mock from 'mockjs'
    Mock.mock('/api/user/login', data)
    // 本地集成mockjs实现数据mock 没有发送真正的请求,代码阶段拦截

    给网站设置最小宽度 min-weight

    1
    2
    3
    4
    安装sass
    npm config set registry="https://registry.npmjs.org/"
    registry = "https://registry.npm.taobao.org/"
    $ set SASS_BINARY_SITE=http://npm.taobao.org/mirrors/node-sass&&npm i node-sass
    1
    2
    导入sass
    @import './assets/sass/reset.scss';
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
              a {
    display: inline-block;
    width: 110px;
    height: 55px;
    border: 1px solid red;
    &:before { // sass 给a加伪类

    }
    cursor: pointer
    设置背景图片
    background: url() no-repeat center;
    background-size: contain

    <input autocomplete="off"/> // 历史搜索
    1
    2
    3
    4
    5
    6
    7
    8
    // flex 布局复用
    @mixin flex ($hov:space-between, $col: center) {
    display: flex;
    justify-content: $hov;
    align-items: $col;
    }
    使用
    @include flex();
    1
    2
    3
    4
    5
    6
    7
    8
    @mixin bgImg ($w: 0, $h: 0, $img: '', $size: contain) {
    display: inline-block;
    width: $w;
    height: $h;
    background: url($img) no-repeat center;
    background-size: $size;
    margin-right: 4px;
    }
    1
    2
    <a target="_blank"></a> // 新页面打开
    图片一般自适应展开,要么定义高度,要么定义宽度
    1
    2
    3
    4
    5
    6
    7
    8
    9
    过滤器
    filters: {
    currency (val) {
    if (!val) return '0.00';
    return '¥' + val.toFixed(2) + '元'
    }
    },

    {{item.price | currency}} // 使用
    1
    2
    路由跳转 $router.push()
    路由取参:$router.params 或者 $router.query

    首页轮播

    1
    2
    3
    4
    5
    6
    7
    npm install --save vue-awesome-swiper@3.1.3 
    对应swiper 4

    import { swiper, swiperSlide} from 'vue-awesome-swiper' // swiper组件很大,按需加载
    import 'swiper/dist/css/swiper.css'

    2 报错 Error in render: "TypeError: Cannot set property 'params' of undefined" ---跟版本号有关系,4.0 版本首字母大写,3.0版本,首字母小写。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <swiper :options="swiperOption">
    <swiper-slide v-for="(item, index) in slideList" :key="index">
    <a href="'/#/product/' + item.id"><img :src="item.img"></a>
    </swiper-slide>
    </swiper>
    分页器
    <div class="swiper-pagination" slot="pagination"></div>
    <div class="swiper-button-prev" slot="button-prev"></div>
    <div class="swiper-button-next" slot="button-next"></div>
    <div class="swiper-scrollbar" slot="scrollbar"></div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    swiperOption: {
    autoplay: true,
    // 循环播放
    loop: true,
    // 切换效果
    effect: 'cube',
    slidesPerView: 3, // 显示的数目
    spaceBetween: 30, // 元素之间的距离
    pagination: {
    el: '.swiper-pagination',
    // 点击分页器切换
    clickable :true,
    },
    navigation: {
    nextEl: '.swiper-button-next',
    prevEl: '.swiper-button-prev',
    },
    },
    1
    2
    opacity 会把内容全部加透明度
    rgba 内容不会有透明度

    可以用二维数组实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div class="children">
    <ul v-for="(item, index1) in menuList" :key="index1">
    <li v-for="(sub, index) in item" :key="index">
    <a :href="sub ? '/#/product/' + sub.id : '' ">
    <img :src="sub ? sub.img : '/imgs/item-box-1.png'" alt="">
    {{sub ? sub.name : '小米9'}}
    </a>
    </li>
    </ul>
    </div>
    1
    this.phoneList = [res.list.slice(0, 4), res.list.slice(4, 8)] // 赋值

    modal组件(transition)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    props: {
    // 弹框类型: 小框,中框,大框,表单
    modalType: {
    type: String,
    default: 'form'
    },
    title: String,
    btnType: String, // 按钮类型: 1.确定按钮 2.取消按钮 3.确定取消按钮
    sureText: {
    type: String,
    default: '确定'
    },
    cancelText: {
    type: String,
    default: '取消'
    },
    showModal: Boolean
    },
    1
    2
    3
    4
    5
    6
    7
    @mixin position($pos, $top, $left, $w, $h) {
    position: $pos;
    top: $top;
    left: $left;
    width: $w;
    height: $h
    }
    1
    2
    transition 
    样式先定义 -active 因为样式会覆盖

    图片懒加载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    npm i vue-lazyload -S

    import VueLazyLoad from 'vue-lazyload'
    Vue.use(VueLazyLoad, {
    loading: '/imgs/loading-svg/loading-bars.svg'
    })

    <img src="item.img" alt=""> => <img v-lazy="item.img" alt="">

    // 注意,指令里面的是变量
    <img v-lazy="'/imgs/banner-1.png'" alt="">

    登录交互

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
      methods: {
    login () {
    let {username, password} = this // 数据绑定在this上
    this.axios.post('/user/login' {username, password}).then(res => ...)
    this.$cookie.set('userId', res.id, { expires:'1M' })
    }
    }

    main.js
    Vue.use(VueCookie) // 保存token

    单页面应用,刷新会丢失数据,需要在App.vue中重新拉取
    Cookie: JSESSIONID=25E86D07E1B121DB1C6B38ECCF6CCADA; userId=2840
    // java写的

    Vuex

    vue 中数据是单项流通的,如果 A 与 B 之间没有任何关系,而A想用B的变量。兄弟组件传递数据。

    1
    vuex state 中的数据不具有响应式, state中的数据发生变化,不会主动通知调用该数据的对象,我们可以用computed监听数据

    产品站参数

    吸顶功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    mounted() {
    window.addEventListener('scroll', this.initHeight) // 方法抽离出来,为了移除事件使用,使用function() 事件在组件销毁时不会移除
    },

    data () {
    return {
    isFixed: false
    }
    },
    mounted() {
    window.addEventListener('scroll', this.initHeight)
    },
    methods: {
    initHeight () {
    let scrollTop = window.scrollY || document.documentElement.scrollTop || document.scrollTop; // 兼容浏览器
    console.log(scrollTop);
    this.isFixed = scrollTop > 152
    }
    }
    destroyed() {
    window.removeEventListener('scroll', this.initHeight, false)
    },
    1
    box-shadow:X轴偏移量、Y轴偏移量、模糊半径、扩散半径和颜色。

    video

    1
    2
    3
    <div class="overlay"></div> // 遮罩
    <video src="/imgs/product/video.mp4" muted autoplay controls="controls"></video>
    设置自动播放 需要先数值muted loop
    1
    2
    3
    object-fit:cover // 类似backgroundimg 这个CSS属性可以达到最佳最完美的居中自动剪裁图片的功能。
    background-size只能作用于有背景图的,
    object-fit可以作用于类似 img, video等的标签。
    1
    2
    3
    4
    5
    6
    closeVideo () {
    this.showSlide = 'slideUp';
    setTimeout (() => {
    this.showSlide = false
    }, 600)
    },
    1
    2
    1.router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的...
    2.route是一个跳转的路由对象,每一个路由都会有一个route对象,是一个局部的对象,可以获取对应的name,path,params,query等 我们可以从vue devtools中看到每个路由对象的不同.当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。

    商品详情页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    浮动及清除浮动
    .fl{
    float: left;
    }
    .fr{
    float: right;
    }
    .clearfix:before,.clearfix:after{
    content:' ';
    display:table;
    }
    .clearfix:after{
    clear: both;
    }

    <div class="container clearfix"> 父元素添加清除浮动

    购物车页面

    1
    @import './'  // 不带./默认是插件 去node_modules中查找

    数据一般放在后台,防止前端篡改数据

    1
    2
    3
    4
    5
    6
    7
    // 公共赋值
    renderData(res){
    this.list = res.cartProductVoList || [];
    this.allChecked = res.selectedAll;
    this.cartTotalPrice = res.cartTotalPrice;
    this.checkedNum = this.list.filter(item=>item.productSelected).length;
    }, // 某项数据改变后,与其相关联的数据不变化,需要重新赋值

    Element UI

    1
    2
    <a> 优先级高于class
    <h2> 优先级高于class

    主要用于B端

    1
    2
    3
    import { Message } from 'element-ui'
    import 'element-ui/lib/theme-chalk/index.css'
    Vue.prototype.$message = Message;

    babel

    babel.config.js.babelrc 不会被合并

    js.map 源码存放 上线后删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    "plugins": [
    [
    "component",
    {
    "libraryName": "element-ui",
    "styleLibraryName": "theme-chalk"
    }
    ]
    ] // 按需加载

    退出登录

    因为是单页面,所以把数据存储在Vuex中,Vuex中的数据存储在内存,页面刷新后,内存中的数据自动消失。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
          this.$router.push({
    path: '/index',
    query: {
    from: 'login'
    }
    }) // query传参

    this.$router.push({
    name: 'index',
    params: {
    from: 'login'
    }
    }) // params 传参,用 name

    if (this.$route.params && this.$route.params.from == 'login')
    this.getCartCount()
    },
    1
    2
    3
    4
    5
    6
    7
    if (this.$cookie.get('userId')) {
    this.getUser();
    this.getCartCount()
    } // 如果未登录 不需要调取登录信息 判断 cookie
    清除cookie this.$cookie.set('xx', '', {expires: -1})
    expires 和后端保持一致 登录有效期
    会话级别:Session 把整个浏览器关闭coookie消失

    订单确认

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    mounted() {
    let path = this.$route.path;
    if (path == '/order/confirm') {
    this.title = "确认订单"
    this.tip = "请认真填写收获地址"
    } else if (path == 'order/list') {
    this.title = '订单列表'
    this.tip = "请谨防钓鱼链接或诈骗电话"

    } else if (path == '/order/pay') {
    this.title = '订单支付'
    this.tip = "请谨防钓鱼链接或诈骗电话"
    }
    },

    地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    // axios的封装
    submitAddress() {
    let { checkedItem, userAction } = this;
    let method,
    url,
    params = {};
    if (userAction == 0) {
    (method = "post"), (url = "/shippings");
    } else if (userAction == 1) {
    (method = "put"), (url = `/shippings/${checkedItem.id}`);
    } else {
    (method = "delete"), (url = `/shippings/${checkedItem.id}`);
    }
    if (userAction == 0 || userAction == 1) {
    let {
    receiverName,
    receiverMobile,
    receiverProvince,
    receiverCity,
    receiverDistrict,
    receiverAddress,
    receiverZip
    } = checkedItem;
    let errMsg = "";
    if (!receiverName) {
    errMsg = "请输入收货人名称";
    } else if (!receiverMobile || !/\d{11}/.test(receiverMobile)) {
    errMsg = "请输入正确格式的手机号";
    } else if (!receiverProvince) {
    errMsg = "请选择省份";
    } else if (!receiverCity) {
    errMsg = "请选择对应的城市";
    } else if (!receiverAddress || !receiverDistrict) {
    errMsg = "请输入收货地址";
    } else if (!/\d{6}/.test(receiverZip)) {
    errMsg = "请输入六位邮编";
    }
    if (errMsg) {
    this.$message.error(errMsg);
    return;
    }
    params = {
    receiverName,
    receiverMobile,
    receiverProvince,
    receiverCity,
    receiverDistrict,
    receiverAddress,
    receiverZip
    };
    }
    this.axios[method](url, params).then(() => {
    this.closeModal();
    this.getAddressList();
    this.$message.success("操作成功");
    });
    },
    1
    /\d{11}/.text(arg) 

    订单支付

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    paySubmit(payType){
    if(payType == 1){
    window.open('/#/order/alipay?orderId='+this.orderId,'_blank');
    // 可以打开新窗口 window.open(URL,name,specs,replace) specs: '_blank&_self'
    }else{
    this.axios.post('/pay',{
    orderId:this.orderId,
    orderName:'Vue高仿小米商城',
    amount:0.01,//单位元
    payType:2 //1支付宝,2微信
    }).then((res)=>{
    QRCode.toDataURL(res.content)
    .then(url => {
    this.showPay = true;
    this.payImg = url;
    this.loopOrderState();
    })
    .catch(() => {
    this.$message.error('微信二维码生成失败,请稍后重试');
    })
    })
    }
    },
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    paySubmit () {
    this.axios.post('/pay', {
    orderId: this.orderId,
    orderName: 'Vue高仿小米',
    amount: 0.01,
    payType: 1
    }).then((res) => {
    this.content = res.content
    setTimeout (() => { document.forms[0].submit() }, 100) // 提交表单
    })
    }

    支付对接

    调用后端接口,

    传入订单id,商品名称,金额,支付类型,

    得到表单 通过v-html赋值 通过document.forms[0].submit() 提交 (做一个中间页面)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    QRCode.toDataURL(res.content)
    .then(url => { // 转换成base64
    this.showPay = true;
    this.payImg = url;
    this.loopOrderState();
    })
    .catch(() => {
    this.$message.error('微信二维码生成失败,请稍后重试');
    })
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 轮询当前订单支付状态
    loopOrderState(){
    this.T = setInterval(()=>{
    this.axios.get(`/orders/${this.orderId}`).then((res)=>{
    if(res.status == 20){
    clearInterval(this.T);
    this.goOrderList();
    }
    })
    },1000);
    }, // 如果用户已经支付,自动跳转
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    axios.interceptors.response.use(function (response) {
    let res = response.data;
    let path = location.hash;
    if (res.status == 0) { // 成功
    return res.data
    } else if (res.status == 10) { // 未登录 自定义状态码
    if (path !== '#/index') { // 处于首页不需要跳转login
    window.location.href = '/#/login'
    // 路由是挂载在vue实例中 这里是js文件 不能用路由跳转
    }
    } else {
    Message.warning(res.msg)
    return Promise.reject(res) // 不抛出认为是成功状态
    }
    }, (error) => { // 状态码拦截 请求发送失败
    let res = error.response;
    Message.error(res.data.message)
    return Promise.reject(res)
    })

    订单列表

    1
    2
    import { Pagination,Button } from 'element-ui'
    import infiniteScroll from 'vue-infinite-scroll'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <el-pagination
    v-if="false"
    class="pagination"
    background
    layout="prev, pager, next"
    :pageSize="pageSize" // :pageSize="pageSize"
    :total="total"
    @current-change="handleChange"
    >
    </el-pagination>
    components:{
    OrderHeader,
    Loading,
    NoData,
    [Pagination.name]:Pagination, // 使用方法
    [Button.name]:Button
    },
    1
    2
    3
    4
    5
    // 第一种方法:分页器
    handleChange(pageNum){
    this.pageNum = pageNum;
    this.getOrderList();
    }, // 向右:添加text-align: right
    1
    2
    3
    4
    5
     // 第二种方法:加载更多按钮
    loadMore(){
    this.pageNum++;
    this.getOrderList();
    },
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    // 第三种方法:滚动加载,通过npm插件实现
    npm i infinite-scroll 是一个指令
    directives: {
    infiniteScroll
    }

    <div class="scroll-more"
    v-infinite-scroll="scrollMore"
    infinite-scroll-disabled="true"
    infinite-scroll-distance="410"
    v-if="true"
    >
    <img src="/imgs/loading-svg/loading-spinning-bubbles.svg" alt="" v-show="loading">
    </div>

    scrollMore(){
    this.busy = true;
    setTimeout(()=>{
    this.pageNum++;
    this.getList();
    },500);
    },
    // 专门给scrollMore使用
    getList(){
    this.loading = true;
    this.axios.get('/orders',{
    params:{
    pageSize:10,
    pageNum:this.pageNum
    }
    }).then((res)=>{
    this.list = this.list.concat(res.list);
    this.loading = false;
    if(res.hasNextPage){
    this.busy=false;
    }else{
    this.busy=true;
    }
    });

    优化

    1
    2
    3
    4
    5
    component: () => import('../pages/product.vue') // 预加载,文件加载进html,等待空闲时生成js文件。
    实现真正按需加载:
    chainWebpack: (config) => {
    config.plugins.delete('prefetch')
    }
    上一篇:
    在Flexbox Froggy 游戏中搞定flex布局
    下一篇:
    用js代码实现观察者模式
    本文目录
    本文目录