开发指南
如果您需要把报表集成展示到您的业务系统中,或是需要在您的业务系统里集中管理报表,都可以基于数颜报表开发包,进行报表应用程序的开发,通过新增或改造您的业务系统功能,来实现您的需求。
数颜报表开发包提供了脚本库和开放接口,从官方渠道获得脚本库,数颜报表浏览器内置提供开放接口。
我们强烈推荐:应该让您的前端程序调用数颜脚本库和您的后端接口,您的后端接口调用数颜开放接口。

数颜脚本库中提供了报表组件和函数,报表组件可以完美呈现报表,在呈现报表时会调用四个后端接口:获取报表列表、获取报表模板文件、获取报表数据、获取报表图片,您的后端接口程序至少应该提供这四个接口,由您的后端接口调用数颜开放接口中对应的四个接口,最终支持和实现报表渲染功能。
可供您免费使用的开发资源
数颜脚本库:shuyan-js-lib
shuyan-js-lib/style.css
shuyan-js-lib/show.js
请在代码中引用上述两个文件,就可以使用脚本库中的报表组件,在您的页面中,完美呈现报表。
数颜开放接口:shuyan-open-api
请求地址
请求地址 = 数颜报表浏览器根地址 + 接口地址
若根地址是http://192.168.1.8:9090,接口地址是/open/api/v1/report/data/get
则请求地址是http://192.168.1.8:9090/open/api/v1/report/data/get
请求方式
POST:基于HTTP协议、POST方法发起请求,调用接口。
请求参数
请求参数全部放入请求体(body),数据格式采用JSON。
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa"
}
每个请求都必须携带以上三个参数:登录人员编号、访问KEY、保密KEY。
人员是您的业务系统中的操作人员(或用户),是身份验证和数据授权的主体对象。
人员与数颜报表浏览器中的用户是一对一的关系,通过用户的人员编号建立起关系。
人员编号是业务系统操作人员的唯一标识,也是数颜报表浏览器中用户的人员编号。
访问KEY和保密KEY是数颜报表浏览器提供的,您可以在配置文件中找到和修改他们。
配置文件位置:安装目录\shuyan\apps\report-browser\config\application.properties。
请求响应
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
每个请求返回的数据格式是JSON,都会携带以上三个属性:状态码、消息、数据。
状态码:100-成功,其他值都是失败,导致失败的原因请查看消息。
消息:操作成功的提示,或操作失败的原因。
数据:操作成功或失败时返回的数据。
接口说明
- 获取报表列表:/open/api/v1/report/list/get
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa"
}
请求参数:登录人员编号、访问KEY、保密KEY。
{
"statusCode": 100,
"message": "操作成功!",
"data": [
{
"id": "1",
"type": 4,
"name": "目录A",
"sortNum": 0,
"children": [
{
"id": "4",
"type": 4,
"name": "目录B",
"sortNum": 0
},
{
"id": "2",
"type": 1,
"name": "报表1",
"sortNum": 0
},
{
"id": "3",
"type": 1,
"name": "报表2",
"sortNum": 2
}
]
},
{
"id": "5",
"type": 4,
"name": "目录C",
"sortNum": 0
}
]
}
请求返回:状态码、消息、数据,若状态码是成功,数据是json,是当前登录人员有权访问的全部报表。
数据(data):以树结构包装数据,报表、目录都是树节点,有编号、类型、名称、序号、子节点属性。
- 获取报表模板文件:/open/api/v1/report/template/file/get
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"id": "26277974359",
"imageUrl": "/your/web/api/v1/report/image/get"
}
请求参数:登录人员编号、访问KEY、保密KEY、报表编号、图片地址。
报表编号是报表的唯一标识,图片地址是您的后端接口中获取报表图片接口的地址。
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是json字符串,是报表模板文件的内容。
- 获取报表图片:/open/api/v1/report/image/get
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"id": "62779749354",
"type": "jpg"
}
请求参数:登录人员编号、访问KEY、保密KEY、图片编号、图片类型。
图片编号是报表设计时生成的,被存储到报表模板文件中。
图片类型是报表设计时确定的,被存储到报表模板文件中。
当报表中使用了图片,在呈现报表时,就需要调用本接口获得图片数据。
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是Base64字符串,是图片数据内容。
- 获取报表地图:/open/api/v1/report/map/get
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"fileName": "china.json"
}
请求参数:登录人员编号、访问KEY、保密KEY、文件名称。
文件名称是指存储地图数据的文件名称,存储格式是 geojson。
当报表中使用了地图,在呈现报表时,就需要调用本接口获得地图数据。
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是 geojosn 字符串,是地图数据内容。
- 获取报表数据:/open/api/v1/report/data/get
{
"loginPersonId": "001",
"loginPersonPropertyList": ["dept_id:501","dept_name:行政部"],
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"id": "126277974935912",
"parameters": "{year:2019}"
}
请求参数:登录人员编号、登录人员属性列表、访问KEY、保密KEY、报表编号、查询参数。
查询参数是前端输入的查询条件,是一个json字符串。
登录人员属性列表是您的后端程序向报表引擎提供的与人员相关的环境变量,是字符串数组。
每个人员属性按 ‘属性名:属性值’ 的格式构成一个字符串,放入登录人员属列表中。
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是json字符串,是报表数据的内容。
- 获取用户列表:/open/api/v1/user/list/get
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa"
}
请求参数:登录人员编号、访问KEY、保密KEY。
{
"statusCode": 100,
"message": "操作成功!",
"data": [
{
"id": "1",
"name": "管理员",
"account": "admin",
"personId": "001",
"personName": "李世民",
"personProperties": "dept_id:501;dept_name:董事会",
"status": 1,
"remark": "内置"
}
]
}
请求返回:状态码、消息、数据,若状态码是成功,数据是数颜报表浏览器中的全部用户。
- 获取角色列表:/open/api/v1/role/list/get
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa"
}
请求参数:登录人员编号、访问KEY、保密KEY。
{
"statusCode": 100,
"message": "操作成功!",
"data": [
{
"id": "1",
"code": "admin",
"name": "管理员"
}
]
}
请求返回:状态码、消息、数据,若状态码是成功,数据是数颜报表浏览器中的全部角色。
- 添加用户:/open/api/v1/user/add
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"name": "zhangsan",
"account": "zs",
"personId": "002",
"personName": "张三",
"personProperties": "dept_id:502",
"roleIds": "1,2,3",
"remark": "业务系统添加的用户"
}
请求参数:登录人员编号、访问KEY、保密KEY、用户名、账号、人员编号、姓名、属性、角色、备注。
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是当前添加用户的人员编号。
- 移除用户:/open/api/v1/user/remove
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"personId": "002"
}
请求参数:登录人员编号、访问KEY、保密KEY、人员编号
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是当前移除用户的人员编号。
- 更新用户:/open/api/v1/user/update
{
"loginPersonId": "001",
"accessKey": "imzOrb8vRC47o",
"secretKey": "oicMxdLoVWSxb2mhiVGosxy9aIoa",
"personId": "002",
"personName": "张三",
"personProperties": "dept_id:502",
"roleIds": "1,2,3",
"status":"1",
"remark": "业务系统添加的用户"
}
请求参数:登录人员编号、访问KEY、保密KEY、人员编号、姓名、属性、角色、状态、备注。
状态(status):0-已停用,1-已启用。
{
"statusCode": 100,
"message": "操作成功!",
"data": ""
}
请求返回:状态码、消息、数据,若状态码是成功,数据是当前更新用户的人员编号。
在您的业务系统中集成展示报表
前端开发:基于 shuyan-js-lib
- 基于 nodejs 开发环境,新建项目,或在您的 nodejs 项目中,执行下列命令
npm install vue@3.3.4 --save
npm install axios@1.4.0 --save
npm install element-plus@2.3.9 --save
- 在 main.ts 中写入下列代码
import "./assets/main.css";
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import "element-plus/dist/index.css";
import ElementPlus from "element-plus";
import zhCn from "element-plus/dist/locale/zh-cn.min.mjs";
const app = createApp(App);
app.use(router);
app.use(ElementPlus, { locale: zhCn });
app.mount("#app");
- 在 http.ts 中写入下列代码
import axios, {
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse,
} from "axios";
function createPostInstance(config: AxiosRequestConfig): AxiosInstance {
return axios.create({
method: "post",
timeout: 30000,
baseURL: "",
...config,
});
}
const useResponseInterceptor = (service: AxiosInstance): void => {
service.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
return Promise.reject(error);
}
);
};
function post(url: string, data: any): Promise<AxiosResponse> {
const config = {
url: url,
data: data,
};
const instance = createPostInstance(config);
useResponseInterceptor(instance);
return instance(config);
}
const http = {
post,
};
export { http };
- 在 App.vue 中写入下列代码
<script setup>
import { ref } from 'vue'
import { http } from './http'
import '../lib/style.css'
import { Report, hasDefaultQuery, getDefaultQuery, addStaticParameters, showPrintDialog } from '../lib/show'
const showReport = ref(false)
const reportkey = ref(0)
const reportTemplateFile = ref({})
const reportTemplateFileId = ref('65945355')
const reportDataUrl = '/api/v1/report/data/get'
const reportTemplateFileUrl = '/api/v1/report/template/file/get'
const reportData = ref({
tables: [{ id: '' }],
parameters: []
})
let k = 0
const newReportKey = () => {
return k++
}
const dataLoader = (request, onSuccess, onError) => {
http.post(reportDataUrl, request).then((response) => {
const r = JSON.parse(response)
if (onSuccess) {
onSuccess(r)
} else {
reportData.value = r.data
reportkey.value = newReportKey()
showReport.value = true
}
}).catch((error) => {
if (onError) {
onError(error)
}
})
}
const loadData = (parameterList) => {
if (hasDefaultQuery(reportTemplateFile.value)) {
let query = getDefaultQuery(reportTemplateFile.value);
if (query) {
addStaticParameters(query, parameterList);
}
}
let request = { id: reportTemplateFileId.value, parameters: JSON.stringify(parameterList) }
dataLoader(request, null, null);
}
const load = () => {
const request = { id: reportTemplateFileId.value }
http.post(reportTemplateFileUrl, request).then((response) => {
const r = JSON.parse(response)
reportTemplateFile.value = JSON.parse(r.data)
let parameterList = {}
loadData(parameterList)
})
}
load();
</script>
<template>
<div>
<el-button @click="showPrintDialog">打印报表</el-button>
</div>
<div style="height: 800px;overflow: hidden;margin: 10px;">
<Report v-if="showReport" :key="reportkey" :file="reportTemplateFile" :data="reportData"
:file-id="reportTemplateFileId" :data-loader="dataLoader">
</Report>
</div>
</template>
后端开发:基于 shuyan-open-api
后端不限制开发语言和开发环境,我们将使用 java 和 spring-boot,演示后端代码的开发过程。
提示:在您的业务系统中集成展示报表需要您的后端接口程序调用以下五个开放接口。
/open/api/v1/report/list/get
/open/api/v1/report/template/file/get
/open/api/v1/report/data/get
/open/api/v1/report/image/get
/open/api/v1/report/map/get
- 新建项目,或在您的项目中,添加类文件
ApiV1Controller.java
OpenApiClient.java
Request.java
GetReportTemplateFileRequest.java
GetReportDataRequest.java
GetReportImageRequest.java
GetReportMapRequest.java
DownloadFileHandler.java
AddUserRequest.java
RemoveUserRequest.java
UpdateUserRequest.java
- 在 ApiV1Controller.java 中写入下列代码
package your.web.app;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
@RestController
@RequestMapping("/api/v1")
public class ApiV1Controller {
@Autowired
private OpenApiClient openApiClient;
@PostMapping("/report/list/get")
public String getReportList() {
Request request = new Request();
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
String response = openApiClient.post("report/list/get", request);
return response;
}
@PostMapping("/report/template/file/get")
public String getReportTemplateFile(@RequestBody GetReportTemplateFileRequest request) {
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
request.setMapUrl("/api/v1/report/map/get");
request.setImageUrl("/api/v1/report/image/get");
String response = openApiClient.post("report/template/file/get", request);
return response;
}
@PostMapping("/report/data/get")
public String getReportData(@RequestBody GetReportDataRequest request) {
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
//当前登录人员ID:会被报表引擎视作环境变量
request.setLoginPersonId("001");
//当前登录人员的属性列表:会被报表引擎视作环境变量
request.setLoginPersonPropertyList(new ArrayList<>());
request.getLoginPersonPropertyList().add("dept_id:501");
String response = openApiClient.post("report/data/get", request);
return response;
}
@GetMapping("/report/image/get")
public void getReportImage(@RequestParam("id") String id, @RequestParam("type") String type, HttpServletResponse servletResponse) {
GetReportImageRequest request = new GetReportImageRequest();
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
request.setId(id);
request.setType(type);
String response = openApiClient.post("report/image/get", request);
Map<String, Object> map = JSON.parseObject(response, HashMap.class);
String fileName = id + "." + type;
byte[] data = Base64.getDecoder().decode(map.get("data").toString());
DownloadFileHandler.output(fileName, servletResponse, data);
}
@GetMapping("/report/map/get")
public void getReportMap(@RequestParam("fileName") String fileName, HttpServletResponse servletResponse) {
GetReportMapRequest request = new GetReportMapRequest();
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
request.setFileName(fileName);
String response = openApiClient.post("report/map/get", request);
Map<String, Object> map = JSON.parseObject(response, HashMap.class);
byte[] data = map.get("data").toString().getBytes();
DownloadFileHandler.output(fileName, servletResponse, data);
}
@PostMapping("/user/list/get")
public String getUserList() {
Request request = new Request();
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
String response = openApiClient.post("user/list/get", request);
return response;
}
@PostMapping("/role/list/get")
public String getRoleList() {
Request request = new Request();
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
String response = openApiClient.post("role/list/get", request);
return response;
}
@PostMapping("/user/add")
public String addUser(@RequestBody AddUserRequest request) {
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
String response = openApiClient.post("user/add", request);
return response;
}
@PostMapping("/user/remove")
public String removeUser(@RequestBody RemoveUserRequest request) {
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
String response = openApiClient.post("user/remove", request);
return response;
}
@PostMapping("/user/update")
public String updateUser(@RequestBody UpdateUserRequest request) {
request.setAccessKey(openApiClient.getAccessKey());
request.setSecretKey(openApiClient.getSecretKey());
request.setLoginPersonId("001");
String response = openApiClient.post("user/update", request);
return response;
}
}
- 在 OpenApiClient.java 中写入下列代码
请在数颜报表浏览器应用程序配置文件中取得 accessKey 和 secretKey。
package your.web.app;
import lombok.Data;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import com.alibaba.fastjson.JSON;
@Configuration
@Data
public class OpenApiClient {
@Value("${report.browser.open.api.root.path}")
private String rootPath;
@Value("${report.browser.open.api.access.key}")
private String accessKey;
@Value("${report.browser.open.api.secret.key}")
private String secretKey;
private String getFullApiPath(String path) {
return this.getRootPath() + path;
}
public String post(String path, Object body) {
try {
String url = getFullApiPath(path);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build();
MediaType mediaType = MediaType.parse("application/json;charset=utf-8");
String json = JSON.toJSON(body).toString();
byte[] message = json.getBytes(OpenApiSpec.CHARSET_NAME);
RequestBody requestBody = RequestBody.create(message, mediaType);
Request request = new Request.Builder()
.url(url)
.addHeader(OpenApiSpec.ACCESS_KEY_PARAMETER_NAME, this.accessKey)
.post(requestBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
return response.body().string();
} catch (IOException ex) {
ex.printStackTrace();
return "{statusCode:501}";
}
}
}
- 在 Request.java 中写入下列代码
package your.web.app;
import lombok.Data;
import java.io.Serializable;
@Data
public class Request implements Serializable {
private String loginPersonId;
private String accessKey;
private String secretKey;
}
- 在 GetReportTemplateFileRequest.java 中写入下列代码
package your.web.app
import lombok.Data;
@Data
public class GetReportTemplateFileRequest extends Request{
private Long id;
private String imageUrl;
}
- 在 GetReportDataRequest.java 中写入下列代码
package your.web.app
import lombok.Data;
import java.util.List;
@Data
public class GetReportDataRequest extends Request {
private Long id;
private String parameters;
private List<String> loginPersonPropertyList;
}
- 在 GetReportImageRequest.java 中写入下列代码
package your.web.app;
import lombok.Data;
import java.io.Serializable;
@Data
public class GetReportImageRequest extends Request {
private String id;
private String type;
}
- 在 GetReportMapRequest.java 中写入下列代码
package your.web.app;
import lombok.Data;
import java.io.Serializable;
@Data
public class GetReportMapRequest extends Request {
private String fileName;
}
- 在 DownloadFileHandler.java 中写入下列代码
package your.web.app;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
public class DownloadFileHandler {
public static HttpServletResponse output(String fileName, HttpServletResponse response, byte[] data) {
try {
InputStream inputStream = new ByteArrayInputStream(data);
byte[] buffer = new byte[inputStream.available()];
inputStream.read(buffer);
inputStream.close();
response.reset();
response.addHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes()));
response.addHeader("Content-Length", "" + data.length);
OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
response.setContentType("application/octet-stream");
toClient.write(buffer);
toClient.flush();
toClient.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return response;
}
}
- 在 AddUserRequest.java 中写入下列代码
package your.web.app;
import lombok.Data;
@Data
public class AddUserRequest extends Request {
private String name;
private String account;
private String personId;
private String personName;
private String personProperties;
private String remark;
private String roleIds;
}
- 在 RemoveUserRequest.java 中写入下列代码
package your.web.app;
import lombok.Data;
@Data
public class RemoveUserRequest extends Request {
private String personId;
}
- 在 UpdateUserRequest.java 中写入下列代码
package your.web.app;
import lombok.Data;
@Data
public class UpdateUserRequest extends Request {
private String personId;
private String personName;
private String personProperties;
private Integer status;
private String remark;
private String roleIds;
}
在您的业务系统里集中管理报表
在您的业务系统里集中管理报表,推荐您改造新增用户、删除用户、添加角色、移除角色相关的功能。
前端开发:基于您喜欢的开发技术
前端不限制开发语言和开发环境,您可以根据实际需求,灵活调用您的后端接口,完成前端程序开发。
后端开发:基于 shuyan-open-api
后端不限制开发语言和开发环境,您可以根据实际需求,灵活调用数颜开放接口,完成后端程序开发。
提示:在您的业务系统里集中管理报表需要您的后端接口程序调用以下五个开放接口。
/open/api/v1/role/list/get
/open/api/v1/user/list/get
/open/api/v1/user/add
/open/api/v1/user/remove
/open/api/v1/user/update
报表管理的核心需求是不同用户可以看到不同的报表,数颜报表浏览器基于用户和角色进行授权和鉴权,业务系统的用户身份,必须映射到报表浏览器的用户身份上,也只有建立双边用户一对一的映射关系,才能实现在报表浏览器和您的业务系统中进行双向的授权和鉴权。
调用上述接口,可以获得报表浏览器中的用户和角色,向报表浏览器中添加用户,移除和更新报表浏览器中的用户,添加和更新用户时,可以为用户指定多个角色,在报表浏览器中,本身是按角色分配报表访问权限的,这样就能在您的业务系统中,实现对报表访问权限的控制。