feat(User Management): Supports user synchronization

This commit is contained in:
dataeaseShu
2026-02-02 14:46:57 +08:00
committed by dataeaseShu
parent 19c38321c0
commit d952c9bf49
10 changed files with 989 additions and 4 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,11 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="14" cy="14" r="14" fill="#7F3BF5"/>
<g clip-path="url(#clip0_11418_4897)">
<path d="M10.6771 16.625H17.3224C17.4683 16.5375 17.6252 16.4675 17.7914 16.4179V15.4584C17.7914 15.3036 17.73 15.1553 17.6206 15.0459C17.5112 14.9365 17.3628 14.875 17.2081 14.875H10.7914C10.6367 14.875 10.4884 14.9365 10.379 15.0459C10.2696 15.1553 10.2081 15.3036 10.2081 15.4584V16.4179C10.3744 16.4675 10.5316 16.5375 10.6771 16.625ZM9.04145 16.4179V15.4584C9.04145 14.9942 9.22582 14.5491 9.55401 14.2209C9.8822 13.8927 10.3273 13.7084 10.7914 13.7084H13.4164V12.4682C12.8666 12.3262 12.3873 11.9886 12.0686 11.5185C11.7499 11.0485 11.6135 10.4783 11.685 9.91493C11.7566 9.35154 12.0312 8.83359 12.4573 8.45816C12.8834 8.08274 13.4319 7.87561 13.9998 7.87561C14.5677 7.87561 15.1161 8.08274 15.5423 8.45816C15.9684 8.83359 16.243 9.35154 16.3145 9.91493C16.3861 10.4783 16.2497 11.0485 15.931 11.5185C15.6122 11.9886 15.133 12.3262 14.5831 12.4682V13.7084H17.2081C17.6722 13.7084 18.1174 13.8927 18.4455 14.2209C18.7737 14.5491 18.9581 14.9942 18.9581 15.4584V16.4179C19.4069 16.5517 19.7961 16.8357 20.0604 17.2224C20.3247 17.609 20.448 18.0747 20.4097 18.5415C20.3714 19.0083 20.1738 19.4477 19.85 19.7861C19.5262 20.1244 19.0959 20.3412 18.6313 20.4C18.1667 20.4588 17.696 20.3561 17.2981 20.1091C16.9002 19.8621 16.5993 19.4858 16.4459 19.0434C16.2925 18.6009 16.2958 18.1191 16.4554 17.6788C16.615 17.2385 16.9211 16.8664 17.3224 16.625H10.6771C11.0784 16.8664 11.3845 17.2385 11.5441 17.6788C11.7037 18.1191 11.7071 18.6009 11.5537 19.0434C11.4003 19.4858 11.0994 19.8621 10.7015 20.1091C10.3036 20.3561 9.83286 20.4588 9.36824 20.4C8.90362 20.3412 8.47334 20.1244 8.14955 19.7861C7.82577 19.4477 7.62816 19.0083 7.58985 18.5415C7.55155 18.0747 7.67488 17.609 7.93917 17.2224C8.20345 16.8357 8.59263 16.5517 9.04145 16.4179Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_11418_4897">
<rect width="14" height="14" fill="white" transform="translate(7 7)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+14
View File
@@ -7,6 +7,20 @@
"AI Model Configuration": "AI Model Config",
"Details": "Details"
},
"sync": {
"sync_users": "Sync Users",
"sync_wechat_users": "Sync WeChat Users",
"sync_dingtalk_users": "Sync DingTalk Users",
"sync_lark_users": "Sync Lark Users",
"sync_complete": "Sync Complete",
"synced_10_users": "Successfully synced {num} users",
"failed_3_users": "Successfully synced {success} users, sync failed {failed} users",
"download_failure_list": "Download Failure List",
"sync_failed": "Sync Failed",
"failed_10_users": "Sync failed {num} users",
"continue_syncing": "Continue Syncing",
"return_to_view": "Return to View"
},
"parameter": {
"execution_details": "Execution Details",
"overview": "Overview",
+14
View File
@@ -7,6 +7,20 @@
"AI Model Configuration": "모델 구성",
"Details": "세부"
},
"sync": {
"sync_users": "사용자 동기화",
"sync_wechat_users": "위챗 사용자 동기화",
"sync_dingtalk_users": "딩톡 사용자 동기화",
"sync_lark_users": "라크 사용자 동기화",
"sync_complete": "동기화 완료",
"synced_10_users": "{num}명 동기화 성공",
"failed_3_users": "{failed}명 동기화 성공, {success}명 동기화 실패",
"download_failure_list": "실패 목록 다운로드",
"sync_failed": "동기화 실패",
"failed_10_users": "{failed}명 동기화 실패",
"continue_syncing": "동기화 계속",
"return_to_view": "화면으로 돌아가기"
},
"parameter": {
"execution_details": "실행 세부 정보",
"overview": "개요",
+15 -1
View File
@@ -7,6 +7,20 @@
"AI Model Configuration": "模型配置",
"Details": "详情"
},
"sync": {
"sync_users": "同步用户",
"sync_wechat_users": "同步企业微信用户",
"sync_dingtalk_users": "同步钉钉用户",
"sync_lark_users": "同步飞书用户",
"sync_complete": "同步完成",
"synced_10_users": "成功同步 {num} 个用户",
"failed_3_users": "成功同步 {success} 个用户,同步失败 {failed} 个用户,",
"download_failure_list": "下载失败列表",
"sync_failed": "同步失败",
"failed_10_users": "同步失败 {num} 个用户,",
"continue_syncing": "继续同步",
"return_to_view": "返回查看"
},
"parameter": {
"execution_details": "执行详情",
"overview": "概览",
@@ -890,4 +904,4 @@
"to_doc": "查看 API",
"trigger_limit": "最多支持创建 {0} 个 API Key"
}
}
}
+365
View File
@@ -0,0 +1,365 @@
<template>
<el-dialog
v-model="centerDialogVisible"
:title="$t(dialogTitle)"
modal-class="sync-user"
width="840"
>
<div v-loading="loading" class="flex border" style="height: 428px; border-radius: 6px">
<div class="p-16 border-r">
<el-input
v-model="search"
:validate-event="false"
:placeholder="$t('datasource.search')"
style="width: 364px; margin-left: 16px"
clearable
>
<template #prefix>
<el-icon>
<Search></Search>
</el-icon>
</template>
</el-input>
<div class="mt-8 max-height_workspace">
<template v-if="workspaceWithKeywords.length">
<el-checkbox
v-model="checkAll"
class="mb-8"
style="margin-left: 16px"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
>
{{ $t('datasource.select_all') }}
</el-checkbox>
<el-checkbox-group
v-model="checkedWorkspace"
class="checkbox-group-block"
@change="handleCheckedWorkspaceChange"
>
<el-checkbox
v-for="space in workspaceWithKeywords"
:key="space.id"
:label="space.name"
:value="space"
class="hover-bg"
>
<div class="flex">
<el-icon size="28">
<avatar_personal></avatar_personal>
</el-icon>
<span class="ml-8 ellipsis" style="max-width: 40%" :title="space.name">
{{ space.name }}</span
>
<span class="account ellipsis ml-8" style="max-width: 40%" :title="space.account"
>({{ space.account }})</span
>
</div>
</el-checkbox>
</el-checkbox-group>
</template>
<div v-else class="ed-tree__empty-block">
{{ $t(!!search ? 'dashboard.no_data' : 'qa.no_data') }}
</div>
</div>
</div>
<div class="p-16 w-full">
<div class="flex-between mb-16" style="margin: 0 16px">
<span class="lighter">
{{ $t('workspace.selected_2_people', { msg: checkTableList.length }) }}
</span>
<el-button text @click="clearWorkspaceAll">
{{ $t('workspace.clear') }}
</el-button>
</div>
<div
v-for="ele in checkTableList"
:key="ele.name"
style="margin: 0 16px; position: relative"
class="flex-between align-center hover-bg_select"
>
<div
:title="`${ele.name}(${ele.account})`"
class="flex align-center ellipsis"
style="width: 100%"
>
<el-icon size="28">
<avatar_personal></avatar_personal>
</el-icon>
<span class="ml-8 lighter ellipsis" style="max-width: 40%" :title="ele.name">{{
ele.name
}}</span>
<span class="account ellipsis ml-8" style="max-width: 40%" :title="ele.account"
>({{ ele.account }})</span
>
</div>
<el-button class="close-btn" text>
<el-icon size="16" @click="clearWorkspace(ele)"><Close /></el-icon>
</el-button>
</div>
</div>
</div>
<template #footer>
<el-button secondary @click="centerDialogVisible = false">
{{ $t('common.cancel') }}</el-button
>
<el-button v-if="!checkedWorkspace.length" disabled type="info">{{
$t('common.confirm2')
}}</el-button>
<el-button v-else type="primary" @click="handleConfirm">
{{ $t('common.confirm2') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, computed, watch } from 'vue'
import { workspaceOptionUserList, workspaceUwsCreate } from '@/api/workspace'
import avatar_personal from '@/assets/svg/avatar_personal.svg'
import Close from '@/assets/svg/icon_close_outlined_w.svg'
import Search from '@/assets/svg/icon_search-outline_outlined.svg'
import type { CheckboxValueType } from 'element-plus-secondary'
const checkAll = ref(false)
const isIndeterminate = ref(false)
const checkedWorkspace = ref<any[]>([])
const workspace = ref<any[]>([])
const search = ref('')
const dialogTitle = ref('')
const loading = ref(false)
const centerDialogVisible = ref(false)
const checkTableList = ref([] as any[])
const workspaceWithKeywords = computed(() => {
return workspace.value.filter((ele: any) =>
(ele.name.toLowerCase() as string).includes(search.value.toLowerCase())
)
})
watch(search, () => {
const tableNameArr = workspaceWithKeywords.value.map((ele: any) => ele.name)
checkedWorkspace.value = checkTableList.value.filter((ele: any) =>
tableNameArr.includes(ele.name)
)
const checkedCount = checkedWorkspace.value.length
checkAll.value = checkedCount === workspaceWithKeywords.value.length
isIndeterminate.value = checkedCount > 0 && checkedCount < workspaceWithKeywords.value.length
})
const handleCheckAllChange = (val: CheckboxValueType) => {
const tableNameArr = workspaceWithKeywords.value.map((ele: any) => ele.name)
checkedWorkspace.value = val
? [
...new Set([
...workspaceWithKeywords.value,
...checkedWorkspace.value.filter((ele: any) => !tableNameArr.includes(ele.name)),
]),
]
: []
isIndeterminate.value = false
checkTableList.value = val
? [
...new Set([
...workspaceWithKeywords.value,
...checkTableList.value.filter((ele: any) => !tableNameArr.includes(ele.name)),
]),
]
: checkTableList.value.filter((ele: any) => !tableNameArr.includes(ele.name))
}
const handleCheckedWorkspaceChange = (value: CheckboxValueType[]) => {
const checkedCount = value.length
checkAll.value = checkedCount === workspaceWithKeywords.value.length
isIndeterminate.value = checkedCount > 0 && checkedCount < workspaceWithKeywords.value.length
const tableNameArr = workspaceWithKeywords.value.map((ele: any) => ele.name)
checkTableList.value = [
...new Set([
...checkTableList.value.filter((ele: any) => !tableNameArr.includes(ele.name)),
...value,
]),
]
}
let oid: any = null
const open = async (id: any) => {
dialogTitle.value = 'sync.sync_wechat_users'
loading.value = true
search.value = ''
oid = id
checkedWorkspace.value = []
checkTableList.value = []
checkAll.value = false
isIndeterminate.value = false
const systemWorkspaceList = await workspaceOptionUserList({ oid }, 1, 1000)
workspace.value = JSON.parse(
JSON.stringify(systemWorkspaceList.items.filter((ele: any) => +ele.id !== 1) as any)
)
loading.value = false
centerDialogVisible.value = true
}
const emits = defineEmits(['refresh'])
const handleConfirm = () => {
workspaceUwsCreate({
uid_list: checkTableList.value.map((ele: any) => ele.id),
oid,
weight: 0,
}).then(() => {
centerDialogVisible.value = false
emits('refresh')
})
}
const clearWorkspace = (val: any) => {
checkedWorkspace.value = checkedWorkspace.value.filter((ele: any) => ele.id !== val.id)
checkTableList.value = checkTableList.value.filter((ele: any) => ele.id !== val.id)
handleCheckedWorkspaceChange(checkedWorkspace.value)
}
const clearWorkspaceAll = () => {
checkedWorkspace.value = []
handleCheckedWorkspaceChange([])
}
defineExpose({
open,
})
</script>
<style lang="less">
.sync-user {
.mb-8 {
margin-bottom: 8px;
}
.ed-checkbox {
margin-right: 0;
position: relative;
}
.hover-bg,
.hover-bg_select {
&:hover {
&::after {
content: '';
height: 44px;
width: calc(100% + 34px);
background: #1f23291a;
position: absolute;
border-radius: 6px;
top: 50%;
transform: translateY(-50%);
left: -8px;
z-index: 1;
}
}
}
.hover-bg_select {
&:hover {
&::after {
width: calc(100% + 16px);
}
}
}
.mt-16 {
margin-top: 16px;
}
.p-16 {
padding: 16px 0;
}
.lighter {
font-weight: 400;
font-size: 14px;
line-height: 22px;
}
.checkbox-group-block {
margin: 0 16px;
}
.ed-tree__empty-block {
margin-top: 147px;
color: #646a73;
}
.checkbox-group-block {
.ed-checkbox,
.ed-checkbox__label,
.flex {
width: 96%;
height: 44px;
}
.flex {
align-items: center;
.account {
color: #8f959e;
}
}
}
.close-btn {
position: relative;
z-index: 10;
height: 24px;
line-height: 24px;
&:hover,
&:active,
&:focus {
background: #1f23291a !important;
}
}
.border {
border: 1px solid #dee0e3;
}
.w-full {
height: 100%;
width: 50%;
overflow-y: auto;
.flex-between {
height: 44px;
}
}
.mt-8 {
margin-top: 8px;
}
.max-height_workspace {
max-height: calc(100% - 24px);
overflow-y: auto;
}
.align-center {
align-items: center;
}
.flex-between {
display: flex;
justify-content: space-between;
}
.ml-4 {
margin-left: 4px;
}
.ml-8 {
margin-left: 8px;
}
.flex {
display: flex;
}
.border-r {
border-right: 1px solid #dee0e3;
width: 50%;
overflow: hidden;
}
}
</style>
@@ -0,0 +1,377 @@
<template>
<el-dialog
v-model="centerDialogVisible"
:title="$t('sync.sync_dingtalk_users')"
modal-class="sync-user_ding"
width="840"
>
<div v-loading="loading" class="flex border" style="height: 428px; border-radius: 6px">
<div class="p-16 border-r">
<el-input
v-model="search"
:validate-event="false"
:placeholder="$t('datasource.search')"
style="width: 364px; margin-left: 16px"
clearable
>
<template #prefix>
<el-icon>
<Search></Search>
</el-icon>
</template>
</el-input>
<div class="mt-8 max-height_workspace">
<el-tree
ref="organizationUserRef"
style="max-width: 426px"
class="checkbox-group-block"
:data="organizationUserList"
:filter-node-method="filterNode"
show-checkbox
:default-checked-keys="defaultCheckedKeys"
@check="handleCheck"
:props="defaultProps"
node-key="id"
default-expand-all
:expand-on-click-node="false"
>
<template #default="{ node }">
<div class="custom-tree-node flex">
<el-icon size="28">
<avatar_personal v-if="node.isLeaf"></avatar_personal>
<avatar_organize v-else></avatar_organize>
</el-icon>
<span class="ml-8 ellipsis" style="max-width: 40%" :title="node.label">
{{ node.label }}</span
>
<span class="account ellipsis ml-8" style="max-width: 40%" :title="node.account"
>({{ node.id }})</span
>
</div>
</template>
<template #empty>
{{ $t(!!search ? 'dashboard.no_data' : 'qa.no_data') }}
</template>
</el-tree>
</div>
</div>
<div class="p-16 w-full">
<div class="flex-between mb-16" style="margin: 0 16px">
<span class="lighter">
{{ $t('workspace.selected_2_people', { msg: checkTableList.length }) }}
</span>
<el-button text @click="clearWorkspaceAll">
{{ $t('workspace.clear') }}
</el-button>
</div>
<div
v-for="ele in checkTableList"
:key="ele.name"
style="margin: 0 16px; position: relative"
class="flex-between align-center hover-bg_select"
>
<div
:title="`${ele.name}(${ele.account})`"
class="flex align-center ellipsis"
style="width: 100%"
>
<el-icon size="28">
<avatar_personal></avatar_personal>
</el-icon>
<span class="ml-8 lighter ellipsis" style="max-width: 40%" :title="ele.name">{{
ele.name
}}</span>
<span class="account ellipsis ml-8" style="max-width: 40%" :title="ele.account"
>({{ ele.account }})</span
>
</div>
<el-button class="close-btn" text>
<el-icon size="16" @click="clearWorkspace(ele)"><Close /></el-icon>
</el-button>
</div>
</div>
</div>
<template #footer>
<el-button secondary @click="centerDialogVisible = false">
{{ $t('common.cancel') }}</el-button
>
<el-button v-if="!checkedWorkspace.length" disabled type="info">{{
$t('common.confirm2')
}}</el-button>
<el-button v-else type="primary" @click="handleConfirm">
{{ $t('common.confirm2') }}
</el-button>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref, computed, watch, nextTick } from 'vue'
import { workspaceOptionUserList, workspaceUwsCreate } from '@/api/workspace'
import avatar_personal from '@/assets/svg/avatar_personal.svg'
import avatar_organize from '@/assets/svg/avatar_organize.svg'
import Close from '@/assets/svg/icon_close_outlined_w.svg'
import Search from '@/assets/svg/icon_search-outline_outlined.svg'
import type { CheckboxValueType } from 'element-plus-secondary'
import type { FilterNodeMethodFunction } from 'element-plus-secondary'
const checkAll = ref(false)
const isIndeterminate = ref(false)
const checkedWorkspace = ref<any[]>([])
const workspace = ref<any[]>([])
const search = ref('')
const organizationUserRef = ref()
const defaultCheckedKeys = ref<any[]>([])
const defaultProps = {
children: 'children',
label: 'name',
}
const organizationUserList = ref<any[]>([])
const loading = ref(false)
const centerDialogVisible = ref(false)
const checkTableList = ref([] as any[])
const workspaceWithKeywords = computed(() => {
return workspace.value.filter((ele: any) =>
(ele.name.toLowerCase() as string).includes(search.value.toLowerCase())
)
})
watch(search, (val) => {
organizationUserRef.value!.filter(val)
nextTick(() => {
organizationUserRef.value.setCheckedKeys(checkTableList.value.map((ele: any) => ele.id))
})
})
const filterNode: FilterNodeMethodFunction = (value: string, data: any) => {
if (!value) return true
return data.name.includes(value)
}
watch(search, () => {
const tableNameArr = workspaceWithKeywords.value.map((ele: any) => ele.name)
checkedWorkspace.value = checkTableList.value.filter((ele: any) =>
tableNameArr.includes(ele.name)
)
const checkedCount = checkedWorkspace.value.length
checkAll.value = checkedCount === workspaceWithKeywords.value.length
isIndeterminate.value = checkedCount > 0 && checkedCount < workspaceWithKeywords.value.length
})
function isLeafNode(node: any) {
return !node.children || node.children.length === 0
}
const handleCheck = () => {
checkTableList.value = organizationUserRef.value
.getCheckedNodes()
.filter((ele: any) => isLeafNode(ele))
.map((ele: any) => ({
name: ele.name,
id: ele.id,
account: ele.id,
}))
}
const handleCheckedWorkspaceChange = (value: CheckboxValueType[]) => {
const checkedCount = value.length
checkAll.value = checkedCount === workspaceWithKeywords.value.length
isIndeterminate.value = checkedCount > 0 && checkedCount < workspaceWithKeywords.value.length
const tableNameArr = workspaceWithKeywords.value.map((ele: any) => ele.name)
checkTableList.value = [
...new Set([
...checkTableList.value.filter((ele: any) => !tableNameArr.includes(ele.name)),
...value,
]),
]
organizationUserRef.value.setCheckedKeys(checkTableList.value.map((ele: any) => ele.id))
}
let oid: any = null
const open = async (id: any) => {
loading.value = true
search.value = ''
oid = id
checkedWorkspace.value = []
checkTableList.value = []
checkAll.value = false
isIndeterminate.value = false
const systemWorkspaceList = await workspaceOptionUserList({ oid }, 1, 1000)
workspace.value = JSON.parse(
JSON.stringify(systemWorkspaceList.items.filter((ele: any) => +ele.id !== 1) as any)
)
loading.value = false
centerDialogVisible.value = true
}
const emits = defineEmits(['refresh'])
const handleConfirm = () => {
workspaceUwsCreate({
uid_list: checkTableList.value.map((ele: any) => ele.id),
oid,
weight: 0,
}).then(() => {
centerDialogVisible.value = false
emits('refresh')
})
}
const clearWorkspace = (val: any) => {
checkedWorkspace.value = checkedWorkspace.value.filter((ele: any) => ele.id !== val.id)
checkTableList.value = checkTableList.value.filter((ele: any) => ele.id !== val.id)
handleCheckedWorkspaceChange(checkedWorkspace.value)
}
const clearWorkspaceAll = () => {
checkedWorkspace.value = []
handleCheckedWorkspaceChange([])
}
defineExpose({
open,
})
</script>
<style lang="less">
.sync-user_ding {
.mb-8 {
margin-bottom: 8px;
}
.ed-checkbox {
margin-right: 0;
position: relative;
}
.hover-bg,
.hover-bg_select {
&:hover {
&::after {
content: '';
height: 44px;
width: calc(100% + 34px);
background: #1f23291a;
position: absolute;
border-radius: 6px;
top: 50%;
transform: translateY(-50%);
left: -8px;
z-index: 1;
}
}
}
.hover-bg_select {
&:hover {
&::after {
width: calc(100% + 16px);
}
}
}
.ed-tree__empty-block {
margin-top: 147px;
color: #646a73;
}
.mt-16 {
margin-top: 16px;
}
.p-16 {
padding: 16px 0;
}
.lighter {
font-weight: 400;
font-size: 14px;
line-height: 22px;
}
.checkbox-group-block {
margin: 0 8px;
--ed-tree-node-content-height: 44px;
}
.ed-tree-node__content {
border-radius: 6px;
}
.custom-tree-node {
width: 82%;
align-items: center;
.account {
color: #8f959e;
}
}
.close-btn {
position: relative;
z-index: 10;
height: 24px;
line-height: 24px;
&:hover,
&:active,
&:focus {
background: #1f23291a !important;
}
}
.border {
border: 1px solid #dee0e3;
}
.w-full {
height: 100%;
width: 50%;
overflow-y: auto;
.flex-between {
height: 44px;
}
}
.mt-8 {
margin-top: 8px;
}
.max-height_workspace {
max-height: calc(100% - 24px);
overflow-y: auto;
}
.align-center {
align-items: center;
}
.flex-between {
display: flex;
justify-content: space-between;
}
.ml-4 {
margin-left: 4px;
}
.ml-8 {
margin-left: 8px;
}
.flex {
display: flex;
.account {
color: #8f959e;
}
}
.border-r {
border-right: 1px solid #dee0e3;
width: 50%;
overflow: hidden;
}
}
</style>
+193 -3
View File
@@ -23,6 +23,25 @@
</template>
{{ $t('user.filter') }}
</el-button>
<el-popover popper-class="sync-platform" placement="bottom-start">
<template #reference>
<el-button secondary>
<template #icon>
<icon_replace_outlined />
</template>
{{ t('sync.sync_users') }}
</el-button></template
>
<div class="popover">
<div class="popover-content">
<div v-for="ele in platformType" :key="ele.name" class="popover-item">
<img height="24" width="24" :src="ele.icon" />
<div class="model-name">{{ $t(ele.name) }}</div>
</div>
</div>
</div>
</el-popover>
<!-- <el-button secondary @click="handleUserImport">
<template #icon>
<ccmUpload></ccmUpload>
@@ -377,23 +396,28 @@
:filter-options="filterOption"
@trigger-filter="searchCondition"
/>
<SyncUserDing ref="syncUserDingRef" />
</template>
<script setup lang="ts">
import { ref, unref, reactive, onMounted, nextTick } from 'vue'
import { ref, unref, reactive, onMounted, nextTick, h } from 'vue'
import UserImport from './UserImport.vue'
import SuccessFilled from '@/assets/svg/gou_icon.svg'
import icon_replace_outlined from '@/assets/svg/icon_replace_outlined.svg'
import CircleCloseFilled from '@/assets/svg/icon_ban_filled.svg'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import { useI18n } from 'vue-i18n'
import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
import { convertFilterText, FilterText } from '@/components/filter-text'
// import SyncUserDing from './SyncUserDing.vue'
import SyncUserDing from './SyncUser.vue'
import IconLock from '@/assets/svg/icon-key_outlined.svg'
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
import IconOpeDelete from '@/assets/svg/icon_delete.svg'
import iconFilter from '@/assets/svg/icon-filter_outlined.svg'
// import ccmUpload from '@/assets/svg/icon_ccm-upload_outlined.svg'
import logo_dingtalk from '@/assets/img/dingtalk.png'
import logo_lark from '@/assets/img/lark.png'
import logo_wechat_work from '@/assets/img/wechat.png'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import { userApi } from '@/api/user'
import { workspaceList } from '@/api/workspace'
@@ -414,6 +438,7 @@ const dialogVisiblePassword = ref(false)
const isIndeterminate = ref(true)
const drawerMainRef = ref()
const userImportRef = ref()
const syncUserDingRef = ref()
const selectionLoading = ref(false)
const filterOption = ref<any[]>([
{
@@ -506,6 +531,24 @@ const rules = {
],
}
const platformType = ref<any[]>([
{
icon: logo_wechat_work,
value: 'wechat',
name: 'sync.sync_wechat_users',
},
{
icon: logo_dingtalk,
value: 'dingtalk',
name: 'sync.sync_dingtalk_users',
},
{
icon: logo_lark,
value: 'lark',
name: 'sync.sync_lark_users',
},
])
const passwordRules = {
new: [
{
@@ -859,6 +902,105 @@ onMounted(() => {
search()
loadDefaultPwd()
})
const downErrorExcel = () => {
// if (errorFileKey.value) {
// showLoading()
// userImportApi
// .downErrorRecordApi(errorFileKey.value)
// .then((res) => {
// const blob = new Blob([res], {
// type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// })
// const link = document.createElement('a')
// link.style.display = 'none'
// link.href = URL.createObjectURL(blob)
// link.download = 'error.xlsx'
// document.body.appendChild(link)
// link.click()
// document.body.removeChild(link)
// closeLoading()
// })
// .catch(() => {
// closeLoading()
// })
// }
}
const showTips = (successCount: any, errorCount: any) => {
let title = successCount ? t('sync.sync_complete') : t('sync.sync_failed')
const childrenDomList = [h('span', null, t('sync.synced_10_users', { num: successCount }))]
const contentDomList = h(
'div',
{
style: 'display: flex;align-items: center;',
},
childrenDomList
)
const headerDomList = [
h(
'div',
{
style: 'font-weight: 500;font-size: 16px;line-height: 24px;margin-bottom: 24px',
},
title
),
contentDomList,
]
if (successCount && errorCount) {
childrenDomList.pop()
const halfCountDom = h(
'span',
null,
t('sync.failed_3_users', { success: successCount, failed: errorCount })
)
childrenDomList.push(halfCountDom)
}
if (!successCount && errorCount) {
const errorCountDom = h('span', null, t('sync.failed_10_users', { num: errorCount }))
childrenDomList.pop()
childrenDomList.push(errorCountDom)
}
if (errorCount) {
const errorDom = h('div', { class: 'error-record-tip flex-align-center' }, [
h(
ElButton,
{
onClick: downErrorExcel,
text: true,
class: 'down-button',
},
t('sync.download_failure_list')
),
])
childrenDomList.push(errorDom)
}
ElMessageBox.confirm('', {
confirmButtonType: 'primary',
autofocus: false,
dangerouslyUseHTMLString: true,
message: h(
'div',
{ class: 'sync-tip-box' },
headerDomList
),
cancelButtonText: t('sync.return_to_view'),
confirmButtonText: t('sync.continue_syncing'),
})
.then(() => {
// clearErrorRecord()
})
.catch(() => {
// clearErrorRecord()
})
}
showTips(1, 2)
</script>
<style lang="less" scoped>
@@ -1003,6 +1145,54 @@ onMounted(() => {
</style>
<style lang="less">
.ed-message-box:has(.sync-tip-box) {
padding: 24px;
}
.sync-tip-box {
.error-record-tip {
display: inline-block;
}
}
.sync-platform.sync-platform {
padding: 4px 0;
width: 180px !important;
box-shadow: 0px 4px 8px 0px #1f23291a;
border: 1px solid #dee0e3;
.popover {
.popover-content {
padding: 4px;
max-height: 300px;
overflow-y: auto;
}
.popover-item {
height: 32px;
display: flex;
align-items: center;
padding-left: 12px;
padding-right: 8px;
position: relative;
border-radius: 4px;
cursor: pointer;
&:not(:last-child) {
margin-bottom: 2px;
}
&:hover {
background: #1f23291a;
}
.model-name {
margin-left: 8px;
font-weight: 400;
font-size: 14px;
line-height: 22px;
max-width: 220px;
}
}
}
}
.reset-pwd-confirm {
padding: 5px 15px;
.confirm-header {