报表助手

1.简介

  • 该应用适用于网页打印场景
  • 支持直接打印、预览报表,模板设计,生成文档
  • 支持开机自启和URL Protocol唤醒
  • 支持多指令、多任务、多线程、多模式
  • 支持ws与wss双协议
  • 软件下载地址:点击下载open in new window

2.架构

  • 开发语言:C#

  • 应用框架:.NET FrameWork 4.8

  • 依赖组件:

    • Fleck 1.2
    • Newtonsoft.Json 13.0
    • FastReport 2023.2.4
  • 通信协议:WebStock

  • 通信端口:3676

3.文件说明

  • 安装目录\ReportHelper.exe 为主应用
  • 安装目录\ReportDesign.exe 为设计应用
  • 安装目录\config.ini 为配置文件

4.界面介绍

  • 服务
    • 启动服务:程序将开启端口监听与相关服务
    • 停止服务:程序将停止端口监听与停止服务
  • 地址
    • 默认显示本机IP地址
    • 支持127.0.0.1localhost
    • 点击可复制到剪切板
  • 端口
    • 监听端口号为3676
    • 请确保端口未占用
    • 点击可复制到剪切板
  • 模式
    • 应用模式:需人工自行开启与启动服务
    • 服务模式:开启自启并启动服务且最小化运行
  • 托盘
    • 托盘区内可操作显示界面
    • 应用的退出操作在托盘区内操作

5.配置介绍

配置文件名称:config.ini

配置项默认值说明
schemws协议类型(ws或wss)
server0.0.0.0监听地址
port3676监听端口
pathcertificate.pfx证书路径(wss模式下必填)
password000000证书密码(wss模式下必填)

6.功能介绍

  • 数据交互

    • 采用JSON作为交互格式。
    • 编码统一使用UTF-8
  • 指令字段

    • 字段名称:Instruct
      • 类型:String
      • 描述:指令内容
      • 场景:通用
      • 可选项:
        • print:直接打印
        • view:预览报表
        • design:设计模板
        • document:输出文档
    • 字段名称:name
      • 类型:String
      • 描述:报表名称
      • 场景:通用
    • 字段名称:templates
      • 类型:Array
      • 描述:报表名称
      • 场景:直接打印、预览报表、输出文档
      • 内容项
        • source:
          • 类型:String
          • 描述:数据源
          • 格式:Json=Str;Encoding=utf-8
          • 内容:Str为数据对象JSON编码后Base64编码
        • template
          • 类型:String
          • 描述:模板编码
          • 编码:Base64
    • 字段名称:id
      • 类型:String
      • 描述:模板ID
      • 场景:设计模板
    • 字段名称:source
      • 类型:String
      • 描述:数据源
      • 场景:设计模板
      • 内容:同Templates下source
    • 字段名称:template
      • 类型:String
      • 描述:数据源
      • 场景:设计模板
      • 内容:同templates下template
    • 字段名称:token
      • 类型:String
      • 描述:授权码
      • 场景:直接打印
      • 内容:可为空
  • 回传字段

    • 字段名称:state
      • 类型:String
      • 描述:任务状态
      • 场景:通用
      • 内容项:
        • success:处理成功
        • error:处理失败
    • 字段名称:instruct
      • 类型:String
      • 描述:任务名称
      • 场景:通用
      • 内容项:
        • print:已发送到打印机
        • document:输出文档
        • viewOpen:预览打开
        • designLoad:设计打开
        • designClose:设计关闭
        • designSave:设计保存
    • 字段名称:name
      • 类型:String
      • 描述:报表名称
      • 场景:通用
    • 字段名称:id
      • 类型:String
      • 描述:报表ID
      • 场景:设计模板
    • 字段名称:template
      • 类型:String
      • 描述:模板内容
      • 场景:设计模板
      • 编码:Base64
    • 字段名称:mime
      • 类型:String
      • 描述:文档类型
      • 场景:输出文档
    • 字段名称:str
      • 类型:String
      • 描述:文档类型
      • 场景:输出文档
      • 编码:Base64Blob
  • 交互须知

    • 采用数据源与模板分离模式
    • 在指令下发时数据源与模板应分开传输
    • 指令下发时应用将自动组合数据与模板
    • 设计模式下模板回传将自动剔除数据源区块
    • 模板内容在交互过程中均采用Base64编码
    • 文档模式下将输出Base64编码后的Blob内容

7.交互示例

  • 直接打印

指令内容

{"instruct":"print","name":"Test","templates":[{"source":"Json=eyJjbGFzcyI6eyJ0aXRsZSI6IlJlcG9ydEhlbHBlcjAiLCJsaXN0IjpbMSwyLDNdfX0=;Encoding=utf-8","template":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVwb3J0IFNjcmlwdExhbmd1YWdlPSJDU2hhcnAiIFJlcG9ydEluZm8uQ3JlYXRlZD0iMTAvMTYvMjAyMiAxNjoxMTozMCIgUmVwb3J0SW5mby5Nb2RpZmllZD0iMTAvMjAvMjAyMiAxNjozMjo0MCIgUmVwb3J0SW5mby5DcmVhdG9yVmVyc2lvbj0iMjAyMi4zLjkuMCI+PFJlcG9ydFBhZ2UgTmFtZT0iUGFnZTEiIFdhdGVybWFyay5Gb250PSLlrovkvZMsIDYwcHQiPjxSZXBvcnRUaXRsZUJhbmQgTmFtZT0iUmVwb3J0VGl0bGUxIiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDEiIExlZnQ9IjE1MS4yIiBUb3A9IjkuNDUiIFdpZHRoPSIxODkiIEhlaWdodD0iMTguOSIgVGV4dD0i5rWL6K+VLVtKU09OLml0ZW0uY2xhc3MudGl0bGVdIiBGb250PSLlrovkvZMsIDlwdCIvPjwvUmVwb3J0VGl0bGVCYW5kPjxEYXRhQmFuZCBOYW1lPSJEYXRhMSIgVG9wPSI0MS44IiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCIgU3RhcnROZXdQYWdlPSJ0cnVlIiBEYXRhU291cmNlPSJsaXN0Ij48VGV4dE9iamVjdCBOYW1lPSJUZXh0MiIgTGVmdD0iMTUxLjIiIFRvcD0iOS40NSIgV2lkdGg9IjIxNy4zNSIgSGVpZ2h0PSIxOC45IiBUZXh0PSJbSlNPTi5pdGVtLmNsYXNzLmxpc3QuaXRlbV0iIEZvcm1hdD0iTnVtYmVyIiBGb3JtYXQuVXNlTG9jYWxlPSJ0cnVlIiBGb3JtYXQuRGVjaW1hbERpZ2l0cz0iMiIgSG9yekFsaWduPSJDZW50ZXIiIFdvcmRXcmFwPSJmYWxzZSIgRm9udD0i5a6L5L2TLCA5cHQiIFRyaW1taW5nPSJFbGxpcHNpc0NoYXJhY3RlciIvPjwvRGF0YUJhbmQ+PC9SZXBvcnRQYWdlPjwvUmVwb3J0Pg0K"}]}

指令内容

{"state":"success","instruct":"print","name":"Test"}
  • 预览报表

指令内容

{"instruct":"view","name":"Test","templates":[{"source":"Json=eyJjbGFzcyI6eyJ0aXRsZSI6IlJlcG9ydEhlbHBlcjAiLCJsaXN0IjpbMSwyLDNdfX0=;Encoding=utf-8","template":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVwb3J0IFNjcmlwdExhbmd1YWdlPSJDU2hhcnAiIFJlcG9ydEluZm8uQ3JlYXRlZD0iMTAvMTYvMjAyMiAxNjoxMTozMCIgUmVwb3J0SW5mby5Nb2RpZmllZD0iMTAvMjAvMjAyMiAxNjozMjo0MCIgUmVwb3J0SW5mby5DcmVhdG9yVmVyc2lvbj0iMjAyMi4zLjkuMCI+PFJlcG9ydFBhZ2UgTmFtZT0iUGFnZTEiIFdhdGVybWFyay5Gb250PSLlrovkvZMsIDYwcHQiPjxSZXBvcnRUaXRsZUJhbmQgTmFtZT0iUmVwb3J0VGl0bGUxIiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDEiIExlZnQ9IjE1MS4yIiBUb3A9IjkuNDUiIFdpZHRoPSIxODkiIEhlaWdodD0iMTguOSIgVGV4dD0i5rWL6K+VLVtKU09OLml0ZW0uY2xhc3MudGl0bGVdIiBGb250PSLlrovkvZMsIDlwdCIvPjwvUmVwb3J0VGl0bGVCYW5kPjxEYXRhQmFuZCBOYW1lPSJEYXRhMSIgVG9wPSI0MS44IiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCIgU3RhcnROZXdQYWdlPSJ0cnVlIiBEYXRhU291cmNlPSJsaXN0Ij48VGV4dE9iamVjdCBOYW1lPSJUZXh0MiIgTGVmdD0iMTUxLjIiIFRvcD0iOS40NSIgV2lkdGg9IjIxNy4zNSIgSGVpZ2h0PSIxOC45IiBUZXh0PSJbSlNPTi5pdGVtLmNsYXNzLmxpc3QuaXRlbV0iIEZvcm1hdD0iTnVtYmVyIiBGb3JtYXQuVXNlTG9jYWxlPSJ0cnVlIiBGb3JtYXQuRGVjaW1hbERpZ2l0cz0iMiIgSG9yekFsaWduPSJDZW50ZXIiIFdvcmRXcmFwPSJmYWxzZSIgRm9udD0i5a6L5L2TLCA5cHQiIFRyaW1taW5nPSJFbGxpcHNpc0NoYXJhY3RlciIvPjwvRGF0YUJhbmQ+PC9SZXBvcnRQYWdlPjwvUmVwb3J0Pg0K"}]}

回传内容

{"state":"success","instruct":"viewOpen","name":"Test"}
  • 设计模板

  • 指令内容

{"instruct":"design","id":2,"name":"Test","source":"Json=eyJjbGFzcyI6eyJ0aXRsZSI6IlJlcG9ydEhlbHBlcjAiLCJsaXN0IjpbMSwyLDNdfX0=;Encoding=utf-8","template":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVwb3J0IFNjcmlwdExhbmd1YWdlPSJDU2hhcnAiIFJlcG9ydEluZm8uQ3JlYXRlZD0iMTAvMTYvMjAyMiAxNjoxMTozMCIgUmVwb3J0SW5mby5Nb2RpZmllZD0iMTAvMjAvMjAyMiAxNjozMjo0MCIgUmVwb3J0SW5mby5DcmVhdG9yVmVyc2lvbj0iMjAyMi4zLjkuMCI+PFJlcG9ydFBhZ2UgTmFtZT0iUGFnZTEiIFdhdGVybWFyay5Gb250PSLlrovkvZMsIDYwcHQiPjxSZXBvcnRUaXRsZUJhbmQgTmFtZT0iUmVwb3J0VGl0bGUxIiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDEiIExlZnQ9IjE1MS4yIiBUb3A9IjkuNDUiIFdpZHRoPSIxODkiIEhlaWdodD0iMTguOSIgVGV4dD0i5rWL6K+VLVtKU09OLml0ZW0uY2xhc3MudGl0bGVdIiBGb250PSLlrovkvZMsIDlwdCIvPjwvUmVwb3J0VGl0bGVCYW5kPjxEYXRhQmFuZCBOYW1lPSJEYXRhMSIgVG9wPSI0MS44IiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCIgU3RhcnROZXdQYWdlPSJ0cnVlIiBEYXRhU291cmNlPSJsaXN0Ij48VGV4dE9iamVjdCBOYW1lPSJUZXh0MiIgTGVmdD0iMTUxLjIiIFRvcD0iOS40NSIgV2lkdGg9IjIxNy4zNSIgSGVpZ2h0PSIxOC45IiBUZXh0PSJbSlNPTi5pdGVtLmNsYXNzLmxpc3QuaXRlbV0iIEZvcm1hdD0iTnVtYmVyIiBGb3JtYXQuVXNlTG9jYWxlPSJ0cnVlIiBGb3JtYXQuRGVjaW1hbERpZ2l0cz0iMiIgSG9yekFsaWduPSJDZW50ZXIiIFdvcmRXcmFwPSJmYWxzZSIgRm9udD0i5a6L5L2TLCA5cHQiIFRyaW1taW5nPSJFbGxpcHNpc0NoYXJhY3RlciIvPjwvRGF0YUJhbmQ+PC9SZXBvcnRQYWdlPjwvUmVwb3J0Pg0K"}
  • 回传内容

设计打开

{"state":"success","instruct":"designLoad","id":"2","name":"Test"}

模板回传

{"state":"success","instruct":"designSave","id":"2","name":"Test","template":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVwb3J0IFNjcmlwdExhbmd1YWdlPSJDU2hhcnAiIFJlcG9ydEluZm8uQ3JlYXRlZD0iMTAvMTYvMjAyMiAxNjoxMTozMCIgUmVwb3J0SW5mby5Nb2RpZmllZD0iMTAvMjAvMjAyMiAxNzoyNjowOSIgUmVwb3J0SW5mby5DcmVhdG9yVmVyc2lvbj0iMjAyMi4zLjkuMCI+PFJlcG9ydFBhZ2UgTmFtZT0iUGFnZTEiIFdhdGVybWFyay5Gb250PSLlrovkvZMsIDYwcHQiPjxSZXBvcnRUaXRsZUJhbmQgTmFtZT0iUmVwb3J0VGl0bGUxIiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDEiIExlZnQ9IjE4OSIgVG9wPSI5LjQ1IiBXaWR0aD0iMTg5IiBIZWlnaHQ9IjE4LjkiIFRleHQ9Iua1i+ivlS1bSlNPTi5pdGVtLmNsYXNzLnRpdGxlXSIgRm9udD0i5a6L5L2TLCA5cHQiLz48L1JlcG9ydFRpdGxlQmFuZD48RGF0YUJhbmQgTmFtZT0iRGF0YTEiIFRvcD0iNDEuOCIgV2lkdGg9IjcxOC4yIiBIZWlnaHQ9IjM3LjgiIFN0YXJ0TmV3UGFnZT0idHJ1ZSIgRGF0YVNvdXJjZT0ibGlzdCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDIiIExlZnQ9IjE1MS4yIiBUb3A9IjkuNDUiIFdpZHRoPSIyMTcuMzUiIEhlaWdodD0iMTguOSIgVGV4dD0iW0pTT04uaXRlbS5jbGFzcy5saXN0Lml0ZW1dIiBGb3JtYXQ9Ik51bWJlciIgRm9ybWF0LlVzZUxvY2FsZT0idHJ1ZSIgRm9ybWF0LkRlY2ltYWxEaWdpdHM9IjIiIEhvcnpBbGlnbj0iQ2VudGVyIiBXb3JkV3JhcD0iZmFsc2UiIEZvbnQ9IuWui+S9kywgOXB0IiBUcmltbWluZz0iRWxsaXBzaXNDaGFyYWN0ZXIiLz48L0RhdGFCYW5kPjwvUmVwb3J0UGFnZT48L1JlcG9ydD4NCg=="}

设计关闭

{"state":"success","instruct":"designClose","id":"2","name":"Test"}
  • 输出文档

指令内容

{"instruct":"document","name":"Test","templates":[{"source":"Json=eyJjbGFzcyI6eyJ0aXRsZSI6IlJlcG9ydEhlbHBlcjAiLCJsaXN0IjpbMSwyLDNdfX0=;Encoding=utf-8","template":"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48UmVwb3J0IFNjcmlwdExhbmd1YWdlPSJDU2hhcnAiIFJlcG9ydEluZm8uQ3JlYXRlZD0iMTAvMTYvMjAyMiAxNjoxMTozMCIgUmVwb3J0SW5mby5Nb2RpZmllZD0iMTAvMjAvMjAyMiAxNzoyNjowOSIgUmVwb3J0SW5mby5DcmVhdG9yVmVyc2lvbj0iMjAyMi4zLjkuMCI+PFJlcG9ydFBhZ2UgTmFtZT0iUGFnZTEiIFdhdGVybWFyay5Gb250PSLlrovkvZMsIDYwcHQiPjxSZXBvcnRUaXRsZUJhbmQgTmFtZT0iUmVwb3J0VGl0bGUxIiBXaWR0aD0iNzE4LjIiIEhlaWdodD0iMzcuOCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDEiIExlZnQ9IjE4OSIgVG9wPSI5LjQ1IiBXaWR0aD0iMTg5IiBIZWlnaHQ9IjE4LjkiIFRleHQ9Iua1i+ivlS1bSlNPTi5pdGVtLmNsYXNzLnRpdGxlXSIgRm9udD0i5a6L5L2TLCA5cHQiLz48L1JlcG9ydFRpdGxlQmFuZD48RGF0YUJhbmQgTmFtZT0iRGF0YTEiIFRvcD0iNDEuOCIgV2lkdGg9IjcxOC4yIiBIZWlnaHQ9IjM3LjgiIFN0YXJ0TmV3UGFnZT0idHJ1ZSIgRGF0YVNvdXJjZT0ibGlzdCI+PFRleHRPYmplY3QgTmFtZT0iVGV4dDIiIExlZnQ9IjE1MS4yIiBUb3A9IjkuNDUiIFdpZHRoPSIyMTcuMzUiIEhlaWdodD0iMTguOSIgVGV4dD0iW0pTT04uaXRlbS5jbGFzcy5saXN0Lml0ZW1dIiBGb3JtYXQ9Ik51bWJlciIgRm9ybWF0LlVzZUxvY2FsZT0idHJ1ZSIgRm9ybWF0LkRlY2ltYWxEaWdpdHM9IjIiIEhvcnpBbGlnbj0iQ2VudGVyIiBXb3JkV3JhcD0iZmFsc2UiIEZvbnQ9IuWui+S9kywgOXB0IiBUcmltbWluZz0iRWxsaXBzaXNDaGFyYWN0ZXIiLz48L0RhdGFCYW5kPjwvUmVwb3J0UGFnZT48L1JlcG9ydD4NCg=="}]}

回传内容

{{"state":"success","instruct":"document","name":"Test","mime":"application/pdf","str":"base64..."}

8.JavaScript 示例

import { Base64 } from 'js-base64';
//报表助手
const helper = {
    token:'',
    ver: '1.3.2.4',
    ip: '127.0.0.1',
    port: '3676',
    ws: null,
    task: {},
    //创建连接
    connect() {
        return new Promise((resolve, reject) => {
            if (this.ws && this.ws.readyState == WebSocket.OPEN) {
                resolve();
            } else {
                try {
                    this.ws = new WebSocket('ws://' + this.ip + ':' + this.port);
                    this.ws.onmessage = (e) => this.message(e);
                    this.ws.onopen = () => resolve();
                    this.ws.onerror = () => reject();
                    this.ws.onclose = () => {
                        this.ws = null;
                        this.task = {};
                    };
                } catch {
                    reject();
                }
            }
        });
    },
    //直接打印
    print(name, templates) {
        if (this.ws && this.ws.readyState == WebSocket.OPEN) {
            let msg = {
                instruct: 'print',
                name: name,
                token:this.token,
                templates: templates.map((row) => {
                    return {
                        source: 'Json=' + Base64.encode(JSON.`string`ify(row.source)) + ';Encoding=utf-8',
                        template: row.template
                    };
                })
            };
            this.ws.send(JSON.stringify(msg));
        } else {
            throw Error('ReportHelper Not connected');
        }
    },
    //预览打印
    view(name, templates) {
        if (this.ws && this.ws.readyState == WebSocket.OPEN) {
            let msg = {
                instruct: 'view',
                name: name,
                templates: templates.map((row) => {
                    return {
                        source: 'Json=' + Base64.encode(JSON.stringify(row.source)) + ';Encoding=utf-8',
                        template: row.template
                    };
                })
            };
            this.ws.send(JSON.stringify(msg));
        } else {
            throw Error('ReportHelper Not connected');
        }
    },
    //文档预览
    document(name, templates) {
        if (this.ws && this.ws.readyState == WebSocket.OPEN) {
            let msg = {
                instruct: 'document',
                name: name,
                templates: templates.map((row) => {
                    return {
                        source: 'Json=' + Base64.encode(JSON.stringify(row.source)) + ';Encoding=utf-8',
                        template: row.template
                    };
                })
            };
            this.ws.send(JSON.stringify(msg));
        } else {
            throw Error('ReportHelper Not connected');
        }
    },
    //模板设计
    design(id, name, param, template, fun) {
        if (this.ws && this.ws.readyState == WebSocket.OPEN) {
            let msg = {
                instruct: 'design',
                id: id,
                name: name,
                source: 'Json=' + Base64.encode(JSON.stringify(param)) + ';Encoding=utf-8',
                template: template
            };
            this.task['design_' + id] = fun;
            this.ws.send(JSON.stringify(msg));
        } else {
            throw Error('ReportHelper Not connected');
        }
    },
    //消息处理
    message(e) {
        try {
            let result = JSON.parse(e.data);
            if (result.state == 'success') {
                //设计保存
                result.instruct == 'designSave' && this.task['design_' + result.id](result.id, result.name, result.template);
                //设计关闭
                result.instruct == 'designClose' && delete this.task['design_' + result.id];
                //输出文档
                if (result.instruct == 'document') {
                    let ua = Base64.toUint8Array(result.str);
                    let blob = new Blob([ua], { type: result.mime });
                    window.open(URL.createObjectURL(blob));
                }
            } else {
                //错误回调
                alert(result.message);
            }
        } catch (e) {
            alert(e);
        }
    },
    //错误处理
    error: () => {
        console.log('ReportHelper Connect Error');
    }
};
export default helper;
  • 调用方法

直接打印

helper.token="";
helper.connect().then(() => {
    helper.print("Test", [{ source:{title: "ReportHelper" },template:"str"}]);
}).catch(()=>{});

预览报表

helper.connect().then(() => {
    helper.view("Test", [{ source:{title: "ReportHelper" },template:"str"}]);
}).catch(()=>{});

设计模板

helper.connect().then(() => {
    helper.design("1", "Test", {title: "ReportHelper" }, template, (id, name, template) => {
        console.log(id, name, template);
    });
}).catch(()=>{});

输出文档

helper.connect().then(() => {
    helper.document("Test", [{ source:{title: "ReportHelper" },template:"str"}]);
}).catch(()=>{});

9.杂项

  • URL Protocol唤醒:ReportHelper://run
  • 命令参数
    • run 开启服务
    • serve 开启服务并最小化

10.参考