fix(identity): complete native certification review flow

This commit is contained in:
kate522 committed 2026-07-29 12:25:58 +08:00
1 parent 32658be87d
commit 2dfaa10798
16 files changed
+571 -1167

No files matched your search

@@ -1,387 +0,0 @@
<template>
<div class="aksrt-page-container">
<PageHeader
title="身份认证"
desc="维护公开认证展示和后台审核台,统一查看待审、已通过、已驳回的人工认证记录。"
/>
<n-grid cols="1 s:1 m:2" :x-gap="16" :y-gap="16" responsive="screen">
<n-grid-item>
<n-card size="small" title="当前状态">
<n-space wrap>
<n-tag round :type="form.user_verify_enabled ? 'success' : 'default'">
认证结构:{{ form.user_verify_enabled ? '已启用' : '已关闭' }}
</n-tag>
<n-tag round :type="authSummary.emailVerificationEnabled ? 'info' : 'default'">
邮箱验证:{{ authSummary.emailVerificationEnabled ? '已启用' : '未启用' }}
</n-tag>
<n-tag round type="warning">
待审核:{{ counts.pending }}
</n-tag>
<n-tag round type="success">
已通过:{{ counts.verified }}
</n-tag>
<n-tag round type="default">
已驳回:{{ counts.rejected }}
</n-tag>
</n-space>
</n-card>
</n-grid-item>
<n-grid-item>
<n-card size="small" title="审核说明">
<n-alert type="info" :show-icon="false">
官方认证必须由用户提交申请并经审核通过;邮箱验证仅用于账户安全,不授予官方认证徽标。
</n-alert>
</n-card>
</n-grid-item>
</n-grid>
<n-card class="aksrt-verify-page__card" size="small" title="公开展示与审核开关">
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
<n-form-item label="启用身份认证结构">
<n-switch v-model:value="form.user_verify_enabled" />
</n-form-item>
<n-form-item label="公开显示认证徽标">
<n-switch v-model:value="form.user_verify_public_display" :disabled="!form.user_verify_enabled" />
</n-form-item>
<n-form-item label="认证徽标文案">
<n-input v-model:value="form.user_verify_badge_text" :disabled="!form.user_verify_enabled" maxlength="20" />
</n-form-item>
<n-form-item label="启用人工审核台">
<n-switch v-model:value="form.user_verify_require_manual_review" :disabled="!form.user_verify_enabled" />
</n-form-item>
<n-form-item label="申请类型选项">
<n-input
v-model:value="form.user_verify_apply_option"
type="textarea"
:autosize="{ minRows: 2, maxRows: 6 }"
:disabled="!form.user_verify_enabled"
placeholder="slug|显示名称,每行一条"
/>
</n-form-item>
<n-form-item label="允许用户前台申请">
<n-switch v-model:value="form.user_verify_allow_apply" :disabled="!form.user_verify_enabled" />
</n-form-item>
<n-form-item v-if="!form.user_verify_allow_apply" label="停用申请说明">
<n-input
v-model:value="form.user_verify_disable_desc"
type="textarea"
:autosize="{ minRows: 2, maxRows: 6 }"
:disabled="!form.user_verify_enabled"
/>
</n-form-item>
<n-form-item v-if="form.user_verify_allow_apply" label="申请要求等级">
<n-input-number
v-model:value="form.user_verify_limit_level"
:min="0"
:max="99"
:disabled="!form.user_verify_enabled"
style="width: 180px;"
/>
</n-form-item>
<n-form-item v-if="form.user_verify_allow_apply" label="申请介绍">
<n-input
v-model:value="form.user_verify_desc"
type="textarea"
:autosize="{ minRows: 3, maxRows: 8 }"
:disabled="!form.user_verify_enabled"
/>
</n-form-item>
<div class="aksrt-form-actions">
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
</div>
</n-form>
</n-card>
<n-card class="aksrt-verify-page__card" size="small" title="认证审核台">
<n-space vertical :size="12">
<n-grid cols="1 s:2 m:4" :x-gap="12" :y-gap="12" responsive="screen">
<n-grid-item>
<n-input v-model:value="filters.keyword" clearable placeholder="搜索用户、邮箱、备注" />
</n-grid-item>
<n-grid-item>
<n-select v-model:value="filters.status" :options="statusOptions" placeholder="审核状态" />
</n-grid-item>
<n-grid-item>
<n-date-picker v-model:value="filters.dateRange" type="daterange" clearable style="width: 100%;" />
</n-grid-item>
<n-grid-item>
<n-button type="primary" block @click="handleSearch">筛选记录</n-button>
</n-grid-item>
</n-grid>
<n-data-table
:columns="columns"
:data="requests"
:loading="reviewLoading"
:bordered="false"
:pagination="pagination"
:row-key="(row) => row.user_id"
/>
</n-space>
</n-card>
<VerifyReviewDrawer
v-model:show="drawerVisible"
:user-id="currentUserId"
@updated="loadRequests"
/>
</div>
</template>
<script setup>
import { computed, h, onMounted, reactive, ref } from 'vue'
import { NButton, NSpace, NTag, useMessage } from 'naive-ui'
import api from '@/api'
import PageHeader from '@/components/PageHeader.vue'
import VerifyReviewDrawer from './components/VerifyReviewDrawer.vue'
const message = useMessage()
const loading = ref(true)
const saving = ref(false)
const reviewLoading = ref(false)
const requests = ref([])
const drawerVisible = ref(false)
const currentUserId = ref(null)
const form = reactive({
user_verify_enabled: true,
user_verify_public_display: true,
user_verify_badge_text: '已认证',
user_verify_require_manual_review: false,
user_verify_allow_apply: true,
user_verify_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
user_verify_limit_level: 2,
user_verify_desc: '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
user_verify_apply_option: 'real_name|实名认证\norganization|企业认证',
})
const authSummary = reactive({
emailVerificationEnabled: false,
})
const counts = reactive({
pending: 0,
verified: 0,
rejected: 0,
total: 0,
})
const filters = reactive({
keyword: '',
status: '',
dateRange: null,
})
const pager = reactive({
page: 1,
pageSize: 20,
itemCount: 0,
})
const optionsState = reactive({
statuses: [],
})
const statusOptions = computed(() => optionsState.statuses.length
? [{ label: '全部状态', value: '' }, ...optionsState.statuses]
: [
{ label: '全部状态', value: '' },
{ label: '待审核', value: 'pending' },
{ label: '已通过', value: 'verified' },
{ label: '已驳回', value: 'rejected' },
])
const pagination = computed(() => ({
page: pager.page,
pageSize: pager.pageSize,
itemCount: pager.itemCount,
showSizePicker: true,
pageSizes: [10, 20, 50],
onUpdatePage: (page) => {
pager.page = page
loadRequests()
},
onUpdatePageSize: (pageSize) => {
pager.pageSize = pageSize
pager.page = 1
loadRequests()
},
}))
const columns = [
{
title: '用户',
key: 'display_name',
width: 160,
},
{
title: '邮箱',
key: 'email',
minWidth: 220,
ellipsis: { tooltip: true },
},
{
title: '状态',
key: 'status',
width: 100,
render: (row) => {
const typeMap = { pending: 'warning', verified: 'success', rejected: 'error' }
return h(NTag, { size: 'small', round: true, type: typeMap[row.status] || 'default' }, () => row.status)
},
},
{
title: '邮箱验证',
key: 'email_verified',
width: 100,
render: (row) => row.email_verified ? '已完成' : '未完成',
},
{
title: '申请时间',
key: 'requested_at',
width: 170,
},
{
title: '处理时间',
key: 'reviewed_at',
width: 170,
render: (row) => row.reviewed_at || '-',
},
{
title: '历史',
key: 'history_count',
width: 80,
},
{
title: '操作',
key: 'actions',
width: 120,
render: (row) => h(NSpace, { size: 'small' }, () => [
h(NButton, {
size: 'tiny',
type: 'primary',
quaternary: true,
onClick: () => openDrawer(row.user_id),
}, () => '审核'),
]),
},
]
onMounted(async () => {
await load()
})
function extractData(response) {
return response?.data ?? response ?? {}
}
function formatDateValue(value) {
const date = new Date(value)
const year = date.getFullYear()
const month = `${date.getMonth() + 1}`.padStart(2, '0')
const day = `${date.getDate()}`.padStart(2, '0')
return `${year}-${month}-${day}`
}
function buildParams() {
const params = {
page: pager.page,
per_page: pager.pageSize,
}
if (filters.keyword) params.keyword = filters.keyword
if (filters.status) params.status = filters.status
if (Array.isArray(filters.dateRange) && filters.dateRange.length === 2) {
params.date_from = formatDateValue(filters.dateRange[0])
params.date_to = formatDateValue(filters.dateRange[1])
}
return params
}
async function load() {
loading.value = true
try {
const [schemaRes, authRes] = await Promise.all([
api.getSchemaPage('users', 'verify'),
api.getAuthSettings(),
])
const schemaData = extractData(schemaRes)
const authData = extractData(authRes)
form.user_verify_enabled = schemaData.user_verify_enabled !== false
form.user_verify_public_display = schemaData.user_verify_public_display !== false
form.user_verify_badge_text = schemaData.user_verify_badge_text || '已认证'
form.user_verify_require_manual_review = !!schemaData.user_verify_require_manual_review
form.user_verify_allow_apply = schemaData.user_verify_allow_apply !== false
form.user_verify_disable_desc = schemaData.user_verify_disable_desc
|| '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员'
form.user_verify_limit_level = Number(schemaData.user_verify_limit_level ?? 2)
form.user_verify_desc = schemaData.user_verify_desc
|| '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。'
form.user_verify_apply_option = schemaData.user_verify_apply_option
|| 'real_name|实名认证\norganization|企业认证'
authSummary.emailVerificationEnabled = !!authData.registration_email_verification_enabled
await loadRequests()
} catch (error) {
message.error(error?.message || '加载身份认证页失败')
} finally {
loading.value = false
}
}
async function loadRequests() {
reviewLoading.value = true
try {
const res = await api.request('/verify/requests', { params: buildParams() })
const data = res?.data || {}
requests.value = data.items || []
pager.itemCount = data.total || 0
Object.assign(counts, data.counts || {})
optionsState.statuses = data.options?.statuses || []
} catch (error) {
message.error(error?.message || '加载认证审核台失败')
} finally {
reviewLoading.value = false
}
}
async function handleSave() {
saving.value = true
try {
await api.saveSchemaPage('users', 'verify', {
user_verify_enabled: form.user_verify_enabled,
user_verify_public_display: form.user_verify_public_display,
user_verify_badge_text: form.user_verify_badge_text,
user_verify_require_manual_review: form.user_verify_require_manual_review,
user_verify_allow_apply: form.user_verify_allow_apply,
user_verify_disable_desc: form.user_verify_disable_desc,
user_verify_limit_level: Number(form.user_verify_limit_level || 0),
user_verify_desc: form.user_verify_desc,
user_verify_apply_option: form.user_verify_apply_option,
})
message.success('身份认证配置已保存')
} catch (error) {
message.error(error?.message || '保存失败')
} finally {
saving.value = false
}
}
function handleSearch() {
pager.page = 1
loadRequests()
}
function openDrawer(userId) {
currentUserId.value = userId
drawerVisible.value = true
}
</script>
<style scoped>
.aksrt-verify-page__card {
margin-top: 16px;
}
</style>