Authored by yyq

Merge remote-tracking branch 'origin/master' into feature/userPage

Showing 38 changed files with 553 additions and 360 deletions
... ... @@ -31,7 +31,7 @@ export const createApi = (context, store) => {
url,
params,
method: 'get',
}), options, store);
}, options), store);
},
post(url, data, options) {
return request(Object.assign({
... ...
... ... @@ -18,19 +18,21 @@ const request = async({url, method, reqParams = {}, context}) => {
return Promise.reject(new Error(`未找到对应的接口:${url}`));
}
if (!apiInfo.service) {
Object.assign(reqParams, {
uid: (user && user.uid) ? {
reqParams.method = apiInfo.api;
}
if (apiInfo.auth) {
if (user && user.uid) {
reqParams.uid = {
toString: () => {
return user.uid;
},
sessionKey: user.sessionKey,
appSessionType: user.appSessionType
} : 1,
method: apiInfo.api,
});
};
}
}
const params = checkParams.getParams(reqParams, apiInfo);
const cache = method.toLowerCase() !== 'get' ? false : apiInfo.cache;
const cache = (method.toLowerCase() !== 'get' || apiInfo.auth) ? false : apiInfo.cache;
const headers = {
'X-YOHO-IP': env.clientIp,
'X-Forwarded-For': env.clientIp,
... ...
... ... @@ -7,25 +7,32 @@
<p class="comment-user-name">{{parentComment.userName || '&nbsp;'}}</p>
<p class="comment-time">{{parentComment.createTime | time}}</p>
</div>
<WidgetFav class="comment-fav" :num="parentComment.praiseTotal"></WidgetFav>
<WidgetFav
class="comment-fav"
:comment-id="parentComment.id"
:num="parentComment.praiseTotal"
:option="favOption"></WidgetFav>
</div>
<div class="comment-cont" @click="replyComment" :data-parent-id="parentComment.parentId" :data-root-id="parentComment.rootId">
<div class="comment-cont" @click="replyComment(parentComment.id)" :data-parent-id="parentComment.parentId" :data-root-id="parentComment.rootId">
{{parentComment.content}}
</div>
<div class="reply-main" v-if="childrenComments.length">
<p class="reply-item" v-for="(reply, replyIndex) in childrenComments" :key="replyIndex" v-show="isShowReply(replyIndex)">
<div class="reply-main" v-if="replayShowList.length">
<p class="reply-item" v-for="(reply, replyIndex) in replayShowList" :key="replyIndex">
<span class="reply-user">
{{reply.userName}}
</span>
<span v-if="reply.parentUserName">
回复<em class="reply-to-user">@{{reply.parentUserName}}</em>
</span>:
<span @click="replyComment" :data-parent-id="reply.parentId" :data-root-id="reply.rootId">
<span @click="replyComment(reply.id)" :data-parent-id="reply.parentId" :data-root-id="reply.rootId">
{{reply.content}}
</span>
</p>
<p class="reply-more" v-if="isShowReplyMore" @click="viewMoreReply" v-html="replyMoreText"></p>
<p class="reply-more" v-if="moreReplyNum > 0" @click="onShowMore">
{{replyMoreText}}
<i class="iconfont icon-right" v-if="!isShowAllReply"></i>
</p>
</div>
</div>
</div>
... ... @@ -47,39 +54,48 @@ export default {
},
data() {
return {
showAllReply: false,
replyMoreText: '',
moreText: ''
isShowAllReply: false,
limitNum: 2,
};
},
computed: {
isShowReplyMore() {
return this.childrenComments.length > 2;
favOption() {
return {
selected: this.parentComment.isPraise === 'Y'
};
},
viewReplyNum() {
return this.childrenComments.length - 2;
replayShowList() {
if (this.childrenComments.length <= this.limitNum) {
return this.childrenComments;
}
if (this.isShowAllReply) {
return this.childrenComments;
}
return this.childrenComments.slice(0, this.limitNum);
},
moreReplyNum() {
return this.childrenComments.length - this.limitNum;
},
replyMoreText() {
return this.isShowAllReply ? '收起' : `查看${this.moreReplyNum}条回复`;
}
},
created() {
this.moreText = `查看${this.viewReplyNum}条回复<span class="iconfont icon-right"></span>`;
this.replyMoreText = this.moreText;
},
methods: {
...mapActions(['postComment']),
isShowReply(index) {
return index <= 1 || this.showAllReply;
onShowMore() {
this.isShowAllReply = !this.isShowAllReply;
},
viewMoreReply() {
this.replyMoreText = this.showAllReply ? this.moreText : '收起';
this.showAllReply = !this.showAllReply;
},
replyComment() {
this.postComment({
async replyComment(commentId) {
const result = await this.postComment({
content: '这还是一条测试回复',
commentId: this.parentComment.id,
commentId: commentId,
addType: 1,
columnType: this.columnType
});
if (result.code === 200) {
this.$emit('on-reply', {commentId: this.parentComment.id});
}
}
}
};
... ... @@ -88,7 +104,7 @@ export default {
<style scoped lang="scss">
.comment-item {
display: flex;
margin-bottom: 20px;
margin-bottom: 40px;
&:last-child {
margin-bottom: 0;
... ... @@ -133,6 +149,10 @@ export default {
}
}
.comment-fav {
padding-right: 20px;
}
.comment-cont {
font-size: 28px;
color: #444;
... ... @@ -149,6 +169,7 @@ export default {
font-size: 24px;
color: #444;
line-height: 34px;
margin-top: 20px;
.reply-item {
margin-bottom: 20px;
... ... @@ -171,7 +192,6 @@ export default {
.reply-more {
text-align: right;
color: #4a90e2;
margin-top: 10px;
}
}
}
... ...
<template>
<div class="comment-list">
<Scroll :options="scrollOption" @pulling-up="onPullingUp">
<CommentItem
v-for="(comment, index) in commentList"
:key="index"
:parent-comment="comment.parentComment"
:children-comments="comment.childrenComments"
:column-type="columnType">
</CommentItem>
</Scroll>
<div class="comment-content">
<Scroll ref="scroll" :data="commentList" :options="scrollOption" @pulling-up="onPullingUp">
<CommentItem
v-for="(comment, index) in commentList"
:key="index"
:parent-comment="comment.parentComment"
:children-comments="comment.childrenComments"
:column-type="columnType"
@on-reply="onReply">
</CommentItem>
</Scroll>
</div>
<div class="comment-footer">
<div class="comment-input" @click="onComment">参与评论</div>
</div>
</div>
</template>
... ... @@ -33,7 +39,13 @@ export default {
page: 1,
commentList: [],
scrollOption: {
pullUpLoad: true
pullUpLoad: {
threshold: 200,
txt: {
more: '上拉加载',
noMore: '- 已经到底了 -'
}
}
}
};
},
... ... @@ -41,16 +53,33 @@ export default {
this.fetchComments();
},
methods: {
...mapActions(['fetchCommentList']),
async fetchComments() {
...mapActions(['fetchCommentList', 'fetchReplayList', 'postComment']),
async fetchComments(refresh) {
const result = await this.fetchCommentList({
destId: this.destId,
columnType: this.columnType,
page: this.page
page: this.page,
});
let dirty = true;
if (result.code === 200) {
this.commentList = this.commentList.concat(get(result, 'data.commentInfos', []));
const appendComments = get(result, 'data.commentInfos', []);
if (refresh) {
this.commentList = appendComments;
} else {
this.commentList = this.commentList.concat(appendComments);
}
if (appendComments.length) {
this.page++;
} else {
dirty = false;
}
this.$emit('on-page-change', {
page: this.page,
size: result.data.total
});
} else {
this.$createToast && this.$createToast({
txt: result.message || '服务器开小差了',
... ... @@ -58,10 +87,54 @@ export default {
time: 1000
}).show();
}
this.$refs.scroll.forceUpdate(dirty);
return result;
},
onPullingUp() {
this.page++;
this.fetchComments();
},
async onComment() {
const result = await this.postComment({
content: '这还是一条测试回复1',
destId: this.destId,
addType: 0,
columnType: this.columnType
});
if (result.code === 200) {
this.onRefresh();
} else {
this.$createToast({
txt: result.message || '服务器开小差了',
type: 'warn',
time: 1000
}).show();
}
},
async onRefresh() {
this.page = 1;
await this.fetchComments(true);
this.$refs.scroll.forceUpdate(true);
},
async onReply({commentId}) {
const result = await this.fetchReplayList({
commentId
});
if (result.code === 200) {
this.commentList.forEach(comment => {
if (comment.parentComment.id === commentId) {
comment.childrenComments = get(result, 'data.childrenComments');
}
});
} else {
this.$createToast({
txt: result.message || '服务器开小差了',
type: 'warn',
time: 1000
}).show();
}
}
},
components: {Scroll, CommentItem}
... ... @@ -71,7 +144,43 @@ export default {
<style scoped lang="scss">
.comment-list {
width: 100%;
padding: 40px 30px;
height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
.comment-content {
flex: 1;
padding-top: 40px;
padding-left: 30px;
padding-right: 30px;
overflow: hidden;
/deep/ .before-trigger {
color: #b0b0b0;
}
}
.comment-footer {
width: 100%;
height: 100px;
overflow: hidden;
padding: 14px 30px;
background-color: #fff;
border-top: solid 1px #e0e0e0;
.comment-input {
width: 100%;
height: 100%;
background: #f0f0f0;
border: 1px solid #e0e0e0;
border-radius: 4PX;
color: #b0b0b0;
padding-left: 22px;
display: flex;
align-items: center;
}
}
}
</style>
... ...
... ... @@ -41,6 +41,8 @@ export default {
bottom: 0;
display: flex;
flex-direction: column;
background-color: #fff;
font-size: 24px;
&.header-fixed {
display: block;
... ...
import ProductGroup from './product-group';
import ProductList from './product-list';
import ProductItem from './product-item';
export default [
ProductGroup,
ProductList,
ProductItem
ProductList
];
... ...
<template>
<div class="product-item">
<div
class="product-item"
:class="{single}"
@click="onClick"
>
<div class="product-content">
<ImageFormat class="product-image" :src="src" width="136" height="180"></ImageFormat>
<ImageFormat :lazy="lazy" class="product-image" :src="product.productImage" :width="136" :height="180"></ImageFormat>
<div class="product-info">
<p class="product-name">{{name}}</p>
<p class="price">¥{{price}}</p>
<p class="product-name">{{product.productName}}</p>
<p class="price">¥{{product.salesPrice}}</p>
</div>
</div>
<div class="btn-fav hover-opacity" v-if="!isFav">收藏</div>
<div class="btn-fav btn-is-fav hover-opacity" v-else>已收藏</div>
<div
class="btn-fav hover-opacity"
@click="onFav"
:class="favClass">{{favText}}</div>
</div>
</template>
<script>
import {Button} from 'cube-ui';
import {createNamespacedHelpers} from 'vuex';
const {mapActions} = createNamespacedHelpers('product');
export default {
name: 'ProductItem',
props: ['name', 'src', 'price', 'isFav'],
components: {Button}
name: 'ProductGroupItem',
props: {
single: Boolean,
lazy: Boolean,
product: Object
},
data() {
return {
posting: false,
favorite: this.product.favorite
};
},
computed: {
favClass() {
return {'btn-is-fav': this.favorite, loading: this.posting};
},
favText() {
return this.favorite ? '已收藏' : '收藏';
}
},
methods: {
...mapActions(['postProductFav']),
async onFav() {
if (this.posting) {
return;
}
this.posting = true;
const favorite = !this.favorite;
const result = await this.postProductFav({
productId: this.product.id,
favorite,
productType: this.product.productType
});
this.posting = false;
if (result.code === 200) {
this.$createToast({
txt: favorite ? '收藏成功' : '取消收藏成功',
type: 'success',
time: 1000
}).show();
this.favorite = favorite;
} else {
this.$createToast({
txt: result.message || '服务器开小差了',
type: 'warn',
time: 1000
}).show();
}
},
onClick() {
console.log('click');
}
}
};
</script>
... ... @@ -26,13 +86,23 @@ export default {
.product-item {
position: relative;
margin-top: 20px;
padding: 10px 20px;
margin-right: 20px;
margin-left: 30px;
height: 180px;
width: 670px;
width: 580px;
background-color: #fff;
box-shadow: 0 5px 10px 0 rgba(107, 95, 95, 0.2);
display: inline-block;
padding: 10px 20px;
white-space: initial;
&:last-child {
margin-right: 30px;
}
&.single {
width: 690px;
}
}
.product-content {
... ... @@ -56,6 +126,8 @@ export default {
color: #9b9b9b;
letter-spacing: -0.25PX;
height: 104px;
display: flex;
align-content: center;
}
.price {
... ...
<template>
<div class="product-group">
<div
class="product-item"
:class="{single}"
<ProductGroupItem
v-for="(product, inx) in data"
:key="inx">
<div class="product-content">
<ImageFormat :lazy="lazy" class="product-image" :src="product.productImage" :width="136" :height="180"></ImageFormat>
<div class="product-info">
<p class="product-name">{{product.productName}}</p>
<p class="price">¥{{product.salesPrice}}</p>
</div>
</div>
<div class="btn-fav hover-opacity" @click="onFav" :class="{'btn-is-fav': product.isFav}">{{product.isFav ? '已收藏' : '收藏'}}</div>
</div>
:single="single"
:product="product"
:lazy="lazy"
:key="inx"></ProductGroupItem>
</div>
</template>
<script>
import ProductGroupItem from './product-group-item';
import {Button} from 'cube-ui';
export default {
... ... @@ -39,12 +32,7 @@ export default {
return this.data.length === 1;
},
},
methods: {
onFav() {
}
},
components: {Button}
components: {Button, ProductGroupItem}
};
</script>
... ... @@ -58,83 +46,5 @@ export default {
padding-bottom: 40px;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
.product-item {
position: relative;
margin-top: 20px;
margin-right: 20px;
margin-left: 30px;
height: 180px;
width: 580px;
background-color: #fff;
box-shadow: 0 5px 10px 0 rgba(107, 95, 95, 0.2);
display: inline-block;
padding: 10px 20px;
white-space: initial;
&:last-child {
margin-right: 30px;
}
&.single {
width: 690px;
}
}
.product-content {
display: flex;
width: 100%;
height: 100%;
}
.product-image {
width: 136px;
height: 180px;
margin-top: -30px;
}
.product-info {
padding-left: 20px;
flex: 1;
.product-name {
font-size: 24px;
color: #9b9b9b;
letter-spacing: -0.25PX;
height: 104px;
display: flex;
align-content: center;
}
.price {
font-size: 32px;
color: #d0021b;
letter-spacing: -0.34PX;
}
}
.btn-fav {
position: absolute;
bottom: 20px;
right: -20px;
width: 120px;
height: 50px;
padding: 0;
font-size: 24px;
background-color: #444;
background-size: contain;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
&.btn-is-fav {
background-color: #b0b0b0;
}
& /deep/ .iconfont {
margin: 0;
}
}
}
</style>
... ...
... ... @@ -9,7 +9,7 @@ const {mapActions} = createNamespacedHelpers('user');
export default {
name: 'WidgetFollow',
props: {
authorUid: Number,
authorUid: [Number, String],
follow: Boolean
},
data() {
... ...
... ... @@ -10,7 +10,7 @@
<script>
import {pxToRem} from '../../../utils/helpers.js';
import {foreach, get} from 'lodash';
import {forEach, get} from 'lodash';
import {createNamespacedHelpers} from 'vuex';
const {mapActions} = createNamespacedHelpers('user');
... ... @@ -71,7 +71,7 @@
}
},
created() {
foreach(defaultOption, (value, key) => {
forEach(defaultOption, (value, key) => {
if (!this.option.hasOwnProperty(key)) {
this.option[key] = value;
}
... ... @@ -191,6 +191,7 @@
this.syncService(syncFnName, {
articleId: this.articleId,
commentId: this.commentId,
status: this.btnSelected
}).then(backFn).catch(backFn);
}
... ...
... ... @@ -27,7 +27,7 @@ window._router = get(store, 'state.yoho.context.route');
Vue.prop('yoho', yoho);
Vue.prop('sdk', sdk);
Vue.prop('auth', function() {
if (!get(this.$store, '$context.islogin')) {
if (!get(this.$store, '$context.isLogin')) {
this.$sdk && this.$sdk.goLogin && this.$sdk.goLogin();
return false;
}
... ... @@ -37,7 +37,7 @@ Vue.prop('auth', function() {
Vue.use(Toast);
Vue.use(Dialog);
Vue.use(ActionSheet);
Vue.prop('api', createApi());
Vue.prop('api', createApi(context, store));
Vue.use(Lazy, {error: ''});
const fetchAsycData = (matched, r) => {
... ...
... ... @@ -45,7 +45,7 @@ export default {
},
methods: {
...mapMutations(['CHANGE_AUTHOR_FOLLOW']),
...mapActions(['fetchArticleList']),
...mapActions(['fetchArticleList', 'fetchArticleProductFavs']),
init() {
this.page = 1;
this.$refs.article.init();
... ... @@ -53,6 +53,9 @@ export default {
async onFetch() {
if (this.page === 1 && this.articleList.length) {
this.page++;
this.fetchArticleProductFavs({
articles: this.articleList
});
return this.articleList;
}
const articleId = parseInt(this.id, 10);
... ... @@ -68,6 +71,9 @@ export default {
if (result.code === 200) {
if (get(result, 'data.detailList', []).length) {
this.page++;
this.fetchArticleProductFavs({
articles: result.data.detailList
});
return Promise.resolve(result.data.detailList);
}
return Promise.resolve(false);
... ...
... ... @@ -5,16 +5,18 @@
<span class="comment-content">{{comment.content}}</span>
</p>
<div class="comment">
<div class="comment-input hover-opacity">添加回复:赞美是一种美德</div>
<div class="comment-input hover-opacity" @click="onComment">添加回复:赞美是一种美德</div>
</div>
<div class="total-comment">
<div class="total hover-opacity" @click="onComment">查看{{data.commentCount}}条评论</div>
<div class="total hover-opacity" @click="onGoComment">查看{{data.commentCount}}条评论</div>
<div class="last-time">{{data.date}}</div>
</div>
</div>
</template>
<script>
import {Input} from 'cube-ui';
import {createNamespacedHelpers} from 'vuex';
const {mapActions} = createNamespacedHelpers('comment');
export default {
name: 'ArticleItemComment',
... ... @@ -27,13 +29,22 @@ export default {
},
},
methods: {
onComment() {
...mapActions(['postComment']),
onGoComment() {
this.$router.push({
name: 'article.comment',
params: {
articleId: this.data.articleId
}
});
},
onComment() {
this.postComment({
content: '这还是一条测试回复',
destId: this.data.articleId,
addType: 0,
columnType: this.columnType
});
}
},
components: {CubeInput: Input}
... ...
... ... @@ -87,6 +87,9 @@ export default {
<style lang="scss" scoped>
.article-item-slide {
position: relative;
width: 750px;
height: 750px;
overflow: hidden;
& /deep/ .cube-slide-dots {
position: absolute;
... ... @@ -119,6 +122,7 @@ export default {
.image-slide-item {
width: 750px;
height: 750px;
overflow: hidden;
}
.pages {
... ...
... ... @@ -16,7 +16,7 @@
<template v-if="item.smallImage">
<div class="small-img-block clearfix">
<ImageFormat :lazy="false" :src="i.src" width="315" height="420" v-for="i in item.smallImage" ></ImageFormat>
<ImageFormat :lazy="false" :src="i.src" width="315" height="420" v-for="(i,index) in item.smallImage" :key="index"></ImageFormat>
</div>
</template>
... ... @@ -28,9 +28,7 @@
<template v-if="item.relatedReco">
<div class="products">
<ProductItem class="product-item" v-for="p in item.relatedReco.goods" :src="p.default_images" :price="p.sales_price" :name="p.product_name"></ProductItem>
<!--<ProductGroup v-for="p in item.relatedReco.goods" :data="[p]"></ProductGroup>-->
<ProductGroup v-for="(p,index) in item.relatedReco.goods" :data="[p]" :key="index"></ProductGroup>
</div>
</template>
</template>
... ... @@ -72,6 +70,7 @@ export default {
return {
scrollOpts: {
eventPassthrough: 'horizontal',
bounce: false
}
};
},
... ... @@ -140,12 +139,20 @@ export default {
}
.products {
margin-top: 40px;
margin-bottom: 40px;
}
/deep/ .product-group {
height: 220px;
padding-top: 0;
padding-bottom: 0;
margin: 40px 0;
}
.product-item + .product-item {
margin-top: 64px;
/deep/ .product-item {
margin-left: 0;
}
/deep/ .single {
width: 670px;
}
}
.big-img-block {
... ...
... ... @@ -33,33 +33,24 @@ export default {
ArticleFooter,
CommentActionSheet
},
props: {
articleId: {
type: [Number, String],
default() {
return 69156;
}
},
grassId: {
type: [Number, String],
default() {
return 82;
}
}
},
data() {
return {
articleId: 0,
grassId: 0
};
},
mounted() {
},
methods: {
onCommentClick() {
this.$refs.actionSheet.destId = this.grassId;
this.$refs.actionSheet.click();
},
...mapActions(['getDetail']),
fetch(params) {
this.articleId = params.articleId;
this.grassId = params.grassId;
return this.getDetail({
article_id: params.articleId,
grass_id: params.grassId
... ...
... ... @@ -5,8 +5,7 @@
<span class="name">{{data.name}}</span>
</div>
<div class="opts">
<button class="btn-follow hover-opacity" v-if="true">关注</button>
<button class="btn-follow followed hover-opacity" v-else>已关注</button>
<WidgetFollow v-bind="data"></WidgetFollow>
</div>
</div>
</template>
... ... @@ -60,24 +59,5 @@ export default {
display: flex;
align-items: center;
justify-content: flex-end;
.btn-follow {
width: 120px;
height: 50px;
padding: 0;
font-size: 26px;
border-radius: 3PX;
background-color: #222;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
&.followed {
border: solid 1px #4a4a4a;
background-color: #fff;
color: #000;
}
}
}
</style>
... ...
<template>
<YohoActionSheet ref="actionSheet" :full="true">
<div class="content">
<!--<template v-if="list.length === 0">-->
<!--<Loading class="loading" :size="50"></Loading>-->
<!--</template>-->
<CommentScrollView ref="commentList" @on-back="onBackClick"></CommentScrollView>
<CommentScrollView ref="commentList" :destId="destId" @on-back="onBackClick"></CommentScrollView>
</div>
</YohoActionSheet>
</template>
... ... @@ -23,12 +19,12 @@ export default {
},
data() {
return {
destId: 0
};
},
methods: {
click() {
this.$refs.actionSheet.show();
this.$refs.commentList.click();
},
onBackClick() {
this.$refs.actionSheet.hide();
... ... @@ -37,7 +33,7 @@ export default {
};
</script>
<style>
<style lang="scss" scoped>
.content {
width: 100%;
height: 100vh;
... ...
... ... @@ -4,14 +4,9 @@
<div class="back" @click="handleBack">
<i class="iconfont icon-left"></i>
</div>
{{ count }}评论
</div>
<Scroll class="scroll-wrapper" ref="scroll" :options="scrollOptions">
<div v-for="i in list" class="item">huangtao {{i}}</div>
</Scroll>
<div class="footer">
<div class="input">评论</div>
{{ count > 0 ? count + '条' : '' }}评论
</div>
<CommentList class="scroll-wrapper" :dest-id="destId" :column-type="1001" @on-page-change="onPageChange" v-if="destId"></CommentList>
</div>
</template>
... ... @@ -21,6 +16,14 @@ import {Scroll} from 'cube-ui';
export default {
name: 'CommentScrollView',
props: {
destId: {
type: [Number, String],
default() {
return 0;
}
}
},
components: {
Scroll
},
... ... @@ -29,32 +32,16 @@ export default {
list: [],
scrollOptions: {
bounce: false
}
},
count: 0
};
},
computed: {
count() {
return this.list.length > 0 ? this.list.length + '条' : '';
}
},
methods: {
click() {
this.$nextTick(() => {
this.initData();
this.forceUpdate();
}, 1000);
},
forceUpdate() {
this.$refs.scroll.forceUpdate();
},
initData() {
console.log('click')
for (let i = 0; i < 100; i++) {
this.list.push(i);
}
},
handleBack() {
this.$emit('on-back');
},
onPageChange({size}) {
this.count = size;
}
}
};
... ...
<template>
<div class="product-list">
<RecommendProductItem class="item" v-for="i in list" v-bind="i"></RecommendProductItem>
<RecommendProductItem class="item" v-for="(i, index) in list" v-bind="i" :key="index"></RecommendProductItem>
</div>
</template>
... ...
<template>
<div>
<WidgetTopic v-for="i in list" :topic="i.name"></WidgetTopic>
<WidgetTopic v-for="(i, index) in list" :topic="i.name" :key="index"></WidgetTopic>
</div>
</template>
... ...
... ... @@ -7,12 +7,24 @@
</template>
<script>
import dayjs from 'dayjs';
export default {
name: 'ZanBar',
props: ['praiseHeadIco', 'praiseCount', 'publish_time'],
filters: {
formatTime(str) {
return '1天前';
const now = dayjs();
const pubTime = dayjs(str * 1000);
if (now.year() === pubTime.year() && now.month() === pubTime.month() && now.day() === pubTime.day()) {
return pubTime.format('HH:mm');
} else if (now.year() === pubTime.year()) {
return pubTime.format('MM-DD');
} else {
return pubTime.format('YYYY-MM-DD');
}
}
}
};
... ...
... ... @@ -26,6 +26,40 @@ export default {
}
return result;
},
async fetchArticleProductFavs({commit}, {articles}) {
const products = [], ufoProducts = [];
articles.forEach(article => {
get(article, 'productList', []).forEach(product => {
if (article.articleProductType === 1) {
products.push(product.id);
} else if (article.articleProductType === 2) {
ufoProducts.push(product.id);
}
});
});
if (products.length) {
const result = await this.$api.get('/api/favorite/batchCheckIsFavorite', {
favIds: products.join(','),
type: 'product'
});
if (result.code === 200) {
if (!result.data) {
result.data = [];
}
commit(Types.FETCH_ARTICLE_PRODUCT_SUCCESS, {articles, favs: result.data, articleProductType: 1});
}
}
if (ufoProducts.length) {
const result = await this.$api.get('/api/ufo/user/isFavoriteBatch', {
productIds: ufoProducts.join(','),
});
commit(Types.FETCH_ARTICLE_PRODUCT_SUCCESS, {articles, favs: result.data, articleProductType: 2});
}
},
async fetchArticleListByTopic({commit, state}, {labelId, limit = 5, page = 1}) {
commit(Types.FETCH_ARTICLE_TOPIC_REQUEST, {page});
const result = await this.$api.get('/api/grass/labelRealtedArticleDetail', {
... ... @@ -80,6 +114,8 @@ export default {
if (author && author.code === 200 && author.data) {
result.getAuthor = author.data;
result.getAuthor.authorUid = result.getAuthor.author_id;
result.getAuthor.fellow = get(zan, 'data.hasAttention', false);
}
if (content && content.code === 200 && content.data) {
... ... @@ -93,15 +129,18 @@ export default {
const processContents = guangProcess.processArticleDetail(content.data);
// 插入商品
const goodsList = await this.$api.get('/api/guang/article/queryGoods', {
const [goodsList, favsList] = await Promise.all([this.$api.get('/api/guang/article/queryGoods', {
query: processContents.allgoods.join(','),
order: 's_t_desc',
limit: processContents.allgoods.length || 1
}).then(res => {
return get(res, 'data.product_list', []);
}), this.$api.get('/api/favorite/batchCheckIsFavorite', {
favIds: processContents.allgoods.join(','),
type: 'product'
})]).then(([res1, res2]) => {
return [get(res1, 'data.product_list', []), get(res2, 'data', [])];
});
result.getArticleContent = guangProcess.pushGoodsInfo(processContents.finalDetail, goodsList);
result.getArticleContent = guangProcess.pushGoodsInfo(processContents.finalDetail, goodsList, favsList);
}
if (zan && zan.code === 200 && zan.data) {
... ...
import { get, foreach, keyby, uniq, remove, clonedeep } from 'lodash';
import { get, forEach, keyBy, uniq, remove, cloneDeep } from 'lodash';
let domainList = {
'01': [
... ... @@ -89,14 +89,14 @@ export function processArticleDetail(articleContent) {
// 普通商品
if (tagList.length > 0) {
foreach(tagList, (tag, tagIndex) => {
forEach(tagList, (tag, tagIndex) => {
tagList[tagIndex].href = '//m.yohobuy.com/product/' + tag.product_skn + '.html';
});
}
// 全球购商品
if (tagListGlobal.length > 0) {
foreach(tagListGlobal, tagGlobal => {
forEach(tagListGlobal, tagGlobal => {
tagList.push(Object.assign(tagGlobal, {
href: '//m.yohobuy.com/product/global/' + tagGlobal.product_skn + '.html',
noCart: true
... ... @@ -106,7 +106,7 @@ export function processArticleDetail(articleContent) {
// 限定商品
if (tagListLimit.length > 0) {
foreach(tagListLimit, tagLimit => {
forEach(tagListLimit, tagLimit => {
tagList.push(Object.assign(tagLimit, {
href: '//m.yohobuy.com/product/' + tagLimit.product_skn + '.html',
noCart: true
... ... @@ -148,18 +148,18 @@ export function processArticleDetail(articleContent) {
let goodsDataLimit = get(value, 'goods.dataLimit', []);
// 普通商品
foreach(goodsData, (item) => {
forEach(goodsData, (item) => {
allgoods.push(item.id);
});
// 全球购商品
foreach(goodsDataGlobal, (item) => {
forEach(goodsDataGlobal, (item) => {
allgoods.push(item.id);
goodsData.push(item);
});
// 限定商品
foreach(goodsDataLimit, (item) => {
forEach(goodsDataLimit, (item) => {
allgoods.push(item.id);
goodsData.push(item);
});
... ... @@ -198,10 +198,10 @@ export function processArticleDetail(articleContent) {
/**
* 商品搜索商品数据处理
*/
function processProductList(list) {
function processProductList(list, favsList) {
const pruductList = [];
foreach(list, (product) => {
forEach(list, (product) => {
if (!product) {
return;
}
... ... @@ -243,12 +243,24 @@ function processProductList(list) {
product.default_images = getSourceUrl(product.default_images, 'goodsimg');
product.productImage = product.default_images;
product.salesPrice = product.sales_price;
product.productName = product.product_name;
product.is_soon_sold_out = product.is_soon_sold_out === 'Y';
if (product.cn_alphabet) {
product.cn_alphabet = productNameProcess(product.cn_alphabet);
}
// fav
const fav = favsList.find(i => i.id === product.product_id);
product.productId = product.product_id;
product.productType = 1;
product.favorite = fav || false;
product.url = product.is_global === 'Y' ? `/product/global/${product.product_skn}.html` :
`/product/${product.product_skn}.html`; // 商品url改版 // eslint-disable-line
... ... @@ -281,7 +293,7 @@ function processProductList(list) {
}
const _goodsArrayToObj = (goodsArray) => {
return keyby(goodsArray, value => {
return keyBy(goodsArray, value => {
return value.product_skn;
});
};
... ... @@ -289,21 +301,21 @@ const _goodsArrayToObj = (goodsArray) => {
/**
* 重新获取商品数据
*/
export function pushGoodsInfo(finalDetail, goodsList) {
let goodsObj = _goodsArrayToObj(processProductList(goodsList));
export function pushGoodsInfo(finalDetail, goodsList, favsList) {
let goodsObj = _goodsArrayToObj(processProductList(goodsList, favsList));
foreach(finalDetail, (value, key) => {
forEach(finalDetail, (value, key) => {
if (value.relatedReco) {
let goodsIds = [];
foreach(value.relatedReco.goods, relatedGoods => {
forEach(value.relatedReco.goods, relatedGoods => {
goodsIds.push(relatedGoods.id);
});
goodsIds = uniq(goodsIds);
finalDetail[key].relatedReco.goods = [];
foreach(goodsIds, (item, subKey) => {
forEach(goodsIds, (item, subKey) => {
if (goodsObj[item]) {
finalDetail[key].relatedReco.goods[subKey] = goodsObj[item];
} else {
... ... @@ -321,13 +333,13 @@ export function pushGoodsInfo(finalDetail, goodsList) {
}
if (value.collocation) {
foreach(value.collocation, (item, subKey) => {
foreach(item.goods, (thItem, thKey) => {
forEach(value.collocation, (item, subKey) => {
forEach(item.goods, (thItem, thKey) => {
if (thItem) {
finalDetail[key].collocation[subKey].goods[thKey] = goodsObj[thItem.id];
if (goodsObj[thItem.id]) {
let newGoods = clonedeep(goodsObj[thItem.id]);
let newGoods = cloneDeep(goodsObj[thItem.id]);
newGoods.default_images = thItem.src;
finalDetail[key].collocation[subKey].goods[thKey] = newGoods;
... ...
import * as Types from './types';
import { get } from 'lodash';
export default {
[Types.FETCH_ARTICLE_DETAIL_REQUEST](state) {
... ... @@ -14,10 +15,28 @@ export default {
state.articleList.forEach((item, index) => {
item.index = index;
});
data.forEach(item => {
get(item, 'productList', []).forEach(product => {
product.favorite = false;
});
});
},
[Types.FETCH_ARTICLE_DETAIL_FAILD](state) {
state.fetchArticleList = false;
},
[Types.FETCH_ARTICLE_PRODUCT_SUCCESS](state, {articles, favs, articleProductType}) {
articles.forEach(article => {
if (article.articleProductType === articleProductType) {
get(article, 'productList', []).forEach(product => {
const find = favs.find(f => f.id === product.id);
if (find) {
product.favorite = find.favorite;
}
});
}
});
},
[Types.FETCH_GUANG_SUCCESS](state, data) {
state.articleDetail = data;
},
... ... @@ -53,5 +72,5 @@ export default {
},
[Types.FETCH_ARTICLE_TOPIC_FAILD](state) {
state.fetchArticleListByTopic = false;
},
}
};
... ...
... ... @@ -2,6 +2,8 @@ export const FETCH_ARTICLE_DETAIL_REQUEST = 'FETCH_ARTICLE_DETAIL_REQUEST';
export const FETCH_ARTICLE_DETAIL_FAILD = 'FETCH_ARTICLE_DETAIL_FAILD';
export const FETCH_ARTICLE_DETAIL_SUCCESS = 'FETCH_ARTICLE_DETAIL_SUCCESS';
export const FETCH_ARTICLE_PRODUCT_SUCCESS = 'FETCH_ARTICLE_PRODUCT_SUCCESS';
export const FETCH_GUANG_REQUEST = 'FETCH_GUANG_REQUEST';
export const FETCH_GUANG_FAILED = 'FETCH_GUANG_FAILED';
export const FETCH_GUANG_SUCCESS = 'FETCH_GUANG_SUCCESS';
... ... @@ -10,6 +12,7 @@ export const FETCH_ZAN_SUCCESS = 'FETCH_ZAN_SUCCESS';
export const CHANGE_AUTHOR_FOLLOW = 'CHANGE_AUTHOR_FOLLOW';
export const CHANGE_AUTHOR_TOPIC_FOLLOW = 'CHANGE_AUTHOR_TOPIC_FOLLOW';
export const CHANGE_AUTHOR_PRODUCT_FAV = 'CHANGE_AUTHOR_PRODUCT_FAV';
export const FETCH_ARTICLE_TOPIC_REQUEST = 'FETCH_ARTICLE_TOPIC_REQUEST';
export const FETCH_ARTICLE_TOPIC_FAILD = 'FETCH_ARTICLE_TOPIC_FAILD';
... ...
import * as Types from './types';
export default {
async fetchCommentList({commit}, {destId, columnType = 1001, limit = 10, page = 1}) {
async fetchCommentList({commit}, {destId, columnType = 1001, limit = 20, page = 1}) {
commit(Types.FETCH_COMMENT_REQUEST);
const result = await this.$api.get('/api/grass/queryArticleComments', {
destId,
... ... @@ -20,6 +20,20 @@ export default {
}
return result;
},
async fetchReplayList(actions, {commentId}) {
const result = await this.$api.get('/api/grass/queryArticleCommentReply', {
commentId,
limit: 200,
page: 1,
});
if (result && result.code === 200) {
if (!result.data.childrenComments) {
result.data.childrenComments = [];
}
}
return result;
},
async fetchCommentFeebbackList({commit}, {commentId, limit = 10, page = 1}) {
commit(Types.FETCH_COMMENT_FEEDBACK_REQUEST);
const result = await this.$api.get('/api/grass/queryArticleCommentReply', {
... ... @@ -38,8 +52,7 @@ export default {
}
return result;
},
async postComment({commit}, {content, destId, commentId, addType, columnType = 1001}) {
commit(Types.POST_COMMENT_REQUEST);
async postComment(actions, {content, destId, commentId, addType, columnType = 1001}) {
const result = await this.$api.get('/api/grass/addArticleComment', {
destId,
content,
... ... @@ -48,11 +61,6 @@ export default {
columnType
});
if (result && result.code === 200) {
commit(Types.POST_COMMENT_SUCCESS);
} else {
commit(Types.POST_COMMENT_FAILD);
}
return result;
}
};
... ...
... ... @@ -5,6 +5,7 @@ import storeYoho from './yoho';
import storeArticle from './article';
import storeUser from './user';
import storeComment from './comment';
import storeProduct from './product';
import plugin from './plugin';
Vue.use(Vuex);
... ... @@ -16,7 +17,8 @@ export function createStore(context) {
yoho: storeYoho(),
article: storeArticle(),
user: storeUser(),
comment: storeComment()
comment: storeComment(),
product: storeProduct()
},
strict: process.env.NODE_ENV !== 'production',
plugins: [plugin]
... ...
import * as Types from './types';
export default {
fetchProductFavStatus({state, commit}, {productId}) {
if (!state.favBatchs.length) {
commit(Types.FETCH_PRODUCT_FAV_REQUEST, {productId, promise: () => {
return new Promise(resolve => {
setTimeout(async() => {
let fetchIds;
if (state.favBatchs.some(id => id === productId)) {
fetchIds = state.favBatchs.map(id => id);
} else {
fetchIds = [productId];
}
const result = await this.$api.get('/api/favorite/batchCheckIsFavorite', {
favIds: fetchIds,
});
async postProductFav({commit}, {productId, favorite, productType}) {
let postUrl, params;
if (result.code === 200) {
resolve(result.data);
} else {
resolve([]);
}
}, 50);
});
}});
} else {
commit(Types.FETCH_PRODUCT_FAV_REQUEST, {productId});
if (productType === 1) {
postUrl = favorite ? '/api/favorite/add' : '/api/favorite/cancel';
params = {
type: 'product',
fav_id: productId
};
} else if (productType === 2) { // ufo
postUrl = favorite ? '/api/ufo/user/favoriteAdd' : '/api/ufo/user/favoriteCancel';
params = {
productId
};
}
const result = await this.$api.get(postUrl, params);
return result;
}
};
... ...
... ... @@ -5,8 +5,6 @@ export default function() {
return {
namespaced: true,
state: {
favBatchs: [],
favBatchPromise: {}
},
actions,
mutations
... ...
import * as Types from './types';
export default {
[Types.FETCH_PRODUCT_FAV_REQUEST](state, {productId}) {
state.favBatchs.push(productId);
}
};
... ...
export const FETCH_PRODUCT_FAV_REQUEST = 'FETCH_PRODUCT_FAV_REQUEST';
export const FETCH_PRODUCT_FAV_FAILD = 'FETCH_PRODUCT_FAV_FAILD';
export const FETCH_PRODUCT_FAV_SUCCESS = 'FETCH_PRODUCT_FAV_SUCCESS';
export const POST_PRODUCT_FAV_REQUEST = 'POST_PRODUCT_FAV_REQUEST';
export const POST_PRODUCT_FAV_FAILD = 'POST_PRODUCT_FAV_FAILD';
export const POST_PRODUCT_FAV_SUCCESS = 'POST_PRODUCT_FAV_SUCCESS';
... ...
... ... @@ -66,7 +66,6 @@ export default function() {
},
[Types.SET_SUPPORT_PASSIVE](state, {supportsPassive}) {
state.window.supportsPassive = supportsPassive;
console.log(state.window.supportsPassive)
}
},
actions: {
... ...
... ... @@ -28,6 +28,7 @@ module.exports = {
},
'/api/grass/updateAttention': {
api: 'app.grass.updateAttention',
auth: true,
params: {
topicId: {type: Number, require: false},
followUid: {type: Number, require: false},
... ... @@ -37,6 +38,7 @@ module.exports = {
},
'/api/grass/updateFavorite': {
api: 'app.grass.updateFavorite',
auth: true,
params: {
articleId: {type: Number},
isAdd: {type: String}
... ... @@ -44,6 +46,7 @@ module.exports = {
},
'/api/grass/updateArticlePraise': {
api: 'app.grass.updateArticlePraise',
auth: true,
params: {
articleId: {type: Number},
status: {type: Number}
... ... @@ -51,6 +54,7 @@ module.exports = {
},
'/api/grass/updateCommentPraise': {
api: 'app.grass.updateCommentPraise',
auth: true,
params: {
commentId: {type: Number},
status: {type: Number}
... ... @@ -82,6 +86,7 @@ module.exports = {
},
'/api/guang/article/zan': {
api: 'app.grass.articleDetailsForGuang',
auth: true,
params: {
uid: {type: Number, require: false},
articleId: {type: Number, require: false},
... ... @@ -89,6 +94,7 @@ module.exports = {
},
'/api/guang/article/setFav': {
api: 'app.grass.updateFavorite',
auth: true,
params: {
uid: {type: Number, require: false},
articleId: {type: Number, require: false},
... ... @@ -97,6 +103,7 @@ module.exports = {
},
'/api/guang/article/setZan': {
api: 'app.grass.updateFavorite',
auth: true,
params: {
uid: {type: Number, require: false},
articleId: {type: Number, require: false},
... ... @@ -111,22 +118,9 @@ module.exports = {
limit: {type: Number, require: false}
}
},
'/api/favorite/add': {
api: 'app.favorite.add',
params: {
id: {type: Number},
type: {type: Number},
}
},
'/api/favorite/cancel': {
api: 'app.favorite.cancel',
params: {
fav_id: {type: Number},
type: {type: Number},
}
},
'/api/grass/queryArticleComments': {
api: 'app.grass.queryArticleComments',
auth: true,
params: {
page: {type: Number, require: false},
limit: {type: Number, require: false},
... ... @@ -136,6 +130,7 @@ module.exports = {
},
'/api/grass/queryArticleCommentReply': {
api: 'app.grass.queryArticleCommentReply',
auth: true,
params: {
page: {type: Number, require: false},
limit: {type: Number, require: false},
... ... @@ -144,6 +139,7 @@ module.exports = {
},
'/api/grass/addArticleComment': {
api: 'app.grass.addArticleComment',
auth: true,
params: {
content: {type: String},
destId: {type: Number},
... ... @@ -152,21 +148,51 @@ module.exports = {
columnType: {type: Number},
}
},
'/api/favorite/add': {
api: 'app.favorite.add',
auth: true,
params: {
fav_id: {type: Number},
type: {type: String},
}
},
'/api/favorite/cancel': {
api: 'app.favorite.cancel',
auth: true,
params: {
fav_id: {type: Number},
type: {type: String},
}
},
'/api/favorite/batchCheckIsFavorite': {
api: 'app.favorite.batchCheckIsFavorite',
auth: true,
params: {
favIds: {type: String},
}
},
'/api/ufo/user/favoriteAdd': {
api: 'ufo.user.favoriteAdd',
auth: true,
ufo: true,
params: {
favIds: {type: Array},
productId: {type: Number},
}
},
'/api/ufo/user/favoriteCancel': {
api: 'ufo.user.favoriteCancel',
auth: true,
ufo: true,
params: {
productId: {type: Number},
}
},
'/api/ufo/user/isFavoriteBatch': {
api: 'ufo.user.isFavoriteBatch',
auth: true,
ufo: true,
params: {
content: {type: String},
destId: {type: Number},
commentId: {type: Number},
addType: {type: Number},
columnType: {type: Number},
productIds: {type: String},
}
}
};
... ...
... ... @@ -103,7 +103,7 @@ module.exports = {
enable_offline_queue: false,
retry_strategy(options) {
if (options.error && options.error.code === 'ECONNREFUSED') {
console.log('connect redis server fail');
// console.log('connect redis server fail');
}
if (options.attempt < 10) {
... ... @@ -152,7 +152,7 @@ if (isProduction) {
enable_offline_queue: false,
retry_strategy(options) {
if (options.error && options.error.code === 'ECONNREFUSED') {
console.log('connect redis server fail');
// console.log('connect redis server fail');
}
if (options.attempt < 10) {
... ...
... ... @@ -15,25 +15,27 @@ module.exports = async(req, res, next) => {
if (!apiInfo) {
return next();
}
let baseParams;
const baseParams = {};
if (!apiInfo.service) {
baseParams = {
uid: (req.user && req.user.uid) ? {
baseParams.method = apiInfo.api;
}
if (apiInfo.auth) {
if (req.user && req.user.uid) {
baseParams.uid = {
toString: () => {
return req.user.uid || 0;
return req.user.uid;
},
sessionKey: req.user.sessionKey,
appSessionType: req.user.appSessionType
} : 1,
method: apiInfo.api
};
};
}
}
try {
const reqParams = Object.assign({}, req.query, req.body, baseParams);
const params = checkParams.getParams(reqParams, apiInfo);
const cache = req.method.toLowerCase() !== 'get' ? false : apiInfo.cache;
const cache = (req.method.toLowerCase() !== 'get' || apiInfo.auth) ? false : apiInfo.cache;
let method = req.method.toLowerCase() === 'post' ? 'post' : 'get';
let result;
... ...
... ... @@ -26,8 +26,7 @@
"kebabCase": true
},
"lodash": {
"transform": "lodash/${member}",
"kebabCase": true
"transform": "lodash/${member}"
}
},
"dependencies": {
... ...