chore: establish V1.1 recovery baseline

This commit is contained in:
2026-09-09 22:05:00 +08:00
commit b46b48eb33
947 changed files with 155471 additions and 0 deletions
@@ -0,0 +1,176 @@
<template>
<div class="page-container" v-loading="loading">
<el-card shadow="never" v-if="user">
<template #header>
<div class="card-header">
<span>用户详情</span>
<el-tag :type="accountStatusType(user.accountStatus)">{{ accountStatusText(user.accountStatus) }}</el-tag>
<el-tag :type="riskLevelType(user.riskLevel)">{{ riskLevelText(user.riskLevel) }}</el-tag>
</div>
</template>
<el-tabs v-model="activeTab">
<!-- 基础信息 -->
<el-tab-pane label="基础信息" name="basic">
<el-descriptions :column="2" border>
<el-descriptions-item label="用户ID">{{ user.id }}</el-descriptions-item>
<el-descriptions-item label="显示名称">{{ user.displayName }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ genderText(user.gender) }}</el-descriptions-item>
<el-descriptions-item label="年龄">{{ user.age || '-' }}</el-descriptions-item>
<el-descriptions-item label="手机">
<template v-if="user.sensitiveMasked">
<span class="masked">{{ user.phoneMasked }}</span>
<el-button size="small" link @click="requestSensitive">需要查看</el-button>
</template>
<template v-else>{{ user.phone }}</template>
</el-descriptions-item>
<el-descriptions-item label="区域">{{ user.regionName || '-' }}</el-descriptions-item>
<el-descriptions-item label="注册时间">{{ formatTime(user.registeredAt) }}</el-descriptions-item>
<el-descriptions-item label="最近登录">{{ formatTime(user.lastLoginAt) }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 婚恋资料 -->
<el-tab-pane label="婚恋资料" name="profile">
<el-descriptions :column="2" border>
<el-descriptions-item label="婚姻状态">{{ maritalText(user.maritalStatus) }}</el-descriptions-item>
<el-descriptions-item label="子女情况">{{ childrenText(user.childrenStatus) }}</el-descriptions-item>
<el-descriptions-item label="学历">{{ eduText(user.educationCode) }}</el-descriptions-item>
<el-descriptions-item label="职业">{{ occText(user.occupationCode) }}</el-descriptions-item>
<el-descriptions-item label="自我介绍" :span="2">{{ user.selfIntro || '未填写' }}</el-descriptions-item>
<el-descriptions-item label="资料完整度">{{ user.profileCompletion }}%</el-descriptions-item>
<el-descriptions-item label="资料状态">{{ profileStatusText(user.profileStatus) }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
<!-- 风险记录 -->
<el-tab-pane label="风险记录" name="risk">
<el-descriptions :column="2" border>
<el-descriptions-item label="风险分数">{{ user.totalRiskScore ?? 0 }}</el-descriptions-item>
<el-descriptions-item label="风险等级">
<el-tag :type="riskLevelType(user.riskLevel)">{{ riskLevelText(user.riskLevel) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="历史举报数">{{ user.totalReportsCount ?? 0 }}</el-descriptions-item>
<el-descriptions-item label="当前处置数">{{ user.activeActionsCount ?? 0 }}</el-descriptions-item>
</el-descriptions>
<div style="margin-top:16px">
<el-button type="primary" size="small" @click="showStatusDialog = true">修改账号状态</el-button>
</div>
</el-tab-pane>
<!-- 状态记录 -->
<el-tab-pane label="状态记录" name="status">
<el-descriptions :column="2" border>
<el-descriptions-item label="账号状态">
<el-tag :type="accountStatusType(user.accountStatus)">{{ accountStatusText(user.accountStatus) }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="婚恋状态">{{ relationshipText(user.relationshipStatus) }}</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 修改状态对话框 -->
<el-dialog v-model="showStatusDialog" title="修改账号状态" width="400px">
<el-form label-width="100px">
<el-form-item label="新状态">
<el-select v-model="newStatus" style="width:100%">
<el-option label="正常" value="normal" />
<el-option label="风险审核中" value="reviewing_risk" />
<el-option label="功能受限" value="limited" />
<el-option label="临时暂停" value="suspended" />
<el-option label="封禁" value="banned" />
<el-option label="已注销" value="closed" />
</el-select>
</el-form-item>
<el-form-item label="原因">
<el-input v-model="statusNote" type="textarea" :rows="3" placeholder="请输入修改原因" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showStatusDialog = false">取消</el-button>
<el-button type="primary" :loading="updating" @click="updateStatus">确认</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router' // eslint-disable-next-line
import { get, put } from '@/api/client'
const route = useRoute()
const loading = ref(true)
const user = ref<any>(null)
const activeTab = ref('basic')
const showStatusDialog = ref(false)
const newStatus = ref('normal')
const statusNote = ref('')
const updating = ref(false)
const accountId = route.query.id as string
const ACCOUNT_MAP: Record<string, { text: string; type: string }> = {
normal: { text: '正常', type: 'success' },
banned: { text: '封禁', type: 'danger' },
suspended: { text: '暂停', type: 'warning' },
reviewing_risk: { text: '风险审核', type: 'danger' },
limited: { text: '受限', type: 'info' }
}
const RISK_MAP: Record<string, { text: string; type: string }> = {
normal: { text: '正常', type: 'info' },
watch: { text: '关注', type: '' },
high: { text: '高风险', type: 'warning' },
critical: { text: '极高风险', type: 'danger' }
}
async function loadDetail() {
if (!accountId) return
loading.value = true
try {
const token = localStorage.getItem('accessToken')
const res: any = await get(`/operator/users/${accountId}`, {}, { Authorization: `Bearer ${token}` })
user.value = res
} catch (e) { console.error(e) }
finally { loading.value = false }
}
async function updateStatus() {
if (!newStatus.value) return
updating.value = true
try {
const token = localStorage.getItem('accessToken')
await put(`/operator/users/${accountId}/status`, { accountStatus: newStatus.value }, {
Authorization: `Bearer ${token}`
})
showStatusDialog.value = false
loadDetail()
} catch (e) { console.error(e) }
finally { updating.value = false }
}
function requestSensitive() { /* TODO: 需要敏感权限的申请流程 */ }
function genderText(g?: string) { return g === 'male' ? '男' : g === 'female' ? '女' : '-' }
function maritalText(m?: string) { const m2 = m as string | undefined; return m2 && { single: '未婚', divorced: '离异', widowed: '丧偶' }[m2] || '-' }
function childrenText(c?: string) { const c2 = c as string | undefined; return c2 && { none: '无子女', with_children: '有子女' }[c2] || '-' }
function eduText(e?: string) { return e || '-' }
function occText(o?: string) { return o || '-' }
function profileStatusText(s?: string) { const s2 = s as string | undefined; return s2 && { approved: '已审核', pending: '待审核', need_edit: '需修改' }[s2] || s }
function relationshipText(r?: string) { const r2 = r as string | undefined; return r2 && { searching: '寻找中', getting_to_know: '正在了解', matched_successfully: '已牵手', paused: '暂停' }[r2] || r || '-' }
function accountStatusType(s: string) { return ACCOUNT_MAP[s]?.type || 'info' }
function accountStatusText(s: string) { return ACCOUNT_MAP[s]?.text || s }
function riskLevelType(l: string) { return RISK_MAP[l]?.type || 'info' }
function riskLevelText(l: string) { return RISK_MAP[l]?.text || l }
function formatTime(t?: string) { return t ? new Date(t).toLocaleString('zh-CN') : '-' }
onMounted(loadDetail)
</script>
<style scoped>
.page-container { padding: 24px; }
.card-header { display: flex; align-items: center; gap: 12px; }
.masked { color: #999; font-style: italic; }
</style>