ArticleBookshelf.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. <template>
  2. <view class="bs-section">
  3. <!-- 无数据 -->
  4. <view v-if="(!articles || articles.length === 0) && !loading" class="bs-empty">
  5. <text class="bs-empty-text">书册整理中,敬请期待...</text>
  6. </view>
  7. <!-- 骨架屏 -->
  8. <view v-else-if="loading && (!articles || articles.length === 0)" class="bs-shelf-skeleton">
  9. <view class="bs-skeleton-grid">
  10. <view v-for="n in 3" :key="n" class="bs-skeleton-card">
  11. <view class="bs-skeleton-cover"></view>
  12. <view class="bs-skeleton-line"></view>
  13. </view>
  14. </view>
  15. </view>
  16. <!-- 四列文章网格 -->
  17. <view v-else class="bs-grid">
  18. <view
  19. v-for="(article, idx) in displayArticles"
  20. :key="idx"
  21. class="bs-card"
  22. @click="$emit('articleClick', article)"
  23. >
  24. <view
  25. class="bs-card-cover"
  26. :style="{
  27. background: getBookColor(article, idx)
  28. }"
  29. >
  30. <image
  31. v-if="article.coverSrc"
  32. class="bs-cover-img"
  33. :src="article.coverSrc"
  34. mode="aspectFill"
  35. lazy-load />
  36. <view v-else class="bs-cover-text">
  37. <text
  38. class="bs-cover-title"
  39. :style="{ color: getTextColor(article, idx) }"
  40. >{{ article.title }}</text>
  41. </view>
  42. <view v-if="article.coverSrc" class="bs-cover-title-bottom">
  43. <text class="bs-cover-title-overlay">{{ article.title }}</text>
  44. </view>
  45. </view>
  46. <text class="bs-card-name">{{ article.title }}</text>
  47. </view>
  48. </view>
  49. </view>
  50. </template>
  51. <script>
  52. // 五维配色体系(书脊颜色)
  53. import config from '@/config.js'
  54. var DIMENSION_COLORS = {
  55. body: { bg: 'linear-gradient(180deg, #FF8C42 0%, #E8762A 100%)', text: '#FFF5EE' },
  56. mind: { bg: 'linear-gradient(180deg, #FF6B9D 0%, #E84A7F 100%)', text: '#FFF0F5' },
  57. wisdom: { bg: 'linear-gradient(180deg, #6366F1 0%, #4F46E5 100%)', text: '#EEF2FF' },
  58. action: { bg: 'linear-gradient(180deg, #10B981 0%, #059669 100%)', text: '#ECFDF5' },
  59. wealth: { bg: 'linear-gradient(180deg, #F59E0B 0%, #D97706 100%)', text: '#FFFBEB' }
  60. }
  61. // 回退渐变(用于无维度分类的文章)
  62. var FALLBACK_GRADIENTS = [
  63. { bg: 'linear-gradient(180deg, #8B5CF6 0%, #7C3AED 100%)', text: '#F5F3FF' },
  64. { bg: 'linear-gradient(180deg, #06B6D4 0%, #0891B2 100%)', text: '#ECFEFF' },
  65. { bg: 'linear-gradient(180deg, #EC4899 0%, #DB2777 100%)', text: '#FDF2F8' },
  66. { bg: 'linear-gradient(180deg, #14B8A6 0%, #0D9488 100%)', text: '#F0FDFA' },
  67. { bg: 'linear-gradient(180deg, #F97316 0%, #EA580C 100%)', text: 'BrandColors.primaryPale' }
  68. ]
  69. export default {
  70. name: 'ArticleBookshelf',
  71. props: {
  72. articles: { type: Array, default: function() { return [] } },
  73. loading: { type: Boolean, default: false },
  74. isLoggedIn: { type: Boolean, default: false }
  75. },
  76. data: function() {
  77. return {}
  78. },
  79. computed: {
  80. displayArticles: function() {
  81. var list
  82. if (!this.articles || this.articles.length === 0) {
  83. list = [
  84. { title: '培养孩子自驱力的关键', category: '家庭成长', summary: '如何帮助孩子建立内在动机' },
  85. { title: '五维能量与家庭健康管理', category: '健康科普', summary: '科学理解健康的五个维度' },
  86. { title: '亲子沟通的心理学智慧', category: '心理养育', summary: '有效的倾听与表达技巧' },
  87. { title: '规划师推荐的书单', category: '成长规划', summary: '培养未来领导力的阅读路径' },
  88. { title: '儿童营养均衡完全指南', category: '营养健康', summary: '0-12岁各阶段营养需求' }
  89. ].slice(0, 3)
  90. } else {
  91. list = this.articles.slice(0, 3)
  92. }
  93. var self = this
  94. return list.map(function(a) {
  95. var item = Object.assign({}, a)
  96. item.coverSrc = self.getImageUrl(a && a.coverImage)
  97. return item
  98. })
  99. }
  100. },
  101. methods: {
  102. // 提取首个维度 code(兼容字符串 "mind,emotion" / JSON 字符串 / 数组 ['mind','emotion'])
  103. getFirstDimension: function(article) {
  104. if (!article || !article.relatedDimensions) return ''
  105. var raw = article.relatedDimensions
  106. var first = ''
  107. if (typeof raw === 'string') {
  108. var trimmed = raw.trim()
  109. if (trimmed.indexOf('[') === 0) {
  110. try {
  111. var parsed = JSON.parse(trimmed)
  112. if (Array.isArray(parsed)) first = parsed[0] || ''
  113. } catch (e) {
  114. first = ''
  115. }
  116. }
  117. if (!first) {
  118. first = trimmed.split(',')[0] || ''
  119. }
  120. } else if (Array.isArray(raw)) {
  121. first = raw[0] || ''
  122. }
  123. return String(first).trim().toLowerCase()
  124. },
  125. getBookColor: function(article, idx) {
  126. var firstDim = this.getFirstDimension(article)
  127. if (firstDim && DIMENSION_COLORS[firstDim]) {
  128. return DIMENSION_COLORS[firstDim].bg
  129. }
  130. if (article && article.dimensionCode && DIMENSION_COLORS[article.dimensionCode]) {
  131. return DIMENSION_COLORS[article.dimensionCode].bg
  132. }
  133. return FALLBACK_GRADIENTS[idx % FALLBACK_GRADIENTS.length].bg
  134. },
  135. getTextColor: function(article, idx) {
  136. var firstDim = this.getFirstDimension(article)
  137. if (firstDim && DIMENSION_COLORS[firstDim]) {
  138. return DIMENSION_COLORS[firstDim].text
  139. }
  140. if (article && article.dimensionCode && DIMENSION_COLORS[article.dimensionCode]) {
  141. return DIMENSION_COLORS[article.dimensionCode].text
  142. }
  143. return FALLBACK_GRADIENTS[idx % FALLBACK_GRADIENTS.length].text
  144. },
  145. getImageUrl: function(path) {
  146. if (!path || path === 'null' || path === 'undefined' || path === '/null') return ''
  147. var s = String(path).trim()
  148. if (!s || s === 'null' || s === '/null') return ''
  149. if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) return s
  150. return config.API_BASE_URL + (s.charAt(0) === '/' ? s : '/' + s)
  151. }
  152. }
  153. }
  154. </script>
  155. import { primaryPale } from '@/utils/brand-colors.js'
  156. <stylescoped lang="scss">
  157. /* ======================================
  158. ArticleBookshelf — 四列网格推荐阅读组件
  159. 浠艾福 (Care Family) 设计系统
  160. ====================================== */
  161. /* ---- Section Container ---- */
  162. .bs-section {
  163. margin: 24rpx 0;
  164. padding: 0 0 16rpx;
  165. }
  166. /* ---- Empty State ---- */
  167. .bs-empty {
  168. padding: 40rpx 0;
  169. display: flex;
  170. justify-content: center;
  171. }
  172. .bs-empty-text {
  173. font-size: 24rpx;
  174. color: $facade-text-gray-400;
  175. }
  176. /* ---- Skeleton ---- */
  177. .bs-shelf-skeleton {
  178. padding: 0 28rpx;
  179. }
  180. .bs-skeleton-grid {
  181. display: flex;
  182. flex-wrap: wrap;
  183. padding-top: 16rpx;
  184. }
  185. .bs-skeleton-card {
  186. width: calc(33.33% - 12rpx);
  187. margin-right: 16rpx;
  188. margin-bottom: 16rpx;
  189. }
  190. .bs-skeleton-card:nth-child(3n) {
  191. margin-right: 0;
  192. }
  193. .bs-skeleton-cover {
  194. width: 100%;
  195. height: 0;
  196. padding-top: 133.33%; /* 3:4 书封比例 */
  197. border-radius: 12rpx;
  198. background: linear-gradient(180deg, $facade-fill-4 0%, $facade-fill 50%, $facade-fill-4 100%);
  199. background-size: 200% 100%;
  200. animation: skeleton-wave 1.4s ease-in-out infinite;
  201. }
  202. .bs-skeleton-line {
  203. height: 24rpx;
  204. margin-top: 10rpx;
  205. border-radius: 6rpx;
  206. background: linear-gradient(180deg, $facade-fill-4 0%, $facade-fill 50%, $facade-fill-4 100%);
  207. background-size: 200% 100%;
  208. animation: skeleton-wave 1.4s ease-in-out infinite;
  209. }
  210. @keyframes skeleton-wave {
  211. 0% { background-position: 200% 0; }
  212. 100% { background-position: -200% 0; }
  213. }
  214. /* ---- 三列文章网格 ---- */
  215. .bs-grid {
  216. display: flex;
  217. flex-wrap: wrap;
  218. }
  219. .bs-card {
  220. width: calc(33.33% - 12rpx);
  221. margin-right: 16rpx;
  222. margin-bottom: 16rpx;
  223. }
  224. .bs-card:nth-child(3n) {
  225. margin-right: 0;
  226. }
  227. .bs-card-cover {
  228. width: 100%;
  229. height: 0;
  230. padding-top: 133.33%; /* 3:4 书封比例 */
  231. border-radius: 12rpx;
  232. position: relative;
  233. display: flex;
  234. align-items: center;
  235. justify-content: center;
  236. overflow: hidden;
  237. box-sizing: border-box;
  238. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
  239. transition: transform 0.18s, box-shadow 0.18s;
  240. }
  241. .bs-card:active .bs-card-cover {
  242. transform: translateY(-4rpx) scale(1.02);
  243. box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.18);
  244. }
  245. /* 封面图 */
  246. .bs-cover-img {
  247. position: absolute;
  248. top: 0;
  249. left: 0;
  250. width: 100%;
  251. height: 100%;
  252. }
  253. /* 无封面图时:渐变底 + 书名 */
  254. .bs-cover-text {
  255. position: absolute;
  256. top: 0;
  257. left: 0;
  258. width: 100%;
  259. height: 100%;
  260. display: flex;
  261. align-items: center;
  262. justify-content: center;
  263. padding: 16rpx 12rpx;
  264. box-sizing: border-box;
  265. }
  266. .bs-cover-title {
  267. font-size: 22rpx;
  268. font-weight: 700;
  269. line-height: 1.5;
  270. letter-spacing: 1rpx;
  271. text-align: center;
  272. overflow: hidden;
  273. text-overflow: ellipsis;
  274. display: -webkit-box;
  275. -webkit-line-clamp: 3;
  276. -webkit-box-orient: vertical;
  277. word-break: break-all;
  278. }
  279. /* 有封面图时:底部叠加文章标题 */
  280. .bs-cover-title-bottom {
  281. position: absolute;
  282. bottom: 0;
  283. left: 0;
  284. right: 0;
  285. display: flex;
  286. justify-content: center;
  287. align-items: center;
  288. padding: 10rpx 8rpx 8rpx;
  289. background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.55) 100%);
  290. }
  291. .bs-cover-title-overlay {
  292. font-size: 18rpx;
  293. font-weight: 700;
  294. line-height: 1.4;
  295. color: $app-surface;
  296. text-align: center;
  297. overflow: hidden;
  298. text-overflow: ellipsis;
  299. display: -webkit-box;
  300. -webkit-line-clamp: 2;
  301. -webkit-box-orient: vertical;
  302. word-break: break-all;
  303. max-width: 90%;
  304. }
  305. /* 卡片底部标题 */
  306. .bs-card-name {
  307. display: block;
  308. margin-top: 8rpx;
  309. font-size: 22rpx;
  310. color: $facade-text-strong;
  311. line-height: 1.4;
  312. overflow: hidden;
  313. text-overflow: ellipsis;
  314. white-space: nowrap;
  315. }
  316. </style>