Bläddra i källkod

关键词推荐更新

liuchengsen 3 veckor sedan
förälder
incheckning
39a5b4bc5f

+ 5 - 0
backend-java/src/main/java/com/pharmacopoeia/controller/AdminKnowledgeController.java

@@ -259,6 +259,11 @@ public class AdminKnowledgeController {
         return ResponseEntity.ok(brandRecommendService.listBrands(keyword, page, pageSize));
     }
 
+    @GetMapping("/brands/{id}")
+    public ResponseEntity<Map<String, Object>> getBrand(@PathVariable int id) {
+        return ResponseEntity.ok(brandRecommendService.getBrand(id));
+    }
+
     @PostMapping("/brands")
     public ResponseEntity<Map<String, Object>> createBrand(@RequestBody Map<String, Object> body) {
         return ResponseEntity.ok(brandRecommendService.createBrand(body));

+ 26 - 0
backend-java/src/main/java/com/pharmacopoeia/service/BrandRecommendService.java

@@ -190,6 +190,32 @@ public class BrandRecommendService {
         return Map.of("ok", true, "message", "已删除");
     }
 
+    public Map<String, Object> getBrand(int id) {
+        var opt = brandRepository.findById(id);
+        if (opt.isEmpty()) return Map.of("ok", false, "message", "未找到该品牌");
+        Brand b = opt.get();
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("id", b.getId());
+        m.put("name", b.getName());
+        m.put("function_indication", b.getFunctionIndication());
+        m.put("usage_dosage", b.getUsageDosage());
+        m.put("contraindication", b.getContraindication());
+        m.put("ingredients", b.getIngredients());
+        m.put("properties", b.getProperties());
+        m.put("specification", b.getSpecification());
+        m.put("adverse_reactions", b.getAdverseReactions());
+        m.put("precautions", b.getPrecautions());
+        m.put("execution_standard", b.getExecutionStandard());
+        m.put("storage", b.getStorage());
+        m.put("jump_url", b.getJumpUrl());
+        m.put("description", b.getDescription());
+        m.put("sort_order", b.getSortOrder());
+        m.put("is_active", b.getIsActive());
+        m.put("created_at", b.getCreatedAt());
+        m.put("updated_at", b.getUpdatedAt());
+        return m;
+    }
+
     // ==================== 匹配规则 CRUD ====================
 
     public Map<String, Object> listRules(String keyword, int page, int pageSize) {

+ 58 - 4
static/index.html

@@ -64,6 +64,17 @@
     .brand-card-arrow{float:right;font-size:13px;color:#c4a035;margin-left:8px}
     .brand-more-btn{display:block;width:100%;padding:8px;margin-top:6px;background:#fffdf5;border:1px dashed #e0c080;border-radius:8px;font-size:12px;color:#b8860b;cursor:pointer;text-align:center;transition:all .15s}
     .brand-more-btn:hover{background:#fff8e1;border-color:#d4a017}
+    .brand-detail-overlay{position:fixed;inset:0;background:rgba(0,0,0,.4);display:flex;align-items:center;justify-content:center;z-index:200;padding:20px}
+    .brand-detail-box{background:#fff;border-radius:14px;max-width:420px;width:100%;max-height:85vh;overflow-y:auto;box-shadow:0 8px 32px rgba(0,0,0,.18)}
+    .brand-detail-header{position:sticky;top:0;background:#fff;padding:16px 18px 10px;border-bottom:1px solid #eee;z-index:1}
+    .brand-detail-header h2{font-size:17px;color:var(--bk);margin:0 0 4px}
+    .brand-detail-header .bd-close{float:right;font-size:22px;color:#aaa;cursor:pointer;line-height:1;padding:0 4px;background:none;border:none}
+    .brand-detail-body{padding:14px 18px 20px}
+    .brand-detail-body .bd-field{margin-bottom:14px}
+    .brand-detail-body .bd-label{font-size:12px;color:var(--sub);margin-bottom:4px;font-weight:600}
+    .brand-detail-body .bd-value{font-size:14px;color:var(--text);line-height:1.7;word-break:break-word}
+    .brand-detail-body .bd-link{font-size:13px;color:var(--bk);text-decoration:underline;cursor:pointer}
+    .brand-detail-body .bd-link:hover{color:var(--rd)}
     .input-area{display:flex;padding:10px 14px;background:#fff;border-top:1px solid #e0e0e0;gap:8px;flex-shrink:0;z-index:10}
         #previewArea{flex-shrink:0}
     .input-area input{flex:1;padding:10px 14px;border:1px solid #ddd;border-radius:24px;font-size:14px;outline:none;min-height:44px}
@@ -403,12 +414,17 @@
         var tag=m.intent||'';
         var tagHtml=tag?'<div class="tag '+tagClass(tag)+'">'+intentLabel(tag)+'</div>':'<div class="tag tag-fallback">AI 药典助手</div>';
         div.innerHTML=tagHtml+'<div class="content">'+md2html(m.content)+'</div>';
-        // 恢复历史记录中的来源引用
+        // 恢复历史记录中的来源引用和品牌推荐
         if (m.sources && m.sources.length) {
           sourcesData = m.sources;
           div.innerHTML += buildSourceTags();
           sourcesData = [];
         }
+        if (m.brand_recommendations && m.brand_recommendations.length) {
+          brandRecsData = m.brand_recommendations;
+          div.innerHTML += buildBrandRecs();
+          brandRecsData = [];
+        }
       }
       document.getElementById('chat').appendChild(div);
     }
@@ -471,7 +487,6 @@
       if(!r.ok){contentDiv.innerHTML=md2html('请求失败: HTTP '+r.status);aiDiv.classList.remove('streaming');STREAMING=false;resetSendBtn();return}
       sourcesData=[];
       brandRecsData=[];
-      brandRecsData=[];
       var reader=r.body.getReader(),decoder=new TextDecoder(),buffer='',fullText='',eventType='';
       while(true){
         var{ done, value }=await reader.read();
@@ -609,7 +624,8 @@
         h+='<div class="brand-card-desc">'+esc(desc)+'</div>';
         h+='</a>';
       } else {
-        h+='<div class="brand-card" style="cursor:default">';
+        h+='<div class="brand-card" onclick="showBrandDetail('+JSON.stringify(r)+')" style="cursor:pointer">';
+        h+='<span class="brand-card-arrow" style="font-size:11px">查看详情 →</span>';
         h+='<div class="brand-card-name">'+esc(name)+'</div>';
         h+='<div class="brand-card-desc">'+esc(desc)+'</div>';
         h+='</div>';
@@ -631,7 +647,8 @@
           h+='<div class="brand-card-desc">'+esc(edesc)+'</div>';
           h+='</a>';
         } else {
-          h+='<div class="brand-card" style="cursor:default">';
+          h+='<div class="brand-card" onclick="showBrandDetail('+JSON.stringify(er)+')" style="cursor:pointer">';
+          h+='<span class="brand-card-arrow" style="font-size:11px">查看详情 →</span>';
           h+='<div class="brand-card-name">'+esc(ename)+'</div>';
           h+='<div class="brand-card-desc">'+esc(edesc)+'</div>';
           h+='</div>';
@@ -644,6 +661,43 @@
     return h;
   }
 
+  window.showBrandDetail=function(brand){
+    var fields=[
+      {label:'功能主治',key:'function'},
+      {label:'用法用量',key:'usage_dosage'},
+      {label:'禁忌',key:'contraindication'},
+      {label:'成份',key:'ingredients'},
+      {label:'性状',key:'properties'},
+      {label:'规格',key:'specification'},
+      {label:'不良反应',key:'adverse_reactions'},
+      {label:'注意事项',key:'precautions'},
+      {label:'执行标准',key:'execution_standard'},
+      {label:'贮藏',key:'storage'}
+    ];
+    var body='';
+    for(var i=0;i<fields.length;i++){
+      var val=brand[fields[i].key]||'';
+      if(val){
+        body+='<div class="bd-field"><div class="bd-label">'+fields[i].label+'</div><div class="bd-value">'+esc(val)+'</div></div>';
+      }
+    }
+    if(!body)body='<div style="padding:20px;text-align:center;color:#aaa">暂无详细信息</div>';
+    if(brand.jump_url){
+      body+='<div class="bd-field"><a class="bd-link" href="'+escAttr(brand.jump_url)+'" target="_blank" rel="noopener">🔗 查看商品详情 →</a></div>';
+    }
+    var overlay=document.createElement('div');
+    overlay.className='brand-detail-overlay';
+    overlay.onclick=function(e){if(e.target===this)this.remove()};
+    overlay.innerHTML='<div class="brand-detail-box">'+
+      '<div class="brand-detail-header">'+
+        '<button class="bd-close" onclick="this.closest(\'.brand-detail-overlay\').remove()">×</button>'+
+        '<h2>💊 '+esc(brand.brand_name||'')+'</h2>'+
+      '</div>'+
+      '<div class="brand-detail-body">'+body+'</div>'+
+    '</div>';
+    document.body.appendChild(overlay);
+  };
+
   function buildSourceTags(){
     if(!sourcesData||!sourcesData.length)return'';
     // 按药名分组