Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 57 additions & 59 deletions apps/system_manage/api/chat_user.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# coding=utf-8
"""
@project: MaxKB
@Author:虎虎
@file: user.py
@date:2025/4/14 19:23
@desc:
@project: MaxKB
@Author:虎虎
@file: user.py
@date:2025/4/14 19:23
@desc:
"""

from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter

Expand All @@ -25,14 +26,11 @@ def get_data(self):

class CreateChatUserRequestSerializer(CreateUserSerializer):
user_group_ids = serializers.ListField(
child=serializers.CharField(required=True),
required=True,
label=_('User Group IDs')
child=serializers.CharField(required=True), required=True, label=_("User Group IDs")
)


class ChatUserAPI(APIMixin):

@staticmethod
def get_response():
return ChatUserResponse
Expand All @@ -43,31 +41,31 @@ def get_request():

@staticmethod
def get_parameters():
return [OpenApiParameter(
name="user_id",
description=_('User ID'),
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH,
required=True,
)]
return [
OpenApiParameter(
name="user_id",
description=_("User ID"),
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH,
required=True,
)
]


class BatchAddGroupApi(APIMixin):

@staticmethod
def get_request():
return ChatUserSerializer.BatchAddGroup


class UserPasswordResponse(APIMixin):

@staticmethod
def get_response():
return PasswordResponse


class Password(serializers.Serializer):
password = serializers.CharField(required=True, label=_('Password'))
password = serializers.CharField(required=True, label=_("Password"))


class PasswordResponse(ResultSerializer):
Expand All @@ -77,44 +75,40 @@ def get_data(self):

class EditChatUserRequestSerializer(ChatUserSerializer.UserEditInstance):
user_group_ids = serializers.ListField(
child=serializers.CharField(required=True),
required=True,
label=_('User Group IDs')
child=serializers.CharField(required=True), required=True, label=_("User Group IDs")
)


class EditUserApi(APIMixin):
@staticmethod
def get_parameters():
return [OpenApiParameter(
name="user_id",
description=_('User ID'),
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH,
required=True,
)]
return [
OpenApiParameter(
name="user_id",
description=_("User ID"),
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH,
required=True,
)
]

@staticmethod
def get_request():
return EditChatUserRequestSerializer


class ChatUserListResponse(serializers.Serializer):
id = serializers.CharField(required=True, label=_('ID'))
username = serializers.CharField(required=True, label=_('Username'))
nick_name = serializers.CharField(required=True, label=_('Nickname'))
email = serializers.EmailField(required=False, allow_blank=True, label=_('Email'))
phone = serializers.CharField(required=False, allow_blank=True, label=_('Phone'))
is_active = serializers.BooleanField(required=False, default=True, label=_('Is Active'))
id = serializers.CharField(required=True, label=_("ID"))
username = serializers.CharField(required=True, label=_("Username"))
nick_name = serializers.CharField(required=True, label=_("Nickname"))
email = serializers.EmailField(required=False, allow_blank=True, label=_("Email"))
phone = serializers.CharField(required=False, allow_blank=True, label=_("Phone"))
is_active = serializers.BooleanField(required=False, default=True, label=_("Is Active"))
user_group_ids = serializers.ListField(
child=serializers.CharField(required=True),
required=True,
label=_('User Group IDs')
child=serializers.CharField(required=True), required=True, label=_("User Group IDs")
)
user_group_names = serializers.ListField(
child=serializers.CharField(required=True),
required=True,
label=_('User Group Names')
child=serializers.CharField(required=True), required=True, label=_("User Group Names")
)


Expand All @@ -126,54 +120,58 @@ def get_data(self):
class ChatUserPageApi(APIMixin):
@staticmethod
def get_parameters():
return [OpenApiParameter(
name="username",
description=_('Username'),
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
return [
OpenApiParameter(
name="username",
description=_("Username"),
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter(
name="nick_name",
description=_('Nickname'),
description=_("Nickname"),
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter(
name="email",
description=_("Email"),
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter(
name="source",
description=_('Source'),
description=_("Source"),
type=OpenApiTypes.STR,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter(
name="is_active",
description=_('Is Active'),
description=_("Is Active"),
type=OpenApiTypes.BOOL,
location=OpenApiParameter.QUERY,
required=False,
),
OpenApiParameter(
name='current_page',
name="current_page",
type=OpenApiTypes.INT,
description=_('Current page'),
description=_("Current page"),
required=True,
location=OpenApiParameter.PATH,
),
OpenApiParameter(
name='page_size',
name="page_size",
type=OpenApiTypes.INT,
description=_('Page size'),
description=_("Page size"),
required=True,
location=OpenApiParameter.PATH,
),

]

@staticmethod
def get_response():
return ChatUsersListResponse



4 changes: 4 additions & 0 deletions apps/system_manage/serializers/chat_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def _check_unique_username_and_email(self):
class Query(serializers.Serializer):
username = serializers.CharField(required=False, label=_("Username"), allow_null=True, allow_blank=True)
nick_name = serializers.CharField(required=False, label=_("Nickname"), allow_null=True, allow_blank=True)
email = serializers.CharField(required=False, label=_("Email"), allow_null=True, allow_blank=True)
source = serializers.CharField(required=False, label=_("Source"), allow_null=True, allow_blank=True)
is_active = serializers.BooleanField(required=False, label=_("Is active"), allow_null=True)

Expand All @@ -144,6 +145,9 @@ def get_query_set(self):
nick_name = self.data.get("nick_name")
if nick_name is not None:
query_set = query_set.filter(Q(nick_name__contains=nick_name))
email = self.data.get("email")
if email is not None:
query_set = query_set.filter(Q(email__contains=email))
source = self.data.get("source")
if source is not None:
query_set = query_set.filter(source=source)
Expand Down
1 change: 1 addition & 0 deletions apps/system_manage/views/system_chat_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ def get(self, request: Request, current_page, page_size):
data={
"username": request.query_params.get("username", None),
"nick_name": request.query_params.get("nick_name", None),
"email": request.query_params.get("email", None),
"source": request.query_params.get("source", None),
"is_active": request.query_params.get("is_active", None),
"user_id": str(request.user.id),
Expand Down
25 changes: 21 additions & 4 deletions ui/src/views/system/chat-management/user-groups/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, ref, useTemplateRef } from 'vue'
import { computed, onMounted, ref, useTemplateRef } from 'vue'
import ChatGroupsApi from '@/api/admin/system/chat-management/chat-user-groups.ts'
import { LOGIN_METHOD } from '@/api/enums'
import type { ListItem, ChatUserGroupMember, LoginMethod, OptionItem, Dict } from '@/api/types'
Expand Down Expand Up @@ -68,11 +68,21 @@ const membersLoading = ref(false)
const paginationConfig = ref({ currentPage: 1, pageSize: 20, total: 0 })
const memberQuery = ref<Dict<unknown>>()
const memberSearchFields: OptionItem<string>[] = [
{ label: '用户名', value: 'username' },
{ label: '姓名', value: 'nick_name' },
{ label: '用户来源', value: 'source' },
{ label: '用户名', value: 'username' },
]

/* 表头筛选 */
const selectedMemberSource = ref<string | null>(null)
const memberSourceOptions: OptionItem<string>[] = Object.entries(LOGIN_METHOD_LABELS).map(([value, label]) => ({
label: value === LOGIN_METHOD.LOCAL ? '系统用户' : label,
value,
}))
const memberListQuery = computed<Dict<unknown>>(() => ({
...memberQuery.value,
...(selectedMemberSource.value !== null ? { source: selectedMemberSource.value } : {}),
}))

const groupMembers = ref<ChatUserGroupMember[]>([])

function loadGroupMembers(resetPage = false) {
Expand All @@ -81,7 +91,7 @@ function loadGroupMembers(resetPage = false) {
if (resetPage) paginationConfig.value.currentPage = 1

membersLoading.value = true
return ChatGroupsApi.getChatUserGroupMembers(groupId, paginationConfig.value, memberQuery.value)
return ChatGroupsApi.getChatUserGroupMembers(groupId, paginationConfig.value, memberListQuery.value)
.then((page) => {
groupMembers.value = page.records
paginationConfig.value.total = page.total
Expand All @@ -96,6 +106,10 @@ function handleMemberSearch(query?: Dict<unknown>) {
loadGroupMembers(true)
}

function handleMemberFilterChange() {
loadGroupMembers(true)
}

/* 添加成员drawer */
const memberDialogRef = useTemplateRef<InstanceType<typeof CreateGroupMemberDialog>>('memberDialogRef')

Expand Down Expand Up @@ -196,6 +210,9 @@ onMounted(() => loadChatUserGroups())
<el-table-column prop="nick_name" label="姓名" show-overflow-tooltip />
<el-table-column prop="username" label="用户名" show-overflow-tooltip />
<el-table-column prop="source" label="用户来源">
<template #header>
<MkTableFilter mode="single" v-model="selectedMemberSource" label="用户来源" :options="memberSourceOptions" @change="handleMemberFilterChange" />
</template>
<template #default="{ row }">
{{ row.source === LOGIN_METHOD.LOCAL ? '系统用户' : LOGIN_METHOD_LABELS[row.source as LoginMethod] }}
</template>
Expand Down
54 changes: 35 additions & 19 deletions ui/src/views/system/chat-management/users/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, ref, useTemplateRef } from 'vue'
import { computed, onMounted, ref, useTemplateRef } from 'vue'
import ChatUserApi from '@/api/admin/system/chat-management/chat-user.ts'
import type { ChatUser, Dict, LoginMethod, OptionItem } from '@/api/types'
import { LOGIN_METHOD, QUOTA_TYPE } from '@/api/enums'
Expand Down Expand Up @@ -27,23 +27,28 @@ const chatUsersLoading = ref(false)
const paginationConfig = ref({ currentPage: 1, pageSize: 20, total: 0 })
const chatUsersData = ref<ChatUser[]>([])
const searchFields: OptionItem<string>[] = [
{ label: '用户名', value: 'username' },
{ label: '姓名', value: 'nick_name' },
{
label: '状态',
value: 'is_active',
options: [
{ label: '启用', value: true },
{ label: '禁用', value: false },
],
},
{
label: '用户来源',
value: 'source',
options: Object.entries(LOGIN_METHOD_LABELS).map(([value, label]) => ({ label: value === LOGIN_METHOD.LOCAL ? '本地创建' : label, value })),
},
{ label: '用户名', value: 'username' },
{ label: '邮箱', value: 'email' },
]
const chatUserQuery = ref<Dict<unknown>>()
const chatUserSearchQuery = ref<Dict<unknown>>()

/* 表头筛选 */
const selectedStatus = ref<boolean | null>(null)
const statusOptions: OptionItem<boolean>[] = [
{ label: '已启用', value: true },
{ label: '已禁用', value: false },
]
const selectedSource = ref<string | null>(null)
const sourceOptions: OptionItem<string>[] = Object.entries(LOGIN_METHOD_LABELS).map(([value, label]) => ({
label: value === LOGIN_METHOD.LOCAL ? '本地创建' : label,
value,
}))
const chatUserQuery = computed<Dict<unknown>>(() => ({
...chatUserSearchQuery.value,
...(selectedStatus.value !== null ? { is_active: selectedStatus.value } : {}),
...(selectedSource.value !== null ? { source: selectedSource.value } : {}),
}))

function formatQuotaUsage(tokenQuota?: ChatUser['token_quota']) {
if (!tokenQuota) return '-'
Expand All @@ -53,15 +58,20 @@ function formatQuotaUsage(tokenQuota?: ChatUser['token_quota']) {
}

function handleSearchChange(query?: Dict<unknown>) {
chatUserQuery.value = query
chatUserSearchQuery.value = query
paginationConfig.value.currentPage = 1
loadChatUsers()
}

function handleFilterChange() {
paginationConfig.value.currentPage = 1
loadChatUsers()
}

function loadChatUsers(resetQuery = false) {
chatUsersLoading.value = true
if (resetQuery) {
chatUserQuery.value = undefined
chatUserSearchQuery.value = undefined
paginationConfig.value.currentPage = 1
}
return ChatUserApi.getChatUserPage(paginationConfig.value, chatUserQuery.value)
Expand Down Expand Up @@ -159,7 +169,10 @@ onMounted(() => loadChatUsers())
<el-table-column type="selection" width="40" />
<el-table-column prop="nick_name" label="姓名" min-width="150" show-overflow-tooltip />
<el-table-column prop="username" label="用户名" min-width="150" show-overflow-tooltip />
<el-table-column width="100" label="启用状态">
<el-table-column width="100" label="状态">
<template #header>
<MkTableFilter mode="single" v-model="selectedStatus" label="状态" :options="statusOptions" @change="handleFilterChange" />
</template>
<template #default="{ row }">
<MkStatusLabel :active="row.is_active" />
</template>
Expand All @@ -183,6 +196,9 @@ onMounted(() => loadChatUsers())
</el-table-column>

<el-table-column label="用户来源" width="120">
<template #header>
<MkTableFilter mode="single" v-model="selectedSource" label="用户来源" :options="sourceOptions" @change="handleFilterChange" />
</template>
<template #default="{ row }">
{{ row.source === LOGIN_METHOD.LOCAL ? '本地创建' : LOGIN_METHOD_LABELS[row.source as LoginMethod] }}
</template>
Expand Down
Loading
Loading