Files
par/web/src/views/SiteReview.vue
2026-06-30 14:18:16 +08:00

151 lines
7.3 KiB
Vue

<template>
<div>
<h2>站点审核</h2>
<el-card style="margin-top: 20px" v-if="submissions.length === 0">
<div style="text-align:center;padding:40px;color:#909399">暂无待审核提交</div>
</el-card>
<el-card style="margin-top: 20px" v-else>
<el-table :data="submissions" stripe size="small">
<el-table-column prop="siteId" label="站点ID" width="120" />
<el-table-column label="名称" width="180">
<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="200">
<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-column label="站点状态" width="90">
<template #default="scope">
<el-tag size="small" :type="scope.row.siteStatus==='正常'?'success':scope.row.siteStatus==='关停'?'danger':'warning'">{{ scope.row.siteStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column label="备注" min-width="120">
<template #default="scope"><span class="text-xs text-muted">{{ scope.row.notes||'-' }}</span></template>
</el-table-column>
<el-table-column prop="createdAt" label="提交时间" width="160" />
<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>
</el-card>
<!-- 审核编辑弹窗 -->
<el-dialog v-model="approveVisible" title="审核通过 - 编辑站点信息" width="500px" :close-on-click-modal="false">
<el-form 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.namesText" type="textarea" :rows="3" placeholder="站点名称(一行一个)" />
</el-form-item>
<el-form-item label="地址">
<el-input v-model="approveForm.urlsText" type="textarea" :rows="3" placeholder="站点地址(一行一个)" />
</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-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>
<script setup>
import api from '../api'
const submissions = ref([])
const approveVisible = ref(false), saving = ref(false)
const approveForm = reactive({ id: null, siteId: '', namesText: '', urlsText: '', siteType: '', status: 'active', notes: '' })
const siteTypes = [
{ value: 'nexusphp', label: 'NexusPHP' }, { value: 'mtorrent', label: 'MTorrent' },
{ value: 'gazelle', label: 'Gazelle' }, { value: 'unit3d', label: 'UNIT3D' },
{ value: 'unknown', label: '未知' },
]
const matchVisible = ref(false), matchQuery = ref(''), matchResults = ref([]), matchSearching = ref(false)
let matchTargetId = null
const fetch = async () => {
try { const r = await api.sites.pendingChanges(); submissions.value = r.data || [] }
catch { ElMessage.error('获取待审核列表失败') }
}
const handleApprove = (row) => {
approveForm.id = row.id
approveForm.siteId = row.siteId
approveForm.namesText = (row.names || []).join('\n')
approveForm.urlsText = (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 () => {
const names = approveForm.namesText.split('\n').map(s => s.trim()).filter(Boolean)
const urls = approveForm.urlsText.split('\n').map(s => s.trim()).filter(Boolean)
if (names.length === 0 || urls.length === 0) { ElMessage.warning('名称和地址不能为空'); return }
saving.value = true
try {
await api.sites.approveChange(approveForm.id, {
names, urls,
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) => {
try {
await ElMessageBox.confirm(`确认拒绝 "${(row.names||[])[0]||row.siteId}" 的提交?`, '警告', { type: 'warning' })
await api.sites.rejectChange(row.id); ElMessage.success('已拒绝'); fetch()
} catch (e) { if (e !== 'cancel') ElMessage.error(e.response?.data?.message||'操作失败') }
}
const handleMatch = (row) => { matchTargetId = 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 r = await api.sites.search(matchQuery.value.trim()); matchResults.value = r.data||[] }
catch { matchResults.value = [] }
finally { matchSearching.value = false }
}
const handleLinkSite = async (row) => {
try { await api.sites.linkChange(matchTargetId, row.siteId); ElMessage.success(`已关联到 ${row.siteId}`); matchVisible.value = false; fetch() }
catch (e) { ElMessage.error(e.response?.data?.message||'关联失败') }
}
onMounted(fetch)
</script>
<style scoped>
.text-muted { color: #909399; }
</style>