在最近的 Vue 項目中,爲了完成需求使用了一些小技巧,作個筆記,或許也能幫到道友。css
在開發過程當中,咱們常常須要引入各類文件,如圖片、CSS、JS等,爲了不寫很長的相對路徑(../
),咱們能夠爲不一樣的目錄配置一個別名。html
找到 webpack.base.config.js
中的 resolve
配置項,在其 alias
中增長別名,以下:vue
建立一個 CSS 文件,隨便寫點樣式:webpack
.avatar
display: flex;
justify-content: center;
align-items: center;
.avatar-img
padding 20px
border solid 1px #ccc
border-radius 5px
複製代碼
接着,在咱們須要引入的文件中就能夠直接使用了:ios
<template>
<div class="avatar">
<img class="avatar-img" src="~img/avatar.png" alt="">
</div>
</template>
<script>
export default {
name: "Home"
}
</script>
<style scoped lang="stylus">
@import "~css/avatar";
</style>
複製代碼
須要注意的是,若是不是經過 import
引入則須要在別名前加上 ~
,效果以下:web
這個需求,怎麼說呢,反正就是需求,就想辦法實現吧。element-ui
假設有一個 apiConfig.js
文件,用於對 axios
作一些配置,以下:json
import axios from 'axios';
axios.defaults.timeout = 10000;
axios.defaults.retry = 3;
axios.defaults.retryDelay = 2000;
axios.defaults.responseType = 'json';
axios.defaults.withCredentials = true;
axios.defaults.headers.post["Content-type"] = "application/json";
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
export default axios
複製代碼
在 static
文件夾中增長一個 config.json
文件,用於統一管理全部的 api 地址:axios
{
"base": "/api",
"static": "//static.com/api",
"news": "//news.com.api"
}
複製代碼
打開 main.js
,寫入下列代碼:api
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import axios from 'js/apiConfig'; //import直接引入,不用添加~
Vue.config.productionTip = false;
Vue.use(ElementUI);
/* eslint-disable no-new */
let startApp = function () {
let randomStamp = new Date().getTime();
axios.get(`/static/config.json?t=${randomStamp}`).then((data) => {
axios.defaults.baseURL = data.base; //設置一個默認的根路徑
Vue.prototype.$axios = axios;
Vue.prototype.$apiURL = data; //將全部路徑配置掛載到 Vue 原型上
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: {App},
template: '<App/>'
});
})
};
startApp();
複製代碼
就是先用 axios
獲取 api 文件,而後再初始化。
菜單是樹形結構(PS:就算不是樹形結構,你也得處理成樹形結構),我這裏使用的是 ElementUI ,參考了道友的這篇文章,實現以下:
新建一個 Menu.vue
文件,寫入以下代碼:
<script>
export default {
name: "MenuItem",
props: {
data: {
type: Array
},
collapse: {
type: Boolean
}
},
methods: {
//生成菜單項
createMenuItem(data, createElement) {
return data.map(item => {
if (item.children && item.children.length) {
return createElement('el-submenu', {props: {index: item.id.toString()}},
[
createElement('template', {slot: 'title'}, [
createElement('i', {class: item.icon}),
createElement('span', [item.title]),
]
),
this.createMenuItem(item.children, createElement) //遞歸
]
)
} else {
return createElement('el-menu-item', {props: {index: item.path}},
[
createElement('i', {class: item.icon}),
createElement('span', {slot: 'title'}, [item.title]),
]
)
}
})
},
//選中菜單
onSelect(key, keyPath) {
console.log(key, keyPath);
}
},
render(createElement) {
return createElement(
'el-menu',
{
props: {
backgroundColor: "#545c64",
textColor: "#fff",
activeTextColor: "#ffd04b",
collapse: this.collapse,
router:true
},
class:'el-menu-vertical-demo',
on: {
select: this.onSelect
}
},
this.createMenuItem(this.data, createElement)
)
}
}
</script>
<style scoped lang="stylus">
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
</style>
複製代碼
這裏主要用到兩個東西,一個是 render
函數,一個是遞歸,若是不熟悉 render
函數的道友請點這裏。可能有道友會問爲何不用模板,由於······作不到啊😭,在 template
中只能有一個根元素,而 Vue 限制了不能對根元素使用 v-for
;再者,經過在瀏覽器中查看代碼能夠知道,菜單就是 ul
加上 li
,若是有了根元素會破壞標籤結構(雖然不影響功能,但仍是以爲不舒服😂)。而後,在須要使用的地方:
<template>
<el-container>
<el-aside width="auto">
<Menu :data="menu" :collapse="isCollapsed"></Menu>
</el-aside>
<el-container>
<el-header>
<el-button type="text" icon="el-icon-d-arrow-left"
@click="isCollapsed=!isCollapsed"></el-button>
<h3>MenuName</h3>
<span>MeFelixWang</span>
</el-header>
<el-main>
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script>
import Menu from '@/components/Menu';
export default {
name: 'App',
data() {
return {
menu: [
{
title: '導航一',
id: 1,
path: '',
icon: 'el-icon-search',
children: [
{
title: '導航一槓一', id: 2, path: '', icon: '', children: [
{title: '導航一槓一槓一', id: 4, path: '/test', icon: '', children: []},
{
title: '導航一槓一槓二', id: 5, path: '', icon: '', children: [
{title: '導航一槓一槓二槓一', id: 6, path: '/6', icon: '', children: []},
{title: '導航一槓一槓二槓二', id: 7, path: '/7', icon: '', children: []},
]
},
]
},
{title: '導航一槓二', id: 3, path: '/3', icon: '', children: []}
]
},
{title: '導航二', id: 8, path: '/8', icon: 'el-icon-setting', children: []},
{title: '導航三', id: 9, path: '/9', icon: 'el-icon-document', children: []},
{
title: '導航四', id: 10, path: '', icon: 'el-icon-date', children: [
{title: '導航四槓一', id: 11, path: '/11', icon: '', children: []},
{
title: '導航四槓二', id: 12, path: '', icon: '', children: [
{title: '導航四槓二槓一', id: 14, path: '/14', icon: '', children: []}
]
},
{title: '導航四槓三', id: 13, path: '/13', icon: '', children: []},
]
},
],
isCollapsed: false
}
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath);
},
handleClose(key, keyPath) {
console.log(key, keyPath);
}
},
components: {
Menu
}
}
</script>
<style lang="stylus">
*
margin 0
padding 0
html, body, .el-container, .el-aside
height 100%
.el-aside
background-color rgb(84, 92, 100)
.el-menu
border-right solid 1px rgb(84, 92, 100)
.el-header
display flex
justify-content space-between
align-items center
background-color aliceblue
.el-button--text
color: #606266;
i
font-weight bold
</style>
複製代碼
效果以下:
樹形結構就樹形結構吧,不就是樣式嘛,改改應該就能夠了。
<template>
<div>
<el-select v-model="tree" placeholder="請選擇活動區域">
<el-option v-for="(item,index) in options" :key="index" :label="item.label" :value="item.id"
:style="{paddingLeft:(item.level*10+20)+'px'}" :class="item.level?'is-sub':''"></el-option>
</el-select>
選擇的是:{{tree}}
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
tree: '',
options: [],
originData: [
{
label: '這是根一', id: 1, children: [
{label: '這是莖一一', id: 2, children: []},
{label: '這是莖一二', id: 3, children: []},
{
label: '這是莖一三', id: 4, children: [
{label: '這是葉一三一', id: 6, children: []},
{label: '這是葉一三二', id: 7, children: []},
]
},
{label: '這是莖一四', id: 5, children: []},
]
},
{
label: '這是根二', id: 8, children: [],
},
{
label: '這是根三', id: 9, children: [
{label: '這是莖三一', id: 10, children: []},
{
label: '這是莖三二', id: 11, children: [
{label: '這是葉三二一', id: 12, children: []}
]
},
],
},
]
}
},
created() {
this.options = this.decomposeTree(this.originData, 0);
},
methods: {
//分解樹形結構
decomposeTree(array, level) {
let tmpArr = [];
(function decompose(arr, lev) {
for (let i = 0; i < arr.length; i++) {
let tmpObj = {};
let item = arr[i];
item.level = lev;
tmpObj = Object.assign({}, item);
tmpArr.push(tmpObj);
if (item.children) {
decompose(item.children, lev + 1); //遞歸
}
delete tmpObj.children; //刪掉其 children,避免數據過大(不刪也能夠,也許後面有用呢)
}
})(array, level);
return tmpArr;
}
}
}
</script>
<style scoped lang="stylus">
.is-sub:before
content '- '
</style>
複製代碼
由於 option
接收的是一個一維數組,因此經過遞歸展平樹形結構,在展平的時候設置每項的層級,經過層級來設置縮進及前綴符號,效果以下:
之因此這樣作,是由於是管理系統,簡單有效,不必由於這一個組件引個新的插件或者本身寫一個(之後用得着的除外哈);也能夠用 input
加上 tree
控件來模擬(PS:最終仍是引入了一個插件,哈哈😂)。
這個需求是讓用戶本身寫模版,是的,沒錯,就是要讓用戶本身寫模版,嗯,和根據用戶手機殼改變界面顏色同樣的需求,看了動態組件和異步組件,本身試了幾種方式,仍是不行,後來在社區論壇看到有位大佬的回答(還真有人遇到和我同樣的需求(┬_┬)),這是地址,實現以下:
<template>
<component :is="dynComponent" v-bind="data"></component>
</template>
<script>
export default {
name: "test",
props: ['test'],
data() {
return {
template: '',
data: {}
}
},
created() {
this.getTemplate() // 獲取模版
},
methods: {
getTemplate() {
this.$axios.get('http://localhost:8080/static/test.json').then((result) => {
this.template = result.template;
this.data = result;
});
}
},
computed: {
dynComponent() {
const template = this.template ? `<div>${this.template}</div>` : `<div>nothing here yet</div>`;
return {
template, // template 就是模版
props: ['data'] // 傳入數據
}
},
}
}
</script>
<style scoped lang="stylus">
</style>
複製代碼
由於 JS 是單線程,在異步模版回來前就會渲染頁面,此方法是經過計算屬性的響應式特性,在取回模版後讓 Vue 從新渲染一次。給這位大佬獻上膝蓋。若是道友們有更好的方法,麻煩告訴我一下,感激涕零!
有些圖片可能來自於另外一個網站或者啥的,反正道友們都懂,這個時候要設置一張默認圖,實現以下:
<template>
<div>
<img v-bind:src="imgUrl" @error="handleError" alt="">
</div>
</template>
<script>
export default {
name: "userList",
data() {
return {
imgUrl: 'url of the image'
}
},
methods: {
handleError(e) {
e.target.src = '/static/default.png'
}
}
}
</script>
<style scoped lang="stylus">
</style>
複製代碼
在 static
文件夾中放一張默認圖,而後處理 img
的 onerror
事件,將 src
設置 static
中默認圖片路徑。
這個需求,很合理!若是不終止以前的請求,就可能在新頁面看到以前請求成功(或失敗)後彈出的一些提示信息,這確定是不合理的。怎麼實現呢? axios 提供了取消請求的方法:
但這裏有個小問題,一個頁面的請求可能有不少,那麼在切換頁面的時候確定不能一個一個的取消(並且你也不知道具體調用了哪些接口),網友這裏提供了一個方法,我作了一些優化:
init() {
let self = this;
//配置全局取消數組
window.__axiosPromiseArr = [];
//請求攔截
this.$axios.interceptors.request.use(function (config) {
//爲每一個請求設置 cancelToken
config.cancelToken = new self.$axios.CancelToken(cancel => {
window.__axiosPromiseArr.push({cancel}) //放入一個全局數組,以便以後統一取消
});
return config;
}, function (error) {
return Promise.reject(error);
});
//響應攔截
this.$axios.interceptors.response.use((response) => {
switch (response.status) {
case 204:
this.$message.success('操做成功!');
break;
default:
return response.data;
}
}, (error) => {
if (error.message === 'cancel') {
//終止請求會拋出一個錯誤,捕獲一下,不讓其顯示在控制檯
}
});
},
複製代碼
而後在路由守衛中:
vueRouter.beforeEach((to, from, next) => {
//路由切換時終止全部請求
let axiosPromiseArr = window.__axiosPromiseArr;
if (axiosPromiseArr) {
console.log(axiosPromiseArr);
let len = axiosPromiseArr.length;
while (len--) { //從後向前終止請求,並刪除 cancelToken,避免數組索引帶來的問題
axiosPromiseArr[len].cancel('cancel');
axiosPromiseArr.splice(len, 1);
}
//或者:window.__axiosPromiseArr = [];
}
next()
});
複製代碼
目前好像這個方法還算實用,若是道友們有更好的方法,請留言告訴我一下。
本文是我最近用到的一些小技巧,若是道友們有更好的實現方法,歡迎在評論區留言討論,文中錯誤也歡迎指出,共同窗習(固然,有疑難需求也能夠留言,一塊兒商討解決方案😄),本文會不按期更新,就當是筆記本了😏。