金蝶云星空开发~第四篇:报表篇——账表开发与数据分析

前言:让数据说话

前三篇我们完成了单据的建模和插件开发,业务数据已经能够正常录入和流转。但企业的管理层和业务人员真正关心的是数据分析和决策支持 ------本月报价了多少?哪些物料被询价最多?报价转化率是多少?这些问题的答案,都需要通过报表来呈现。

金蝶云星空提供了丰富的报表开发体系,从最简单的SQL账表到复杂的数据分析看板,满足不同层级的报表需求。本篇的目标是:让你掌握三种主流报表的开发方法------SQL账表、简单账表、万能报表,并理解各自的适用场景和性能优化策略。全程以"销售报价分析报表"为实战案例,从最基础的SQL查询开始,逐步演进到可交互的综合看板。


第1章:账表开发体系概述

1.1 三种账表类型对比

金蝶云星空支持三种账表开发方式,各有优劣:

报表类型 开发方式 适用场景 难度 性能
SQL账表 直接编写SQL语句 数据来源单一、逻辑清晰、对性能要求高 ⭐⭐ ⭐⭐⭐⭐⭐
简单账表 BOS IDE可视设计+插件扩展 需要参数过滤、列排序、导出等交互 ⭐⭐⭐ ⭐⭐⭐⭐
万能报表 配置化拖拽设计 业务人员自助分析、灵活字段组合 ⭐⭐⭐

选型建议

  • 如果报表逻辑固定、数据量大、对响应速度要求极高 → 选择SQL账表
  • 如果报表需要交互式过滤、排序、需要与插件配合做复杂格式化 → 选择简单账表
  • 如果是业务人员临时分析、字段组合多变 → 选择万能报表

1.2 账表插件的服务器端继承类

无论是SQL账表还是简单账表,都支持通过插件扩展报表行为。常用基类:

基类 用途 关键方法
SysReportBase SQL账表服务端插件基类 InitializeBuildSql
SimpleReportBase 简单账表服务端插件基类 InitializeBuildQuerySql
ReportListPlugIn 账表列表插件基类(客户端) 同列表插件,用于格式化显示

第2章:SQL账表开发

SQL账表是最直接的报表开发方式------你写什么SQL,系统就展示什么数据。适合逻辑明确、对性能有极致要求的报表。

2.1 编写SQL查询语句与参数化

实战场景:开发一张"销售报价分析报表",展示各客户的报价数量、金额、转化率。

步骤一:编写SQL查询

sql 复制代码
-- 销售报价分析报表 - 核心SQL
SELECT 
    C.FName AS 客户名称,
    COUNT(DISTINCT Q.FBillNo) AS 报价单数,
    SUM(EF.FAmount) AS 报价总金额,
    SUM(EF.FTaxAmount) AS 报价含税总金额,
    AVG(EF.FQty) AS 平均数量,
    -- 转化率计算:已确认的报价单数 / 总报价单数
    CAST(SUM(CASE WHEN Q.FQuoteStatus = '2' THEN 1 ELSE 0 END) AS DECIMAL(10,2)) 
    / NULLIF(COUNT(DISTINCT Q.FBillNo), 0) * 100 AS 转化率,
    MIN(Q.FDate) AS 最早报价日期,
    MAX(Q.FDate) AS 最近报价日期
FROM T_DEM_SaleQuote Q
INNER JOIN T_BD_Customer C ON Q.FCustomerId = C.FID
INNER JOIN T_DEM_SaleQuoteEntry EF ON Q.FID = EF.FID
WHERE Q.FDate BETWEEN @BeginDate AND @EndDate
  AND Q.FQuoteStatus IN ('1', '2', '3')  -- 排除草稿
GROUP BY C.FName
ORDER BY 报价总金额 DESC

关键设计要点

  • 使用参数@BeginDate@EndDate支持日期范围筛选
  • 使用NULLIF避免除零错误
  • 使用CAST控制小数位数
  • 状态过滤排除了"草稿"状态的报价单,让分析更有意义

步骤二:在BOS IDE中创建SQL账表

  1. 打开BOS IDE → 新建 → 选择"SQL账表"
  2. 输入名称:"销售报价分析报表",编码:DEM_SaleQuoteAnalysisReport
  3. 在"SQL语句"编辑框中粘贴上述SQL
  4. 点击"解析SQL",系统自动识别结果集字段

2.2 账表数据源配置

配置参数(过滤条件)

SQL账表支持在界面上设置查询参数,参数会自动替换SQL中的变量。

  1. 在BOS IDE中打开报表设计器 → 切换到"参数"页签
  2. 点击"新增",配置日期参数:
参数名称 标识 类型 默认值
开始日期 BeginDate 日期 本月1日
结束日期 EndDate 日期 今天
  1. 在SQL语句中,用@BeginDate@EndDate引用参数

配置字段显示

在"字段"页签中,可以配置每个列的显示格式:

  • 字段名称:自定义列标题(如"客户名称")
  • 字段宽度:控制列宽
  • 对齐方式:左对齐/居中/右对齐(数字建议右对齐)
  • 格式化字符串 :如数字保留2位小数#,##0.00

2.3 SQL增强报表支持的样式

SQL账表支持通过简单配置实现高级效果:

分组汇总

在报表属性中设置"分组字段",系统会自动按该字段分组并显示小计。

sql 复制代码
-- 增加组织维度分组
SELECT 
    O.FName AS 组织,
    C.FName AS 客户名称,
    ...
FROM T_DEM_SaleQuote Q
INNER JOIN T_ORG_Organizations O ON Q.FOrgId = O.FID
...
GROUP BY O.FName, C.FName

在BOS IDE中设置"分组字段"为组织,报表会自动按组织分组展示。

交叉表(行转列)

如果需要按月份横向展示数据,SQL中可做行列转换:

sql 复制代码
-- 按月份展开报价金额
SELECT 
    C.FName AS 客户名称,
    SUM(CASE WHEN MONTH(Q.FDate) = 1 THEN EF.FAmount ELSE 0 END) AS 一月,
    SUM(CASE WHEN MONTH(Q.FDate) = 2 THEN EF.FAmount ELSE 0 END) AS 二月,
    -- ... 以此类推到十二月
    SUM(EF.FAmount) AS 合计
FROM ...
GROUP BY C.FName

💡 性能提示:SQL账表的性能完全取决于SQL语句的质量。复杂查询务必在SQL Server Management Studio中先用真实数据量测试执行计划,确认索引命中情况。


第3章:简单账表与插件开发

简单账表相比于SQL账表,提供了更丰富的交互能力:列排序、列过滤、数据导出、自定义按钮等。同时通过插件可以动态干预查询逻辑和显示格式。

3.1 简单账表的设计器使用

创建步骤

  1. BOS IDE → 新建 → 选择"简单账表"
  2. 输入名称:"销售报价明细账表",编码:DEM_SaleQuoteDetailReport
  3. 设计界面布局(与单据设计类似):
    • 顶部:查询条件区域(日期范围、客户、报价状态等)
    • 底部:表格区域(展示数据)

配置数据源

简单账表的数据源绑定一个查询对象(Query Object),而不是直接写SQL:

  1. 在"数据源"属性中,选择或新建一个查询对象
  2. 查询对象中关联"销售报价单"主表和明细表
  3. 选择需要展示的字段:单据编号、日期、客户、物料、数量、单价、金额等

这种方式的好处是:系统自动处理分页、排序、过滤,不需要手动编写SQL。

3.2 账表插件:数据预处理与格式化

简单账表的插件扩展主要有两个场景:

  1. 动态修改查询条件:根据当前用户上下文自动添加过滤
  2. 数据格式化:在数据展示前做额外处理(如计算衍生字段)

代码示例:简单账表插件

csharp 复制代码
using Kingdee.BOS.Core.Report.PlugIn;
using Kingdee.BOS.Core.Report.PlugIn.Args;
using Kingdee.BOS.Core.List.PlugIn.Args;

namespace DEM.SCM.PlugIn
{
    public class SaleQuoteDetailReportPlugin : SimpleReportBase
    {
        /// <summary>
        /// 初始化报表:设置默认查询条件
        /// </summary>
        public override void Initialize(InitializeArgs e)
        {
            base.Initialize(e);
            
            // 默认日期范围:本月
            DateTime firstDayOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
            DateTime lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);
            
            // 设置查询参数默认值
            this.ReportParameter.SetParamValue("FBeginDate", firstDayOfMonth);
            this.ReportParameter.SetParamValue("FEndDate", lastDayOfMonth);
            
            // 默认只显示"已确认"和"已提交"的报价单
            this.ReportParameter.SetParamValue("FQuoteStatus", new object[] { "1", "2" });
        }

        /// <summary>
        /// 构建查询SQL之前:动态添加过滤条件
        /// </summary>
        public override void BeforeBuildQuerySql(BeforeBuildQuerySqlArgs e)
        {
            base.BeforeBuildQuerySql(e);
            
            // 场景1:普通用户只能看自己部门的报价单
            if (!IsAdministrator())
            {
                string currentDept = this.Context.User.DeptId?.ToString() ?? "";
                e.SqlBuilder.AppendCondition($"Q.FDeptId = '{currentDept}'");
            }
            
            // 场景2:如果选择了客户,自动加过滤
            object customerParam = this.ReportParameter.GetParamValue("FCustomerId");
            if (customerParam != null && !string.IsNullOrEmpty(customerParam.ToString()))
            {
                e.SqlBuilder.AppendCondition($"Q.FCustomerId = {customerParam}");
            }
        }

        /// <summary>
        /// 数据绑定后:格式化额外字段
        /// </summary>
        public override void AfterBindRowData(AfterBindRowDataArgs e)
        {
            base.AfterBindRowData(e);
            
            // 计算"是否超期"标记
            DateTime? quoteDate = e.RowData["FDate"] as DateTime?;
            int? validDays = e.RowData["FValidDays"] as int?;
            
            if (quoteDate.HasValue && validDays.HasValue)
            {
                DateTime expireDate = quoteDate.Value.AddDays(validDays.Value);
                bool isOverdue = DateTime.Now.Date > expireDate.Date;
                
                // 添加一个计算列(如果数据源中不存在)
                e.RowData["FIsOverdue"] = isOverdue ? "是" : "否";
                e.RowData["FExpireDate"] = expireDate.ToString("yyyy-MM-dd");
            }
        }

        /// <summary>
        /// 行格式化:超期行标红
        /// </summary>
        public override void OnFormatRowCondition(FormatRowConditionArgs e)
        {
            base.OnFormatRowCondition(e);
            
            string isOverdue = e.RowData["FIsOverdue"]?.ToString() ?? "";
            if (isOverdue == "是")
            {
                e.RowStyle.BackColor = System.Drawing.Color.LightPink;
            }
        }

        private bool IsAdministrator()
        {
            // 判断当前用户是否管理员
            return this.Context.User.IsAdministrator;
        }
    }
}

3.3 账表扩展方法与常见问题

扩展方法一:添加自定义按钮

在简单账表工具栏中添加自定义操作按钮(与单据插件类似):

csharp 复制代码
public override void BarItemClick(BarItemClickEventArgs e)
{
    base.BarItemClick(e);
    
    if (e.BarItemKey == "btnExportExcel")
    {
        // 触发自定义导出逻辑
        ExportToExcelWithFormat();
    }
}

private void ExportToExcelWithFormat()
{
    // 获取当前报表数据
    var data = this.ReportData;
    
    // 自定义Excel导出(可使用NPOI等库)
    // 注意:大数据量时考虑异步处理
    this.View.ShowMessage("Excel导出完成!");
}

扩展方法二:报表钻取(点击跳转)

双击报表行,跳转到对应的单据详情页:

csharp 复制代码
public override void EntityRowDoubleClick(EntityRowDoubleClickEventArgs e)
{
    base.EntityRowDoubleClick(e);
    
    if (e.EntityKey == "FReportTable")
    {
        int rowIndex = e.RowIndex;
        string billNo = this.ReportData[rowIndex]["FBillNo"]?.ToString() ?? "";
        
        if (!string.IsNullOrEmpty(billNo))
        {
            // 打开销售报价单详情
            DynamicFormShowParameter showParam = new DynamicFormShowParameter();
            showParam.FormId = "DEM_SaleQuote";
            showParam.ParentPageId = this.View.PageId;
            showParam.PKey = "FBillNo";
            showParam.PKeyValue = billNo;
            
            this.View.ShowForm(showParam);
        }
    }
}

第4章:万能报表与轻分析

万能报表和轻分析是金蝶云星空面向业务人员的数据分析工具,配置化程度极高,开发人员了解后可以更好地支持业务团队。

4.1 万能报表配置与使用

万能报表的定位是:让业务人员通过拖拽配置,快速生成数据分析报表

配置流程

  1. 系统路径:主控台 → 供应链 → 报表分析 → 万能报表
  2. 点击"新建报表" → 选择数据源(可选择单据或基础资料)
  3. 维度配置:拖拽字段到行或列(如"客户"行、"物料"列)
  4. 度量配置:选择统计方式(求和、计数、平均值等),如"报价金额求和"
  5. 过滤条件:设置数据范围
  6. 保存并发布报表

适用场景

  • 业务部门临时数据分析需求
  • 管理层综合看板
  • 多维交叉分析

开发人员职责

  • 确保业务对象发布了正确的字段和关联关系
  • 对性能问题做优化(大数据量时万能报表可能较慢)

4.2 轻分析平台介绍与数据建模

轻分析(Light Analysis)是金蝶云星空的高级BI平台,支持:

  • 多数据源整合(金蝶内部+外部数据)
  • 可视化拖拽建模
  • 丰富的图表类型(柱状图、折线图、饼图、地图等)
  • 仪表板组合展示

轻分析开发流程

  1. 数据建模:在轻分析平台中建立数据模型,关联多张表
  2. 创建数据集:选择维度和度量,构建分析数据集
  3. 设计看板:将数据集以图表形式展示在仪表板中
  4. 发布分享:将看板发布给目标用户

4.3 轻分析性能调优指南

轻分析在大数据量场景下容易遇到性能瓶颈,以下是调优要点:

问题 解决方案
数据加载慢 减少数据集行数(增加过滤条件);使用汇总表而非明细表
图表渲染卡顿 减少图表数据点数量(如只显示Top 20)
多维分析响应慢 预创建分析立方体(Cube),定时刷新
跨数据源关联慢 在数据建模时进行预关联,避免运行时Join

建议 :对于超过10万行的数据,轻分析推荐使用汇总表(Aggregation Table) 方式------在数据库中预先按维度汇总数据,轻分析直接读取汇总结果而非原始明细。


本章实战总结:销售报价报表体系

至此,我们为"销售报价单"构建了完整的报表体系:

  • SQL账表(销售报价分析报表)

    • 客户维度汇总:报价单数、总金额、转化率
    • 支持日期范围参数
    • 按报价总金额降序排列
    • 使用了分组汇总和行列转换
  • 简单账表(销售报价明细账表)

    • 展示了所有报价单的明细数据
    • 查询条件:日期范围、客户、状态
    • 插件实现了:部门级数据隔离、超期标记计算、行标红提示
    • 双击跳转到单据详情
  • 万能报表/轻分析

    • 为业务人员配置了多维分析看板
    • 按物料、客户、月份多维度分析

报表开发最佳实践清单

最佳实践 说明
优先考虑SQL账表 逻辑明确、数据量大的报表优先使用SQL账表
避免全表扫描 所有查询必须带上索引字段的过滤条件
合理使用分页 列表型报表默认分页,每页50-100行
避免复杂子查询 复杂逻辑用临时表或CTE(公用表表达式)分解
缓存常用数据 基础资料名称等可通过缓存减少重复查询
设置查询超时 报表查询超时建议设置为180秒以上
考虑导出性能 大数据量导出建议异步处理,通过消息通知下载

常见问题速查

问题 解决方案
SQL账表查询超时 检查SQL执行计划,添加索引,或拆分查询
简单账表数据量太大 增加查询条件的必填项,减少默认数据范围
报表参数不生效 检查SQL中参数引用格式是否正确(@参数名)
报表显示乱码 检查数据库字符集设置,使用Unicode类型字段
钻取跳转无效 确认目标单据的FormId和主键字段是否正确

下一篇预告

第五篇将进入工作流与业务流开发------教你如何为"销售报价单"配置审批流程,以及如何通过单据转换(BOTP)实现报价单转销售订单的业务流。这是金蝶云星空实现业务流程自动化的核心能力。


练习题

  1. 为"销售报价分析报表"增加一个"物料维度"的SQL账表版本,展示每个物料的报价次数、平均单价、报价总数量
  2. 在简单账表中增加一个"导出汇总Excel"按钮,导出时自动按客户汇总(而不是导出明细)
  3. 在轻分析中创建一个仪表板,包含:报价趋势折线图(按月)、客户报价排行柱状图、报价状态分布饼图
相关推荐
tryCbest17 天前
金蝶云星空开发~第三篇:插件篇——C#插件开发与事件体系
金蝶云星空·kingdee