回頭看自己的代碼,猶如雞肋!!!里面有很多問題,建議大家不要看。我沒時間整理╮(╯▽╰)╭
跨域解決方案
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設置
/*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將擁有流體大小。換句話說,它將按比例縮放以適應其容器。 // 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); }); }