feat: 员工同步改为钉钉开放API方式
All checks were successful
continuous-integration/drone/push Build is passing

- 新增 dingtalk_service.py 调用钉钉开放API
- 支持获取 Access Token、部门列表、员工列表
- employee_sync_service 改为从钉钉API获取员工
- 前端配置界面支持配置 CorpId、ClientId、ClientSecret
- 移除外部数据库表依赖
This commit is contained in:
yuliang_guo
2026-01-31 17:25:44 +08:00
parent cabc3c3442
commit 7be1ac1787
4 changed files with 441 additions and 145 deletions

View File

@@ -41,13 +41,10 @@ class DingtalkConfigResponse(BaseModel):
class EmployeeSyncConfigUpdate(BaseModel): class EmployeeSyncConfigUpdate(BaseModel):
"""员工同步配置更新请求""" """员工同步配置更新请求(钉钉 API 方式)"""
db_host: Optional[str] = Field(None, description="数据库主机") corp_id: Optional[str] = Field(None, description="钉钉企业 CorpId")
db_port: Optional[int] = Field(None, description="数据库端口") client_id: Optional[str] = Field(None, description="应用 ClientId (AppKey)")
db_name: Optional[str] = Field(None, description="数据库名") client_secret: Optional[str] = Field(None, description="应用 ClientSecret (AppSecret)")
db_user: Optional[str] = Field(None, description="数据库用户名")
db_password: Optional[str] = Field(None, description="数据库密码")
table_name: Optional[str] = Field(None, description="员工表/视图名称")
enabled: Optional[bool] = Field(None, description="是否启用自动同步") enabled: Optional[bool] = Field(None, description="是否启用自动同步")
@@ -294,30 +291,39 @@ async def get_employee_sync_config(
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> ResponseModel: ) -> ResponseModel:
""" """
获取员工同步配置 获取员工同步配置(钉钉 API 方式)
仅限管理员访问 仅限管理员访问
数据库连接从环境变量读取,前端只能配置表名和开关
""" """
check_admin_permission(current_user) check_admin_permission(current_user)
import os
tenant_id = await get_or_create_tenant_id(db) tenant_id = await get_or_create_tenant_id(db)
# 从数据库获取表名和开关配置 # 从数据库获取钉钉 API 配置
table_name = await get_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME') corp_id = await get_system_config(db, tenant_id, 'employee_sync', 'CORP_ID')
client_id = await get_system_config(db, tenant_id, 'employee_sync', 'CLIENT_ID')
client_secret = await get_system_config(db, tenant_id, 'employee_sync', 'CLIENT_SECRET')
enabled = await get_feature_switch(db, tenant_id, 'employee_sync') enabled = await get_feature_switch(db, tenant_id, 'employee_sync')
# 从环境变量检查数据源是否已配置 # 脱敏处理 client_secret
sync_db_url = os.environ.get('EMPLOYEE_SYNC_DB_URL', '') client_secret_masked = None
configured = bool(sync_db_url) if client_secret:
if len(client_secret) > 8:
client_secret_masked = client_secret[:4] + '****' + client_secret[-4:]
else:
client_secret_masked = '****'
# 检查配置是否完整
configured = bool(corp_id and client_id and client_secret)
return ResponseModel( return ResponseModel(
message="获取成功", message="获取成功",
data={ data={
"table_name": table_name or "v_钉钉员工表", "corp_id": corp_id,
"client_id": client_id,
"client_secret_masked": client_secret_masked,
"enabled": enabled, "enabled": enabled,
"configured": configured, # 数据源是否已在环境变量配置 "configured": configured,
} }
) )
@@ -329,18 +335,23 @@ async def update_employee_sync_config(
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> ResponseModel: ) -> ResponseModel:
""" """
更新员工同步配置 更新员工同步配置(钉钉 API 方式)
仅限管理员访问 仅限管理员访问
只能配置表名和开关,数据库连接从环境变量读取
""" """
check_admin_permission(current_user) check_admin_permission(current_user)
tenant_id = await get_or_create_tenant_id(db) tenant_id = await get_or_create_tenant_id(db)
try: try:
if config.table_name is not None: if config.corp_id is not None:
await set_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME', config.table_name) await set_system_config(db, tenant_id, 'employee_sync', 'CORP_ID', config.corp_id)
if config.client_id is not None:
await set_system_config(db, tenant_id, 'employee_sync', 'CLIENT_ID', config.client_id)
if config.client_secret is not None:
await set_system_config(db, tenant_id, 'employee_sync', 'CLIENT_SECRET', config.client_secret)
if config.enabled is not None: if config.enabled is not None:
await set_feature_switch(db, tenant_id, 'employee_sync', config.enabled) await set_feature_switch(db, tenant_id, 'employee_sync', config.enabled)
@@ -370,44 +381,46 @@ async def test_employee_sync_connection(
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
) -> ResponseModel: ) -> ResponseModel:
""" """
测试员工同步数据库连接 测试钉钉 API 连接
仅限管理员访问 仅限管理员访问
使用环境变量中的数据库连接配置
""" """
check_admin_permission(current_user) check_admin_permission(current_user)
import os
tenant_id = await get_or_create_tenant_id(db) tenant_id = await get_or_create_tenant_id(db)
# 从环境变量获取数据库连接 # 获取钉钉配置
sync_db_url = os.environ.get('EMPLOYEE_SYNC_DB_URL', '') corp_id = await get_system_config(db, tenant_id, 'employee_sync', 'CORP_ID')
client_id = await get_system_config(db, tenant_id, 'employee_sync', 'CLIENT_ID')
client_secret = await get_system_config(db, tenant_id, 'employee_sync', 'CLIENT_SECRET')
if not sync_db_url: if not all([corp_id, client_id, client_secret]):
return ResponseModel( return ResponseModel(
code=400, code=400,
message="数据源未配置,请联系系统管理员配置 EMPLOYEE_SYNC_DB_URL 环境变量" message="钉钉 API 配置不完整,请先填写 CorpId、ClientId、ClientSecret"
) )
# 获取表名配置
table_name = await get_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME') or "v_钉钉员工表"
try: try:
from sqlalchemy.ext.asyncio import create_async_engine from app.services.dingtalk_service import DingTalkService
engine = create_async_engine(sync_db_url, echo=False, pool_pre_ping=True) dingtalk = DingTalkService(
corp_id=corp_id,
async with engine.connect() as conn: client_id=client_id,
# 测试查询员工表 client_secret=client_secret
result = await conn.execute(text(f"SELECT COUNT(*) FROM {table_name}"))
count = result.scalar()
await engine.dispose()
return ResponseModel(
message=f"连接成功!员工表共有 {count} 条在职员工",
data={"employee_count": count}
) )
result = await dingtalk.test_connection()
if result["success"]:
return ResponseModel(
message=f"连接成功!已获取到组织架构",
data=result
)
else:
return ResponseModel(
code=500,
message=result["message"]
)
except Exception as e: except Exception as e:
logger.error(f"测试连接失败: {str(e)}") logger.error(f"测试连接失败: {str(e)}")

View File

@@ -0,0 +1,276 @@
"""
钉钉开放平台 API 服务
用于通过钉钉 API 获取组织架构和员工信息
"""
import httpx
from typing import List, Dict, Any, Optional
from datetime import datetime, timedelta
from app.core.logger import get_logger
logger = get_logger(__name__)
class DingTalkService:
"""钉钉 API 服务"""
BASE_URL = "https://api.dingtalk.com"
OAPI_URL = "https://oapi.dingtalk.com"
def __init__(
self,
corp_id: str,
client_id: str,
client_secret: str
):
"""
初始化钉钉服务
Args:
corp_id: 企业 CorpId
client_id: 应用 ClientId (AppKey)
client_secret: 应用 ClientSecret (AppSecret)
"""
self.corp_id = corp_id
self.client_id = client_id
self.client_secret = client_secret
self._access_token: Optional[str] = None
self._token_expires_at: Optional[datetime] = None
async def get_access_token(self) -> str:
"""
获取钉钉 Access Token
使用新版 OAuth2 接口获取
Returns:
access_token
"""
# 检查缓存的 token 是否有效
if self._access_token and self._token_expires_at:
if datetime.now() < self._token_expires_at - timedelta(minutes=5):
return self._access_token
url = f"{self.BASE_URL}/v1.0/oauth2/{self.corp_id}/token"
payload = {
"client_id": self.client_id,
"client_secret": self.client_secret,
"grant_type": "client_credentials"
}
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(url, json=payload)
response.raise_for_status()
data = response.json()
self._access_token = data["access_token"]
expires_in = data.get("expires_in", 7200)
self._token_expires_at = datetime.now() + timedelta(seconds=expires_in)
logger.info(f"获取钉钉 Access Token 成功,有效期 {expires_in}")
return self._access_token
async def get_department_list(self, dept_id: int = 1) -> List[Dict[str, Any]]:
"""
获取部门列表
Args:
dept_id: 父部门ID根部门为1
Returns:
部门列表
"""
access_token = await self.get_access_token()
url = f"{self.OAPI_URL}/topapi/v2/department/listsub"
params = {"access_token": access_token}
payload = {"dept_id": dept_id}
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(url, params=params, json=payload)
response.raise_for_status()
data = response.json()
if data.get("errcode") != 0:
raise Exception(f"获取部门列表失败: {data.get('errmsg')}")
return data.get("result", [])
async def get_all_departments(self) -> List[Dict[str, Any]]:
"""
递归获取所有部门
Returns:
所有部门列表(扁平化)
"""
all_departments = []
async def fetch_recursive(parent_id: int):
departments = await self.get_department_list(parent_id)
for dept in departments:
all_departments.append(dept)
# 递归获取子部门
await fetch_recursive(dept["dept_id"])
await fetch_recursive(1) # 从根部门开始
logger.info(f"获取到 {len(all_departments)} 个部门")
return all_departments
async def get_department_users(
self,
dept_id: int,
cursor: int = 0,
size: int = 100
) -> Dict[str, Any]:
"""
获取部门用户列表
Args:
dept_id: 部门ID
cursor: 分页游标
size: 每页大小最大100
Returns:
用户列表和分页信息
"""
access_token = await self.get_access_token()
url = f"{self.OAPI_URL}/topapi/v2/user/list"
params = {"access_token": access_token}
payload = {
"dept_id": dept_id,
"cursor": cursor,
"size": size
}
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(url, params=params, json=payload)
response.raise_for_status()
data = response.json()
if data.get("errcode") != 0:
raise Exception(f"获取部门用户失败: {data.get('errmsg')}")
return data.get("result", {})
async def get_all_employees(self) -> List[Dict[str, Any]]:
"""
获取所有在职员工
遍历所有部门获取员工列表
Returns:
员工列表
"""
logger.info("开始从钉钉 API 获取所有员工...")
# 1. 获取所有部门
departments = await self.get_all_departments()
# 创建部门ID到名称的映射
dept_map = {dept["dept_id"]: dept["name"] for dept in departments}
dept_map[1] = "根部门" # 添加根部门
# 2. 遍历所有部门获取员工
all_employees = {} # 使用字典去重(按 userid
for dept in [{"dept_id": 1, "name": "根部门"}] + departments:
dept_id = dept["dept_id"]
dept_name = dept["name"]
cursor = 0
while True:
result = await self.get_department_users(dept_id, cursor)
users = result.get("list", [])
for user in users:
userid = user.get("userid")
if userid and userid not in all_employees:
# 转换为统一格式
employee = self._convert_user_to_employee(user, dept_name)
all_employees[userid] = employee
# 检查是否还有更多数据
if not result.get("has_more", False):
break
cursor = result.get("next_cursor", 0)
employees = list(all_employees.values())
logger.info(f"获取到 {len(employees)} 位在职员工")
return employees
def _convert_user_to_employee(
self,
user: Dict[str, Any],
dept_name: str
) -> Dict[str, Any]:
"""
将钉钉用户数据转换为员工数据格式
Args:
user: 钉钉用户数据
dept_name: 部门名称
Returns:
标准员工数据格式
"""
return {
'full_name': user.get('name', ''),
'phone': user.get('mobile', ''),
'email': user.get('email', ''),
'department': dept_name,
'position': user.get('title', ''),
'employee_no': user.get('job_number', ''),
'is_leader': user.get('leader', False),
'is_active': user.get('active', True),
'dingtalk_id': user.get('userid', ''),
'join_date': user.get('hired_date'),
'work_location': user.get('work_place', ''),
'avatar': user.get('avatar', ''),
}
async def test_connection(self) -> Dict[str, Any]:
"""
测试钉钉 API 连接
Returns:
测试结果
"""
try:
# 1. 测试获取 token
token = await self.get_access_token()
# 2. 测试获取根部门信息
departments = await self.get_department_list(1)
# 3. 获取根部门员工数量
result = await self.get_department_users(1, size=1)
return {
"success": True,
"message": "连接成功",
"corp_id": self.corp_id,
"department_count": len(departments) + 1, # +1 是根部门
"has_employees": result.get("has_more", False) or len(result.get("list", [])) > 0
}
except httpx.HTTPStatusError as e:
error_detail = "HTTP错误"
if e.response.status_code == 400:
try:
error_data = e.response.json()
error_detail = error_data.get("message", str(e))
except:
pass
return {
"success": False,
"message": f"连接失败: {error_detail}",
"error": str(e)
}
except Exception as e:
return {
"success": False,
"message": f"连接失败: {str(e)}",
"error": str(e)
}

View File

@@ -1,6 +1,6 @@
""" """
员工同步服务 员工同步服务
外部钉钉员工表同步员工数据到考培练系统 钉钉开放 API 同步员工数据到考培练系统
""" """
from typing import List, Dict, Any, Optional, Tuple from typing import List, Dict, Any, Optional, Tuple
@@ -23,115 +23,79 @@ logger = get_logger(__name__)
class EmployeeSyncService: class EmployeeSyncService:
"""员工同步服务""" """员工同步服务"""
# 默认外部数据库连接配置(向后兼容,从环境变量读取)
DEFAULT_TABLE_NAME = "v_钉钉员工表"
def __init__(self, db: AsyncSession, tenant_id: int = 1): def __init__(self, db: AsyncSession, tenant_id: int = 1):
self.db = db self.db = db
self.tenant_id = tenant_id self.tenant_id = tenant_id
self.external_engine = None self._dingtalk_config = None
self.table_name = self.DEFAULT_TABLE_NAME
self._db_url = None
async def _get_table_name_from_db(self) -> str: async def _get_dingtalk_config(self) -> Dict[str, str]:
"""从数据库获取员工表名配置""" """从数据库获取钉钉 API 配置"""
if self._dingtalk_config:
return self._dingtalk_config
try: try:
result = await self.db.execute( result = await self.db.execute(
text(""" text("""
SELECT config_value SELECT config_key, config_value
FROM tenant_configs FROM tenant_configs
WHERE tenant_id = :tenant_id WHERE tenant_id = :tenant_id
AND config_group = 'employee_sync' AND config_group = 'employee_sync'
AND config_key = 'TABLE_NAME'
"""), """),
{"tenant_id": self.tenant_id} {"tenant_id": self.tenant_id}
) )
row = result.fetchone() rows = result.fetchall()
return row[0] if row else self.DEFAULT_TABLE_NAME
except Exception: config = {}
return self.DEFAULT_TABLE_NAME for key, value in rows:
config[key] = value
self._dingtalk_config = config
return config
except Exception as e:
logger.error(f"获取钉钉配置失败: {e}")
return {}
def _get_db_url_from_env(self) -> str:
"""从环境变量获取数据库连接URL"""
import os
# 优先使用环境变量中的完整URL
db_url = os.environ.get('EMPLOYEE_SYNC_DB_URL', '')
if db_url:
return db_url
# 向后兼容:如果没有配置环境变量,使用默认值
logger.warning("EMPLOYEE_SYNC_DB_URL 环境变量未配置,使用默认数据源")
return "mysql+aiomysql://neuron_new:NWxGM6CQoMLKyEszXhfuLBIIo1QbeK@120.77.144.233:29613/neuron_new?charset=utf8mb4"
async def __aenter__(self): async def __aenter__(self):
"""异步上下文管理器入口""" """异步上下文管理器入口"""
self._db_url = self._get_db_url_from_env() # 预加载钉钉配置
self.table_name = await self._get_table_name_from_db() await self._get_dingtalk_config()
self.external_engine = create_async_engine(
self._db_url,
echo=False,
pool_pre_ping=True,
pool_recycle=3600
)
return self return self
async def __aexit__(self, exc_type, exc_val, exc_tb): async def __aexit__(self, exc_type, exc_val, exc_tb):
"""异步上下文管理器出口""" """异步上下文管理器出口"""
if self.external_engine: pass
await self.external_engine.dispose()
async def fetch_employees_from_dingtalk(self) -> List[Dict[str, Any]]: async def fetch_employees_from_dingtalk(self) -> List[Dict[str, Any]]:
""" """
从钉钉员工表获取在职员工数据 从钉钉 API 获取在职员工数据
Returns: Returns:
员工数据列表 员工数据列表
""" """
logger.info(f"开始从员工表 {self.table_name} 获取数据...") config = await self._get_dingtalk_config()
query = f""" corp_id = config.get('CORP_ID')
SELECT client_id = config.get('CLIENT_ID')
员工姓名, client_secret = config.get('CLIENT_SECRET')
手机号,
邮箱,
所属部门,
职位,
工号,
是否领导,
是否在职,
钉钉用户ID,
入职日期,
工作地点
FROM {self.table_name}
WHERE 是否在职 = 1
ORDER BY 员工姓名
"""
async with self.external_engine.connect() as conn: if not all([corp_id, client_id, client_secret]):
result = await conn.execute(text(query)) raise Exception("钉钉 API 配置不完整,请先配置 CorpId、ClientId、ClientSecret")
rows = result.fetchall()
from app.services.dingtalk_service import DingTalkService
employees = []
for row in rows: dingtalk = DingTalkService(
employees.append({ corp_id=corp_id,
'full_name': row[0], client_id=client_id,
'phone': row[1], client_secret=client_secret
'email': row[2], )
'department': row[3],
'position': row[4], employees = await dingtalk.get_all_employees()
'employee_no': row[5],
'is_leader': bool(row[6]), # 过滤在职员工
'is_active': bool(row[7]), active_employees = [emp for emp in employees if emp.get('is_active', True)]
'dingtalk_id': row[8], logger.info(f"获取到 {len(active_employees)} 条在职员工数据")
'join_date': row[9],
'work_location': row[10] return active_employees
})
logger.info(f"获取到 {len(employees)} 条在职员工数据")
return employees
def generate_email(self, phone: str, original_email: Optional[str]) -> Optional[str]: def generate_email(self, phone: str, original_email: Optional[str]) -> Optional[str]:
""" """

View File

@@ -103,7 +103,7 @@
style="margin-bottom: 20px;" style="margin-bottom: 20px;"
> >
<template #default> <template #default>
<p>系统将从钉钉员工数据源自动同步员工信息姓名手机号部门岗位等</p> <p>通过钉钉开放 API 自动同步组织架构和员工信息姓名手机号部门岗位等</p>
<p style="margin-top: 8px;">同步的员工将自动创建系统账号初始密码为 123456</p> <p style="margin-top: 8px;">同步的员工将自动创建系统账号初始密码为 123456</p>
</template> </template>
</el-alert> </el-alert>
@@ -111,6 +111,7 @@
<el-form <el-form
ref="syncFormRef" ref="syncFormRef"
:model="syncForm" :model="syncForm"
:rules="syncRules"
label-width="140px" label-width="140px"
v-loading="syncLoading" v-loading="syncLoading"
> >
@@ -120,30 +121,53 @@
active-text="已启用" active-text="已启用"
inactive-text="已禁用" inactive-text="已禁用"
/> />
<span class="form-tip">启用后将每日自动同步员工数据</span> <span class="form-tip">启用后将每日自动从钉钉同步员工数据</span>
</el-form-item> </el-form-item>
<el-form-item label="员工表名" prop="table_name"> <el-divider content-position="left">钉钉应用配置</el-divider>
<el-form-item label="企业 CorpId" prop="corp_id">
<el-input <el-input
v-model="syncForm.table_name" v-model="syncForm.corp_id"
placeholder="v_钉钉员工表" placeholder="请输入钉钉企业 CorpId"
style="width: 300px;" style="width: 350px;"
/> />
<span class="form-tip">视图需包含员工姓名手机号所属部门职位钉钉用户ID等字段</span> <span class="form-tip">在钉钉管理后台-设置中查看</span>
</el-form-item> </el-form-item>
<el-form-item label="数据源状态"> <el-form-item label="应用 ClientId" prop="client_id">
<el-input
v-model="syncForm.client_id"
placeholder="请输入应用 ClientId (AppKey)"
style="width: 350px;"
/>
<span class="form-tip">钉钉开发者后台-应用凭证</span>
</el-form-item>
<el-form-item label="应用 ClientSecret" prop="client_secret">
<el-input
v-model="syncForm.client_secret"
type="password"
show-password
:placeholder="syncForm.client_secret_masked || '请输入应用 ClientSecret'"
style="width: 350px;"
/>
<span class="form-tip" v-if="syncForm.client_secret_masked && !syncForm.client_secret">
当前值: {{ syncForm.client_secret_masked }}
</span>
</el-form-item>
<el-form-item label="配置状态">
<el-tag :type="syncForm.configured ? 'success' : 'warning'"> <el-tag :type="syncForm.configured ? 'success' : 'warning'">
{{ syncForm.configured ? '已配置' : '未配置' }} {{ syncForm.configured ? '已配置' : '未配置' }}
</el-tag> </el-tag>
<span class="form-tip">数据源由系统管理员在后台配置</span>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="saveSyncConfig" :loading="syncSaving"> <el-button type="primary" @click="saveSyncConfig" :loading="syncSaving">
保存配置 保存配置
</el-button> </el-button>
<el-button @click="testSyncConnection" :loading="syncTesting" :disabled="!syncForm.configured"> <el-button @click="testSyncConnection" :loading="syncTesting">
测试连接 测试连接
</el-button> </el-button>
</el-form-item> </el-form-item>
@@ -182,11 +206,14 @@ const dingtalkForm = reactive({
corp_id: '', corp_id: '',
}) })
// 员工同步配置表单 // 员工同步配置表单(钉钉 API 方式)
const syncForm = reactive({ const syncForm = reactive({
enabled: false, enabled: false,
table_name: 'v_钉钉员工表', corp_id: '',
configured: false, // 数据源是否已配置 client_id: '',
client_secret: '',
client_secret_masked: '',
configured: false,
}) })
// 表单验证规则 // 表单验证规则
@@ -202,7 +229,14 @@ const dingtalkRules = reactive<FormRules>({
] ]
}) })
const syncRules = reactive<FormRules>({}) const syncRules = reactive<FormRules>({
corp_id: [
{ required: false, message: '请输入企业 CorpId', trigger: 'blur' }
],
client_id: [
{ required: false, message: '请输入应用 ClientId', trigger: 'blur' }
]
})
/** /**
* 加载钉钉配置 * 加载钉钉配置
@@ -274,7 +308,7 @@ const saveDingtalkConfig = async () => {
} }
/** /**
* 加载员工同步配置 * 加载员工同步配置(钉钉 API 方式)
*/ */
const loadSyncConfig = async () => { const loadSyncConfig = async () => {
syncLoading.value = true syncLoading.value = true
@@ -282,7 +316,10 @@ const loadSyncConfig = async () => {
const response = await request.get('/api/v1/settings/employee-sync') const response = await request.get('/api/v1/settings/employee-sync')
if (response.code === 200 && response.data) { if (response.code === 200 && response.data) {
syncForm.enabled = response.data.enabled || false syncForm.enabled = response.data.enabled || false
syncForm.table_name = response.data.table_name || 'v_钉钉员工表' syncForm.corp_id = response.data.corp_id || ''
syncForm.client_id = response.data.client_id || ''
syncForm.client_secret = ''
syncForm.client_secret_masked = response.data.client_secret_masked || ''
syncForm.configured = response.data.configured || false syncForm.configured = response.data.configured || false
} }
} catch (error: any) { } catch (error: any) {
@@ -293,14 +330,20 @@ const loadSyncConfig = async () => {
} }
/** /**
* 保存员工同步配置 * 保存员工同步配置(钉钉 API 方式)
*/ */
const saveSyncConfig = async () => { const saveSyncConfig = async () => {
syncSaving.value = true syncSaving.value = true
try { try {
const updateData = { const updateData: any = {
enabled: syncForm.enabled, enabled: syncForm.enabled,
table_name: syncForm.table_name, corp_id: syncForm.corp_id,
client_id: syncForm.client_id,
}
// 只有输入了新密码才传递
if (syncForm.client_secret) {
updateData.client_secret = syncForm.client_secret
} }
const response = await request.put('/api/v1/settings/employee-sync', updateData) const response = await request.put('/api/v1/settings/employee-sync', updateData)