免费网站代码大全,网站免费正能量小说,怎么样开网站,网站ip和pv零、实现原理与应用案例设计
1、原理
基础实例 Demo 可以参照以下这篇博文#xff0c;
基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客文章浏览阅读291次。基于 .Net CEF 库#xff0c;能够使用 Vue 等前端技术栈构建 Windows 窗体应用https://blog.c…零、实现原理与应用案例设计
1、原理
基础实例 Demo 可以参照以下这篇博文
基于.Net CEF 实现 Vue 等前端技术栈构建 Windows 窗体应用-CSDN博客文章浏览阅读291次。基于 .Net CEF 库能够使用 Vue 等前端技术栈构建 Windows 窗体应用https://blog.csdn.net/weixin_47560078/article/details/133974513原理非常简单基于 .Net CEF 实现用到的库为 CefSharp。
2、优势
可以使用Vue/React等前端技术美化页面提升用户友好度可以调度操作系统资源例如打印机命令行文件前后端开发可以并行
3、劣势
损失部分性能占用系统资源更高调试需要前后端分开调试对开发人员要求高需要懂前后端技术栈非跨平台仅支持Window
4、应用案例
该桌面应用从数据库加载数据到页面表格用户可以根据需求修改表格数据保存到Excel打印PDF。
5、技术栈
Vite Vue3 TS ElementUI(plus) .NET Framework 4.7.2开发环境为 Win10VS2019VS Code。
6、开发流程
整合 Vue Vite ElementUI把 JS 需要调用的 .Net 方法临时用 JS 方法代替页面开发完毕接着开发 .Net 方法业务处理逻辑导出 .Net 方法临时 JS 方法替换为真正的 .Net 方法最后发布测试
一、前端设计与实现
1、整合 Vue Vite ElementUI
# 创建 vite vue
cnpm create vitelatest # 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 和样式
// myapp\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.csscreateApp(App).use(ElementPlus).mount(#app)
配置 vite
// myapp\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()],}),],
})新建一个组件页面在 App.vue 中引用
// myapp\src\components\DataViewer.vue
templateel-table :datatableData stylewidth: 100%el-table-column fixed propdate labelDate width150 /el-table-column propname labelName width120 /el-table-column propstate labelState width120 /el-table-column propcity labelCity width120 /el-table-column propaddress labelAddress width600 /el-table-column propzip labelZip width120 /el-table-column fixedright labelOperations width120template #defaultel-button link typeprimary sizesmall clickhandleClickDetail/el-buttonel-button link typeprimary sizesmallEdit/el-button/template/el-table-column/el-table
/templatescript langts setup
const handleClick () {console.log(click)
}const tableData [{date: 2016-05-03,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Home,},{date: 2016-05-02,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Office,},{date: 2016-05-04,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Home,},{date: 2016-05-01,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Office,},
]
/script运行
npm run dev 官方 Table 组件示例运行效果如下 2、使用图标 Icon补充 cnpm install element-plus/icons-vue 3、api 封装
封装 DataUtil 用于模拟调用 .Net 方法获取数据
// myapp\src\api\DataUtil.tsexport const getData async (): Promiseany {// await CefSharp.BindObjectAsync(dataUtil)// return dataUtil.getData()return new Promise((resolve, _) {resolve([{date: 2016-05-03,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Home,},{date: 2016-05-02,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Office,},{date: 2016-05-04,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Home,},{date: 2016-05-01,name: Tom,state: California,city: Los Angeles,address: No. 189, Grove St, Los Angeles,zip: CA 90036,tag: Office,},])})
}
4、获取数据实现
页面 DataViewer.vue 加载数据调用 api 的 getData 异步方法在页面挂载时请求数据
// myapp\src\components\DataViewer.vue
templateel-table v-loadingloading :datatableData stylewidth: 100%el-table-column fixed propdate labelDate width150 /el-table-column propname labelName width120 /el-table-column propstate labelState width120 /el-table-column propcity labelCity width120 /el-table-column propaddress labelAddress width600 /el-table-column propzip labelZip width120 /el-table-column fixedright labelOperations width120template #defaultel-button link typeprimary sizesmall clickhandleClickEdit/el-buttonel-button link typeprimary sizesmallDelete/el-button/template/el-table-column/el-table
/templatescript langts setup
import { ref, onMounted, reactive } from vue
import { getData } from ../api/DataUtil// 表格数据加载标志
const loading ref(true)const handleClick () {console.log(click)
}const tableData reactive([])onMounted(() {// 获取数据后将加载标志位位置 false并且绑定到表格getData().then((res: any) {loading.value falseconsole.log( getData , res)Object.assign(tableData, res)})})/script本地运行效果如下 5、更新数据实现
页面 DataViewer.vue 选中表格的某行数据后点击”Edit“进行编辑编辑后确认更新回显到页面
// myapp\src\components\DataViewer.vue
template!-- 获取数据后展示 --el-table v-loadingloading :datatableData stylewidth: 100%el-table-column fixed typeindex :indexindexMethod /el-table-column propdate labelDate width150 /el-table-column propname labelName width120 /el-table-column propstate labelState width120 /el-table-column propcity labelCity width120 /el-table-column propaddress labelAddress width600 /el-table-column propzip labelZip width120 /el-table-column fixedright labelOperations width120template #defaultscopeel-button link typeprimary sizesmall clickhandleEdit(scope)Edit/el-buttonel-button link typeprimary sizesmallDelete/el-button/template/el-table-column/el-table!-- 更新数据时对话框 --el-dialog v-modeldialogFormVisible titleShipping addressel-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogFormVisible falseCancel/el-buttonel-button typeprimary clickhandleUpdate()Confirm/el-button/span/template/el-dialog
/templatescript langts setup
import { ref, onMounted, reactive } from vue
import { getData } from ../api/DataUtil// 表格数据加载标志
const loading ref(true)
const tableData reactive([])// 更新数据对话框
const dialogFormVisible ref(false)
const formLabelWidth 140px// 当前选中行数据
const currentRow reactive({date: please input date,name: please input name,state: please input state,city: please input city,address: please input address,zip: please input zip,tag: please input tag,
})const currentRowIndex ref(1)// 更新事件
const handleUpdate () {//console.log( handleUpdate , currentRow, currentRowIndex.value)Object.assign(tableData[currentRowIndex.value], currentRow)dialogFormVisible.value false
}// 索引规则
const indexMethod (index: number) {return index 1
}// Edit 事件
const handleEdit (scope: any) {//console.log(edit,scope.$index,scope.row)Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogFormVisible.value true
}// 挂载方法
onMounted(() {// 获取数据后将加载标志位位置 false并且绑定到表格getData().then((res: any) {//console.log( getData , res)loading.value falseObject.assign(tableData, res)})})/script
style
.el-button--text {margin-right: 15px;
}.el-select {width: 300px;
}.el-input {width: 300px;
}.dialog-footer button:first-child {margin-right: 10px;
}
/style
本地运行效果如下 6、删除数据实现
页面 DataViewer.vue 选中表格的某行数据后点击”Delete“进行删除弹窗确认将删除结果回显到页面
// myapp\src\components\DataViewer.vue
template!-- 获取数据后展示 --el-table v-loadingloading :datatableData stylewidth: 100%el-table-column fixed typeindex :indexindexMethod /el-table-column propdate labelDate width150 /el-table-column propname labelName width120 /el-table-column propstate labelState width120 /el-table-column propcity labelCity width120 /el-table-column propaddress labelAddress width600 /el-table-column propzip labelZip width120 /el-table-column fixedright labelOperations width120template #defaultscopeel-button link typeprimary sizesmall clickhandleEdit(scope)Edit/el-buttonel-button link typeprimary sizesmall clickhandleDelete(scope)Delete/el-button/template/el-table-column/el-table!-- 更新数据时对话框 --el-dialog v-modeldialogUpdateFormVisible titleUpdate Shipping address ?el-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogUpdateFormVisible falseCancel/el-buttonel-button typeprimary clickdoUpdate()Confirm/el-button/span/template/el-dialog!-- 删除数据时对话框 --el-dialog v-modeldialogDeleteFormVisible titleDelete Shipping address ?el-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input disabled v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input disabled v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input disabled v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input disabled v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input disabled v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input disabled v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input disabled v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogDeleteFormVisible falseCancel/el-buttonel-button typeprimary clickdoDelete()Confirm/el-button/span/template/el-dialog
/templatescript langts setup
import { ref, onMounted, reactive } from vue
import { getData } from ../api/DataUtil
import { ElMessage } from element-plus// 表格数据加载标志
const loading ref(true)
const tableData reactive([])// 更新数据对话框
const dialogUpdateFormVisible ref(false)
const dialogDeleteFormVisible ref(false)
const formLabelWidth 140px// 当前选中行数据
const currentRow reactive({date: please input date,name: please input name,state: please input state,city: please input city,address: please input address,zip: please input zip,tag: please input tag,
})const currentRowIndex ref(1)// 更新事件
const doUpdate () {//console.log( doUpdate , currentRow, currentRowIndex.value)Object.assign(tableData[currentRowIndex.value], currentRow)dialogUpdateFormVisible.value false
}// 删除事件
const doDelete () {// console.log(doDelete , currentRowIndex.value)tableData.splice(currentRowIndex.value, 1)dialogDeleteFormVisible.value falseElMessage({message: Delete success.,type: success,})
}// 索引规则
const indexMethod (index: number) {return index 1
}
// Delete 事件
const handleDelete (scope: any) {Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogDeleteFormVisible.value true
}// Edit 事件
const handleEdit (scope: any) {//console.log(edit,scope.$index,scope.row)Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogUpdateFormVisible.value true
}// 挂载方法
onMounted(() {// 获取数据后将加载标志位位置 false并且绑定到表格getData().then((res: any) {//console.log( getData , res)loading.value falseObject.assign(tableData, res)})})/script
style
.el-button--text {margin-right: 15px;
}.el-select {width: 300px;
}.el-input {width: 300px;
}.dialog-footer button:first-child {margin-right: 10px;
}
/style
本地运行效果如下 7、保存/打印数据实现
点击页面的“Save”按钮弹出对话框选择文件保存的路径将数据保存为 Excel 文件点击页面的“Print”按钮将数据转化为PDF格式打印两个功能都需要调用 .Net 方法实现
// myapp\src\api\ExcelUtil.ts
export const saveAsExcel async (data: any): Promiseany {// await CefSharp.BindObjectAsync(excelUtil)// return excelUtil.saveAsExcel(data)return new Promise((resolve, _) {resolve({code: 1,msg: ok,isSuccess: true})})
}
// myapp\src\api\PrinterUtil.ts
export const printPdf async (data: any): Promiseany {// await CefSharp.BindObjectAsync(printerlUtil)// return printerlUtil.printPdf(data)return new Promise((resolve, _) {resolve({code: 1,msg: ok,isSuccess: true})})
}
// myapp\src\components\DataViewer.vue
templatediv classcommon-layoutel-containerel-main!-- 获取数据后展示 --el-table v-loadingloading :datatableData height485 stylewidth: 100%el-table-column fixed typeindex :indexindexMethod /el-table-column propdate labelDate width150 /el-table-column propname labelName width120 /el-table-column propstate labelState width120 /el-table-column propcity labelCity width120 /el-table-column propaddress labelAddress width600 /el-table-column propzip labelZip width120 /el-table-column fixedright labelOperations width120template #defaultscopeel-button link typeprimary sizesmall clickhandleEdit(scope)Edit/el-buttonel-button link typeprimary sizesmall clickhandleDelete(scope)Delete/el-button/template/el-table-column/el-table!-- 更新数据时对话框 --el-dialog v-modeldialogUpdateFormVisible titleUpdate Shipping address ?el-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogUpdateFormVisible falseCancel/el-buttonel-button typeprimary clickdoUpdate()Confirm/el-button/span/template/el-dialog!-- 删除数据时对话框 --el-dialog v-modeldialogDeleteFormVisible titleDelete Shipping address ?el-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input disabled v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input disabled v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input disabled v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input disabled v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input disabled v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input disabled v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input disabled v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogDeleteFormVisible falseCancel/el-buttonel-button typeprimary clickdoDelete()Confirm/el-button/span/template/el-dialog/el-mainel-headerel-row classrow-bg justifyendel-col :span2el-button clickhandleSaveData typeprimarySaveel-icon classel-icon--rightDocument //el-icon/el-button/el-colel-col :span2el-button clickhandlePrintData typeprimaryPrintel-icon classel-icon--rightPrinter //el-icon/el-button/el-col/el-row/el-header/el-container/div
/templatescript langts setup
import { ref, onMounted, reactive } from vue
import { getData } from ../api/DataUtil
import { saveAsExcel } from ../api/ExcelUtil
import { printPdf } from ../api/PrinterUtil
import { ElMessage } from element-plus
import { Document, Printer } from element-plus/icons-vue// 表格数据加载标志
const loading ref(true)
const tableData reactive([])// 更新数据对话框
const dialogUpdateFormVisible ref(false)
const dialogDeleteFormVisible ref(false)
const formLabelWidth 140px// 当前选中行数据
const currentRow reactive({date: please input date,name: please input name,state: please input state,city: please input city,address: please input address,zip: please input zip,tag: please input tag,
})const currentRowIndex ref(1)// 更新事件
const doUpdate () {//console.log( doUpdate , currentRow, currentRowIndex.value)Object.assign(tableData[currentRowIndex.value], currentRow)dialogUpdateFormVisible.value falseElMessage({message: Update success.,type: success,})
}// 删除事件
const doDelete () {// console.log(doDelete , currentRowIndex.value)tableData.splice(currentRowIndex.value, 1)dialogDeleteFormVisible.value falseElMessage({message: Delete success.,type: success,})
}// 索引规则
const indexMethod (index: number) {return index 1
}
// Delete 事件
const handleDelete (scope: any) {Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogDeleteFormVisible.value true
}// Edit 事件
const handleEdit (scope: any) {//console.log(edit,scope.$index,scope.row)Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogUpdateFormVisible.value true
}// 保存事件
const handleSaveData () {saveAsExcel(tableData).then((res: any) {if (res.isSuccess) {ElMessage({message: Save success.,type: success,})} else {ElMessage({message: res.msg,type: error,})}})
}// 打印事件
const handlePrintData () {printPdf(tableData).then((res: any) {if (res.isSuccess) {ElMessage({message: Save success.,type: success,})} else {ElMessage({message: res.msg,type: error,})}})
}// 挂载方法
onMounted(() {// 获取数据后将加载标志位位置 false并且绑定到表格getData().then((res: any) {//console.log( getData , res)loading.value falseObject.assign(tableData, res)})})/script
style
.el-button--text {margin-right: 15px;
}.el-select {width: 300px;
}.el-input {width: 300px;
}.dialog-footer button:first-child {margin-right: 10px;
}.el-row {margin-bottom: 20px;
}.el-row:last-child {margin-bottom: 0;
}.el-col {border-radius: 4px;
}
/style 二、后端设计与实现
1、新建 WimForm 项目 2、安装 CefSharp 程序包
CefSharp.WinForms 3、窗体无边框设置可选
3.1、FormBorderStyle 属性置为 NONE可选 3.2、实现窗体事件可选
通过窗体 MouseDown、MouseMove、MouseUp 鼠标事件实现窗体移动 // DataToolForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace DataToolApp
{public partial class DataToolForm : Form{/// summary/// 鼠标按下时的点/// /summaryprivate Point point;/// summary/// 拖动标识/// /summaryprivate bool isMoving false;public DataToolForm(){InitializeComponent();}/// summary/// 鼠标按下时启用拖动/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_MouseDown(object sender, MouseEventArgs e){point e.Location;isMoving true;}/// summary/// 鼠标移动计算移动的位置/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_MouseMove(object sender, MouseEventArgs e){if (e.Button MouseButtons.Left isMoving){Point pNew new Point(e.Location.X - point.X, e.Location.Y - point.Y);Location new Size(pNew);}}/// summary/// 鼠标停下/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_MouseUp(object sender, MouseEventArgs e){isMoving false;}}
}3.3、窗体拖拽效果可选 4、窗体页面配置
4.1、在 UI 线程上异步执行 Action
新建文件夹添加类 ControlExtensions // DataToolApp\Controls\ControlExtensions.cs
using System;
using System.Windows.Forms;namespace DataToolApp.Controls
{public static class ControlExtensions{/// summary/// Executes the Action asynchronously on the UI thread, does not block execution on the calling thread./// /summary/// param namecontrolthe control for which the update is required/param/// param nameactionaction to be performed on the control/parampublic static void InvokeOnUiThreadIfRequired(this Control control, Action action){//If you are planning on using a similar function in your own code then please be sure to//have a quick read over https://stackoverflow.com/questions/1874728/avoid-calling-invoke-when-the-control-is-disposed//No actionif (control.Disposing || control.IsDisposed || !control.IsHandleCreated){return;}if (control.InvokeRequired){control.BeginInvoke(action);}else{action.Invoke();}}}
}// 异步调用示例在控件 outputLabel 中显示文本 output
this.InvokeOnUiThreadIfRequired(() outputLabel.Text output);
4.2、全屏设置可选
在窗体构造方法中将 WindowState 置为最大分辨率 public DataToolForm(){InitializeComponent();Text title;// 这里将窗体设置为最大屏幕分辨率WindowState FormWindowState.Maximized;browser new ChromiumWebBrowser(www.baidu.com);this.Controls.Add(browser);}
5、弹窗选择文件夹
安装 Ookii 包这里的版本是 4.0.0实现弹窗选择文件夹 弹窗示例代码如下
var folderDialog new Ookii.Dialogs.WinForms.VistaFolderBrowserDialog
{Description 选择文件夹
};if (folderDialog.ShowDialog() ! DialogResult.OK)
{Debug.WriteLine(res);
}
也可以使用原生方法
FolderBrowserDialog folderBrowserDialog new FolderBrowserDialog()
{Description请选择文件夹,ShowNewFolderButton true,
};
if(folderBrowserDialog.ShowDialog() DialogResult.OK)
{string selectedFolderPath folderBrowserDialog.SelectedPath;MessageBox.Show(选择的文件夹路径为 selectedFolderPath);
}
效果如下 使用同步委托逻辑上是先获取文件夹路径再保存文件
// DataToolApp\Dialogs\CustomerFolderBrowserDialog.cs
using System.Windows.Forms;namespace DataToolApp.Dialogs
{public class CustomerFolderBrowserDialog{/// summary/// 委托实现显示文件夹浏览器返回选中的文件夹路径/// /summary/// returns/returnspublic static string ShowFolderBrowserDialog(){FolderBrowserDialog folderBrowserDialog new FolderBrowserDialog(){Description 请选择文件夹,ShowNewFolderButton true,};if (folderBrowserDialog.ShowDialog() DialogResult.OK){return folderBrowserDialog.SelectedPath;}return ;}}
}// 主窗体 DataToolForm.cs// 自定义委托public delegate string MyFolderBrowserDialog();/// summary/// 获取选中的文件夹路径/// /summarypublic Object GetSelectedFolderPath(){MyFolderBrowserDialog myFolderBrowserDialog CustomerFolderBrowserDialog.ShowFolderBrowserDialog;return this.Invoke(myFolderBrowserDialog);}6、ExcelUtil 工具类封装
安装 NPOI 包这里的版本是 2.6.2 封装 SaveAsExcel 方法
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;namespace DataToolApp.Utils
{public class ExcelUtil{public Object SaveAsExcel(Object obj){// (IDictionaryString, Object) obj// System.Collections.Generic.ICollectionSystem.Collections.Generic.KeyValuePairstring, object obj// 结果DictionaryString, Object res new DictionaryString, Object(3);// 获取保存文件夹string selectedPath Program.dataToolForm.GetSelectedFolderPath().ToString();if (string.IsNullOrEmpty(selectedPath)){// 返回结果res.Add(code, 0);res.Add(msg, fail);res.Add(isSuccess, false);return res;}string filename Guid.NewGuid().ToString() .xls;// 创建 workbookIWorkbook workbook new HSSFWorkbook();// 添加一个 sheetISheet sheet1 workbook.CreateSheet(sheet1);// 写入 Excelbool isHeader true;Liststring header new Liststring();int rowCounter 0;ICollectionKeyValuePairstring, object entities (ICollectionKeyValuePairstring, object)obj;foreach (var entity in entities){IDictionaryString, Object entityInfo (IDictionaryString, object)entity.Value;// 写入表头if (isHeader){foreach (var key in entityInfo.Keys){header.Add(key);}// 第一行IRow firstRow sheet1.CreateRow(0);for (int j 0; j header.Count; j){firstRow.CreateCell(j).SetCellValue(header[j]);}isHeader false;}rowCounter;// 第 N 行IRow row sheet1.CreateRow(rowCounter);// 写入内容for (int k 0; k header.Count; k){row.CreateCell(k).SetCellValue(entityInfo[header[k]].ToString());}}// 写入文件using (FileStream file new FileStream(selectedPath \\ filename, FileMode.Create)){workbook.Write(file);}// 返回结果res.Add(code, 1);res.Add(msg, ok);res.Add(isSuccess, true);return res;}}
}7、PrinterUitl 工具类封装
安装 Spire.XLS 包这里的版本是 13.10.1 安装 Spire.PDF 包收费这里的版本是 9.10.2 // PrinterlUtil.cs
using iTextSharp.text;
using iTextSharp.text.pdf;
using System;
using System.Collections.Generic;
using System.IO;namespace DataToolApp.Utils
{public class PrinterlUtil{/// summary/// 创建一个 PDF 文档/// /summary/// param namefilename/parampublic void CreateDocument(string filename){// 创建新的PDF文档Document document new Document(PageSize.A4);// 创建PDF写入器PdfWriter.GetInstance(document, new FileStream(filename, FileMode.Create));// 打开文档document.Open();// 创建一个PdfPTable对象设置表格的列数和列宽PdfPTable table new PdfPTable(3);table.SetWidths(new float[] { 1, 2, 3 });// 创建一个PdfPCell对象设置单元格的内容和样式PdfPCell cell new PdfPCell(new Phrase(Header));cell.Colspan 3;cell.HorizontalAlignment 1;table.AddCell(cell);table.AddCell(Col 1 Row 1);table.AddCell(Col 2 Row 1);table.AddCell(Col 3 Row 1);table.AddCell(Col 1 Row 2);table.AddCell(Col 2 Row 2);table.AddCell(Col 3 Row 2);//将表格添加到Document对象中document.Add(table);//关闭Document对象document.Close();}/// summary/// 打印 PDF 文档/// /summary/// param nameobj/param/// returns/returnspublic Object PrintPdf(Object obj){string tempfile E:\Test.pdf;string savefile E:\Test2.pdf;string printerName Microsoft Print to PDF;CreateDocument(tempfile);// 加载 PDF 文档Spire.Pdf.PdfDocument doc new Spire.Pdf.PdfDocument();doc.LoadFromFile(tempfile);// 选择 Microsoft Print to PDF 打印机doc.PrintSettings.PrinterName printerName;// 打印 PDF 文档doc.PrintSettings.PrintToFile(savefile);doc.Print();// 删除缓存文件File.Delete(tempfile);// 结果DictionaryString, Object res new DictionaryString, Object(3);res.Add(code, 1);res.Add(msg, ok);res.Add(isSuccess, true);return res;}}
}8、DataUtil 工具类封装
// DataUtil.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DataToolApp.Utils
{public class DataUtil{// 模拟从数据库中返回数据public ListDataEntity getData(){int length 100;ListDataEntity dataEntities new ListDataEntity(length);for (int i 0; i length; i){DataEntity e new DataEntity(){date 2023-10-31,name Guid.NewGuid().ToString(),state Guid.NewGuid().ToString(),city Guid.NewGuid().ToString(),address Guid.NewGuid().ToString(),zip Guid.NewGuid().ToString(),tag Guid.NewGuid().ToString(),};dataEntities.Add(e);}return dataEntities;}public class DataEntity{public string date { get; set; }public string name { get; set; }public string state { get; set; }public string city { get; set; }public string address { get; set; }public string zip { get; set; }public string tag { get; set; } }}
}9、导出 .Net 方法
注意以下两点
导出的 .Net 方法有限制不能导出 Form/Window/Control必须使用委托实现窗体异步事件否则会出现报错“在可以调用 OLE 之前必须将当前线程设置为单线程单元(STA)模式”
// DataToolForm.cs
using System;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;
using CefSharp;
using CefSharp.JavascriptBinding;
using CefSharp.WinForms;
using DataToolApp.Controls;
using DataToolApp.Dialogs;
using DataToolApp.Utils;namespace DataToolApp
{public partial class DataToolForm : Form{/// summary/// 鼠标按下时的点/// /summaryprivate Point point;/// summary/// 拖动标识/// /summaryprivate bool isMoving false;/// summary/// 打包编译类型/// /summary
#if DEBUGprivate const string Build Debug;
#elseprivate const string Build Release;
#endif/// summary/// 应用标题/// /summaryprivate readonly string title DataToolApp ( Build );/// summary/// ChromiumWebBrowser/// /summaryprivate static ChromiumWebBrowser browser;// 委托public delegate string MyFolderBrowserDialog();/// summary/// 获取选中的文件夹路径/// /summarypublic Object GetSelectedFolderPath(){MyFolderBrowserDialog myFolderBrowserDialog CustomerFolderBrowserDialog.ShowFolderBrowserDialog;return this.Invoke(myFolderBrowserDialog);}public DataToolForm(){InitializeComponent();Text title;WindowState FormWindowState.Maximized;AddChromiumWebBrowser();}/// summary/// Create a new instance in code or add via the designer/// /summaryprivate void AddChromiumWebBrowser(){browser new ChromiumWebBrowser(http://datatool.test);browser.MenuHandler new CustomContextMenuHandler();// 导出 .Net 方法ExposeDotnetClass();this.Controls.Add(browser);}/// summary/// 鼠标按下时启用拖动/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_MouseDown(object sender, MouseEventArgs e){point e.Location;isMoving true;}/// summary/// 鼠标移动计算移动的位置/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_MouseMove(object sender, MouseEventArgs e){if (e.Button MouseButtons.Left isMoving){Point pNew new Point(e.Location.X - point.X, e.Location.Y - point.Y);Location new Size(pNew);}}/// summary/// 鼠标停下/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_MouseUp(object sender, MouseEventArgs e){isMoving false;}/// summary/// 导出类方法/// /summarypublic static void ExposeDotnetClass(){browser.JavascriptObjectRepository.ResolveObject (sender, e) {// 注册 ExcelUtil 实例DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, excelUtil, new ExcelUtil());// 注册 PrinterlUtil 实例DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, printerlUtil, new PrinterlUtil());// 注册其他实例 ...};browser.JavascriptObjectRepository.ObjectBoundInJavascript (sender, e) {var name e.ObjectName;Debug.WriteLine($Object {e.ObjectName} was bound successfully.);};}/// summary/// 注册 DoNet 实例/// /summary/// param namerepo IJavascriptObjectRepository /param/// param nameeventObjectName事件对象名/param/// param namefuncName方法名/param/// param nameobjectToBind需要绑定的DotNet对象/paramprivate static void DoRegisterDotNetFunc(IJavascriptObjectRepository repo, string eventObjectName, string funcName, object objectToBind){if (eventObjectName.Equals(funcName)){BindingOptions bindingOptions null;bindingOptions BindingOptions.DefaultBinder;repo.NameConverter null;repo.NameConverter new CamelCaseJavascriptNameConverter();repo.Register(funcName, objectToBind, isAsync: true, options: bindingOptions);}}/// summary/// 自定义右键菜单/// /summarypublic class CustomContextMenuHandler : IContextMenuHandler{/// summary/// 上下文菜单列表在这里加菜单/// /summary/// param namechromiumWebBrowser/param/// param namebrowser/param/// param nameframe/param/// param nameparameters/param/// param namemodel/paramvoid IContextMenuHandler.OnBeforeContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){if (model.Count 0){// 添加分隔符model.AddSeparator();}model.AddItem((CefMenuCommand)29501, Show DevTools);}/// summary/// 上下文菜单指令这里实现菜单要做的事情/// /summary/// param namechromiumWebBrowser/param/// param namebrowser/param/// param nameframe/param/// param nameparameters/param/// param namecommandId/param/// param nameeventFlags/param/// returns/returnsbool IContextMenuHandler.OnContextMenuCommand(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){if (commandId (CefMenuCommand)29501){browser.GetHost().ShowDevTools();return true;}return false;}void IContextMenuHandler.OnContextMenuDismissed(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame){var webBrowser (ChromiumWebBrowser)chromiumWebBrowser;Action setContextAction delegate (){webBrowser.ContextMenu null;};webBrowser.Invoke(setContextAction);}bool IContextMenuHandler.RunContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){// 必须返回 falsereturn false;}}/// summary/// 窗体加载事件/// /summary/// param namesender/param/// param namee/paramprivate void DataToolForm_Load(object sender, System.EventArgs e){browser.LoadUrl(http://datatool.test);}}
}三、整合前后端
1、前端打包
Vite 配置 ESLint否则打包的时候会报错
# 安装 eslint
cnpm i -D eslint babel/eslint-parser
# 初始化配置
npx eslint --init
# 安装依赖
cnpm i typescript-eslint/eslint-pluginlatest eslint-plugin-vuelatest typescript-eslint/parserlatest
# 安装插件
cnpm i -D vite-plugin-eslint 配置 vite
// vite.config.js
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
import eslintPlugin from vite-plugin-eslint// https://vitejs.dev/config/
export default defineConfig({plugins: [vue(),// ESLint 插件配置eslintPlugin({include: [src/**/*.js, src/**/*.vue, src/*.js, src/*.vue]}),AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],
})配置 eslint 规则
// .eslintrc.cjs
module.exports {env: {browser: true,es2021: true,node: true},extends: [eslint:recommended,plugin:typescript-eslint/recommended,plugin:vue/vue3-essential],overrides: [{env: {node: true},files: [.eslintrc.{js,cjs}],parserOptions: {sourceType: script}}],parserOptions: {ecmaVersion: latest,parser: typescript-eslint/parser,sourceType: module},plugins: [typescript-eslint,vue],rules: {typescript-eslint/no-explicit-any: 1,no-console: 1,no-debugger: 1,no-undefined: 1,}
}修改 vite 打包指令
// package.json
{name: myapp,private: true,version: 0.0.0,type: module,scripts: {dev: vite,build: vite build, // 修改这里preview: vite preview},dependencies: {element-plus/icons-vue: ^2.1.0,typescript-eslint/eslint-plugin: latest,typescript-eslint/parser: latest,element-plus: ^2.4.1,eslint-plugin-vue: latest,vue: ^3.3.4},devDependencies: {babel/eslint-parser: ^7.22.15,vitejs/plugin-vue: ^4.2.3,eslint: ^8.52.0,typescript: ^5.0.2,unplugin-auto-import: ^0.16.7,unplugin-vue-components: ^0.25.2,vite: ^4.4.5,vite-plugin-eslint: ^1.8.1,vue-tsc: ^1.8.5}
}打包
npm run build 2、引入静态资源
项目新建文件夹 Vite把前端打包好的资源引入进来并设为“嵌入的资源” 3、配置 CEF
程式主入口配置本地网页的访问域、缓存目录等信息
// Program.cs
using CefSharp;
using CefSharp.SchemeHandler;
using CefSharp.WinForms;
using System;
using System.IO;
using System.Windows.Forms;namespace DataToolApp
{static class Program{/// summary/// 应用程序的主入口点。/// /summary[STAThread]static void Main(){InitCefSettings();Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new DataToolForm());}private static void InitCefSettings(){#if ANYCPUCefRuntime.SubscribeAnyCpuAssemblyResolver();
#endif// Pseudo code; you probably need more in your CefSettings also.var settings new CefSettings(){//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist dataCachePath Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), CefSharp\\Cache)};//Example of setting a command line argument//Enables WebRTC// - CEF Doesnt currently support permissions on a per browser basis see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access// - CEF Doesnt currently support displaying a UI for media access permissions////NOTE: WebRTC Device Ids arent persisted as they are in Chrome see https://bitbucket.org/chromiumembedded/cef/issues/2064/persist-webrtc-deviceids-across-restartsettings.CefCommandLineArgs.Add(enable-media-stream);//https://peter.sh/experiments/chromium-command-line-switches/#use-fake-ui-for-media-streamsettings.CefCommandLineArgs.Add(use-fake-ui-for-media-stream);//For screen sharing add (see https://bitbucket.org/chromiumembedded/cef/issues/2582/allow-run-time-handling-of-media-access#comment-58677180)settings.CefCommandLineArgs.Add(enable-usermedia-screen-capturing);settings.RegisterScheme(new CefCustomScheme{SchemeName http,DomainName datatool.test,SchemeHandlerFactory new FolderSchemeHandlerFactory(rootFolder: ..\..\..\DataToolApp\Vite,hostName: datatool.test, //Optional param no hostname/domain checking if nulldefaultPage: index.html) //Optional param will default to index.html});//Perform dependency check to make sure all relevant resources are in our output directory.Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);}}
}4、配置右键菜单
右键显示控制台菜单需要实现 IContextMenuHandler 接口 /// summary/// 自定义右键菜单/// /summarypublic class CustomContextMenuHandler : IContextMenuHandler{/// summary/// 上下文菜单列表在这里加菜单/// /summary/// param namechromiumWebBrowser/param/// param namebrowser/param/// param nameframe/param/// param nameparameters/param/// param namemodel/paramvoid IContextMenuHandler.OnBeforeContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){if (model.Count 0){// 添加分隔符model.AddSeparator();}model.AddItem((CefMenuCommand)29501, Show DevTools);}/// summary/// 上下文菜单指令这里实现菜单要做的事情/// /summary/// param namechromiumWebBrowser/param/// param namebrowser/param/// param nameframe/param/// param nameparameters/param/// param namecommandId/param/// param nameeventFlags/param/// returns/returnsbool IContextMenuHandler.OnContextMenuCommand(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){if (commandId (CefMenuCommand)29501){browser.GetHost().ShowDevTools();return true;}return false;}void IContextMenuHandler.OnContextMenuDismissed(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame){var webBrowser (ChromiumWebBrowser)chromiumWebBrowser;Action setContextAction delegate (){webBrowser.ContextMenu null;};webBrowser.Invoke(setContextAction);}bool IContextMenuHandler.RunContextMenu(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback){// 必须返回 falsereturn false;}}
然后绑定浏览器的 MenuHandler browser.MenuHandler new CustomContextMenuHandler();
5、关闭窗口事件补充
5.1、顶部栏菜单补充
// myapp\src\api\WindowsUtil.ts
export const closeWindows async () {await CefSharp.BindObjectAsync(windowsUtil)windowsUtil.Close()
}
// myapp\src\components\DataViewer.vue
templateel-menu :default-activeactiveIndex classel-menu-demo :ellipsisfalse modehorizontal selecthandleSelectel-menu-item index1Processing Center/el-menu-itemdiv classflex-grow /el-sub-menu index2template #titleWorkspace/templateel-menu-item index2-1item one/el-menu-itemel-menu-item index2-2item two/el-menu-itemel-menu-item index2-3item three/el-menu-itemel-sub-menu index2-4template #titleitem four/templateel-menu-item index2-4-1item one/el-menu-itemel-menu-item index2-4-2item two/el-menu-itemel-menu-item index2-4-3item three/el-menu-item/el-sub-menu/el-sub-menuel-menu-item index3 disabledInfo/el-menu-itemel-menu-item index4Exit/el-menu-item/el-menudiv classcommon-layoutel-containerel-main!-- 获取数据后展示 --el-table v-loadingloading :datatableData height400 stylewidth: 100%el-table-column fixed typeindex :indexindexMethod /el-table-column propdate labelDate width150 /el-table-column propname labelName width120 /el-table-column propstate labelState width120 /el-table-column propcity labelCity width120 /el-table-column propaddress labelAddress width600 /el-table-column propzip labelZip width120 /el-table-column fixedright labelOperations width120template #defaultscopeel-button link typeprimary sizesmall clickhandleEdit(scope)Edit/el-buttonel-button link typeprimary sizesmall clickhandleDelete(scope)Delete/el-button/template/el-table-column/el-table!-- 更新数据时对话框 --el-dialog v-modeldialogUpdateFormVisible titleUpdate Shipping address ?el-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogUpdateFormVisible falseCancel/el-buttonel-button typeprimary clickdoUpdate()Confirm/el-button/span/template/el-dialog!-- 删除数据时对话框 --el-dialog v-modeldialogDeleteFormVisible titleDelete Shipping address ?el-form :modelcurrentRowel-form-item labeldate :label-widthformLabelWidthel-input disabled v-modelcurrentRow.date autocompleteoff //el-form-itemel-form-item labelname :label-widthformLabelWidthel-input disabled v-modelcurrentRow.name autocompleteoff //el-form-itemel-form-item labelstate :label-widthformLabelWidthel-input disabled v-modelcurrentRow.state autocompleteoff //el-form-itemel-form-item labelcity :label-widthformLabelWidthel-input disabled v-modelcurrentRow.city autocompleteoff //el-form-itemel-form-item labeladdress :label-widthformLabelWidthel-input disabled v-modelcurrentRow.address autocompleteoff //el-form-itemel-form-item labelzip :label-widthformLabelWidthel-input disabled v-modelcurrentRow.zip autocompleteoff //el-form-itemel-form-item labeltag :label-widthformLabelWidthel-input disabled v-modelcurrentRow.tag autocompleteoff //el-form-item/el-formtemplate #footerspan classdialog-footerel-button clickdialogDeleteFormVisible falseCancel/el-buttonel-button typeprimary clickdoDelete()Confirm/el-button/span/template/el-dialog!-- 关闭窗口时 --el-dialog v-modeldialogVisible title提示 width30% :before-closehandleClosespan确认关闭窗口/spantemplate #footerspan classdialog-footerel-button clickdialogVisible false取消/el-buttonel-button typeprimary clickdoClose确认/el-button/span/template/el-dialog/el-mainel-headerel-row classrow-bg justifyendel-col :span2el-button clickhandleSaveData() typeprimarySaveel-icon classel-icon--rightDocument //el-icon/el-button/el-colel-col :span2el-button clickhandlePrintData() typeprimaryPrintel-icon classel-icon--rightPrinter //el-icon/el-button/el-col/el-row/el-header/el-container/div
/templatescript langts setup
import { ref, onMounted, reactive } from vue
import { getData } from ../api/DataUtil
import { saveAsExcel } from ../api/ExcelUtil
import { printPdf } from ../api/PrinterUtil
import { closeWindows } from ../api/WindowsUtil
import { ElMessage, ElMessageBox } from element-plus
import { Document, Printer } from element-plus/icons-vue// 表格数据加载标志
const loading ref(true)
const tableData reactive([])// 更新数据对话框
const dialogUpdateFormVisible ref(false)
const dialogDeleteFormVisible ref(false)
const formLabelWidth 140px// 当前选中行数据
const currentRow reactive({date: please input date,name: please input name,state: please input state,city: please input city,address: please input address,zip: please input zip,tag: please input tag,
})const currentRowIndex ref(1)const dialogVisible ref(false)// 确认关闭
const doClose () {dialogVisible.value falsecloseWindows()
}// 关闭窗口
const handleClose (done: () void) {ElMessageBox.confirm(Are you sure to close this dialog?).then(() {done()}).catch(() {// catch error})
}
// 顶部栏菜单
const activeIndex ref(1)
const handleSelect (key: string, keyPath: string[]) {console.log(key, keyPath)if (key 4) {dialogVisible.value true}
}// 更新事件
const doUpdate () {//console.log( doUpdate , currentRow, currentRowIndex.value)Object.assign(tableData[currentRowIndex.value], currentRow)dialogUpdateFormVisible.value falseElMessage({message: Update success.,type: success,})
}// 删除事件
const doDelete () {// console.log(doDelete , currentRowIndex.value)tableData.splice(currentRowIndex.value, 1)dialogDeleteFormVisible.value falseElMessage({message: Delete success.,type: success,})
}// 索引规则
const indexMethod (index: number) {return index 1
}
// Delete 事件
const handleDelete (scope: any) {Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogDeleteFormVisible.value true
}// Edit 事件
const handleEdit (scope: any) {//console.log(edit,scope.$index,scope.row)Object.assign(currentRow, scope.row)currentRowIndex.value scope.$indexdialogUpdateFormVisible.value true
}// 保存事件
const handleSaveData () {console.log(handleSaveData)saveAsExcel(tableData).then((res: any) {console.log(res, res)if (res.isSuccess) {ElMessage({message: Save success.,type: success,})} else {ElMessage({message: res.msg,type: error,})}}).catch((err: any) {console.log(err, err)})
}// 打印事件
const handlePrintData () {printPdf(tableData).then((res: any) {if (res.isSuccess) {ElMessage({message: Save success.,type: success,})} else {ElMessage({message: res.msg,type: error,})}})
}// 挂载方法
onMounted(() {// 获取数据后将加载标志位位置 false并且绑定到表格getData().then((res: any) {//console.log( getData , res)loading.value falseObject.assign(tableData, res)})})/script
style
.el-button--text {margin-right: 15px;
}.el-select {width: 300px;
}.el-input {width: 300px;
}.dialog-footer button:first-child {margin-right: 10px;
}.el-row {margin-bottom: 20px;
}.el-row:last-child {margin-bottom: 0;
}.el-col {border-radius: 4px;
}.flex-grow {flex-grow: 1;
}
/style
顶部栏菜单效果 5.2、关闭方法补充
// WindowsUtil.cs
namespace DataToolApp.Utils
{public class WindowsUtil{public void Close(){Program.dataToolForm.DoCloseWindows();}}
}// DataToolForm.cs
/// summary
/// 关闭窗口
/// /summary
public void DoCloseWindows()
{this.InvokeOnUiThreadIfRequired((){rowser.Dispose();Cef.Shutdown();Close();});
}/// summary
/// 导出类方法
/// /summary
public static void ExposeDotnetClass()
{browser.JavascriptObjectRepository.ResolveObject (sender, e) {// 注册 ExcelUtil 实例DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, excelUtil, new ExcelUtil());// 注册 PrinterlUtil 实例DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, printerlUtil, new PrinterlUtil());// 注册 WindowsUtil 实例DoRegisterDotNetFunc(e.ObjectRepository, e.ObjectName, windowsUtil, new WindowsUtil());// 注册其他实例 ...};browser.JavascriptObjectRepository.ObjectBoundInJavascript (sender, e) {var name e.ObjectName;Debug.WriteLine($Object {e.ObjectName} was bound successfully.);};}
6、调整 DoRegisterDotNetFunc
NameConverter 只能赋值一次 /// summary/// 注册 DoNet 实例/// /summary/// param namerepo IJavascriptObjectRepository /param/// param nameeventObjectName事件对象名/param/// param namefuncName方法名/param/// param nameobjectToBind需要绑定的DotNet对象/paramprivate static void DoRegisterDotNetFunc(IJavascriptObjectRepository repo, string eventObjectName, string funcName, object objectToBind){if (eventObjectName.Equals(funcName)){if (!IsSetNameConverter){repo.NameConverter new CamelCaseJavascriptNameConverter();IsSetNameConverter true;}BindingOptions bindingOptions null;bindingOptions BindingOptions.DefaultBinder;//repo.NameConverter null;//repo.NameConverter new CamelCaseJavascriptNameConverter();repo.Register(funcName, objectToBind, isAsync: true, options: bindingOptions);}}
四、效果测试
1、查询 2、修改 3、删除 4、保存 Excel 5、打印 PDF 6、关闭 五、窗口属性配置补充
1、自适应分辨率 2、动态边框长宽 3、显示任务栏图标 4、窗口屏幕居中 六、Demo 最终成品