初始版本
This commit is contained in:
805
app/pages/admin/git-review.vue
Normal file
805
app/pages/admin/git-review.vue
Normal file
@@ -0,0 +1,805 @@
|
||||
<template>
|
||||
<div class="review-page">
|
||||
<!-- 页面头部 -->
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2 class="page-title">🔧 Git 审核管理</h2>
|
||||
<p class="page-desc">审核开发者的 Git 账号绑定与仓库权限申请</p>
|
||||
</div>
|
||||
<a-space>
|
||||
<a-button @click="loadAll" :loading="loadingGit || loadingPerm">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
刷新
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- Tab 切换 -->
|
||||
<a-tabs v-model:activeKey="activeTab" @change="handleTabChange">
|
||||
<!-- Git 账号审核 -->
|
||||
<a-tab-pane key="git-account" tab="Git 账号审核">
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="[16, 16]" class="mb-6">
|
||||
<a-col :xs="8" :md="6" v-for="stat in gitStats" :key="stat.key">
|
||||
<div
|
||||
class="stat-card"
|
||||
:class="[stat.color, { active: gitFilter.status === stat.key }]"
|
||||
@click="handleGitStatFilter(stat.key)"
|
||||
>
|
||||
<div class="stat-icon">{{ stat.icon }}</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ stat.value }}</div>
|
||||
<div class="stat-label">{{ stat.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">📋 Git 账号绑定列表</span>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
v-model:value="gitFilter.keyword"
|
||||
placeholder="搜索用户名/邮箱"
|
||||
style="width: 200px"
|
||||
@search="loadGitAccounts"
|
||||
allow-clear
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="gitColumns"
|
||||
:data-source="gitAccounts"
|
||||
:loading="loadingGit"
|
||||
:pagination="gitPagination"
|
||||
row-key="id"
|
||||
@change="handleGitTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'userInfo'">
|
||||
<div class="user-info-cell">
|
||||
<a-avatar :size="36" class="user-avatar">
|
||||
<template #icon><UserOutlined /></template>
|
||||
</a-avatar>
|
||||
<div class="user-info-text">
|
||||
<div class="user-name">{{ record.username }}</div>
|
||||
<div class="user-email" v-if="record.email">{{ record.email }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'remark'">
|
||||
<span class="text-gray">{{ record.remark || '-' }}</span>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="gitStatusColor(record.status)">
|
||||
{{ gitStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'time'">
|
||||
<span class="text-sm text-gray">{{ formatTime(record.updateTime || record.createTime) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleViewGitDetail(record)">详情</a-button>
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-popconfirm title="确认通过此 Git 账号绑定?" @confirm="handleApproveGit(record)">
|
||||
<a-button type="primary" size="small">通过</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button danger size="small" @click="handleRejectGit(record)">拒绝</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 仓库权限审核 -->
|
||||
<a-tab-pane key="permission-request" tab="仓库权限审核">
|
||||
<!-- 统计卡片 -->
|
||||
<a-row :gutter="[16, 16]" class="mb-6">
|
||||
<a-col :xs="8" :md="6" v-for="stat in permStats" :key="stat.key">
|
||||
<div
|
||||
class="stat-card"
|
||||
:class="[stat.color, { active: permFilter.status === stat.key }]"
|
||||
@click="handlePermStatFilter(stat.key)"
|
||||
>
|
||||
<div class="stat-icon">{{ stat.icon }}</div>
|
||||
<div class="stat-info">
|
||||
<div class="stat-value">{{ stat.value }}</div>
|
||||
<div class="stat-label">{{ stat.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 筛选栏 -->
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">📋 仓库权限申请列表</span>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
v-model:value="permFilter.keyword"
|
||||
placeholder="搜索用户名/仓库名"
|
||||
style="width: 200px"
|
||||
@search="loadPermRequests"
|
||||
allow-clear
|
||||
/>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<a-table
|
||||
:columns="permColumns"
|
||||
:data-source="permRequests"
|
||||
:loading="loadingPerm"
|
||||
:pagination="permPagination"
|
||||
row-key="id"
|
||||
@change="handlePermTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'applicant'">
|
||||
<div class="user-info-cell">
|
||||
<a-avatar :size="32" class="user-avatar">
|
||||
<template #icon><UserOutlined /></template>
|
||||
</a-avatar>
|
||||
<span class="user-name">{{ record.gitUsername }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'repo'">
|
||||
<a-tag color="blue">{{ record.repo }}</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'reason'">
|
||||
<a-tooltip :title="record.reason">
|
||||
<span class="reason-text">{{ record.reason }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag :color="permStatusColor(record.status)">
|
||||
{{ permStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'time'">
|
||||
<span class="text-sm text-gray">{{ formatTime(record.createdAt || record.reviewedAt) }}</span>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link" size="small" @click="handleViewPermDetail(record)">详情</a-button>
|
||||
<template v-if="record.status === 'pending'">
|
||||
<a-popconfirm title="确认通过此仓库权限申请?" @confirm="handleApprovePerm(record)">
|
||||
<a-button type="primary" size="small">通过</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button danger size="small" @click="handleRejectPerm(record)">拒绝</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<!-- Git 账号详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showGitDetailModal"
|
||||
:title="`Git 账号详情:${currentGit?.username || ''}`"
|
||||
width="560px"
|
||||
:footer="null"
|
||||
>
|
||||
<template v-if="currentGit">
|
||||
<a-descriptions :column="2" bordered size="small">
|
||||
<a-descriptions-item label="Gitea 用户名">{{ currentGit.username }}</a-descriptions-item>
|
||||
<a-descriptions-item label="邮箱">{{ currentGit.email || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="gitStatusColor(currentGit.status)">{{ gitStatusText(currentGit.status) }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="用户ID">{{ currentGit.userId }}</a-descriptions-item>
|
||||
<a-descriptions-item label="提交时间">{{ formatTime(currentGit.createTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="更新时间">{{ formatTime(currentGit.updateTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="备注" :span="2">{{ currentGit.remark || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="currentGit.verificationNote" label="审核备注" :span="2">
|
||||
<a-alert :type="currentGit.status === 'rejected' ? 'error' : 'success'" :message="currentGit.verificationNote" show-icon />
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<div v-if="currentGit.status === 'pending'" class="detail-actions">
|
||||
<a-popconfirm title="确认通过此 Git 账号绑定?" @confirm="handleApproveGit(currentGit)">
|
||||
<a-button type="primary" :loading="approvingGit">✅ 审核通过</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button danger @click="handleRejectGit(currentGit)">❌ 拒绝绑定</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 权限申请详情弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showPermDetailModal"
|
||||
:title="`权限申请详情:${currentPerm?.repoName || ''}`"
|
||||
width="560px"
|
||||
:footer="null"
|
||||
>
|
||||
<template v-if="currentPerm">
|
||||
<a-descriptions :column="2" bordered size="small">
|
||||
<a-descriptions-item label="申请人">{{ currentPerm.gitUsername }}</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="permStatusColor(currentPerm.status)">{{ permStatusText(currentPerm.status) }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="仓库" :span="2">
|
||||
<a-tag color="blue">{{ currentPerm.repo }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请理由" :span="2">
|
||||
<div class="detail-reason">{{ currentPerm.reason }}</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="申请时间">{{ formatTime(currentPerm.createdAt) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="审核时间">{{ formatTime(currentPerm.reviewedAt) || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="currentPerm.reviewerName" label="审核人">{{ currentPerm.reviewerName }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="currentPerm.rejectReason" label="拒绝原因" :span="2">
|
||||
<a-alert type="error" :message="currentPerm.rejectReason" show-icon />
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<div v-if="currentPerm.status === 'pending'" class="detail-actions">
|
||||
<a-popconfirm title="确认通过此仓库权限申请?" @confirm="handleApprovePerm(currentPerm)">
|
||||
<a-button type="primary" :loading="approvingPerm">✅ 审核通过</a-button>
|
||||
</a-popconfirm>
|
||||
<a-button danger @click="handleRejectPerm(currentPerm)">❌ 拒绝申请</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-modal>
|
||||
|
||||
<!-- 拒绝原因弹窗 -->
|
||||
<a-modal
|
||||
v-model:open="showRejectModal"
|
||||
:title="rejectModalTitle"
|
||||
:confirm-loading="rejecting"
|
||||
@ok="confirmReject"
|
||||
@cancel="showRejectModal = false"
|
||||
>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="拒绝原因" required>
|
||||
<a-textarea
|
||||
v-model:value="rejectReasonInput"
|
||||
:rows="4"
|
||||
:placeholder="rejectPlaceholder"
|
||||
:maxlength="500"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="reject-tips">
|
||||
<p>💡 常见拒绝原因:</p>
|
||||
<a-space wrap>
|
||||
<a-tag
|
||||
v-for="tip in rejectTips"
|
||||
:key="tip"
|
||||
class="reject-tip-tag"
|
||||
@click="rejectReasonInput = tip"
|
||||
>{{ tip }}</a-tag>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ReloadOutlined, UserOutlined } from '@ant-design/icons-vue'
|
||||
import { message } from 'ant-design-vue'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
pageGitAccounts,
|
||||
approveGitAccount,
|
||||
rejectGitAccount,
|
||||
pagePermissionRequestsAdmin,
|
||||
approvePermissionRequest,
|
||||
rejectPermissionRequest,
|
||||
type GitAccountItem,
|
||||
type PermissionRequestItem,
|
||||
} from '@/api/developer'
|
||||
|
||||
definePageMeta({ layout: 'admin' })
|
||||
useHead({ title: 'Git 审核管理 - 平台管理' })
|
||||
|
||||
// ==================== 通用 ====================
|
||||
const activeTab = ref('git-account')
|
||||
|
||||
function formatTime(time?: string) {
|
||||
return time ? dayjs(time).format('YYYY-MM-DD HH:mm') : '-'
|
||||
}
|
||||
|
||||
// ==================== 拒绝弹窗 ====================
|
||||
const showRejectModal = ref(false)
|
||||
const rejectReasonInput = ref('')
|
||||
const rejecting = ref(false)
|
||||
const rejectType = ref<'git' | 'perm'>('git')
|
||||
const rejectTargetId = ref<number>(0)
|
||||
const rejectTargetRecord = ref<GitAccountItem | PermissionRequestItem | null>(null)
|
||||
|
||||
const rejectModalTitle = computed(() => rejectType.value === 'git' ? '拒绝 Git 账号绑定' : '拒绝仓库权限申请')
|
||||
const rejectPlaceholder = computed(() => rejectType.value === 'git'
|
||||
? '请填写拒绝原因,以便开发者了解问题并修改'
|
||||
: '请填写拒绝原因,以便开发者了解问题')
|
||||
|
||||
const rejectTips = [
|
||||
'用户名与 Gitea 平台不一致,请核实后重新提交',
|
||||
'提交信息不完整,请补充后重新提交',
|
||||
'该账号存在异常,请联系管理员核实',
|
||||
'仓库暂时不对外开放权限申请',
|
||||
'申请理由不充分,请详细说明使用场景',
|
||||
]
|
||||
|
||||
function handleTabChange() {
|
||||
// tab 切换时无需额外操作,数据已加载
|
||||
}
|
||||
|
||||
// ==================== Git 账号审核 ====================
|
||||
const loadingGit = ref(false)
|
||||
const gitAccounts = ref<GitAccountItem[]>([])
|
||||
const gitFilter = reactive({ status: '', keyword: '' })
|
||||
const gitPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
})
|
||||
|
||||
const gitStats = reactive([
|
||||
{ key: 'pending', icon: '⏳', label: '待审核', value: 0, color: 'orange' },
|
||||
{ key: 'verified', icon: '✅', label: '已通过', value: 0, color: 'green' },
|
||||
{ key: 'rejected', icon: '❌', label: '已拒绝', value: 0, color: 'red' },
|
||||
{ key: '', icon: '📦', label: '全部', value: 0, color: 'blue' },
|
||||
])
|
||||
|
||||
const gitColumns = [
|
||||
{ title: '用户信息', key: 'userInfo', width: 220 },
|
||||
{ title: '备注', key: 'remark', ellipsis: true },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '更新时间', key: 'time', width: 160 },
|
||||
{ title: '操作', key: 'action', width: 200 },
|
||||
]
|
||||
|
||||
// 详情弹窗
|
||||
const showGitDetailModal = ref(false)
|
||||
const currentGit = ref<GitAccountItem | null>(null)
|
||||
const approvingGit = ref(false)
|
||||
|
||||
async function loadGitAccounts() {
|
||||
loadingGit.value = true
|
||||
try {
|
||||
const res = await pageGitAccounts({
|
||||
page: gitPagination.current,
|
||||
size: gitPagination.pageSize,
|
||||
status: gitFilter.status || undefined,
|
||||
keyword: gitFilter.keyword || undefined,
|
||||
})
|
||||
const listData = (res as any)?.data?.data
|
||||
gitAccounts.value = listData?.records || []
|
||||
gitPagination.total = listData?.total || 0
|
||||
updateGitStats()
|
||||
} catch {
|
||||
message.error('加载 Git 账号列表失败')
|
||||
} finally {
|
||||
loadingGit.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateGitStats() {
|
||||
try {
|
||||
const [pendingRes, verifiedRes, rejectedRes, allRes] = await Promise.allSettled([
|
||||
pageGitAccounts({ page: 1, size: 1, status: 'pending' }),
|
||||
pageGitAccounts({ page: 1, size: 1, status: 'verified' }),
|
||||
pageGitAccounts({ page: 1, size: 1, status: 'rejected' }),
|
||||
pageGitAccounts({ page: 1, size: 1 }),
|
||||
])
|
||||
const extract = (r: any) => (r.status === 'fulfilled' ? (r.value as any)?.data?.data?.total || 0 : 0)
|
||||
gitStats[0].value = extract(pendingRes)
|
||||
gitStats[1].value = extract(verifiedRes)
|
||||
gitStats[2].value = extract(rejectedRes)
|
||||
gitStats[3].value = extract(allRes)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function handleGitStatFilter(key: string) {
|
||||
gitFilter.status = key
|
||||
gitPagination.current = 1
|
||||
loadGitAccounts()
|
||||
}
|
||||
|
||||
function handleGitTableChange(pag: any) {
|
||||
gitPagination.current = pag.current
|
||||
gitPagination.pageSize = pag.pageSize
|
||||
loadGitAccounts()
|
||||
}
|
||||
|
||||
function handleViewGitDetail(record: GitAccountItem) {
|
||||
currentGit.value = record
|
||||
showGitDetailModal.value = true
|
||||
}
|
||||
|
||||
async function handleApproveGit(record: GitAccountItem) {
|
||||
approvingGit.value = true
|
||||
try {
|
||||
const res = await approveGitAccount(record.id) as any
|
||||
if (res?.data?.code === 200 || res?.data?.code === 0) {
|
||||
message.success(`Git 账号「${record.username}」已通过审核`)
|
||||
showGitDetailModal.value = false
|
||||
loadGitAccounts()
|
||||
} else {
|
||||
message.error(res?.data?.message || '操作失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.data?.message || e?.message || '操作失败')
|
||||
} finally {
|
||||
approvingGit.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleRejectGit(record: GitAccountItem) {
|
||||
rejectType.value = 'git'
|
||||
rejectTargetId.value = record.id
|
||||
rejectTargetRecord.value = record
|
||||
rejectReasonInput.value = ''
|
||||
showRejectModal.value = true
|
||||
}
|
||||
|
||||
// ==================== 权限申请审核 ====================
|
||||
const loadingPerm = ref(false)
|
||||
const permRequests = ref<PermissionRequestItem[]>([])
|
||||
const permFilter = reactive({ status: '', keyword: '' })
|
||||
const permPagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
})
|
||||
|
||||
const permStats = reactive([
|
||||
{ key: 'pending', icon: '⏳', label: '待审核', value: 0, color: 'orange' },
|
||||
{ key: 'approved', icon: '✅', label: '已通过', value: 0, color: 'green' },
|
||||
{ key: 'rejected', icon: '❌', label: '已拒绝', value: 0, color: 'red' },
|
||||
{ key: '', icon: '📦', label: '全部', value: 0, color: 'blue' },
|
||||
])
|
||||
|
||||
const permColumns = [
|
||||
{ title: '申请人', key: 'applicant', width: 160 },
|
||||
{ title: '仓库', key: 'repo', width: 200 },
|
||||
{ title: '申请理由', key: 'reason', ellipsis: true },
|
||||
{ title: '状态', key: 'status', width: 100 },
|
||||
{ title: '申请时间', key: 'time', width: 160 },
|
||||
{ title: '操作', key: 'action', width: 200 },
|
||||
]
|
||||
|
||||
// 详情弹窗
|
||||
const showPermDetailModal = ref(false)
|
||||
const currentPerm = ref<PermissionRequestItem | null>(null)
|
||||
const approvingPerm = ref(false)
|
||||
|
||||
async function loadPermRequests() {
|
||||
loadingPerm.value = true
|
||||
try {
|
||||
const res = await pagePermissionRequestsAdmin({
|
||||
page: permPagination.current,
|
||||
size: permPagination.pageSize,
|
||||
status: permFilter.status || undefined,
|
||||
keyword: permFilter.keyword || undefined,
|
||||
})
|
||||
const listData = (res as any)?.data?.data
|
||||
permRequests.value = listData?.records || []
|
||||
permPagination.total = listData?.total || 0
|
||||
updatePermStats()
|
||||
} catch {
|
||||
message.error('加载权限申请列表失败')
|
||||
} finally {
|
||||
loadingPerm.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePermStats() {
|
||||
try {
|
||||
const [pendingRes, approvedRes, rejectedRes, allRes] = await Promise.allSettled([
|
||||
pagePermissionRequestsAdmin({ page: 1, size: 1, status: 'pending' }),
|
||||
pagePermissionRequestsAdmin({ page: 1, size: 1, status: 'approved' }),
|
||||
pagePermissionRequestsAdmin({ page: 1, size: 1, status: 'rejected' }),
|
||||
pagePermissionRequestsAdmin({ page: 1, size: 1 }),
|
||||
])
|
||||
const extract = (r: any) => (r.status === 'fulfilled' ? (r.value as any)?.data?.data?.total || 0 : 0)
|
||||
permStats[0].value = extract(pendingRes)
|
||||
permStats[1].value = extract(approvedRes)
|
||||
permStats[2].value = extract(rejectedRes)
|
||||
permStats[3].value = extract(allRes)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function handlePermStatFilter(key: string) {
|
||||
permFilter.status = key
|
||||
permPagination.current = 1
|
||||
loadPermRequests()
|
||||
}
|
||||
|
||||
function handlePermTableChange(pag: any) {
|
||||
permPagination.current = pag.current
|
||||
permPagination.pageSize = pag.pageSize
|
||||
loadPermRequests()
|
||||
}
|
||||
|
||||
function handleViewPermDetail(record: PermissionRequestItem) {
|
||||
currentPerm.value = record
|
||||
showPermDetailModal.value = true
|
||||
}
|
||||
|
||||
async function handleApprovePerm(record: PermissionRequestItem) {
|
||||
approvingPerm.value = true
|
||||
try {
|
||||
const res = await approvePermissionRequest(record.id) as any
|
||||
if (res?.data?.code === 200 || res?.data?.code === 0) {
|
||||
message.success(`仓库权限「${record.repo}」已通过审核`)
|
||||
showPermDetailModal.value = false
|
||||
loadPermRequests()
|
||||
} else {
|
||||
message.error(res?.data?.message || '操作失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.data?.message || e?.message || '操作失败')
|
||||
} finally {
|
||||
approvingPerm.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleRejectPerm(record: PermissionRequestItem) {
|
||||
rejectType.value = 'perm'
|
||||
rejectTargetId.value = record.id
|
||||
rejectTargetRecord.value = record
|
||||
rejectReasonInput.value = ''
|
||||
showRejectModal.value = true
|
||||
}
|
||||
|
||||
// ==================== 确认拒绝 ====================
|
||||
async function confirmReject() {
|
||||
if (!rejectReasonInput.value.trim()) {
|
||||
message.warning('请填写拒绝原因')
|
||||
return
|
||||
}
|
||||
rejecting.value = true
|
||||
try {
|
||||
if (rejectType.value === 'git') {
|
||||
const res = await rejectGitAccount(rejectTargetId.value, rejectReasonInput.value) as any
|
||||
if (res?.data?.code === 200 || res?.data?.code === 0) {
|
||||
message.success('已拒绝 Git 账号绑定申请')
|
||||
showGitDetailModal.value = false
|
||||
showRejectModal.value = false
|
||||
loadGitAccounts()
|
||||
} else {
|
||||
message.error(res?.data?.message || '操作失败')
|
||||
}
|
||||
} else {
|
||||
const res = await rejectPermissionRequest(rejectTargetId.value, rejectReasonInput.value) as any
|
||||
if (res?.data?.code === 200 || res?.data?.code === 0) {
|
||||
message.success('已拒绝仓库权限申请')
|
||||
showPermDetailModal.value = false
|
||||
showRejectModal.value = false
|
||||
loadPermRequests()
|
||||
} else {
|
||||
message.error(res?.data?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e?.data?.message || e?.message || '操作失败')
|
||||
} finally {
|
||||
rejecting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 状态映射 ====================
|
||||
function gitStatusText(status?: string) {
|
||||
const map: Record<string, string> = { pending: '待审核', verified: '已通过', rejected: '已拒绝' }
|
||||
return map[status || ''] || status || '-'
|
||||
}
|
||||
|
||||
function gitStatusColor(status?: string) {
|
||||
const map: Record<string, string> = { pending: 'orange', verified: 'success', rejected: 'error' }
|
||||
return map[status || ''] || 'default'
|
||||
}
|
||||
|
||||
function permStatusText(status?: string) {
|
||||
const map: Record<string, string> = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
|
||||
return map[status || ''] || status || '-'
|
||||
}
|
||||
|
||||
function permStatusColor(status?: string) {
|
||||
const map: Record<string, string> = { pending: 'orange', approved: 'success', rejected: 'error' }
|
||||
return map[status || ''] || 'default'
|
||||
}
|
||||
|
||||
// ==================== 加载全部 ====================
|
||||
function loadAll() {
|
||||
loadGitAccounts()
|
||||
loadPermRequests()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadAll()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.review-page {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
font-size: 13px;
|
||||
color: #9ca3af;
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
|
||||
/* 统计卡片 */
|
||||
.stat-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
|
||||
.stat-card.active { border-color: currentColor; box-shadow: 0 4px 12px rgba(0,0,0,0.12); }
|
||||
|
||||
.stat-card.blue { background: #eff6ff; border-color: #dbeafe; }
|
||||
.stat-card.orange { background: #fff7ed; border-color: #fed7aa; }
|
||||
.stat-card.green { background: #f0fdf4; border-color: #bbf7d0; }
|
||||
.stat-card.red { background: #fff1f2; border-color: #fecdd3; }
|
||||
|
||||
.stat-card.active.blue { border-color: #3b82f6; }
|
||||
.stat-card.active.orange { border-color: #f97316; }
|
||||
.stat-card.active.green { border-color: #22c55e; }
|
||||
.stat-card.active.red { border-color: #ef4444; }
|
||||
|
||||
.stat-icon { font-size: 28px; flex-shrink: 0; }
|
||||
|
||||
.stat-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
/* 面板 */
|
||||
.panel {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
/* 用户信息格 */
|
||||
.user-info-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
flex-shrink: 0;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.user-info-text { flex: 1; min-width: 0; }
|
||||
|
||||
.user-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.user-email {
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.reason-text {
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 详情弹窗 */
|
||||
.detail-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.detail-reason {
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 拒绝原因提示 */
|
||||
.reject-tips {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.reject-tips p {
|
||||
font-size: 12px;
|
||||
color: rgba(0,0,0,0.45);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.reject-tip-tag {
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.reject-tip-tag:hover {
|
||||
color: #4f46e5;
|
||||
border-color: #4f46e5;
|
||||
}
|
||||
|
||||
.mb-6 { margin-bottom: 24px; }
|
||||
.text-sm { font-size: 12px; }
|
||||
.text-gray { color: rgba(0,0,0,0.45); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user