The original

Nowadays, there are so many projects based on Vue in the community. In order to improve my further understanding of Vue, I have been looking for a project with good-looking interface and complete functions to practice. I found bytedance’s official recruitment website is very suitable for me to practice when BROWSING various recruitment websites. When I found out that this site was implemented by React, I wondered if I could completely refactor it using Vue’s technology stack.

I’ll preview it here

Where does the data come from?

A completed online project cannot do without complete data, so how can I get the real data of the project I want to do? So I silently opened the developer tool of the original website and found the official API interface requested by the browser in the Network panel. The problem is that companies like Bytedance will definitely restrict cross-domain access to server API requests. Even if an interface can successfully request data, it will be unstable for an interface that wants to be used as its own project access for a long time. Therefore, I came up with the realization scheme of interface proxy. The general realization idea is to use Express to build a server of my own, including the hosting of static resources after the project goes online.

Tips for server-side interface proxies

For those caught on the browser sideAPIData interface, pure analysis of its address and various parameters is undoubtedly a very troublesome thing, is there a way to reuse it? The answer is yes! There are no native nodesfetchRequest method, which requires the help of a third-party Node modulenode-fetchThis can be installed directly with NPM similar to browser-side nativefetchRequest module. With it we’re using a browserNetworkThe one-click interface copy function in the panel, please see the demonstration below for specific operation, and click on the detailed API code casehere

This feature is available only on older versions of Chrome. If your browser does not have this copy option, please upgrade to the latest browser before using itCopy the code

Project technical framework

In order to further improve my technical level and deepen my understanding of Vue, I chose zero code to develop all page functions (without using any third party UI libraries).

  • Project front-end technology stack
    • Vue main frame
    • Vue-router Is an official plug-in for route forwarding
    • Lodash is a javascript function library
    • Axios is responsible for the plug-in for HTTP requests
  • Server technology stack
    • Express to buildwebThe server’snodejsThe framework
    • Node-fetch is similar to the polyfill of a fetch request on the browser side
    • Connect-history-api-fallback addresses single page applications inhistoryThe access server appears in mode404The middleware
  • Project development tools
    • Vue-cli quick setupVueOfficial scaffolding for the project
    • less cssThe preprocessor
    • Vscode is a lightweight code editor
    • Postman test debuggingAPIInterface tools
    • vue-devtools VueProject official debugging tool
    • Chrome application running/debugging environment
    • Git open source version control system
  • The deployment environment
    • Ali cloud server online at http://123.57.204.48:3000
    • Git remote repository address[email protected]:konglingwen94/vue-bytedanceJob.git
    • Github code hosting repository github.com/konglingwen…

Project source directory

Analysis of important functions of the project

Pager components components/pagination. Vue view source pointshere

Before developing this pager component, I also referred to the paging function of several websites. Various paging functions are different. After selecting, I finally decided on one that I recognized

As can be seen from the figure above, this is a functional pager component, the basic code here is not much introduction, specific implementation clickhere. The following is the main analysis of the difficulties encountered in the development process!

When the mouse clicks on the number of pages, the entire page bar is dynamically switched accordingly. When the total number of pages exceeds a certain value, or the page switches to a certain range, the corresponding ellipsis will appear instead of the hidden page number display. So how should this functional logic be implemented?

Good thinking is the first step to writing elegant code, and I’ve broken down the possible states of paging into four categories

  1. The first ellipsis appears before the maximum number of pages
  2. When two ellipses appear on the page bar
  3. When the ellipsis appears after the minimum page number
  4. The total page number of the pager is less than the number of pages displayed by default (no ellipsis appears on the page bar)

According to the above listed several cases can be used to achieve the code, here I use a calculation attribute of Vue visiblePagers to dynamically display all the page numbers, the complete code of the component is as follows

<template> <div class="pagination"> <ul class="pagination"> @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 = "next page" @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>Copy the code

Check the shuttle box components/checkbox-transfer.vue source codeaddress

rendering

The implementation process

For the development of this component function, I really painstakingly, a long story. First of all, there is no use case for the same functional requirements on the market for reference. Secondly, in the process of development, the realization of various logic components is also groping for realization. After spending some time and still not having a good idea, I searched Github, the largest gay dating platform for programmers in the world, and finally found a logical implementation method for reference in the Transfer component in element-UI, the component library based on Vue of the open source project.

Key logic analysis

Template section

<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>Copy the code

Defining the basic initialization data state is the first step for such a complex interactive check through box. When this component is called, the template passes in the props option with a property named Data (in this case, an array) as the source of the component’s data, and then the focus shifts to the various implementations of the component’s internal data interaction. I first define an array type variable called Targets in the component state data to store the default expanded checkbox key. The two calculated properties sourceData and targetData can then be derived from the data and targets base data states to render expanded and hidden checkbox selections, respectively. The basic static template rendering logic for the component is now conceived. The relevant codes are as follows

Component Script section

<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 ); }},Copy the code

In addition, this component also has the characteristics of bidirectional data binding, which is typical of Vue components. Using the V-Model instruction, you can specify the options selected by the checkbox by default. The logic implementation of this piece will not be described here

Component Script section

<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);
    }
  }
 }
   
Copy the code

Home page head navigation bar interactive display function

View the source code here

The effect

Function is introduced

  1. The navigation bar at the top has a feature that absorbs the top as the page scrolls down
  2. bannerThe navigation bar changes the theme color after scrolling out of the visual area of the page
  3. The navigation bar is hidden and displayed according to the scrolling direction of the page.

Implementation approach

The first two navigation bar features are relatively simple to implement and won’t be covered here. The third point in the function is analyzed below.

How to realize the navigation bar display state according to the direction of page scrolling? Simple to do a display state switch for skilled use of Vue students is very simple, the pit here is how to determine the page in the scrolling process of the scrolling direction? At this point, one can imagine that you can use the browser’s native API to listen for element scroll events and further handle logical judgments in the event Scroll callback function. Thinking about this is a big step towards our ultimate goal, so the Scroll event of the browser HTML element gives us a limited number of callback parameters to use, meaning that the event object does not directly provide information about the direction of the scroll. So this problem needs to be solved manually.

Manually encapsulate functions that listen for element scrolling

In order to determine the scrolling direction of the element relative to the last scrolling event, we need to record and store the information of the last scrolling event, and then judge the scrolling direction of the page by comparing the coordinate values of the two scrolling events (scrolling down or up is only used here). In order to make this logic to judge the rolling direction of elements more reusable, I separate it into a tool function, which can be directly reused when we need to use this logic. The specific implementation code is as follows

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);
  };
};
Copy the code

The page code is implemented as follows

Views /home.vue component script section

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(); }}Copy the code

Application screenshots

Home page

position

Products and Services

Position details

Employees story

support

If reading this article is helpful, please like it. Thank you!

If you want to discuss the project or participate in the construction, welcome to leave a message!

If you have better suggestions or find bugs in this project, please feel free to issue

Application online address: http://123.57.204.48:3000/

Project warehouse address: github.com/konglingwen… , welcome Star and Follow, thank you!

This article belongs to the personal original, reproduced for reference, please indicate the source, thank you!