Authored by yyq

auther

<template>
<Layout class="author-page">
<LayoutHeader slot='header' theme="white">
<div ref="headerAuthor" class="header-author">
<div class="h-name flex">{{baseData.nickName}}</div>
<div class="h-more">
<div class="flex">
<WidgetAvatar v-if="baseData.headIco" class="h-headico" :src="baseData.headIco" :width="100" :height="100"></WidgetAvatar>
</div>
<div class="h-follow flex">
<WidgetFollow class="widget-follow" :author-uid="autherInfo.authorUid" :follow="isAttention" @on-follow="follow => onFollow(follow)"></WidgetFollow>
</div>
</div>
</div>
<template v-slot:opts>
<WidgetShare class="header-share"></WidgetShare>
</template>
</LayoutHeader>
<cube-sticky :pos="scrollY">
<cube-scroll
class="main-container"
:scroll-events="scrollEvents"
@scroll="scrollHandler">
<div ref="authorProfile" class="author-profile">
<span class="avatar-box">
<WidgetAvatar v-if="baseData.headIco" :src="baseData.headIco" :width="100" :height="100"></WidgetAvatar>
</span>
<div class="author-section">
<ul class="author-fans">
<li>
<span class="num">{{baseData.attCount}}</span>
<p class="name">
<span>关注</span>
</p>
</li>
<li>
<span class="num">{{baseData.fansCount}}</span>
<p class="name">
<span>粉丝</span>
</p>
</li>
<li>
<span class="num">{{baseData.praiseAndfavorite}}</span>
<p class="name">
<span>获赞与收藏</span>
</p>
</li>
</ul>
<div class="operate-wrap">
<label v-if="isOwner" class="operate-btn btn-user-edit">编辑个人资料</label>
<WidgetFollow v-else class="operate-btn" :author-uid="autherInfo.authorUid" :follow="isAttention" @on-follow="follow => onFollow(follow)"></WidgetFollow>
</div>
</div>
</div>
<p v-if="baseData.signature" class="author-desc">{{baseData.signature}}</p>
<cube-sticky-ele ele-key="11">
<FavTabBlock :tabs-num="tabsNum" :active-index="activeIndex" @change="changeTab"></FavTabBlock>
</cube-sticky-ele>
<div class="contant-list">
<WaterFall class="pannel-wrap" :list="list" :pos="scrollY"></WaterFall>
</div>
<div v-if="loadStatus" class="loading">
<Loading v-if="loadStatus === 1" class="load-icon" :size="20"></Loading>
<p v-else class="load-text">没有更多了</p>
</div>
</cube-scroll>
<template slot="fixed" slot-scope="props">
<FavTabBlock :tabs-num="tabsNum" :active-index="activeIndex" @change="changeTab"></FavTabBlock>
</template>
</cube-sticky>
</Layout>
</template>
<script>
import {assign, get} from 'lodash';
import {Scroll, Sticky, Loading} from 'cube-ui';
import CubeStickyEle from 'cube-ui/src/components/sticky/sticky-ele.vue';
import FavTabBlock from './components/fav-tab-block';
import WaterFall from './components/water-fall';
import {createNamespacedHelpers} from 'vuex';
const {mapActions} = createNamespacedHelpers('user');
export default {
name: 'userpage',
data() {
return {
autherInfo: {},
scrollEvents: ['scroll'],
scrollY: 0,
baseData: {},
isAttention: false,
isOwner: false,
tabsNum: [10, 0],
activeIndex: 0,
fetchInfo: {},
loadStatus: ''
}
},
created() {
this.autherInfo = {
authorUid: +this.$route.params.id,
authorType: this.$route.params.type
};
this.fetchBaseInfo();
this.fetchList();
},
mounted() {
let $dom = this.$refs.headerAuthor;
if ($dom.offsetHeight) {
this._animeDuration = 300;
import('animejs').then(({default: anime}) => {
this._animeEl = anime({
targets: $dom,
translateY: -$dom.offsetHeight,
easing: 'easeInOutSine',
duration: this._animeDuration,
autoplay: false
});
});
}
},
watch: {
scrollY(top) {
let animePlayed = false;
if (top > this.$refs.authorProfile.offsetHeight) {
animePlayed = true;
}
if (!this._animePlayed === !animePlayed) {
return;
}
let start;
let self = this;
function step(timestamp) {
if (!start) {
start = timestamp
};
let progress = Math.floor(timestamp - start);
self._animeEl.seek(animePlayed ? progress : self._animeDuration - progress);
if (progress < self._animeDuration) {
window.requestAnimationFrame(step);
}
};
window.requestAnimationFrame(step);
this._animePlayed = animePlayed;
}
},
computed: {
list() {
return get(this.fetchInfo, `${this.activeIndex}.list`) || [];
}
},
methods: {
...mapActions(['autherBaseInfo', 'autherAritcleNum', 'autherPubList', 'autherFavList']),
scrollHandler({ y }) {
this.scrollY = -y;
if (this.scrollY + 1000 > this.$el.offsetHeight) {
this._listTimer && clearTimeout(this._listTimer);
this._listTimer = setTimeout(() => {
this.fetchList();
}, 100);
}
},
changeTab(index) {
if (this.activeIndex !== index) {
this.activeIndex = index;
this.fetchList();
}
},
fetchBaseInfo() {
this.autherBaseInfo(this.autherInfo).then(res => {
if (res.code === 200) {
this.baseData = res.data;
this.isOwner = res.data.isOwner;
this.isAttention = res.data.isAttention === 'Y';
}
});
this.autherAritcleNum(this.autherInfo).then(res => {
this.tabsNum = [get(res, 'data.articleCount'), get(res, 'data.favoriteCount')];
});
},
async fetchList() {
this.fetchInfo = this.fetchInfo || [];
if (this.syncing) {
return;
}
let info = this.fetchInfo[this.activeIndex] || {};
let result;
info.page = info.page || 1;
if (info.page >= info.totalPage) {
return;
}
let syncServiceName;
if (this.activeIndex === 1) {
syncServiceName = 'autherFavList';
} else {
syncServiceName = 'autherPubList';
}
if (this[syncServiceName]) {
this.syncing = true;
result = await this[syncServiceName](assign({
page: info.page,
lastedTime: info.lastedTime || ''
}, this.autherInfo));
this.syncing = false;
}
if (result.code === 200) {
info.list = (info.list || []).concat(result.data.list);
info.page++;
info.totalPage = 10 || result.data.totalPage;
info.lastedTime = result.data.lastedTime;
}
if (info.page > info.totalPage) {
this.loadStatus = 2;
} else {
this.loadStatus = 1;
}
this.fetchInfo[this.activeIndex] = info;
this.fetchInfo = {...this.fetchInfo};
},
onFollow(follow) {
this.isAttention = follow;
}
},
components: {
CubeScroll: Scroll,
CubeSticky: Sticky,
CubeStickyEle,
Loading,
FavTabBlock,
WaterFall
}
};
</script>
<style lang="scss">
.author-page {
box-sizing: border-box;
color: #4a4a4a;
}
.header-author {
width: 100%;
height: 100%;
position: relative;
.flex {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.h-name {
font-size: 36px;
font-weight: 500;
}
.h-more {
width: 100%;
height: 100%;
position: absolute;
top: 100%;
left: 0;
}
.h-headico {
width: 60px;
height: 60px;
}
.h-follow {
position: absolute;
top: 0;
right: -50px;
}
}
.header-share {
margin-right: 26px;
color: #222;
font-weight: bold;
}
.author-profile {
padding: 24px 30px;
display: flex;
justify-content: space-between;
.avatar-box {
width: 150px;
height: 150px;
overflow: hidden;
border-radius: 50%;
> img {
width: 100%;
height: 100%;
display: block;
}
}
}
.author-section {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.author-fans {
display: flex;
justify-content: flex-end;
padding-top: 4px;
padding-right: 54px;
li {
margin-left: 140px;
position: relative;
&:first-child {
margin-left: 0;
}
.num {
font-size: 28px;
font-weight: 500;
padding-bottom: 6px;
display: block;
}
.name {
position: absolute;
word-break: keep-all;
font-size: 20px;
font-weight: 300;
color: #9b9b9b;
margin-left: 50%;
> * {
position: relative;
left: -50%;
}
}
}
}
.operate-wrap {
text-align: right;
.operate-btn {
width: calc(100% - 20px);
font-size: 23px;
line-height: 48px;
display: inline-block;
}
.btn-user-edit {
color: #222;
border: 1px solid #4a4a4a;
border-radius: 8px;
text-align: center;
}
}
.author-desc {
margin: 20px 30px;
font-size: 24px;
font-weight: 300;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.loading {
padding: 20px 0;
.load-icon > span {
margin: auto;
}
.load-text {
text-align: center;
}
}
</style>
... ...
... ... @@ -6,7 +6,6 @@
<span v-if="item.num" class="t-num">({{item.num}})</span>
</li>
</ul>
<label class="draft-box">草稿箱</label>
</div>
</template>
... ... @@ -42,7 +41,9 @@ export default {
this.$emit('change', index);
},
computetabsNum() {
computetabsNum(a) {
let tabList = this.tabList;
for (let i = this.tabList.length - 1; i >= 0; i--) {
let num = '';
... ... @@ -50,8 +51,10 @@ export default {
num = this.tabsNum[i];
}
this.tabList[i].num = num;
tabList[i].num = num;
}
this.tabList = [...tabList];
},
computeCurrentTab() {
this.changeType(this.activeIndex || 0);
... ... @@ -107,15 +110,5 @@ export default {
margin-left: -8px;
}
}
.draft-box {
font-size: 24px;
font-weight: 300;
line-height: 40px;
border-radius: 20px;
color: #b0b0b0;
background-color: #f0f0f0;
padding: 0 22px;
}
}
</style>
... ...
<template>
<div class="wf-list">
<div v-for="(i, index) in viewList" :key="index"
<div class="wf-list" :style="{'height': listHeight + 'px'}">
<div
v-for="i in viewList"
:key="`${i._temporary ? '_' : ''}${i.articleId}`"
class="wf-item"
:class="{'wf-item-default': i._default, 'wf-item-temp': i._temporary}"
:style="{width: 100 / cols + '%', left: i.left + '%', top: i.top + 'px'}">
:style="`width: ${100 / cols}%;transform: translate(${i.left}px, ${i.top}px)`">
<div class="wf-item-mid">
<router-link to="/ufo/order/1">
<div class="layer-image" :style="{height: i.coverHeight + 'px'}">
<img v-if="!i._temporary" :src="i[srcKey]" :alt="i.content">
<router-link :to="'/article/' + i.articleId">
<div class="layer-image" :style="{'height': i.coverHeight + 'px'}">
<ImageFormat v-if="!i._temporary" :src="i[srcKey]" :width="coverImageWidth" :height="i.coverHeight"></ImageFormat>
</div>
<div class="description">{{i.content}}</div>
</router-link>
<div class="attribution">
<router-link to="/ufo/order/1" class="auther">
<router-link :to="'/article/' + i.articleId" class="auther">
<span class="avatar">
<img v-if="!i._temporary" :src="i.authorHeadIco">
<WidgetAvatar v-if="!i._temporary" :src="i.authorHeadIco" :width="70" :height="70"></WidgetAvatar>
</span>
<span class="name">{{i.authorName}}</span>
</router-link>
<div class="fav">21321</div>
<div class="fav">
<WidgetFav :articleId="i.articleId" :num="i.praiseCount" :option="favOption"></WidgetFav>
</div>
</div>
</div>
</div>
... ... @@ -36,9 +40,15 @@ export default {
viewList: [{
_default: true
}],
coverImageWidth: 0,
colWidthPer: 0,
calcIndex: 0,
loadedIndex: 0
loadedIndex: 0,
colsHeight: [],
favOption: {
iconFontSize: 26,
textAlign: 'normal'
}
}
},
props: {
... ... @@ -80,7 +90,7 @@ export default {
this.timer = setTimeout(this.resetViewList, 0);
},
list(newList, oldList) {
if (this.viewList.length > newList.length) {
if (oldList.length > newList.length) {
this.reset();
}
... ... @@ -88,10 +98,12 @@ export default {
}
},
computed: {
// isViewItem() {
// console.log(arguments);
// return true;
// },
listHeight() {
return Math.max.apply(null, this.colsHeight);
},
colWidth() {
return this.$el.offsetWidth / this.cols;
}
},
methods: {
clacCoverSize() {
... ... @@ -100,9 +112,8 @@ export default {
for (let i = this.calcIndex; i < this.list.length; i++) {
let item = this.list[i];
item.coverHeight = item.imageHeight / item.imageWidth * this.coverImageWidth;
console.log(item.imageHeight / item.imageWidth * this.coverImageWidth);
console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
item.coverHeight = Math.floor(item.imageHeight / item.imageWidth * this.coverImageWidth);
nlist.push(assign({_temporary: true}, item));
};
... ... @@ -130,7 +141,7 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
}
let height = $elem.offsetHeight;
let top, left;
let top, left, leftPer;
let item = this.list[i];
item.height = height;
... ... @@ -138,19 +149,23 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
if (i < this.cols) {
this.colsHeight[i] = height;
top = 0
left = i * this.colWidthPer
left = i * this.colWidth;
leftPer = i * this.colWidthPer;
} else {
let minHeight = Math.min.apply(null, this.colsHeight);
let minIndex = this.colsHeight.indexOf(minHeight);
top = minHeight;
left = minIndex * this.colWidthPer;
left = minIndex * this.colWidth;
leftPer = minIndex * this.colWidthPer;
this.colsHeight[minIndex] = minHeight + height;
}
item.left = left;
item.leftPer = leftPer;
item.top = top;
item.bottom = top + height;
}
this.loadedIndex = this.list.length;
... ... @@ -182,6 +197,10 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
this.loadedIndex = 0;
this.calcIndex = 0;
this.viewList = [];
this.lastPos = 0;
this.viewIndex = 0;
this.setImgWidth();
},
resetViewList() {
... ... @@ -194,10 +213,10 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
let indexArr = [];
if (this.pos < this.lastPos) {
i = this.viewIndex['end'] || 0;
i = Math.min.apply(null, [(this.viewIndex['end'] || 0), this.list.length - 1]);
step = -1;
} else {
i = this.viewIndex['start'] || 0;
i = Math.max.apply(null, [(this.viewIndex['start'] || 0), 0]);
step = 1;
}
... ... @@ -205,20 +224,26 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
while (loop)
{
let item = this.list[i];
if (!item) {
if (i < 0 || i >= this.list.length) {
loop = false;
continue;
}
if (item.top > endPos - this.offsetTop) {
(step > 0) && (loop = false);
} else if (item.top < startPos - this.offsetTop) {
(step < 0) && (loop = false);
} else {
indexArr.push(i);
list.push(item);
let item = this.list[i];
if (item) {
if (item.top > endPos - this.offsetTop) {
if (step > 0) {
loop = false;
}
} else if (item.bottom < startPos - this.offsetTop) {
if (step < 0) {
loop = false;
}
} else {
indexArr.push(i);
list.push(item);
}
}
i += step;
... ... @@ -228,7 +253,9 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
return;
}
indexArr = indexArr.sort();
indexArr = indexArr.sort(function(a, b) {
return a - b;
});
let viewIndex = {
start: indexArr[0],
... ... @@ -253,7 +280,6 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
}
.wf-item {
width: 50%;
padding: 10px;
font-size: 24px;
overflow: hidden;
... ... @@ -305,6 +331,10 @@ console.log(item.imageHeight, item.imageWidth, this.coverImageWidth);
display: inline-block;
vertical-align: middle;
}
.fav {
line-height: 60px;
}
}
.wf-item-default,
... ...
export default [{
path: '/userpage',
name: 'userpage',
component: () => import(/* webpackChunkName: "userpage" */ './userpage')
path: '/author/:type/:id',
name: 'author',
component: () => import(/* webpackChunkName: "author" */ './author')
}];
... ...
<template>
<Layout class="user-page">
<LayoutHeader slot='header'></LayoutHeader>
<cube-sticky :pos="scrollY">
<cube-scroll
class="main-container"
:scroll-events="scrollEvents"
@scroll="scrollHandler">
<div class="author-profile">
<span class="avatar-box">
<img src="https://tvax3.sinaimg.cn/crop.0.0.996.996.180/b2226be7ly8fwi9zpop35j20ro0roq45.jpg">
</span>
<div class="author-section">
<ul class="author-fans">
<li>
<span class="num">56</span>
<p class="name">
<span>关注</span>
</p>
</li>
<li>
<span class="num">56</span>
<p class="name">
<span>粉丝</span>
</p>
</li>
<li>
<span class="num">56</span>
<p class="name">
<span>获赞与收藏</span>
</p>
</li>
</ul>
<div class="operate-wrap">
<label class="fill">编辑个人资料</label>
</div>
</div>
</div>
<p class="author-desc">炒价70倍,这个潮流艺术品的升值空间比KAWS还大!</p>
<cube-sticky-ele ele-key="11">
<FavTabBlock :tabs-num="tabsNum" :active-index="activeIndex" @change="changeTab"></FavTabBlock>
</cube-sticky-ele>
<div class="contant-list">
<WaterFall class="pannel-wrap" :list="list" :pos="scrollY"></WaterFall>
</div>
</cube-scroll>
<template slot="fixed" slot-scope="props">
<FavTabBlock :tabs-num="tabsNum" :active-index="activeIndex" @change="changeTab"></FavTabBlock>
</template>
</cube-sticky>
</Layout>
</template>
<script>
import {Scroll, Sticky} from 'cube-ui';
import CubeStickyEle from 'cube-ui/src/components/sticky/sticky-ele.vue';
import FavTabBlock from './components/fav-tab-block';
import WaterFall from './components/water-fall';
export default {
name: 'userpage',
data() {
return {
scrollEvents: ['scroll'],
scrollY: 0,
tabsNum: [10, 0],
activeIndex: 0,
list: []
}
},
created() {
let list = [];
for (let i = 0; i < 1000; i++) {
let _h = Math.floor(Math.random() * 888);
list.push({
articleId: i,
articleType: 1,
coverImage: '//fakeimg.pl/320x' + _h,
content: '海报' + i,
authorName: '干脆面' + i,
authorHeadIco: '//img.xiaohongshu.com/avatar/5c5c0790d9e25f0001905596.jpg@80w_80h_90q_1e_1c_1x.jpg',
imageHeight: _h,
imageWidth: 320
});
}
this.list = list;
},
methods: {
scrollHandler({ y }) {
this.scrollY = -y;
},
changeTab(index) {
if (this.activeIndex !== index) {
this.activeIndex = index;
}
}
},
components: {
CubeScroll: Scroll,
CubeSticky: Sticky,
CubeStickyEle,
FavTabBlock,
WaterFall
}
};
</script>
<style lang="scss">
.user-page {
box-sizing: border-box;
color: #4a4a4a;
}
.author-profile {
padding: 24px 30px;
display: flex;
justify-content: space-between;
.avatar-box {
width: 150px;
height: 150px;
overflow: hidden;
border-radius: 50%;
> img {
width: 100%;
height: 100%;
display: block;
}
}
}
.author-section {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.author-fans {
display: flex;
justify-content: flex-end;
padding-top: 4px;
padding-right: 54px;
li {
margin-left: 140px;
position: relative;
&:first-child {
margin-left: 0;
}
.num {
font-size: 28px;
font-weight: 500;
padding-bottom: 6px;
display: block;
}
.name {
position: absolute;
word-break: keep-all;
font-size: 20px;
font-weight: 300;
color: #9b9b9b;
margin-left: 50%;
> * {
position: relative;
left: -50%;
}
}
}
}
.operate-wrap {
label {
display: block;
margin-left: 30px;
font-size: 23px;
line-height: 48px;
color: #222;
border: 1px solid #4a4a4a;
border-radius: 8px;
text-align: center;
}
.fill {
background-color: #4a4a4a;
color: #fff;
font-weight: 300;
}
}
.author-desc {
margin: 20px 30px;
font-size: 24px;
font-weight: 300;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.contant-list {
height: 100000px;
}
</style>
... ... @@ -41,4 +41,40 @@ export default {
return result;
},
async autherBaseInfo(actions, {authorUid, authorType}) {
const result = await this.$api.get('/api/grass/getGrassUserBaseInfo', {
authorUid,
authorType
});
return result;
},
async autherAritcleNum(actions, {authorUid, authorType}) {
const result = await this.$api.get('/api/grass/getGrassPubAndFavorNum', {
authorUid,
authorType
});
return result;
},
async autherPubList(actions, {authorUid, authorType, page, lastedTime}) {
const result = await this.$api.get('/api/grass/userPublishedArticleList', {
authorUid,
authorType,
page,
lastedTime
});
return result;
},
async autherFavList(actions, {authorUid, authorType, page, lastedTime}) {
const result = await this.$api.get('/api/grass/userFavouriteArticleList', {
authorUid,
authorType,
page,
lastedTime
});
return result;
},
};
... ...
... ... @@ -2,7 +2,10 @@ const URI_PACKAGE_ARTICLE = 'guang/service/v2/article/';
const URI_PACKAGE_AUTHOR = 'guang/service/v1/author/';
const URI_PACKAGE_PRAISE = 'guang/api/v1/article/';
const userPageApis = require('./api-map/userpage');
module.exports = {
...userPageApis,
'/api/grass/labelRealtedArticleDetail': {
api: 'app.grass.labelRealtedArticleDetail',
cache: true,
... ...
module.exports = {
'/api/grass/getGrassUserBaseInfo': {
api: 'app.grass.getGrassUserBaseInfo',
cache: true,
params: {
authorUid: {type: Number, require: true},
authorType: {type: Number, require: true}
}
},
'/api/grass/getGrassPubAndFavorNum': {
api: 'app.grass.getGrassPubAndFavorNum',
cache: true,
params: {
authorUid: {type: Number, require: true},
authorType: {type: Number, require: true}
}
},
'/api/grass/userPublishedArticleList': {
api: 'app.grass.userPublishedArticleList',
cache: true,
params: {
authorUid: {type: Number, require: true},
authorType: {type: Number, require: true},
page: {type: Number},
lastedTime: {type: Number}
}
},
'/api/grass/userFavouriteArticleList': {
api: 'app.grass.userFavouriteArticleList',
cache: true,
params: {
authorUid: {type: Number, require: true},
authorType: {type: Number, require: true},
page: {type: Number},
lastedTime: {type: Number}
}
},
}
... ...