feat: chat user operation

This commit is contained in:
teukkk 2025-06-18 17:43:41 +08:00
parent 4975c4b2bf
commit 0af7da36a1
14 changed files with 347 additions and 112 deletions

View File

@ -1,6 +1,6 @@
import { Result } from '@/request/Result'
import { get, put, post, del } from '@/request/index'
import type { pageRequest } from '@/api/type/common'
import type { pageRequest, PageList } from '@/api/type/common'
import type { ChatUserItem } from '@/api/type/systemChatUser'
import type { Ref } from 'vue'
const prefix = '/system/chat_user'
@ -22,7 +22,7 @@ const getUserManage: (
page: pageRequest,
username_or_nickname: string,
loading?: Ref<boolean>,
) => Promise<Result<any>> = (page, username_or_nickname, loading) => {
) => Promise<Result<PageList<ChatUserItem[]>>> = (page, username_or_nickname, loading) => {
return get(
`${prefix}/user_manage/${page.current_page}/${page.page_size}`,
username_or_nickname ? { username_or_nickname } : undefined,
@ -73,12 +73,43 @@ const putUserManagePassword: (
return put(`${prefix}/${user_id}/re_password`, data, undefined, loading)
}
/**
*
*/
const batchAddGroup: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading,
) => {
return post(`${prefix}/batch_add_group`, data, undefined, loading)
}
/**
*
*/
const batchDelete: (data: string[], loading?: Ref<boolean>) => Promise<Result<any>> = (
data,
loading,
) => {
return post(`${prefix}/batch_delete`, data, undefined, loading)
}
/**
*
*/
const batchSync: (sync_type: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
sync_type,
loading,
) => {
return post(`${prefix}/sync/${sync_type}`, undefined, undefined, loading)
}
export default {
getUserManage,
putUserManage,
delUserManage,
postUserManage,
putUserManagePassword,
getChatUserList
getChatUserList,
batchAddGroup,
batchDelete,
batchSync
}

View File

@ -12,15 +12,12 @@ interface ChatUserItem {
user_group_names?: string[],
}
// TODO
interface ChatUserGroupUserItem {
id: string,
is_auth: boolean,
email: string,
phone: string,
nick_name: string,
username: string,
password: string,
source: string,
is_active: boolean,
create_time: string,

View File

@ -1,4 +1,5 @@
export default {
syncSuccess: 'Successful',
create: 'Create',
createSuccess: 'Successful',
copy: 'Copy',

View File

@ -1,19 +1,24 @@
export default {
title: 'Chat users',
syncUsers: 'Synchronize users',
setUserGroups: 'Configure user groups',
knowledgeTitleTip: 'This configuration will only take effect when the associated application enables chat user login authentication',
title: 'Chat Users',
syncUsers: 'Sync Users',
syncUsersTip: 'Only sync newly added users',
setUserGroups: 'Configure User Groups',
knowledgeTitleTip: 'This configuration will only take effect after enabling chat user login authentication in the associated application',
applicationTitleTip: 'This configuration requires login authentication to be enabled in the application',
autoAuthorization: 'Auto authorization',
autoAuthorization: 'Auto Authorization',
authorization: 'Authorization',
batchDeleteUser: 'Delete selected {count} users?',
settingMethod: 'Configuration Method',
append: 'Append',
replace: 'Replace',
group: {
title: 'User groups',
name: 'User group name',
title: 'User Groups',
name: 'User Group Name',
usernameOrName: 'Username/Name',
delete: {
confirmTitle: 'Confirm to delete user group:',
confirmMessage: 'After deletion, all members in this user group will be removed. Please proceed with caution!',
confirmMessage: 'All members in this group will be removed after deletion. Proceed with caution!',
},
batchDeleteMember: 'Remove selected {count} members?',
}
};
}

View File

@ -1,4 +1,5 @@
export default {
syncSuccess: '同步成功',
create: '创建',
createSuccess: '创建成功',
copy: '复制',

View File

@ -1,11 +1,16 @@
export default {
title: '对话用户',
syncUsers: '同步用户',
syncUsersTip: '仅同步新增用户',
setUserGroups: '设置用户组',
knowledgeTitleTip: '该配置需要关联的应用开启对话用户登录认证后才会生效',
applicationTitleTip: '该配置需要应用开启登录认证后生效',
autoAuthorization: '自动授权',
authorization: '授权',
batchDeleteUser: '是否删除选中的 {count} 个用户?',
settingMethod: '设置方式',
append: '追加',
replace: '替换',
group: {
title: '用户组',
name: '用户组名称',

View File

@ -1,4 +1,5 @@
export default {
syncSuccess: '同步完成',
create: '創建',
createSuccess: '創建成功',
copy: '複製',

View File

@ -1,11 +1,16 @@
export default {
title: '對話用戶',
syncUsers: '同步用戶',
syncUsersTip: '僅同步新增用戶',
setUserGroups: '設定用戶組',
knowledgeTitleTip: '該配置需要關聯的應用開啟對話用戶登入認證後才會生效',
applicationTitleTip: '該配置需要應用開啟登入認證後生效',
autoAuthorization: '自動授權',
authorization: '授權',
batchDeleteUser: '是否刪除選中的 {count} 個用戶?',
settingMethod: '設定方式',
append: '追加',
replace: '替換',
group: {
title: '用戶組',
name: '用戶組名稱',
@ -16,4 +21,4 @@ export default {
},
batchDeleteMember: '是否移除選中的 {count} 個成員?',
}
};
}

View File

@ -263,7 +263,7 @@ watch(() => current.value?.id, () => {
const createGroupUserDialogRef = ref<InstanceType<typeof CreateGroupUserDialog>>()
function createUser() {
createGroupUserDialogRef.value?.open(current.value?.id);
createGroupUserDialogRef.value?.open(current.value?.id as string);
}
const multipleSelection = ref<any[]>([])

View File

@ -0,0 +1,86 @@
<template>
<el-dialog width="600" :title="$t('views.chatUser.setUserGroups')" v-model="dialogVisible"
:close-on-click-modal="false" :close-on-press-escape="false" :destroy-on-close="true">
<el-form label-position="top" ref="formRef" :rules="rules" :model="form" require-asterisk-position="right">
<el-form-item :label="$t('views.chatUser.settingMethod')" prop="user_group_ids">
<el-radio-group v-model="form.is_append">
<el-radio :value="true">{{ $t('views.chatUser.append') }}</el-radio>
<el-radio :value="false">{{ $t('views.chatUser.replace') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item :label="$t('views.chatUser.group.title')" prop="user_group_ids">
<el-select v-model="form.user_group_ids" multiple filterable :placeholder="$t('common.selectPlaceholder')"
:loading="props.optionLoading">
<el-option v-for="item in props.chatGroupList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(formRef)" :loading="loading">
{{ $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import userManageApi from '@/api/system/chat-user'
import type { ListItem } from '@/api/type/common'
const props = defineProps<{
optionLoading: boolean,
chatGroupList: ListItem[],
}>()
const emit = defineEmits<{
(e: 'refresh'): void;
}>();
const dialogVisible = ref<boolean>(false)
const defaultForm = {
user_group_ids: [],
is_append: true,
ids: []
}
const form = ref<{
ids: string[], user_group_ids: string[], is_append: boolean
}>({
...defaultForm,
})
function open(ids: string[]) {
form.value = { ...defaultForm, ids }
dialogVisible.value = true
}
const formRef = ref<FormInstance>();
const rules = reactive({
user_group_ids: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],
is_append: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],
})
const loading = ref<boolean>(false)
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
userManageApi.batchAddGroup(form.value, loading).then(() => {
MsgSuccess(t('common.settingSuccess'))
emit('refresh')
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>

View File

@ -0,0 +1,74 @@
<template>
<el-dialog v-model="dialogVisible" :close-on-click-modal="false" :close-on-press-escape="false"
:destroy-on-close="true" width="600">
<template #header>
<h4 class="mb-8 medium">{{ t('views.chatUser.syncUsers') }}</h4>
<div class="color-secondary lighter">{{ t('views.chatUser.syncUsersTip') }}</div>
</template>
<el-form label-position="top" ref="formRef" :rules="rules" :model="form" require-asterisk-position="right">
<el-form-item :label="$t('views.userManage.source.label')" prop="sync_type">
<el-select v-model="form.sync_type" :placeholder="$t('common.selectPlaceholder')">
<el-option :label="t('views.userManage.source.local')" value="LOCAL">
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" @click="submit(formRef)" :loading="loading">
{{ $t('common.save') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance } from 'element-plus'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import userManageApi from '@/api/system/chat-user'
const emit = defineEmits<{
(e: 'refresh'): void;
}>();
const dialogVisible = ref<boolean>(false)
const defaultForm = {
sync_type: 'LOCAL',
}
const form = ref<{
sync_type: string
}>({
...defaultForm,
})
function open() {
form.value = { ...defaultForm }
dialogVisible.value = true
}
const formRef = ref<FormInstance>();
const rules = reactive({
sync_type: [{ required: true, message: t('common.selectPlaceholder'), trigger: 'blur' }],
})
const loading = ref<boolean>(false)
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid) => {
if (valid) {
userManageApi.batchSync(form.value.sync_type, loading).then(() => {
MsgSuccess(t('common.syncSuccess'))
emit('refresh')
dialogVisible.value = false
})
}
})
}
defineExpose({ open })
</script>

View File

@ -1,68 +1,43 @@
<template>
<el-drawer v-model="visible" size="60%">
<template #header>
<h4>{{ title }}</h4>
<h4>{{ props.title }}</h4>
</template>
<h4 class="title-decoration-1 mb-16 mt-8">{{ $t('common.info') }}</h4>
<el-form
ref="userFormRef"
:model="userForm"
:rules="rules"
label-position="top"
require-asterisk-position="right"
@submit.prevent
:close-on-click-modal="false"
:close-on-press-escape="false"
>
<el-form-item
:prop="isEdit ? '' : 'username'"
:label="$t('views.login.loginForm.username.label')"
>
<el-input
v-model="userForm.username"
:placeholder="$t('views.login.loginForm.username.placeholder')"
maxlength="20"
show-word-limit
:disabled="isEdit"
>
<el-form ref="userFormRef" :model="userForm" :rules="rules" label-position="top" require-asterisk-position="right"
@submit.prevent :close-on-click-modal="false" :close-on-press-escape="false">
<el-form-item :prop="isEdit ? '' : 'username'" :label="$t('views.login.loginForm.username.label')">
<el-input v-model="userForm.username" :placeholder="$t('views.login.loginForm.username.placeholder')"
maxlength="20" show-word-limit :disabled="isEdit">
</el-input>
</el-form-item>
<el-form-item :label="$t('views.userManage.userForm.nick_name.label')">
<el-input
v-model="userForm.nick_name"
:placeholder="$t('views.userManage.userForm.nick_name.placeholder')"
maxlength="64"
show-word-limit
>
<el-form-item prop="nick_name" :label="$t('views.userManage.userForm.nick_name.label')">
<el-input v-model="userForm.nick_name" :placeholder="$t('views.userManage.userForm.nick_name.placeholder')"
maxlength="64" show-word-limit>
</el-input>
</el-form-item>
<el-form-item :label="$t('views.login.loginForm.email.label')" prop="email">
<el-input
type="email"
v-model="userForm.email"
:placeholder="$t('views.login.loginForm.email.placeholder')"
>
<el-input type="email" v-model="userForm.email" :placeholder="$t('views.login.loginForm.email.placeholder')">
</el-input>
</el-form-item>
<el-form-item :label="$t('views.userManage.userForm.phone.label')">
<el-input
v-model="userForm.phone"
:placeholder="$t('views.userManage.userForm.phone.placeholder')"
>
<el-input v-model="userForm.phone" :placeholder="$t('views.userManage.userForm.phone.placeholder')">
</el-input>
</el-form-item>
<el-form-item
:label="$t('views.userManage.form.password.label')"
prop="password"
v-if="!isEdit"
>
<el-input
type="password"
v-model="userForm.password"
:placeholder="$t('views.userManage.form.password.placeholder')"
show-password
>
</el-input>
<el-form-item label="默认密码" v-if="!isEdit">
<span class="mr-8">{{ userForm.password }}</span>
<el-button type="primary" link @click="copyClick(userForm.password)">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</el-form-item>
<h4 class="title-decoration-1 mb-16 mt-8">{{ $t('views.chatUser.group.title') }}</h4>
<el-form-item :label="$t('views.chatUser.group.title')" prop="user_group_ids">
<el-select v-model="userForm.user_group_ids" multiple filterable
:placeholder="`${$t('common.selectPlaceholder')}${$t('views.chatUser.group.title')}`"
:loading="props.optionLoading">
<el-option v-for="item in props.chatGroupList" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
<template #footer>
@ -74,15 +49,20 @@
</el-drawer>
</template>
<script setup lang="ts">
import {ref, reactive, watch} from 'vue'
import type {FormInstance} from 'element-plus'
import userManageApi from '@/api/system/chat-user'
import {MsgSuccess} from '@/utils/message'
import {t} from '@/locales'
import { ref, reactive, watch } from 'vue'
import type { FormInstance } from 'element-plus'
import chatUserApi from '@/api/system/chat-user'
import userManageApi from '@/api/user/user-manage'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
import type { ListItem } from '@/api/type/common'
import { copyClick } from '@/utils/clipboard'
const props = defineProps({
title: String,
})
const props = defineProps<{
title: string,
optionLoading: boolean,
chatGroupList: ListItem[],
}>()
const emit = defineEmits(['refresh'])
@ -93,6 +73,7 @@ const userForm = ref<any>({
password: '',
phone: '',
nick_name: '',
user_group_ids: []
})
const rules = reactive({
@ -109,23 +90,10 @@ const rules = reactive({
trigger: 'blur',
},
],
email: [
nick_name: [
{
required: true,
message: t('views.login.loginForm.email.requiredMessage'),
trigger: 'blur',
},
],
password: [
{
required: true,
message: t('views.login.loginForm.password.requiredMessage'),
trigger: 'blur',
},
{
min: 6,
max: 20,
message: t('views.login.loginForm.password.lengthMessage'),
message: t('views.userManage.userForm.nick_name.placeholder'),
trigger: 'blur',
},
],
@ -142,6 +110,7 @@ watch(visible, (bool) => {
password: '',
phone: '',
nick_name: '',
user_group_ids: []
}
isEdit.value = false
userFormRef.value?.clearValidate()
@ -153,11 +122,16 @@ const open = (data: any) => {
userForm.value['id'] = data.id
userForm.value.username = data.username
userForm.value.email = data.email
userForm.value.password = data.password
userForm.value.phone = data.phone
userForm.value.nick_name = data.nick_name
userForm.value.user_group_ids = data.user_group_ids
isEdit.value = true
} else {
userManageApi.getSystemDefaultPassword().then((res: any) => {
userForm.value.password = res.data.password
})
}
visible.value = true
}
@ -166,13 +140,13 @@ const submit = async (formEl: FormInstance | undefined) => {
await formEl.validate((valid, fields) => {
if (valid) {
if (isEdit.value) {
userManageApi.putUserManage(userForm.value.id, userForm.value, loading).then((res) => {
chatUserApi.putUserManage(userForm.value.id, userForm.value, loading).then((res) => {
emit('refresh')
MsgSuccess(t('common.editSuccess'))
visible.value = false
})
} else {
userManageApi.postUserManage(userForm.value, loading).then((res) => {
chatUserApi.postUserManage(userForm.value, loading).then((res) => {
emit('refresh')
MsgSuccess(t('common.createSuccess'))
visible.value = false
@ -182,6 +156,10 @@ const submit = async (formEl: FormInstance | undefined) => {
})
}
defineExpose({open})
defineExpose({ open })
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
:deep(.el-form-item__content) {
font-weight: 400
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<el-dialog :title="$t('views.userManage.setting.updatePwd')" v-model="dialogVisible">
<el-dialog :title="$t('views.login.resetPassword')" v-model="dialogVisible">
<el-form
ref="userFormRef"
:model="userForm"
@ -44,7 +44,7 @@ import { ref, reactive, watch } from 'vue'
import useStore from '@/stores'
import type { FormInstance, FormRules } from 'element-plus'
import type { ResetPasswordRequest } from '@/api/type/user'
import userManageApi from '@/api/user/user-manage'
import userManageApi from '@/api/system/chat-user'
import { MsgSuccess } from '@/utils/message'
import { t } from '@/locales'
const emit = defineEmits(['refresh'])

View File

@ -15,13 +15,13 @@
<el-button type="primary" @click="createUser()">
{{ t('views.userManage.createUser') }}
</el-button>
<el-button :disabled="multipleSelection.length === 0">
<el-button @click="syncUsers">
{{ $t('views.chatUser.syncUsers') }}
</el-button>
<el-button :disabled="multipleSelection.length === 0">
<el-button :disabled="multipleSelection.length === 0" @click="setUserGroups">
{{ $t('views.chatUser.setUserGroups') }}
</el-button>
<el-button :disabled="multipleSelection.length === 0">
<el-button :disabled="multipleSelection.length === 0" @click="handleBatchDelete">
{{ $t('common.delete') }}
</el-button>
</div>
@ -135,19 +135,28 @@
</el-card>
</ContentContainer>
<UserDrawer :title="title" ref="UserDrawerRef" @refresh="refresh" />
<UserDrawer :title="title" :optionLoading="optionLoading" :chatGroupList="chatGroupList" ref="UserDrawerRef"
@refresh="refresh" />
<UserPwdDialog ref="UserPwdDialogRef" @refresh="refresh" />
<SetUserGroupsDialog :optionLoading="optionLoading" :chatGroupList="chatGroupList" ref="setUserGroupsRef"
@refresh="refresh" />
<SyncUsersDialog ref="syncUsersDialogRef" @refresh="refresh" />
</template>
<script lang="ts" setup>
import { onMounted, ref, reactive, watch } from 'vue'
import { onMounted, ref, reactive } from 'vue'
import UserDrawer from './component/UserDrawer.vue'
import UserPwdDialog from './component/UserPwdDialog.vue'
import SetUserGroupsDialog from './component/SetUserGroupsDialog.vue'
import SyncUsersDialog from './component/SyncUsersDialog.vue'
import userManageApi from '@/api/system/chat-user'
import { datetimeFormat } from '@/utils/time'
import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { t } from '@/locales'
import iconMap from '@/components/app-icon/icons/common'
import type { ChatUserItem } from '@/api/type/systemChatUser'
import SystemGroupApi from '@/api/system/user-group'
import type { ListItem } from '@/api/type/common'
const rightOutlined = iconMap['right-outlined'].iconReader()
@ -163,8 +172,8 @@ const search_type_change = () => {
const loading = ref(false)
const multipleSelection = ref<string[]>([])
function handleSelectionChange(val: string[]) {
const multipleSelection = ref<any[]>([])
function handleSelectionChange(val: any[]) {
multipleSelection.value = val
}
@ -174,7 +183,7 @@ const paginationConfig = reactive({
total: 0,
})
const userTableData = ref<any[]>([])
const userTableData = ref<ChatUserItem[]>([])
function getList() {
return userManageApi
@ -196,14 +205,15 @@ function handleSizeChange() {
getList()
}
function changeState(row: any) {
function changeState(row: ChatUserItem) {
const obj = {
...row,
is_active: !row.is_active,
}
const str = obj.is_active ? t('common.status.enableSuccess') : t('common.status.disableSuccess')
userManageApi
.putUserManage(row.id, obj, loading)
.then((res) => {
.then(() => {
getList()
MsgSuccess(str)
return true
@ -215,7 +225,7 @@ function changeState(row: any) {
const title = ref('')
const UserDrawerRef = ref()
function editUser(row: any) {
function editUser(row: ChatUserItem) {
title.value = t('views.userManage.editUser')
UserDrawerRef.value.open(row)
}
@ -225,10 +235,10 @@ function createUser() {
UserDrawerRef.value.open()
}
function deleteUserManage(row: any) {
function deleteUserManage(row: ChatUserItem) {
MsgConfirm(
`${t('views.user.delete.confirmTitle')}${row.username} ?`,
t('views.user.delete.confirmMessage'),
`${t('views.userManage.delete.confirmTitle')}${row.username} ?`,
'',
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger',
@ -246,7 +256,7 @@ function deleteUserManage(row: any) {
}
const UserPwdDialogRef = ref()
function editPwdUser(row: any) {
function editPwdUser(row: ChatUserItem) {
UserPwdDialogRef.value.open(row)
}
@ -255,8 +265,49 @@ function refresh() {
}
onMounted(() => {
getChatGroupList()
getList()
})
const optionLoading = ref(false)
const chatGroupList = ref<ListItem[]>([])
async function getChatGroupList() {
try {
const res = await SystemGroupApi.getUserGroup(optionLoading)
chatGroupList.value = res.data
} catch (e) {
console.error(e)
}
}
function handleBatchDelete() {
MsgConfirm(
t('views.chatUser.batchDeleteUser', { count: multipleSelection.value.length }),
'',
{
confirmButtonText: t('common.confirm'),
confirmButtonClass: 'danger',
},
)
.then(() => {
userManageApi.batchDelete(multipleSelection.value.map(item => (item.id)), loading).then(async () => {
MsgSuccess(t('common.deleteSuccess'))
await getList()
})
})
.catch(() => {
})
}
const setUserGroupsRef = ref<InstanceType<typeof SetUserGroupsDialog>>()
function setUserGroups() {
setUserGroupsRef.value?.open(multipleSelection.value.map(item => (item.id)))
}
const syncUsersDialogRef = ref<InstanceType<typeof SyncUsersDialog>>()
function syncUsers() {
syncUsersDialogRef.value?.open()
}
</script>
<style lang="scss" scoped>