feat(system): 实现用户审核功能
- 新增用户模型字段:auditStatus、auditRemark、auditTime - 添加用户审核接口 auditUser,支持单条审核 - 用户列表页增加审核状态筛选项和对应列展示 - 列表行内仅待审核用户显示“审核”操作链接 - 工具栏添加批量审核按钮,实现批量审核功能 - 审核弹窗支持选择通过或拒绝及填写备注 - 审核提交支持单条和批量调用接口 - 保持代码类型检查无新增错误,符合项目字段命名规范
This commit is contained in:
@@ -35,6 +35,15 @@
|
||||
- 不改:`admin` 第294行 `isAdmin: 1` 是查询条件(`UserParam.isAdmin?: number`),语义正确。
|
||||
- 验证:`vue-tsc --noEmit` 确认改动无新增错误;旧写法已无残留。
|
||||
|
||||
### 审核功能(19:27):system/user 用户审核
|
||||
- 需求:audit_status=0(待审核)时实现审核功能。已与用户确认:后端接口已有(`POST /system/user/audit/{id}`,入参 `{status, remark}`,与 shop 模块一致);状态 `0待审核 / 1通过 / 2拒绝`;支持行内单条 + 勾选批量审核。
|
||||
- 改动文件:
|
||||
- `src/api/system/user/model/index.ts`:User 加 `auditStatus / auditRemark / auditTime`;UserParam 加 `auditStatus`;新增 `UserAuditRequest { status: number; remark?: string }`。
|
||||
- `src/api/system/user/index.ts`:新增 `auditUser(id, data)` → `POST /system/user/audit/{id}`。
|
||||
- `src/views/system/user/index.vue`:高级筛选加「审核状态」下拉;列配置加「审核状态」列(a-tag 展示);行内仅 `auditStatus===0` 显示「审核」链接(`@click.stop` 防冒泡触发行详情);工具栏加「批量审核」按钮;新增 `a-modal` 审核弹窗(radio 通过/拒绝 + 备注 textarea);脚本 `openAudit / openBatchAudit / submitAudit`(批量用 `Promise.all` 循环调单条接口)。
|
||||
- 验证:vue-tsc 仅剩项目既有错误(`updateUserRecommend` 的 form any、`$router`、`loginUser/updateIsAdmin` 未使用等),本功能无新增类型错误。
|
||||
- 注意点:字段用 camelCase `auditStatus`(与项目约定一致);若后端实际返回下划线 `audit_status` 需同步改模型字段名。批量审核为循环调单条接口(不依赖后端批量接口)。
|
||||
|
||||
### 备注
|
||||
- 方案中的「Batch 3 长期维护项」(admin/user 组件合并、导入模板配置化、search.vue 重命名)本次未执行,属可选重构,风险较高,留待后续。
|
||||
- Grep 工具在当前环境因 pcre2 动态库缺失报错,改用 bash grep 替代。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { User, UserParam } from './model';
|
||||
import type { User, UserParam, UserAuditRequest } from './model';
|
||||
import { SERVER_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
@@ -293,3 +293,19 @@ export async function exportUsers(params?: UserParam) {
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核用户
|
||||
* @param id 用户id
|
||||
* @param data 审核结果
|
||||
*/
|
||||
export async function auditUser(id: number, data: UserAuditRequest) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
SERVER_API_URL + '/system/user/audit/' + id,
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
@@ -129,6 +129,12 @@ export interface User {
|
||||
followers?: number;
|
||||
// 推荐人ID
|
||||
dealerId?: number;
|
||||
// 审核状态: 0待审核 1通过 2拒绝
|
||||
auditStatus?: number;
|
||||
// 审核备注
|
||||
auditRemark?: string;
|
||||
// 审核时间
|
||||
auditTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,4 +164,16 @@ export interface UserParam extends PageParam {
|
||||
showProfile?: boolean;
|
||||
isStaff?: boolean;
|
||||
templateId?: number;
|
||||
// 审核状态: 0待审核 1通过 2拒绝
|
||||
auditStatus?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户审核请求
|
||||
*/
|
||||
export interface UserAuditRequest {
|
||||
// 审核状态: 1-通过 2-拒绝
|
||||
status: number;
|
||||
// 审核备注
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,18 @@
|
||||
<a-select-option :value="2">普通用户</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核状态">
|
||||
<a-select
|
||||
v-model:value="searchWhere.auditStatus"
|
||||
placeholder="全部"
|
||||
allow-clear
|
||||
style="width: 140px"
|
||||
>
|
||||
<a-select-option :value="0">待审核</a-select-option>
|
||||
<a-select-option :value="1">通过</a-select-option>
|
||||
<a-select-option :value="2">拒绝</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="reload()">搜索</a-button>
|
||||
<a-button style="margin-left: 8px" @click="resetSearch">重置</a-button>
|
||||
@@ -84,6 +96,17 @@
|
||||
:disabled="selection.length === 0"
|
||||
>重置密码
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
@click="openBatchAudit"
|
||||
:disabled="selection.length === 0"
|
||||
>
|
||||
<template #icon>
|
||||
<CheckCircleOutlined />
|
||||
</template>
|
||||
<span>批量审核</span>
|
||||
</a-button>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
@@ -142,6 +165,12 @@
|
||||
<a-tag v-else-if="record.status === 1" color="red">冻结</a-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'auditStatus'">
|
||||
<a-tag v-if="record.auditStatus === 0" color="orange">待审核</a-tag>
|
||||
<a-tag v-else-if="record.auditStatus === 1" color="green">通过</a-tag>
|
||||
<a-tag v-else-if="record.auditStatus === 2" color="red">拒绝</a-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'platform'">
|
||||
<WechatOutlined v-if="record.platform === 'MP-WEIXIN'" />
|
||||
<Html5Outlined v-if="record.platform === 'H5'" />
|
||||
@@ -168,7 +197,9 @@
|
||||
<template v-if="column.key === 'action'">
|
||||
<div>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-divider type="vertical" v-if="record.auditStatus === 0" />
|
||||
<a v-if="record.auditStatus === 0" @click.stop="openAudit(record)">审核</a>
|
||||
<a-divider type="vertical" v-if="record.auditStatus === 0" />
|
||||
<!-- <a @click="resetPsw(record)">重置</a>-->
|
||||
<!-- <a-divider type="vertical" />-->
|
||||
<a-popconfirm
|
||||
@@ -194,6 +225,28 @@
|
||||
<user-import v-model:visible="showImport" @done="reload" />
|
||||
<!-- 用户详情 -->
|
||||
<user-info v-model:visible="showInfo" :data="current" @done="reload" />
|
||||
<!-- 审核弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="showAudit"
|
||||
:title="auditTitle"
|
||||
@ok="submitAudit"
|
||||
>
|
||||
<a-form :model="auditForm" layout="vertical">
|
||||
<a-form-item label="审核结果">
|
||||
<a-radio-group v-model:value="auditForm.status">
|
||||
<a-radio :value="1">通过</a-radio>
|
||||
<a-radio :value="2">拒绝</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核备注">
|
||||
<a-textarea
|
||||
v-model:value="auditForm.remark"
|
||||
:rows="3"
|
||||
placeholder="请输入审核备注(拒绝时建议填写)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
@@ -209,7 +262,8 @@
|
||||
Html5Outlined,
|
||||
ChromeOutlined,
|
||||
WechatOutlined,
|
||||
ExclamationCircleOutlined
|
||||
ExclamationCircleOutlined,
|
||||
CheckCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
@@ -227,7 +281,8 @@
|
||||
removeUsers,
|
||||
updateUserPassword,
|
||||
updateUser,
|
||||
listUsers
|
||||
listUsers,
|
||||
auditUser
|
||||
} from '@/api/system/user';
|
||||
import type { User, UserParam } from '@/api/system/user/model';
|
||||
import { toTreeData, uuid } from 'ele-admin-pro';
|
||||
@@ -258,17 +313,31 @@
|
||||
const showInfo = ref(false);
|
||||
// 是否显示用户导入弹窗
|
||||
const showImport = ref(false);
|
||||
// 是否显示审核弹窗
|
||||
const showAudit = ref(false);
|
||||
// 是否批量审核
|
||||
const batchAudit = ref(false);
|
||||
// 审核弹窗标题
|
||||
const auditTitle = ref('审核用户');
|
||||
// 审核表单
|
||||
const auditForm = reactive({
|
||||
userId: undefined as number | undefined,
|
||||
status: 1,
|
||||
remark: ''
|
||||
});
|
||||
const searchText = ref('');
|
||||
// 高级筛选条件
|
||||
const searchWhere = reactive({
|
||||
roleId: undefined,
|
||||
platform: undefined,
|
||||
isAdmin: undefined
|
||||
isAdmin: undefined,
|
||||
auditStatus: undefined
|
||||
});
|
||||
const resetSearch = () => {
|
||||
searchWhere.roleId = undefined;
|
||||
searchWhere.platform = undefined;
|
||||
searchWhere.isAdmin = undefined;
|
||||
searchWhere.auditStatus = undefined;
|
||||
reload();
|
||||
};
|
||||
const userStore = useUserStore();
|
||||
@@ -457,6 +526,13 @@
|
||||
align: 'center',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '审核状态',
|
||||
dataIndex: 'auditStatus',
|
||||
key: 'auditStatus',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '管理员',
|
||||
key: 'isAdmin',
|
||||
@@ -477,7 +553,7 @@
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
width: 160,
|
||||
fixed: 'right',
|
||||
align: 'center'
|
||||
}
|
||||
@@ -496,7 +572,8 @@
|
||||
keywords: searchText.value,
|
||||
roleId: searchWhere.roleId,
|
||||
platform: searchWhere.platform,
|
||||
isAdmin: searchWhere.isAdmin || undefined
|
||||
isAdmin: searchWhere.isAdmin || undefined,
|
||||
auditStatus: searchWhere.auditStatus || undefined
|
||||
};
|
||||
return pageUsers({ page, limit, ...params, ...orders });
|
||||
};
|
||||
@@ -604,6 +681,51 @@
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开单条审核弹窗 */
|
||||
const openAudit = (row: User) => {
|
||||
batchAudit.value = false;
|
||||
auditForm.userId = row.userId;
|
||||
auditForm.status = 1;
|
||||
auditForm.remark = '';
|
||||
auditTitle.value = `审核用户 - ${row.nickname || row.username}`;
|
||||
showAudit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量审核弹窗 */
|
||||
const openBatchAudit = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
batchAudit.value = true;
|
||||
auditForm.userId = undefined;
|
||||
auditForm.status = 1;
|
||||
auditForm.remark = '';
|
||||
auditTitle.value = `批量审核(已选 ${selection.value.length} 条)`;
|
||||
showAudit.value = true;
|
||||
};
|
||||
|
||||
/* 提交审核 */
|
||||
const submitAudit = () => {
|
||||
const hide = message.loading('提交中..', 0);
|
||||
const doOne = (userId: number) =>
|
||||
auditUser(userId, { status: auditForm.status, remark: auditForm.remark });
|
||||
const tasks = batchAudit.value
|
||||
? selection.value.map((d) => doOne(d.userId as number))
|
||||
: [doOne(auditForm.userId as number)];
|
||||
Promise.all(tasks)
|
||||
.then(() => {
|
||||
hide();
|
||||
message.success('审核完成');
|
||||
showAudit.value = false;
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索处理 */
|
||||
const handleSearch = () => {
|
||||
reload();
|
||||
|
||||
Reference in New Issue
Block a user