mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-29 00:10:31 +00:00
feat(dingtalk): 钉钉 OAuth 登录接入与 internal_only 用户属性同步
⚠️ 应用类型约束:当前实现仅支持「钉钉登录-企业内部应用」(DingTalk 开放平台 internal_app 类型)。第三方个人应用、第三方企业应用类型暂不支持——OAuth 流程 相同但 corp 校验、跨企业行为不同。backend 通过 DingTalkAppKind 校验对非 internal_app 类型 fail-closed(硬约束)。 钉钉 OAuth 登录主链 - 4 步 OAuth 链:ExchangeCodeForUserToken / GetUnionIdByUserToken / GetUserIdByUnionId / GetStaffInfoByUserId;app token 缓存 - pending session 机制持久化 OAuth 中间态;cookie-only token 持久化 - 三种分流:bind_login_required / email_completion / choose_account_action - corp_restriction_policy 支持 none + internal_only;stale "whitelist" 在 加载层与写入层均静默 coerce 为 none + slog.Warn - bypass_registration 开关:企业内部模式豁免全局 REGISTRATION_DISABLED - isReservedEmail / signup_source / canUnbindProvider / OAuth pending flow 等横切点支持 dingtalk provider - migration 136:4 表 CHECK 约束加入 'dingtalk' provider 值 internal_only 模式同步企业邮箱/姓名/部门到用户属性 - SyncCorpEmail / SyncDisplayName / SyncDept 三个独立开关 + 对应 SyncXxxAttrKey 目标属性 key(默认 dingtalk_email / dingtalk_name / dingtalk_department);非 internal_only policy 在写入层与加载层均 coerce 为 false,admin handler 与 setting_service 双层兜底 - 同步语义:首次注册写 users.username(昵称优先 → 企业姓名 fallback), 之后每次登录刷新 3 个属性;空值也写入以覆盖旧值 - 邮箱三级 fallback:org_email > email > extension["企业邮箱"] (钉钉自定义字段 JSON) - 部门路径递归向上拼接,跳过 dept_id=1 选首个真实子部门,剥离根组织名 - GetUnionIdByUserToken 同时返回 OIDC /contact/users/me 的 nick 字段; 新增 GetDeptInfo 调用 OAPI /topapi/v2/department/get - AuthHandler 注入 UserAttributeService;OAuth pending flow 在 createPendingOAuthAccount / bindPendingOAuthLogin 分别派发到 AfterRegistration(syncUsername=true)/ AfterLogin - migration 137 seed dingtalk_email/name/department 三个用户属性定义 附带修复(同集成路径暴露的两个 OAuth 注册回归) - LoginOrRegisterOAuthWithTokenPair 新建用户分支用 inferLegacySignupSource 覆写 caller 显式传入的 signupSource,导致 dingtalk/linuxdo/oidc/wechat 渠道授权按 email 渠道读取;改为只在 caller 未显式传入时回退邮箱推断 - mergeProviderDefaultGrantSettings 把 parse fallback 默认值 (Concurrency=5 / Balance=0) 当作"未配置"哨兵,admin 显式设 5 时被误判 退回全局默认(复现:全局默认 1 + 渠道默认并发 5 + grant_on_signup → 新 用户实际 concurrency=1);去掉哨兵,admin 任何 >=0 值都覆盖 globalDefaults 前端 - DingTalk Login / Callback / EmailCompletion / ChoiceAccount / Error 视图;router + auth API client - admin SettingsView:corp policy radio(none / internal_only)+ bypass 注册开关 + i18n;internal_only 下展示三同步开关 + 目标 attr key 下拉 (拉取 user attribute definitions),展示 fieldEmail / qyapi_get_department_list 钉钉权限申请提示 - Profile:S1 主动绑定 / S5 解绑钉钉按钮 + 合成邮箱防自锁 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
committed by
QTom
co-authored by
Claude Opus 4.7
parent
b006e36af9
commit
b19da9c7fe
@@ -2333,6 +2333,294 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DingTalk Connect OAuth 登录 -->
|
||||
<div class="card">
|
||||
<div
|
||||
class="border-b border-gray-100 px-6 py-4 dark:border-dark-700"
|
||||
>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ t("admin.settings.dingtalk.title") }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.description") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-5 p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.enable")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.enableHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.dingtalk_connect_enabled"
|
||||
class="border-t border-gray-100 pt-4 dark:border-dark-700"
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-6">
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.dingtalk.clientId") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_client_id"
|
||||
type="text"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="
|
||||
t('admin.settings.dingtalk.clientIdPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.clientIdHint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.dingtalk.clientSecret") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_client_secret"
|
||||
type="password"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="
|
||||
form.dingtalk_connect_client_secret_configured
|
||||
? t(
|
||||
'admin.settings.dingtalk.clientSecretConfiguredPlaceholder',
|
||||
)
|
||||
: t('admin.settings.dingtalk.clientSecretPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
form.dingtalk_connect_client_secret_configured
|
||||
? t(
|
||||
"admin.settings.dingtalk.clientSecretConfiguredHint",
|
||||
)
|
||||
: t("admin.settings.dingtalk.clientSecretHint")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.dingtalk.redirectUrl") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_redirect_url"
|
||||
type="url"
|
||||
class="input font-mono text-sm"
|
||||
:placeholder="
|
||||
t('admin.settings.dingtalk.redirectUrlPlaceholder')
|
||||
"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.redirectUrlHint") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Corp Restriction Policy -->
|
||||
<div class="border-t border-gray-100 pt-4 dark:border-dark-700">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.label") }}
|
||||
</label>
|
||||
<p class="mb-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.hint") }}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
v-model="form.dingtalk_connect_corp_restriction_policy"
|
||||
type="radio"
|
||||
value="none"
|
||||
class="h-4 w-4 text-primary-600"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.none") }}
|
||||
</span>
|
||||
</label>
|
||||
<label class="flex cursor-pointer items-center gap-3">
|
||||
<input
|
||||
v-model="form.dingtalk_connect_corp_restriction_policy"
|
||||
type="radio"
|
||||
value="internal_only"
|
||||
class="h-4 w-4 text-primary-600"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t("admin.settings.dingtalk.corpPolicy.internalOnly") }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- bypass_registration toggle(仅 internal_only 模式下可见可用) -->
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="flex items-center justify-between pt-4 border-t border-gray-100 dark:border-dark-700"
|
||||
>
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.bypassRegistration")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.bypassRegistrationHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_bypass_registration" />
|
||||
</div>
|
||||
|
||||
<!-- 身份同步开关(仅 internal_only 模式下可见) -->
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="pt-4 border-t border-gray-100 dark:border-dark-700 space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.syncDisplayName")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.syncDisplayNameHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_sync_display_name" />
|
||||
</div>
|
||||
<div v-if="form.dingtalk_connect_sync_display_name" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncDisplayNameTarget") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_display_name_attr_key"
|
||||
type="text"
|
||||
placeholder="dingtalk_name"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncAttrDisplayName") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_display_name_attr_name"
|
||||
type="text"
|
||||
placeholder="钉钉姓名"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.dingtalk_connect_sync_display_name" class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t("admin.settings.dingtalk.syncDisplayNameTargetHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="pt-4 border-t border-gray-100 dark:border-dark-700 space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.syncCorpEmail")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailHint") }}
|
||||
</p>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailPermissionHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_sync_corp_email" />
|
||||
</div>
|
||||
<div v-if="form.dingtalk_connect_sync_corp_email" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailTarget") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_corp_email_attr_key"
|
||||
type="text"
|
||||
placeholder="dingtalk_email"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncAttrDisplayName") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_corp_email_attr_name"
|
||||
type="text"
|
||||
placeholder="钉钉企业邮箱"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.dingtalk_connect_sync_corp_email" class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t("admin.settings.dingtalk.syncCorpEmailTargetHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="form.dingtalk_connect_corp_restriction_policy === 'internal_only'"
|
||||
class="pt-4 border-t border-gray-100 dark:border-dark-700 space-y-2"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t("admin.settings.dingtalk.syncDept")
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.dingtalk.syncDeptHint") }}
|
||||
</p>
|
||||
<p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
|
||||
{{ t("admin.settings.dingtalk.syncDeptPermissionHint") }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.dingtalk_connect_sync_dept" />
|
||||
</div>
|
||||
<div v-if="form.dingtalk_connect_sync_dept" class="space-y-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncDeptTarget") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_dept_attr_key"
|
||||
type="text"
|
||||
placeholder="dingtalk_department"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap min-w-[5rem]">
|
||||
{{ t("admin.settings.dingtalk.syncAttrDisplayName") }}
|
||||
</label>
|
||||
<input
|
||||
v-model="form.dingtalk_connect_sync_dept_attr_name"
|
||||
type="text"
|
||||
placeholder="钉钉部门"
|
||||
class="input text-sm flex-1 max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="form.dingtalk_connect_sync_dept" class="text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t("admin.settings.dingtalk.syncDeptTargetHint") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Generic OIDC OAuth 登录 -->
|
||||
<div class="card">
|
||||
<div
|
||||
@@ -6417,6 +6705,7 @@ type SettingsForm = Omit<
|
||||
smtp_password: string;
|
||||
turnstile_secret_key: string;
|
||||
linuxdo_connect_client_secret: string;
|
||||
dingtalk_connect_client_secret: string;
|
||||
wechat_connect_app_secret: string;
|
||||
wechat_connect_open_app_secret: string;
|
||||
wechat_connect_mp_app_secret: string;
|
||||
@@ -6518,6 +6807,24 @@ const form = reactive<SettingsForm>({
|
||||
linuxdo_connect_client_secret: "",
|
||||
linuxdo_connect_client_secret_configured: false,
|
||||
linuxdo_connect_redirect_url: "",
|
||||
// DingTalk Connect OAuth 登录
|
||||
dingtalk_connect_enabled: false,
|
||||
dingtalk_connect_client_id: "",
|
||||
dingtalk_connect_client_secret: "",
|
||||
dingtalk_connect_client_secret_configured: false,
|
||||
dingtalk_connect_redirect_url: "",
|
||||
dingtalk_connect_corp_restriction_policy: "none",
|
||||
dingtalk_connect_internal_corp_id: "",
|
||||
dingtalk_connect_bypass_registration: false,
|
||||
dingtalk_connect_sync_corp_email: false,
|
||||
dingtalk_connect_sync_display_name: false,
|
||||
dingtalk_connect_sync_dept: false,
|
||||
dingtalk_connect_sync_corp_email_attr_key: "dingtalk_email",
|
||||
dingtalk_connect_sync_display_name_attr_key: "dingtalk_name",
|
||||
dingtalk_connect_sync_dept_attr_key: "dingtalk_department",
|
||||
dingtalk_connect_sync_corp_email_attr_name: "钉钉企业邮箱",
|
||||
dingtalk_connect_sync_display_name_attr_name: "钉钉姓名",
|
||||
dingtalk_connect_sync_dept_attr_name: "钉钉部门",
|
||||
wechat_connect_enabled: false,
|
||||
wechat_connect_app_id: "",
|
||||
wechat_connect_app_secret: "",
|
||||
@@ -6658,6 +6965,14 @@ const authSourceDefaultsMeta = computed(() => [
|
||||
"Applied on first signup or first bind through a verified Google email.",
|
||||
),
|
||||
},
|
||||
{
|
||||
source: "dingtalk" as AuthSourceType,
|
||||
title: "钉钉",
|
||||
description: localText(
|
||||
"通过钉钉首次注册或首次绑定时应用。",
|
||||
"Applied on first signup or first bind through DingTalk.",
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
// Proxies for web search emulation ProxySelector
|
||||
@@ -7241,6 +7556,7 @@ async function loadSettings() {
|
||||
smtpPasswordManuallyEdited.value = false;
|
||||
form.turnstile_secret_key = "";
|
||||
form.linuxdo_connect_client_secret = "";
|
||||
form.dingtalk_connect_client_secret = "";
|
||||
form.github_oauth_client_secret = "";
|
||||
form.google_oauth_client_secret = "";
|
||||
form.wechat_connect_app_secret = "";
|
||||
@@ -7593,6 +7909,24 @@ async function saveSettings() {
|
||||
linuxdo_connect_client_secret:
|
||||
form.linuxdo_connect_client_secret || undefined,
|
||||
linuxdo_connect_redirect_url: form.linuxdo_connect_redirect_url,
|
||||
dingtalk_connect_enabled: form.dingtalk_connect_enabled,
|
||||
dingtalk_connect_client_id: form.dingtalk_connect_client_id,
|
||||
dingtalk_connect_client_secret:
|
||||
form.dingtalk_connect_client_secret || undefined,
|
||||
dingtalk_connect_redirect_url: form.dingtalk_connect_redirect_url,
|
||||
dingtalk_connect_corp_restriction_policy:
|
||||
form.dingtalk_connect_corp_restriction_policy,
|
||||
dingtalk_connect_internal_corp_id: form.dingtalk_connect_internal_corp_id,
|
||||
dingtalk_connect_bypass_registration: form.dingtalk_connect_bypass_registration,
|
||||
dingtalk_connect_sync_corp_email: form.dingtalk_connect_sync_corp_email,
|
||||
dingtalk_connect_sync_display_name: form.dingtalk_connect_sync_display_name,
|
||||
dingtalk_connect_sync_dept: form.dingtalk_connect_sync_dept,
|
||||
dingtalk_connect_sync_corp_email_attr_key: form.dingtalk_connect_sync_corp_email_attr_key,
|
||||
dingtalk_connect_sync_display_name_attr_key: form.dingtalk_connect_sync_display_name_attr_key,
|
||||
dingtalk_connect_sync_dept_attr_key: form.dingtalk_connect_sync_dept_attr_key,
|
||||
dingtalk_connect_sync_corp_email_attr_name: form.dingtalk_connect_sync_corp_email_attr_name,
|
||||
dingtalk_connect_sync_display_name_attr_name: form.dingtalk_connect_sync_display_name_attr_name,
|
||||
dingtalk_connect_sync_dept_attr_name: form.dingtalk_connect_sync_dept_attr_name,
|
||||
wechat_connect_enabled: form.wechat_connect_enabled,
|
||||
wechat_connect_app_id:
|
||||
form.wechat_connect_open_app_id ||
|
||||
@@ -7775,6 +8109,7 @@ async function saveSettings() {
|
||||
smtpPasswordManuallyEdited.value = false;
|
||||
form.turnstile_secret_key = "";
|
||||
form.linuxdo_connect_client_secret = "";
|
||||
form.dingtalk_connect_client_secret = "";
|
||||
form.github_oauth_client_secret = "";
|
||||
form.google_oauth_client_secret = "";
|
||||
form.wechat_connect_app_secret = "";
|
||||
@@ -8992,6 +9327,21 @@ watch(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// bypass_registration 与身份同步三开关仅在 internal_only 模式下生效。切换 policy 到其它值时,
|
||||
// 立即把相关字段重置为 false,避免保存请求里残留旧值。后端 admin handler 与
|
||||
// 配置加载层都有 coerce 兜底,这里是 UX 层的同步而非安全防线。
|
||||
watch(
|
||||
() => form.dingtalk_connect_corp_restriction_policy,
|
||||
(policy) => {
|
||||
if (policy !== "internal_only") {
|
||||
if (form.dingtalk_connect_bypass_registration) form.dingtalk_connect_bypass_registration = false;
|
||||
if (form.dingtalk_connect_sync_corp_email) form.dingtalk_connect_sync_corp_email = false;
|
||||
if (form.dingtalk_connect_sync_display_name) form.dingtalk_connect_sync_display_name = false;
|
||||
if (form.dingtalk_connect_sync_dept) form.dingtalk_connect_sync_dept = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user