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
12 changes: 9 additions & 3 deletions ui/src/api/admin/system/chat-user/chat-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,15 @@ const getChatUserSyncTypes = () => {
return get<string[]>(`${prefix}/sync/types`)
}

/** 从指定来源导入对话用户。 */
const postSyncChatUsers = (syncType: string) => {
return post<undefined, ChatUserSyncResult>(`${prefix}/sync/${syncType}`)
/** 从指定来源导入对话用户(file 来源需携带 xlsx 文件)。 */
const postSyncChatUsers = (syncType: string, defaultGroupId?: string, syncFile?: File) => {
if (syncFile) {
const payload = new FormData()
payload.append('xlsx_file', syncFile)
if (defaultGroupId) payload.append('default_group_id', defaultGroupId)
return post<FormData, ChatUserSyncResult>(`${prefix}/sync/${syncType}`, payload)
}
return post<{ default_group_id?: string }, ChatUserSyncResult>(`${prefix}/sync/${syncType}`, { default_group_id: defaultGroupId })
}

export default {
Expand Down
2 changes: 1 addition & 1 deletion ui/src/api/admin/system/settings/theme-setting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ThemeInfo } from '../../auth/types'

/** 保存系统外观主题设置。 */
const putThemeSetting = (payload: FormData) => {
return put<FormData, boolean>('/settings/theme', payload)
return put<FormData, boolean>('/display/update', payload)
}

export default { putThemeSetting }
27 changes: 24 additions & 3 deletions ui/src/api/admin/system/user-manage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { del, get, post, put } from '../core/request'
import { del, get, getExportFile, post, put } from '../core/request'
import type { ResponsePage, ParamsPage, PasswordRequest } from '../core/types'
import type { Dict, SystemUser, SystemUserPayload, SystemUserUpdateRequest, BatchSetUserRolesRequest, BatchSetUserWorkspaceRolesRequest } from '@/api/types'
import type { Dict, SystemUser, SystemUserPayload, SystemUserUpdateRequest, BatchSetUserRolesRequest, BatchSetUserWorkspaceRolesRequest, ChatUserSyncResult } from '@/api/types'

const prefix = '/user_manage'

Expand Down Expand Up @@ -44,4 +44,25 @@ const postBatchSetUserWorkspaceRoles = (payload: BatchSetUserWorkspaceRolesReque
return post<BatchSetUserWorkspaceRolesRequest, boolean>(`${prefix}/batch/add_role_ee`, payload)
}

export default { deleteUser, getUserManagePage, postUser, postBatchDeleteUsers, postBatchSetUserRoles, postBatchSetUserWorkspaceRoles, putUser, putUserPassword }

/** 下载系统用户导入模板。 */
const getUserManageImportTemplate = () => {
return getExportFile('user_import_template.xlsx', `${prefix}/template/export`)
}

/** 获取可导入的系统用户来源。 */
const getUserManageSyncTypes = () => {
return get<string[]>(`${prefix}/sync/types`)
}

/** 从指定来源同步系统用户(file 来源需携带 xlsx 文件)。 */
const postSyncSystemUsers = (syncType: string, syncFile?: File, workspaceId?: string, roleId?: string, defaultGroupId?: string) => {
const payload = new FormData()
if (workspaceId) payload.append('workspace_id', workspaceId)
if (roleId) payload.append('role_id', roleId)
if (defaultGroupId) payload.append('default_group_id', defaultGroupId)
if (syncFile) payload.append('xlsx_file', syncFile)
return post<FormData, ChatUserSyncResult>(`${prefix}/sync/${syncType}`, payload)
}

export default { deleteUser, getUserManageImportTemplate, getUserManagePage, getUserManageSyncTypes, postUser, postBatchDeleteUsers, postBatchSetUserRoles, postBatchSetUserWorkspaceRoles, postSyncSystemUsers, putUser, putUserPassword }
99 changes: 89 additions & 10 deletions ui/src/views/system/chat/users/dialog/ImportUsersDialog.vue
Original file line number Diff line number Diff line change
@@ -1,42 +1,89 @@
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { computed, nextTick, reactive, ref } from 'vue'
import type { FormInstance, FormRules, UploadFile, UploadUserFile } from 'element-plus'
import ChatUserApi from '@/api/admin/system/chat-user/chat-user'
import type { ChatUserSyncConflict, OptionItem, LoginMethod } from '@/api/types'
import type { FormInstance, FormRules } from 'element-plus'
import ChatUserGroupsApi from '@/api/admin/system/chat-user/chat-user-groups'
import UserManageApi from '@/api/admin/system/user-manage'
import type { ChatUserSyncConflict, ListItem, OptionItem, LoginMethod } from '@/api/types'
import { LOGIN_METHOD } from '@/api/enums'
import { LOGIN_METHOD_LABELS } from '@/constants'
import { MsgSuccess } from '@/utils/message'
import { MsgSuccess, MsgWarning } from '@/utils/message'
import MkDragUpload from '@/components/mk-drag-upload/index.vue'

defineOptions({ name: 'ImportUsersDialog' })

const emit = defineEmits<{ refresh: [] }>()

const LOCAL_FILE_SOURCE = 'LOCAL_FILE' as const

interface ImportUsersForm {
files: UploadUserFile[]
syncType: string
userGroupId: string
}

const dialogVisible = ref(false)

const importing = ref(false)
const importUsersFormRef = ref<FormInstance>()
const dragUploadRef = ref<InstanceType<typeof MkDragUpload>>()
const syncTypeOptions = ref<OptionItem<string>[]>([])
const importUsersForm = reactive<ImportUsersForm>({ syncType: '' })
const importUsersFormRules: FormRules<ImportUsersForm> = { syncType: [{ required: true, message: '请选择用户来源', trigger: 'change' }] }
const importUsersForm = reactive<ImportUsersForm>({ files: [], syncType: '', userGroupId: '' })
const importUsersFormRules: FormRules<ImportUsersForm> = {
files: [{ required: true, type: 'array', min: 1, message: '请上传文件', trigger: 'change' }],
syncType: [{ required: true, message: '请选择用户来源', trigger: 'change' }],
}
const isLocalFileSource = computed(() => importUsersForm.syncType === LOCAL_FILE_SOURCE)
const canImport = computed(() => Boolean(importUsersForm.syncType) && (!isLocalFileSource.value || importUsersForm.files.length > 0))

/* 获取导入用户的来源类型 */
const syncTypesLoading = ref(false)
function loadSyncTypes() {
syncTypesLoading.value = true
return ChatUserApi.getChatUserSyncTypes()
.then((syncTypes) => {
syncTypeOptions.value = syncTypes.map((syncType) => ({ label: syncType === LOGIN_METHOD.LOCAL ? '系统用户' : LOGIN_METHOD_LABELS[syncType as LoginMethod], value: syncType }))
syncTypeOptions.value = [
...syncTypes.map((syncType) => ({ label: syncType === LOGIN_METHOD.LOCAL ? '系统用户' : LOGIN_METHOD_LABELS[syncType as LoginMethod], value: syncType })),
{ label: '本地文件', value: LOCAL_FILE_SOURCE },
]
importUsersForm.syncType = syncTypeOptions.value[0]?.value ?? ''
})
.finally(() => {
syncTypesLoading.value = false
})
}

/* 用户组选项 */
const userGroupOptionsLoading = ref(false)
const userGroupOptions = ref<ListItem[]>([])
function loadUserGroupOptions() {
userGroupOptionsLoading.value = true
return ChatUserGroupsApi.getChatUserGroups()
.then((groups) => {
userGroupOptions.value = groups
})
.finally(() => {
userGroupOptionsLoading.value = false
})
}

function handleFileChange(file: UploadFile) {
if (!/\.xlsx$/i.test(file.name)) {
importUsersForm.files = []
dragUploadRef.value?.clearFiles()
MsgWarning('仅支持上传 xlsx 格式的文件')
return
}

importUsersForm.files = [file]
nextTick(() => importUsersFormRef.value?.validateField('files'))
}

function handleFileRemove() {
importUsersForm.files = []
nextTick(() => importUsersFormRef.value?.validateField('files'))
}

function formatConflictMessage(conflicts: ChatUserSyncConflict[]) {
return conflicts
.map((conflict) => {
Expand All @@ -46,15 +93,20 @@ function formatConflictMessage(conflicts: ChatUserSyncConflict[]) {
.join(';')
}

function handleDownloadTemplate() {
UserManageApi.getUserManageImportTemplate()
}

/* 导入用户 */
function submitImportUsers() {
if (!importUsersFormRef.value) return

importUsersFormRef.value.validate((valid) => {
if (!valid) return

const syncFile = isLocalFileSource.value ? ((importUsersForm.files[0]?.raw ?? undefined) as File | undefined) : undefined
importing.value = true
return ChatUserApi.postSyncChatUsers(importUsersForm.syncType)
return ChatUserApi.postSyncChatUsers(isLocalFileSource.value ? 'file' : importUsersForm.syncType, importUsersForm.userGroupId || undefined, syncFile)
.then((result) => {
const conflictMessage = formatConflictMessage(result.conflict_users ?? [])
MsgSuccess(`成功导入 ${result.success_count} 个用户${conflictMessage ? `,${conflictMessage}` : ''}`)
Expand All @@ -70,13 +122,17 @@ function submitImportUsers() {
function open() {
dialogVisible.value = true
loadSyncTypes()
loadUserGroupOptions()
}

function resetData() {
importUsersForm.syncType = ''
Object.assign(importUsersForm, { files: [], syncType: '', userGroupId: '' })
syncTypeOptions.value = []
syncTypesLoading.value = false
userGroupOptionsLoading.value = false
userGroupOptions.value = []
importing.value = false
dragUploadRef.value?.clearFiles()
importUsersFormRef.value?.clearValidate()
}

Expand All @@ -100,11 +156,34 @@ defineExpose({ open })
<el-option v-for="syncTypeOption in syncTypeOptions" :key="syncTypeOption.value" :label="syncTypeOption.label" :value="syncTypeOption.value" />
</el-select>
</el-form-item>

<el-form-item v-if="isLocalFileSource" prop="files" :required="false">
<template #label>
<div class="flex-between w-full">
<span>上传文件<span class="ml-1 text-danger">*</span></span>
<el-button link type="primary" @click="handleDownloadTemplate">下载模板</el-button>
</div>
</template>
<MkDragUpload
ref="dragUploadRef"
v-model="importUsersForm.files"
accept=".xlsx"
tip-text="支持格式:xlsx"
@change="handleFileChange"
@remove="handleFileRemove"
/>
</el-form-item>

<el-form-item label="用户组">
<el-select v-model="importUsersForm.userGroupId" class="w-full" :loading="userGroupOptionsLoading" clearable filterable placeholder="请选择用户组" fit-input-width>
<el-option v-for="userGroup in userGroupOptions" :key="userGroup.id" :label="userGroup.name" :value="userGroup.id" />
</el-select>
</el-form-item>
</el-form>

<template #footer>
<el-button plain @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="importing" @click="submitImportUsers">导入</el-button>
<el-button type="primary" :loading="importing" :disabled="!canImport" @click="submitImportUsers">导入</el-button>
</template>
</MkDialog>
</template>
2 changes: 1 addition & 1 deletion ui/src/views/system/identity/users/UserListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ onMounted(() => loadSystemUsers())
</template>
</MkViewLayout>
<UserFromDrawer ref="userFormDrawerRef" @refresh="loadSystemUsers" />
<ImportUsersDialog ref="importUsersDialogRef" />
<ImportUsersDialog ref="importUsersDialogRef" @refresh="loadSystemUsers" />
<UserPwdDialog ref="userPwdDialogRef" @refresh="loadSystemUsers(false)" />
<BatchSetUserRoleDialog ref="batchSetUserRoleDialogRef" @refresh="loadSystemUsers(false)" />
</template>
Expand Down
Loading
Loading