Selaa lähdekoodia

feat(knowledge): 清理废弃 microbiome_article,健康知识库管理页支持全文详情

- 删除 MicrobiomeArticle 三件套(Entity/Mapper/Controller),原表已 DROP(备份表 microbiome_article_bak_20261020 保留),数据统一由 health_knowledge_base 管理
- 前端 health-knowledge.vue:说明列截断 120 字符;新增「详情」按钮 + 弹窗完整展示 71K 字符全文(pre-wrap 保留格式);分类下拉动态合并数据中的分类
Xiaogang Liao 4 päivää sitten
vanhempi
sitoutus
da7cd82cc0

+ 0 - 37
cfc-backend/src/main/java/com/etotem/cfc/controller/MicrobiomeArticleController.java

@@ -1,37 +0,0 @@
-package com.etotem.cfc.controller;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.MicrobiomeArticle;
-import com.etotem.cfc.mapper.MicrobiomeArticleMapper;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.web.bind.annotation.*;
-import javax.annotation.Resource;
-import java.util.Map;
-@Tag(name = "菌群知识库文章")
-@RestController
-@RequestMapping("/api/microbiome/article")
-public class MicrobiomeArticleController {
-    @Resource
-    private MicrobiomeArticleMapper microbiomeArticleMapper;
-    @Operation(summary = "获取菌群知识库文章列表(分页)")
-    @PostMapping("/list")
-    public Result<Page<MicrobiomeArticle>> list(@RequestBody Map<String, Object> params) {
-        int page = params.get("page") != null ? Integer.parseInt(params.get("page").toString()) : 1;
-        int size = params.get("size") != null ? Integer.parseInt(params.get("size").toString()) : 1000;
-        LambdaQueryWrapper<MicrobiomeArticle> wrapper = new LambdaQueryWrapper<MicrobiomeArticle>()
-                .eq(MicrobiomeArticle::getStatus, 1);
-        if (params != null) {
-            Object category = params.get("category");
-            if (category != null && !category.toString().isEmpty())
-                wrapper.eq(MicrobiomeArticle::getCategory, category.toString());
-            Object keyword = params.get("keyword");
-            if (keyword != null && !keyword.toString().isEmpty())
-                wrapper.and(w -> w.like(MicrobiomeArticle::getTitle, keyword.toString())
-                        .or().like(MicrobiomeArticle::getContent, keyword.toString()));
-        }
-        wrapper.orderByDesc(MicrobiomeArticle::getCreatedAt);
-        return Result.success(microbiomeArticleMapper.selectPage(new Page<>(page, size), wrapper));
-    }
-}

+ 0 - 16
cfc-backend/src/main/java/com/etotem/cfc/entity/MicrobiomeArticle.java

@@ -1,16 +0,0 @@
-package com.etotem.cfc.entity;
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import java.io.Serializable;
-import java.util.Date;
-@Data
-@TableName("microbiome_article")
-public class MicrobiomeArticle implements Serializable {
-    @TableId(type = IdType.AUTO)
-    private Long id; private String title; private String content;
-    private String category; private String tags; private String sourceUrl;
-    private String pubDate; private Integer status;
-    private Date createdAt; private Date updatedAt;
-}

+ 0 - 4
cfc-backend/src/main/java/com/etotem/cfc/mapper/MicrobiomeArticleMapper.java

@@ -1,4 +0,0 @@
-package com.etotem.cfc.mapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.etotem.cfc.entity.MicrobiomeArticle;
-public interface MicrobiomeArticleMapper extends BaseMapper<MicrobiomeArticle> {}

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-8f93672779c4a860fc7c2bd901c88eed53a863c9
+77d42f093803b085cd72420de9a0a09a37f50f0e

+ 36 - 2
cfc-web/src/views/admin/health-knowledge.vue

@@ -43,18 +43,22 @@
             <span class="sort-header" @click.stop="onSortClick('normalRange', $event)">正常范围<span v-if="sortIndex('normalRange') !== null" class="sort-index" :class="sortClass('normalRange') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('normalRange') }}</span>{{ sortIcon('normalRange') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="description" label="说明" min-width="260" show-overflow-tooltip>
+        <el-table-column prop="description" label="说明" min-width="260">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('description', $event)">说明<span v-if="sortIndex('description') !== null" class="sort-index" :class="sortClass('description') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('description') }}</span>{{ sortIcon('description') }}</span>
           </template>
+          <template slot-scope="{ row }">
+            <span class="desc-ellipsis">{{ (row.description || '').slice(0, 120) }}{{ (row.description || '').length > 120 ? '…' : '' }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="source" label="来源" width="120" show-overflow-tooltip>
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('source', $event)">来源<span v-if="sortIndex('source') !== null" class="sort-index" :class="sortClass('source') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('source') }}</span>{{ sortIcon('source') }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="130" fixed="right">
+        <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
+            <el-button type="text" @click="showDetail(row)">详情</el-button>
             <el-button type="text" @click="editRow(row)">编辑</el-button>
             <el-button type="text" style="color:#f56c6c" @click="deleteRow(row)">删除</el-button>
           </template>
@@ -88,6 +92,22 @@
         <el-button type="primary" @click="saveDialog" size="small" :loading="dialog.saving">保存</el-button>
       </div>
     </el-dialog>
+
+    <!-- 详情弹窗:完整展示文章/条目全文(菌群文章最长 71K 字符) -->
+    <el-dialog :visible.sync="detail.visible" :title="detail.row ? detail.row.itemName : '详情'" width="820px" :close-on-click-modal="true">
+      <div v-if="detail.row">
+        <div class="detail-meta">
+          <el-tag v-if="detail.row.category" size="small">{{ detail.row.category }}</el-tag>
+          <span class="detail-meta-item">来源:{{ detail.row.source || '-' }}</span>
+          <span class="detail-meta-item">ID:{{ detail.row.id }}</span>
+          <span class="detail-meta-item">正文 {{ (detail.row.description || '').length }} 字符</span>
+        </div>
+        <div class="detail-content">{{ detail.row.description || '(无正文)' }}</div>
+      </div>
+      <div slot="footer">
+        <el-button @click="detail.visible = false" size="small">关闭</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
@@ -108,6 +128,7 @@ export default {
       sortableColumns: { id: 'ID', itemName: '指标名称', category: '分类', normalRange: '正常范围', description: '说明', source: '来源' },
       filters: { name: '', category: '' },
       categories: ['维生素', '氨基酸', '微量元素', '短链脂肪酸', '神经递质与激素', '肠道屏障与代谢物', '抗生素风险评估'],
+      detail: { visible: false, row: null },
       dialog: { visible: false, isEdit: false, saving: false, form: {} }
     }
   },
@@ -132,6 +153,12 @@ export default {
           if (this.filters.category) {
             rows = rows.filter(function(r) { return r.category === this.filters.category }.bind(this))
           }
+          // 动态合并数据中的分类(菌群文章等外部来源的分类可能不在硬编码列表内)
+          var cats = this.categories.slice()
+          rows.forEach(function(r) {
+            if (r.category && cats.indexOf(r.category) < 0) cats.push(r.category)
+          })
+          this.categories = cats
           this.list = rows
         }
       }).finally(() => { this.loading = false })
@@ -142,6 +169,9 @@ export default {
     editRow(row) {
       this.dialog = { visible: true, isEdit: true, saving: false, form: { id: row.id, itemName: row.itemName, category: row.category, normalRange: row.normalRange, description: row.description, source: row.source } }
     },
+    showDetail(row) {
+      this.detail = { visible: true, row: row }
+    },
     saveDialog() {
       if (!this.dialog.form.itemName || !this.dialog.form.category) {
         this.$message.warning('指标名称和分类不能为空'); return
@@ -184,6 +214,10 @@ export default {
 
 <style scoped>
 .knowledge-page { padding: 20px; }
+.desc-ellipsis { display: inline; }
+.detail-meta { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid #ebeef5; font-size: 12px; color: #606266; }
+.detail-meta-item { color: #909399; }
+.detail-content { max-height: 62vh; overflow-y: auto; padding: 10px 12px; background: #fafafa; border-radius: 4px; white-space: pre-wrap; word-break: break-word; font-size: 13px; line-height: 1.75; color: #303133; }
 .sort-header {
   cursor: pointer;
   user-select: none;