初衷
現(xiàn)如今社區(qū)上基于Vue的項(xiàng)目已經(jīng)多如牛毛了,為了提升自己對(duì)vue的進(jìn)一步理解,一直想找一個(gè)界面好看,功能完成的項(xiàng)目練練手。本人在逛各大招聘網(wǎng)站的時(shí)候發(fā)現(xiàn)了字節(jié)跳動(dòng)的官方招聘網(wǎng)站https://job.bytedance.com/society很適合自己練手。我在看到這個(gè)網(wǎng)站后翻了翻其源代碼發(fā)現(xiàn)這個(gè)項(xiàng)目并不是由當(dāng)下比較流行的框架Vue實(shí)現(xiàn)的,思考過(guò)后那我能不能用Vue重新打造一個(gè)復(fù)刻版的網(wǎng)站呢?
此項(xiàng)目?jī)H參考了原版網(wǎng)站的界面設(shè)計(jì)和功能特點(diǎn),功能實(shí)現(xiàn)方式和代碼設(shè)計(jì)均是由本人獨(dú)立構(gòu)思開發(fā)完成,預(yù)覽點(diǎn)這里
數(shù)據(jù)從哪來(lái)?
一個(gè)完成的上線項(xiàng)目離不開完整的數(shù)據(jù),那么我要做的這個(gè)項(xiàng)目真實(shí)數(shù)據(jù)要怎么才能拿到呢?于是自己又默默的打開了原版網(wǎng)站的開發(fā)者工具,在Network面板里發(fā)現(xiàn)了瀏覽器請(qǐng)求到的官方API接口。找到了API接口就省心讀了,問(wèn)題是像字節(jié)跳動(dòng)這樣的公司對(duì)服務(wù)端API請(qǐng)求肯定會(huì)做跨域訪問(wèn)權(quán)限的限制,就算某一個(gè)接口能成功的請(qǐng)求到數(shù)據(jù),對(duì)于一個(gè)想要長(zhǎng)期作為自己項(xiàng)目訪問(wèn)的接口使用來(lái)說(shuō)也是不穩(wěn)定的。于是我又想到了接口代理的實(shí)現(xiàn)方案,大概的實(shí)現(xiàn)思路是使用express搭建一個(gè)自己的服務(wù)器,包括項(xiàng)目上線后靜態(tài)資源的托管都會(huì)用到。
服務(wù)端接口代理的小技巧
對(duì)于在瀏覽器端抓到的API數(shù)據(jù)接口,純粹的分析其地址和各種各樣的參數(shù)無(wú)疑是很麻煩的一件事情,有沒(méi)有一種辦法可以一鍵復(fù)用它呢?答案是肯定的!由于node端沒(méi)有原生的fetch請(qǐng)求方法,這里需要借助一個(gè)第三方的node模塊node-fetch,這個(gè)是可以直接用npm安裝的類似于瀏覽器端原生的fetch請(qǐng)求模塊。有了它我們?cè)谑褂脼g覽器Network面板里面的接口一鍵復(fù)制功能,具體操作請(qǐng)看下面的演示,詳細(xì)的API代碼案例請(qǐng)點(diǎn)擊這里
此功能只有高版本的chrome 瀏覽器有此功能,如果您的瀏覽器沒(méi)有此復(fù)制選項(xiàng),請(qǐng)您升級(jí)到最新的瀏覽器后在使用
項(xiàng)目技術(shù)架構(gòu)
為了進(jìn)一步的提高自己的技術(shù)水平和更好的加深對(duì)Vue的理解,我選擇了零代碼開發(fā)所有的頁(yè)面功能(沒(méi)有使用任何第三方UI庫(kù))。
- 項(xiàng)目前端技術(shù)棧
- Vue 主框架
- vue-router 路由跳轉(zhuǎn)的官方插件
- lodash 一個(gè)javascript的函數(shù)工具庫(kù)
- axios 負(fù)責(zé)HTTP請(qǐng)求的插件
- 服務(wù)端技術(shù)棧
- express 搭建
web服務(wù)器的nodejs框架 - node-fetch 類似于瀏覽器端的fetch請(qǐng)求的polyfill
- connect-history-api-fallback 解決單頁(yè)面應(yīng)用程序在
history模式下訪問(wèn)服務(wù)端出現(xiàn)404的中間件
- express 搭建
- 項(xiàng)目開發(fā)工具
- vue-cli 快速搭建
Vue工程的官方腳手架 - less
css預(yù)處理器 - vscode 輕量的代碼編輯器
- postman 測(cè)試調(diào)試
API接口的工具 - vue-devtools
Vue項(xiàng)目官方調(diào)試工具 - chrome 應(yīng)用運(yùn)行/調(diào)試環(huán)境
- git 開源版本控制系統(tǒng)
- vue-cli 快速搭建
- 部署環(huán)境
- 阿里云服務(wù)器線上地址 http://123.56.124.33:3000
- git遠(yuǎn)程倉(cāng)庫(kù)地址
git@github.com:konglingwen94/vue-bytedanceJob.git - github 代碼托管倉(cāng)庫(kù)https://github.com/konglingwen94/vue-bytedanceJob
項(xiàng)目源碼目錄
項(xiàng)目重要功能剖析
分頁(yè)器組件 components/pagination.vue 查看源代碼點(diǎn)這里
本人在開發(fā)這個(gè)分頁(yè)器組件之前也是參考了多個(gè)網(wǎng)站的分頁(yè)功能,各種各樣的分頁(yè)功能各不相同,挑選之后最終確定了自己比較認(rèn)可的一個(gè),此組件實(shí)現(xiàn)的功能如下圖所示
從上圖可以看出這是一個(gè)功能完成的分頁(yè)器組件,基礎(chǔ)性的代碼這里就不過(guò)多的介紹了,具體實(shí)現(xiàn)點(diǎn)擊這里。下面就主要分析一下在開發(fā)過(guò)程中遇到的難點(diǎn)!
當(dāng)鼠標(biāo)點(diǎn)擊分頁(yè)的數(shù)字按鈕時(shí),整個(gè)分頁(yè)條會(huì)做相應(yīng)的動(dòng)態(tài)切換。當(dāng)總頁(yè)數(shù)超過(guò)一定的數(shù)值后,或者頁(yè)面切換到某一個(gè)范圍時(shí)會(huì)出現(xiàn)相應(yīng)的省略號(hào)代替隱藏的頁(yè)碼數(shù)字展示。那這個(gè)功能邏輯應(yīng)該怎么實(shí)現(xiàn)呢?
良好的思路是寫出優(yōu)雅代碼的第一步,我把分頁(yè)可能出現(xiàn)的狀態(tài)分為四種情況
- 第一個(gè)省略號(hào)出現(xiàn)在最大頁(yè)數(shù)前面時(shí)
- 分頁(yè)條出現(xiàn)兩個(gè)省略號(hào)時(shí)
- 省略號(hào)出現(xiàn)在最小頁(yè)數(shù)后面時(shí)
- 分頁(yè)器總頁(yè)碼小于默認(rèn)展示的頁(yè)碼個(gè)數(shù)(分頁(yè)條沒(méi)有出現(xiàn)省略號(hào))
根據(jù)以上列出的幾種情況就可以使用代碼去實(shí)現(xiàn)了,這里我使用Vue 的一個(gè)計(jì)算屬性visiblePagers進(jìn)行了動(dòng)態(tài)展示所有出現(xiàn)的頁(yè)碼,組件完整的代碼如下
<template>
<div class="pagination">
<ul class="pagination-list">
<li
title="上一頁(yè)"
@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="下一頁(yè)"
@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 源代碼地址
效果圖
實(shí)現(xiàn)過(guò)程
對(duì)于這個(gè)組件功能的開發(fā),我真的是煞費(fèi)苦心,一言難盡。首先市面上沒(méi)有一樣的功能需求用例可供參考,其次在開發(fā)的過(guò)程中組件各種邏輯的實(shí)現(xiàn)也是在摸索著進(jìn)行實(shí)現(xiàn)。在花費(fèi)了一定時(shí)間仍沒(méi)有較好的思路后,我默默的打開了世界上最大的程序員同性交友平臺(tái) github一番搜索,最終在開源項(xiàng)目基于Vue的組件庫(kù)element-ui中的transfer組件中找到了可參考實(shí)現(xiàn)的邏輯實(shí)現(xiàn)方式。
重點(diǎ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>
對(duì)于這樣一個(gè)交互復(fù)雜的復(fù)選穿梭框而言,定義好其基本的初始化數(shù)據(jù)狀態(tài)是第一步要做的。對(duì)于此組件調(diào)用的時(shí)候,模板會(huì)傳入一個(gè)屬性名為data(這里是一個(gè)數(shù)組)的props選項(xiàng)作為組件的數(shù)據(jù)來(lái)源,接下來(lái)要關(guān)注的焦點(diǎn)就轉(zhuǎn)移到了組件內(nèi)部數(shù)據(jù)交互的各種實(shí)現(xiàn)方式上了。我首先在組件狀態(tài)data里面定義了一個(gè)名叫targets的數(shù)組類型的變量用來(lái)存儲(chǔ)默認(rèn)展開的復(fù)選框列表項(xiàng)key值,然后根據(jù)data和targets這兩個(gè)基礎(chǔ)數(shù)據(jù)狀態(tài)又可以派生出兩個(gè)計(jì)算屬性sourceData和targetData用來(lái)分別渲染展開和隱藏起來(lái)的復(fù)選框選擇項(xiàng)。至此組件基本的靜態(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ù)綁定的特點(diǎn),使用v-model的指令可以默認(rèn)指定復(fù)選框選中的選項(xiàng),有關(guān)這一塊的邏輯實(shí)現(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);
}
}
}
首頁(yè)頭部導(dǎo)航欄交互顯示功能
查看源碼點(diǎn)這里
效果
功能介紹
- 頂部的導(dǎo)航欄在頁(yè)面向下滾動(dòng)的過(guò)程中有一個(gè)吸附頂部的功能
-
banner視頻部分滾動(dòng)出頁(yè)面可視區(qū)域后導(dǎo)航欄更換主題顏色 - 導(dǎo)航欄根據(jù)頁(yè)面滾動(dòng)方向的不同隱藏和顯示。
實(shí)現(xiàn)思路
導(dǎo)航欄功能的前兩個(gè)實(shí)現(xiàn)起來(lái)比較簡(jiǎn)單,這里就不多介紹了。下面主要分析一下功能中的第三點(diǎn)。
根據(jù)頁(yè)面滾動(dòng)的方向判斷導(dǎo)航欄顯示狀態(tài)要怎么實(shí)現(xiàn)呢?單純的做一個(gè)顯示狀態(tài)的切換對(duì)于熟練使用Vue的同學(xué)來(lái)說(shuō)再簡(jiǎn)單不過(guò)了,這里的坑就在于如何判斷頁(yè)面在滾動(dòng)過(guò)程中的滾動(dòng)方向呢?說(shuō)道這里,有人一定能想到可以使用瀏覽器原生API監(jiān)聽元素滾動(dòng)的事件,在事件scroll的回調(diào)函數(shù)中進(jìn)一步處理邏輯判斷。能想到這一點(diǎn)對(duì)于我們要實(shí)現(xiàn)的最終目標(biāo)有邁進(jìn)了一大步,那么瀏覽器HTML元素的scroll事件能提供給我們使用的回調(diào)參數(shù)是有限的,就是說(shuō)這個(gè)事件對(duì)象沒(méi)有直接提供此次滾動(dòng)的方向信息。所以這個(gè)問(wèn)題就需要我們手動(dòng)去解決了。
手動(dòng)封裝監(jiān)聽元素滾動(dòng)的函數(shù)
為了判斷出元素此次滾動(dòng)事件相對(duì)于上次滾動(dòng)時(shí)的方向,我們需要記錄上一次的滾動(dòng)事件信息并存儲(chǔ)起來(lái),然后通過(guò)比對(duì)兩次滾動(dòng)事件的坐標(biāo)值判斷出此次頁(yè)面滾動(dòng)的方向(這里只做滾動(dòng)向下或者向上的判斷)。為了讓這一個(gè)判斷元素滾動(dòng)方向的邏輯有更好的復(fù)用性,我把它單獨(dú)抽離成了一個(gè)工具函數(shù),當(dāng)我們需要用到這個(gè)邏輯時(shí)就可以直接拿來(lái)復(fù)用,具體實(shí)現(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);
};
};
頁(yè)面代碼實(shí)現(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)用截圖
首頁(yè)
職位
產(chǎn)品與服務(wù)
職位詳情
員工故事
支持
閱讀完本篇文章如果對(duì)您有幫助,請(qǐng)您點(diǎn)贊,謝謝!
如果想討論項(xiàng)目有關(guān)問(wèn)題或者參與共建,歡迎留言!
對(duì)本項(xiàng)目如果您有更好的建議或發(fā)現(xiàn)bug,歡迎提 issue
應(yīng)用線上地址: http://123.56.124.33:3000/
項(xiàng)目倉(cāng)庫(kù)地址: https://github.com/konglingwen94/vue-bytedanceJob,歡迎star和follow,謝謝!
本篇文章屬于個(gè)人原創(chuàng),轉(zhuǎn)載借鑒請(qǐng)注明出處,謝謝!