老山君 發表於 2025-7-25 12:22:00

在SqlSugar的开发框架的Vue3+ElementPlus前端中增加对报表模块的封装处理,实现常规报表的快速处理

<p>在我们开发业务系统的时候,往往都需要一些数据报表进行统计查看,本篇内容介绍如何在实际的前端中对报表内容进行的一些封装操作,以便提高报表模块开发的效率,报表模块的展示主要是结合Vue3中比较广泛使用的echarts图表组件进行展示。</p>
<h3>1、ECharts 图表组件介绍</h3>
<p>ECharts 是一款基于 JavaScript 的开源可视化图表库,它非常高效,能够支持大量数据的渲染。与 Vue 3 配合时,ECharts 能够快速响应视图更新,确保报表的平滑渲染和高性能表现。ECharts 提供了多种类型的图表(如折线图、柱状图、饼图、散点图、雷达图等),并且支持多种交互方式(如缩放、提示框、动态数据等)。</p>
<p>Vue 3 提供了强大的组件化开发方式,可以将不同的图表封装成独立的组件,方便维护、重用和组合。每个图表组件可以根据不同的报表需求定制,实现高度复用。</p>
<p>Vue 3 提供了双向绑定和响应式的数据流机制,当 Vue 组件的状态发生变化时,图表可以自动更新。例如,通过绑定数据到 <code data-start="935" data-end="948">chartOption</code>,一旦数据变化,ECharts 会自动重新渲染相应的图表。因此我们可以通过动态绑定数据的方式,实现报表模块的图表展示。</p>
<p>ECharts&nbsp;的官网地址:https://echarts.apache.org/zh/index.html&nbsp;</p>
<p>ECharts 的各种案例地址:https://echarts.apache.org/examples/zh/index.html</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202507/8867-20250725115045674-1305084861.png" alt="image" width="893" height="570" loading="lazy"></p>
<p>我们单击每个具体的图表例子,可以查看对应的数据和形状,根据具体业务的数据和相关设置,替换这些数据就可以了。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202507/8867-20250725115723589-330360823.png" alt="image" width="901" height="714" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h3>2、定义通用图表组件和具体业务图表组件</h3>
<p>我们为了方便开发各类型的业务图表,我们可以针对性的对图表类型、折线类型、条状类型图表进行一些简单的封装,以便方便统一使用相关的数据。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202507/8867-20250725120114600-1118550937.png" alt="image" width="448" height="327" loading="lazy"></p>
<p>上面我在组件目录里面创建了几个不同类型的图表组件,组件主要公布一些props参数来传递,如下说是定义数据的属性。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">声明Props的接口类型</span>
<span style="color: rgba(0, 0, 0, 1)">interface Props {
data</span>?: any[]; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 固定列表方式,直接绑定,项目包括id,label属性</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用默认值定义Props</span>
const props = withDefaults(defineProps&lt;Props&gt;<span style="color: rgba(0, 0, 0, 1)">(), {
data: () </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [];
},
});</span></pre>
</div>
<p>然后对data的属性监控,变化的时候,加载图表数据即可。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">watch(
() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> props.data,
(newValue </span>= []) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    loadChart(newValue);
},
{ immediate: </span><span style="color: rgba(0, 0, 255, 1)">true</span> } <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可选:如果你希望首次立即触发</span>
<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 加载图表数据</span>
async <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> loadChart(res) {
setOptions(
    {
      tooltip: {
      trigger: </span>"item"<span style="color: rgba(0, 0, 0, 1)">
      },
      legend: { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">图例设置</span>
      orient: "vertical"<span style="color: rgba(0, 0, 0, 1)">,
      right: </span>'right'<span style="color: rgba(0, 0, 0, 1)">
      },
      series: [
      {
          name: </span>"标题信息", <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">图表标题</span>
          type: "pie",    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">图表类型,饼图</span>
          radius: "60%"<span style="color: rgba(0, 0, 0, 1)">,
          center: [</span>"30%", "50%"<span style="color: rgba(0, 0, 0, 1)">],
          data: res, </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">动态数据</span>
<span style="color: rgba(0, 0, 0, 1)">          emphasis: {
            itemStyle: {
            shadowBlur: </span>10<span style="color: rgba(0, 0, 0, 1)">,
            shadowOffsetX: </span>0<span style="color: rgba(0, 0, 0, 1)">,
            shadowColor: </span>"rgba(0, 0, 0, 0.5)"<span style="color: rgba(0, 0, 0, 1)">
            }
          }
      }
      ]
    },
    {
      name: </span>"click"<span style="color: rgba(0, 0, 0, 1)">,
      callback: params </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      console.log(</span>"click"<span style="color: rgba(0, 0, 0, 1)">, params);
      }
    },
    {
      type: </span>"zrender"<span style="color: rgba(0, 0, 0, 1)">,
      name: </span>"click"<span style="color: rgba(0, 0, 0, 1)">,
      callback: params </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      console.log(</span>"点击空白处"<span style="color: rgba(0, 0, 0, 1)">, params);
      }
    }
);
};</span></pre>
</div>
<p>而通用图表组件的界面代码比较简单,只需要标记下控件即可,如下代码所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">div </span><span style="color: rgba(255, 0, 0, 1)">ref</span><span style="color: rgba(0, 0, 255, 1)">="pieChartRef"</span><span style="color: rgba(255, 0, 0, 1)"> style</span><span style="color: rgba(0, 0, 255, 1)">="width: 100%; height: 35vh"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<p>有了简单的组件,我们再次在此基础上,对不同业务表现类型的图表进行更高层次的组件封装,以便可以用在首页或者其他地方,实现多个案例重用显示的处理。</p>
<p>例如,对应统计某个类型的业务图表,如下所示。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202507/8867-20250725121102517-1299032204.png" alt="image" width="643" height="412" loading="lazy"></p>
<p>&nbsp;对于数据的处理,我们通过接口动态获取图表统计的data数据,如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 饼图处理</span>
async <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> searchPie() {
const data </span>=<span style="color: rgba(0, 0, 0, 1)"> await report.CarbonSummaryCategory(Number(year.value));
pieData.value </span>= data.map(item =&gt;<span style="color: rgba(0, 0, 0, 1)"> ({
    value: item.value,
    name: `${item.category} (${item.percentage})`
}));
}</span></pre>
</div>
<p>组件封装的时候,我们直接使用前面封装的组件。</p>
<div class="cnblogs_code">
<pre>import <span style="color: rgba(255, 0, 0, 1)">Pie</span> from '@/components/echarts/Pie.vue';</pre>
</div>
<p>这个业务图表组件,我们为了通用,也需要提供一些属性供外部传入,实现数据的动态化处理,因此通过提供prop的属性方式处理。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">声明Props的接口类型</span>
<span style="color: rgba(0, 0, 0, 1)">interface Props {
year</span>?: number |<span style="color: rgba(0, 0, 0, 1)"> string;
month</span>?: number |<span style="color: rgba(0, 0, 0, 1)"> string;
stack</span>?: <span style="color: rgba(0, 0, 255, 1)">boolean</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">是否堆叠</span>
type?: string; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">图表类型</span>
showLink?: <span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">;
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">使用默认值定义Props</span>
const props = withDefaults(defineProps&lt;Props&gt;<span style="color: rgba(0, 0, 0, 1)">(), {
year: </span>0<span style="color: rgba(0, 0, 0, 1)">,
month: </span>0<span style="color: rgba(0, 0, 0, 1)">,
stack: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">,
type: </span>'bar'<span style="color: rgba(0, 0, 0, 1)">,
showLink: </span><span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
});</span></pre>
</div>
<p>这样组件的处理逻辑代码如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 获取当前日期</span>
const currentDate = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Date();
const currentYear </span>=<span style="color: rgba(0, 0, 0, 1)"> ref(currentDate.getFullYear());
const currentMonth </span>= ref(currentDate.getMonth() + 1); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 月份从0开始,所以加1</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ✅ 在 setup 中补充默认值(只当 props 没传时才使用当前时间)</span>
const year = computed(() =&gt; Number(props.year ||<span style="color: rgba(0, 0, 0, 1)"> currentYear.value))
const month </span>= computed(() =&gt; Number(props.month ||<span style="color: rgba(0, 0, 0, 1)"> currentMonth.value))

const loading </span>= ref(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">);
const barData </span>= ref(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 折线图数据</span>
const pieData = ref([]); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 饼图数据</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">页面初始化加载</span>
onMounted(async () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
await search();
});

async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> search() {
loading.value </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
await searchPie();

setTimeout(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    loading.value </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}, </span>500<span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监听 Props 变化</span>
<span style="color: rgba(0, 0, 0, 1)">watch(
() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> ,
async () </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    await search();
}
);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 饼图处理</span>
async <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> searchPie() {
const data </span>=<span style="color: rgba(0, 0, 0, 1)"> await report.CarbonSummaryCategory(Number(year.value));
pieData.value </span>= data.map(item =&gt;<span style="color: rgba(0, 0, 0, 1)"> ({
    value: item.value,
    name: `${item.category} (${item.percentage})`
}));
}</span></pre>
</div>
<p>界面代码处理上,我们使用第一次封装的饼图组件,并通过提供外部卡片的显示封装,使它更加好看一些。如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">div </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="welcome"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">el-card</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">template </span><span style="color: rgba(255, 0, 0, 1)">#header</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">span </span><span style="color: rgba(255, 0, 0, 1)">style</span><span style="color: rgba(0, 0, 255, 1)">="font-size: 16px; font-weight: 500"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span> {{ year }} 年碳排放占比 <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">span</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">div </span><span style="color: rgba(255, 0, 0, 1)">class</span><span style="color: rgba(0, 0, 255, 1)">="float-end"</span><span style="color: rgba(255, 0, 0, 1)"> v-if</span><span style="color: rgba(0, 0, 255, 1)">="showLink"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
          <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">router-link </span><span style="color: rgba(255, 0, 0, 1)">to</span><span style="color: rgba(0, 0, 255, 1)">="/report/carbon_summary"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
            <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">el-link </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="primary"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>查看详细<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">el-link</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
          <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">router-link</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">div</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">el-skeleton </span><span style="color: rgba(255, 0, 0, 1)">animated :rows</span><span style="color: rgba(0, 0, 255, 1)">="7"</span><span style="color: rgba(255, 0, 0, 1)"> :loading</span><span style="color: rgba(0, 0, 255, 1)">="loading"</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">template </span><span style="color: rgba(255, 0, 0, 1)">#default</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
          <strong><span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">Pie </span><span style="color: rgba(255, 0, 0, 1)">:data</span><span style="color: rgba(0, 0, 255, 1)">="pieData"</span> <span style="color: rgba(0, 0, 255, 1)">/&gt;</span></strong>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
      <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">el-skeleton</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
    <span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">el-card</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">div</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>
<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">template</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span></pre>
</div>
<p>其他条状图表、折线图表等其他类型的图表,依次通过这样的处理方式,可以实现业务组件的重用。</p>
<p>如我们可能在首页上放置一些图表组件,具体报表页面上也放置相同的图表组件,这样就可以很好的重用了。</p>
<p><img src="https://img2024.cnblogs.com/blog/8867/202507/8867-20250725121932280-109517297.png" alt="image" loading="lazy"></p>
<p>&nbsp;</p>
<p>在前端界面开发中,良好的组件封装和使用,可以给我们提供更好的开发效率,因此为了业务的快速开发,我们不仅在代码生成代码的方面持续优化,也在一些前端页面的开发中,提取一些常用的场景组件,最大化的实现代码的快速开发。</p>
<p>&nbsp;</p>

</div>
<div id="MySignature" role="contentinfo">
    <div style="border-right-color: #cccccc; border-right-width: 1px; border-right-style: solid; padding-right: 5px; border-top-color: #cccccc; border-top-width: 1px; border-top-style: solid; padding-left: 4px; font-size: 13px; padding-bottom: 4px; border-left-color: #cccccc; border-left-width: 1px; border-left-style: solid; width: 98%; padding-top: 4px; border-bottom-color: #cccccc; border-bottom-width: 1px; border-bottom-style: solid; background-color: #eeeeee;">
    <img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" alt>
    <span style="color: #000000"><span class="Apple-tab-span" style="white-space: pre"></span>
   专注于代码生成工具、.Net/Python 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架、Python开发框架等框架产品。
   <br>  转载请注明出处:撰写人:伍华聪  http://www.iqidi.com <br>    </span></div><br><br>
来源:https://www.cnblogs.com/wuhuacong/p/19004341
頁: [1]
查看完整版本: 在SqlSugar的开发框架的Vue3+ElementPlus前端中增加对报表模块的封装处理,实现常规报表的快速处理