Authored by yyq

server prefetch && share

@@ -2,8 +2,8 @@ @@ -2,8 +2,8 @@
2 <Layout class="article-detail"> 2 <Layout class="article-detail">
3 <RecycleScrollReveal :size="10" ref="scroll" @scroll="onScroll" :offset="2000" :on-fetch="onFetch" :manual-init="true"> 3 <RecycleScrollReveal :size="10" ref="scroll" @scroll="onScroll" :offset="2000" :on-fetch="onFetch" :manual-init="true">
4 <template v-slot:eternalTop> 4 <template v-slot:eternalTop>
5 - <ArticleDeatilLong v-if="article.sort == 2" ref="detailLong" :data="article" :scroll-top="scrollTop" :list-title="listTitle" :scroll-to="scrollTo" @on-show-more="onShowMore"></ArticleDeatilLong>  
6 - <ArticleDeatilNote v-else :data="article" :scroll-top="scrollTop" :list-title="listTitle" :scroll-to="scrollTo" @on-show-more="onShowMore"></ArticleDeatilNote> 5 + <ArticleDeatilLong v-if="articleSingleDetail.sort == 2" ref="detailLong" :data="articleSingleDetail" :scroll-top="scrollTop" :list-title="listTitle" :scroll-to="scrollTo" @on-show-more="onShowMore"></ArticleDeatilLong>
  6 + <ArticleDeatilNote v-else :data="articleSingleDetail" :scroll-top="scrollTop" :list-title="listTitle" :scroll-to="scrollTo" @on-show-more="onShowMore"></ArticleDeatilNote>
7 </template> 7 </template>
8 <template class="article-item" #item="{ data }"> 8 <template class="article-item" #item="{ data }">
9 <ArticleItem2 9 <ArticleItem2
@@ -42,7 +42,6 @@ export default { @@ -42,7 +42,6 @@ export default {
42 id: 0, 42 id: 0,
43 scrollTop: 0, 43 scrollTop: 0,
44 scrolling: false, 44 scrolling: false,
45 - article: {},  
46 share: false, 45 share: false,
47 listTitle: '', 46 listTitle: '',
48 colWidthForTwo: 370, 47 colWidthForTwo: 370,
@@ -62,10 +61,24 @@ export default { @@ -62,10 +61,24 @@ export default {
62 } 61 }
63 next(); 62 next();
64 }, 63 },
  64 + async serverPrefetch() {
  65 + const articleId = parseInt(this.$route.params.id, 10);
  66 +
  67 + if (articleId > 0) {
  68 + return this.fetchArticleList({
  69 + articleId,
  70 + singleDetail: 'Y',
  71 + thumb: true
  72 + });
  73 + }
  74 +
  75 + return;
  76 + },
65 mounted() { 77 mounted() {
66 this.colWidthForTwo = Math.floor(this.$el.offsetWidth / 2); 78 this.colWidthForTwo = Math.floor(this.$el.offsetWidth / 2);
67 }, 79 },
68 computed: { 80 computed: {
  81 + ...mapState(['articleSingleDetail'])
69 }, 82 },
70 methods: { 83 methods: {
71 ...mapActions(['fetchArticleList', 'fetchDetailRecommendAricles']), 84 ...mapActions(['fetchArticleList', 'fetchDetailRecommendAricles']),
@@ -89,8 +102,6 @@ export default { @@ -89,8 +102,6 @@ export default {
89 articleId, 102 articleId,
90 singleDetail: 'Y' 103 singleDetail: 'Y'
91 }).then(res => { 104 }).then(res => {
92 - this.article = get(res, 'data.detailList[0]', {});  
93 -  
94 if (this.$refs.scroll) { 105 if (this.$refs.scroll) {
95 this.$refs.scroll.$el.scrollTop = 0; 106 this.$refs.scroll.$el.scrollTop = 0;
96 this.$refs.scroll.clear(); 107 this.$refs.scroll.clear();
@@ -146,7 +157,7 @@ export default { @@ -146,7 +157,7 @@ export default {
146 this.scrollTop = scrollTop; 157 this.scrollTop = scrollTop;
147 }, 158 },
148 onShowMore() { 159 onShowMore() {
149 - this.$refs.moreAction.show(this.article); 160 + this.$refs.moreAction.show(this.articleSingleDetail);
150 }, 161 },
151 onFollow() {}, 162 onFollow() {},
152 onDelete() {} 163 onDelete() {}
@@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
2 <div class="article-footer-wrapper"> 2 <div class="article-footer-wrapper">
3 <slot name="before"></slot> 3 <slot name="before"></slot>
4 <div class="tool-bar"> 4 <div class="tool-bar">
5 - <WidgetIconBtn class="item" type="fav" :pos-id="sceneId" :text="praiseCount" :articleId="articleId" :option="optionPraise"></WidgetIconBtn> 5 + <WidgetIconBtn ref="favIcon" class="item" type="fav" :pos-id="sceneId" :text="praiseCount" :articleId="articleId" :option="optionPraise"></WidgetIconBtn>
6 <WidgetIconBtn class="item" type="star" :pos-id="sceneId" :text="favoriteCount" :articleId="articleId" :option="optionFav" ></WidgetIconBtn> 6 <WidgetIconBtn class="item" type="star" :pos-id="sceneId" :text="favoriteCount" :articleId="articleId" :option="optionFav" ></WidgetIconBtn>
7 <WidgetIconBtn class="item" type="msg" :text="commentCount" :option="optionComment" @click="onComment"></WidgetIconBtn> 7 <WidgetIconBtn class="item" type="msg" :text="commentCount" :option="optionComment" @click="onComment"></WidgetIconBtn>
8 </div> 8 </div>
@@ -21,12 +21,6 @@ export default { @@ -21,12 +21,6 @@ export default {
21 props: ['favoriteCount', 'praiseCount', 'commentCount', 'hasFavor', 'hasPraise', 'articleId'], 21 props: ['favoriteCount', 'praiseCount', 'commentCount', 'hasFavor', 'hasPraise', 'articleId'],
22 data() { 22 data() {
23 return { 23 return {
24 - optionPraise: {  
25 - selected: this.hasPraise === 'Y'  
26 - },  
27 - optionFav: {  
28 - selected: this.hasFavor === 'Y'  
29 - },  
30 optionComment: { 24 optionComment: {
31 emitName: 'click', 25 emitName: 'click',
32 canSelect: false 26 canSelect: false
@@ -35,8 +29,16 @@ export default { @@ -35,8 +29,16 @@ export default {
35 }; 29 };
36 }, 30 },
37 computed: { 31 computed: {
38 - },  
39 - mounted() { 32 + optionPraise() {
  33 + return {
  34 + selected: this.hasPraise === 'Y'
  35 + };
  36 + },
  37 + optionFav() {
  38 + return {
  39 + selected: this.hasFavor === 'Y'
  40 + }
  41 + }
40 }, 42 },
41 methods: { 43 methods: {
42 onComment() { 44 onComment() {
@@ -44,7 +46,12 @@ export default { @@ -44,7 +46,12 @@ export default {
44 }, 46 },
45 onClose() { 47 onClose() {
46 this.$emit('on-close'); 48 this.$emit('on-close');
47 - } 49 + },
  50 + onPraise() {
  51 + if (this.hasPraise !== 'Y') {
  52 + this.$refs.favIcon.onClick();
  53 + }
  54 + },
48 }, 55 },
49 }; 56 };
50 </script> 57 </script>
@@ -14,8 +14,9 @@ @@ -14,8 +14,9 @@
14 </template> 14 </template>
15 15
16 <script> 16 <script>
17 -import {get} from 'lodash';  
18 import ArticleItemHeader from '../article/article-item-header'; 17 import ArticleItemHeader from '../article/article-item-header';
  18 +import {getDetailShareData} from 'utils/share-handler';
  19 +
19 export default { 20 export default {
20 name: 'ArticleDetailHeader', 21 name: 'ArticleDetailHeader',
21 data() { 22 data() {
@@ -59,17 +60,9 @@ export default { @@ -59,17 +60,9 @@ export default {
59 return '-' + parseInt(`0${this.titleStep}`, 10) + '%'; 60 return '-' + parseInt(`0${this.titleStep}`, 10) + '%';
60 } 61 }
61 }, 62 },
62 - watch: {  
63 - },  
64 methods: { 63 methods: {
65 onShare() { 64 onShare() {
66 - this.$yoho.share({  
67 - title: this.data.topicName,  
68 - imgUrl: this.data.topicImageUrl,  
69 - link: `${location.origin}/grass/topic/share/${this.data.topicId}`,  
70 - desc: '我在有货的社区发现一个热门话题。' + this.data.topicDesc,  
71 - hideType: ['7', '8', '9']  
72 - }); 65 + this.$yoho.share(getDetailShareData(this.data));
73 }, 66 },
74 onFollow() { 67 onFollow() {
75 return parseInt(this.step, 10) / 100; 68 return parseInt(this.step, 10) / 100;
1 <template> 1 <template>
2 <div class="article-detail-long"> 2 <div class="article-detail-long">
3 - <ArticleDetailHeader ref="header" :step="headerAnimateStep" :title-step="headerTitleAnimateStep"> 3 + <ArticleDetailHeader ref="header" :data="data" :step="headerAnimateStep" :title-step="headerTitleAnimateStep">
4 <div class="title-main"> 4 <div class="title-main">
5 <div class="title-info" :style="`transform: translate3d(0, ${titleTranslateY}, 0)`"> 5 <div class="title-info" :style="`transform: translate3d(0, ${titleTranslateY}, 0)`">
6 <ArticleItemHeader class="title-info-author" :share="share" :data="authorData" :lazy="lazy" :more="showMoreOpt" @on-follow="onFollow"></ArticleItemHeader> 6 <ArticleItemHeader class="title-info-author" :share="share" :data="authorData" :lazy="lazy" :more="showMoreOpt" @on-follow="onFollow"></ArticleItemHeader>
@@ -54,7 +54,8 @@ import ArticleItemHeader from '../article/article-item-header'; @@ -54,7 +54,8 @@ import ArticleItemHeader from '../article/article-item-header';
54 import ArticleItemTopics from '../article/article-item-topics'; 54 import ArticleItemTopics from '../article/article-item-topics';
55 import ArticleDetailFooter from './article-footer'; 55 import ArticleDetailFooter from './article-footer';
56 import ArticleDetailHeader from './article-header'; 56 import ArticleDetailHeader from './article-header';
57 -import {mapState, mapMutations} from 'vuex'; 57 +import {mapState, mapMutations, createNamespacedHelpers} from 'vuex';
  58 +const {mapState: mapArticleState} = createNamespacedHelpers('article');
58 59
59 export default { 60 export default {
60 name: 'ArticleDetailLong', 61 name: 'ArticleDetailLong',
@@ -89,6 +90,10 @@ export default { @@ -89,6 +90,10 @@ export default {
89 }, 90 },
90 computed: { 91 computed: {
91 ...mapState(['yoho']), 92 ...mapState(['yoho']),
  93 + ...mapArticleState(['articleStates']),
  94 + articleState() {
  95 + return this.articleStates[this.data.articleId] || this.data;
  96 + },
92 coverStyle() { 97 coverStyle() {
93 return { 98 return {
94 height: `${floor(this.data.imageHeight / this.data.imageHeight * 750 / 40, 2)}rem` 99 height: `${floor(this.data.imageHeight / this.data.imageHeight * 750 / 40, 2)}rem`
@@ -186,8 +191,8 @@ export default { @@ -186,8 +191,8 @@ export default {
186 favoriteCount: this.data.favoriteCount, 191 favoriteCount: this.data.favoriteCount,
187 praiseCount: this.data.praiseCount, 192 praiseCount: this.data.praiseCount,
188 commentCount: this.data.commentCount, 193 commentCount: this.data.commentCount,
189 - hasFavor: this.data.hasFavor,  
190 - hasPraise: this.data.hasPraise, 194 + hasFavor: this.articleState.hasFavor,
  195 + hasPraise: this.articleState.hasPraise,
191 articleId: this.data.articleId 196 articleId: this.data.articleId
192 }; 197 };
193 }, 198 },
1 <template> 1 <template>
2 <div class="article-detail-notes"> 2 <div class="article-detail-notes">
3 - <ArticleDetailHeader ref="header" :step="100" :title-step="100"> 3 + <ArticleDetailHeader ref="header" :data="data" :step="100" :title-step="100">
4 <div class="title-main"> 4 <div class="title-main">
5 <div class="title-info" :style="`transform: translate3d(0, ${titleTranslateY}, 0)`"> 5 <div class="title-info" :style="`transform: translate3d(0, ${titleTranslateY}, 0)`">
6 - <ArticleItemHeader class="title-info-author" :share="share" :data="authorData" :lazy="lazy" :more="false" @on-follow="onFollow"></ArticleItemHeader> 6 + <ArticleItemHeader class="title-info-author" :share="share" :data="authorData" :lazy="lazy" :more="false"></ArticleItemHeader>
7 <div class="title-info-rec">{{listTitle}}</div> 7 <div class="title-info-rec">{{listTitle}}</div>
8 </div> 8 </div>
9 </div> 9 </div>
@@ -28,7 +28,7 @@ @@ -28,7 +28,7 @@
28 <LayoutTitle>{{listTitle}}</LayoutTitle> 28 <LayoutTitle>{{listTitle}}</LayoutTitle>
29 </div> 29 </div>
30 </div> 30 </div>
31 - <ArticleDetailFooter class="detail-fixed-footer" v-bind="footerData" @on-comment-click="toCommentList"> 31 + <ArticleDetailFooter ref="footer" class="detail-fixed-footer" v-bind="footerData" @on-comment-click="toCommentList">
32 <template v-slot:before> 32 <template v-slot:before>
33 <div class="footer-comment"> 33 <div class="footer-comment">
34 <CommentPlaceholder 34 <CommentPlaceholder
@@ -63,6 +63,8 @@ import ArticleItemTopics from '../article/article-item-topics'; @@ -63,6 +63,8 @@ import ArticleItemTopics from '../article/article-item-topics';
63 import ArticleDetailFooter from './article-footer'; 63 import ArticleDetailFooter from './article-footer';
64 import ArticleDetailIntro from './article-intro'; 64 import ArticleDetailIntro from './article-intro';
65 import dayjs from 'utils/day'; 65 import dayjs from 'utils/day';
  66 +import {createNamespacedHelpers} from 'vuex';
  67 +const {mapState} = createNamespacedHelpers('article');
66 68
67 export default { 69 export default {
68 name: 'ArticleDetailNote', 70 name: 'ArticleDetailNote',
@@ -89,6 +91,10 @@ export default { @@ -89,6 +91,10 @@ export default {
89 } 91 }
90 }, 92 },
91 computed: { 93 computed: {
  94 + ...mapState(['articleStates']),
  95 + articleState() {
  96 + return this.articleStates[this.data.articleId] || this.data;
  97 + },
92 titleTranslateY() { 98 titleTranslateY() {
93 let scrollTop = this.scrollTop; 99 let scrollTop = this.scrollTop;
94 100
@@ -137,23 +143,21 @@ export default { @@ -137,23 +143,21 @@ export default {
137 favoriteCount: this.data.favoriteCount, 143 favoriteCount: this.data.favoriteCount,
138 praiseCount: this.data.praiseCount, 144 praiseCount: this.data.praiseCount,
139 commentCount: this.data.commentCount, 145 commentCount: this.data.commentCount,
140 - hasFavor: this.data.hasFavor,  
141 - hasPraise: this.data.hasPraise, 146 + hasFavor: this.articleState.hasFavor,
  147 + hasPraise: this.articleState.hasPraise,
142 articleId: this.data.articleId 148 articleId: this.data.articleId
143 }; 149 };
144 }, 150 },
145 publishTime() { 151 publishTime() {
146 - return dayjs(this.data.publishTime).fromNow(); 152 + return this.data.publishTime ? dayjs(this.data.publishTime).fromNow() : '';
147 }, 153 },
148 lazy() { 154 lazy() {
149 return this.data.lazy; 155 return this.data.lazy;
150 } 156 }
151 }, 157 },
152 methods: { 158 methods: {
153 - onFollow() {  
154 - },  
155 onPraise() { 159 onPraise() {
156 - 160 + this.$refs.footer.onPraise();
157 }, 161 },
158 onChangeSlide({index}) { 162 onChangeSlide({index}) {
159 this.slideIndex = index; 163 this.slideIndex = index;
@@ -5,7 +5,10 @@ import * as sleep from '../../utils/sleep'; @@ -5,7 +5,10 @@ import * as sleep from '../../utils/sleep';
5 5
6 export default { 6 export default {
7 async fetchArticleList({ commit }, { articleId, authorUid, authorType, limit = 5, page = 1, thumb = false, columnType = 1001, singleDetail = 'N'}) { 7 async fetchArticleList({ commit }, { articleId, authorUid, authorType, limit = 5, page = 1, thumb = false, columnType = 1001, singleDetail = 'N'}) {
8 - commit(Types.FETCH_ARTICLE_LIST_REQUEST, { refresh: page === 1 }); 8 + const commitType = singleDetail === 'Y' ? 'ARTICLE_SINGLE_DETAIL' : 'ARTICLE_LIST';
  9 +
  10 + commit(Types[`FETCH_${commitType}_REQUEST`], { refresh: page === 1 });
  11 +
9 const result = await this.$api.get('/api/grass/columnArticleDetail', { 12 const result = await this.$api.get('/api/grass/columnArticleDetail', {
10 articleId, 13 articleId,
11 limit, 14 limit,
@@ -20,12 +23,13 @@ export default { @@ -20,12 +23,13 @@ export default {
20 if (!result.data.detailList) { 23 if (!result.data.detailList) {
21 result.data.detailList = []; 24 result.data.detailList = [];
22 } 25 }
23 - commit(Types.FETCH_ARTICLE_LIST_SUCCESS, { 26 +
  27 + commit(Types[`FETCH_${commitType}_SUCCESS`], {
24 data: result.data.detailList, 28 data: result.data.detailList,
25 thumb 29 thumb
26 }); 30 });
27 } else { 31 } else {
28 - commit(Types.FETCH_ARTICLE_LIST_FAILD); 32 + commit(Types[`FETCH_${commitType}_FAILD`]);
29 } 33 }
30 return result; 34 return result;
31 }, 35 },
@@ -21,7 +21,8 @@ export default function() { @@ -21,7 +21,8 @@ export default function() {
21 fetchTopicInfo: false, 21 fetchTopicInfo: false,
22 topicInfo: {}, 22 topicInfo: {},
23 fetchTopicArticles: false, 23 fetchTopicArticles: false,
24 - articleDetailList: [], 24 + articleSingleDetail: {},
  25 + fetchArticleSingleDetail: false,
25 fetchDetailRecommendArticles: false 26 fetchDetailRecommendArticles: false
26 }, 27 },
27 actions, 28 actions,
@@ -230,6 +230,31 @@ export default { @@ -230,6 +230,31 @@ export default {
230 state.topicInfo.hasAttention = follow; 230 state.topicInfo.hasAttention = follow;
231 } 231 }
232 }, 232 },
  233 + [Types.FETCH_ARTICLE_SINGLE_DETAIL_REQUEST](state, topicId) {
  234 + state.fetchArticleSingleDetail = true;
  235 + },
  236 + [Types.FETCH_ARTICLE_SINGLE_DETAIL_SUCCESS](state, {data, thumb}) {
  237 + state.fetchArticleSingleDetail = false;
  238 +
  239 + let item = data[0] || {};
  240 +
  241 + if (thumb) {
  242 + item.comments = [];
  243 + item.hasAttention = '';
  244 + item.hasFavor = '';
  245 + item.hasPraise = '';
  246 + item.commentCount = 0;
  247 + item.favoriteCount = 0;
  248 + item.praiseCount = 0;
  249 + } else {
  250 + setArticleList(state, data, 'detail');
  251 + }
  252 +
  253 + state.articleSingleDetail = item;
  254 + },
  255 + [Types.FETCH_ARTICLE_SINGLE_DETAIL_FAILD](state) {
  256 + state.fetchArticleSingleDetail = false;
  257 + },
233 [Types.FETCH_DETAIL_RECOMMEND_REQUEST](state, topicId) { 258 [Types.FETCH_DETAIL_RECOMMEND_REQUEST](state, topicId) {
234 state.fetchDetailRecommendArticles = true; 259 state.fetchDetailRecommendArticles = true;
235 }, 260 },
@@ -34,6 +34,10 @@ export const FETCH_TOPIC_INFO_FAILD = 'FETCH_TOPIC_INFO_FAILD'; @@ -34,6 +34,10 @@ export const FETCH_TOPIC_INFO_FAILD = 'FETCH_TOPIC_INFO_FAILD';
34 export const FETCH_TOPIC_INFO_SUCCESS = 'FETCH_TOPIC_INFO_SUCCESS'; 34 export const FETCH_TOPIC_INFO_SUCCESS = 'FETCH_TOPIC_INFO_SUCCESS';
35 export const CHANGE_TOPIC_FOLLOW = 'CHANGE_TOPIC_FOLLOW'; 35 export const CHANGE_TOPIC_FOLLOW = 'CHANGE_TOPIC_FOLLOW';
36 36
  37 +export const FETCH_ARTICLE_SINGLE_DETAIL_REQUEST = 'FETCH_ARTICLE_SINGLE_DETAIL_REQUEST';
  38 +export const FETCH_ARTICLE_SINGLE_DETAIL_FAILD = 'FETCH_ARTICLE_SINGLE_DETAIL_FAILD';
  39 +export const FETCH_ARTICLE_SINGLE_DETAIL_SUCCESS = 'FETCH_ARTICLE_SINGLE_DETAIL_SUCCESS';
  40 +
37 export const FETCH_DETAIL_RECOMMEND_REQUEST = 'FETCH_DETAIL_RECOMMEND_REQUEST'; 41 export const FETCH_DETAIL_RECOMMEND_REQUEST = 'FETCH_DETAIL_RECOMMEND_REQUEST';
38 export const FETCH_DETAIL_RECOMMEND_FAILD = 'FETCH_DETAIL_RECOMMEND_FAILD'; 42 export const FETCH_DETAIL_RECOMMEND_FAILD = 'FETCH_DETAIL_RECOMMEND_FAILD';
39 export const FETCH_DETAIL_RECOMMEND_SUCCESS = 'FETCH_DETAIL_RECOMMEND_SUCCESS'; 43 export const FETCH_DETAIL_RECOMMEND_SUCCESS = 'FETCH_DETAIL_RECOMMEND_SUCCESS';
  1 +import {get, first} from 'lodash';
  2 +
  3 +const getDetailShareData = (article) => {
  4 + let shareImage = '';
  5 + let desc = '';
  6 +
  7 + if (article.sort === 2) {
  8 + shareImage = article.coverImage;
  9 + desc = article.articleTitle;
  10 + } else {
  11 + let blockList = get(article, 'blockList', []);
  12 +
  13 + shareImage = get(first(blockList.filter(block => block.templateKey === 'image')), 'contentData', '');
  14 + desc = get(blockList.filter(block => block.templateKey === 'text'), '[0].contentData', '');
  15 + }
  16 +
  17 + return {
  18 + title: `@${article.authorName} 在有货社区上发了一篇笔记,快点开看看!`,
  19 + imgUrl: shareImage.replace('{mode}', 2).replace('{width}', 200).replace('{height}', 200),
  20 + link: `${window ? window.location.origin : ''}/grass/article/share/${article.articleId}`,
  21 + desc,
  22 + hideType: ['7', '8', '9']
  23 + };
  24 +}
  25 +
  26 +export {
  27 + getDetailShareData
  28 +};