兩周擼一個掘金微信小程序


利益相關

聲明

這並不是掘金官方小程序(貌似沒有搜到掘金 APP 對應的官方小程序),完全為第三者開發者開發,僅用於學習交流,禁止用於其他用途。若要使用官方正版,可訪問掘金 官方網站,或下載掘金官方 APP,或訪問掘金官方其他途徑。

該小程序所有 API 均來自掘金官方 web 網站和官方 AndroidAPP(以 web 為主),UI照抄參照的掘金官方 AndroidAPP(ver 5.4.3)。部分靜態資源(icon、圖片等)直接從掘金官方 apk 里拷出來的。

該小程序代碼已開源,點擊可查看源碼,可隨意 star。也可以先掃描下方的小程序碼直接體驗。

寫在前面

前段時間寫了一個簡單的小程序 QuietWeather源碼在這里,具體實現相關可查看這篇文章:兩天擼一個天氣應用微信小程序。但是這個 掘金小程序QuietWeather 完全不是一個數量級的。so,該文章梳理內容會有那么一點兒多,想跳過的可以直接拉到最下面。。。

這里先上效果圖,感興趣的也可以 查看源碼 。實際體驗可掃描👆上面的小程序碼。

效果圖

對應頁面可打開掘金官方 APP 對比

PC 開發者工具錄制,會有些卡頓

文章數據入口調整了,也保留了動畫,請酌情忽略 gif 卡頓

實現

過濾器

filter 目錄下創建了一個 wxs,里面是用到的過濾器,需要注意的是,wxs 的語法只能是 es5,而且部分 js 語法是不支持的,具體支持的語法可查看微信小程序開發文檔。

組件

細分的話,細分的話目前總共寫了 8 個組件:

  • 頁面為空組件 empty
  • 沸點 item 組件 feidianItem:分為上半部分 feidianItemTop 和下半部分組件 feidianItemBottom,會在沸點、贊過的沸點等相關頁面復用
  • 文章 item:有兩種展現樣式,這里寫了獨立的兩個組件:postItemOnepostItemTwo
  • 標簽展現 item tagItem:會在標簽管理等相關頁面復用
  • 小冊 item xiaoceItem:會在小冊 tab、購買的小冊等相關頁面復用

組件相對比較簡單,基本上是以展現為主,並沒有太多交互,可下載源碼 在微信開發者工具調試查看相關效果。

頁面

HOME 頁

HOME 頁主要展現兩部分:頂部熱門文章推薦和下面的推薦文章列表,使用了兩個上面提到的組件: postItemOnepostItemTwo。且未登錄時,會有一個登陸提示。實現如下:

<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
  <navigator url='/pages/login/login' wx:if='{{!logined}}'>
    <view class='card guide'>
      <view class='l'>
        <view class='t'>登錄賬號</view>
        <view class='c'>收藏文章,同步閱讀記錄,數據永不丟失</view>
      </view>
      <view class='r'>登錄</view>
    </view>
  </navigator>
  <view class='hot card' wx:if='{{hotRecomment.length && hotRrecommendShow}}'>
    <view class='btitle'>
      <view class='l'>
        <image class='icon' src='/img/ic_hot_home.png'></image>
        <view>熱門推薦</view>
      </view>
      <view class='r'>
        <image catchtap='refreshHot' class='refresh {{rotate}}' src='/img/refresh_icon.png'></image>
        <image catchtap='closeHot' class='close' src='/img/chart_close.png'></image>
      </view>
    </view>
    <postItemOne list='{{hotRecomment}}' graphics='{{true}}'></postItemOne>
  </view>
  <view class='timeline'>
    <postItemTwo list='{{timeline}}'></postItemTwo>
  </view>
</view>

其中,頂部熱門推薦的刷新會有以下的實現效果,這里需要稍微注意下:

熱門推薦點擊刷新,將當前的 3 條文章 objectId 以 id|id|id 的格式發送請求,然后重新拉取熱門推薦列表

看抓包,熱門推薦只返回 20 條,刷新一次移除三條,所以簡單處理的話,user_filter_entry 之后直接將熱門推薦數組的前三條移除即可;上面方式更精確,以防服務端之后又有什么返回呢

同時,搜索 tab 的頂部 banner 列表也是在這個頁面預先請求,然后保存到本地。實現如下:

getBannerImgList() {
    const auth = this.data.auth
    wx.request({
      url: `${config.bannerRequestUrl}/get_banner`,
      data: {
        position: 'explore',
        page: 0,
        pageSize: 20,
        platform: 'android',
        device_id: auth.clientId,
        client_id: auth.clientId,
        token: auth.token,
        src: 'android',
      },
      success: (res) => {
        let data = res.data
        if (data.s === 1) {
          let bannerImgList = (data.d && data.d.banner) || []
          wx.setStorage({
            key: 'bannerImgList',
            data: bannerImgList,
          })
        } else {
          wx.showToast({
            title: data.m.toString(),
            icon: 'none',
          })
        }
      },
      fail: () => {
        wx.showToast({
          title: '網路開小差,請稍后再試',
          icon: 'none',
        })
      },
    })
  },

然后配合下拉刷新 onPullDownRefresh 重新獲取數據。

其他具體細節這里不再贅述可查看源碼

搜索 TAB

搜索 tab 頁兩部分組成:頂部的 swiper 和下面的熱門文章列表,這里復用 postItemOne 組件即可。頂部的 swiper 在首頁已經預先獲取過了(見上面),這里直接讀取即可。實現如下:

<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
  <swiper autoplay circular interval="3500" duration="500" wx:if='{{bannerImgList.length}}' style='height:{{swiperHeight}}'>
    <block wx:for="{{bannerImgList}}" wx:key="{{index}}">
      <swiper-item>
        <image src='{{item.screenshot}}' class="banner" mode='widthFix'></image>
      </swiper-item>
    </block>
  </swiper>
  <view class='hot card'>
    <view class='btitle'>
      <view class='l'>
        <image class='icon' src='/img/pin_hot.png'></image>
        <view>熱門文章</view>
      </view>
    </view>
    <recommendItem list='{{rankList}}' graphics='{{false}}'></recommendItem>
  </view>
</view>

沸點 TAB

該頁面復用的組件是 feidianItem,頂部的熱門沸點是使用的 swiper 實現的。

該頁面有一個小細節需要稍微注意下:如果頁面保持在頂部,那么切換 tab 后需要重新刷新獲取新數據,如果頁面已經往下滑動了,那么切換 tab 后就不需要刷新獲取新數據。實現如下:

wxml:

<view class='container'>
  <view class='recommendList'>
    <swiper autoplay='{{false}}' circular='{{false}}' duration="500" wx:if='{{recommendList.length}}' next-margin='100rpx' style='height:{{swiperHeight}}'>
      <block wx:for="{{recommendList}}" wx:key="{{index}}">
        <swiper-item>
          <view class='item'>
            <view class='title' wx:if='{{item.isRecommend}}'>
              <image class='icon' src='/img/ic_topic_star.png'></image>
              <text>編輯推薦</text>
            </view>
            <view class='title' wx:else>
              <image class='icon' src='/img/pin_hot.png'></image>
              <text>熱門沸點</text>
            </view>
            <view class='content'>
              <view class='text'>{{item.content}}</view>
              <view class='img' wx:if='{{item.pictures && item.pictures.length}}'>
                <image mode='aspectFill' src='{{item.pictures[0]}}'></image>
              </view>
            </view>
          </view>
        </swiper-item>
      </block>
    </swiper>
  </view>
  <view class='pinList'>
    <feidianItem item='{{item}}' wx:for='{{list}}' wx:key='{{index}}'></feidianItem>
  </view>
</view>

js:

const config = getApp().globalData.config
const utils = require('../../utils/utils.js')
Page({
  data: {
    COUNT: 30,
    swiperHeight: 'auto',
    recommendList: [],
    list: [],
    auth: {},
    scrollTop: 0,
  },
  onShow () {
    // 如果 scrollTop 為 0,也 reload
    if (utils.pageReload(this.data.auth, [this.data.list]) || !this.data.scrollTop) {
      this.init()
    }
  },
  onPullDownRefresh() {
    this.init()
  },
  init() {
    wx.showLoading({
      title: '數據加載中',
    })
    this.setData({
      auth: {},
    })
    let auth = utils.ifLogined()
    this.setData({
      auth,
    })
    this.initSwiper()
    this.getHotRecommendList()
    this.pinListRecommend(true)
  },
  initSwiper() {
    wx.getSystemInfo({
      success: (res) => {
        this.setData({
          swiperHeight: `${(res.windowWidth || res.screenWidth) / 375 * 135}px`
        })
      },
    })
  },
  // 熱門推薦列表
  getHotRecommendList() {
    const auth = this.data.auth
    wx.request({
      url: `${config.shortMsgMsRequestUrl}/getHotRecommendList`,
      data: {
        uid: auth.uid,
        device_id: auth.clientId,
        client_id: auth.client_id,
        token: auth.token,
        src: 'web',
      },
      success: (res) => {
        let data = res.data
        if (data.s === 1) {
          this.setData({
            recommendList: (data.d && data.d.list) || [],
          })
        } else {
          wx.showToast({
            title: data.m.toString(),
            icon: 'none',
          })
        }
      },
      fail: () => {
        wx.showToast({
          title: '網路開小差,請稍后再試',
          icon: 'none',
        })
      },
    })
  },
  // 沸點列表
  pinListRecommend(reload) {
    const auth = this.data.auth
    let list = this.data.list
    if (utils.isEmptyObject(list) || reload) {
      list = [{ createdAt: '' }]
    }
    let createdAt = (list.slice(-1)[0].createdAt) || ''
    wx.request({
      url: `${config.shortMsgMsRequestUrl}/pinList/recommend`,
      data: {
        uid: auth.uid,
        device_id: auth.clientId,
        token: auth.token,
        src: 'web',
        limit: this.data.COUNT,
        before: createdAt,
      },
      success: (res) => {
        let data = res.data
        if (data.s === 1) {
          wx.hideLoading()
          let list = (data.d && data.d.list) || []
          this.setData({
            list: reload ? list : this.data.list.concat(list),
          })
        } else {
          wx.showToast({
            title: data.m.toString(),
            icon: 'none',
          })
        }
      },
      fail: () => {
        wx.showToast({
          title: '網路開小差,請稍后再試',
          icon: 'none',
        })
      },
    })
  },
  onReachBottom() {
    this.pinListRecommend()
  },
  onPageScroll (e) {
    this.setData({
      scrollTop: e.scrollTop,
    })
  },
  onShareAppMessage(res) {
    return {}
  },
})

小冊 TAB

小冊頁面基本上沒有什么可說的,只有小冊展現。。。組件復用 xiaoceItem,實現如下:

<view class='lists'>
  <xiaoceItem list='{{xiaoceList}}'></xiaoceItem>
</view>

我的 TAB

該頁面以簡單的數目展現(未讀消息條數、收藏集數目、閱讀過的文章數)和跳轉為主,調用相關的 API 即可,沒有什么難度。實現如下:

wxml:

<view class='wrapper'>
  <view class='card profile'  catchtap='navigatItem' data-url='/pages/personal/personal'>
    <view class='info'>
      <image class='avatar' src='{{userInfo.avatarLarge}}' wx:if='{{userInfo.avatarLarge}}'></image>
      <image class='avatar' src='/img/empty_avatar_user.png' wx:else></image>
      <view class='text'>
        <view class='name'>{{userInfo.username || '登錄/注冊'}}</view>
        <view>{{userInfo.jobTitle || '添加職位'}} @ {{userInfo.company || '添加公司'}}</view>
      </view>
    </view>
    <view class='more'>
      <view class='reddot' wx:if='{{auth && !userInfo.company}}'></view>
      <image src='/img/profile_arrow.png'></image>
    </view>
  </view>

  <view class='card items'>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/infoCenter/infoCenter'>
      <view class='title'>
        <image src='/img/ic_notification.png'></image>
        <view>消息中心</view>
      </view>
      <view class='count reddot' wx:if='{{userNotificationNum}}'>{{userNotificationNum}}</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/favorate/favorate'>
      <view class='title'>
        <image src='/img/ic_heart_entry_bottom_full.png'></image>
        <view>我喜歡的</view>
      </view>
      <view class='count'>{{userInfo.collectedEntriesCount || 0}}篇</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/collectionSet/collectionSet'>
      <view class='title'>
        <image src='/img/ic_collection_set.png'></image>
        <view>收藏集</view>
      </view>
      <view class='count'>{{userInfo.collectionSetCount || 0}}個</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/purchasedXiaoce/purchasedXiaoce'>
      <view class='title'>
        <image src='/img/user_buy.png'></image>
        <view>已購小冊</view>
      </view>
      <view class='count'>{{userInfo.purchasedBookletCount
 || 0}}本</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/myPins/myPins?liked=1'>
      <view class='title'>
        <image src='/img/user_liked_pin.png'></image>
        <view>贊過的沸點</view>
      </view>
      <view class='count'>{{userInfo.likedPinCount || 0}}個</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/readHistory/readHistory'>
      <view class='title'>
        <image src='/img/view.png'></image>
        <view>閱讀過的文章</view>
      </view>
      <view class='count'>{{userInfo.viewedEntriesCount || 0}}篇</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/manageTag/manageTag'>
      <view class='title'>
        <image src='/img/tag.png'></image>
        <view>標簽管理</view>
      </view>
      <view class='count'>{{userInfo.subscribedTagsCount || 0}}個</view>
    </view>
  </view>

  <view class='card items'>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/feedback/feedback' data-open='true'>
      <view class='title'>
        <image src='/img/icon_feed_back.png'></image>
        <view>意見反饋</view>
      </view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/setting/setting' data-open='true'>
      <view class='title'>
        <image src='/img/settings.png'></image>
        <view>設置</view>
      </view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/miniqrcode/miniqrcode' data-open='true'>
      <view class='title'>
        <image src='/img/qrcode.png' style='width:28rpx;height:28rpx;padding:10rpx'></image>
        <view>小程序碼</view>
      </view>
    </view>
  </view>

</view>

js:

const utils = require('../../utils/utils.js')
const config = getApp().globalData.config
Page({
  data: {
    userInfo: {},
    userNotificationNum: 0,
    auth: {},
  },
  onShow () {
    let auth = utils.ifLogined()
    this.setData({
      auth,
    })
    if (auth) {
      this.getUserInfo()
      this.userNotificationNum()
    } else {
      this.setData({
        userInfo: {},
        userNotificationNum: 0,
      })
    }
  },
  navigatItem (e) {
    return utils.navigatItem(e)
  },
  // 獲取用戶信息
  getUserInfo() {
    const auth = this.data.auth
    wx.request({
      url: `${config.apiRequestUrl}/getUserInfo`,
      data: {
        src: 'web',
        device_id: auth.clientId,
        uid: auth.uid,
        token: auth.token,
        current_uid: auth.uid,
      },
      success: (res) => {
        let data = res.data
        if (data.s === 1) {
          this.setData({
            userInfo: data.d,
          })
        } else {
          wx.showToast({
            title: data.m.toString(),
            icon: 'none',
          })
        }
      },
      fail: () => {
        wx.showToast({
          title: '網路開小差,請稍后再試',
          icon: 'none',
        })
      },
    })
  },
  // 消息中心消息條數
  userNotificationNum() {
    const auth = this.data.auth
    wx.request({
      url: `${config.notifyRequestUrl}/getUserNotificationNum`,
      data: {
        src: 'web',
        uid: auth.uid,
        token: auth.token,
      },
      success: (res) => {
        let data = res.data
        if (data.s === 1) {
          this.setData({
            userNotificationNum: data.d && data.d.notification_num,
          })
        } else {
          wx.showToast({
            title: data.m.toString(),
            icon: 'none',
          })
        }
      },
      fail: () => {
        wx.showToast({
          title: '網路開小差,請稍后再試',
          icon: 'none',
        })
      },
    })
  },
})

文章詳情頁

文章詳情頁返回的數據是整篇文章的 html 格式,如果是瀏覽器的話,直接顯示即可,小程序里這里使用的是 wxParse 解析的。實現如下:

<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<import src="../../wxParse/wxParse.wxml"/>
<view class='container'>
  <image wx:if='{{postInfo.screenshot}}' style='width:100%;height:340rpx;' mode='aspectFill' src='{{postInfo.screenshot}}'></image>
  <view class='content'>
    <view class='user'>
      <view class='avatar' catchtap='toPersonal'>
        <image mode='aspectFill' src='{{(postInfo.user && postInfo.user.avatarLarge) || "/img/default_avatar.png"}}'></image>
      </view>
      <view class='info'>
        <view class='name'>{{postInfo.user && postInfo.user.username}}</view>
        <view class='others'>
          <text class='time'>{{postInfo.createdAt}}</text>
          <text>閱讀 {{postInfo.viewsCount}}</text>
        </view>
      </view>
    </view>
    <view class='title'>{{postInfo.title}}</view>
    <template is="wxParse" data="{{wxParseData:article.nodes}}"/>
  </view>
</view>

至於評論相關的,還沒有寫。。。

個人中心頁

個人中心頁和我的頁面展現差不多,也是顯示條目和頁面跳轉為主,調用相關的 API 即可,不再贅述。實現如下:

<view class='wrapper'>
  <view class='card profile'>
    <view class='info'>
      <image class='avatar' mode='aspectFill' src='{{userInfo.avatarLarge}}' wx:if='{{userInfo.avatarLarge}}'></image>
    <image class='avatar' src='/img/empty_avatar_user.png' wx:else></image>
      <view class='text'>
        <view class='name'>{{userInfo.username}}</view>
        <view class='jobtitle'>{{userInfo.jobTitle}}</view>
        <view class='others'>{{userInfo.selfDescription }}</view>
      </view>
    </view>
    <view class='bottom'>
      <view class='l'>
        <view class='action'>
          <view>{{userInfo.followeesCount}}</view>
          <view class='key'>關注</view>
        </view>
        <view class='action'>
          <view>{{userInfo.followersCount}}</view>
          <view class='key'>關注者</view>
        </view>
      </view>
      <!-- <view class='edit'>編輯</view> -->
      <image src='/img/ic_dynamic_vote.png' class='trend' catchtap='showDataTrend'></image>
    </view>
  </view>

  <view class='card items'>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/dynamic/dynamic?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>動態</view>
      </view>
    </view>
  </view>

  <view class='card items'>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/myPins/myPins?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>沸點</view>
      </view>
      <view class='count'>{{userInfo.pinCount || 0}}</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/originalPost/originalPost?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>原創文章</view>
      </view>
      <view class='count'>{{userInfo.postedPostsCount}}</view>
    </view>
    <view class='item' wx:if='{{userInfo.postedEntriesCount}}' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/sharePost/sharePost?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>分享文章</view>
      </view>
      <view class='count'>{{userInfo.postedEntriesCount}}</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/collectionSet/collectionSet?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>收藏集</view>
      </view>
      <view class='count'>{{userInfo.collectionSetCount}}</view>
    </view>
  </view>

  <view class='card items'>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/favorate/favorate?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>喜歡的文章</view>
      </view>
      <view class='count'>{{userInfo.collectedEntriesCount}}</view>
    </view>
    <view class='item' hover-class='hover-class' catchtap='navigatItem' data-url='/pages/subscribedTag/subscribedTag?thirduid={{thirduid}}' data-open='true'>
      <view class='title'>
        <view>關注的標簽</view>
      </view>
      <view class='count'>{{userInfo.subscribedTagsCount}}</view>
    </view>
  </view>

  <view class='card items'>
    <view class='item' wx:if='{{userInfo.community && userInfo.community.weibo && userInfo.community.weibo.username}}'>
      <view class='title'>
        <image src='/img/icon_profile_weibo.png'></image>
        <view class='val'>{{userInfo.community.weibo.username}}</view>
      </view>
    </view>
    <view class='item' wx:if='{{userInfo.blogAddress}}'>
      <view class='title'>
        <image src='/img/icon_profile_blog.png'></image>
        <view class='val'>{{userInfo.blogAddress}}</view>
      </view>
    </view>
  </view>

</view>

文章數據頁

文章數據頁就是顯示你的文章獲得了多少收藏、多少評論、多少閱讀相關的數據,該頁面主要是數字滾動動畫的實現。這里的實現思路是這樣的:

將數字從 0 到 N 縱向排列,然后 translateY 到相應的數字即可,主要的實現如下:

<view class='countInner' style='transform:translateY(-{{100*(item.length-1)/item.length}}%)' wx:for='{{filters.strToNumArr(userInfo.totalCollectionsCount)}}' wx:key='{{index}}' wx:for-item='item' wx:for-index='index'>
    <view wx:for='{{item}}' wx:key='{{idx}}' wx:for-item='i' wx:for-index='idx'>{{i}}</view>
</view>

// 按照長度生成 0 字符串
generateZeroArr (len) {
  Array.apply(null, Array(len)).map(function (item, i) {
    return 0
  })
}

其他細節不再贅述,可查看源碼

消息中心頁

消息中心有兩個 tab,可以點擊、滑動切換,這里的實現是 swiper。這里需要注意的是:消息分為不同的 category,不同的 category 展現的內容是不一樣的,所以這里需要區分下,目前我獲取到的 category 只有幾種,是否全部覆蓋所有的 category 只能等遇到沒有覆蓋的時候隨手補上了。。。實現如下:

<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
  <view class='top tabs'>
    <view class='inner'>
      <view class='tab {{currentSwiper === "0" ? "active" : ""}}' data-index='0' catchtap='switchSwiper'>用戶消息</view>
      <view class='tab {{currentSwiper === "1" ? "active" : ""}}' data-index='1' catchtap='switchSwiper'>系統消息</view>
    </view>
    <view class='bar' style='left:{{currentSwiper*50}}%'></view>
  </view>
  <swiper class='swiper' autoplay='{{false}}' indicator-dots='{{false}}' bindchange='swiperChanged' current='{{currentSwiper}}'>
    <swiper-item item-id='0'>
      <scroll-view scroll-y bindscrolltolower='getMoreUserNotification'>
        <view class='item' wx:for='{{list}}' wx:key='{{index}}'>
          <view class='avatar' data-id='{{item.users[0].objectId}}' catchtap='toPersonal'>
            <image mode='aspectFill' src='{{item.users[0].avatarLarge || "/img/default_avatar.png"}}'></image>
          </view>
          <view class='content' wx:if='{{item.category==="collection"}}'>
            <view>{{item.users[0].username}}等{{item.count}}人 喜歡了你的文章 <text data-id='{{item.entry && item.entry.objectId}}' catchtap='toPostDetail'>{{item.entry && item.entry.title}}</text></view>
            <view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
          </view>
          <view class='content' wx:elif='{{item.category==="comment"}}'>
            <view>{{item.users[0].username}}回復了你在文章 <text data-id='{{item.entry && item.entry.objectId}}' catchtap='toPostDetail'>{{item.entry && item.entry.title}}</text> 的評論</view>
            <view class='comment'>{{(item.reply && item.reply.content) || (item.comment && item.comment.content)}}</view>
            <view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
          </view>
          <view class='content' wx:elif='{{item.category==="follow"}}'>
            <view>{{item.users[0] && item.users[0].username}} 關注了你</view>
            <view class='time'>{{item.users[0] && item.users[0].jobTitle}}</view>
          </view>
          <view class='content' wx:elif='{{item.category==="comment-like"}}'>
            <view>{{item.users[0] && item.users[0].username}} 贊了你在 <text>{{item.entry && item.entry.title}}</text> 的評論</view>
          </view>
          <view class='content' wx:elif='{{item.category==="pin-like"}}'>
            <view>{{item.users[0] && item.users[0].username}} 贊了你的 <text>沸點</text></view>
            <view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
          </view>
          <view class='content' wx:elif='{{item.category==="pin-comment"}}'>
            <view>{{item.users[0] && item.users[0].username}} 回復了你的 <text>沸點</text></view>
            <view class='comment'>{{(item.reply && item.reply.content) || (item.pinComment && item.pinComment.content)}}</view>
            <view class='time'>{{filters.timeBefore(item.updatedAtString)}}</view>
          </view>
          <view class='content' wx:else>未知狀態,可提交給開發者</view>
        </view>
      </scroll-view>
      <empty wx:if='{{!list.length}}' tip='暫無消息'></empty>
    </swiper-item>
    <swiper-item item-id='1'>
      <view wx:if='{{systemInfoList.length}}'>
        不好意思,我沒有系統消息,所以看不到系統消息 API 的數據結構,也看不到樣式。。。
      </view>
      <empty wx:if='{{!systemInfoList.length}}' tip='暫時沒有系統通知'></empty>
    </swiper-item>
  </swiper>
</view>

動態頁

動態頁也需要稍微注意一點,動態頁分不同的 category,這里覆蓋的有 followcollectionsubscribe,如果遇到未覆蓋的只能順手補上。實現如下:

<wxs module='filters' src='../../filter/filter.wxs'></wxs>
<view class='container'>
  <view class='item' wx:for='{{list}}' wx:key='{{index}}'>
    <view class='inner' wx:if='{{item.category === "follow"}}'>
      <view class='top'>
        <view class='l'>
          <image mode='widthFix' src='/img/ic_dynamic_user.png'></image>
          <view>關注了:</view>
        </view>
        <view class='r'>{{filters.timeBefore(item.createdAtString)}}</view>
      </view>
      <view class='bottom'>
        <view class='tagcard'>
          <image mode='aspectFill' src='{{item.users[0].avatarLarge || "/img/entry_image_default.png"}}'></image>
          <view class='others'>
            <view class='title'>{{item.users && item.users[0].username}}</view>
            <view class='info' wx:if='{{item.users && item.users[0].jobTitle && item.users[0].company}}'>{{item.users && item.users[0].jobTitle}} @ {{item.users && item.users[0].company}}</view>
          </view>
        </view>
      </view>
    </view>
    <view class='inner' wx:if='{{item.category === "collection"}}'>
      <view class='top'>
        <view class='l'>
          <image mode='widthFix' src='/img/ic_dynamic_collect.png'></image>
          <view>喜歡了:</view>
        </view>
        <view class='r'>{{filters.timeBefore(item.createdAtString)}}</view>
      </view>
      <view class='bottom'>
        <view class='tagcard' data-id='{{item.entry && item.entry.objectId}}' catchtap='toPostDetail'>
          <image mode='aspectFill' src='{{item.entry.screenshotUrl  || "/img/entry_image_default.png"}}'></image>
          <view class='others'>
            <view class='title'>{{item.entry && item.entry.title}}</view>
          </view>
        </view>
      </view>
    </view>
    <view class='inner' wx:if='{{item.category === "subscribe"}}'>
      <view class='top'>
        <view class='l'>
          <image mode='widthFix' src='/img/ic_dynamic_tag.png'></image>
          <view>關注了 {{item.tags && item.tags[0].title}} 等 {{item.tags && item.tags.length}} 個標簽</view>
        </view>
        <view class='r'>{{filters.timeBefore(item.createdAtString)}}</view>
      </view>
      <view class='bottom'>
        <view class='imglist'>
          <image mode='aspectFill' wx:for='{{item.tags}}' wx:key='{{idx}}' wx:for-index='idx' wx:for-item='i' src='{{i.icon}}'></image>
        </view>
      </view>
    </view>
  </view>
</view>

標簽管理頁

這個頁面的 tab 也是用 swiper 實現的,復用的組件是 tagItem。實現如下:

<view class='container'>
  <view class='top tabs'>
    <view class='inner'>
      <view class='tab {{currentSwiper === "0" ? "active" : ""}}' data-index='0' catchtap='switchSwiper'>已關注標簽</view>
      <view class='tab {{currentSwiper === "1" ? "active" : ""}}' data-index='1' catchtap='switchSwiper'>所有標簽</view>
    </view>
    <view class='bar' style='left:{{currentSwiper*50}}%'></view>
  </view>
  <swiper class='swiper' autoplay='{{false}}' indicator-dots='{{false}}' bindchange='swiperChanged' current='{{currentSwiper}}'>
    <swiper-item item-id='0'>
      <view>
        <tagItem list='{{tagList}}'></tagItem>
      </view>
      <empty wx:if='{{!tagList.length}}' tip='暫無消息'></empty>
    </swiper-item>
    <swiper-item item-id='1'>
      <scroll-view scroll-y bindscrolltolower='getMoreRecommendTags'>
        <view class='hot' wx:if='{{hotTagList.length}}'>
          <view class='title'>推薦標簽</view>
          <tagItem list='{{hotTagList}}'></tagItem>
        </view>
        <view class='suggest' wx:if='{{recommendTagList.length}}'>
          <view class='title'>你可能感興趣的標簽</view>
          <tagItem list='{{recommendTagList}}'></tagItem>
        </view>
      </scroll-view>
      <empty wx:if='{{!hotTagList.length && recommendTagList.length}}' tip='暫時沒有系統通知'></empty>
    </swiper-item>
  </swiper>
</view>

原創文章頁、喜歡的文章頁、閱讀過的文章頁、贊過的沸點頁、關注的標簽頁

這幾個頁面都是組件的復用,沒有太多要說的。

意見反饋頁、設置頁

這兩個頁面只是一個關於頁面而已。。。

完成度

APP 里面的東西實在是不少,包括頁面和交互,要完全照抄實現確實需要一些時間和精力,UI 之類的都是簡單測量+肉眼調試實現的,下面列出頁面和交互的完成度,這里應該只是列出了絕大部分(還是上面那句話,APP 里面的東西實在是不少),未列出、未實現的后續會根據時間、精力來實現。

實際完成度請以代碼為主(線上小程序也會持續更新)。

頁面完成度

貌似不支持 markdown 待辦事宜寫法?QAQ

  • [x] 啟動頁
  • [x] 登錄、未登錄跳轉邏輯和頁面數據刷新邏輯等
  • [x] HOME、搜索、沸點、小冊 TAB 涉及到的上拉、下拉刷新
  • [x] POST、ENTRY(文章類型不同) 詳情頁
  • [ ] HOME TAB
    • [x] 首頁
      • [x] 熱門推薦
      • [x] 下部列表
    • [ ] 標簽展示相關
  • [ ] 搜索 TAB
    • [x] 頂部輪播
    • [x] 熱門文章
    • [ ] 搜索功能相關
    • [ ] 本周最熱
    • [ ] 收藏集
      • [ ] ...
    • [ ] 活動
      • [ ] ...
  • [ ] 沸點 TAB
    • [ ] 推薦
      • [x] 頂部熱門沸點
      • [x] 沸點列表
      • [x] 沸點詳情
    • [ ] 話題
    • [ ] 動態
    • [ ] 發布沸點
  • [ ] 小冊 TAB
    • [x] 小冊列表
    • [ ] 小冊詳情
  • [ ] 我的 TAB
    • [ ] 個人主頁
      • [x] 文章數據
      • [ ] 編輯
      • [ ] 關注、被關注列表
      • [x] 動態頁
      • [x] 沸點頁
      • [x] 原創文章頁
      • [x] 收藏集
        • [ ] 收藏集詳情頁
      • [x] 喜歡的文章
      • [x] 關注的標簽
        • [ ] 標簽詳情頁
    • [x] 我喜歡的
    • [x] 收藏集
    • [ ] 已購小冊
    • [x] 贊過的沸點
    • [x] 閱讀過的文章
    • [x] 標簽管理
      • [x] 已關注標簽
      • [x] 所有標簽
        • [x] 推薦標簽
        • [x] 所有標簽
    • [ ] 夜間模式
    • [x] 意見反饋(和官方 APP 有差異,這里是個簡單的關於頁)
    • [ ] 設置
      • [ ] ...
  • [x] 登錄頁
  • [ ] 注冊頁
  • [ ] 修改密碼頁
  • [x] 其他完成部分...
  • [ ] 未完待續部分...

交互完成度

評論、留言、關注、添加到收藏集、喜歡、發表沸點等暫時均沒有實現,因為 APP 里面的東西實在是不少......

  • [ ] 評論
  • [ ] 留言
  • [ ] 關注
  • [ ] 喜歡
  • [ ] 未完待續部分...

說明

  • 1、話說掘金的 API 域名(二級)真是多啊,小程序后台域名白名單最多只能配 20 個,現在已經占了 16 個了,感覺要完整抄完實現掘金 APP 版小程序,配額不夠啊。不行的話,就只能搭個 server 代理了;
  • 2、個別接口只有 APP 用到了,請求字段需要按照 web 的略作調整;個別接口也要設置對應的 header
  • 3、文章詳情頁返回的是整片文章的 html 格式的 content,這里使用的是開源的 wxParse 進行富文本解析;
  • 4、由於小程序的限制,第三方的 url 不能在 webview 中打開,所以文章里面的外鏈能點開算我輸;
  • 5、開發時,個別細節需要稍微注意,比如:沸點 tab 頁,如果已經滑到了頂部,onShow 獲取新數據,否則,不刷新;未登錄時,首頁 APP 調用的 API 是 get_recommended_entry就是懶為了方便小程序里仍然使用 get_entry_by_timeline;其他的不一一贅述,詳情可 查看源碼
  • 6、由於賬號權限等問題有些 API 需要天時地利人和,部分 API 返回的數據格式沒有拿到,所以對應的頁面也沒有寫,比如:系統消息頁面(最近一直沒有系統消息)等;
  • 7、部分數據可能未完全覆蓋,比如:用戶消息這塊,目前列舉出的 categorycollectioncommentfollowcomment-likepin-likepin-comment,可能還會有其他消息類型,遇到了會一一補上;還有動態頁,也是同樣的問題;可能還有其他沒有完全覆蓋的數據;
  • 8、點擊某些文章進入詳情頁會提示 illegal token,亦或文章沒有正常顯示出來,應該是請求參數需要略作調整,或者文章類型需要判斷。類似這樣的小問題,后續會調整補充;
  • 9、小程序(非小游戲)在於一個 字,應該是一個應用的濃縮精華版,而不應該是一個內容豐富多彩的 APP100% 的復制版,這樣會顯得比較臃腫,此處應該有 but,該小程序僅僅是出於學習交流的目的,所以這個問題不在我們的考慮范圍內;
  • 10、登錄現在只能手機號登錄,郵箱登錄給忘記了,回頭一並加上;
  • 11、我也是有人生夢想的人;

轉載請注明出處



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM