iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
Modern Web

Vue CLI + Firebase 雲端資料庫 30天打造簡易部落格及後臺管理系列 第 29

Day 29: 依照文章 tags 分類進行相應渲染

今天多新增一些功能,好讓我們的 Recently.vue 有點作用。

首先去到 AddArticle.vue 將我們的 template 多新增可以添加文章 tags 的部分。

<template>
  <b-row>
    <b-modal id="modal-1" title="下一步?" @ok="F_updateArticle(articleData, addOrUpdate , $attrs)">
      <p class="my-4">如要{{ addOrUpdate }}文章請按確認</p>
    </b-modal>
    <b-col cols="12">
      <label for="input-large">文章標題:</label>
      <b-form-input id="input-large" size="lg" placeholder="請輸入文章標題" v-model="title"></b-form-input>
    </b-col>
    <b-col cols="12">
      <MarkdownPro
        @on-save="updateData"
        v-model="value"
      ></MarkdownPro>
    </b-col>
    <b-col cols="12">
      <div>
        <label for="tags-pills">請輸入文章標籤</label>
        <b-form-tags
          input-id="tags-pills"
          v-model="tags"
          tag-variant="info"
          size="lg"
          separator=" ,;"
          placeholder="可以使用空格、逗號、分號、enter 鍵來輸入標籤"
          remove-on-delete
        ></b-form-tags>
        <p class="mt-2">Value: {{ tags }}</p>
      </div>
    </b-col>
    <b-col class="mt-2"><b-button v-b-modal.modal-1 variant="primary">點擊{{ addOrUpdate }}文章</b-button></b-col>
  </b-row>
</template>

<script>
...
data () {
  return {
    articleData: {},
    title: '',
    value: '',
    addOrUpdate: '新增',
    tags: [],
    createdAt: null
  }
},
methods: {
  updateData (saveEventInfo) {
    const splitter = '<!-- more -->'
    const self = this
    if (saveEventInfo.value.indexOf(splitter) === -1) {
      saveEventInfo.value = saveEventInfo.value.slice(0, 20) + splitter + saveEventInfo.value.slice(20)
    }

    saveEventInfo.stopOnMore = saveEventInfo.value.split(splitter)
    saveEventInfo.stopOnMore = saveEventInfo.stopOnMore[0] + '...'

    if (this.addOrUpdate !== '更新') this.createdAt = new Date().getTime()

    this.F_showUser().then(res => {
      this.articleData.contentData = {
        title: self.title,
        createdAt: self.createdAt,
        value: saveEventInfo.value,
        stopOnMore: saveEventInfo.stopOnMore,
        html: saveEventInfo.html
      }

      this.articleData.authorInfo = {
        displayName: res.displayName,
        email: res.email,
        uid: res.uid,
        photoURL: res.photoURL
      }

      this.articleData.aboutCategory = {
        tags: self.tags
      }
    })
  }
}
</script>

給文章添加標題並上傳文章看看,應該會上傳陣列,我們後面就用這個陣列來渲染當作可點擊 tag。

本章目標 : Recently.vue 乘載所有 tag 並且點擊時切換至所有相對應文章

實現思路

  1. 點擊 tag 時首頁的文章會相應的切換,所以我們要先得到所有的 tag,我們可以透過 ArticleList.vue 取得文章下來後把所有的 tag 抓出來,並利用 tagsFiltered 暴漏出去給父層 ( Home.vue ) 取值放到 Recently.vue 中,具體請看註解。
// ArticleList.vue 
<template>
  <div class="article__blocks">
    <div class="article__blocks__block" v-for="article in filterPosts" :key="article.id">
      <div class="article__blocks__block_title">
        <h3>{{ article.contentData.title }}</h3>
      </div>
      <div class="article__blocks__block_content">
        <p>{{ article.aboutCategory.tags }}</p>
      </div>
      <div class="article__blocks__block_tags">
        <p>{{ article.contentData.stopOnMore }}</p>
      </div>
      <div class="goto" @click="gotoArticle(article.id)"  :data-articleId="article.id">
        <p> >> 繼續閱讀</p>
      </div>
    </div>

    <div class="article__tags--expose" v-show="false">
			// 用插槽暴漏出去我們的所有 tag 
      <slot name="tagsFiltered" :tagsFiltered="tagsFiltered"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ArticleList',
  props: ['chooseTag'],
  data () {
    return {
      mainPosts: [],
      tags: [],
      tagsFiltered: null,
      filterPosts: []
    }
  },

  watch: {
		// 用來處理父層 bind 進來的值,用於顯示相對應 tag 的文章
    chooseTag: function (newVal, oldVal) {
      if (newVal === 'all') {
        this.filterPosts = this.mainPosts
        return 'done'
      }
      const filterPost = this.mainPosts.filter(ele => {
        return ele.aboutCategory.tags.indexOf(newVal) !== -1
      })
      this.filterPosts = filterPost
    }
  },

  created () {
    const buffer = []
    this.F_getCollectionDocsSort('posts', { where: 'contentData.createdAt', order: 'desc' }).then(docs => {
      

			// this.mainPosts 成為我們回復顯示所有文章的原始資料
      this.mainPosts = docs

		  // 另外拷貝一份出去作為過濾文章使用,v-for 掛載這個
      this.filterPosts = JSON.parse(JSON.stringify(this.mainPosts))

      this.tags = this.filterPosts.map(ele => {
        return ele.aboutCategory.tags
      })

      this.tags.forEach(ele => [
        buffer.push(...ele)
      ])

			// 放入 Set 中自動過濾所有重複 tag
      this.tagsFiltered = new Set(buffer)

			// 轉回陣列
      this.tagsFiltered = Array.from(this.tagsFiltered)
    })
  }
}
</script>
  1. Recently.vue 取的值的方法; 先透過父層 Home.vue 抓出值在 bind 進去,請看註解
// Home.vue
<template>
  <b-container class="pageHome">
    <b-row>
      <b-col cols="9">
        <ArticleList
          :chooseTag="storeChooseTags"
        >
					<!-- 先暴露值出來丟入方法改變父層作用域 storeAllTags,儲存所有 tag  -->
          <template v-slot:tagsFiltered="{tagsFiltered: tags}">
            {{ getChildValue(tags) }}
          </template>
        </ArticleList>
      </b-col>

      <b-col cols="3">
				<!-- 在綁定所有 tags 給 Recently.vue -->
        <Recently
          :tags="storeAllTags"
        >
          <template v-slot:chooseTagDispose="{chooseTagDispose: tag}">
            {{ getChildSelectTag(tag) }}
          </template>
        </Recently>
      </b-col>
    </b-row>
  </b-container>
</template>

<script>
// @ is an alias to /src
import ArticleList from '@/components/ArticleList.vue'
import Recently from '@/components/Recently.vue'

export default {
  name: 'Home',
  data () {
    return {
      storeAllTags: [],
      storeChooseTags: null
    }
  },
  components: {
    ArticleList,
    Recently
  },
  methods: {
    getChildValue (value) {
      this.storeAllTags = value
    },

    getChildSelectTag (tag) {
      this.storeChooseTags = tag
    }
  }
}
</script>
  1. 再來看到 Recently.vue,利用 tags prop 接 ArticleList 暴漏給 Home 丟進來的值 ( 陣列包含所有 tag ),並且 v-for 進行渲染以及 @click 值的切換,值每次切換都藉由 chooseTagDispose 在暴漏出去給 Home.vue
<template>
  <div class="recentlyArticle">
    <b-list-group>
      <b-list-group-item @click="chooseTagDispose='all'" button>
        所有文章
      </b-list-group-item>
      <b-list-group-item @click="chooseTagDispose=tag" v-for="tag in tags" :key="tag" class="d-flex justify-content-between align-items-center" button>
        {{ tag }}
      </b-list-group-item>
    </b-list-group>
    <slot v-show="false" name="chooseTagDispose" :chooseTagDispose="chooseTagDispose"></slot>
  </div>
</template>

<script>
export default {
  name: 'Recently',
  props: ['tags'],
  data () {
    return {
      chooseTagDispose: null
    }
  }
}
</script>

再看到 Home.vue,可以看見我們把暴漏出來每次 click 的值再綁定進 ArticleList,裡面有個 watch 還記得嗎 ? 每次都會去依照丟進來的 tag 進行過濾,並返回相對應的文章們,到這邊結束,我們即可透過點擊 Recently.vue 渲染出來的 tag 進行文章的切換,你也可以把這些 tag 改成其他你喜歡的樣式,比如說 button 等等。

// Home.vue 沒改變,換註解方便解說而已
<template>
  <b-container class="pageHome">
    <b-row>
      <b-col cols="9">
				<!-- 再把每次改變的 storeChooseTags 都 bind 進 ArticleList -->
        <ArticleList
          :chooseTag="storeChooseTags"
        >
          <template v-slot:tagsFiltered="{tagsFiltered: tags}">
            {{ getChildValue(tags) }}
          </template>
        </ArticleList>
      </b-col>

      <b-col cols="3">
        <Recently
          :tags="storeAllTags"
        >
					<!-- 先暴漏出來每次 click 的 tag 並丟進方法改變 storeChooseTags 值 -->
          <template v-slot:chooseTagDispose="{chooseTagDispose: tag}">
            {{ getChildSelectTag(tag) }}
          </template>
        </Recently>
      </b-col>
    </b-row>
  </b-container>
</template>

<script>
// @ is an alias to /src
import ArticleList from '@/components/ArticleList.vue'
import Recently from '@/components/Recently.vue'

export default {
  name: 'Home',
  data () {
    return {
      storeAllTags: [],
      storeChooseTags: null
    }
  },
  components: {
    ArticleList,
    Recently
  },
  methods: {
    getChildValue (value) {
      this.storeAllTags = value
    },

    getChildSelectTag (tag) {
      this.storeChooseTags = tag
    }
  }
}
</script>

沒事也可以逛逛我們其他團隊成員的文章啦 ~~
eien_zheng: 前端小嘍嘍的Golang學習旅程_The journey of learning Golang 系列
PollyPO技術: 前端設計轉前端工程師-JS踩坑雜記 30 天 系列
阿電: 忍住不打牌位,只要30天VueJS帶你上A牌 系列
喬依司: 實作經典 JavaScript 30 系列


上一篇
Day 28: 快來不及了,先用 Hosting 發布網頁吧 ( 需連結 billing account )
下一篇
Day 30: 發現 bug 收尾 + 完賽 & 後續計畫
系列文
Vue CLI + Firebase 雲端資料庫 30天打造簡易部落格及後臺管理30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言