优化用户列表界面,调整移动端和桌面端布局,增强用户体验,修复数据处理逻辑,确保积分余额字段正确显示。

This commit is contained in:
zyh
2025-08-28 10:08:11 +08:00
parent ecace6eb88
commit 602d88a5a2
2 changed files with 272 additions and 267 deletions

View File

@@ -546,3 +546,4 @@ export default {
color: #a3a6ad; color: #a3a6ad;
} }
</style> </style>

View File

@@ -15,219 +15,229 @@
<!-- 用户管理内容 --> <!-- 用户管理内容 -->
<div v-else> <div v-else>
<!-- 移动端卡片列表 --> <!-- 移动端卡片列表 -->
<div v-if="isMobile" class="mobile-user-list"> <div v-if="isMobile" class="mobile-user-list">
<!-- 移动端搜索栏 --> <!-- 移动端搜索栏 -->
<el-card class="mobile-search-card" shadow="never"> <el-card class="mobile-search-card" shadow="never">
<el-form :model="query" @submit.prevent> <el-form :model="query" @submit.prevent>
<el-form-item> <el-form-item>
<el-input <el-input
v-model.trim="query.keyword" v-model.trim="query.keyword"
placeholder="搜索用户名" placeholder="搜索用户名"
clearable clearable
style="width: 100%" style="width: 100%"
> >
<template #prefix> <template #prefix>
<el-icon><Search /></el-icon> <el-icon><Search /></el-icon>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="12"> <el-col :span="12">
<el-select v-model="query.userType" clearable placeholder="用户类型" style="width: 100%"> <el-select v-model="query.userType" clearable placeholder="用户类型" style="width: 100%">
<el-option value="ADMIN" label="管理员" /> <el-option value="ADMIN" label="管理员" />
<el-option value="AGENT" label="代理" /> <el-option value="AGENT" label="代理" />
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-select v-model="query.status" clearable placeholder="状态" style="width: 100%"> <el-select v-model="query.status" clearable placeholder="状态" style="width: 100%">
<el-option value="ENABLED" label="启用" /> <el-option value="ENABLED" label="启用" />
<el-option value="DISABLED" label="禁用" /> <el-option value="DISABLED" label="禁用" />
</el-select> </el-select>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-row :gutter="12"> <el-row :gutter="12">
<el-col :span="12"> <el-col :span="12">
<el-button type="primary" :loading="loading" @click="onSearch" style="width: 100%">查询</el-button> <el-button type="primary" :loading="loading" @click="onSearch" style="width: 100%">查询</el-button>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-button @click="onReset" style="width: 100%">重置</el-button> <el-button @click="onReset" style="width: 100%">重置</el-button>
</el-col> </el-col>
</el-row> </el-row>
</el-form-item> </el-form-item>
<el-form-item v-if="canCreateUser"> <el-form-item v-if="canCreateUser">
<el-button type="success" @click="openCreate" style="width: 100%">新增用户</el-button> <el-button type="success" @click="openCreate" style="width: 100%">新增用户</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>
<!-- 移动端用户卡片列表 --> <!-- 移动端用户卡片列表 -->
<div v-loading="loading" class="mobile-cards"> <div v-loading="loading" class="mobile-cards">
<el-card <el-card
v-for="(user, index) in list" v-for="(user, index) in list"
:key="user.id" :key="user.id"
class="mobile-user-card" class="mobile-user-card"
shadow="hover" shadow="hover"
> >
<div class="mobile-user-info"> <div class="mobile-user-info">
<div class="user-header"> <div class="user-header">
<div class="user-name"> <div class="user-name">
<span class="index-number">#{{ (query.page - 1) * query.pageSize + index + 1 }}</span> <span class="index-number">#{{ (query.page - 1) * query.pageSize + index + 1 }}</span>
<span class="username">{{ user.username }}</span> <span class="username">{{ user.username }}</span>
</div>
<el-switch
:model-value="user.status === 'ENABLED'"
@change="(v)=>onToggle(user, v)"
size="small"
/>
</div> </div>
<el-switch
:model-value="user.status === 'ENABLED'"
@change="(v)=>onToggle(user, v)"
size="small"
/>
</div>
<div class="user-details"> <div class="user-details">
<div class="detail-item"> <div class="detail-item">
<span class="label">类型:</span> <span class="label">类型:</span>
<el-tag :type="user.userType === 'ADMIN' ? 'danger' : 'success'" size="small"> <el-tag :type="user.userType === 'ADMIN' ? 'danger' : 'success'" size="small">
{{ getUserTypeDisplayName(user.userType) }} {{ getUserTypeDisplayName(user.userType) }}
</el-tag>
</div>
<div v-if="user.userType === 'AGENT'" class="detail-item">
<span class="label">积分余额:</span>
<span class="value">{{ user.pointsBalance || 0 }}</span>
</div>
<div class="detail-item">
<span class="label">创建时间:</span>
<span class="value">{{ user.createdAt }}</span>
</div>
</div>
<div class="user-actions">
<el-button v-if="canEditUser" size="small" @click="openEdit(user)">编辑</el-button>
<el-button v-if="canResetPassword" size="small" type="warning" @click="onResetPwd(user)">重置密码</el-button>
<el-button v-if="canDeleteUser" size="small" type="danger" @click="onRemove(user)">删除</el-button>
</div>
</div>
</el-card>
</div>
<!-- 移动端分页 -->
<div class="mobile-pagination">
<el-pagination
background
layout="prev, pager, next"
:total="total"
:page-size="query.pageSize"
:current-page="query.page"
@current-change="(p)=>{ query.page=p; load(); }"
small
/>
</div>
</div>
<!-- 桌面端表格 -->
<div v-else class="desktop-content">
<el-card class="mb16" shadow="never">
<el-form :inline="true" :model="query" @submit.prevent>
<el-form-item label="关键词">
<el-input v-model.trim="query.keyword" placeholder="用户名" clearable style="width: 220px" />
</el-form-item>
<el-form-item label="用户类型">
<el-select v-model="query.userType" clearable placeholder="全部" style="width: 140px">
<el-option value="ADMIN" label="管理员" />
<el-option value="AGENT" label="代理" />
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="query.status" clearable placeholder="全部" style="width: 140px">
<el-option value="ENABLED" label="启用" />
<el-option value="DISABLED" label="禁用" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="onSearch">查询</el-button>
<el-button @click="onReset">重置</el-button>
</el-form-item>
<el-form-item v-if="canCreateUser">
<el-button type="success" @click="openCreate">新增用户</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never">
<el-table :data="list" v-loading="loading" border stripe>
<el-table-column type="index" label="#" width="60" />
<el-table-column prop="username" label="用户名" min-width="140" />
<el-table-column label="用户类型" min-width="120">
<template #default="{ row }">
<el-tag :type="row.userType === 'ADMIN' ? 'danger' : 'success'">
{{ getUserTypeDisplayName(row.userType) }}
</el-tag> </el-tag>
</div> </template>
</el-table-column>
<el-table-column prop="pointsBalance" label="积分余额" min-width="120" align="center">
<template #default="{ row }">
<span v-if="row.userType === 'AGENT'">{{ row.pointsBalance || 0 }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="120" align="center">
<template #default="{ row }">
<el-switch :model-value="row.status === 'ENABLED'" @change="(v)=>onToggle(row, v)" />
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" min-width="180" />
<el-table-column label="操作" width="260" fixed="right">
<template #default="{ row }">
<el-button v-if="canEditUser" size="small" @click="openEdit(row)">编辑</el-button>
<el-button v-if="canResetPassword" size="small" type="warning" @click="onResetPwd(row)">重置密码</el-button>
<el-button v-if="canDeleteUser" size="small" type="danger" @click="onRemove(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="user.userType === 'AGENT'" class="detail-item"> <div class="pager">
<span class="label">积分余额:</span> <el-pagination
<span class="value">{{ user.pointsBalance || 0 }}</span> background
</div> layout="total, sizes, prev, pager, next, jumper"
:total="total"
<div class="detail-item"> :page-size="query.pageSize"
<span class="label">创建时间:</span> :current-page="query.page"
<span class="value">{{ user.createdAt }}</span> :page-sizes="[10,20,50,100]"
</div> @size-change="(s)=>{ query.pageSize=s; query.page=1; load(); }"
</div> @current-change="(p)=>{ query.page=p; load(); }"
/>
<div class="user-actions">
<el-button v-if="canEditUser" size="small" @click="openEdit(user)">编辑</el-button>
<el-button v-if="canResetPassword" size="small" type="warning" @click="onResetPwd(user)">重置密码</el-button>
<el-button v-if="canDeleteUser" size="small" type="danger" @click="onRemove(user)">删除</el-button>
</div>
</div> </div>
</el-card> </el-card>
</div> </div>
<!-- 移动端分页 --> <!-- 统一弹窗(重要:放在两套容器之外;手机端全屏) -->
<div class="mobile-pagination"> <el-dialog
<el-pagination v-model="visible"
background :title="isEdit ? '编辑用户' : '新增用户'"
layout="prev, pager, next" :width="isMobile ? '96vw' : '520px'"
:total="total" :fullscreen="isMobile"
:page-size="query.pageSize" :append-to-body="true"
:current-page="query.page" :destroy-on-close="true"
@current-change="(p)=>{ query.page=p; load(); }" :close-on-click-modal="false"
small top="8vh"
/> >
</div> <el-form ref="formRef" :model="form" :rules="rules" label-width="88px">
</div> <el-form-item label="用户名" prop="username">
<el-input v-model.trim="form.username" :disabled="isEdit" placeholder="请输入用户名" />
<!-- 桌面端表格 --> </el-form-item>
<div v-else class="desktop-content"> <el-form-item label="用户类型" prop="userType">
<el-card class="mb16" shadow="never"> <el-select v-model="form.userType" placeholder="选择用户类型">
<el-form :inline="true" :model="query" @submit.prevent> <el-option v-for="ut in userTypeOptions" :key="ut.value" :label="ut.label" :value="ut.value" />
<el-form-item label="关键词"> </el-select>
<el-input v-model.trim="query.keyword" placeholder="用户名" clearable style="width: 220px" /> </el-form-item>
</el-form-item> <el-form-item v-if="form.userType === 'AGENT'" label="积分余额" prop="pointsBalance">
<el-form-item label="用户类型"> <el-input-number v-model.number="form.pointsBalance" :min="0" :max="999999" style="width: 100%" />
<el-select v-model="query.userType" clearable placeholder="全部" style="width: 140px"> </el-form-item>
<el-option value="ADMIN" label="管理员" /> <el-form-item v-if="!isEdit" label="初始密码" prop="password">
<el-option value="AGENT" label="代理" /> <el-input v-model.trim="form.password" type="password" show-password placeholder="至少6位" />
</el-select> </el-form-item>
</el-form-item> <el-form-item label="状态" prop="enabled">
<el-form-item label="状态"> <el-switch v-model="form.enabled" />
<el-select v-model="query.status" clearable placeholder="全部" style="width: 140px"> </el-form-item>
<el-option value="ENABLED" label="启用" /> </el-form>
<el-option value="DISABLED" label="禁用" /> <template #footer>
</el-select> <el-button @click="visible=false">取消</el-button>
</el-form-item> <el-button type="primary" :loading="saving" @click="onSubmit">保存</el-button>
<el-form-item> </template>
<el-button type="primary" :loading="loading" @click="onSearch">查询</el-button> </el-dialog>
<el-button @click="onReset">重置</el-button>
</el-form-item>
<el-form-item v-if="canCreateUser">
<el-button type="success" @click="openCreate">新增用户</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never">
<el-table :data="list" v-loading="loading" border stripe>
<el-table-column type="index" label="#" width="60" />
<el-table-column prop="username" label="用户名" min-width="140" />
<el-table-column label="用户类型" min-width="120">
<template #default="{ row }">
<el-tag :type="row.userType === 'ADMIN' ? 'danger' : 'success'">
{{ getUserTypeDisplayName(row.userType) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="pointsBalance" label="积分余额" min-width="120" align="center">
<template #default="{ row }">
<span v-if="row.userType === 'AGENT'">{{ row.pointsBalance || 0 }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="状态" width="120" align="center">
<template #default="{ row }">
<el-switch :model-value="row.status === 'ENABLED'" @change="(v)=>onToggle(row, v)" />
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" min-width="180" />
<el-table-column label="操作" width="260" fixed="right">
<template #default="{ row }">
<el-button v-if="canEditUser" size="small" @click="openEdit(row)">编辑</el-button>
<el-button v-if="canResetPassword" size="small" type="warning" @click="onResetPwd(row)">重置密码</el-button>
<el-button v-if="canDeleteUser" size="small" type="danger" @click="onRemove(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager">
<el-pagination
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
:page-size="query.pageSize"
:current-page="query.page"
:page-sizes="[10,20,50,100]"
@size-change="(s)=>{ query.pageSize=s; query.page=1; load(); }"
@current-change="(p)=>{ query.page=p; load(); }"
/>
</div>
</el-card>
<el-dialog v-model="visible" :title="isEdit ? '编辑用户' : '新增用户'" width="520px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="88px">
<el-form-item label="用户名" prop="username">
<el-input v-model.trim="form.username" :disabled="isEdit" placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="用户类型" prop="userType">
<el-select v-model="form.userType" placeholder="选择用户类型">
<el-option v-for="ut in userTypeOptions" :key="ut.value" :label="ut.label" :value="ut.value" />
</el-select>
</el-form-item>
<el-form-item label="积分余额" prop="pointsBalance" v-if="form.userType === 'AGENT'">
<el-input-number v-model="form.pointsBalance" :min="0" :max="999999" />
</el-form-item>
<el-form-item v-if="!isEdit" label="初始密码" prop="password">
<el-input v-model.trim="form.password" type="password" show-password placeholder="至少6位" />
</el-form-item>
<el-form-item label="状态" prop="enabled">
<el-switch v-model="form.enabled" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="visible=false">取消</el-button>
<el-button type="primary" :loading="saving" @click="onSubmit">保存</el-button>
</template>
</el-dialog>
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -245,7 +255,6 @@ const list = ref([])
const total = ref(0) const total = ref(0)
const isMobile = ref(false) const isMobile = ref(false)
// 检测移动端
const checkMobile = () => { const checkMobile = () => {
isMobile.value = window.innerWidth <= 768 isMobile.value = window.innerWidth <= 768
} }
@@ -264,13 +273,12 @@ const userTypeOptions = ref([
{ value: 'AGENT', label: '代理商' } { value: 'AGENT', label: '代理商' }
]) ])
// 用户类型显示名称映射 // 显示名称映射
const userTypeDisplayMap = { const userTypeDisplayMap = {
'ADMIN': '管理员', 'ADMIN': '管理员',
'AGENT': '代理商' 'AGENT': '代理商'
} }
// 权限检查
const canViewUsers = computed(() => { const canViewUsers = computed(() => {
const permission = hasPermission(PERMISSIONS.USER_VIEW) const permission = hasPermission(PERMISSIONS.USER_VIEW)
console.log('canViewUsers:', permission, 'USER_VIEW:', PERMISSIONS.USER_VIEW) console.log('canViewUsers:', permission, 'USER_VIEW:', PERMISSIONS.USER_VIEW)
@@ -293,7 +301,6 @@ function unwrap(res) {
async function load() { async function load() {
console.log('开始加载用户数据,权限检查:', canViewUsers.value) console.log('开始加载用户数据,权限检查:', canViewUsers.value)
if (!canViewUsers.value) { if (!canViewUsers.value) {
ElMessage.warning('没有权限查看用户列表') ElMessage.warning('没有权限查看用户列表')
return return
@@ -301,11 +308,7 @@ async function load() {
loading.value = true loading.value = true
try { try {
// 根据OpenAPI文档参数映射pageSize -> size const params = { ...query, size: query.pageSize }
const params = {
...query,
size: query.pageSize
}
delete params.pageSize delete params.pageSize
console.log('请求参数:', params) console.log('请求参数:', params)
@@ -315,8 +318,15 @@ async function load() {
const data = unwrap(res) || {} const data = unwrap(res) || {}
console.log('解析后数据:', data) console.log('解析后数据:', data)
// 兼容后端返回两种分页结构 const rows = data.list || data.items || []
list.value = data.list || data.items || [] // 规范化字段:保证 pointsBalance 为 number
rows.forEach(r => {
if (r.userType === 'AGENT') {
r.pointsBalance = Number(r.pointsBalance || 0)
}
})
list.value = rows
total.value = data.total || data.totalCount || list.value.length total.value = data.total || data.totalCount || list.value.length
console.log('用户列表数据:', list.value) console.log('用户列表数据:', list.value)
@@ -346,7 +356,6 @@ function onReset() {
async function onToggle(row, val) { async function onToggle(row, val) {
try { try {
await setUserStatus(row.id, val) await setUserStatus(row.id, val)
// 更新 status 字段,保持与后端一致
row.status = val ? 'ENABLED' : 'DISABLED' row.status = val ? 'ENABLED' : 'DISABLED'
showSuccessMessage('状态已更新') showSuccessMessage('状态已更新')
} catch (e) { } catch (e) {
@@ -383,7 +392,6 @@ async function onResetPwd(row) {
inputErrorMessage: '请输入有效的密码' inputErrorMessage: '请输入有效的密码'
} }
) )
await resetUserPassword(row.id, newPassword) await resetUserPassword(row.id, newPassword)
showSuccessMessage('密码重置成功') showSuccessMessage('密码重置成功')
} catch (e) { } catch (e) {
@@ -411,11 +419,13 @@ const rules = {
{ required: () => !isEdit.value, message: '请输入初始密码', trigger: 'blur' }, { required: () => !isEdit.value, message: '请输入初始密码', trigger: 'blur' },
{ min: 6, message: '至少 6 位', trigger: 'blur' }, { min: 6, message: '至少 6 位', trigger: 'blur' },
], ],
pointsBalance: [{ pointsBalance: [
required: () => form.userType === 'AGENT', {
message: '请输入积分余额', required: () => form.userType === 'AGENT',
trigger: 'blur' message: '请输入积分余额',
}], trigger: 'change' // 关键el-input-number 用 change 更稳
}
],
} }
function openCreate() { function openCreate() {
@@ -436,27 +446,22 @@ function openEdit(row) {
Object.assign(form, { Object.assign(form, {
...row, ...row,
password: '', password: '',
pointsBalance: row.pointsBalance || 0, pointsBalance: Number(row.pointsBalance || 0),
// 将后端的 status 字段映射为前端的 enabled 字段
enabled: row.status === 'ENABLED' enabled: row.status === 'ENABLED'
}) })
visible.value = true visible.value = true
} }
function doSubmit() { function doSubmit() {
// 根据接口文档构建请求参数
const payload = { const payload = {
userType: form.userType, userType: form.userType,
username: form.username, username: form.username,
status: form.enabled ? 'ENABLED' : 'DISABLED', status: form.enabled ? 'ENABLED' : 'DISABLED',
pointsBalance: form.pointsBalance || 0 pointsBalance: Number(form.pointsBalance || 0)
} }
// 如果是新增用户,需要设置密码
if (!isEdit.value) { if (!isEdit.value) {
payload.password = form.password payload.password = form.password
} }
return isEdit.value return isEdit.value
? updateUser(form.id, payload) ? updateUser(form.id, payload)
: createUser(payload) : createUser(payload)
@@ -480,7 +485,7 @@ async function onSubmit() {
}) })
} }
// 监听用户类型变化,重置积分余额字段 // 监听类型变化,管理员清零积分
watch(() => form.userType, (newType) => { watch(() => form.userType, (newType) => {
if (newType === 'ADMIN') { if (newType === 'ADMIN') {
form.pointsBalance = 0 form.pointsBalance = 0
@@ -488,7 +493,6 @@ watch(() => form.userType, (newType) => {
}) })
onMounted(() => { onMounted(() => {
// 添加调试信息
console.log('用户管理页面加载') console.log('用户管理页面加载')
const auth = getCurrentUser() const auth = getCurrentUser()
const userType = getCurrentUserType() const userType = getCurrentUserType()