1591 lines
40 KiB
Vue
1591 lines
40 KiB
Vue
<template>
|
||
<div class="user-list-container">
|
||
<!-- 搜索和用户列表 -->
|
||
<el-card class="main-container" shadow="never">
|
||
<!-- 搜索和操作栏 -->
|
||
<div class="filter-section">
|
||
<!-- 第一行:搜索区域 -->
|
||
<div class="filter-row search-row">
|
||
<div class="search-group">
|
||
<span class="search-label">关键字</span>
|
||
<el-input
|
||
v-model="queryParams.key"
|
||
placeholder="请输入用户名/邮箱"
|
||
clearable
|
||
class="search-input"
|
||
/>
|
||
</div>
|
||
<div class="search-group">
|
||
<span class="search-label">用户ID</span>
|
||
<el-input
|
||
v-model="jumpUserId"
|
||
placeholder="输入ID跳转"
|
||
clearable
|
||
class="search-input-small"
|
||
@keyup.enter="handleJumpToUser"
|
||
/>
|
||
</div>
|
||
<div class="search-buttons">
|
||
<el-button type="primary" @click="handleQuery">
|
||
<el-icon><Search /></el-icon><span class="btn-text">查询</span>
|
||
</el-button>
|
||
<el-button @click="resetQuery">重置</el-button>
|
||
<el-button type="success" @click="fetchUserList">
|
||
<el-icon><Refresh /></el-icon><span class="btn-text">刷新</span>
|
||
</el-button>
|
||
<el-button type="warning" @click="handleJumpToUser">
|
||
<el-icon><Position /></el-icon><span class="btn-text">跳转</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
<!-- 第二行:操作栏 -->
|
||
<div class="filter-row action-row">
|
||
<div class="action-bar">
|
||
<el-button type="primary" @click="handleAdd">
|
||
<el-icon><Plus /></el-icon>新增用户
|
||
</el-button>
|
||
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
|
||
<el-icon><Delete /></el-icon>批量删除
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 用户列表 -->
|
||
<div class="table-section">
|
||
<!-- 骨架屏 -->
|
||
<div v-if="loading" class="skeleton-container">
|
||
<div v-for="i in 5" :key="i" class="skeleton-row">
|
||
<div class="skeleton-cell skeleton-checkbox"></div>
|
||
<div class="skeleton-cell skeleton-id"></div>
|
||
<div class="skeleton-cell skeleton-user">
|
||
<div class="skeleton-avatar"></div>
|
||
<div class="skeleton-text-group">
|
||
<div class="skeleton-text skeleton-text-primary"></div>
|
||
<div class="skeleton-text skeleton-text-secondary"></div>
|
||
</div>
|
||
</div>
|
||
<div class="skeleton-cell skeleton-avatar"></div>
|
||
<div class="skeleton-cell skeleton-phone"></div>
|
||
<div class="skeleton-cell skeleton-realname">
|
||
<div class="skeleton-text skeleton-text-primary"></div>
|
||
<div class="skeleton-text skeleton-text-secondary"></div>
|
||
</div>
|
||
<div class="skeleton-cell skeleton-group"></div>
|
||
<div class="skeleton-cell skeleton-status"></div>
|
||
<div class="skeleton-cell skeleton-time"></div>
|
||
<div class="skeleton-cell skeleton-action"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 移动端卡片列表 -->
|
||
<div v-else class="mobile-card-list">
|
||
<div v-for="row in userList" :key="row.UserId" class="user-card">
|
||
<div class="card-header">
|
||
<el-checkbox v-model="row.selected" @change="handleCardSelect(row)" />
|
||
<el-avatar :size="48" :src="row.avatarUrl || ''" class="card-avatar" />
|
||
<div class="card-user-info">
|
||
<div class="card-username">{{ row.UserName }}</div>
|
||
<div class="card-email">{{ row.Email || '未设置邮箱' }}</div>
|
||
</div>
|
||
<el-tag :type="row.IsDeleted ? 'danger' : 'success'" size="small">
|
||
{{ row.IsDeleted ? '已删除' : '正常' }}
|
||
</el-tag>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="card-info-row">
|
||
<span class="card-label">用户ID:</span>
|
||
<span class="card-value">{{ row.UserId }}</span>
|
||
</div>
|
||
<div class="card-info-row">
|
||
<span class="card-label">手机号:</span>
|
||
<span class="card-value">{{ row.Phone || '未设置' }}</span>
|
||
</div>
|
||
<div class="card-info-row">
|
||
<span class="card-label">用户组:</span>
|
||
<span class="card-value">{{ row.UserGroup?.Name || '默认用户组' }}</span>
|
||
</div>
|
||
<div class="card-info-row">
|
||
<span class="card-label">实名:</span>
|
||
<span class="card-value">{{ row.RealName?.Name || '未实名' }}</span>
|
||
</div>
|
||
<div class="card-info-row">
|
||
<span class="card-label">注册时间:</span>
|
||
<span class="card-value">{{ formatDate(row.CreatedAt) }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="card-footer">
|
||
<el-button type="primary" size="small" @click="handleUserDetail(row)">详情</el-button>
|
||
<el-dropdown trigger="click" @command="(command) => handleCommand(command, row)">
|
||
<el-button size="small">更多<el-icon><ArrowDown /></el-icon></el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item command="edit">编辑用户</el-dropdown-item>
|
||
<el-dropdown-item command="avatar">修改头像</el-dropdown-item>
|
||
<el-dropdown-item command="password">修改密码</el-dropdown-item>
|
||
<el-dropdown-item command="group">修改用户组</el-dropdown-item>
|
||
<el-dropdown-item command="realname">实名信息</el-dropdown-item>
|
||
<el-dropdown-item command="balance">余额管理</el-dropdown-item>
|
||
<el-dropdown-item command="delete" divided>删除用户</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数据表格(PC端) -->
|
||
<el-table
|
||
v-if="!loading"
|
||
:data="userList"
|
||
@selection-change="handleSelectionChange"
|
||
class="desktop-table"
|
||
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
|
||
>
|
||
<el-table-column type="selection" width="55" />
|
||
<el-table-column prop="UserId" label="用户ID" width="100" />
|
||
<el-table-column label="用户信息" min-width="100">
|
||
<template #default="{ row }">
|
||
<div class="user-info">
|
||
<div class="user-detail">
|
||
<div class="username">{{ row.UserName }}</div>
|
||
<div class="email">{{ row.Email || '未设置' }}</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="CoverID" label="头像" width="100">
|
||
<template #default="{ row }">
|
||
<el-avatar :size="40" :src="row.avatarUrl || ''" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="Phone" label="手机号码" width="130">
|
||
<template #default="{ row }">
|
||
{{ row.Phone || '未设置' }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="实名信息" width="150">
|
||
<template #default="{ row }">
|
||
<div v-if="row.RealName && row.RealName.Name">
|
||
<div class="real-name">{{ row.RealName.Name }}</div>
|
||
<div class="id-card">{{ row.RealName.IdCard || '未设置' }}</div>
|
||
</div>
|
||
<span v-else class="text-gray">未实名</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="用户组" width="120">
|
||
<template #default="{ row }">
|
||
<div class="group_name">{{ row.UserGroup?.Name || '默认用户组' }}</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="状态" width="100">
|
||
<template #default="{ row }">
|
||
<el-tag :type="row.IsDeleted ? 'danger' : 'success'">
|
||
{{ row.IsDeleted ? '已删除' : '正常' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="CreatedAt" label="注册时间" width="180">
|
||
<template #default="{ row }">
|
||
{{ formatDate(row.CreatedAt) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="200" fixed="right">
|
||
<template #default="{ row }">
|
||
<div class="action-buttons">
|
||
<el-button type="primary" link @click="handleUserDetail(row)">
|
||
<el-icon><View /></el-icon>详情
|
||
</el-button>
|
||
<el-dropdown trigger="click" @command="(command) => handleCommand(command, row)">
|
||
<el-button type="primary" link>
|
||
更多<el-icon><ArrowDown /></el-icon>
|
||
</el-button>
|
||
<template #dropdown>
|
||
<el-dropdown-menu>
|
||
<el-dropdown-item command="edit">编辑用户</el-dropdown-item>
|
||
<el-dropdown-item command="avatar">修改头像</el-dropdown-item>
|
||
<el-dropdown-item command="password">修改密码</el-dropdown-item>
|
||
<el-dropdown-item command="group">修改用户组</el-dropdown-item>
|
||
<el-dropdown-item command="realname">实名信息</el-dropdown-item>
|
||
<el-dropdown-item command="balance">余额管理</el-dropdown-item>
|
||
<el-dropdown-item command="loginHistory">登录记录</el-dropdown-item>
|
||
<el-dropdown-item command="operationHistory">操作记录</el-dropdown-item>
|
||
<el-dropdown-item command="simulateLogin">模拟登录</el-dropdown-item>
|
||
<el-dropdown-item command="delete" divided>删除用户</el-dropdown-item>
|
||
</el-dropdown-menu>
|
||
</template>
|
||
</el-dropdown>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<!-- 分页 -->
|
||
<el-pagination
|
||
v-model:current-page="queryParams.page"
|
||
v-model:page-size="queryParams.count"
|
||
:page-sizes="[10, 20, 50, 100]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
:total="total"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
background
|
||
class="pagination"
|
||
/>
|
||
</div>
|
||
</el-card>
|
||
|
||
<!-- 用户编辑对话框 -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogType === 'add' ? '新增用户' : '编辑用户'"
|
||
width="600px"
|
||
append-to-body
|
||
>
|
||
<el-form
|
||
ref="userFormRef"
|
||
:model="userForm"
|
||
:rules="userRules"
|
||
label-width="100px"
|
||
>
|
||
<el-form-item label="用户名" prop="username">
|
||
<el-input v-model="userForm.username" placeholder="请输入用户名" />
|
||
</el-form-item>
|
||
<el-form-item label="密码" prop="password" v-if="dialogType === 'add'">
|
||
<el-input v-model="userForm.password" type="password" placeholder="请输入密码" show-password />
|
||
</el-form-item>
|
||
<el-form-item label="手机号" prop="Phone">
|
||
<el-input v-model="userForm.Phone" placeholder="请输入手机号" />
|
||
</el-form-item>
|
||
<!-- 编辑时显示其他字段 -->
|
||
<template v-if="dialogType === 'edit'">
|
||
<el-form-item label="邮箱" prop="Email">
|
||
<el-input v-model="userForm.Email" placeholder="请输入邮箱" />
|
||
</el-form-item>
|
||
<el-form-item label="用户组" prop="UserGroupId">
|
||
<el-select v-model="userForm.UserGroupId" placeholder="请选择用户组" style="width: 100%">
|
||
<el-option label="默认用户组" :value="1" />
|
||
<el-option label="VIP用户组" :value="2" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="实名姓名" prop="RealName.Name">
|
||
<el-input v-model="userForm.RealName.Name" placeholder="请输入实名姓名" />
|
||
</el-form-item>
|
||
<el-form-item label="身份证号" prop="RealName.IdCard">
|
||
<el-input v-model="userForm.RealName.IdCard" placeholder="请输入身份证号" />
|
||
</el-form-item>
|
||
</template>
|
||
</el-form>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="dialogVisible = false">取消</el-button>
|
||
<el-button type="primary" @click="submitForm">确定</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
|
||
<!-- 修改头像对话框 -->
|
||
<el-dialog
|
||
v-model="avatarDialogVisible"
|
||
title="修改用户头像"
|
||
width="400px"
|
||
append-to-body
|
||
>
|
||
<el-form :model="avatarForm" label-width="80px">
|
||
<el-form-item label="用户ID">
|
||
<el-input v-model="avatarForm.user_id" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="当前头像">
|
||
<div class="avatar-preview-container">
|
||
<div class="avatar-preview" @click="showAvatarSelector = true">
|
||
<el-avatar
|
||
:size="80"
|
||
:src="currentAvatarUrl"
|
||
fit="cover"
|
||
>
|
||
<el-icon :size="40"><User /></el-icon>
|
||
</el-avatar>
|
||
<div class="avatar-overlay">
|
||
<el-icon :size="24"><Edit /></el-icon>
|
||
<span>点击选择</span>
|
||
</div>
|
||
</div>
|
||
<div class="avatar-info">
|
||
<p class="avatar-id">头像ID: {{ avatarForm.cover_id || '未设置' }}</p>
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="showAvatarSelector = true"
|
||
>
|
||
选择头像
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
</el-form>
|
||
<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="用户ID">
|
||
<el-input v-model="passwordForm.user_id" disabled />
|
||
</el-form-item>
|
||
<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="用户ID">
|
||
<el-input v-model="groupForm.user_id" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="用户组ID">
|
||
<el-input v-model="groupForm.user_group_id" placeholder="请输入用户组ID" />
|
||
</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="用户ID">
|
||
<el-input v-model="realnameForm.user_id" disabled />
|
||
</el-form-item>
|
||
<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-input-number v-model="realnameForm.type" :min="0" :max="2" />
|
||
</el-form-item>
|
||
<el-form-item label="状态">
|
||
<el-input-number v-model="realnameForm.status" :min="0" :max="1" />
|
||
</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>
|
||
|
||
<!-- 登录记录对话框 -->
|
||
<el-dialog
|
||
v-model="loginHistoryDialogVisible"
|
||
title="用户登录记录"
|
||
width="800px"
|
||
append-to-body
|
||
>
|
||
<el-table :data="loginHistory" style="width: 100%">
|
||
<el-table-column prop="ip" label="IP地址" />
|
||
<el-table-column prop="user_agent" label="用户代理" />
|
||
<el-table-column prop="created_at" label="登录时间" />
|
||
</el-table>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="loginHistoryDialogVisible = false">关闭</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 操作记录对话框 -->
|
||
<el-dialog
|
||
v-model="operationHistoryDialogVisible"
|
||
title="用户操作记录"
|
||
width="800px"
|
||
append-to-body
|
||
>
|
||
<el-table :data="operationHistory" style="width: 100%">
|
||
<el-table-column prop="action" label="操作" />
|
||
<el-table-column prop="description" label="描述" />
|
||
<el-table-column prop="ip" label="IP地址" />
|
||
<el-table-column prop="created_at" label="操作时间" />
|
||
</el-table>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<el-button @click="operationHistoryDialogVisible = false">关闭</el-button>
|
||
</div>
|
||
</template>
|
||
</el-dialog>
|
||
<AvatarSelector
|
||
v-model="showAvatarSelector"
|
||
:userId="currentEditUser.UserId"
|
||
:current-cover-id="currentEditUser.CoverID"
|
||
@confirm="handleAvatarConfirm"
|
||
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, onMounted, computed } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Plus, Delete, Search, ArrowDown, View, User, Edit, Refresh, Position } from '@element-plus/icons-vue'
|
||
import AvatarSelector from '@/components/admin/AvatarSelector.vue'
|
||
import {
|
||
getUserList,
|
||
deleteUser,
|
||
createTask,
|
||
updateUserInfo,
|
||
mockUserLogin,
|
||
updateUserAvatar,
|
||
updateUserPassword,
|
||
updateUserGroup,
|
||
updateUserRealName,
|
||
getUserLoginRecord,
|
||
getUserOperationRecord
|
||
} from '@/api/admin/user'
|
||
import { getFileDetail } from '@/api/admin/file'
|
||
import { closeAllMessage } from '../../utils/message'
|
||
|
||
const router = useRouter()
|
||
|
||
// 跳转用户ID
|
||
const jumpUserId = ref('')
|
||
|
||
// 查询参数
|
||
const queryParams = reactive({
|
||
key: '',
|
||
page: 1,
|
||
count: 10
|
||
})
|
||
|
||
// 用户表单
|
||
const userForm = reactive({
|
||
UserId: undefined,
|
||
username: '',
|
||
Email: '',
|
||
Phone: '',
|
||
password: '',
|
||
UserGroupId: undefined,
|
||
RealName: {
|
||
Name: '',
|
||
IdCard: ''
|
||
}
|
||
})
|
||
const showAvatarSelector = ref(false)
|
||
const currentEditUser = ref({})
|
||
|
||
// 表单规则
|
||
const userRules = computed(() => {
|
||
const baseRules = {
|
||
UserName: [
|
||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
// 新建用户时添加密码验证
|
||
if (dialogType.value === 'add') {
|
||
baseRules.Password = [
|
||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||
{ min: 6, message: '密码长度至少6位', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
// 编辑用户时添加其他字段验证
|
||
if (dialogType.value === 'edit') {
|
||
baseRules.Email = [
|
||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
|
||
]
|
||
}
|
||
|
||
return baseRules
|
||
})
|
||
|
||
// 状态数据
|
||
const loading = ref(false)
|
||
const userList = ref([])
|
||
const total = ref(0)
|
||
const selectedRows = ref([])
|
||
const dialogVisible = ref(false)
|
||
const dialogType = ref('add')
|
||
const userFormRef = ref(null)
|
||
|
||
// 当前操作用户
|
||
const currentUser = ref({})
|
||
|
||
// 头像管理相关
|
||
const avatarDialogVisible = ref(false)
|
||
const avatarForm = reactive({
|
||
user_id: '',
|
||
cover_id: ''
|
||
})
|
||
const currentAvatarUrl = ref('')
|
||
const selectedAvatarId = ref('')
|
||
|
||
// 密码管理相关
|
||
const passwordDialogVisible = ref(false)
|
||
const passwordForm = reactive({
|
||
user_id: '',
|
||
password: ''
|
||
})
|
||
|
||
// 用户组管理相关
|
||
const groupDialogVisible = ref(false)
|
||
const groupForm = reactive({
|
||
user_id: '',
|
||
user_group_id: ''
|
||
})
|
||
|
||
// 实名信息管理相关
|
||
const realnameDialogVisible = ref(false)
|
||
const realnameForm = reactive({
|
||
user_id: '',
|
||
name: '',
|
||
id_card: '',
|
||
type: 0,
|
||
status: 0
|
||
})
|
||
|
||
// 登录记录相关
|
||
const loginHistoryDialogVisible = ref(false)
|
||
const loginHistory = ref([])
|
||
|
||
// 操作记录相关
|
||
const operationHistoryDialogVisible = ref(false)
|
||
const operationHistory = ref([])
|
||
|
||
// 获取用户列表
|
||
const fetchUserList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const res = await getUserList(queryParams)
|
||
console.log("获取用户列表:", res)
|
||
if (res.data.code === 200) {
|
||
// 映射 API 返回的字段到组件使用的字段格式
|
||
userList.value = (res.data.data.data || []).map(user => ({
|
||
UserId: user.user_id,
|
||
UserName: user.user_name,
|
||
Phone: user.phone,
|
||
Email: user.email,
|
||
Sex: user.sex,
|
||
Age: user.age,
|
||
IsAdmin: user.is_admin,
|
||
CoverID: user.cover_id,
|
||
avatarUrl: user.cover || '', // 直接使用 cover 字段作为头像 URL
|
||
UserGroup: user.user_group,
|
||
RealName: user.real_name,
|
||
IsDeleted: user.is_deleted,
|
||
CreatedAt: user.created_at
|
||
}))
|
||
console.log("用户列表:", userList.value)
|
||
total.value = res.data.data.all_count || 0
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取用户列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 查询用户列表
|
||
const handleQuery = () => {
|
||
queryParams.page = 1
|
||
fetchUserList()
|
||
}
|
||
|
||
// 重置查询
|
||
const resetQuery = () => {
|
||
queryParams.key = ''
|
||
queryParams.page = 1
|
||
fetchUserList()
|
||
}
|
||
|
||
// 跳转到指定用户详情
|
||
const handleJumpToUser = () => {
|
||
if (!jumpUserId.value || !jumpUserId.value.trim()) {
|
||
ElMessage.warning('请输入用户ID')
|
||
return
|
||
}
|
||
router.push({
|
||
path: '/user/detail',
|
||
query: { user_id: jumpUserId.value.trim() }
|
||
})
|
||
}
|
||
|
||
// 选择项变化
|
||
const handleSelectionChange = (selection) => {
|
||
selectedRows.value = selection
|
||
}
|
||
|
||
// 分页大小变化
|
||
const handleSizeChange = (size) => {
|
||
queryParams.count = size
|
||
fetchUserList()
|
||
}
|
||
|
||
// 分页页码变化
|
||
const handleCurrentChange = (page) => {
|
||
queryParams.page = page
|
||
fetchUserList()
|
||
}
|
||
|
||
// 新增用户
|
||
const handleAdd = () => {
|
||
dialogType.value = 'add'
|
||
dialogVisible.value = true
|
||
Object.assign(userForm, {
|
||
username: '',
|
||
password: '',
|
||
Email: '',
|
||
Phone: '',
|
||
UserGroupId: undefined,
|
||
RealName: {
|
||
Name: '',
|
||
IdCard: ''
|
||
}
|
||
})
|
||
userFormRef.value?.resetFields()
|
||
}
|
||
|
||
// 编辑用户
|
||
const handleEdit = (row) => {
|
||
dialogType.value = 'edit'
|
||
dialogVisible.value = true
|
||
Object.assign(userForm, {
|
||
UserId: row.UserId,
|
||
username: row.username,
|
||
Email: row.Email,
|
||
Phone: row.Phone,
|
||
UserGroupId: row.UserGroupId,
|
||
RealName: {
|
||
Name: row.RealName?.Name || '',
|
||
IdCard: row.RealName?.IdCard || ''
|
||
}
|
||
})
|
||
}
|
||
|
||
// 删除用户
|
||
const handleDelete = (row) => {
|
||
ElMessageBox.confirm(`确认删除用户 ${row.UserName} 吗?`, '警告', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(async () => {
|
||
console.log("用户信息:",row)
|
||
try {
|
||
const res = await deleteUser({ user_id: String(row.UserId) })
|
||
console.log("删除用户",res)
|
||
if (res.data.code === 200) {
|
||
ElMessage.success('删除成功')
|
||
fetchUserList()
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 批量删除
|
||
const handleBatchDelete = () => {
|
||
if (selectedRows.value.length === 0) {
|
||
ElMessage.warning('请至少选择一条记录')
|
||
return
|
||
}
|
||
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
ElMessage.success('批量删除成功')
|
||
fetchUserList()
|
||
}).catch(() => {})
|
||
}
|
||
|
||
// 用户详情跳转
|
||
const handleUserDetail = (row) => {
|
||
console.log("用户详情跳转:", row.UserId)
|
||
router.push({
|
||
path: '/user/detail',
|
||
query: { user_id: row.UserId }
|
||
})
|
||
}
|
||
|
||
// 命令处理函数
|
||
const handleCommand = (command, row) => {
|
||
currentUser.value = row
|
||
switch (command) {
|
||
case 'edit':
|
||
handleEdit(row)
|
||
break
|
||
case 'avatar':
|
||
handleAvatarModify(row)
|
||
break
|
||
case 'password':
|
||
handlePasswordModify(row)
|
||
break
|
||
case 'group':
|
||
handleGroupModify(row)
|
||
break
|
||
case 'realname':
|
||
handleRealnameModify(row)
|
||
break
|
||
case 'balance':
|
||
handleBalanceManage(row)
|
||
break
|
||
case 'loginHistory':
|
||
handleLoginHistory(row)
|
||
break
|
||
case 'operationHistory':
|
||
handleOperationHistory(row)
|
||
break
|
||
case 'simulateLogin':
|
||
handleSimulateLogin(row)
|
||
break
|
||
case 'delete':
|
||
handleDelete(row)
|
||
break
|
||
}
|
||
}
|
||
|
||
// 余额管理
|
||
const handleBalanceManage = (row) => {
|
||
router.push({
|
||
path: '/user/balance',
|
||
query: { user_id: row.UserId }
|
||
})
|
||
}
|
||
|
||
|
||
// 模拟登录
|
||
const handleSimulateLogin = async (row) => {
|
||
try {
|
||
const res = await mockUserLogin({ user_id: row.UserId })
|
||
if (res.code === 200) {
|
||
ElMessage.success('模拟登录成功')
|
||
// 可以跳转到用户视角页面
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('模拟登录失败')
|
||
}
|
||
}
|
||
|
||
// 提交表单
|
||
const submitForm = () => {
|
||
userFormRef.value?.validate(async (valid) => {
|
||
if (valid) {
|
||
try {
|
||
let res
|
||
if (dialogType.value === 'add') {
|
||
// 新建用户只传递用户名和密码
|
||
const createData = {
|
||
username: userForm.username,
|
||
password: userForm.password,
|
||
phone:userForm.Phone
|
||
|
||
}
|
||
res = await createTask(createData)
|
||
|
||
} else {
|
||
// 编辑用户传递完整信息
|
||
res = await updateUserInfo(userForm)
|
||
}
|
||
if (res.data.code === 200) {
|
||
ElMessage.success(dialogType.value === 'add' ? '新增成功' : '修改成功')
|
||
dialogVisible.value = false
|
||
fetchUserList()
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('操作失败')
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 日期格式化
|
||
const formatDate = (dateString) => {
|
||
if (!dateString) return '未设置'
|
||
const date = new Date(dateString)
|
||
return date.toLocaleString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
})
|
||
}
|
||
|
||
// 修改头像
|
||
const handleAvatarModify = async (row) => {
|
||
avatarForm.user_id = row.UserId
|
||
avatarForm.cover_id = row.CoverID || ''
|
||
currentEditUser.value = row
|
||
|
||
// 获取当前头像URL
|
||
if (row.CoverID) {
|
||
try {
|
||
const res = await getFileDetail({ file_id: row.CoverID })
|
||
console.log("获取头像1234:", res)
|
||
if (res.data.code === 200) {
|
||
currentAvatarUrl.value = res.data.data.url
|
||
}
|
||
} catch (error) {
|
||
console.error('获取头像失败:', error)
|
||
currentAvatarUrl.value = ''
|
||
}
|
||
} else {
|
||
currentAvatarUrl.value = ''
|
||
}
|
||
|
||
avatarDialogVisible.value = true
|
||
}
|
||
|
||
const handleAvatarConfirm = async (data) => {
|
||
selectedAvatarId.value = data.cover_id
|
||
avatarForm.cover_id = data.cover_id
|
||
currentAvatarUrl.value = data.url
|
||
ElMessage.success('头像选择成功,请点击确定按钮保存')
|
||
}
|
||
|
||
// 修改密码
|
||
const handlePasswordModify = (row) => {
|
||
passwordForm.user_id = row.UserId
|
||
passwordForm.password = ''
|
||
passwordDialogVisible.value = true
|
||
}
|
||
|
||
// 修改用户组
|
||
const handleGroupModify = (row) => {
|
||
groupForm.user_id = row.UserId
|
||
groupForm.user_group_id = row.UserGroupId || ''
|
||
groupDialogVisible.value = true
|
||
}
|
||
|
||
// 修改实名信息
|
||
const handleRealnameModify = (row) => {
|
||
realnameForm.user_id = row.UserId
|
||
realnameForm.name = row.RealName?.Name || ''
|
||
realnameForm.id_card = row.RealName?.IdCard || ''
|
||
realnameForm.type = row.RealName?.Type || 0
|
||
realnameForm.status = row.RealName?.Status || 0
|
||
realnameDialogVisible.value = true
|
||
}
|
||
|
||
// 获取登录记录
|
||
const handleLoginHistory = async (row) => {
|
||
try {
|
||
const res = await getUserLoginRecord({
|
||
user_id: row.UserId,
|
||
page: 1,
|
||
count: 20
|
||
})
|
||
if (res.code === 200) {
|
||
loginHistory.value = res.data.data || []
|
||
loginHistoryDialogVisible.value = true
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取登录记录失败')
|
||
}
|
||
}
|
||
|
||
// 获取操作记录
|
||
const handleOperationHistory = async (row) => {
|
||
try {
|
||
const res = await getUserOperationRecord({
|
||
user_id: row.UserId,
|
||
page: 1,
|
||
count: 20
|
||
})
|
||
if (res.code === 200) {
|
||
operationHistory.value = res.data.data || []
|
||
operationHistoryDialogVisible.value = true
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('获取操作记录失败')
|
||
}
|
||
}
|
||
|
||
|
||
// 提交头像修改
|
||
const submitAvatarModify = async () => {
|
||
try {
|
||
const res = await updateUserAvatar({
|
||
user_id: avatarForm.user_id,
|
||
cover_id: avatarForm.cover_id
|
||
})
|
||
console.log("1234",res)
|
||
console.log("提交头像修改:",res)
|
||
if (res.data.code == 200) {
|
||
ElMessage.success('头像修改成功')
|
||
avatarDialogVisible.value = false
|
||
fetchUserList()
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('头像修改失败')
|
||
}
|
||
}
|
||
|
||
// 提交密码修改
|
||
const submitPasswordModify = async () => {
|
||
try {
|
||
const res = await updateUserPassword({
|
||
user_id: passwordForm.user_id,
|
||
password: passwordForm.password
|
||
})
|
||
if (res.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.code === 200) {
|
||
ElMessage.success('用户组修改成功')
|
||
groupDialogVisible.value = false
|
||
fetchUserList()
|
||
}
|
||
} 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.code === 200) {
|
||
ElMessage.success('实名信息修改成功')
|
||
realnameDialogVisible.value = false
|
||
fetchUserList()
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error('实名信息修改失败')
|
||
}
|
||
}
|
||
|
||
// 初始化
|
||
onMounted(() => {
|
||
fetchUserList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.user-list-container {
|
||
padding: 0;
|
||
}
|
||
|
||
.main-container {
|
||
border: 1px solid #e1e8ed;
|
||
background: #ffffff;
|
||
}
|
||
|
||
.filter-section {
|
||
padding: 0;
|
||
border-bottom: 1px solid #e1e8ed;
|
||
background: #fafbfc;
|
||
}
|
||
|
||
.filter-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 12px 20px;
|
||
gap: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.filter-row:not(:last-child) {
|
||
border-bottom: 1px solid #ebeef5;
|
||
}
|
||
|
||
.search-row {
|
||
padding: 16px 20px;
|
||
}
|
||
|
||
.action-row {
|
||
padding: 12px 20px;
|
||
background: #fff;
|
||
}
|
||
|
||
.search-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.search-label {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
white-space: nowrap;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.search-input {
|
||
width: 200px;
|
||
}
|
||
|
||
.search-input-small {
|
||
width: 140px;
|
||
}
|
||
|
||
.search-buttons {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-text {
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.action-bar {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
/* 移动端卡片列表样式 */
|
||
.mobile-card-list {
|
||
display: none;
|
||
padding: 16px;
|
||
gap: 16px;
|
||
}
|
||
|
||
.user-card {
|
||
background: #fff;
|
||
border: 1px solid #e1e8ed;
|
||
padding: 16px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.card-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #f0f2f5;
|
||
}
|
||
|
||
.card-avatar {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.card-user-info {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.card-username {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #2c3e50;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.card-email {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.card-body {
|
||
padding: 12px 0;
|
||
}
|
||
|
||
.card-info-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 6px 0;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.card-label {
|
||
color: #909399;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.card-value {
|
||
color: #2c3e50;
|
||
text-align: right;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.card-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 8px;
|
||
padding-top: 12px;
|
||
border-top: 1px solid #f0f2f5;
|
||
}
|
||
|
||
/* PC端表格显示 */
|
||
.desktop-table {
|
||
display: table;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 平板适配 */
|
||
@media (max-width: 1024px) {
|
||
.search-row {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 12px;
|
||
}
|
||
|
||
.search-group {
|
||
width: 100%;
|
||
}
|
||
|
||
.search-input,
|
||
.search-input-small {
|
||
flex: 1;
|
||
width: auto !important;
|
||
}
|
||
|
||
.search-buttons {
|
||
width: 100%;
|
||
justify-content: flex-start;
|
||
}
|
||
}
|
||
|
||
/* 移动端适配 */
|
||
@media (max-width: 768px) {
|
||
.filter-row {
|
||
padding: 12px 16px;
|
||
}
|
||
|
||
.search-row {
|
||
padding: 12px 16px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.action-row {
|
||
padding: 10px 16px;
|
||
}
|
||
|
||
.search-group {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 6px;
|
||
}
|
||
|
||
.search-label {
|
||
font-size: 13px;
|
||
}
|
||
|
||
.search-input,
|
||
.search-input-small {
|
||
width: 100% !important;
|
||
}
|
||
|
||
.search-buttons {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
.search-buttons .el-button {
|
||
margin: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
.btn-text {
|
||
display: none;
|
||
}
|
||
|
||
.action-bar {
|
||
width: 100%;
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-bar .el-button {
|
||
margin: 0;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 移动端显示卡片,隐藏表格 */
|
||
.mobile-card-list {
|
||
display: block;
|
||
}
|
||
|
||
.desktop-table {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 分页移动端样式 */
|
||
.pagination {
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 12px 16px;
|
||
}
|
||
|
||
.pagination :deep(.el-pagination__sizes),
|
||
.pagination :deep(.el-pagination__jump) {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* 超小屏幕适配 */
|
||
@media (max-width: 480px) {
|
||
.filter-row {
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.search-buttons {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
}
|
||
|
||
.search-buttons .el-button {
|
||
padding: 8px 0;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.action-bar {
|
||
grid-template-columns: 1fr 1fr;
|
||
}
|
||
|
||
.action-bar .el-button {
|
||
font-size: 13px;
|
||
padding: 8px 12px;
|
||
}
|
||
}
|
||
|
||
.table-section {
|
||
padding: 0;
|
||
}
|
||
|
||
/* 骨架屏样式 */
|
||
.skeleton-container {
|
||
padding: 20px;
|
||
}
|
||
|
||
.skeleton-row {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 16px 0;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
gap: 16px;
|
||
}
|
||
|
||
.skeleton-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.skeleton-cell {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.skeleton-checkbox {
|
||
width: 55px;
|
||
height: 20px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-id {
|
||
width: 100px;
|
||
height: 20px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-user {
|
||
flex: 1;
|
||
min-width: 150px;
|
||
gap: 12px;
|
||
}
|
||
|
||
.skeleton-avatar {
|
||
width: 40px;
|
||
height: 40px;
|
||
flex-shrink: 0;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-text-group {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.skeleton-text {
|
||
height: 14px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-text-primary {
|
||
width: 80px;
|
||
}
|
||
|
||
.skeleton-text-secondary {
|
||
width: 120px;
|
||
}
|
||
|
||
.skeleton-phone {
|
||
width: 130px;
|
||
height: 20px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-realname {
|
||
width: 150px;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.skeleton-group {
|
||
width: 120px;
|
||
height: 20px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-status {
|
||
width: 100px;
|
||
height: 24px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-time {
|
||
width: 180px;
|
||
height: 20px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
.skeleton-action {
|
||
width: 200px;
|
||
height: 32px;
|
||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||
background-size: 200% 100%;
|
||
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes skeleton-loading {
|
||
0% {
|
||
background-position: 200% 0;
|
||
}
|
||
100% {
|
||
background-position: -200% 0;
|
||
}
|
||
}
|
||
|
||
.user-info {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.user-detail {
|
||
margin-left: 12px;
|
||
}
|
||
|
||
.username {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.email {
|
||
font-size: 12px;
|
||
color: #7f8c8d;
|
||
}
|
||
|
||
.real-name {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
margin-bottom: 2px;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.id-card {
|
||
font-size: 12px;
|
||
color: #7f8c8d;
|
||
}
|
||
|
||
.text-gray {
|
||
color: #95a5a6;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.pagination {
|
||
margin-top: 20px;
|
||
padding: 16px 20px;
|
||
border-top: 1px solid #e1e8ed;
|
||
background: #fafbfc;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.dialog-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 12px;
|
||
padding: 0;
|
||
}
|
||
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.avatar-preview-container {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
}
|
||
|
||
.avatar-preview {
|
||
position: relative;
|
||
cursor: pointer;
|
||
overflow: hidden;
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
|
||
.avatar-preview:hover {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.avatar-preview:hover .avatar-overlay {
|
||
opacity: 1;
|
||
}
|
||
|
||
.avatar-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 4px;
|
||
opacity: 0;
|
||
transition: opacity 0.2s ease;
|
||
color: white;
|
||
}
|
||
|
||
.avatar-overlay span {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.avatar-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.avatar-id {
|
||
margin: 0;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
}
|
||
|
||
/* 表格样式优化 */
|
||
:deep(.el-table) {
|
||
border: none;
|
||
color: #2c3e50;
|
||
}
|
||
|
||
:deep(.el-table__header) {
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
:deep(.el-table th) {
|
||
background: #f8f9fa !important;
|
||
border-bottom: 2px solid #e1e8ed;
|
||
color: #2c3e50;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
}
|
||
|
||
:deep(.el-table td) {
|
||
border-bottom: 1px solid #f0f2f5;
|
||
color: #34495e;
|
||
}
|
||
|
||
:deep(.el-table tr:hover > td) {
|
||
background-color: #f8f9fa !important;
|
||
}
|
||
|
||
:deep(.el-card__body) {
|
||
padding: 0;
|
||
}
|
||
</style>
|
||
|