文章目錄
關鍵詞
- @vue/cli4, vant, rem, svg, axios
參考鏈接
一. 使用vue/cli4
-
全局安裝@vue/cli最新版本
yarn add -g @vue/cli
或者npm install @vue/cli -g
-
查看安裝的vue/cli版本
vue --version
-
創建vue項目
vue create hello-world
-
創建項目時候讓選擇,默認or手動,一般選擇手動,按照提示選擇自己需要的即可。我選擇了以下2個關鍵的。
- CSS Pre-processors
- scss(node scss)
- eslint(prettier)
- CSS Pre-processors
-
vue/cli有個小坑。如果刪除了依賴,自己安裝一遍,發現有警告⚠️:
warning " > [email protected]" has unmet peer dependency "webpack@^4.36.0 || ^5.0.0".
。應該是腳手架的坑,暫時不知怎麼去改。
二. 使用vant
- 安裝插件
yarn add vant
- 按需引入插件
yarn add babel-plugin-import --dev
(注:這個插件裝到開發依賴) - 自動按需引入使用示例:
// template
<van-button type="default">默認按鈕</van-button>
// script
import { Button } from "vant";
components: {
[Button.name]: Button
}
三. 加入響應式佈局
1. rem適配插件
- postcss-pxtorem 是一款 postcss 插件,用於將單位轉化爲 rem 。(!安裝到開發依賴 --dev)
- lib-flexible 用於動態改變根節點的font-size,設置 rem 基準值。(!安裝到生產依賴 --save)
- 【小坑】lib-flexible按照官網提供的在html引入js會報錯,改爲在main.js中引入依賴
import "amfe-flexible/index.js";
就ok了。
2. PostCSS配置
- vue.config.js中配置
css: { loaderOptions: { postcss: { plugins: [ require("autoprefixer")({ // 配置使用 autoprefixer overrideBrowserslist: ["last 15 versions"] }), require("postcss-pxtorem")({ rootValue: 37.5, // 換算的基數 // 忽略轉換正則匹配項。插件會轉化所有的樣式的px。比如引入了三方UI,也會被轉化。目前我使用 selectorBlackList字段,來過濾 //如果個別地方不想轉化px。可以簡單的使用大寫的 PX 或 Px 。 selectorBlackList: ["ig"], propList: ["*"] }) ] } } }
- postcss.config.js中配置
module.exports = { plugins: { autoprefixer: { overrideBrowserslist: ['Android >= 4.0', 'iOS >= 8'], }, 'postcss-pxtorem': { rootValue: 37.5, // ⚠️這裏是設計稿的1/10 propList: ['*'], mediaQuery: true }, }, };
在配置 postcss-loader 時,應避免 ignore node_modules 目錄,否則將導致 Vant 樣式無法被編譯
四. 圖標庫:封裝svg圖標組件
- 原因:svg放大後不失真,可以像css一樣設置顏色,非常方便。
- 使用步驟:
1. 建立如下目錄結構:
icon
index.js
svg
test1.svg // (去阿里的iconfont隨便下載一個來試驗)
test2.svg
components
SvgIcon.vue
2. components/SvgIcon.vue
<template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: "SvgIcon",
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ""
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`;
},
svgClass() {
if (this.className) {
return "svg-icon " + this.className;
} else {
return "svg-icon";
}
}
}
};
</script>
<style scoped>
.svg-icon {
width: 16px;
height: 16px;
vertical-align: -3px;
fill: currentColor;
overflow: hidden;
}
</style>
3. icon/index.js
import Vue from "vue";
import SvgIcon from "@/components/SvgIcon"; // svg組件
// register globally
Vue.component("svg-icon", SvgIcon);
const req = require.context("./svg", false, /\.svg$/);
const requireAll = requireContext => requireContext.keys().map(requireContext);
requireAll(req);
4. 配置vue.config.js
// 添加svg-sprite-loader,同時不要忽略了其他不作爲圖片的svg文件,
// file-loader 用來處理除了icon/svg文件夾下其他地方的.svg文件
chainWebpack: config => {
const svgRule = config.module.rule("svg");
// 清除已有的所有 loader。
// 如果你不這樣做,接下來的 loader 會附加在該規則現有的 loader 之後。
svgRule.uses.clear();
svgRule
.test(/\.svg$/)
.include.add(path.resolve(__dirname, "./src/icons/svg"))
.end()
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]"
});
const fileRule = config.module.rule("file");
fileRule.uses.clear();
fileRule
.test(/\.svg$/)
.exclude.add(path.resolve(__dirname, "./src/icons/svg"))
.end()
.use("file-loader")
.loader("file-loader");
}
5. svg圖標使用
// class="color-red" 可以添加自定義的樣式,可以覆蓋默認的fill
<svg-icon
class="color-red"
icon-class="arrow_bottom_solid"
></svg-icon>
五.axios+api封裝
目錄結構(示例)
request
http.js
api
index.js
user.js
http.js封裝
import axios from "axios";
import router from "../router";
import store from "../store";
/**
* 提示函數
* 禁止點擊蒙層、顯示一秒後關閉
*/
const tip = msg => {
Toast({
message: msg,
duration: 1000,
forbidClick: true
});
};
/**
* 跳轉登錄頁
* 攜帶當前頁面路由,以期在登錄頁面完成登錄後返回當前頁面
*/
const toLogin = async () => {
router.replace({
path: "/login",
query: {
redirect: router.currentRoute.fullPath
}
});
};
/**
* 請求失敗後的錯誤統一處理
* @param {Number} status 請求失敗的狀態碼
*/
const errorHandle = status => {
// 狀態碼判斷
switch (status) {
// 401: 未登錄狀態,跳轉登錄頁
case 401:
toLogin();
break;
// 403 token過期
// 清除token並跳轉登錄頁
case 403:
tip("登錄過期,請重新登錄");
localStorage.removeItem("token");
setTimeout(() => {
toLogin();
}, 1000);
break;
// 404請求不存在
case 404:
tip("請求的資源不存在");
break;
default:
tip(`其他未知錯誤,狀態碼:${status}`);
}
};
// 狀態200時候, code碼判斷
const errorCodeHandle = ({ code, message }) => {
switch (code) {
case "000000": //系統交易成功
break;
case "999999": //系統異常
tip(message);
break;
case "AUTH_x1": //用戶未登陸
store.commit("storeUser/clearUserInfo");
toLogin();
break;
case "AUTH_x2": //用戶無權限
store.commit("storeUser/clearUserInfo");
tip(message);
break;
case "LOGIN_x3": //用戶已禁用
store.commit("storeUser/clearUserInfo");
tip(message);
break;
case "LOGIN_x4": //用戶session失效
store.commit("storeUser/clearUserInfo");
toLogin();
break;
default:
tip(message);
break;
}
};
// 創建axios實例
var instance = axios.create({ timeout: 5000 });
// 設置post請求頭
instance.defaults.headers.post["Content-Type"] =
"application/json;charset=UTF-8;";
instance.defaults.baseURL = "api";
// 請求攔截器
instance.interceptors.request.use(
config => {
// 對config做一些處理
// ...
// 加載彈窗
Toast.loading({
message: "加載中...",
forbidClick: true
});
return config;
},
error => Promise.error(error)
);
// 響應攔截器
instance.interceptors.response.use(
// 請求成功
res => {
Toast.clear();
if (!store.state.storeGlobal.network) {
store.commit("storeGlobal/changeNetwork", true);
}
if (res.status === 200 && res.data.code === "000000") {
return Promise.resolve(res.data);
} else {
errorCodeHandle(res.data);
return Promise.reject(res);
}
},
// 請求失敗
error => {
const { response } = error;
if (response) {
// 請求已發出,但是不在2xx的範圍
errorHandle(response.status, response.data.message);
return Promise.reject(response);
} else {
// 處理斷網的情況
// eg:請求超時或斷網時,更新state的network狀態
// network狀態在app.vue中控制着一個全局的斷網提示組件的顯示隱藏
// 關於斷網組件中的刷新重新獲取數據,會在斷網組件(refresh.vue)中說明
if (!window.navigator.onLine) {
store.commit("storeGlobal/changeNetwork", false);
} else {
return Promise.reject(error);
}
}
}
);
export default instance;
api/index.js
/**
* api接口的統一出口
*/
import user from "@/request/api/user";
// 導出接口
export default {
user
};
api/user.js
/**
* user模塊接口列表
*/
import axios from "@/request/http"; // 導入http中創建的axios實例
const user = {
login(params) {
return axios.post("/login", params);
}
};
export default user;
api註冊到全局(main.js文件)
import api from '@/request/api';
Vue.prototype.$api = api;
api接口調用示例
// Login.vue
methods: {
async onSubmit() {
let params = {
loginName: '小美',
password: '123'
};
const res = await this.$api.login(params);
console.log("登錄信息:", res)
}
}
App.vue(斷網代碼示例)
使用一個全局的store狀態存儲網絡狀態
<template>
<div id="app">
<div v-if="!network" class="offline">
哎呀,網絡開小差啦。<van-icon name="replay" @click.native="onRefresh" />
</div>
<router-view />
</div>
</template>
<script>
import { mapState } from "vuex";
import { Icon } from "vant";
export default {
components: {
[Icon.name]: Icon
},
computed: {
...mapState("storeGlobal", ["network"])
},
methods: {
onRefresh() {
this.$router.replace("/refresh");
}
}
};
</script>
<style lang="scss">
#app {
font-family: STHeitiSC-Medium, STHeitiSC, Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
background-color: #f5f5f5;
height: 100vh;
.offline {
text-align: center;
padding: 10px;
background-color: #ffeeaa;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
refresh.vue
<template>
<div></div>
</template>
<script>
/* 從app.vue來,這裏簡單介紹一下斷網。在http.js中介紹了,我們會在斷網的時候,來更新vue中network的狀態,
* 那麼這裏我們根據network的狀態來判斷是否需要加載這個斷網組件。斷網情況下,加載斷網組件,不加載對應頁面的組件。
* 當點擊刷新的時候,我們通過跳轉refesh頁面然後立即返回的方式來實現重新獲取數據的操作。
* 因此我們需要新建一個refresh.vue頁面,並在其beforeRouteEnter鉤子中再返回當前頁面。
*/
export default {
beforeRouteEnter(to, from, next) {
next(vm => {
vm.$router.replace(from.fullPath);
});
}
};
</script>
六. router全局守衛處理
// router/index.js
const routes = [
{
path: "/login",
name: "Login",
component: Login,
meta: {
title: "登錄"
}
},
]
router.beforeEach((to, from, next) => {
// 添加title, 無需每個頁面設置
if (to.meta && to.meta.title) {
document.title = to.meta.title;
}
// 添加路由來源,無需每個頁面添加路由守衛判斷來自哪個頁面
to.params.last = from;
next();
});
七. 跨域代理proxy -> 配置vue.config.js文件
從vue/cli3開始項目就看不到webpcak.config.js之類的配置文件了。需要添加前端代理需要自己在根目錄下添加vue.config.js進行配置。
下面⬇️展示一個配置比較齊全的文件。
const path = require("path");
module.exports = {
/* 部署生產環境和開發環境下的URL:可對當前環境進行區分,baseUrl 從 Vue CLI 3.3 起已棄用,要使用publicPath */
/* baseUrl: process.env.NODE_ENV === 'production' ? './' : '/' */
publicPath: process.env.NODE_ENV === "production" ? "/public/" : "./",
/* 輸出文件目錄:在npm run build時,生成文件的目錄名稱 */
outputDir: "dist",
/* 放置生成的靜態資源 (js、css、img、fonts) 的 (相對於 outputDir 的) 目錄 */
assetsDir: "assets",
/* 是否在構建生產包時生成 sourceMap 文件,false將提高構建速度 */
productionSourceMap: false,
/* 默認情況下,生成的靜態資源在它們的文件名中包含了 hash 以便更好的控制緩存,你可以通過將這個選項設爲 false 來關閉文件名哈希。(false的時候就是讓原來的文件名不改變) */
filenameHashing: false,
/* 代碼保存時進行eslint檢測 */
lintOnSave: true,
/* webpack-dev-server 相關配置 */
devServer: {
/* 自動打開瀏覽器 */
open: true,
/* 設置爲0.0.0.0則所有的地址均能訪問 */
host: "0.0.0.0",
port: 8088,
https: false,
hotOnly: false,
/* 使用代理 */
proxy: {
"/sunrise-gateway": {
/* 目標代理服務器地址 */
target: "http://xxx.com/",
/* 允許跨域 */
changeOrigin: true
}
}
},
css: {
loaderOptions: {
postcss: {
plugins: [
require("autoprefixer")({
// 配置使用 autoprefixer
overrideBrowserslist: ["last 15 versions"]
}),
require("postcss-pxtorem")({
rootValue: 37.5, // 換算的基數
// 忽略轉換正則匹配項。插件會轉化所有的樣式的px。比如引入了三方UI,也會被轉化。目前我使用 selectorBlackList字段,來過濾
//如果個別地方不想轉化px。可以簡單的使用大寫的 PX 或 Px 。
selectorBlackList: ["ig"],
propList: ["*"]
})
]
}
}
},
chainWebpack: config => {
const svgRule = config.module.rule("svg");
// 清除已有的所有 loader。
// 如果你不這樣做,接下來的 loader 會附加在該規則現有的 loader 之後。
svgRule.uses.clear();
svgRule
.test(/\.svg$/)
.include.add(path.resolve(__dirname, "./src/icons/svg"))
.end()
.use("svg-sprite-loader")
.loader("svg-sprite-loader")
.options({
symbolId: "icon-[name]"
});
const fileRule = config.module.rule("file");
fileRule.uses.clear();
fileRule
.test(/\.svg$/)
.exclude.add(path.resolve(__dirname, "./src/icons/svg"))
.end()
.use("file-loader")
.loader("file-loader");
}
};
八. vscode中自定義配置prettier
- vscode安裝插件:Prettier - Code formatter
- 問題:插件格式化的文件和vuecli要求的prettimer需要的不一致。所以需要自定義配置成vuecli要求的效果。
- 解決:代碼(code) -> 首選項(preference) -> 設置(settings) -> extensions -> premitter
- 具體配置可以參考premitter配置文件官方網站
- 中文的找到一篇基本配置+解釋的參考文章Prettier格式化配置
{
// 使能每一種語言默認格式化規則
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[less]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
/* prettier的配置 */
"prettier.printWidth": 100, // 超過最大值換行
"prettier.tabWidth": 4, // 縮進字節數
"prettier.useTabs": false, // 縮進不使用tab,使用空格
"prettier.semi": true, // 句尾添加分號
"prettier.singleQuote": true, // 使用單引號代替雙引號
"prettier.proseWrap": "preserve", // 默認值。因爲使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本樣式進行折行
"prettier.arrowParens": "avoid", // (x) => {} 箭頭函數參數只有一個時是否要有小括號。avoid:省略括號
"prettier.bracketSpacing": true, // 在對象,數組括號與文字之間加空格 "{ foo: bar }"
"prettier.disableLanguages": ["vue"], // 不格式化vue文件,vue文件的格式化單獨設置
"prettier.endOfLine": "auto", // 結尾是 \n \r \n\r auto
"prettier.eslintIntegration": false, //不讓prettier使用eslint的代碼格式進行校驗
"prettier.htmlWhitespaceSensitivity": "ignore",
"prettier.ignorePath": ".prettierignore", // 不使用prettier格式化的文件填寫在項目的.prettierignore文件中
"prettier.jsxBracketSameLine": false, // 在jsx中把'>' 是否單獨放一行
"prettier.jsxSingleQuote": false, // 在jsx中使用單引號代替雙引號
"prettier.parser": "babylon", // 格式化的解析器,默認是babylon
"prettier.requireConfig": false, // Require a 'prettierconfig' to format prettier
"prettier.stylelintIntegration": false, //不讓prettier使用stylelint的代碼格式進行校驗
"prettier.trailingComma": "es5", // 在對象或數組最後一個元素後面是否加逗號(在ES5中加尾逗號)
"prettier.tslintIntegration": false // 不讓prettier使用tslint的代碼格式進行校驗
}
九. 查看隱藏的webpack配置:
- vue inspect 執行後,控制檯會顯示你的webpack所有的配置
- vue inspect --rules 顯示所有的rule配置規則
- vue inspect --rule svg (我們在上面配置了svg)