Authored by yyq

long detail context

... ... @@ -2,8 +2,8 @@
<Layout class="article-detail">
<RecycleScrollReveal :size="5" ref="scroll" @scroll="onScroll" :offset="800" :on-fetch="onFetch" :manual-init="true">
<template v-slot:eternalTop>
<ArticleDeatilNote :data="article" :scroll-top="scrollTop" :list-title="listTitle"></ArticleDeatilNote>
<!-- <ArticleDeatilLong ref="detailLong" :data="article" :scroll-top="scrollTop" :list-title="listTitle"></ArticleDeatilLong> -->
<!-- <ArticleDeatilNote :data="article" :scroll-top="scrollTop" :list-title="listTitle"></ArticleDeatilNote> -->
<ArticleDeatilLong ref="detailLong" :data="article" :scroll-top="scrollTop" :list-title="listTitle"></ArticleDeatilLong>
</template>
<template class="article-item" #item="{ data }">
<ArticleItem2
... ... @@ -78,7 +78,7 @@ export default {
articleId,
page: 1,
limit: 1,
columnType: 1002
singleDetail: 'Y'
}).then(res => {
this.article = get(res, 'data.detailList[0]', {});
});
... ...
... ... @@ -54,7 +54,7 @@ export default {
.article-footer-wrapper {
display: flex;
height: 100px;
border-top: 1px solid #e0e0e0;
border-top: 1px solid #f0f0f0;
background-color: white;
}
... ...
<template>
<div ref="intro" class="intro-wrap" :class="introClass" :style="introStyle" @click="onExpanding">
<div v-if="trimIntro" class="intro-content">
<p class="pre-wrap">{{intro}}</p>
<div ref="introPool" class="intro-pool pre-wrap">{{trimIntro}}</div>
<span ref="expand" class="expand" v-if="showExpandTxt">…<b>继续阅读</b></span>
</div>
<div class="collapse" v-if="showCollapseTxt">收起</div>
</div>
</template>
<script>
import {get, trim, forEach} from 'lodash';
const MAX_LINES = 6;
export default {
name: 'ArticleIntro',
props: {
data: {
type: Object,
default() {
return {};
}
},
share: Boolean,
thumb: Boolean
},
data() {
return {
introHeight: 0,
introCollapseHeight: 0,
isIntroEllipsis: true,
isIntroExpand: false,
isIntroAnimateing: false,
canRetract: false,
}
},
watch: {
'data.intro': function() {
this.introHeight = 0;
this.updateHeight();
}
},
computed: {
maxLines() {
return (this.data.maxLines || MAX_LINES) - 1;
},
trimIntro() {
return trim(this.data.intro);
},
showIntro() {
let line = 0;
let textArr = [];
forEach(this.trimIntro, (val, index) => {
let isEnter = /(\r\n)|(\n)/.test(val);
textArr[line] = textArr[line] || '';
textArr[line] += val;
if (textArr[line].length >= 24 || isEnter) {
line++;
}
if (line > this.maxLines) {
textArr[this.maxLines] = textArr[this.maxLines].substring(0, Math.min(21, textArr[this.maxLines].length));
return false;
}
});
this.isIntroEllipsis = line > this.maxLines;
return trim(textArr.join(''));
},
intro() {
if (this.isIntroExpand || !this.isIntroEllipsis) {
return this.trimIntro;
} else {
return this.showIntro;
}
},
introClass() {
return {
'intro-expand': this.isIntroExpand,
'no-more': !this.isIntroEllipsis,
'intro-will-change': this.isIntroAnimateing
};
},
introStyle() {
let introHeight;
if (this.isIntroExpand) {
introHeight = this.introHeight;
} else {
introHeight = this.introCollapseHeight;
}
return {
height: introHeight ? `${introHeight}px` : void 0
};
},
showExpandTxt() {
return this.isIntroEllipsis && !this.isIntroExpand && !this.isIntroAnimateing;
},
showCollapseTxt() {
return this.isIntroEllipsis && this.isIntroExpand && !this.isIntroAnimateing && this.canRetract;
}
},
mounted() {
this.updateHeight();
this.$refs.intro && this.$refs.intro.addEventListener('transitionend', this.onExpand.bind(this));
},
destroyed() {
this.$refs.intro && this.$refs.intro.removeEventListener('transitionend', this.onExpand.bind(this));
},
methods: {
updateHeight() {
this.$nextTick(() => {
this.introHeight = get(this.$refs, 'introPool.scrollHeight', 0) + 20;
this.introCollapseHeight = get(this.$refs, 'intro.offsetHeight', 0);
console.log(this.introHeight, this.introCollapseHeight);
});
},
onExpanding() {
if (!this.isIntroEllipsis || (this.isIntroExpand && !this.canRetract)) {
return;
}
if (!this.canRetract) {
this.canRetract = this.introHeight - 21 > get(this.$refs, 'expand.offsetHeight', 0) * (this.maxLines + 4);
}
this.isIntroExpand = !this.isIntroExpand;
this.isIntroAnimateing = true;
},
onExpand() {
this.isIntroAnimateing = false;
this.$nextTick(() => {
this.$emit('on-expand');
});
},
},
};
</script>
<style lang="scss" scoped>
.intro-wrap {
padding: 0 30px;
margin-top: 20px;
overflow: hidden;
transition: height 250ms cubic-bezier(0.165, 0.84, 0.44, 1);
.intro-content {
line-height: 48px;
font-size: 28px;
color: #4a4a4a;
letter-spacing: 0.06PX;
box-sizing: content-box;
position: relative;
}
.pre-wrap {
white-space: pre-wrap;
word-wrap: break-word;
display: inline;
}
&.intro-will-change {
will-change: height;
}
&.intro-expand {
-webkit-line-clamp: initial;
text-overflow: initial;
}
&.no-more {
height: auto;
}
}
.intro-pool {
position: absolute;
width: 100%;
top: -1000px;
visibility: hidden;
}
.collapse {
width: 100%;
text-align: right;
font-size: 28px;
color: #000;
font-weight: bold;
}
.expand {
font-size: 28px;
color: #000;
line-height: 20px;
&.collapse {
position: absolute;
right: 14px;
bottom: 0;
height: 28px;
}
> b {
font-weight: bold;
}
}
</style>
... ...
... ... @@ -9,21 +9,27 @@
</div>
</ArticleDetailHeader>
<div ref="coverFigure" class="cover-figure">
<img class="cover-img" :src="coverImage.src" :style="`transform: translate3d(0, ${coverTranslateY}px, 0)`">
<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="onFollow"></ArticleItemHeader>
</div>
<div class="main-detail">
<div class="article-context" style="height: 1000px;background: #ccc;">
<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>
<ArticleItemTopics class="topics-wrap" :data="data" :share="share"></ArticleItemTopics>
<LayoutTitle class="rec-goods-title">推荐商品</LayoutTitle>
<ProductGroup :data="recomendProduct" model="2"></ProductGroup>
<LayoutTitle class="rec-article-title">{{listTitle}}</LayoutTitle>
</div>
<ArticleDetailFooter class="detail-fixed-footer" @on-comment-click="onComment">
<ArticleDetailFooter class="detail-fixed-footer" v-bind="footerData" @on-comment-click="onComment">
<template v-slot:after>
<div class="article-goods">文中商品</div>
</template>
... ... @@ -58,57 +64,7 @@ export default {
downgrade: false,
showMoreOpt: false,
authorBlock: {},
recomendProduct: [{
"id": 230,
"marketPrice": 390,
"orderBy": 0,
"productId": 142427,
"productImage": "http://img10.static.yhbimg.com/goodsimg/2019/01/16/16/0123e8e8c7243ca83e37e6a11d4a431777.png?imageView2/{mode}/w/{width}/h/{height}",
"productName": "Timberland 插肩印花短袖T恤",
"productSkn": 51085719,
"productType": 1,
"salesPrice": 11
}, {
"id": 232,
"marketPrice": 1290,
"orderBy": 1,
"productId": 142447,
"productImage": "http://img13.static.yhbimg.com/goodsimg/2015/02/13/08/024becd3478789516c2749d5bc0e4e48cc.jpg?imageView2/{mode}/w/{width}/h/{height}",
"productName": "Timberland 女士经典无里衬帆船鞋",
"productSkn": 51085694,
"productType": 1,
"salesPrice": 11
}, {
"id": 234,
"marketPrice": 444,
"orderBy": 2,
"productId": 508376,
"productImage": "http://img13.static.yhbimg.com/goodsimg/2017/03/31/17/021204a24a95b6a5c258e7469e7bbbd22f.jpg?imageView2/{mode}/w/{width}/h/{height}",
"productName": "bigrabbit男手套",
"productSkn": 512587700,
"productType": 1,
"salesPrice": 444
}, {
"id": 236,
"marketPrice": 790,
"orderBy": 3,
"productId": 142413,
"productImage": "http://img10.static.yhbimg.com/goodsimg/2019/01/17/10/01d10b02308ecbfb42176cbf965478181a.jpg?imageView2/{mode}/w/{width}/h/{height}",
"productName": "Timberland 男士休闲短裤116",
"productSkn": 51085737,
"productType": 1,
"salesPrice": 11
}, {
"id": 238,
"marketPrice": 429,
"orderBy": 4,
"productId": 69792,
"productImage": "http://img13.static.yhbimg.com/goodsimg/2014/05/14/03/02e5b2050d3fd73584dd786cadc4eaf68d.jpg?imageView2/{mode}/w/{width}/h/{height}",
"productName": "izzue 碎花拼接T恤",
"productSkn": 51041119,
"productType": 1,
"salesPrice": 11
}],
recomendProduct: [],
};
},
mounted() {
... ... @@ -134,8 +90,12 @@ export default {
}
});
let width = Math.min(+this.data.imageWidth, 1000);
return {
src: '//flv01.static.yhbimg.com/grassImg/2019/05/07/12/03a620a29bb8b3a4f508dc8f86f6974a7a.jpg'
src: this.data.coverImage,
width,
height: width * this.data.imageHeight / this.data.imageWidth
};
},
coverTranslateY() {
... ... @@ -198,6 +158,16 @@ export default {
isAuthor: this.data.isAuthor
};
},
footerData() {
return {
favoriteCount: this.data.favoriteCount,
praiseCount: this.data.praiseCount,
commentCount: this.data.commentCount,
hasFavor: this.data.hasFavor,
hasPraise: this.data.hasPraise,
articleId: this.data.articleId
};
},
lazy() {
return this.data.lazy;
}
... ... @@ -268,12 +238,30 @@ export default {
.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;
}
.context-rich-text /deep/ {
img {
max-width: 100%!important;
}
p {
font-size: 24px!important;
}
}
.topics-wrap {
padding-left: 30px;
margin-bottom: 36px;
... ...
... ... @@ -13,13 +13,14 @@
<div class="article-context">
<ArticleItemSlide :thumb="thumb" :share="share" :data="slideData" :slide-index="slideIndex" :lazy="lazy" @on-praise="onPraise" @change="onChangeSlide"></ArticleItemSlide>
<ProductGroup :article-id="data.articleId" :pos-id="0" :index="0" :thumb="thumb" v-if="productListData.length" :share="share" :data="productListData" :lazy="lazy"></ProductGroup>
<ArticleDetailIntro :data="introData"></ArticleDetailIntro>
<ArticleItemTopics class="topics-wrap" :data="topicsData" :share="share"></ArticleItemTopics>
<p class="publish-time">{{publishTime}}</p>
<ArticleDetailCommentList :dest-id="140838" :article-id="data.articleId" :comment-count="data.commentCount"></ArticleDetailCommentList>
<ArticleDetailCommentList v-if="data.commentCount" :article-id="data.articleId" :comment-count="data.commentCount"></ArticleDetailCommentList>
<LayoutTitle class="rec-article-title">{{listTitle}}</LayoutTitle>
</div>
<ArticleDetailFooter class="detail-fixed-footer" @on-comment-click="onComment">
<ArticleDetailFooter class="detail-fixed-footer" v-bind="footerData" @on-comment-click="onComment">
<template v-slot:before>
<div class="footer-comment">
<CommentPlaceholder
... ... @@ -51,6 +52,7 @@ import ArticleItemHeader from '../article/article-item-header';
import ArticleItemSlide from '../article/article-item-slide';
import ArticleItemTopics from '../article/article-item-topics';
import ArticleDetailFooter from './article-footer';
import ArticleDetailIntro from './article-intro';
import dayjs from 'utils/day';
export default {
... ... @@ -107,12 +109,30 @@ export default {
productListData() {
return this.data.productList || [];
},
introData() {
let blockList = get(this.data, 'blockList', []);
return {
intro: get(blockList.filter(block => block.templateKey === 'text'), '[0].contentData', ''),
maxLines: 10
};
},
topicsData() {
return {
topicList: this.data.topicList,
articleType: this.data.articleType
};
},
footerData() {
return {
favoriteCount: this.data.favoriteCount,
praiseCount: this.data.praiseCount,
commentCount: this.data.commentCount,
hasFavor: this.data.hasFavor,
hasPraise: this.data.hasPraise,
articleId: this.data.articleId
};
},
publishTime() {
return dayjs(this.data.publishTime).fromNow();
},
... ... @@ -141,6 +161,7 @@ export default {
ArticleItemHeader,
ArticleItemSlide,
ArticleItemTopics,
ArticleDetailIntro,
ArticleDetailCommentList,
ArticleDetailFooter
}
... ...
... ... @@ -34,7 +34,6 @@ const {mapActions} = createNamespacedHelpers('comment');
export default {
name: 'ArticleDetailCommentList',
props: {
destId: [Number, String],
commentCount: [Number, String],
articleId: [Number, String],
columnType: {
... ... @@ -71,7 +70,7 @@ export default {
this.onFetching = true;
const result = await this.fetchCommentList({
destId: this.destId,
destId: this.articleId,
columnType: this.columnType,
page: this.page,
limit: 10
... ...
... ... @@ -4,7 +4,7 @@ import * as guangProcess from './guangProcess';
import * as sleep from '../../utils/sleep';
export default {
async fetchArticleList({ commit }, { articleId, authorUid, authorType, limit = 5, page = 1, thumb = false, columnType = 1001}) {
async fetchArticleList({ commit }, { articleId, authorUid, authorType, limit = 5, page = 1, thumb = false, columnType = 1001, singleDetail = 'N'}) {
commit(Types.FETCH_ARTICLE_LIST_REQUEST, { refresh: page === 1 });
const result = await this.$api.get('/api/grass/columnArticleDetail', {
articleId,
... ... @@ -12,7 +12,8 @@ export default {
page,
authorUid,
authorType,
columnType
columnType,
singleDetail
});
if (result && result.code === 200) {
... ...
... ... @@ -33,7 +33,8 @@ module.exports = {
page: {type: Number, require: false},
limit: {type: Number, require: false},
articleId: {type: Number},
columnType: {type: Number}
columnType: {type: Number},
singleDetail: {type: String}
}
},
'/api/grass/updateAttention': {
... ...