feat(system-user): 合并用户信息列并拆分审核状态列表
- 删除用户列表中的头像、手机号和真实姓名列,新增用户信息列,显示头像、姓名和手机号 - 更新用户信息列布局和样式,姓名加粗,手机号显示灰色小字 - 前后端支持按审核状态拆分用户列表与待审列表 - 后端UserParam新增auditPending字段,实现“待审核/被驳回”状态查询 - UserMapper.xml调整查询条件逻辑,支持auditStatus和auditPending条件 - 用户列表固定auditStatus为1,待审列表使用auditPending=true过滤数据 - 待审列表审核按钮显示条件扩展至状态0和2(被驳回) - 清理用户列表的未使用auditStatus相关代码与绑定 - 前后端均通过类型检查和功能验证,保证改动正确落地
This commit is contained in:
@@ -44,3 +44,35 @@
|
|||||||
### 验证
|
### 验证
|
||||||
- `vue-tsc --noEmit`:本页面无新增类型错误(残留报错均为项目既有问题)。
|
- `vue-tsc --noEmit`:本页面无新增类型错误(残留报错均为项目既有问题)。
|
||||||
- 落地核对:高级筛选残留 0、searchWhere/resetSearch/roles/filters/listRoles 残留 0、datasource 固定 auditStatus:0 = 1。
|
- 落地核对:高级筛选残留 0、searchWhere/resetSearch/roles/filters/listRoles 残留 0、datasource 固定 auditStatus:0 = 1。
|
||||||
|
|
||||||
|
## 用户列表「头像/手机号/真实姓名」合并为「用户信息」列
|
||||||
|
|
||||||
|
`src/views/system/user/index.vue`:
|
||||||
|
|
||||||
|
1. **列定义**:删除原来的 `avatar`(头像)、`mobile`(手机号码)、`realName`(真实姓名)三列,新增一列 `userInfo`(用户信息),宽度 220px。
|
||||||
|
2. **渲染模板**:新 `bodyCell` 分支 `key='userInfo'`,使用 flex 布局:
|
||||||
|
- 左侧:36px 头像(a-avatar)
|
||||||
|
- 右侧上下排列:真实姓名(加粗)+ 手机号码(灰色小字)
|
||||||
|
- 保留 superAdmin 权限判断逻辑(phone vs mobile)
|
||||||
|
3. **样式**:更新 `.user-box` 样式,增加 gap、user-name 加粗、user-mobile 灰色小字。
|
||||||
|
|
||||||
|
## 按审核状态拆分 user / pending 列表(前后端)
|
||||||
|
|
||||||
|
需求:`/system/user/pending` 只显示待审核(0)与被驳回(2);`/system/user` 只显示已通过(1)。后端在 `/Users/gxwebsoft/JAVA/com.gxwebsoft.core`。
|
||||||
|
|
||||||
|
### 后端改动
|
||||||
|
- `src/main/java/com/gxwebsoft/common/system/param/UserParam.java`:
|
||||||
|
- 给 `auditStatus` 字段补 `@QueryField(type = QueryType.EQ)` 注解(原缺失,不参与查询)。
|
||||||
|
- 新增 `private Boolean auditPending`(带 `@TableField(exist=false)`)语义字段:是否查询"待处理审核"(待审核0/被驳回2)。
|
||||||
|
- `src/main/java/com/gxwebsoft/common/system/mapper/xml/UserMapper.xml`:主查询 `<where>` 在 `status` 条件后追加:
|
||||||
|
- `<if test="param.auditStatus != null"> AND a.audit_status = #{param.auditStatus} </if>`(单值,user 页用)
|
||||||
|
- `<if test="param.auditPending != null and param.auditPending"> AND a.audit_status IN (0, 2) </if>`(多值,pending 页用)
|
||||||
|
- 未采用数组参数方案(`auditStatusList: [0,2]`):因 `UserController.page` 是 GET 且 Spring 绑定 `UserParam` 对象,axios 默认把数组序列化为 `key[]=v` 形式,Spring MVC 默认不识别,存在序列化风险。改用语义布尔字段彻底规避。
|
||||||
|
|
||||||
|
### 前端改动
|
||||||
|
- `src/views/system/user/index.vue`:datasource 固定 `auditStatus: 1`;清理未绑定的死代码 `searchWhere.auditStatus`(定义 + resetSearch)。
|
||||||
|
- `src/views/system/user/pending/index.vue`:datasource 由 `auditStatus: 0` 改为 `auditPending: true`;审核按钮 `v-if` 由仅 `auditStatus===0` 扩展为 `===0 || ===2`(被驳回用户也可重新审核)。
|
||||||
|
|
||||||
|
### 验证
|
||||||
|
- 前端 `vue-tsc --noEmit`:user/pending 两页无新增实质类型错误(残留均为项目既有噪音:$router、getPageTitle、TS6133 未使用、TS7053 导出数组索引)。
|
||||||
|
- 后端落地核对:UserParam auditPending 字段=1、XML 两条件各=1,片段上下文正确。SELECT 用 `a.*` 会返回 `audit_status`(驼峰映射 auditStatus),列表标签正常。
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
:columns="columns"
|
:columns="columns"
|
||||||
:datasource="datasource"
|
:datasource="datasource"
|
||||||
v-model:selection="selection"
|
v-model:selection="selection"
|
||||||
:scroll="{ x: 1300 }"
|
:scroll="{ x: 1800 }"
|
||||||
:where="defaultWhere"
|
:where="defaultWhere"
|
||||||
:customRow="customRow"
|
:customRow="customRow"
|
||||||
cache-key="proSystemUserTable"
|
cache-key="proSystemUserTable"
|
||||||
@@ -114,16 +114,21 @@
|
|||||||
</a-space>
|
</a-space>
|
||||||
</template>
|
</template>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'avatar'">
|
<template v-if="column.key === 'userInfo'">
|
||||||
<a-avatar
|
<div class="user-box">
|
||||||
:size="30"
|
<a-avatar
|
||||||
:src="`${record.avatar}`"
|
:size="36"
|
||||||
style="margin-right: 4px"
|
:src="`${record.avatar}`"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
</template>
|
</template>
|
||||||
</a-avatar>
|
</a-avatar>
|
||||||
|
<div class="user-info">
|
||||||
|
<span class="user-name">{{ record.realName || '-' }}</span>
|
||||||
|
<span class="user-mobile">{{ hasRole('superAdmin') ? record.phone : record.mobile }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'nickname'">
|
<template v-if="column.key === 'nickname'">
|
||||||
<span>{{ record.nickname }}</span>
|
<span>{{ record.nickname }}</span>
|
||||||
@@ -131,10 +136,6 @@
|
|||||||
<template v-if="column.key === 'storeName'">
|
<template v-if="column.key === 'storeName'">
|
||||||
<span :title="record.storeName">{{ record.storeName || '-' }}</span>
|
<span :title="record.storeName">{{ record.storeName || '-' }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'mobile'">
|
|
||||||
<span v-if="hasRole('superAdmin')">{{ record.phone }}</span>
|
|
||||||
<span v-else>{{ record.mobile }}</span>
|
|
||||||
</template>
|
|
||||||
<template v-if="column.key === 'roles'">
|
<template v-if="column.key === 'roles'">
|
||||||
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
|
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
|
||||||
{{ item.roleName }}
|
{{ item.roleName }}
|
||||||
@@ -325,14 +326,12 @@
|
|||||||
const searchWhere = reactive({
|
const searchWhere = reactive({
|
||||||
roleId: undefined,
|
roleId: undefined,
|
||||||
platform: undefined,
|
platform: undefined,
|
||||||
isAdmin: undefined,
|
isAdmin: undefined
|
||||||
auditStatus: undefined
|
|
||||||
});
|
});
|
||||||
const resetSearch = () => {
|
const resetSearch = () => {
|
||||||
searchWhere.roleId = undefined;
|
searchWhere.roleId = undefined;
|
||||||
searchWhere.platform = undefined;
|
searchWhere.platform = undefined;
|
||||||
searchWhere.isAdmin = undefined;
|
searchWhere.isAdmin = undefined;
|
||||||
searchWhere.auditStatus = undefined;
|
|
||||||
reload();
|
reload();
|
||||||
};
|
};
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -393,57 +392,39 @@
|
|||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'userId',
|
dataIndex: 'userId',
|
||||||
width: 90,
|
width: 90,
|
||||||
|
fixed: 'left',
|
||||||
showSorterTooltip: false
|
showSorterTooltip: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '头像',
|
title: '用户信息',
|
||||||
key: 'avatar',
|
key: 'userInfo',
|
||||||
dataIndex: 'avatar',
|
fixed: 'left',
|
||||||
align: 'center',
|
width: 220
|
||||||
width: 90
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '手机号码',
|
|
||||||
dataIndex: 'mobile',
|
|
||||||
align: 'center',
|
|
||||||
key: 'mobile',
|
|
||||||
showSorterTooltip: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '真实姓名',
|
|
||||||
dataIndex: 'realName',
|
|
||||||
align: 'center',
|
|
||||||
showSorterTooltip: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '昵称',
|
title: '昵称',
|
||||||
key: 'nickname',
|
key: 'nickname',
|
||||||
align: 'center',
|
|
||||||
dataIndex: 'nickname'
|
dataIndex: 'nickname'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '性别',
|
title: '性别',
|
||||||
dataIndex: 'sex',
|
dataIndex: 'sex',
|
||||||
align: 'center',
|
|
||||||
showSorterTooltip: false,
|
showSorterTooltip: false,
|
||||||
customRender: ({ text }) => ['', '男', '女'][text]
|
customRender: ({ text }) => ['', '男', '女'][text]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '邮箱',
|
title: '邮箱',
|
||||||
dataIndex: 'email',
|
dataIndex: 'email',
|
||||||
align: 'center',
|
|
||||||
hideInTable: true,
|
|
||||||
showSorterTooltip: false
|
showSorterTooltip: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '可用余额',
|
title: '可用余额',
|
||||||
dataIndex: 'balance',
|
dataIndex: 'balance',
|
||||||
align: 'center'
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '可用积分',
|
title: '可用积分',
|
||||||
dataIndex: 'points',
|
dataIndex: 'points',
|
||||||
align: 'center',
|
|
||||||
sorter: true
|
sorter: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -451,21 +432,18 @@
|
|||||||
dataIndex: 'expendMoney',
|
dataIndex: 'expendMoney',
|
||||||
key: 'expendMoney',
|
key: 'expendMoney',
|
||||||
sorter: true,
|
sorter: true,
|
||||||
align: 'center',
|
width: 120,
|
||||||
hideInTable: true,
|
|
||||||
showSorterTooltip: false
|
showSorterTooltip: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '注册来源',
|
title: '注册来源',
|
||||||
key: 'platform',
|
key: 'platform',
|
||||||
align: 'center',
|
|
||||||
dataIndex: 'platform',
|
dataIndex: 'platform',
|
||||||
width: 120
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '证件号码',
|
title: '证件号码',
|
||||||
dataIndex: 'idCard',
|
dataIndex: 'idCard',
|
||||||
align: 'center',
|
|
||||||
hideInTable: true
|
hideInTable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -474,26 +452,23 @@
|
|||||||
key: 'birthday',
|
key: 'birthday',
|
||||||
hideInTable: true
|
hideInTable: true
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: '省份',
|
title: '省份',
|
||||||
// dataIndex: 'province',
|
dataIndex: 'province',
|
||||||
// key: 'province',
|
key: 'province',
|
||||||
// hideInTable: true
|
},
|
||||||
// },
|
{
|
||||||
// {
|
title: '城市',
|
||||||
// title: '城市',
|
dataIndex: 'city',
|
||||||
// dataIndex: 'city',
|
key: 'city',
|
||||||
// key: 'city',
|
showSorterTooltip: false
|
||||||
// hideInTable: true,
|
},
|
||||||
// showSorterTooltip: false
|
{
|
||||||
// },
|
title: '地区',
|
||||||
// {
|
dataIndex: 'region',
|
||||||
// title: '地区',
|
key: 'region',
|
||||||
// dataIndex: 'region',
|
showSorterTooltip: false
|
||||||
// key: 'region',
|
},
|
||||||
// hideInTable: true,
|
|
||||||
// showSorterTooltip: false
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: '邮箱认证',
|
title: '邮箱认证',
|
||||||
dataIndex: 'emailVerified',
|
dataIndex: 'emailVerified',
|
||||||
@@ -561,7 +536,7 @@
|
|||||||
roleId: searchWhere.roleId,
|
roleId: searchWhere.roleId,
|
||||||
platform: searchWhere.platform,
|
platform: searchWhere.platform,
|
||||||
isAdmin: searchWhere.isAdmin || undefined,
|
isAdmin: searchWhere.isAdmin || undefined,
|
||||||
auditStatus: searchWhere.auditStatus || undefined
|
auditStatus: 1
|
||||||
};
|
};
|
||||||
return pageUsers({ page, limit, ...params, ...orders });
|
return pageUsers({ page, limit, ...params, ...orders });
|
||||||
};
|
};
|
||||||
@@ -841,11 +816,23 @@
|
|||||||
.user-box {
|
.user-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: flex-start;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-mobile {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -110,8 +110,8 @@
|
|||||||
<template v-if="column.key === 'action'">
|
<template v-if="column.key === 'action'">
|
||||||
<a-space>
|
<a-space>
|
||||||
<a @click="openEdit(record)">修改</a>
|
<a @click="openEdit(record)">修改</a>
|
||||||
<a-divider type="vertical" v-if="record.auditStatus === 0" />
|
<a-divider type="vertical" v-if="record.auditStatus === 0 || record.auditStatus === 2" />
|
||||||
<a v-if="record.auditStatus === 0" @click.stop="openAudit(record)">审核</a>
|
<a v-if="record.auditStatus === 0 || record.auditStatus === 2" @click.stop="openAudit(record)">审核</a>
|
||||||
<a-divider type="vertical" />
|
<a-divider type="vertical" />
|
||||||
<!-- <a @click="resetPsw(record)">重置</a>-->
|
<!-- <a @click="resetPsw(record)">重置</a>-->
|
||||||
<a-popconfirm
|
<a-popconfirm
|
||||||
@@ -404,7 +404,7 @@ const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
|||||||
const params = {
|
const params = {
|
||||||
...where,
|
...where,
|
||||||
keywords: searchText.value,
|
keywords: searchText.value,
|
||||||
auditStatus: 0
|
auditPending: true
|
||||||
};
|
};
|
||||||
return pageUsers({ page, limit, ...params, ...orders });
|
return pageUsers({ page, limit, ...params, ...orders });
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user