home.vue 14 KB

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