黑馬程序員-傳智健康項目(第十一章)


傳智健康項目


第11章 圖形報表、POI報表

1. 套餐預約占比餅形圖

1.1 需求分析

會員可以通過移動端自助進行體檢預約,在預約時需要選擇預約的體檢套餐。本章節我們需要通過餅形圖直觀的展示出會員預約的各個套餐占比情況。展示效果如下圖:

1.2 完善頁面

套餐預約占比餅形圖對應的頁面為/pages/report_setmeal.html。

1.2.1 導入ECharts庫

<script src="../plugins/echarts/echarts.js"></script>

1.2.2 參照官方實例導入餅形圖

<div class="box">
  <!-- 為 ECharts 准備一個具備大小(寬高)的 DOM -->
  <div id="chart1" style="height:600px;"></div>
</div>
<script type="text/javascript">
  // 基於准備好的dom,初始化echarts實例
  var myChart1 = echarts.init(document.getElementById('chart1'));
  //發送ajax請求獲取動態數據
  axios.get("/report/getSetmealReport.do").then((res)=>{
    myChart1.setOption({
      title : {
        text: '套餐預約占比',
        subtext: '',
        x:'center'
      },
      tooltip : {//提示框組件
        trigger: 'item',//觸發類型,在餅形圖中為item
        formatter: "{a} <br/>{b} : {c} ({d}%)"//提示內容格式
      },
      legend: {
        orient: 'vertical',
        left: 'left',
        data: res.data.data.setmealNames
      },
      series : [
        {
          name: '套餐預約占比',
          type: 'pie',
          radius : '55%',
          center: ['50%', '60%'],
          data:res.data.data.setmealCount,
          itemStyle: {
            emphasis: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: 'rgba(0, 0, 0, 0.5)'
            }
          }
        }
      ]
    });
  });
</script>

根據餅形圖對數據格式的要求,我們發送ajax請求,服務端需要返回如下格式的數據:

{
	"data":{
			"setmealNames":["套餐1","套餐2","套餐3"],
			"setmealCount":[
							{"name":"套餐1","value":10},
							{"name":"套餐2","value":30},
							{"name":"套餐3","value":25}
						   ]
		   },
	"flag":true,
	"message":"獲取套餐統計數據成功"
}

1.3 后台代碼

1.3.1 Controller

在health_backend工程的ReportController中提供getSetmealReport方法

@Reference
private SetmealService setmealService;
/**
 * 套餐占比統計
 * @return
 */
@RequestMapping("/getSetmealReport")
public Result getSetmealReport(){
  List<Map<String, Object>> list = setmealService.findSetmealCount();

  Map<String,Object> map = new HashMap<>();
  map.put("setmealCount",list);

  List<String> setmealNames = new ArrayList<>();
  for(Map<String,Object> m : list){
    String name = (String) m.get("name");
    setmealNames.add(name);
  }
  map.put("setmealNames",setmealNames);
  
  return new Result(true, MessageConstant.GET_SETMEAL_COUNT_REPORT_SUCCESS,map);
}

1.3.2 服務接口

在SetmealService服務接口中擴展方法findSetmealCount

public List<Map<String,Object>> findSetmealCount();

1.3.3 服務實現類

在SetmealServiceImpl服務實現類中實現findSetmealCount方法

public List<Map<String, Object>> findSetmealCount() {
  return setmealDao.findSetmealCount();
}

1.3.4 Dao接口

在SetmealDao接口中擴展方法findSetmealCount

public List<Map<String,Object>> findSetmealCount();

1.3.5 Mapper映射文件

在SetmealDao.xml映射文件中提供SQL語句

<select id="findSetmealCount" resultType="map">
  select s.name,count(o.id) as value 
  	from t_order o ,t_setmeal s 
  	where o.setmeal_id = s.id 
  	group by s.name
</select>

2. 運營數據統計

2.1 需求分析

通過運營數據統計可以展示出體檢機構的運營情況,包括會員數據、預約到診數據、熱門套餐等信息。本章節就是要通過一個表格的形式來展示這些運營數據。效果如下圖:

2.2 完善頁面

運營數據統計對應的頁面為/pages/report_business.html。

2.2.1 定義模型數據

定義數據模型,通過VUE的數據綁定展示數據

<script>
  var vue = new Vue({
    el: '#app',
    data:{
      reportData:{
        reportDate:null,
        todayNewMember :0,
        totalMember :0,
        thisWeekNewMember :0,
        thisMonthNewMember :0,
        todayOrderNumber :0,
        todayVisitsNumber :0,
        thisWeekOrderNumber :0,
        thisWeekVisitsNumber :0,
        thisMonthOrderNumber :0,
        thisMonthVisitsNumber :0,
        hotSetmeal :[]
      }
    }
  })
</script>
<div class="box" style="height: 900px">
  <div class="excelTitle" >
    <el-button @click="exportExcel">導出Excel</el-button>運營數據統計
  </div>
  <div class="excelTime">日期:{{reportData.reportDate}}</div>
  <table class="exceTable" cellspacing="0" cellpadding="0">
    <tr>
      <td colspan="4" class="headBody">會員數據統計</td>
    </tr>
    <tr>
      <td width='20%' class="tabletrBg">新增會員數</td>
      <td width='30%'>{{reportData.todayNewMember}}</td>
      <td width='20%' class="tabletrBg">總會員數</td>
      <td width='30%'>{{reportData.totalMember}}</td>
    </tr>
    <tr>
      <td class="tabletrBg">本周新增會員數</td>
      <td>{{reportData.thisWeekNewMember}}</td>
      <td class="tabletrBg">本月新增會員數</td>
      <td>{{reportData.thisMonthNewMember}}</td>
    </tr>
    <tr>
      <td colspan="4" class="headBody">預約到診數據統計</td>
    </tr>
    <tr>
      <td class="tabletrBg">今日預約數</td>
      <td>{{reportData.todayOrderNumber}}</td>
      <td class="tabletrBg">今日到診數</td>
      <td>{{reportData.todayVisitsNumber}}</td>
    </tr>
    <tr>
      <td class="tabletrBg">本周預約數</td>
      <td>{{reportData.thisWeekOrderNumber}}</td>
      <td class="tabletrBg">本周到診數</td>
      <td>{{reportData.thisWeekVisitsNumber}}</td>
    </tr>
    <tr>
      <td class="tabletrBg">本月預約數</td>
      <td>{{reportData.thisMonthOrderNumber}}</td>
      <td class="tabletrBg">本月到診數</td>
      <td>{{reportData.thisMonthVisitsNumber}}</td>
    </tr>
    <tr>
      <td colspan="4" class="headBody">熱門套餐</td>
    </tr>
    <tr class="tabletrBg textCenter">
      <td>套餐名稱</td>
      <td>預約數量</td>
      <td>占比</td>
      <td>備注</td>
    </tr>
    <tr v-for="s in reportData.hotSetmeal">
      <td>{{s.name}}</td>
      <td>{{s.setmeal_count}}</td>
      <td>{{s.proportion}}</td>
      <td></td>
    </tr>
  </table>
</div>

2.2.2 發送請求獲取動態數據

在VUE的鈎子函數中發送ajax請求獲取動態數據,通過VUE的數據綁定將數據展示到頁面

<script>
  var vue = new Vue({
    el: '#app',
    data:{
      reportData:{
        reportDate:null,
        todayNewMember :0,
        totalMember :0,
        thisWeekNewMember :0,
        thisMonthNewMember :0,
        todayOrderNumber :0,
        todayVisitsNumber :0,
        thisWeekOrderNumber :0,
        thisWeekVisitsNumber :0,
        thisMonthOrderNumber :0,
        thisMonthVisitsNumber :0,
        hotSetmeal :[]
      }
    },
    created() {
      //發送ajax請求獲取動態數據
      axios.get("/report/getBusinessReportData.do").then((res)=>{
        this.reportData = res.data.data;
      });
    }
  })
</script>

根據頁面對數據格式的要求,我們發送ajax請求,服務端需要返回如下格式的數據:

{
  "data":{
    "todayVisitsNumber":0,
    "reportDate":"2019-04-25",
    "todayNewMember":0,
    "thisWeekVisitsNumber":0,
    "thisMonthNewMember":2,
    "thisWeekNewMember":0,
    "totalMember":10,
    "thisMonthOrderNumber":2,
    "thisMonthVisitsNumber":0,
    "todayOrderNumber":0,
    "thisWeekOrderNumber":0,
    "hotSetmeal":[
      {"proportion":0.4545,"name":"粉紅珍愛(女)升級TM12項篩查體檢套餐","setmeal_count":5},
      {"proportion":0.1818,"name":"陽光爸媽升級腫瘤12項篩查體檢套餐","setmeal_count":2},
      {"proportion":0.1818,"name":"珍愛高端升級腫瘤12項篩查","setmeal_count":2},
      {"proportion":0.0909,"name":"孕前檢查套餐","setmeal_count":1}
    ],
  },
  "flag":true,
  "message":"獲取運營統計數據成功"
}

2.3 后台代碼

2.3.1 Controller

在ReportController中提供getBusinessReportData方法

@Reference
private ReportService reportService;

/**
 * 獲取運營統計數據
 * @return
*/
@RequestMapping("/getBusinessReportData")
public Result getBusinessReportData(){
  try {
    Map<String, Object> result = reportService.getBusinessReport();
    return new Result(true,MessageConstant.GET_BUSINESS_REPORT_SUCCESS,result);
  } catch (Exception e) {
    e.printStackTrace();
    return new Result(true,MessageConstant.GET_BUSINESS_REPORT_FAIL);
  }
}

2.3.2 服務接口

在health_interface工程中創建ReportService服務接口並聲明getBusinessReport方法

package com.itheima.service;

import java.util.Map;

public interface ReportService {
    /**
     * 獲得運營統計數據
     * Map數據格式:
     *      todayNewMember -> number
     *      totalMember -> number
     *      thisWeekNewMember -> number
     *      thisMonthNewMember -> number
     *      todayOrderNumber -> number
     *      todayVisitsNumber -> number
     *      thisWeekOrderNumber -> number
     *      thisWeekVisitsNumber -> number
     *      thisMonthOrderNumber -> number
     *      thisMonthVisitsNumber -> number
     *      hotSetmeals -> List<Setmeal>
     */
    public Map<String,Object> getBusinessReport() throws Exception;
}

2.3.3 服務實現類

在health_service_provider工程中創建服務實現類ReportServiceImpl並實現ReportService接口

package com.itheima.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.itheima.dao.MemberDao;
import com.itheima.dao.OrderDao;
import com.itheima.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 統計報表服務
 */
@Service(interfaceClass = ReportService.class)
@Transactional
public class ReportServiceImpl implements ReportService {
    @Autowired
    private MemberDao memberDao;
    @Autowired
    private OrderDao orderDao;

    /**
     * 獲得運營統計數據
     * Map數據格式:
     *      todayNewMember -> number
     *      totalMember -> number
     *      thisWeekNewMember -> number
     *      thisMonthNewMember -> number
     *      todayOrderNumber -> number
     *      todayVisitsNumber -> number
     *      thisWeekOrderNumber -> number
     *      thisWeekVisitsNumber -> number
     *      thisMonthOrderNumber -> number
     *      thisMonthVisitsNumber -> number
     *      hotSetmeal -> List<Setmeal>
     */
    public Map<String, Object> getBusinessReport() throws Exception{
      	//獲得當前日期
        String today = DateUtils.parseDate2String(DateUtils.getToday());
        //獲得本周一的日期
        String thisWeekMonday = DateUtils.parseDate2String(DateUtils.getThisWeekMonday());
        //獲得本月第一天的日期  
        String firstDay4ThisMonth = 
          					DateUtils.parseDate2String(DateUtils.getFirstDay4ThisMonth());

        //今日新增會員數
        Integer todayNewMember = memberDao.findMemberCountByDate(today);

        //總會員數
        Integer totalMember = memberDao.findMemberTotalCount();

        //本周新增會員數
        Integer thisWeekNewMember = memberDao.findMemberCountAfterDate(thisWeekMonday);

        //本月新增會員數
        Integer thisMonthNewMember = memberDao.findMemberCountAfterDate(firstDay4ThisMonth);

        //今日預約數
        Integer todayOrderNumber = orderDao.findOrderCountByDate(today);

        //本周預約數
        Integer thisWeekOrderNumber = orderDao.findOrderCountAfterDate(thisWeekMonday);

        //本月預約數
        Integer thisMonthOrderNumber = orderDao.findOrderCountAfterDate(firstDay4ThisMonth);

        //今日到診數
        Integer todayVisitsNumber = orderDao.findVisitsCountByDate(today);

        //本周到診數
        Integer thisWeekVisitsNumber = orderDao.findVisitsCountAfterDate(thisWeekMonday);

        //本月到診數
        Integer thisMonthVisitsNumber = orderDao.findVisitsCountAfterDate(firstDay4ThisMonth);

        //熱門套餐(取前4)
        List<Map> hotSetmeal = orderDao.findHotSetmeal();

        Map<String,Object> result = new HashMap<>();
        result.put("reportDate",today);
        result.put("todayNewMember",todayNewMember);
        result.put("totalMember",totalMember);
        result.put("thisWeekNewMember",thisWeekNewMember);
        result.put("thisMonthNewMember",thisMonthNewMember);
        result.put("todayOrderNumber",todayOrderNumber);
        result.put("thisWeekOrderNumber",thisWeekOrderNumber);
        result.put("thisMonthOrderNumber",thisMonthOrderNumber);
        result.put("todayVisitsNumber",todayVisitsNumber);
        result.put("thisWeekVisitsNumber",thisWeekVisitsNumber);
        result.put("thisMonthVisitsNumber",thisMonthVisitsNumber);
        result.put("hotSetmeal",hotSetmeal);

        return result;
    }
}

2.3.4 Dao接口

在OrderDao和MemberDao中聲明相關統計查詢方法

package com.itheima.dao;

import com.itheima.pojo.Order;
import java.util.List;
import java.util.Map;

public interface OrderDao {
    public void add(Order order);
    public List<Order> findByCondition(Order order);
    public Map findById4Detail(Integer id);
    public Integer findOrderCountByDate(String date);
    public Integer findOrderCountAfterDate(String date);
    public Integer findVisitsCountByDate(String date);
    public Integer findVisitsCountAfterDate(String date);
    public List<Map> findHotSetmeal();
}
package com.itheima.dao;

import com.github.pagehelper.Page;
import com.itheima.pojo.Member;
import java.util.List;

public interface MemberDao {
    public List<Member> findAll();
    public Page<Member> selectByCondition(String queryString);
    public void add(Member member);
    public void deleteById(Integer id);
    public Member findById(Integer id);
    public Member findByTelephone(String telephone);
    public void edit(Member member);
    public Integer findMemberCountBeforeDate(String date);
    public Integer findMemberCountByDate(String date);
    public Integer findMemberCountAfterDate(String date);
    public Integer findMemberTotalCount();
}

2.3.5 Mapper映射文件

在OrderDao.xml和MemberDao.xml中定義SQL語句

OrderDao.xml:

<!--根據日期統計預約數-->
<select id="findOrderCountByDate" parameterType="string" resultType="int">
  select count(id) from t_order where orderDate = #{value}
</select>

<!--根據日期統計預約數,統計指定日期之后的預約數-->
<select id="findOrderCountAfterDate" parameterType="string" resultType="int">
  select count(id) from t_order where orderDate &gt;= #{value}
</select>

<!--根據日期統計到診數-->
<select id="findVisitsCountByDate" parameterType="string" resultType="int">
  select count(id) from t_order where orderDate = #{value} and orderStatus = '已到診'
</select>

<!--根據日期統計到診數,統計指定日期之后的到診數-->
<select id="findVisitsCountAfterDate" parameterType="string" resultType="int">
  select count(id) from t_order where orderDate &gt;= #{value} and orderStatus = '已到診'
</select>

<!--熱門套餐,查詢前4條-->
<select id="findHotSetmeal" resultType="map">
  select 
      s.name, 
      count(o.id) setmeal_count ,
      count(o.id)/(select count(id) from t_order) proportion
    from t_order o inner join t_setmeal s on s.id = o.setmeal_id
    group by o.setmeal_id
    order by setmeal_count desc 
  	limit 0,4
</select>

MemberDao.xml:

<!--根據日期統計會員數,統計指定日期之前的會員數-->
<select id="findMemberCountBeforeDate" parameterType="string" resultType="int">
  select count(id) from t_member where regTime &lt;= #{value}
</select>

<!--根據日期統計會員數-->
<select id="findMemberCountByDate" parameterType="string" resultType="int">
  select count(id) from t_member where regTime = #{value}
</select>

<!--根據日期統計會員數,統計指定日期之后的會員數-->
<select id="findMemberCountAfterDate" parameterType="string" resultType="int">
  select count(id) from t_member where regTime &gt;= #{value}
</select>

<!--總會員數-->
<select id="findMemberTotalCount" resultType="int">
  select count(id) from t_member
</select>

3. 運營數據統計報表導出

3.1 需求分析

運營數據統計報表導出就是將統計數據寫入到Excel並提供給客戶端瀏覽器進行下載,以便體檢機構管理人員對運營數據的查看和存檔。

3.2 提供模板文件

本章節我們需要將運營統計數據通過POI寫入到Excel文件,對應的Excel效果如下:

通過上面的Excel效果可以看到,表格比較復雜,涉及到合並單元格、字體、字號、字體加粗、對齊方式等的設置。如果我們通過POI編程的方式來設置這些效果代碼會非常繁瑣。

在企業實際開發中,對於這種比較復雜的表格導出一般我們會提前設計一個Excel模板文件,在這個模板文件中提前將表格的結構和樣式設置好,我們的程序只需要讀取這個文件並在文件中的相應位置寫入具體的值就可以了。

在本章節資料中已經提供了一個名為report_template.xlsx的模板文件,需要將這個文件復制到health_backend工程的template目錄中

3.3 完善頁面

在report_business.html頁面提供導出按鈕並綁定事件

<div class="excelTitle" >
  <el-button @click="exportExcel">導出Excel</el-button>運營數據統計
</div>
methods:{
  //導出Excel報表
  exportExcel(){
    window.location.href = '/report/exportBusinessReport.do';
  }
}

3.4 后台代碼

在ReportController中提供exportBusinessReport方法,基於POI將數據寫入到Excel中並通過輸出流下載到客戶端

/**
  * 導出Excel報表
  * @return
*/
@RequestMapping("/exportBusinessReport")
public Result exportBusinessReport(HttpServletRequest request, HttpServletResponse response){
  try{
    //遠程調用報表服務獲取報表數據
    Map<String, Object> result = reportService.getBusinessReport();
    
    //取出返回結果數據,准備將報表數據寫入到Excel文件中
    String reportDate = (String) result.get("reportDate");
    Integer todayNewMember = (Integer) result.get("todayNewMember");
    Integer totalMember = (Integer) result.get("totalMember");
    Integer thisWeekNewMember = (Integer) result.get("thisWeekNewMember");
    Integer thisMonthNewMember = (Integer) result.get("thisMonthNewMember");
    Integer todayOrderNumber = (Integer) result.get("todayOrderNumber");
    Integer thisWeekOrderNumber = (Integer) result.get("thisWeekOrderNumber");
    Integer thisMonthOrderNumber = (Integer) result.get("thisMonthOrderNumber");
    Integer todayVisitsNumber = (Integer) result.get("todayVisitsNumber");
    Integer thisWeekVisitsNumber = (Integer) result.get("thisWeekVisitsNumber");
    Integer thisMonthVisitsNumber = (Integer) result.get("thisMonthVisitsNumber");
    List<Map> hotSetmeal = (List<Map>) result.get("hotSetmeal");
	
    //獲得Excel模板文件絕對路徑
    String temlateRealPath = request.getSession().getServletContext().getRealPath("template") +
      											File.separator + "report_template.xlsx";
	
    //讀取模板文件創建Excel表格對象
    XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream(new File(temlateRealPath)));
    XSSFSheet sheet = workbook.getSheetAt(0);
    
    XSSFRow row = sheet.getRow(2);
    row.getCell(5).setCellValue(reportDate);//日期

    row = sheet.getRow(4);
    row.getCell(5).setCellValue(todayNewMember);//新增會員數(本日)
    row.getCell(7).setCellValue(totalMember);//總會員數

    row = sheet.getRow(5);
    row.getCell(5).setCellValue(thisWeekNewMember);//本周新增會員數
    row.getCell(7).setCellValue(thisMonthNewMember);//本月新增會員數

    row = sheet.getRow(7);
    row.getCell(5).setCellValue(todayOrderNumber);//今日預約數	
    row.getCell(7).setCellValue(todayVisitsNumber);//今日到診數

    row = sheet.getRow(8);
    row.getCell(5).setCellValue(thisWeekOrderNumber);//本周預約數
    row.getCell(7).setCellValue(thisWeekVisitsNumber);//本周到診數

    row = sheet.getRow(9);
    row.getCell(5).setCellValue(thisMonthOrderNumber);//本月預約數
    row.getCell(7).setCellValue(thisMonthVisitsNumber);//本月到診數

    int rowNum = 12;
    for(Map map : hotSetmeal){//熱門套餐
      String name = (String) map.get("name");
      Long setmeal_count = (Long) map.get("setmeal_count");
      BigDecimal proportion = (BigDecimal) map.get("proportion");
      row = sheet.getRow(rowNum ++);
      row.getCell(4).setCellValue(name);//套餐名稱
      row.getCell(5).setCellValue(setmeal_count);//預約數量
      row.getCell(6).setCellValue(proportion.doubleValue());//占比
    }

    //通過輸出流進行文件下載
    ServletOutputStream out = response.getOutputStream();
    response.setContentType("application/vnd.ms-excel");
    response.setHeader("content-Disposition", "attachment;filename=report.xlsx");
    workbook.write(out);
    
    out.flush();
    out.close();
    workbook.close();
    
    return null;
  }catch (Exception e){
    return new Result(false, MessageConstant.GET_BUSINESS_REPORT_FAIL,null);
  }
}

視頻地址:https://www.bilibili.com/video/BV1Bo4y117zV


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM