feat(System Management ): Supports integration with WeChat Work platform; log in using your WeChat Work account.

This commit is contained in:
dataeaseShu
2026-02-02 14:46:57 +08:00
committed by dataeaseShu
parent 7ca6b75731
commit d5be4d8ac3
5 changed files with 85 additions and 431 deletions
+2
View File
@@ -27,4 +27,6 @@ export const modelApi = {
query: (id: number) => request.get(`/system/aimodel/${id}`),
setDefault: (id: number) => request.put(`/system/aimodel/default/${id}`),
check: (data: any) => request.fetchStream('/system/aimodel/status', data),
platform: (id: number) => request.get(`/system/platform/org/${id}`),
userSync: (data: any) => request.post(`/system/platform/user/sync`, data),
}
+1
View File
@@ -21,6 +21,7 @@ export const userApi = {
add: (data: any) => request.post('/user', data),
edit: (data: any) => request.put('/user', data),
clearErrorApi: (key: string) => request.get(`/user/clearErrorRecord/${key}`),
errorRecord: (key: string) => request.get(`/user/errorRecord/${key}`),
delete: (key: string) => request.delete(`/user/${key}`),
deleteBatch: (data: any) => request.delete(`/user`, { data }),
get: (key: string) => request.get(`/user/${key}`),
-369
View File
@@ -1,369 +0,0 @@
<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-checkbox style="float: left" v-model="existingUser">
{{ $t('sync.the_existing_user') }}
</el-checkbox>
<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 existingUser = 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(true)
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>
+27 -26
View File
@@ -1,7 +1,7 @@
<template>
<el-dialog
v-model="centerDialogVisible"
:title="$t('sync.sync_dingtalk_users')"
:title="$t(dialogTitle)"
modal-class="sync-user_ding"
width="840"
>
@@ -101,7 +101,7 @@
<el-button secondary @click="centerDialogVisible = false">
{{ $t('common.cancel') }}</el-button
>
<el-button v-if="!checkedWorkspace.length" disabled type="info">{{
<el-button v-if="!checkTableList.length" disabled type="info">{{
$t('common.confirm2')
}}</el-button>
<el-button v-else type="primary" @click="handleConfirm">
@@ -113,7 +113,8 @@
<script lang="ts" setup>
import { ref, computed, watch, nextTick } from 'vue'
import { workspaceOptionUserList, workspaceUwsCreate } from '@/api/workspace'
import { modelApi } from '@/api/system'
import { ElLoading } from 'element-plus-secondary'
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'
@@ -126,6 +127,7 @@ const isIndeterminate = ref(false)
const checkedWorkspace = ref<any[]>([])
const workspace = ref<any[]>([])
const search = ref('')
const dialogTitle = ref('')
const organizationUserRef = ref()
const defaultCheckedKeys = ref<any[]>([])
const defaultProps = {
@@ -157,15 +159,6 @@ const filterNode: FilterNodeMethodFunction = (value: string, data: any) => {
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
}
@@ -192,12 +185,12 @@ const handleCheckedWorkspaceChange = (value: CheckboxValueType[]) => {
...value,
]),
]
organizationUserRef.value.setCheckedKeys(checkTableList.value.map((ele: any) => ele.id))
}
let oid: any = null
const open = async (id: any) => {
const open = async (id: any, title: any) => {
dialogTitle.value = title
loading.value = true
search.value = ''
oid = id
@@ -205,23 +198,30 @@ const open = async (id: any) => {
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)
)
const loadingInstance = ElLoading.service({ fullscreen: true })
const systemWorkspaceList = await modelApi.platform(id)
organizationUserList.value = systemWorkspaceList.tree || []
loadingInstance?.close()
nextTick(() => {
nextTick(() => {
handleCheckedWorkspaceChange(systemWorkspaceList.checked_list)
})
})
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')
})
modelApi
.userSync({
user_list: checkTableList.value.map((ele: any) => ({ id: ele.id, name: ele.name })),
origin: oid,
cover: existingUser.value,
})
.then((res: any) => {
centerDialogVisible.value = false
emits('refresh', res)
})
}
const clearWorkspace = (val: any) => {
@@ -232,6 +232,7 @@ const clearWorkspace = (val: any) => {
const clearWorkspaceAll = () => {
checkedWorkspace.value = []
checkTableList.value = []
handleCheckedWorkspaceChange([])
}
+55 -36
View File
@@ -35,7 +35,12 @@
>
<div class="popover">
<div class="popover-content">
<div v-for="ele in platformType" :key="ele.name" class="popover-item">
<div
@click="handleSyncUser(ele)"
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>
@@ -396,7 +401,7 @@
:filter-options="filterOption"
@trigger-filter="searchCondition"
/>
<SyncUserDing ref="syncUserDingRef" />
<SyncUserDing @refresh="refresh" ref="syncUserRef"></SyncUserDing>
</template>
<script setup lang="ts">
@@ -409,8 +414,7 @@ import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlin
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 SyncUserDing from './SyncUserDing.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'
@@ -420,6 +424,7 @@ 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 { request } from '@/utils/request'
import { workspaceList } from '@/api/workspace'
import { formatTimestamp } from '@/utils/date'
import { ClickOutside as vClickOutside } from 'element-plus-secondary'
@@ -438,7 +443,7 @@ const dialogVisiblePassword = ref(false)
const isIndeterminate = ref(true)
const drawerMainRef = ref()
const userImportRef = ref()
const syncUserDingRef = ref()
const syncUserRef = ref()
const selectionLoading = ref(false)
const filterOption = ref<any[]>([
{
@@ -501,6 +506,8 @@ const state = reactive<any>({
total: 0,
},
})
const currentPlatform = ref<any>({})
const rules = {
name: [
{
@@ -534,21 +541,33 @@ const rules = {
const platformType = ref<any[]>([
{
icon: logo_wechat_work,
value: 'wechat',
value: 6,
name: 'sync.sync_wechat_users',
},
{
icon: logo_dingtalk,
value: 'dingtalk',
value: 7,
name: 'sync.sync_dingtalk_users',
},
{
icon: logo_lark,
value: 'lark',
value: 8,
name: 'sync.sync_lark_users',
},
])
const refresh = (res: any) => {
showTips(res.successCount, res.errorCount, res.dataKey)
if (res.successCount) {
search()
}
}
const handleSyncUser = (ele: any) => {
currentPlatform.value = ele
syncUserRef.value.open(ele.value, ele.name)
}
const passwordRules = {
new: [
{
@@ -574,6 +593,14 @@ const setPopoverRef = (el: any, row: any) => {
row.popoverRef = el
}
const loadData = () => {
const url = '/system/platform'
request.get(url).then((res: any) => {
const idArr = res.filter((card: any) => card.valid && card.enable).map((ele: any) => ele.id)
platformType.value = platformType.value.filter((card: any) => idArr.includes(card.value))
})
}
const copyText = () => {
copy(defaultPwd.value)
.then(function () {
@@ -895,6 +922,7 @@ const formatUserOrigin = (origin?: number) => {
return originArray[origin - 1]
}
onMounted(() => {
loadData()
workspaceList().then((res) => {
options.value = res || []
filterOption.value[2].option = [...options.value]
@@ -902,31 +930,22 @@ 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 downErrorExcel = (dataKey: any) => {
userApi.errorRecord(dataKey).then((res: any) => {
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)
})
}
const showTips = (successCount: any, errorCount: any) => {
const showTips = (successCount: any, errorCount: any, dataKey: 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(
@@ -969,7 +988,7 @@ const showTips = (successCount: any, errorCount: any) => {
h(
ElButton,
{
onClick: downErrorExcel,
onClick: () => downErrorExcel(dataKey),
text: true,
class: 'down-button',
},
@@ -993,14 +1012,14 @@ const showTips = (successCount: any, errorCount: any) => {
confirmButtonText: t('sync.continue_syncing'),
})
.then(() => {
// clearErrorRecord()
const { value, name } = currentPlatform.value
syncUserRef.value.open(value, name)
currentPlatform.value = null
})
.catch(() => {
// clearErrorRecord()
currentPlatform.value = null
})
}
showTips(1, 2)
</script>
<style lang="less" scoped>