ArticleBookshelf.vue 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  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. />
  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: '#FFF7ED' }
  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. <style scoped>
  156. /* ======================================
  157. ArticleBookshelf — 四列网格推荐阅读组件
  158. 浠艾福 (Care Family) 设计系统
  159. ====================================== */
  160. /* ---- Section Container ---- */
  161. .bs-section {
  162. margin: 24rpx 0;
  163. padding: 0 0 16rpx;
  164. }
  165. /* ---- Empty State ---- */
  166. .bs-empty {
  167. padding: 40rpx 0;
  168. display: flex;
  169. justify-content: center;
  170. }
  171. .bs-empty-text {
  172. font-size: 24rpx;
  173. color: #9CA3AF;
  174. }
  175. /* ---- Skeleton ---- */
  176. .bs-shelf-skeleton {
  177. padding: 0 28rpx;
  178. }
  179. .bs-skeleton-grid {
  180. display: flex;
  181. flex-wrap: wrap;
  182. padding-top: 16rpx;
  183. }
  184. .bs-skeleton-card {
  185. width: calc(33.33% - 12rpx);
  186. margin-right: 16rpx;
  187. margin-bottom: 16rpx;
  188. }
  189. .bs-skeleton-card:nth-child(3n) {
  190. margin-right: 0;
  191. }
  192. .bs-skeleton-cover {
  193. width: 100%;
  194. height: 0;
  195. padding-top: 133.33%; /* 3:4 书封比例 */
  196. border-radius: 12rpx;
  197. background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
  198. background-size: 200% 100%;
  199. animation: skeleton-wave 1.4s ease-in-out infinite;
  200. }
  201. .bs-skeleton-line {
  202. height: 24rpx;
  203. margin-top: 10rpx;
  204. border-radius: 6rpx;
  205. background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
  206. background-size: 200% 100%;
  207. animation: skeleton-wave 1.4s ease-in-out infinite;
  208. }
  209. @keyframes skeleton-wave {
  210. 0% { background-position: 200% 0; }
  211. 100% { background-position: -200% 0; }
  212. }
  213. /* ---- 三列文章网格 ---- */
  214. .bs-grid {
  215. display: flex;
  216. flex-wrap: wrap;
  217. }
  218. .bs-card {
  219. width: calc(33.33% - 12rpx);
  220. margin-right: 16rpx;
  221. margin-bottom: 16rpx;
  222. }
  223. .bs-card:nth-child(3n) {
  224. margin-right: 0;
  225. }
  226. .bs-card-cover {
  227. width: 100%;
  228. height: 0;
  229. padding-top: 133.33%; /* 3:4 书封比例 */
  230. border-radius: 12rpx;
  231. position: relative;
  232. display: flex;
  233. align-items: center;
  234. justify-content: center;
  235. overflow: hidden;
  236. box-sizing: border-box;
  237. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
  238. transition: transform 0.18s, box-shadow 0.18s;
  239. }
  240. .bs-card:active .bs-card-cover {
  241. transform: translateY(-4rpx) scale(1.02);
  242. box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.18);
  243. }
  244. /* 封面图 */
  245. .bs-cover-img {
  246. position: absolute;
  247. top: 0;
  248. left: 0;
  249. width: 100%;
  250. height: 100%;
  251. }
  252. /* 无封面图时:渐变底 + 书名 */
  253. .bs-cover-text {
  254. position: absolute;
  255. top: 0;
  256. left: 0;
  257. width: 100%;
  258. height: 100%;
  259. display: flex;
  260. align-items: center;
  261. justify-content: center;
  262. padding: 16rpx 12rpx;
  263. box-sizing: border-box;
  264. }
  265. .bs-cover-title {
  266. font-size: 22rpx;
  267. font-weight: 700;
  268. line-height: 1.5;
  269. letter-spacing: 1rpx;
  270. text-align: center;
  271. overflow: hidden;
  272. text-overflow: ellipsis;
  273. display: -webkit-box;
  274. -webkit-line-clamp: 3;
  275. -webkit-box-orient: vertical;
  276. word-break: break-all;
  277. }
  278. /* 有封面图时:底部叠加文章标题 */
  279. .bs-cover-title-bottom {
  280. position: absolute;
  281. bottom: 0;
  282. left: 0;
  283. right: 0;
  284. display: flex;
  285. justify-content: center;
  286. align-items: center;
  287. padding: 10rpx 8rpx 8rpx;
  288. background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.55) 100%);
  289. }
  290. .bs-cover-title-overlay {
  291. font-size: 18rpx;
  292. font-weight: 700;
  293. line-height: 1.4;
  294. color: #FFFFFF;
  295. text-align: center;
  296. overflow: hidden;
  297. text-overflow: ellipsis;
  298. display: -webkit-box;
  299. -webkit-line-clamp: 2;
  300. -webkit-box-orient: vertical;
  301. word-break: break-all;
  302. max-width: 90%;
  303. }
  304. /* 卡片底部标题 */
  305. .bs-card-name {
  306. display: block;
  307. margin-top: 8rpx;
  308. font-size: 22rpx;
  309. color: #333;
  310. line-height: 1.4;
  311. overflow: hidden;
  312. text-overflow: ellipsis;
  313. white-space: nowrap;
  314. }
  315. </style>