diff --git a/README.md b/README.md index d71f99a..d0ab7c3 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,6 @@ yarn dev | `txw-gxzx-web` | 个人工作台 | | `txw-kxtfwzx-web` | 科学碳体重中心 | | `txw-mhzc-web` | 碳门户主页 | -| `txw-tzzx-web` | 碳资讯 | -| `txw-ytzx-web` | 碳云学堂 | | `txw-yygl-web` | 运营管理 | | `local-nodemodules` | 本地私有包(ggzc-web 组件库) | diff --git a/docs/superpowers/plans/2026-04-10-search-plan.md b/docs/superpowers/plans/2026-04-10-search-plan.md new file mode 100644 index 0000000..4156ede --- /dev/null +++ b/docs/superpowers/plans/2026-04-10-search-plan.md @@ -0,0 +1,1709 @@ +# 全站搜索功能实现计划 + +> **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:** 为碳信网实现全站搜索功能,包括搜索页面、API接口、搜索建议、热门搜索、搜索历史 + +**Architecture:** 前端聚合在 txw-mhzc-web(搜索结果页),后端聚合在 txw-mhzc(SearchController + SearchService)。搜索聚合服务第一阶段只搜索资讯中心数据。 + +**实现策略:** +- 搜索历史:Phase 1 使用 localStorage(无用户认证),后续接入后端 API +- 热门搜索:Phase 1 返回硬编码数据,后续从数据库读取 + +**Tech Stack:** Vue 2 + TDesign + Vue Router (前端), Spring Boot + MyBatis Plus (后端) + +--- + +## 文件结构 + +### 前端 (txw-mhzc-web) + +``` +txw-mhzc-web/src/pages/index/ +├── views/ +│ └── search/ +│ └── index.vue # 搜索结果页 [新建] +├── components/ +│ └── search/ +│ ├── SearchArea.vue # 搜索区域 [新建] +│ ├── SearchSuggestion.vue # 搜索建议下拉 [新建] +│ ├── SearchHistoryBar.vue # 搜索历史横条 [新建] +│ ├── SearchCategoryTabs.vue # 分类Tab [新建] +│ ├── SearchResultList.vue # 结果列表 [新建] +│ └── SearchResultItem.vue # 结果项 [新建] +└── api/ + └── search.js # 搜索接口 [新建] +``` + +### 后端 (txw-mhzc) + +``` +txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/ +├── controller/ +│ └── SearchController.java # 搜索接口 [新建] +├── service/ +│ ├── SearchService.java # 搜索服务接口 [新建] +│ └── impl/ +│ └── SearchServiceImpl.java # 搜索服务实现 [新建] +├── mapper/ +│ └── SearchMapper.java # 搜索Mapper [新建] +└── pojo/ + ├── req/ + │ └── SearchReqVO.java # 搜索请求 [新建] + └── vo/ + ├── SearchResultVO.java # 搜索结果 [新建] + └── HotSearchConfig.java # 热门搜索配置 [新建] +``` + +--- + +## 后端任务 + +### Task 1: 创建搜索请求 VO + +**Files:** +- Create: `txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/pojo/req/SearchReqVO.java` + +- [ ] **Step 1: 创建 SearchReqVO** + +```java +package com.css.txw.mhzc.pojo.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.hibernate.validator.constraints.NotBlank; + +@Data +@Schema(description = "搜索请求") +public class SearchReqVO { + + @Schema(description = "关键词", required = true) + @NotBlank(message = "关键词不能为空") + @Length(max = 50, message = "关键词不能超过50个字符") + private String keyword; + + @Schema(description = "分类类型: all, carbon_cert, service, news") + private String categoryType = "all"; + + @Schema(description = "页码") + private Integer page = 1; + + @Schema(description = "每页条数") + private Integer pageSize = 10; +} +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/pojo/req/SearchReqVO.java +git commit -m "feat(search): 添加搜索请求VO" +``` + +--- + +### Task 2: 创建搜索结果 VO + +**Files:** +- Create: `txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/pojo/vo/SearchResultVO.java` + +- [ ] **Step 1: 创建 SearchResultVO** + +```java +package com.css.txw.mhzc.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "搜索结果") +public class SearchResultVO { + + @Schema(description = "内容ID") + private String id; + + @Schema(description = "标题(关键词高亮)") + private String title; + + @Schema(description = "摘要") + private String summary; + + @Schema(description = "缩略图URL") + private String thumbnail; + + @Schema(description = "分类标签(展示用)") + private String category; + + @Schema(description = "分类类型(筛选用)") + private String categoryType; + + @Schema(description = "来源名称") + private String source; + + @Schema(description = "来源类型") + private String sourceType; + + @Schema(description = "发布时间") + private String publishTime; + + @Schema(description = "跳转链接") + private String url; +} +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/pojo/vo/SearchResultVO.java +git commit -m "feat(search): 添加搜索结果VO" +``` + +--- + +### Task 3: 创建搜索服务接口和实现 + +**Files:** +- Create: `txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/SearchService.java` +- Create: `txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/impl/SearchServiceImpl.java` + +- [ ] **Step 1: 创建 SearchService 接口** + +```java +package com.css.txw.mhzc.service; + +import com.css.txw.mhzc.pojo.req.SearchReqVO; +import com.css.txw.mhzc.pojo.vo.SearchResultVO; +import java.util.List; + +public interface SearchService { + + /** + * 搜索 + */ + List search(SearchReqVO reqVO); + + /** + * 获取热门搜索词 + */ + List getHotSearchKeywords(); + + /** + * 获取搜索建议 + */ + List getSearchSuggestions(String keyword); + + /** + * 获取搜索历史 + */ + List getSearchHistory(); + + /** + * 清除搜索历史 + */ + void clearSearchHistory(); +} +``` + +- [ ] **Step 2: 创建 SearchServiceImpl 实现** + +```java +package com.css.txw.mhzc.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.css.ggzc.framework.common.pojo.CommonResult; +import com.css.txw.mhzc.pojo.req.SearchReqVO; +import com.css.txw.mhzc.pojo.vo.SearchResultVO; +import com.css.txw.mhzc.pojo.vo.SyzxxxVO; +import com.css.txw.mhzc.service.SearchService; +import com.css.txw.mhzc.service.TxwMhzcZxxxbService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Slf4j +public class SearchServiceImpl implements SearchService { + + @Resource + private TxwMhzcZxxxbService zxxxbService; + + private static final String HIGHLIGHT_START = ""; + private static final String HIGHLIGHT_END = ""; + + @Override + public List search(SearchReqVO reqVO) { + // 调用资讯接口获取数据 + CommonResult>> result = zxxxbService.zxxx(); + + List resultList = new ArrayList<>(); + + if (result != null && result.getData() != null) { + java.util.Map> data = result.getData(); + + // 从资讯数据中筛选 + data.forEach((category, list) -> { + list.forEach(zxxx -> { + // 简单匹配逻辑,实际应更复杂 + if (matchKeyword(zxxx.getTitle(), reqVO.getKeyword()) || + matchKeyword(zxxx.getContent(), reqVO.getKeyword())) { + + SearchResultVO vo = new SearchResultVO(); + vo.setId(zxxx.getId()); + vo.setTitle(highlightKeyword(zxxx.getTitle(), reqVO.getKeyword())); + vo.setSummary(highlightKeyword(truncate(zxxx.getContent(), 200), reqVO.getKeyword())); + vo.setCategory(category); + vo.setCategoryType(getCategoryType(category)); + vo.setSource("碳信网"); + vo.setSourceType("news"); + vo.setPublishTime(zxxx.getPublishTime()); + vo.setUrl("/mhzc/news/" + zxxx.getId()); + + resultList.add(vo); + } + }); + }); + } + + // 分页 + int start = (reqVO.getPage() - 1) * reqVO.getPageSize(); + int end = Math.min(start + reqVO.getPageSize(), resultList.size()); + + if (start >= resultList.size()) { + return new ArrayList<>(); + } + + return resultList.subList(start, end); + } + + @Override + public List getHotSearchKeywords() { + // TODO: 从数据库读取运营配置的热门搜索词 + return Arrays.asList("江苏电厂配额", "林业碳汇开发", "CBAM 报告", "零碳展会"); + } + + @Override + public List getSearchSuggestions(String keyword) { + if (!StringUtils.hasText(keyword)) { + return new ArrayList<>(); + } + + List suggestions = new ArrayList<>(); + String lowerKeyword = keyword.toLowerCase(); + + // TODO: 实际应从数据库查询匹配的建议词 + // 模拟返回 + suggestions.add(keyword); + + return suggestions.stream() + .filter(s -> s.toLowerCase().contains(lowerKeyword)) + .limit(10) + .collect(Collectors.toList()); + } + + private boolean matchKeyword(String text, String keyword) { + if (!StringUtils.hasText(text) || !StringUtils.hasText(keyword)) { + return false; + } + return text.contains(keyword); + } + + private String highlightKeyword(String text, String keyword) { + if (!StringUtils.hasText(text) || !StringUtils.hasText(keyword)) { + return text; + } + return text.replace(keyword, HIGHLIGHT_START + keyword + HIGHLIGHT_END); + } + + private String truncate(String text, int maxLength) { + if (text == null || text.length() <= maxLength) { + return text; + } + return text.substring(0, maxLength) + "..."; + } + + private String getCategoryType(String category) { + if ("行业专题".equals(category)) { + return "news"; + } else if ("碳证中心".equals(category)) { + return "carbon_cert"; + } else if ("服务中心".equals(category)) { + return "service"; + } + return "all"; + } + + @Override + public List getSearchHistory() { + // Phase 1: 返回空列表(前端使用localStorage) + // TODO: 用户认证后从数据库或Redis获取 + return new ArrayList<>(); + } + + @Override + public void clearSearchHistory() { + // Phase 1: 前端localStorage已清除 + // TODO: 用户认证后清除数据库或Redis中的数据 + } +} +``` + +- [ ] **Step 3: 提交代码** + +```bash +git add txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/SearchService.java +git add txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/impl/SearchServiceImpl.java +git commit -m "feat(search): 添加搜索服务接口和实现" +``` + +--- + +### Task 4: 创建搜索控制器 + +**Files:** +- Create: `txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/controller/SearchController.java` + +- [ ] **Step 1: 创建 SearchController** + +```java +package com.css.txw.mhzc.controller; + +import com.css.ggzc.framework.common.pojo.CommonResult; +import com.css.txw.mhzc.pojo.req.SearchReqVO; +import com.css.txw.mhzc.pojo.vo.SearchResultVO; +import com.css.txw.mhzc.service.SearchService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/search") +@Tag(name = "搜索接口") +@Validated +@Slf4j +public class SearchController { + + @Resource + private SearchService searchService; + + @GetMapping + @Operation(summary = "搜索", description = "全站搜索") + public CommonResult> search(@Valid SearchReqVO reqVO) { + List list = searchService.search(reqVO); + + // 构建返回结果 + // 注意:实际应计算 categoryCount,这里简化处理 + Map result = new java.util.HashMap<>(); + result.put("total", list.size()); + result.put("list", list); + + Map categoryCount = new java.util.HashMap<>(); + categoryCount.put("all", list.size()); + categoryCount.put("carbon_cert", 0); + categoryCount.put("service", 0); + categoryCount.put("news", list.size()); + result.put("categoryCount", categoryCount); + + return CommonResult.success(result); + } + + @GetMapping("/hot") + @Operation(summary = "热门搜索", description = "获取热门搜索词") + public CommonResult> getHotSearch() { + return CommonResult.success(searchService.getHotSearchKeywords()); + } + + @GetMapping("/suggest") + @Operation(summary = "搜索建议", description = "获取搜索建议") + public CommonResult>> getSuggest(@RequestParam String keyword) { + List suggestions = searchService.getSearchSuggestions(keyword); + Map> result = new java.util.HashMap<>(); + result.put("suggestions", suggestions); + return CommonResult.success(result); + } + + @GetMapping("/history") + @Operation(summary = "搜索历史", description = "获取用户搜索历史") + public CommonResult> getSearchHistory() { + return CommonResult.success(searchService.getSearchHistory()); + } + + @DeleteMapping("/history") + @Operation(summary = "清除搜索历史", description = "清除用户搜索历史") + public CommonResult clearSearchHistory() { + searchService.clearSearchHistory(); + return CommonResult.success(true); + } +} +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/controller/SearchController.java +git commit -m "feat(search): 添加搜索控制器" +``` + +--- + +## 前端任务 + +### Task 5: 创建搜索 API + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/api/search.js` + +- [ ] **Step 1: 创建 search.js API** + +```javascript +import { fetchSso } from '@/core/request'; + +const basurl = ''; + +export default { + /** + * 搜索 + * @param {Object} params - { keyword, categoryType, page, pageSize } + */ + search(params) { + return fetchSso({ + url: `${basurl}/search`, + method: 'get', + loading: true, + params, + }); + }, + + /** + * 获取热门搜索词 + */ + getHotSearch() { + return fetchSso({ + url: `${basurl}/search/hot`, + method: 'get', + loading: false, + }); + }, + + /** + * 获取搜索建议 + * @param {String} keyword - 关键词 + */ + getSuggest(keyword) { + return fetchSso({ + url: `${basurl}/search/suggest`, + method: 'get', + loading: false, + params: { keyword }, + }); + }, + + /** + * 获取搜索历史 + */ + getSearchHistory() { + return fetchSso({ + url: `${basurl}/search/history`, + method: 'get', + loading: false, + }); + }, + + /** + * 清除搜索历史 + */ + clearSearchHistory() { + return fetchSso({ + url: `${basurl}/search/history`, + method: 'delete', + loading: false, + }); + }, +}; +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/api/search.js +git commit -m "feat(search): 添加搜索API" +``` + +--- + +### Task 6: 创建搜索区域组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchArea.vue` + +- [ ] **Step 1: 创建 SearchArea.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchArea.vue +git commit -m "feat(search): 添加搜索区域组件" +``` + +--- + +### Task 7: 创建搜索建议组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchSuggestion.vue` + +- [ ] **Step 1: 创建 SearchSuggestion.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchSuggestion.vue +git commit -m "feat(search): 添加搜索建议组件" +``` + +--- + +### Task 8: 创建搜索历史横条组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchHistoryBar.vue` + +- [ ] **Step 1: 创建 SearchHistoryBar.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchHistoryBar.vue +git commit -m "feat(search): 添加搜索历史横条组件" +``` + +--- + +### Task 9: 创建分类 Tab 组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchCategoryTabs.vue` + +- [ ] **Step 1: 创建 SearchCategoryTabs.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchCategoryTabs.vue +git commit -m "feat(search): 添加分类Tab组件" +``` + +--- + +### Task 10: 创建搜索结果项组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchResultItem.vue` + +- [ ] **Step 1: 创建 SearchResultItem.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchResultItem.vue +git commit -m "feat(search): 添加搜索结果项组件" +``` + +--- + +### Task 11: 创建搜索结果列表组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchResultList.vue` + +- [ ] **Step 1: 创建 SearchResultList.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchResultList.vue +git commit -m "feat(search): 添加搜索结果列表组件" +``` + +--- + +### Task 12: 创建空状态组件 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/components/search/SearchEmpty.vue` + +- [ ] **Step 1: 创建 SearchEmpty.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/components/search/SearchEmpty.vue +git commit -m "feat(search): 添加空状态组件" +``` + +--- + +### Task 13: 创建搜索结果页 + +**Files:** +- Create: `txw-mhzc-web/src/pages/index/views/search/index.vue` + +- [ ] **Step 1: 创建搜索结果页 index.vue** + +```vue + + + + + +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/views/search/index.vue +git commit -m "feat(search): 添加搜索结果页" +``` + +--- + +### Task 14: 添加搜索路由 + +**Files:** +- Modify: `txw-mhzc-web/src/pages/index/router/routes.js` + +- [ ] **Step 1: 添加搜索路由** + +在 routes.js 中添加: + +```javascript +// 搜索 +function search() { + return import(/* webpackChunkName: "search" */ '@/pages/index/views/search/index.vue'); +} +``` + +在 export default 的路由数组中添加: + +```javascript +{ + name: 'search', + path: '/search', + component: search, + meta: { + title: '搜索结果', + isShowSideBar: false, + hasHome: true, + breadCrumbs: [{ title: '首页', to: '/home' }, { title: '搜索结果', to: '/search' }], + disableBack: true, + }, +}, +``` + +- [ ] **Step 2: 提交代码** + +```bash +git add txw-mhzc-web/src/pages/index/router/routes.js +git commit -m "feat(search): 添加搜索路由" +``` + +--- + +## 联调与测试 + +### Task 15: 前后端联调 + +- [ ] **Step 1: 验证后端接口** + +启动后端服务,访问 Swagger 文档: +``` +http://localhost:xxxx/swagger-ui.html +``` + +测试接口: +- `GET /search?keyword=碳排放&categoryType=all&page=1&pageSize=10` +- `GET /search/hot` +- `GET /search/suggest?keyword=碳` + +- [ ] **Step 2: 验证前端页面** + +启动前端项目: +```bash +cd txw-mhzc-web +yarn dev +``` + +访问搜索页面: +``` +http://localhost:8080/#/search?keyword=碳排放 +``` + +测试功能: +1. 搜索框输入关键词,点击搜索 +2. 点击热门搜索词 +3. 分类 Tab 切换 +4. 分页功能 +5. 搜索历史展示和清除 + +- [ ] **Step 3: 提交联调代码** + +```bash +git add . +git commit -m "fix(search): 联调修复" +``` + +--- + +## 完成标准 + +- [ ] 所有组件正常渲染 +- [ ] 搜索功能返回正确结果 +- [ ] 分类筛选正常工作 +- [ ] 分页功能正常 +- [ ] 搜索历史本地存储正常 +- [ ] 热门搜索词展示正常 +- [ ] 移动端适配正常(可选) diff --git a/docs/superpowers/specs/2026-04-10-search-design.md b/docs/superpowers/specs/2026-04-10-search-design.md new file mode 100644 index 0000000..6e9f88a --- /dev/null +++ b/docs/superpowers/specs/2026-04-10-search-design.md @@ -0,0 +1,359 @@ +# 全站搜索功能设计文档 + +## 1. 概述 + +### 1.1 功能说明 + +为碳信网(txw-mhzc-web)提供全站统一的搜索功能,支持搜索全站内容(碳证中心、服务中心、行业专题等),提供搜索建议、搜索历史、热门搜索等辅助功能。 + +### 1.2 项目背景 + +- 碳信网各子系统(供需大厅、绿色金融、绿色交易等)目前分散独立 +- 用户需要在全站范围内快速查找内容 +- 需要为后续子系统迁移到 txw-mhzc 做准备 + +### 1.3 设计原则 + +- 前端聚合在 txw-mhzc-web +- 后端聚合在 txw-mhzc +- 搜索服务架构先搭好,后期快速对接迁移后的子系统 + +--- + +## 2. 前端设计 + +### 2.1 路由 + +| 路径 | 组件 | 说明 | +|------|------|------| +| `/search` | SearchView | 搜索结果页 | + +### 2.2 页面结构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ Header (复用现有) - Logo + 网站名 + 登录/注册 + 工作台 │ +├─────────────────────────────────────────────────────────┤ +│ 搜索区域 (灰色背景 #F5F5F5) │ +│ [分类 ▼] [搜索输入框........................] [搜索] │ +│ 热门搜索:江苏电厂配额 | 林业碳汇开发 | CBAM 报告... │ +├─────────────────────────────────────────────────────────┤ +│ [全部] [碳证中心] [服务中心] ← 分类Tab (左侧) │ +│ ───────────── (绿色下划线指示选中) │ +├─────────────────────────────────────────────────────────┤ +│ 搜索结果列表 (单列) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ [行业专题] 构建新型碳排放数据治理框架 │ │ +│ │ 2025年5月29日 核算技术双轨并行发展... │ │ +│ └─────────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ [碳减排服务] 碳排放... │ │ +│ │ 2025年5月29日 ... │ │ +│ └─────────────────────────────────────────────────┘ │ +│ ... │ +├─────────────────────────────────────────────────────────┤ +│ [1] [2] [3] [4] [5] [下一页] ← 分页 │ +├─────────────────────────────────────────────────────────┤ +│ 版权信息等 │ +└─────────────────────────────────────────────────────────┘ +``` + +**说明:** +- 搜索区域包含:分类下拉、搜索输入框、搜索按钮、热门搜索词 +- 搜索历史在搜索区域下方显示(横向排列,右侧有"清除全部") +- 分类Tab在分隔线下方,左对齐 +- 结果列表单列展示,包含分类标签、标题、日期、摘要 +- 无左侧边栏 + +### 2.3 组件列表 + +| 组件 | 说明 | +|------|------| +| SearchView | 搜索结果页主组件(外层使用现有 Nav 组件) | +| SearchArea | 搜索区域(输入框+下拉+按钮+热门词) | +| SearchSuggestion | 搜索建议下拉 | +| SearchHistoryBar | 搜索历史横条(历史词+清除按钮) | +| SearchCategoryTabs | 分类Tab(全部/碳证中心/服务中心) | +| SearchResultList | 结果列表 | +| SearchResultItem | 结果项 | +| SearchPagination | 分页组件(复用 TDesign) | +| SearchEmpty | 空状态 | +| SearchLoading | 加载状态 | + +> **Header/Nav 复用项目现有 `Nav` 组件**,搜索页只需开发 SearchArea 及以下部分 + +### 2.4 分类筛选 + +| 分类 | categoryType | 说明 | +|------|--------------|------| +| 全部 | all | 所有内容 | +| 碳证中心 | carbon_cert | 碳证相关 | +| 服务中心 | service | 服务市场、金融市场、需求市场、数据市场 | +| 行业专题 | news | 新闻资讯 | + +### 2.5 搜索建议 + +触发方式:用户输入时,延迟 300ms 后请求(前端防抖) +限流:接口级别限流,相同关键词 1 分钟内不重复请求 +数据来源: +- 用户输入匹配(模糊匹配标题/内容) +- 历史搜索词(取当前用户最近 10 条) +- 运营配置的热门词 + +--- + +## 3. 后端设计 + +### 3.1 技术栈 + +- Spring Boot +- MyBatis Plus +- Redis(缓存搜索历史、热门词) + +### 3.2 接口设计 + +#### 3.2.1 搜索 + +``` +GET /search?keyword=碳排放&categoryType=all&page=1&pageSize=10 + +参数说明: +- keyword: 搜索关键词(必填,最大 50 字符,需 XSS 过滤) +- categoryType: 分类筛选(all, carbon_cert, service, news) +- page: 页码(默认 1) +- pageSize: 每页条数(默认 10,最大 50) + +Response: +{ + "code": 0, + "msg": "", + "data": { + "total": 18, + "categoryCount": { + "all": 18, + "carbon_cert": 2, + "service": 16, + "news": 0 + }, + "list": [ + { + "id": "xxx", + "title": "构建新型碳排放数据治理框架", + "summary": "核算技术双轨并行发展...", + "category": "行业专题", + "categoryType": "news", + "source": "碳证中心", + "sourceType": "carbon_cert", + "publishTime": "2025-05-29", + "url": "/gxzx/detail/xxx" + } + ] + } +} +``` + +#### 3.2.2 搜索建议 + +``` +GET /search/suggest?keyword=碳排 + +参数说明: +- keyword: 搜索关键词(必填,最大 50 字符) + +Response: +{ + "code": 0, + "msg": "", + "data": { + "suggestions": [ + "碳排放", + "碳排放权", + "碳排放核算" + ] + } +} +``` + +#### 3.2.3 热门搜索 + +``` +GET /search/hot + +Response: +{ + "code": 0, + "data": [ + "江苏电厂配额", + "林业碳汇开发", + "CBAM 报告", + "零碳展会" + ] +} +``` + +#### 3.2.4 搜索历史 + +``` +GET /search/history + +Response: +{ + "code": 0, + "data": [ + "碳排放", + "林业碳汇", + "CBAM" + ] +} +``` + +#### 3.2.5 清除搜索历史 + +``` +DELETE /search/history + +Response: +{ + "code": 0, + "msg": "" +} +``` + +### 3.3 数据模型 + +#### 3.3.1 搜索结果(SearchResultVO) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | String | 内容ID | +| title | String | 标题(关键词高亮,使用 `` 标签) | +| summary | String | 摘要(截取前 200 字符) | +| thumbnail | String | 缩略图URL(可为空) | +| category | String | 分类标签(展示用,如"行业专题") | +| categoryType | String | 分类类型(筛选用,如"news") | +| source | String | 来源名称 | +| sourceType | String | 来源类型 | +| publishTime | String | 发布时间 | +| url | String | 跳转链接 | + +#### 3.3.2 热门搜索配置(HotSearchConfig) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| keyword | String | 关键词 | +| sort | Integer | 排序 | +| status | Integer | 状态(0禁用1启用) | +| startTime | LocalDateTime | 生效开始时间(可选) | +| endTime | LocalDateTime | 生效结束时间(可选) | + +> 注:支持临时活动配置热门词,通过生效时间控制 + +#### 3.3.3 搜索历史(SearchHistory) + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | Long | 主键 | +| userId | String | 用户ID(登录用户) | +| deviceId | String | 设备ID(游客,用于关联未登录用户) | +| keyword | String | 搜索词 | +| searchTime | LocalDateTime | 搜索时间 | + +> 注:搜索历史最多存储 20 条,超出后删除最早的记录 + +### 3.4 搜索聚合服务 + +由于目前各子系统还没有迁移,搜索聚合服务采用以下策略: + +**阶段一(当前)**: +- 只搜索资讯中心 `/sy/zxxx` 的内容 +- 数据直接存储在 txw-mhzc 中 + +**阶段二(子系统迁移后)**: +- 各子系统迁移到 txw-mhzc 后 +- SearchAggregatorService 并行调用各子系统的搜索方法 +- 聚合去重后返回 + +--- + +## 4. 文件结构 + +### 4.1 前端(txw-mhzc-web) + +``` +txw-mhzc-web/src/pages/index/ +├── views/ +│ └── search/ +│ └── index.vue # 搜索结果页 +├── components/ +│ └── search/ +│ ├── SearchArea.vue # 搜索区域 +│ ├── SearchSuggestion.vue # 搜索建议下拉 +│ ├── SearchHistoryBar.vue # 搜索历史横条 +│ ├── SearchCategoryTabs.vue # 分类Tab +│ ├── SearchResultList.vue +│ └── SearchResultItem.vue +└── api/ + └── search.js # 搜索接口 +``` + +### 4.2 后端(txw-mhzc) + +``` +txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/ +├── controller/ +│ └── SearchController.java +├── service/ +│ ├── SearchService.java +│ └── impl/ +│ └── SearchServiceImpl.java +├── mapper/ +│ └── SearchMapper.java +└── pojo/ + ├── dto/ + │ └── SearchDTO.java + └── vo/ + ├── SearchReqVO.java + ├── SearchResultVO.java + ├── HotSearchConfig.java + └── SearchHistory.java +``` + +--- + +## 5. 开发计划 + +### 5.1 第一阶段(预计 3 天) + +| 任务 | 优先级 | 预估工时 | +|------|--------|----------| +| 前端:搜索页面基础结构 | P0 | 0.5d | +| 前端:搜索框组件 + 搜索建议 | P0 | 0.5d | +| 前端:左侧边栏(历史+热门+分类) | P0 | 0.5d | +| 前端:结果列表展示 | P0 | 0.5d | +| 前端:分类筛选 + 分页 | P0 | 0.5d | +| 后端:搜索接口(资讯中心)| P0 | 1d | +| 后端:搜索建议接口 | P1 | 0.5d | +| 后端:热门搜索接口 | P1 | 0.5d | +| 后端:搜索历史接口 | P1 | 0.5d | +| **前后端联调 + 测试** | P0 | 1d | + +### 5.2 第二阶段(子系统迁移后) + +- [ ] 后端:聚合其他子系统搜索 +- [ ] 搜索结果去重优化 +- [ ] 搜索权重配置 + +### 5.3 数据埋点(可后续迭代) + +- 搜索关键词上报 +- 搜索结果点击上报 +- 热门词曝光点击统计 + +--- + +## 6. 待确认 + +1. ~~ 搜索结果高亮标签使用 `` 还是其他?~~ → **使用 ``** +2. ~~每个分类的数量是否需要实时从后端获取?~~ → **随搜索结果一起返回,在 data.categoryCount 中** +3. 搜索结果点击后跳转到原详情页,详情页是否需要开发? → **由各子系统详情页处理,搜索只提供 URL** diff --git a/script/copy-changed-files.sh b/script/copy-changed-files.sh deleted file mode 100644 index a200be3..0000000 --- a/script/copy-changed-files.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -# 复制 git 变更文件到 change 目录,保留原文件相对路径 - -set -e - -TARGET_DIR="change" - -# 创建目标目录 -mkdir -p "$TARGET_DIR" - -# 获取所有变更文件(包含未跟踪文件) -files=$(git status --porcelain | awk '{print $2}') - -if [ -z "$files" ]; then - echo "没有变更文件" - exit 0 -fi - -count=0 -for f in $files; do - # 跳过目录(如 docs/) - if [ -d "$f" ]; then - mkdir -p "$TARGET_DIR/$f" - cp -r "$f" "$TARGET_DIR/$f" - echo "✓ $f/" - elif [ -f "$f" ]; then - mkdir -p "$TARGET_DIR/$(dirname "$f")" - cp "$f" "$TARGET_DIR/$f" - echo "✓ $f" - ((count++)) - fi -done - -echo "" -echo "已完成,复制了 $count 个文件到 $TARGET_DIR 目录"