使用 ElementUI 组件构建 Window 桌面应用探索与实践(WPF)

本文详细介绍了如何利用Vue、Vite、ElementUI和.NET CEF库构建Windows桌面应用,涵盖前端设计(包括图标、ECharts图表、页签显示等)、后端实现(如WPF项目设置、CefSharp集成、数据处理)以及SQLite环境搭建。通过这种技术栈,开发者可以利用前端技术提升应用界面的友好度,并实现前后端分离的开发模式。
摘要由CSDN通过智能技术生成

Tip: Beginners mind

Remember the days were you were a beginner. Or memorize, if you still are one. You have never learned enough. Think of yourself as you were a beginner, every day. Always try to see technologies from a beginners mind. You can accept corrections to your software better and leave the standard path if you need it more easily. There are some good ideas even from people who don’t have your experience.

零、实现原理与应用案例设计

1、原理

基础实例 Demo 可以参照以下这篇博文,

基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客文章浏览阅读389次。基于 .Net CEF 库,能够使用 Vue 等前端技术栈构建 Windows 窗体应用https://blog.csdn.net/weixin_47560078/article/details/133974513?spm=1001.2014.3001.5501原理非常简单,基于 .Net CEF 实现,用到的库为 CefSharp。

2、优势

  1. 可以使用Vue/React等前端技术美化页面,提升用户友好度
  2. 可以调度操作系统资源,例如打印机,命令行,文件
  3. 前后端开发可以并行

3、劣势

  1. 损失部分性能,占用系统资源更高
  2. 调试需要前后端分开调试,对开发人员要求高,需要懂前后端技术栈
  3. 非跨平台,仅支持Window

4、应用案例

从 Sqlite 数据库中加载数据,并渲染到前端,使用 Echart 进行可视化。

5、技术栈

Vite + Vue3 + TS + Echarts 5.4.3 + ElementUI(plus) + .NET Framework 4.7.2,开发环境为 Win10,VS2019,VS Code。 

6、开发流程

  1. 整合 Vue + Vite + ElementUI +Echarts
  2. 把 JS 需要调用的 .Net 方法临时用 JS 方法代替
  3. 页面开发完毕,接着开发 .Net 方法,业务处理逻辑
  4. 导出 .Net 方法,临时 JS 方法替换为真正的 .Net 方法
  5. 最后发布测试

一、前端设计与实现

1、整合 Vue + Vite + ElementUI

# 创建 vite vue
cnpm create vite@latest

# element-plus 国内镜像 https://element-plus.gitee.io/zh-CN/
# 安装 element-plus
cnpm install element-plus --save

按需引入 element plus,

# 安装导入插件
cnpm install -D unplugin-vue-components unplugin-auto-import

在 main.ts 引入 element-plus 和样式,

// app\src\main.ts
import { createApp } from 'vue'
//import './style.css'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
 
createApp(App).use(ElementPlus).mount('#app')

配置 vite,

// app\vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
 
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

2、使用图标 Icon

 cnpm install @element-plus/icons-vue

3、使用 ECharts

cnpm install echarts

折线图示例,

// src\components\MyChart.vue
<template>
  <div ref="chartDom" style="width:100%;height:200px;"> </div>
</template>

<script setup lang="ts">
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
type EChartsOption = echarts.EChartsOption;

const chartDom = ref(null)

var option: EChartsOption;
option = {
  title: {
    text: 'Stacked Line'
  },
  tooltip: {
    trigger: 'axis'
  },
  legend: {
    data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine']
  },
  grid: {
    left: '3%',
    right: '4%',
    bottom: '3%',
    containLabel: true
  },
  toolbox: {
    feature: {
      saveAsImage: {}
    }
  },
  xAxis: {
    type: 'category',
    boundaryGap: false,
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  series: [
    {
      name: 'Email',
      type: 'line',
      stack: 'Total',
      data: [120, 132, 101, 134, 90, 230, 210]
    },
    {
      name: 'Union Ads',
      type: 'line',
      stack: 'Total',
      data: [220, 182, 191, 234, 290, 330, 310]
    },
    {
      name: 'Video Ads',
      type: 'line',
      stack: 'Total',
      data: [150, 232, 201, 154, 190, 330, 410]
    },
    {
      name: 'Direct',
      type: 'line',
      stack: 'Total',
      data: [320, 332, 301, 334, 390, 330, 320]
    },
    {
      name: 'Search Engine',
      type: 'line',
      stack: 'Total',
      data: [820, 932, 901, 934, 1290, 1330, 1320]
    }
  ]
};

onMounted(() => {
  var myChart = echarts.init(chartDom.value);
  myChart.setOption(option);

  // 自适应屏幕
  window.addEventListener('resize', () => {
    myChart.resize()
  })

})

</script>

<style scoped></style>

效果,

4、页签显示

MyChart 页面使用页签切换不同的图表,不同的图表封装为单独的一个组件,由 MyChart 页面按需引入,

// src\components\MyChart.vue
<template>
  <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
    <el-tab-pane label="PictorialBar" name="PictorialBar">
      <MyPictorialBar />
    </el-tab-pane>
    <el-tab-pane label="StackedLine" name="StackedLine">
      <MyStackedLine />
    </el-tab-pane>
    <el-tab-pane label="Scatter" name="Scatter">
      <MyScatter />
    </el-tab-pane>
    <el-tab-pane label="Bar" name="Bar">
      <MyBar />
    </el-tab-pane>
    <el-tab-pane label="Radar" name="Radar">
      <MyRadar />
    </el-tab-pane>
    <el-tab-pane label="Graph" name="Graph">
      <MyGraph />
    </el-tab-pane>
    <el-tab-pane label="Gauge" name="Gauge">
      <MyGauge />
    </el-tab-pane>
    <el-tab-pane label="Rich" name="Rich">
      <MyRich />
    </el-tab-pane>
  </el-tabs>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'
import MyStackedLine from './MyStackedLine.vue'
import MyPictorialBar from './MyPictorialBar.vue'
import MyScatter from './MyScatter.vue'
import MyBar from './MyBar.vue'
import MyRadar from './MyRadar.vue'
import MyGraph from './MyGraph.vue'
import MyGauge from './MyGauge.vue'
import MyRich from './MyRich.vue'

const activeName = ref('PictorialBar')

const handleClick = (tab: TabsPaneContext, event: Event) => {
  console.log(tab, event)
}
</script>
<style>
.demo-tabs>.el-tabs__content {
  padding: 32px;
  color: #6b778c;
  font-size: 32px;
  font-weight: 600;
}</style>

// src\components\MyPictorialBar.vue
<template>
    <div ref="chartDom" style="width:1200px;height:450px;"> </div>
</template>
    
<script setup lang="ts">
import * as echarts from 'echarts';
import { ref, onMounted } from 'vue';
type EChartsOption = echarts.EChartsOption;

// 前端 DEBUG 用
const ROOT_PATH = 'http://localhost:5173';
// 后端整合用
// const ROOT_PATH = 'http://wpf.test';

const paperDataURI =
  'data:image/png;base64,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';

const chartDom = ref(null);
let myChart: echarts.ECharts;
var option: EChartsOption;
option = {
  backgroundColor: '#0f375f',
  tooltip: {},
  legend: {
    textStyle: { color: '#ddd' }
  },
  xAxis: [
    {
      data: ['Christmas Wish List', '', 'Qomolangma', 'Kilimanjaro'],
      axisTick: { show: false },
      axisLine: { show: false },
      axisLabel: {
        margin: 20,
        color: '#ddd',
        fontSize: 14
      }
    }
  ],
  yAxis: {
    splitLine: { show: false },
    axisTick: { show: false },
    axisLine: { show: false },
    axisLabel: { show: false }
  },
  markLine: {
    z: -1
  },
  animationEasing: 'elasticOut',
  series: [
    {
      type: 'pictorialBar',
      name: 'All',
      emphasis: {
        scale: true
      },
      label: {
        show: true,
        position: 'top',
        formatter: '{c} m',
        fontSize: 16,
        color: '#e54035'
      },
      data: [
        {
          value: 13000,
          symbol: 'image://' + paperDataURI,
          symbolRepeat: true,
          symbolSize: ['130%', '20%'],
          symbolOffset: [0, 10],
          symbolMargin: '-30%',
          animationDelay: function (_dataIndex, params: any) {
            return params.index * 30;
          }
        },
        {
          value: '-',
          symbol: 'none'
        },
        {
          value: 8844,
          symbol:
            'image://' + ROOT_PATH + '/hill-Qomolangma.png',
          symbolSize: ['200%', '105%'],
          symbolPosition: 'end',
          z: 10
        },
        {
          value: 5895,
          symbol:
            'image://' + ROOT_PATH + '/hill-Kilimanjaro.png',
          symbolSize: ['200%', '105%'],
          symbolPosition: 'end'
        }
      ],
      markLine: {
        symbol: ['none', 'none'],
        label: {
          show: false
        },
        lineStyle: {
          color: '#e54035',
          width: 2
        },
        data: [
          {
            yAxis: 8844
          }
        ]
      }
    },
    {
      name: 'All',
      type: 'pictorialBar',
      barGap: '-100%',
      symbol: 'circle',
      itemStyle: {
        color: '#185491'
      },
      silent: true,
      symbolOffset: [0, '50%'],
      z: -10,
      data: [
        {
          value: 1,
          symbolSize: ['150%', 50]
        },
        {
          value: '-'
        },
        {
          value: 1,
          symbolSize: ['200%', 50]
        },
        {
          value: 1,
          symbolSize: ['200
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

余衫马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值