article-long.vue 9.5 KB
<template>
  <div class="article-detail-long">
    <ArticleDetailHeader ref="header" :data="data" :step="headerAnimateStep" :title-step="headerTitleAnimateStep">
      <div v-if="data.articleId && !data.empty" class="title-main">
        <div class="title-info" :style="`transform: translate3d(0, ${viewMoreArticles ? '-50%' : '0'}, 0)`">
          <ArticleItemHeader class="title-info-author" :share="share" :data="authorData" :lazy="lazy" :more="showMoreOpt" @on-follow="onFollowAuthor"></ArticleItemHeader>
          <div class="title-info-rec">{{listTitle}}</div>
        </div>
      </div>
    </ArticleDetailHeader>
    <div ref="coverFigure" class="cover-figure" :style="coverStyle">
        <ImageFormat class="cover-img" :src="coverImage.src" :width="coverImage.width" :height="coverImage.height" :style="`transform: translate3d(0, ${coverTranslateY}px, 0)`"></ImageFormat>
    </div>
    <div ref="authorBlock" class="author-block">
      <ArticleItemHeader :share="share" :data="authorData" :lazy="lazy" :more="showMoreOpt" @on-follow="onFollowAuthor"></ArticleItemHeader>
    </div>
    <div class="main-detail">
      <div class="article-context">
        <div class="context-title">{{data.articleTitle}}</div>
        <div class="context-rich-text" v-html="data.richText"></div>
      </div>

      <ArticleItemTopics v-if="data.topicList && data.topicList.length" class="topics-wrap" :data="data" :share="share"></ArticleItemTopics>

      <div v-if="recomendProduct.length">
        <LayoutTitle class="rec-goods-title">推荐商品</LayoutTitle>
        <ProductGroup :data="recomendProduct" model="2"></ProductGroup>
      </div>

      <LayoutTitle v-if="listTitle" class="rec-article-title">{{listTitle}}</LayoutTitle>
    </div>
    <ArticleDetailFooter class="detail-fixed-footer" :style="`transform: translate3d(0, ${viewMoreArticles ? '100%' : '0'}, 0)`" v-bind="footerData" @on-comment-click="onComment">
      <template v-slot:after>
        <div v-if="articleProducts.length" class="article-goods">文中商品</div>
        <div v-else></div>
      </template>
    </ArticleDetailFooter>

    <YohoActionSheet transfer v-if="showCommentAction" ref="commentAction" :full="true">
      <Comment ref="comment"
        :destId="data.articleId"
        :popup="true"
        :article-id="data.articleId"
        :commentCount="data.commentCount"
        :pos-id="posId"
        @on-close="onCloseComment"
        @on-comment="onActionComment"></Comment>
    </YohoActionSheet>
  </div>
</template>

<script>
import {floor} from 'lodash';
import ArticleItemHeader from '../article/article-item-header';
import ArticleItemTopics from '../article/article-item-topics';
import ArticleDetailFooter from './article-footer';
import ArticleDetailHeader from './article-header';
import {mapState, mapMutations, createNamespacedHelpers} from 'vuex';
const {mapState: mapArticleState} = createNamespacedHelpers('article');

export default {
  name: 'ArticleDetailLong',
  props: {
    data: {
      type: Object,
      default() {
        return {};
      }
    },
    listTitle: String,
    scrollTop: Number,
    share: Boolean,
    posId: Number,
  },
  data() {
    return {
      coverHeight: 0,
      downgrade: false,
      showMoreOpt: false,
      authorBlock: {},
      recomendProduct: [],
      showCommentAction: false,
      showCommentActioning: false,
      articleProducts: []
    };
  },
  mounted() {
    if (!this.$yoho.isiOS) {
      this.downgrade = true;
    }
  },
  computed: {
    ...mapState(['yoho']),
    ...mapArticleState(['articleStates', 'authorStates']),
    articleState() {
      const articleState = this.articleStates[this.data.articleId] || this.data;
      const authorState = this.authorStates[`${this.data.authorUid}-${this.data.authorType}`] || this.data;

      return Object.assign({...articleState}, {hasAttention: authorState.hasAttention});
    },
    coverStyle() {
      return {
        height: `${floor(this.data.imageHeight / this.data.imageWidth * 750 / 40, 2)}rem`
      };
    },
    coverImage() {
      this.$nextTick(() => {
        if (this.$refs.coverFigure) {
          this.coverHeight = this.$refs.coverFigure.offsetHeight;
        }

        if (this.$refs.authorBlock) {
          this.authorBlock = {
            height: this.$refs.authorBlock.offsetHeight,
            top: this.$refs.authorBlock.offsetTop
          };
        }
      });

      let width = Math.min(+this.data.imageWidth, 1000);

      return {
        src: this.data.coverImage,
        width,
        height: Math.round(width * this.data.imageHeight / this.data.imageWidth)
      };
    },
    coverTranslateY() {
      let top = this.scrollTop > 0 ? this.scrollTop : 0;

      if (top && !this.downgrade && this.coverHeight && top < this.coverHeight) {
        return top / 2;
      } else {
        return 0;
      }
    },
    headerAnimateStep() {
      if (this.scrollTop > this.coverHeight) {
        return 100;
      } else if (this.scrollTop > 0) {
        let step = Math.round(this.scrollTop - 10 / (this.coverHeight - 10) * 100);

        step = Math.max(Math.min(step, 100), 0);
        this.SET_STATUS_BAR_COLOR({
          color: step > 55 ? 'black' : 'white'
        });

        return step;
      } else {
        return 0;
      }
    },
    headerTitleAnimateStep() {
      let {height, top} = this.authorBlock;
      let scrollTop = this.scrollTop;

      if (this.$refs && this.$refs.header) {
        scrollTop += this.$refs.header.$el.offsetHeight;
      }

      if (top && height) {
        if (scrollTop >= top + height) {
          return 100;
        } else if (scrollTop > top) {
          let step = Math.round((scrollTop - top) / height * 100);

          return Math.max(Math.min(step, 100), 0);
        }
      }

      return 0;
    },
    viewMoreArticles() {
      let scrollTop = this.scrollTop;

      if (this.$refs && this.$refs.header) {
        scrollTop += this.$refs.header.$el.offsetHeight;
        return scrollTop > this.$el.offsetHeight;
      } else {
        return false;
      }
    },
    authorData() {
      return {
        authorName: this.data.authorName,
        authorUid: this.data.authorUid,
        authorType: this.data.authorType,
        authorHeadIco: this.data.authorHeadIco,
        authGroupId: this.data.authGroupId,
        hasAttention: this.articleState.hasAttention,
        isAuthor: this.data.isAuthor
      };
    },
    footerData() {
      return {
        favoriteCount: this.articleState.favoriteCount,
        praiseCount: this.articleState.praiseCount,
        commentCount: this.articleState.commentCount,
        hasFavor: this.articleState.hasFavor,
        hasPraise: this.articleState.hasPraise,
        articleId: this.data.articleId
      };
    },
    lazy() {
      return this.data.lazy;
    }
  },
  methods: {
    ...mapMutations(['SET_STATUS_BAR_COLOR']),
    onFollowAuthor(follow) {
      this.$emit('on-follow', this.data, follow);
    },
    onComment() {
      this.showCommentAction = true;
      this.$nextTick(() => {
        if (this.showCommentActioning) {
          return;
        }
        this.showCommentActioning = true;
        this.$refs.comment.init();
        this.$refs.commentAction.show();
        setTimeout(() => {
          this.showCommentActioning = false;
        }, 300);
      });
    },
    onCloseComment() {
      this.$refs.commentAction.hide();
    },
    onActionComment() {
    },
  },
  components: {
    ArticleDetailHeader,
    ArticleItemHeader,
    ArticleItemTopics,
    ArticleDetailFooter
  }
};
</script>

<style lang="scss" scoped>
.title-main {
  height: 100%;
  color: #444;
  overflow: hidden;

  .title-info {
    height: 200%;
    transition: all 200ms;

    > * {
      height: 50%;
      background: none;
    }

    .title-info-rec {
      font-size: 32px;
      line-height: 1.2;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  }

  /deep/ .avatar {
    padding-left: 0;
  }

  /deep/ .opts {
    padding-right: 10px;
  }
}

.cover-img {
  width: 100%;
  display: block;
  position: relative;
  z-index: 0;
}

.author-block,
.main-detail {
  position: relative;
  z-index: 1;
  background-color: #fff;
}

.article-context {
  padding: 30px;
}

.context-title {
  font-size: 34px;
  color: #444;
  line-height: 52px;
  margin-bottom: 10px;
  font-weight: 700;
}

.context-rich-text /deep/ {
  font-size: 28px;
  line-height: 1.8!important;

  * {
    font-family: "PingFang SC", "HiraginoSansGB-W3", "SanFranciscoText-Regular", Helvetica, Roboto, "Heiti SC", "黑体", Arial!important;
  }

  h1,
  h2,
  h3 {
    font-weight: 700!important;
  }

  h1 {
    font-size: 32px!important;
  }

  h2 {
    font-size: 30px!important;
  }

  h3 {
    font-size: 28px!important;
  }

  img {
    max-width: 100%!important;
  }

  p {
    font-size: 28px!important;
  }

  b,
  strong {
    font-weight: 500;
  }

  blockquote {
    border-left: 12px solid #b4b4b4;
    background-color: #f7f7f7;
    padding: 30px;
  }

  ol,
  ul {
    padding-inline-start: 38px;

    li {
      list-style: inherit;
    }
  }
}

.topics-wrap {
  padding-left: 30px;
  margin-bottom: 36px;
}

.rec-goods-title {
  margin-bottom: -32px;
}

/deep/ .product-item:first-child {
  margin-left: 30px;
}

/deep/ .product-item:last-child {
  margin-right: 30px;
}

.rec-article-title {
  margin-top: -6px;
}

.detail-fixed-footer {
  width: 100%;
  position: fixed;
  bottom: 0;
  z-index: 10;
  transition: all 300ms;

  .article-goods {
    width: 200px;
    color: white;
    font-size: 32px;
    line-height: 100px;
    font-weight: 300;
    background-color: #d0021b;
    text-align: center;
  }
}
</style>