初看到掘金 - 2020年度人气创作者榜单这个网站,感觉整体界面成果给我一种清新的感觉,于是花了点工夫推敲如何实现。目前实现的性能有:列表展现,搜寻,有限加载(与原网站有些区别,加了loading成果),流动介绍,tab切换。通过这些,我对vue3.0的composition api有了肯定的认知,上面让咱们来看看吧!

ps:集体认为原网站应该是应用react.js写的

间接申请该网站的数据接口,应该是会报跨域问题的。于是我想了一个方法,就是通过node.js来爬取数据。上面来看看代码:

node后端爬取数据

代码如下:

const superagent = require('superagent');const express = require('express');const app = express();const port = 8081;function isObject(value) {    return value && typeof value === 'object';}function getApi(url, params,method) {    return new Promise((resolve) => {        if (!isObject(params)) {            return resolve(setResponse(400, null, '请传入参数!'));        } else {            let paramMethod = method.toLowerCase() === 'post' ? 'send' : 'query';            superagent(method,url)[paramMethod](params).set('X-Agent', 'Juejin/Web').end((err, supRes) => {                if (err) {                    return resolve(setResponse(400, null, err));                }                let data = JSON.parse(supRes.text);                resolve(setResponse(data.err_no === 0 ? 200 : data.err_no, data.data, data.err_msg));            });        }    })}app.use(express.json());app.all("*", function (req, res, next) {    //设置容许跨域的域名,*代表容许任意域名跨域    res.header("Access-Control-Allow-Origin", "*");    //容许的header类型    res.header("Access-Control-Allow-Headers", "content-type");    //跨域容许的申请形式     res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");    if (req.method.toLowerCase() == 'options') {        res.send(200);    } else {        next();    }});function setResponse(code, data, message) {    return {        code: code,        data: data,        message: message    }}app.post('/info', (req, res) => {    const params = req.body;    getApi('https://api.juejin.cn/list_api/v1/annual/info', params,'post').then(data => {        res.send(JSON.stringify(data));    })})app.post('/list', (req, res) => {    const params = req.body;    getApi('https://api.juejin.cn/list_api/v1/annual/list', params,'post').then(data => {        res.send(JSON.stringify(data));    });})app.get('/user',(req,res) => {    const params = req.query;    getApi('https://api.juejin.cn/user_api/v1/user/get',params,'get').then(data => {        res.send(JSON.stringify(data));    })})app.listen(port, () => console.log(`Example app listening on port ${port}!`))

以上只是爬了次要的三个接口,如list接口,info接口以及user接口。当然还有登录性能没有写,掘金应该是通过cookie技术去实现判断用户是否登录的,当从掘金关上,跳往该网站,会向浏览器的cookie存储用户相干登录信息。如下图所示:

这一个性能的实现思路晓得即可,源码不会实现。而后在该网站去获取cookie并传递参数给user接口既能够获取登录相干信息。

以上代码思路也很简略,就是通过搭建一个本地服务器,而后爬取该网站的三个次要的接口,次要应用了superagent这个库来进行爬取。相干API能够参考superagent文档。而后就是容许跨域的设置,用了node框架express。没什么技术难点。

web前端

技术点:vue3.0,typescript,vue-cli4.0,axios,less

首先剖析一下页面,次要分为首页和流动介绍页。其中HeaderFooter组件作为一个公共组件,这是毋庸置疑的。当然,这两个组件的代码也比较简单,能够不做剖析。如下:

Header
<template>  <div class="header">      <div class="header-logo"></div>      <div class="header-screen"></div>      <div class="header-cascade"></div>      <div class="header-person"></div>      <div class="header-python"></div>      <div class="header-vue"></div>      <div class="header-react"></div>      <div class="header-phone"></div>      <div class="header-phone-wolpe"></div>      <div class="header-bug"></div>      <div class="header-coffee"></div>      <div class="header-year"></div>      <div class="header-title"></div>  </div></template>

显然,Header组件次要考查CSS布局,好吧,尽管能够说是模拟写了一遍布局(所有布局都是同理,没什么好说的),但也算是剽窃了(PS:心愿掘金技术团队不介意吧)。

Footer
<template>  <div class="footer">    <ul class="footer-web">      <li v-for="(web, index) in footerWebNavList" :key="web.text + index">        <template v-if="web.url">          <a :href="web.url" target="_blank">{{ web.text }}</a>        </template>        <template v-else>{{ web.text }}</template>      </li>    </ul>    <div class="footer-app">      <ul        class="footer-app-item"        v-for="(app, index) in footerAppNavList"        :key="app + index"      >        <li          v-for="(app_item, app_index) in app"          :key="app_item.text + app_index"        >          <template v-if="app_item.url">            <a :href="app_item.url" target="_blank">{{ app_item.text }}</a>          </template>          <template v-else>{{ app_item.text }}</template>        </li>      </ul>    </div>  </div></template><script lang="ts">import { reactive, toRefs } from "vue";interface FooterItem {  text: string;  url?: string;}type FooterList = Array<FooterItem>;export default {  setup() {    const state = reactive({      footerWebNavList: [        {          text: "@2020掘金",        },        {          text: "对于咱们",          url: "https://juejin.cn/about",        },        {          text: "营业执照",          url: "https://juejin.cn/license",        },        {          text: "用户协定",          url: "https://juejin.cn/terms",        },        {          text: "京ICP备18012699号-3",          url: "https://beian.miit.gov.cn/",        },        {          text: "京公网案备11010802026719号",          url:            "http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010802026719",        },        {          text: "北京北比信息技术有限公司版权所有",        },      ],      footerAppNavList: [] as any[],    });    const first: FooterList = state.footerWebNavList.slice(0, 4);    const second: FooterList = state.footerWebNavList.slice(4);    state.footerAppNavList = [first, second];    return {      ...toRefs(state),    };  },};</script>

这个组件难度也不大,就是把导航数据归纳到一起了而已。

流动介绍页面也比较简单,就一个tab组件,而后其它都是图片布局。

<template>  <div class="info-container">    <Header />    <div class="pc-info"></div>    <div>      <div class="home-button-container">        <router-link to="/">          <div class="home-button"></div>        </router-link>      </div>      <div class="info-box">        <div class="info-title"></div>        <div class="info-box1"></div>        <div class="info-box2"></div>        <div class="info-box3"></div>        <div class="info-box4">          <div class="info-prizes">            <div class="info-prizes-tab">              <div                class="info-prizes-tab1"                :style="{ 'z-index': curInfoTab === 0 ? 3 : 1 }"                @click="onChangeInfoTab(0)"              ></div>              <div                class="info-prizes-tab2"                :style="{ 'z-index': curInfoTab === 1 ? 3 : 1 }"                @click="onChangeInfoTab(1)"              ></div>            </div>            <div>              <img                :src="require('../assets/' + (curInfoTab === 0 ? 'individual' : 'group') + '_prize_web.png')"                alt="图片加载中"                style="width: 100%"              />            </div>          </div>        </div>      </div>    </div>  </div></template><script lang="ts">import { reactive, toRefs } from "vue";import Header from "../components/Header.vue";export default {  components: {    Header,  },  setup() {    const state = reactive({      curInfoTab: 0,    });    const onChangeInfoTab = (value: number) => {      state.curInfoTab = value;    };    return {      ...toRefs(state),      onChangeInfoTab,    };  },};</script>

当然后续代码我就不一一展现了,我次要总结一下所用到的技术知识点。

首先是vuexvue2纯熟应用的话,其实vue3语法也差异不大。

import { useStore } from "vuex"; // store.state // store.dispath(办法名,数据)

次要如果子组件想通过事件传递给父组件,则须要通过mitt插件,譬如搜寻组件的代码实现如下:

import mitt from 'mitt';export const emitter = mitt();export default {  setup() {    const state = reactive({        keyword:""    })    const refState = toRefs(state);    const onSearch = () => {        if(!state.keyword)return alert('请输出你喜爱的作者名!');        //传递给父组件        emitter.emit('on-search',state.keyword);    }    return {        ...refState,        onSearch    };  },};

其它的都是vue3.0的语法了,比方watch监听等等,更多源码在这里。

PS:不晓得到工夫了掘金官网会不会进行相干数据接口的服务,所以下一步,我可能会思考写静态数据,而后把axios封装一下,当然代码还有些毛糙,因为实现的有些匆忙,后续会做优化。

最初,附上局部效果图: