Authored by 邱骏

Merge branch 'feature/2019/1230-community-article' of http://git.yoho.cn/fe/xian…

…yu-ufo-app-web into feature/2019/1230-community-article
... ... @@ -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 v-if="content" 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-border" @click="onPraise(detailInfo)"></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, mapActions } = createNamespacedHelpers('article/articleDetail');
export default {
name: 'articleDetail',
... ... @@ -49,33 +52,62 @@ 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');
if (!contentBlock) {
return '';
}
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() {
this.fetchRecommendArticle({articleId: this.articleId});
},
methods: {
...mapActions(['updatePraiseStatus', 'fetchRecommendArticle']),
async onPraise({articleId, hasPraise}) {
const user = await this.$sdk.getUser(true);
if (user && user.uid) {
this.updatePraiseStatus({
articleId,
status: hasPraise === 'Y' ? 1 : 0
});
} else {
this.$yoho.auth();
}
}
}
};
</script>
... ... @@ -153,9 +185,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">-->
... ...
<template>
<LayoutApp class="yohoufo-channel-page" :show-back="true" :hide-header="hideHeader" :title="title" :no-safe-area="true">
<div class="wrapper-search" :class="{'hidden': !isFixSearch}">
<div class="search-header" @click="goSearch">
<div class="search-img"></div>
<input class="search-input" type="search" disabled="true" :placeholder="defaultSearchWord || '搜索商品名称或货号'"/>
</div>
</div>
<div class="fixed-nav scroll-nav-wrap" :class="{'hidden': !isShow}" v-if="navList.length">
<ScrollNav :list="navList" :current="active" @transfer="getIndex"></ScrollNav>
</div>
... ... @@ -11,7 +18,7 @@
@pulling-up="onPullingUp" >
<div ref="body">
<div ref="topSource" class="channel-html">
<div class="search-header middle" @click="goSearch">
<div ref="searchSource" class="search-header middle" @click="goSearch">
<div class="search-img"></div>
<input class="search-input" type="search" disabled="true" :placeholder="defaultSearchWord || '搜索商品名称或货号'"/>
</div>
... ... @@ -22,8 +29,7 @@
<TwoBanner :list="item.data" :ref="index" :PAGE_URL="PAGE_URL" :key="index" v-if="item.template_name == 'twoPicture'"/>
</template>
</div>
<!-- <div ref="scrollNav" :class="['scroll-nav-wrap',isShow?'fixed':'']" v-if="navList.length"> -->
<div ref="scrollNav" class="scroll-nav-wrap" v-if="navList.length">
<div ref="scrollNav" class="scroll-nav-wrap" v-if="navList.length">
<ScrollNav :list="navList" :current="active" @transfer="getIndex"></ScrollNav>
</div>
<div class="list-wrap" :style="listStyle">
... ... @@ -46,7 +52,6 @@ import TwoBanner from './components/twoBanner';
import Hot from './components/hot';
import ScrollNav from './components/scrollNav';
import ProductList from '../../list/components/productList';
import { setTimeout } from 'timers';
import UfoNoItem from '../../../components/ufo-no-item';
const { mapState, mapActions } = createNamespacedHelpers('home/channel');
... ... @@ -70,6 +75,7 @@ export default {
navTop: 0,
navHeight: 0,
isShow: false,
isFixSearch: false,
total: 0,
active: 0,
yasHeight: 0,
... ... @@ -128,7 +134,6 @@ export default {
listStyle() {
return {
minHeight: this.total + 'px',
// marginTop: this.isShow? this.navHeight +'px' : 0
};
}
},
... ... @@ -149,9 +154,8 @@ export default {
this.fetchChannelList();
}
this.init();
// this.getAllInboxCatInfo();
this.fetchDefaultSearchWord()
this.fetchDefaultSearchWord();
this.PAGE_URL = window.location.href;
... ... @@ -172,7 +176,7 @@ export default {
// 初始化 选中类目
const [firstNav] = this.navList;
if(firstNav) {
if (firstNav) {
const {url = ''} = firstNav;
this.selectedCategory = queryString.parse(url);
... ... @@ -220,7 +224,6 @@ export default {
}
if (scrollY) { // 滚动时
if (scrollHeight + scrollY > eleTop && item.template_name === 'guessLike') {
// console.log('guessLike report')
item.data.forEach((val, i) => {
reportParams = {...val.reportParams, PAGE_URL: this.PAGE_URL};
this.homeYasParams.push(reportParams);
... ... @@ -228,7 +231,6 @@ export default {
}
if (scrollY < (eleTop + eleHeight) && item.template_name !== 'guessLike') {
// console.log(item.template_name)
let reportMoreParams = {};
item.data.forEach((val, i) => {
... ... @@ -264,24 +266,25 @@ export default {
// console.log(this.homeYasParams)
this.$store.dispatch('reportYas', {
params: {
param: {DATA:this.homeYasParams},
param: {DATA: this.homeYasParams},
appop: 'XY_UFO_SHOW_EVENT'
}
});
},
reportTabYas() {
let guessLikeTabParams = this.listYasParams;
delete guessLikeTabParams.I_INDEX;
// 防止重复上报
if(this.guessLikeId !== guessLikeTabParams.TAB_ID) {
this.guessLikeId = guessLikeTabParams.TAB_ID
console.log('guesslisttab',guessLikeTabParams)
if (this.guessLikeId !== guessLikeTabParams.TAB_ID) {
this.guessLikeId = guessLikeTabParams.TAB_ID;
this.$store.dispatch('reportYas', {
params: {
param: guessLikeTabParams,
appop: 'XY_UFO_MAIN_EVENT'
}
});
params: {
param: guessLikeTabParams,
appop: 'XY_UFO_MAIN_EVENT'
}
});
}
},
async guessLikeListParams(params) {
... ... @@ -301,8 +304,10 @@ export default {
this.active = Number(index);
this.isShow && this.$refs.scroll.scrollTo(this.navTop);
await this.guessLikeListParams({index, ...params});
// tab点击
this.reportTabYas()
this.reportTabYas();
// 商品列表曝光
this.productList.list.length > 0 && this.listScrollY > 0 && this.$refs.product && this.$refs.product.yasShowEvent(this.yasHeight);
},
... ... @@ -326,8 +331,10 @@ export default {
this.scrollY = -y;
if (this.navTop) {
this.isShow = (this.scrollY >= this.$refs.topSource.offsetHeight);
this.isShow = (this.scrollY >= this.$refs.topSource.offsetHeight - 32);
}
this.isFixSearch = (this.scrollY >= this.$refs.searchSource.offsetHeight);
},
async refreshProductList(index) {
let str = get(get(this.navList, `[${index}].url`, '').split('?'), '[1]', '');
... ... @@ -386,27 +393,25 @@ export default {
let {data} = result;
if (result.code === 200) {
data.endReached = (data.page === data.page_total) && (data.page_size !== 1) || !data.page_total;
}
if (typeof data === 'object' && Object.keys(data).length) {
for (let key in data) {
if (key === 'product_list') {
list.list = data.page > 1 ? list.list.concat(data.product_list) : data.product_list;
} else {
list[key] = data[key];
}
if (!data.product_list) {
this.productList.list = [];
if (data.hasOwnProperty(key)) {
if (key === 'product_list') {
list.list = data.page > 1 ? list.list.concat(data.product_list) : data.product_list;
} else {
list[key] = data[key];
}
if (!data.product_list) {
this.productList.list = [];
}
}
}
this.productList = list;
}
} catch (e) {
// console.log(e);
if (this.productList.page <= 1) {
this.productList.list = [];
}
... ... @@ -458,6 +463,7 @@ export default {
display: none;
}
}
.scroll-nav-wrap {
position: relative;
background-color: #fefefe;
... ... @@ -465,6 +471,7 @@ export default {
&.fixed-nav {
width: 100%;
position: absolute;
top: 64px;
z-index: 10;
overflow: hidden;
}
... ... @@ -487,16 +494,6 @@ export default {
.channel-html {
padding: 20px 24px 0;
// &:before {
// content: "";
// width: 520px;
// height: 28px;
// background-image: url("~statics/image/channel/service-info.png");
// background-size: 100% 100%;
// margin: 10px auto 20px;
// display: block;
// }
&:after {
content: "";
width: 100%;
... ... @@ -520,18 +517,29 @@ export default {
margin: 0 auto;
}
}
.yohoufo-channel-page {
/deep/ .cube-pullup-wrapper {
background: #f2f2f2;
}
}
/deep/ .cube-scroll-nav-bar-item_active {
&:after {
width: 16px;
// left: 50%;
transform: translateX(-10px)
}
}
.wrapper-search {
background: #fff;
padding: 0 24px;
width: 100%;
position: absolute;
z-index: 10;
overflow: hidden;
}
.search-header {
height: 64px;
background: #f5f5f5;
... ... @@ -561,6 +569,7 @@ input::-webkit-input-placeholder {
.middle {
margin: 0 10px;
}
.search-img {
width: 28px;
height: 28px;
... ... @@ -568,9 +577,11 @@ input::-webkit-input-placeholder {
background: url(~statics/image/list/searchPage_icon@3x.png) no-repeat;
background-size: cover;
}
.class-a {
padding-top: 104px;
}
.hidden {
visibility: hidden;
opacity: 0;
... ...
... ... @@ -1123,7 +1123,7 @@ export default {
&-item {
display: flex;
justify-content: space-between;
border-bottom: 1px solid #eee;
// border-bottom: 1px solid #eee;
font-size: 28px;
box-sizing: border-box;
}
... ... @@ -1137,7 +1137,7 @@ export default {
/* font-family: SFProText-Medium; */
color: #000;
letter-spacing: 0;
font-weight: bold;
// font-weight: bold;
}
}
... ...
export const FETCH_DETAIL_INFO = 'FETCH_DETAIL_INFO';
const FETCH_DETAIL_INFO = 'FETCH_DETAIL_INFO';
const UPDATE_ARTICLE_PRAISE_INFO = 'UPDATE_ARTICLE_PRAISE_INFO';
const FETCH_RECOMMEND_ARTICLES = 'FETCH_RECOMMEND_ARTICLES';
export default function() {
return {
namespaced: true,
state: {
detailInfo: {}
// sort: 1 笔记 4 视频
detailInfo: {},
recommendArticleList: []
},
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] || {});
}
},
async updatePraiseStatus({commit}, {articleId, status}) {
const res = await this.$api.get('/api/grass/updateArticlePraise', {
articleId,
status
});
if (res.code === 200) {
commit(UPDATE_ARTICLE_PRAISE_INFO);
}
},
async fetchRecommendArticle({commit}, {articleId}) {
const res = await this.$api.get('/api/grass/xianyuOtherArticles', {
articleId
});
if (res.code === 200) {
commit(FETCH_RECOMMEND_ARTICLES, res.data);
}
}
},
mutations: {
// 获取详情
[FETCH_DETAIL_INFO](state, detailInfo) {
state.detailInfo = detailInfo;
},
[UPDATE_ARTICLE_PRAISE_INFO](state) {
let {hasPraise, praiseCount} = state.detailInfo;
state.detailInfo = {
...state.detailInfo,
hasPraise: hasPraise === 'Y' ? 'N' : 'Y',
praiseCount: hasPraise === 'Y' ? praiseCount - 1 : praiseCount + 1
};
},
[FETCH_RECOMMEND_ARTICLES](state, list = []) {
state.recommendArticleList = list;
}
},
getters: {
authorInfo({detailInfo}) {
const {authorHeadIco, authorName, authorType, authorUid} = detailInfo;
return {authorHeadIco, authorName, authorType, authorUid};
}
}
};
}
... ...
... ... @@ -29,14 +29,18 @@ export default {
});
});
let [detail, resource, activity, recommend, vedioResource, limitInfo] = await Promise.all(queryTasks);
if(vedioResource) {
let [detail, resource, activity, recommend, videoResource, limitInfo] = await Promise.all(queryTasks);
if(videoResource) {
videoResource = {
src: videoResource.vedioImageUrl,
url: videoResource.vedioUrl
}
} else {
// 视频资源位
const videoResourceInfo = resource.find(r=> /(\.mp4)/.test(r.data[0].url));
videoResource = get(videoResourceInfo, 'data[0]', {});
}
// 视频资源位
const videoResourceInfo = resource.find(r=> /(\.mp4)/.test(r.data[0].url));
const videoResource = get(videoResourceInfo, 'data[0]', {});
resource = get(resource, '[0].data[0]', {});
... ...
... ... @@ -21,6 +21,7 @@ module.exports = {
// 文章详情
'/api/grass/columnArticleDetail': {
api: 'app.grass.columnArticleDetail',
auth: true,
params: {
uid: {type: Number},
articleId: {type: Number, require: true},
... ...