ArticleBookshelf.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  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 4" :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.coverImage"
  32. class="bs-cover-img"
  33. :src="getImageUrl(article.coverImage)"
  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.coverImage" 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. if (!this.articles || this.articles.length === 0) {
  82. return [
  83. { title: '培养孩子自驱力的关键', category: '家庭成长', summary: '如何帮助孩子建立内在动机' },
  84. { title: '五维能量与家庭健康管理', category: '健康科普', summary: '科学理解健康的五个维度' },
  85. { title: '亲子沟通的心理学智慧', category: '心理养育', summary: '有效的倾听与表达技巧' },
  86. { title: '规划师推荐的书单', category: '成长规划', summary: '培养未来领导力的阅读路径' },
  87. { title: '儿童营养均衡完全指南', category: '营养健康', summary: '0-12岁各阶段营养需求' }
  88. ].slice(0, 4)
  89. }
  90. return this.articles.slice(0, 4)
  91. }
  92. },
  93. methods: {
  94. // 提取首个维度 code(兼容字符串 "mind,emotion" / JSON 字符串 / 数组 ['mind','emotion'])
  95. getFirstDimension: function(article) {
  96. if (!article || !article.relatedDimensions) return ''
  97. var raw = article.relatedDimensions
  98. var first = ''
  99. if (typeof raw === 'string') {
  100. var trimmed = raw.trim()
  101. if (trimmed.indexOf('[') === 0) {
  102. try {
  103. var parsed = JSON.parse(trimmed)
  104. if (Array.isArray(parsed)) first = parsed[0] || ''
  105. } catch (e) {
  106. first = ''
  107. }
  108. }
  109. if (!first) {
  110. first = trimmed.split(',')[0] || ''
  111. }
  112. } else if (Array.isArray(raw)) {
  113. first = raw[0] || ''
  114. }
  115. return String(first).trim().toLowerCase()
  116. },
  117. getBookColor: function(article, idx) {
  118. var firstDim = this.getFirstDimension(article)
  119. if (firstDim && DIMENSION_COLORS[firstDim]) {
  120. return DIMENSION_COLORS[firstDim].bg
  121. }
  122. if (article && article.dimensionCode && DIMENSION_COLORS[article.dimensionCode]) {
  123. return DIMENSION_COLORS[article.dimensionCode].bg
  124. }
  125. return FALLBACK_GRADIENTS[idx % FALLBACK_GRADIENTS.length].bg
  126. },
  127. getTextColor: function(article, idx) {
  128. var firstDim = this.getFirstDimension(article)
  129. if (firstDim && DIMENSION_COLORS[firstDim]) {
  130. return DIMENSION_COLORS[firstDim].text
  131. }
  132. if (article && article.dimensionCode && DIMENSION_COLORS[article.dimensionCode]) {
  133. return DIMENSION_COLORS[article.dimensionCode].text
  134. }
  135. return FALLBACK_GRADIENTS[idx % FALLBACK_GRADIENTS.length].text
  136. },
  137. getImageUrl: function(path) {
  138. return config.API_BASE_URL + path
  139. }
  140. }
  141. }
  142. </script>
  143. <style scoped>
  144. /* ======================================
  145. ArticleBookshelf — 四列网格推荐阅读组件
  146. 浠艾福 (Care Family) 设计系统
  147. ====================================== */
  148. /* ---- Section Container ---- */
  149. .bs-section {
  150. margin: 24rpx 0;
  151. padding: 0 0 16rpx;
  152. }
  153. /* ---- Empty State ---- */
  154. .bs-empty {
  155. padding: 40rpx 0;
  156. display: flex;
  157. justify-content: center;
  158. }
  159. .bs-empty-text {
  160. font-size: 24rpx;
  161. color: #9CA3AF;
  162. }
  163. /* ---- Skeleton ---- */
  164. .bs-shelf-skeleton {
  165. padding: 0 28rpx;
  166. }
  167. .bs-skeleton-grid {
  168. display: flex;
  169. flex-wrap: wrap;
  170. padding-top: 16rpx;
  171. }
  172. .bs-skeleton-card {
  173. width: calc(25% - 12rpx);
  174. margin-right: 16rpx;
  175. margin-bottom: 16rpx;
  176. }
  177. .bs-skeleton-card:nth-child(4n) {
  178. margin-right: 0;
  179. }
  180. .bs-skeleton-cover {
  181. width: 100%;
  182. height: 160rpx;
  183. border-radius: 12rpx;
  184. background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
  185. background-size: 200% 100%;
  186. animation: skeleton-wave 1.4s ease-in-out infinite;
  187. }
  188. .bs-skeleton-line {
  189. height: 24rpx;
  190. margin-top: 10rpx;
  191. border-radius: 6rpx;
  192. background: linear-gradient(180deg, #e8e8e8 0%, #f0f0f0 50%, #e8e8e8 100%);
  193. background-size: 200% 100%;
  194. animation: skeleton-wave 1.4s ease-in-out infinite;
  195. }
  196. @keyframes skeleton-wave {
  197. 0% { background-position: 200% 0; }
  198. 100% { background-position: -200% 0; }
  199. }
  200. /* ---- 四列文章网格 ---- */
  201. .bs-grid {
  202. display: flex;
  203. flex-wrap: wrap;
  204. }
  205. .bs-card {
  206. width: calc(25% - 12rpx);
  207. margin-right: 16rpx;
  208. margin-bottom: 16rpx;
  209. }
  210. .bs-card:nth-child(4n) {
  211. margin-right: 0;
  212. }
  213. .bs-card-cover {
  214. width: 100%;
  215. height: 160rpx;
  216. border-radius: 12rpx;
  217. position: relative;
  218. display: flex;
  219. align-items: center;
  220. justify-content: center;
  221. overflow: hidden;
  222. box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
  223. transition: transform 0.18s, box-shadow 0.18s;
  224. }
  225. .bs-card:active .bs-card-cover {
  226. transform: translateY(-4rpx) scale(1.02);
  227. box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.18);
  228. }
  229. /* 封面图 */
  230. .bs-cover-img {
  231. width: 100%;
  232. height: 100%;
  233. }
  234. /* 无封面图时:渐变底 + 书名 */
  235. .bs-cover-text {
  236. width: 100%;
  237. height: 100%;
  238. display: flex;
  239. align-items: center;
  240. justify-content: center;
  241. padding: 16rpx 12rpx;
  242. box-sizing: border-box;
  243. }
  244. .bs-cover-title {
  245. font-size: 22rpx;
  246. font-weight: 700;
  247. line-height: 1.5;
  248. letter-spacing: 1rpx;
  249. text-align: center;
  250. overflow: hidden;
  251. text-overflow: ellipsis;
  252. display: -webkit-box;
  253. -webkit-line-clamp: 3;
  254. -webkit-box-orient: vertical;
  255. word-break: break-all;
  256. }
  257. /* 有封面图时:底部叠加文章标题 */
  258. .bs-cover-title-bottom {
  259. position: absolute;
  260. bottom: 0;
  261. left: 0;
  262. right: 0;
  263. display: flex;
  264. justify-content: center;
  265. align-items: center;
  266. padding: 10rpx 8rpx 8rpx;
  267. background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.55) 100%);
  268. }
  269. .bs-cover-title-overlay {
  270. font-size: 18rpx;
  271. font-weight: 700;
  272. line-height: 1.4;
  273. color: #FFFFFF;
  274. text-align: center;
  275. overflow: hidden;
  276. text-overflow: ellipsis;
  277. display: -webkit-box;
  278. -webkit-line-clamp: 2;
  279. -webkit-box-orient: vertical;
  280. word-break: break-all;
  281. max-width: 90%;
  282. }
  283. /* 卡片底部标题 */
  284. .bs-card-name {
  285. display: block;
  286. margin-top: 8rpx;
  287. font-size: 22rpx;
  288. color: #333;
  289. line-height: 1.4;
  290. overflow: hidden;
  291. text-overflow: ellipsis;
  292. white-space: nowrap;
  293. }
  294. </style>