Authored by yyq

detail

... ... @@ -102,7 +102,9 @@ export default {
let list = newList.slice(oldList.length, newList.length);
if (list.length) {
this.revealCalcing = true;
this.loadItems(list, oldList.length).then(() => {
this.revealCalcing = false;
if (oldList.length < 2) {
this._updateList();
}
... ... @@ -141,14 +143,16 @@ export default {
this.load(true);
},
clear() {
this.clearTimestamp = new Date().getTime();
this.noMore = false;
this.items = [];
for (let i = 0; i < this.cols; i++) {
this.visibleItems[i].length = 0;
this.colsHeight[i] = 0;
this.startIndexs[i] = 0;
}
this.items = [];
},
load(reload) {
if (reload) {
... ... @@ -182,6 +186,7 @@ export default {
}
let startCol = this.getMinHeightCol();
const timestamp = this.clearTimestamp;
for (let i = 0; i < list.length; i++) {
await this.loadItem({
... ... @@ -190,7 +195,7 @@ export default {
width: this.itemWidth,
isThumb: true,
placeholder: false
}, i < lastIndex ? (startCol + i) % this.cols : -1);
}, i < lastIndex ? (startCol + i) % this.cols : -1, timestamp);
}
this.$nextTick(() => {
... ... @@ -228,8 +233,12 @@ export default {
return true;
});
},
loadItem(item, index) {
loadItem(item, index, timestamp) {
return new Promise(r => {
if (timestamp !== this.clearTimestamp) {
return r();
}
if (index < 0) {
index = this.getMinHeightCol();
}
... ... @@ -298,7 +307,7 @@ export default {
const heights = this.$refs.scroll.offsetHeight;
// trigger load
if (scrollTop + this.$el.offsetHeight > heights - this.offset) {
if (scrollTop + this.$el.offsetHeight > heights - this.offset && !this.revealCalcing) {
this.load();
}
... ...
<template>
<Layout class="article-detail">
<RecycleScrollReveal :size="5" ref="scroll" @scroll="onScroll" :offset="800" :on-fetch="onFetch" :manual-init="true">
<RecycleScrollReveal :size="10" ref="scroll" @scroll="onScroll" :offset="2000" :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>
<ArticleDeatilLong v-if="article.sort == 2" ref="detailLong" :data="article" :scroll-top="scrollTop" :list-title="listTitle"></ArticleDeatilLong>
<ArticleDeatilNote v-else :data="article" :scroll-top="scrollTop" :list-title="listTitle"></ArticleDeatilNote>
</template>
<template class="article-item" #item="{ data }">
<ArticleItem2
... ... @@ -52,14 +52,22 @@ export default {
this.init();
}
},
beforeRouteUpdate(to, from, next) {
if (+this.$route.params.id !== +to.params.id) {
this.id = +to.params.id;
this.init();
}
next();
},
mounted() {
this.colWidthForTwo = Math.floor(this.$el.offsetWidth / 2);
},
computed: {
},
methods: {
...mapActions(['fetchArticleList']),
...mapActions(['fetchArticleList', 'fetchDetailRecommendAricles']),
init() {
this.recommendArticles = {};
this.syncServiceArticleDetail();
},
onScroll({scrollTop}) {
... ... @@ -76,15 +84,57 @@ export default {
this.fetchArticleList({
articleId,
page: 1,
limit: 1,
singleDetail: 'Y'
}).then(res => {
this.article = get(res, 'data.detailList[0]', {});
if (this.$refs.scroll) {
this.$refs.scroll.$el.scrollTop = 0;
this.$refs.scroll.clear();
this.$nextTick(() => {
this.$refs.scroll.init();
});
}
});
},
onFetch() {
return Promise.resolve([this.article]);
async onFetch() {
if (!this.id || this.fetching) {
return [];
}
// 推荐文章接口一次性提供,不支持分页
if (this.recommendArticles[this.id]) {
return false;
}
this.fetching = true;
let list = [];
const result = await this.fetchDetailRecommendAricles({
articleId: this.id
});
this.fetching = false;
if (result.code === 200) {
list = get(result, 'data', []);
if (!list || !list.length) {
list = false;
} else {
this.recommendArticles[this.id] = true;
}
} else {
this.$createToast && this.$createToast({
txt: result.message || '服务器开小差了',
type: 'warn',
time: 1000
}).show();
}
return list;
}
},
components: {
... ... @@ -100,4 +150,8 @@ export default {
padding: 6px;
background-color: #f0f0f0;
}
/deep/ .loading {
height: 100px;
visibility: hidden;
}
</style>
... ...
... ... @@ -2,8 +2,9 @@
<div class="article-item">
<slot>
<div class="article-item-main">
<a v-if="actionUrl" class="action-article" :href="actionUrl" target="_blank"></a>
<div class="layer-image" :style="`height: ${Math.floor(width * coverImage.scale)}px`" @click="toArticlePage">
<ImageFormat :mode="1" :src="coverImage.contentData" :width="imgWidth" :height="Math.floor(coverImage.scale * imgWidth)" :style="imageStyle"></ImageFormat>
<ImageFormat :mode="1" :src="coverImage.contentData" :width="coverImage.width" :height="coverImage.height"></ImageFormat>
</div>
<div v-if="intro" class="description" @click="toArticlePage">{{intro}}</div>
<div class="attribution">
... ... @@ -23,6 +24,7 @@
<script>
import {get, round} from 'lodash';
import YAS from 'utils/yas-constants';
import {getArticleImageSize} from 'utils/image-handler';
import {createNamespacedHelpers} from 'vuex';
const {mapState} = createNamespacedHelpers('article');
... ... @@ -55,23 +57,31 @@ export default {
return this.articleStates[this.data.articleId] || this.data;
},
coverImage() {
let img = get(this.data, 'blockList', []).filter(block => block.templateKey === 'image')[0] || {};
let img = get(this.data, 'blockList', []).filter(block => block.templateKey === 'image')[0] || {
contentData: this.data.coverImage,
width: this.data.imageWidth,
height: this.data.imageHeight
};
Object.assign(img, getArticleImageSize({
width: img.width,
height: img.height,
maxWidth: 500
}));
img.scale = img.height / img.width;
return img;
},
imageStyle() {
let width = 370;
let height = Math.floor(this.coverImage.scale * width);
return {
width: `${round(width / 40, 2)}rem`,
height: `${round(height / 40, 2)}rem`,
};
},
intro() {
return get(get(this.data, 'blockList', []).filter(block => block.templateKey === 'text'), '[0].contentData', '')
return get(get(this.data, 'blockList', []).filter(block => block.templateKey === 'text'), '[0].contentData', this.data.content)
},
actionUrl() {
if (this.data.sort === 3) {
return this.data.actionUrl;
}
return '';
},
favOption() {
return {
... ... @@ -110,12 +120,10 @@ export default {
}
this.$router.push({
name: 'article',
name: 'article.detail',
params: {
id: this.data.articleId
},
query: {
columnType: 1002
id: this.data.articleId,
type: this.data.sort
}
});
this.reportClickArticle();
... ... @@ -155,9 +163,24 @@ export default {
.article-item-main {
background-color: #fff;
border-radius: 2PX;
position: relative;
overflow: hidden;
}
.action-article {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
.layer-image img {
width: 100%;
height: 100%;
}
.description {
margin: 14px 20px;
font-size: 24px;
... ...
... ... @@ -2,7 +2,7 @@
<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>
<div ref="introPool" class="intro-pool pre-wrap" v-if="!introHeight">{{trimIntro}}</div>
<span ref="expand" class="expand" v-if="showExpandTxt">…<b>继续阅读</b></span>
</div>
<div class="collapse" v-if="showCollapseTxt">收起</div>
... ... @@ -47,6 +47,8 @@ export default {
return (this.data.maxLines || MAX_LINES) - 1;
},
trimIntro() {
this.introHeight = 0;
this.introCollapseHeight = 0;
return trim(this.data.intro);
},
showIntro() {
... ... @@ -120,8 +122,6 @@ export default {
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() {
... ...
... ... @@ -8,7 +8,7 @@
</div>
</div>
</ArticleDetailHeader>
<div ref="coverFigure" class="cover-figure">
<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">
... ... @@ -34,11 +34,22 @@
<div class="article-goods">文中商品</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 {get} from 'lodash';
import {get, floor} from 'lodash';
import ArticleItemHeader from '../article/article-item-header';
import ArticleItemTopics from '../article/article-item-topics';
import ArticleDetailFooter from './article-footer';
... ... @@ -65,6 +76,8 @@ export default {
showMoreOpt: false,
authorBlock: {},
recomendProduct: [],
showCommentAction: false,
showCommentActioning: false
};
},
mounted() {
... ... @@ -76,6 +89,11 @@ export default {
},
computed: {
...mapState(['yoho']),
coverStyle() {
return {
height: `${floor(this.data.imageHeight / this.data.imageHeight * 750 / 40, 2)}rem`
};
},
coverImage() {
this.$nextTick(() => {
if (this.$refs.coverFigure) {
... ... @@ -125,7 +143,7 @@ export default {
if (this.$refs && this.$refs.header) {
scrollTop += this.$refs.header.$el.offsetHeight;
}
console.log(scrollTop, top, height)
if (top && height) {
if (scrollTop >= top + height) {
return 100;
... ... @@ -182,8 +200,24 @@ export default {
this.$emit('on-follow', 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,
... ...
... ... @@ -15,17 +15,25 @@ export default [{
keepAlive: true
}
}, {
path: '/article/detail/:id',
name: 'article.detail',
alias: '/article/detail/:id',
path: '/article/detail2/:id',
name: 'article.detail2',
alias: '/article/detail2/:id',
component: () => import(/* webpackChunkName: "article" */ './article-detail'),
meta: {
keepAlive: true
}
}, {
path: '/article/detail2/:id',
name: 'article.detail2',
alias: '/article/detail2/:id',
path: '/article/:type/:id',
name: 'article.detail',
alias: '/article/:type/:id',
component: () => import(/* webpackChunkName: "article-detail" */ './article-detail2'),
meta: {
keepAlive: true
}
}, {
path: '/article/detail/:id',
name: 'article.detail',
alias: '/article/detail/:id',
component: () => import(/* webpackChunkName: "article-detail" */ './article-detail2'),
meta: {
keepAlive: true
... ...
... ... @@ -2,6 +2,7 @@
<div class="wf-item" :class="temporary ? 'wf-temp' : ''">
<div v-if="temporary"></div>
<div v-else class="wf-item-mid">
<a v-if="actionUrl" class="action-article" :href="actionUrl" target="_blank"></a>
<div class="layer-image" @click="onClick" :style="`height: ${data.blockWidth * data.scale}px`">
<ImageFormat :mode="1" :src="data.coverImage" :width="imgWidth" :height="Math.floor(data.scale * imgWidth)"></ImageFormat>
</div>
... ... @@ -57,6 +58,13 @@ export default {
iconFontSize: 30,
textAlign: 'normal'
};
},
actionUrl() {
if (this.data.sort === 3) {
return this.data.actionUrl;
}
return '';
}
},
methods: {
... ... @@ -91,9 +99,19 @@ export default {
.wf-item-mid {
border-radius: 2PX;
overflow: hidden;
position: relative;
background-color: #fff;
}
.action-article {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
}
.layer-image {
background-color: #f4f4f4;
min-height: 100px;
... ...
... ... @@ -280,5 +280,21 @@ export default {
}
return result;
},
async fetchDetailRecommendAricles({ commit }, { articleId }) {
commit(Types.FETCH_DETAIL_RECOMMEND_REQUEST);
const result = await this.$api.post('/api/grass/detailRecommendArticles', {
articleId
});
if (result && result.code === 200) {
commit(Types.FETCH_DETAIL_RECOMMEND_SUCCESS, {
data: result.data || []
});
} else {
commit(Types.FETCH_DETAIL_RECOMMEND_FAILD);
}
return result;
}
};
... ...
... ... @@ -20,7 +20,9 @@ export default function() {
articleStates: {},
fetchTopicInfo: false,
topicInfo: {},
fetchTopicArticles: false
fetchTopicArticles: false,
articleDetailList: [],
fetchDetailRecommendArticles: false
},
actions,
mutations
... ...
... ... @@ -63,14 +63,17 @@ function setArticleList(state, data, type, thumb) {
let {width, height} = getArticleImageSize({
width: img.width,
height: img.height,
MIN_SCALE: inx === 0 ? (3 / 4) : 0
minScale: inx === 0 ? (3 / 4) : 0
});
img.width = parseInt(width, 10);
img.height = parseInt(height, 10);
});
});
state[articlefield(type, thumb)] = state[articlefield(type, thumb)].concat(data);
if (articlefield(type, thumb)) {
state[articlefield(type, thumb)] = state[articlefield(type, thumb)].concat(data);
}
}
export default {
... ... @@ -227,4 +230,14 @@ export default {
state.topicInfo.hasAttention = follow;
}
},
[Types.FETCH_DETAIL_RECOMMEND_REQUEST](state, topicId) {
state.fetchDetailRecommendArticles = true;
},
[Types.FETCH_DETAIL_RECOMMEND_SUCCESS](state, {data}) {
state.fetchDetailRecommendArticles = false;
setArticleList(state, data, 'detail');
},
[Types.FETCH_DETAIL_RECOMMEND_FAILD](state) {
state.fetchDetailRecommendArticles = false;
},
};
... ...
... ... @@ -33,3 +33,7 @@ export const FETCH_TOPIC_INFO_REQUEST = 'FETCH_TOPIC_INFO_REQUEST';
export const FETCH_TOPIC_INFO_FAILD = 'FETCH_TOPIC_INFO_FAILD';
export const FETCH_TOPIC_INFO_SUCCESS = 'FETCH_TOPIC_INFO_SUCCESS';
export const CHANGE_TOPIC_FOLLOW = 'CHANGE_TOPIC_FOLLOW';
export const FETCH_DETAIL_RECOMMEND_REQUEST = 'FETCH_DETAIL_RECOMMEND_REQUEST';
export const FETCH_DETAIL_RECOMMEND_FAILD = 'FETCH_DETAIL_RECOMMEND_FAILD';
export const FETCH_DETAIL_RECOMMEND_SUCCESS = 'FETCH_DETAIL_RECOMMEND_SUCCESS';
... ...
const MAX_WIDTH = 1000;
export function getArticleImageSize({width, height, MIN_SCALE = 0.75}) {
export function getArticleImageSize({width, height, minScale = 0.75, maxWidth = MAX_WIDTH}) {
width = +width;
height = +height;
if (width > MAX_WIDTH) {
height = height / (width / MAX_WIDTH);
width = MAX_WIDTH;
if (width > maxWidth) {
height = height / (width / maxWidth);
width = maxWidth;
}
if (MIN_SCALE && width / height < MIN_SCALE) {
height = width / MIN_SCALE;
if (minScale && width / height < minScale) {
height = width / minScale;
}
if (width === 1) {
width = MAX_WIDTH / 2;
width = maxWidth / 2;
}
if (height === 1) {
height = MAX_WIDTH / 2;
height = maxWidth / 2;
}
return {width, height};
return {width, height: Math.round(height)};
}
... ...
... ... @@ -37,6 +37,14 @@ module.exports = {
singleDetail: {type: String}
}
},
'/api/grass/detailRecommendArticles': {
api: 'app.grass.otherArticle',
cache: true,
auth: true,
params: {
articleId: {type: Number, require: false}
}
},
'/api/grass/updateAttention': {
api: 'app.grass.updateAttention',
auth: true,
... ...