4 changed files with 470 additions and 4 deletions
@ -0,0 +1,143 @@ |
|||
import request from '@/utils/request'; |
|||
import { AxiosPromise } from 'axios'; |
|||
import { UserVO, UserBanDTO, UserUnbanDTO, UserQuery, UserForm } from '@/api/im/user/types'; |
|||
|
|||
/** |
|||
* 查询用户列表 |
|||
* @param query |
|||
* @returns {*} |
|||
*/ |
|||
|
|||
export const listUser = (query?: UserQuery): AxiosPromise<UserVO[]> => { |
|||
return request({ |
|||
url: '/im/user/listCustomer', |
|||
method: 'get', |
|||
params: query |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 新增客服 |
|||
*/ |
|||
export const addUser = (data: UserForm) => { |
|||
return request({ |
|||
url: '/im/user/addCustomer', |
|||
method: 'post', |
|||
data: data |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 修改客服 |
|||
*/ |
|||
export const updateUser = (data: UserForm) => { |
|||
return request({ |
|||
url: '/im/user/editCustomer', |
|||
method: 'post', |
|||
data: data |
|||
}); |
|||
}; |
|||
|
|||
// 删除客服
|
|||
export function delUser(data: Array<string | number>) { |
|||
return request({ |
|||
url: '/im/user/removeCustomer', |
|||
method: 'post', |
|||
data: data |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 用户密码重置 |
|||
* @param id 用户ID |
|||
* @param password 密码 |
|||
*/ |
|||
export const resetUserPwd = (id: string | number, password: string) => { |
|||
const data = { |
|||
id: id, |
|||
password |
|||
}; |
|||
return request({ |
|||
url: '/im/user/resetPwdCustomer', |
|||
method: 'post', |
|||
headers: { |
|||
isEncrypt: true, |
|||
repeatSubmit: false |
|||
}, |
|||
data: data |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 查询用户详细 |
|||
* @param id |
|||
*/ |
|||
export const getUser = (id: string | number): AxiosPromise<UserVO> => { |
|||
return request({ |
|||
url: '/im/user/' + id, |
|||
method: 'get' |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 封禁用户 |
|||
* @param data |
|||
*/ |
|||
export const ban = (data: UserBanDTO) => { |
|||
return request({ |
|||
url: '/im/user/ban', |
|||
method: 'put', |
|||
data: data |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 解封用户 |
|||
* @param data |
|||
*/ |
|||
export const unban = (data: UserUnbanDTO) => { |
|||
return request({ |
|||
url: '/im/user/unban', |
|||
method: 'put', |
|||
data: data |
|||
}); |
|||
}; |
|||
|
|||
export const findUserByName = (name?: string): AxiosPromise<UserVO[]> => { |
|||
return request({ |
|||
url: '/im/user/findByName?name=' + name, |
|||
method: 'get' |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 按天统计用户注册数量 |
|||
* @param days 统计天数 |
|||
*/ |
|||
export const getDailyRegistrationCount = (days?: number): AxiosPromise<any[]> => { |
|||
return request({ |
|||
url: '/im/user/dailyRegistrationCount', |
|||
method: 'get', |
|||
params: { days } |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 获取总用户数量 |
|||
*/ |
|||
export const getTotalUserCount = (): AxiosPromise<number> => { |
|||
return request({ |
|||
url: '/im/user/totalCount', |
|||
method: 'get' |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* 获取活跃用户统计(日活、周活、月活) |
|||
*/ |
|||
export const getActiveUserStats = (): AxiosPromise<any> => { |
|||
return request({ |
|||
url: '/im/user/activeStats', |
|||
method: 'get' |
|||
}); |
|||
}; |
|||
@ -0,0 +1,325 @@ |
|||
<template> |
|||
<div class="p-2"> |
|||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> |
|||
<div v-show="showSearch" class="mb-[10px]"> |
|||
<el-card shadow="hover"> |
|||
<el-form ref="queryFormRef" :model="queryParams" :inline="true"> |
|||
<el-form-item label="用户名" prop="userName"> |
|||
<el-input v-model="queryParams.userName" placeholder="请输入用户名" clearable @keyup.enter="handleQuery" /> |
|||
</el-form-item> |
|||
<el-form-item label="用户昵称" prop="nickName"> |
|||
<el-input v-model="queryParams.nickName" placeholder="请输入用户昵称" clearable @keyup.enter="handleQuery" /> |
|||
</el-form-item> |
|||
<el-form-item> |
|||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
|||
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
|||
<!-- <el-button v-hasPermi="['im:user:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button> --> |
|||
</el-form-item> |
|||
</el-form> |
|||
</el-card> |
|||
</div> |
|||
</transition> |
|||
<el-card shadow="never"> |
|||
<template #header> |
|||
<el-row :gutter="10" class="mb8"> |
|||
<el-col :span="1.5"> |
|||
<el-button v-hasPermi="['im:user:addCustomer']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button> |
|||
</el-col> |
|||
|
|||
<right-toolbar v-model:showSearch="showSearch" @query-table="getList"></right-toolbar> |
|||
</el-row> |
|||
</template> |
|||
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> |
|||
<el-table-column label="用户名" align="center" prop="userName" /> |
|||
<el-table-column label="用户昵称" align="center" prop="nickName" /> |
|||
<el-table-column label="用户头像" align="center" prop="headImageThumb" width="100"> |
|||
<template #default="scope"> |
|||
<image-preview :src="scope.row.headImageThumb" :full-src="scope.row.headImage" :width="50" :height="50" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="性别" align="center" prop="sex"> |
|||
<template #default="scope"> |
|||
<dict-tag :options="sys_user_sex" :value="scope.row.sex" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="是否被封禁" align="center" prop="isBanned"> |
|||
<template #default="scope"> |
|||
<dict-tag :options="im_bool" :value="scope.row.isBanned" /> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="注册时间" align="center" prop="createdTime" width="180"> |
|||
<template #default="scope"> |
|||
<span>{{ parseTime(scope.row.createdTime, '{y}-{m}-{d}') }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="最后登录时间" align="center" prop="lastLoginTime" width="180"> |
|||
<template #default="scope"> |
|||
<span>{{ parseTime(scope.row.lastLoginTime, '{y}-{m}-{d}') }}</span> |
|||
</template> |
|||
</el-table-column> |
|||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> |
|||
<template #default="scope"> |
|||
<el-tooltip content="修改" placement="top"> |
|||
<el-button v-hasPermi="['im:user:editCustomer']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> |
|||
</el-tooltip> |
|||
<el-tooltip content="重置密码" placement="top"> |
|||
<el-button v-hasPermi="['im:user:resetPwdCustomer']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button> |
|||
</el-tooltip> |
|||
<el-tooltip content="删除" placement="top"> |
|||
<el-button v-hasPermi="['im:user:removeCustomer']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> |
|||
</el-tooltip> |
|||
</template> |
|||
</el-table-column> |
|||
</el-table> |
|||
|
|||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> |
|||
</el-card> |
|||
<!-- 添加或修改用户对话框 --> |
|||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="800px" append-to-body> |
|||
<el-form ref="userFormRef" :model="form" :rules="rules" label-width="100px"> |
|||
<el-form-item label="头像" prop="headImage"> |
|||
<image-upload v-model="form.headImage" v-model:thumb="form.headImageThumb" :width="100" :height="100" /> |
|||
</el-form-item> |
|||
<el-form-item label="账号" prop="userName"> |
|||
<el-input v-model="form.userName" /> |
|||
</el-form-item> |
|||
<el-form-item v-if="form.id == undefined" label="密码" prop="password"> |
|||
<el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password /> |
|||
</el-form-item> |
|||
<el-form-item label="性别" prop="sex"> |
|||
<el-select v-model="form.sex" placeholder="请选择"> |
|||
<el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="String(dict.value)"></el-option> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="昵称" prop="nickName"> |
|||
<el-input v-model="form.nickName" /> |
|||
</el-form-item> |
|||
<!-- |
|||
<el-form-item label="个性签名" prop="signature"> |
|||
<el-input v-model="form.signature" /> |
|||
</el-form-item> |
|||
--> |
|||
</el-form> |
|||
<template #footer> |
|||
<div class="dialog-footer"> |
|||
<el-button type="primary" @click="submitForm">确 定</el-button> |
|||
</div> |
|||
</template> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup name="User" lang="ts"> |
|||
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd } from '@/api/im/user/customer'; |
|||
import { UserVO, UserQuery, UserForm } from '@/api/im/user/types'; |
|||
import { to } from 'await-to-js'; |
|||
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|||
|
|||
const userList = ref<UserVO[]>([]); |
|||
const loading = ref(true); |
|||
const showSearch = ref(true); |
|||
const ids = ref<Array<string | number>>([]); |
|||
const single = ref(true); |
|||
const multiple = ref(true); |
|||
const total = ref(0); |
|||
|
|||
const queryFormRef = ref<ElFormInstance>(); |
|||
const userFormRef = ref<ElFormInstance>(); |
|||
const dialog = reactive<DialogOption>({ |
|||
visible: false, |
|||
title: '' |
|||
}); |
|||
|
|||
const initFormData: UserForm = { |
|||
id: undefined, |
|||
userName: undefined, |
|||
nickName: undefined, |
|||
headImage: undefined, |
|||
headImageThumb: undefined, |
|||
password: undefined, |
|||
sex: undefined, |
|||
signature: undefined, |
|||
lastLoginTime: undefined, |
|||
createdTime: undefined, |
|||
type: undefined, |
|||
isBanned: undefined, |
|||
reason: undefined |
|||
}; |
|||
const data = reactive<PageData<UserForm, UserQuery>>({ |
|||
form: { ...initFormData }, |
|||
queryParams: { |
|||
pageNum: 1, |
|||
pageSize: 10, |
|||
userName: undefined, |
|||
nickName: undefined, |
|||
params: {} |
|||
}, |
|||
rules: {} |
|||
}); |
|||
const { queryParams, form, rules } = toRefs(data); |
|||
|
|||
const { im_bool } = toRefs<any>(proxy?.useDict('im_bool')); |
|||
const { sys_user_sex } = toRefs<any>(proxy?.useDict('sys_user_sex')); |
|||
console.log(sys_user_sex); |
|||
/** 提交按钮 */ |
|||
const submitForm = () => { |
|||
userFormRef.value?.validate(async (valid: boolean) => { |
|||
if (valid) { |
|||
// 提交前将 sex 从字符串转换回数字(若为数字字符串)以兼容后端 |
|||
const payload = { ...form.value } as any; |
|||
if (typeof payload.sex === 'string' && /^\d+$/.test(payload.sex)) { |
|||
payload.sex = Number(payload.sex); |
|||
} |
|||
payload.id ? await updateUser(payload) : await addUser(payload); |
|||
proxy?.$modal.msgSuccess('操作成功'); |
|||
dialog.visible = false; |
|||
await getList(); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
/** 查询用户列表 */ |
|||
const getList = async () => { |
|||
loading.value = true; |
|||
const res = await listUser(queryParams.value); |
|||
userList.value = res.rows; |
|||
total.value = res.total; |
|||
loading.value = false; |
|||
console.log('getList'); |
|||
}; |
|||
/** 搜索按钮操作 */ |
|||
const handleQuery = () => { |
|||
queryParams.value.pageNum = 1; |
|||
getList(); |
|||
console.log('handleQuery'); |
|||
}; |
|||
|
|||
/** 重置按钮操作 */ |
|||
const resetQuery = () => { |
|||
queryFormRef.value?.resetFields(); |
|||
handleQuery(); |
|||
console.log('handleQuery'); |
|||
}; |
|||
|
|||
/** 新增按钮操作 */ |
|||
const handleAdd = () => { |
|||
reset(); |
|||
dialog.visible = true; |
|||
dialog.title = '添加客服'; |
|||
}; |
|||
/** 修改按钮操作 */ |
|||
const handleUpdate = async (row?: UserVO) => { |
|||
reset(); |
|||
const _id = row?.id || ids.value[0]; |
|||
const res = await getUser(_id); |
|||
Object.assign(form.value, res.data); |
|||
// 保证 sex 为字符串以匹配 select 的 option value |
|||
(form.value as any).sex = form.value.sex !== undefined && form.value.sex !== null ? String(form.value.sex) : undefined; |
|||
dialog.visible = true; |
|||
dialog.title = '修改客服信息'; |
|||
}; |
|||
/** 删除按钮操作 */ |
|||
const handleDelete = async (row?: UserVO) => { |
|||
const _id = row?.id ? [row.id] : ids.value; |
|||
await proxy?.$modal.confirm('是否确认删除客服编号为"' + (row?.userName || '') + '"的数据项?'); |
|||
await delUser(_id); |
|||
getList(); |
|||
proxy?.$modal.msgSuccess('删除成功'); |
|||
}; |
|||
|
|||
/** 重置密码按钮操作 */ |
|||
const handleResetPwd = async (row: UserVO) => { |
|||
const [err, res] = await to( |
|||
ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消', |
|||
closeOnClickModal: false, |
|||
inputPattern: /^.{5,20}$/, |
|||
inputErrorMessage: '用户密码长度必须介于 5 和 20 之间', |
|||
inputValidator: (value) => { |
|||
if (/<|>|"|'|\||\\/.test(value)) { |
|||
return '不能包含非法字符:< > " \' \\\ |'; |
|||
} |
|||
} |
|||
}) |
|||
); |
|||
if (!err && res) { |
|||
await resetUserPwd(row.id, res.value); |
|||
proxy?.$modal.msgSuccess('修改成功,新密码是:' + res.value); |
|||
} |
|||
}; |
|||
|
|||
/** 详情按钮操作 */ |
|||
const handleDetail = async (row?: UserVO) => { |
|||
reset(); |
|||
const _id = row?.id || ids.value[0]; |
|||
const res = await getUser(_id); |
|||
Object.assign(form.value, res.data); |
|||
// 保证 sex 为字符串以匹配 select 的 option value |
|||
(form.value as any).sex = form.value.sex !== undefined && form.value.sex !== null ? String(form.value.sex) : undefined; |
|||
dialog.visible = true; |
|||
dialog.title = '客服信息'; |
|||
}; |
|||
/** 多选框选中数据 */ |
|||
const handleSelectionChange = (selection: UserVO[]) => { |
|||
ids.value = selection.map((item) => item.id); |
|||
single.value = selection.length != 1; |
|||
multiple.value = !selection.length; |
|||
console.log('handleSelectionChange'); |
|||
}; |
|||
|
|||
/** 表单重置 */ |
|||
const reset = () => { |
|||
form.value = { ...initFormData }; |
|||
userFormRef.value?.resetFields(); |
|||
console.log('reset'); |
|||
}; |
|||
|
|||
/** 提交按钮 */ |
|||
// const submitForm = () => { |
|||
// dialog.visible = false; |
|||
// }; |
|||
|
|||
const banHandle = (user: any) => { |
|||
ElMessageBox.prompt('封禁原因:', '确定对该用户进行封禁?', { |
|||
inputPattern: /\S/, |
|||
inputErrorMessage: '请输入封禁原因', |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(({ value }) => { |
|||
const data = { id: user.id, reason: value }; |
|||
ban(data).then(() => { |
|||
user.isBanned = true; |
|||
ElMessage.success(`用户'${user.userName}'已被封禁`); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
const unbanHandle = (user: any) => { |
|||
ElMessageBox.confirm('确定解除该用户的封禁状态??', '提示', { |
|||
confirmButtonText: '确定', |
|||
cancelButtonText: '取消' |
|||
}).then(() => { |
|||
const data = { id: user.id }; |
|||
unban(data).then(() => { |
|||
user.isBanned = false; |
|||
ElMessage.success(`用户'${user.userName}'解锁成功`); |
|||
}); |
|||
}); |
|||
}; |
|||
|
|||
/** 导出按钮操作 */ |
|||
const handleExport = () => { |
|||
proxy?.download( |
|||
'im/user/export', |
|||
{ |
|||
...queryParams.value |
|||
}, |
|||
`user_${new Date().getTime()}.xlsx` |
|||
); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
getList(); |
|||
}); |
|||
</script> |
|||
Loading…
Reference in new issue