diff --git a/apps/components/layouts/recycle-scroll-reveal.vue b/apps/components/layouts/recycle-scroll-reveal.vue index 205e9f2..332dc7b 100644 --- a/apps/components/layouts/recycle-scroll-reveal.vue +++ b/apps/components/layouts/recycle-scroll-reveal.vue @@ -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(); } diff --git a/apps/pages/article/article-detail2.vue b/apps/pages/article/article-detail2.vue index ac5101c..73db3db 100644 --- a/apps/pages/article/article-detail2.vue +++ b/apps/pages/article/article-detail2.vue @@ -1,9 +1,9 @@ <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> diff --git a/apps/pages/article/components/article/article-item2.vue b/apps/pages/article/components/article/article-item2.vue index d096470..267abf2 100644 --- a/apps/pages/article/components/article/article-item2.vue +++ b/apps/pages/article/components/article/article-item2.vue @@ -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; diff --git a/apps/pages/article/components/detail/article-intro.vue b/apps/pages/article/components/detail/article-intro.vue index a1c5aaa..fb289f4 100644 --- a/apps/pages/article/components/detail/article-intro.vue +++ b/apps/pages/article/components/detail/article-intro.vue @@ -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() { diff --git a/apps/pages/article/components/detail/article-long.vue b/apps/pages/article/components/detail/article-long.vue index 0535403..69a7401 100644 --- a/apps/pages/article/components/detail/article-long.vue +++ b/apps/pages/article/components/detail/article-long.vue @@ -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, diff --git a/apps/pages/article/index.js b/apps/pages/article/index.js index 981916f..381e172 100644 --- a/apps/pages/article/index.js +++ b/apps/pages/article/index.js @@ -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 diff --git a/apps/pages/userpage/components/author-article-item.vue b/apps/pages/userpage/components/author-article-item.vue index 3819560..7fa60f9 100644 --- a/apps/pages/userpage/components/author-article-item.vue +++ b/apps/pages/userpage/components/author-article-item.vue @@ -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; diff --git a/apps/store/article/actions.js b/apps/store/article/actions.js index b5f8a8e..f5aa039 100644 --- a/apps/store/article/actions.js +++ b/apps/store/article/actions.js @@ -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; } }; diff --git a/apps/store/article/index.js b/apps/store/article/index.js index 38d57e7..ba17150 100644 --- a/apps/store/article/index.js +++ b/apps/store/article/index.js @@ -20,7 +20,9 @@ export default function() { articleStates: {}, fetchTopicInfo: false, topicInfo: {}, - fetchTopicArticles: false + fetchTopicArticles: false, + articleDetailList: [], + fetchDetailRecommendArticles: false }, actions, mutations diff --git a/apps/store/article/mutations.js b/apps/store/article/mutations.js index f61a052..f75faf4 100644 --- a/apps/store/article/mutations.js +++ b/apps/store/article/mutations.js @@ -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; + }, }; diff --git a/apps/store/article/types.js b/apps/store/article/types.js index ebeace6..022ddbc 100644 --- a/apps/store/article/types.js +++ b/apps/store/article/types.js @@ -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'; diff --git a/apps/utils/image-handler.js b/apps/utils/image-handler.js index 3a567d9..0766b8e 100644 --- a/apps/utils/image-handler.js +++ b/apps/utils/image-handler.js @@ -1,21 +1,21 @@ 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)}; } diff --git a/config/api-map.js b/config/api-map.js index f2f9a9c..c96b1a1 100644 --- a/config/api-map.js +++ b/config/api-map.js @@ -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,