UnisKB/ui/src/views/chat-user/index.vue

447 lines
14 KiB
Vue
Raw Normal View History

2025-06-16 12:14:36 +00:00
<template>
2025-07-01 13:22:21 +00:00
<div class="group p-16-24">
<div class="mb-16">
<h2>{{ $t('views.chatUser.title') }}</h2>
<div class="color-secondary">
{{
resource.resource_type === SourceTypeEnum.APPLICATION
? $t('views.chatUser.applicationTitleTip')
: $t('views.chatUser.knowledgeTitleTip')
}}
2025-06-16 12:14:36 +00:00
</div>
2025-07-01 13:22:21 +00:00
</div>
<el-card style="--el-card-padding: 0">
<div class="flex">
2025-07-09 08:12:02 +00:00
<div class="user-left border-r">
<div class="p-24 pb-0">
<h4 class="medium mb-12">{{ $t('views.chatUser.group.title') }}</h4>
<el-input
v-model="filterText"
:placeholder="$t('common.search')"
prefix-icon="Search"
clearable
/>
2025-06-16 12:14:36 +00:00
</div>
<div class="list-height-left">
<el-scrollbar v-loading="loading">
2025-07-09 08:12:02 +00:00
<div class="p-16">
<common-list
:data="filterList"
@click="clickUserGroup"
:default-active="current?.id"
>
<template #default="{ row }">
<span class="ellipsis-1" :title="row.name">{{ row.name }}</span>
</template>
<template #empty>
<span></span>
</template>
</common-list>
</div>
2025-06-16 12:14:36 +00:00
</el-scrollbar>
</div>
</div>
<!-- 右边 -->
<div class="user-right" v-loading="rightLoading">
<div class="flex-between">
<div class="flex align-center">
2025-07-09 08:12:02 +00:00
<h4 class="medium ellipsis" :title="current?.name">{{ current?.name || '-' }}</h4>
<el-divider direction="vertical" class="mr-8 ml-8" />
2025-07-01 13:22:21 +00:00
<el-icon class="color-input-placeholder">
<UserFilled />
</el-icon>
2025-06-16 12:14:36 +00:00
<span class="color-input-placeholder ml-4">
2025-06-17 08:44:17 +00:00
{{ paginationConfig.total }}
2025-06-16 12:14:36 +00:00
</span>
</div>
<div
class="flex align-center"
2025-07-01 12:03:13 +00:00
v-if="
2025-07-09 08:12:02 +00:00
route.path.includes('share/')
? false
: hasPermission(permissionObj[currentPermissionKey], 'OR')
2025-07-01 12:03:13 +00:00
"
2025-06-25 11:04:02 +00:00
>
<div class="color-secondary mr-8">{{ $t('views.chatUser.autoAuthorization') }}</div>
<el-switch
size="small"
:model-value="current?.is_auth"
@click="changeAuth"
:loading="loading"
></el-switch>
</div>
2025-06-16 12:14:36 +00:00
</div>
2025-07-01 12:03:13 +00:00
<div class="flex-between mb-16" style="margin-top: 18px">
2025-06-16 12:14:36 +00:00
<div class="flex complex-search">
<el-select class="complex-search__left" v-model="searchType" style="width: 120px">
<el-option :label="$t('views.login.loginForm.username.label')" value="username" />
<el-option
:label="$t('views.userManage.userForm.nick_name.label')"
value="nick_name"
/>
<el-option :label="$t('views.userManage.source.label')" value="source" />
2025-06-16 12:14:36 +00:00
</el-select>
2025-07-01 12:03:13 +00:00
<el-input
v-if="searchType === 'username'"
v-model="searchForm.username"
2025-07-01 12:03:13 +00:00
@change="getList"
:placeholder="$t('common.inputPlaceholder')"
style="width: 220px"
clearable
/>
<el-input
v-else-if="searchType === 'nick_name'"
v-model="searchForm.nick_name"
@change="getList"
:placeholder="$t('common.inputPlaceholder')"
style="width: 220px"
clearable
/>
<el-select
v-else-if="searchType === 'source'"
v-model="searchForm.source"
@change="getList"
:placeholder="$t('common.selectPlaceholder')"
style="width: 220px"
clearable
>
<el-option :label="$t('views.userManage.source.local')" value="LOCAL" />
<el-option label="CAS" value="CAS" />
<el-option label="LDAP" value="LDAP" />
<el-option label="OIDC" value="OIDC" />
<el-option label="OAuth2" value="OAuth2" />
<el-option :label="$t('views.userManage.source.wecom')" value="wecom" />
<el-option :label="$t('views.userManage.source.lark')" value="lark" />
<el-option :label="$t('views.userManage.source.dingtalk')" value="dingtalk" />
</el-select>
2025-06-16 12:14:36 +00:00
</div>
<el-button
type="primary"
:disabled="current?.is_auth"
@click="handleSave"
2025-07-01 12:03:13 +00:00
v-if="
2025-07-09 08:12:02 +00:00
route.path.includes('share/')
? false
: hasPermission(permissionObj[currentPermissionKey], 'OR')
2025-07-01 12:03:13 +00:00
"
2025-06-25 11:04:02 +00:00
>
{{ t('common.save') }}
</el-button>
2025-06-16 12:14:36 +00:00
</div>
2025-07-01 12:03:13 +00:00
<app-table
:data="tableData"
:pagination-config="paginationConfig"
@sizeChange="handleSizeChange"
@changePage="getList"
2025-07-09 08:12:02 +00:00
:maxTableHeight="350"
2025-07-01 12:03:13 +00:00
>
<el-table-column
prop="nick_name"
:label="$t('views.userManage.userForm.nick_name.label')"
/>
<el-table-column prop="username" :label="$t('views.login.loginForm.username.label')" />
2025-06-16 12:14:36 +00:00
<el-table-column prop="source" :label="$t('views.userManage.source.label')">
<template #default="{ row }">
{{
row.source === 'LOCAL'
? $t('views.userManage.source.local')
: row.source === 'wecom'
2025-07-15 07:20:57 +00:00
? $t('views.userManage.source.wecom')
: row.source === 'lark'
? $t('views.userManage.source.lark')
: row.source === 'dingtalk'
? $t('views.userManage.source.dingtalk')
: row.source === 'OAUTH2' || row.source === 'OAuth2'
? 'OAuth2'
: row.source
2025-06-16 12:14:36 +00:00
}}
</template>
</el-table-column>
<el-table-column :width="140" align="center">
<template #header>
2025-07-01 12:03:13 +00:00
<el-checkbox
:model-value="allChecked"
:indeterminate="allIndeterminate"
:disabled="current?.is_auth"
@change="handleCheckAll"
>{{ $t('views.chatUser.authorization') }}
</el-checkbox>
2025-06-16 12:14:36 +00:00
</template>
<template #default="{ row }">
2025-07-01 12:03:13 +00:00
<el-checkbox
v-model="row.is_auth"
:indeterminate="row.indeterminate"
:disabled="current?.is_auth"
@change="(value: boolean) => handleRowChange(value, row)"
/>
2025-06-16 12:14:36 +00:00
</template>
</el-table-column>
</app-table>
</div>
</div>
</el-card>
2025-07-01 13:22:21 +00:00
</div>
2025-06-16 12:14:36 +00:00
</template>
<script lang="ts" setup>
import { onMounted, ref, watch, reactive, computed } from 'vue'
import { t } from '@/locales'
import type { ChatUserGroupItem, ChatUserGroupUserItem } from '@/api/type/workspaceChatUser'
import { useRoute } from 'vue-router'
import { SourceTypeEnum } from '@/enums/common'
import { MsgSuccess } from '@/utils/message'
import { ComplexPermission } from '@/utils/permission/type'
import { RoleConst, PermissionConst } from '@/utils/permission/data'
import { hasPermission } from '@/utils/permission/index'
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
2025-06-16 12:14:36 +00:00
const route = useRoute()
2025-06-25 11:04:02 +00:00
const {
params: { id, folderId },
2025-06-25 11:04:02 +00:00
} = route as any
2025-07-01 12:03:13 +00:00
const permissionObj = ref<any>({
APPLICATION: new ComplexPermission(
2025-07-12 09:42:40 +00:00
[RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.USER.getWorkspaceRole],
2025-07-01 12:03:13 +00:00
[
PermissionConst.APPLICATION_CHAT_USER_EDIT,
PermissionConst.APPLICATION_CHAT_USER_EDIT.getApplicationWorkspaceResourcePermission(id),
],
[],
'OR',
),
KNOWLEDGE: new ComplexPermission(
2025-07-12 09:42:40 +00:00
[RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole, RoleConst.USER.getWorkspaceRole],
2025-07-01 12:03:13 +00:00
[
PermissionConst.KNOWLEDGE_CHAT_USER_EDIT,
PermissionConst.KNOWLEDGE_CHAT_USER_EDIT.getKnowledgeWorkspaceResourcePermission(id),
],
[],
'OR',
),
2025-07-31 06:42:16 +00:00
RESOURCE_APPLICATION: [RoleConst.ADMIN, PermissionConst.RESOURCE_APPLICATION_CHAT_USER_EDIT],
2025-07-30 07:45:30 +00:00
RESOURCE_KNOWLEDGE: [RoleConst.ADMIN, PermissionConst.RESOURCE_KNOWLEDGE_CHAT_USER_EDIT],
2025-07-01 12:03:13 +00:00
SHAREDKNOWLEDGE: new ComplexPermission(
[RoleConst.ADMIN],
[PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_EDIT],
[],
'OR',
),
2025-06-30 10:22:35 +00:00
})
2025-07-30 07:45:30 +00:00
const currentPermissionKey = computed(() => {
if (route.path.includes('shared')) return 'SHAREDKNOWLEDGE'
if (route.path.includes('resource-management')) {
if (route.meta?.resourceType === 'KNOWLEDGE') {
return 'RESOURCE_KNOWLEDGE'
} else if (route.meta?.resourceType === 'APPLICATION') {
return 'RESOURCE_APPLICATION'
2025-07-30 07:45:30 +00:00
}
}
return route.meta?.resourceType as string
2025-07-30 07:45:30 +00:00
})
2025-07-01 12:03:13 +00:00
const resource = reactive({
resource_id: route.params.id as string,
resource_type: route.meta.resourceType as string,
})
2025-06-16 12:14:36 +00:00
const filterText = ref('')
const loading = ref(false)
const list = ref<ChatUserGroupItem[]>([])
const filterList = ref<ChatUserGroupItem[]>([]) // 搜索过滤后列表
const current = ref<ChatUserGroupItem>()
2025-07-01 12:03:13 +00:00
const apiType = computed(() => {
if (route.path.includes('shared')) {
return 'systemShare'
} else if (route.path.includes('resource-management')) {
return 'systemManage'
} else {
return 'workspace'
2025-07-01 10:05:57 +00:00
}
})
2025-06-16 12:14:36 +00:00
async function getUserGroupList() {
try {
2025-07-01 12:03:13 +00:00
const res = await loadSharedApi({
type: 'chatUser',
isShared: isShared.value,
2025-07-01 12:03:13 +00:00
systemType: apiType.value,
}).getUserGroupList(resource, loading)
2025-06-16 12:14:36 +00:00
list.value = res.data
filterList.value = filter(list.value, filterText.value)
} catch (error) {
console.error(error)
}
}
onMounted(async () => {
await getUserGroupList()
current.value = list.value[0]
})
function filter(list: ChatUserGroupItem[], filterText: string) {
if (!filterText.length) {
return list
}
return list.filter((v: ChatUserGroupItem) =>
v.name.toLowerCase().includes(filterText.toLowerCase()),
)
}
watch(filterText, (val: string) => {
filterList.value = filter(list.value, val)
})
2025-07-09 09:58:26 +00:00
const checkedMap = reactive<Record<string, boolean>>({}) // 选中的
2025-06-16 12:14:36 +00:00
function clickUserGroup(item: ChatUserGroupItem) {
2025-07-09 09:58:26 +00:00
// 清空跨组勾选缓存
for (const key in checkedMap) delete checkedMap[key]
2025-06-16 12:14:36 +00:00
current.value = item
}
2025-06-17 11:37:43 +00:00
async function changeAuth() {
const params = [{ user_group_id: current.value?.id as string, is_auth: !current.value?.is_auth }]
2025-06-17 11:37:43 +00:00
try {
2025-07-01 12:03:13 +00:00
await loadSharedApi({
type: 'chatUser',
systemType: apiType.value,
}).editUserGroupList(resource, params, loading)
2025-06-17 11:37:43 +00:00
await getUserGroupList()
2025-07-01 12:03:13 +00:00
current.value = {
name: current.value?.name as string,
id: current.value?.id as string,
is_auth: !current.value?.is_auth,
}
2025-06-17 11:37:43 +00:00
getList()
} catch (error) {
console.error(error)
}
}
2025-06-16 12:14:36 +00:00
const rightLoading = ref(false)
const searchType = ref('username')
2025-06-16 12:14:36 +00:00
const searchForm = ref<Record<string, any>>({
username: '',
nick_name: '',
source: '',
2025-06-16 12:14:36 +00:00
})
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
total: 0,
})
const tableData = ref<ChatUserGroupUserItem[]>([])
const isShared = computed(() => {
return folderId === 'share'
})
2025-06-16 12:14:36 +00:00
async function getList() {
if (!current.value?.id) return
const params: any = {}
const searchValue = searchForm.value[searchType.value as keyof typeof searchForm.value]
if (searchValue !== undefined && searchValue !== null && searchValue !== '') {
params[searchType.value] = searchValue
}
2025-06-16 12:14:36 +00:00
try {
2025-07-01 12:03:13 +00:00
const res = await loadSharedApi({
type: 'chatUser',
isShared: isShared.value,
2025-07-01 12:03:13 +00:00
systemType: apiType.value,
}).getUserGroupUserList(resource, current.value?.id, paginationConfig, params, rightLoading)
2025-07-09 09:58:26 +00:00
// 更新缓存和回显状态
res.data.records.forEach((item: any) => {
if (checkedMap[item.id] === undefined) {
checkedMap[item.id] = item.is_auth
}
item.is_auth = checkedMap[item.id]
})
2025-06-16 12:14:36 +00:00
tableData.value = res.data.records
paginationConfig.total = res.data.total
} catch (error) {
console.error(error)
}
}
function handleSizeChange() {
paginationConfig.current_page = 1
getList()
}
2025-07-15 07:20:57 +00:00
watch(
() => current.value?.id,
() => {
paginationConfig.current_page = 1
getList()
},
)
2025-06-16 12:14:36 +00:00
2025-07-15 07:20:57 +00:00
const allChecked = computed(
() => tableData.value.length > 0 && tableData.value.every((item) => checkedMap[item.id]),
2025-07-01 12:03:13 +00:00
)
2025-06-16 12:14:36 +00:00
2025-07-15 07:20:57 +00:00
const allIndeterminate = computed(
() => !allChecked.value && tableData.value.some((item) => checkedMap[item.id]),
2025-07-01 12:03:13 +00:00
)
2025-06-16 12:14:36 +00:00
const handleCheckAll = (checked: boolean) => {
2025-07-15 07:20:57 +00:00
tableData.value.forEach((item) => {
2025-07-01 12:03:13 +00:00
item.is_auth = checked
2025-07-09 09:58:26 +00:00
checkedMap[item.id] = checked
2025-07-01 12:03:13 +00:00
})
}
2025-06-16 12:14:36 +00:00
const handleRowChange = (value: boolean, row: ChatUserGroupUserItem) => {
2025-07-01 12:03:13 +00:00
row.is_auth = value
2025-07-09 09:58:26 +00:00
checkedMap[row.id] = value
2025-07-01 12:03:13 +00:00
}
2025-06-16 12:14:36 +00:00
async function handleSave() {
try {
2025-07-09 09:58:26 +00:00
const params = Object.entries(checkedMap).map(([id, is_auth]) => ({
chat_user_id: id,
is_auth,
}))
2025-07-01 12:03:13 +00:00
await loadSharedApi({
type: 'chatUser',
systemType: apiType.value,
}).putUserGroupUser(resource, current.value?.id as string, params, rightLoading)
2025-06-17 11:37:43 +00:00
MsgSuccess(t('common.saveSuccess'))
2025-06-16 12:14:36 +00:00
} catch (error) {
console.error(error)
}
}
</script>
<style lang="scss" scoped>
.user-left {
box-sizing: border-box;
width: var(--setting-left-width);
min-width: var(--setting-left-width);
.list-height-left {
2025-07-09 08:12:02 +00:00
height: calc(100vh - 251px);
2025-06-16 12:14:36 +00:00
}
}
.user-right {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 24px;
}
2025-06-26 08:36:08 +00:00
</style>