钉钉应用开发-Python操作钉钉文档

一: 服务端SDK下载

服务端SDK下载 - 钉钉开放平台 (dingtalk.com)

pip3 install alibabacloud_dingtalk

二:钉钉开放平台

开发者后台 (dingtalk.com)

基础概念 - 钉钉开放平台 (dingtalk.com)

2.1:创建应用

2.2:获取应用基本信息

2.3:权限申请,获取读写权限

批量申请所需权限

申请查询用户详情权限,否则后面不能根据用户UserID查询用户UnionID的信息。

2.4:创建文档,获取workbookID

在文档的右上角,左键三个点的按钮,在弹出的框中选择文档信息。

获取表格ID

2.5:获取开发者的UserID

进入【钉钉管理后台】成员管理 (dingtalk.com)

在详细信息中,这里的员工UserID就是需要的信息。

2.6:获取AccessToken

获取Acess Token API: API Explorer (dingtalk.com)

根据前面获取到的appKey和appSecret信息,作为API的入参,调用后返回的accessToken就是需要的token。有效期7200秒。

2.7:获取开发者的UnionID

根据UserID,获取开发者的UnionID(OperatorID)。

根据UserID获取UnionID的钉钉API接口: API Explorer (dingtalk.com)

把前面获取到的AccessToken和UserID信息作为入参传入,获取用户UnionID。

2.8:所有基本信息

  • AppID
  • AgentID
  • ClientID(原AppKey和SuiteKey)
  • ClientSecret(原AppSecret和SuiteSecret)
  • WorkBookID
  • UserID
  • UnionID(原OperatorID)
  • AccessToken

相关API调用页面:

API Explorer (dingtalk.com)

获取Acess Token

API Explorer (dingtalk.com)

获取UserID:

成员管理 (dingtalk.com)

根据UserID查询unionid

API Explorer (dingtalk.com)

三:生成代码demo

3.1:安装python模块

pip install alibabacloud_dingtalk

3.2:开发通用模块代码

根据官方提供的Demo,按照不同功能调整成自己的代码。

代码结构如下:

  • conf目录下settings.py是配置文件(配置日志,系统变量等)
  • data目录下token.json是缓存token的文件。
  • utils目录下是通用工具模块
    • conndb.py是连接数据库的模块。
    • decrypt.py是加密解密模块。
    • pyding_workbook.py是调用钉钉文档API接口,操作钉钉文档的模块。
    • AYOGI目录下,(比如rpt_luckbean_daily.py)是业务相关的代码。

  • 代码路径:./utils/pyding_workbook.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @CreateDate : 2024/6/7 下午10:41
# @Author : DBArtist
# @Email : 1572764381@qq.com
# @Project : my-python-scripts
# @ScriptFile : pyding_workbook.py
# @Describe : # pip install alibabacloud_dingtalk
import os
import sys
import json
from datetime import datetime,timedelta
from typing import List
from alibabacloud_dingtalk.doc_1_0.client import Client as dingtalkdoc_1_0Client
from alibabacloud_dingtalk.doc_1_0 import models as dingtalkdoc__1__0_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_tea_util.client import Client as UtilClient
from alibabacloud_dingtalk.oauth2_1_0.client import Client as dingtalkoauth2_1_0Client
from alibabacloud_dingtalk.oauth2_1_0 import models as dingtalkoauth_2__1__0_models
from conf.settings import * class MyWorkbook():
def __init__(self,access_token,workbook_id=WORKBOOK_ID,operator_id=OPERATOR_ID):
self.access_token = access_token
self.workbook_id = workbook_id
self.operator_id = operator_id
self.client = self.create_client() @staticmethod
def create_client() -> dingtalkdoc_1_0Client:
"""
使用 Token 初始化账号Client
@return: Client
@throws Exception
"""
config = open_api_models.Config()
config.protocol = 'https'
config.region_id = 'central'
return dingtalkdoc_1_0Client(config) def get_all_sheets(self):
"""
获取所有的sheets
:return:
"""
get_all_sheets_headers = dingtalkdoc__1__0_models.GetAllSheetsHeaders()
get_all_sheets_headers.x_acs_dingtalk_access_token = self.access_token
get_all_sheets_request = dingtalkdoc__1__0_models.GetAllSheetsRequest(
operator_id=self.operator_id
)
try:
ret = self.client.get_all_sheets_with_options(self.workbook_id, get_all_sheets_request, get_all_sheets_headers, util_models.RuntimeOptions())
wb_sheets_list = ret.body.value
wb_sheets_list_format = []
for wb_sheet in wb_sheets_list:
wb_sheets_list_format.append([wb_sheet.id, wb_sheet.name]) return wb_sheets_list_format
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] get_all_sheets err: {err =}')
pass def check_sheet_exist(self, sheet_name):
"""
检测某个sheet_name是否存在
:param sheet_name:
:return:
"""
all_sheets_list = self.get_all_sheets()
for wb_sheet in all_sheets_list:
if sheet_name in wb_sheet:
return True
return False def create_sheet(self,sheet_name):
"""
创建sheet
:param sheet_name:
:return:
"""
if self.check_sheet_exist(sheet_name):
LOGGER.info(f'[pyding_workbook] create sheet fail, {sheet_name} is already exist.')
return None create_sheet_headers = dingtalkdoc__1__0_models.CreateSheetHeaders()
create_sheet_headers.x_acs_dingtalk_access_token = self.access_token
create_sheet_request = dingtalkdoc__1__0_models.CreateSheetRequest(
operator_id=self.operator_id,
name=sheet_name
)
try:
sheet_obj = self.client.create_sheet_with_options(self.workbook_id, create_sheet_request, create_sheet_headers, util_models.RuntimeOptions()) return sheet_obj
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] create_sheet err: {err =}')
pass def get_sheetid_by_name(self,sheet_name):
"""
根据sheet名称查找对应的sheet_id
:param sheet_name:
:return:
"""
all_sheets = self.get_all_sheets()
for wb_sheet in all_sheets:
if sheet_name in wb_sheet[1]:
return wb_sheet[0]
return False def get_sheet(self,sheet_id=None, sheet_name=None):
"""
获取sheet
:return:
:param self:
:return:
"""
if sheet_id is None and sheet_name is None:
LOGGER.info(f'[pyding_workbook] get sheet fail, sheet_id or sheet_name must atleast be specified.')
return None
if sheet_id is None and sheet_name is not None:
sheet_id = self.get_sheetid_by_name(sheet_name)
if not sheet_id:
LOGGER.info(f'[pyding_workbook] get sheet fail, sheet_name:"{sheet_name}" is not exist.')
return None get_sheet_headers = dingtalkdoc__1__0_models.GetSheetHeaders()
get_sheet_headers.x_acs_dingtalk_access_token = self.access_token
get_sheet_request = dingtalkdoc__1__0_models.GetSheetRequest(
operator_id=self.operator_id
) try:
sheet_obj = self.client.get_sheet_with_options(self.workbook_id, sheet_id,
get_sheet_request, get_sheet_headers, util_models.RuntimeOptions()) return sheet_obj
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] get_sheet err: {err =}')
pass def delete_sheet(self,sheet_id=None,sheet_name=None):
"""
删除sheet
:return:
:param self:
:return:
"""
if sheet_id is None and sheet_name is None:
LOGGER.info(f'[pyding_workbook] delete sheet fail, sheet_id or sheet_name must atleast be specified.')
return None
if sheet_id is None and sheet_name is not None:
sheet_id = self.get_sheetid_by_name(sheet_name)
if not sheet_id:
LOGGER.info(f'[pyding_workbook] delete sheet fail, sheet_name:"{sheet_name}" is not exist.')
return None delete_sheet_headers = dingtalkdoc__1__0_models.DeleteSheetHeaders()
delete_sheet_headers.x_acs_dingtalk_access_token = self.access_token
delete_sheet_request = dingtalkdoc__1__0_models.DeleteSheetRequest(
operator_id=self.operator_id,
)
try:
ret = self.client.delete_sheet_with_options(self.workbook_id, sheet_id, delete_sheet_request, delete_sheet_headers,
util_models.RuntimeOptions())
return ret
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] delete_sheet err: {err =}')
pass def get_range(self,range_address,sheet_id=None,sheet_name=None):
"""
获取单元格信息
:param range_address:
:param sheet_id:
:param sheet_name:
:return:
"""
if sheet_id is None and sheet_name is None:
LOGGER.info(f'[pyding_workbook] get range fail, sheet_id or sheet_name must atleast be specified.')
return None
if sheet_id is None and sheet_name is not None:
sheet_id = self.get_sheetid_by_name(sheet_name)
if not sheet_id:
LOGGER.info(f'[pyding_workbook] get range fail, sheet_name:"{sheet_name}" is not exist.')
return None get_range_headers = dingtalkdoc__1__0_models.GetRangeHeaders()
get_range_headers.x_acs_dingtalk_access_token = self.access_token
get_range_request = dingtalkdoc__1__0_models.GetRangeRequest(
operator_id=self.operator_id
)
try:
ret = self.client.get_range_with_options(self.workbook_id, sheet_id, range_address,
get_range_request, get_range_headers,util_models.RuntimeOptions())
return ret
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] get_range err: {err =}')
pass def clear_range_all(self,range_address,sheet_id=None,sheet_name=None):
"""
清除区域内所有内容(包括数据,格式等。。。)
:return:
"""
if sheet_id is None and sheet_name is None:
LOGGER.info(f'[pyding_workbook] clear range all fail, sheet_id or sheet_name must atleast be specified.')
return None
if sheet_id is None and sheet_name is not None:
sheet_id = self.get_sheetid_by_name(sheet_name)
if not sheet_id:
LOGGER.info(f'[pyding_workbook] clear range all fail, sheet_name:"{sheet_name}" is not exist.')
return None clear_headers = dingtalkdoc__1__0_models.ClearHeaders()
clear_headers.x_acs_dingtalk_access_token = self.access_token
clear_request = dingtalkdoc__1__0_models.ClearRequest(
operator_id=self.operator_id
)
try:
cra_obj = self.client.clear_with_options(self.workbook_id, sheet_id, range_address,
clear_request, clear_headers, util_models.RuntimeOptions())
return cra_obj
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] clear_range_all err: {err =}')
pass def clear_range_data(self,range_address,sheet_id=None,sheet_name=None):
"""
清除单元格所有数据(格式等其他保留)
:param range_address:
:param sheet_id:
:param sheet_name:
:return:
"""
if sheet_id is None and sheet_name is None:
LOGGER.info(f'[pyding_workbook] clear range data fail, sheet_id or sheet_name must atleast be specified.')
return None
if sheet_id is None and sheet_name is not None:
sheet_id = self.get_sheetid_by_name(sheet_name)
if not sheet_id:
LOGGER.info(f'[pyding_workbook] clear range data faile, sheet_name:"{sheet_name}" is not exist.')
return None clear_data_headers = dingtalkdoc__1__0_models.ClearDataHeaders()
clear_data_headers.x_acs_dingtalk_access_token = self.access_token
clear_data_request = dingtalkdoc__1__0_models.ClearDataRequest(
operator_id=self.operator_id
)
try:
crd_obj = self.client.clear_data_with_options(self.workbook_id, sheet_id, range_address,
clear_data_request, clear_data_headers,util_models.RuntimeOptions())
return crd_obj
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] clear_range_data err: {err =}')
pass def update_range(self,range_address,range_values,sheet_id=None,sheet_name=None,**kwargs):
"""
更新单元格信息,包括单元格中的值、背景色、超链接等。
:param range_address:
:param sheet_id:
:param sheet_name:
:return:
"""
if sheet_id is None and sheet_name is None:
LOGGER.info(f'[pyding_workbook] update range fail, sheet_id or sheet_name must atleast be specified.')
return None
if sheet_id is None and sheet_name is not None:
sheet_id = self.get_sheetid_by_name(sheet_name)
if not sheet_id:
LOGGER.info(f'[pyding_workbook] update range fail, sheet_name:"{sheet_name}" is not exist.')
return None update_range_headers = dingtalkdoc__1__0_models.UpdateRangeHeaders()
update_range_headers.x_acs_dingtalk_access_token = self.access_token
update_range_request = dingtalkdoc__1__0_models.UpdateRangeRequest(
operator_id=self.operator_id,
values=range_values,
**kwargs
)
try:
# print(self.workbook_id, sheet_id, range_address,update_range_request, update_range_headers)
upt_ret = self.client.update_range_with_options(self.workbook_id, sheet_id, range_address,
update_range_request, update_range_headers, util_models.RuntimeOptions()) return upt_ret
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] update_range err: {err =}')
pass class MyToken:
def __init__(self):
pass @staticmethod
def create_client() -> dingtalkoauth2_1_0Client:
"""
使用 Token 初始化账号Client
@return: Client
@throws Exception
"""
config = open_api_models.Config()
config.protocol = 'https'
config.region_id = 'central'
return dingtalkoauth2_1_0Client(config) @staticmethod
def create_token(app_key=APP_KEY,app_secret=APP_SECRET) -> None:
"""
创建token
:param app_key:
:param app_secret:
:return:
"""
client = MyToken.create_client()
get_access_token_request = dingtalkoauth_2__1__0_models.GetAccessTokenRequest(
app_key=app_key,
app_secret=app_secret
)
try:
ret_token_obj = client.get_access_token(get_access_token_request)
return ret_token_obj
except Exception as err:
if not UtilClient.empty(err.code) and not UtilClient.empty(err.message):
# err 中含有 code 和 message 属性,可帮助开发定位问题
ERROR_LOGGER.error(f'[pyding_workbook] create_token err: {__qualname__} {err=}')
pass @staticmethod
def get_token(file_path,flush=False):
"""
获取token
1:先从本地文件读取,如果没有本地文件,则执行创建token,获取并存储在本地。
2:如果本地文件读取到数据,再判断是否过期,如果过期,则执行创建token,获取并存储在本地。
3:如果本地文件读取到数据,没有过期,则直接返回本地文件中存储的token信息
:param save_path:
:return:
""" ## 本地文件不存在或者需要强制刷新,则获取再存储到本地
if not os.path.isfile(file_path) or flush:
LOGGER.info(f"[pyding_workbook] {file_path} is not exists, begin to create new token file.")
# 创建获取token : 76eb8496841b3155850e126ad13a8a46
token_obj = MyToken.create_token(app_key=APP_KEY,app_secret=APP_SECRET)
access_token = token_obj.body.access_token
expire_in = token_obj.body.expire_in
expire_at = datetime.now() + timedelta(seconds=expire_in)
expire_at_str = expire_at.isoformat() tokenData = {
'access_token': access_token,
'expire_in': expire_in,
'expire_at': expire_at_str
} MyToken.save_token(file_path,tokenData)
return access_token # 本地文件存在,则读取本地json文件
data = MyToken.read_token(file_path)
access_token_local=data['access_token']
expire_at_str_local=data['expire_at']
expire_at_local = datetime.fromisoformat(expire_at_str_local) if expire_at_local < datetime.now():
# 创建获取token : 76eb8496841b3155850e126ad13a8a46
token_obj = MyToken.create_token(app_key=APP_KEY,app_secret=APP_SECRET)
access_token_new = token_obj.body.access_token
expire_in_new = token_obj.body.expire_in
expire_at_new = datetime.now() + timedelta(seconds=expire_in_new)
expire_at_new_str = expire_at_new.isoformat() tokenData = {
'access_token': access_token_new,
'expire_in': expire_in_new,
'expire_at': expire_at_new_str
} MyToken.save_token(file_path,tokenData)
LOGGER.info(f"[pyding_workbook] Token expired, regenerate.")
return access_token_new
else:
return access_token_local @staticmethod
def save_token(file_path,token_data):
# 将字典数据写入到JSON文件中
with open(file_path, 'w', encoding='utf-8') as file:
# 使用json.dump()写入数据,indent参数用于美化输出,表示缩进的空格数
json.dump(token_data, file, indent=4, ensure_ascii=False)
LOGGER.info(f"[pyding_workbook] Token data save to {file_path}")
return token_data['access_token'] @staticmethod
def read_token(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)
LOGGER.info(f"[pyding_workbook] Token data read from {file_path}")
return data def create_range_address(row_len, col_len, start_row=2,start_col="A"):
"""
根据根据传入的起始值,及数据行,列数。获取单元格区间
比如: 从A2开始,3行4列的数据,单元格区间是A2:D4
:param row_len:
:param col_len:
:param start_row:
:param start_col:
:return:
"""
if row_len <= 0 or col_len <= 0:
return None if not isinstance(start_col, str) or not (1 <= len(start_col) <= 2) or not start_col.isalpha() or not start_col.isupper():
raise ValueError("起始列名必须是1或2位大写字母。") if start_row < 2:
raise ValueError("起始行数必须是大约等于2。") end_col = get_end_column_name(start_col,col_len - 1)
end_row = start_row + row_len - 1 range_address = f"{start_col}{start_row}:{end_col}{end_row}"
return range_address def get_end_column_name(start_col, num_cols):
"""
# 将列名转换为数字,每个字母对应26的幂次。
# 举例传入(A,3),表示从A列开始往后3列,结束列就是C。
:param start_col:
:param num_cols:
:return:
"""
def col_to_num(col):
return sum((26 ** i) * (ord(char) - ord('A') + 1) for i, char in enumerate(reversed(col))) # 将数字转换回列名
def num_to_col(num):
col_name = ''
while num > 0:
num, rem = divmod(num - 1, 26)
col_name = chr(rem + ord('A')) + col_name
return col_name # 计算起始列的数字表示
start_num = col_to_num(start_col) # 计算结束列的数字表示
end_num = start_num + num_cols # 将结束列的数字表示转换回列名
return num_to_col(end_num) def get_last_column_name(total_columns, start_column='A'):
"""
根据传入的列数量,起始列名,获取最后一列的列名。
比如:传入2,最后一列列名是B;
传入12,最后一列列名是L;
默认都是从A列开始计算。
:param total_columns: 总列数
:param start_column: 开始列名或列序号
:return:
"""
if total_columns < 1:
raise ValueError("Invalid column number") def column_index(column_name):
"""Convert a column name to a 1-based index."""
if isinstance(column_name, str):
result = 0
for i, char in enumerate(reversed(column_name.upper())):
result += (ord(char) - 64) * (26 ** i)
return result
elif isinstance(column_name, int) and 1 <= column_name <= 702:
# Excel columns can go up to 'XFD'
# Excel 2007 及以后版本支持的最大列数(16,384 列)
return column_name
else:
raise ValueError("start_column must be a column letter (A-XFD) or a positive integer between 1 and 702") start_index = column_index(start_column)
adjusted_columns = total_columns + start_index - 1 # 计算基于起始列的总列索引 column_name = ""
while adjusted_columns > 0:
adjusted_columns -= 1 # 转换为从0开始的索引
remainder = adjusted_columns % 26
column_name = chr(65 + remainder) + column_name
adjusted_columns //= 26 return column_name if __name__ == '__main__':
pass
# ## 1: 获取access_token
# access_token = MyToken.get_token('./data/token.json')
# # print(f'{access_token =}')
#
# ## 生成实例对象
# mysample = MyWorkbook(access_token,WORKBOOK_ID,OPERATOR_ID)
#
# ## 2: 获取所有sheets列表
# all_sheets = mysample.get_all_sheets()
# print(f'{all_sheets = }')
#
# # for wb_sheet in all_sheets:
# # wb_sheet_id = wb_sheet[0]
# # wb_sheet_name = wb_sheet[1]
# # print(f'[{wb_sheet_id =} ,{wb_sheet_name=}]')
# #
# # ## 3: 删除每个sheets(必须保留最少一个sheet,不能全部都删除完)
# # mysample.delete_sheet(wb_sheet_id)
#
# # 3: 新创建sheet
# sheet_name = 'new_sheet2'
# cs_obj = mysample.create_sheet(sheet_name)
# if not cs_obj:
# print('create sheet fail.')
# else:
# print(f'{cs_obj.body.id =},{cs_obj.body.name =},{cs_obj.body.visibility =}')
#
# # 4: 删除指定sheet
# ds_obj = mysample.delete_sheet(None, 'new_sheet2')
# if not ds_obj:
# print('delete sheet fail.')
# else:
# print(f'delete sheet status: {ds_obj.body}')
#
# # 5: 查询指定sheet
# gs_obj = mysample.get_sheet(None,'new_sheet1')
# if not gs_obj:
# print('get sheet fail.')
# else:
# print(f'get sheet msg: {gs_obj.body}')
#
#
# # 6: 查询区域内容
# ret = mysample.get_range("A1:B3", None,'new_sheet1')
# if not ret:
# print('get range fail.')
# else:
# # print(f'{ret.body}')
# print(f'{ret.body.values}')
# # print(f'{ret.body.background_colors}')
# # print(f'{ret.body.display_values}')
# # print(f'{ret.body.font_sizes}')
# # print(f'{ret.body.font_weights}')
# # print(f'{ret.body.formulas}')
# # print(f'{ret.body.horizontal_alignments}')
# # print(f'{ret.body.vertical_alignments}')
#
# # 7: 清除区域内所有数据
# range_address = "A2:B3"
# sheet_id = None
# sheet_name = 'new_sheet1'
# ret = mysample.clear_range_data(range_address,sheet_id,sheet_name)
# if not ret:
# print(f'clear range data fail.')
# else:
# print(f'clear range data success: {ret}')
#
# # 8: 清除区域内所有数据+格式
# range_address = "A2:B3"
# sheet_id = None
# sheet_name = 'new_sheet1'
# ret = mysample.clear_range_all(range_address,sheet_id,sheet_name)
# if not ret:
# print(f'clear range all fail.')
# else:
# print(f'clear range all success: {ret}')
#
#
# # 9: 更新单元格内容
# range_address = "A1:B3"
# range_values = [["1.21", "3.31"], ["3.43", "4.54"], ["5.65", "6.02"]]
# sheet_id = None
# sheet_name = 'new_sheet1'
# ret = mysample.update_range(range_address, range_values, sheet_id, sheet_name,number_format="#,##0.00")
# if not ret:
# print(f'update range fail.')
# else:
# print(f'update range success: {ret.body.a_1notation}')
#
# # 10: 再次查询区域内容
# ret = mysample.get_range(range_address,sheet_id, sheet_name)
# if not ret:
# print('get range fail.')
# else:
# # print(f'{ret.body}')
# print(f'get range success: {ret.body.values}')

说明:pyding_workbook.py就是调用钉钉API的接口核心代码。

其它代码就根据业务调用相关API接口即可。

settings.py文件主要配置变量:

APP_KEY = "xxx"
APP_SECRET = "xxx"
WORKBOOK_ID = "xxx"
OPERATOR_ID = "xxx" ## unionid

token.json格式如下

{
"access_token": "xxx",
"expire_in": 7200,
"expire_at": "2024-07-04T16:40:25.944534"
}

数字格式:

名称 数字格式 示例
常规 "General"
文本 "@"
数字 "#,##0" 1,234
数字(小数点) "#,##0.00" 1,234.56
百分数 "0%" 12%
百分数(小数点) "0.00%" 12.34%
科学计数 "0.00E+00" 1.01E+03
人民币 "¥#,##0" ¥1,234
人民币(小数点) ¥#,##0.00" ¥1,234.56
美元 "$#,##0" $1,234
美元(小数点) "$#,##0.00" $1,234.56
日期 "yyyy/m/d" 2022/1/1
日期(中文) "yyyy年m月d日" 2022年1月1日
日期(中文年月) "yyyy年m月" 2022年1月
时间 "hh:mm:ss" 00:00:00
日期时间 "yyyy/m/d hh:mm:ss" 2022/1/1 00:00:00

单元格的值,根据Range地址范围传参,格式为二维数组。

详情请参考如下示例:

Range地址范围有几行,该参数二维数组内就有几个元素;Range地址范围内有几列,该参数二维数组每个元素内就有几个值。

示例1:Range地址为A1:B3,范围内是一个三行两列的表格,该参数值格式如下: { "values": [ ["1", "2"], ["3", "4"], ["5", "6"] ] } 示例2:Range地址为A1:C3,范围内是一个三行三列的表格,该参数值格式如下: { "values": [ ["1","2","3"], ["4","5","6"], ["7","8","9"] ] }

背景色,颜色的16进制值,根据Range地址范围传参,格式为二维数组。详情请参考如下示例:

Range地址范围有几行,该参数二维数组内就有几个元素;Range地址范围内有几列,该参数二维数组每个元素内就有几个值。

示例1:Range地址为A1:B3,范围内是一个三行两列的表格,该参数值格式如下: { "backgroundColors": [ ["#ff0000", "#00ff00"], ["#f0f0f0", "#0000ff"], ["#f0f0f0", "#0000ff"] ] }

示例2:Range地址为A1:C3,范围内是一个三行三列的表格,该参数值格式如下: { "backgroundColors": [ ["#ff0000","#ff0000","#ff0000"], ["#ff0000","#ff0000","#ff0000"], ["#ff0000","#ff0000","#ff0000"] ] }

超链接,根据Range地址范围传参,格式为二维数组。详情请参考如下示例:

Range地址范围有几行,该参数二维数组内就有几个元素;

Range地址范围内有几列,该参数二维数组每个元素内就有几个值。

示例1:Range地址为A1:B3,范围内是一个三行两列的表格,该参数值格式如下:

{ "hyperlinks": [ [ { "type": "path", "link": "https://www.dingtalk.com", "text": "test" }, { "type": "sheet", "link": "Sheet2", "text": "测试" } ], [ { "type": "range", "link": "Sheet2!A4", "text": "test" }, { "type": "path", "link": "https://www.dingtalk.com", "text": "测试" } ], [ { "type": "range", "link": "Sheet2!A4", "text": "2" }, { "type": "sheet", "link": "Sheet2", "text": "测试" } ] ] }

示例2:Range地址为A1:C3,范围内是一个三行三列的表格,该参数值格式如下:

{ "hyperlinks": [ [ { "type": "path", "link": "https://www.dingtalk.com", "text": "test" }, { "type": "sheet", "link": "Sheet2", "text": "测试" },{ "type": "path", "link": "https://www.dingtalk.com", "text": "test" } ], [ { "type": "range", "link": "Sheet2!A4", "text": "test" }, { "type": "path", "link": "https://www.dingtalk.com", "text": "测试" },{ "type": "path", "link": "https://www.dingtalk.com", "text": "test" } ], [ { "type": "range", "link": "Sheet2!A4", "text": "2" }, { "type": "sheet", "link": "Sheet2", "text": "测试" },{ "type": "path", "link": "https://www.dingtalk.com", "text": "test" } ] ] }

更新清除区域格式

更新单元格区域 - 钉钉开放平台 (dingtalk.com)

清除单元格区域内所有内容 - 钉钉开放平台 (dingtalk.com)

钉钉应用开发-Python操作钉钉文档(excel版)的更多相关文章

  1. 使用sphinx快速为你python注释生成API文档

    sphinx简介sphinx是一种基于Python的文档工具,它可以令人轻松的撰写出清晰且优美的文档,由Georg Brandl在BSD许可证下开发.新版的Python3文档就是由sphinx生成的, ...

  2. 使用sphinx为python注释生成docAPI文档

    sphinx简介 sphinx是一种基于Python的文档工具,它可以令人轻松的撰写出清晰且优美的文档,由Georg Brandl在BSD许可证下开发. 新版的Python3文档就是由sphinx生成 ...

  3. python快速生成注释文档的方法

    python快速生成注释文档的方法 今天将告诉大家一个简单平时只要注意的小细节,就可以轻松生成注释文档,也可以检查我们写的类方法引用名称是否重复有问题等.一看别人专业的大牛们写的文档多牛多羡慕,不用担 ...

  4. jquery.cookie 使用文档,$.cookie() 文档教程, js 操作 cookie 教程文档。

    jquery.cookie 使用文档,$.cookie() 文档教程, js 操作 cookie 教程文档. jquery.cookie中的操作: jquery.cookie.js是一个基于jquer ...

  5. 传智播客C/C++各种开发环境搭建视频工具文档免费教程

    传智播客作为中国IT培训的领军品牌,一直把握技术趋势,给大家带来最新的技术分享!传智播客C/C++主流开发环境免费分享视频文档中,就有写一个helloworld程序的示范.火速前来下载吧 所谓&quo ...

  6. 找到python官方标准库文档

    python中有很多标准库.我们没法记住全部标准库,但是可以在:https://docs.python.org/3/py-modindex.html 中查看标准库的索引 在python的官方文档中,如 ...

  7. 【DevOps敏捷开发动手实验】开源文档 v2015.2 stable 版发布

    Team Foundation Server 2015 Update 2版本终于在2周前的//Build 2016大会上正式发布了,借这个东风,小编也完成了[DevOps敏捷开发动手实验]开源文档的第 ...

  8. python常用模块-配置文档模块(configparser)

    python常用模块-配置文档模块(configparser) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. ConfigParser模块用于生成和修改常见配置文档,当前模块的名称 ...

  9. Python批量创建word文档(2)- 加图片和表格

    Python创建word文档,任务要求:小杨在一家公司上班,每天都需要给不同的客户发送word文档,以告知客户每日黄金价格.要求在文档开始处给出banner条,价格日期等用表格表示.最后贴上自己的联系 ...

  10. Python批量创建word文档(1)- 纯文字

    Python创建word文档,任务要求:小杨在一家公司上班,每天都需要给不同的客户发送word文档,以告知客户每日黄金价格.最后贴上自己的联系方式.代码如下: 1 ''' 2 #python根据需求新 ...

随机推荐

  1. 【爬虫+数据清洗+可视化分析】Python舆情分析哔哩哔哩"狂飙"的评论

    目录 一.背景介绍 二.爬虫代码 2.1 展示爬取结果 2.2 爬虫代码讲解 三.可视化代码 3.1 读取数据 3.2 数据清洗 3.3 可视化 3.3.1 IP属地分析-柱形图 3.3.2 评论时间 ...

  2. Seata原理浅析

    前言 Seata是阿里开源的分布式事务解决方案,本文将详细介绍 Seata 的事务模式.原理以及使用.了解之前需清楚什么是分布式事务. 一.什么是 Seata Seata 是一款开源的分布式事务解决方 ...

  3. 官宣:Splashtop与JumpCloud合作 提供单次登录远程访问解决方案

    号外! 官宣:Splashtop与JumpCloud合作 提供单次登录远程访问解决方案! 打开百度APP,查看更多高清图片 以下是一本正经的官宣新闻,我是没感情的翻译机器人,嘻嘻. Bad Robot ...

  4. Web3连接以太网

    1. Infura Infura 是一种托管服务,提供对各种区块链网络的安全可靠访问,消除了管理区块链基础设施的复杂性,使开发者能够专注于构建创新的 Web3 应用程序. Infura 作为连接应用程 ...

  5. Unraid 使用 Docker Compose 安装 Immich 套件无法启用人脸识别的原因及修复方法

    原因 问题原因是官方教程中的 docker-compose.yml 指明的机器学习组件 immich-machine-learning 中的 container_name 与 也就是 docker-c ...

  6. ETSI GS MEC 013,UE 位置 API

    目录 文章目录 目录 版本 功能理解 Relation with OMA APIs Relation with OMA API for Zonal Presence Relation with OMA ...

  7. k8s证书相关

    1.cfssl 字签证书 查看证书 可以使用以下命令查询CFSSL证书是否过期: 复制代码   cfssl certinfo -cert <certificate_file> 其中,< ...

  8. # [NOIP2011 提高组] 铺地毯

    传送锚点:https://www.luogu.com.cn/problem/P1003 题目描述 为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形 ...

  9. mac for docker访问宿主机服务

    转载链接 https://blog.csdn.net/weixin_33860528/article/details/91461648

  10. linux中磁盘清理方法(简单好用)

    文章目录1.命令2.df参数说明3.find参数说明4.清理日志文件1.命令先来看解决办法 df -h --显示当前磁盘使用情况cd / --cd到要清理文件的路径下面find . -type f - ...