# 家庭天盘·传统文化 + 五维基础能量分 实现计划 > **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。 **目标:** 完善家庭天盘传统文化展示设计,基于生辰八字/星座/灵数计算五维基础能量分并落库,提供重算接口,前端三处增强(canvas 节点标注、成员弹窗、传统命理页)。 **架构:** 后端新增 `FiveDimensionScoreService` 计算服务,新建 `family_member_dimension_base_scores` 表存储五维基础分,支持手动/批量重算;前端改造现有天盘页面,在 canvas 节点、成员弹窗、新增传统命理页展示传统文化信息和五维基础分。 **技术栈:** Spring Boot 2.7 + MyBatis-Plus(后端);uni-app Vue 2 Options API(小程序前端);MySQL 8.0 --- ## 文件清单 | 操作 | 文件路径 | 职责 | |------|----------|------| | 创建 | `cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionScoreService.java` | 五维基础分计算/落库/重算服务 | | 创建 | `cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMemberDimensionBaseScore.java` | 五维基础分实体 | | 创建 | `cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyMemberDimensionBaseScoreMapper.java` | MyBatis-Plus Mapper 接口 | | 修改 | `cfc-backend/src/main/java/com/etotem/cfc/dto/TianpanMemberVO.java` | 新增 dimensionBaseScores 字段 | | 修改 | `cfc-backend/src/main/java/com/etotem/cfc/service/TianpanService.java` | 扩展 enrichMembers 填充 zodiacName 和 dimensionBaseScores | | 修改 | `cfc-backend/src/main/java/com/etotem/cfc/controller/tianpan/TianpanController.java` | 新增重算接口 | | 修改 | `cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` | 新增迁移 SQL | | 修改 | `cfc-backend/src/main/resources/schema.sql` | 同步新表定义 | | 创建 | `cfc-backend/src/test/java/com/etotem/cfc/unit/FiveDimensionScoreServiceTest.java` | 单元测试 | | 修改 | `cfc-frontend/pages/tianpan/index.vue` | canvas 节点标注 + 成员弹窗增强 | | 创建 | `cfc-frontend/pages/tianpan/traditional.vue` | 新增传统命理页 | | 修改 | `cfc-frontend/pages.json` | 注册新页面 | --- ## 任务列表 ### 任务 1:后端 — 数据库迁移和新表实体 **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java` - 创建:`cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMemberDimensionBaseScore.java` - 创建:`cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyMemberDimensionBaseScoreMapper.java` - 修改:`cfc-backend/src/main/resources/schema.sql` - [ ] **步骤 1:确认最新迁移编号** 在 `DatabaseInitializer.java` 末尾搜索 `// 迁移` 找到最新编号 N。 运行: ```bash grep -n "// 迁移" cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java | tail -5 ``` - [ ] **步骤 2:创建实体类** 创建 `cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMemberDimensionBaseScore.java`: ```java 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("family_member_dimension_base_scores") public class FamilyMemberDimensionBaseScore implements Serializable { @TableId(type = IdType.AUTO) private Long id; private Long memberId; private String memberType; private String dimensionCode; // body/mind/wisdom/action/wealth private Integer baseScore; private Date lastCalculatedAt; private String calculatedBy; // system/manual/admin } ``` - [ ] **步骤 3:创建 Mapper 接口** 创建 `cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyMemberDimensionBaseScoreMapper.java`: ```java package com.etotem.cfc.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.etotem.cfc.entity.FamilyMemberDimensionBaseScore; import org.apache.ibatis.annotations.Mapper; @Mapper public interface FamilyMemberDimensionBaseScoreMapper extends BaseMapper { } ``` - [ ] **步骤 4:在 schema.sql 添加建表语句** 在 `cfc-backend/src/main/resources/schema.sql` 末尾追加: ```sql CREATE TABLE IF NOT EXISTS family_member_dimension_base_scores ( id BIGINT AUTO_INCREMENT PRIMARY KEY, member_id BIGINT NOT NULL COMMENT '家庭成员ID', member_type VARCHAR(20) NOT NULL COMMENT '成员类型: child/parent', dimension_code VARCHAR(20) NOT NULL COMMENT '维度: body/mind/wisdom/action/wealth', base_score INT NOT NULL COMMENT '基础能量分(0-100)', last_calculated_at DATETIME NOT NULL COMMENT '最后计算时间', calculated_by VARCHAR(50) DEFAULT 'system' COMMENT '计算来源(system/manual)', UNIQUE KEY uk_member_dimension (member_id, dimension_code), INDEX idx_member (member_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维基础能量分(落库,支持重算)'; ``` - [ ] **步骤 5:在 DatabaseInitializer 添加迁移** 在 `DatabaseInitializer.java` 的 `runMigrations()` 末尾(迁移N之后)追加: ```java // 迁移N+1: 创建 family_member_dimension_base_scores 表 try { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_member_dimension_base_scores (" + "id BIGINT AUTO_INCREMENT PRIMARY KEY, " + "member_id BIGINT NOT NULL COMMENT '家庭成员ID', " + "member_type VARCHAR(20) NOT NULL COMMENT '成员类型: child/parent', " + "dimension_code VARCHAR(20) NOT NULL COMMENT '维度: body/mind/wisdom/action/wealth', " + "base_score INT NOT NULL COMMENT '基础能量分(0-100)', " + "last_calculated_at DATETIME NOT NULL COMMENT '最后计算时间', " + "calculated_by VARCHAR(50) DEFAULT 'system' COMMENT '计算来源', " + "UNIQUE KEY uk_member_dimension (member_id, dimension_code), " + "INDEX idx_member (member_id)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维基础能量分'"); log.info("已创建family_member_dimension_base_scores表"); } catch (Exception e) { // 表已存在,忽略错误 } ``` - [ ] **步骤 6:编译验证** 运行: ```bash cd cfc-backend && mvn clean compile ``` 预期:BUILD SUCCESS - [ ] **步骤 7:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java \ cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMemberDimensionBaseScore.java \ cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyMemberDimensionBaseScoreMapper.java \ cfc-backend/src/main/resources/schema.sql git commit -m "feat: 新增五维基础分表及迁移" ``` --- ### 任务 2:后端 — FiveDimensionScoreService 核心服务 **文件:** - 创建:`cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionScoreService.java` - 创建:`cfc-backend/src/test/java/com/etotem/cfc/unit/FiveDimensionScoreServiceTest.java` - [ ] **步骤 1:编写测试** 创建 `cfc-backend/src/test/java/com/etotem/cfc/unit/FiveDimensionScoreServiceTest.java`: ```java package com.etotem.cfc.unit; import com.etotem.cfc.service.FiveDimensionScoreService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.junit.jupiter.MockitoExtension; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) class FiveDimensionScoreServiceTest { @InjectMocks private FiveDimensionScoreService service; @Test void calcBaseScores_fullData_returnsCorrectScores() { // 八字五行 {wood:40, fire:60, earth:50, metal:30, water:20} // 星座狮子座(火象→心) // 灵数7(→智) Map wuxing = Map.of("wood", 40, "fire", 60, "earth", 50, "metal", 30, "water", 20); Map result = service.calcBaseScores(wuxing, "狮子座", 7); assertEquals(30, result.get("body")); // 50×0.6=30 assertEquals(56, result.get("mind")); // 60×0.6+20=56 assertEquals(38, result.get("wisdom")); // 30×0.6+20=38 assertEquals(24, result.get("action")); // 40×0.6=24 assertEquals(12, result.get("wealth")); // 20×0.6=12 } @Test void calcBaseScores_nullWuxing_returnsNeutral50() { Map result = service.calcBaseScores(null, null, null); assertEquals(50, result.get("body")); assertEquals(50, result.get("mind")); assertEquals(50, result.get("wisdom")); assertEquals(50, result.get("action")); assertEquals(50, result.get("wealth")); } @Test void calcBaseScores_masterNumber_11MapsTo2() { Map wuxing = Map.of("wood", 50, "fire", 50, "earth", 50, "metal", 50, "water", 50); Map result = service.calcBaseScores(wuxing, null, 11); // 11→2→心 assertEquals(60 + 20, result.get("mind")); // 土×0.6=50, 灵数11→2→心+20 } @Test void calcBaseScores_allZodiacSigns_coverage() { Map wuxing = Map.of("wood", 50, "fire", 50, "earth", 50, "metal", 50, "water", 50); // 火象 Map fireResult = service.calcBaseScores(wuxing, "白羊座", null); assertTrue(fireResult.get("mind") > 50); // 土象 Map earthResult = service.calcBaseScores(wuxing, "金牛座", null); assertTrue(earthResult.get("body") > 50); // 风象 Map airResult = service.calcBaseScores(wuxing, "双子座", null); assertTrue(airResult.get("action") > 50); // 水象 Map waterResult = service.calcBaseScores(wuxing, "巨蟹座", null); assertTrue(waterResult.get("wealth") > 50); } } ``` - [ ] **步骤 2:运行测试确认失败** ```bash cd cfc-backend && mvn test -pl . -Dtest=FiveDimensionScoreServiceTest ``` 预期:FAIL(方法不存在) - [ ] **步骤 3:实现服务** 创建 `cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionScoreService.java`: ```java package com.etotem.cfc.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.etotem.cfc.entity.FamilyMemberDimensionBaseScore; import com.etotem.cfc.mapper.FamilyMemberDimensionBaseScoreMapper; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; @Service public class FiveDimensionScoreService { @Resource private FamilyMemberDimensionBaseScoreMapper dimensionBaseScoreMapper; private static final Map ZODIAC_ELEMENT_TO_DIM = new LinkedHashMap<>(); private static final Map LIFE_NUMBER_DIM = new LinkedHashMap<>(); private static final String[] DIMENSIONS = {"body", "mind", "wisdom", "action", "wealth"}; static { // 星座 → 维度(火象→心, 土象→身, 风象→行, 水象→富) ZODIAC_ELEMENT_TO_DIM.put("白羊座", "mind"); ZODIAC_ELEMENT_TO_DIM.put("狮子座", "mind"); ZODIAC_ELEMENT_TO_DIM.put("射手座", "mind"); ZODIAC_ELEMENT_TO_DIM.put("金牛座", "body"); ZODIAC_ELEMENT_TO_DIM.put("处女座", "body"); ZODIAC_ELEMENT_TO_DIM.put("摩羯座", "body"); ZODIAC_ELEMENT_TO_DIM.put("双子座", "action"); ZODIAC_ELEMENT_TO_DIM.put("天秤座", "action"); ZODIAC_ELEMENT_TO_DIM.put("水瓶座", "action"); ZODIAC_ELEMENT_TO_DIM.put("巨蟹座", "wealth"); ZODIAC_ELEMENT_TO_DIM.put("天蝎座", "wealth"); ZODIAC_ELEMENT_TO_DIM.put("双鱼座", "wealth"); // 灵数 → 主维度 LIFE_NUMBER_DIM.put(1, "action"); LIFE_NUMBER_DIM.put(2, "mind"); LIFE_NUMBER_DIM.put(3, "wisdom"); LIFE_NUMBER_DIM.put(4, "body"); LIFE_NUMBER_DIM.put(5, "action"); LIFE_NUMBER_DIM.put(6, "mind"); LIFE_NUMBER_DIM.put(7, "wisdom"); LIFE_NUMBER_DIM.put(8, "wealth"); LIFE_NUMBER_DIM.put(9, "mind"); } /** * 计算五维基础能量分(纯算法,不落库) */ public Map calcBaseScores(Map wuxingElements, String westernSign, Integer lifeNumber) { Map scores = new HashMap<>(); for (String dim : DIMENSIONS) { scores.put(dim, 50); // 默认中性值 } // 1. 八字五行层(权重 60%) if (wuxingElements != null) { scores.put("action", Math.round((float) wuxingElements.getOrDefault("wood", 50) * 0.6)); scores.put("mind", Math.round((float) wuxingElements.getOrDefault("fire", 50) * 0.6)); scores.put("body", Math.round((float) wuxingElements.getOrDefault("earth", 50) * 0.6)); scores.put("wisdom", Math.round((float) wuxingElements.getOrDefault("metal", 50) * 0.6)); scores.put("wealth", Math.round((float) wuxingElements.getOrDefault("water", 50) * 0.6)); } // 2. 星座层(权重 20%) if (westernSign != null && ZODIAC_ELEMENT_TO_DIM.containsKey(westernSign)) { String dim = ZODIAC_ELEMENT_TO_DIM.get(westernSign); scores.put(dim, scores.get(dim) + 20); } // 3. 灵数层(权重 20%) if (lifeNumber != null) { int reduced = reduceMaster(lifeNumber); if (LIFE_NUMBER_DIM.containsKey(reduced)) { String dim = LIFE_NUMBER_DIM.get(reduced); scores.put(dim, scores.get(dim) + 20); } } return scores; } /** * 计算并落库(INSERT ON DUPLICATE KEY UPDATE) */ public void calcAndSave(Long memberId, String memberType, Map wuxingElements, String westernSign, Integer lifeNumber, String operator) { Map scores = calcBaseScores(wuxingElements, westernSign, lifeNumber); saveScores(memberId, memberType, scores, operator); } private void saveScores(Long memberId, String memberType, Map scores, String operator) { Date now = new Date(); for (Map.Entry entry : scores.entrySet()) { FamilyMemberDimensionBaseScore entity = new FamilyMemberDimensionBaseScore(); entity.setMemberId(memberId); entity.setMemberType(memberType); entity.setDimensionCode(entry.getKey()); entity.setBaseScore(entry.getValue()); entity.setLastCalculatedAt(now); entity.setCalculatedBy(operator); LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(FamilyMemberDimensionBaseScore::getMemberId, memberId) .eq(FamilyMemberDimensionBaseScore::getDimensionCode, entry.getKey()); FamilyMemberDimensionBaseScore existing = dimensionBaseScoreMapper.selectOne(qw); if (existing != null) { existing.setBaseScore(entry.getValue()); existing.setLastCalculatedAt(now); existing.setCalculatedBy(operator); dimensionBaseScoreMapper.updateById(existing); } else { entity.setId(null); dimensionBaseScoreMapper.insert(entity); } } } /** * 重算单个成员 */ public void recalibrate(Long memberId, String memberType, String operator) { // 从 family_member_attributes 读取源数据 // 简化:直接调用 calcAndSave,传入正确的源数据 // 实际调用方应负责准备 wuxingElements/westernSign/lifeNumber } /** * 查询五维基础分(供 dashboard 接口读取) */ public Map getScores(Long memberId) { Map scores = new LinkedHashMap<>(); for (String dim : DIMENSIONS) { scores.put(dim, 50); // 默认中性值 } LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(FamilyMemberDimensionBaseScore::getMemberId, memberId); List list = dimensionBaseScoreMapper.selectList(qw); for (FamilyMemberDimensionBaseScore item : list) { scores.put(item.getDimensionCode(), item.getBaseScore()); } return scores; } /** * 主数化简:11→2, 22→4, 33→6, 其他>9按数位和递归 */ private int reduceMaster(int n) { if (n == 11) return 2; if (n == 22) return 4; if (n == 33) return 6; while (n >= 10) { int sum = 0; while (n > 0) { sum += n % 10; n /= 10; } n = sum; } return n; } } ``` - [ ] **步骤 4:运行测试确认通过** ```bash cd cfc-backend && mvn test -pl . -Dtest=FiveDimensionScoreServiceTest ``` 预期:PASS - [ ] **步骤 5:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/service/FiveDimensionScoreService.java \ cfc-backend/src/test/java/com/etotem/cfc/unit/FiveDimensionScoreServiceTest.java git commit -m "feat: 新增五维基础分计算服务" ``` --- ### 任务 3:后端 — 扩展 TianpanService 和 DTO **文件:** - 修改:`cfc-backend/src/main/java/com/etotem/cfc/dto/TianpanMemberVO.java` - 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/TianpanService.java` - 修改:`cfc-backend/src/main/java/com/etotem/cfc/controller/tianpan/TianpanController.java` - [ ] **步骤 1:扩展 TianpanMemberVO** 在 `TianpanMemberVO.java` 中追加字段: ```java private Map dimensionBaseScores; // 五维基础分 private String zodiacName; // 生肖中文名(rat→鼠等) ``` - [ ] **步骤 2:修改 TianpanService.enrichMembers()** 在 `TianpanService.java` 的 `enrichMembers()` 方法(第 216 行附近)追加: ```java // 注入新服务 @Resource private FiveDimensionScoreService fiveDimensionScoreService; ``` 在 `enrichMembers()` 循环末尾(`vo.setEightCharacters(...)` 之后)追加: ```java // 生肖中文名 String zodiacName = findZodiacName(m.getZodiac()); vo.setZodiacName(zodiacName); // 五维基础分:优先读库,无则计算并落库 Map cachedScores = fiveDimensionScoreService.getScores(m.getMemberId()); boolean hasScores = cachedScores.values().stream().anyMatch(s -> s != 50); if (!hasScores) { fiveDimensionScoreService.calcAndSave( m.getMemberId(), m.getMemberType(), m.getWuxingElements(), vo.getWesternSign(), m.getLifeNumber(), "system"); cachedScores = fiveDimensionScoreService.getScores(m.getMemberId()); } vo.setDimensionBaseScores(cachedScores); ``` 添加辅助方法: ```java private String findZodiacName(String zodiacCode) { if (zodiacCode == null) return null; Map nameMap = new LinkedHashMap<>(); nameMap.put("rat", "鼠"); nameMap.put("ox", "牛"); nameMap.put("tiger", "虎"); nameMap.put("rabbit", "兔"); nameMap.put("dragon", "龙"); nameMap.put("snake", "蛇"); nameMap.put("horse", "马"); nameMap.put("sheep", "羊"); nameMap.put("monkey", "猴"); nameMap.put("rooster", "鸡"); nameMap.put("dog", "狗"); nameMap.put("pig", "猪"); return nameMap.get(zodiacCode); } ``` - [ ] **步骤 3:新增重算接口** 在 `TianpanController.java` 追加: ```java @PostMapping("/recalibrate") public Result> recalibrate(@RequestBody Map params) { Long memberId = Long.valueOf(params.get("memberId").toString()); String memberType = params.get("memberType") != null ? params.get("memberType").toString() : "child"; String operator = params.get("operator") != null ? params.get("operator").toString() : "admin"; // 从 family_member_attributes 读取源数据并重算 FamilyMemberAttributes attrs = familyMemberAttributeService.getByMember(memberId, memberType); if (attrs == null) { return Result.error("成员属性不存在"); } Map wuxingElements = parseJsonInt(attrs.getWuxingElements()); String westernSign = null; if (attrs.getBirthDatetime() != null) { Calendar cal = Calendar.getInstance(); cal.setTime(attrs.getBirthDatetime()); westernSign = zodiacAnnualEnergyService.getWesternSign(cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)); } String behaviorModifier = attrs.getBehaviorModifier(); Integer lifeNumber = null; if (behaviorModifier != null) { try { Map bm = objectMapper.readValue(behaviorModifier, Map.class); Object ln = bm.get("lifeNumber"); if (ln instanceof Number) lifeNumber = ((Number) ln).intValue(); } catch (Exception e) { /* ignore */ } } fiveDimensionScoreService.calcAndSave(memberId, memberType, wuxingElements, westernSign, lifeNumber, operator); Map scores = fiveDimensionScoreService.getScores(memberId); return Result.success(scores); } ``` - [ ] **步骤 4:编译验证** ```bash cd cfc-backend && mvn clean compile ``` 预期:BUILD SUCCESS - [ ] **步骤 5:Commit** ```bash git add cfc-backend/src/main/java/com/etotem/cfc/dto/TianpanMemberVO.java \ cfc-backend/src/main/java/com/etotem/cfc/service/TianpanService.java \ cfc-backend/src/main/java/com/etotem/cfc/controller/tianpan/TianpanController.java git commit -m "feat: 扩展天盘服务支持五维基础分展示和重算" ``` --- ### 任务 4:小程序 — 天盘首页增强 **文件:** - 修改:`cfc-frontend/pages/tianpan/index.vue` - [ ] **步骤 1:增强 drawMemberNodes** 在 `drawMemberNodes` 方法(第 375 行附近)的成员节点绘制后追加: ```javascript // 在 ctx.fillText(member.nickname || member.name, x, y) 之后追加 // 绘制生肖+灵数标注 var showLabel = member.zodiacName || member.lifeNumber if (showLabel) { var labelParts = [] if (member.zodiacName) labelParts.push(member.zodiacName) if (member.lifeNumber) labelParts.push('灵数' + member.lifeNumber) var label = labelParts.join('·') ctx.font = '9px sans-serif' ctx.fillStyle = this.getMemberColor(member.effectiveRole) ctx.textAlign = 'center' ctx.textBaseline = 'top' ctx.fillText(label, x, y + 18) } ``` - [ ] **步骤 2:增强成员弹窗** 在模板中(第 98-119 行),基本信息区追加八字和灵数,能量区追加五维基础分: ```html 灵数: {{ selectedMember.lifeNumber || '-' }} 八字: {{ getEightCharsText(selectedMember.eightCharacters) }} ``` 在 methods 中追加: ```javascript getEightCharsText: function(eightChars) { if (!eightChars) return '' var parts = [] var keys = ['year', 'month', 'day', 'hour'] for (var i = 0; i < keys.length; i++) { if (eightChars[keys[i]]) parts.push(eightChars[keys[i]]) } return parts.join(' ') }, ``` 在 `.member-info-row` 之后追加五维基础分展示: ```html {{ getDimLabel(key) }} {{ val }} ``` 在 methods 中追加: ```javascript getKey: function(key) { return 'dim-' + key }, getDimLabel: function(key) { var labels = { body: '身', mind: '心', wisdom: '智', action: '行', wealth: '富' } return labels[key] || key }, getDimColor: function(key) { var colors = { body: '#FF8C42', mind: '#FF6B9D', wisdom: '#6366F1', action: '#10B981', wealth: '#F59E0B' } return colors[key] || '#6B7280' } ``` - [ ] **步骤 3:语法检查** 运行: ```bash node -c cfc-frontend/pages/tianpan/index.vue 2>&1 || echo "需要检查script块语法" ``` 由于这是 .vue 文件,手动检查 script 块语法: ```bash sed -n '/ ``` - [ ] **步骤 3:语法检查** ```bash sed -n '/