打印100种格式迥异的医用图文报告单——1周的时间有点长__Vue.js__前端
发布于 3 年前 作者 banyungong 1577 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

  • 📢欢迎点赞 :👍 收藏 ⭐留言 📝 如有错误敬请指正,赐人玫瑰,手留余香!
  • 📢本文作者:由webmote 原创,首发于 【掘金】
  • 📢作者格言: 生活在于折腾,当你不折腾生活时,生活就开始折腾你,让我们一起加油!💪💪💪

🎏 序言

掘友们,大家好,我又来了。🕺🕺🕺

大家在工作中最烦恼的是什么? 是不是重复做类似的工作啊?你有过设计报告做到吐的感受吗?

是的,最近我碰上了个大麻烦🥺,Ctrl +C、V键快被我敲掉了。

它就是制作价值XX w💰💰💰(据说具体数字容易被举报,这里用XX替换)的某医院用报告单。 该项目主要做心理问卷,然后根据问卷、经后台算法后解析出报告。由于处理的是各种类型的心理体检报告单,所以花样繁多,总共有100+ 的不同报告需要展示和打印。接手项目的时候,对这些报告单算是懵懂无知,看了几个感觉大同小异,就误以为差不多都类似。

还好报酬足够丰厚,要不然对不起我这快要敲坏的手,看我二指禅✌️。

经过叮叮当当一阵响的脚手架、环境的准备,我以我最快的速度搞定了基本数据的增删改查工作(感谢 vue-element-admin项目),是时候表演制作报告的拿手绝活了。

🎏 01.等等,让我炫个技

最最核心💕的也就是图文混排报告了,先秀秀效果。

💫第一方队是问卷调查报告和艾森克个性测验报告。

4.png

💫第二方队是明尼苏达多相人格调查表评估报告。 1.png

💫第三方队是多项人格调查表评估报告。 2.png 希望能给新手以启迪💏,让老手有东西吐槽💏。

💫第四方队是…

打住!后面的方队都回去吧,领导不审阅了,都擦球不多的样子。

🎏 02.使用三方技术大汇总

一篇图文混排可打印报告单的技术实现,主要涉及到的技术是表格🏢、各类图📊、📈、各类报告块📃、打印🖨️。

轮子虽然也要造,但我们选择站在巨人的肩膀上造轮子,毕竟站得高看得远,能省一点是一点。

下面列下使用的三方库或包:

src=http___cdn.geekdigging.com_data_analysis_data_visualization_pyecharts_3.gif&refer=http___cdn.geekdigging.gif

  • 封装后在vue内直接和Echart交互: vue-echarts
  • 可打印Echart图的print.js 脚本,具体谁写的也不知道了,没有留版权信息,有需要的童鞋可以留言。
  • 很强大的打印脚本 print-js ,我并没用,据说最新版也支持 echart; 其核心思想是把打印的dom输出到iframe内,并枚举canvas,转换成image。
getHtml: function () {
     ... //这里仅贴部分代码
     //canvass echars图表转为图片
     for (var k4 = 0; k4 < canvass.length; k4++) {
       var imageURL = canvass[k4].toDataURL("image/png");
       var img = document.createElement("img");
       img.src = imageURL;
       img.setAttribute('style', 'max-width: 100%;');
       img.className = 'isNeedRemove'
       // canvass[k4].style.display = 'none'
       // canvass[k4].parentNode.style.width = '100%'
       // canvass[k4].parentNode.style.textAlign = 'center'
       canvass[k4].parentNode.insertBefore(img,canvass[k4].nextElementSibling);
     }
     //做分页
     //style="page-break-after: always"
     var pages = document.querySelectorAll('.result');
     for (var k5 = 0; k5 < pages.length; k5++) {
       pages[k5].setAttribute('style', 'page-break-after: always');
     }
     return this.dom.outerHTML;
   },

🎏 03.你的报告实现思路?

小伙子,来,姨给你社(说)句话…

住过西安城中村(吉祥村)的娃都应该听过这个段子。

现在活来了,⚡你摊上事了⚡。

需求: 制作报告,每种报告都需要处理不同的数据,展示不同的格式;

image.png

往下看之前,不妨留给自己5分钟⏱️思考时间,看看我们的实现有哪些差异?

金樽清酒斗十千,玉盘珍羞直万钱。🥂🥂🥂
停杯投箸不能食,拔剑四顾心茫然。🤺🤺🤺
欲渡黄河冰塞川,将登太行雪满山。🚶‍♀️🚶‍♀️🚶‍
闲来垂钓碧溪上,忽复乘舟梦日边。🎣🎣🎣
行路难,行路难,多歧路,今安在?🚶‍♂️🚶‍♂️🚶‍
长风破浪会有时,直挂云帆济沧海。🏄🏄🏄

🎏 03.1 动态模板方案

所谓“动态模板方案”,就是按照报告类型定制该类型的模板组件。

我们只需要判断模板类型,然后加载相应模板进行渲染,就搞定了这个需求,是不是超简单?

看下代码组织形式:

image.png

  • WQReport/index.vue 是报告的父组件,利用 slot加载模板
  • WQReport/reportTemplate.vue 负责加载动态模板
  • templates 文件夹内就是100个模板定义
  • templates/default 为默认模板,用来兜底,万一找不到模板就用它

03.1.1 WQReport/index.vue 内容

<template>
 <div ref="wrap" class="form-wrap">
   <div class="form-content-wrap">
     <div ref="print" class="reportBorder">
       <div id="print" class="reportBlock">
         <slot name="print" />
       </div>
       <div class="footer" />
     </div>
   </div>
 </div>
</template>

<script>
export default {
 name: 'WqPageReport',
 data() {
   return {
   }
 },
}
</script>

03.1.2 WQReport/reportTemplate.vue 内容

这里利用vue的 动态组件 component 技术进行加载动态模板。

并且利用计算属性 loader 来返回加载组件的 Promise。 注意需要使用 require(./templates/${this.type}).default 完成载入。

载入失败了,就返回 this.rptType = () => import(./templates/default)默认模板。

当然数据需要赋值给模板组件的属性data。

<template>
  <div class="theRpt">
    <component :is="rptType" v-if="rptType" ref="theRpt" :data="rptData" :type="type" />
  </div>
</template>

<script>
export default {
  name: 'ReportTemplate',
  props: ['rptData', 'type'],
  data() {
    return {
      rptType: null
    }
  },
  computed: {
    loader() {
      if (!this.type) {
        return null
      }
      return () => Promise.resolve(require(`./templates/${this.type}`).default)
    }
  },
  mounted() {
    this.loader()
      .then(() => {
        console.log('load template:' + this.type)
        this.rptType = () => this.loader()
      })
      .catch(() => {
        console.log('load template failed.' + this.type)
        this.rptType = () => import(`./templates/default`)
      })
  }
}
</script>

03.1.3 templates/t0-000 内容

报告模板的内容较多,这里会简化一部分html代码。

<template>
  <div :id="id" class="template">
    <div
      style="
                width: 100%;
            "
    >
      {{ data.SCALE_NAME }}评估报告单      
    </div>
    <div
      style="
                width: 100%;              
            "
    >
      <div style="width: 90%;">{{ data.REPORT_ID }}</div>
    </div>
    <div style="width: 100%; text-align: center; margin: 30px 0;">
      <table
        style="
                    width: 90%;
                "
      >
        <tr>
          <td style="width: 12%; text-align: right; font-weight: 800;">姓名:</td>
          <td style="width: 12%; text-align: left;">{{ data.USER_REAL_NAME }}</td>
          <td style="width: 12%; text-align: right; font-weight: 800;">性别:</td>
          <td style="width: 12%; text-align: left;">{{ data.USER_SEX }}</td>
          <td style="width: 12%; text-align: right; font-weight: 800;">年龄:</td>
        </tr>        
      </table>
    </div>
    <div style="width: 100%; text-align: center;">
      <div
        style="
                    width: 90%;
                "
      >
        {{ data.SCALE_EXPLAIN }}
      </div>
    </div>    
    ... ...
      本评定表最终解释权由临床医师和心理测评专家作出。    
  </div>
</template>

<script>
export default {
  name: 't0000',
  props: {
    data: {
      type: Object,
      default: () => {
        return {}
      }
    },
    type: String
  },
  data() {
    return {
      id: `template-${this.type}`
    }
  },
  created() {
    console.log('subcom:' + this.type)
  }
}
</script>

03.1.4 使用报告组件

使用动态模板报告组件,就很容易了。

import wqPageReport from "@/components/WQReport/index";
import rptTemplate from '@/components/WQReport/reportTemplate'

//增加组件引用
components: { wqPageReport, rptTemplate },

//增加模板代码
<wq-page-report ref="form">
      <div slot="print" class="printContent">
        <rpt-template
          :type="template"
          :rpt-data="rptData"
        />
      </div>
    </wq-page-report>

03.1.5 有啥不妥吗?

100个模板我已经Ctrl+C、V完了,命名也都改了一遍。

只等按照报告类型,逐一修改每个模板的html定义,以及渲染显示实现了。

天,还有渲染显示的逻辑呢!!!✨这,真的要把手敲断啊?✨

每个报告有一部分是相似的,比如个人资料,签名提示等,这些就算都做成组件,我也得100个模板一个个复制过去啊!

😂我已经哭晕在厕所了😂,钱真尼玛不好挣~~ 我退出好不好?

我感觉自己已经上了梁山,下不来了。

并且我感觉打包速度有点慢,利用 webpack-bundle-analyzer 插件扫描了下代码,templates模板文件夹所占性能比重超大! 100个模板组件不是盖的~~

报告类型太多了,必须换方案,要不这重复的报告拷贝来拷贝去烦都烦死了。🥺

🎏 03.2 动态配置方案

喝杯白开水,🧺闭目养神10分钟。

好了,冷静过后,加油, webmote!

重要的时刻需要冷静下来,然后再开动脑筋

先绘制下图。

image.png

抽象一下: 每个报告都由不同的组件按照顺序结构排列而成。

顺序结构可以看数组,不同的组件可能会有不同的属性定义,那么如果使用配置来定义一个报告,可以定义如下结构:

't0-000': [{},{},{}],
't0-001': [{},{},{}],
't0-002': [{},{},{}],
...

先看看能不能解决方案1的问题🔥?

如果t0-100的报告格式和t0-002的报告格式相似,则可以复制配置,看起来这个工作量是可控的。

{},组件的属性是什么鬼东西呢?

嗯,我们暂且不要抽象,用到一个具体组件时在定义不迟。

既然已经由了初步的构思,那让我们先实现默认报告配置吧!

03.2.1 改造1方案

  • 复用 WQReport/index.vue ,因其模板再slot内,因此无需改动代码
  • 改造 WQReport/reportTemplate.vue 按照配置方案依次渲染相应的组件

报告使用代码:

<wq-page-report ref="form">
      <div slot="print" class="printContent">
        <rpt-template
          :type="template"
          :rpt-data="rptData"
          :report="report"
          :st="theSt"
          :config="theConfig"
        />
      </div>
    </wq-page-report>

这里我们增加了属性 theConfig,表示某类型报告的配置; theSt,某类型报告配置相关联的数据, report,报告的详细原始数据,rptData,报告的个人信息。

03.2.1 reportTemplate 代码

该类负责按照报告类型绘制各类报告组件。

由于 rptTitle、rptTail、rptPersonalInfo、rptResult几乎每个报告都有,因此就按照固定方式配置在组件内。

<template>
  <div class="rptTemplate">
    <vue-lazy-component :timeout="1000">
      <rpt-title :data="rptData" />
      <rpt-personal-info :data="rptData" />
      <div v-for="(com,index) in config" :key="index">
        <rpt-total-table v-if="totalTable(com)" :data="st" :config="com" />
        <rpt-guage v-if="guage(com)" :data="st" :config="com" />
        <rpt-single-line v-if="singleLine(com)" :data="st" :config="com" />
      </div>
      <rpt-result :data="report" :config="rptData" />
      <rpt-tail :data="rptData" />
    </vue-lazy-component>
  </div>
</template>

<script>
import rptTitle from '../rptTitle'
import rptTail from '../rptTail'
import rptResult from '../rptResult'
import rptPersonalInfo from '../rptPersonalInfo'
import rptTotalTable from '../rptTotalTable'
import rptGuage from '../rptGuage'
import rptSingleLine from '../rptSingleLine'

export default {
  name: 'RptTemplate',
  components: { rptTitle, rptTail, rptResult, rptPersonalInfo, rptTotalTable,  rptGuage,  rptSingleLine },
  props: {
    rptData: {
      type: Object,
      default: () => {
        return {}
      },
    },
    report: {
      type: Object,
      default: () => {
        return {}
      },
    },
    st: {
      type: Object,
      default: () => {
        return null
      },
    },
    config: {
      type: Array,
      default: () => {
        return []
      },
    },
  },
  data() {
    return {
      id: `${this.type}`,
    }
  },
  computed: {

  },
  created() {
    console.log('subcom:' + this.type)
  },
  methods: {
    totalTable(config) {
      return this.getConfigValue(config, 'rptTotalTable')
    },   
    stackLine(config) {
      return this.getConfigValue(config, 'rptStackLine')
    },
    guage(config) {
      return this.getConfigValue(config, 'rptGuage')
    },   
    singleLine(config) {
      return this.getConfigValue(config, 'rptSingleLine')
    },    
    getConfigValue(config, key) {
      if (config && 'type' in config && config.type == key) {
        return config
      } else {
        return null
      }
    },
  },
}
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
  .rptTemplate{
      width:100%;
      padding: 0 15px;
    }

</style>

03.2.2 rptTitle等组件 代码

按可复用的粒度,切分报告的各个部分为组件,忽然发现组件实现超级简单了。

比如标题切分成组件后,只需要关心怎么显示标题、图片等。

<template>
  <div class="titleSpan">
    <table class="printTable">
      <tr v-if="logo && !data.hiddenTitle">
        <td valign="top" align="center">
          <img :src="logo" style="max-height: 100px" />
        </td>
      </tr>
      <tr v-if="!data.hiddenTitle">
        <td align="center">
          <!-- margin-top: 60px; -->
          <div style="text-align: center; font-size: 38px; height: 60px">
            {{ data.SYSTEM_NAME }}
          </div>
        </td>
      </tr>
      <tr>
        <td align="center">
          <div :class="data.hiddenTitle ? 'Bigtitle' : 'title'">
            {{ data.SCALE_NAME }}评估报告单            
          </div>
        </td>
      </tr>
      <tr>
        <td>
          <div style="text-align: right; font-size: 18px; ">
            <div style="line-height: auto">
              {{ data.REPORT_ID
              }}
            </div>
          </div>
        </td>
      </tr>
    </table>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
export default {
  name: "RptTitle",
  props: {
    data: {
      type: Object,
      default: () => {
        return {};
      }
    }
  },
  data() {
    return {};
  },
  computed: {
    ...mapGetters(["sysConfig"]),
    styleObject() {
      return {
        color: this.$options.filters["statusColor3"](this.data.alertValue)
      };
    },
    logo() {
      return this.sysConfig && this.sysConfig["report.logo"]
        ? `/api/tools/download/${this.sysConfig["report.logo"]}`
        : "";
    }
  },

};
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.inline {
  display: inline;
  width: 15px;
  height: 15px;
}
.printTable {
  width: 100%;
}
.Bigtitle {
  text-align: center;
  font-size: 32px;
  height: 60px;
  margin-top: 50px;
}
.title {
  text-align: center;
  font-size: 28px;
  height: 40px;
}
</style>

03.2.3 仪表盘组件 代码

image.png 仪表盘组件按照每行4个显示,并且为了打印美观,设定该组件整体换页page-break-inside: avoid;

根据需要,还可以设定配置属性,以便配置仪表盘的最大值,切分几块,分区颜色等。

<template>
  <div class="printBlock">
    <div v-if="config.title" class="title">
      {{ this.$t("report." + config.title) }}
    </div>
    <table style="width:100%;border:1px solid #000">
      <tr v-for="(g, x) in chartData" :key="x">
        <td v-for="(item, y) in g" :key="y" align="center">
          <v-chart
            ref="line"
            class="chart"
            :theme="theme"
            :autoresize="true"
            :init-options="initOptions"
            :option="options[4 * x + y]"
          />
        </td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  name: "RptGuage",
  props: {
    data: {    
      type: Object,
      default: () => {
        return null;
      }
    },
    config: {
      type: Object,
      default: () => {
        return {};
      }
    }
  },
  data() {
    return {
      initOptions: {
        renderer: "canvas",
        locale: this.$i18n.locale
      },
      theme: "default", // default\light\dark
      option: {
        series: [
          {
            type: "gauge",
            min: 0,
            max: 5,
            splitNumber: 5,
            axisLine: {
              lineStyle: {
                width: 15,
                color: [
                  [0.25, "#7CFFB2"],
                  [0.5, "#0eb83a"],
                  [0.75, "#FDDD60"],
                  [1, "#FF6E76"]
                ]
              }
            },
            pointer: {
              itemStyle: {
                color: "auto"
              }
            },
            axisTick: {
              distance: -5,
              length: 10,
              lineStyle: {
                color: "#fff",
                width: 2
              }
            },
            splitLine: {
              distance: -10,
              length: 20,
              lineStyle: {
                color: "#fff",
                width: 4
              }
            },
            axisLabel: {
              color: "auto",
              distance: 10,
              fontSize: 14
            },
            detail: {
              valueAnimation: true,
              formatter: "{value}",
              // offsetCenter: [0, '0%'],
              color: "auto",
              fontSize: "16"
            },
            title: {
              show: true,
              offsetCenter: [0, "95%"]
            },
            data: [
              {
                value: 70,
                name: "人际关系敏感"
              }
            ]
          }
        ]
      },
      options: [],
      chartData: []
    };
  },
  created() {
    this.chartData = [];
    this.options = [];
    const arr = this.config.formatData(this.data);
    for (let i = 0; i < arr.length; i += 4) {
      const len = Math.min(4, arr.length - i);
      if (arr.length < 4) len = arr.length;
      this.chartData.push(arr.slice(i, i + len));
      for (let j = 0; j < len; j++) {
        const opt = JSON.parse(JSON.stringify(this.option));
        if (this.config.scale) {
          if (this.config.scale.length > i + j) {
            opt.series[0].max = this.config.scale[i + j].max || 5;
            opt.series[0].splitNumber =
              this.config.scale[i + j].splitNumber || 5;
            opt.series[0].axisLine.lineStyle.color = this.config.scale[
              i + j
            ].color;
          } else {
            opt.series[0].max = this.config.scale[0].max || 5;
            opt.series[0].splitNumber = this.config.scale[0].splitNumber || 5;
            opt.series[0].axisLine.lineStyle.color = this.config.scale[0].color;
          }
        }
        opt.series[0].data[0] = {
          title: { width: 160, overflow: "break" },
          ...arr[i + j]
        };
        this.options.push(opt);
      }
    }
  },
};
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.chart {
  width: 160px; //100%打印有bug
  height: 160px;
  border: 0px solid #000;
}
.title {
  width: 100%;
  text-align: center;
  font-weight: 800;
  font-size: 22px;
  margin: 20px auto;
}
.printBlock {
  page-break-inside: avoid;
}
</style>

03.2.4 折线图代码

注意: 因为data内无法使用计算属性跟踪变化,因此如果需要初始化数据后显示的化,应该在组件属性赋值前处理。

而我因为是后期才有类似需求,因此被逼在 created时初始化数据,并通过对echart的option属性修改,触发Echart的重绘,有点笨拙。

<template>
  <div>
    <div v-if="config.title" class="title">
      {{ this.$t("report." + config.title) }}
    </div>
    <v-chart
      ref="line"
      class="chart"
      :theme="theme"
      :autoresize="true"
      :init-options="initOptions"
      :option="option"
    />
  </div>
</template>

<script>
export default {
  name: "RptSingleLine",
  props: {
    data: {
      // scoresTool
      type: Object,
      default: () => {
        return null;
      }
    },
    config: {
      type: Object,
      default: () => {
        return {};
      }
    }
  }, // 因线图
  created() {
    if (this.config.init) {
      this.config.init(this.data);
      this.option.legend.data = this.config.keys;
      this.option.xAxis.data = this.config.keys;
      this.option.series[0].data = this.chartData();
    }
  },
  data() {
    return {
      initOptions: {
        renderer: "canvas",
        locale: this.$i18n.locale
      },
      theme: "default", // default\light\dark
      option: {
        title: {
          text: "",
          show: true,
          subtext: "西安西京医院-by webmote",
          // textAlign:'center',
          left: "right",
          top: "-10"
        },
        tooltip: {
          trigger: "axis"
        },
        legend: {
          width: 580,
          data: this.config.keys
        },
        grid: {
          left: "5%",
          right: "5%",
          bottom: "5",
          containLabel: true
        },
        xAxis: {
          type: "category",
          boundaryGap: true,
          data: this.config.keys,
          axisTick: { interval: 0, alignWithLabel: true },
          axisLabel: {
            interval: 0,
            rotate: this.config.keys.length > 6 ? 30 : 0
          }
        },
        yAxis: {
          name: this.$t("report." + this.config.yAxis),
          nameLocation: "middle",
          nameGap: 40,
          type: "value",
          min: 0,
          max: 100
        },
        series: [
          {
            data: this.chartData(),
            type: "line",
            smooth: true
          }
        ]
      }
    };
  },
  methods: {
    chartData() {    
      return this.config.formatData(this.data);
    }
  }
};
</script>

<style rel="stylesheet/scss" lang="scss" scoped>
.chart {
  width: 700px; //100%打印有bug
  height: 300px;
  border: 1px solid #000;
}
.title {
  width: 100%;
  text-align: center;
  font-weight: 800;
  font-size: 22px;
  margin: 20px auto;
}
</style>

03.2.5 报告配置文件定义

配置很多了,这里展示了默认的报告配置。 st是来自报告的相关数据,为了绘制图和仪表盘,总需要相关数据的。

🐢🐢🐢按着我的龟速算,不包含组件编写的话,基本3个小时可以完成20-30个配置的编写。

这查看和编写拷贝,已经让我烦不胜烦了。

做完后,我后悔了。

哎,先做个报告编辑器就好了,又可以涨一波技能了。

export default {
  default: [    
    {
      type: 'rptGuage',
      title: 'factorImage',    
      formatData: function(st) {       
        const keys = st.getScoreCols()
        if (!st) return []
        const arr = []
        keys.forEach(name => {
          if (name) {
            const data = st.getRaw(name)
            arr.push({
              name: name,
              value: data,
            })
          }
        })

        return arr
      },
    },
    {
      type: 'rptFactorTable',
      title: '',
      cols: [
        {
          name: 'factor',
          width: '15%',
        },
        {
          name: 'scoreValue',
          width: '10%',
        },
        {
          name: 'reducingRate',
          width: '10%',
        },
        {
          name: '',
          width: '15%',
        },
        {
          name: 'factor',
          width: '15%',
        },
        {
          name: 'scoreValue',
          width: '10%',
        },
        {
          name: 'reducingRate',
          width: '10%',
        },
        {
          name: '',
          width: '15%',
        }],    
      formatData: function(st) {
        const keys = st.getScoreCols()
        if (!st) return []
        const arr = []
        for (let i = 0; i < keys.length; i += 2) {
          arr.push([
            keys[i], st.getRawString(keys[i]), st.getRawReducingRate(keys[i]), '',
            keys[i + 1], st.getRawString(keys[i + 1]), st.getRawReducingRate(keys[i + 1]), '',
          ])
        }
        return arr
      },
    },

    {
      type: 'rptStackLine',
      title: 'historyReducingRate',   
      formatData: function(st) {
        const keys = st.getScoreCols()       
        if (!st) return []
        const arr = []
        keys.forEach(name => {
          if (name) {
            const data = st.getAllRawReducingRate(name)
            arr.push({
              name: name,
              type: 'line',
              data: data,
            })
          }
        })

        return arr
      },
    },
  ],
  ... //可以增加各个报告类型的配置
  }

🎏 04.再看看效果?

做完问卷调查,就是报告列表了。

我们处的这个时代,内卷太厉害,不过不管你是抑郁还是焦虑,本系统都能给你测一测。

image.png

查看报告~~

image.png

除了报告,本系统的算法也是很值钱的。

🎏 05.打印的缺陷——页眉页脚

利用脚本打印的报告整体是OK的,但页眉页脚显示出来比较难看,会显示网页链接等信息。

仅有2个方法能搞定它:

  • 利用打印选项,勾选掉页眉页脚选项,需要教导客户

image.png

  • 设置打印上下页边距为 3mm
// 去除页眉页脚
    @page {
            size: auto A4 landscape;
            margin: 3mm;
        }
        html{
    background-color: #FFFFFF;
    margin: 0;  /* this affects the margin on the html before sending to printer */
  }
 
  body{
    border: solid 1px blue ;
    margin: 10mm 15mm 10mm 15mm; 
    }

注意: 不要考虑定制页眉页脚,仅仅通过js方案是搞不定的,无数大牛已经证明这一点,别再浪费时间了! (我浪费了很多时间在这上面…)

有定制页眉页脚硬需求的

  • 请在服务端生成pdf,然后打印。

  • 或者安装打印插件…这个我没用过。

🎏 06. 结语

报告前前后后搞了有1周? 因为上班期间大概持续了有大半个月吧,只算纯时间,估计有1周,最后总算顺利搞定了,唯一的遗憾就是没有报告设计器

先把功能搞定,这也是做项目的基本原则。

下一个版本再增加报告设计器!

年少不识前端香,🕺🕺🕺 错把后端当个宝!

例行小结,理性看待!

结的是啥啊,结的是我想你点赞而不可得的寂寞。😳😳😳

👓都看到这了,还在乎点个赞吗?

👓都点赞了,还在乎一个收藏吗?

👓都收藏了,还在乎一个评论吗?

还有系列前端文章,客官,你不瞧瞧? 👉关于微前端(阿里QianKun)的那点事——上线一个“微前端”逼走了2位90后

👉前端项目,看我在这里管理全局后台初始化的数据,就问你飒不飒?

👉十分钟手把手教你设计简单易用的组件级考试题(单选、多选、填空、图片),建议收藏

👉解放前端工程师——手把手教你开发自己的自定义列表和自定义表单系列之一缘起

👉解放前端工程师——手把手教你开发自己的自定义列表和自定义表单系列之二接口

👉解放前端工程师——手把手教你开发自己的自定义列表和自定义表单系列之三表格

👉Vue组件定制——动态查询规则生成组件

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: webmote33 原文链接:https://juejin.im/post/6983510277576720415

回到顶部