Bootstrap

golang跨平台GUI框架fyne介绍与使用详解,开放案例

golang跨平台GUI框架fyne介绍与使用详解

Fyne 是一个使用 Go 编写的易于使用的 UI 工具包和应用程序 API。 它旨在构建使用单一代码库在桌面和移动设备上运行的应用程序。
通过批量调用身份证实名和三网手机实名和银行卡核验等接口,完成fyne框架的基本使用介绍
主要包括

  • fyne框架的基本应用
  • fyne框架的自定义字体使用
  • 通过go的并发协程方式实现同时处理多任务

福利彩蛋:没有好玩的 API 接口?上百款免费接口等你来,免费 API,免费 API 大全

介绍

这是一款本地批量核验小工具,测试接口示例来源聚合数据
计划支持:showapi、阿里云等
该应用集成了身份证实名、三网手机实名和银行卡二、三、四元素校验等多种核验方式;
旨在帮助有需求但无技术的用户快速、准确地完成身份验证,提升用户体验和工作效率;
让实名认证、银行卡核验变得简单、高效!
有技术支持的用户,可以选择接口调用

特点

  • 身份证实名认证:通过姓名和身份证号码,快速验证身份证的真实性和一致性,确保您的身份信息安全。
  • 三网手机实名认证:核验手机运营商三要素(手机号码、姓名、身份证号)信息是否一致。
  • 银行卡二、三、四元素校验:姓名、身份证号码、银行卡号、手机号等相关信息,快速验证银行卡的真实性和一致性,保障您的资金安全。
  • 本地批量处理:通过模板上传数据,批量进行核验
  • 用户友好界面:简洁直观的操作界面,让您轻松上手,快速完成信息核验。

应用下载:本地批量核验工具

上代码

1.初始化应用

package main

import (
	"fyne_study/global"
	"fyne_study/model"
	"fyne_study/mytheme"
	"fyne_study/widgets"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/container"
)

func main() {

	global.AppRoot = app.NewWithID("cn.juhe.preferences.v1.0")
	global.AppRoot.Settings().SetTheme(&mytheme.MyTheme{})
	global.W = global.AppRoot.NewWindow("身份证、手机号、银行卡批量核验工具")
	global.W.Resize(fyne.NewSize(1200, 820))
	if !model.CheckTable() {
		model.InitDatabase()
		global.InfoDialog("数据库初始化结束")
	}

	// 设置主菜单:mainmenu是桌面应用上的菜单
	// global.W.SetMainMenu(widgets.MenuList())
	header := container.NewVBox(widgets.Row1(), widgets.Row2())
	// header := container.NewVBox(widget.NewLabel("databalse"))

	ly := container.NewBorder(header, nil, nil, nil, widgets.Row3())
	global.W.SetContent(ly)
	global.W.ShowAndRun()
}

2.menu 属性菜单

package widgets

import (
	"fyne_study/global"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/widget"
)

func MenuList() *fyne.MainMenu {
	//打开菜单项
	openMenuItem := fyne.NewMenuItem("延迟有效期", func() {

		sign := widget.NewEntry()
		sign.SetPlaceHolder("请输入签名,延迟有效期")
		dialog.ShowForm("延迟有效期", "确认", "取消", []*widget.FormItem{
			{Text: "", Widget: container.NewGridWrap(fyne.NewSize(300, 35), sign)},
		}, func(b bool) {
			if b {
				if sign.Text != "" {
					global.LabelMsgText(sign.Text)
				} else {
					global.LabelMsgText("签名不正确")
				}
			}
		}, global.W)

	})
	//保存菜单项
	saveMenuItem := fyne.NewMenuItem("联系作者", func() {
		dialog.ShowInformation("联系作者","QQ:1776403827 ",global.W)
	})

	fileMenu := fyne.NewMenu("File", openMenuItem, saveMenuItem)
	// 表示文件菜单有三个选项
	menu := fyne.NewMainMenu(fileMenu)

	return menu

}

3.接口地址和appkey选择输入,保存

示例代码框中的appkey获取
在数据中心 > 我的API获取已申请的接口Appkey
同样在这里可以申请您感兴趣的接口

将申请接的接口的Key黏贴到对应的接口位置

每个接口仅第一次使用时需要配置并保存一次key
选择核验并发次数

在这里插入图片描述

选择对应的接口后,下载上传模板,严格按照模板格式上传
上传完数据后,点击开始
完成后先导出数据
再清空数据列表,进行下一批次核验

package widgets

import (
	"fmt"
	"fyne_study/global"
	"fyne_study/utils"
	"net/url"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/data/binding"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

func Row1() *fyne.Container {

	// 重新启动是选择上次的结果
	api := global.GetCurrentApi()

	// 接口选择
	global.ApiSelect = widget.NewSelect(global.ApiList, nil)

	global.ApiSelect.OnChanged = func(api string) {
		_api := global.GetCurrentApi()

		if global.Total > 0 && api != _api {
			global.ErrorDialog("请求处理完并导出已有数据并清空表单,然后切换接口")
			global.ApiSelect.SetSelected(_api)
			return
		}

		// 保存选择的Api接口
		global.SetPreferenceVal("selectedApi", "", "", api)

		// 设置并发绑定数
		nums := global.GetPreferenceVal(api, "concurrency", "string", "5").(string)
		if global.PoolSelect != nil {
			global.PoolSelect.SetSelected(nums)
		}

		// appkey处理
		keyStr := global.GetPreferenceVal(api, "key", "string", "").(string)
		global.BindAppKey.Set(keyStr)
		fmt.Println("appkey:", keyStr)

		if keyStr == "" {
			global.LabelMsgText("当前任务:" + api + ";AppKey:未设置Key")
		} else {
			global.LabelMsgText("当前任务:" + api + ";AppKey:" + keyStr + ";并发数:" + nums)
		}

		if global.ListTitle != nil {
			global.GetApiInfoSlice(api, "colums")
			global.CurCols = global.GetApiInfoSlice(api, "colums")
			global.CurRxp = global.GetApiInfoStr(api, "codeExp")

			_cols := global.GetApiInfoSlice(_api, "colums")
			diff := len(_cols) - len(global.CurCols)
			// VBox->GridWarp->Label
			grids := global.ListTitle.Objects

			for i := 0; i < len(global.CurCols); i++ {
				grids[i].(*fyne.Container).Objects[0].(*widget.Label).SetText(global.CurCols[i])
			}

			if diff > 0 {
				// 列数减少
				for i := 0; i < len(_cols); i++ {
					if i >= len(global.CurCols) {
						grids[i].(*fyne.Container).Objects[0].(*widget.Label).SetText("")
					}
				}
			}
		}

	}

	// 从配置加载选中的API
	global.ApiSelect.SetSelected(api)

	// appkey输入框
	global.AppKeyEntry = widget.NewEntryWithData(global.BindAppKey)
	global.AppKeyEntry.SetPlaceHolder("配置该接口的appkey")

	// 并发数选择框
	global.PoolSelect = widget.NewSelect([]string{"1", "5", "10", "15", "20" /*, "25", "30", "35", "40", "50"*/}, nil)

	global.PoolSelect.OnChanged = func(val string) {
		_nums := global.GetPreferenceVal(api, "concurrency", "string", "5").(string)
		// 设置并发数之前,渠道任务未开启或暂停
		if global.QueueStatus == "start" && _nums != val {
			global.ErrorDialog("请先确任务为开始或已暂停,然后在设置并发数")

			global.PoolSelect.SetSelected(_nums)
			return
		}
		global.LabelMsgText(fmt.Sprintf("接口情况:%s,当前并发:%s", global.ApiSelect.Selected, val))

		// 当并发数改变时,根据选择的接口进行,并发数设置
		global.SetPreferenceVal(global.ApiSelect.Selected, "concurrency", "string", val)
	}

	// 从配置加载并发数量
	nums := global.GetPreferenceVal(api, "concurrency", "string", "5").(string)
	global.PoolSelect.SetSelected(nums)

	save := widget.NewButtonWithIcon("保存配置", theme.DocumentSaveIcon(), func() {
		msg := "当前任务:" + api
		keyStr := global.AppKeyEntry.Text
		global.SetPreferenceVal(global.ApiSelect.Selected, "key", "string", keyStr)
		if keyStr == "" {
			msg += ";AppKey:未设置Key"
		} else {
			msg += ";AppKey:" + keyStr
		}
		msg += ";并发数:" + nums

		global.LabelMsgText(msg)
	})

	// // 通过NewGridWrap实现组件尺寸自定义
	// rowEn := container.NewGridWrap(fyne.NewSize(280, 35), global.ApiSelect)
	// rowKy := container.NewGridWrap(fyne.NewSize(280, 35), global.AppKeyEntry)
	// rowPoL := container.NewGridWrap(fyne.NewSize(50, 35), widget.NewLabel("并发数:"))
	// rowPo := container.NewGridWrap(fyne.NewSize(80, 35), global.PoolSelect)
	// rowBt := container.NewGridWrap(fyne.NewSize(100, 35), save)
	left := container.NewGridWithColumns(2, global.ApiSelect, global.AppKeyEntry)

	right := container.NewGridWithColumns(3, widget.NewLabel("并发数:"), global.PoolSelect, save)

	global.LabelMsg = widget.NewLabel("操作提示:")
	df := time.Now().AddDate(0, 6, 0).Format(global.DateTime)
	expire := global.GetPreferenceVal("app-expire", "", "string", df).(string)
	if expire == "" {
		expire = df
		global.SetPreferenceVal("app-expire", "", "string", expire)
	}

	global.Expire = binding.BindString(&expire)
	global.SignBtn = widget.NewButtonWithIcon("激活", theme.HistoryIcon(), func() {
		SignActive(expire)
	})

	if expire >= time.Now().Format(global.DateTime) {
		global.SignBtn.Hide()
	} else {
		global.SignBtn.Show()
	}

	_url, _ := url.Parse("https://www.juhe.cn/s/swnkequ0q5dccl=?s=utm_id55")
	box := container.NewHBox(widget.NewHyperlink("注册聚合账号", _url), widget.NewLabel("问题反馈:1776403827(QQ)"), global.SignBtn, widget.NewLabel("有效期:"), widget.NewLabelWithData(global.Expire))
	row := container.NewBorder(container.NewBorder(nil, nil, nil, box, global.LabelMsg), nil, nil, right, left)
	return row
}

// 签名激活
func SignActive(expire string) {
	sign := widget.NewMultiLineEntry()
	sign.Wrapping = fyne.TextWrapWord
	sign.SetPlaceHolder("请输入激活码,激活软件")
	dialog.ShowForm("激活", "确认", "取消", []*widget.FormItem{
		{Text: "", Widget: container.NewGridWrap(fyne.NewSize(300, 75), sign)},
	}, func(b bool) {
		if b {
			if sign.Text != "" {
				tx := global.GetPreferenceVal("app-active-sing", "", "string", "").(string)
				if tx == sign.Text {
					global.ErrorDialog("激活码已使用")
					return
				}
				dec, err := utils.ECBDecrypt([]byte(sign.Text), global.EcbKey)
				if err == nil && string(dec)[:10] == global.SignKey {
					if err == nil {
						date, err := time.Parse(global.DateTime, expire)
						if err == nil {
							expire = date.AddDate(1, 0, 0).Format(global.DateTime)
							global.Expire.Set(expire)
							global.SignBtn.Hide()
							global.SetPreferenceVal("app-expire", "", "string", expire)
							global.SetPreferenceVal("app-active-sing", "", "string", sign.Text)
						}
					}
				}

				if err != nil {
					global.ErrorDialog("激活码不正确")
					return
				} else {
					global.InfoDialog("软件激活成功")
					return
				}

			} else {
				global.LabelMsgText("签名不正确")
			}
		}
	}, global.W)
}

4.上传需要核验的数据文件

布局一个文件上传以及模板下载区域

在这里插入图片描述

package widgets

import (
	"fmt"
	"fyne_study/global"
	"fyne_study/model"
	"strings"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/storage"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"github.com/tealeg/xlsx"
)

// border : center,right
func Row2() *fyne.Container {
	file := widget.NewEntry()
	open := widget.NewButtonWithIcon("打开文件", theme.DocumentSaveIcon(), nil)

	// 点击打开上传文件
	open.OnTapped = func() {
		// 再次上传时候判断
		if global.Total > 0 {
			global.ErrorDialog("请求处理完并导出已有数据并清空表单,然后切换接口")
			return
		}

		fileDialog := dialog.NewFileOpen(func(uc fyne.URIReadCloser, err error) {
			if uc != nil {
				fmt.Println(uc.URI().Path())
				file.SetText(uc.URI().Path())
				fmt.Println("uc.URI().Extension()", uc.URI().Extension())
			}
			fmt.Println("保存配置=", err)
		}, global.W)
		
		fileDialog.Resize(fyne.NewSize(1000,600))
		fileDialog.SetFilter(storage.NewExtensionFileFilter([]string{".xlsx"}))
		fileDialog.Show()
		fmt.Println("保存配置")
	}

	// 处理进度条
	upload := widget.NewButtonWithIcon("上传", theme.UploadIcon(), func() {

		api := global.GetCurrentApi()
		cols := global.GetApiInfoSlice(api, "colums")

		dialog.ShowConfirm("提示", "确定上传该文件?"+file.Text, func(b bool) {
			if b && file.Text != "" && strings.Contains(file.Text, ".xlsx") {
				fmt.Println("正在读取:", file.Text)
				excel, err := xlsx.OpenFile(file.Text)
				if err == nil {

					sheet := excel.Sheets[0]
					// 上传数据校验
					if len(sheet.Cols) != (len(cols) - 4) {
						global.ErrorDialog(fmt.Sprintf("上传数据格式不正确:列数不匹配【%d】,请严格安照模板上传【%d】", len(sheet.Cols), (len(cols) - 4)))
						return
					}

					pBar := widget.NewProgressBar()
					pc := container.NewGridWrap(fyne.NewSize(300, 20), pBar)
					bar := dialog.NewCustom("提示", "上传数据处理中...", pc, global.W)
					bar.Show()

					rows, err := model.BatchInsert(sheet, pBar)
					if err != nil {
						bar.Hide()
						global.ErrorDialog("数据上传异常:" + err.Error())

					} else {
						bar.SetDismissText(fmt.Sprintf("上传结束:处理数据%d", rows))
						bar.SetOnClosed(func() {
							ListTabelRefresh(1, global.PageSize)

							fmt.Println("重置当前页码:", 1)
							file.SetText("")
							if global.QueueStatus == "end" && global.QueueBtn != nil {
								global.QueueBtn.SetIcon(theme.MediaPlayIcon())
								global.QueueBtn.SetText("开始")
							}

						})
					}
					if rows > 0 {
						global.ProcessBar.SetValue(0)
						global.ProcessBar.Show()
					}

				} else {
					dialog.ShowError(err, global.W)
				}

			} else if file.Text == "" || !strings.Contains(file.Text, ".xlsx") {
				global.ErrorDialog("请选择正确的上传文件")
			} else {
				fmt.Println("文件地址", file.Text)
			}
		}, global.W)

	})

	// 示例文件下载
	// download := widget.NewHyperlink("下载模板", nil)
	// download.OnTapped = func() {
	// 	api := global.GetCurrentApi()
	// 	exampleUrl := global.GetApiInfoStr(api, "example")
	// 	_url, _ := url.Parse(exampleUrl)
	// 	fyne.CurrentApp().OpenURL(_url)
	// }

	download := widget.NewButtonWithIcon("下载模板", theme.DocumentSaveIcon(), func() {
		// 获取当前选中的接口
		api := global.GetCurrentApi()

		// 打开文件保存地址
		fileDialog := dialog.NewFileSave(func(uc fyne.URIWriteCloser, err error) {
			if uc != nil {
				example := global.GetApiInfo2Slice(api, "example")

				f := xlsx.NewFile()
				sheet, err := f.AddSheet("Sheet1")
				if err != nil {
					global.ErrorDialog(err.Error())
					return
				}
				for _, item := range example {
					row := sheet.AddRow()
					for _, col := range item {
						cell := row.AddCell()
						cell.Value = col
					}
				}

				err = f.Save(uc.URI().Path())
				if err != nil {
					global.LabelMsg.SetText(err.Error())
					return
				}
				time.Sleep(time.Second * 1)
				uc.Close()
				global.InfoDialog("模板下载成功")
			} else {
				if err == nil {
					global.LabelMsgText("")
				} else {
					global.LabelMsgText(err.Error())
				}
			}
		}, global.W)
		fileDialog.Resize(fyne.NewSize(1000,600))
		fileDialog.SetFileName(api + "-tpl.xlsx")
		fileDialog.Show()

	})

	row := container.NewBorder(nil, nil, nil, container.NewGridWithColumns(3, open, upload, download), file)

	return row
}

5.渲染上传进度条,并发处理进度条,以及数据执行按钮等工具栏

渲染上传进度条,并发处理进度条,以及数据执行按钮等工具栏
核验结果的数据导出功能

package widgets

import (
	"encoding/json"
	"fmt"
	"fyne_study/global"
	"fyne_study/model"
	"fyne_study/utils"
	"strconv"
	"strings"
	"time"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"github.com/tealeg/xlsx"
)

// border : right
func Row3() *fyne.Container {

	label := widget.NewLabel("数据列表:")
	// ready,start,pause,end
	global.QueueBtn = widget.NewButtonWithIcon("开始", theme.MediaPlayIcon(), nil)

	global.QueueBtn.OnTapped = func() {
		if global.QueueBtn != nil {
			fmt.Println("=====", global.QueueBtn.Text)
			if global.QueueBtn.Text == "开始" {
				key, _ := global.BindAppKey.Get()
				if len(key) != 32 {
					global.InfoDialog("请先配置正确的接口请求Key")
					return
				}

				expire, err := global.Expire.Get()
				if err != nil {
					global.LabelMsgText(err.Error())
					return
				}

				if expire < time.Now().Format("2006-01-02") {
					global.InfoDialog("该软件使用已过有效期,请重新激活")
					global.SignBtn.Show()
					return
				} else {
					global.SignBtn.Hide()
				}

				if global.Total > 0 {
					if model.CompletedCount() == global.Total {
						global.InfoDialog("该任务已完成,请导出并清空数据,进行下一批任务")
						return
					}
					run := dialog.NewConfirm("提示", "确定立即开始任务?", func(e bool) {
						if e {
							global.QueueStatus = "start"
							global.QueueBtn.SetText("暂停")
							global.QueueBtn.SetIcon(theme.MediaPauseIcon())

							go queueRun(global.QueueBtn)
						}
					}, global.W)
					run.SetConfirmText("确认")
					run.SetDismissText("取消")
					run.Show()

				} else {
					global.InfoDialog("当前没有任务要执行")
				}

			} else if global.QueueBtn.Text == "暂停" {
				global.QueueStatus = "pause"
				global.QueueBtn.SetText("开始")
				global.QueueBtn.SetIcon(theme.MediaPlayIcon())
				global.LabelMsgText("当前任务已暂停")
			} else {
				global.InfoDialog("该任务已完成,请导出并清空数据,进行下一批任务")
				return
			}
		}
	}

	export := widget.NewButtonWithIcon("导出数据", theme.DownloadIcon(), nil)
	export.OnTapped = func() {
		// 设置并发数之前,渠道任务未开启或暂停
		if global.QueueStatus == "start" {
			global.ErrorDialog("请先确任务未开始或已暂停,然后再导出数据")
			return
		}
		count, err := model.UnCompletedCount()
		if err != nil {
			global.ErrorDialog(err.Error())
			return
		}
		// 存在未处理的数据
		if count > 0 {
			dialog.ShowConfirm("提示", "数据未处理完,是否确认导出?", func(b bool) {
				if b {
					ExportData()
				}
			}, global.W)
		} else {
			ExportData()
		}
	}

	// 情况数据按钮
	clear := widget.NewButtonWithIcon("清空数据", theme.ContentClearIcon(), func() {
		dialog.ShowConfirm("提示", "确认要情况当前任务数据?", func(b bool) {
			if b {
				// 设置并发数之前,渠道任务未开启或暂停
				if global.QueueStatus == "start" {
					global.ErrorDialog("请先确任务未开始或已暂停且数据已导出,然后再清空数据")
					return
				}

				err := model.ClearDatabase()
				if err != nil {
					dialog.ShowError(err, global.W)
				} else {
					global.InfoDialog("数据库清空成功!")
					ListTabelRefresh(1, 10)
				}
				global.ProcessBar.Hide()
			}

		}, global.W)

	})

	// 横向布局开始、导出、清空按钮
	btns := container.NewHBox(global.QueueBtn, export, clear)
	hed := container.NewBorder(nil, nil, label, btns, nil)

	// 列表布局
	list := Rowlist()

	global.ProcessBar = widget.NewProgressBar()
	process := model.CompletedCount()
	if global.Total > 0 {
		global.ProcessBar.SetValue(float64(process) / float64(global.Total))
	} else {
		global.ProcessBar.Hide()
	}

	barList := container.NewVBox(hed, global.ProcessBar)

	row := container.NewBorder(barList, nil, nil, nil, list)

	return row
}

// 数据处理结果导出
func ExportData() {
	api := global.GetCurrentApi()
	cols := global.GetApiInfoSlice(api, "colums")
	cols = append([]string{"Id"}, cols...)

	// 打开文件保存地址
	fileDialog := dialog.NewFileSave(func(uc fyne.URIWriteCloser, err error) {

		if uc != nil {
			bar := widget.NewProgressBar()
			pc := container.NewGridWrap(fyne.NewSize(300, 20), bar)
			bs := dialog.NewCustom("导出进度", "数据写入中...", pc, global.W)
			bs.Show()
			f := xlsx.NewFile()
			sheet, err := f.AddSheet("Sheet3")

			if err != nil {
				global.LabelMsg.SetText(err.Error())
			}
			items, err := model.ExportDataList()

			if err != nil {
				global.LabelMsgText(err.Error())
			} else {

				for i, item := range items {
					bar.SetValue(float64(i+1) / float64(global.Total))

					if i == 0 {
						title := sheet.AddRow()
						for _, col := range cols {
							cell := title.AddCell()
							cell.Value = col
						}
					}
					row := sheet.AddRow()
					for _, col := range cols {
						cell := row.AddCell()
						var cellData string
						switch col {
						case "Id":
							cellData = fmt.Sprintf("%d", item.Id)
						case "姓名":
							cellData = item.Realname
						case "身份证号":
							cellData = item.Idcard
						case "手机号":
							cellData = item.Mobile
						case "银行卡号":
							cellData = item.Bankcard
						case "结果":
							text := item.Result

							if text == "1" {
								text = "一致"
							} else if text == "2" {
								text = "不一致"
							}
							cellData = text
						case "描述":
							cellData = item.Msg
						case "状态":
							text := "未处理"
							if item.Completed == 1 {
								text = "已处理"
							}
							cellData = text
						case "任务Id":
							cellData = item.Jobid
						default:
							cellData = ""
						}
						cell.Value = cellData
					}

				}
			}

			err = f.Save(uc.URI().Path())
			if err != nil {
				global.LabelMsg.SetText(err.Error())
			}
			bs.SetDismissText("导出完成")
			uc.Close()
			time.Sleep(time.Second * 1)
			bs.Hide()
		}

	}, global.W)

	fileDialog.Resize(fyne.NewSize(1000, 600))
	fileDialog.SetFileName(api + "-export.xlsx")
	fileDialog.Show()

}

// 队列任务执行
func queueRun(btn *widget.Button) {
	api := global.GetCurrentApi()
	limit, _ := strconv.Atoi(global.GetPreferenceVal(api, "concurrency", "string", "1").(string))

	// 接口请求参数统一处理
	authKey := global.GetPreferenceVal(api, "key", "string", "").(string)
	// 接口标识,用于处理和提取接口参数
	var apiHeader, apiParams map[string]string
	// 接口地址
	url := global.GetApiInfoStr(api, "url")
	// 请求方式
	method := global.GetApiInfoStr(api, "method")
	// 请求header
	apiHeaderTpl := global.GetApiInfoMap(api, "header")
	jmh, _ := json.Marshal(apiHeaderTpl)
	json.Unmarshal(jmh, &apiHeader)
	// 请求参数
	apiParamsTpl := global.GetApiInfoMap(api, "params")
	jmp, _ := json.Marshal(apiParamsTpl)
	json.Unmarshal(jmp, &apiParams)
	// 响应参数模板
	responseTpl := global.GetApiInfoMapI(api, "response")
	// 响应参数模板
	checkCode := global.GetApiInfoStr(api, "checkCode")
	fmt.Println("key:", authKey, apiParams)
	// 接口appkey参数传递方式
	authtype := global.GetApiInfoStr(api, "authtype")
	switch authtype {
	case "params":
		for k, v := range apiParams {
			if strings.Contains(v, "APP-KEY") {
				apiHeader[k] = strings.Replace(apiHeader[k], "APP-KEY", authKey, -1)
			}
		}
	case "header":
		for k, v := range apiHeader {
			if strings.Contains(v, "APP-KEY") {
				apiHeader[k] = strings.Replace(apiHeader[k], "APP-KEY", authKey, -1)
			}
		}
	}

	pcw := utils.NewPool(limit)
	process := 0
	refresh := true
	for global.QueueStatus == "start" {

		rows, err := model.QueueList(limit)
		if err != nil {
			global.QueueStatus = "pause"
			global.QueueBtn.SetText("开始")
			global.QueueBtn.SetIcon(theme.MediaPlayIcon())
			global.LabelMsgText("当前任务已暂停")
			global.ErrorDialog("任务查询异常:" + err.Error())
			break
		}
		for _, item := range rows {
			pcw.Add(1)
			go apiRequest(item, pcw, url, method, checkCode, apiHeader, apiParams, responseTpl)

		}
		pcw.Wait()

		process = model.CompletedCount()

		global.ProcessBar.SetValue(float64(process) / float64(global.Total))
		fmt.Println("--执行任务:", limit, len(rows), process, global.Total, float64(process)/float64(global.Total), global.PageSize, process, refresh)

		if len(rows) < limit {
			global.QueueStatus = "end"
		}

		if global.PageSize < process && refresh {
			ListTabelRefresh(1, global.PageSize)
			refresh = false
		}
	}

	fmt.Println("任务暂停或结束:", global.QueueStatus)
	if global.QueueStatus == "end" {
		btn.SetIcon(theme.MediaStopIcon())
		btn.SetText("完成")
		ListTabelRefresh(1, global.PageSize)

		global.InfoDialog("任务执行结束,请及时导出数据")

	}
	if global.QueueStatus == "pause" {
		ListTabelRefresh(1, global.PageSize)
	}

}

// 接口请求
func apiRequest(row model.Persons, pcw *utils.PoolChanWt, url, method, checkCode string, apiHeader, apiParams map[string]string, responseTpl map[string]interface{}) {
	// 参数处理赋值
	for key, val := range apiParams {
		switch val {
		case "Realname":
			apiParams[key] = row.Realname
		case "Idcard":
			apiParams[key] = row.Idcard
		case "Mobile":
			apiParams[key] = row.Mobile
		case "Bankcard":
			apiParams[key] = row.Bankcard
		}
	}

	result := ApiRequstDispatch(url, method, checkCode, apiHeader, apiParams, responseTpl)
	model.UpdateResult(row, result)
	pcw.Done()

}

6. 核验数据结果列表

导入文件的数据列表
核验结果列

在这里插入图片描述

package widgets

import (
	"fmt"
	"fyne_study/global"
	"fyne_study/model"
	"reflect"
	"strconv"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/container"

	"fyne.io/fyne/v2/data/binding"
	"fyne.io/fyne/v2/widget"
	"github.com/gogf/gf/v2/container/garray"
)

// border : right
// func rowlist() *fyne.Container {
func Rowlist() *fyne.Container {

	// global.ListPerson, total = GetDataList(pageSize, currPage)

	fmt.Println("列表查询结果:", len(global.ListPerson), global.Total)
	api := global.GetCurrentApi()
	global.CurCols = global.GetApiInfoSlice(api, "colums")
	global.CurRxp = global.GetApiInfoStr(api, "codeExp")
	// 设置表头数据
	titles := []fyne.CanvasObject{}
	for i, item := range global.DefCols {
		lt := ""
		if i < len(global.CurCols) {
			lt = global.CurCols[i]
		}
		lbl := widget.NewLabel(lt)

		for _, w := range item {
			cl := container.NewGridWrap(fyne.NewSize(float32(w), 35), lbl)
			titles = append(titles, cl)
		}
	}
	global.ListTitle = container.NewHBox(titles...)

	// 列表信息
	global.ListTable = widget.NewList(func() int {
		return len(global.ListPerson)
	}, func() fyne.CanvasObject {

		titles := []fyne.CanvasObject{}
		for _, item := range global.DefCols {
			lbl := widget.NewLabel("")
			lbl.Wrapping = fyne.TextWrapWord

			for _, w := range item {
				cl := container.NewGridWrap(fyne.NewSize(float32(w), 45), lbl)
				titles = append(titles, cl)
			}
		}
		vBox := container.NewHBox(titles...)
		return vBox
		// return widget.NewLabel("")
	}, func(index widget.ListItemID, co fyne.CanvasObject) {
		ListTabelItem(index, co)
	},
	)

	// 分页组装处理
	PaginatorCombing()

	return container.NewBorder(global.ListTitle, global.PaginatorBox, nil, nil, global.ListTable)
}

// list刷新
func ListTabelRefresh(currPage, pageSize int) {
	var err error
	global.ListPerson, global.Total, err = model.GetDataList(pageSize, currPage)
	if err != nil {
		global.LabelMsgText(err.Error())
		global.ErrorDialog(err.Error())
		return
	}

	fmt.Println("ListTabelRefresh:", currPage, pageSize, global.Total, len(global.ListPerson))
	global.Paginator = global.PaginatorFunc(currPage, pageSize, int64(global.Total))

	if len(global.PagesBox.Objects) > 0 {
		for r := global.Paginator.Totalpages; r < len(global.PagesBox.Objects) && r > 0; r++ {
			global.PagesBox.Remove(global.PagesBox.Objects[r])
		}
	}

	if global.PaginatorBox != nil {

		if global.Paginator.Totalpages == 0 {
			global.PaginatorBox.Hide()
		} else {
			global.PaginatorBox.Show()
		}
	}

	garray.NewIntArrayFrom(global.Paginator.Pages).Iterator(func(k, v int) bool {
		// 如果当前分页按钮数量小于5个,且k大于当前数量,则新增一个
		if len(global.PagesBox.Objects) < 5 && k+1 > len(global.PagesBox.Objects) {
			global.PagesBox.Add(widget.NewButton("1", nil))
		}

		if k > global.Paginator.Totalpages && global.Paginator.Totalpages < 5 {
			for r := global.Paginator.Totalpages; r < 5; r++ {
				global.PagesBox.Remove(global.PagesBox.Objects[r])
			}
		}

		btn := global.PagesBox.Objects[k].(*widget.Button)
		if v == currPage {
			btn.SetText(fmt.Sprintf("*%d", v))
		} else {
			btn.SetText(fmt.Sprintf("%d", v))
		}
		btn.OnTapped = func() {
			global.CurrPagedb.Set(v)
		}
		global.ListTable.Refresh()

		fmt.Println("--", v)
		return true
	})

	global.PaginatorLabel.SetText(fmt.Sprintf("%d/%d : %d", currPage, global.Paginator.Totalpages, global.Total))
}

// 列表的每项
func ListTabelItem(index widget.ListItemID, co fyne.CanvasObject) {
	// VBox->GridWarp->Label

	oneRow := co.(*fyne.Container)
	for i, col := range global.CurCols {
		label := oneRow.Objects[i].(*fyne.Container)
		if reflect.TypeOf(label.Objects[0]) == reflect.TypeOf(widget.NewLabel("")) {
			lb1 := label.Objects[0].(*widget.Label)
			switch col {
			case "姓名":
				s := fmt.Sprintf("%d = %s", global.ListPerson[index].Id, global.ListPerson[index].Realname)
				lb1.SetText(s)
			case "身份证号":
				lb1.SetText(global.ListPerson[index].Idcard)
			case "手机号":
				lb1.SetText(global.ListPerson[index].Mobile)
			case "银行卡号":
				lb1.SetText(global.ListPerson[index].Bankcard)
			case "结果":
				text := global.ListPerson[index].Result
				if text == "1" {
					text = "一致"
				} else if text == "2" {
					text = "不一致"
				}
				lb1.SetText(text)
			case "描述":
				lb1.SetText(global.ListPerson[index].Msg)
			case "状态":
				text := "未处理"
				if global.ListPerson[index].Completed == 1 {
					text = "已处理"
				}
				lb1.SetText(text)
			case "任务Id":
				lb1.SetText(global.ListPerson[index].Jobid)
			default:
				lb1.SetText("")
			}
		} else {
			fmt.Println("===", reflect.TypeOf(label.Objects[0]))
		}
	}
}

// 分页数据项组装
func PaginatorCombing() {
	currPage := 1
	// 添加分页按钮,绑定当前值,并监听页面变更
	global.CurrPagedb = binding.BindInt(&currPage)
	global.PageSized = binding.BindInt(&global.PageSize)

	global.CurrPagedb.AddListener(binding.NewDataListener(func() {
		ListTabelRefresh(currPage, global.PageSize)
	}))

	// 处理分页
	global.Paginator = global.PaginatorFunc(currPage, global.PageSize, int64(global.Total))
	// 默认显示5页按钮
	global.PagesBox = container.NewHBox()
	box := 5
	if box > global.Paginator.Totalpages {
		box = global.Paginator.Totalpages
	}
	for i := 0; i < box; i++ {
		global.PagesBox.Add(widget.NewButton("1", nil))
	}

	global.PaginatorLabel = widget.NewLabel(fmt.Sprintf("%d/%d : %d", currPage, global.Paginator.Totalpages, global.Total))

	first := widget.NewButton("第一页", func() {
		global.CurrPagedb.Set(1)
	})
	pre := widget.NewButton("上一页", func() {
		global.CurrPagedb.Set(global.Paginator.Perpage)
	})
	next := widget.NewButton("下一页", func() {
		global.CurrPagedb.Set(global.Paginator.Nextpage)
	})
	last := widget.NewButton("尾页", nil)
	last.OnTapped = func() {
		global.CurrPagedb.Set(global.Paginator.Totalpages)
	}

	// 每页数据选择
	pageSizeSel := widget.NewSelect([]string{"10", "15", "20", "50", "100"}, nil)
	pageSizeSel.OnChanged = func(pg string) {
		// nums = 0
		ps, _ := strconv.Atoi(pg)
		global.PageSized.Set(ps)
		ListTabelRefresh(currPage, ps)
	}
	pageSizeSel.SetSelected(strconv.Itoa(global.PageSize))

	global.PaginatorBox = container.NewHBox(first, pre, global.PagesBox, next, last, global.PaginatorLabel, pageSizeSel)
	if global.Paginator.Totalpages > 0 {
		global.PaginatorBox.Show()
	} else {
		global.PaginatorBox.Hide()
	}
}

注意:

  • 1、应用第一次启动时候会在当前目录下初始化数据库文件
  • 2、在切换接口前确保之前的数据已导出
  • 3、请严格按照模板格式上传数据
  • 4、一定要少量数据先测试、功能无误再大量测试,避免不必要的损失
  • 5、该工具仅供交流学习使用,因此产生的任何损失概不负责
    应用下载:本地批量核验工具

福利彩蛋:没有好玩的 API 接口?上百款免费接口等你来,免费 API,免费 API 大全

;