home.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <template>
  2. <view class="app-page">
  3. <tm-menubars iconColor="white" color="white" :flat="true" :showback="false">
  4. <view slot="left">
  5. <image @click="fnOnLogoToPage" 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 text-overflow">{{ appInfo.name }}</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
  27. height="400rpx"
  28. dotPosition="right"
  29. :autoplay="true"
  30. :useDot="false"
  31. :list="bannerList"
  32. @on-click="fnOnBannerClick"
  33. />
  34. </view>
  35. </view>
  36. <!-- 精品分类 -->
  37. <block v-if="calcIsShowCategory">
  38. <view class="flex flex-between mt-16 mb-24 pl-24 pr-24">
  39. <view class="page-item_title text-weight-b ">精品分类</view>
  40. <view class="show-more flex flex-center bg-white round-3" @click="fnToCategoryPage">
  41. <text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
  42. </view>
  43. <view v-if="false" class="flex flex-center text-size-s text-grey-darken-1"
  44. @click="fnToCategoryPage">
  45. <text class=" text-size-m">查看更多</text>
  46. <text class="iconfont icon-angle-right text-size-s "></text>
  47. </view>
  48. </view>
  49. <scroll-view class="category" scroll-x="true">
  50. <view v-if="categoryList.length === 0" class="cate-empty round-3 mr-5 flex flex-center text-grey">
  51. 还没有任何分类~
  52. </view>
  53. <block v-else>
  54. <view class="content" v-for="(category, index) in categoryList" :key="category.metadata.name"
  55. @click="fnToCategoryBy(category)">
  56. <category-mini-card :category="category"></category-mini-card>
  57. </view>
  58. </block>
  59. </scroll-view>
  60. </block>
  61. <!-- 最新文章 -->
  62. <view class="flex flex-between mt-24 mb-24 pl-24 pr-24">
  63. <view class="page-item_title text-weight-b">最新列表</view>
  64. <view class="show-more flex flex-center bg-white round-3" @click="fnToArticlesPage">
  65. <text class="iconfont icon-angle-right text-size-s text-grey-darken-1"></text>
  66. </view>
  67. <view v-if="false" class="flex flex-center text-size-s text-grey-darken-1" @click="fnToArticlesPage">
  68. <text class=" text-size-m ">查看更多</text>
  69. <text class="iconfont icon-angle-right text-size-s "></text>
  70. </view>
  71. </view>
  72. <view v-if="articleList.length === 0" class="article-empty">
  73. <tm-empty icon="icon-shiliangzhinengduixiang-" label="博主还没有发表任何内容~"></tm-empty>
  74. </view>
  75. <block v-else>
  76. <view :class="globalAppSettings.layout.home">
  77. <tm-translate v-for="(article, index) in articleList" :key="index" class="ani-item"
  78. animation-name="fadeUp" :wait="calcAniWait(index)">
  79. <article-card from="home" :article="article" :post="article"
  80. @on-click="fnToArticleDetail"></article-card>
  81. </tm-translate>
  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. </view>
  89. </template>
  90. <script>
  91. import tmMenubars from '@/tm-vuetify/components/tm-menubars/tm-menubars.vue';
  92. import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
  93. import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
  94. import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
  95. import tmIcons from '@/tm-vuetify/components/tm-icons/tm-icons.vue';
  96. import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
  97. import eSwiper from '@/components/e-swiper/e-swiper.vue';
  98. import qs from 'qs'
  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. sort: ['spec.pinned,desc', 'spec.publishTime,desc']
  116. },
  117. result: {},
  118. isLoadMore: false,
  119. loadMoreText: '加载中...',
  120. bannerCurrent: 0,
  121. bannerList: [],
  122. noticeList: [],
  123. articleList: [],
  124. categoryList: [],
  125. };
  126. },
  127. computed: {
  128. haloConfigs() {
  129. return this.$tm.vx.getters().getConfigs;
  130. },
  131. bloggerInfo() {
  132. const blogger = this.$tm.vx.getters().getConfigs.authorConfig.blogger;
  133. blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
  134. return blogger;
  135. },
  136. appInfo() {
  137. const appInfo = this.haloConfigs.appConfig.appInfo;
  138. appInfo.logo = this.$utils.checkImageUrl(appInfo.logo)
  139. return appInfo;
  140. },
  141. mockJson() {
  142. return this.$tm.vx.getters().getMockJson;
  143. },
  144. calcIsShowCategory() {
  145. if (this.haloConfigs.basicConfig.auditModeEnabled && this.categoryList.length !== 0) {
  146. return false
  147. }
  148. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  149. return false
  150. }
  151. return this.haloConfigs.pageConfig.homeConfig.useCategory
  152. }
  153. },
  154. onLoad() {
  155. this.fnSetPageTitle();
  156. },
  157. created() {
  158. this.fnQuery();
  159. },
  160. onPullDownRefresh() {
  161. this.isLoadMore = false;
  162. this.queryParams.page = 1;
  163. this.fnQuery();
  164. },
  165. onReachBottom(e) {
  166. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  167. uni.showToast({
  168. icon: 'none',
  169. title: '没有更多数据了'
  170. });
  171. return
  172. }
  173. if (this.result.hasNext) {
  174. this.queryParams.page += 1;
  175. this.isLoadMore = true;
  176. this.fnGetArticleList();
  177. } else {
  178. uni.showToast({
  179. icon: 'none',
  180. title: '没有更多数据了'
  181. });
  182. }
  183. },
  184. methods: {
  185. fnQuery() {
  186. console.log('this.mockJson', this.mockJson)
  187. this.fnGetBanner();
  188. this.fnGetArticleList();
  189. this.fnGetCategoryList();
  190. },
  191. fnGetCategoryList() {
  192. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  193. this.categoryList = this.mockJson.home.categoryList.map((item) => {
  194. return {
  195. metadata: {
  196. name: Date.now() * Math.random(),
  197. },
  198. spec: {
  199. displayName: item.title,
  200. cover: item.cover
  201. },
  202. postCount: 0
  203. }
  204. });
  205. return;
  206. }
  207. if (!this.calcIsShowCategory) {
  208. return;
  209. }
  210. this.$httpApi.v2
  211. .getCategoryList({
  212. fieldSelector:['spec.hideFromList=false']
  213. })
  214. .then(res => {
  215. this.categoryList = res.items.sort((a, b) => {
  216. return b.postCount - a.postCount;
  217. });
  218. setTimeout(() => {
  219. this.loading = 'success';
  220. }, 500);
  221. })
  222. .catch(err => {
  223. console.error(err);
  224. this.loading = 'error';
  225. })
  226. .finally(() => {
  227. setTimeout(() => {
  228. uni.hideLoading();
  229. uni.stopPullDownRefresh();
  230. }, 500);
  231. });
  232. },
  233. // 获取轮播图
  234. fnGetBanner() {
  235. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  236. this.bannerList = this.mockJson.home.bannerList.map((item) => {
  237. return {
  238. mp4: '',
  239. id: Date.now() * Math.random(),
  240. nickname: this.haloConfigs.authorConfig.blogger.nickname,
  241. avatar: this.$utils.checkAvatarUrl(this.haloConfigs.authorConfig.blogger.avatar),
  242. address: '',
  243. createTime: item.time,
  244. title: item.title,
  245. src: this.$utils.checkThumbnailUrl(item.cover),
  246. image: this.$utils.checkThumbnailUrl(item.cover)
  247. }
  248. });
  249. return;
  250. }
  251. const _this = this;
  252. const _format = function (list) {
  253. return list.map((item, index) => {
  254. return {
  255. mp4: '',
  256. id: item.metadata.name,
  257. nickname: item.owner.displayName,
  258. avatar: _this.$utils.checkAvatarUrl(item.owner.avatar),
  259. address: '',
  260. createTime: uni.$tm.dayjs(item.spec.publishTime).fromNow(),
  261. title: item.spec.title,
  262. src: _this.$utils.checkThumbnailUrl(item.spec.cover),
  263. image: _this.$utils.checkThumbnailUrl(item.spec.cover)
  264. };
  265. });
  266. };
  267. const paramsStr = qs.stringify(this.queryParams, {
  268. allowDots: true,
  269. encodeValuesOnly: true,
  270. skipNulls: true,
  271. encode: true,
  272. arrayFormat: 'repeat'
  273. })
  274. uni.request({
  275. url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
  276. method: 'GET',
  277. success: (res) => {
  278. this.bannerList = _format(res.data.items);
  279. },
  280. fail: (err) => {
  281. }
  282. })
  283. },
  284. fnOnBannerChange(e) {
  285. this.bannerCurrent = e.current;
  286. },
  287. fnOnBannerClick(item) {
  288. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  289. return;
  290. }
  291. if (item.id === '') return;
  292. this.fnToArticleDetail({
  293. metadata: {
  294. name: item.id
  295. }
  296. });
  297. },
  298. // 文章列表
  299. fnGetArticleList() {
  300. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  301. this.articleList = this.mockJson.home.postList.map((item) => {
  302. return {
  303. metadata: {
  304. name: Date.now() * Math.random(),
  305. },
  306. spec: {
  307. pinned: false,
  308. cover: item.cover,
  309. title: item.title,
  310. publishTime: item.time
  311. },
  312. status: {
  313. excerpt: item.desc
  314. },
  315. stats: {
  316. visit: 0
  317. }
  318. }
  319. });
  320. this.loading = 'success';
  321. this.loadMoreText = '呜呜,没有更多数据啦~';
  322. uni.hideLoading();
  323. uni.stopPullDownRefresh();
  324. return;
  325. }
  326. // 设置状态为加载中
  327. if (!this.isLoadMore) {
  328. this.loading = 'loading';
  329. }
  330. this.loadMoreText = '加载中...';
  331. const paramsStr = qs.stringify(this.queryParams, {
  332. allowDots: true,
  333. encodeValuesOnly: true,
  334. skipNulls: true,
  335. encode: true,
  336. arrayFormat: 'repeat'
  337. })
  338. uni.request({
  339. url: this.$baseApiUrl + '/apis/api.content.halo.run/v1alpha1/posts?' + paramsStr,
  340. method: 'GET',
  341. success: (res) => {
  342. const data = res.data;
  343. this.result.hasNext = data.hasNext;
  344. if (this.isLoadMore) {
  345. this.articleList = this.articleList.concat(data.items);
  346. } else {
  347. this.articleList = data.items;
  348. }
  349. this.loading = 'success';
  350. this.loadMoreText = data.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
  351. uni.hideLoading();
  352. uni.stopPullDownRefresh();
  353. },
  354. fail: (err) => {
  355. this.loading = 'error';
  356. this.loadMoreText = '加载失败,请下拉刷新!';
  357. uni.$tm.toast(err.message || '数据加载失败!');
  358. uni.stopPullDownRefresh();
  359. }
  360. })
  361. },
  362. //跳转文章详情
  363. fnToArticleDetail(article) {
  364. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  365. return;
  366. }
  367. uni.navigateTo({
  368. url: '/pagesA/article-detail/article-detail?name=' + article.metadata.name,
  369. animationType: 'slide-in-right'
  370. });
  371. },
  372. // 快捷导航页面跳转
  373. fnToNavPage(item) {
  374. switch (item.type) {
  375. case 'tabbar':
  376. uni.switchTab({
  377. url: item.path
  378. });
  379. break;
  380. case 'page':
  381. uni.navigateTo({
  382. url: item.path
  383. });
  384. break;
  385. }
  386. },
  387. // 分类页面
  388. fnToCategoryPage() {
  389. uni.switchTab({
  390. url: '/pages/tabbar/category/category'
  391. });
  392. },
  393. // 所有的文章列表页面
  394. fnToArticlesPage() {
  395. uni.navigateTo({
  396. url: '/pagesA/articles/articles'
  397. });
  398. },
  399. // 根据slug查询分类下的文章
  400. fnToCategoryBy(category) {
  401. if (this.haloConfigs.basicConfig.auditModeEnabled) {
  402. return;
  403. }
  404. uni.navigateTo({
  405. url: `/pagesA/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`
  406. });
  407. },
  408. fnChangeMode() {
  409. const isBlackTheme = this.$tm.vx.state().tmVuetify.black;
  410. this.$tm.theme.setBlack(!isBlackTheme);
  411. uni.setNavigationBarColor({
  412. backgroundColor: !isBlackTheme ? '#0a0a0a' : '#ffffff',
  413. frontColor: !isBlackTheme ? '#ffffff' : '#0a0a0a'
  414. });
  415. },
  416. fnToSearch() {
  417. uni.navigateTo({
  418. url: '/pagesA/articles/articles'
  419. });
  420. },
  421. fnOnLogoToPage() {
  422. uni.switchTab({
  423. url: '/pages/tabbar/about/about'
  424. })
  425. }
  426. }
  427. };
  428. </script>
  429. <style lang="scss" scoped>
  430. .app-page {
  431. width: 100vw;
  432. min-height: 100vh;
  433. display: flex;
  434. flex-direction: column;
  435. // background-color: #ffffff;
  436. .logo {
  437. width: 60rpx;
  438. height: 60rpx;
  439. box-sizing: border-box;
  440. }
  441. ::v-deep {
  442. .tm-menubars .body .body_wk .left {
  443. min-width: initial;
  444. }
  445. }
  446. }
  447. .loading-wrap {
  448. padding: 24rpx;
  449. }
  450. .search-input {
  451. background-color: #f5f5f5;
  452. align-items: center;
  453. /* #ifdef MP-WEIXIN */
  454. margin-right: 24rpx;
  455. /* #endif */
  456. &_icon {
  457. }
  458. &_text {
  459. }
  460. }
  461. .show-more {
  462. width: 42rpx;
  463. height: 42rpx;
  464. box-sizing: border-box;
  465. box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
  466. }
  467. .banner {
  468. overflow: hidden;
  469. }
  470. .quick-nav {
  471. background-color: #fff;
  472. box-sizing: border-box;
  473. // box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
  474. .name {
  475. color: var(--main-text-color);
  476. }
  477. }
  478. .category {
  479. width: 94vw;
  480. display: flex;
  481. height: 200rpx;
  482. white-space: nowrap;
  483. margin: 0 24rpx;
  484. .content {
  485. display: inline-block;
  486. padding-left: 24rpx;
  487. &:first-child {
  488. padding-left: 0;
  489. }
  490. }
  491. .cate-empty {
  492. height: inherit;
  493. }
  494. }
  495. .page-item {
  496. &_title {
  497. position: relative;
  498. padding-left: 24rpx;
  499. font-size: 32rpx;
  500. z-index: 1;
  501. color: var(--main-text-color);
  502. &:before {
  503. content: '';
  504. position: absolute;
  505. left: 0rpx;
  506. top: 8rpx;
  507. width: 8rpx;
  508. height: 30rpx;
  509. background-color: rgba(33, 150, 243, 1);
  510. border-radius: 6rpx;
  511. z-index: 0;
  512. }
  513. }
  514. }
  515. .h_row_col2 {
  516. display: flex;
  517. flex-wrap: wrap;
  518. box-sizing: border-box;
  519. padding: 0 12rpx;
  520. .ani-item {
  521. width: 50%;
  522. }
  523. }
  524. </style>