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設置*/ 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 的 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
這個格式的視頻仍是挺多的,可是實現播放的話,就有點複雜了(要裝兩個插件,還一堆問題),折騰了兩天,這速度算快仍是慢呢。
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 的 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>
<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); }); }