feat: 待审核变更支持手动搜索匹配已有站点
This commit is contained in:
@@ -296,6 +296,28 @@ public class AdminController {
|
||||
return ApiResponse.success(siteService.listChangesBySite(siteId));
|
||||
}
|
||||
|
||||
@GetMapping("/sites/search")
|
||||
public ApiResponse<List<com.par.core.dto.SiteDTO>> searchSites(
|
||||
@RequestParam("q") String q, HttpServletRequest request) {
|
||||
checkAdmin(request);
|
||||
return ApiResponse.success(siteService.searchSites(q));
|
||||
}
|
||||
|
||||
@PostMapping("/changes/{id}/link")
|
||||
public ApiResponse<Void> linkChangeToSite(
|
||||
@PathVariable Long id,
|
||||
@RequestBody Map<String, String> 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) {
|
||||
|
||||
@@ -60,4 +60,10 @@ public interface SiteService {
|
||||
|
||||
/** 查看站点的变更记录 */
|
||||
List<SiteChangeDTO> listChangesBySite(String siteId);
|
||||
|
||||
/** 搜索站点(按名称/URL 模糊匹配) */
|
||||
List<SiteDTO> searchSites(String keyword);
|
||||
|
||||
/** 将变更关联到指定站点 */
|
||||
void linkChangeToSite(Long changeId, String siteId);
|
||||
}
|
||||
|
||||
@@ -302,6 +302,29 @@ public class SiteServiceImpl implements SiteService {
|
||||
.map(this::toChangeDTO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SiteDTO> searchSites(String keyword) {
|
||||
return siteMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Site>()
|
||||
.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());
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -30,10 +30,11 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createdAt" label="提交时间" width="160" />
|
||||
<el-table-column label="操作" width="150">
|
||||
<el-table-column label="操作" width="210">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="success" @click="handleApprove(scope.row)">通过</el-button>
|
||||
<el-button size="small" type="danger" @click="handleReject(scope.row)">拒绝</el-button>
|
||||
<el-button size="small" @click="handleMatch(scope.row)">匹配</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -146,6 +147,27 @@
|
||||
<el-button @click="historyVisible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 手动匹配对话框 -->
|
||||
<el-dialog v-model="matchVisible" title="关联到已有站点" width="500px" :close-on-click-modal="false">
|
||||
<el-input v-model="matchQuery" placeholder="搜索站点名称或URL..." clearable @input="handleSearch" style="margin-bottom:12px" />
|
||||
<el-table :data="matchResults" stripe size="small" v-loading="matchSearching" max-height="300" @row-click="handleLinkSite" highlight-current-row>
|
||||
<el-table-column prop="siteId" label="站点ID" width="120" />
|
||||
<el-table-column label="名称" min-width="150">
|
||||
<template #default="scope">
|
||||
<div v-for="(n, i) in scope.row.names" :key="i" class="text-xs">{{ n }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="地址" min-width="150">
|
||||
<template #default="scope">
|
||||
<div v-for="(u, i) in scope.row.urls" :key="i" class="text-xs">{{ u }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button @click="matchVisible = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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([])
|
||||
|
||||
Reference in New Issue
Block a user