1834 lines
54 KiB
Vue
1834 lines
54 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" v-for="balance in userBalanceList" :key="balance.id">
|
||
<div class="stat-label">{{ balance.typeName }}</div>
|
||
<div class="stat-value">{{ formatBalance(balance.price) }}</div>
|
||
</div>
|
||
<!-- 其他状态数据 -->
|
||
<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="性别">{{ formatSex(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-tab-pane label="订单列表" name="3">
|
||
<el-table :data="userOrderList" v-loading="orderListLoading" stripe style="width: 100%">
|
||
<el-table-column prop="id" label="订单ID" width="100" />
|
||
<el-table-column prop="name" label="商品名称" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="price" label="金额" width="100">
|
||
<template #default="{row}">¥{{ (row.price / 100).toFixed(2) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="{row}">
|
||
<el-tag :type="getOrderStatusType(row.status)" size="small">{{ getOrderStatusText(row.status) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="created_at" label="创建时间" width="160">
|
||
<template #default="{row}">{{ formatDate(row.created_at) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80" fixed="right">
|
||
<template #default="scope">
|
||
<el-button type="primary" link size="small" @click="handleViewOrder(scope.row)">详情</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-wrapper" v-if="orderListTotal > 0">
|
||
<el-pagination
|
||
v-model:current-page="orderListPage"
|
||
v-model:page-size="orderListPageSize"
|
||
:total="orderListTotal"
|
||
:page-sizes="[10, 20, 50]"
|
||
layout="total, prev, pager, next"
|
||
@size-change="handleOrderListSizeChange"
|
||
@current-change="handleOrderListPageChange"
|
||
/>
|
||
</div>
|
||
<el-empty v-else description="暂无订单记录" :image-size="100" />
|
||
</el-tab-pane>
|
||
<el-tab-pane label="工单列表" name="4">
|
||
<el-table :data="userTicketList" v-loading="ticketListLoading" stripe style="width: 100%">
|
||
<el-table-column prop="work_id" label="工单ID" width="100" />
|
||
<el-table-column prop="name" label="标题" min-width="150" show-overflow-tooltip />
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template #default="{row}">
|
||
<el-tag :type="getTicketStatusType(row.status)" size="small">{{ getTicketStatusText(row.status) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="created_at" label="创建时间" width="160">
|
||
<template #default="{row}">{{ formatDate(row.created_at) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="80" fixed="right">
|
||
<template #default="scope">
|
||
<el-button type="primary" link size="small" @click="handleViewTicket(scope.row)">详情</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-wrapper" v-if="ticketListTotal > 0">
|
||
<el-pagination
|
||
v-model:current-page="ticketListPage"
|
||
v-model:page-size="ticketListPageSize"
|
||
:total="ticketListTotal"
|
||
:page-sizes="[10, 20, 50]"
|
||
layout="total, prev, pager, next"
|
||
@size-change="handleTicketListSizeChange"
|
||
@current-change="handleTicketListPageChange"
|
||
/>
|
||
</div>
|
||
<el-empty v-else description="暂无工单记录" :image-size="100" />
|
||
</el-tab-pane>
|
||
<el-tab-pane label="已购商品" name="5">
|
||
<el-table :data="userGoodsList" v-loading="goodsListLoading" stripe style="width: 100%" table-layout="auto">
|
||
<el-table-column prop="id" label="ID" width="70" />
|
||
<el-table-column label="商品名称" min-width="120" show-overflow-tooltip>
|
||
<template #default="{row}">{{ row.good?.name || '-' }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="标签" min-width="90">
|
||
<template #default="{row}">
|
||
<el-tag v-if="row.tag" size="small">{{ row.tag }}</el-tag>
|
||
<span v-else>-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="到期时间" min-width="140">
|
||
<template #default="{row}">{{ formatDate(row.expireTime) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="购买时间" min-width="140">
|
||
<template #default="{row}">{{ formatDate(row.CreatedAt) }}</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<div class="pagination-wrapper" v-if="goodsListTotal > 0">
|
||
<el-pagination
|
||
v-model:current-page="goodsListPage"
|
||
v-model:page-size="goodsListPageSize"
|
||
:total="goodsListTotal"
|
||
:page-sizes="[10, 20, 50]"
|
||
layout="total, prev, pager, next"
|
||
@size-change="handleGoodsListSizeChange"
|
||
@current-change="handleGoodsListPageChange"
|
||
/>
|
||
</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="600px" append-to-body destroy-on-close>
|
||
<el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="100px">
|
||
<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="请输入邮箱" />
|
||
</el-form-item>
|
||
<el-form-item label="手机号" prop="Phone">
|
||
<el-input v-model="editForm.Phone" placeholder="请输入手机号" />
|
||
</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="推荐人">
|
||
<el-input
|
||
v-if="recommendUserInfo.UserId"
|
||
:model-value="`${recommendUserInfo.UserName} (ID: ${recommendUserInfo.UserId})`"
|
||
readonly
|
||
style="width: 100%"
|
||
>
|
||
<template #suffix>
|
||
<el-icon class="clear-icon" @click="clearRecommendUser"><Close /></el-icon>
|
||
</template>
|
||
<template #append>
|
||
<el-button @click="showUserSelectorDialog = true">
|
||
<el-icon><User /></el-icon>
|
||
</el-button>
|
||
</template>
|
||
</el-input>
|
||
<el-input
|
||
v-else
|
||
placeholder="请选择推荐人"
|
||
readonly
|
||
style="width: 100%"
|
||
@click="showUserSelectorDialog = true"
|
||
>
|
||
<template #append>
|
||
<el-button @click="showUserSelectorDialog = true">
|
||
<el-icon><User /></el-icon>
|
||
</el-button>
|
||
</template>
|
||
</el-input>
|
||
</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>
|
||
|
||
<!-- 用户选择器(推荐人选择) -->
|
||
<UserListSelector
|
||
v-model="showUserSelectorDialog"
|
||
:current-user-id="editForm.RecommendUserId"
|
||
@confirm="handleRecommendUserSelect"
|
||
/>
|
||
|
||
<!-- 头像选择对话框 -->
|
||
<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 UserListSelector from '@/components/admin/UserListSelector.vue'
|
||
import {
|
||
ArrowLeft, Refresh, Edit as EditIcon, Delete, Wallet, Avatar, Lock,
|
||
UserFilled, Document, Clock, List, Switch, User, Camera, Upload,
|
||
UploadFilled, Key, Monitor, Setting, Close
|
||
} from '@element-plus/icons-vue'
|
||
import { getUserGroupList, getUserBalanceCount } from '@/api/admin/user'
|
||
import { getFileDetail } 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'
|
||
import { getOrderList } from '@/api/admin/order'
|
||
import { getTickerList } from '@/api/ticket'
|
||
import { getUserGoodsList } from '@/api/admin/product'
|
||
|
||
const Edit = EditIcon
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
|
||
// 引入tagsViewStore
|
||
import { useTagsViewStore } from '@/store/tagsViewStore'
|
||
const tagsViewStore = useTagsViewStore()
|
||
|
||
// 用户信息
|
||
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 userOrderList = ref([])
|
||
const orderListLoading = ref(false)
|
||
const orderListTotal = ref(0)
|
||
const orderListPage = ref(1)
|
||
const orderListPageSize = ref(10)
|
||
|
||
// 工单列表相关
|
||
const userTicketList = ref([])
|
||
const ticketListLoading = ref(false)
|
||
const ticketListTotal = ref(0)
|
||
const ticketListPage = ref(1)
|
||
const ticketListPageSize = ref(10)
|
||
|
||
// 已购商品列表相关
|
||
const userGoodsList = ref([])
|
||
const goodsListLoading = ref(false)
|
||
const goodsListTotal = ref(0)
|
||
const goodsListPage = ref(1)
|
||
const goodsListPageSize = ref(10)
|
||
|
||
// 用户余额相关
|
||
const userBalance = ref({
|
||
balance: 0,
|
||
frozen_balance: 0,
|
||
total_recharge: 0,
|
||
total_consume: 0
|
||
})
|
||
|
||
// 用户余额列表(用于概览卡片展示)
|
||
const userBalanceList = ref([])
|
||
|
||
// 余额类型映射
|
||
const balanceTypeMap = {
|
||
entity: { name: '云钻', type: 'cloud_diamond', color: '#409EFF' },
|
||
general: { name: '云币', type: 'cloud_coin', color: '#67C23A' },
|
||
withdraw: { name: '云点', type: 'cloud_points', color: '#E6A23C' }
|
||
}
|
||
|
||
// 编辑用户相关
|
||
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 showUserSelectorDialog = ref(false)
|
||
const recommendUserInfo = ref({
|
||
UserId: '',
|
||
UserName: '',
|
||
Avatar: ''
|
||
})
|
||
|
||
|
||
// 密码管理相关
|
||
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()
|
||
} else if (tab.props.name === '3') {
|
||
fetchUserOrderList()
|
||
} else if (tab.props.name === '4') {
|
||
fetchUserTicketList()
|
||
} else if (tab.props.name === '5') {
|
||
fetchUserGoodsList()
|
||
}
|
||
};
|
||
|
||
// 格式化性别
|
||
const formatSex = (sex) => {
|
||
if (sex === true) {
|
||
return '男'
|
||
} else if (sex === false) {
|
||
return '女'
|
||
} else {
|
||
return '-'
|
||
}
|
||
}
|
||
// 获取用户信息
|
||
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)
|
||
}
|
||
// 获取用户余额列表
|
||
fetchUserBalanceList(userId)
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取用户信息失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取用户余额列表(用于概览卡片展示)
|
||
const fetchUserBalanceList = async (userId) => {
|
||
try {
|
||
const res = await getUserBalanceCount({ user_id: userId })
|
||
if (res.data.code === 200) {
|
||
// 只保留映射类型中存在的余额
|
||
userBalanceList.value = (res.data.data || []).filter(item => {
|
||
return balanceTypeMap[item.type]
|
||
}).map(item => ({
|
||
...item,
|
||
typeName: balanceTypeMap[item.type]?.name || item.type,
|
||
typeColor: '#000000'
|
||
}))
|
||
}
|
||
} catch (error) {
|
||
console.error('获取用户余额失败:', error)
|
||
}
|
||
}
|
||
|
||
// 刷新数据
|
||
const refreshData = () => {
|
||
fetchUserInfo()
|
||
fetchActiveTabData()
|
||
}
|
||
|
||
// 返回上一页
|
||
const goBack = () => {
|
||
// 关闭当前tab
|
||
tagsViewStore.delVisitedView(route)
|
||
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 || ''
|
||
|
||
// 初始化推荐人信息
|
||
if (userInfo.value.RecommendUserId) {
|
||
await fetchRecommendUserInfo(userInfo.value.RecommendUserId)
|
||
} else {
|
||
recommendUserInfo.value = { UserId: '', UserName: '', Avatar: '' }
|
||
}
|
||
|
||
editDialogVisible.value = true
|
||
}
|
||
|
||
// 获取推荐人信息
|
||
const fetchRecommendUserInfo = async (userId) => {
|
||
try {
|
||
const res = await getUserInfo({ user_id: userId })
|
||
if (res.data.code === 200) {
|
||
const user = res.data.data
|
||
recommendUserInfo.value = {
|
||
UserId: user.UserId || user.user_id,
|
||
UserName: user.UserName || user.user_name,
|
||
Avatar: user.Avatar || user.cover || ''
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取推荐人信息失败:', error)
|
||
}
|
||
}
|
||
|
||
|
||
// 推荐人选择处理
|
||
const handleRecommendUserSelect = (user) => {
|
||
recommendUserInfo.value = {
|
||
UserId: user.user_id || user.UserId,
|
||
UserName: user.user_name || user.UserName,
|
||
Avatar: user.cover || user.Avatar || ''
|
||
}
|
||
editForm.RecommendUserId = user.user_id || user.UserId
|
||
}
|
||
|
||
// 清除推荐人
|
||
const clearRecommendUser = () => {
|
||
recommendUserInfo.value = { UserId: '', UserName: '', Avatar: '' }
|
||
editForm.RecommendUserId = ''
|
||
}
|
||
|
||
|
||
// 提交编辑表单
|
||
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,
|
||
email: editForm.Email,
|
||
phone: editForm.Phone
|
||
}
|
||
console.log("提交编辑表单数据:",data)
|
||
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) {
|
||
try {
|
||
const res = await getFileDetail({ file_id: userInfo.value.CoverID })
|
||
if (res.data.code === 200) {
|
||
avatarPreviewUrl.value = res.data.data.url
|
||
} else {
|
||
avatarPreviewUrl.value = ''
|
||
}
|
||
} catch (error) {
|
||
console.error('获取头像信息失败:', error)
|
||
avatarPreviewUrl.value = ''
|
||
}
|
||
} 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: 10 })
|
||
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 fetchUserOrderList = async () => {
|
||
if (!route.query.user_id) return
|
||
orderListLoading.value = true
|
||
try {
|
||
const res = await getOrderList({
|
||
user_id: route.query.user_id,
|
||
page: orderListPage.value,
|
||
count: orderListPageSize.value
|
||
})
|
||
console.log('111',res)
|
||
if (res.data.code === 200) {
|
||
userOrderList.value = res.data.data.list || []
|
||
orderListTotal.value = res.data.data.all_count || 0
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取订单列表失败')
|
||
} finally {
|
||
orderListLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleOrderListSizeChange = (size) => {
|
||
orderListPageSize.value = size
|
||
orderListPage.value = 1
|
||
fetchUserOrderList()
|
||
}
|
||
|
||
const handleOrderListPageChange = (page) => {
|
||
orderListPage.value = page
|
||
fetchUserOrderList()
|
||
}
|
||
|
||
// 获取用户工单列表
|
||
const fetchUserTicketList = async () => {
|
||
if (!route.query.user_id) return
|
||
ticketListLoading.value = true
|
||
try {
|
||
const res = await getTickerList(ticketListPageSize.value, ticketListPage.value, undefined, undefined, undefined, route.query.user_id)
|
||
if (res.code === 200) {
|
||
userTicketList.value = res.data.data || []
|
||
ticketListTotal.value = res.data.all_count || 0
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取工单列表失败')
|
||
} finally {
|
||
ticketListLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleTicketListSizeChange = (size) => {
|
||
ticketListPageSize.value = size
|
||
ticketListPage.value = 1
|
||
fetchUserTicketList()
|
||
}
|
||
|
||
const handleTicketListPageChange = (page) => {
|
||
ticketListPage.value = page
|
||
fetchUserTicketList()
|
||
}
|
||
|
||
// 获取用户已购商品列表
|
||
const fetchUserGoodsList = async () => {
|
||
if (!route.query.user_id) return
|
||
goodsListLoading.value = true
|
||
try {
|
||
const res = await getUserGoodsList({
|
||
user_id: route.query.user_id,
|
||
page: goodsListPage.value,
|
||
count: goodsListPageSize.value
|
||
})
|
||
if (res.data.code === 200) {
|
||
userGoodsList.value = res.data.data.data || res.data.data.list || []
|
||
goodsListTotal.value = res.data.data.all_count || res.data.data.total || 0
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取已购商品列表失败')
|
||
} finally {
|
||
goodsListLoading.value = false
|
||
}
|
||
}
|
||
|
||
const handleGoodsListSizeChange = (size) => {
|
||
goodsListPageSize.value = size
|
||
goodsListPage.value = 1
|
||
fetchUserGoodsList()
|
||
}
|
||
|
||
const handleGoodsListPageChange = (page) => {
|
||
goodsListPage.value = page
|
||
fetchUserGoodsList()
|
||
}
|
||
|
||
// 获取用户余额信息
|
||
const fetchUserBalance = async () => {
|
||
if (!route.query.user_id) return
|
||
try {
|
||
const res = await getUserBalanceCount({ user_id: route.query.user_id })
|
||
if (res.data.code === 200) {
|
||
userBalance.value = {
|
||
balance: res.data.data.balance || 0,
|
||
frozen_balance: res.data.data.frozen_balance || 0,
|
||
total_recharge: res.data.data.total_recharge || 0,
|
||
total_consume: res.data.data.total_consume || 0
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取用户余额失败:', error)
|
||
}
|
||
}
|
||
|
||
// 订单状态
|
||
const getOrderStatusText = (status) => {
|
||
const map = { 0: '待支付', 1: '已支付', 2: '已取消', 3: '已退款', 4: '已完成' }
|
||
return map[status] || '未知'
|
||
}
|
||
|
||
const getOrderStatusType = (status) => {
|
||
const map = { 0: 'warning', 1: 'success', 2: 'info', 3: 'danger', 4: 'success' }
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
// 工单状态
|
||
const getTicketStatusText = (status) => {
|
||
const map = { 0: '待处理', 1: '处理中', 2: '已回复', 3: '已关闭' }
|
||
return map[status] || '未知'
|
||
}
|
||
|
||
const getTicketStatusType = (status) => {
|
||
const map = { 0: 'warning', 1: 'primary', 2: 'success', 3: 'info' }
|
||
return map[status] || 'info'
|
||
}
|
||
|
||
// 查看订单详情
|
||
const handleViewOrder = (row) => {
|
||
router.push({
|
||
path: '/order/list',
|
||
query: { order_id: row.order_id }
|
||
})
|
||
}
|
||
|
||
// 查看工单详情
|
||
const handleViewTicket = (row) => {
|
||
router.push({
|
||
path: '/ticket/detail',
|
||
query: { id: row.work_id }
|
||
})
|
||
}
|
||
|
||
// 余额操作
|
||
const handleRecharge = () => {
|
||
router.push({
|
||
path: '/user/balance',
|
||
query: { user_id: userInfo.value.UserId, action: 'recharge' }
|
||
})
|
||
}
|
||
|
||
const handleDeduct = () => {
|
||
router.push({
|
||
path: '/user/balance',
|
||
query: { user_id: userInfo.value.UserId, action: 'deduct' }
|
||
})
|
||
}
|
||
|
||
const handleViewBalanceHistory = () => {
|
||
router.push({
|
||
path: '/user/balance',
|
||
query: { user_id: userInfo.value.UserId }
|
||
})
|
||
}
|
||
|
||
// 删除用户
|
||
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: 10 } })
|
||
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'
|
||
})
|
||
}
|
||
|
||
// 余额格式化(分转元,保留2位小数)
|
||
const formatBalance = (price) => {
|
||
if (price === undefined || price === null) return '0.00'
|
||
return (price / 100).toFixed(2)
|
||
}
|
||
|
||
const fetchActiveTabData = () => {
|
||
const tab = activeTabName.value
|
||
if (tab === '1') fetchLoginHistory()
|
||
else if (tab === '2') fetchOperationHistory()
|
||
else if (tab === '3') fetchUserOrderList()
|
||
else if (tab === '4') fetchUserTicketList()
|
||
else if (tab === '5') fetchUserGoodsList()
|
||
}
|
||
|
||
const loadUserData = async () => {
|
||
if(!route.query.user_id){
|
||
ElMessage.error('缺少用户ID参数');
|
||
goBack();
|
||
return;
|
||
}
|
||
await fetchUserInfo();
|
||
await fetchUserBalance();
|
||
fetchActiveTabData();
|
||
}
|
||
|
||
// 初始化
|
||
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;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.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;
|
||
align-items: center;
|
||
gap: 24px;
|
||
margin-left: auto;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.profile-stats .stat-item {
|
||
text-align: center;
|
||
min-width: 70px;
|
||
}
|
||
|
||
.profile-stats .stat-label {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.profile-stats .stat-value {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
|
||
.stats-divider {
|
||
height: 40px;
|
||
margin: 0 8px;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.right-column {
|
||
overflow: hidden;
|
||
min-width: 0;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* 推荐人选择器 */
|
||
/* 推荐人选择器样式 */
|
||
.clear-icon {
|
||
cursor: pointer;
|
||
color: #909399;
|
||
transition: color 0.2s;
|
||
}
|
||
|
||
.clear-icon:hover {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* Balance Card */
|
||
.balance-card {
|
||
border-radius: 12px;
|
||
border: none;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.03);
|
||
}
|
||
|
||
.balance-card .card-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.balance-card .card-header .title {
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
color: #303133;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.balance-content {
|
||
padding: 10px 0;
|
||
}
|
||
|
||
.balance-overview {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 20px;
|
||
}
|
||
|
||
.balance-item {
|
||
text-align: center;
|
||
padding: 16px;
|
||
background: #f9fafc;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.balance-label {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.balance-value {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.balance-value.primary {
|
||
color: #409eff;
|
||
}
|
||
|
||
.balance-value.warning {
|
||
color: #e6a23c;
|
||
}
|
||
|
||
.balance-value.success {
|
||
color: #67c23a;
|
||
}
|
||
|
||
.balance-value.danger {
|
||
color: #f56c6c;
|
||
}
|
||
|
||
.balance-actions {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
/* 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: flex-start;
|
||
background: #f9fafc;
|
||
padding: 16px;
|
||
border-radius: 8px;
|
||
margin-left: 0;
|
||
}
|
||
|
||
.stats-divider {
|
||
display: none;
|
||
}
|
||
|
||
.balance-overview {
|
||
grid-template-columns: repeat(2, 1fr);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 600px) {
|
||
.balance-overview {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.profile-stats {
|
||
gap: 16px;
|
||
}
|
||
|
||
.profile-stats .stat-item {
|
||
min-width: 60px;
|
||
}
|
||
}
|
||
</style>
|