reghao 4 лет назад
Родитель
Сommit
43c78f2e9a

+ 33 - 0
src/api/config/api-config.js

@@ -0,0 +1,33 @@
+const key_config = {
+  app_key: '3436445128',
+  app_secret: '6a1d41d969da490d9a37d4457184ea0b',
+  redirect_uri: 'http://127.0.0.1:8089/splash'
+}
+
+const host_config = {
+  local: 'http://127.0.0.1:8089/',
+  host: 'https://pixel-api.herokuapp.com/',
+  oauth: 'https://open.weibo.cn/oauth2/authorize'
+}
+
+const api_router_config = {
+  oauth_post: '/oauth2/access_token',
+  home_timeline: '/statuses/home_timeline.json',
+  public_timeline: '/statuses/public_timeline.json',
+  my_content: '/statuses/user_timeline.json',
+  userinfo: '/users/show.json',
+  content_comments: '/comments/show.json',
+  send_post_text: '/statuses/update.json',
+  send_post_image: '/statuses/upload.json',
+  at_me_statue: '/statuses/mentions.json',
+  at_me_comment: '/comments/mentions.json',
+  receive_comment: '/comments/to_me.json',
+  send_comment: '/comments/by_me.json',
+  my_follower: '/friendships/followers.json',
+  my_friend: '/friendships/friends.json'
+}
+
+export const HOST_CONCIG = host_config
+export const KEY_CONFIG = key_config
+export const API_ROUTER_CONFIG = api_router_config
+export const DEBUG = true

+ 42 - 0
src/api/impl/my-content.js

@@ -0,0 +1,42 @@
+import axios from 'axios'
+import { HOST_CONCIG, API_ROUTER_CONFIG, DEBUG } from '@/api/config/api-config'
+import { logger } from '@/utils/logger'
+import store from '../../store/'
+import * as data from '../../assets/debug-data/getData'
+
+export const getMyContent = (page, okCallback, errorCallback) => {
+  if (DEBUG) {
+    setTimeout(function() {
+      okCallback(data.metimelime)
+    }, 1500)
+    return
+  }
+
+  const accesstoken = store.getters.token.access_token
+
+  var request_data = {
+    access_token: accesstoken,
+    count: 30,
+    page: page
+  }
+
+  var config = {
+    method: 'get',
+    url: API_ROUTER_CONFIG.my_content,
+    baseURL: HOST_CONCIG.host,
+    params: request_data,
+    headers: {
+      'Content-Type': 'application/json'
+    }
+  }
+
+  axios(config)
+    .then(function(response) {
+      logger('oauthPost-ok', 'getMyContent response succeed')
+      okCallback(response.data)
+    })
+    .catch(function(error) {
+      console.log(error)
+      errorCallback(error)
+    })
+}

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/debug-data/data/content.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/debug-data/data/friendship.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
src/assets/debug-data/data/notify.js


Разница между файлами не показана из-за своего большого размера
+ 5409 - 0
src/assets/debug-data/data/status.js


+ 95 - 0
src/assets/debug-data/data/userInfo.js

@@ -0,0 +1,95 @@
+export const userInfo = {
+    "id": 3024601477,
+    "idstr": "3024601477",
+    "class": 1,
+    "screen_name": "wanbo-",
+    "name": "wanbo-",
+    "province": "11",
+    "city": "1000",
+    "location": "北京",
+    "description": "我想做一名音乐人。",
+    "url": "",
+    "profile_image_url": "http://tvax3.sinaimg.cn/crop.0.0.750.750.50/b447c185ly8fdocwsavc6j20ku0kuaba.jpg",
+    "cover_image_phone": "http://ww1.sinaimg.cn/crop.0.0.640.640.640/6ce2240djw1e9snuksa5nj20hs0hs78b.jpg",
+    "profile_url": "singerwannber",
+    "domain": "singerwannber",
+    "weihao": "",
+    "gender": "m",
+    "followers_count": 675,
+    "friends_count": 397,
+    "pagefriends_count": 1,
+    "statuses_count": 192,
+    "favourites_count": 91,
+    "created_at": "Sun Feb 17 22:51:23 +0800 2013",
+    "following": false,
+    "allow_all_act_msg": false,
+    "geo_enabled": true,
+    "verified": false,
+    "verified_type": 220,
+    "remark": "",
+    "insecurity": {
+        "sexual_content": false
+    },
+    "status": {
+        "created_at": "Mon Jun 26 00:28:34 +0800 2017",
+        "id": 4122684797467015,
+        "mid": "4122684797467015",
+        "idstr": "4122684797467015",
+        "text": "前排举手🙋我是你的小迷弟。🤗",
+        "source_allowclick": 0,
+        "source_type": 1,
+        "source": "<a href=\"http://app.weibo.com/t/feed/3jskmg\" rel=\"nofollow\">iPhone 6s</a>",
+        "favorited": false,
+        "truncated": false,
+        "in_reply_to_status_id": "",
+        "in_reply_to_user_id": "",
+        "in_reply_to_screen_name": "",
+        "pic_urls": [],
+        "geo": null,
+        "annotations": [
+            {
+                "mapi_request": true
+            }
+        ],
+        "reposts_count": 0,
+        "comments_count": 0,
+        "attitudes_count": 0,
+        "isLongText": false,
+        "mlevel": 0,
+        "visible": {
+            "type": 0,
+            "list_id": 0
+        },
+        "biz_feature": 0,
+        "hasActionTypeCard": 0,
+        "darwin_tags": [],
+        "hot_weibo_tags": [],
+        "text_tag_tips": [],
+        "userType": 0,
+        "positive_recom_flag": 0,
+        "gif_ids": "",
+        "is_show_bulletin": 2
+    },
+    "ptype": 0,
+    "allow_all_comment": true,
+    "avatar_large": "http://tvax3.sinaimg.cn/crop.0.0.750.750.180/b447c185ly8fdocwsavc6j20ku0kuaba.jpg",
+    "avatar_hd": "http://tvax3.sinaimg.cn/crop.0.0.750.750.1024/b447c185ly8fdocwsavc6j20ku0kuaba.jpg",
+    "verified_reason": "",
+    "verified_trade": "",
+    "verified_reason_url": "",
+    "verified_source": "",
+    "verified_source_url": "",
+    "follow_me": false,
+    "online_status": 0,
+    "bi_followers_count": 163,
+    "lang": "zh-cn",
+    "star": 0,
+    "mbtype": 0,
+    "mbrank": 0,
+    "block_word": 0,
+    "block_app": 0,
+    "credit_score": 80,
+    "user_ability": 1024,
+    "urank": 28,
+    "story_read_state": -1
+}

+ 32 - 0
src/assets/debug-data/getData.js

@@ -0,0 +1,32 @@
+import * as user from './data/userInfo'
+import * as status from './data/status'
+import * as notify from './data/notify'
+import * as friendship from './data/friendship'
+import * as content from './data/content'
+
+
+var userInfo = user.userInfo
+var hometimeline = status.hometimeline
+var publictimeline = status.publictimeline
+var metimelime = status.metimeline
+var atmestatus = notify.atmestatus
+var atmecomment = notify.atmecomment
+var receivecomment = notify.receivecomment
+var sendcomment = notify.sendcomment
+var myfollowers = friendship.myfollowers
+var myfriends = friendship.myfriends
+var contentcomment = content.comment
+
+export {
+    userInfo,
+    hometimeline,
+    publictimeline,
+    metimelime,
+    atmestatus,
+    atmecomment,
+    receivecomment,
+    sendcomment,
+    myfollowers,
+    myfriends,
+    contentcomment
+}

+ 182 - 0
src/components/DetailContent.vue

@@ -0,0 +1,182 @@
+<template lang="html">
+    <div class="detail">
+        <div class="detail-header">
+            <div class="header-close" v-on:click="goBack">
+                <svg viewBox="0 0 46 72" style="display: inline-block; fill: currentcolor; height: 100%; width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M40 33H15.243l7.878-7.879a2.998 2.998 0 0 0 0-4.242 2.998 2.998 0 0 0-4.242 0l-13 13a2.998 2.998 0 0 0 0 4.242l13 13c.585.586 1.353.879 2.121.879s1.536-.293 2.121-.879a2.998 2.998 0 0 0 0-4.242L15.243 39H40a3 3 0 1 0 0-6z"></path></g></svg>
+            </div>
+            <span class="header-title">正文</span>
+        </div>
+        <div class="detail-content">
+             <pixel-content :x="detail_content"></pixel-content>
+        </div>
+        <nav class="detail-switch-tab">
+            <span class="detail-switch-tag" >评论</span>
+            <span class="detail-switch-tag"></span>
+             <span class="detail-switch-tag"></span>
+        </nav>
+        <div id="comment">
+            <div class="content-comment">
+                <div class="commentlist" v-for="comment in list">
+                    <pixel-comment :comment="comment"></pixel-comment>
+                </div>
+                <div class="refresh-footer" v-if="option.refresh">
+                    <pixel-spinner :size="'45px'" :color="'#007AFF'"></pixel-spinner>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+ 
+<script>
+import { mapActions, mapGetters } from 'vuex'
+import * as StringUtils from '../utils/string-utils'
+export default {
+    name: "detail-content",
+    computed: {
+        ...mapGetters({
+            detail_content: 'detail_content',
+            comments: 'comments',
+            option: 'comments_option'
+        })
+    },
+    watch: {
+        option: {
+            handler: function (val, oldVal) {
+                if (val && val.page == 1) {
+                    this.list = []
+                }
+            },
+            deep: true
+        },
+        comments: function (val, oldVal) {
+            if (val) {
+                if (this.option.page == 1) {
+                    this.list = val;
+                } else {
+                    this.list = [...this.list, ...val]
+                }
+            }
+        }
+    },
+    activated() {
+        this.contentComments(1)
+        window.addEventListener('scroll', this.scrollBar)
+    },
+    deactivated() {
+        window.removeEventListener('scroll', this.scrollBar)
+    },
+    methods: {
+        ...mapActions([
+            'getContentComments'
+        ]),
+        goBack() {
+            this.$router.go(-1)
+        },
+        formatNum(num) {
+            return StringUtils.formatNum(num)
+        },
+        contentComments(page) {
+            this.getContentComments(
+                {
+                    id: this.detail_content.id,
+                    page: page
+                }
+            )
+        },
+        loadMore() {
+            let vue = this
+            vue.option.refresh = true
+            var page = vue.option.page + 1
+            vue.contentComments(page)
+        },
+        scrollBar() {
+            var a = document.documentElement.scrollTop == 0 ? document.body.clientHeight : document.documentElement.clientHeight;
+            var b = document.documentElement.scrollTop == 0 ? document.body.scrollTop : document.documentElement.scrollTop;
+            var c = document.documentElement.scrollTop == 0 ? document.body.scrollHeight : document.documentElement.scrollHeight;
+            if (a + b == c) {
+                this.loadMore()
+            }
+        }
+    }
+}
+</script>
+ 
+<style lang="css">
+.detail {
+    width: 100%;
+    height: 100%;
+}
+
+.detail-header {
+    width: 100%;
+    height: 4rem;
+    background-color: #ffffff;
+    position: fixed;
+    top: 0;
+    z-index: 150;
+    overflow: auto;
+    display: flex;
+    flex-flow: row;
+    box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+}
+
+.detail-header .header-close {
+    color: #007AFF;
+    width: 2rem;
+    height: 2rem;
+    margin: 1rem;
+    z-index: 150;
+}
+
+.detail-header .header-title {
+    height: 4rem;
+    line-height: 4rem;
+    margin-left: 1rem;
+    font-weight: 800;
+    color: #5d5d5d;
+}
+
+.detail-content {
+    margin-top: 4rem;
+    background-color: #ffffff;
+    padding: 1rem;
+}
+
+.detail-switch-tab {
+    width: 100%;
+    height: 3.8rem;
+    background: #FFFFFF;
+    border: 1px solid rgba(0, 0, 0, .05);
+    display: flex;
+    flex-flow: row;
+}
+
+.detail-switch-tab .detail-switch-tag {
+    padding-left: 1rem;
+    font-weight: 600;
+    line-height: 3.8rem;
+    color: #007AFF;
+    font-size: 1.3rem;
+    border-bottom: 2px solid #007AFF;
+}
+
+.detail-switch-tab .detail-num {
+    font-weight: 400;
+    color: #007AFF;
+    font-size: 1rem;
+    margin-left: .3rem;
+}
+
+.content-comment .commentlist {
+    flex: 1;
+    background-color: #fff;
+    border-radius: 2px;
+    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
+}
+
+.content-comment .refresh-footer {
+    margin-bottom: .8rem;
+    margin-top: .8rem;
+    text-align: center;
+}
+</style>

+ 261 - 0
src/components/status/PixelContent.vue

@@ -0,0 +1,261 @@
+<template lang="html">
+  <div class="content" @click="goDetailContent">
+    <div class="list-header">
+      <img v-if="x.user" class="avatar" :src="x.user.avatar_large">
+      <div class="user-info">
+        <h3 v-if="x.user" class="user-name">{{ x.user.name }}</h3>
+        <span class="user-source" v-html="x.source" />
+      </div>
+      <span class="user-time">{{ formatTime(x.created_at) }}</span>
+    </div>
+    <div class="list-content">
+      <span class="content-text" v-html="formatContent(x.text)" />
+      <div class="content-img">
+        <ul class="content-img-ul clear-fix">
+          <li v-for="y in x.pic_urls" :key="y" class="img-li-default" :class="imgClass(x.pic_urls.length)">
+            <div class="img-div" :style="{backgroundImage:'url(' + formatThumbImg(y.thumbnail_pic) + ')'}" @click.stop="imageZoom(y.thumbnail_pic)" />
+          </li>
+        </ul>
+      </div>
+      <div v-if="x.retweeted_status" class="content-re-content">
+        <span
+          class="re-content-text"
+          v-html="formatContent( '@' + x.retweeted_status.user.name + ': '
+            + x.retweeted_status.text)"
+        />
+        <div v-if="x.retweeted_status.pic_urls" class="content-img">
+          <ul class="content-img-ul clear-fix">
+            <li v-for="z in x.retweeted_status.pic_urls" :key="z" class="img-li-default" :class="imgClass(x.retweeted_status.pic_urls.length)">
+              <div
+                class="img-div"
+                :style="{backgroundImage:'url(' + formatThumbImg(z.thumbnail_pic) + ')'}"
+                @click.stop="imageZoom(z.thumbnail_pic)"
+              />
+            </li>
+          </ul>
+        </div>
+      </div>
+    </div>
+    <div class="list-footer">
+      <div class="footer-tag">
+        <svg viewBox="0 0 62 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M41 31h-9V19a2.999 2.999 0 0 0-4.817-2.386l-21 16a3 3 0 0 0-.001 4.773l21 16a3.006 3.006 0 0 0 3.15.301A2.997 2.997 0 0 0 32 51V39h9c5.514 0 10 4.486 10 10a4 4 0 0 0 8 0c0-9.925-8.075-18-18-18z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.reposts_count) }}</span>
+      </div>
+      <div class="footer-tag">
+        <svg class="" viewBox="0 0 74 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M70.676 36.644A3 3 0 0 0 68 35h-7V19a4 4 0 0 0-4-4H34a4 4 0 0 0 0 8h18a1 1 0 0 1 1 .998V35h-7a3.001 3.001 0 0 0-2.419 4.775l11 15a3.003 3.003 0 0 0 4.839-.001l11-15a3.001 3.001 0 0 0 .256-3.13zM40.001 48H22a.995.995 0 0 1-.992-.96L21.001 36h7a3.001 3.001 0 0 0 2.419-4.775l-11-15a3.003 3.003 0 0 0-4.839.001l-11 15A3 3 0 0 0 6.001 36h7l.011 16.003a4 4 0 0 0 4 3.997h22.989a4 4 0 0 0 0-8z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.comments_count) }}</span>
+      </div>
+      <div class="footer-tag">
+        <svg class="" viewBox="0 0 54 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M38.723 12c-7.187 0-11.16 7.306-11.723 8.131C26.437 19.306 22.504 12 15.277 12 8.791 12 3.533 18.163 3.533 24.647 3.533 39.964 21.891 55.907 27 56c5.109-.093 23.467-16.036 23.467-31.353C50.467 18.163 45.209 12 38.723 12z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.attitudes_count) }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as DateUtils from '@/utils/date-utils'
+import * as StringUtils from '@/utils/string-utils'
+import { mapActions } from 'vuex'
+export default {
+  name: 'PixelContent',
+  props: [
+    'x'
+  ],
+  data() {
+    return {
+    }
+  },
+  methods: {
+    ...mapActions([
+      'setImageZoom',
+      'setDetailContent'
+    ]),
+    goDetailContent() {
+      this.setDetailContent(this.x)
+      this.$router.push({ name: 'detail-content' })
+    },
+    formatTime(time) {
+      return DateUtils.format(time)
+    },
+    formatContent(content) {
+      return StringUtils.formatContent(content)
+    },
+    compareImgSize(size) {
+      const count = size / 3
+      if (count >= 1) {
+        return true
+      } else {
+        return false
+      }
+    },
+    formatThumbImg(img) {
+      return StringUtils.formatImgThumb(img)
+    },
+    formatMidImg(img) {
+      return StringUtils.formatImgMiddle(img)
+    },
+    formatNum(num) {
+      return StringUtils.formatNum(num)
+    },
+    imageZoom(url) {
+      this.setImageZoom(this.formatMidImg(url))
+      this.$router.push({ name: 'imageZoom' })
+    },
+    imgClass(size) {
+      let clazz = ''
+      switch (size) {
+        case 1:
+          clazz = 'img-li-one'
+          break
+        case 2:
+        case 4:
+          clazz = 'img-li-two'
+          break
+        default:
+          clazz = 'img-li-other'
+          break
+      }
+      return clazz
+    }
+  }
+}
+</script>
+
+<style lang="css">
+a {
+    color: #007AFF;
+}
+
+.content .list-header {
+    width: 70%;
+    height: 100%;
+    display: flex;
+    flex-flow: row;
+}
+
+.content .list-footer {
+    width: 68%;
+    margin-top: .7rem;
+    color: #cdcdcd;
+    display: flex;
+    flex-flow: row;
+}
+
+.content .list-footer .footer-tag {
+    width: 100%;
+    height: 1.3rem;
+    color: inherit;
+}
+
+.content .list-footer .footer-tag .tag-style {
+    font-size: 12px;
+}
+
+.content .avatar {
+    width: 3.5rem;
+    height: 3.5rem;
+    border-radius: 50%;
+    border: 1px solid rgba(0, 0, 0, .05);
+}
+
+.content .user-info {
+    margin-left: 1rem;
+    display: flex;
+    flex-flow: column;
+    flex: 1;
+}
+
+.content .user-time {
+    font-size: 1rem;
+    color: #A4A8AC;
+    height: 100%;
+    display: table-cell
+}
+
+.content .user-info .user-name {
+    margin: 0;
+    flex: 1;
+    font-size: 1.5rem;
+}
+
+.content .user-info .user-source {
+    margin: 0;
+    flex: 1;
+    font-size: 1rem;
+    color: #A4A8AC
+}
+
+.content .user-info .user-source a {
+    color: #A4A8AC;
+}
+
+.content .list-content {
+  width: 70%;
+    margin-top: .7rem;
+}
+
+.content .list-content .content-text {
+    font-size: 1.3rem;
+    line-height: 1rem;
+}
+
+.content .list-content .content-at {
+    color: #007AFF;
+}
+
+.clear-fix::after {
+    content: '';
+    display: block;
+    clear: both;
+}
+
+.content .list-content .content-img .content-img-ul {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.content-img .content-img-ul .img-li-default {
+    float: left;
+    height: 0;
+    margin-top: .4rem;
+    margin-right: .4rem
+}
+
+.content-img .content-img-ul .img-li-one {
+    width: 52%;
+    padding-bottom: 52%;
+}
+
+.content-img .content-img-ul .img-li-two {
+    width: 43%;
+    padding-bottom: 43%;
+}
+
+.content-img .content-img-ul .img-li-other {
+    width: 28%;
+    padding-bottom: 28%;
+}
+
+.content-img .content-img-ul .img-div {
+    width: 100%;
+    padding-bottom: 100%;
+    background-position: center;
+    background-repeat: no-repeat;
+}
+
+.content .list-content .content-re-content {
+    width: 100%;
+    margin-top: .7rem;
+    border: 1px solid rgba(0, 0, 0, .05);
+    border-radius: 3px;
+    background-color: #f5f5f5;
+    padding: .5rem;
+}
+
+.content .list-content .content-re-content .re-content-text {
+    font-size: 1.3rem;
+    line-height: 1rem;
+}
+</style>

+ 106 - 0
src/components/status/PixelSpinner.vue

@@ -0,0 +1,106 @@
+<template>
+  <div v-show="loading" class="v-spinner">
+    <div class="v-bounce v-bounce1" :style="spinnerBasicStyle">
+      <div class="v-bounce v-bounce2" :style="spinnerStyle" />
+      <div class="v-bounce v-bounce3" :style="spinnerStyle" />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PixelSpinner',
+  props: {
+    loading: {
+      type: Boolean,
+      default: true
+    },
+    color: {
+      type: String,
+      default: '#5dc596'
+    },
+    size: {
+      type: String,
+      default: '60px'
+    },
+    margin: {
+      type: String,
+      default: '2px'
+    },
+    radius: {
+      type: String,
+      default: '100%'
+    }
+  },
+  data() {
+    return {
+      spinnerStyle: {
+        backgroundColor: this.color,
+        height: this.size,
+        width: this.size,
+        borderRadius: this.radius,
+        opacity: 0.6,
+        position: 'absolute',
+        top: 0,
+        left: 0
+      }
+    }
+  },
+  computed: {
+    spinnerBasicStyle() {
+      return {
+        height: this.size,
+        width: this.size,
+        position: 'relative'
+      }
+    }
+  }
+}
+</script>
+
+<style>
+.v-spinner .v-bounce {
+    left: 50%;
+    transform: translateX(-50%);
+}
+
+.v-spinner .v-bounce1 {}
+
+.v-spinner .v-bounce2 {
+    -webkit-animation: v-bounceStretchDelay 2s 1s infinite ease-in-out;
+    animation: v-bounceStretchDelay 2s 1s infinite ease-in-out;
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+}
+
+.v-spinner .v-bounce3 {
+    -webkit-animation: v-bounceStretchDelay 2s 0s infinite ease-in-out;
+    animation: v-bounceStretchDelay 2s 0s infinite ease-in-out;
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+}
+
+@-webkit-keyframes v-bounceStretchDelay {
+    0%,
+    100% {
+        -webkit-transform: scale(0);
+        transform: scale(0);
+    }
+    50% {
+        -webkit-transform: scale(1.0);
+        transform: scale(1.0);
+    }
+}
+
+@keyframes v-bounceStretchDelay {
+    0%,
+    100% {
+        -webkit-transform: scale(0);
+        transform: scale(0);
+    }
+    50% {
+        -webkit-transform: scale(1.0);
+        transform: scale(1.0);
+    }
+}
+</style>

+ 263 - 0
src/components/status/status-card.vue

@@ -0,0 +1,263 @@
+<template>
+  <div class="content" @click="goDetailContent">
+    <div class="list-header">
+      <img v-if="x.user" class="avatar" :src="x.user.avatar_large">
+      <div class="user-info">
+        <h3 v-if="x.user" class="user-name">{{ x.user.name }}</h3>
+        <span class="user-source" v-html="x.source" />
+      </div>
+      <span class="user-time">{{ formatTime(x.created_at) }}</span>
+    </div>
+    <div class="list-content">
+      <span class="content-text" v-html="formatContent(x.text)" />
+      <div class="content-img">
+        <ul class="content-img-ul clear-fix">
+          <li v-for="y in x.pic_urls" :key="y" class="img-li-default" :class="imgClass(x.pic_urls.length)">
+            <div class="img-div" :style="{backgroundImage:'url(' + formatThumbImg(y.thumbnail_pic) + ')'}" @click.stop="imageZoom(y.thumbnail_pic)" />
+          </li>
+        </ul>
+      </div>
+      <div v-if="x.retweeted_status" class="content-re-content">
+        <span
+          class="re-content-text"
+          v-html="formatContent( '@' + x.retweeted_status.user.name + ': '
+            + x.retweeted_status.text)"
+        />
+        <div v-if="x.retweeted_status.pic_urls" class="content-img">
+          <ul class="content-img-ul clear-fix">
+            <li v-for="z in x.retweeted_status.pic_urls" :key="z" class="img-li-default" :class="imgClass(x.retweeted_status.pic_urls.length)">
+              <div
+                class="img-div"
+                :style="{backgroundImage:'url(' + formatThumbImg(z.thumbnail_pic) + ')'}"
+                @click.stop="imageZoom(z.thumbnail_pic)"
+              />
+            </li>
+          </ul>
+        </div>
+      </div>
+    </div>
+    <div class="list-footer">
+      <div class="footer-tag">
+        <svg viewBox="0 0 62 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M41 31h-9V19a2.999 2.999 0 0 0-4.817-2.386l-21 16a3 3 0 0 0-.001 4.773l21 16a3.006 3.006 0 0 0 3.15.301A2.997 2.997 0 0 0 32 51V39h9c5.514 0 10 4.486 10 10a4 4 0 0 0 8 0c0-9.925-8.075-18-18-18z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.reposts_count) }}</span>
+      </div>
+      <div class="footer-tag">
+        <svg class="" viewBox="0 0 74 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M70.676 36.644A3 3 0 0 0 68 35h-7V19a4 4 0 0 0-4-4H34a4 4 0 0 0 0 8h18a1 1 0 0 1 1 .998V35h-7a3.001 3.001 0 0 0-2.419 4.775l11 15a3.003 3.003 0 0 0 4.839-.001l11-15a3.001 3.001 0 0 0 .256-3.13zM40.001 48H22a.995.995 0 0 1-.992-.96L21.001 36h7a3.001 3.001 0 0 0 2.419-4.775l-11-15a3.003 3.003 0 0 0-4.839.001l-11 15A3 3 0 0 0 6.001 36h7l.011 16.003a4 4 0 0 0 4 3.997h22.989a4 4 0 0 0 0-8z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.comments_count) }}</span>
+      </div>
+      <div class="footer-tag">
+        <svg class="" viewBox="0 0 54 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M38.723 12c-7.187 0-11.16 7.306-11.723 8.131C26.437 19.306 22.504 12 15.277 12 8.791 12 3.533 18.163 3.533 24.647 3.533 39.964 21.891 55.907 27 56c5.109-.093 23.467-16.036 23.467-31.353C50.467 18.163 45.209 12 38.723 12z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.attitudes_count) }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as DateUtils from '@/utils/date-utils'
+import * as StringUtils from '@/utils/string-utils'
+import { mapActions } from 'vuex'
+export default {
+  name: 'StatusCard',
+  props: {
+    x: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  data() {
+    return {
+    }
+  },
+  methods: {
+    ...mapActions([
+      'setImageZoom',
+      'setDetailContent'
+    ]),
+    goDetailContent() {
+      this.setDetailContent(this.x)
+      this.$router.push({ name: 'detail-content' })
+    },
+    formatTime(time) {
+      return DateUtils.format(time)
+    },
+    formatContent(content) {
+      return StringUtils.formatContent(content)
+    },
+    compareImgSize(size) {
+      const count = size / 3
+      if (count >= 1) {
+        return true
+      } else {
+        return false
+      }
+    },
+    formatThumbImg(img) {
+      return StringUtils.formatImgThumb(img)
+    },
+    formatMidImg(img) {
+      return StringUtils.formatImgMiddle(img)
+    },
+    formatNum(num) {
+      return StringUtils.formatNum(num)
+    },
+    imageZoom(url) {
+      this.setImageZoom(this.formatMidImg(url))
+      this.$router.push({ name: 'imageZoom' })
+    },
+    imgClass(size) {
+      let clazz = ''
+      switch (size) {
+        case 1:
+          clazz = 'img-li-one'
+          break
+        case 2:
+        case 4:
+          clazz = 'img-li-two'
+          break
+        default:
+          clazz = 'img-li-other'
+          break
+      }
+      return clazz
+    }
+  }
+}
+</script>
+
+<style lang="css">
+a {
+  color: #007AFF;
+}
+
+.content .list-header {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-flow: row;
+}
+
+.content .list-footer {
+  width: 68%;
+  margin-top: .7rem;
+  color: #cdcdcd;
+  display: flex;
+  flex-flow: row;
+}
+
+.content .list-footer .footer-tag {
+  width: 100%;
+  height: 1.3rem;
+  color: inherit;
+}
+
+.content .list-footer .footer-tag .tag-style {
+  font-size: 12px;
+}
+
+.content .avatar {
+  width: 3.5rem;
+  height: 3.5rem;
+  border-radius: 50%;
+  border: 1px solid rgba(0, 0, 0, .05);
+}
+
+.content .user-info {
+  margin-left: 1rem;
+  display: flex;
+  flex-flow: column;
+  flex: 1;
+}
+
+.content .user-time {
+  font-size: 1rem;
+  color: #A4A8AC;
+  height: 100%;
+  display: table-cell
+}
+
+.content .user-info .user-name {
+  margin: 0;
+  flex: 1;
+  font-size: 1.5rem;
+}
+
+.content .user-info .user-source {
+  margin: 0;
+  flex: 1;
+  font-size: 1rem;
+  color: #A4A8AC
+}
+
+.content .user-info .user-source a {
+  color: #A4A8AC;
+}
+
+.content .list-content {
+  margin-top: .7rem;
+}
+
+.content .list-content .content-text {
+  font-size: 1.3rem;
+  line-height: 1rem;
+}
+
+.content .list-content .content-at {
+  color: #007AFF;
+}
+
+.clear-fix::after {
+  content: '';
+  display: block;
+  clear: both;
+}
+
+.content .list-content .content-img .content-img-ul {
+  margin: 0;
+  padding: 0;
+  list-style: none;
+}
+
+.content-img .content-img-ul .img-li-default {
+  float: left;
+  height: 0;
+  margin-top: .4rem;
+  margin-right: .4rem
+}
+
+.content-img .content-img-ul .img-li-one {
+  width: 52%;
+  padding-bottom: 52%;
+}
+
+.content-img .content-img-ul .img-li-two {
+  width: 43%;
+  padding-bottom: 43%;
+}
+
+.content-img .content-img-ul .img-li-other {
+  width: 28%;
+  padding-bottom: 28%;
+}
+
+.content-img .content-img-ul .img-div {
+  width: 100%;
+  padding-bottom: 100%;
+  background-position: center;
+  background-repeat: no-repeat;
+}
+
+.content .list-content .content-re-content {
+  width: 100%;
+  margin-top: .7rem;
+  border: 1px solid rgba(0, 0, 0, .05);
+  border-radius: 3px;
+  background-color: #f5f5f5;
+  padding: .5rem;
+}
+
+.content .list-content .content-re-content .re-content-text {
+  font-size: 1.3rem;
+  line-height: 1rem;
+}
+</style>

+ 1 - 1
src/router/index.js

@@ -21,7 +21,7 @@ const routes = [
       {
         path: '/hot',
         name: 'Hot',
-        component: () => import('@/views/home/hot.vue'),
+        component: () => import('@/views/home/MyContent.vue'),
         meta: { title: 'HerTube 时下流行' }
       },
       {

+ 7 - 1
src/store/getters.js

@@ -3,7 +3,13 @@ const getters = {
   token: state => state.user.token,
   roles: state => state.user.roles,
   avatar: state => state.user.avatar,
-  name: state => state.user.name
+  name: state => state.user.name,
+  // mycontent
+  my_content: state => state.my_content.statuses,
+  my_content_option: state => state.my_content.option,
+  // imagezoom
+  image_zoom_url: state => state.image_zoom.image_url,
+  image_zoom_show: state => state.image_zoom.show
 }
 
 export default getters

+ 5 - 1
src/store/index.js

@@ -3,6 +3,8 @@ import Vuex from 'vuex'
 import persistedState from 'vuex-persistedstate'
 import getters from './getters'
 import user from './modules/user'
+import my_content from '@/store/modules/my_content'
+import image_zoom from '@/store/modules/image_zoom'
 
 Vue.use(Vuex)
 
@@ -42,7 +44,9 @@ const store = new Vuex.Store({
   actions: {
   },
   modules: {
-    user
+    user,
+    my_content,
+    image_zoom
   },
   getters,
   plugins: [persistedState()]

+ 36 - 0
src/store/modules/image_zoom.js

@@ -0,0 +1,36 @@
+import {
+  IMAGE_ZOOM
+} from '../mutations-type'
+
+const state = {
+  image_url: '',
+  show: false
+}
+
+const mutations = {
+
+  [IMAGE_ZOOM](state, data) {
+    // save in state
+    state.image_url = data
+    if (data) {
+      state.show = true
+    } else {
+      state.show = false
+    }
+  }
+
+}
+
+const actions = {
+
+  setImageZoom: ({ commit }, url) => {
+    commit(IMAGE_ZOOM, url)
+  }
+
+}
+
+export default {
+  state,
+  actions,
+  mutations
+}

+ 63 - 0
src/store/modules/my_content.js

@@ -0,0 +1,63 @@
+import {
+  MY_CONTENT,
+  PROFILE_REFRESH
+} from '../mutations-type'
+
+import * as api from '../../api/impl/my-content'
+import { logger } from '@/utils/logger'
+
+const state = {
+  statuses: [],
+  option: {
+    refresh: false,
+    page: 1
+  }
+}
+
+const mutations = {
+
+  [MY_CONTENT](state, data) {
+    // save in state
+    state.statuses = data
+    logger('my-content', 'save store succeed !')
+  },
+
+  [PROFILE_REFRESH](state, refresh) {
+    if (refresh) {
+      state.option.page = 1
+    }
+    state.option.refresh = refresh
+    logger('my-content', refresh)
+  }
+
+}
+
+const actions = {
+
+  getMyContent: ({ commit }, page) => {
+    console.log('getMyContent')
+
+    if (page === 1) {
+      commit(PROFILE_REFRESH, true)
+    } else {
+      commit(PROFILE_REFRESH, false)
+    }
+
+    api.getMyContent(
+      page,
+      response => {
+        commit(MY_CONTENT, response.statuses)
+        commit(PROFILE_REFRESH, false)
+      },
+      err => {
+        console.log(err)
+      }
+    )
+  }
+}
+
+export default {
+  state,
+  actions,
+  mutations
+}

+ 27 - 0
src/store/mutations-type.js

@@ -0,0 +1,27 @@
+export const LOGIN = 'login'
+export const LOGOUT = 'logout'
+export const TOKEN = 'token'
+export const USERINFO = 'userInfo'
+export const HOME_TIMELINE = 'home_timeline'
+export const PUBLIC_TIMELINE = 'public_timeline'
+export const MY_CONTENT = 'my_content'
+export const HOME_REFRESH = 'home_refresh'
+export const PUBLIC_REFRESH = 'public_refresh'
+export const PROFILE_REFRESH = 'profile_refresh'
+export const IMAGE_ZOOM = 'image_zoom'
+export const DETAILCONTENT = 'detail_content'
+export const CONTENT_COMMENTS = 'content_comments'
+export const CONTENT_COMMENTS_REFRESH = 'content_comments_refresh'
+export const AT_ME_STATUS = 'at_me_status'
+export const AT_ME_STATUS_REFRESH = 'at_me_status_refresh'
+export const AT_ME_COMMENT = 'at_me_comment'
+export const AT_ME_COMMENT_REFRESH = 'at_me_comment_refresh'
+export const RECEIVE_COMMENT = 'receive_comment'
+export const RECEIVE_COMMENT_REFRESH = 'receive_comment_refresh'
+export const SEND_COMMENT = 'send_comment'
+export const SEND_COMMENT_REFRESH = 'send_comment_refresh'
+export const MY_FOLLOWER = 'my_follower'
+export const MY_FOLLOWER_REFRESH = 'my_follower_refresh'
+export const MY_FRIEND = 'my_friend'
+export const MY_FRIEND_REFRESH = 'my_friend_refresh'
+

+ 34 - 0
src/utils/date-utils.js

@@ -0,0 +1,34 @@
+export const format = (date) => {
+  return getDateDiff(new Date(date))
+}
+
+function getDateDiff(dateTimeStamp) {
+  const minute = 1000 * 60
+  const hour = minute * 60
+  const day = hour * 24
+  const halfamonth = day * 15
+  const month = day * 30
+  const now = new Date().getTime()
+  const diffValue = now - dateTimeStamp
+  if (diffValue < 0) { return }
+  const monthC = diffValue / month
+  const weekC = diffValue / (7 * day)
+  const dayC = diffValue / day
+  const hourC = diffValue / hour
+  const minC = diffValue / minute
+  let result = ''
+  if (monthC >= 1) {
+    result = '' + parseInt(monthC) + '月前'
+  } else if (weekC >= 1) {
+    result = '' + parseInt(weekC) + '周前'
+  } else if (dayC >= 1) {
+    result = '' + parseInt(dayC) + '天前'
+  } else if (hourC >= 1) {
+    result = '' + parseInt(hourC) + '小时前'
+  } else if (minC >= 1) {
+    result = '' + parseInt(minC) + '分钟前'
+  } else {
+    result = '刚刚'
+  }
+  return result
+}

+ 7 - 0
src/utils/logger.js

@@ -0,0 +1,7 @@
+export const logger = (tag, message) => {
+  if (typeof message === 'object') {
+    console.log(tag + ':' + JSON.stringify(message))
+  } else {
+    console.log(tag + ':' + message)
+  }
+}

+ 49 - 0
src/utils/string-utils.js

@@ -0,0 +1,49 @@
+const URL_REG = /((\w+:\/\/)[-a-zA-Z0-9:@;?&=\/%\+\.\*!'\(\),\$_\{\}\^~\[\]`#|]+)/g
+const AT_REG = /@[\u4e00-\u9fa5a-zA-Z0-9_-]{2,30}/g
+const TAG_REG = /#[^#]+#/g
+
+export const getUrlKey = (key) => {
+  // eslint-disable-next-line no-sparse-arrays
+  return decodeURIComponent((new RegExp('[?|&]' + key + '=' + '([^&;]+?)(&|#|;|$)').exec(location.href) || [, ''])[1].replace(/\+/g, '%20')) || null
+}
+
+export const formatContent = (content) => {
+  let format = content.replace(URL_REG, function($0) {
+    return '<a href="$0" >' + $0 + '</a>'
+  })
+
+  format = format.replace(AT_REG, function($0) {
+    return '<a href="$0" >' + $0 + '</a>'
+  })
+
+  format = format.replace(TAG_REG, function($0) {
+    return '<a href="$0" >' + $0 + '</a>'
+  })
+
+  return format
+}
+
+export const formatImgThumb = (img) => {
+  let format = img.replace('thumbnail', 'thumb180')
+  format = format.replace('wx1', 'wx3')
+  return format
+}
+
+export const formatImgMiddle = (img) => {
+  const format = img.replace('thumbnail', 'bmiddle')
+  return format
+}
+
+export const formatNum = (num) => {
+  let result
+  if (num > 999) {
+    if (num > 9999) {
+      result = '9k+'
+    } else {
+      result = Math.round(num / 1000) + 'k+'
+    }
+  } else {
+    result = num
+  }
+  return result
+}

+ 9 - 0
src/views/Content/index.js

@@ -0,0 +1,9 @@
+/*
+import PixelContent from './src/Content'
+
+PixelContent.install = function(Vue) {
+  Vue.component(PixelContent.name, PixelContent)
+}
+
+export default PixelContent
+*/

+ 260 - 0
src/views/Content/src/Content.vue

@@ -0,0 +1,260 @@
+<template lang="html">
+  <div class="content" @click="goDetailContent">
+    <div class="list-header">
+      <img v-if="x.user" class="avatar" :src="x.user.avatar_large">
+      <div class="user-info">
+        <h3 v-if="x.user" class="user-name">{{ x.user.name }}</h3>
+        <span class="user-source" v-html="x.source" />
+      </div>
+      <span class="user-time">{{ formatTime(x.created_at) }}</span>
+    </div>
+    <div class="list-content">
+      <span class="content-text" v-html="formatContent(x.text)" />
+      <div class="content-img">
+        <ul class="content-img-ul clear-fix">
+          <li v-for="y in x.pic_urls" class="img-li-default" :class="imgClass(x.pic_urls.length)">
+            <div class="img-div" :style="{backgroundImage:'url(' + formatThumbImg(y.thumbnail_pic) + ')'}" @click.stop="imageZoom(y.thumbnail_pic)" />
+          </li>
+        </ul>
+      </div>
+      <div v-if="x.retweeted_status" class="content-re-content">
+        <span
+          class="re-content-text"
+          v-html="formatContent( '@' + x.retweeted_status.user.name + ': '
+            + x.retweeted_status.text)"
+        />
+        <div v-if="x.retweeted_status.pic_urls" class="content-img">
+          <ul class="content-img-ul clear-fix">
+            <li v-for="z in x.retweeted_status.pic_urls" class="img-li-default" :class="imgClass(x.retweeted_status.pic_urls.length)">
+              <div
+                class="img-div"
+                :style="{backgroundImage:'url(' + formatThumbImg(z.thumbnail_pic) + ')'}"
+                @click.stop="imageZoom(z.thumbnail_pic)"
+              />
+            </li>
+          </ul>
+        </div>
+      </div>
+    </div>
+    <div class="list-footer">
+      <div class="footer-tag">
+        <svg viewBox="0 0 62 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M41 31h-9V19a2.999 2.999 0 0 0-4.817-2.386l-21 16a3 3 0 0 0-.001 4.773l21 16a3.006 3.006 0 0 0 3.15.301A2.997 2.997 0 0 0 32 51V39h9c5.514 0 10 4.486 10 10a4 4 0 0 0 8 0c0-9.925-8.075-18-18-18z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.reposts_count) }}</span>
+      </div>
+      <div class="footer-tag">
+        <svg class="" viewBox="0 0 74 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M70.676 36.644A3 3 0 0 0 68 35h-7V19a4 4 0 0 0-4-4H34a4 4 0 0 0 0 8h18a1 1 0 0 1 1 .998V35h-7a3.001 3.001 0 0 0-2.419 4.775l11 15a3.003 3.003 0 0 0 4.839-.001l11-15a3.001 3.001 0 0 0 .256-3.13zM40.001 48H22a.995.995 0 0 1-.992-.96L21.001 36h7a3.001 3.001 0 0 0 2.419-4.775l-11-15a3.003 3.003 0 0 0-4.839.001l-11 15A3 3 0 0 0 6.001 36h7l.011 16.003a4 4 0 0 0 4 3.997h22.989a4 4 0 0 0 0-8z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.comments_count) }}</span>
+      </div>
+      <div class="footer-tag">
+        <svg class="" viewBox="0 0 54 72" style="display: inline-block; fill: currentcolor; height: 1.25rem; max-width: 100%; position: relative; user-select: none; vertical-align: text-bottom;"><g><path d="M38.723 12c-7.187 0-11.16 7.306-11.723 8.131C26.437 19.306 22.504 12 15.277 12 8.791 12 3.533 18.163 3.533 24.647 3.533 39.964 21.891 55.907 27 56c5.109-.093 23.467-16.036 23.467-31.353C50.467 18.163 45.209 12 38.723 12z" /></g></svg>
+        <span class="tag-style">{{ formatNum(x.attitudes_count) }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import * as DateUtils from '../../../utils/date-utils'
+import * as StringUtils from '../../../utils/string-utils'
+import { mapActions } from 'vuex'
+export default {
+  name: 'PixelContent',
+  props: [
+    'x'
+  ],
+  data() {
+    return {
+    }
+  },
+  methods: {
+    ...mapActions([
+      'setImageZoom',
+      'setDetailContent'
+    ]),
+    goDetailContent() {
+      this.setDetailContent(this.x)
+      this.$router.push({ name: 'detail-content' })
+    },
+    formatTime(time) {
+      return DateUtils.format(time)
+    },
+    formatContent(content) {
+      return StringUtils.formatContent(content)
+    },
+    compareImgSize(size) {
+      const count = size / 3
+      if (count >= 1) {
+        return true
+      } else {
+        return false
+      }
+    },
+    formatThumbImg(img) {
+      return StringUtils.formatImgThumb(img)
+    },
+    formatMidImg(img) {
+      return StringUtils.formatImgMiddle(img)
+    },
+    formatNum(num) {
+      return StringUtils.formatNum(num)
+    },
+    imageZoom(url) {
+      this.setImageZoom(this.formatMidImg(url))
+      this.$router.push({ name: 'imageZoom' })
+    },
+    imgClass(size) {
+      let clazz = ''
+      switch (size) {
+        case 1:
+          clazz = 'img-li-one'
+          break
+        case 2:
+        case 4:
+          clazz = 'img-li-two'
+          break
+        default:
+          clazz = 'img-li-other'
+          break
+      }
+      return clazz
+    }
+  }
+}
+</script>
+
+<style lang="css">
+a {
+    color: #007AFF;
+}
+
+.content .list-header {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-flow: row;
+}
+
+.content .list-footer {
+    width: 68%;
+    margin-top: .7rem;
+    color: #cdcdcd;
+    display: flex;
+    flex-flow: row;
+}
+
+.content .list-footer .footer-tag {
+    width: 100%;
+    height: 1.3rem;
+    color: inherit;
+}
+
+.content .list-footer .footer-tag .tag-style {
+    font-size: 12px;
+}
+
+.content .avatar {
+    width: 3.5rem;
+    height: 3.5rem;
+    border-radius: 50%;
+    border: 1px solid rgba(0, 0, 0, .05);
+}
+
+.content .user-info {
+    margin-left: 1rem;
+    display: flex;
+    flex-flow: column;
+    flex: 1;
+}
+
+.content .user-time {
+    font-size: 1rem;
+    color: #A4A8AC;
+    height: 100%;
+    display: table-cell
+}
+
+.content .user-info .user-name {
+    margin: 0;
+    flex: 1;
+    font-size: 1.5rem;
+}
+
+.content .user-info .user-source {
+    margin: 0;
+    flex: 1;
+    font-size: 1rem;
+    color: #A4A8AC
+}
+
+.content .user-info .user-source a {
+    color: #A4A8AC;
+}
+
+.content .list-content {
+    margin-top: .7rem;
+}
+
+.content .list-content .content-text {
+    font-size: 1.3rem;
+    line-height: 1rem;
+}
+
+.content .list-content .content-at {
+    color: #007AFF;
+}
+
+.clear-fix::after {
+    content: '';
+    display: block;
+    clear: both;
+}
+
+.content .list-content .content-img .content-img-ul {
+    margin: 0;
+    padding: 0;
+    list-style: none;
+}
+
+.content-img .content-img-ul .img-li-default {
+    float: left;
+    height: 0;
+    margin-top: .4rem;
+    margin-right: .4rem
+}
+
+.content-img .content-img-ul .img-li-one {
+    width: 52%;
+    padding-bottom: 52%;
+}
+
+.content-img .content-img-ul .img-li-two {
+    width: 43%;
+    padding-bottom: 43%;
+}
+
+.content-img .content-img-ul .img-li-other {
+    width: 28%;
+    padding-bottom: 28%;
+}
+
+.content-img .content-img-ul .img-div {
+    width: 100%;
+    padding-bottom: 100%;
+    background-position: center;
+    background-repeat: no-repeat;
+}
+
+.content .list-content .content-re-content {
+    width: 100%;
+    margin-top: .7rem;
+    border: 1px solid rgba(0, 0, 0, .05);
+    border-radius: 3px;
+    background-color: #f5f5f5;
+    padding: .5rem;
+}
+
+.content .list-content .content-re-content .re-content-text {
+    font-size: 1.3rem;
+    line-height: 1rem;
+}
+</style>

+ 9 - 0
src/views/Spinner/index.js

@@ -0,0 +1,9 @@
+/*
+import PixelSpinner from './src/Spinner'
+
+PixelSpinner.install = function(Vue) {
+  Vue.component(PixelSpinner.name, PixelSpinner)
+}
+
+export default PixelSpinner
+*/

+ 106 - 0
src/views/Spinner/src/Spinner.vue

@@ -0,0 +1,106 @@
+<template>
+  <div v-show="loading" class="v-spinner">
+    <div class="v-bounce v-bounce1" :style="spinnerBasicStyle">
+      <div class="v-bounce v-bounce2" :style="spinnerStyle" />
+      <div class="v-bounce v-bounce3" :style="spinnerStyle" />
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'PixelSpinner',
+  props: {
+    loading: {
+      type: Boolean,
+      default: true
+    },
+    color: {
+      type: String,
+      default: '#5dc596'
+    },
+    size: {
+      type: String,
+      default: '60px'
+    },
+    margin: {
+      type: String,
+      default: '2px'
+    },
+    radius: {
+      type: String,
+      default: '100%'
+    }
+  },
+  data() {
+    return {
+      spinnerStyle: {
+        backgroundColor: this.color,
+        height: this.size,
+        width: this.size,
+        borderRadius: this.radius,
+        opacity: 0.6,
+        position: 'absolute',
+        top: 0,
+        left: 0
+      }
+    }
+  },
+  computed: {
+    spinnerBasicStyle() {
+      return {
+        height: this.size,
+        width: this.size,
+        position: 'relative'
+      }
+    }
+  }
+}
+</script>
+
+<style>
+.v-spinner .v-bounce {
+    left: 50%;
+    transform: translateX(-50%);
+}
+
+.v-spinner .v-bounce1 {}
+
+.v-spinner .v-bounce2 {
+    -webkit-animation: v-bounceStretchDelay 2s 1s infinite ease-in-out;
+    animation: v-bounceStretchDelay 2s 1s infinite ease-in-out;
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+}
+
+.v-spinner .v-bounce3 {
+    -webkit-animation: v-bounceStretchDelay 2s 0s infinite ease-in-out;
+    animation: v-bounceStretchDelay 2s 0s infinite ease-in-out;
+    -webkit-animation-fill-mode: both;
+    animation-fill-mode: both;
+}
+
+@-webkit-keyframes v-bounceStretchDelay {
+    0%,
+    100% {
+        -webkit-transform: scale(0);
+        transform: scale(0);
+    }
+    50% {
+        -webkit-transform: scale(1.0);
+        transform: scale(1.0);
+    }
+}
+
+@keyframes v-bounceStretchDelay {
+    0%,
+    100% {
+        -webkit-transform: scale(0);
+        transform: scale(0);
+    }
+    50% {
+        -webkit-transform: scale(1.0);
+        transform: scale(1.0);
+    }
+}
+</style>

+ 108 - 0
src/views/home/MyContent.vue

@@ -0,0 +1,108 @@
+<template lang="html">
+  <div id="home" class="home">
+    <div v-for="x in list" class="list">
+      <pixel-content :x="x" />
+    </div>
+    <div v-if="option.refresh" class="refresh-footer">
+      <pixel-spinner :size="'45px'" :color="'#007AFF'" />
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapActions, mapGetters } from 'vuex'
+import PixelContent from '@/components/status/PixelContent'
+import PixelSpinner from '@/components/status/PixelSpinner'
+
+export default {
+  name: 'Home',
+  components: {
+    PixelContent,
+    PixelSpinner
+  },
+  data() {
+    return {
+      list: []
+    }
+  },
+  computed: {
+    ...mapGetters({
+      statuses: 'my_content',
+      option: 'my_content_option',
+      showImage: 'image_zoom_show'
+    })
+  },
+  watch: {
+    option: {
+      handler: function(val, oldVal) {
+        if (val && val.page === 1) {
+          this.list = []
+        }
+      },
+      deep: true
+    },
+    statuses: function(val, oldVal) {
+      if (val) {
+        if (this.option.page === 1) {
+          this.list = val
+        } else {
+          this.list = [...this.list, ...val]
+        }
+      }
+    }
+  },
+  created() {
+    this.myContent(1)
+  },
+  mounted() {
+
+  },
+  activated() {
+    window.addEventListener('scroll', this.scrollBar)
+  },
+  deactivated() {
+    window.removeEventListener('scroll', this.scrollBar)
+  },
+  methods: {
+    ...mapActions([
+      'getMyContent'
+    ]),
+    myContent(page) {
+      this.getMyContent(page)
+    },
+    loadMore() {
+      const vue = this
+      vue.option.refresh = true
+      var page = vue.option.page + 1
+      vue.myContent(page)
+    },
+    scrollBar() {
+      var a = document.documentElement.scrollTop === 0 ? document.body.clientHeight : document.documentElement.clientHeight
+      var b = document.documentElement.scrollTop === 0 ? document.body.scrollTop : document.documentElement.scrollTop
+      var c = document.documentElement.scrollTop === 0 ? document.body.scrollHeight : document.documentElement.scrollHeight
+      if (a + b === c && !this.showImage) {
+        console.log(a + b)
+        this.loadMore()
+      }
+    }
+  }
+
+}
+</script>
+
+<style lang="css">
+.list {
+    flex: 1;
+    background-color: #fff;
+    border-radius: 2px;
+    padding: 1rem;
+    margin-bottom: .8rem;
+    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
+}
+
+.refresh-footer {
+    margin-bottom: .8rem;
+    margin-top: .8rem;
+    text-align: center;
+}
+</style>

+ 92 - 4
src/views/home/hot.vue

@@ -1,15 +1,103 @@
 <template>
-  <v-container fill-height>
-    hot
+  <v-container fill-height fluid style="padding-left: 24px; padding-right: 24px">
+    <div v-infinite-scroll="loadMore" infinite-scroll-disabled="true" infinite-scroll-distance="10">
+      <v-row
+        v-for="x in list"
+        :key="x.id"
+        no-gutters
+      >
+        <status-card :x="x" />
+      </v-row>
+      <!--      <v-row no-gutters>
+        <div v-for="x in list" :key="x.id" class="list">
+          <status-card :x="x" />
+        </div>
+      </v-row>-->
+    </div>
   </v-container>
 </template>
 
 <script>
+import { mapActions, mapGetters } from 'vuex'
+import StatusCard from '@/components/status/status-card'
+
 export default {
-  name: 'Hot'
+  name: 'Home',
+  components: {
+    StatusCard
+  },
+  data() {
+    return {
+      list: []
+    }
+  },
+  computed: {
+    ...mapGetters({
+      statuses: 'my_content',
+      option: 'my_content_option',
+      showImage: 'image_zoom_show'
+    })
+  },
+  watch: {
+    option: {
+      handler: function(val, oldVal) {
+        if (val && val.page === 1) {
+          this.list = []
+        }
+      },
+      deep: true
+    },
+    statuses: function(val, oldVal) {
+      if (val) {
+        if (this.option.page === 1) {
+          this.list = val
+        } else {
+          this.list = [...this.list, ...val]
+        }
+      }
+    }
+  },
+  created() {
+    this.myContent(1)
+  },
+  mounted() {
+
+  },
+  activated() {
+    window.addEventListener('scroll', this.scrollBar)
+  },
+  deactivated() {
+    window.removeEventListener('scroll', this.scrollBar)
+  },
+  methods: {
+    ...mapActions([
+      'getMyContent'
+    ]),
+    myContent(page) {
+      this.getMyContent(page)
+    },
+    loadMore() {
+      const vue = this
+      vue.option.refresh = true
+      var page = vue.option.page + 1
+      vue.myContent(page)
+    },
+    scrollBar() {
+      var a = document.documentElement.scrollTop === 0 ? document.body.clientHeight : document.documentElement.clientHeight
+      var b = document.documentElement.scrollTop === 0 ? document.body.scrollTop : document.documentElement.scrollTop
+      var c = document.documentElement.scrollTop === 0 ? document.body.scrollHeight : document.documentElement.scrollHeight
+      if (a + b === c && !this.showImage) {
+        console.log(a + b)
+        this.loadMore()
+      }
+    }
+  }
+
 }
 </script>
 
 <style>
-
+a {
+  text-decoration: none;
+}
 </style>

Некоторые файлы не были показаны из-за большого количества измененных файлов