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);
        });
    }
相關文章
相關標籤/搜索