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);
        });
    }
原文地址:https://www.cnblogs.com/sspeng/p/10338599.html