vue 移动端项目总结(mint-ui)

回头看本身的代码,犹如鸡肋!!!里面有不少问题,建议你们不要看。我没时间整理╮(╯▽╰)╭

跨域解决方案

  config/dev.env.js   css

'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 ,生产的服务器(你线上运行时的服务器)html

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

  config/index.jsvue

    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

  接口请求的时候webpack

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,以下。ios

(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 序列化。后来又改回来了。web

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

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的底部菜单栏,我我的以为用的不是很习惯,找了不少资料才勉强填上这个坑,以下:axios


<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 是第一层组件,点击底部元素,可切换不一样模块api

{
      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将拥有流体大小。换句话说,它将按比例缩放以适应其容器。
      // width: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);
        });
    }
相关文章
相关标签/搜索