摘要:項(xiàng)目架構(gòu)項(xiàng)目目錄項(xiàng)目目錄是采用自動(dòng)生成,其它按需自己新建就好了。
項(xiàng)目架構(gòu) 項(xiàng)目目錄
├── build ├── config ├── dist │?? └── static │?? ├── css │?? ├── fonts │?? ├── images │?? ├── js │?? └── lib ├── src │?? ├── api │?? ├── assets │?? │?? ├── global │?? │?? └── images │?? │?? └── footer │?? ├── components │?? │?? ├── common │?? │?? ├── news │?? │?? └── profile │?? │?? └── charge │?? ├── config │?? ├── mixin │?? ├── router │?? ├── service │?? ├── store │?? └── util └── static ├── images └── lib
項(xiàng)目目錄是采用 vue-cli 自動(dòng)生成,其它按需自己新建就好了。
開發(fā)實(shí)踐 動(dòng)態(tài)修改 document title在不同的路由頁(yè)面,我們需要?jiǎng)討B(tài)的修改文檔標(biāo)題,可以將每個(gè)頁(yè)面的標(biāo)題配置在路由元信息 meta 里面帶上,然后在 router.afterEach 鉤子函數(shù)中修改:
import Vue from "vue"; import Router from "vue-router"; Vue.use(Router); const router = new Router({ mode: "history", routes: [ { path: "/", component: Index, meta: { title: "推薦產(chǎn)品得豐厚獎(jiǎng)金" } }, { path: "/news", component: News, meta: { title: "公告列表" }, children: [ { path: "", redirect: "list" }, { path: "list", component: NewsList }, { path: "detail/:newsId", component: NewsDetail, meta: { title: "公告詳情" } } ] }, { path: "/guide", component: GuideProtocol, meta: { title: "新手指南" } } ] }); // 使用 afterEach 鉤子函數(shù),保證路由已經(jīng)跳轉(zhuǎn)成功之后修改 title router.afterEach((route) => { let documentTitle = "xxx商城會(huì)員平臺(tái)"; route.matched.forEach((path) => { if (path.meta.title) { documentTitle += ` - ${path.meta.title}`; } }); document.title = documentTitle; });根據(jù) URL 的變化,動(dòng)態(tài)更新數(shù)據(jù)
通常在一個(gè)列表集合頁(yè),我們需要做分頁(yè)操作,同時(shí)分頁(yè)數(shù)據(jù)需要體現(xiàn)在 URL 中,那么如何動(dòng)態(tài)的根據(jù) URL 的變動(dòng)來(lái)動(dòng)態(tài)的獲取數(shù)據(jù)呢,我們可以使用 watch API,在 watch 里面監(jiān)聽 $route,同時(shí)使用 this.$router.replace API 來(lái)改變 URL 的值。下面是示例代碼 common.js:
import qs from "qs"; export default { data() { return { queryParams: { currentPage: 1, pageSize: 10 } }; }, methods: { handlePageNoChange(e) { this.queryParams.currentPage = e; this.replaceRouter(); }, replaceRouter() { const query = qs.stringify(this.queryParams); this.$router.replace(`${location.pathname}?${query}`); }, routeChange() { this.assignParams(); this.fetchData(); }, assignParams() { this.queryParams = Object.assign({}, this.queryParams, this.$route.query); } }, mounted() { this.assignParams(); this.fetchData(); }, watch: { $route: "routeChange" } };
我們將這部分代碼抽取到一個(gè)公共的 mixin 中,在需要的組件那里引入它,同時(shí)實(shí)現(xiàn)自定義的同名 fetchData() 方法
mixin API 文檔:https://cn.vuejs.org/v2/guide...
export default DemoComponent { mixins: [common], data() { return { // 組件內(nèi)部自定義同名查詢參數(shù),將會(huì)和 mixin 中的默認(rèn)參數(shù)合并 queryParams: { categoryId: "", pageSize: 12 }, } }, methods: { fetchData() { // 發(fā)送請(qǐng)求 } } }Event Bus 使用場(chǎng)景
我們?cè)陧?xiàng)目中引入了 vuex ,通常情況下是不需要使用 event bus 的,但是有一種情況下我們需要使用它,那就是在路由鉤子函數(shù)內(nèi)部的時(shí),在項(xiàng)目中,我們需要在 beforeEnter 路由鉤子里面對(duì)外拋出事件,在這個(gè)鉤子函數(shù)中我們無(wú)法去到 this 對(duì)象。
beforeEnter: (to, from, next) => { const userInfo = localStorage.getItem(userFlag); if (isPrivateMode()) { EventBus.$emit("get-localdata-error"); next(false); return; } })
在 App.vue 的 mouted 方法中監(jiān)聽這個(gè)事件
EventBus.$on("get-localdata-error", () => { this.$alert("請(qǐng)勿使用無(wú)痕模式瀏覽"); });自定義指令實(shí)現(xiàn)埋點(diǎn)數(shù)據(jù)統(tǒng)計(jì)
在項(xiàng)目中通常需要做數(shù)據(jù)埋點(diǎn),這個(gè)時(shí)候,使用自定義指令將會(huì)變非常簡(jiǎn)單
在項(xiàng)目入口文件 main.js 中配置我們的自定義指令
// 坑位埋點(diǎn)指令 Vue.directive("stat", { bind(el, binding) { el.addEventListener("click", () => { const data = binding.value; let prefix = "store"; if (OS.isAndroid || OS.isPhone) { prefix = "mall"; } analytics.request({ ty: `${prefix}_${data.type}`, dc: data.desc || "" }, "n"); }, false); } });使用路由攔截統(tǒng)計(jì)頁(yè)面級(jí)別的 PV
由于第一次在單頁(yè)應(yīng)用中嘗試數(shù)據(jù)埋點(diǎn),在項(xiàng)目上線一個(gè)星期之后,數(shù)據(jù)統(tǒng)計(jì)后臺(tái)發(fā)現(xiàn),首頁(yè)的 PV 遠(yuǎn)遠(yuǎn)高于其它頁(yè)面,數(shù)據(jù)很不正常。后來(lái)跟數(shù)據(jù)后臺(tái)的人溝通詢問(wèn)他們的埋點(diǎn)統(tǒng)計(jì)原理之后,才發(fā)現(xiàn)其中的問(wèn)題所在。
傳統(tǒng)應(yīng)用,一般都在頁(yè)面加載的時(shí)候,會(huì)有一個(gè)異步的 js 加載,就像百度的統(tǒng)計(jì)代碼類似,所以我們每個(gè)頁(yè)面的加載的時(shí)候,都會(huì)統(tǒng)計(jì)到數(shù)據(jù);然而在單頁(yè)應(yīng)用,頁(yè)面加載初始化只有一次,所以其它頁(yè)面的統(tǒng)計(jì)數(shù)據(jù)需要我們自己手動(dòng)上報(bào)
解決方案
使用 vue-router 的 beforeEach 或者 afterEach 鉤子上報(bào)數(shù)據(jù),具體使用哪個(gè)最好是根據(jù)業(yè)務(wù)邏輯來(lái)選擇。
const analyticsRequest = (to, from) => { // 只統(tǒng)計(jì)頁(yè)面跳轉(zhuǎn)數(shù)據(jù),不統(tǒng)計(jì)當(dāng)前頁(yè) query 不同的數(shù)據(jù) // 所以這里只使用了 path, 如果需要統(tǒng)計(jì) query 的,可以使用 to.fullPath if (to.path !== from.path) { analytics.request({ url: `${location.protocol}//${location.host}${to.path}` }); } }; router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { // 這里做登錄等前置邏輯判斷 // 判斷通過(guò)之后,再上報(bào)數(shù)據(jù) ... analyticsRequest(to, from); } else { // 不需要判斷的,直接上報(bào)數(shù)據(jù) analyticsRequest(to, from); next(); } });
在組件中使用我們的自定義指令
使用過(guò)濾器實(shí)現(xiàn)展示信息格式化如下圖中獎(jiǎng)金數(shù)據(jù)信息,我們需要將后臺(tái)返回的獎(jiǎng)金格式化為帶兩位小數(shù)點(diǎn)的格式,同時(shí),如果返回的金額是區(qū)間類型,需要額外加上 起 字和 ¥ 金額符號(hào)
在入口文件 main.js 中配置我們自定義的過(guò)濾器
Vue.filter("money", (value, config = { unit: "¥", fixed: 2 }) => { const moneyStr = `${value}`; if (moneyStr.indexOf("-") > -1) { const scope = moneyStr.split("-"); return `${config.unit}${parseFloat(scope[0]).toFixed(config.fixed).toString()} 起`; } else if (value === 0) { return value; } return `${config.unit}${parseFloat(moneyStr).toFixed(config.fixed).toString()}`; });
在組件中使用:
axios 使用配置{{detail.priceScope | money}}
比率:{{detail.commissionRateScope}}%
獎(jiǎng)金:{{detail.expectedIncome | money}}
在項(xiàng)目中,我們使用了 axios 做接口請(qǐng)求
在項(xiàng)目中全局配置 /api/common.js
import axios from "axios"; import qs from "qs"; import store from "../store"; // 全局默認(rèn)配置 // 設(shè)置 POST 請(qǐng)求頭 axios.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded"; // 配置 CORS 跨域 axios.defaults.withCredentials = true; axios.defaults.crossDomain = true; // 請(qǐng)求發(fā)起前攔截器 axios.interceptors.request.use((config) => { // 全局 loading 狀態(tài),觸發(fā) loading 效果 store.dispatch("updateLoadingStatus", { isLoading: true }); // POST 請(qǐng)求參數(shù)處理成 axios post 方法所需的格式 if (config.method === "post") { config.data = qs.stringify(config.data); } // 這句不能省,不然后面的請(qǐng)求就無(wú)法成功發(fā)起,因?yàn)樽x不到配置參數(shù) return config; }, () => { // 異常處理 store.dispatch("updateLoadingStatus", { isLoading: false }); }); // 響應(yīng)攔截 axios.interceptors.response.use((response) => { // 關(guān)閉 loading 效果 store.dispatch("updateLoadingStatus", { isLoading: false }); // 全局登錄過(guò)濾,如果沒有登錄,直接跳轉(zhuǎn)到登錄 URL if (response.data.code === 300) { // 未登錄 window.location.href = getLoginUrl(); return false; } // 這里返回的 response.data 是被 axios 包裝過(guò)的一成,所以在這里抽取出來(lái) return response.data; }, (error) => { store.dispatch("updateLoadingStatus", { isLoading: false }); return Promise.reject(error); }); // 導(dǎo)出 export default axios;
然后我們?cè)诮涌谥惺褂镁头奖愫芏嗔?/api/xxx.js
import axios from "./common"; const baseURL = "/api/profile"; const USER_BASE_INFO = `${baseURL}/getUserBaseInfo.json`; const UPDATE_USER_INFO = `${baseURL}/saveUserInfo.json`; // 更新用戶實(shí)名認(rèn)證信息 const updateUserInfo = userinfo => axios.post(UPDATE_USER_INFO, userinfo); // 獲取用戶基礎(chǔ)信息 const getUserBaseInfo = () => axios.get(USER_BASE_INFO);vuex 狀態(tài)在響應(yīng)式頁(yè)面中的妙用
由于項(xiàng)目是響應(yīng)式頁(yè)面,PC 端和移動(dòng)端在表現(xiàn)成有很多不一致的地方,有時(shí)候單單通過(guò) CSS 無(wú)法實(shí)現(xiàn)交互,這個(gè)時(shí)候,我們的 vuex 狀態(tài)就派上用場(chǎng)了,
我們一開始在 App.vue 里面監(jiān)聽了頁(yè)面的 resize 事件,動(dòng)態(tài)的更新 vuex 里面 isMobile 的狀態(tài)值
window.onresize = throttle(() => { this.updatePlatformStatus({ isMobile: isMobile() }); }, 500);
然后,我們?cè)诮M件層,就能響應(yīng)式的渲染不同的 dom 結(jié)構(gòu)了。其中最常見的是 PC 端和移動(dòng)端加載的圖片需要不同的規(guī)格的,這個(gè)時(shí)候我們可以這個(gè)做
methods: { loadImgAssets(name, suffix = ".jpg") { return require(`../assets/images/${name}${this.isMobile ? "-mobile" : ""}${suffix}`); }, } // 動(dòng)態(tài)渲染不同規(guī)格的 dislog
下圖分別是 PC 端和移動(dòng)短的表現(xiàn)形式,然后配合 CSS 媒體查詢實(shí)現(xiàn)各種布局
開發(fā)相關(guān)配置 反向代理在項(xiàng)目目錄的 config 文件下面的 index.js 配置我們的本地反向代理和端口信息
dev: { env: require("./dev.env"), port: 80, autoOpenBrowser: true, assetsSubDirectory: "static", assetsPublicPath: "/", proxyTable: { "/api/profile": { target: "[真實(shí)接口地址]:[端口號(hào)]", // 例如: http://api.xxx.com changeOrigin: true, pathRewrite: { "^/api/profile": "/profile" } } ... },
然后我們調(diào)用接口的形式就會(huì)變成如下映射,當(dāng)我們調(diào)用 /api/profile/xxxx 的時(shí)候,其實(shí)是調(diào)用了 [真實(shí)接口地址]/profile/xxxx
/api/profile/xxxx => [真實(shí)接口地址]/profile/xxxx
nginx 配置
upstream api.xxx.com { #ip_hash; server [接口服務(wù)器 ip 地址]:[端口]; } server { ... location ^~ /api/profile { index index.php index.html index.html; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://api.xxx.com; rewrite ^/api/profile/(.*)$ /profile/$1 break; } ... }線上部署
如果路由使用的是 history 模式的話,需要在 nginx 里面配置將所有的請(qǐng)求到轉(zhuǎn)發(fā)到 index.html 去
在 nginx.conf 或者對(duì)應(yīng)的站點(diǎn) vhost 文件下面配置
location / { try_files $uri $uri/ /index.html; }優(yōu)化
開啟靜態(tài)資源長(zhǎng)緩存
location ~ .*.(gif|jpg|jpeg|png|bmp|swf|woff|ttf|eot|svg)$ { expires 1y; } location ~ .*.(js|css)$ { expires 1y; }
開啟靜態(tài)資源 gzip 壓縮
// 找到 nginx.conf 配置文件 vim /data/nginx/conf/nginx.conf gzip on; gzip_min_length 1k; gzip_buffers 4 8k; gzip_http_version 1.1; gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;
開啟了 gzip 壓縮之后,頁(yè)面資源請(qǐng)求大小將大大減小,如下圖所示,表示已經(jīng)開啟了 gzip 壓縮
Q&A文章到這就結(jié)束了,如果有遺漏或者錯(cuò)誤的地方,歡迎私信指出。
希望這篇文章能帶給大家一絲絲收獲。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/88422.html
摘要:項(xiàng)目架構(gòu)項(xiàng)目目錄項(xiàng)目目錄是采用自動(dòng)生成,其它按需自己新建就好了。 項(xiàng)目架構(gòu) 項(xiàng)目目錄 ├── build ├── config ├── dist │?? └── static │?? ├── css │?? ├── fonts │?? ├── images │?? ├── js │?? └── lib ├── src │?? ├── api │?...
摘要:項(xiàng)目架構(gòu)項(xiàng)目目錄項(xiàng)目目錄是采用自動(dòng)生成,其它按需自己新建就好了。 項(xiàng)目架構(gòu) 項(xiàng)目目錄 ├── build ├── config ├── dist │?? └── static │?? ├── css │?? ├── fonts │?? ├── images │?? ├── js │?? └── lib ├── src │?? ├── api │?...
摘要:年底,公司項(xiàng)目番茄表單的前端部分,開始了從傳統(tǒng)的到的徹底重構(gòu)。上傳流程圖不重要看文字事件觸發(fā)后,先去如果是圖片,可以同時(shí)通過(guò)以及將圖片預(yù)覽在頁(yè)面上后臺(tái)請(qǐng)求七牛的上傳,將拿到的和以及通過(guò)傳遞過(guò)來(lái)的一起到中。 關(guān)于上傳,總是有很多可以說(shuō)道的。16年底,公司項(xiàng)目番茄表單的前端部分,開始了從傳統(tǒng)的jquery到vue 2.0的徹底重構(gòu)。但是上傳部分,無(wú)論是之前的傳統(tǒng)版本,還是Vue新版本,都是...
摘要:原文來(lái)自集前端最近很火的框架資源定時(shí)更新,歡迎一下。推送自己整理近期三波關(guān)于的資訊這里就拋磚引玉了,望有更屌的資源送助攻。 原文來(lái)自:集web前端最近很火的vue2框架資源;定時(shí)更新,歡迎Star一下。 推送自己整理近期三波關(guān)于Vue.js的資訊; 這里就拋磚引玉了,望有更屌的資源送助攻。 showImg(https://segmentfault.com/img/bVVeiZ); 第...
閱讀 1840·2021-11-23 09:51
閱讀 1293·2021-11-18 10:02
閱讀 970·2021-10-25 09:44
閱讀 2108·2019-08-26 18:36
閱讀 1629·2019-08-26 12:17
閱讀 1153·2019-08-26 11:59
閱讀 2751·2019-08-23 15:56
閱讀 3362·2019-08-23 15:05