Vue全棧重構(gòu)字節(jié)跳動招聘網(wǎng)站(上)

初衷

現(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代碼案例請點擊這里

image

此功能只有高版本的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的中間件
  • 項目開發(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)
  • 部署環(huán)境

項目源碼目錄

image

項目重要功能剖析

分頁器組件 components/pagination.vue 查看源代碼點這里

本人在開發(fā)這個分頁器組件之前也是參考了多個網(wǎng)站的分頁功能局齿,各種各樣的分頁功能各不相同橄登,挑選之后最終確定了自己比較認(rèn)可的一個,此組件實現(xiàn)的功能如下圖所示

image

從上圖可以看出這是一個功能完成的分頁器組件谣妻,基礎(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)分為四種情況

  1. 第一個省略號出現(xiàn)在最大頁數(shù)前面時
  2. 分頁條出現(xiàn)兩個省略號時
  3. 省略號出現(xiàn)在最小頁數(shù)后面時
  4. 分頁器總頁碼小于默認(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 源代碼地址

效果圖

image

實現(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ù)datatargets這兩個基礎(chǔ)數(shù)據(jù)狀態(tài)又可以派生出兩個計算屬性sourceDatatargetData用來分別渲染展開和隱藏起來的復(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)航欄交互顯示功能

查看源碼點這里

效果

image

功能介紹

  1. 頂部的導(dǎo)航欄在頁面向下滾動的過程中有一個吸附頂部的功能
  2. banner視頻部分滾動出頁面可視區(qū)域后導(dǎo)航欄更換主題顏色
  3. 導(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)用截圖

首頁

image

職位

image

產(chǎn)品與服務(wù)

image

職位詳情

image

員工故事

image

支持

閱讀完本篇文章如果對您有幫助鲁猩,請您點贊,謝謝搅窿!

如果想討論項目有關(guān)問題或者參與共建隙券,歡迎留言!

對本項目如果您有更好的建議或發(fā)現(xiàn)bug,歡迎提 issue

應(yīng)用線上地址: http://123.56.124.33:3000/

項目倉庫地址: https://github.com/konglingwen94/vue-bytedanceJob,歡迎starfollow,謝謝沐飘!

本篇文章屬于個人原創(chuàng),轉(zhuǎn)載借鑒請注明出處借卧,謝謝筛峭!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市镰吵,隨后出現(xiàn)的幾起案子挂签,更是在濱河造成了極大的恐慌,老刑警劉巖画株,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啦辐,死亡現(xiàn)場離奇詭異芹关,居然都是意外死亡,警方通過查閱死者的電腦和手機侥衬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門轴总,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人功偿,你說我怎么就攤上這事往堡。” “怎么了吨瞎?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵穆咐,是天一觀的道長字旭。 經(jīng)常有香客問我着绊,道長归露,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任恐锦,我火速辦了婚禮疆液,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘潘飘。我一直安慰自己掉缺,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布艰毒。 她就那樣靜靜地躺著搜囱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绊汹。 梳的紋絲不亂的頭發(fā)上扮宠,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天涵卵,我揣著相機與錄音荒叼,去河邊找鬼。 笑死被廓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昆婿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼睁冬,長吁一口氣:“原來是場噩夢啊……” “哼豆拨!你這毒婦竟也來了能庆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弥搞,失蹤者是張志新(化名)和其女友劉穎渠旁,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肛度,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡投慈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年伪煤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片职烧。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡防泵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出足删,到底是詐尸還是另有隱情锁右,我是刑警寧澤讶泰,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布痪署,位于F島的核電站兄旬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辜王。R本人自食惡果不足惜罐孝,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汹来。 院中可真熱鬧改艇,春花似錦谒兄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啊研。三九已至,卻和暖如春党远,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钠绍。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工柳爽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留碱屁,地道東北人。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓赵誓,卻偏偏與公主長得像柿赊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诡蜓,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容