当前位置:首页 > 技术 > 正文内容

健康管理系统数据可视化:会员增长趋势与套餐预约分析

访客 技术 2026年9月2日 1

会员数量趋势图表实现

会员数据是体检平台的核心资产,通过分析会员增长情况可以评估业务发展状况。本节将实现过去一年内会员数量的趋势展示,采用折线图形式直观呈现数据变化。

1. 前端页面配置

会员趋势图表对应页面为/views/member_trend.html。

1.1 引入图表库

首先需要将ECharts库文件放置在项目的static/js目录下,然后在HTML页面中引入:

<script src="../static/js/echarts.min.js"></script>

1.2 创建图表容器

在页面中添加一个DOM元素作为图表容器:

<div class="chart-container">
  <div id="memberChart" style="width: 100%; height: 500px;"></div>
</div>

1.3 初始化图表并加载数据

<script type="text/javascript">
  // 初始化图表实例
  var chartInstance = echarts.init(document.getElementById('memberChart'));
  
  // 获取动态数据
  fetch('/api/report/memberGrowth')
    .then(response => response.json())
    .then(data => {
      chartInstance.setOption({
        title: {
          text: '会员增长趋势'
        },
        tooltip: {
          trigger: 'axis'
        },
        xAxis: {
          type: 'category',
          data: data.months
        },
        yAxis: {
          type: 'value'
        },
        series: [{
          name: '会员总数',
          type: 'line',
          smooth: true,
          data: data.memberCounts,
          areaStyle: {}
        }]
      });
    });
</script>

后端API应返回以下格式的数据:

{
  "months": ["2023.01", "2023.02", "2023.03", "2023.04"],
  "memberCounts": [120, 150, 180, 220]
}

2. 后端实现

2.1 控制器层

@RestController
@RequestMapping("/api/report")
public class DataVisualizationController {
  
  @Autowired
  private MemberDataService memberDataService;
  
  @GetMapping("/memberGrowth")
  public ResponseData getMemberGrowthData() {
    Map<String, Object> resultData = new HashMap<>();
    
    // 生成过去12个月的月份列表
    List<String> monthList = generateMonthList(12);
    resultData.put("months", monthList);
    
    // 获取每月会员数量
    List<Integer> memberCounts = memberDataService.getMonthlyMemberCounts(monthList);
    resultData.put("memberCounts", memberCounts);
    
    return ResponseData.success(resultData);
  }
  
  private List<String> generateMonthList(int count) {
    List<String> months = new ArrayList<>();
    LocalDate currentDate = LocalDate.now();
    
    for (int i = count - 1; i >= 0; i--) {
      LocalDate monthDate = currentDate.minusMonths(i);
      months.add(monthDate.format(DateTimeFormatter.ofPattern("yyyy.MM")));
    }
    
    return months;
  }
}

2.2 服务接口与实现

服务接口定义:

public interface MemberDataService {
  List<Integer> getMonthlyMemberCounts(List<String> months);
}

服务实现类:

@Service
public class MemberDataServiceImpl implements MemberDataService {
  
  @Autowired
  private MemberDataRepository memberDataRepository;
  
  @Override
  public List<Integer> getMonthlyMemberCounts(List<String> months) {
    List<Integer> counts = new ArrayList<>();
    
    for (String month : months) {
      // 构造月份最后一天
      String endDate = month + ".31";
      int count = memberDataRepository.countMembersBeforeDate(endDate);
      counts.add(count);
    }
    
    return counts;
  }
}

2.3 数据访问层

数据访问接口:

@Repository
public interface MemberDataRepository {
  int countMembersBeforeDate(String date);
}

SQL映射配置:

<select id="countMembersBeforeDate" parameterType="string" resultType="int">
  SELECT COUNT(*) FROM t_member WHERE registration_time <= #{date}
</select>

套餐预约占比图表实现

套餐预约数据反映了不同健康体检套餐的受欢迎程度,通过饼图可以直观展示各套餐的预约占比情况。

1. 前端页面配置

套餐占比图表对应页面为/views/package_proportion.html。

1.1 创建图表容器

<div class="chart-container">
  <div id="packageChart" style="width: 100%; height: 500px;"></div>
</div>

1.2 初始化图表并加载数据

<script type="text/javascript">
  // 初始化图表实例
  var packageChart = echarts.init(document.getElementById('packageChart'));
  
  // 获取动态数据
  fetch('/api/report/packageProportion')
    .then(response => response.json())
    .then(data => {
      packageChart.setOption({
        title: {
          text: '套餐预约占比',
          left: 'center'
        },
        tooltip: {
          trigger: 'item',
          formatter: '{a} <br/>{b}: {c} ({d}%)'
        },
        legend: {
          orient: 'vertical',
          left: 'left',
          data: data.packageNames
        },
        series: [
          {
            name: '预约占比',
            type: 'pie',
            radius: '60%',
            center: ['60%', '50%'],
            data: data.packageData,
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      });
    });
</script>

后端API应返回以下格式的数据:

{
  "packageNames": ["基础体检", "全面体检", "高端体检"],
  "packageData": [
    {"name": "基础体检", "value": 35},
    {"name": "全面体检", "value": 45},
    {"name": "高端体检", "value": 20}
  ]
}

2. 后端实现

2.1 控制器层

在DataVisualizationController中添加新方法:

@Autowired
private PackageDataService packageDataService;

@GetMapping("/packageProportion")
public ResponseData getPackageProportionData() {
  try {
    Map<String, Object> resultData = new HashMap<>();
    
    // 获取套餐统计数据
    List<Map<String, Object>> packageStats = packageDataService.getPackageStatistics();
    resultData.put("packageData", packageStats);
    
    // 提取套餐名称列表
    List<String> packageNames = packageStats.stream()
      .map(stat -> (String) stat.get("name"))
      .collect(Collectors.toList());
    resultData.put("packageNames", packageNames);
    
    return ResponseData.success(resultData);
  } catch (Exception e) {
    return ResponseData.error("获取套餐统计数据失败");
  }
}

2.2 服务接口与实现

服务接口定义:

public interface PackageDataService {
  List<Map<String, Object>> getPackageStatistics();
}

服务实现类:

@Service
public class PackageDataServiceImpl implements PackageDataService {
  
  @Autowired
  private PackageDataRepository packageDataRepository;
  
  @Override
  public List<Map<String, Object>> getPackageStatistics() {
    return packageDataRepository.findPackageBookingStats();
  }
}

2.3 数据访问层

数据访问接口:

@Repository
public interface PackageDataRepository {
  List<Map<String, Object>> findPackageBookingStats();
}

SQL映射配置:

<select id="findPackageBookingStats" resultType="map">
  SELECT 
    p.name AS name,
    COUNT(o.id) AS value
  FROM 
    t_order o
    JOIN t_package p ON o.package_id = p.id
  GROUP BY 
    p.name
  ORDER BY 
    value DESC
</select>
标签: Echarts

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。