前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RM: 基于页面结构化数据生成报表,一键导出图片,生成定制图表 文末有效果图 , 开放部分代码

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

作者头像
拿我格子衫来
发布2022-01-24 17:54:55
3120
发布2022-01-24 17:54:55
举报
文章被收录于专栏:TopFETopFE

背景

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

故事是这样的,我们公司是一个非常重视员工健康的公司,一年前老董说让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

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

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

关键代码如下:

代码语言:javascript
复制
// ==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()

})();

最后:

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 技术方案
  • 以下是产品使用的动态图
  • 关键代码如下:
相关产品与服务
腾讯云 BI
腾讯云 BI(Business Intelligence,BI)提供从数据源接入、数据建模到数据可视化分析全流程的BI能力,帮助经营者快速获取决策数据依据。系统采用敏捷自助式设计,使用者仅需通过简单拖拽即可完成原本复杂的报表开发过程,并支持报表的分享、推送等企业协作场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档