preface

The last article “vue. js easy to achieve a page back, restore the scroll position” is just a simple implementation of the route switch when the scroll position restore, many friends will ask how to achieve pull loading ah! Then I remembered a previous project called VUE-cNode, so I spent two days to reconstruct it, completely removing Vuex and using Vuet as a state management tool. If you follow Vuet, you will find that the version update is so fast, it is like version emperor!! In fact, Vuet version upgrades are downward compatible. Each version release will go through complete unit testing and E2E testing, which greatly ensures the stability of the released version.

Program source code

  • Vuet status management tool
  • Vue-cnode Github and effect preview address

Demand analysis

  • Records the number of pages when a pull-up request is made
  • When the page rewinds, the state of the previous list page is restored
  • The status of the list is reset when the list category is switched
  • Click detail A from list A, back up the page, open detail A again, and restore the previous accessed state of detail A
  • Click detail A from list A, back up the page, open detail B again, clear the state of detail A, and initialize the state of detail B

The installation

npm install --save vuetCopy the code

Vuet instance

import Vue from 'vue'
import Vuet from 'vuet'
import utils from 'utils'
import http from 'http'

Vue.use(Vuet)

export default new Vuet({
  pathJoin: The '-'.// Define the module's connector
  modules: {
    topic: {
      create: {
        data () {
          return {
            title: ' './ / title
            tab: ' '.// Published plate
            content: ' ' // The content of the publication}},manuals: {
          async create ({ state }) {
            if(! state.title) {return utils.toast('Title cannot be empty')}else if(! state.tab) {return utils.toast('Option cannot be empty')}else if(! state.content) {return utils.toast('Content cannot be empty')}const res = await http.post(`/topics`, {
              ...state
            })
            if (res.success) {
              this.reset()
            } else {
              utils.toast(res.error_msg)
            }
            return res
          }
        }
      },
      /********* Implement list pull up load scroll position restore core code start *************/
      list: {
        data () {
          return {
            data: [].// List stored data
            loading: true.// Data is being loaded
            done: false.// Whether all data has been loaded
            page: 1 // The number of pages loaded}},async fetch ({ state, route, params, path }) {
          // In vuet 0.1.2 and above, there is an extra params.routeWatch parameter, which we can use to determine whether the page has changed
          if (params.routeWatch === true) { // Route changed, reset module state
            this.reset(path)
          } else if (params.routeWatch === false) { // Routing does not trigger requests that may be returned from details to the list
            return{}}// params.routeWatch has no arguments, so it is a pull-up call
          const { tab = ' ' } = route.query
          const query = {
            tab,
            mdrender: false.limit: 20.page: state.page
          }
          const res = await http.get('/topics', query)
          const data = params.routeWatch ? res.data : [...state.data, ...res.data]
          return {
            data, // Update module list data
            page: ++state.page, // The number of pages +1 after each successful request
            loading: false.// Data loading is complete
            done: res.data.length < 20 // Check whether all the pages of the list are loaded}}},/********* implement list pull up load scroll position restore core code end *************/
      detail: {
        data () {
          return {
            data: {
              id: null.author_id: null.tab: null.content: null.title: null.last_reply_at: null.good: false.top: false.reply_count: 0.visit_count: 0.create_at: null.author: {
                loginname: null.avatar_url: null
              },
              replies: [].is_collect: false
            },
            existence: true.loading: true.commentId: null}},async fetch ({ route }) {
          const { data } = await http.get(`/topic/${route.params.id}`)
          if (data) {
            return {
              data,
              loading: false}}return {
            existence: false.loading: false}}}},user: { // Login user's module
      self: {
        data () {
          return {
            data: JSON.parse(localStorage.getItem('vue_cnode_self')) || {
              avatar_url: null.id: null.loginname: null.success: false}}},manuals: {
          async login ({ state }, accesstoken) { // User login method
            const res = await http.post(`/accesstoken`, { accesstoken })
            if (typeof res === 'object' && res.success) {
              state.data = res
              localStorage.setItem('vue_cnode_self'.JSON.stringify(res))
              localStorage.setItem('vue_cnode_accesstoken', accesstoken)
            }
            return res
          },
          signout () { // User exit method
            localStorage.removeItem('vue_cnode_self')
            localStorage.removeItem('vue_cnode_accesstoken')
            this.reset()
          }
        }
      },
      detail: {
        data () {
          return {
            data: {
              loginname: null.avatar_url: null.githubUsername: null.create_at: null.score: 0.recent_topics: [].recent_replies: []},existence: true.loading: true.tabIndex: 0}},async fetch ({ route }) {
          const { data } = await http.get(`/user/${route.params.username}`)
          if (data) {
            return {
              data,
              loading: false}}return {
            existence: false.loading: false}}},messages: {
        data () {
          return {
            data: {
              has_read_messages: [].hasnot_read_messages: []},loading: true}},async fetch () {
            // The user is not logged in
          if (!this.getState('user-self').data.id) return
          const { data } = await http.get(`/messages`, { mdrender: true })
          return {
            data
          }
        },
        count: {
          data () {
            return {
              data: 0}},async fetch () {
            // The user is not logged in
            if (!this.getState('user-self').data.id) return
            const res = await http.get('/message/count')
            if(! res.data)return
            return {
              data: res.data
            }
          }
        }
      }
    }
  }
})Copy the code

After the Vuet instance is created, we can wire our Vuet in the component.

  • Home page list
<template>
  <div>
    <nav class="nav">
      <ul flex="box:mean">

        <li v-for="item in tabs" :class="{ active: item.tab === ($route.query.tab || '') }">
          <router-link :to="{ name: 'index', query: { tab: item.tab } }">{{ item.title }}</router-link>
        </li>
      </ul>
    </nav>
    <! If your page is a global scrollbar, set the command to V-vuet-scroll. window="{path: 'topic-list'}" -->
    <v-content v-vuet-scroll="{ path: 'topic-list', name: 'content' }">
      <ul class="list">
        <li v-for="item in list.data" key="item.id">
          <router-link :to="{ name: 'topic-detail', params: { id: item.id } }">
            <div class="top" flex="box:first">
              <div class="headimg" :style="{ backgroundImage: 'url(' + item.author.avatar_url + ')' }"></div>
              <div class="box" flex="dir:top">
                <strong>{{ item.author.loginname }}</strong>
                <div flex>
                  <time>{{ item.create_at | formatDate }}</time>
                  <span class="tag">Share the # #</span>
                </div>
              </div>
            </div>
            <div class="common-typeicon" flex v-if="item.top || item.good">
              <div class="icon" v-if="item.good">
                <i class="iconfont icon-topic-good"></i>
              </div>
              <div class="icon" v-if="item.top">
                <i class="iconfont icon-topic-top"></i>
              </div>
            </div>
            <div class="tit">{{ item.title }}</div>
            <div class="expand" flex="box:mean">
              <div class="item click" flex="main:center cross:center">
                <i class="iconfont icon-click"></i>
                <div class="num">{{ item.visit_count > 0 ? Item.visit_count: 'Not read yet'}}</div>
              </div>
              <div class="item reply" flex="main:center cross:center">
                <i class="iconfont icon-comment"></i>
                <div class="num">{{ item.reply_count > 0 ? Item. reply_count: 'No comment yet'}}</div>
              </div>
              <div class="item last-reply" flex="main:center cross:center">
                <time class="time">{{ item.last_reply_at | formatDate }}</time>
              </div>
            </div>
          </router-link>
        </li>
      </ul>
      <v-loading :done="list.done" :loading="list.loading" @seeing="$vuet.fetch('topic-list')"></v-loading>
    </v-content>
    <v-footer></v-footer>
  </div>
</template>
<script>
  import { mapModules, mapRules } from 'vuet'

  export default {
    mixins: [
      mapModules({ list: 'topic-list' }), // Connect the state of the vuet.js we defined
      mapRules({ route: 'topic-list' }) // Use vuet.js's built-in route rules to manage page data and scroll positions
    ],
    data () {
      return {
        tabs: [{title: 'all'.tab: ' '
          },
          {
            title: 'essence'.tab: 'good'
          },
          {
            title: 'sharing'.tab: 'share'
          },
          {
            title: 'q&a'.tab: 'ask'
          },
          {
            title: 'recruitment'.tab: 'job'}]}}}</script>Copy the code
  • Page for details
<template>
  <div>
    <v-header title="Theme">
      <div slot="left" class="item" flex="main:center cross:center" v-on:click="$router.go(-1)">
        <i class="iconfont icon-back"></i>
      </div>
    </v-header>
    <! Set local scroll bar for details -->
    <v-content style="bottom: 0;" v-vuet-scroll="{ path: 'topic-detail', name: 'content' }">
      <v-loading v-if="detail.loading"></v-loading>
      <v-data-null v-if=! "" detail.existence" msg="The topic doesn't exist."></v-data-null>
      <template v-if=! "" detail.loading && detail.existence">
        <div class="common-typeicon" flex v-if="data.top || data.good">
          <div class="icon" v-if="data.good">
            <i class="iconfont icon-topic-good"></i>
          </div>
          <div class="icon" v-if="data.top">
            <i class="iconfont icon-topic-top"></i>
          </div>
        </div>

        <ul class="re-list">
          <! Start -->
          <li flex="box:first">
            <div class="headimg">
              <router-link class="pic" :to="{ name: 'user-detail', params: { username: author.loginname } }" :style="{ backgroundImage: 'url(' + author.avatar_url + ')' }"></router-link>
            </div>
            <div class="bd">
              <div flex>
                <router-link flex-box="0" :to="{ name: 'user-detail', params: { username: author.loginname } }">{{ author.loginname }}</router-link>
                <time flex-box="1">{{ data.create_at | formatDate }}</time>
                <div flex-box="0" class="num"># the building</div>
              </div>
            </div>
          </li>
          <! End -->
          <! Start -->
          <li>
            <div class="datas">
              <div class="tit">{{ data.title }}</div>
              <div class="bottom" flex="main:center">
                <div class="item click" flex="main:center cross:center">
                  <i class="iconfont icon-click"></i>
                  <div class="num">{{ data.visit_count }}</div>
                </div>
                <div class="item reply" flex="main:center cross:center">
                  <i class="iconfont icon-comment"></i>
                  <div class="num">{{ data.reply_count }}</div>
                </div>
              </div>
            </div>
            <div class="markdown-body" v-html="data.content"></div>
          </li>
          <! End -->
          <li class="replies-count" v-if="replies.length">A total of (<em>{{ replies.length }}</em>Article) reply</li>
          <! Start -->
          <li v-for="(item, $index) in replies">
            <div flex="box:first">
              <div class="headimg">
                <router-link class="pic" :to="{ name: 'user-detail', params: { username: item.author.loginname } }" :style="{ backgroundImage: 'url(' + item.author.avatar_url + ')' }"></router-link>
              </div>
              <div class="bd">
                <div flex>
                  <router-link flex-box="0" :to="{ name: 'user-detail', params: { username: item.author.loginname } }">{{ item.author.loginname }}</router-link>
                  <time flex-box="1">{{ item.create_at | formatDate }}</time>
                  <div flex-box="0" class="num">#{{ $index + 1 }}</div>
                </div>
                <div class="markdown-body" v-html="item.content"></div>
                <div class="bottom" flex="dir:right cross:center">
                  <div class="icon" @click="commentShow(item, $index)">
                    <i class="iconfont icon-comment-topic"></i>
                  </div>
                  <div class="icon" :class="{ fabulous: testThing(item.ups) }" v-if="item.author.loginname ! == user.data.loginname" @click="fabulousItem(item)">
                    <i class="iconfont icon-comment-fabulous"></i>
                    <em v-if="item.ups.length">{{ item.ups.length }}</em>
                  </div>
                </div>
              </div>
            </div>
            <reply-box v-if="detail.commentId === item.id" :loginname="item.author.loginname" :replyId="item.id"></reply-box>
          </li>
          <! -- Theme comment end -->
        </ul>
        <div class="reply" v-if="user.data.id">
          <reply-box @success="$vuet.fetch('topic-detail')"></reply-box>
        </div>
        <div class="tip-login" v-if=! "" user.data.id">You have not logged in yet, please first<router-link to="/login">The login</router-link>
        </div>
      </template>
    </v-content>
  </div>
</template>
<script>
  import http from 'http'
  import replyBox from './reply-box'
  import { mapModules, mapRules } from 'vuet'

  export default {
    mixins: [
      // Connection details and login user module
      mapModules({ detail: 'topic-detail'.user: 'user-self' }),
      // Use the route rule to manage the page data
      mapRules({ route: 'topic-detail'})].components: { replyBox },
    computed: {
      data () {
        return this.detail.data
      },
      author () {
        return this.detail.data.author
      },
      replies () {
        return this.detail.data.replies
      }
    },
    methods: {
      testThing (ups) { // Verify whether to like
        return ups.indexOf(this.user.data.id || ' ') > - 1
      },
      fabulousItem ({ ups, id }) { / / thumb up
        if (!this.user.data.id) return this.$router.push('/login')
        var index = ups.indexOf(this.user.data.id)
        if (index > - 1) {
          ups.splice(index, 1)}else {
          ups.push(this.user.data.id)
        }
        http.post(`/reply/${id}/ups`)
      },
      commentShow (item) { // Display the hidden reply box
        if (!this.user.data.id) return this.$router.push('/login')
        this.detail.commentId = this.detail.commentId === item.id ? null : item.id
      }
    }
  }

</script>Copy the code

conclusion

Because space is limited, so only lists the list and details of the code, if you are interested in in-depth, you can take a look at vue-cNode code. This is a complete project of state management based on Vuet, including user login and exit, routing page, scrolling position restoration, post editing state preservation and so on. Although small, it has all the five organs.