1241 lines
36 KiB
Vue
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>
|