<template> <Layout class="author-page"> <LayoutHeader slot='header' theme="white" class="author-page-header"> <div ref="headerAuthor" class="header-author"> <div class="h-name flex"> <span class="h-name-b">{{authorBaseData.nickName}}</span> <span v-if="authorBaseData.sex" class="iconfont icon-women author-sex-icon" :class="`icon-${authorBaseData.sex}`"></span> </div> <div class="h-more"> <div class="flex"> <WidgetAvatar class="h-headico" :src="authorBaseData.headIco" :width="100" :height="100"></WidgetAvatar> </div> <div v-if="!isOwner" class="h-follow flex"> <WidgetFollow class="widget-follow" :author-uid="autherInfo.authorUid" :follow="authorBaseData.hasAttention === 'Y'" @on-follow="follow => onFollow(follow)" :pos-id="sceneId"></WidgetFollow> </div> </div> </div> </LayoutHeader> <div class="fixed-tab"> <FavTabBlock v-if="tabFixed" :tabs-num="tabsNum" :active-index="activeIndex" @change="changeTab"></FavTabBlock> </div> <cube-scroll class="main-container" ref="scroll" :options="scrollOpts" :scroll-events="['scroll', 'scroll-end', 'before-scroll-start']" @scroll="onScrollHandle" @scroll-end="onScrollEndHandle" @before-scroll-start="beforeScrollStartHandle"> <div ref="authorProfile" class="author-profile"> <span class="avatar-box"> <WidgetAvatar :src="authorBaseData.headIco" :width="100" :height="100"></WidgetAvatar> </span> <div class="author-section"> <ul class="author-fans"> <li v-for="(item, key) in fansList" :key="key"> <span class="num">{{authorBaseData[key] || 0}}</span> <p class="name"><span>{{item}}</span></p> </li> </ul> <div class="operate-wrap"> <a v-if="isOwner" class="operate-btn btn-user-edit" :href="mineInfoUrl">编辑个人资料</a> <WidgetFollow v-if="!isOwner && authorBaseData.hasAttention" class="operate-btn" :author-uid="autherInfo.authorUid" :follow="authorBaseData.hasAttention === 'Y'" @on-follow="follow => onFollow(follow)" :pos-id="sceneId"></WidgetFollow> </div> </div> </div> <p v-if="authorBaseData.signature" class="author-desc">{{authorBaseData.signature}}</p> <div ref="tabBlock"> <FavTabBlock :tabs-num="tabsNum" :active-index="activeIndex" @change="changeTab"></FavTabBlock> </div> <div ref="contantList" class="contant-list" :style="`min-height: ${listMinHeight}px;`"> <p v-if="emptyTip" class="empty-tip">{{emptyTip}}</p> <WaterFall class="pannel-wrap" :list="list" :params="params" :tab="activeIndex"></WaterFall> <div v-if="loadStatus && !emptyTip" class="loading"> <Loading v-if="loadStatus === 1" class="load-icon" :size="20"></Loading> <p v-else class="load-text">没有更多了</p> </div> </div> </cube-scroll> <a v-if="isOwner" class="publish hover-opacity" :class="{'scroll-opacity': scrolling}" :href="publishUrl"></a> </Layout> </template> <script> import {assign, get} from 'lodash'; import {Scroll, Loading} from 'cube-ui'; import FavTabBlock from './components/fav-tab-block'; import WaterFall from './components/scroll-reveal'; import YAS from 'utils/yas-constants'; import {createNamespacedHelpers} from 'vuex'; const {mapState, mapActions, mapMutations} = createNamespacedHelpers('user'); export default { name: 'userpage', data() { return { scrollY: 0, scrolling: false, mineInfoUrl: '//m.yohobuy.com/home/mydetails?openby:yohobuy={"action":"go.mineinfo"}', publishUrl: '?openby:yohobuy={"action":"go.grasspublish"}', autherInfo: {}, fansList: { attCount: '关注', fansCount: '粉丝', praiseAndfavorite: '获赞与收藏' }, tabFixed: false, tabsNum: [0, 0], activeIndex: 0, listMinHeight: 0, fetchInfo: {}, loadStatus: '', emptyTip: '', scrollOpts: { bounce: false, }, sceneId: YAS.scene.author }; }, created() { this.CHANGE_AUTHOR_OWNER_STATUS(!this.$route.params.id); }, mounted() { let $dom = this.$refs.headerAuthor; this.init(this.$route.params); this.listMinHeight = this.$el.offsetHeight - this.$refs.tabBlock.offsetHeight - $dom.offsetHeight; 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 }); }); } }, activated() { if (this.$route.params.id && this.$route.params.id !== this.autherInfo.authorUid) { this.init(this.$route.params); } }, beforeRouteUpdate(to, from, next) { if (this.$route.params.id !== to.params.authorUid) { this.init(to.params); } next(); }, computed: { ...mapState(['authorBaseData', 'isOwner']), list() { return get(this.fetchInfo, `${this.activeIndex}.list`) || []; }, params() { return { params: { type: this.waterFallType, authorType: this.autherInfo.authorType, authorUid: this.autherInfo.authorUid, }, query: { userName: this.authorBaseData.nickName } }; }, waterFallType() { return ['publish', 'fav'][this.activeIndex]; } }, methods: { ...mapMutations(['CHANGE_AUTHOR_OWNER_STATUS', 'CHANGE_AUTHOR_ATTENTION_STATUS']), ...mapActions(['autherBaseInfo', 'autherAritcleNum', 'autherPubList', 'autherFavList', 'autherMineBaseInfo', 'autherMineAritcleNum', 'autherMinePubList', 'autherMineFavList']), init(params) { params = params || {}; this._apiNamePre = 'auther'; this.CHANGE_AUTHOR_OWNER_STATUS(!this.$route.params.id); this.tabFixed = false; this.tabsNum = [0, 0]; this.activeIndex = 0; this.fetchInfo = {}; this.loadStatus = ''; this.emptyTip = ''; if (this.scrollY > 0) { if (this._animeEl) { this._animePlayed = false; this._animeEl.seek(0); } this.$refs.scroll.scrollTo(0, 0); } if (!params.id) { this._apiNamePre += 'Mine'; this.checkLogin(uid => { this.fetchData(uid, 1); }); } else { this.fetchData(params.id, params.type || 1); } }, fetchData(authorUid, authorType, noBase) { this.autherInfo = {authorUid, authorType}; this.fetchBaseInfo(); this.fetchList(true); }, checkLogin(cb) { try { this.$sdk.getUser().then(res => { if (get(res, 'uid') > 0) { return cb && cb(res.uid); } else { this.$sdk.goLogin(); } }); } catch (e) { console.log(e); } }, onScrollHandle({y}) { this.scrollY = -y; let animePlayed = this.scrollY > this.$refs.authorProfile.offsetHeight; if (!this._animePlayed === !animePlayed) { return; } this.tabFixed = animePlayed; 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; }, onScrollEndHandle(scroll) { this.scrolling = false; if (1000 - scroll.y > this.$refs.contantList.offsetHeight) { this._listTimer && clearTimeout(this._listTimer); this._listTimer = setTimeout(() => { this.fetchList(); }, 50); } }, beforeScrollStartHandle() { this.scrolling = true; }, changeTab(index) { if (this.activeIndex !== index) { this.activeIndex = index; this.loadStatus = ''; this.emptyTip = ''; let info = this.fetchInfo[index] || {}; info.page = 1; info.lastedTime = ''; this.fetchList(); let top = this.$refs.authorProfile.offsetHeight; if (this.scrollY > top) { this.$nextTick(() => { this.$refs.scroll.scrollTo(0, -top - 1, 0); }); } } }, fetchBaseInfo() { this[this._apiNamePre + 'BaseInfo'](this.autherInfo); this[this._apiNamePre + 'AritcleNum'](this.autherInfo).then(res => { this.tabsNum = [get(res, 'data.articleCount'), get(res, 'data.favoriteCount')]; }); }, async fetchList(wait) { 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) { this.loadStatus = 2; return; } this.loadStatus = 1; let syncServiceName; if (this.activeIndex === 1) { syncServiceName = this._apiNamePre + 'FavList'; } else { syncServiceName = this._apiNamePre + 'PubList'; } if (this[syncServiceName]) { this.syncing = true; result = await this[syncServiceName](assign({ limit: 20, page: info.page, lastedTime: info.lastedTime || '' }, this.autherInfo)); this.syncing = false; } if (result && result.code === 200) { if (info.page === 1) { info.list = []; } info.list = (info.list || []).concat(result.data.list || []); info.page++; info.totalPage = result.data.totalPage || 1; info.lastedTime = result.data.lastedTime; this.setEmptyTip(info.list, result.data.totalCount); if (info.list.length) { info.list[0]._type = `${info.list[0].articleId}_${this.activeIndex}`; } } setTimeout(() => { if (info.page > info.totalPage) { this.loadStatus = 2; } this.fetchInfo[this.activeIndex] = info; this.fetchInfo = {...this.fetchInfo}; setTimeout(() => { this.$refs.scroll.refresh(); }, 100); }, wait ? 300 : 0); }, setEmptyTip(list, totalCount) { let tip = ''; if (!list.length && !totalCount) { switch (this.activeIndex) { case 0: tip = this.isOwner ? '发布你的第一篇潮人态度' : 'TA还没有分享过哦'; break; case 1: tip = this.isOwner ? '快去收藏你的第一篇内容吧' : 'TA还没有收藏内容'; break; default: break; } } this.emptyTip = tip; }, onFollow(follow) { this.CHANGE_AUTHOR_ATTENTION_STATUS(follow); } }, components: { CubeScroll: Scroll, Loading, FavTabBlock, WaterFall } }; </script> <style lang="scss"> .author-page { box-sizing: border-box; color: #4a4a4a; } .author-page-header > .title { overflow: visible!important; } .fixed-tab { width: 100%; position: absolute; z-index: 2; } .header-author { position: absolute; left: 0; right: 0; top: 0; bottom: 0; .flex { height: 100%; display: flex; align-items: center; justify-content: center; } .h-name { font-size: 36px; font-weight: 500; .h-name-b { max-width: 60%; line-height: 1.4; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: inline-block; } .author-sex-icon { font-size: 28px; padding-left: 10px; padding-right: 0; } .icon-man { color: #96d8f3; } .icon-women { color: #f7c3e1; } } .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: -130px; } } .header-share { margin-right: 26px; color: #222; font-weight: bold; } .author-profile { padding: 24px 30px; display: flex; justify-content: space-between; overflow: hidden; .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 { min-width: 30px; font-size: 28px; font-weight: 500; padding-bottom: 6px; display: block; text-align: center; } .name { position: absolute; font-size: 20px; font-weight: 300; color: #9b9b9b; margin-left: 50%; word-break: keep-all; white-space: nowrap; > * { position: relative; left: -50%; } } } } .operate-wrap { text-align: right; .operate-btn { width: calc(100% - 20px); height: 50px; font-size: 23px; line-height: 50px; display: inline-block; box-sizing: border-box; } .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; } .contant-list { position: relative; } .empty-tip { width: 100%; display: flex; justify-content: center; align-items: center; font-size: 28px; color: #ddd; position: absolute; top: 0; bottom: 240px; } .loading { padding: 20px 0; .load-icon > span { margin: auto; } .load-text { text-align: center; } } .publish { width: 100px; height: 100px; position: absolute; bottom: 52px; right: 40px; background-image: url('../../statics/image/userpage/publish.png'); background-size: 100% 100%; z-index: 10; transition: all 600ms ease-in-out; &.scroll-opacity { opacity: 0.3; } } </style>