|
|
|
@ -23,7 +23,7 @@ |
|
|
|
<el-form-item> |
|
|
|
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button> |
|
|
|
<el-button icon="Refresh" @click="resetQuery">重置</el-button> |
|
|
|
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['im:user:export']">导出</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> |
|
|
|
@ -31,7 +31,20 @@ |
|
|
|
</transition> |
|
|
|
|
|
|
|
<el-card shadow="never"> |
|
|
|
<template #header> |
|
|
|
<el-row :gutter="10"> |
|
|
|
<el-col :span="1.5"> |
|
|
|
<el-button v-hasPermi="['im:user:labelIds']" type="primary" plain icon="Plus" @click="handleLabelIds()">批量设置标签</el-button> |
|
|
|
</el-col> |
|
|
|
<el-col :span="1.5"> |
|
|
|
<el-button v-hasPermi="['im:user:groupIds']" type="primary" plain icon="Plus" @click="handleGroupIds()">批量设置分组</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 type="selection" width="55" align="center" /> |
|
|
|
<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"> |
|
|
|
@ -47,10 +60,10 @@ |
|
|
|
<el-table-column label="标签" align="center" prop="labelIds" width="200"> |
|
|
|
<template #default="scope"> |
|
|
|
<el-tag |
|
|
|
v-for="labelId in (scope.row.labelIds || '').split(',').filter(id => id)" |
|
|
|
v-for="labelId in (scope.row.labelIds || '').split(',').filter((id) => id)" |
|
|
|
:key="labelId" |
|
|
|
size="small" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px;" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px" |
|
|
|
> |
|
|
|
{{ getLabelNameById(labelId) }} |
|
|
|
</el-tag> |
|
|
|
@ -59,21 +72,23 @@ |
|
|
|
<el-table-column label="群组" align="center" prop="groupIds" width="200"> |
|
|
|
<template #default="scope"> |
|
|
|
<el-tag |
|
|
|
v-for="groupId in (scope.row.groupIds || '').split(',').filter(id => id)" |
|
|
|
v-for="groupId in (scope.row.groupIds || '').split(',').filter((id) => id)" |
|
|
|
:key="groupId" |
|
|
|
type="info" |
|
|
|
size="small" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px;" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px" |
|
|
|
> |
|
|
|
{{ getGroupNameById(groupId) }} |
|
|
|
</el-tag> |
|
|
|
</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> |
|
|
|
@ -86,17 +101,21 @@ |
|
|
|
</el-table-column> |
|
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width"> |
|
|
|
<template #default="scope"> |
|
|
|
<el-button link type="primary" v-hasPermi="['im:user:query']" @click="handleDetail(scope.row)">详情</el-button> |
|
|
|
<el-button v-if="scope.row.isBanned" link type="danger" v-hasPermi="['im:user:ban']" @click="unbanHandle(scope.row)">解封</el-button> |
|
|
|
<el-button v-else link type="danger" v-hasPermi="['im:user:ban']" @click="banHandle(scope.row)">封禁</el-button> |
|
|
|
<!-- |
|
|
|
<el-button v-hasPermi="['im:user:query']" link type="primary" @click="handleDetail(scope.row)">详情</el-button> |
|
|
|
<el-button v-if="scope.row.isBanned" v-hasPermi="['im:user:ban']" link type="danger" @click="unbanHandle(scope.row)">解封</el-button> |
|
|
|
<el-button v-else v-hasPermi="['im:user:ban']" link type="danger" @click="banHandle(scope.row)">封禁</el-button> |
|
|
|
--> |
|
|
|
<el-button v-hasPermi="['im:user:label']" link type="primary" @click="handleLabel(scope.row)">设置标签</el-button> |
|
|
|
<el-button v-hasPermi="['im:user:group']" link type="primary" @click="handleGroup(scope.row)">设置分组</el-button> |
|
|
|
</template> |
|
|
|
</el-table-column> |
|
|
|
</el-table> |
|
|
|
|
|
|
|
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> |
|
|
|
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> |
|
|
|
</el-card> |
|
|
|
<!-- 添加或修改用户对话框 --> |
|
|
|
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body> |
|
|
|
<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" disabled> |
|
|
|
<el-form-item label="用户头像" prop="headImage"> |
|
|
|
<image-preview v-if="form.headImageThumb" :src="form.headImageThumb" :full-src="form.headImage" :width="100" :height="100" /> |
|
|
|
@ -114,28 +133,28 @@ |
|
|
|
<el-input v-model="form.signature" /> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="最后登录时间" prop="lastLoginTime"> |
|
|
|
<el-date-picker clearable v-model="form.lastLoginTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"> </el-date-picker> |
|
|
|
<el-date-picker v-model="form.lastLoginTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss"> </el-date-picker> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="注册时间" prop="createdTime"> |
|
|
|
<el-date-picker clearable v-model="form.createdTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"> </el-date-picker> |
|
|
|
<el-date-picker v-model="form.createdTime" clearable type="datetime" value-format="YYYY-MM-DD HH:mm:ss"> </el-date-picker> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="标签" prop="labelIds"> |
|
|
|
<el-tag |
|
|
|
v-for="labelId in (form.labelIds || '').split(',').filter(id => id)" |
|
|
|
v-for="labelId in (form.labelIds || '').split(',').filter((id) => id)" |
|
|
|
:key="labelId" |
|
|
|
size="small" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px;" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px" |
|
|
|
> |
|
|
|
{{ getLabelNameById(labelId) }} |
|
|
|
</el-tag> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item label="群组" prop="groupIds"> |
|
|
|
<el-tag |
|
|
|
v-for="groupId in (form.groupIds || '').split(',').filter(id => id)" |
|
|
|
v-for="groupId in (form.groupIds || '').split(',').filter((id) => id)" |
|
|
|
:key="groupId" |
|
|
|
type="info" |
|
|
|
size="small" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px;" |
|
|
|
style="margin-right: 5px; margin-bottom: 5px" |
|
|
|
> |
|
|
|
{{ getGroupNameById(groupId) }} |
|
|
|
</el-tag> |
|
|
|
@ -153,11 +172,40 @@ |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</el-dialog> |
|
|
|
<!-- 设置标签对话框 --> |
|
|
|
<el-dialog v-model="labelDialogVisible" title="设置标签" width="520px"> |
|
|
|
<div> |
|
|
|
<el-select v-model="selectedLabelIds" multiple filterable placeholder="请选择标签" style="width: 100%"> |
|
|
|
<el-option v-for="opt in labelOptions" :key="opt.id" :label="opt.labelName" :value="opt.id" /> |
|
|
|
</el-select> |
|
|
|
</div> |
|
|
|
<template #footer> |
|
|
|
<div class="dialog-footer"> |
|
|
|
<el-button @click="cancelLabel">取消</el-button> |
|
|
|
<el-button type="primary" @click="confirmLabel">保存</el-button> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
<!-- 设置分组对话框 --> |
|
|
|
<el-dialog v-model="groupDialogVisible" title="设置分组" width="520px"> |
|
|
|
<div> |
|
|
|
<el-select v-model="selectedGroupIds" multiple filterable placeholder="请选择群组" style="width: 100%"> |
|
|
|
<el-option v-for="opt in groupOptions" :key="opt.id" :label="opt.groupName" :value="opt.id" /> |
|
|
|
</el-select> |
|
|
|
</div> |
|
|
|
<template #footer> |
|
|
|
<div class="dialog-footer"> |
|
|
|
<el-button @click="cancelGroup">取消</el-button> |
|
|
|
<el-button type="primary" @click="confirmGroup">保存</el-button> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
</el-dialog> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup name="User" lang="ts"> |
|
|
|
import { listUser, getUser, ban, unban, getLabelList, getGroupList } from '@/api/im/user'; |
|
|
|
import { listUser, getUser, ban, unban, getLabelList, getGroupList, updateBatchUser } from '@/api/im/user'; |
|
|
|
import { UserVO, UserQuery, UserForm } from '@/api/im/user/types'; |
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance; |
|
|
|
@ -205,8 +253,8 @@ const data = reactive<PageData<UserForm, UserQuery>>({ |
|
|
|
pageSize: 10, |
|
|
|
userName: undefined, |
|
|
|
nickName: undefined, |
|
|
|
labelIds: [], |
|
|
|
groupIds: [], |
|
|
|
labelIds: [] as any, |
|
|
|
groupIds: [] as any, |
|
|
|
params: {} |
|
|
|
}, |
|
|
|
rules: {} |
|
|
|
@ -216,6 +264,90 @@ 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')); |
|
|
|
|
|
|
|
// 标签/群组对话状态 |
|
|
|
const labelDialogVisible = ref(false); |
|
|
|
const groupDialogVisible = ref(false); |
|
|
|
const editingUser = ref<UserVO | null>(null); |
|
|
|
const selectedLabelIds = ref<Array<string | number>>([]); |
|
|
|
const selectedGroupIds = ref<Array<string | number>>([]); |
|
|
|
|
|
|
|
/** 打开标签设置窗口 */ |
|
|
|
const handleLabel = (row?: UserVO) => { |
|
|
|
editingUser.value = row || userList.value.find((u) => u.id === ids.value[0]) || null; |
|
|
|
if (!editingUser.value) { |
|
|
|
proxy?.$modal?.msgError('请先选择一个用户'); |
|
|
|
return; |
|
|
|
} |
|
|
|
selectedLabelIds.value = (editingUser.value.labelIds || '') |
|
|
|
.toString() |
|
|
|
.split(',') |
|
|
|
.filter((id) => id) |
|
|
|
.map((v) => (Number(v).toString() === v ? Number(v) : v)); |
|
|
|
labelDialogVisible.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
const cancelLabel = () => { |
|
|
|
labelDialogVisible.value = false; |
|
|
|
editingUser.value = null; |
|
|
|
selectedLabelIds.value = []; |
|
|
|
}; |
|
|
|
|
|
|
|
const confirmLabel = async () => { |
|
|
|
if (!editingUser.value) return; |
|
|
|
const payload: any = { id: editingUser.value.id, labelIds: selectedLabelIds.value.join(',') }; |
|
|
|
// optimistic update in UI |
|
|
|
editingUser.value.labelIds = payload.labelIds; |
|
|
|
try { |
|
|
|
const mod: any = await await updateBatchUser(payload); |
|
|
|
if (mod && typeof mod.updateUser === 'function') { |
|
|
|
await mod.updateUser(payload); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// ignore if API not present |
|
|
|
} |
|
|
|
labelDialogVisible.value = false; |
|
|
|
proxy?.$modal?.msgSuccess('设置标签成功'); |
|
|
|
await getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 打开群组设置窗口 */ |
|
|
|
const handleGroup = (row?: UserVO) => { |
|
|
|
editingUser.value = row || userList.value.find((u) => u.id === ids.value[0]) || null; |
|
|
|
if (!editingUser.value) { |
|
|
|
proxy?.$modal?.msgError('请先选择一个用户'); |
|
|
|
return; |
|
|
|
} |
|
|
|
selectedGroupIds.value = (editingUser.value.groupIds || '') |
|
|
|
.toString() |
|
|
|
.split(',') |
|
|
|
.filter((id) => id) |
|
|
|
.map((v) => (Number(v).toString() === v ? Number(v) : v)); |
|
|
|
groupDialogVisible.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
const cancelGroup = () => { |
|
|
|
groupDialogVisible.value = false; |
|
|
|
editingUser.value = null; |
|
|
|
selectedGroupIds.value = []; |
|
|
|
}; |
|
|
|
|
|
|
|
const confirmGroup = async () => { |
|
|
|
if (!editingUser.value) return; |
|
|
|
const payload: any = { id: editingUser.value.id, groupIds: selectedGroupIds.value.join(',') }; |
|
|
|
editingUser.value.groupIds = payload.groupIds; |
|
|
|
try { |
|
|
|
const mod: any = await updateBatchUser(payload); |
|
|
|
if (mod && typeof mod.updateUser === 'function') { |
|
|
|
await mod.updateUser(payload); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// ignore |
|
|
|
} |
|
|
|
groupDialogVisible.value = false; |
|
|
|
proxy?.$modal?.msgSuccess('设置分组成功'); |
|
|
|
await getList(); |
|
|
|
}; |
|
|
|
|
|
|
|
/** 查询用户列表 */ |
|
|
|
const getList = async () => { |
|
|
|
loading.value = true; |
|
|
|
@ -247,8 +379,8 @@ const handleQuery = () => { |
|
|
|
const resetQuery = () => { |
|
|
|
queryFormRef.value?.resetFields(); |
|
|
|
// 清空标签和群组选择 |
|
|
|
queryParams.value.labelIds = []; |
|
|
|
queryParams.value.groupIds = []; |
|
|
|
queryParams.value.labelIds = [] as any; |
|
|
|
queryParams.value.groupIds = [] as any; |
|
|
|
handleQuery(); |
|
|
|
console.log('handleQuery'); |
|
|
|
}; |
|
|
|
|