RM: 基于页面结构化数据生成报表,一键导出图片,生成定制图表 文末有效果图 , 开放部分代码

背景


      开发这个工具是因为一句抱怨

       故事是这样的,我们公司是一个非常重视员工健康的公司,一年前老董说让HR(后面改为ZT)督促员工多多运动,可持续地位公司创造价值.并拿出了一部预算来奖励那些积极运动的人.于是我们公司一百多人,被ZT分为三组,一组大约37人,然后每月统一下每组的运动积分, 男生8公里3分,女生6公里三分, 未来督促大家积极参与集体运动,HR有说, 每组的总积分为: 总分数 * 总参与人数 / 总人数

    具体的统计方式是这样的,每个人跑了步后,只需将运动截图通过公司办公软件发给ZT,然后ZT会在月末的时候,将全公司一百多人的聊天记录扒一边,找到发送给她的运动截图.统计到excel里,这里还要查这个人是那一组的,应该给多少积分.过程极其繁杂,并且容易出错.需要收集的信息也很多.因为算运动积分这事只有她自己,经常导致我们到了下个月十号左右才出来运动结果,因为如果遗漏了谁的记录,需要ZT再去补充,计算.反正很麻烦. 那么麻烦,妹子当然有怨言啊,这是一件吃力不讨好的事情,算不准确,还会被人质疑能力有问题.反正我就被她漏掉过一次运动积分,还有一次日期记错了. 妹子在运动群里的抱怨,

大家都看到了,作为开发人员的大家,都没当一回事,有的还调侃了一下. 这个时候我略作思考就提出了很合理,高效的方案.

 

这个方案将收集信息的操作,下发到个人,而管理员只担任一个审核积分,跑步截图和计算积分的角色.并最终输出报表.

说完这句话我就后悔了, 因为我想到了一个更加优秀的方案来解放生产力, 在这里我也想问一下,屏幕前的你,如果把这个计算运动积分的事情交给你,你要怎么做? 好好想想.....可以先别往下看

 

 

首先我们分析一下, 在Confluence 我们可以创建一个运动模板,添加一个表格, 可以设置几列,这个表格可以无限地往下加,每个人运动了,都可以在上面添加一条记录 这个表格就长这个样子

 

标题,内容就是一个表格

想到表格我们就知道了,表格属于一种结构化的数据,  所谓结构化数据 就是 结构化数据也称作行数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理(来自百度百科)。

大致意思就是,表头明确定义了这一列是什么数据,第一列是名字,你就不能把运动积分填到第一列. 结构化的数据安装一定的规则排列,填写. 而处理结构化数据正是软件程序的拿手好菜....于是用软件来处理这些运动记录准没错. 所以我一看到这列表我就立即想到了可以使用TM写一个工具脚本来分组,计算积分,制作报表. Nice 说干就干....这对我来说轻车熟路. 因为我就是为了提高社会生产力而来,去吧 皮卡丘.....

技术方案

方向有了,就是制定思路

  1. 使用脚本拿到结构化数据
  2. 处理数据,积分相加,人员去重,分组
  3. 创建展示页面
  4. 使用html2canvas一键下载报表图片

 

      思路有了,那就给它起一个名字吧, RM怎么样? report mark, report mother 报表制作, 报表之母  简洁明了.高大上.强, 我起名字的能力真是越来越强了...哈哈哈.... 中文释义为:基于页面结构化数据一键生成报表

 

经过下班后的几个晚上的艰苦调试,最后完成了这个不到300行代码的小产品 

以下是产品使用的动态图

效果图显示

 

全部代码使用原生js写的, 样式使用了bootstrap,对原有页面有稍微一些影响.但不妨碍浏览信息. 下载报表使用的是html2canvas

高级功能 个人榜单  图表显示 需要使用星巴克咖啡充值 解锁 

 

做这个功能的时候,感觉界面的味道不够,于是找了一张背包行者的图片, 瞬间界面提升了几个逼格...哈哈哈...于是我找到了制作一个优秀产品的小技巧, 善用背景  背景音乐, 背景图片, 都是很好的技巧. 

关键代码如下: 

欢迎点在,留言讨论. 关注我的TM专栏 有更多硬核干货

// ==UserScript==
// @name         RM
// @namespace    https://fizzz.blog.csdn.net/
// @version      0.1
// @description  Report Maker: 基于页面结构化数据生成报表
// @author       Fizz
// @match        https://**/**
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @require      https://cdn.staticfile.org/html2canvas/0.5.0-beta4/html2canvas.min.js
// @resource bootstrapcss https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css

// ==/UserScript==

(function() {
  'use strict';
  let pageTitle = ''
  let groupTotalPerson = {'第一组':37,'第二组':37,'第三组': 39} // 各组总人数

  // 获取页面表格数据
  function getPageTableData () {
    let pageTableData = []
    let allTr = document.querySelectorAll('#main-content .confluenceTable tbody [role="row"]'); // 获取所有行
    [...allTr].forEach(trItem => {
        let allTrInnerTd = trItem.querySelectorAll('td')
        let name = allTrInnerTd[0].innerHTML.trim()
        if (name !== '<br>' && name) {
          let groupName = allTrInnerTd[1].innerText
          let date = allTrInnerTd[2].innerText
          let distance = allTrInnerTd[3].innerText
          let integral = allTrInnerTd[4].innerText
          let img = allTrInnerTd[5].innerText
          let remark = allTrInnerTd[6].innerText
          let trDataItem = {name,date,distance,integral,img,remark, groupName}
          pageTableData.push(trDataItem)
        }
    })
    return pageTableData
  }

  // 添加样式
  function addStyle () {
    let bootstrapcss = GM_getResourceText('bootstrapcss')
    bootstrapcss = bootstrapcss + `
      .fizz_inject_wrap_div{position: fixed;font-size: 14px !important;width: 100%;display: flex;justify-content: center;height: 100%;z-index: 100;z-index: 999;top:0;background: #fff;padding-top:20px;background-image:url('https://cf.qizhidao.com/download/attachments/63810224/1hikedr.png?version=1&modificationDate=1576220765550&api=v2')}
      .text-danger.import-info{color: #CC9900;font-weight: 700;font-size:16px}
      .myinject{position: fixed;bottom: 20px;right: 20px;vertical-align: middle;text-align: center;cursor: pointer;z-index: 999;padding: 5px 14px;border-radius: 4px;background-color: #2e6da4;border-color: #2e6da4;opacity: .2;color: #fff;}
      .myinject:hover{opacity: 1;color: #fff;}
      .fizz_operation_div{position: fixed;right: 30px;bottom: 50px;}
      .fizz_operation_div * {margin: 0;padding: 0;border: 0;outline: 0;font-size: 14px;box-sizing: content-box;}
      .fizz_operation_div ul,.fizz_operation_div li{list-style: none;border-left: 1px solid #2e6da4;border-right: 1px solid #2e6da4;margin: 0;padding: 0;outline: 0;font-size: 14px;box-sizing: content-box;}
      .fizz_operation_div li{padding: 6px 0px;text-align: center;cursor: pointer;vertical-align: middle;line-height: 20px;height: 20px;}
      .fizz_operation_div li:hover{background-color: #337ab7;color: #fff;}
      .fizz_operation_div .fizz_main_btn {border: none;outline: none;padding: 8px 14px;border-radius: 4px;background-color: #2e6da4;border-color: #2e6da4;opacity: .5;color: #fff;vertical-align: middle;text-align: center;cursor: pointer;line-height: 16px;vertical-align: middle;}
      .fizz_operation_div .fizz_main_btn:hover{opacity: 1;}
      .fizz_operation_div .fizz_menu_ul{display: none;}
      .fizz_operation_div:hover .fizz_menu_ul{display: block;}
      .hidden{display:none}
    `
    GM_addStyle(bootstrapcss)
  }

  // 处理表格数据,返回Map
  function showRes (pageTableData) {
    const resMap = new Map();
    // ...
    let groupHtmlStringObj = ceateResTableDiv(resMap)
    dataRender(groupHtmlStringObj)
  }

  // 创建显示结果的Div resMap:{'A': {},'B': {}}
  function ceateResTableDiv (resMap) {
    let groupHtmlStringObj = {
      '第一组': {groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, finalTotalIntegral: 0, joinPersonData: []},
      '第二组': {groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, finalTotalIntegral: 0, joinPersonData: []},
      '第三组': {groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, finalTotalIntegral: 0, joinPersonData: []}
    }
    // ...
    return groupHtmlStringObj
  }

  // 表格数据渲染到页面 {'第一组':{groupTotalIntegral: 0, groupTotalDistance: 0, groupTotalJoinNum: 0, joinPersonData: []},'第二组': [], '第三组': []}
  function dataRender (groupObjData) {
    console.log(groupObjData)
    for(let [key,value] of Object.entries(groupObjData)) {
     // ...
    }
  }

  // 关闭报表页面,视图
  function closeReportView () {
    document.querySelector('.fizz_inject_wrap_div').classList.add('hidden')
  }

  // 导出报表为图片
  function exportReportImg () {
    // ...
  }

  // 创建报表页面
  function creatReportWrapDiv () {
    let reportWrapDiv = document.createElement('div')
    let htmlString = `
      <div>
        ....
      </div>
    `
    reportWrapDiv.innerHTML = htmlString
    reportWrapDiv.classList.add('fizz_inject_wrap_div')
    reportWrapDiv.classList.add('hidden')
    return reportWrapDiv
  }

  // 显示报表图标视图
  function showReportChatView () {
    alert('此功能仅限高级会员使用,请使用星巴克咖啡充值解锁')
  }

  // 显示个人维度图标视图
  function showPersonView () {
    alert('此功能仅限高级会员使用,请使用星巴克咖啡充值解锁')
  }

  // 页面初始化函数
  function initFun () {
    pageTitle = document.querySelector('#title-text').innerText
    let body = document.querySelector('body')
    let injectDiv = document.createElement('div')
    injectDiv.classList.add('myinject')
    injectDiv.title = '生成报表'
    injectDiv.innerHTML = `R  M`
    injectDiv.onclick = function (e) {
      document.querySelector('.fizz_inject_wrap_div').classList.remove('hidden')
      let pageTableData = getPageTableData()
      showRes(pageTableData)
    }
    addStyle()
    body.appendChild(injectDiv)
    let reportWrapDiv = creatReportWrapDiv()
    body.appendChild(reportWrapDiv)

    document.querySelector('#fizz_rm_close_report').addEventListener('click', closeReportView, false)
    document.querySelector('#fizz_rm_export_report_img').addEventListener('click', exportReportImg, false)
    document.querySelector('#fizz_rm_report_chart_img').addEventListener('click', showReportChatView, false)
    document.querySelector('#fizz_rm_person_rank_img').addEventListener('click', showPersonView, false)
  }

  initFun()

})();

 

最后:

希望更多的人能够使用软件提高自己的工作效率,多点时间陪家人.多点时间去做自己喜欢做的事情.

 

拿我格子衫来 CSDN认证博客专家 拿我格子衫来 范马勇次郎 琦玉君
积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页