maxkb/ui/src/views/chat-user/index.vue
2025-07-10 18:28:34 +08:00

413 lines
12 KiB
Vue

<template>
<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')
}}
</div>
</div>
<el-card style="--el-card-padding: 0">
<div class="flex">
<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
/>
</div>
<div class="list-height-left">
<el-scrollbar v-loading="loading">
<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>
</el-scrollbar>
</div>
</div>
<!-- 右边 -->
<div class="user-right" v-loading="rightLoading">
<div class="flex-between">
<div class="flex align-center">
<h4 class="medium ellipsis" :title="current?.name">{{ current?.name || '-' }}</h4>
<el-divider direction="vertical" class="mr-8 ml-8" />
<el-icon class="color-input-placeholder"><UserFilled /></el-icon>
<span class="color-input-placeholder ml-4">
{{ paginationConfig.total }}
</span>
</div>
<el-button
type="primary"
:disabled="current?.is_auth"
@click="handleSave"
v-if="
route.path.includes('share/')
? false
: hasPermission(
permissionObj[
route.path.includes('shared')
? 'SHAREDKNOWLEDGE'
: (route.meta?.resourceType as string)
],
'OR',
)
"
>
{{ t('common.save') }}
</el-button>
</div>
<div class="flex-between mb-16" style="margin-top: 18px">
<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="name" />
</el-select>
<el-input
v-if="searchType === 'name'"
v-model="searchForm.name"
@change="getList"
:placeholder="$t('common.inputPlaceholder')"
style="width: 220px"
clearable
/>
</div>
<div
class="flex align-center"
v-if="
route.path.includes('share/')
? false
: hasPermission(
permissionObj[
route.path.includes('shared')
? 'SHAREDKNOWLEDGE'
: (route.meta?.resourceType as string)
],
'OR',
)
"
>
<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>
</div>
<app-table
:data="tableData"
:pagination-config="paginationConfig"
@sizeChange="handleSizeChange"
@changePage="getList"
:maxTableHeight="350"
>
<el-table-column
prop="nick_name"
:label="$t('views.userManage.userForm.nick_name.label')"
show-overflow-tooltip
/>
<el-table-column prop="username" :label="$t('views.login.loginForm.username.label')" />
<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'
? $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
}}
</template>
</el-table-column>
<el-table-column :width="140" align="center">
<template #header>
<el-checkbox
:model-value="allChecked"
:indeterminate="allIndeterminate"
:disabled="current?.is_auth"
@change="handleCheckAll"
>{{ $t('views.chatUser.authorization') }}</el-checkbox
>
</template>
<template #default="{ row }">
<el-checkbox
v-model="row.is_auth"
:indeterminate="row.indeterminate"
:disabled="current?.is_auth"
@change="(value: boolean) => handleRowChange(value, row)"
/>
</template>
</el-table-column>
</app-table>
</div>
</div>
</el-card>
</div>
</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'
const route = useRoute()
const {
params: { id, folderId },
} = route as any
const permissionObj = ref<any>({
APPLICATION: new ComplexPermission(
[RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],
[
PermissionConst.APPLICATION_CHAT_USER_EDIT,
PermissionConst.APPLICATION_CHAT_USER_EDIT.getApplicationWorkspaceResourcePermission(id),
],
[],
'OR',
),
KNOWLEDGE: new ComplexPermission(
[RoleConst.ADMIN, RoleConst.WORKSPACE_MANAGE.getWorkspaceRole],
[
PermissionConst.KNOWLEDGE_CHAT_USER_EDIT,
PermissionConst.KNOWLEDGE_CHAT_USER_EDIT.getKnowledgeWorkspaceResourcePermission(id),
],
[],
'OR',
),
SHAREDKNOWLEDGE: new ComplexPermission(
[RoleConst.ADMIN],
[PermissionConst.SHARED_KNOWLEDGE_CHAT_USER_EDIT],
[],
'OR',
),
})
const resource = reactive({
resource_id: route.params.id as string,
resource_type: route.meta.resourceType as string,
})
const filterText = ref('')
const loading = ref(false)
const list = ref<ChatUserGroupItem[]>([])
const filterList = ref<ChatUserGroupItem[]>([]) // 搜索过滤后列表
const current = ref<ChatUserGroupItem>()
const apiType = computed(() => {
if (route.path.includes('shared')) {
return 'systemShare'
} else if (route.path.includes('resource-management')) {
return 'systemManage'
} else {
return 'workspace'
}
})
async function getUserGroupList() {
try {
const res = await loadSharedApi({
type: 'chatUser',
isShared: isShared.value,
systemType: apiType.value,
}).getUserGroupList(resource, loading)
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)
})
const checkedMap = reactive<Record<string, boolean>>({}) // 选中的
function clickUserGroup(item: ChatUserGroupItem) {
// 清空跨组勾选缓存
for (const key in checkedMap) delete checkedMap[key]
current.value = item
}
async function changeAuth() {
const params = [{ user_group_id: current.value?.id as string, is_auth: !current.value?.is_auth }]
try {
await loadSharedApi({
type: 'chatUser',
systemType: apiType.value,
}).editUserGroupList(resource, params, loading)
await getUserGroupList()
current.value = {
name: current.value?.name as string,
id: current.value?.id as string,
is_auth: !current.value?.is_auth,
}
getList()
} catch (error) {
console.error(error)
}
}
const rightLoading = ref(false)
const searchType = ref('name')
const searchForm = ref<Record<string, any>>({
name: '',
})
const paginationConfig = reactive({
current_page: 1,
page_size: 20,
total: 0,
})
const tableData = ref<ChatUserGroupUserItem[]>([])
const isShared = computed(() => {
return folderId === 'share'
})
async function getList() {
if (!current.value?.id) return
try {
const res = await loadSharedApi({
type: 'chatUser',
isShared: isShared.value,
systemType: apiType.value,
}).getUserGroupUserList(
resource,
current.value?.id,
paginationConfig,
searchForm.value.name,
rightLoading,
)
// 更新缓存和回显状态
res.data.records.forEach((item: any) => {
if (checkedMap[item.id] === undefined) {
checkedMap[item.id] = item.is_auth
}
item.is_auth = checkedMap[item.id]
})
tableData.value = res.data.records
paginationConfig.total = res.data.total
} catch (error) {
console.error(error)
}
}
function handleSizeChange() {
paginationConfig.current_page = 1
getList()
}
watch(() => current.value?.id, () => {
paginationConfig.current_page = 1
getList()
})
const allChecked = computed(() =>
tableData.value.length > 0 &&
tableData.value.every(item => checkedMap[item.id])
)
const allIndeterminate = computed(() =>
!allChecked.value &&
tableData.value.some(item => checkedMap[item.id])
)
const handleCheckAll = (checked: boolean) => {
tableData.value.forEach(item => {
item.is_auth = checked
checkedMap[item.id] = checked
})
}
const handleRowChange = (value: boolean, row: ChatUserGroupUserItem) => {
row.is_auth = value
checkedMap[row.id] = value
}
async function handleSave() {
try {
const params = Object.entries(checkedMap).map(([id, is_auth]) => ({
chat_user_id: id,
is_auth,
}))
await loadSharedApi({
type: 'chatUser',
systemType: apiType.value,
}).putUserGroupUser(resource, current.value?.id as string, params, rightLoading)
MsgSuccess(t('common.saveSuccess'))
} 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 {
height: calc(100vh - 251px);
}
}
.user-right {
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
padding: 24px;
}
</style>