初衷
現(xiàn)如今社區(qū)上基于Vue
的項目已經(jīng)多如牛毛了,為了提升自己對vue
的進(jìn)一步理解玩裙,一直想找一個界面好看锰扶,功能完成的項目練練手诀艰。本人在逛各大招聘網(wǎng)站的時候發(fā)現(xiàn)了字節(jié)跳動
的官方招聘網(wǎng)站https://job.bytedance.com/society
很適合自己練手呻率。我在看到這個網(wǎng)站后翻了翻其源代碼發(fā)現(xiàn)這個項目并不是由當(dāng)下比較流行的框架Vue
實現(xiàn)的京闰,思考過后那我能不能用Vue
重新打造一個復(fù)刻版的網(wǎng)站呢?
此項目僅參考了原版網(wǎng)站的界面設(shè)計和功能特點甩苛,功能實現(xiàn)方式和代碼設(shè)計均是由本人獨立構(gòu)思開發(fā)完成讯蒲,預(yù)覽點這里
數(shù)據(jù)從哪來?
一個完成的上線項目離不開完整的數(shù)據(jù)墨林,那么我要做的這個項目真實數(shù)據(jù)要怎么才能拿到呢?于是自己又默默的打開了原版網(wǎng)站的開發(fā)者工具旭等,在Network
面板里發(fā)現(xiàn)了瀏覽器請求到的官方API
接口。找到了API
接口就省心讀了隙袁,問題是像字節(jié)跳動這樣的公司對服務(wù)端API請求肯定會做跨域訪問權(quán)限的限制弃榨,就算某一個接口能成功的請求到數(shù)據(jù),對于一個想要長期作為自己項目訪問的接口使用來說也是不穩(wěn)定的鲸睛。于是我又想到了接口代理的實現(xiàn)方案腊凶,大概的實現(xiàn)思路是使用express
搭建一個自己的服務(wù)器拴念,包括項目上線后靜態(tài)資源的托管都會用到褐缠。
服務(wù)端接口代理的小技巧
對于在瀏覽器端抓到的API
數(shù)據(jù)接口,純粹的分析其地址和各種各樣的參數(shù)無疑是很麻煩的一件事情公般,有沒有一種辦法可以一鍵復(fù)用它呢?答案是肯定的!由于node端沒有原生的fetch
請求方法胡桨,這里需要借助一個第三方的node模塊node-fetch
,這個是可以直接用npm安裝的類似于瀏覽器端原生的fetch
請求模塊昧谊。有了它我們在使用瀏覽器Network
面板里面的接口一鍵復(fù)制功能呢诬,具體操作請看下面的演示,詳細(xì)的API代碼案例請點擊這里
此功能只有高版本的chrome 瀏覽器有此功能阀圾,如果您的瀏覽器沒有此復(fù)制選項狗唉,請您升級到最新的瀏覽器后在使用
項目技術(shù)架構(gòu)
為了進(jìn)一步的提高自己的技術(shù)水平和更好的加深對Vue的理解,我選擇了零代碼開發(fā)所有的頁面功能(沒有使用任何第三方UI庫)分俯。
- 項目前端技術(shù)棧
- Vue 主框架
- vue-router 路由跳轉(zhuǎn)的官方插件
- lodash 一個javascript的函數(shù)工具庫
- axios 負(fù)責(zé)HTTP請求的插件
- 服務(wù)端技術(shù)棧
- express 搭建
web
服務(wù)器的nodejs
框架 - node-fetch 類似于瀏覽器端的fetch請求的polyfill
- connect-history-api-fallback 解決單頁面應(yīng)用程序在
history
模式下訪問服務(wù)端出現(xiàn)404
的中間件
- express 搭建
- 項目開發(fā)工具
- vue-cli 快速搭建
Vue
工程的官方腳手架 - less
css
預(yù)處理器 - vscode 輕量的代碼編輯器
- postman 測試調(diào)試
API
接口的工具 - vue-devtools
Vue
項目官方調(diào)試工具 - chrome 應(yīng)用運行/調(diào)試環(huán)境
- git 開源版本控制系統(tǒng)
- vue-cli 快速搭建
- 部署環(huán)境
- 阿里云服務(wù)器線上地址 http://123.56.124.33:3000
- git遠(yuǎn)程倉庫地址
git@github.com:konglingwen94/vue-bytedanceJob.git
- github 代碼托管倉庫https://github.com/konglingwen94/vue-bytedanceJob
項目源碼目錄
項目重要功能剖析
分頁器組件 components/pagination.vue 查看源代碼點這里
本人在開發(fā)這個分頁器組件之前也是參考了多個網(wǎng)站的分頁功能局齿,各種各樣的分頁功能各不相同橄登,挑選之后最終確定了自己比較認(rèn)可的一個,此組件實現(xiàn)的功能如下圖所示
從上圖可以看出這是一個功能完成的分頁器組件谣妻,基礎(chǔ)性的代碼這里就不過多的介紹了卒稳,具體實現(xiàn)點擊這里充坑。下面就主要分析一下在開發(fā)過程中遇到的難點减江!
當(dāng)鼠標(biāo)點擊分頁的數(shù)字按鈕時辈灼,整個分頁條會做相應(yīng)的動態(tài)切換。當(dāng)總頁數(shù)超過一定的數(shù)值后司志,或者頁面切換到某一個范圍時會出現(xiàn)相應(yīng)的省略號代替隱藏的頁碼數(shù)字展示降宅。那這個功能邏輯應(yīng)該怎么實現(xiàn)呢?
良好的思路是寫出優(yōu)雅代碼的第一步,我把分頁可能出現(xiàn)的狀態(tài)分為四種情況
- 第一個省略號出現(xiàn)在最大頁數(shù)前面時
- 分頁條出現(xiàn)兩個省略號時
- 省略號出現(xiàn)在最小頁數(shù)后面時
- 分頁器總頁碼小于默認(rèn)展示的頁碼個數(shù)(分頁條沒有出現(xiàn)省略號)
根據(jù)以上列出的幾種情況就可以使用代碼去實現(xiàn)了激才,這里我使用Vue
的一個計算屬性visiblePagers
進(jìn)行了動態(tài)展示所有出現(xiàn)的頁碼唠雕,組件完整的代碼如下
<template>
<div class="pagination">
<ul class="pagination-list">
<li
title="上一頁"
@click="$emit('update:currentPage',Math.max(1,currentPage-1))"
class="pagination-item"
>
<span><</span>
</li>
<li
class="pagination-item"
:class="{current:currentPage===item}"
v-for="(item,index) in visiblePages"
@click="change(item)"
:key="index"
>
<span>{{item}}</span>
</li>
<li
title="下一頁"
@click="$emit('update:currentPage',Math.min(totalPage,currentPage+1))"
class="pagination-item"
>
<span>></span>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "Pagination",
props: {
total: Number,
perPage: {
type: Number,
default: 10
},
currentPage: {
type: Number,
default: 1
},
pagerCount: {
type: Number,
default: 9
}
},
computed: {
totalPage() {
return Math.ceil(parseInt(this.total) / this.perPage);
},
visiblePages() {
let pages = [];
const currentPage = Math.max(
1,
Math.min(this.currentPage, this.totalPage)
);
if (this.totalPage <= this.pagerCount) {
for (let i = 1; i <= this.totalPage; i++) {
pages.push(i);
}
return pages;
}
if (currentPage >= this.totalPage - 3) {
pages.push(1, "...");
const minPage = Math.min(currentPage - 2, this.totalPage - 4);
for (let i = minPage, len = this.totalPage; i <= len; i++) {
pages.push(i);
}
} else if (currentPage <= 4) {
const maxPage = Math.min(Math.max(currentPage + 2, 5), this.totalPage);
for (let i = 1; i <= maxPage; i++) {
pages.push(i);
}
pages.push("...", this.totalPage);
} else {
pages.push(1, "...");
for (let i = currentPage - 2; i <= currentPage + 2; i++) {
pages.push(i);
}
pages.push("...", this.totalPage);
}
return pages;
}
},
methods: {
change(num) {
if (typeof num !== "number") {
return;
}
this.$emit("current-change", num);
this.$emit("update:currentPage", num);
}
}
};
</script>
<style lang="less" scoped>
.pagination-list {
display: flex;
}
.pagination-item {
margin-right: 4px;
cursor: pointer;
padding: 8px;
&:hover {
color: @main-color;
}
&.current {
color: @main-color;
}
}
</style>
復(fù)選穿梭框組件 components/checkbox-transfer.vue 源代碼地址
效果圖
實現(xiàn)過程
對于這個組件功能的開發(fā),我真的是煞費苦心捕儒,一言難盡邓夕。首先市面上沒有一樣的功能需求用例可供參考,其次在開發(fā)的過程中組件各種邏輯的實現(xiàn)也是在摸索著進(jìn)行實現(xiàn)点弯。在花費了一定時間仍沒有較好的思路后矿咕,我默默的打開了世界上最大的程序員同性交友平臺 github一番搜索,最終在開源項目基于Vue的組件庫element-ui
中的transfer
組件中找到了可參考實現(xiàn)的邏輯實現(xiàn)方式捡絮。
重點邏輯分析
template
部分代碼
<template>
<div class="checkbox">
<h2>{{title}}</h2>
<ul class="checkbox-list">
<li class="checkbox-item" v-for="(item, index) in targetData" :key="index">
<input
@change="check(item, $event)"
type="checkbox"
:id="item[props.key]"
:checked="checked[index]"
/>
<label :for="item[props.key]" class="label-text">{{ item[props.label] }}</label>
</li>
</ul>
<div class="search" v-if="sourceData.length">
<input
@blur="onInputBlur"
@focus="focusing = true"
class="search-input"
:class="{focusing}"
:placeholder="placeholder"
type="text"
v-model="filterKeyword"
/>
<ul class="search-list" v-show="focusing">
<li
v-for="item in filterableData"
:key="item[props.key]"
class="search-item"
@click="addToTarget(item)"
>
<span>{{ item[props.label] }}</span>
</li>
</ul>
</div>
</div>
</template>
對于這樣一個交互復(fù)雜的復(fù)選穿梭框而言福稳,定義好其基本的初始化數(shù)據(jù)狀態(tài)是第一步要做的瑞侮。對于此組件調(diào)用的時候鼓拧,模板會傳入一個屬性名為data
(這里是一個數(shù)組)的props
選項作為組件的數(shù)據(jù)來源毁枯,接下來要關(guān)注的焦點就轉(zhuǎn)移到了組件內(nèi)部數(shù)據(jù)交互的各種實現(xiàn)方式上了叮称。我首先在組件狀態(tài)data
里面定義了一個名叫targets
的數(shù)組類型的變量用來存儲默認(rèn)展開的復(fù)選框列表項key
值,然后根據(jù)data
和targets
這兩個基礎(chǔ)數(shù)據(jù)狀態(tài)又可以派生出兩個計算屬性sourceData
和targetData
用來分別渲染展開和隱藏起來的復(fù)選框選擇項赂韵。至此組件基本的靜態(tài)模板渲染的邏輯就構(gòu)思完成了挠蛉。相關(guān)部分代碼如下
組件script
部分
<script>
export default {
name: "checkbox-transfer",
data() {
return {
focusing: false,
filterKeyword: "",
targets: []
};
},
props: {
data: {
type: Array,
default: () => []
},
},
computed: {
targetData() {
return this.targets
.map(key => {
return this.data.find(item => item[this.props.key] === key);
})
.filter(item => item && item[this.props.key]);
},
sourceData() {
return this.data.filter(
item => this.targets.indexOf(item[this.props.key]) === -1
);
},
},
另外此組件也擁有Vue
組件代表性的雙向數(shù)據(jù)綁定的特點谴古,使用v-model
的指令可以默認(rèn)指定復(fù)選框選中的選項,有關(guān)這一塊的邏輯實現(xiàn)在這里就不在贅述了汇陆,相關(guān)邏輯代碼看下面
組件script
部分
<script>
export default {
name: "checkbox-transfer",
props: {
value: {
type: Array,
default: () => []
}
},
computed: {
checked() {
return this.targets.map(key => this.value.includes(key));
},
},
methods:{
check(item, e) {
if (!e.target.checked) {
const delIndex = this.value.indexOf(item[this.props.key]);
if (delIndex > -1) {
this.value.splice(delIndex, 1);
}
} else {
if (!this.value.includes(item[this.props.key])) {
this.value.push(item[this.props.key]);
}
}
this.$emit("check", e.target.checked, item[this.props.key]);
this.$emit("input", this.value);
}
}
}
首頁頭部導(dǎo)航欄交互顯示功能
查看源碼點這里
效果
功能介紹
- 頂部的導(dǎo)航欄在頁面向下滾動的過程中有一個吸附頂部的功能
-
banner
視頻部分滾動出頁面可視區(qū)域后導(dǎo)航欄更換主題顏色 - 導(dǎo)航欄根據(jù)頁面滾動方向的不同隱藏和顯示毡代。
實現(xiàn)思路
導(dǎo)航欄功能的前兩個實現(xiàn)起來比較簡單勺疼,這里就不多介紹了执庐。下面主要分析一下功能中的第三點。
根據(jù)頁面滾動的方向判斷導(dǎo)航欄顯示狀態(tài)要怎么實現(xiàn)呢轨淌?單純的做一個顯示狀態(tài)的切換對于熟練使用Vue
的同學(xué)來說再簡單不過了猿诸,這里的坑就在于如何判斷頁面在滾動過程中的滾動方向呢?說道這里梳虽,有人一定能想到可以使用瀏覽器原生API
監(jiān)聽元素滾動的事件,在事件scroll
的回調(diào)函數(shù)中進(jìn)一步處理邏輯判斷谷炸。能想到這一點對于我們要實現(xiàn)的最終目標(biāo)有邁進(jìn)了一大步旬陡,那么瀏覽器HTML
元素的scroll
事件能提供給我們使用的回調(diào)參數(shù)是有限的,就是說這個事件對象沒有直接提供此次滾動的方向信息驶睦。所以這個問題就需要我們手動去解決了匿醒。
手動封裝監(jiān)聽元素滾動的函數(shù)
為了判斷出元素此次滾動事件相對于上次滾動時的方向,我們需要記錄上一次的滾動事件信息并存儲起來溉痢,然后通過比對兩次滾動事件的坐標(biāo)值判斷出此次頁面滾動的方向(這里只做滾動向下或者向上的判斷)憋他。為了讓這一個判斷元素滾動方向的邏輯有更好的復(fù)用性,我把它單獨抽離成了一個工具函數(shù)镀娶,當(dāng)我們需要用到這個邏輯時就可以直接拿來復(fù)用此迅,具體實現(xiàn)的代碼如下
helper/untilities.js
export const watchScrollDirection = function(scrollElement, callback) {
const scrollPos = { x: 0, y: 0 };
const scrollDirection = {
directionX: 1,
directionY: 1,
};
function onScroll(e) {
const scrollTop = scrollElement.scrollTop || scrollElement.pageYOffset;
const scrollLeft = scrollElement.scrollLeft || scrollElement.pageXOffset;
if (scrollPos.y > scrollTop) {
scrollDirection.directionY = -1;
} else {
scrollDirection.directionY = 1;
}
if (scrollPos.x > scrollLeft) {
scrollDirection.directionX = -1;
} else {
scrollDirection.directionX = 1;
}
callback.call(scrollElement, scrollDirection,scrollPos);
scrollPos.x = scrollLeft;
scrollPos.y = scrollTop;
}
scrollElement.addEventListener("scroll", onScroll);
return function() {
scrollElement.removeEventListener("scroll", onScroll);
};
};
頁面代碼實現(xiàn)如下
views/home.vue
組件script
部分
import { watchScrollDirection } from "@/helper/utilities.js";
export default {
mounted() {
const rootVm = this.$root;
rootVm.$emit(
"home-scrolling",
{ directionX: 1, directionY: -1 },
{ x: document.body.scrollLeft, y: document.body.scrollTop }
);
this.unwatch = watchScrollDirection(window, function(...args) {
rootVm.$emit("home-scrolling", ...args);
});
},
destroyed() {
this.unwatch();
}
}
應(yīng)用截圖
首頁
職位
產(chǎn)品與服務(wù)
職位詳情
員工故事
支持
閱讀完本篇文章如果對您有幫助鲁猩,請您點贊,謝謝搅窿!
如果想討論項目有關(guān)問題或者參與共建隙券,歡迎留言!
對本項目如果您有更好的建議或發(fā)現(xiàn)bug
,歡迎提 issue
應(yīng)用線上地址: http://123.56.124.33:3000/
項目倉庫地址: https://github.com/konglingwen94/vue-bytedanceJob,歡迎star
和follow
,謝謝沐飘!
本篇文章屬于個人原創(chuàng),轉(zhuǎn)載借鑒請注明出處借卧,謝謝筛峭!