home.vue 11 KB

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