西藏巴青项目

index.vue 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. <template>
  2. <!-- 养殖资讯:顶区固定(搜索 + 横向标签 + 分类入口),下区 up-virtual-list;文案走 i18n,根节点随语言 class 以适配藏文 -->
  3. <view :class="pageRootClass" class="tab-page breed-page" :style="pageStyle">
  4. <view class="breed-top">
  5. <up-search
  6. v-model="searchKeyword"
  7. shape="round"
  8. :placeholder="$t('breedingNewsPage.searchPlaceholder')"
  9. :show-action="false"
  10. :clearabled="true"
  11. bg-color="#f5f2ef"
  12. border-color="#e5ded6"
  13. />
  14. <view class="breed-tabs-row">
  15. <up-tabs
  16. :current="tabCurrentIndex"
  17. class="breed-tabs"
  18. :list="tabsList"
  19. key-name="name"
  20. :scrollable="true"
  21. line-color="#22C55E"
  22. :active-style="{ color: '#15803d', fontWeight: '600' }"
  23. :inactive-style="{ color: '#78716c' }"
  24. @update:current="tabCurrentIndex = $event"
  25. @change="onTabsChange"
  26. />
  27. <view class="breed-tabs-extra" role="button" @click="popupVisible = true">
  28. <up-icon name="grid-fill" color="#22C55E" :size="22" />
  29. </view>
  30. </view>
  31. </view>
  32. <view class="breed-body">
  33. <view v-if="listLoading && !articles.length" class="breed-empty">
  34. <text class="text-body breed-empty__txt">{{ $t('breedingNewsPage.loading') }}</text>
  35. </view>
  36. <view v-else-if="!articles.length" class="breed-empty">
  37. <text class="text-body breed-empty__txt">{{ $t('breedingNewsPage.empty') }}</text>
  38. </view>
  39. <view v-else class="breed-list-wrap">
  40. <up-virtual-list
  41. ref="breedVList"
  42. class="breed-vlist"
  43. :list-data="articles"
  44. :item-height="slotHeightPx"
  45. :height="listHeightPx"
  46. :buffer="6"
  47. key-field="id"
  48. :scroll-top="vScrollTop"
  49. @update:scrollTop="vScrollTop = $event"
  50. @scroll="onVirtualListScroll"
  51. >
  52. <template #default="{ item }">
  53. <view class="breed-row-cell">
  54. <view class="breed-row" :style="{ height: rowBodyPx + 'px' }">
  55. <view class="breed-row__left" role="button" @click="openNewsDetail(item)">
  56. <text class="breed-row__title">{{ item.title }}</text>
  57. <text class="text-body breed-row__summary">{{ item.introduction || $t('breedingNewsPage.noIntro') }}</text>
  58. <view class="breed-row__meta">
  59. <text class="breed-row__meta-tag text-body">{{ tabTitle(item.type) }}</text>
  60. <text class="breed-row__meta-date text-body">{{ item.publishTime || $t('breedingNewsPage.noDate') }}</text>
  61. </view>
  62. </view>
  63. <view class="breed-row__right">
  64. <!-- up-lazy-load:进入视区再加载;点击用 uni.previewImage 放大 -->
  65. <up-lazy-load
  66. class="breed-lazy-cover"
  67. role="button"
  68. :image="articleCover(item)"
  69. height="120"
  70. :border-radius="12"
  71. img-mode="aspectFill"
  72. :threshold="200"
  73. :index="item.id"
  74. @click="() => onPreviewCover(item)"
  75. />
  76. </view>
  77. </view>
  78. </view>
  79. </template>
  80. </up-virtual-list>
  81. <view v-if="listLoadingMore || listNoMore" class="breed-list-foot">
  82. <text class="text-body breed-list-foot__txt">
  83. {{ listLoadingMore ? $t('breedingNewsPage.loadingMore') : $t('breedingNewsPage.noMore') }}
  84. </text>
  85. </view>
  86. </view>
  87. </view>
  88. <up-popup
  89. :show="popupVisible"
  90. mode="bottom"
  91. :round="20"
  92. :safe-area-inset-bottom="true"
  93. :close-on-click-overlay="true"
  94. @update:show="popupVisible = $event"
  95. >
  96. <view class="breed-popup">
  97. <view class="breed-popup__handle" />
  98. <scroll-view scroll-y class="breed-popup__scroll" :show-scrollbar="false">
  99. <view v-for="grp in popupGroups" :key="grp.key" class="breed-popup__block">
  100. <text class="breed-popup__group-title text-title">{{ grp.title || $t(grp.titleKey) }}</text>
  101. <view class="breed-popup__chips">
  102. <view
  103. v-for="tid in grp.tabIds"
  104. :key="tid"
  105. class="breed-chip"
  106. :class="{ 'breed-chip--on': selectedTabId === tid }"
  107. role="button"
  108. @click="onPickChip(tid)"
  109. >
  110. <text class="breed-chip__txt text-body">{{ tabTitle(tid) }}</text>
  111. </view>
  112. </view>
  113. </view>
  114. </scroll-view>
  115. </view>
  116. </up-popup>
  117. </view>
  118. </template>
  119. <script>
  120. import USearch from 'uview-plus/components/u-search/u-search.vue'
  121. import UTabs from 'uview-plus/components/u-tabs/u-tabs.vue'
  122. import UPopup from 'uview-plus/components/u-popup/u-popup.vue'
  123. import UIcon from 'uview-plus/components/u-icon/u-icon.vue'
  124. import ULazyLoad from 'uview-plus/components/u-lazy-load/u-lazy-load.vue'
  125. import UVirtualList from 'uview-plus/components/u-virtual-list/u-virtual-list.vue'
  126. import tabPage from '@/mixins/tabPage'
  127. import { resolveResourceUrl } from '@/utils/resourceUrl'
  128. import { listInformationCategoryTree } from '@/api/category/informationCategory'
  129. import { listFarmingNews } from '@/api/farmingNews'
  130. /** 列表分页(接口默认 10,上限 50) */
  131. const LIST_PAGE_SIZE = 20
  132. /** 养殖资讯模块(与资讯类别接口 moduleId 一致) */
  133. const FARMING_NEWS_MODULE_ID = '01'
  134. /** 接口不可用时的静态兜底(visible=1 叶子 code,不含 001013) */
  135. const FALLBACK_TABS = [
  136. { id: '001001', name: '繁育作业' },
  137. { id: '001002', name: '饲养工作' },
  138. { id: '001003', name: '免疫程序' },
  139. { id: '001004', name: '环境调控' },
  140. { id: '001005', name: '饲料配方' },
  141. { id: '001006', name: '牦牛投喂' },
  142. { id: '001007', name: '设备操作' },
  143. { id: '001008', name: '设备保养' },
  144. { id: '001009', name: '设备排障' },
  145. { id: '001010', name: '设备维修' },
  146. { id: '001011', name: '牦牛生长' },
  147. { id: '001012', name: '牦牛出栏' },
  148. { id: '002001', name: '高新技术' },
  149. { id: '002002', name: '农业科技' },
  150. { id: '002003', name: '社会发展' },
  151. { id: '002004', name: '基础研究' },
  152. { id: '003001', name: '涉农政策' },
  153. { id: '003002', name: '产业项目' },
  154. { id: '003003', name: '惠农补贴' },
  155. { id: '003004', name: '共富项目' }
  156. ]
  157. const FALLBACK_POPUP_GROUPS = [
  158. { key: '001', titleKey: 'breedingNewsPage.groupStandard', tabIds: FALLBACK_TABS.slice(0, 12).map((t) => t.id) },
  159. { key: '002', titleKey: 'breedingNewsPage.groupTech', tabIds: FALLBACK_TABS.slice(12, 16).map((t) => t.id) },
  160. { key: '003', titleKey: 'breedingNewsPage.groupPolicy', tabIds: FALLBACK_TABS.slice(16, 20).map((t) => t.id) }
  161. ]
  162. /** 列表行内容区(rpx),不含与下一行的间距;虚拟列表单项高度 = 内容 + 间隔 */
  163. const ROW_BODY_RPX = 260
  164. const ROW_GAP_RPX = 20
  165. const BREED_BODY_PAD_RPX = 40
  166. const COVER = '/static/ai/hero.png'
  167. /** 资讯详情分包路径(英文目录名) */
  168. const NEWS_DETAIL_PATH = '/package-a/news-detail/index'
  169. export default {
  170. components: {
  171. 'up-search': USearch,
  172. 'up-tabs': UTabs,
  173. 'up-popup': UPopup,
  174. 'up-icon': UIcon,
  175. 'up-lazy-load': ULazyLoad,
  176. 'up-virtual-list': UVirtualList
  177. },
  178. mixins: [tabPage],
  179. data() {
  180. return {
  181. navTitleKey: 'breedingNewsPage.navTitle',
  182. searchKeyword: '',
  183. tabCurrentIndex: 0,
  184. popupVisible: false,
  185. categoryTabs: [],
  186. popupGroupsData: [],
  187. articles: [],
  188. listLoading: false,
  189. listLoadingMore: false,
  190. listTotal: 0,
  191. pageNum: 1,
  192. pageSize: LIST_PAGE_SIZE,
  193. _searchTimer: null,
  194. loadMoreTimer: null,
  195. rowBodyPx: 120,
  196. marginPx: 10,
  197. slotHeightPx: 130,
  198. listHeightPx: 400,
  199. pageHeightPx: 0,
  200. vScrollTop: 0,
  201. coverSrc: COVER
  202. }
  203. },
  204. computed: {
  205. /** u-tabs:id 为资讯类别叶子 code,name 为接口 name */
  206. tabsList() {
  207. return this.categoryTabs.length ? this.categoryTabs : FALLBACK_TABS
  208. },
  209. selectedTabId() {
  210. const row = this.tabsList[this.tabCurrentIndex]
  211. return row ? row.id : ''
  212. },
  213. popupGroups() {
  214. return this.popupGroupsData.length ? this.popupGroupsData : FALLBACK_POPUP_GROUPS
  215. },
  216. /** 已加载条数 ≥ total 时无更多 */
  217. listNoMore() {
  218. return this.listTotal > 0 && this.articles.length >= this.listTotal
  219. },
  220. pageStyle() {
  221. if (this.pageHeightPx > 0) {
  222. return { height: `${this.pageHeightPx}px` }
  223. }
  224. return {}
  225. }
  226. },
  227. watch: {
  228. searchKeyword() {
  229. this.vScrollTop = 0
  230. clearTimeout(this._searchTimer)
  231. this._searchTimer = setTimeout(() => {
  232. this.loadNewsList(true)
  233. }, 300)
  234. },
  235. tabCurrentIndex() {
  236. this.vScrollTop = 0
  237. this.loadNewsList(true)
  238. },
  239. listLoadingMore() {
  240. this.$nextTick(() => this.calcLayoutHeights())
  241. },
  242. listNoMore() {
  243. this.$nextTick(() => this.calcLayoutHeights())
  244. }
  245. },
  246. created() {
  247. this.loadCategoryTabs().finally(() => {
  248. this.loadNewsList(true)
  249. })
  250. },
  251. onReady() {
  252. try {
  253. this.marginPx = Math.ceil(uni.upx2px(ROW_GAP_RPX))
  254. this.rowBodyPx = Math.max(100, Math.ceil(uni.upx2px(ROW_BODY_RPX)))
  255. this.slotHeightPx = this.rowBodyPx + this.marginPx
  256. } catch (e) {
  257. this.marginPx = 10
  258. this.rowBodyPx = 120
  259. this.slotHeightPx = 130
  260. }
  261. this.applyListHeightFallback()
  262. this.$nextTick(() => {
  263. this.calcLayoutHeights()
  264. })
  265. },
  266. onShow() {
  267. this.$nextTick(() => this.calcLayoutHeights())
  268. },
  269. onUnload() {
  270. if (this.loadMoreTimer) clearTimeout(this.loadMoreTimer)
  271. if (this._searchTimer) clearTimeout(this._searchTimer)
  272. },
  273. methods: {
  274. loadCategoryTabs() {
  275. return listInformationCategoryTree({ moduleId: FARMING_NEWS_MODULE_ID })
  276. .then((res) => {
  277. const tree = res.data || []
  278. const tabs = []
  279. const groups = []
  280. tree.forEach((root) => {
  281. const children = root.children || []
  282. groups.push({
  283. key: root.code,
  284. title: root.name,
  285. tabIds: children.map((c) => c.code)
  286. })
  287. children.forEach((child) => {
  288. tabs.push({ id: child.code, name: child.name })
  289. })
  290. })
  291. this.categoryTabs = tabs
  292. this.popupGroupsData = groups
  293. if (this.tabCurrentIndex >= tabs.length) {
  294. this.tabCurrentIndex = 0
  295. }
  296. })
  297. .catch(() => {
  298. this.categoryTabs = []
  299. this.popupGroupsData = []
  300. })
  301. .finally(() => {
  302. this.$nextTick(() => this.calcLayoutHeights())
  303. })
  304. },
  305. mapNewsRow(row, index) {
  306. const type = row.type || ''
  307. const publishTime = row.publishTime || ''
  308. const title = row.title || ''
  309. const newsId = row.id
  310. return {
  311. /** 虚拟列表 key:同 type 下用业务 id,无 id 时回退合成键 */
  312. id: newsId != null ? `${type}-${newsId}` : `${type}-${publishTime}-${index}-${title}`,
  313. newsId,
  314. title,
  315. introduction: row.introduction || '',
  316. type,
  317. coverFileUrl: row.coverFileUrl || '',
  318. contentFileUrl: row.contentFileUrl || '',
  319. publishTime
  320. }
  321. },
  322. loadNewsList(reset = false) {
  323. const type = this.selectedTabId
  324. if (!type) {
  325. this.articles = []
  326. this.listTotal = 0
  327. return Promise.resolve()
  328. }
  329. if (!reset) {
  330. if (this.listLoading || this.listLoadingMore || this.listNoMore) {
  331. return Promise.resolve()
  332. }
  333. this.pageNum += 1
  334. this.listLoadingMore = true
  335. } else {
  336. this.pageNum = 1
  337. this.listLoading = true
  338. }
  339. const params = {
  340. type,
  341. pageNum: this.pageNum,
  342. pageSize: this.pageSize
  343. }
  344. const titleKw = (this.searchKeyword || '').trim()
  345. if (titleKw) {
  346. params.title = titleKw
  347. }
  348. const baseIndex = reset ? 0 : this.articles.length
  349. return listFarmingNews(params)
  350. .then((res) => {
  351. const rows = res.rows || []
  352. this.listTotal = res.total != null ? Number(res.total) : 0
  353. const mapped = rows.map((row, i) => this.mapNewsRow(row, baseIndex + i))
  354. this.articles = reset ? mapped : this.articles.concat(mapped)
  355. this.$nextTick(() => {
  356. this.calcLayoutHeights()
  357. })
  358. })
  359. .catch(() => {
  360. if (reset) {
  361. this.articles = []
  362. this.listTotal = 0
  363. } else {
  364. this.pageNum -= 1
  365. }
  366. })
  367. .finally(() => {
  368. if (reset) {
  369. this.listLoading = false
  370. } else {
  371. this.listLoadingMore = false
  372. }
  373. })
  374. },
  375. /** 首屏内容未填满可视区时自动补页 */
  376. tryAutoLoadMore() {
  377. if (this.listLoading || this.listLoadingMore || this.listNoMore) return
  378. const totalH = this.articles.length * this.slotHeightPx
  379. const viewH = this.listHeightPx
  380. if (totalH > 0 && totalH <= viewH) {
  381. this.loadNewsList(false)
  382. }
  383. },
  384. /**
  385. * up-virtual-list scroll:接近底部时 pageNum++ 加载下一页
  386. * 回调为 scrollTop 数值(非原生 event)
  387. */
  388. onVirtualListScroll(scrollTop) {
  389. if (this.loadMoreTimer) clearTimeout(this.loadMoreTimer)
  390. this.loadMoreTimer = setTimeout(() => {
  391. this.loadMoreTimer = null
  392. this.tryLoadMoreOnScroll(typeof scrollTop === 'number' ? scrollTop : 0)
  393. }, 180)
  394. },
  395. tryLoadMoreOnScroll(scrollTop) {
  396. if (this.listLoading || this.listLoadingMore || this.listNoMore) return
  397. const totalH = this.articles.length * this.slotHeightPx
  398. const viewH = this.listHeightPx
  399. const threshold = Math.max(this.slotHeightPx * 2, 120)
  400. if (totalH <= viewH) {
  401. this.loadNewsList(false)
  402. return
  403. }
  404. if (scrollTop + viewH >= totalH - threshold) {
  405. this.loadNewsList(false)
  406. }
  407. },
  408. applyListHeightFallback() {
  409. const contentH = this.getViewportContentHeight()
  410. const topFallback = Math.ceil(uni.upx2px(220))
  411. const bodyPad = Math.ceil(uni.upx2px(BREED_BODY_PAD_RPX))
  412. this.pageHeightPx = contentH
  413. this.listHeightPx = Math.max(240, contentH - topFallback - bodyPad)
  414. },
  415. getNavigationBarHeight() {
  416. const sys = uni.getSystemInfoSync()
  417. const statusBar = sys.statusBarHeight || 0
  418. // #ifdef MP-WEIXIN
  419. try {
  420. const menu = uni.getMenuButtonBoundingClientRect()
  421. if (menu && menu.height) {
  422. return statusBar + (menu.top - statusBar) * 2 + menu.height
  423. }
  424. } catch (e) {
  425. /* noop */
  426. }
  427. // #endif
  428. return statusBar + 44
  429. },
  430. getViewportContentHeight() {
  431. const sys = uni.getSystemInfoSync()
  432. const navH = this.getNavigationBarHeight()
  433. // #ifdef H5
  434. if (typeof window !== 'undefined' && window.innerHeight) {
  435. return Math.max(320, window.innerHeight - navH)
  436. }
  437. // #endif
  438. const screenH = sys.screenHeight || sys.windowHeight || 600
  439. return Math.max(320, screenH - navH)
  440. },
  441. calcLayoutHeights() {
  442. const contentH = this.getViewportContentHeight()
  443. this.pageHeightPx = contentH
  444. const applyListHeight = (bodyH) => {
  445. let listH = Math.max(240, Math.floor(bodyH))
  446. if (!this.articles.length) {
  447. this.listHeightPx = listH
  448. return
  449. }
  450. uni.createSelectorQuery()
  451. .in(this)
  452. .select('.breed-list-foot')
  453. .boundingClientRect((footRect) => {
  454. if (footRect && footRect.height > 0) {
  455. listH -= Math.ceil(footRect.height)
  456. }
  457. this.listHeightPx = Math.max(240, listH)
  458. this.$nextTick(() => {
  459. this.$refs.breedVList?.measureContainerHeight?.()
  460. this.tryAutoLoadMore()
  461. })
  462. })
  463. .exec()
  464. }
  465. uni.createSelectorQuery()
  466. .in(this)
  467. .select('.breed-body')
  468. .boundingClientRect((bodyRect) => {
  469. if (bodyRect && bodyRect.height > 0) {
  470. applyListHeight(bodyRect.height)
  471. return
  472. }
  473. uni.createSelectorQuery()
  474. .in(this)
  475. .select('.breed-top')
  476. .boundingClientRect((topRect) => {
  477. const topH =
  478. topRect && topRect.height > 0 ? Math.ceil(topRect.height) : Math.ceil(uni.upx2px(220))
  479. const bodyPad = Math.ceil(uni.upx2px(BREED_BODY_PAD_RPX))
  480. applyListHeight(Math.max(240, contentH - topH - bodyPad))
  481. })
  482. .exec()
  483. })
  484. .exec()
  485. },
  486. tabTitle(tabId) {
  487. const row = this.tabsList.find((t) => t.id === tabId)
  488. return row ? row.name : String(tabId || '')
  489. },
  490. /** 列表项封面 */
  491. articleCover(item) {
  492. if (item.coverFileUrl) {
  493. return resolveResourceUrl(item.coverFileUrl)
  494. }
  495. return this.coverSrc
  496. },
  497. /** 点击缩略图:系统预览放大 */
  498. onPreviewCover(item) {
  499. const url = this.articleCover(item)
  500. if (!url) return
  501. uni.previewImage({
  502. urls: [url],
  503. current: 0
  504. })
  505. },
  506. /** 进入资讯详情:携带列表项 id + type,详情页请求 /app/farmingNews/{id}?type= */
  507. openNewsDetail(item) {
  508. if (!item || item.newsId == null || !item.type) {
  509. uni.showToast({
  510. title: this.$t('newsDetailPage.invalidParams'),
  511. icon: 'none'
  512. })
  513. return
  514. }
  515. const q = [
  516. 'kind=breeding',
  517. `id=${encodeURIComponent(String(item.newsId))}`,
  518. `type=${encodeURIComponent(String(item.type))}`,
  519. `typeLabel=${encodeURIComponent(this.tabTitle(item.type))}`
  520. ].join('&')
  521. uni.navigateTo({ url: `${NEWS_DETAIL_PATH}?${q}` })
  522. },
  523. onTabsChange(_item, index) {
  524. this.tabCurrentIndex = typeof index === 'number' ? index : this.tabCurrentIndex
  525. },
  526. /** 弹层中点选子类:与顶部 tabs 同一套 id */
  527. onPickChip(tabId) {
  528. const idx = this.tabsList.findIndex((t) => t.id === tabId)
  529. if (idx >= 0) this.tabCurrentIndex = idx
  530. this.popupVisible = false
  531. this.vScrollTop = 0
  532. }
  533. }
  534. }
  535. </script>
  536. <style lang="scss" scoped>
  537. @import '@/styles/morandi.scss';
  538. @import '@/styles/tab-page.scss';
  539. .breed-page {
  540. display: flex;
  541. flex-direction: column;
  542. min-width: 0;
  543. width: 100%;
  544. height: 100%;
  545. min-height: 100%;
  546. overflow: hidden;
  547. box-sizing: border-box;
  548. background: $morandi-bg-page;
  549. }
  550. .breed-top {
  551. flex-shrink: 0;
  552. display: flex;
  553. flex-direction: column;
  554. gap: 20rpx;
  555. min-width: 0;
  556. padding: 20rpx 24rpx 16rpx;
  557. box-sizing: border-box;
  558. background: $morandi-bg-page;
  559. border-bottom: 1rpx solid $morandi-border-soft;
  560. }
  561. .breed-tabs-row {
  562. display: flex;
  563. flex-direction: row;
  564. align-items: stretch;
  565. min-width: 0;
  566. gap: 12rpx;
  567. }
  568. .breed-tabs {
  569. flex: 1;
  570. min-width: 0;
  571. }
  572. .breed-tabs-extra {
  573. flex-shrink: 0;
  574. display: flex;
  575. flex-direction: column;
  576. align-items: center;
  577. justify-content: center;
  578. gap: 4rpx;
  579. padding: 8rpx 12rpx;
  580. border-radius: 16rpx;
  581. background: $morandi-bg-card-inner;
  582. border: 1rpx solid $morandi-border;
  583. box-sizing: border-box;
  584. }
  585. .breed-tabs-extra__txt {
  586. font-size: 20rpx;
  587. color: $morandi-text-muted;
  588. text-align: center;
  589. max-width: 88rpx;
  590. word-break: break-word;
  591. overflow-wrap: anywhere;
  592. }
  593. .breed-body {
  594. // flex: 1;
  595. min-height: 0;
  596. height: calc(100vh - 100px);
  597. min-width: 0;
  598. display: flex;
  599. flex-direction: column;
  600. padding: 16rpx 24rpx 24rpx;
  601. box-sizing: border-box;
  602. overflow: hidden;
  603. }
  604. .breed-list-wrap {
  605. flex: 1;
  606. min-height: 0;
  607. display: flex;
  608. flex-direction: column;
  609. min-width: 0;
  610. }
  611. .breed-vlist {
  612. flex: 1;
  613. min-height: 0;
  614. width: 100%;
  615. height: 100%;
  616. }
  617. .breed-list-foot {
  618. flex-shrink: 0;
  619. display: flex;
  620. align-items: center;
  621. justify-content: center;
  622. padding: 16rpx 0 8rpx;
  623. box-sizing: border-box;
  624. }
  625. .breed-list-foot__txt {
  626. font-size: 24rpx;
  627. color: $morandi-text-muted;
  628. text-align: center;
  629. }
  630. .breed-empty {
  631. flex: 1;
  632. display: flex;
  633. align-items: center;
  634. justify-content: center;
  635. min-height: 0;
  636. padding: 48rpx 24rpx;
  637. box-sizing: border-box;
  638. }
  639. .breed-empty__txt {
  640. color: $morandi-text-muted;
  641. text-align: center;
  642. }
  643. .breed-row-cell {
  644. height: 100%;
  645. box-sizing: border-box;
  646. display: flex;
  647. flex-direction: column;
  648. justify-content: flex-start;
  649. }
  650. .breed-row {
  651. display: flex;
  652. flex-direction: row;
  653. align-items: stretch;
  654. gap: 20rpx;
  655. min-width: 0;
  656. padding: 20rpx;
  657. box-sizing: border-box;
  658. border-radius: 16rpx;
  659. background: $morandi-bg-card;
  660. border: 1rpx solid $morandi-border;
  661. flex-shrink: 0;
  662. }
  663. .breed-row__left {
  664. flex: 1;
  665. min-width: 0;
  666. display: flex;
  667. flex-direction: column;
  668. gap: 12rpx;
  669. }
  670. .breed-row__title {
  671. font-size: 32rpx;
  672. font-weight: 600;
  673. line-height: 1.45;
  674. color: $morandi-text;
  675. word-break: break-word;
  676. overflow-wrap: anywhere;
  677. }
  678. .breed-row__summary {
  679. font-size: 24rpx;
  680. line-height: 1.55;
  681. color: $morandi-text-secondary;
  682. word-break: break-word;
  683. overflow-wrap: anywhere;
  684. }
  685. .breed-row__meta {
  686. display: flex;
  687. flex-direction: row;
  688. flex-wrap: wrap;
  689. align-items: center;
  690. gap: 12rpx 20rpx;
  691. min-width: 0;
  692. }
  693. .breed-row__meta-tag {
  694. font-size: 22rpx;
  695. color: $morandi-accent-soft;
  696. }
  697. .breed-row__meta-date {
  698. font-size: 22rpx;
  699. color: $morandi-text-soft;
  700. }
  701. .breed-row__right {
  702. flex-shrink: 0;
  703. align-self: center;
  704. width: 160rpx;
  705. min-width: 0;
  706. }
  707. .breed-lazy-cover {
  708. width: 100%;
  709. display: block;
  710. }
  711. /* 藏文:标题/摘要略小字号、行高与字间距(与全站 app-lang 协调) */
  712. .breed-page.lang-bo {
  713. .breed-row__title {
  714. font-size: 30rpx;
  715. line-height: 1.75;
  716. letter-spacing: 2rpx;
  717. font-family: 'Noto Sans Tibetan', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  718. }
  719. .breed-row__summary {
  720. font-size: 22rpx;
  721. line-height: 1.75;
  722. letter-spacing: 2rpx;
  723. font-family: 'Noto Sans Tibetan', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  724. }
  725. .breed-row__meta-tag,
  726. .breed-row__meta-date {
  727. font-size: 20rpx;
  728. line-height: 1.75;
  729. letter-spacing: 2rpx;
  730. font-family: 'Noto Sans Tibetan', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  731. }
  732. .breed-tabs-extra__txt {
  733. font-size: 18rpx;
  734. line-height: 1.75;
  735. letter-spacing: 2rpx;
  736. }
  737. .breed-chip__txt {
  738. font-size: 20rpx;
  739. line-height: 1.75;
  740. letter-spacing: 2rpx;
  741. font-family: 'Noto Sans Tibetan', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  742. }
  743. }
  744. .breed-popup {
  745. display: flex;
  746. flex-direction: column;
  747. min-width: 0;
  748. max-height: 72vh;
  749. padding: 12rpx 24rpx 24rpx;
  750. box-sizing: border-box;
  751. }
  752. .breed-popup__handle {
  753. align-self: center;
  754. width: 72rpx;
  755. height: 8rpx;
  756. border-radius: 999rpx;
  757. background: $morandi-border-strong;
  758. margin-bottom: 16rpx;
  759. }
  760. .breed-popup__scroll {
  761. flex: 1;
  762. min-height: 0;
  763. max-height: 68vh;
  764. }
  765. .breed-popup__block {
  766. display: flex;
  767. flex-direction: column;
  768. gap: 16rpx;
  769. margin-bottom: 28rpx;
  770. min-width: 0;
  771. }
  772. .breed-popup__group-title {
  773. color: $morandi-text;
  774. word-break: break-word;
  775. overflow-wrap: anywhere;
  776. }
  777. .breed-popup__chips {
  778. display: grid;
  779. grid-template-columns: repeat(4, minmax(0, 1fr));
  780. gap: 12rpx;
  781. min-width: 0;
  782. }
  783. .breed-chip {
  784. min-width: 0;
  785. padding: 14rpx 8rpx;
  786. box-sizing: border-box;
  787. border-radius: 12rpx;
  788. border: 1rpx solid $morandi-border-strong;
  789. background: $morandi-bg-card-inner;
  790. display: flex;
  791. align-items: center;
  792. justify-content: center;
  793. }
  794. .breed-chip--on {
  795. border-color: #22c55e;
  796. background: rgba(34, 197, 94, 0.08);
  797. }
  798. .breed-chip__txt {
  799. font-size: 22rpx;
  800. color: $morandi-text-secondary;
  801. text-align: center;
  802. word-break: break-word;
  803. overflow-wrap: anywhere;
  804. }
  805. .breed-chip--on .breed-chip__txt {
  806. color: #15803d;
  807. font-weight: 600;
  808. }
  809. </style>