Authored by 张文文

merge

Showing 45 changed files with 814 additions and 160 deletions
@@ -5,6 +5,7 @@ import {createStore} from './store'; @@ -5,6 +5,7 @@ import {createStore} from './store';
5 import 'filters'; 5 import 'filters';
6 import 'directives'; 6 import 'directives';
7 import titleMixin from './mixins/title'; 7 import titleMixin from './mixins/title';
  8 +import downloadMixin from './mixins/download';
8 import pluginCore from './plugins/core'; 9 import pluginCore from './plugins/core';
9 import lazyload from 'vue-lazyload'; 10 import lazyload from 'vue-lazyload';
10 import reportError from 'report-error'; 11 import reportError from 'report-error';
@@ -19,6 +20,7 @@ Vue.use(lazyload, { @@ -19,6 +20,7 @@ Vue.use(lazyload, {
19 }); 20 });
20 Vue.use(pluginCore); 21 Vue.use(pluginCore);
21 Vue.mixin(titleMixin); 22 Vue.mixin(titleMixin);
  23 +Vue.mixin(downloadMixin);
22 dayjs.locale('zh-cn'); 24 dayjs.locale('zh-cn');
23 dayjs.extend(relativeTime); 25 dayjs.extend(relativeTime);
24 26
  1 +function queryString() {
  2 + var vars = {},
  3 + hash,
  4 + i;
  5 + var hashes = window.location.search.slice(1).split('&');
  6 +
  7 + for (i = 0; i < hashes.length; i++) {
  8 + hash = hashes[i].split('=');
  9 + vars[decodeURIComponent(hash[0])] = decodeURIComponent(hash[1]);
  10 + }
  11 + return vars;
  12 +}
  13 +
1 const getAppPath = () => { 14 const getAppPath = () => {
2 - let appPath = document.getElementById('main-wrap').dataset.apppath || 'yohobuy://yohobuy.com/goapp?openby:yohobuy={"action":"go.home","params":{"gender":"1","channel":"2"}}'; 15 + let params = queryString();
  16 + let openbyYohobuy = params['openby:yohobuy'] || '';
  17 + let appPath = '';
  18 +
  19 + if (openbyYohobuy) {
  20 + appPath = 'yohobuy://yohobuy.com/goapp?openby:yohobuy=' + openbyYohobuy;
  21 + } else {
  22 + appPath =
  23 + document.getElementById('main-wrap').dataset.apppath ||
  24 + 'yohobuy://yohobuy.com/goapp?openby:yohobuy={"action":"go.home","params":{"gender":"1","channel":"2"}}';
  25 + }
3 26
4 return appPath; 27 return appPath;
5 }; 28 };
6 29
7 const u = navigator.userAgent; 30 const u = navigator.userAgent;
  31 +// eslint-disable-next-line no-unused-vars
8 const isFromYOHO = /m\.yohobuy\.com/i.test(document.referrer); 32 const isFromYOHO = /m\.yohobuy\.com/i.test(document.referrer);
9 -const isApp = /yoho/i.test(u) || 33 +const isApp =
  34 + /yoho/i.test(u) ||
10 !!window.yohoInterface || 35 !!window.yohoInterface ||
11 /app_version=/i.test(location.search) || 36 /app_version=/i.test(location.search) ||
12 /openrefer=/i.test(location.search); 37 /openrefer=/i.test(location.search);
13 38
14 const isiOS = /(iPhone|iPad|iPod|iOS)/i.test(u); // ios终端 39 const isiOS = /(iPhone|iPad|iPod|iOS)/i.test(u); // ios终端
15 const isAndroid = /Android/i.test(u); // android终端 40 const isAndroid = /Android/i.test(u); // android终端
  41 +const isWechat = /MicroMessenger/i.test(navigator.userAgent);
  42 +const isMobileQQ = /MQQBrowser.+QQ/i.test(navigator.userAgent);
16 const isWechatDevtool = /wechatdevtools/i.test(u); // 微信开发者工具 43 const isWechatDevtool = /wechatdevtools/i.test(u); // 微信开发者工具
  44 +// eslint-disable-next-line no-unused-vars
17 const nodownload = document.getElementById('no-download'); // 页面不需要下载 45 const nodownload = document.getElementById('no-download'); // 页面不需要下载
18 46
19 const getIOSVersion = () => { 47 const getIOSVersion = () => {
@@ -22,17 +50,22 @@ const getIOSVersion = () => { @@ -22,17 +50,22 @@ const getIOSVersion = () => {
22 return verion ? parseInt(verion[1], 10) : 0; 50 return verion ? parseInt(verion[1], 10) : 0;
23 }; 51 };
24 const ua = navigator.userAgent; 52 const ua = navigator.userAgent;
25 -const isOriginalChrome = /chrome\/[\d.]+ Mobile Safari\/[\d.]+/i.test(ua) && 53 +const isOriginalChrome =
  54 + /chrome\/[\d.]+ Mobile Safari\/[\d.]+/i.test(ua) &&
26 isAndroid && 55 isAndroid &&
27 ua.indexOf('Version') < 0; 56 ua.indexOf('Version') < 0;
28 57
29 -const getFallUrl = (schemeUrl) => {  
30 - const appPath = (schemeUrl || '').replace('yohobuy://yohobuy.com/goapp?', '') || 'openby:yohobuy={"action":"go.home","params":{"gender":"1","channel":"2"}}'; 58 +const getFallUrl = schemeUrl => {
  59 + const appPath =
  60 + (schemeUrl || '').replace('yohobuy://yohobuy.com/goapp?', '') ||
  61 + 'openby:yohobuy={"action":"go.home","params":{"gender":"1","channel":"2"}}';
31 62
32 return `https://union.yoho.cn/union/app-downloads.html?${appPath}`; 63 return `https://union.yoho.cn/union/app-downloads.html?${appPath}`;
33 }; 64 };
34 65
35 const checkOpenFall = (url, callFunc, noFall) => { 66 const checkOpenFall = (url, callFunc, noFall) => {
  67 + let time = Date.now();
  68 +
36 callFunc(); 69 callFunc();
37 70
38 const fallUrl = getFallUrl(url); 71 const fallUrl = getFallUrl(url);
@@ -41,30 +74,51 @@ const checkOpenFall = (url, callFunc, noFall) => { @@ -41,30 +74,51 @@ const checkOpenFall = (url, callFunc, noFall) => {
41 return; 74 return;
42 } 75 }
43 76
44 - window.location.href = fallUrl; 77 + window.setTimeout(function() {
  78 + if (Date.now() - time < 1200) {
  79 + window.location.href = fallUrl;
  80 + }
  81 + }, 1000);
45 }; 82 };
46 83
47 const callIframe = (url, noFall) => { 84 const callIframe = (url, noFall) => {
48 - checkOpenFall(url, () => {  
49 - window.location.href = url;  
50 - }, noFall); 85 + checkOpenFall(
  86 + url,
  87 + () => {
  88 + const ifr = document.createElement('iframe');
  89 +
  90 + ifr.src = url;
  91 + ifr.style.display = 'none';
  92 + document.body.appendChild(ifr);
  93 + },
  94 + noFall,
  95 + );
51 }; 96 };
52 97
53 const callUrl = (url, noFall) => { 98 const callUrl = (url, noFall) => {
54 - checkOpenFall(url, () => {  
55 - }, noFall); 99 + checkOpenFall(
  100 + url,
  101 + () => {
  102 + window.location.href = url;
  103 + },
  104 + noFall,
  105 + );
56 }; 106 };
57 107
58 const callA = (url, noFall) => { 108 const callA = (url, noFall) => {
59 - checkOpenFall(url, () => {  
60 - const ca = document.createElement('a');  
61 -  
62 - ca.setAttribute('href', url);  
63 - ca.style.display = 'none';  
64 - document.body.appendChild(ca);  
65 -  
66 - ca.click();  
67 - }, noFall); 109 + checkOpenFall(
  110 + url,
  111 + () => {
  112 + const ca = document.createElement('a');
  113 +
  114 + ca.setAttribute('href', url);
  115 + ca.style.display = 'none';
  116 + document.body.appendChild(ca);
  117 +
  118 + ca.click();
  119 + },
  120 + noFall,
  121 + );
68 }; 122 };
69 123
70 const toAppPage = (appUrl, noFall) => { 124 const toAppPage = (appUrl, noFall) => {
@@ -82,16 +136,23 @@ const toAppPage = (appUrl, noFall) => { @@ -82,16 +136,23 @@ const toAppPage = (appUrl, noFall) => {
82 }; 136 };
83 137
84 const canOpenApp = () => { 138 const canOpenApp = () => {
85 - if (isWechatDevtool || isApp || isFromYOHO || nodownload) { 139 + if (isWechatDevtool || isApp) {
86 return false; 140 return false;
87 } 141 }
88 return isAndroid || isiOS; 142 return isAndroid || isiOS;
89 }; 143 };
90 144
91 -export default () => { 145 +export default function openApp() {
92 if (canOpenApp()) { 146 if (canOpenApp()) {
93 - let appPath = getAppPath(); 147 + setTimeout(function() {
  148 + let appPath = getAppPath();
94 149
95 - toAppPage(appPath, false); 150 + toAppPage(appPath, false);
  151 + }, 3000);
96 } 152 }
97 -}; 153 +}
  154 +
  155 +// 在 android 中,不在微信和QQ和App中,打开页面
  156 +if (isAndroid && !(isMobileQQ || isWechat) && !isApp) {
  157 + openApp();
  158 +}
@@ -59,6 +59,7 @@ const yoho = { @@ -59,6 +59,7 @@ const yoho = {
59 isAndroid: /Android/i.test(navigator.userAgent || ''), 59 isAndroid: /Android/i.test(navigator.userAgent || ''),
60 isYohoBuy: isYohoBuy, 60 isYohoBuy: isYohoBuy,
61 isWechat: /MicroMessenger/i.test(navigator.userAgent), 61 isWechat: /MicroMessenger/i.test(navigator.userAgent),
  62 + isMobileQQ: /QQ/i.test(navigator.userAgent),
62 isLocal: window.location.protocol === 'yoho-protocol:' || /yoho-protocol/i.test(navigator.userAgent || ''), 63 isLocal: window.location.protocol === 'yoho-protocol:' || /yoho-protocol/i.test(navigator.userAgent || ''),
63 appVersion: getAppVersion(navigator.userAgent, ';') || getAppVersion(document.cookie, ';'), 64 appVersion: getAppVersion(navigator.userAgent, ';') || getAppVersion(document.cookie, ';'),
64 65
1 <template> 1 <template>
2 <div class="comment-item"> 2 <div class="comment-item">
3 - <ImageFormat :lazy="false" :src="parentComment.headIco" :width="80" :height="80" class="comment-user-avatar" @click.native="toUserPage"></ImageFormat> 3 + <ImageFormat :lazy="false" :src="parentComment.headIco" :width="80" :height="80" class="comment-user-avatar"
  4 + @click.native="toUserPage"></ImageFormat>
4 <div class="comment"> 5 <div class="comment">
5 <div class="comment-nav"> 6 <div class="comment-nav">
6 <div class="comment-nav-left"> 7 <div class="comment-nav-left">
@@ -20,20 +21,26 @@ @@ -20,20 +21,26 @@
20 </div> 21 </div>
21 </div> 22 </div>
22 23
23 - <CommentPlaceholder 24 + <CommentPlaceholderActionSheet
24 class="comment-cont" 25 class="comment-cont"
25 :dest-id="parentComment.id" 26 :dest-id="parentComment.id"
  27 + :root-id="parentComment.id"
26 :pos-id="posId" 28 :pos-id="posId"
27 :article-id="articleId" 29 :article-id="articleId"
28 :add-type="1" 30 :add-type="1"
29 :user="parentComment.userName" 31 :user="parentComment.userName"
30 :column-type="columnType" 32 :column-type="columnType"
31 :share="share" 33 :share="share"
32 - @on-comment="onReply"> 34 + :authorUid="authorUid"
  35 + :commentUid="parentComment.uid"
  36 + commentType="parent"
  37 + @on-comment="onReply"
  38 + @remove-comment="onRemoveComment"
  39 + >
33 {{parentComment.content}} 40 {{parentComment.content}}
34 - </CommentPlaceholder> 41 + </CommentPlaceholderActionSheet>
35 <div class="reply-main" v-if="replayShowList.length"> 42 <div class="reply-main" v-if="replayShowList.length">
36 - <CommentPlaceholder 43 + <CommentPlaceholderActionSheet
37 tag="p" 44 tag="p"
38 class="reply-item" 45 class="reply-item"
39 v-for="(reply, replyIndex) in replayShowList" 46 v-for="(reply, replyIndex) in replayShowList"
@@ -44,13 +51,19 @@ @@ -44,13 +51,19 @@
44 :user="reply.userName" 51 :user="reply.userName"
45 :column-type="columnType" 52 :column-type="columnType"
46 :share="share" 53 :share="share"
47 - @on-comment="onReplyChildren"> 54 + :authorUid="authorUid"
  55 + :commentUid="reply.uid"
  56 + :article-id="articleId"
  57 + commentType="child"
  58 + @on-comment="onReplyChildren"
  59 + @remove-comment="onRemoveComment"
  60 + >
48 <span class="reply-user">{{reply.userName}}</span> 61 <span class="reply-user">{{reply.userName}}</span>
49 <template v-if="reply.parentUserName"> 62 <template v-if="reply.parentUserName">
50 <span>回复</span><em class="reply-to-user">@{{reply.parentUserName}}</em> 63 <span>回复</span><em class="reply-to-user">@{{reply.parentUserName}}</em>
51 </template>: 64 </template>:
52 <span>{{reply.content}}</span> 65 <span>{{reply.content}}</span>
53 - </CommentPlaceholder> 66 + </CommentPlaceholderActionSheet>
54 <p class="reply-more" v-if="moreReplyNum > 0" @click="onShowMore"> 67 <p class="reply-more" v-if="moreReplyNum > 0" @click="onShowMore">
55 {{replyMoreText}} 68 {{replyMoreText}}
56 <i class="iconfont icon-right" v-if="!isShowAllReply"></i> 69 <i class="iconfont icon-right" v-if="!isShowAllReply"></i>
@@ -61,11 +74,14 @@ @@ -61,11 +74,14 @@
61 </template> 74 </template>
62 75
63 <script> 76 <script>
64 -import {createNamespacedHelpers} from 'vuex';  
65 -const {mapActions} = createNamespacedHelpers('comment'); 77 +import { createNamespacedHelpers } from 'vuex';
  78 +import CommentPlaceholderActionSheet from './comment-placeholder-actionsheet';
  79 +
  80 +const { mapActions } = createNamespacedHelpers('comment');
66 81
67 export default { 82 export default {
68 name: 'CommentItem', 83 name: 'CommentItem',
  84 + components: { CommentPlaceholderActionSheet },
69 props: { 85 props: {
70 parentComment: Object, 86 parentComment: Object,
71 childrenComments: Array, 87 childrenComments: Array,
@@ -75,7 +91,8 @@ export default { @@ -75,7 +91,8 @@ export default {
75 }, 91 },
76 share: Boolean, 92 share: Boolean,
77 posId: Number, 93 posId: Number,
78 - articleId: Number 94 + articleId: Number,
  95 + authorUid: Number
79 }, 96 },
80 data() { 97 data() {
81 return { 98 return {
@@ -138,6 +155,16 @@ export default { @@ -138,6 +155,16 @@ export default {
138 }); 155 });
139 }, 300); 156 }, 300);
140 }, 157 },
  158 + onRemoveComment({ commentId, commentType, articleId }) {
  159 + let subCount = 0;
  160 +
  161 + if (commentType === 'parent') {
  162 + subCount = this.childrenComments.length + 1;
  163 + } else {
  164 + subCount = 1;
  165 + }
  166 + this.$emit('remove-comment', { commentId, commentType, articleId, subCount});
  167 + }
141 } 168 }
142 }; 169 };
143 </script> 170 </script>
@@ -5,14 +5,16 @@ @@ -5,14 +5,16 @@
5 <div class="loading" v-if="firstLoading"> 5 <div class="loading" v-if="firstLoading">
6 <Loading></Loading> 6 <Loading></Loading>
7 </div> 7 </div>
8 - <Scroll v-else ref="scroll" :data="commentList" :scroll-events="['scroll', 'scroll-end']" :options="scrollOption" @scroll="onScrollHandle" @scroll-end="onScrollEndHandle" @pulling-up="onPullingUp"> 8 + <Scroll v-else ref="scroll" :data="commentList" :scroll-events="['scroll', 'scroll-end']"
  9 + :options="scrollOption" @scroll="onScrollHandle" @scroll-end="onScrollEndHandle"
  10 + @pulling-up="onPullingUp">
9 <CommentEmpty v-if="empty"></CommentEmpty> 11 <CommentEmpty v-if="empty"></CommentEmpty>
10 <div v-else ref="commentList" class="comment-list"> 12 <div v-else ref="commentList" class="comment-list">
11 <div ref="commentListTop" class="comment-list-top"> 13 <div ref="commentListTop" class="comment-list-top">
12 <div ref="commentPre" class="comment-pre-list"> 14 <div ref="commentPre" class="comment-pre-list">
13 <div 15 <div
14 - v-for="(comments, index) in commentPreList"  
15 - :key="commentPreList.length - index"> 16 + v-for="(comments, index) in commentPreList"
  17 + :key="commentPreList.length - index">
16 <CommentItem 18 <CommentItem
17 v-for="comment in comments" 19 v-for="comment in comments"
18 :key="comment.parentComment.id" 20 :key="comment.parentComment.id"
@@ -21,8 +23,12 @@ @@ -21,8 +23,12 @@
21 :column-type="columnType" 23 :column-type="columnType"
22 :pos-id="posId" 24 :pos-id="posId"
23 :article-id="articleId" 25 :article-id="articleId"
  26 + :authorUid="authorUid"
  27 + :commentUid="comment.parentComment.uid"
24 @on-reply="onReply" 28 @on-reply="onReply"
25 - @on-close="onClose"> 29 + @on-close="onClose"
  30 + @remove-comment="onRemoveComment"
  31 + >
26 </CommentItem> 32 </CommentItem>
27 </div> 33 </div>
28 </div> 34 </div>
@@ -36,34 +42,42 @@ @@ -36,34 +42,42 @@
36 :column-type="columnType" 42 :column-type="columnType"
37 :pos-id="posId" 43 :pos-id="posId"
38 :article-id="articleId" 44 :article-id="articleId"
  45 + :authorUid="authorUid"
  46 + :commentUid="comment.parentComment.uid"
39 @on-reply="onReply" 47 @on-reply="onReply"
40 - @on-close="onClose"> 48 + @on-close="onClose"
  49 + @remove-comment="onRemoveComment"
  50 + >
41 </CommentItem> 51 </CommentItem>
42 </div> 52 </div>
43 </Scroll> 53 </Scroll>
44 </div> 54 </div>
45 </div> 55 </div>
46 <div class="comment-footer" v-if="!firstLoading"> 56 <div class="comment-footer" v-if="!firstLoading">
47 - <CommentPlaceholder 57 + <CommentPlaceholderActionSheet
48 class="comment-input" 58 class="comment-input"
49 :dest-id="destId" 59 :dest-id="destId"
50 :pos-id="posId" 60 :pos-id="posId"
51 :article-id="articleId" 61 :article-id="articleId"
52 :add-type="0" 62 :add-type="0"
53 :column-type="columnType" 63 :column-type="columnType"
  64 + :authorUid="authorUid"
54 @on-comment="onComment"> 65 @on-comment="onComment">
55 参与评论 66 参与评论
56 - </CommentPlaceholder> 67 + </CommentPlaceholderActionSheet>
57 </div> 68 </div>
58 </div> 69 </div>
59 </template> 70 </template>
60 71
61 <script> 72 <script>
62 import CommentItem from './comment-item.vue'; 73 import CommentItem from './comment-item.vue';
63 -import {Scroll, Loading} from 'cube-ui';  
64 -import {get, throttle} from 'lodash';  
65 -import {createNamespacedHelpers} from 'vuex';  
66 -const {mapActions} = createNamespacedHelpers('comment'); 74 +import { Scroll, Loading } from 'cube-ui';
  75 +import { get, throttle, findIndex } from 'lodash';
  76 +import { createNamespacedHelpers } from 'vuex';
  77 +import CommentPlaceholderActionSheet from './comment-placeholder-actionsheet';
  78 +
  79 +const { mapActions } = createNamespacedHelpers('comment');
  80 +const {mapMutations: articleMapMutations } = createNamespacedHelpers('article');
67 81
68 export default { 82 export default {
69 name: 'CommentList', 83 name: 'CommentList',
@@ -75,7 +89,7 @@ export default { @@ -75,7 +89,7 @@ export default {
75 }, 89 },
76 posId: Number, 90 posId: Number,
77 articleId: Number, 91 articleId: Number,
78 - commentId: Number 92 + authorUid: Number
79 }, 93 },
80 data() { 94 data() {
81 return { 95 return {
@@ -105,6 +119,7 @@ export default { @@ -105,6 +119,7 @@ export default {
105 }, 119 },
106 methods: { 120 methods: {
107 ...mapActions(['fetchCommentList', 'fetchReplayList', 'postComment']), 121 ...mapActions(['fetchCommentList', 'fetchReplayList', 'postComment']),
  122 + ...articleMapMutations(['SUB_ARTICLE_COMMENT_COUNT']),
108 async fetchCommentsAsync(pre) { 123 async fetchCommentsAsync(pre) {
109 let params = { 124 let params = {
110 destId: this.destId, 125 destId: this.destId,
@@ -113,7 +128,7 @@ export default { @@ -113,7 +128,7 @@ export default {
113 }; 128 };
114 129
115 if (params.page < 1 || params.page > this.totalPage) { 130 if (params.page < 1 || params.page > this.totalPage) {
116 - return {code: 200}; 131 + return { code: 200 };
117 } 132 }
118 133
119 if (this.commentId && (this.prePage === this.page)) { 134 if (this.commentId && (this.prePage === this.page)) {
@@ -208,7 +223,7 @@ export default { @@ -208,7 +223,7 @@ export default {
208 type: 'warn', 223 type: 'warn',
209 time: 1000 224 time: 1000
210 }).show(); 225 }).show();
211 - this.$emit('on-page-ready', {success: false}); 226 + this.$emit('on-page-ready', { success: false });
212 } 227 }
213 return result; 228 return result;
214 }, 229 },
@@ -249,7 +264,7 @@ export default { @@ -249,7 +264,7 @@ export default {
249 this.totalPage = 1; 264 this.totalPage = 1;
250 this.$refs.scroll.scrollTo(0, 0, 200); 265 this.$refs.scroll.scrollTo(0, 0, 200);
251 this.fetchComments(); 266 this.fetchComments();
252 - this.$emit('on-comment', {destId: this.destId}); 267 + this.$emit('on-comment', { destId: this.destId });
253 }, 268 },
254 async init() { 269 async init() {
255 this.page = 1; 270 this.page = 1;
@@ -259,7 +274,7 @@ export default { @@ -259,7 +274,7 @@ export default {
259 this.firstLoading = true; 274 this.firstLoading = true;
260 this.fetchComments(); 275 this.fetchComments();
261 }, 276 },
262 - async onReply({destId, parentId}) { 277 + async onReply({ destId, parentId }) {
263 const commentId = parentId || destId; 278 const commentId = parentId || destId;
264 279
265 if (!commentId) { 280 if (!commentId) {
@@ -286,9 +301,42 @@ export default { @@ -286,9 +301,42 @@ export default {
286 }, 301 },
287 onClose() { 302 onClose() {
288 this.$emit('on-close'); 303 this.$emit('on-close');
289 - } 304 + },
  305 + onRemoveComment({ commentId, subCount }) {
  306 + for (let comments of this.commentPreList) {
  307 + for (let [index, comment] of Object.entries(comments)) {
  308 + if (comment.parentComment && +comment.parentComment.id === +commentId) {
  309 + comments.splice(index, 1);
  310 + break;
  311 + }
  312 +
  313 + const childrenIndex = findIndex(comment.childrenComments, { id: +commentId });
  314 +
  315 + if ((+index !== -1) && (childrenIndex !== -1)) {
  316 + comments[index].childrenComments.splice(childrenIndex, 1);
  317 + break;
  318 + }
  319 + }
  320 + }
  321 +
  322 + for (let [index, comment] of Object.entries(this.commentList)) {
  323 + if (comment.parentComment && +comment.parentComment.id === +commentId) {
  324 + this.commentList.splice(index, 1);
  325 + break;
  326 + }
  327 +
  328 + const childrenIndex = findIndex(comment.childrenComments, { id: +commentId });
  329 +
  330 + if ((+index !== -1) && childrenIndex !== -1) {
  331 + this.commentList[index].childrenComments.splice(childrenIndex, 1);
  332 + break;
  333 + }
  334 + }
  335 +
  336 + this.SUB_ARTICLE_COMMENT_COUNT({ articleId: this.articleId, subCount });
  337 + },
290 }, 338 },
291 - components: {Scroll, CommentItem, Loading} 339 + components: { CommentPlaceholderActionSheet, Scroll, CommentItem, Loading }
292 }; 340 };
293 </script> 341 </script>
294 342
  1 +<template>
  2 + <div class="actionsheet" @click.capture.stop="onClick">
  3 + <CommentPlaceholder ref="placeholder" v-bind="$attrs" v-on="$listeners">
  4 + <slot></slot>
  5 + </CommentPlaceholder>
  6 + </div>
  7 +</template>
  8 +
  9 +
  10 +<script>
  11 +import { createNamespacedHelpers } from 'vuex';
  12 +import CommentPlaceholder from './comment-placeholder';
  13 +import { get } from 'lodash';
  14 +
  15 +const { mapActions } = createNamespacedHelpers('comment');
  16 +
  17 +const ITEM = {
  18 + comment: '回复',
  19 + remove: '删除',
  20 + report: '举报'
  21 +};
  22 +
  23 +export default {
  24 + name: 'CommentPlaceholderActionSheet',
  25 + components: { CommentPlaceholder },
  26 + data() {
  27 + return {};
  28 + },
  29 + methods: {
  30 + ...mapActions(['setCommentStatus']),
  31 + async onClick() {
  32 + const authorUid = this.$attrs.authorUid;
  33 + const commentUid = this.$attrs.commentUid;
  34 + const uid = get(await this.$sdk.getUser(), 'uid', 0);
  35 + const commentId = this.$attrs['dest-id'];
  36 + const articleId = this.$attrs['article-id'];
  37 + const commentType = this.$attrs.commentType;
  38 + let menu = [];
  39 +
  40 + if (authorUid === uid || commentUid === uid) {
  41 + menu = [
  42 + {
  43 + content: ITEM.comment
  44 + },
  45 + {
  46 + content: ITEM.remove
  47 + },
  48 + {
  49 + content: ITEM.report
  50 + }
  51 + ];
  52 + } else {
  53 + menu = [
  54 + {
  55 + content: ITEM.comment
  56 + },
  57 + {
  58 + content: ITEM.report
  59 + }
  60 + ];
  61 + }
  62 +
  63 + this.$createActionSheet({
  64 + data: menu,
  65 + zIndex: 200,
  66 + onSelect: item => {
  67 + switch (item.content) {
  68 + case ITEM.comment: {
  69 + this.$refs.placeholder.openComentInput();
  70 + break;
  71 + }
  72 + case ITEM.remove: {
  73 + this.setCommentStatus({ type: 1, commentId }).then(result => {
  74 + if (result.code === 200) {
  75 + this.$createToast({
  76 + txt: '已删除',
  77 + type: 'correct',
  78 + time: 2000
  79 + }).show();
  80 + this.$emit('remove-comment', { commentId, commentType, articleId });
  81 + } else {
  82 + this.$createToast({
  83 + txt: '删除失败,请重试',
  84 + type: 'warn',
  85 + time: 2000
  86 + }).show();
  87 + }
  88 + });
  89 + break;
  90 + }
  91 + case ITEM.report: {
  92 + this.setCommentStatus({ type: 2, commentId }).then(result => {
  93 + if (result.code === 200) {
  94 + this.$createToast({
  95 + txt: '已举报',
  96 + type: 'correct',
  97 + time: 2000
  98 + }).show();
  99 + } else {
  100 + this.$createToast({
  101 + txt: '举报失败,请重试',
  102 + type: 'warn',
  103 + time: 2000
  104 + }).show();
  105 + }
  106 + });
  107 + break;
  108 + }
  109 + default: {
  110 + // pass
  111 + }
  112 + }
  113 + }
  114 + }).show();
  115 + }
  116 + }
  117 +};
  118 +</script>
  119 +
  120 +<style lang="scss">
  121 +.cube-action-sheet-panel {
  122 + margin-left: 15px;
  123 + margin-right: 15px;
  124 + margin-bottom: 17px;
  125 + border-radius: 10px;
  126 + overflow: hidden;
  127 +}
  128 +
  129 +.cube-action-sheet-item {
  130 + color: #222 !important;
  131 + font-weight: bold !important;
  132 +}
  133 +
  134 +.cube-action-sheet-cancel {
  135 + span {
  136 + color: #222 !important;
  137 + font-weight: bold !important;
  138 + }
  139 +}
  140 +
  141 +.cube-action-sheet-space {
  142 + height: 1px !important;
  143 + background-color: rgb(235, 235, 235) !important;
  144 +}
  145 +</style>
@@ -115,10 +115,10 @@ export default { @@ -115,10 +115,10 @@ export default {
115 }; 115 };
116 116
117 if (result.code === 200) { 117 if (result.code === 200) {
118 - this.UPDATE_ARTICLE_COMMENT_COUNT({articleId: this.destId}); 118 + this.UPDATE_ARTICLE_COMMENT_COUNT({articleId: this.articleId});
119 119
120 if (this.autoUpdate && this.addType === 0) { 120 if (this.autoUpdate && this.addType === 0) {
121 - await this.fetchArticleUpdate({articleId: this.destId}); 121 + await this.fetchArticleUpdate({articleId: this.articleId});
122 } 122 }
123 123
124 this.$emit('on-comment', { 124 this.$emit('on-comment', {
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 <i class="iconfont icon-close icon" @touchend.prevent="onClose"></i> 6 <i class="iconfont icon-close icon" @touchend.prevent="onClose"></i>
7 </template> 7 </template>
8 </LayoutHeader> 8 </LayoutHeader>
9 - <CommentList ref="commentList" :dest-id="destId" :pos-id="posId" :article-id="articleId" :comment-id="commentId" :column-type="1001" @on-page-change="onPageChange" @on-comment="onComment" @on-page-ready="onPageReady" @on-close="onClose"></CommentList> 9 + <CommentList ref="commentList" :dest-id="destId" :pos-id="posId" :article-id="articleId" :comment-id="commentId" :column-type="1001" @on-page-change="onPageChange" @on-comment="onComment" @on-page-ready="onPageReady" @on-close="onClose" :authorUid="authorUid"></CommentList>
10 </Layout> 10 </Layout>
11 </template> 11 </template>
12 12
@@ -24,7 +24,8 @@ export default { @@ -24,7 +24,8 @@ export default {
24 posId: Number, 24 posId: Number,
25 articleId: Number, 25 articleId: Number,
26 commentId: Number, 26 commentId: Number,
27 - commentCount: Number 27 + commentCount: Number,
  28 + authorUid: Number
28 }, 29 },
29 data() { 30 data() {
30 return { 31 return {
@@ -2,10 +2,12 @@ import Comment from './comment'; @@ -2,10 +2,12 @@ import Comment from './comment';
2 import CommentItem from './comment-item'; 2 import CommentItem from './comment-item';
3 import CommentEmpty from './comment-empty'; 3 import CommentEmpty from './comment-empty';
4 import CommentPlaceholder from './comment-placeholder'; 4 import CommentPlaceholder from './comment-placeholder';
  5 +import CommentPlaceholderActionSheet from './comment-placeholder-actionsheet';
5 6
6 export default [ 7 export default [
7 Comment, 8 Comment,
8 CommentItem, 9 CommentItem,
9 CommentEmpty, 10 CommentEmpty,
10 - CommentPlaceholder 11 + CommentPlaceholder,
  12 + CommentPlaceholderActionSheet
11 ]; 13 ];
@@ -2,7 +2,7 @@ export default { @@ -2,7 +2,7 @@ export default {
2 name: 'AuthComponent', 2 name: 'AuthComponent',
3 props: { 3 props: {
4 tag: String, 4 tag: String,
5 - auth: { 5 + requiredAuth: {
6 type: Boolean, 6 type: Boolean,
7 default: true 7 default: true
8 } 8 }
@@ -15,17 +15,17 @@ export default { @@ -15,17 +15,17 @@ export default {
15 15
16 this._lastTime = e.timeStamp; 16 this._lastTime = e.timeStamp;
17 17
18 - if (!this.auth) {  
19 - return this.$emit('click');  
20 - } 18 + if (this.requiredAuth) {
  19 + const user = await this.$sdk.getUser();
21 20
22 - const user = await this.$sdk.getUser();  
23 -  
24 - if (user && user.uid) {  
25 - this.$emit('click', {uid: user.uid}); 21 + if (user && user.uid) {
  22 + this.$emit('click', {uid: user.uid});
  23 + } else {
  24 + this.$emit('cancel');
  25 + this.$sdk.goLogin();
  26 + }
26 } else { 27 } else {
27 - this.$emit('cancel');  
28 - this.$sdk.goLogin(); 28 + return this.$emit('click');
29 } 29 }
30 } 30 }
31 }, 31 },
1 <template> 1 <template>
2 - <AuthComponent class="btn-follow hover-opacity" :class="followClass" :auth="isAuth" @click="onFollow"> 2 + <AuthComponent class="btn-follow hover-opacity" :class="followClass" :requiredAuth="isAuth" @click="onFollow">
3 <span>{{followText}}</span> 3 <span>{{followText}}</span>
4 </AuthComponent> 4 </AuthComponent>
5 </template> 5 </template>
6 6
7 <script> 7 <script>
8 -import {createNamespacedHelpers} from 'vuex'; 8 +import { createNamespacedHelpers } from 'vuex';
9 import YAS from 'utils/yas-constants'; 9 import YAS from 'utils/yas-constants';
10 -const {mapActions} = createNamespacedHelpers('user'); 10 +
  11 +const { mapActions } = createNamespacedHelpers('user');
11 12
12 export default { 13 export default {
13 name: 'WidgetFollow', 14 name: 'WidgetFollow',
14 props: { 15 props: {
15 authorUid: [Number, String], 16 authorUid: [Number, String],
16 - follow: Boolean, 17 + follow: [String, Boolean],
17 authorType: { 18 authorType: {
18 type: [Number, String], 19 type: [Number, String],
19 default: 1 20 default: 1
@@ -25,12 +26,12 @@ export default { @@ -25,12 +26,12 @@ export default {
25 data() { 26 data() {
26 return { 27 return {
27 loading: false, 28 loading: false,
28 - followStatus: this.follow 29 + followStatus: this.follow === 'Y' || this.follow === 'O'
29 }; 30 };
30 }, 31 },
31 watch: { 32 watch: {
32 follow(val) { 33 follow(val) {
33 - this.followStatus = val; 34 + this.followStatus = val === 'Y' || val === 'O';
34 } 35 }
35 }, 36 },
36 computed: { 37 computed: {
@@ -41,7 +42,20 @@ export default { @@ -41,7 +42,20 @@ export default {
41 }; 42 };
42 }, 43 },
43 followText() { 44 followText() {
44 - return this.followStatus ? '已关注' : '关注'; 45 + switch (this.follow) {
  46 + case 'Y': {
  47 + return '已关注';
  48 + }
  49 + case 'N': {
  50 + return '关注';
  51 + }
  52 + case 'O': {
  53 + return '已相互关注';
  54 + }
  55 + default: {
  56 + return '关注';
  57 + }
  58 + }
45 }, 59 },
46 isAuth() { 60 isAuth() {
47 return !this.share; 61 return !this.share;
@@ -85,7 +99,7 @@ export default { @@ -85,7 +99,7 @@ export default {
85 }).show(); 99 }).show();
86 this.reportFellow(); 100 this.reportFellow();
87 } 101 }
88 - this.$emit('on-follow', this.followStatus); 102 + this.$emit('on-follow', this.followStatus ? 'Y' : 'N');
89 } else { 103 } else {
90 this.$createToast && this.$createToast({ 104 this.$createToast && this.$createToast({
91 txt: result.message || '服务器开小差了', 105 txt: result.message || '服务器开小差了',
@@ -140,5 +154,12 @@ export default { @@ -140,5 +154,12 @@ export default {
140 > span { 154 > span {
141 line-height: 1; 155 line-height: 1;
142 } 156 }
  157 +
  158 + .each-follow {
  159 + width: 110px;
  160 + height: 50px;
  161 + background-image: url("~statics/image/userpage/follow.png");
  162 + background-size: cover;
  163 + }
143 } 164 }
144 </style> 165 </style>
1 <template> 1 <template>
2 <div class="icon-btn" :class="{'btn-selected': viewOption.selected}" :style="btnStyle"> 2 <div class="icon-btn" :class="{'btn-selected': viewOption.selected}" :style="btnStyle">
3 - <AuthComponent :auth="isAuth" class="click-wrap" @click="onClick"></AuthComponent> 3 + <AuthComponent :requiredAuth="isAuth" class="click-wrap" @click="onClick"></AuthComponent>
4 <i class="iconfont" :class="iconClass" :style="iconStyle"> 4 <i class="iconfont" :class="iconClass" :style="iconStyle">
5 <div v-if="type === 'fav'" class="praise-lottie"> 5 <div v-if="type === 'fav'" class="praise-lottie">
6 <WidgetLottie ref="lottie" class="praise-lottie-wrap" :options="lottieOptions"></WidgetLottie> 6 <WidgetLottie ref="lottie" class="praise-lottie-wrap" :options="lottieOptions"></WidgetLottie>
@@ -244,10 +244,11 @@ export default { @@ -244,10 +244,11 @@ export default {
244 return Promise.resolve({code: 404}); 244 return Promise.resolve({code: 404});
245 } 245 }
246 }, 246 },
247 - onClick(evt) { 247 + async onClick(evt) {
248 if (this.share) { 248 if (this.share) {
249 return this.$links.toDownloadApp(); 249 return this.$links.toDownloadApp();
250 } 250 }
  251 +
251 if (this.syncing) { 252 if (this.syncing) {
252 return; 253 return;
253 } 254 }
@@ -14,6 +14,10 @@ import sdk from 'common/sdk'; @@ -14,6 +14,10 @@ import sdk from 'common/sdk';
14 import links from 'utils/links'; 14 import links from 'utils/links';
15 import openApp from 'common/open-app'; 15 import openApp from 'common/open-app';
16 16
  17 +if (process.env.NODE_ENV === 'development') {
  18 + require('./utils/debug');
  19 +}
  20 +
17 import {initClient, getYohoState} from 'utils/init-client'; 21 import {initClient, getYohoState} from 'utils/init-client';
18 import 'statics/scss/common.scss'; 22 import 'statics/scss/common.scss';
19 import 'statics/scss/grass-prompt.scss'; 23 import 'statics/scss/grass-prompt.scss';
  1 +const serverDownloadMixin = {
  2 + methods: {
  3 + async isDownloadBarHide() {
  4 + return true;
  5 + },
  6 +
  7 + async isMiniapp() {
  8 + return false;
  9 + },
  10 +
  11 + openByBrowser() {
  12 +
  13 + }
  14 +
  15 + }
  16 +};
  17 +
  18 +const clientDownloadMixin = {
  19 + methods: {
  20 + async isDownloadBarHide() {
  21 + let isMiniapp = await this.isMiniapp();
  22 +
  23 + if (isMiniapp) {
  24 + return true;
  25 + }
  26 +
  27 + return document.getElementById('no-download');
  28 + },
  29 +
  30 + isMiniapp() {
  31 + /* eslint-disable-next-line */
  32 + return this.$store.$context.env.isMiniApp;
  33 + },
  34 +
  35 + sleep(ns) {
  36 + return new Promise(resolve => {
  37 + setTimeout(resolve, ns);
  38 + });
  39 + },
  40 +
  41 + openByBrowser() {
  42 + let appPathUrl = window.location.pathname;
  43 +
  44 + const query = Object.assign({}, this.$route.query, {
  45 + headerid: -1,
  46 + toplayoutByH5: 'Y'
  47 + });
  48 +
  49 + const openBy = {
  50 + action: 'go.h5',
  51 + params: {
  52 + url: 'http://m.yohobuy.com' + appPathUrl,
  53 + param: JSON.stringify(query)
  54 + }
  55 + };
  56 +
  57 + this.$router.push({
  58 + name: 'openByBrowser',
  59 + query: {
  60 + 'openby:yohobuy': encodeURIComponent(JSON.stringify(openBy)),
  61 + share: false
  62 + }
  63 + });
  64 + }
  65 + }
  66 +};
  67 +
  68 +export default process.env.VUE_ENV === 'server' ?
  69 + serverDownloadMixin :
  70 + clientDownloadMixin;
@@ -20,7 +20,7 @@ export default { @@ -20,7 +20,7 @@ export default {
20 return; 20 return;
21 } 21 }
22 22
23 - let shareData = getDetailShareData(article); 23 + let shareData = getDetailShareData(article, this.$yoho.appVersion);
24 24
25 document && (document.title = shareData.title); 25 document && (document.title = shareData.title);
26 26
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <ArticleDetailHeader ref="header" class="article-detail-header" v-show="!share" :data="articleInfo" :step="headerAnimateStep" :title-step="headerTitleAnimateStep"> 3 <ArticleDetailHeader ref="header" class="article-detail-header" v-show="!share" :data="articleInfo" :step="headerAnimateStep" :title-step="headerTitleAnimateStep">
4 <div v-if="articleInfo.articleId && !articleInfo.empty" class="title-main"> 4 <div v-if="articleInfo.articleId && !articleInfo.empty" class="title-main">
5 <div class="title-info" :style="`transform: translate3d(0, ${viewMoreArticles ? '-50%' : '0'}, 0)`"> 5 <div class="title-info" :style="`transform: translate3d(0, ${viewMoreArticles ? '-50%' : '0'}, 0)`">
6 - <ArticleItemHeader class="title-info-author" :share="share" :data="authorData" :more="false" @on-follow="onFollowAuthor"></ArticleItemHeader> 6 + <ArticleItemHeader class="title-info-author" :share="false" :data="authorData" :more="false" @on-follow="onFollowAuthor"></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>
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 <ClientOnly> 13 <ClientOnly>
14 <Download v-if="share"></Download> 14 <Download v-if="share"></Download>
15 </ClientOnly> 15 </ClientOnly>
16 - <ArticleDeatilLong 16 + <ArticleDetailLong
17 v-if="articleInfo.sort == 2" 17 v-if="articleInfo.sort == 2"
18 ref="detailLong" 18 ref="detailLong"
19 :data="articleInfo" 19 :data="articleInfo"
@@ -24,8 +24,8 @@ @@ -24,8 +24,8 @@
24 :pos-id="posId" 24 :pos-id="posId"
25 @on-show-more="onShowMore" 25 @on-show-more="onShowMore"
26 @on-follow="onFollowAuthor"> 26 @on-follow="onFollowAuthor">
27 - </ArticleDeatilLong>  
28 - <ArticleDeatilNote 27 + </ArticleDetailLong>
  28 + <ArticleDetailNote
29 v-else 29 v-else
30 ref="detailNote" 30 ref="detailNote"
31 :data="articleInfo" 31 :data="articleInfo"
@@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
37 @on-show-more="onShowMore" 37 @on-show-more="onShowMore"
38 @on-follow="onFollowAuthor" 38 @on-follow="onFollowAuthor"
39 @on-praise="onPraise"> 39 @on-praise="onPraise">
40 - </ArticleDeatilNote> 40 + </ArticleDetailNote>
41 </template> 41 </template>
42 <template class="article-item" #item="{ data }"> 42 <template class="article-item" #item="{ data }">
43 <ArticleItem2 43 <ArticleItem2
@@ -92,8 +92,8 @@ import {getDetailShareData} from 'utils/share-handler'; @@ -92,8 +92,8 @@ import {getDetailShareData} from 'utils/share-handler';
92 import YAS from 'utils/yas-constants'; 92 import YAS from 'utils/yas-constants';
93 import ArticleDetailHeader from './components/detail/article-header'; 93 import ArticleDetailHeader from './components/detail/article-header';
94 import ArticleItemHeader from './components/article/article-item-header'; 94 import ArticleItemHeader from './components/article/article-item-header';
95 -import ArticleDeatilLong from './components/detail/article-long';  
96 -import ArticleDeatilNote from './components/detail/article-note'; 95 +import ArticleDetailLong from './components/detail/article-long';
  96 +import ArticleDetailNote from './components/detail/article-note';
97 import ArticleItem2 from './components/article/article-item2'; 97 import ArticleItem2 from './components/article/article-item2';
98 import ArticleDetailFooter from './components/detail/article-footer'; 98 import ArticleDetailFooter from './components/detail/article-footer';
99 import Download from './components/download-top'; 99 import Download from './components/download-top';
@@ -383,7 +383,7 @@ export default { @@ -383,7 +383,7 @@ export default {
383 return; 383 return;
384 } 384 }
385 385
386 - Share.setShareInfo(getDetailShareData(article)); 386 + Share.setShareInfo(getDetailShareData(article, this.$yoho.appVersion));
387 }, 387 },
388 reportArticleShow(items) { 388 reportArticleShow(items) {
389 if (!items || !items.length) { 389 if (!items || !items.length) {
@@ -469,8 +469,8 @@ export default { @@ -469,8 +469,8 @@ export default {
469 ReplaceToHome, 469 ReplaceToHome,
470 ArticleDetailHeader, 470 ArticleDetailHeader,
471 ArticleItemHeader, 471 ArticleItemHeader,
472 - ArticleDeatilLong,  
473 - ArticleDeatilNote, 472 + ArticleDetailLong,
  473 + ArticleDetailNote,
474 ArticleItem2, 474 ArticleItem2,
475 MoreActionSheet, 475 MoreActionSheet,
476 ArticleDetailFooter, 476 ArticleDetailFooter,
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 </div> 10 </div>
11 </div> 11 </div>
12 <div class="opts"> 12 <div class="opts">
13 - <WidgetFollow :class="invisibleClass" v-if="data.hasAttention" :share="share" :author-uid="data.authorUid" :authorType="data.authorType" :follow="data.hasAttention === 'Y'" @on-follow="onFollow" :pos-id="posId"></WidgetFollow> 13 + <WidgetFollow :class="invisibleClass" v-if="data.hasAttention" :share="share" :author-uid="data.authorUid" :authorType="data.authorType" :follow="data.hasAttention" @on-follow="onFollow" :pos-id="posId"></WidgetFollow>
14 <i v-if="more" class="iconfont icon-more1" @click="onMore"></i> 14 <i v-if="more" class="iconfont icon-more1" @click="onMore"></i>
15 </div> 15 </div>
16 </div> 16 </div>
@@ -173,7 +173,7 @@ export default { @@ -173,7 +173,7 @@ export default {
173 this.reportLabel(topicId); 173 this.reportLabel(topicId);
174 }, 174 },
175 onShare() { 175 onShare() {
176 - this.$yoho.share(getDetailShareData(this.data)); 176 + this.$yoho.share(getDetailShareData(this.data, this.$yoho.appVersion));
177 }, 177 },
178 onExpanding() { 178 onExpanding() {
179 if (this.data.articleType === 2) { 179 if (this.data.articleType === 2) {
@@ -137,8 +137,15 @@ export default { @@ -137,8 +137,15 @@ export default {
137 this.reportClickAvatar(); 137 this.reportClickAvatar();
138 }, 138 },
139 toArticlePage() { 139 toArticlePage() {
140 - if (this.share) {  
141 - return this.$links.toDownloadApp(); 140 + if (this.isMiniapp()) {
  141 + let url = `http://m.yohobuy.com/grass/article/${this.data.articleId}`;
  142 + let h5url = `http://m.yohobuy.com?openby:yohobuy={"action":"go.h5","params":{"h5back":"${encodeURIComponent(url)}"}}`;
  143 +
  144 + /* eslint-disable-next-line */
  145 + wx.miniProgram.navigateTo({
  146 + url: '/pages/common/webback?url=' + encodeURIComponent(h5url)
  147 + });
  148 + return;
142 } 149 }
143 150
144 this.$router.push({ 151 this.$router.push({
@@ -258,7 +258,7 @@ export default { @@ -258,7 +258,7 @@ export default {
258 }, 258 },
259 onFollow(data, follow) { 259 onFollow(data, follow) {
260 if (data.authorUid === this.currentAuthor.authorUid) { 260 if (data.authorUid === this.currentAuthor.authorUid) {
261 - this.currentAuthor.hasAttention = follow ? 'Y' : 'N'; 261 + this.currentAuthor.hasAttention = follow === 'Y' || follow === 'O' ? 'Y' : 'N';
262 } 262 }
263 this.CHANGE_AUTHOR_FOLLOW({authorUid: data.authorUid, authorType: data.authorType, follow, type: this.type}); 263 this.CHANGE_AUTHOR_FOLLOW({authorUid: data.authorUid, authorType: data.authorType, follow, type: this.type});
264 }, 264 },
@@ -86,7 +86,7 @@ export default { @@ -86,7 +86,7 @@ export default {
86 } 86 }
87 }, 87 },
88 onShare() { 88 onShare() {
89 - const share = getDetailShareData(this.data); 89 + const share = getDetailShareData(this.data, this.$yoho.appVersion);
90 90
91 keys(this.data.atUserInfo).forEach(k => { 91 keys(this.data.atUserInfo).forEach(k => {
92 share.desc = share.desc.replace(new RegExp(`@${k}#`, 'gm'), `${this.data.atUserInfo[k]}`); 92 share.desc = share.desc.replace(new RegExp(`@${k}#`, 'gm'), `${this.data.atUserInfo[k]}`);
@@ -31,6 +31,7 @@ @@ -31,6 +31,7 @@
31 :commentCount="articleState.commentCount" 31 :commentCount="articleState.commentCount"
32 :share="share" 32 :share="share"
33 :pos-id="posId" 33 :pos-id="posId"
  34 + :authorUid="data.authorUid"
34 @on-close="onCloseComment" 35 @on-close="onCloseComment"
35 @on-comment="onActionComment"></Comment> 36 @on-comment="onActionComment"></Comment>
36 </YohoActionSheet> 37 </YohoActionSheet>
@@ -46,7 +47,7 @@ import ArticleItemSlideImage from '../article/article-item-slide-image'; @@ -46,7 +47,7 @@ import ArticleItemSlideImage from '../article/article-item-slide-image';
46 import ArticleDetailHeader from './article-header'; 47 import ArticleDetailHeader from './article-header';
47 import YAS from 'utils/yas-constants'; 48 import YAS from 'utils/yas-constants';
48 import {mapState, mapMutations, createNamespacedHelpers} from 'vuex'; 49 import {mapState, mapMutations, createNamespacedHelpers} from 'vuex';
49 -const {mapState: mapArticleState} = createNamespacedHelpers('article'); 50 +const {mapState: mapArticleState, mapActions} = createNamespacedHelpers('article');
50 51
51 let timeOutEvent = null; 52 let timeOutEvent = null;
52 53
@@ -74,6 +75,8 @@ export default { @@ -74,6 +75,8 @@ export default {
74 recomendProduct: [], 75 recomendProduct: [],
75 showCommentAction: false, 76 showCommentAction: false,
76 showCommentActioning: false, 77 showCommentActioning: false,
  78 + productSknList: [],
  79 + productEleList: []
77 }; 80 };
78 }, 81 },
79 mounted() { 82 mounted() {
@@ -149,6 +152,7 @@ export default { @@ -149,6 +152,7 @@ export default {
149 }, 152 },
150 methods: { 153 methods: {
151 ...mapMutations(['SET_STATUS_BAR_COLOR']), 154 ...mapMutations(['SET_STATUS_BAR_COLOR']),
  155 + ...mapActions(['fetchProductBySknList']),
152 tStart(e) { 156 tStart(e) {
153 if (e.target.src) { 157 if (e.target.src) {
154 timeOutEvent = setTimeout(() => { 158 timeOutEvent = setTimeout(() => {
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <i class="iconfont icon-more1" @click="onMore"></i> 22 <i class="iconfont icon-more1" @click="onMore"></i>
23 </div> 23 </div>
24 </div> 24 </div>
25 - <ArticleDetailCommentList ref="commentList" v-if="data.articleId" :article-id="data.articleId" :share="share" :comment-count="articleState.commentCount" :has-comment="data.hasComment"></ArticleDetailCommentList> 25 + <ArticleDetailCommentList ref="commentList" v-if="data.articleId" :article-id="data.articleId" :share="share" :comment-count="articleState.commentCount" :has-comment="data.hasComment" :authorUid="data.authorUid"></ArticleDetailCommentList>
26 </div> 26 </div>
27 27
28 <div v-if="listTitle" class="rec-article-title"> 28 <div v-if="listTitle" class="rec-article-title">
@@ -14,6 +14,9 @@ @@ -14,6 +14,9 @@
14 :share="share" 14 :share="share"
15 :pos-id="0" 15 :pos-id="0"
16 :article-id="articleId" 16 :article-id="articleId"
  17 + :authorUid="authorUid"
  18 + :commentUid="comment.parentComment.uid"
  19 + @remove-comment="onRemoveComment"
17 @on-reply="onReply"> 20 @on-reply="onReply">
18 </CommentItem> 21 </CommentItem>
19 </div> 22 </div>
@@ -30,11 +33,11 @@ @@ -30,11 +33,11 @@
30 </template> 33 </template>
31 34
32 <script> 35 <script>
33 -import {get, forEach, find} from 'lodash';  
34 -import {Loading} from 'cube-ui';  
35 -import {mapState as mapYohoState, mapActions as mapYohoActions, createNamespacedHelpers} from 'vuex'; 36 +import { get, forEach, find, findIndex } from 'lodash';
  37 +import { Loading } from 'cube-ui';
  38 +import { mapState as mapYohoState, mapActions as mapYohoActions, createNamespacedHelpers } from 'vuex';
36 39
37 -const {mapActions} = createNamespacedHelpers('comment'); 40 +const { mapActions } = createNamespacedHelpers('comment');
38 41
39 export default { 42 export default {
40 name: 'ArticleDetailCommentList', 43 name: 'ArticleDetailCommentList',
@@ -46,7 +49,8 @@ export default { @@ -46,7 +49,8 @@ export default {
46 default: 1001 49 default: 1001
47 }, 50 },
48 share: Boolean, 51 share: Boolean,
49 - hasComment: Boolean 52 + hasComment: Boolean,
  53 + authorUid: Number
50 }, 54 },
51 data() { 55 data() {
52 return { 56 return {
@@ -128,29 +132,50 @@ export default { @@ -128,29 +132,50 @@ export default {
128 this.onFetching = false; 132 this.onFetching = false;
129 }, this.page > 1 ? 800 : 0); 133 }, this.page > 1 ? 800 : 0);
130 }, 134 },
131 - onReply({comment}) { 135 + async onReply({ comment }) {
  136 + const uid = (await this.$sdk.getUser()).uid;
  137 +
132 forEach(this.commentList, (val) => { 138 forEach(this.commentList, (val) => {
133 - if (val.parentComment && +val.parentComment.id === +comment.parentId) { 139 + if (val.parentComment && +val.parentComment.id === +comment.rootId) {
134 val.childrenComments.unshift(Object.assign(comment, { 140 val.childrenComments.unshift(Object.assign(comment, {
135 id: comment.destId, 141 id: comment.destId,
136 destId: this.articleId, 142 destId: this.articleId,
137 headIco: this.yoho.context.userHeadIco, 143 headIco: this.yoho.context.userHeadIco,
138 userName: this.yoho.context.userName, 144 userName: this.yoho.context.userName,
139 - parentUserName: get(find(val.childrenComments, {id: comment.parentId}), 'userName', '') 145 + uid,
  146 + parentUserName: +val.parentComment.id === +comment.parentId ?
  147 + val.parentComment.userName :
  148 + get(find(val.childrenComments, { id: +comment.parentId }), 'userName', '')
140 })); 149 }));
141 } 150 }
142 }); 151 });
143 }, 152 },
144 - addComment(comment) { 153 + async addComment(comment) {
145 this.commentList.unshift({ 154 this.commentList.unshift({
146 childrenComments: [], 155 childrenComments: [],
147 parentComment: Object.assign(comment, { 156 parentComment: Object.assign(comment, {
148 id: comment.destId, 157 id: comment.destId,
149 destId: this.articleId, 158 destId: this.articleId,
150 headIco: this.yoho.context.userHeadIco, 159 headIco: this.yoho.context.userHeadIco,
151 - userName: this.yoho.context.userName 160 + userName: this.yoho.context.userName,
  161 + uid: (await this.$sdk.getUser()).uid
152 }) 162 })
153 }); 163 });
  164 + },
  165 + onRemoveComment({ commentId }) {
  166 + for (let [index, val] of Object.entries(this.commentList)) {
  167 + if (val.parentComment && +val.parentComment.id === +commentId) {
  168 + this.commentList.splice(index, 1);
  169 + break;
  170 + }
  171 +
  172 + const childrenIndex = findIndex(val.childrenComments, { id: +commentId });
  173 +
  174 + if ((+index !== -1) && (childrenIndex !== -1)) {
  175 + this.commentList[index].childrenComments.splice(childrenIndex, 1);
  176 + break;
  177 + }
  178 + }
154 } 179 }
155 }, 180 },
156 components: { 181 components: {
1 <template> 1 <template>
2 - <a class="openapp-btn hover-opacity" href="javascript:;" @click="toDownloadPage"> 2 + <a v-if="visible" class="openapp-btn hover-opacity" href="javascript:;" @click="toDownloadPage">
3 <span class="avatar-block"> 3 <span class="avatar-block">
4 <div class="avatar"></div> 4 <div class="avatar"></div>
5 </span> 5 </span>
@@ -10,9 +10,25 @@ @@ -10,9 +10,25 @@
10 10
11 <script> 11 <script>
12 export default { 12 export default {
  13 + data() {
  14 + return {
  15 + visible: true
  16 + };
  17 + },
  18 + async mounted() {
  19 + let isHide = await this.isDownloadBarHide();
  20 +
  21 + if (isHide) {
  22 + this.visible = false;
  23 + }
  24 + },
13 methods: { 25 methods: {
14 toDownloadPage() { 26 toDownloadPage() {
15 - this.$openApp(); 27 + if (this.$yoho.isAndroid && (this.$yoho.isWechat || this.$yoho.isMobileQQ)) {
  28 + this.openByBrowser();
  29 + } else {
  30 + this.$openApp();
  31 + }
16 } 32 }
17 } 33 }
18 }; 34 };
@@ -11,11 +11,18 @@ export default { @@ -11,11 +11,18 @@ export default {
11 visible: true 11 visible: true
12 }; 12 };
13 }, 13 },
14 - mounted() { 14 + async mounted() {
  15 + const isHide = await this.isDownloadBarHide();
  16 +
  17 + if (isHide) {
  18 + this.visible = false;
  19 + return;
  20 + }
  21 +
15 this.getDownloadElem((elem) => { 22 this.getDownloadElem((elem) => {
16 const newElem = this.handleElem(elem.cloneNode(true)); 23 const newElem = this.handleElem(elem.cloneNode(true));
17 24
18 - // elem.parentNode.removeChild(elem); 25 + elem.parentNode.removeChild(elem);
19 this.$el.appendChild(newElem); 26 this.$el.appendChild(newElem);
20 }); 27 });
21 }, 28 },
@@ -34,6 +41,13 @@ export default { @@ -34,6 +41,13 @@ export default {
34 41
35 checkForDownload(); 42 checkForDownload();
36 }, 43 },
  44 + openApp() {
  45 + if (this.$yoho.isAndroid && (this.$yoho.isWechat || this.$yoho.isMobileQQ)) {
  46 + this.openByBrowser();
  47 + } else {
  48 + this.$openApp();
  49 + }
  50 + },
37 handleElem(elem) { 51 handleElem(elem) {
38 const vm = this; 52 const vm = this;
39 const miniElem = elem.querySelector('#mini-app-open'); 53 const miniElem = elem.querySelector('#mini-app-open');
@@ -54,7 +68,7 @@ export default { @@ -54,7 +68,7 @@ export default {
54 } 68 }
55 case 'download-go': { 69 case 'download-go': {
56 // 尝试打开app 70 // 尝试打开app
57 - vm.$openApp(); 71 + vm.openApp();
58 72
59 e.stopPropagation(); 73 e.stopPropagation();
60 e.preventDefault(); 74 e.preventDefault();
@@ -62,7 +76,7 @@ export default { @@ -62,7 +76,7 @@ export default {
62 } 76 }
63 case 'download-go-wechat': { 77 case 'download-go-wechat': {
64 // 尝试打开app 78 // 尝试打开app
65 - vm.$openApp(); 79 + vm.openApp();
66 80
67 e.stopPropagation(); 81 e.stopPropagation();
68 e.preventDefault(); 82 e.preventDefault();
@@ -113,7 +113,6 @@ export default { @@ -113,7 +113,6 @@ export default {
113 } 113 }
114 }, 114 },
115 mounted() { 115 mounted() {
116 -  
117 this.share = !this.$yoho.isApp; 116 this.share = !this.$yoho.isApp;
118 117
119 this.scrollEvent = throttle(this.onDounceScroll.bind(this), throttleTime); 118 this.scrollEvent = throttle(this.onDounceScroll.bind(this), throttleTime);
1 import Article from './article'; 1 import Article from './article';
2 import Common from './common'; 2 import Common from './common';
3 import UserPage from './userpage'; 3 import UserPage from './userpage';
  4 +import OpenApp from './openapp';
4 5
5 -export default [...Article, ...UserPage, ...Common]; 6 +export default [...Article, ...UserPage, ...Common, ...OpenApp];
  1 +export default [{
  2 + path: '/openapp.html',
  3 + name: 'openByBrowser',
  4 +
  5 + component: () => import(/* webpackChunkName: "openapp" */ './openapp')
  6 +}];
  1 +<template>
  2 +<div class="bg" v-if="image">
  3 + <div class="guide"></div>
  4 + <a class="btn" href="http://a.app.qq.com/o/simple.jsp?pkgname=com.yoho&g_f=995445"></a>
  5 +</div>
  6 +</template>
  7 +
  8 +<script>
  9 +export default {
  10 + name: 'openByBrowser',
  11 + title: 'Yoho!Buy有货|年轻人潮流购物中心 ',
  12 + mounted() {
  13 + if (!(this.$yoho.isWechat || this.$yoho.isMobileQQ)) {
  14 + this.$nextTick().then(() => {
  15 + this.$openApp();
  16 + });
  17 + }
  18 + },
  19 + computed: {
  20 + image() {
  21 + return this.$yoho.isWechat || this.$yoho.isMobileQQ;
  22 + }
  23 + }
  24 +};
  25 +</script>
  26 +
  27 +<style lang="scss" scoped>
  28 +.bg {
  29 + background-color: white;
  30 + width: 100%;
  31 + height: 100%;
  32 + overflow: hidden;
  33 + position: absolute;
  34 + top: 0;
  35 + left: 0;
  36 + right: 0;
  37 + bottom: 0;
  38 +}
  39 +
  40 +.guide {
  41 + width: 610px;
  42 + height: 852px;
  43 + margin: 50px 60px 0 92px;
  44 + background-image: url("~statics/image/openapp/guide.png");
  45 + background-size: cover;
  46 +}
  47 +
  48 +.btn {
  49 + display: block;
  50 + width: 440px;
  51 + height: 88px;
  52 + margin-left: 92px;
  53 + margin-top: 30px;
  54 + background-image: url("~statics/image/openapp/download.png");
  55 + background-size: cover;
  56 +}
  57 +</style>
1 <template> 1 <template>
2 <Layout class="author-fans-page"> 2 <Layout class="author-fans-page">
3 <LayoutHeader slot='header' :hide="noHeader" theme="white" class="author-page-header" :title="(isMine ? '我' : 'TA') + '的粉丝'"></LayoutHeader> 3 <LayoutHeader slot='header' :hide="noHeader" theme="white" class="author-page-header" :title="(isMine ? '我' : 'TA') + '的粉丝'"></LayoutHeader>
4 - <UserList ref="userList" :follow-name="isMine ? '回粉' : '关注'" :on-fetch="onFetch"></UserList> 4 + <UserList ref="userList" type="fans" :follow-name="isMine ? '回粉' : '关注'" :on-fetch="onFetch"></UserList>
5 </Layout> 5 </Layout>
6 </template> 6 </template>
7 7
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 <TabBlock @on-change-tab="onChangeTab"></TabBlock> 5 <TabBlock @on-change-tab="onChangeTab"></TabBlock>
6 </template> 6 </template>
7 </LayoutHeader> 7 </LayoutHeader>
8 - <UserList ref="userList" :on-fetch="onFetch"> 8 + <UserList ref="userList" type="follow" :on-fetch="onFetch">
9 <template v-if="activeType == 0" #item="{ data }"> 9 <template v-if="activeType == 0" #item="{ data }">
10 <TopicItem :data="data"></TopicItem> 10 <TopicItem :data="data"></TopicItem>
11 </template> 11 </template>
@@ -165,7 +165,7 @@ export default { @@ -165,7 +165,7 @@ export default {
165 return this.authorStates[`${this.authorInfo.authorUid}-${this.authorInfo.authorType}`] || this.authorBaseData; 165 return this.authorStates[`${this.authorInfo.authorUid}-${this.authorInfo.authorType}`] || this.authorBaseData;
166 }, 166 },
167 isAttention() { 167 isAttention() {
168 - return this.authorState.hasAttention === 'Y'; 168 + return this.authorState.hasAttention;
169 }, 169 },
170 list() { 170 list() {
171 if (this.activeIndex === 0) { 171 if (this.activeIndex === 0) {
@@ -12,7 +12,8 @@ @@ -12,7 +12,8 @@
12 <div v-for="(item, index) in list" :key="index" class="item"> 12 <div v-for="(item, index) in list" :key="index" class="item">
13 <slot name="item" :data="item"> 13 <slot name="item" :data="item">
14 <div class="user-item"> 14 <div class="user-item">
15 - <WidgetAvatar class="head-ico" :src="item.headIcon" :group="item.authGroupId" :width="100" :height="100" @click.native="toUserPage(item)"></WidgetAvatar> 15 + <WidgetAvatar class="head-ico" :src="item.headIcon" :group="item.authGroupId" :width="100" :height="100"
  16 + @click.native="toUserPage(item)"></WidgetAvatar>
16 <div class="user-info" @click="toUserPage(item)"> 17 <div class="user-info" @click="toUserPage(item)">
17 <p class="name">{{item.nickName}}</p> 18 <p class="name">{{item.nickName}}</p>
18 <p v-if="item.articleCount || item.fansCount" class="extra"> 19 <p v-if="item.articleCount || item.fansCount" class="extra">
@@ -20,9 +21,18 @@ @@ -20,9 +21,18 @@
20 <span v-if="item.fansCount">粉丝·{{item.fansCount}}</span> 21 <span v-if="item.fansCount">粉丝·{{item.fansCount}}</span>
21 </p> 22 </p>
22 </div> 23 </div>
23 - <div class="option-btn" :class="{'follow': item.isAttention}">  
24 - <WidgetFollow class="click-wrap" :author-uid="item.userId" :author-type="item.userType" :follow="item.isAttention" @on-follow="follow => onFollow(follow, index)"></WidgetFollow>  
25 - {{item.isAttention ? '已关注' : followName}} 24 + <div class="option-btn" :class="{'follow': item.isAttention === 'Y', 'rm-padding': item.isAttention === 'O'}">
  25 + <WidgetFollow class="click-wrap" :author-uid="item.userId" :author-type="item.userType"
  26 + :follow="item.isAttention" @on-follow="follow => onFollow(follow, index)"></WidgetFollow>
  27 + <template v-if="item.isAttention === 'Y'">
  28 + {{'已关注'}}
  29 + </template>
  30 + <template v-else-if="item.isAttention === 'N'">
  31 + {{'关注'}}
  32 + </template>
  33 + <template v-else-if="item.isAttention === 'O'">
  34 + <div class="each-follow"></div>
  35 + </template>
26 </div> 36 </div>
27 </div> 37 </div>
28 </slot> 38 </slot>
@@ -36,8 +46,8 @@ @@ -36,8 +46,8 @@
36 </template> 46 </template>
37 47
38 <script> 48 <script>
39 -import {get} from 'lodash';  
40 -import {Scroll, Loading} from 'cube-ui'; 49 +import { get } from 'lodash';
  50 +import { Scroll, Loading } from 'cube-ui';
41 51
42 export default { 52 export default {
43 props: { 53 props: {
@@ -48,7 +58,8 @@ export default { @@ -48,7 +58,8 @@ export default {
48 onFetch: { 58 onFetch: {
49 type: Function, 59 type: Function,
50 required: true 60 required: true
51 - } 61 + },
  62 + type: String
52 }, 63 },
53 data() { 64 data() {
54 return { 65 return {
@@ -100,7 +111,7 @@ export default { @@ -100,7 +111,7 @@ export default {
100 } 111 }
101 112
102 list.forEach(val => { 113 list.forEach(val => {
103 - val.isAttention = val.hasAttention === 'Y'; 114 + val.isAttention = val.hasAttention;
104 }); 115 });
105 116
106 this.list = this.list.concat(list); 117 this.list = this.list.concat(list);
@@ -118,7 +129,17 @@ export default { @@ -118,7 +129,17 @@ export default {
118 }); 129 });
119 }, 130 },
120 onFollow(follow, index) { 131 onFollow(follow, index) {
121 - this.list[index].isAttention = follow; 132 +
  133 + if (this.type === 'fans') {
  134 + // 粉丝是已关注的列表,你关注他,就变成互相关注
  135 + if (follow === 'Y') {
  136 + this.list[index].isAttention = 'O';
  137 + } else {
  138 + this.list[index].isAttention = follow;
  139 + }
  140 + } else {
  141 + this.list[index].isAttention = follow;
  142 + }
122 }, 143 },
123 toUserPage(item) { 144 toUserPage(item) {
124 this.$router.push({ 145 this.$router.push({
@@ -178,6 +199,11 @@ export default { @@ -178,6 +199,11 @@ export default {
178 border-color: #b0b0b0; 199 border-color: #b0b0b0;
179 } 200 }
180 201
  202 + &.rm-padding {
  203 + padding: 0;
  204 + border: none;
  205 + }
  206 +
181 .click-wrap { 207 .click-wrap {
182 width: 140%; 208 width: 140%;
183 height: 180%; 209 height: 180%;
@@ -189,6 +215,13 @@ export default { @@ -189,6 +215,13 @@ export default {
189 font-size: 0; 215 font-size: 0;
190 opacity: 0; 216 opacity: 0;
191 } 217 }
  218 +
  219 + .each-follow {
  220 + width: 110px;
  221 + height: 50px;
  222 + background-image: url("~statics/image/userpage/follow.png");
  223 + background-size: cover;
  224 + }
192 } 225 }
193 } 226 }
194 227
@@ -276,33 +276,33 @@ export default { @@ -276,33 +276,33 @@ export default {
276 276
277 commit(Types.GUANG_CHANGE_PRODUCT_FAV, favsList); 277 commit(Types.GUANG_CHANGE_PRODUCT_FAV, favsList);
278 }, 278 },
279 - async reportArticle(actions, {articleId}) {  
280 - const result = await this.$api.post('/api/grass/reportIllegalArticle', {articleId}); 279 + async reportArticle(actions, { articleId }) {
  280 + const result = await this.$api.post('/api/grass/reportIllegalArticle', { articleId });
281 281
282 return result; 282 return result;
283 }, 283 },
284 - async deleteArticle(actions, {articleId}) {  
285 - const result = await this.$api.post('/api/grass/deleteGrassArticle', {articleId}); 284 + async deleteArticle(actions, { articleId }) {
  285 + const result = await this.$api.post('/api/grass/deleteGrassArticle', { articleId });
286 286
287 return result; 287 return result;
288 }, 288 },
289 - async fetchTopicSimpleInfo({ commit, state }, {topicId, thumb}) { 289 + async fetchTopicSimpleInfo({ commit, state }, { topicId, thumb }) {
290 if (state.fetchTopicInfo) { 290 if (state.fetchTopicInfo) {
291 return Promise.resolve({}); 291 return Promise.resolve({});
292 } 292 }
293 293
294 commit(Types.FETCH_TOPIC_INFO_REQUEST); 294 commit(Types.FETCH_TOPIC_INFO_REQUEST);
295 - const result = await this.$api.post('/api/grass/getGrassTopicSimpleInfo', {topicId}); 295 + const result = await this.$api.post('/api/grass/getGrassTopicSimpleInfo', { topicId });
296 296
297 if (result.code === 200) { 297 if (result.code === 200) {
298 - commit(Types.FETCH_TOPIC_INFO_SUCCESS, {topicId, data: result.data, thumb}); 298 + commit(Types.FETCH_TOPIC_INFO_SUCCESS, { topicId, data: result.data, thumb });
299 } else { 299 } else {
300 commit(Types.FETCH_TOPIC_INFO_FAILD, {}); 300 commit(Types.FETCH_TOPIC_INFO_FAILD, {});
301 } 301 }
302 302
303 return result; 303 return result;
304 }, 304 },
305 - async fetchTopicRelatedArticles({ commit, state }, {topicId, page, type}) { 305 + async fetchTopicRelatedArticles({ commit, state }, { topicId, page, type }) {
306 commit(Types.FETCH_ARTICLE_TOPIC_REQUEST, { page }); 306 commit(Types.FETCH_ARTICLE_TOPIC_REQUEST, { page });
307 const result = await this.$api.post('/api/grass/topicRelatedArticles', { 307 const result = await this.$api.post('/api/grass/topicRelatedArticles', {
308 topicId, 308 topicId,
@@ -342,7 +342,7 @@ export default { @@ -342,7 +342,7 @@ export default {
342 342
343 return result; 343 return result;
344 }, 344 },
345 - async fetchFindNiceGoodsArticles({ commit, state }, { page, limit, thumb}) { 345 + async fetchFindNiceGoodsArticles({ commit, state }, { page, limit, thumb }) {
346 commit(Types.FETCH_NICE_GOODS_REQUEST, { page }); 346 commit(Types.FETCH_NICE_GOODS_REQUEST, { page });
347 const result = await this.$api.post('/api/grass/findGoodsList', { 347 const result = await this.$api.post('/api/grass/findGoodsList', {
348 page, 348 page,
@@ -365,7 +365,7 @@ export default { @@ -365,7 +365,7 @@ export default {
365 365
366 return result; 366 return result;
367 }, 367 },
368 - async fetchTopicList({ commit, state }, {page, limit}) { 368 + async fetchTopicList({ commit, state }, { page, limit }) {
369 if (state.fetchTopicList) { 369 if (state.fetchTopicList) {
370 return {}; 370 return {};
371 } 371 }
@@ -391,5 +391,17 @@ export default { @@ -391,5 +391,17 @@ export default {
391 } 391 }
392 392
393 return result; 393 return result;
394 - } 394 + },
  395 +
  396 + async fetchProductBySknList(ctx, { productSknList }) {
  397 + const productSkn = productSknList.join(',');
  398 +
  399 + const result = await this.$api.post('/api/grass/product', {
  400 + productSkn
  401 + });
  402 +
  403 + return get(result, 'data.product_list', []);
  404 + },
  405 +
  406 +
395 }; 407 };
@@ -212,6 +212,14 @@ export default { @@ -212,6 +212,14 @@ export default {
212 updateArticleState(state, article); 212 updateArticleState(state, article);
213 } 213 }
214 }, 214 },
  215 + [Types.SUB_ARTICLE_COMMENT_COUNT](state, {articleId, subCount}) {
  216 + let article = state.articleStates[articleId];
  217 +
  218 + if (article) {
  219 + article.commentCount = subCount ? (article.commentCount - subCount) : (articleId.commentCount - 1);
  220 + updateArticleState(state, article);
  221 + }
  222 + },
215 [Types.ASYNC_ARTICLE_COMMENT](state, {articleId, type}) { 223 [Types.ASYNC_ARTICLE_COMMENT](state, {articleId, type}) {
216 state[articlefield(type)].forEach(article => { 224 state[articlefield(type)].forEach(article => {
217 if (article.articleId === articleId) { 225 if (article.articleId === articleId) {
@@ -26,6 +26,7 @@ export const CHANGE_ARTICLE_LIST_SLIDE = 'CHANGE_ARTICLE_LIST_SLIDE'; @@ -26,6 +26,7 @@ export const CHANGE_ARTICLE_LIST_SLIDE = 'CHANGE_ARTICLE_LIST_SLIDE';
26 26
27 export const UPDATE_ARTICLE_STATE = 'UPDATE_ARTICLE_STATE'; 27 export const UPDATE_ARTICLE_STATE = 'UPDATE_ARTICLE_STATE';
28 export const UPDATE_ARTICLE_COMMENT_COUNT = 'UPDATE_ARTICLE_COMMENT_COUNT'; 28 export const UPDATE_ARTICLE_COMMENT_COUNT = 'UPDATE_ARTICLE_COMMENT_COUNT';
  29 +export const SUB_ARTICLE_COMMENT_COUNT = 'SUB_ARTICLE_COMMENT_COUNT';
29 export const ASYNC_ARTICLE_COMMENT = 'ASYNC_ARTICLE_COMMENT'; 30 export const ASYNC_ARTICLE_COMMENT = 'ASYNC_ARTICLE_COMMENT';
30 export const CHANGE_ARTICLE_LIST_INTRO = 'CHANGE_ARTICLE_LIST_INTRO'; 31 export const CHANGE_ARTICLE_LIST_INTRO = 'CHANGE_ARTICLE_LIST_INTRO';
31 export const CHANGE_ARTICLE_LIST_INTRO_HEIGHT = 'CHANGE_ARTICLE_LIST_INTRO_HEIGHT'; 32 export const CHANGE_ARTICLE_LIST_INTRO_HEIGHT = 'CHANGE_ARTICLE_LIST_INTRO_HEIGHT';
@@ -69,5 +69,17 @@ export default { @@ -69,5 +69,17 @@ export default {
69 }); 69 });
70 70
71 return result; 71 return result;
  72 + },
  73 +
  74 + async setCommentStatus(ctx, {type, commentId}) {
  75 + if (type === 1) {
  76 + return this.$api.post('/api/grass/deleteComment', {
  77 + commentId
  78 + });
  79 + } else if (type === 2) {
  80 + return this.$api.post('/api/grass/reportComment', {
  81 + commentId
  82 + });
  83 + }
72 } 84 }
73 }; 85 };
1 -import {get, first} from 'lodash'; 1 +import { get, first } from 'lodash';
2 2
3 -const DEFAULT_SHARE_IMAGE = 'http://static.yohobuy.com/m/v1/img/touch/apple-touch-icon-144x144-precomposed-new.png'; 3 +function _version2num(version) {
  4 + if (!version) {
  5 + return 0;
  6 + }
  7 +
  8 + let [m, j, b] = version.split(',');
  9 +
  10 + return +m * 10000 + +j * 100 + +b;
  11 +}
  12 +
  13 +function versionCompare(left, right) {
  14 + let leftNum = _version2num(left);
  15 + let rightNum = _version2num(right);
  16 +
  17 + if (leftNum === rightNum) {
  18 + return 0;
  19 + } else if (leftNum > rightNum) {
  20 + return 1;
  21 + } else {
  22 + return -1;
  23 + }
  24 +}
  25 +
  26 +const DEFAULT_SHARE_IMAGE =
  27 + 'http://static.yohobuy.com/m/v1/img/touch/apple-touch-icon-144x144-precomposed-new.png';
4 28
5 function handleProtocol(url) { 29 function handleProtocol(url) {
6 if (url.indexOf('//') < 0 || url.indexOf('http') >= 0) { 30 if (url.indexOf('//') < 0 || url.indexOf('http') >= 0) {
@@ -13,7 +37,7 @@ function handleProtocol(url) { @@ -13,7 +37,7 @@ function handleProtocol(url) {
13 return url.join('//'); 37 return url.join('//');
14 } 38 }
15 39
16 -const getDetailShareData = (article) => { 40 +const getDetailShareData = (article, app_version = '6.9.11') => {
17 let shareImage = ''; 41 let shareImage = '';
18 42
19 let desc = ''; 43 let desc = '';
@@ -27,45 +51,76 @@ const getDetailShareData = (article) => { @@ -27,45 +51,76 @@ const getDetailShareData = (article) => {
27 } else { 51 } else {
28 let blockList = get(article, 'blockList', []); 52 let blockList = get(article, 'blockList', []);
29 53
30 - shareImage = get(first(blockList.filter(block => block.templateKey === 'image')), 'contentData', '');  
31 - desc = get(blockList.filter(block => block.templateKey === 'text'), '[0].contentData', ''); 54 + shareImage = get(
  55 + first(blockList.filter(block => block.templateKey === 'image')),
  56 + 'contentData',
  57 + '',
  58 + );
  59 + desc = get(
  60 + blockList.filter(block => block.templateKey === 'text'),
  61 + '[0].contentData',
  62 + '',
  63 + );
32 } 64 }
33 65
34 if (shareImage && shareImage.indexOf('//') >= 0) { 66 if (shareImage && shareImage.indexOf('//') >= 0) {
35 - shareImage = `${window ? window.location.protocol : ''}//${shareImage.split('//')[1]}`; 67 + shareImage = `${window ? window.location.protocol : ''}//${
  68 + shareImage.split('//')[1]
  69 + }`;
36 } 70 }
37 71
  72 + const requiredVersion = '6.9.11';
  73 +
  74 + // let hideType = ['7', '8', '9'];
  75 + let hideType = ['8', '9'];
  76 +
  77 + // if (versionCompare(app_version, requiredVersion) >= 0) {
  78 + // hideType = ['8', '9'];
  79 + // }
  80 +
38 return { 81 return {
39 title: `@${article.authorName} 在有货社区上发了一篇内容,快点开看看!`, 82 title: `@${article.authorName} 在有货社区上发了一篇内容,快点开看看!`,
40 - imgUrl: handleProtocol(get(shareImage.split('?'), '[0]') || DEFAULT_SHARE_IMAGE),  
41 - link: handleProtocol(`${window ? window.location.origin : ''}/grass/article/${article.articleId}?share=true`), 83 + imgUrl: handleProtocol(
  84 + get(shareImage.split('?'), '[0]') || DEFAULT_SHARE_IMAGE,
  85 + ),
  86 + link: handleProtocol(
  87 + `${window ? window.location.origin : ''}/grass/article/${
  88 + article.articleId
  89 + }?share=true`,
  90 + ),
42 desc, 91 desc,
43 - hideType: ['7', '8', '9'] 92 + hideType,
  93 + shareType: 'grassDetail',
  94 + userName: article.authorName,
  95 + userIcon: article.authorHeadIco,
  96 + articleId: article.articleId,
44 }; 97 };
45 }; 98 };
46 99
47 -const getTopicShareData = (topic) => { 100 +const getTopicShareData = topic => {
48 return { 101 return {
49 title: topic.topicName, 102 title: topic.topicName,
50 imgUrl: handleProtocol(topic.topicImageUrl), 103 imgUrl: handleProtocol(topic.topicImageUrl),
51 - link: handleProtocol(`${location.origin}/grass/topic/${topic.topicId}?share=true`), 104 + link: handleProtocol(
  105 + `${location.origin}/grass/topic/${topic.topicId}?share=true`,
  106 + ),
52 desc: '我在有货的社区发现一个热门话题。' + topic.topicDesc, 107 desc: '我在有货的社区发现一个热门话题。' + topic.topicDesc,
53 - hideType: ['7', '8', '9'] 108 + hideType: ['7', '8', '9'],
54 }; 109 };
55 }; 110 };
56 111
57 -const getAuthorShareData = (author) => { 112 +const getAuthorShareData = author => {
58 return { 113 return {
59 title: `@${author.nickName} YO!社区,一起来玩潮流!`, 114 title: `@${author.nickName} YO!社区,一起来玩潮流!`,
60 - imgUrl: handleProtocol(get(author, 'headIco', '').split('?')[0] || DEFAULT_SHARE_IMAGE),  
61 - link: handleProtocol(`${location.origin}/grass/author/${author.authorType}/${author.authorUid}?share=true`), 115 + imgUrl: handleProtocol(
  116 + get(author, 'headIco', '').split('?')[0] || DEFAULT_SHARE_IMAGE,
  117 + ),
  118 + link: handleProtocol(
  119 + `${location.origin}/grass/author/${author.authorType}/${author.authorUid}?share=true`,
  120 + ),
62 desc: author.signature || '', 121 desc: author.signature || '',
63 - hideType: ['7', '8', '9'] 122 + hideType: ['7', '8', '9'],
64 }; 123 };
65 }; 124 };
66 125
67 -export {  
68 - getDetailShareData,  
69 - getTopicShareData,  
70 - getAuthorShareData  
71 -}; 126 +export { getDetailShareData, getTopicShareData, getAuthorShareData };
@@ -275,5 +275,24 @@ module.exports = { @@ -275,5 +275,24 @@ module.exports = {
275 params: { 275 params: {
276 articleId: {type: Number} 276 articleId: {type: Number}
277 } 277 }
  278 + },
  279 + '/api/grass/product': {
  280 + api: 'h5.product.batch',
  281 + params: {
  282 + productSkn: {type: String}
  283 + }
  284 + },
  285 + '/api/grass/reportComment': {
  286 + api: 'app.grass.reportIllegalArticle',
  287 + params: {
  288 + commentId: {type: Number}
  289 + }
  290 + },
  291 + '/api/grass/deleteComment': {
  292 + api: 'app.grass.delArticleComment',
  293 + auth: true,
  294 + params: {
  295 + commentId: {type: Number}
  296 + }
278 } 297 }
279 }; 298 };
@@ -67,7 +67,8 @@ const getContext = (req) => { @@ -67,7 +67,8 @@ const getContext = (req) => {
67 isWeibo: req.yoho.isWeibo, 67 isWeibo: req.yoho.isWeibo,
68 isYohoBuy: req.yoho.isYohoApp, 68 isYohoBuy: req.yoho.isYohoApp,
69 isChat: req.yoho.isChat, 69 isChat: req.yoho.isChat,
70 - clientIp: req.yoho.clientIp, 70 + isMiniApp: req.yoho.isMiniApp,
  71 + clientIp: req.yoho.clientIp
71 }, 72 },
72 ua: req.get('user-agent'), 73 ua: req.get('user-agent'),
73 hostname: os.hostname(), 74 hostname: os.hostname(),
@@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
14 <script type="text/javascript"> 14 <script type="text/javascript">
15 (function(d,c){var e=d.documentElement,a="orientationchange" in window?"orientationchange":"resize",b=function(){var f=e.clientWidth;if(!f){return}if(f>=750){e.style.fontSize="40px"}else{e.style.fontSize=40*(f/750)+"px"}};if(!d.addEventListener){return}b();c.addEventListener(a,b,false);d.addEventListener("DOMContentLoaded",b,false)})(document,window); 15 (function(d,c){var e=d.documentElement,a="orientationchange" in window?"orientationchange":"resize",b=function(){var f=e.clientWidth;if(!f){return}if(f>=750){e.style.fontSize="40px"}else{e.style.fontSize=40*(f/750)+"px"}};if(!d.addEventListener){return}b();c.addEventListener(a,b,false);d.addEventListener("DOMContentLoaded",b,false)})(document,window);
16 </script> 16 </script>
  17 + <script type="text/javascript" src="https://res.wx.qq.com/open/js/jweixin-1.3.2.js"></script>
17 <link rel="dns-prefetch" href="//cdn.yoho.cn"> 18 <link rel="dns-prefetch" href="//cdn.yoho.cn">
18 <link rel="dns-prefetch" href="imgsocial.yohobuy.com"> 19 <link rel="dns-prefetch" href="imgsocial.yohobuy.com">
19 <link rel="dns-prefetch" href="flv01.static.yhbimg.com"> 20 <link rel="dns-prefetch" href="flv01.static.yhbimg.com">