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

news2024/11/25 13:39:55

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
同样在这里可以申请您感兴趣的接口

  • [208]三网手机实名制认证[简]
  • [103]身份证实名认证
  • [188]银行卡姓名二要素核验
  • [762]银行卡身份证二要素核验
  • [763]银行卡手机号二要素核验
  • [207]银行卡三要素核验[简]
  • [213]银行卡四要素核验[简]
将申请接的接口的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 大全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1876125.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux系统编程--进程间通信

目录 1. 介绍 1.1 进程间通信的目的 1.2 进程间通信的分类 2. 管道 2.1 什么是管道 2.2 匿名管道 2.2.1 接口 2.2.2 步骤--以父子进程通信为例 2.2.3 站在文件描述符角度-深度理解 2.2.4 管道代码 2.2.5 读写特征 2.2.6 管道特征 2.3 命名管道 2.3.1 接口 2.3.2…

【方案】基于5G智慧工业园区解决方案(PPT原件)

5G智慧工业园区整体解决方案旨在通过集成5G通信技术、物联网、大数据和云计算等先进技术&#xff0c;实现园区的智能化、高效化和绿色化。 该方案首先构建高速、稳定的5G网络&#xff0c;确保园区内设备、人员与物流的实时连接和高效沟通。其次&#xff0c;通过工业物联网技术&…

【算法】Merge Sort 合并排序

Merge Sort概述 分而治之算法 递归地将问题分解为多个子问题&#xff0c;直到它们变得简单易解 将解决方案组合起来&#xff0c;解决原有问题 O&#xff08;n*log&#xff08;n&#xff09;&#xff09;运行时间 基于比较的算法的最佳运行时间 一般原则 合并排序: 1. 将数…

进程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

进程 进程的概念 何为进程&#xff1f; 进程是一个应用程序的执行实例&#xff0c;也就是系统中正在运行的应用程序&#xff0c;程序一旦运行就是进程 进程是一个动态过程&#xff0c;它是程序的一次运行过程&#xff0c;而非静态文件 同一个程序可以被运行多次&#xff0c;…

扎克伯格抨击闭源人工智能竞争对手试图“创造上帝”

Meta 首席执行官马克-扎克伯格&#xff08;Mark Zuckerberg&#xff09;在周四发表的一篇访谈中谈到了他对人工智能未来的看法&#xff0c;他深信"不会只有一种人工智能"。扎克伯格强调了开源的价值&#xff0c;即把人工智能工具交到许多人手中&#xff0c;他还不忘贬…

博创智能IPO终止:曾因对赌失败而赔偿,左手分红、右手募资补流

近日&#xff0c;上海证券交易所披露的信息显示&#xff0c;博创智能装备股份有限公司&#xff08;下称“博创智能”&#xff09;及其保荐人国金证券撤回上市申请文件。因此&#xff0c;上海证券交易所决定终止对其首次公开发行股票并在科创板上市的审核。 据贝多财经了解&…

从AICore到TensorCore:华为910B与NVIDIA A100全面分析

华为NPU 910B与NVIDIA GPU A100性能对比&#xff0c;从AICore到TensorCore&#xff0c;展现各自计算核心优势。 AI 2.0浪潮汹涌而来&#xff0c;若仍将其与区块链等量齐观&#xff0c;视作炒作泡沫&#xff0c;则将错失新时代的巨大机遇。现在&#xff0c;就是把握AI时代的关键…

OpenGL3.3_C++_Windows(22)

材质&#xff1a; 决定物体在渲染过程中最终视觉呈现的关键因素之一&#xff0c;它通过一系列光学&#xff08;投光物&#xff09;和物理参数&#xff08;反光度&#xff0c;反照率、金属度&#xff0c;折射率……&#xff09;准确模拟现实世界中的材料特性&#xff0c;从而增…

我重生了,学会了珂朵莉树

还玩线段树吗&#xff1f; 前言&注明 我好像一万年没更新了&#xff1f; 化学&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff…

NSSCTF-Web题目18(反序列化)

目录 [NISACTF 2022]babyserialize 1、题目 2、知识点 3、思路 [SWPUCTF 2022 新生赛]ez_ez_unserialize 4、题目 5、知识点 6、思路 [NISACTF 2022]babyserialize 1、题目 2、知识点 反序列化、绕过过滤、命令执行 3、思路 <?php include "waf.php";…

【RAG】FoRAG:面向网络增强型长形式问答的事实性优化RAG

一、解决问题 在基于网络的长形式问答&#xff08;Web-enhanced Long-form Question Answering, LFQA&#xff09;任务中&#xff0c;现有RAG在生成答案时存在的问题&#xff1a; 事实性不足&#xff1a;研究表明&#xff0c;现有系统生成的答案中只有大约一半的陈述能够完全得…

填报高考志愿时,学校、专业和城市怎么选择呢?

我的观点是&#xff1a; 专业>城市>学校 专业是兴趣导向&#xff0c;符合自己的价值观&#xff0c;失去了这种驱动力的专业学习&#xff0c;会变得非常艰难的&#xff0c;而且没有竞争力&#xff0c;所以我的排序第一位是专业。 其次是城市&#xff0c;最好是一线城市&…

vue3.0(十五)内置组件Teleport和Suspense

文章目录 一、Teleport1.基本用法2.禁用Teleport3.多个 Teleport 共享目标4.搭配组件 二、 Suspense1.什么是Suspense2.异步依赖3.加载中状态4.事件5.错误处理6.和其他组件结合注意 一、Teleport <Teleport> 是一个内置组件&#xff0c;它可以将一个组件内部的一部分模板…

java+mysql通讯录管理

完整代码地址如果控制台打印出现乱码&#xff0c;进行如下设置

stm32-hal库(5)--usart串口通信三种模式(主从通信)(关于通信失败和串口不断发送数据问题的解决)

问题&#xff1a; 最近发现&#xff0c;stm32cubemx最新版本f1系列的hal库&#xff08;1.85版本&#xff09;生成的hal库&#xff0c;其中stm32f1xx_hal_uart.c的库文件中&#xff0c;其串口发送接收存在一些问题&#xff1a; 1.没有使用 __HAL_LOCK 和 __HAL_UNLOCK 宏&…

LinkedIn被封原因和解封方法

对于初识领英和对领英生态规则不熟悉的人来说&#xff0c;很容易造成领英账号被封号(被限制登录)的情况&#xff0c;那么如何才能避免和解决领英帐号被封号(被限制登录)的难题呢&#xff1f; 领英帐号被封号或被限制登录主要会有两类情况。 首先要搞清楚&#xff0c; Linkedi…

谷歌邮箱被停用,开发者账号也要废了?还能申诉回来吗?怎么申诉?

相信不少开发者都遭遇过开发者账号的邮箱被暂停的情况&#xff0c;有时候明明什么也没做&#xff0c;就被突然被停用了&#xff1f; 面对这种情况&#xff0c;开发者们最担心的莫过于这是否会波及到他们使用该邮箱注册的开发者账号&#xff1f;APP还会正常审核吗&#xff1f;毕…

接口自动化测试关联token的方法?

引言&#xff1a; 在接口自动化测试中&#xff0c;有时候我们需要关联token来进行身份验证或权限管理。本文将从零开始&#xff0c;介绍如何详细且规范地实现接口自动化测试中token的关联。 步骤一&#xff1a;准备工作 在开始之前&#xff0c;我们需要确保以下准备工作已完成…

一文说明白,香港优才计划到底适合哪些人申请?

香港优才计划的热度仍然在持续&#xff0c;自取消配额限制以来&#xff0c;仅2023年一年时间&#xff0c;优才计划申请人数就超过8万&#xff0c;比历年累积的申请人数还要多。 之所以有如此高的热度&#xff0c;完全是因为优才的灵活性&#xff0c;为许多中产精英拿香港身份甚…

Ruoyi-前后端分离部署

目录 一. 环境准备 二. 安装Nginx 三. 安装Java 四. 安装MySQL、Redis 五. 配置打包环境 1. 配置前端打包环境 2. 配置后端打包环境 3. 获取代码 4. 前端代码打包 5. 后端项目打包 六. 项目上线 1.前端项目上线 2. 后端项目上线 一. 环境准备 项目官网&#xff1a…