feat: 站点审核通过时弹出编辑框,支持审核者修改数据后再通过
- SiteReview.vue: 「通过」打开编辑弹窗,可改名称/地址/类型/状态/备注 - AdminController: approveChange 接受 request body(编辑数据) - SiteService.approveChange: 优先用审核者编辑数据,否则用原始提交 - 拒绝保持原逻辑直接拒绝
This commit is contained in:
@@ -272,10 +272,17 @@ public class AdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/changes/{id}/approve")
|
@PostMapping("/changes/{id}/approve")
|
||||||
public ApiResponse<Void> approveChange(@PathVariable Long id, HttpServletRequest request) {
|
public ApiResponse<Void> approveChange(@PathVariable Long id,
|
||||||
|
@RequestBody(required = false) Map<String, Object> body,
|
||||||
|
HttpServletRequest request) {
|
||||||
checkAdmin(request);
|
checkAdmin(request);
|
||||||
Long reviewerId = (Long) request.getAttribute("accountId");
|
Long reviewerId = (Long) request.getAttribute("accountId");
|
||||||
siteService.approveChange(id, reviewerId);
|
String name = body != null ? (String) body.get("name") : null;
|
||||||
|
String baseUrl = body != null ? (String) body.get("baseUrl") : null;
|
||||||
|
String siteType = body != null ? (String) body.get("siteType") : null;
|
||||||
|
String status = body != null ? (String) body.get("status") : null;
|
||||||
|
String notes = body != null ? (String) body.get("notes") : null;
|
||||||
|
siteService.approveChange(id, reviewerId, name, baseUrl, siteType, status, notes);
|
||||||
log.info("Change approved: id={}, reviewer={}", id, reviewerId);
|
log.info("Change approved: id={}, reviewer={}", id, reviewerId);
|
||||||
return ApiResponse.success();
|
return ApiResponse.success();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,9 @@ public interface SiteService {
|
|||||||
/** 待审核变更列表 */
|
/** 待审核变更列表 */
|
||||||
List<SiteChangeDTO> listPendingChanges();
|
List<SiteChangeDTO> listPendingChanges();
|
||||||
|
|
||||||
/** 审核通过 → 写入 sites 表 */
|
/** 审核通过 → 写入 sites 表(支持审核时编辑 name/baseUrl/siteType/status/notes) */
|
||||||
Site approveChange(Long changeId, Long reviewerId);
|
Site approveChange(Long changeId, Long reviewerId, String name, String baseUrl,
|
||||||
|
String siteType, String status, String notes);
|
||||||
|
|
||||||
/** 拒绝变更 */
|
/** 拒绝变更 */
|
||||||
void rejectChange(Long changeId, Long reviewerId);
|
void rejectChange(Long changeId, Long reviewerId);
|
||||||
|
|||||||
@@ -238,7 +238,8 @@ public class SiteServiceImpl implements SiteService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Site approveChange(Long changeId, Long reviewerId) {
|
public Site approveChange(Long changeId, Long reviewerId, String reviewerName, String reviewerBaseUrl,
|
||||||
|
String reviewerSiteType, String reviewerStatus, String reviewerNotes) {
|
||||||
SiteChange entry = changeMapper.selectById(changeId);
|
SiteChange entry = changeMapper.selectById(changeId);
|
||||||
if (entry == null) throw new IllegalArgumentException("变更记录不存在");
|
if (entry == null) throw new IllegalArgumentException("变更记录不存在");
|
||||||
if (!"pending".equals(entry.getStatus())) throw new IllegalArgumentException("该变更已处理");
|
if (!"pending".equals(entry.getStatus())) throw new IllegalArgumentException("该变更已处理");
|
||||||
@@ -250,11 +251,13 @@ public class SiteServiceImpl implements SiteService {
|
|||||||
throw new RuntimeException("解析变更数据失败", e);
|
throw new RuntimeException("解析变更数据失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 优先使用审核者编辑的数据,否则用原始提交数据
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
List<String> names = (List<String>) raw.getOrDefault("names", List.of());
|
List<String> names = reviewerName != null ? List.of(reviewerName)
|
||||||
@SuppressWarnings("unchecked")
|
: (List<String>) raw.getOrDefault("names", List.of());
|
||||||
List<String> urls = (List<String>) raw.getOrDefault("urls", List.of());
|
List<String> urls = reviewerBaseUrl != null ? List.of(reviewerBaseUrl)
|
||||||
String notes = (String) raw.getOrDefault("notes", "");
|
: (List<String>) raw.getOrDefault("urls", List.of());
|
||||||
|
String notes = reviewerNotes != null ? reviewerNotes : (String) raw.getOrDefault("notes", "");
|
||||||
|
|
||||||
Site existing = siteMapper.selectBySiteId(entry.getSiteId());
|
Site existing = siteMapper.selectBySiteId(entry.getSiteId());
|
||||||
Site site;
|
Site site;
|
||||||
@@ -262,7 +265,14 @@ public class SiteServiceImpl implements SiteService {
|
|||||||
existing.setNamesJson(toJsonArray(names));
|
existing.setNamesJson(toJsonArray(names));
|
||||||
existing.setUrlsJson(toJsonArray(urls));
|
existing.setUrlsJson(toJsonArray(urls));
|
||||||
if (notes != null && !notes.isBlank()) existing.setNotes(notes);
|
if (notes != null && !notes.isBlank()) existing.setNotes(notes);
|
||||||
existing.setStatus(SiteStatus.ACTIVE);
|
if (reviewerSiteType != null) {
|
||||||
|
try { existing.setSiteType(SiteType.fromValue(reviewerSiteType)); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
if (reviewerStatus != null) {
|
||||||
|
try { existing.setStatus(SiteStatus.fromValue(reviewerStatus)); } catch (Exception ignored) {}
|
||||||
|
} else {
|
||||||
|
existing.setStatus(SiteStatus.ACTIVE);
|
||||||
|
}
|
||||||
siteMapper.updateById(existing);
|
siteMapper.updateById(existing);
|
||||||
site = existing;
|
site = existing;
|
||||||
} else {
|
} else {
|
||||||
@@ -271,9 +281,9 @@ public class SiteServiceImpl implements SiteService {
|
|||||||
site.setNamesJson(toJsonArray(names));
|
site.setNamesJson(toJsonArray(names));
|
||||||
site.setUrlsJson(toJsonArray(urls));
|
site.setUrlsJson(toJsonArray(urls));
|
||||||
site.setNotes(notes);
|
site.setNotes(notes);
|
||||||
site.setSiteType(SiteType.UNKNOWN);
|
site.setSiteType(reviewerSiteType != null ? SiteType.fromValue(reviewerSiteType) : SiteType.UNKNOWN);
|
||||||
site.setSchemaVersion("1.0.0");
|
site.setSchemaVersion("1.0.0");
|
||||||
site.setStatus(SiteStatus.ACTIVE);
|
site.setStatus(reviewerStatus != null ? SiteStatus.fromValue(reviewerStatus) : SiteStatus.ACTIVE);
|
||||||
site.setSubmitterId(entry.getSubmitterId());
|
site.setSubmitterId(entry.getSubmitterId());
|
||||||
siteMapper.insert(site);
|
siteMapper.insert(site);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default {
|
|||||||
create: (data) => api.post('/sites', data),
|
create: (data) => api.post('/sites', data),
|
||||||
update: (id, data) => api.put(`/sites/${id}`, data),
|
update: (id, data) => api.put(`/sites/${id}`, data),
|
||||||
pendingChanges: () => api.get('/admin/sites/pending'),
|
pendingChanges: () => api.get('/admin/sites/pending'),
|
||||||
approveChange: (id) => api.post(`/admin/changes/${id}/approve`),
|
approveChange: (id, data) => api.post(`/admin/changes/${id}/approve`, data),
|
||||||
rejectChange: (id) => api.post(`/admin/changes/${id}/reject`),
|
rejectChange: (id) => api.post(`/admin/changes/${id}/reject`),
|
||||||
linkChange: (id, siteId) => api.post(`/admin/changes/${id}/link`, { siteId }),
|
linkChange: (id, siteId) => api.post(`/admin/changes/${id}/link`, { siteId }),
|
||||||
changesBySite: (siteId) => api.get(`/admin/sites/${siteId}/changes`),
|
changesBySite: (siteId) => api.get(`/admin/sites/${siteId}/changes`),
|
||||||
|
|||||||
@@ -32,6 +32,31 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 审核编辑弹窗 -->
|
||||||
|
<el-dialog v-model="approveVisible" title="审核通过 - 编辑站点信息" width="520px" :close-on-click-modal="false">
|
||||||
|
<el-form :model="approveForm" label-width="80px">
|
||||||
|
<el-form-item label="站点ID"><el-input v-model="approveForm.siteId" disabled /></el-form-item>
|
||||||
|
<el-form-item label="名称"><el-input v-model="approveForm.name" type="textarea" :rows="3" /></el-form-item>
|
||||||
|
<el-form-item label="地址"><el-input v-model="approveForm.baseUrl" type="textarea" :rows="3" /></el-form-item>
|
||||||
|
<el-form-item label="类型">
|
||||||
|
<el-select v-model="approveForm.siteType" style="width:100%">
|
||||||
|
<el-option v-for="t in siteTypes" :key="t.value" :label="t.label" :value="t.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态">
|
||||||
|
<el-select v-model="approveForm.status" style="width:100%">
|
||||||
|
<el-option label="活跃" value="active" />
|
||||||
|
<el-option label="弃用" value="deprecated" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注"><el-input v-model="approveForm.notes" type="textarea" :rows="2" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="approveVisible = false">取消</el-button>
|
||||||
|
<el-button type="success" @click="confirmApprove" :loading="saving">确认通过</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="matchVisible" title="关联到已有站点" width="500px">
|
<el-dialog v-model="matchVisible" title="关联到已有站点" width="500px">
|
||||||
<el-input v-model="matchQuery" placeholder="搜索站点名称或URL..." clearable @input="handleSearch" style="margin-bottom:12px" />
|
<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 :data="matchResults" stripe size="small" v-loading="matchSearching" max-height="300" @row-click="handleLinkSite" highlight-current-row>
|
||||||
@@ -48,6 +73,14 @@
|
|||||||
import api from '../api'
|
import api from '../api'
|
||||||
|
|
||||||
const submissions = ref([])
|
const submissions = ref([])
|
||||||
|
const approveVisible = ref(false), saving = ref(false)
|
||||||
|
const approveForm = reactive({ id: null, siteId: '', name: '', baseUrl: '', siteType: '', status: 'active', notes: '' })
|
||||||
|
const siteTypes = [
|
||||||
|
{ value: 'nexusphp', label: 'NexusPHP' }, { value: 'mtorrent', label: 'MTorrent' },
|
||||||
|
{ value: 'mteam', label: 'M-Team API' }, { value: 'pttime', label: 'PTTime' },
|
||||||
|
{ value: 'gazelle', label: 'Gazelle' }, { value: 'unit3d', label: 'UNIT3D' },
|
||||||
|
{ value: 'jsonapi', label: 'JSON API' }, { value: 'unknown', label: '未知' },
|
||||||
|
]
|
||||||
const matchVisible = ref(false), matchQuery = ref(''), matchResults = ref([]), matchSearching = ref(false)
|
const matchVisible = ref(false), matchQuery = ref(''), matchResults = ref([]), matchSearching = ref(false)
|
||||||
let matchTargetId = null
|
let matchTargetId = null
|
||||||
|
|
||||||
@@ -56,9 +89,33 @@ const fetch = async () => {
|
|||||||
catch { ElMessage.error('获取待审核列表失败') }
|
catch { ElMessage.error('获取待审核列表失败') }
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleApprove = async (row) => {
|
const handleApprove = (row) => {
|
||||||
try { await api.sites.approveChange(row.id); ElMessage.success('已通过'); fetch() }
|
approveForm.id = row.id
|
||||||
catch (e) { ElMessage.error(e.response?.data?.message||'操作失败') }
|
approveForm.siteId = row.siteId
|
||||||
|
approveForm.name = (row.names || []).join('\n')
|
||||||
|
approveForm.baseUrl = (row.urls || []).join('\n')
|
||||||
|
approveForm.siteType = row.siteType || 'unknown'
|
||||||
|
approveForm.status = row.siteStatus === '关停' ? 'deprecated' : 'active'
|
||||||
|
approveForm.notes = row.notes || ''
|
||||||
|
approveVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmApprove = async () => {
|
||||||
|
if (!approveForm.name.trim() || !approveForm.baseUrl.trim()) { ElMessage.warning('名称和地址不能为空'); return }
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await api.sites.approveChange(approveForm.id, {
|
||||||
|
name: approveForm.name.trim().split('\n').filter(Boolean)[0] || '',
|
||||||
|
baseUrl: approveForm.baseUrl.trim().split('\n').filter(Boolean)[0] || '',
|
||||||
|
siteType: approveForm.siteType || 'unknown',
|
||||||
|
status: approveForm.status,
|
||||||
|
notes: approveForm.notes || null,
|
||||||
|
})
|
||||||
|
ElMessage.success('已通过')
|
||||||
|
approveVisible.value = false
|
||||||
|
fetch()
|
||||||
|
} catch (e) { ElMessage.error(e.response?.data?.message||'操作失败') }
|
||||||
|
finally { saving.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = async (row) => {
|
const handleReject = async (row) => {
|
||||||
@@ -69,7 +126,6 @@ const handleReject = async (row) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleMatch = (row) => { matchTargetId = row.id; matchQuery.value = (row.names||[])[0]||''; matchVisible.value = true; handleSearch() }
|
const handleMatch = (row) => { matchTargetId = row.id; matchQuery.value = (row.names||[])[0]||''; matchVisible.value = true; handleSearch() }
|
||||||
|
|
||||||
const handleSearch = async () => {
|
const handleSearch = async () => {
|
||||||
if (!matchQuery.value.trim()) { matchResults.value = []; return }
|
if (!matchQuery.value.trim()) { matchResults.value = []; return }
|
||||||
matchSearching.value = true
|
matchSearching.value = true
|
||||||
@@ -77,7 +133,6 @@ const handleSearch = async () => {
|
|||||||
catch { matchResults.value = [] }
|
catch { matchResults.value = [] }
|
||||||
finally { matchSearching.value = false }
|
finally { matchSearching.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLinkSite = async (row) => {
|
const handleLinkSite = async (row) => {
|
||||||
try { await api.sites.linkChange(matchTargetId, row.siteId); ElMessage.success(`已关联到 ${row.siteId}`); matchVisible.value = false; fetch() }
|
try { await api.sites.linkChange(matchTargetId, row.siteId); ElMessage.success(`已关联到 ${row.siteId}`); matchVisible.value = false; fetch() }
|
||||||
catch (e) { ElMessage.error(e.response?.data?.message||'关联失败') }
|
catch (e) { ElMessage.error(e.response?.data?.message||'关联失败') }
|
||||||
|
|||||||
Reference in New Issue
Block a user