Files
ApiServer-Web-admin_dashboa…/src/views/user/UserDetail.vue
T
2025-12-08 15:24:48 +08:00

1241 lines
36 KiB
Vue

<template>
<div class="user-detail-page">
<!-- 顶部导航 -->
<div class="page-header">
<div class="header-left">
<el-button @click="goBack" link class="back-btn">
<el-icon><ArrowLeft /></el-icon> 返回列表
</el-button>
<el-divider direction="vertical" />
<span class="page-title">用户详情</span>
</div>
<div class="header-right">
<el-button type="primary" plain @click="refreshData" :loading="loading">
<el-icon><Refresh /></el-icon> 刷新数据
</el-button>
</div>
</div>
<div class="main-content" v-loading="loading">
<!-- 用户概览卡片 -->
<el-card class="profile-card" shadow="never">
<div class="profile-header">
<div class="profile-basic">
<div class="avatar-wrapper" @click="handleAvatarManage">
<el-avatar :size="80" :src="currentAvatarUrl || userInfo.Avatar" :icon="UserFilled" class="user-avatar" />
<div class="avatar-edit-hint"><el-icon><Camera /></el-icon></div>
</div>
<div class="user-identity">
<div class="name-row">
<h1 class="user-name">{{ userInfo.UserName || '未命名用户' }}</h1>
<el-tag :type="userInfo.IsDeleted ? 'danger' : 'success'" effect="dark" round size="small" class="status-tag">
{{ userInfo.IsDeleted ? '已禁用' : '正常' }}
</el-tag>
<el-tag v-if="userInfo.UserGroup" type="info" effect="plain" round size="small" class="group-tag">
{{ userInfo.UserGroup?.Name }}
</el-tag>
</div>
<div class="id-row">
<span class="label">UID:</span>
<span class="value">{{ userInfo.UserId }}</span>
<el-divider direction="vertical" />
<span class="label">注册时间:</span>
<span class="value">{{ formatDate(userInfo.CreatedAt) }}</span>
</div>
</div>
</div>
<div class="profile-stats">
<div class="stat-item">
<div class="stat-label">实名状态</div>
<div class="stat-value">
<el-tag :type="userInfo.RealName?.Status === 1 ? 'success' : 'warning'" size="small" effect="light">
{{ getRealNameStatusText(userInfo.RealName?.Status) }}
</el-tag>
</div>
</div>
<div class="stat-item">
<div class="stat-label">推荐人ID</div>
<div class="stat-value">{{ userInfo.RecommendUserId || '-' }}</div>
</div>
<div class="stat-item">
<div class="stat-label">用户组ID</div>
<div class="stat-value">{{ userInfo.UserGroupId || '-' }}</div>
</div>
</div>
</div>
<el-divider class="action-divider" />
<!-- 快捷操作栏 -->
<div class="quick-actions">
<el-button type="primary" plain :icon="Edit" @click="handleEditUser">编辑信息</el-button>
<el-button type="success" plain :icon="Wallet" @click="handleBalanceManage">余额管理</el-button>
<el-button type="warning" plain :icon="Lock" @click="handlePasswordManage">修改密码</el-button>
<el-button type="info" plain :icon="UserFilled" @click="handleGroupManage">用户组</el-button>
<el-button type="primary" plain :icon="Document" @click="handleRealnameManage">实名管理</el-button>
<el-button type="danger" plain :icon="Key" @click="handleTokenManage">管理员权限</el-button>
<el-button type="success" plain :icon="Switch" @click="handleSimulateLogin">模拟登录</el-button>
<el-button type="danger" plain :icon="Delete" @click="handleDeleteUser">删除用户</el-button>
</div>
</el-card>
<div class="detail-grid">
<!-- 左侧信息栏 -->
<div class="left-column">
<el-card class="info-section-card" shadow="never">
<template #header>
<div class="card-header">
<span class="title">基本信息</span>
</div>
</template>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="手机号">{{ userInfo.Phone || '-' }}</el-descriptions-item>
<el-descriptions-item label="邮箱">{{ userInfo.Email || '-' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ userInfo.Sex || '-' }}</el-descriptions-item>
<el-descriptions-item label="年龄">{{ userInfo.Age || '-' }}</el-descriptions-item>
<el-descriptions-item label="最后更新">{{ formatDate(userInfo.UpdatedAt) }}</el-descriptions-item>
</el-descriptions>
</el-card>
<el-card class="info-section-card" shadow="never" style="margin-top: 20px;">
<template #header>
<div class="card-header">
<span class="title">实名信息</span>
</div>
</template>
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="真实姓名">{{ userInfo.RealName?.Name || '-' }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ userInfo.RealName?.IdCard || '-' }}</el-descriptions-item>
<el-descriptions-item label="认证类型">{{ getRealNameTypeText(userInfo.RealName?.Type) }}</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
<!-- 右侧记录栏 -->
<div class="right-column">
<el-card class="tabs-card" shadow="never">
<el-tabs v-model="activeTabName" @tab-click="handleTabClick" class="custom-tabs">
<el-tab-pane label="登录记录" name="1">
<el-table :data="loginHistory" v-loading="loginHistoryLoading" stripe style="width: 100%">
<el-table-column prop="CreatedAt" label="时间" width="180">
<template #default="{row}">{{ formatDate(row.CreatedAt) }}</template>
</el-table-column>
<el-table-column prop="Host" label="IP地址" width="140" />
<el-table-column prop="Location" label="地点" width="120" />
<el-table-column prop="Origin" label="来源" show-overflow-tooltip />
<el-table-column label="操作" width="80" fixed="right">
<template #default="scope">
<el-button type="danger" link size="small" @click="handleDeleteLoginRecord(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper" v-if="loginHistoryTotal > 0">
<el-pagination
v-model:current-page="loginHistoryPage"
v-model:page-size="loginHistoryPageSize"
:total="loginHistoryTotal"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next"
@size-change="handleLoginHistorySizeChange"
@current-change="handleLoginHistoryPageChange"
/>
</div>
<el-empty v-else description="暂无登录记录" :image-size="100" />
</el-tab-pane>
<el-tab-pane label="操作记录" name="2">
<el-table :data="operationHistory" v-loading="operationHistoryLoading" stripe style="width: 100%">
<el-table-column prop="CreatedAt" label="时间" width="180">
<template #default="{row}">{{ formatDate(row.CreatedAt) }}</template>
</el-table-column>
<el-table-column prop="Type" label="操作类型" width="120">
<template #default="{row}">
<el-tag size="small">{{ row.Type }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="Note" label="详细内容" show-overflow-tooltip />
<el-table-column label="操作" width="80" fixed="right">
<template #default="scope">
<el-button type="danger" link size="small" @click="handleDeleteOperationRecord(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-wrapper" v-if="operationHistoryTotal > 0">
<el-pagination
v-model:current-page="operationHistoryPage"
v-model:page-size="operationHistoryPageSize"
:total="operationHistoryTotal"
:page-sizes="[10, 20, 50]"
layout="total, prev, pager, next"
@size-change="handleOperationHistorySizeChange"
@current-change="handleOperationHistoryPageChange"
/>
</div>
<el-empty v-else description="暂无操作记录" :image-size="100" />
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</div>
<!-- 编辑用户对话框 -->
<el-dialog v-model="editDialogVisible" title="编辑用户信息" width="500px" append-to-body destroy-on-close>
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="80px">
<el-form-item label="用户名" prop="UserName">
<el-input v-model="editForm.UserName" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="邮箱" prop="Email">
<el-input v-model="editForm.Email" placeholder="请输入邮箱" disabled />
</el-form-item>
<el-form-item label="手机号" prop="Phone">
<el-input v-model="editForm.Phone" placeholder="请输入手机号" disabled />
</el-form-item>
<el-form-item label="年龄">
<el-input-number v-model="editForm.Age" :min="0" :max="150" style="width: 100%" />
</el-form-item>
<el-form-item label="性别">
<el-select v-model="editForm.Sex" placeholder="请选择性别" style="width: 100%">
<el-option label="男" value="true" />
<el-option label="女" value="false" />
</el-select>
</el-form-item>
<el-form-item label="推荐人ID">
<el-input-number v-model="editForm.RecommendUserId" :min="0" style="width: 100%" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="editDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitEditForm">保存</el-button>
</div>
</template>
</el-dialog>
<!-- 头像选择对话框 -->
<AvatarSelector
v-model="showAvatarSelector"
:user-id="route.query.user_id"
:current-cover-id="editForm.CoverID"
@confirm="handleAvatarConfirm"
/>
<!-- 头像管理对话框 -->
<el-dialog v-model="avatarDialogVisible" title="修改用户头像" width="400px" append-to-body>
<div class="avatar-manage-content">
<div class="current-avatar-preview" @click="showAvatarSelectorDialog">
<el-avatar :size="100" :src="avatarPreviewUrl" fit="cover">
<el-icon :size="40"><User /></el-icon>
</el-avatar>
<div class="overlay"><el-icon><Edit /></el-icon></div>
</div>
<p class="hint">点击头像进行更换</p>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="avatarDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAvatarModify">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 密码管理对话框 -->
<el-dialog v-model="passwordDialogVisible" title="修改用户密码" width="400px" append-to-body>
<el-form :model="passwordForm" label-width="80px">
<el-form-item label="新密码">
<el-input v-model="passwordForm.password" type="password" placeholder="请输入新密码" show-password />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="passwordDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitPasswordModify">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 用户组管理对话框 -->
<el-dialog v-model="groupDialogVisible" title="修改用户组" width="400px" append-to-body>
<el-form :model="groupForm" label-width="80px">
<el-form-item label="用户组">
<el-select v-model="groupForm.user_group_id" placeholder="请选择用户组" style="width: 100%">
<el-option v-for="item in userGroupList" :key="item.Id" :label="item.Name" :value="item.Id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="groupDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitGroupModify">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 实名信息管理对话框 -->
<el-dialog v-model="realnameDialogVisible" title="修改实名信息" width="500px" append-to-body>
<el-form :model="realnameForm" label-width="100px">
<el-form-item label="姓名">
<el-input v-model="realnameForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="身份证">
<el-input v-model="realnameForm.id_card" placeholder="请输入身份证号" />
</el-form-item>
<el-form-item label="实名类型">
<el-select v-model="realnameForm.type" placeholder="请选择实名类型" style="width: 100%">
<el-option label="个人" :value="0" />
<el-option label="企业" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="实名状态">
<el-select v-model="realnameForm.status" placeholder="请选择实名状态" style="width: 100%">
<el-option label="未认证" :value="0" />
<el-option label="已认证" :value="1" />
<el-option label="认证中" :value="2" />
<el-option label="认证失败" :value="3" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="realnameDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitRealnameModify">确定</el-button>
</div>
</template>
</el-dialog>
<!-- Token 展示对话框 -->
<el-dialog v-model="tokenDialogVisible" title="模拟登录" width="450px" append-to-body>
<div class="token-container">
<el-form label-width="100px">
<el-form-item label="选择环境">
<el-select v-model="selectedEnvironment" placeholder="请选择登录环境" size="large" style="width: 100%">
<el-option label="正式环境" value="production">
<div class="env-option">
<span>正式环境</span>
<span class="env-url">www.007yjs.com</span>
</div>
</el-option>
<el-option label="测试环境" value="test">
<div class="env-option">
<span>测试环境</span>
<span class="env-url">apiserver.s1f.ren</span>
</div>
</el-option>
<el-option label="本地环境" value="local">
<div class="env-option">
<span>本地环境</span>
<span class="env-url">localhost:5173</span>
</div>
</el-option>
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="tokenDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmJump" :disabled="!selectedEnvironment">确认跳转</el-button>
</div>
</template>
</el-dialog>
<!-- 管理员权限管理对话框 -->
<el-dialog v-model="adminDialogVisible" title="修改管理员权限" width="500px" append-to-body>
<el-form :model="adminForm" label-width="100px">
<el-form-item label="管理员组">
<el-select
v-model="adminForm.admin_group_id"
placeholder="请选择管理员组"
style="width: 100%"
:loading="adminGroupLoading"
filterable
>
<el-option
v-for="item in adminGroupList"
:key="item.Id || item.id"
:label="`${item.Name || item.name} (ID: ${item.Id || item.id})`"
:value="item.Id || item.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="adminDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitAdminModify">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, onActivated } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { baseURL } from '@/utils/request'
import AvatarSelector from '@/components/admin/AvatarSelector.vue'
import {
ArrowLeft, Refresh, Edit as EditIcon, Delete, Wallet, Avatar, Lock,
UserFilled, Document, Clock, List, Switch, User, Camera, Upload,
UploadFilled, Key, Monitor, Setting
} from '@element-plus/icons-vue'
import { getUserGroupList } from '@/api/admin/user'
import { getFileDetail, getFileList, getFile, uploadFile } from '@/api/admin/file'
import {
getUserInfo, updateUserInfo, updateUserAvatar, updateUserPassword,
updateUserGroup, updateUserRealName, getUserLoginRecord,
getUserOperationRecord, mockUserLogin, deleteUser, updateUserAdmin
} from '@/api/admin/user'
import { getAdminGroupList } from '@/api/admin/group'
const Edit = EditIcon
const route = useRoute()
const router = useRouter()
// 用户信息
const userInfo = ref({})
const loading = ref(false)
// 标签页相关
const activeTabName = ref('1') // 默认选中登录记录
// 登录记录相关
const loginHistory = ref([])
const loginHistoryLoading = ref(false)
const loginHistoryTotal = ref(0)
const loginHistoryPage = ref(1)
const loginHistoryPageSize = ref(10)
// 操作记录相关
const operationHistory = ref([])
const operationHistoryLoading = ref(false)
const operationHistoryTotal = ref(0)
const operationHistoryPage = ref(1)
const operationHistoryPageSize = ref(10)
// 编辑用户相关
const editDialogVisible = ref(false)
const editForm = reactive({
UserId: '',
UserName: '',
Email: '',
Phone: '',
Age: '',
Sex: '',
UserGroupId: '',
RecommendUserId: '',
CoverID: '',
RealName: {
Name: '',
IdCard: '',
Status: 0,
Type: 0
}
})
const editRules = {
UserName: [
{ required: true, message: '请输入用户名', trigger: 'blur' }
]
}
// 头像管理相关
const avatarDialogVisible = ref(false)
const avatarForm = reactive({
user_id: '',
cover_id: ''
})
const avatarPreviewUrl = ref('')
const showAvatarSelector = ref(false)
const selectedAvatarId = ref('')
const currentAvatarUrl = ref('')
// 密码管理相关
const passwordDialogVisible = ref(false)
const passwordForm = reactive({
user_id: '',
password: ''
})
// 用户组管理相关
const groupDialogVisible = ref(false)
const groupForm = reactive({
user_id: '',
user_group_id: ''
})
const userGroupList = ref([])
// 实名信息管理相关
const realnameDialogVisible = ref(false)
const realnameForm = reactive({
user_id: '',
name: '',
id_card: '',
type: 0,
status: 0
})
// Token 展示相关
const tokenDialogVisible = ref(false)
const loginToken = ref('')
const loginExpire = ref('')
const selectedEnvironment = ref('')
// 环境配置
const environments = {
production: 'https://www.007yjs.com',
test: 'https://apiserver.s1f.ren',
local: 'http://localhost:5173'
}
// 管理员权限管理相关
const adminDialogVisible = ref(false)
const adminForm = reactive({
user_id: '',
admin_group_id: ''
})
const adminGroupList = ref([])
const adminGroupLoading = ref(false)
// 处理标签页点击
const handleTabClick = (tab) => {
localStorage.setItem('userDetailActiveTab', tab.props.name);
if (tab.props.name === '1') {
fetchLoginHistory()
} else if (tab.props.name === '2') {
fetchOperationHistory()
}
};
// 获取用户信息
const fetchUserInfo = async () => {
const userId = route.query.user_id
if (!userId) {
ElMessage.error('用户ID不能为空')
goBack()
return
}
loading.value = true
try {
const res = await getUserInfo({ user_id: userId })
if (res.data.code === 200) {
userInfo.value = res.data.data
// 获取头像
if (userInfo.value.CoverID) {
fetchCurrentAvatar(userInfo.value.CoverID)
}
}
} catch (error) {
ElMessage.error('获取用户信息失败')
} finally {
loading.value = false
}
}
// 刷新数据
const refreshData = () => {
fetchUserInfo()
if (activeTabName.value === '1') {
fetchLoginHistory()
} else if (activeTabName.value === '2') {
fetchOperationHistory()
}
}
// 返回上一页
const goBack = () => {
router.go(-1)
}
// 编辑用户
const handleEditUser = async () => {
editForm.UserId = userInfo.value.UserId
editForm.UserName = userInfo.value.UserName
editForm.Email = userInfo.value.Email
editForm.Phone = userInfo.value.Phone
editForm.Age = userInfo.value.Age || ''
editForm.Sex = userInfo.value.Sex || ''
editForm.UserGroupId = userInfo.value.UserGroupId
editForm.RecommendUserId = userInfo.value.RecommendUserId || ''
editForm.CoverID = userInfo.value.CoverID || ''
editDialogVisible.value = true
}
// 提交编辑表单
const submitEditForm = async () => {
try {
const data = {
user_id: userInfo.value.UserId,
user_name: editForm.UserName,
age: editForm.Age,
sex: editForm.Sex,
recommend_id: editForm.RecommendUserId,
cover_id: editForm.CoverID
}
const res = await updateUserInfo(data)
if (res.data.code === 200) {
ElMessage.success('用户信息更新成功')
editDialogVisible.value = false
fetchUserInfo()
}
} catch (error) {
ElMessage.error('用户信息更新失败')
}
}
// 余额管理
const handleBalanceManage = () => {
router.push({
path: '/user/balance',
query: { user_id: userInfo.value.UserId }
})
}
// 头像管理
const handleAvatarManage = async () => {
avatarForm.user_id = userInfo.value.UserId
avatarForm.cover_id = userInfo.value.CoverID || ''
if (userInfo.value.CoverID) {
const res = await getFileDetail({ file_id: userInfo.value.CoverID })
if (res.data.code === 200) {
avatarPreviewUrl.value = res.data.data.url
}
} else {
avatarPreviewUrl.value = ''
}
avatarDialogVisible.value = true
}
const showAvatarSelectorDialog = () => {
showAvatarSelector.value = true
}
// 密码管理
const handlePasswordManage = () => {
passwordForm.user_id = userInfo.value.UserId
passwordForm.password = ''
passwordDialogVisible.value = true
}
// 用户组管理
const handleGroupManage = () => {
groupForm.user_id = userInfo.value.UserId
groupForm.user_group_id = userInfo.value.UserGroupId || ''
groupDialogVisible.value = true
fetchUserGroupList()
}
const fetchUserGroupList = async () => {
const res = await getUserGroupList({ page: 1, count: 100 })
if (res.data.code == 200) {
userGroupList.value = res.data.data.data
}
}
// 实名信息管理
const handleRealnameManage = () => {
realnameForm.user_id = userInfo.value.UserId
realnameForm.name = userInfo.value.RealName?.Name || ''
realnameForm.id_card = userInfo.value.RealName?.IdCard || ''
realnameForm.type = userInfo.value.RealName?.Type || 0
realnameForm.status = userInfo.value.RealName?.Status || 0
realnameDialogVisible.value = true
}
// 获取登录记录
const fetchLoginHistory = async () => {
if (!route.query.user_id) return
loginHistoryLoading.value = true
try {
const res = await getUserLoginRecord({
user_id: route.query.user_id,
page: loginHistoryPage.value,
count: loginHistoryPageSize.value
})
if (res.data.code === 200) {
loginHistory.value = res.data.data.data || []
loginHistoryTotal.value = res.data.data.all_count || 0
}
} catch (error) {
ElMessage.error('获取登录记录失败')
} finally {
loginHistoryLoading.value = false
}
}
const handleLoginHistorySizeChange = (size) => {
loginHistoryPageSize.value = size;
loginHistoryPage.value = 1;
fetchLoginHistory();
};
const handleLoginHistoryPageChange = (page) => {
loginHistoryPage.value = page;
fetchLoginHistory();
};
// 删除登录记录
const handleDeleteLoginRecord = async (record) => {
// 暂无API
ElMessage.info('功能开发中')
}
// 获取操作记录
const fetchOperationHistory = async () => {
if (!route.query.user_id) return
operationHistoryLoading.value = true
try {
const res = await getUserOperationRecord({
user_id: route.query.user_id,
page: operationHistoryPage.value,
count: operationHistoryPageSize.value
})
if (res.data.code === 200) {
operationHistory.value = res.data.data.data || []
operationHistoryTotal.value = res.data.data.all_count || 0
}
} catch (error) {
ElMessage.error('获取操作记录失败')
} finally {
operationHistoryLoading.value = false
}
}
const handleOperationHistorySizeChange = (size) => {
operationHistoryPageSize.value = size;
operationHistoryPage.value = 1;
fetchOperationHistory();
};
const handleOperationHistoryPageChange = (page) => {
operationHistoryPage.value = page;
fetchOperationHistory();
};
// 删除操作记录
const handleDeleteOperationRecord = async (record) => {
// 暂无API
ElMessage.info('功能开发中')
}
// 删除用户
const handleDeleteUser = async () => {
try {
ElMessageBox.confirm('确定要删除该用户吗?此操作不可恢复!', '警告', {
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await deleteUser({ user_id: userInfo.value.UserId })
if (res.data.code === 200) {
ElMessage.success('用户删除成功')
setTimeout(() => {
goBack()
}, 2000)
} else {
ElMessage.error('用户删除失败')
}
}).catch(() => {});
} catch (error) {
ElMessage.error('删除用户失败')
}
}
// 模拟登录
const handleSimulateLogin = async () => {
try {
const res = await mockUserLogin({ user_id: userInfo.value.UserId })
if (res.data.code === 200) {
loginToken.value = res.data.data.token || ''
loginExpire.value = res.data.data.expire_time || ''
selectedEnvironment.value = ''
tokenDialogVisible.value = true
//ElMessage.success('模拟登录成功')
} else {
ElMessage.error(res.data.message || '模拟登录失败')
}
} catch (error) {
ElMessage.error('模拟登录失败')
}
}
// 确认跳转
const confirmJump = () => {
if (!selectedEnvironment.value) {
ElMessage.warning('请选择登录环境')
return
}
const baseUrl = environments[selectedEnvironment.value]
const url = `${baseUrl}/token-login?token=${loginToken.value}&expire=${loginExpire.value}`
window.open(url, '_blank')
const envName = selectedEnvironment.value === 'production' ? '正式' : selectedEnvironment.value === 'test' ? '测试' : '本地'
ElMessage.success(`正在跳转到${envName}环境`)
tokenDialogVisible.value = false
}
// 获取实名状态文本
const getRealNameStatusText = (status) => {
const map = { 0: '未认证', 1: '已认证', 2: '认证中', 3: '认证失败' }
return map[status] || '未知'
}
// 获取实名类型文本
const getRealNameTypeText = (type) => {
const map = { 0: '个人', 1: '企业', 2: '其他' }
return map[type] || '未知'
}
// 获取当前头像
const fetchCurrentAvatar = async (coverId) => {
if (!coverId) {
currentAvatarUrl.value = ''
return
}
try {
const res = await getFileDetail({ file_id: coverId })
if (res.data.code === 200) {
currentAvatarUrl.value = res.data.data.url
}
} catch (error) {
currentAvatarUrl.value = ''
}
}
// 提交头像修改
const submitAvatarModify = async () => {
try {
const res = await updateUserAvatar({
user_id: avatarForm.user_id,
cover_id: avatarForm.cover_id
})
if (res.data.code == 200) {
ElMessage.success('头像修改成功')
avatarDialogVisible.value = false
fetchUserInfo()
}
} catch (error) {
ElMessage.error('头像修改失败')
}
}
const handleAvatarConfirm = (data) => {
selectedAvatarId.value = data.cover_id
if (editDialogVisible.value) {
editForm.CoverID = selectedAvatarId.value
}
if (avatarDialogVisible.value) {
avatarForm.cover_id = data.cover_id
avatarPreviewUrl.value = data.url
}
ElMessage.success('头像选择成功,请点击确定按钮保存')
}
// 提交密码修改
const submitPasswordModify = async () => {
try {
const res = await updateUserPassword({
user_id: passwordForm.user_id,
password: passwordForm.password
})
if (res.data.code === 200) {
ElMessage.success('密码修改成功')
passwordDialogVisible.value = false
}
} catch (error) {
ElMessage.error('密码修改失败')
}
}
// 提交用户组修改
const submitGroupModify = async () => {
try {
const res = await updateUserGroup({
user_id: groupForm.user_id,
user_group_id: groupForm.user_group_id
})
if (res.data.code == 200) {
ElMessage.success('用户组修改成功')
groupDialogVisible.value = false
fetchUserInfo()
}
} catch (error) {
ElMessage.error('用户组修改失败')
}
}
// 提交实名信息修改
const submitRealnameModify = async () => {
try {
const res = await updateUserRealName({
user_id: realnameForm.user_id,
name: realnameForm.name,
id_card: realnameForm.id_card,
type: realnameForm.type,
status: realnameForm.status
})
if (res.data.code == 200) {
ElMessage.success('实名信息修改成功')
realnameDialogVisible.value = false
fetchUserInfo()
}
} catch (error) {
ElMessage.error('实名信息修改失败')
}
}
// 管理员权限管理
const handleTokenManage = async () => {
adminForm.user_id = userInfo.value.UserId
adminForm.admin_group_id = ''
await fetchAdminGroupList()
adminDialogVisible.value = true
}
const fetchAdminGroupList = async () => {
adminGroupLoading.value = true
try {
const res = await getAdminGroupList({ params: { page: 1, count: 100 } })
if (res.data.code === 200) {
adminGroupList.value = res.data.data?.data || res.data.data || []
}
} catch (error) {
adminGroupList.value = []
} finally {
adminGroupLoading.value = false
}
}
const submitAdminModify = async () => {
if (!adminForm.admin_group_id) {
ElMessage.warning('请选择管理员组')
return
}
try {
const res = await updateUserAdmin({
user_id: adminForm.user_id,
admin_group_id: adminForm.admin_group_id
})
if (res.data.code === 200) {
ElMessage.success('管理员权限修改成功')
adminDialogVisible.value = false
fetchUserInfo()
} else {
ElMessage.error(res.data.message || '管理员权限修改失败')
}
} catch (error) {
ElMessage.error('管理员权限修改失败')
}
}
// 日期格式化
const formatDate = (dateString) => {
if (!dateString) return '未设置'
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
hour: '2-digit', minute: '2-digit', second: '2-digit'
})
}
const loadUserData = async () => {
if(!route.query.user_id){
ElMessage.error('缺少用户ID参数');
goBack();
return;
}
await fetchUserInfo();
await fetchLoginHistory();
await fetchOperationHistory();
}
// 初始化
onMounted(() => {
if (route.query.user_id) {
const savedTab = localStorage.getItem('userDetailActiveTab');
if (savedTab) {
activeTabName.value = savedTab;
}
loadUserData();
} else {
ElMessage.error('缺少用户ID参数');
goBack();
}
})
onActivated(() => {
loadUserData();
})
</script>
<style scoped>
.user-detail-page {
padding: 24px;
background-color: #f5f7fa;
min-height: 100vh;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.page-title {
font-size: 20px;
font-weight: 600;
color: #1f2d3d;
}
.back-btn {
font-size: 14px;
color: #606266;
}
.main-content {
max-width: 1200px;
margin: 0 auto;
}
/* Profile Card */
.profile-card {
border-radius: 12px;
border: none;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.04);
margin-bottom: 24px;
overflow: visible;
}
.profile-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 10px 20px;
}
.profile-basic {
display: flex;
align-items: center;
gap: 24px;
}
.avatar-wrapper {
position: relative;
cursor: pointer;
}
.user-avatar {
border: 4px solid #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: transform 0.3s;
}
.avatar-wrapper:hover .user-avatar {
transform: scale(1.05);
}
.avatar-edit-hint {
position: absolute;
bottom: 0;
right: 0;
background: #409eff;
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
border: 2px solid #fff;
}
.user-identity .name-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
}
.user-name {
margin: 0;
font-size: 24px;
font-weight: 700;
color: #303133;
}
.id-row {
display: flex;
align-items: center;
color: #909399;
font-size: 13px;
}
.id-row .value {
color: #606266;
font-family: monospace;
margin-left: 4px;
}
.profile-stats {
display: flex;
gap: 32px;
}
.stat-item {
text-align: center;
}
.stat-label {
font-size: 12px;
color: #909399;
margin-bottom: 4px;
}
.stat-value {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.action-divider {
margin: 0 0 20px 0;
}
.quick-actions {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
gap: 12px;
padding: 0 10px 10px;
}
/* Detail Grid */
.detail-grid {
display: grid;
grid-template-columns: 320px 1fr;
gap: 24px;
}
.info-section-card {
border-radius: 12px;
border: none;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.03);
}
.card-header .title {
font-weight: 600;
font-size: 16px;
color: #303133;
}
.tabs-card {
border-radius: 12px;
border: none;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.03);
min-height: 500px;
}
.pagination-wrapper {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.avatar-manage-content {
text-align: center;
padding: 20px 0;
}
.current-avatar-preview {
position: relative;
width: 100px;
height: 100px;
margin: 0 auto 16px;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
}
.current-avatar-preview .overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
color: white;
opacity: 0;
transition: opacity 0.3s;
}
.current-avatar-preview:hover .overlay {
opacity: 1;
}
.hint {
color: #909399;
font-size: 13px;
}
/* Token Dialog */
.token-container {
padding: 20px 0;
}
.env-option {
display: flex;
align-items: center;
justify-content: space-between;
padding: 4px 0;
}
.env-option span:first-child {
font-size: 14px;
color: #303133;
}
.env-url {
font-size: 12px;
color: #909399;
}
/* Responsive */
@media (max-width: 1024px) {
.detail-grid {
grid-template-columns: 1fr;
}
.profile-header {
flex-direction: column;
align-items: flex-start;
gap: 20px;
}
.profile-stats {
width: 100%;
justify-content: space-around;
background: #f9fafc;
padding: 16px;
border-radius: 8px;
}
}
</style>