home.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <template>
  2. <view class="app-page">
  3. <tm-menubars iconColor="white" color="white" :flat="true" :showback="false">
  4. <image slot="left" class="logo ml-24 round-24" :src="bloggerInfo.avatar" mode="scaleToFill"
  5. @click="$tm.toast('以后会放一个彩蛋~')"></image>
  6. <view class="search-input round-12 pt-12 pb-12 flex pl-24" @click="fnToSearch">
  7. <text class="search-input_icon iconfont text-size-m icon-search text-grey"></text>
  8. <view class="search-input_text pl-12 text-size-m text-grey">搜索文章...</view>
  9. </view>
  10. <!-- #ifdef APP-PLUS || H5 -->
  11. <view slot="right" class="mr-24 text-size-m text-grey">uni-halo</view>
  12. <!-- #endif -->
  13. </tm-menubars>
  14. <view v-if="loading != 'success'" class="loading-wrap">
  15. <tm-skeleton model="card"></tm-skeleton>
  16. <tm-skeleton model="cardActions"></tm-skeleton>
  17. <tm-skeleton model="list"></tm-skeleton>
  18. <tm-skeleton model="listAvatr"></tm-skeleton>
  19. <tm-skeleton model="listAvatr"></tm-skeleton>
  20. <tm-skeleton model="listAvatr"></tm-skeleton>
  21. </view>
  22. <block v-else>
  23. <view class="bg-white pb-24">
  24. <!-- 轮播图+广告 -->
  25. <view class="banner bg-white ml-24 mr-24 mt-12 round-3" v-if="bannerList.length != 0">
  26. <e-swiper :dotPosition="globalAppSettings.banner.dotPosition" :autoplay="true"
  27. :useDot="globalAppSettings.banner.useDot" :list="bannerList"
  28. @on-click="fnOnBannerClick"></e-swiper>
  29. </view>
  30. </view>
  31. <!-- 精品分类 -->
  32. <view class="flex flex-between mt-16 mb-24 pl-24 pr-24">
  33. <view class="page-item_title text-weight-b ">精品分类</view>
  34. <view class="show-more flex flex-center bg-white round-3" @click="fnToCategoryPage">
  35. <text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
  36. </view>
  37. <view v-if="false" class="flex flex-center text-size-s text-grey-darken-1" @click="fnToCategoryPage">
  38. <text class=" text-size-m">查看更多</text>
  39. <text class="iconfont icon-angle-right text-size-s "></text>
  40. </view>
  41. </view>
  42. <scroll-view class="category" scroll-x="true">
  43. <view v-if="categoryList.length == 0" class="cate-empty round-3 mr-5 flex flex-center text-grey">
  44. 还没有任何文章分类~</view>
  45. <block v-else>
  46. <view class="content" v-for="(category, index) in categoryList" :key="category.metadata.name"
  47. @click="fnToCategoryBy(category)">
  48. <category-mini-card :category="category"></category-mini-card>
  49. </view>
  50. </block>
  51. </scroll-view>
  52. <!-- 最新文章 -->
  53. <view class="flex flex-between mt-24 mb-24 pl-24 pr-24">
  54. <view class="page-item_title text-weight-b">文章列表</view>
  55. <view class="show-more flex flex-center bg-white round-3" @click="fnToArticlesPage">
  56. <text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
  57. </view>
  58. <view v-if="false" class="flex flex-center text-size-s text-grey-darken-1" @click="fnToArticlesPage">
  59. <text class=" text-size-m ">查看更多</text>
  60. <text class="iconfont icon-angle-right text-size-s "></text>
  61. </view>
  62. </view>
  63. <view v-if="articleList.length == 0" class="article-empty"><tm-empty icon="icon-shiliangzhinengduixiang-"
  64. label="博主还没有发表任何文章~"></tm-empty></view>
  65. <block v-else>
  66. <view :class="globalAppSettings.layout.home">
  67. <block v-for="(article, index) in articleList" :key="article.spec.slug">
  68. <tm-translate class="ani-item" animation-name="fadeUp" :wait="calcAniWait(index)">
  69. <article-card from="home" :article="article" @on-click="fnToArticleDetail"></article-card>
  70. <!-- 广告区域 -->
  71. <view v-if="haloAdConfig.home.use && (index + 1) % haloAdConfig.frequency == 0"
  72. class="ad-wrap ma-24">
  73. <!-- #ifdef MP-WEIXIN -->
  74. <ad v-if="haloAdConfig.unitId" :unit-id="haloAdConfig.unitId"></ad>
  75. <!-- #endif -->
  76. <!-- #ifndef MP-WEIXIN -->
  77. <ad v-if="haloAdConfig.adpid" :adpid="haloAdConfig.adpid"></ad>
  78. <!-- #endif -->
  79. </view>
  80. </tm-translate>
  81. </block>
  82. </view>
  83. <view class="load-text mt-12">{{ loadMoreText }}</view>
  84. <tm-flotbutton v-if="articleList.length > 10" color="light-blue" @click="fnToTopPage" size="m"
  85. icon="icon-angle-up"></tm-flotbutton>
  86. </block>
  87. </block>
  88. <!-- <tm-flotbutton @click="fnChangeMode" size="m" color="light-blue" :icon="$tm.vx.state().tmVuetify.black ? 'icon-lightbulb' : 'icon-lightbulb-fill'"></tm-flotbutton> -->
  89. </view>
  90. </template>
  91. <script>
  92. import tmMenubars from '@/tm-vuetify/components/tm-menubars/tm-menubars.vue';
  93. import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
  94. import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
  95. import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
  96. import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
  97. import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
  98. import eSwiper from '@/components/e-swiper/e-swiper.vue';
  99. export default {
  100. components: {
  101. tmMenubars,
  102. tmSkeleton,
  103. tmTranslate,
  104. tmFlotbutton,
  105. tmIcons,
  106. tmEmpty,
  107. eSwiper
  108. },
  109. data() {
  110. return {
  111. loading: 'loading',
  112. queryParams: {
  113. size: 5,
  114. page: 1
  115. },
  116. result: {},
  117. isLoadMore: false,
  118. loadMoreText: '加载中...',
  119. bannerCurrent: 0,
  120. bannerList: [],
  121. noticeList: [],
  122. articleList: [],
  123. categoryList: [],
  124. };
  125. },
  126. computed: {
  127. bloggerInfo() {
  128. let blogger = this.$tm.vx.getters().getBlogger;
  129. blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
  130. return blogger;
  131. },
  132. },
  133. onLoad() {
  134. this.fnSetPageTitle();
  135. },
  136. created() {
  137. this.fnQuery();
  138. },
  139. onPullDownRefresh() {
  140. this.isLoadMore = false;
  141. this.queryParams.page = 1;
  142. this.fnQuery();
  143. },
  144. onReachBottom(e) {
  145. if (this.result.hasNext) {
  146. this.queryParams.page += 1;
  147. this.isLoadMore = true;
  148. this.fnGetArticleList();
  149. } else {
  150. uni.showToast({
  151. icon: 'none',
  152. title: '没有更多数据了'
  153. });
  154. }
  155. },
  156. methods: {
  157. fnQuery() {
  158. this.fnGetBanner();
  159. this.fnGetArticleList();
  160. this.fnGetCategoryList();
  161. },
  162. fnGetCategoryList() {
  163. this.$httpApi.v2
  164. .getCategoryList({})
  165. .then(res => {
  166. console.log('查询分类成功:', res);
  167. this.categoryList = res.items.sort((a, b) => {
  168. return b.postCount - a.postCount;
  169. });
  170. setTimeout(() => {
  171. this.loading = 'success';
  172. }, 500);
  173. })
  174. .catch(err => {
  175. console.error(err);
  176. this.loading = 'error';
  177. })
  178. .finally(() => {
  179. setTimeout(() => {
  180. uni.hideLoading();
  181. uni.stopPullDownRefresh();
  182. }, 500);
  183. });
  184. },
  185. // 获取轮播图
  186. fnGetBanner() {
  187. const _this = this;
  188. const _format = function(list, type) {
  189. console.log("list", list)
  190. return list.map((item, index) => {
  191. switch (type) {
  192. case 'list':
  193. return {
  194. id: index,
  195. nickname: _this.bloggerInfo.nickname,
  196. avatar: _this.bloggerInfo.avatar,
  197. address: item.href || '',
  198. title: item.title,
  199. image: _this.$utils.checkImageUrl(item.thumbnail)
  200. };
  201. case 'article':
  202. return {
  203. mp4: '',
  204. id: item.metadata.name,
  205. nickname: item.owner.displayName,
  206. avatar: _this.$utils.checkImageUrl(item.owner.avatar),
  207. address: '',
  208. createTime: uni.$tm.dayjs(item.spec.publishTime).fromNow(),
  209. title: item.spec.title,
  210. src: _this.$utils.checkImageUrl(item.spec.cover),
  211. image: _this.$utils.checkImageUrl(item.spec.cover)
  212. };
  213. case 'banner':
  214. return {
  215. id: '',
  216. src: item
  217. };
  218. }
  219. });
  220. };
  221. switch (this.$haloConfig.banner.type) {
  222. case 'list': // 手动配置的banner
  223. this.bannerList = _format(this.$haloConfig.banner.list, 'list');
  224. break;
  225. case 'article': // 来自热门文章的封面
  226. this.$httpApi.getPostList({
  227. page: 0,
  228. size: 6
  229. }).then(res => {
  230. this.bannerList = _format(res.items, 'article');
  231. if (this.bannerList.length == 0) {
  232. this.bannerList = _format(this.$haloConfig.banner.list, 'list');
  233. }
  234. });
  235. break;
  236. case 'banner': // 来自后台配置的banner(暂未开放)
  237. this.bannerList = _format([], 'banner');
  238. break;
  239. }
  240. },
  241. fnOnBannerChange(e) {
  242. this.bannerCurrent = e.current;
  243. },
  244. fnOnBannerClick(item) {
  245. if (item.id == '') return;
  246. this.fnToArticleDetail({
  247. metadata: {
  248. name: item.id
  249. }
  250. });
  251. },
  252. // 文章列表
  253. fnGetArticleList() {
  254. // uni.showLoading({
  255. // mask: true,
  256. // title: '加载中...'
  257. // });
  258. // 设置状态为加载中
  259. if (!this.isLoadMore) {
  260. this.loading = 'loading';
  261. }
  262. this.loadMoreText = '加载中...';
  263. this.$httpApi.v2
  264. .getPostList(this.queryParams)
  265. .then(res => {
  266. console.log('加载成功', res);
  267. this.loading = 'success';
  268. this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
  269. this.result = res;
  270. if (this.isLoadMore) {
  271. this.articleList = this.articleList.concat(res.items);
  272. } else {
  273. this.articleList = res.items;
  274. }
  275. this.loading = 'success';
  276. })
  277. .catch(err => {
  278. this.loading = 'error';
  279. this.loadMoreText = '加载失败,请下拉刷新!';
  280. uni.$tm.toast(err.message || '数据加载失败!');
  281. })
  282. .finally(() => {
  283. uni.hideLoading();
  284. uni.stopPullDownRefresh();
  285. });
  286. },
  287. //跳转文章详情
  288. fnToArticleDetail(article) {
  289. uni.navigateTo({
  290. url: '/pagesA/article-detail/article-detail?name=' + article.metadata.name,
  291. animationType: 'slide-in-right'
  292. });
  293. },
  294. // 快捷导航页面跳转
  295. fnToNavPage(item) {
  296. switch (item.type) {
  297. case 'tabbar':
  298. uni.switchTab({
  299. url: item.path
  300. });
  301. break;
  302. case 'page':
  303. uni.navigateTo({
  304. url: item.path
  305. });
  306. break;
  307. }
  308. },
  309. // 分类页面
  310. fnToCategoryPage() {
  311. uni.switchTab({
  312. url: '/pages/tabbar/category/category'
  313. });
  314. },
  315. // 所有的文章列表页面
  316. fnToArticlesPage() {
  317. uni.navigateTo({
  318. url: '/pagesA/articles/articles'
  319. });
  320. },
  321. // 根据slug查询分类下的文章
  322. fnToCategoryBy(category) {
  323. uni.navigateTo({
  324. url: `/pagesA/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`
  325. });
  326. },
  327. fnChangeMode() {
  328. const isBlackTheme = this.$tm.vx.state().tmVuetify.black;
  329. this.$tm.theme.setBlack(!isBlackTheme);
  330. uni.setNavigationBarColor({
  331. backgroundColor: !isBlackTheme ? '#0a0a0a' : '#ffffff',
  332. frontColor: !isBlackTheme ? '#ffffff' : '#0a0a0a'
  333. });
  334. },
  335. fnToSearch() {
  336. uni.navigateTo({
  337. url: '/pagesA/articles/articles'
  338. });
  339. }
  340. }
  341. };
  342. </script>
  343. <style lang="scss" scoped>
  344. .app-page {
  345. width: 100vw;
  346. min-height: 100vh;
  347. display: flex;
  348. flex-direction: column;
  349. // background-color: #ffffff;
  350. .logo {
  351. width: 60rpx;
  352. height: 60rpx;
  353. box-sizing: border-box;
  354. }
  355. ::v-deep {
  356. .tm-menubars .body .body_wk .left {
  357. min-width: initial;
  358. }
  359. }
  360. }
  361. .loading-wrap {
  362. padding: 24rpx;
  363. }
  364. .search-input {
  365. background-color: #f5f5f5;
  366. align-items: center;
  367. /* #ifdef MP-WEIXIN */
  368. margin-right: 24rpx;
  369. /* #endif */
  370. &_icon {}
  371. &_text {}
  372. }
  373. .show-more {
  374. width: 42rpx;
  375. height: 42rpx;
  376. box-sizing: border-box;
  377. box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
  378. }
  379. .banner {
  380. overflow: hidden;
  381. }
  382. .quick-nav {
  383. background-color: #fff;
  384. box-sizing: border-box;
  385. // box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
  386. .name {
  387. color: var(--main-text-color);
  388. }
  389. }
  390. .category {
  391. width: 94vw;
  392. display: flex;
  393. height: 200rpx;
  394. white-space: nowrap;
  395. margin: 0 24rpx;
  396. .content {
  397. display: inline-block;
  398. padding-left: 24rpx;
  399. &:first-child {
  400. padding-left: 0;
  401. }
  402. }
  403. .cate-empty {
  404. height: inherit;
  405. }
  406. }
  407. .page-item {
  408. &_title {
  409. position: relative;
  410. padding-left: 24rpx;
  411. font-size: 32rpx;
  412. z-index: 1;
  413. color: var(--main-text-color);
  414. &:before {
  415. content: '';
  416. position: absolute;
  417. left: 0rpx;
  418. top: 8rpx;
  419. width: 8rpx;
  420. height: 30rpx;
  421. background-color: rgba(33, 150, 243, 1);
  422. border-radius: 6rpx;
  423. z-index: 0;
  424. }
  425. }
  426. }
  427. .h_row_col2 {
  428. display: flex;
  429. flex-wrap: wrap;
  430. box-sizing: border-box;
  431. padding: 0 12rpx;
  432. .ani-item {
  433. width: 50%;
  434. }
  435. }
  436. </style>