Files
ApiServer-Web-admin_dashboa…/src/views/user/UserDetail.vue
T
lin d72a4f804e
Build and Deploy Vue3 / build (push) Successful in 2m21s
Build and Deploy Vue3 / deploy (push) Successful in 1m12s
fix: 右侧记录栏添加已购商品
2026-04-20 11:32:14 +08:00

1833 lines
54 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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()
if (activeTabName.value === '1') {
fetchLoginHistory()
} else if (activeTabName.value === '2') {
fetchOperationHistory()
}
}
// 返回上一页
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 loadUserData = async () => {
if(!route.query.user_id){
ElMessage.error('缺少用户ID参数');
goBack();
return;
}
await fetchUserInfo();
await fetchUserBalance();
await fetchLoginHistory();
await fetchOperationHistory();
await fetchUserOrderList();
await fetchUserTicketList();
await fetchUserGoodsList();
}
// 初始化
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>