Authored by shuaiguo

feat(article/detail): 文章详情 reviewed by 张文文

... ... @@ -9,6 +9,10 @@ export default {
src: String,
width: Number,
height: Number,
mode: {
type: Number,
default: 2
},
alt: String
},
data() {
... ... @@ -29,7 +33,8 @@ export default {
return (this.src || '')
.replace('http://', '//')
.replace('{width}', this.width || '')
.replace('{height}', this.height || '');
.replace('{height}', this.height || '')
.replace('{mode}', this.mode);
}
}
};
... ...
... ... @@ -3,27 +3,26 @@
<div class="detail-container">
<div class="author-container">
<ArticleAuthor :userHeadIco="userHeadIco" :userName="userName"></ArticleAuthor>
<ArticleAuthor />
</div>
<div class="source-container">
<ArticleVideo v-if="showVideo" :videoUrl="videoUrl" :coverUrl="coverUrl"></ArticleVideo>
<ArticleVideo v-if="isVideo" :videoUrl="detailInfo.videoUrl" :coverUrl="detailInfo.coverUrl"></ArticleVideo>
<ArticleImage v-else :imageList="imageList"></ArticleImage>
</div>
<div class="slide-container">
<HorizontalSlide :value="imageList"></HorizontalSlide>
<associated-item :items="detailInfo.productList"></associated-item>
</div>
<div class="note-container">
<div class="note-header">Nike 旗下大热鞋款 Air Max 95</div>
<div class="note-content">Nike 旗下大热鞋款 Air Max 95 一直以来在街头造型当中的能见度都算高,凭藉其舒适脚感与百搭外型 @NIKE官方 Max 95 也轻松成为许多鞋迷的心头好紧接「Triple White」之后,备受期待的 Nike 全新鞋款 SF-AF1 Mid 又有一双鞋的「Tiger Camo」配色率先现身网络。</div>
<div class="note-date">2019-01-02</div>
<div class="note-content" v-html="content"></div>
<div class="note-date">{{detailInfo.publishTimeStr.split(' ')[0]}}</div>
<div class="praise-wrapper">
<div class="praise-border"></div>
<div class="praise-div">
<div class="praise-icon"></div>
<div class="praise-num">1829</div>
<div :class="detailInfo.hasPraise === 'Y' ? 'praise-icon praised-icon' : 'praise-icon'"></div>
<div class="praise-num">{{detailInfo.praiseCount}}</div>
</div>
</div>
</div>
... ... @@ -38,10 +37,14 @@
</template>
<script>
import { createNamespacedHelpers } from 'vuex';
import ArticleAuthor from './components/article-author';
import ArticleVideo from './components/article-video';
import ArticleImage from './components/article-image';
import HorizontalSlide from './components/horizontalSlide';
import AssociatedItem from './components/associated-item';
const { mapState } = createNamespacedHelpers('article/articleDetail');
export default {
name: 'articleDetail',
... ... @@ -49,33 +52,41 @@ export default {
ArticleAuthor,
ArticleVideo,
ArticleImage,
HorizontalSlide
AssociatedItem
},
props: {
articleId: {
required: true
}
},
data() {
return {
imageList: [
{image_url: 'http://img11.static.yhbimg.com/goodsimg/2019/07/02/16/0191aa70fd1f46d75e5ff974dfe0cdac1c.jpg?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80'},
{image_url: 'http://img11.static.yhbimg.com/goodsimg/2018/11/06/14/01bbabe3eb9597d5a36f51a0a29c23e441.jpg?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80'},
{image_url: 'http://img11.static.yhbimg.com/goodsimg/2018/11/06/14/016c59b642d25a92049003c318ea7b97ff.jpg?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80'},
{image_url: 'http://img11.static.yhbimg.com/goodsimg/2018/11/06/14/012df38fe6117ea9467a01919a4b4c2f3c.jpg?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80'}
],
showVideo: false,
videoUrl: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4',
coverUrl: 'http://file02.16sucai.com/d/file/2015/0408/779334da99e40adb587d0ba715eca102.jpg',
userHeadIco: 'https://img12.static.yhbimg.com/article/2019/02/26/16/02456ade977d8dfdbc4ca548b196c1d62b.png?imageView/2/w/{width}/h/{height}',
userName: 'MOMO草'
};
computed: {
...mapState(['detailInfo']),
isVideo({detailInfo: {sort}}) {
return +sort === 4;
},
imageList({detailInfo: {blockList = []}}) {
return blockList.filter(content => content.templateKey === 'image').map(content => ({image_url: content.contentData}));
},
content({detailInfo: {blockList = [], atUserInfo = {}}}) {
const contentBlock = blockList.find(block => block.templateKey === 'text');
const content = contentBlock.contentData || '';
return content.replace(/@(\d*)#|(\r\n|\n)/gm, (match, p1, p2)=> {
if (p2) {
return '<br/>';
}
return `<span style="color:#1890ff" id='${p1}'>@${atUserInfo[p1]}</span>`;
});
}
},
asyncData({store, router}) {
const articleId = router.params.articleId;
store.dispatch('/article/')
return store.dispatch('article/articleDetail/fetchDetailInfo', {articleId});
},
activated() {
}
};
</script>
... ... @@ -153,9 +164,14 @@ export default {
.praise-icon {
width: 64px;
height: 64px;
background: url(~statics/image/order/alipay@3x.png) no-repeat;
background-image: url(~statics/image/article/praise@3x.png);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.praised-icon {
background-image: url(~statics/image/article/praised@3x.png);
}
.praise-num {
... ...
<template>
<div class="container">
<WidgetAvatar :lazy="true" class="widget-avatar" :src="userHeadIco" :width="70" :height="70"></WidgetAvatar>
<span>{{userName}}</span>
<WidgetAvatar :lazy="true" class="widget-avatar" :src="authorInfo.authorHeadIco" :width="70" :height="70"></WidgetAvatar>
<span>{{authorInfo.authorName}}</span>
</div>
</template>
<script>
import { createNamespacedHelpers } from 'vuex';
import WidgetAvatar from '../../../components/widget-avatar';
const { mapGetters } = createNamespacedHelpers('article/articleDetail');
export default {
name: 'articleAuthor',
components: {
WidgetAvatar
},
props: {
userHeadIco: String,
userName: String
computed: {
...mapGetters(['authorInfo'])
}
};
</script>
... ...
<template>
<div class="horizontalSlide">
<div class="horizontal-slide">
<ul class="list-warp">
<li class="list-item" v-for="(item, index) in value" :key="index">
<li
:class="items.length === 1 ? 'list-item one-item' : 'list-item'"
v-for="(item, index) in items"
:key="index"
@click="goToDetail(item, index)">
<ImageFormat
class="image"
src="//img11.static.yhbimg.com/goodsimg/2018/12/28/15/0160a985f0b5999b77436b7af78a85f3df.jpg" alt="加载失败"
:src="item.productImage" alt="加载失败"
:width="136" :height="180"
@error="imageformatError"/>
<div class="info">
<p class="title">Off-White™ x Nike Air Max 90 全新款「Desert Ore」配色曝光 Off-White™ x Nike Air Max 90 全新款「Desert Ore」配色曝光</p>
<p class="title">{{item.productName}}</p>
<div class="bottom">
<span class='icon-ufo'></span>
<span class="price">¥1299</span>
<span class="price">¥{{item.salesPrice}}</span>
<span class="icon-goumai"></span>
</div>
</div>
... ... @@ -23,22 +27,34 @@
<script>
export default {
name: 'horizontalSlide',
props: ['value'],
data() {
return {};
props: {
items: {
type: Array,
default() {
return [];
}
}
},
methods: {
imageformatError() {
console.log(6666);
},
goToDetail(item) {
this.$router.push({
name: 'ProductDetail',
params: {
productId: item.productSkn,
}
});
}
},
computed: {},
components: {}
mounted() {
}
};
</script>
<style lang="scss" scoped>
.horizontalSlide {
.horizontal-slide {
overflow: hidden;
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
... ... @@ -93,6 +109,7 @@ export default {
justify-content: space-between;
border: 1px solid #F0F0F0;
border-radius: 0 8px 8px 0;
width: 100%;
}
.price {
... ... @@ -107,4 +124,8 @@ export default {
float: right;
}
}
.one-item {
width: 700px;
}
</style>
... ...
... ... @@ -10,7 +10,7 @@
:page="listInfo.pageNo"
@loadmore="loadMore">
<template>
<div class="cell-item" v-for="(item, index) in articleList">
<div class="cell-item" v-for="(item, index) in articleList" :key="index">
<div class="item-image" :data-id="item.articleId" @click="gotoDetailPage">
<img :src="item.coverImage">
<!-- <img :src="item.resourceSrc" v-else-if="item.dataType === 2">-->
... ...
... ... @@ -4,10 +4,35 @@ export default function() {
return {
namespaced: true,
state: {
// sort: 1 笔记 4 视频
detailInfo: {}
},
actions: {
[FETCH_DETAIL_INFO]() {}
async fetchDetailInfo({commit}, {articleId}) {
const res = await this.$api.get('/api/grass/columnArticleDetail', {
articleId,
singleDetail: 'Y',
fromXianyu: 'Y'
});
if (res.code === 200) {
// 历史原因返回为List
commit('FETCH_DETAIL_INFO', res.data.detailList[0] || {});
}
}
},
mutations: {
// 获取详情
[FETCH_DETAIL_INFO](state, detailInfo) {
state.detailInfo = detailInfo;
}
},
getters: {
authorInfo({detailInfo}) {
const {authorHeadIco, authorName, authorType, authorUid} = detailInfo;
return {authorHeadIco, authorName, authorType, authorUid};
}
}
};
}
... ...
... ... @@ -21,6 +21,7 @@ module.exports = {
// 文章详情
'/api/grass/columnArticleDetail': {
api: 'app.grass.columnArticleDetail',
auth: true,
params: {
uid: {type: Number},
articleId: {type: Number, require: true},
... ...