From 56e2615624f35860bf14debc0237fc3de4cb6d40 Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Tue, 30 Jun 2026 09:09:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BE=85=E5=AE=A1=E6=A0=B8=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E6=94=AF=E6=8C=81=E6=89=8B=E5=8A=A8=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=B7=B2=E6=9C=89=E7=AB=99=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../par/api/controller/AdminController.java | 22 +++++++ .../com/par/core/service/SiteService.java | 6 ++ .../core/service/impl/SiteServiceImpl.java | 23 ++++++++ web/src/api/index.js | 2 + web/src/views/Sites.vue | 58 ++++++++++++++++++- 5 files changed, 110 insertions(+), 1 deletion(-) diff --git a/par-api/src/main/java/com/par/api/controller/AdminController.java b/par-api/src/main/java/com/par/api/controller/AdminController.java index c6df3bc..a77967b 100644 --- a/par-api/src/main/java/com/par/api/controller/AdminController.java +++ b/par-api/src/main/java/com/par/api/controller/AdminController.java @@ -296,6 +296,28 @@ public class AdminController { return ApiResponse.success(siteService.listChangesBySite(siteId)); } + @GetMapping("/sites/search") + public ApiResponse> searchSites( + @RequestParam("q") String q, HttpServletRequest request) { + checkAdmin(request); + return ApiResponse.success(siteService.searchSites(q)); + } + + @PostMapping("/changes/{id}/link") + public ApiResponse linkChangeToSite( + @PathVariable Long id, + @RequestBody Map payload, + HttpServletRequest request) { + checkAdmin(request); + String targetSiteId = payload.get("siteId"); + if (targetSiteId == null || targetSiteId.isBlank()) { + return ApiResponse.error(400, "siteId is required"); + } + siteService.linkChangeToSite(id, targetSiteId); + log.info("Change linked: changeId={}, siteId={}", id, targetSiteId); + return ApiResponse.success(); + } + private void checkAdmin(HttpServletRequest request) { TrustLevel level = (TrustLevel) request.getAttribute("trustLevel"); if (level == null || level != TrustLevel.ADMIN) { diff --git a/par-core/src/main/java/com/par/core/service/SiteService.java b/par-core/src/main/java/com/par/core/service/SiteService.java index a1f5dcc..78bd451 100644 --- a/par-core/src/main/java/com/par/core/service/SiteService.java +++ b/par-core/src/main/java/com/par/core/service/SiteService.java @@ -60,4 +60,10 @@ public interface SiteService { /** 查看站点的变更记录 */ List listChangesBySite(String siteId); + + /** 搜索站点(按名称/URL 模糊匹配) */ + List searchSites(String keyword); + + /** 将变更关联到指定站点 */ + void linkChangeToSite(Long changeId, String siteId); } diff --git a/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java b/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java index a5063b1..0800d54 100644 --- a/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java +++ b/par-core/src/main/java/com/par/core/service/impl/SiteServiceImpl.java @@ -302,6 +302,29 @@ public class SiteServiceImpl implements SiteService { .map(this::toChangeDTO).collect(Collectors.toList()); } + @Override + public List searchSites(String keyword) { + return siteMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .and(w -> w.like(Site::getNamesJson, keyword) + .or().like(Site::getUrlsJson, keyword) + .or().eq(Site::getSiteId, keyword)) + .eq(Site::getDeleted, 0) + .orderByDesc(Site::getUpdatedAt) + .last("LIMIT 20") + ).stream().map(this::toDTO).collect(Collectors.toList()); + } + + @Override + public void linkChangeToSite(Long changeId, String siteId) { + SiteChange entry = changeMapper.selectById(changeId); + if (entry == null) throw new IllegalArgumentException("变更记录不存在"); + if (!"pending".equals(entry.getStatus())) throw new IllegalArgumentException("只能关联待审核的变更"); + entry.setSiteId(siteId); + changeMapper.updateById(entry); + log.info("变更手动关联: changeId={} → siteId={}", changeId, siteId); + } + private SiteChangeDTO toChangeDTO(SiteChange entry) { SiteChangeDTO dto = new SiteChangeDTO(); dto.setId(entry.getId()); diff --git a/web/src/api/index.js b/web/src/api/index.js index 2494e25..6dabeaf 100644 --- a/web/src/api/index.js +++ b/web/src/api/index.js @@ -39,7 +39,9 @@ export default { pendingChanges: () => api.get('/admin/sites/pending'), approveChange: (id) => api.post(`/admin/changes/${id}/approve`), rejectChange: (id) => api.post(`/admin/changes/${id}/reject`), + linkChange: (id, siteId) => api.post(`/admin/changes/${id}/link`, { siteId }), changesBySite: (siteId) => api.get(`/admin/sites/${siteId}/changes`), + search: (q) => api.get(`/admin/sites/search`, { params: { q } }), }, configs: { list: () => api.get('/configs'), diff --git a/web/src/views/Sites.vue b/web/src/views/Sites.vue index 1963d0b..8874078 100644 --- a/web/src/views/Sites.vue +++ b/web/src/views/Sites.vue @@ -30,10 +30,11 @@ - + @@ -146,6 +147,27 @@ 关闭 + + + + + + + + + + + + + + + @@ -170,6 +192,40 @@ const siteTypes = [ ] // 配置查看 +const matchVisible = ref(false) +const matchQuery = ref('') +const matchResults = ref([]) +const matchSearching = ref(false) +let matchTargetId = ref(null) + +const handleMatch = (row) => { + matchTargetId.value = row.id + matchQuery.value = (row.names || [])[0] || '' + matchVisible.value = true + handleSearch() +} + +const handleSearch = async () => { + if (!matchQuery.value.trim()) { matchResults.value = []; return } + matchSearching.value = true + try { + const res = await api.sites.search(matchQuery.value.trim()) + matchResults.value = res.data || [] + } catch { matchResults.value = [] } + finally { matchSearching.value = false } +} + +const handleLinkSite = async (row) => { + try { + await api.sites.linkChange(matchTargetId.value, row.siteId) + ElMessage.success(`已关联到 ${row.siteId}`) + matchVisible.value = false + fetchAll() + } catch (e) { + ElMessage.error(e.response?.data?.message || '关联失败') + } +} + const historyVisible = ref(false) const historyLoading = ref(false) const historyList = ref([])