钉钉应用开发-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. Pytorch param.grad.data. 出现 AttributeError: ‘NoneType‘ object has no attribute ‘data‘

    程序中有需要优化的参数未参与前向传播.

  2. k8s-nginx实战部署1

    目录 yaml 资源清单 run_deploy.sh .gitlab-ci.yml yaml 资源清单 deploy.yaml apiVersion: v1 kind: ConfigMap metad ...

  3. 等保测评--postgresql修改默认超级用户,建立普通用户使用

    1.postgresql权限说明 SELECT INSERT UPDATE DELETE TRUNCATE REFERENCES TRIGGER CREATE CONNECT TEMPORARY EX ...

  4. C 语言编程 — 头文件

    目录 文章目录 目录 前文列表 头文件 只引用一次头文件 有条件引用 global.h 前文列表 <程序编译流程与 GCC 编译器> <C 语言编程 - 基本语法> <C ...

  5. PageOffice在线打开编辑Word文件获取指定区域的数据并且保存整篇文件

    一.首先在word文件中给需要在后台获取数据的区域设置以PO_开头的书签. 二.通过pageoffice在线打开文件并编辑保存.有两种打开文件的模式 1.普通编辑模式(docNormalEdit) 普 ...

  6. java启动参考

    启动参数 mvn clean package -Dmaven.test.skip=true -Ptest - java - -server - -Xms2G - -Xmx2G - -Xss256K - ...

  7. sqlerver 报错5120 无法为该请求检索数据 系统找不到指定路径

    背景: 数据库mdf文件所在盘符F盘被删除了,也就是文件不存在了,sqlserver管理器打开就报错5120,并且正常路径的数据库也不显示出来. 要让正常的数据库显示出来,就需要删除掉已经没有的数据库 ...

  8. CSS——样式继承

    CSS的样式表继承指的是,特定的CSS属性向下传递到子孙元素.总的来说,一个HTML文档就是一个家族,然后html元素有两个子元素,相当于它的儿子,分别是head和body,然后body和head各自 ...

  9. WPF开发快速入门【6】下拉框与枚举类型

    概述 本文讲述下拉框和枚举类型进行绑定的一些操作. 下拉框的基本操作 设计部分: <ComboBox ItemsSource="{Binding Fruits}" Selec ...

  10. 腾讯蓝鲸平台部署v5.1版本[去坑]

    腾讯蓝鲸平台部署 1. 环境准备 #1. 基础优化 ulimit -SHn 655360 yum remove mysql-devel -y && yum install mysql- ...