Browse Source

feat(article): 知识中心改名知识分享 + 卡片显示阅读次数总时长

- ArticleService: 新增 enrichTotalDurationSeconds() 聚合 article_reading_records 总阅读时长,供 getPublicList/getFeatured 使用
- article-center/index.vue: 导航栏标题/注册标题改为知识分享,卡片 footer 新增 ⏱ 总时长
- discover/index.vue: 区块标题改为知识分享,推荐卡片 meta 新增 👁 阅读次数 + ⏱ 总时长
- 新增 formatDuration() 前端格式化:<60分钟显示分钟,≥1小时显示小时+分钟
Sisyphus 1 month ago
parent
commit
9062f32a3b

+ 25 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java

@@ -114,6 +114,8 @@ public class ArticleService {
         List<Article> visible = articlePermissionService.filterVisible(
                 result.getRecords(), userId, familyId, vendorType);
 
+        enrichTotalDurationSeconds(visible);
+
         Page<Article> filtered = new Page<>(page, size);
         filtered.setTotal(visible.size());
         filtered.setRecords(visible);
@@ -144,6 +146,8 @@ public class ArticleService {
         List<Article> all = articleMapper.selectList(wrapper);
         List<Article> visible = articlePermissionService.filterVisible(all, userId, familyId, vendorType);
 
+        enrichTotalDurationSeconds(visible);
+
         return visible.size() > size ? visible.subList(0, size) : visible;
     }
 
@@ -382,6 +386,27 @@ public class ArticleService {
         return articleMapper.selectById(id);
     }
 
+    private void enrichTotalDurationSeconds(List<Article> articles) {
+        if (articles == null || articles.isEmpty()) return;
+        String ids = articles.stream()
+                .map(a -> String.valueOf(a.getId()))
+                .collect(Collectors.joining(","));
+        List<Map<String, Object>> durationRows = jdbcTemplate.queryForList(
+                "SELECT article_id, SUM(duration_seconds) AS total_duration " +
+                "FROM article_reading_records WHERE article_id IN (" + ids + ") GROUP BY article_id");
+        Map<Long, Integer> durationMap = new HashMap<>();
+        for (Map<String, Object> row : durationRows) {
+            Number aid = (Number) row.get("article_id");
+            Number total = (Number) row.get("total_duration");
+            if (aid != null && total != null) {
+                durationMap.put(aid.longValue(), total.intValue());
+            }
+        }
+        for (Article article : articles) {
+            article.setTotalDurationSeconds(durationMap.getOrDefault(article.getId(), 0));
+        }
+    }
+
     public void enrichWithCategoryName(List<Article> articles) {
         if (articles == null || articles.isEmpty()) return;
     }

+ 1 - 1
cfc-frontend/pages.json

@@ -1331,7 +1331,7 @@
         {
           "path": "index",
           "style": {
-            "navigationBarTitleText": "知识中心",
+            "navigationBarTitleText": "知识分享",
             "navigationStyle": "custom"
           }
         },

+ 12 - 2
cfc-frontend/pages/article-center/index.vue

@@ -5,7 +5,7 @@
       <view class="ac-nav-left" @click="onBack">
         <text class="ac-nav-back">&#x2190;</text>
       </view>
-      <text class="ac-nav-title">知识中心</text>
+      <text class="ac-nav-title">知识分享</text>
       <view class="ac-nav-right">
         <text class="ac-nav-search-icon" @click="focusSearch">&#x1F50D;</text>
         <text class="ac-nav-add" @click="goEdit">+</text>
@@ -109,6 +109,8 @@
               <text class="ac-card-fav">&#x2B50; {{ item.favCount || 0 }}</text>
               <text class="ac-card-sep">|</text>
               <text class="ac-card-readcount">&#x1F441; {{ item.viewCount || 0 }}</text>
+              <text class="ac-card-sep">|</text>
+              <text class="ac-card-duration">&#x23F1; {{ formatDuration(item.totalDurationSeconds) }}</text>
             </view>
           </view>
         </view>
@@ -267,6 +269,14 @@ export default {
       if (!dateStr) return ''
       return dateStr.slice(0, 10)
     },
+    formatDuration(seconds) {
+      if (!seconds || seconds <= 0) return '0分钟'
+      var mins = Math.round(seconds / 60)
+      if (mins < 60) return mins + '分钟'
+      var hours = Math.floor(mins / 60)
+      var restMins = mins % 60
+      return restMins > 0 ? hours + '小时' + restMins + '分钟' : hours + '小时'
+    },
     parseDimensions(str) {
       if (!str) return []
       var dimMap = {
@@ -305,7 +315,7 @@ export default {
 
 <style scoped>
 /* ====================================
-   知识中心 — 重设计
+   知识分享 — 重设计
    白色导航 + 药丸Tab + 精装卡片
    ==================================== */
 

+ 11 - 3
cfc-frontend/pages/discover/index.vue

@@ -58,10 +58,10 @@
       </view>
     </view>
 
-    <!-- 知识中心 -->
+    <!-- 知识分享 -->
     <view class="article-section">
       <view class="section-header">
-        <text class="section-title">📖 知识中心</text>
+        <text class="section-title">📖 知识分享</text>
         <text class="section-more" @click="goKnowledgeCenter">查看全部 ›</text>
       </view>
       <view class="article-list">
@@ -72,7 +72,7 @@
           <view class="article-info">
             <text class="article-title">{{ item.title }}</text>
             <text class="article-desc">{{ item.summary || item.title }}</text>
-            <text class="article-meta">{{ item.publishedAt ? item.publishedAt.slice(0, 10) : '' }} · {{ item.readTime || 3 }}分钟阅读</text>
+            <text class="article-meta">{{ item.publishedAt ? item.publishedAt.slice(0, 10) : '' }} · {{ item.readTime || 3 }}分钟阅读 · &#x1F441; {{ item.viewCount || 0 }}次 · &#x23F1; {{ formatDuration(item.totalDurationSeconds) }}</text>
           </view>
         </view>
       </view>
@@ -160,6 +160,14 @@ export default {
     this.loadFeaturedArticles()
   },
   methods: {
+    formatDuration(seconds) {
+      if (!seconds || seconds <= 0) return '0分钟'
+      var mins = Math.round(seconds / 60)
+      if (mins < 60) return mins + '分钟'
+      var hours = Math.floor(mins / 60)
+      var restMins = mins % 60
+      return restMins > 0 ? hours + '小时' + restMins + '分钟' : hours + '小时'
+    },
     checkMemberStatus() {
       var that = this
       getMyMembership().then(function(res) {