zoukankan      html  css  js  c++  java
  • vue 移动端项目总结(mint-ui)

    回头看自己的代码,犹如鸡肋!!!里面有很多问题,建议大家不要看。我没时间整理╮(╯▽╰)╭

    跨域解决方案

      config/dev.env.js   

    'use strict'
    const merge = require('webpack-merge')
    const prodEnv = require('./prod.env')
    
    module.exports = merge(prodEnv, {
      NODE_ENV: '"development"',
      API_ROOT: '"/api"'
    })

      config/prod.env.js ,生产的服务器(你线上运行时的服务器)

    'use strict'
    module.exports = {
      NODE_ENV: '"production"',
      API_ROOT: '"http://api.xxx.com/"'
    }

      config/index.js

        proxyTable: {
          '/api': {
            // target: 'https://www.xxx.com/',
            // target: 'http://m.xxx.com/',
            target: 'http://api.xxx.com/',
            changeOrigin: true,
            secure: false,
            pathRewrite: {
              '^/api': ''
            }
          }
        },
    
        // Various Dev Server settings
        // host: 'xxx.xxx.xx.x', // can be overwritten by process.env.HOST   //公司本地IP
        host: 'localhost', // can be overwritten by process.env.HOST
        port: 8085, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined

      接口请求的时候

    export const _HomeNavList = params => {
      return req('post',  rootUrl+ '/xxxx/xxxxx/xxxxx/xxx',params)
    }

    rem设置 

      有多种方式,可以 js,也可以 css 设置。
      鉴于 H5 的浏览器都比较高级,可以使用一些最新的属性,这里先介绍 css 的写法。 
    /*rem设置*/
    html{ 
      font-size: calc(100vw/7.5);  /*1rem=100px*/
    }

       js 设置 rem,如下。

    (function (doc, win) {
      var docEl = doc.documentElement,
        // 手机旋转事件,大部分手机浏览器都支持 onorientationchange 如果不支持,可以使用原始的 resize
        resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
        recalc = function () {
          //clientWidth: 获取对象可见内容的宽度,不包括滚动条,不包括边框
          var clientWidth = docEl.clientWidth;
          if (!clientWidth) return;
          docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
        };
      recalc();
      if (!doc.addEventListener) return;
      //注册翻转事件
      win.addEventListener(resizeEvt, recalc, false);
    })(document, window);

    axios 的封装

      刚开始我也是设置 axios 的 baseURL ,以及在拦截器里把数据用 qs 序列化。后来又改回来了。

      最后有个需求是要把图片上传到七牛云(后面会讲),那么 axios 就不能在拦截器里设置了。

    import axios from 'axios'
    import qs from 'qs'
    
    axios.defaults.timeout = 5000;
    // axios.defaults.baseURL = process.env.API_ROOT; //填写域名
    
    //http request 拦截器
    axios.interceptors.request.use(
      config => {
        // config.data = qs.stringify(config.data);
        config.headers = {
          'Content-Type': 'application/x-www-form-urlencoded'
        }
        return config;
      },
      error => {
        return Promise.reject(err);
      }
    );
    
    //响应拦截器即异常处理
    axios.interceptors.response.use(response => {
      return response
    }, err => {
      if (err && err.response) {
        switch (err.response.status) {
          case 400:
            console.log('错误请求')
            break;
          case 401:
            console.log('未授权,请重新登录')
            break;
          case 403:
            console.log('拒绝访问')
            break;
          case 404:
            console.log('请求错误,未找到该资源')
            break;
          case 405:
            console.log('请求方法未允许')
            break;
          case 408:
            console.log('请求超时')
            break;
          case 500:
            console.log('服务器端出错')
            break;
          case 501:
            console.log('网络未实现')
            break;
          case 502:
            console.log('网络错误')
            break;
          case 503:
            console.log('服务不可用')
            break;
          case 504:
            console.log('网络超时')
            break;
          case 505:
            console.log('http版本不支持该请求')
            break;
          default:
            console.log(`连接错误${err.response.status}`)
        }
      } else {
        console.log('连接到服务器失败')
      }
      return Promise.resolve(err.response)
    })
    
    // 通用公用方法
    export const req = (method, url, params) => {
      return axios({
        method: method,
        url: url,
        data: qs.stringify(params) ,
        // traditional: true,
      }).then(res => res.data);
    };

    底部菜单栏

      mint-ui的底部菜单栏,我个人觉得用的不是很习惯,找了很多资料才勉强填上这个坑,如下:


    <mt-tabbar class="bottom-tab" v-model="tabSelected"> <mt-tab-item id="home"> <span class="iconfont icon-zhuye"></span> <p>首页</p> </mt-tab-item> <mt-tab-item id="study"> <span class="iconfont icon-xianshanghuodong"></span> <p>学习</p> </mt-tab-item> <mt-tab-item id="ask"> <span class="iconfont icon-kefu"></span> <p>咨询</p> </mt-tab-item> <mt-tab-item id="user"> <span class="iconfont icon-wode"></span> <p>我的</p> </mt-tab-item> </mt-tabbar>

       路由嵌套,底部 tabbar 是第一层组件,点击底部元素,可切换不同模块

    {
          path: '/bottomTab',
          component: bottomTab,
          children: [{
              path: '/home',
              name: 'home',
              component: home,
              meta: {
                keepAlive: true,
              }
            },
            {
              path: '/study',
              name: 'study',
              component: study,
              meta: {
                RequireLogin: true
              }
            },
            ...
    }

       最后我是没有设置默认选中,默认的是组件创建的时候的路由名称,然后在路由变化时,直接 watch 监控路由的名称,并作出相关操作

    export default {
      data() {
        return {
          tabSelected: "",
          routerPath: ""
        };
      },
      created() {
        this.tabSelected = this.$route.path.slice(1);
      },
      mounted() {},
      beforeDestroy() {},
      watch: {
        $route(to, from) {
          if (
            to.name == "home" ||
            to.name == "study" ||
            to.name == "ask" ||
            to.name == "user"
          ) {
            this.routerPath = this.$route.path;
            this.tabSelected = this.routerPath.slice(1);
          }
        },
        tabSelected: function(val, oldVal) {
          this.$router.push({
            name: val
          });
        }
      },
      methods: {},
      computed: {}
    };

    返回上一页

      顶部返回上一页,有的需求是直接发个详情页的链接给别人,然后客户在点击返回的时候,是没有本站的浏览记录的。那么可能会退出到一个空白页,造成不必要的客户流失,我们的需求是让他去首页或者本站的其他页面,留住客户。

        <mt-header :title="headTitle">
          <mt-button icon="back" slot="left" @click="goBack">返回</mt-button>
        </mt-header>

      浏览记录最少要有2条,否则去首页。我刚开始写的1,最后发现空白页也是记录

      methods: {
        goBack() {
          if (window.history.length <= 2) {
            this.$router.push({ path: "/" });
            return false;
          } else {
            this.$router.back();
          }
        }
      },

    路由守卫

      有些页面是需要登录了之后才能进的,这样的页面如果多了,就可以用路由守卫来判断

    router.beforeEach((to, from, next) => {
      // 登录页、不需要登陆的和已登录的页面直接跳转
      if (to.path == "/login" || !to.meta.RequireLogin || localStorage.getItem("user")) {
        next();
      } else {
        next({
          path: "/login",
          query: {
            redirect: to.fullPath
          }
        })
      }
    })

      路由守卫跳转过来的登录页,是带参的(目标页面的路径)在登录成功之后,就自动进入目标页面

                  if (r.Code == 0) {
                    this.LOGIN(r.Data)
                    if (this.$route.query.redirect) {
                      this.$router.push({
                        path: this.$route.query.redirect
                      });
                    } else {
                      this.$router.push("/");
                    }
                  }

    列表页上拉加载,下拉刷新

      课程列表页做了这个功能,待验证,使用  mt-loadmore

    视频格式 m3u8 

      这个格式的视频还是挺多的,但是实现播放的话,就有点复杂了(要装两个插件,还一堆问题),折腾了两天,这速度算快还是慢呢。

    import 'video.js/dist/video-js.css'
    import 'vue-video-player/src/custom-theme.css'
    import videojs from 'video.js'
    //得手动绑定对象,不然找不到 window.videojs = videojs
    //这里写成 import 还不行,必须得 require require(
    'videojs-contrib-hls/dist/videojs-contrib-hls');

      写元素的时候,可以不用写 source

          <video id="video-wrap" class="video-js vjs-custom-skin vjs-big-play-centered">
            <!-- <source
              src="http://xxx.m3u8"
              type="application/x-mpegURL"
            > -->
          </video>

      如果多格式类型的视频,可能需要自动判断

      mounted() {// 创建播放器
        this.videoPlay = videojs('video-wrap', {
          playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度
          autoplay: false, //如果true,浏览器准备好时开始播放
          muted: false, //默认情况下将会消除任何音频
          loop: false, //导致视频一结束就重新开始
          preload: 'auto', //建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
          aspectRatio: '4:3', // 16:9 不会自动放中间
          // fluid: true, // 当true时,Video.js player将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
          // 720,
          // height:540,
          notSupportedMessage: '此视频无法播放',
          controls: true,
          // sources: [{
          //   // type: "application/x-mpegURL",  //video/mp4
          //   // type: "", //video/mp4
          //   // src: "http://37273.long-vod.cdn.aodiany210ad87667dd544439b28733e.m3u8",
          // }],
          // bigPlayButton: true,
          // textTrackDisplay: false,
          // posterImage: true,
          // errorDisplay: false,
          // controlBar: true
        })
      },
      watch: {
        onlineVideoVideoId(){
          this.videoPlayUrl(this.onlineVideoInfo)
        }
      },
      methods: {
        videoPlayUrl(info) {
          // 视频观看
          // console.log(info);
          if (this.user) {
            if (info.bought == true) {
              // 获取课程视频
              _StageItemPlayUrl({
                courseId: this.$route.query.id,
                memberId: this.user.id,
                classId: info.videoId,
              }).then(res => {
                // console.log(res)
                if (res.Code === "0") {
                  this.showVideoPlayer = true;
                  this.changeVideo(res.Data.positiveUrl)
                }
              }).catch((err) => {
                console.log(err)
              })
            } else {
              console.log('没有购买')
              this.$toast({
                message: '请购买课程',
                position: 'bottom',
                duration: 2000
              });
            }
          } else {
            this.$router.push({
              path: '/login',
              query: {
                redirect: to.fullPath
              }
            })
          }
        },
        changeVideo(vdSrc) {
          // 切换视频路径及类型
          if (/.m3u8$/.test(vdSrc)) {
            this.videoPlay.src({
              src: vdSrc.replace("http://", "https://"),
              type: 'application/x-mpegURL'
            })
          } else {
            this.videoPlay.src({
              src: vdSrc,
              type: 'video/mp4'
            })
          }
          this.videoPlay.load();
          this.videoPlay.play(); //pause()暂停    销毁 dispose()
        },
      },
      beforeDestroy() {
        this.videoPlay.dispose();
        console.log("video destroy");
      }

      在 build/webpack.base.conf.js 的 moudel 拿了要加上 noParse: [/videojs-contrib-hls/],不然可能会报错 t is not definded 之类的错误。

      但是我在移动端没写上面这个操作,也播放成功了,PC端写了。现在不确定这段代码是否有必要。

      module: {
        noParse: [/videojs-contrib-hls/],
        rules: [
          {
            test: /.vue$/,
            loader: 'vue-loader',
            options: vueLoaderConfig
          },
        ...

    mt-radio 的使用

      需求是做一个单选的数据列表,但是 mt-radio 的 options 的数据结构是 label 和 value ,其中 label 是显示的名称, value 是值。

      那么我们的数据结构就也得是 value 和 label 了,如果不是,就必须得手动转换。

                Object.values(this.majorList).map(value => {
                  value.value = value.id;
                  value.label = value.majorName;
                  // console.log(value)
                  // console.log(this.majorList)
                });

    自适应多层级目录

      使用迭代,

      调用:

                <DetailMultiMenu
                  v-for="(classItem,index) in this.classListData"
                  :key="index"
                  :item="classItem"
                  @videoInfo="videoPlayUrl"
                ></DetailMultiMenu>

      组件:

    <template>
      <ul class="multi-menu">
        <li v-if="item.stageName == ''">
          <div
            class="class-title-wrap"
            @click="toggleItemList = !toggleItemList"
            :style="{marginLeft: 0.3*(item.level-1)  +'rem'}"
          >
            <span>
              <i class="iconfont icon-caidan"></i>
              <span>{{item.classjName}}</span>
            </span>
            <i :class="[toggleItemList?'iconfont icon-xiangshang':'iconfont icon-xiangxia']"></i>
          </div>
    
          <ul v-for="(child,index) in item.sub" :key="index" v-show="toggleItemList">
            <DetailMultiMenu v-if="child.stageName == ''" :item="child" :key="child.classjName"></DetailMultiMenu>
            <li
              v-else
              :key="child.id"
              :class="activeLi+index == child.id+index ? 'activeLi': ''"
              @click="videoInfo(child.id,child.isPay,child.id)"
            >
              <div class="class-item-wrap" :style="{marginLeft: 0.3*(child.level-1)  +'rem'}">
                <span class="class-title">
                  <i class="iconfont icon-zhibo11"></i>
                  <span>{{child.classjName}}</span>
                </span>
              </div>
            </li>
          </ul>
        </li>
    
        <li v-else class="class-item-wrap">
          <div>
            <i class="iconfont icon-zhibo11"></i>
            {{item.classjName}}
          </div>
        </li>
      </ul>
    </template>
    <script>
    import {
      // mapGetters,
      mapMutations
      // mapState
    } from "vuex";
    export default {
      name: "DetailMultiMenu",
      data() {
        return {
          toggleItemList: false,
          activeLi: -1
        };
      },
      props: {
        item: {
          type: Object,
          required: true
        }
      },
      computed: {},
      created() {},
      mounted() {},
      methods: {
        ...mapMutations([
          "ONLINE_VIDEO_VIDEOID",
          "ONLINE_VIDEO_BOUGHT",
          "ONLINE_VIDEO_PLAY"
        ]),
        videoInfo(itemId, bought, videoId) {
          if (bought) {
            this.activeLi = itemId;
          }
          this.ONLINE_VIDEO_BOUGHT(bought);
          this.ONLINE_VIDEO_VIDEOID(videoId);
          // this.ONLINE_VIDEO_PLAY(true);
          // this.$emit("videoInfo",{bought, videoId} );
        }
      },
      watch: {},
      components: {}
    };
    </script>
    <style scoped>
    /* .activeLi {
      background: #26a2ff;
    } */
    /* 课程目录 */
    .multi-menu {
      font-size: 0.3rem;
    }
    .class-title-wrap {
      border-bottom: 1px solid #ddd;
      line-height: 1rem;
      height: 1rem;
      display: flex;
      justify-content: space-between;
    }
    .class-item-wrap {
      border-bottom: 1px solid #ddd;
      overflow: hidden;
      line-height: 1rem;
      height: 1rem;
      display: -webkit-box;
      /*! autoprefixer: off */
      -webkit-box-orient: vertical;
      /* autoprefixer: on */
      -webkit-line-clamp: 1;
      overflow: hidden;
      white-space: pre-line;
      font-size: 0.24rem;
    }
    </style>

    行内切换 class 名

            <i :class="[toggleItemList?'iconfont icon-xiangshang':'iconfont icon-xiangxia']"></i>
    
            <li
              v-else
              :key="child.id"
              :class="activeLi+index == child.id+index ? 'activeLi': ''"
              @click="videoInfo(child.id,child.isPay,child.id)"
            ></li>

    行内样式自动计算

            <div class="class-item-wrap" :style="{marginLeft: 0.3*(child.level-1)  +'rem'}"></div>

    七牛云上传图片

                    <input
                      type="file"
                      id="avatarUpload"
                      ref="imgInput"
                      accept="image/*"
                      @change="PreviewImage"
                    >

      可以不用七牛云的插件,但要手动创建一个 formData,并且设置请求头

        PreviewImage(event) {
          let file = event.target.files[0];
          let formData = new FormData();
          formData.append("file", file);
          formData.append("token", this.qiniutoke);
          this.$http({
            url: "https://up-z2.qiniup.com",
            method: "POST",
            headers: { "Content-Type": "multipart/form-data" },
            data: formData
          })
            // _UploadQiniu({
            //   file:file,
            //   token:this.qiniutoke
            // })
            .then(res => {
              console.log(res);
              this.currentUser.handUrl = res.data.url + res.data.key;
              console.log(this.currentUser.handUrl);
            })
            .catch(err => {
              console.log(err);
            });
        }
  • 相关阅读:
    008-解决sublime text3安装Package Control问题
    156-a++与++a区别?
    155-类型转换的六条规则是什么?
    040-springboot工程无法逆向工程生成?
    153-如何搭建springboot框架?
    【根据生日获取年龄】
    富文本 保存转义StringEscapeUtils.unescapeHtml4(
    git ,报403错误,完美解决方案
    idea创建Maven项目后启动报404
    IntelliJ IDEA2016.1 + maven 创建java web 项目[转]
  • 原文地址:https://www.cnblogs.com/sspeng/p/10338599.html
Copyright © 2011-2022 走看看