Authored by 陈峰

commit

@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 ], 15 ],
16 "rules": { 16 "rules": {
17 "camelcase": "off", 17 "camelcase": "off",
18 - "max-len": "off" 18 + "max-len": "off",
  19 + "new-cap": "off"
19 } 20 }
20 } 21 }
1 <template> 1 <template>
2 - <button class="btn-follow hover-opacity" v-if="!followed">关注</button>  
3 - <button class="btn-follow followed hover-opacity" v-else>已关注</button> 2 + <button class="btn-follow hover-opacity" :class="followClass" @click="onFollow">{{followText}}</button>
4 </template> 3 </template>
5 4
6 <script> 5 <script>
  6 +import {createNamespacedHelpers} from 'vuex';
  7 +const {mapActions} = createNamespacedHelpers('user');
  8 +
7 export default { 9 export default {
8 name: 'WidgetFollow', 10 name: 'WidgetFollow',
9 props: { 11 props: {
10 authorUid: Number, 12 authorUid: Number,
11 - followed: Boolean 13 + follow: Boolean
  14 + },
  15 + data() {
  16 + return {
  17 + loading: false,
  18 + followStatus: this.follow
  19 + };
  20 + },
  21 + watch: {
  22 + follow(val) {
  23 + this.followStatus = val;
  24 + }
  25 + },
  26 + computed: {
  27 + followClass() {
  28 + return {
  29 + loading: this.loading,
  30 + follow: this.followStatus
  31 + };
  32 + },
  33 + followText() {
  34 + return this.followStatus ? '已关注' : '关注';
  35 + }
  36 + },
  37 + methods: {
  38 + ...mapActions(['followUser']),
  39 + async onFollow() {
  40 + if (this.loading) {
  41 + return;
  42 + }
  43 + this.loading = true;
  44 + const result = await this.followUser({followUid: this.authorUid, status: this.followStatus ? 1 : 0});
  45 +
  46 + this.loading = false;
  47 + if (result.code === 200) {
  48 + this.followStatus = !this.followStatus;
  49 + this.$emit('on-follow', this.followStatus);
  50 + } else {
  51 + this.$createToast && this.$createToast({
  52 + txt: result.message || '服务器开小差了',
  53 + type: 'warn',
  54 + time: 1000
  55 + }).show();
  56 + }
  57 + }
12 } 58 }
13 }; 59 };
14 </script> 60 </script>
@@ -26,7 +72,7 @@ export default { @@ -26,7 +72,7 @@ export default {
26 align-items: center; 72 align-items: center;
27 justify-content: center; 73 justify-content: center;
28 74
29 - &.followed { 75 + &.follow {
30 border: solid 1px #4a4a4a; 76 border: solid 1px #4a4a4a;
31 background-color: #fff; 77 background-color: #fff;
32 color: #000; 78 color: #000;
@@ -21,24 +21,23 @@ export default { @@ -21,24 +21,23 @@ export default {
21 }; 21 };
22 }, 22 },
23 serverPrefetch() { 23 serverPrefetch() {
24 - console.log('serverPrefetch')  
25 return this.onFetch(); 24 return this.onFetch();
26 }, 25 },
27 computed: { 26 computed: {
28 - ...mapState(['articleCurrentList']), 27 + ...mapState(['articleList']),
29 currentList() { 28 currentList() {
30 - if (this.articleCurrentList.length > 2) {  
31 - return this.articleCurrentList.slice(0, 2); 29 + if (this.articleList.length > 2) {
  30 + return this.articleList.slice(0, 2);
32 } 31 }
33 - return this.articleCurrentList; 32 + return this.articleList;
34 } 33 }
35 }, 34 },
36 methods: { 35 methods: {
37 ...mapActions(['fetchArticleList']), 36 ...mapActions(['fetchArticleList']),
38 async onFetch() { 37 async onFetch() {
39 - if (this.page === 1 && this.articleCurrentList.length) { 38 + if (this.page === 1 && this.articleList.length) {
40 this.page++; 39 this.page++;
41 - return this.articleCurrentList; 40 + return this.articleList;
42 } 41 }
43 const articleId = parseInt(this.$route.params.id, 10); 42 const articleId = parseInt(this.$route.params.id, 10);
44 43
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 <span class="name">{{data.authorName}}</span> 5 <span class="name">{{data.authorName}}</span>
6 </div> 6 </div>
7 <div class="opts"> 7 <div class="opts">
8 - <WidgetFollow :article-id="data.authorUid" :followed="data.hasAttention === 'Y'"></WidgetFollow> 8 + <WidgetFollow :author-uid="data.authorUid" :follow="data.hasAttention === 'Y'" @on-follow="onFollow"></WidgetFollow>
9 <i class="iconfont icon-more1" @click="onMore"></i> 9 <i class="iconfont icon-more1" @click="onMore"></i>
10 </div> 10 </div>
11 </div> 11 </div>
@@ -40,6 +40,9 @@ export default { @@ -40,6 +40,9 @@ export default {
40 }).show(); 40 }).show();
41 } 41 }
42 }).show(); 42 }).show();
  43 + },
  44 + onFollow(follow) {
  45 + this.$emit('on-follow', follow);
43 } 46 }
44 } 47 }
45 }; 48 };
1 <template> 1 <template>
2 <div class="article-item"> 2 <div class="article-item">
3 - <ArticleItemHeader :data="headerData" :lazy="lazy"></ArticleItemHeader> 3 + <ArticleItemHeader :data="headerData" :lazy="lazy" @on-follow="onFollow"></ArticleItemHeader>
4 <ArticleItemSlide :data="slideData" :lazy="lazy"></ArticleItemSlide> 4 <ArticleItemSlide :data="slideData" :lazy="lazy"></ArticleItemSlide>
5 <ProductGroup :data="productListData" :lazy="lazy"></ProductGroup> 5 <ProductGroup :data="productListData" :lazy="lazy"></ProductGroup>
6 <ArticleItemIntro :data="introData" @on-resize="onResize" @on-resizeing="onResizeing"></ArticleItemIntro> 6 <ArticleItemIntro :data="introData" @on-resize="onResize" @on-resizeing="onResizeing"></ArticleItemIntro>
@@ -73,6 +73,9 @@ export default { @@ -73,6 +73,9 @@ export default {
73 }, 73 },
74 onResizeing() { 74 onResizeing() {
75 this.$emit('on-resizeing'); 75 this.$emit('on-resizeing');
  76 + },
  77 + onFollow(follow) {
  78 + this.$emit('on-follow', follow);
76 } 79 }
77 }, 80 },
78 components: {ArticleItemHeader, ArticleItemSlide, ArticleItemIntro, ArticleItemComment} 81 components: {ArticleItemHeader, ArticleItemSlide, ArticleItemIntro, ArticleItemComment}
@@ -6,12 +6,17 @@ @@ -6,12 +6,17 @@
6 <span class="user-name">{{currentAuthor.authorName}}</span> 6 <span class="user-name">{{currentAuthor.authorName}}</span>
7 </template> 7 </template>
8 <template v-if="showHeader" v-slot:opts> 8 <template v-if="showHeader" v-slot:opts>
9 - <WidgetFollow :article-id="currentAuthor.authorUid" :followed="currentAuthor.hasAttention === 'Y'"></WidgetFollow> 9 + <WidgetFollow class="widget-follow" :author-uid="currentAuthor.authorUid" :follow="currentAuthor.hasAttention === 'Y'" @on-follow="follow => onFollow(currentAuthor, follow)"></WidgetFollow>
10 </template> 10 </template>
11 </LayoutHeader> 11 </LayoutHeader>
12 <LayoutScroll v-if="isMounted" ref="scroll" @scroll="onScroll" :offset="1000" :on-fetch="onFetch"> 12 <LayoutScroll v-if="isMounted" ref="scroll" @scroll="onScroll" :offset="1000" :on-fetch="onFetch">
13 <template class="article-item" v-slot:item="{ data }"> 13 <template class="article-item" v-slot:item="{ data }">
14 - <ArticleItem :id="`item${data.index}`" :data="data" :data-id="data.index" @on-resize="onResize(data)" @on-resizeing="onResizeing(data)"></ArticleItem> 14 + <ArticleItem
  15 + :id="`item${data.index}`"
  16 + :data="data" :data-id="data.index"
  17 + @on-resize="onResize(data)"
  18 + @on-resizeing="onResizeing(data)"
  19 + @on-follow="follow => onFollow(data, follow)"></ArticleItem>
15 <div :id="`ph${data.index}`"></div> 20 <div :id="`ph${data.index}`"></div>
16 </template> 21 </template>
17 </LayoutScroll> 22 </LayoutScroll>
@@ -21,6 +26,8 @@ @@ -21,6 +26,8 @@
21 26
22 <script> 27 <script>
23 import ArticleItem from './article-item'; 28 import ArticleItem from './article-item';
  29 +import {createNamespacedHelpers} from 'vuex';
  30 +const {mapMutations} = createNamespacedHelpers('article');
24 31
25 export default { 32 export default {
26 name: 'Article', 33 name: 'Article',
@@ -59,6 +66,7 @@ export default { @@ -59,6 +66,7 @@ export default {
59 } 66 }
60 }, 67 },
61 methods: { 68 methods: {
  69 + ...mapMutations(['CHANGE_AUTHOR_FOLLOW']),
62 onScroll({item, scrollTop}) { 70 onScroll({item, scrollTop}) {
63 this.scrollTop = scrollTop; 71 this.scrollTop = scrollTop;
64 if (scrollTop === 0) { 72 if (scrollTop === 0) {
@@ -108,6 +116,12 @@ export default { @@ -108,6 +116,12 @@ export default {
108 $phItem.style.zIndex = 999; 116 $phItem.style.zIndex = 999;
109 $phItem.status = 1; 117 $phItem.status = 1;
110 }, 118 },
  119 + onFollow(data, follow) {
  120 + this.CHANGE_AUTHOR_FOLLOW({authorUid: data.authorUid, follow});
  121 + if (data.authorUid === this.currentAuthor.authorUid) {
  122 + this.currentAuthor.hasAttention = follow ? 'Y' : 'N';
  123 + }
  124 + }
111 }, 125 },
112 components: { 126 components: {
113 ArticleItem 127 ArticleItem
@@ -125,24 +139,8 @@ export default { @@ -125,24 +139,8 @@ export default {
125 height: 52px; 139 height: 52px;
126 } 140 }
127 141
128 -.btn-follow {  
129 - width: 120px;  
130 - height: 50px;  
131 - padding: 0;  
132 - font-size: 26px;  
133 - border-radius: 3PX;  
134 - background-color: #222;  
135 - color: #fff;  
136 - display: flex;  
137 - align-items: center;  
138 - justify-content: center; 142 +.widget-follow {
139 margin-right: 30px; 143 margin-right: 30px;
140 -  
141 - &.followed {  
142 - border: solid 1px #4a4a4a;  
143 - background-color: #fff;  
144 - color: #222;  
145 - }  
146 } 144 }
147 145
148 .user-name { 146 .user-name {
@@ -684,6 +684,10 @@ img[lazy=loaded] { @@ -684,6 +684,10 @@ img[lazy=loaded] {
684 &:active { 684 &:active {
685 opacity: 0.8; 685 opacity: 0.8;
686 } 686 }
  687 +
  688 + &.loading {
  689 + opacity: 0.8;
  690 + }
687 } 691 }
688 692
689 button { 693 button {
@@ -14,12 +14,9 @@ export default { @@ -14,12 +14,9 @@ export default {
14 if (!result.data.detailList) { 14 if (!result.data.detailList) {
15 result.data.detailList = []; 15 result.data.detailList = [];
16 } 16 }
17 - result.data.detailList = result.data.detailList.map((item, inx) => {  
18 - item.index = (page - 1) * limit + inx;  
19 - return item;  
20 - });  
21 commit(Types.FETCH_ARTICLE_DETAIL_SUCCESS, { 17 commit(Types.FETCH_ARTICLE_DETAIL_SUCCESS, {
22 data: result.data.detailList, 18 data: result.data.detailList,
  19 + page
23 }); 20 });
24 } else { 21 } else {
25 commit(Types.FETCH_ARTICLE_DETAIL_FAILD); 22 commit(Types.FETCH_ARTICLE_DETAIL_FAILD);
@@ -6,7 +6,7 @@ export default function() { @@ -6,7 +6,7 @@ export default function() {
6 namespaced: true, 6 namespaced: true,
7 state: { 7 state: {
8 fetchArticleList: false, 8 fetchArticleList: false,
9 - articleCurrentList: [], 9 + articleList: [],
10 articleDetail: null, 10 articleDetail: null,
11 }, 11 },
12 actions, 12 actions,
@@ -4,15 +4,28 @@ export default { @@ -4,15 +4,28 @@ export default {
4 [Types.FETCH_ARTICLE_DETAIL_REQUEST](state) { 4 [Types.FETCH_ARTICLE_DETAIL_REQUEST](state) {
5 state.fetchArticleList = true; 5 state.fetchArticleList = true;
6 }, 6 },
7 - [Types.FETCH_ARTICLE_DETAIL_SUCCESS](state, {data}) { 7 + [Types.FETCH_ARTICLE_DETAIL_SUCCESS](state, {data, page}) {
8 state.fetchArticleList = false; 8 state.fetchArticleList = false;
9 - state.articleCurrentList = data; 9 + if (page === 1) {
  10 + state.articleList = [];
  11 + }
  12 + state.articleList = state.articleList.concat(data);
  13 +
  14 + state.articleList.forEach((item, index) => {
  15 + item.index = index;
  16 + });
10 }, 17 },
11 [Types.FETCH_ARTICLE_DETAIL_FAILD](state) { 18 [Types.FETCH_ARTICLE_DETAIL_FAILD](state) {
12 state.fetchArticleList = false; 19 state.fetchArticleList = false;
13 }, 20 },
14 [Types.FETCH_GUANG_REQUEST](state, data) { 21 [Types.FETCH_GUANG_REQUEST](state, data) {
15 state.articleDetail = data; 22 state.articleDetail = data;
  23 + },
  24 + [Types.CHANGE_AUTHOR_FOLLOW](state, {authorUid, follow}) {
  25 + state.articleList.forEach(article => {
  26 + if (article.authorUid === authorUid) {
  27 + article.hasAttention = follow ? 'Y' : 'N';
  28 + }
  29 + });
16 } 30 }
17 -  
18 }; 31 };
1 export const FETCH_ARTICLE_DETAIL_REQUEST = 'FETCH_ARTICLE_DETAIL_REQUEST'; 1 export const FETCH_ARTICLE_DETAIL_REQUEST = 'FETCH_ARTICLE_DETAIL_REQUEST';
2 export const FETCH_ARTICLE_DETAIL_FAILD = 'FETCH_ARTICLE_DETAIL_FAILD'; 2 export const FETCH_ARTICLE_DETAIL_FAILD = 'FETCH_ARTICLE_DETAIL_FAILD';
3 export const FETCH_ARTICLE_DETAIL_SUCCESS = 'FETCH_ARTICLE_DETAIL_SUCCESS'; 3 export const FETCH_ARTICLE_DETAIL_SUCCESS = 'FETCH_ARTICLE_DETAIL_SUCCESS';
  4 +
4 export const FETCH_GUANG_REQUEST = 'FETCH_GUANG_REQUEST'; 5 export const FETCH_GUANG_REQUEST = 'FETCH_GUANG_REQUEST';
5 export const FETCH_GUANG_FAILED = 'FETCH_GUANG_FAILED'; 6 export const FETCH_GUANG_FAILED = 'FETCH_GUANG_FAILED';
6 export const FETCH_GUANG_SUCCESS = 'FETCH_GUANG_SUCCESS'; 7 export const FETCH_GUANG_SUCCESS = 'FETCH_GUANG_SUCCESS';
  8 +
  9 +export const CHANGE_AUTHOR_FOLLOW = 'CHANGE_AUTHOR_FOLLOW';
@@ -3,6 +3,7 @@ import Vuex from 'vuex'; @@ -3,6 +3,7 @@ import Vuex from 'vuex';
3 import {createApi} from 'create-api'; 3 import {createApi} from 'create-api';
4 import storeYoho from './yoho'; 4 import storeYoho from './yoho';
5 import storeArticle from './article'; 5 import storeArticle from './article';
  6 +import storeUser from './user';
6 import plugin from './plugin'; 7 import plugin from './plugin';
7 8
8 Vue.use(Vuex); 9 Vue.use(Vuex);
@@ -12,7 +13,8 @@ export function createStore(context) { @@ -12,7 +13,8 @@ export function createStore(context) {
12 namespaced: true, 13 namespaced: true,
13 modules: { 14 modules: {
14 yoho: storeYoho(), 15 yoho: storeYoho(),
15 - article: storeArticle() 16 + article: storeArticle(),
  17 + user: storeUser()
16 }, 18 },
17 strict: process.env.NODE_ENV !== 'production', 19 strict: process.env.NODE_ENV !== 'production',
18 plugins: [plugin] 20 plugins: [plugin]
  1 +export default {
  2 + async followUser(actions, {followUid, status}) {
  3 + const result = await this.$api.get('/api/grass/updateAttention', {
  4 + followUid,
  5 + status,
  6 + attentionType: 1
  7 + });
  8 +
  9 + return result;
  10 + },
  11 + async followTopic(actions, {topicId, status}) {
  12 + const result = await this.$api.get('/api/grass/updateAttention', {
  13 + topicId,
  14 + status,
  15 + attentionType: 0
  16 + });
  17 +
  18 + return result;
  19 + },
  20 +};
  1 +import actions from './actions';
  2 +import mutations from './mutations';
  3 +
  4 +export default function() {
  5 + return {
  6 + namespaced: true,
  7 + state: {
  8 + },
  9 + actions,
  10 + mutations
  11 + };
  12 +}
  1 +import * as Types from './types';
  2 +
  3 +export default {
  4 +};
  1 +export const FETCH_ARTICLE_DETAIL_REQUEST = 'FETCH_ARTICLE_DETAIL_REQUEST';
  2 +export const FETCH_ARTICLE_DETAIL_FAILD = 'FETCH_ARTICLE_DETAIL_FAILD';
  3 +export const FETCH_ARTICLE_DETAIL_SUCCESS = 'FETCH_ARTICLE_DETAIL_SUCCESS';
@@ -8,11 +8,19 @@ module.exports = { @@ -8,11 +8,19 @@ module.exports = {
8 params: { 8 params: {
9 page: {type: Number, require: false}, 9 page: {type: Number, require: false},
10 limit: {type: Number, require: false}, 10 limit: {type: Number, require: false},
11 - uid: {type: Number, require: false},  
12 articleId: {type: Number}, 11 articleId: {type: Number},
13 columnType: {type: Number} 12 columnType: {type: Number}
14 } 13 }
15 }, 14 },
  15 + '/api/grass/updateAttention': {
  16 + api: 'app.grass.updateAttention',
  17 + params: {
  18 + topicId: {type: Number, require: false},
  19 + followUid: {type: Number, require: false},
  20 + status: {type: Number},
  21 + attentionType: {type: Number}
  22 + }
  23 + },
16 '/api/guang/article/detail': { 24 '/api/guang/article/detail': {
17 service: true, 25 service: true,
18 api: URI_PACKAGE_ARTICLE, 26 api: URI_PACKAGE_ARTICLE,