Writing in the front
In the recent Vue project, I used a few tricks to meet the requirements. Take notes and maybe help my friends too.
Read the key
Requirement 1: Configure an alias for a path
In the development process, we often need to introduce various files, such as images, CSS, JS, etc., to avoid writing long relative paths (.. /), we can configure an alias for different directories.
Go to the resolve configuration item in webpack.base.config.js and add an alias to its alias as follows:
Create a CSS file with arbitrary styles:
.avatar
display: flex;
justify-content: center;
align-items: center;
.avatar-img
padding 20px
border solid 1px #ccc
border-radius 5px
Copy the code
Next, we can use it directly in the files we need to import:
<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>Copy the code
Note that the alias must be preceded by ~ if it is not imported as import:
Requirement 2: The implementation is required to modify the API address directly in the production package
This need, how can I say, is a need, find a way to achieve it.
Suppose you have an apiconfig.js file that does some configuration to AXIOS as follows:
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
Copy the code
Add a config.json file to the static folder to centrally manage all API addresses:
{
"base": "/api"."static": "//static.com/api"."news": "//news.com.api"
}
Copy the code
Open main.js and write the following code:
// 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 is directly introduced without adding ~
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; // Set a default root path
Vue.prototype.$axios = axios;
Vue.prototype.$apiURL = data; // Mount all path configurations to the Vue prototype
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: {App},
template: '<App/>'}); })}; startApp();Copy the code
Get the API file using AXIos and then initialize it.
Requirement 3: The background returns to the menu based on the user permission value
The menu is a tree structure (PS: even if it’s not a tree structure, you have to deal with it as a tree structure). I’m using ElementUI as a reference to the daoyou article, which is implemented as follows:
Create a new menu. vue file and write the following code:
<script> export default { name: "MenuItem", props: { data: { type: Array }, collapse: { type: Boolean } }, methods: {// createMenuItem 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) // recursive])} else {return createElement('el-menu-item', {props: {index: item.path}}, [ createElement('i', {class: item.icon}), createElement('span', {slot: 'title'}, [item.title]),])}}), // select menu 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>Copy the code
Here we mainly use two things, one is the render function, one is recursion, if you are not familiar with the render function, please click here. There is only one root element in template, and Vue limits the use of V-for for root elements. Also, by looking at the code in the browser, you can see that the menu is ul plus Li, and having the root element will break the tag structure (it doesn’t affect functionality, but it’s still uncomfortable 😂). Then, where needed:
<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: 'navigation 1 ', id: 1, path: '', icon: 'el - icon - search, children: [{title:' navigation bar one, id: 2, path: "', icon:" ', children: [{title: 'navigation and a bar', id: 4, path: '/ test' icon: "', children: []}, {title: 'a navigation bar bar 2, id: 5, path:"', icon: "', children: [{title: 'navigation and a bar two poles, id: 6, path: '/ 6' icon: "', children: []}, {title: 'a navigation bar and two poles two', id: 7, path: '/ 7' icon: ' ', the children: []},]}}, {title: 'navigation bar 2, id: 3, path:' / 3, icon: "', children: []}}, {title: 'navigation 2, id: 8, path:' / 8 'icon: 'el-icon-setting', children: []}, {title: 'navigate ', id: 9, path: '/9', icon: 'el-icon-document', children: []}, {title:' navigate ', children: []}, {title: 'navigate ', children: []}, {title: Children: [{title: 'id ', id: 11, path: '/11', icon: '', children: [{title:' id ', path: '/11', icon: '', children: [{title: 'id ', path: '/11', icon: '', children: [{title:' id ', path: '/11', icon: '', children: []}, {title: 'navigation four poles two, id: 12, path: "', icon: "', children: [{title: 'navigation four poles two poles, id: 14, path:' / 14 'icon: 'children: []}}, {title:' navigation four poles, id: 13, path: '/', 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>Copy the code
The effect is as follows:
Requirement 4: The Select option is a tree structure, it must be a tree structure
Tree structure tree structure bar, is not the style, change should be ok.
<template> <div> <el-select V-model ="tree" placeholder=" 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? {{tree}} </div> </template> <script> export default {name: "Home", data() {return {tree: ", options: [], originData: [{label: 'this is the root ', id: 1, children: [{label:' this is the stem ', id: 1, children: [{label: 'this is the stem ', id: 2, children: []}, {label: 'this is a stem ', id: 3, children: []}, {label:' this is a stem ', id: 4, children: [{label: 'this is a stem ', id: 4, children: []}, {label:' this is a stem ', id: 4, children: [{label: 'this is a stem ', id: 4, children: []}, {label:' this is a stem ', id: 4, children: [] 6, children: []}, {label: 'this is leaves the 132', id: 7, children: []},]}, {label: 'this is success stems, id: 5, children: []},]}, {label: 'This is root 2 ', id: 8, children: [],}, {label:' this is root 3 ', id: 9, children: []}, {label: 'this is root 3 ', id: 10, children: []}, {label:' this is root 3 ', id: 10, children: []}, {label: 'This is stem 3 2 ', id: 11, children: [{label:' this is leaf 3 2 1 ', 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; }})(array, level); return tmpArr; } } } </script> <style scoped lang="stylus"> .is-sub:before content '- ' </style>Copy the code
Since option receives a one-dimensional array, the hierarchy of each item is set during the flattening by recursively flattening the tree structure, and the indentation and prefix symbols are set through the hierarchy. The effect is as follows:
The reason for doing so, because it is a management system, simple and effective, there is no need for this component to introduce a new plug-in or write a (later needed except ha); This can also be simulated using input plus tree controls (PS: finally introducing a plugin, haha 😂).
Requirement 5: Allow users to customize the display template
This requirement is to allow users to write their own templates, yes, yes, is to let the user write their own templates, well, and according to the demand of the user interface following change color, dynamic component and asynchronous components, their tried several ways and still won’t do, later in the community BBS see there is a big answer (and I do really somebody in demand (┬ _ ┬)), This is the address, implemented as follows:
<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 is the template props: ['data'] // pass data}},}} </script> <style scoped lang="stylus"> </style>Copy the code
Because JS is single-threaded, the page will be rendered before the asynchronous template comes back. This method computs the responsive properties of the properties and lets Vue re-render once the template is retrieved. Give the big guy a knee. If tao friends have a better way, please tell me, thank you!
Requirement 6: Remote image loading failed, set default image
Some of the images may be from another website or something, so you should set a default image as follows:
<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>
Copy the code
Place a default image in the static folder, then handle the img onError event and set SRC to the default image path in static.
Requirement 7: Single-page applications should terminate previous requests when switching pages
This demand, very reasonable! If you don’t terminate the previous request, you might see some of the messages that pop up on the new page after the previous request succeeded (or failed), which is definitely not reasonable. How do you do that? Axios provides a way to cancel the request:
But there is a small problem, a page may have a lot of requests, so when switching pages can not be cancelled one by one (and you do not know which interface is called specifically), netizens here provide a method, I made some optimization:
init() {
let self = this;
// Configure the global cancel array
window.__axiosPromiseArr = [];
// Request interception
this.$axios.interceptors.request.use(function (config) {
// Set cancelToken for each request
config.cancelToken = new self.$axios.CancelToken(cancel= > {
window.__axiosPromiseArr.push({cancel}) // Put it into a global array so that it can be cancelled uniformly later
});
return config;
}, function (error) {
return Promise.reject(error);
});
// Response interception
this.$axios.interceptors.response.use((response) = > {
switch (response.status) {
case 204:
this.$message.success('Operation successful! ');
break;
default:
return response.data;
}
}, (error) => {
if (error.message === 'cancel') {
// Abort requests throw an error, catch it and keep it from showing on the console}}); },Copy the code
Then in the route guard:
vueRouter.beforeEach((to, from, next) = > {
// Terminates all requests during route switchover
let axiosPromiseArr = window.__axiosPromiseArr;
if (axiosPromiseArr) {
console.log(axiosPromiseArr);
let len = axiosPromiseArr.length;
while (len--) { CancelToken cancelToken cancelToken cancelToken cancelToken cancelToken cancelToken cancelToken cancelToken
axiosPromiseArr[len].cancel('cancel');
axiosPromiseArr.splice(len, 1);
}
// or: window.__axiospromisearr = [];
}
next()
});
Copy the code
So far, it seems that this method is still practical. If you have a better method, please leave a message and tell me.
The last words
This article is my recent use of some small skills, if the road friends have a better way to achieve, welcome in the comments section of the message discussion, the error is also welcome to point out, common learning (of course, there are difficult needs can also leave a message, discuss solutions 😄), this article will be updated from time to time, as a notebook 😏.