diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 47fba33..8835cda 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -26,7 +26,8 @@ "Bash(sed -i 's/com\\\\.css\\\\.txw\\\\.gxzx\\\\.pojo\\\\.vo\\\\.lsjr/com.css.txw.mhzc.pojo.vo.gxzx.lsjr/g' \"E:/00项目/T_碳信网/code/txw/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/pojo/vo/gxzx/lsjr/ProductApplyVO.java\")", "Bash(\"/d/Program Files/apache-maven/apache-maven-3.6.3/bin/mvn\" compile -pl txw-mhzc-service-biz -am)", "Bash(\"/d/Program Files/apache-maven/apache-maven-3.6.3/bin/mvn\" clean package -pl txw-mhzc-service-biz -am -DskipTests)", - "Bash(\"/d/Program Files/jdk8/bin/java\" -Xms256m -Xmx512m -Duser.timezone=Asia/Shanghai -jar target/txw-mhzc-service-biz.jar --spring.profiles.active=local)" + "Bash(\"/d/Program Files/jdk8/bin/java\" -Xms256m -Xmx512m -Duser.timezone=Asia/Shanghai -jar target/txw-mhzc-service-biz.jar --spring.profiles.active=local)", + "Bash(mvn compile *)" ] } } diff --git a/txw-mhzc-web/src/pages/index/components/search/SearchResultItem.vue b/txw-mhzc-web/src/pages/index/components/search/SearchResultItem.vue index 24ca156..4bfa1f6 100644 --- a/txw-mhzc-web/src/pages/index/components/search/SearchResultItem.vue +++ b/txw-mhzc-web/src/pages/index/components/search/SearchResultItem.vue @@ -43,6 +43,7 @@ export default { methods: { handleClick() { if (this.result.url) { + // 后端返回的URL已包含id参数,直接跳转 window.location.href = this.result.url; } }, diff --git a/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue b/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue index 0690db5..7e5367c 100644 --- a/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue +++ b/txw-mhzc-web/src/pages/index/views/fwsc/fwsc.vue @@ -94,6 +94,7 @@
@@ -292,6 +293,12 @@ export default { if (this.$route.query.publish === '1') { this.handlePublish(); } + // 如果URL有id参数,等待数据加载后定位到对应项 + if (this.$route.query.id) { + this.$nextTick(() => { + this.scrollToItem(this.$route.query.id); + }); + } }, methods: { // 初始化用户信息 @@ -419,11 +426,24 @@ export default { this.page.pageNo = 1; this.searchList(); }, - // 分页变化 - onPageChange(pageInfo) { - this.page.pageNo = pageInfo.current; - this.page.pageSize = pageInfo.pageSize; - this.searchList(); + // 滚动到指定项并高亮 + scrollToItem(gxUuid) { + this.$nextTick(() => { + const targetCard = this.cardList.find(card => card.gxUuid === gxUuid); + if (targetCard) { + // 找到对应卡片并滚动到视图中心 + const cardIndex = this.cardList.indexOf(targetCard); + const cardElement = document.querySelector(`[data-gx-uuid="${gxUuid}"]`); + if (cardElement) { + cardElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + // 添加高亮效果 + cardElement.classList.add('highlight-card'); + setTimeout(() => { + cardElement.classList.remove('highlight-card'); + }, 3000); + } + } + }); }, // 处理发布 handlePublish() { @@ -804,6 +824,10 @@ export default { box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); transition: all 0.3s ease; + &.highlight-card { + animation: highlight-pulse 3s ease-out; + } + &::before { position: absolute; top: 0; @@ -1124,4 +1148,16 @@ export default { font-size: 18px; } } + +@keyframes highlight-pulse { + 0% { + box-shadow: 0 0 0 0 rgba(0, 154, 41, 0.4); + } + 50% { + box-shadow: 0 0 20px 10px rgba(0, 154, 41, 0.2); + } + 100% { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + } +} diff --git a/txw-mhzc-web/src/pages/index/views/fwsc/xqsc.vue b/txw-mhzc-web/src/pages/index/views/fwsc/xqsc.vue index 83cac2a..35d1d0e 100644 --- a/txw-mhzc-web/src/pages/index/views/fwsc/xqsc.vue +++ b/txw-mhzc-web/src/pages/index/views/fwsc/xqsc.vue @@ -86,6 +86,7 @@
@@ -261,6 +262,12 @@ export default { if (this.$route.query.publish === '1') { this.handlePublish(); } + // 如果URL有id参数,等待数据加载后定位到对应项 + if (this.$route.query.id) { + this.$nextTick(() => { + this.scrollToItem(this.$route.query.id); + }); + } }, methods: { // 加载代码表 @@ -345,11 +352,21 @@ export default { this.page.pageNo = 1; this.searchList(); }, - // 分页变化 - onPageChange(pageInfo) { - this.page.pageNo = pageInfo.current; - this.page.pageSize = pageInfo.pageSize; - this.searchList(); + // 滚动到指定项并高亮 + scrollToItem(gxUuid) { + this.$nextTick(() => { + const targetCard = this.cardList.find(card => card.gxUuid === gxUuid); + if (targetCard) { + const cardElement = document.querySelector(`[data-gx-uuid="${gxUuid}"]`); + if (cardElement) { + cardElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + cardElement.classList.add('highlight-card'); + setTimeout(() => { + cardElement.classList.remove('highlight-card'); + }, 3000); + } + } + }); }, // 处理发布 handlePublish() { @@ -705,6 +722,10 @@ export default { box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); transition: all 0.3s ease; + &.highlight-card { + animation: highlight-pulse 3s ease-out; + } + &::before { position: absolute; top: 0; @@ -1028,4 +1049,16 @@ export default { font-size: 18px; } } + +@keyframes highlight-pulse { + 0% { + box-shadow: 0 0 0 0 rgba(0, 154, 41, 0.4); + } + 50% { + box-shadow: 0 0 20px 10px rgba(0, 154, 41, 0.2); + } + 100% { + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06); + } +} diff --git a/txw-mhzc-web/src/pages/index/views/home2/index.vue b/txw-mhzc-web/src/pages/index/views/home2/index.vue index 563dcfe..5deb193 100644 --- a/txw-mhzc-web/src/pages/index/views/home2/index.vue +++ b/txw-mhzc-web/src/pages/index/views/home2/index.vue @@ -48,16 +48,12 @@ 登录 @@ -176,6 +177,7 @@ handleCounter() { this.startCountDown(); let params = { captchaVerification: this.loginForm.captchaVerification, + captchaCode: this.loginForm.captchaCode, sjhm1: this.loginForm.sjhm, } sendMsg(params).then((res) => { diff --git a/txw-mhzc-web/src/pages/index/views/search/index.vue b/txw-mhzc-web/src/pages/index/views/search/index.vue index d9ed0c1..567e6a4 100644 --- a/txw-mhzc-web/src/pages/index/views/search/index.vue +++ b/txw-mhzc-web/src/pages/index/views/search/index.vue @@ -1,63 +1,191 @@ @@ -177,19 +361,515 @@ export default { diff --git a/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/controller/SearchController.java b/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/controller/SearchController.java index 2193a87..8600ccb 100644 --- a/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/controller/SearchController.java +++ b/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/controller/SearchController.java @@ -30,16 +30,11 @@ public class SearchController { @Operation(summary = "搜索", description = "全站搜索") public CommonResult> search(@Valid SearchReqVO reqVO) { List list = searchService.search(reqVO); + Map categoryCount = searchService.getCategoryCount(reqVO.getKeyword()); Map result = new HashMap<>(); result.put("total", list.size()); result.put("list", list); - - Map categoryCount = new 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); diff --git a/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/SearchService.java b/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/SearchService.java index e0875ac..82aba1a 100644 --- a/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/SearchService.java +++ b/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/SearchService.java @@ -3,6 +3,7 @@ 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; +import java.util.Map; public interface SearchService { @@ -11,6 +12,11 @@ public interface SearchService { */ List search(SearchReqVO reqVO); + /** + * 获取搜索结果分类统计 + */ + Map getCategoryCount(String keyword); + /** * 获取热门搜索词 */ diff --git a/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/impl/SearchServiceImpl.java b/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/impl/SearchServiceImpl.java index 17c73c8..8827ae6 100644 --- a/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/impl/SearchServiceImpl.java +++ b/txw-mhzc/txw-mhzc-service-biz/src/main/java/com/css/txw/mhzc/service/impl/SearchServiceImpl.java @@ -1,6 +1,10 @@ package com.css.txw.mhzc.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.css.txw.mhzc.mapper.TxwGxzxGxxxbMapper; +import com.css.txw.mhzc.pojo.domain.TxwGxzxGxxxbDO; import com.css.txw.mhzc.pojo.req.SearchReqVO; +import com.css.txw.mhzc.pojo.vo.GxxxVO; import com.css.txw.mhzc.pojo.vo.SearchResultVO; import com.css.txw.mhzc.pojo.vo.SyzxxxVO; import com.css.txw.mhzc.service.SearchService; @@ -12,6 +16,7 @@ import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -23,43 +28,63 @@ public class SearchServiceImpl implements SearchService { @Resource private TxwMhzcZxxxbService zxxxbService; + @Resource + private TxwGxzxGxxxbMapper gxxxbMapper; + private static final String HIGHLIGHT_START = ""; private static final String HIGHLIGHT_END = ""; + private static final String VIEW_MHZC_PREFIX = "/view/mhzc"; + private static final String URL_PARAM_KEYWORD = "keyword"; @Override public List search(SearchReqVO reqVO) { - Map> data = zxxxbService.zxxx(); List resultList = new ArrayList<>(); String categoryType = reqVO.getCategoryType(); + String keyword = reqVO.getKeyword(); - if (data != null) { - data.forEach((category, list) -> { - String currentCategoryType = getCategoryType(category); - if (!"all".equals(categoryType) && !categoryType.equals(currentCategoryType)) { - return; - } - - list.forEach(zxxx -> { - if (matchKeyword(zxxx.getBt1(), reqVO.getKeyword()) || - matchKeyword(zxxx.getZxNr(), reqVO.getKeyword())) { - - SearchResultVO vo = new SearchResultVO(); - vo.setId(zxxx.getUuid()); - vo.setTitle(highlightKeyword(zxxx.getBt1(), reqVO.getKeyword())); - vo.setSummary(highlightKeyword(truncate(zxxx.getZxNr(), 200), reqVO.getKeyword())); - vo.setCategory(category); - vo.setCategoryType(currentCategoryType); - vo.setSource("碳信网"); - vo.setSourceType("news"); - vo.setPublishTime(zxxx.getFbsj() != null ? zxxx.getFbsj().toString() : null); - vo.setUrl("/mhzc/news/" + zxxx.getUuid()); - - resultList.add(vo); + // 搜索资讯信息 + if (!"service".equals(categoryType)) { + Map> data = zxxxbService.zxxx(); + if (data != null) { + data.forEach((category, list) -> { + String currentCategoryType = getCategoryType(category); + if (!"all".equals(categoryType) && !categoryType.equals(currentCategoryType)) { + return; } + + list.forEach(zxxx -> { + if (matchKeyword(zxxx.getBt1(), keyword) || + matchKeyword(zxxx.getZxNr(), keyword)) { + + SearchResultVO vo = new SearchResultVO(); + vo.setId(zxxx.getUuid()); + vo.setTitle(highlightKeyword(zxxx.getBt1(), keyword)); + vo.setSummary(highlightKeyword(truncate(zxxx.getZxNr(), 200), keyword)); + vo.setCategory(category); + vo.setCategoryType(currentCategoryType); + vo.setSource("碳信网"); + vo.setSourceType("news"); + vo.setPublishTime(zxxx.getFbsj() != null ? zxxx.getFbsj().toString() : null); + vo.setUrl(VIEW_MHZC_PREFIX + "/zx?id=" + zxxx.getUuid()); + + resultList.add(vo); + } + }); }); - }); + } } + // 搜索服务中心(供需信息) + if (!"news".equals(categoryType) && !"carbon_cert".equals(categoryType)) { + List serviceResults = searchServiceInfo(keyword); + if (!"all".equals(categoryType)) { + resultList.addAll(serviceResults); + } else { + resultList.addAll(serviceResults); + } + } + + // 分页处理 int start = (reqVO.getPage() - 1) * reqVO.getPageSize(); int end = Math.min(start + reqVO.getPageSize(), resultList.size()); @@ -72,7 +97,47 @@ public class SearchServiceImpl implements SearchService { @Override public List getHotSearchKeywords() { - return Arrays.asList("江苏电厂配额", "林业碳汇开发", "CBAM 报告", "零碳展会"); + return Arrays.asList("碳核查", "ESG", "碳资产管理", "ISO 14067"); + } + + @Override + public Map getCategoryCount(String keyword) { + Map countMap = new HashMap<>(); + countMap.put("all", 0); + countMap.put("news", 0); + countMap.put("service", 0); + countMap.put("carbon_cert", 0); + + // 统计资讯数量 + Map> data = zxxxbService.zxxx(); + if (data != null) { + data.forEach((category, list) -> { + String categoryType = getCategoryType(category); + long count = list.stream() + .filter(zxxx -> matchKeyword(zxxx.getBt1(), keyword) || matchKeyword(zxxx.getZxNr(), keyword)) + .count(); + countMap.put(categoryType, (int) count); + if (!"carbon_cert".equals(categoryType)) { + countMap.put("all", countMap.get("all") + (int) count); + } + }); + } + + // 统计供需信息数量 + if (StringUtils.hasText(keyword)) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .and(w -> w.like(TxwGxzxGxxxbDO::getBt1, keyword) + .or().like(TxwGxzxGxxxbDO::getFwnr, keyword) + .or().like(TxwGxzxGxxxbDO::getQymc, keyword)) + .eq(TxwGxzxGxxxbDO::getSjzt, "Y") + .eq(TxwGxzxGxxxbDO::getZt, "3"); + Long serviceCount = gxxxbMapper.selectCount(wrapper); + countMap.put("service", serviceCount.intValue()); + countMap.put("all", countMap.get("all") + serviceCount.intValue()); + } + + return countMap; } @Override @@ -84,10 +149,42 @@ public class SearchServiceImpl implements SearchService { List suggestions = new ArrayList<>(); String lowerKeyword = keyword.toLowerCase(); - suggestions.add(keyword); + // 从资讯中提取建议 + Map> data = zxxxbService.zxxx(); + if (data != null) { + data.forEach((category, list) -> { + list.forEach(zxxx -> { + if (matchKeyword(zxxx.getBt1(), keyword)) { + suggestions.add(zxxx.getBt1()); + } + }); + }); + } + // 从供需信息中提取建议 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .and(w -> w.like(TxwGxzxGxxxbDO::getBt1, keyword) + .or().like(TxwGxzxGxxxbDO::getFwnr, keyword) + .or().like(TxwGxzxGxxxbDO::getQymc, keyword)) + .eq(TxwGxzxGxxxbDO::getSjzt, "Y") + .eq(TxwGxzxGxxxbDO::getZt, "3") + .select(TxwGxzxGxxxbDO::getBt1, TxwGxzxGxxxbDO::getQymc) + .last("LIMIT 20"); + + List gxxxList = gxxxbMapper.selectList(wrapper); + if (gxxxList != null) { + gxxxList.forEach(gxxx -> { + if (StringUtils.hasText(gxxx.getBt1())) { + suggestions.add(gxxx.getBt1()); + } + }); + } + + // 去重并返回包含关键词的建议 return suggestions.stream() - .filter(s -> s.toLowerCase().contains(lowerKeyword)) + .filter(s -> s != null && s.toLowerCase().contains(lowerKeyword)) + .distinct() .limit(10) .collect(Collectors.toList()); } @@ -124,6 +221,69 @@ public class SearchServiceImpl implements SearchService { return "all"; } + private List searchServiceInfo(String keyword) { + List resultList = new ArrayList<>(); + if (!StringUtils.hasText(keyword)) { + return resultList; + } + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.lambda() + .and(w -> w.like(TxwGxzxGxxxbDO::getBt1, keyword) + .or().like(TxwGxzxGxxxbDO::getFwnr, keyword) + .or().like(TxwGxzxGxxxbDO::getQymc, keyword)) + .eq(TxwGxzxGxxxbDO::getSjzt, "Y") + .eq(TxwGxzxGxxxbDO::getZt, "3") + .orderByDesc(TxwGxzxGxxxbDO::getLrrq); + + List list = gxxxbMapper.selectList(wrapper); + + if (list != null) { + for (TxwGxzxGxxxbDO gxxx : list) { + String ywlxMc = getYwlxMc(gxxx.getYwlxDm()); + String ywlxPath = getYwlxPath(gxxx.getYwlxDm()); + SearchResultVO vo = new SearchResultVO(); + vo.setId(gxxx.getGxUuid()); + vo.setTitle(highlightKeyword(gxxx.getBt1(), keyword)); + vo.setSummary(highlightKeyword(truncate(gxxx.getFwnr(), 200), keyword)); + vo.setCategory(ywlxMc); + vo.setCategoryType("service"); + vo.setSource(gxxx.getQymc()); + vo.setSourceType("service"); + vo.setPublishTime(gxxx.getLrrq() != null ? gxxx.getLrrq().toString() : null); + vo.setUrl(VIEW_MHZC_PREFIX + ywlxPath + "?id=" + gxxx.getGxUuid()); + resultList.add(vo); + } + } + return resultList; + } + + private String getYwlxMc(String ywlxDm) { + if ("01".equals(ywlxDm)) { + return "碳服务市场"; + } else if ("02".equals(ywlxDm)) { + return "碳需求市场"; + } else if ("03".equals(ywlxDm)) { + return "碳金融市场"; + } else if ("04".equals(ywlxDm)) { + return "碳数据市场"; + } + return "服务中心"; + } + + private String getYwlxPath(String ywlxDm) { + if ("01".equals(ywlxDm)) { + return "/tfwsc"; + } else if ("02".equals(ywlxDm)) { + return "/txqsc"; + } else if ("03".equals(ywlxDm)) { + return "/tjrsc"; + } else if ("04".equals(ywlxDm)) { + return "/tsjsc"; + } + return "/tfwsc"; + } + @Override public List getSearchHistory() { return new ArrayList<>(); diff --git a/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/pojo/vo/SendMsgReqVO.java b/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/pojo/vo/SendMsgReqVO.java index bab0724..fa1ea57 100644 --- a/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/pojo/vo/SendMsgReqVO.java +++ b/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/pojo/vo/SendMsgReqVO.java @@ -17,8 +17,12 @@ public class SendMsgReqVO { @NotEmpty(message = "手机号码不能为空") private String sjhm1; - @Schema(description = "验证码,验证码开启时,需要传递", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "图形验证码uuid", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "验证码不能为空", groups = AuthLoginReqVO.CodeEnableGroup.class) private String captchaVerification; + @Schema(description = "图形验证码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "验证码不能为空", groups = AuthLoginReqVO.CodeEnableGroup.class) + private String captchaCode; + } diff --git a/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/auth/impl/AuthServiceImpl.java b/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/auth/impl/AuthServiceImpl.java index b86cfa1..69b8d5b 100644 --- a/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/auth/impl/AuthServiceImpl.java +++ b/txw-sso/txw-sso-service-biz/src/main/java/com/css/txw/sso/service/auth/impl/AuthServiceImpl.java @@ -158,7 +158,7 @@ public class AuthServiceImpl implements AuthService { @Override public Integer sendMsg(SendMsgReqVO reqVO) throws Exception{ - final Boolean checked = verifyService.checkVerifyToken(reqVO.getCaptchaVerification()); + final Boolean checked = verifyService.checkCaptcha(reqVO.getCaptchaVerification(), reqVO.getCaptchaCode()); if (!checked) { throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR); }