# 知识库初始版(蚕病百科 + AI 结果解读)Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 交付知识库初始版:Go 后端蚕病百科/知识文章 API + 规格书种子数据,Web 后台管理页,小程序农户查看页。 **Architecture:** 沿用现有分层(Go/Gin/GORM、React/AntD Pro、Taro 4)。新增 `diseases`(蚕病百科)与 `knowledge_articles`(AI 结果解读等文章)两表;CRUD 复用现有 handler 模式与 `bindUpdates`;种子数据启动时幂等写入;权限码 `knowledge:read` / `knowledge:write` 控制读写。 **Tech Stack:** Go 1.23 / Gin / GORM(PostgreSQL);React 19 / antd 5 / @ant-design/pro-components;Taro 4 + React + Sass。 ## Global Constraints - 不得修改生成产物(`miniapp/dist/`、`web/dist/`);只改源文件。 - 蚕病内容以规格书 `.cc-connect/attachments/蚕病智能防控平台规格说明书.md` 附录 11.1、3.1.4(风险分级)、3.8(处置方向/排查清单)为准,不编造。 - 风险分级阈值(绿 0-30 / 黄 31-60 / 橙 61-80 / 红 81-100)来自规格书 3.1.4。 - 权限:读 `knowledge:read`,写 `knowledge:write`。 - 字段风格沿用现有约定:Go JSON camelCase、DB 列 snake_case、web/miniapp 接口类型与后端 JSON 一致。 - 验证命令:后端 `go test ./... && go build ./... && go vet ./...`;Web `npm run lint && npm run build`;小程序 `npm run build:weapp`。 - 本仓库当前无前端测试基建(Vitest/Jest 均未配置,属后续工作计划 #27),UI 验证以 lint + 类型检查 + 构建为准;Go 侧对纯逻辑(种子数据)写单元测试(TDD)。 --- ### Task 1: Go 模型 + 种子数据测试(TDD RED) **Files:** - Create: `server-go/internal/model/knowledge.go` - Test: `server-go/internal/model/knowledge_seed_test.go` **Interfaces:** - Produces: `model.Disease`、`model.KnowledgeArticle` 结构体;包变量 `model.SeedDiseases []Disease`、`model.SeedKnowledgeArticles []KnowledgeArticle`(Task 2 填充内容)。 - [ ] **Step 1: 写失败测试**(先建 `knowledge.go` 只含结构体定义,不含种子变量) `server-go/internal/model/knowledge_seed_test.go`: ```go package model import "testing" func TestSeedDiseasesCoverCoreFour(t *testing.T) { core := []string{"核型多角体病", "白僵病", "软化病", "微粒子病"} got := map[string]bool{} for _, d := range SeedDiseases { got[d.Name] = true } for _, name := range core { if !got[name] { t.Errorf("核心蚕病缺失: %s", name) } } } func TestSeedDiseasesRequiredFields(t *testing.T) { for _, d := range SeedDiseases { if d.Name == "" || d.Category == "" { t.Errorf("病种名称/类别不能为空: %+v", d) } if d.Pathogen == "" || d.Symptoms == "" || d.Transmission == "" || d.Prevention == "" { t.Errorf("病种必备字段缺失(病原/症状/传播途径/防治指南): %s", d.Name) } } } func TestSeedDiseasesNamesUnique(t *testing.T) { seen := map[string]bool{} for _, d := range SeedDiseases { if seen[d.Name] { t.Errorf("病种名称重复: %s", d.Name) } seen[d.Name] = true } } func TestSeedKnowledgeArticlesHasAIGuide(t *testing.T) { found := false for _, a := range SeedKnowledgeArticles { if a.Kind == "ai_guide" && a.Title != "" && a.Content != "" { found = true } } if !found { t.Error("缺少 AI 结果解读文章(kind=ai_guide)") } } ``` - [ ] **Step 2: 运行测试确认失败** Run: `cd server-go && go test ./internal/model/ -run TestSeed -v` Expected: 编译失败 `undefined: SeedDiseases` / `undefined: SeedKnowledgeArticles`(或测试失败) - [ ] **Step 3: 最小实现**:`knowledge.go` 定义结构体(见下方 Task 2 代码中结构体部分),并声明空变量 `var SeedDiseases []Disease`、`var SeedKnowledgeArticles []KnowledgeArticle` 使编译通过、测试失败。 - [ ] **Step 4: 再运行测试,确认失败原因是数据缺失**(如 `核心蚕病缺失: 核型多角体病`),而非编译错误。 - [ ] **Step 5: Commit** ```bash git add server-go/internal/model/knowledge.go server-go/internal/model/knowledge_seed_test.go git commit -m "test(knowledge): seed 数据完整性测试(RED)" ``` --- ### Task 2: Go 种子数据填充(TDD GREEN) **Files:** - Create: `server-go/internal/model/knowledge_seed.go`(完整内容见下方代码块) **Interfaces:** - Consumes: Task 1 的 `Disease` / `KnowledgeArticle` 结构体。 - Produces: `SeedDiseases`(9 条,含 4 类核心病种)、`SeedKnowledgeArticles`(1 条 AI 结果解读)。字段值全部来自规格书附录 11.1 与 3.1.4;`Prevention`(防治指南)按规格书 3.8 处置方向(内源扩散/外源侵入)与分病种排查清单整理。 - [ ] **Step 1: 写入种子数据**(代码见下) ```go package model // SeedDiseases 蚕病百科种子数据(内容来源:规格书附录 11.1 / 3.8) var SeedDiseases = []Disease{ { Name: "核型多角体病", Category: "viral", SortOrder: 1, Pathogen: "家蚕核型多角体病毒(杆状病毒科,双链DNA病毒)", Transmission: "食下传染为主,创伤传染次之", Medium: "多角体(2-6μm包涵体,存在于病蚕血液、脓汁、粪便中,污染蚕座、桑叶、蚕具)", Incubation: "3-6天", Symptoms: "体色乳白、环节肿胀如竹节、体壁紧张发亮、狂躁爬行、体壁易破流乳白色脓汁", HighRiskStage: "5龄后期至蛹期,春蚕期较多发", HighRiskCondition: "温度突变、密集饲养等环境应激可诱发潜伏感染转急性", LethalTime: "感染后3-5天", SpreadTrend: "病蚕体壁破裂流脓→脓汁污染蚕座桑叶→健康蚕食下感染→群体内快速扩散", Recurrence: "多角体环境存活2-3年,不彻底消毒则跨批次跨年度反复发病", Detection: "LAMP 靶标:polh 基因(已有引物序列)", Prevention: "立即隔离病蚕并深埋;对发病区域及周边彻底消毒;上批次发病后升级消毒方案(含蚕具浸泡);排查蚕具跨批次共用、桑叶来源变更、人员进出记录;保持温湿度稳定、控制饲养密度", }, { Name: "白僵病", Category: "fungal", SortOrder: 2, Pathogen: "白僵菌(丛梗孢科,兼性寄生菌——既有寄生能力又能腐生)", Transmission: "接触传染为主(分生孢子接触蚕体壁→发芽侵入→体腔内繁殖),创伤传染次之,一般不能食下传染", Medium: "分生孢子,通过气流扩散,是真菌病传播的核心媒介", Incubation: "2-6天", Symptoms: "体色暗淡、油渍斑、行动呆滞、死蚕僵硬覆白粉", HighRiskStage: "温度25-30℃、湿度>75%时孢子萌发,连续阴雨高发", HighRiskCondition: "湿度>75%是孢子萌发关键条件,连续阴雨", LethalTime: "小蚕2-3天,大蚕5-6天", SpreadTrend: "点发→面发→暴发;尸体持续产孢,适宜湿度下指数级扩散", Recurrence: "白僵菌广泛寄生于桑园害虫和野外昆虫,可腐生存活,极难彻底清除", Detection: "早期无症状识别难,需结合湿度监测与 AI 巡检", Prevention: "立即隔离病蚕并深埋,防止尸体产孢扩散;湿度>75%时加强通风排湿;检查并加装通风口过滤网;加强人员/蚕具进出管控;蚕房周边昆虫防治、桑园害虫治理", }, { Name: "软化病", Category: "bacterial", SortOrder: 3, Pathogen: "肠道致病菌(多种细菌)", Transmission: "食下传染,环境恶化引发肠道菌群失调,与饲养管理密切相关", Medium: "污染桑叶、蚕座环境", Incubation: "病程缓慢,5龄集中暴发", Symptoms: "食桑减少、排念珠状粪、胸部半透明(空头蚕)、群体发育不齐", HighRiskStage: "蚕头密度大、通风不良、桑叶潮湿,5龄期集中暴发", HighRiskCondition: "密度过大、通风不良、桑叶潮湿", SpreadTrend: "往往群体性发生,同批次扩散;常存在混合感染", Recurrence: "与饲养管理密切相关,密度/通风/桑叶卫生不改善易反复", Detection: "与质型多角体病肉眼难以区分,需分子检测(16S rRNA / LAMP)确认", Prevention: "控制蚕头密度、加强通风、避免桑叶潮湿;发现病蚕及时隔离;桑叶来源批次追溯,排查桑园害虫粪便污染;混合感染风险高,建议结合检测确认病原", }, { Name: "微粒子病", Category: "protozoan", SortOrder: 4, Pathogen: "家蚕微粒子虫(Nosema bombycis),细胞内专性寄生的微孢子虫", Transmission: "双重传播:食下传染(水平传播)+ 胚种传染(垂直传播,经卵传给下一代)", Medium: "孢子,通过粪便、蚕蜕、尸体污染环境;通过卵细胞侵入子代", Incubation: "慢性,可整个幼虫期无明显外观异常", Symptoms: "发育不齐、群体陆续死亡、蚕体瘦小", HighRiskStage: "种蚕生产中发病率可达50-60%;跨代累积,越种越弱", HighRiskCondition: "蚕种来源不明、消毒不彻底", SpreadTrend: "水平传播(病蚕排孢污染环境)+ 垂直传播(带毒母蛾产卵→子代先天感染→跨代累积)", Recurrence: "唯一法定检疫对象;胚种传染使带毒批次代代相传", Detection: "法定检疫:母蛾镜检;qPCR 已开展推广培训;LAMP 可参考文献设计引物", Prevention: "种场必须逐批检测,蚕种来源全链追溯(母蛾镜检→蚕种批次→种场供种链);隔离发病批次;可喷施“防微灵”(国内唯一获兽药批文的治疗药物)后饲蚕;排查桑园周边野外昆虫交叉感染", }, { Name: "质型多角体病", Category: "viral", SortOrder: 5, Pathogen: "家蚕质型多角体病毒(呼肠孤病毒科,双链RNA病毒)", Transmission: "食下传染(经口),侵入中肠上皮细胞", Medium: "多角体随病蚕粪便排出,持续污染蚕座和桑叶", Incubation: "病程较缓慢", Symptoms: "发育缓慢、体躯瘦小、呆滞、体色略带乳白、排出带乳白色的软粪", HighRiskStage: "夏秋蚕期多发(与核型多角体病的春蚕期形成对比)", HighRiskCondition: "夏秋蚕期高温闷热", SpreadTrend: "病蚕排毒(粪便含病毒)→蚕座环境污染→同群体经口反复感染;病程缓慢但粪便持续污染使群体发病率累积上升;可通过卵传给下一代", Recurrence: "可通过卵传代,在种群中形成病毒流行病", Detection: "症状与软化病肉眼难以区分,需分子检测确认", Prevention: "及时清除病蚕与蚕座污染物,防止粪便持续排毒;夏秋高温闷热期加强通风降温;同区域其他蚕房发病关联排查", }, { Name: "浓核病/传染性软化病", Category: "viral", SortOrder: 6, Pathogen: "浓核病毒(BmDNV)、二分浓核病毒(BmBDV)、传染性软化病毒(BmIFV)", Transmission: "食下感染", Medium: "传染源主要来自桑园害虫粪便污染桑叶", Incubation: "—", Symptoms: "空头、下痢、吐液(与质型多角体病肉眼难以区分)", HighRiskStage: "夏秋蚕期多发", HighRiskCondition: "桑叶被桑园害虫粪便污染", SpreadTrend: "桑园害虫粪便污染桑叶→食下感染→慢性消耗死亡或继发细菌感染;常存在混合感染", Recurrence: "病原来自桑园害虫粪便污染,桑园防治不到位易反复", Detection: "裸露病毒,不形成包涵体(多角体),普通显微镜无法观察,诊断难度大", Prevention: "桑叶来源批次追溯(哪个桑园、哪天采摘);核查桑园近期害虫防治记录;保证桑叶储运条件;识别混合感染(常与质型多角体病混合)", }, { Name: "细菌性败血病", Category: "bacterial", SortOrder: 7, Pathogen: "苏云金杆菌等多种细菌", Transmission: "创伤传染——细菌通过蚕体表伤口侵入血腔", Medium: "环境中广泛存在的细菌,通过蚕具刮伤、蚕互相抓伤等创口侵入", Incubation: "—", Symptoms: "密集饲养下蚕体互相抓伤概率增大→个体发病死亡→尸体腐烂细菌扩散", HighRiskStage: "密集饲养、蚕具粗糙时多发", HighRiskCondition: "蚕具刮伤、操作粗暴、消毒不彻底", SpreadTrend: "个体发病死亡→尸体腐烂细菌扩散→接触传染加重", Recurrence: "反复发作时需排查蚕具毛刺/操作粗暴/消毒盲区", Detection: "—", Prevention: "排查蚕具是否有尖锐边缘/毛刺;控制蚕头密度减少相互抓伤;给桑操作轻柔;反复发作时排查消毒盲区并升级消毒", }, { Name: "猝倒病", Category: "bacterial", SortOrder: 8, Pathogen: "细菌毒素(如苏云金杆菌毒素)", Transmission: "食下传染——蚕食入细菌产生的毒素", Medium: "桑叶被细菌污染", Incubation: "急性中毒,发病快、死亡快", Symptoms: "急性中毒,发病快、死亡快", HighRiskStage: "—", HighRiskCondition: "桑叶储运环节污染", SpreadTrend: "与桑叶污染直接相关", Recurrence: "桑叶储运环节污染或蚕房环境", Detection: "—", Prevention: "桑叶来源批次追溯并检查储运条件;发现后立即更换桑叶来源;避免桑叶被细菌污染", }, { Name: "其他真菌病(绿僵病/黄僵病/曲霉病)", Category: "fungal", SortOrder: 9, Pathogen: "金龟子绿僵菌(绿僵病)、曲霉菌(曲霉病)、黄僵病病原(较少见)", Transmission: "接触传染、气流扩散(真菌分生孢子)", Medium: "分生孢子", Incubation: "—", Symptoms: "绿僵病死蚕呈绿色;黄僵病较少见;曲霉病多发于小蚕期", HighRiskStage: "曲霉病多发于小蚕期;高温高湿季真菌病暴发", HighRiskCondition: "高温高湿、蚕座卫生差", SpreadTrend: "绿僵病多为野外昆虫交叉感染", Recurrence: "真菌孢子兼性腐生,环境残留难彻底清除", Detection: "按死蚕孢子颜色肉眼初步区分", Prevention: "与白僵病防控一致:加强通风排湿(湿度>75%重点防范);小蚕期注意蚕座卫生防曲霉病;蚕房周边昆虫防治与桑园害虫治理", }, } // SeedKnowledgeArticles 知识文章种子数据(AI 结果解读,分级阈值来自规格书 3.1.4) var SeedKnowledgeArticles = []KnowledgeArticle{ { Kind: "ai_guide", Title: "AI 巡检结果怎么看(风险分级解读)", Summary: "了解 AI 拍照巡检返回的绿/黄/橙/红风险等级含义与建议动作", Content: "AI 巡检将蚕病风险分为四级:\n绿(0-30分):健康,正常巡检节奏,无需操作。\n黄(31-60分):需观察,增加巡检频次,人工复查、关注异常个体。\n橙(61-80分):疑似异常,触发分子检测任务(如 LAMP),按系统指引取样送检。\n红(81-100分):高度疑似/已发病,立即隔离病蚕、启动消毒,并触发专家会诊。\n\nAI 结果仅为筛查参考,最终确诊以分子检测(LAMP/qPCR)和专家诊断为准。", SortOrder: 1, }, } ``` - [ ] **Step 2: 运行测试确认通过** Run: `cd server-go && go test ./internal/model/ -run TestSeed -v` Expected: 4 个测试全部 PASS - [ ] **Step 3: Commit** ```bash git add server-go/internal/model/knowledge_seed.go git commit -m "feat(knowledge): 蚕病百科与 AI 解读种子数据" ``` --- ### Task 3: Go handler、路由、权限、迁移 **Files:** - Create: `server-go/internal/handler/knowledge.go` - Modify: `server-go/internal/model/permission_seed.go`(新增 2 条权限 + 角色映射) - Modify: `server-go/internal/database/db.go`(AutoMigrate 注册 + `seedKnowledge`) - Modify: `server-go/cmd/server/main.go`(注册 `RegisterKnowledgeRoutes`) **Interfaces:** - Consumes: Task 1-2 的 `model.Disease` / `model.KnowledgeArticle` / 种子变量;`handler.bindUpdates`(现有)。 - Produces: API `GET/POST/PATCH/DELETE /knowledge/diseases(/:id)`、`GET/POST/PATCH/DELETE /knowledge/articles(/:id)`;权限码 `knowledge:read` / `knowledge:write`。 `server-go/internal/handler/knowledge.go` 完整实现: ```go package handler import ( "net/http" "silk-server-go/internal/middleware" "silk-server-go/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // RegisterKnowledgeRoutes 注册知识库路由 func RegisterKnowledgeRoutes(rg *gin.RouterGroup, db *gorm.DB) { read := middleware.RequirePermission(db, "knowledge:read") write := middleware.RequirePermission(db, "knowledge:write") rg.GET("/knowledge/diseases", read, listDiseases(db)) rg.GET("/knowledge/diseases/:id", read, getDisease(db)) rg.POST("/knowledge/diseases", write, createDisease(db)) rg.PATCH("/knowledge/diseases/:id", write, updateDisease(db)) rg.DELETE("/knowledge/diseases/:id", write, deleteDisease(db)) rg.GET("/knowledge/articles", read, listArticles(db)) rg.GET("/knowledge/articles/:id", read, getArticle(db)) rg.POST("/knowledge/articles", write, createArticle(db)) rg.PATCH("/knowledge/articles/:id", write, updateArticle(db)) rg.DELETE("/knowledge/articles/:id", write, deleteArticle(db)) } func listDiseases(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { q := db.Model(&model.Disease{}) if cat := c.Query("category"); cat != "" { q = q.Where("category = ?", cat) } if kw := c.Query("keyword"); kw != "" { q = q.Where("name ILIKE ?", "%"+kw+"%") } if enabled := c.Query("enabled"); enabled != "" { q = q.Where("enabled = ?", enabled == "true") } var list []model.Disease q.Order("sort_order ASC, name ASC").Find(&list) c.JSON(http.StatusOK, list) } } func getDisease(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var d model.Disease if db.Where("id = ?", c.Param("id")).First(&d).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "disease not found"}) return } c.JSON(http.StatusOK, d) } } func createDisease(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var d model.Disease if err := c.ShouldBindJSON(&d); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } d.ID = "" if d.Name == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "病种名称不能为空"}) return } if err := db.Create(&d).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "创建失败"}) return } c.JSON(http.StatusCreated, d) } } func updateDisease(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") var d model.Disease if db.Where("id = ?", id).First(&d).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "disease not found"}) return } updates, err := bindUpdates(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if len(updates) > 0 { db.Model(&model.Disease{}).Where("id = ?", id).Updates(updates) } db.Where("id = ?", id).First(&d) c.JSON(http.StatusOK, d) } } func deleteDisease(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") var d model.Disease if db.Where("id = ?", id).First(&d).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "disease not found"}) return } db.Where("id = ?", id).Delete(&model.Disease{}) c.JSON(http.StatusOK, gin.H{"id": id}) } } func listArticles(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { q := db.Model(&model.KnowledgeArticle{}) if kind := c.Query("kind"); kind != "" { q = q.Where("kind = ?", kind) } if enabled := c.Query("enabled"); enabled != "" { q = q.Where("enabled = ?", enabled == "true") } var list []model.KnowledgeArticle q.Order("sort_order ASC, created_at DESC").Find(&list) c.JSON(http.StatusOK, list) } } func getArticle(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var a model.KnowledgeArticle if db.Where("id = ?", c.Param("id")).First(&a).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "article not found"}) return } c.JSON(http.StatusOK, a) } } func createArticle(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { var a model.KnowledgeArticle if err := c.ShouldBindJSON(&a); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } a.ID = "" if a.Kind == "" || a.Title == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "文章类型和标题不能为空"}) return } if err := db.Create(&a).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "创建失败"}) return } c.JSON(http.StatusCreated, a) } } func updateArticle(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") var a model.KnowledgeArticle if db.Where("id = ?", id).First(&a).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "article not found"}) return } updates, err := bindUpdates(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if len(updates) > 0 { db.Model(&model.KnowledgeArticle{}).Where("id = ?", id).Updates(updates) } db.Where("id = ?", id).First(&a) c.JSON(http.StatusOK, a) } } func deleteArticle(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("id") var a model.KnowledgeArticle if db.Where("id = ?", id).First(&a).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "article not found"}) return } db.Where("id = ?", id).Delete(&model.KnowledgeArticle{}) c.JSON(http.StatusOK, gin.H{"id": id}) } } ``` `server-go/internal/model/permission_seed.go` 追加: ```go {"knowledge:read", "知识库查看", "查看蚕病百科和知识文章"}, {"knowledge:write", "知识库管理", "新增、编辑、删除知识库内容"}, ``` 并在 `RolePermissionMap`:admin/operator 增加 `"knowledge:read", "knowledge:write"`;viewer/farmer 增加 `"knowledge:read"`。 `server-go/internal/database/db.go`: - `AutoMigrate` 列表追加 `&model.Disease{}, &model.KnowledgeArticle{},` - 新增 `seedKnowledge(db)`(幂等,按 `name` / `kind+title` 判重,不存在才 `Create`),并在 `Init` 中 `seedPermissions(db)` 之后调用。 `server-go/cmd/server/main.go`:在 `RegisterStorageRoutes` 后追加 `handler.RegisterKnowledgeRoutes(api, db)`。 - [ ] **Step 1: 修改代码** - [ ] **Step 2: 验证** Run: `cd server-go && go build ./... && go vet ./... && go test ./...` Expected: exit 0,无错误 - [ ] **Step 3: Commit** ```bash git add server-go/internal/handler/knowledge.go server-go/internal/model/permission_seed.go server-go/internal/database/db.go server-go/cmd/server/main.go git commit -m "feat(knowledge): 知识库 API、权限与迁移" ``` --- ### Task 4: Web 后台知识库页 **Files:** - Create: `web/src/dal/knowledge.ts` - Create: `web/src/pages/Knowledge.tsx` - Modify: `web/src/router.tsx`(注册 `/knowledge`,权限 `knowledge:read`) - Modify: `web/src/layout/BasicLayout.tsx`(菜单“知识库”,`BookOutlined`,权限 `knowledge:read`) **Interfaces:** - Consumes: 后端 `Disease` / `KnowledgeArticle` JSON 字段(与 Go JSON tag 完全一致)。 `web/src/dal/knowledge.ts`: ```ts import { get, post, patch, del } from '../api/http'; export interface Disease { id: string; name: string; category: string; pathogen?: string; transmission?: string; medium?: string; incubation?: string; symptoms?: string; highRiskStage?: string; highRiskCondition?: string; lethalTime?: string; spreadTrend?: string; recurrence?: string; detection?: string; prevention?: string; imageUrl?: string; sortOrder?: number; enabled?: boolean; createdAt?: string; updatedAt?: string; } export interface KnowledgeArticle { id: string; kind: string; title: string; summary?: string; content?: string; sortOrder?: number; enabled?: boolean; createdAt?: string; updatedAt?: string; } export const listDiseases = (params?: any) => get('/knowledge/diseases', { params }); export const getDisease = (id: string) => get(`/knowledge/diseases/${id}`); export const createDisease = (data: Partial) => post('/knowledge/diseases', data); export const updateDisease = (id: string, data: Partial) => patch(`/knowledge/diseases/${id}`, data); export const deleteDisease = (id: string) => del(`/knowledge/diseases/${id}`); export const listArticles = (params?: any) => get('/knowledge/articles', { params }); export const createArticle = (data: Partial) => post('/knowledge/articles', data); export const updateArticle = (id: string, data: Partial) => patch(`/knowledge/articles/${id}`, data); export const deleteArticle = (id: string) => del(`/knowledge/articles/${id}`); ``` `web/src/pages/Knowledge.tsx`:参照 `web/src/pages/Silkworm.tsx` 的 ProTable + Modal 风格: - 页面顶部 `Tabs`:Tab1「蚕病百科」,Tab2「AI 结果解读」。 - 蚕病百科 Tab:ProTable(rowKey=id,序号列、名称、类别 Tag、高发阶段、排序、状态、操作列 编辑/删除),toolBar「新建病种」;编辑/新建 Modal 表单含全部字段(名称、类别 Select[viral/fungal/bacterial/protozoan/other]、病原、传播途径、传染媒介、潜伏期、主要症状、高发阶段、高发条件、致死时间、传播趋势、连发性特征、检测方法、防治指南 TextArea、排序、启用 Switch)。 - AI 解读 Tab:ProTable(序号、标题、类型 Tag、摘要、排序、操作),编辑/新建 Modal 含 kind Select[ai_guide/lamp_guide/sers_guide/seasonal_tip]、标题、摘要、正文 TextArea、排序、启用。 - 类别中文映射:`{ viral: '病毒性', fungal: '真菌性', bacterial: '细菌性', protozoan: '原虫性', other: '其他' }`。 - 写操作按钮仅当 `authService.hasPermission('knowledge:write')` 时渲染;无权限时只读列表。 - [ ] **Step 1: 新增 dal 与页面,注册路由与菜单** - [ ] **Step 2: 验证** Run: `cd web && npm run lint && npm run build` Expected: exit 0,无 lint 错误,tsc + vite 构建通过 - [ ] **Step 3: Commit** ```bash git add web/src/dal/knowledge.ts web/src/pages/Knowledge.tsx web/src/router.tsx web/src/layout/BasicLayout.tsx git commit -m "feat(web): 知识库页面(蚕病百科 + AI 解读)" ``` --- ### Task 5: 小程序知识库查看页 **Files:** - Create: `miniapp/src/api/knowledge.ts` - Modify: `miniapp/src/types/index.ts`(追加 `Disease`、`KnowledgeArticle` 接口) - Create: `miniapp/src/pages/knowledge/index.tsx`、`index.config.ts`、`index.module.scss` - Create: `miniapp/src/pages/knowledge/detail/index.tsx`、`detail/index.config.ts`、`detail/index.module.scss` - Modify: `miniapp/src/pages/settings/index.tsx`(“监控管理”菜单追加“知识库”入口) - Modify: `miniapp/src/app.config.ts`(pages 追加两个页面) **Interfaces:** - Consumes: 后端 `GET /knowledge/diseases`、`GET /knowledge/articles?kind=ai_guide`。 `miniapp/src/api/knowledge.ts`: ```ts import { get } from './request'; export interface Disease { id: string; name: string; category: string; pathogen?: string; transmission?: string; medium?: string; incubation?: string; symptoms?: string; highRiskStage?: string; highRiskCondition?: string; lethalTime?: string; spreadTrend?: string; recurrence?: string; detection?: string; prevention?: string; sortOrder?: number; } export interface KnowledgeArticle { id: string; kind: string; title: string; summary?: string; content?: string; sortOrder?: number; } export const listDiseases = () => get('/knowledge/diseases'); export const listAIGuides = () => get('/knowledge/articles', { kind: 'ai_guide' }); ``` 页面行为: - `pages/knowledge/index`:页面标题“知识库”;顶部渲染 AI 结果解读卡片(`listAIGuides` 取第一条,展示标题+摘要,点击 `navigateTo('/pages/knowledge/detail/index?kind=ai_guide&id=xx')`);下方按类别分组渲染病种列表(类别中文 Tag + 病种名 + 症状摘要),点击进入 `detail?kind=disease&id=xx`。 - `pages/knowledge/detail`:按 `kind` 区分:`disease` 用 `get('/knowledge/diseases/'+id)` 渲染分段卡片(病原/传播途径/传染媒介/潜伏期/主要症状/高发阶段/高发条件/致死时间/传播趋势/连发性/检测方法/防治指南);`ai_guide` 用 `get('/knowledge/articles/'+id)` 渲染标题+正文(`white-space: pre-wrap`)。 - 样式参照 `miniapp/src/pages/settings/index.module.scss` 的卡片风格。 - `settings/index.tsx` 的 `handleMenuTap` 增加 `case 'knowledge': Taro.navigateTo({ url: '/pages/knowledge/index' }); break;`,菜单项文案“知识库”。 - [ ] **Step 1: 新增 api、类型与两个页面,注册路由与设置页入口** - [ ] **Step 2: 验证** Run: `cd miniapp && npm run build:weapp` Expected: exit 0,`miniapp/dist` 重新生成(生成产物不提交) - [ ] **Step 3: Commit** ```bash git add miniapp/src git commit -m "feat(miniapp): 知识库查看页" ``` --- ### Task 6: 全量验证与交付 - [ ] **Step 1: 后端** Run: `cd server-go && go test ./... && go build ./... && go vet ./...` - [ ] **Step 2: Web** Run: `cd web && npm run lint && npm run build` - [ ] **Step 3: 小程序** Run: `cd miniapp && npm run build:weapp` - [ ] **Step 4: 检查变更范围** Run: `git status --short`,确认只包含知识库相关源文件,未动生成产物。 - [ ] **Step 5: 交付说明** 汇总:修改文件清单、验证输出、种子数据说明(9 病种 + 1 AI 文章)、风险提示(handler 端到端 API 需 PostgreSQL 运行环境联调;前端测试基建未配置,属于计划 #27)。