category.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. <template>
  2. <view class="app-page" :style="{
  3. padding:calcShowType==='list-post'? 0 : '24rpx 0'
  4. }">
  5. <view v-if="loading !== 'success'" class="loading-wrap">
  6. <tm-skeleton model="listAvatr"></tm-skeleton>
  7. <tm-skeleton model="listAvatr"></tm-skeleton>
  8. <tm-skeleton model="listAvatr"></tm-skeleton>
  9. </view>
  10. <!-- 内容区域 -->
  11. <view v-else class="app-page-content" :class="[calcShowType==='list-post'?'list-post':'']">
  12. <view v-if="dataList.length === 0" class="content-empty flex flex-center" style="height: 70vh;">
  13. <!-- 空布局 -->
  14. <tm-empty icon="icon-shiliangzhinengduixiang-" label="暂无数据"></tm-empty>
  15. </view>
  16. <block v-else>
  17. <block v-if="calcAuditModeEnabled || calcShowType==='list'">
  18. <tm-translate v-for="(item, index) in dataList" :key="index"
  19. style="box-sizing: border-box;width: 50%;padding: 0 8rpx;" animation-name="fadeUp"
  20. :wait="calcAniWait(index)">
  21. <view class="catgory-card" :style="{backgroundImage:`url(${item.spec.cover})`}">
  22. <view class="content" @click="handleToCategory(item)">
  23. <view style="font-size: 32rpx;color: #ffffff;">{{ item.spec.displayName }}</view>
  24. <view v-if="!calcAuditModeEnabled"
  25. style="font-size: 24rpx;color: #ffffff;margin-top: 6rpx;">
  26. 共 {{ item.postCount }} 篇文章
  27. </view>
  28. </view>
  29. </view>
  30. </tm-translate>
  31. <view class="load-text">{{ loadMoreText }}</view>
  32. </block>
  33. <view v-else-if="calcShowType==='list-post'" class="fulled flex" style="min-height:100vh">
  34. <view class="bg-white" :style="{height: '100%'}">
  35. <tm-sliderNav :list="categoryList" bg-color="white" color="light-blue" rang-key="displayName"
  36. @change="fnOnCategoryChange"></tm-sliderNav>
  37. </view>
  38. <scroll-view class="right-content pt-12 pb-12" :scroll-y="true" :scroll-top="scrollTop"
  39. :scroll-with-animation="true" :refresher-enabled="true" :refresher-triggered="triggered"
  40. :refresher-threshold="60" refresher-background="#fafafa"
  41. @refresherrefresh="fnGetPostByCategory(true)" @scrolltolower="fnGetPostByCategory(false)"
  42. @scroll="fnOnScroll" @touchmove.stop @touchstart="fnOnTouchStart" @touchend="fnOnTouchEnd"
  43. @touchcancel="fnOnTouchEnd">
  44. <view v-if="postList.length === 0" class="article-empty flex flex-center">
  45. <tm-empty :size="120" icon="icon-shiliangzhinengduixiang-" label="该分类下暂无文章~"></tm-empty>
  46. </view>
  47. <block v-else>
  48. <block v-for="(post, index) in postList" :key="post.spec.publishTime">
  49. <tm-translate animation-name="fadeUp" :wait="calcAniWait(index)">
  50. <article-min-card :article="post" @on-click="fnToArticleDetail"></article-min-card>
  51. </tm-translate>
  52. </block>
  53. <view class="load-text">{{ loadMoreText }}</view>
  54. </block>
  55. </scroll-view>
  56. </view>
  57. <view class="flot-buttons">
  58. <tm-button @click="fnScrollTop" size="m" :fab="true" theme="light-blue"
  59. icon="icon-angle-up"></tm-button>
  60. <tm-button v-if="!calcAuditModeEnabled" @click="fnChangeShowType" size="m" :fab="true" theme="light-blue"
  61. :icon="calcShowType==='list'?'icon-align-left':'icon-all'"></tm-button>
  62. </view>
  63. </block>
  64. </view>
  65. </view>
  66. </template>
  67. <script>
  68. import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
  69. import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
  70. import tmTranslate from '@/tm-vuetify/components/tm-translate/tm-translate.vue';
  71. import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
  72. import MarkdownConfig from '@/common/markdown/markdown.config.js';
  73. import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
  74. import tmFlowLayout from '@/tm-vuetify/components/tm-flowLayout/tm-flowLayout.vue';
  75. import tmSliderNav from '@/tm-vuetify/components/tm-sliderNav/tm-sliderNav.vue';
  76. import ArticleMinCard from '@/components/article-min-card/article-min-card.vue';
  77. export default {
  78. components: {
  79. tmSkeleton,
  80. tmButton,
  81. tmTranslate,
  82. tmEmpty,
  83. mpHtml,
  84. tmFlowLayout,
  85. tmSliderNav,
  86. ArticleMinCard
  87. },
  88. data() {
  89. return {
  90. markdownConfig: MarkdownConfig,
  91. loading: 'loading',
  92. queryParams: {
  93. size: 20,
  94. page: 1,
  95. fieldSelector: ['spec.hideFromList=false']
  96. },
  97. hasNext: false,
  98. dataList: [],
  99. categoryList: [],
  100. isLoadMore: false,
  101. loadMoreText: '加载中...',
  102. currentCategoryConfig: {
  103. type: "list"
  104. },
  105. currentCategoryName: "",
  106. postQueryParams: {
  107. size: 10,
  108. page: 0,
  109. },
  110. postList: [],
  111. triggered: false,
  112. scrollTop: 0,
  113. tempScrollTop: 0,
  114. scrollTimeout: null,
  115. };
  116. },
  117. computed: {
  118. haloConfigs() {
  119. return this.$tm.vx.getters().getConfigs;
  120. },
  121. calcAuditModeEnabled() {
  122. return this.haloConfigs.auditConfig.auditModeEnabled
  123. },
  124. mockJson() {
  125. return this.$tm.vx.getters().getMockJson;
  126. },
  127. categoryConfig() {
  128. return this.haloConfigs.pageConfig.categoryConfig
  129. },
  130. calcShowType() {
  131. return this.currentCategoryConfig.type
  132. }
  133. },
  134. watch: {
  135. categoryConfig: {
  136. deep: true,
  137. immediate: true,
  138. handler(newVal) {
  139. console.log("执行了", newVal)
  140. if (!newVal) return;
  141. this.currentCategoryConfig = newVal
  142. this.handleInitPage()
  143. },
  144. }
  145. },
  146. onPullDownRefresh() {
  147. this.isLoadMore = false;
  148. this.queryParams.page = 0;
  149. this.fnGetData();
  150. },
  151. onReachBottom(e) {
  152. if (this.calcAuditModeEnabled) {
  153. uni.showToast({
  154. icon: 'none',
  155. title: '没有更多数据了'
  156. });
  157. return
  158. }
  159. if (this.hasNext) {
  160. if (this.calcShowType === 'list') {
  161. this.queryParams.page += 1;
  162. } else {
  163. this.postQueryParams.page += 1;
  164. }
  165. this.isLoadMore = true;
  166. this.fnGetData();
  167. } else {
  168. uni.showToast({
  169. icon: 'none',
  170. title: '没有更多数据了'
  171. });
  172. }
  173. },
  174. methods: {
  175. fnChangeShowType() {
  176. if (this.calcShowType === 'list-post') {
  177. this.currentCategoryConfig.type = 'list'
  178. } else {
  179. this.currentCategoryConfig.type = 'list-post'
  180. }
  181. this.handleInitPage();
  182. },
  183. handleResetInit() {
  184. this.postList = []
  185. this.dataList = []
  186. this.categoryList = []
  187. this.queryParams.page = 0;
  188. this.postQueryParams.page = 0;
  189. this.hasNext = false
  190. this.isLoadMore = false
  191. this.loadMoreText = '加载中...'
  192. this.currentCategoryName = ""
  193. this.triggered = false
  194. this.fnResetSetAniWaitIndex()
  195. },
  196. handleInitPage() {
  197. this.handleResetInit()
  198. if (this.calcShowType === 'list-post') {
  199. this.queryParams.size = 99999
  200. }
  201. this.fnGetData();
  202. },
  203. fnGetData() {
  204. if (this.calcAuditModeEnabled) {
  205. this.categoryConfig.type = "list"
  206. this.dataList = this.mockJson.category.list.map((item) => {
  207. return {
  208. metadata: {
  209. name: Date.now() * Math.random(),
  210. },
  211. spec: {
  212. displayName: item.title,
  213. cover: this.$utils.checkImageUrl(item.cover)
  214. },
  215. postCount: 0
  216. }
  217. });
  218. this.loading = 'success';
  219. this.loadMoreText = '呜呜,没有更多数据啦~';
  220. uni.hideLoading();
  221. uni.stopPullDownRefresh();
  222. return;
  223. }
  224. uni.showLoading({
  225. mask: true,
  226. title: '加载中...'
  227. });
  228. // 设置状态为加载中
  229. if (!this.isLoadMore) {
  230. this.loading = 'loading';
  231. }
  232. this.loadMoreText = '加载中...';
  233. this.$httpApi.v2
  234. .getCategoryList(this.queryParams)
  235. .then(res => {
  236. console.log('请求结果:');
  237. console.log(res);
  238. if (this.calcShowType === 'list') {
  239. this.loading = 'success';
  240. this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
  241. // 处理数据
  242. this.hasNext = res.hasNext;
  243. const tempItems = res.items.map(item => {
  244. item.spec.cover = this.$utils.checkThumbnailUrl(item.spec.cover, true)
  245. return item;
  246. })
  247. if (this.isLoadMore) {
  248. this.dataList = this.dataList.concat(tempItems);
  249. } else {
  250. this.dataList = tempItems;
  251. }
  252. } else {
  253. this.dataList = res.items
  254. this.categoryList = res.items.map(item => {
  255. return {
  256. displayName: item.spec.displayName,
  257. name: item.metadata.name,
  258. ...item,
  259. }
  260. })
  261. this.triggered = false;
  262. this.loading = 'success';
  263. if (this.dataList.length !== 0) {
  264. this.currentCategoryName = this.dataList[0].metadata.name;
  265. this.fnGetPostByCategory()
  266. }
  267. }
  268. })
  269. .catch(err => {
  270. console.error(err);
  271. this.loading = 'error';
  272. this.loadMoreText = '加载失败,请下拉刷新!';
  273. })
  274. .finally(() => {
  275. setTimeout(() => {
  276. uni.hideLoading();
  277. uni.stopPullDownRefresh();
  278. }, 500);
  279. });
  280. },
  281. handlePreview(index, list) {
  282. uni.previewImage({
  283. current: index,
  284. urls: list.map(item => item.url)
  285. })
  286. },
  287. handleToCategory(data) {
  288. if (this.calcAuditModeEnabled) {
  289. return;
  290. }
  291. uni.navigateTo({
  292. url: `/pagesA/category-detail/category-detail?name=${data.metadata.name}&title=${data.spec.displayName}`
  293. })
  294. },
  295. fnGetPostByCategory(isPulldownRefresh = true, triggered = true) {
  296. if (!isPulldownRefresh) {
  297. if (this.hasNext) {
  298. this.postQueryParams.page += 1;
  299. } else {
  300. return uni.showToast({
  301. icon: 'none',
  302. title: '没有更多数据了'
  303. });
  304. }
  305. } else {
  306. this.postQueryParams.page = 0;
  307. if (triggered) {
  308. this.triggered = true;
  309. }
  310. }
  311. this.$httpApi.v2
  312. .getCategoryPostList(this.currentCategoryName, this.postQueryParams)
  313. .then(res => {
  314. this.hasNext = res.hasNext;
  315. if (!isPulldownRefresh) {
  316. this.postList = this.postList.concat(res.items);
  317. } else {
  318. this.postList = res.items;
  319. }
  320. this.loadMoreText = res.hasNext ? '上拉加载更多' : '呜呜,没有更多数据啦~';
  321. })
  322. .catch(err => {
  323. this.loadMoreText = '加载失败!';
  324. })
  325. .finally(() => {
  326. this.triggered = false;
  327. });
  328. },
  329. fnOnCategoryChange(e) {
  330. this.fnResetSetAniWaitIndex();
  331. this.currentCategoryName = this.dataList[e].metadata.name;
  332. this.fnToTopScroll();
  333. this.postList = [];
  334. this.fnGetPostByCategory();
  335. },
  336. fnOnScroll(e) {
  337. this.tempScrollTop = e.detail.scrollTop;
  338. },
  339. fnToTopScroll() {
  340. uni.pageScrollTo({
  341. scrollTop: 0,
  342. duration: 500
  343. });
  344. this.scrollTop = 0;
  345. this.tempScrollTop = 0;
  346. },
  347. fnOnTouchStart() {
  348. clearTimeout(this.scrollTimeout);
  349. },
  350. fnOnTouchEnd() {
  351. this.scrollTimeout = setTimeout(() => {
  352. this.scrollTop = this.tempScrollTop;
  353. }, 500);
  354. },
  355. //跳转文章详情
  356. fnToArticleDetail(post) {
  357. uni.navigateTo({
  358. url: '/pagesA/article-detail/article-detail?name=' + post.metadata.name,
  359. animationType: 'slide-in-right'
  360. });
  361. },
  362. fnScrollTop() {
  363. if (this.calcShowType === 'list') {
  364. this.fnToTopPage()
  365. } else {
  366. this.fnToTopScroll()
  367. }
  368. }
  369. }
  370. };
  371. </script>
  372. <style lang="scss" scoped>
  373. .app-page {
  374. width: 100vw;
  375. display: flex;
  376. flex-direction: column;
  377. padding: 24rpx 0;
  378. }
  379. .auditModeEnabled {
  380. width: 100%;
  381. height: 80vh;
  382. display: flex;
  383. flex-direction: column;
  384. align-items: center;
  385. justify-content: center;
  386. }
  387. .loading-wrap {
  388. padding: 24rpx;
  389. }
  390. .app-page-content {
  391. display: flex;
  392. flex-wrap: wrap;
  393. padding: 0 12rpx;
  394. gap: 20rpx 0;
  395. &.list-post {
  396. padding: 0;
  397. gap: 0;
  398. }
  399. }
  400. .catgory-card {
  401. width: 100%;
  402. height: 200rpx;
  403. position: relative;
  404. display: flex;
  405. flex-direction: column;
  406. box-sizing: border-box;
  407. border-radius: 12rpx;
  408. background-color: #ffff;
  409. box-shadow: 0rpx 2rpx 24rpx rgba(0, 0, 0, 0.03);
  410. overflow: hidden;
  411. background-repeat: no-repeat;
  412. background-size: cover;
  413. &:before {
  414. content: '';
  415. position: absolute;
  416. left: 0;
  417. top: 0;
  418. right: 0;
  419. bottom: 0;
  420. background-color: rgba(0, 0, 0, 0.15);
  421. backdrop-filter: blur(3rpx);
  422. z-index: 1;
  423. }
  424. }
  425. .content {
  426. width: 100%;
  427. height: 100%;
  428. position: relative;
  429. z-index: 2;
  430. display: flex;
  431. flex-direction: column;
  432. align-items: center;
  433. justify-content: center;
  434. }
  435. .load-text {
  436. width: 100%;
  437. text-align: center;
  438. }
  439. .flot-buttons {
  440. position: fixed;
  441. bottom: 100rpx;
  442. right: 32rpx;
  443. flex-direction: column;
  444. display: flex;
  445. gap: 6rpx;
  446. z-index: 999;
  447. }
  448. </style>