各位程序员,大家好!在这个简短系列的第一部分中,我们看到了桌面应用程序的创建和操作,用于存储和加密使用 Wails 框架创建的密码。我们还描述了 Go 后端以及如何将其绑定到前端。
在这一部分中,我们将处理用户界面。正如我们在那篇文章中所述,Wails 允许我们使用任何我们喜欢的 Web 框架,甚至 Vanilla JS,来构建我们的 GUI。正如我所说,Wails 的创作者似乎偏爱 Svelte,因为他们总是将其作为首选。当我们要求使用 Svelte Typescript (wails init -n myproject -t svelte-ts) 创建项目时,Wails CLI(当前版本)会使用 Svelte3 生成脚手架。正如我已经告诉过您的,如果您更喜欢使用 Svelte5(及其新功能),我有一个 bash 脚本可以自动创建它(无论如何,您必须安装 Wails CLI)。此外,它还添加了 Taildwindcss Daisyui,这在我看来是界面设计的完美组合。
事实是,我首先使用 Vanilla Js 和 Vue,然后使用 React,甚至使用那个对于许多人来说是React 的奇怪库。 🎜>HTMX(我不得不说我喜欢❤️)。但是Svelte
让你从一开始就爱上它,我不得不说,我是在尝试Wails时第一次使用它的(我保证会继续使用它......)。但是,尽管 Web 框架很舒服,但我们必须提醒后端开发人员,前端并不那么容易?!!但是让我们进入正题吧。
如果您使用过任何 Web 框架,您很快就会认识到 Wails CLI 在底层使用了 ViteJ:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
如果您使用过 Vite 生成的任何 Web 框架,您不会对其配置文件感到惊讶。这里我使用 Svelte5 (加上 Taildwindcss Daisyui 的配置),这就是生成我自己的 bash 脚本的原因,正如我已经告诉过你的。我们还使用了TypeScript
,这样会方便前端的开发,所以你也可以看到它的配置。
但是这个解释中最重要的是wailsjs文件夹的内容。这就是Wails 所做的编译发挥其魔力的地方。 go 子文件夹是存储必须与前端交互的后端部分的“翻译”为 Js/Ts
的方法的位置。例如,在 main/App.js(或其 TypeScript 版本 main/App.d.ts)中,有 App 结构的所有导出(公共)方法:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
他们都返回一个承诺。如果 Promise“包装”了一些用作返回类型的 Go 结构,或者相应的函数采用参数类型,则会有一个模块(models.ts,在本例中为类型,因为我们使用 TypeScript),其中包含与 Go 对应的类命名空间中的结构及其构造函数。
此外,runtime 子文件夹包含 Go 运行时包中的所有方法,这些方法允许我们分别操作窗口以及从后端侦听或发出的事件。
src 文件夹包含将由 Vite 编译的文件,并将它们保存在“frontend/dist”中(并嵌入到最终的可执行文件中),就像在任何 Web 应用程序中一样。请注意,由于我们使用 Tailwindcss,因此 style.css 包含基本的 Tailwind 配置以及我们需要使用的任何 CSS 类。此外,作为界面使用网络技术的一个优势,我们可以轻松地使用一种或多种字体(文件夹资产/字体)或交换它们。
为了完成这个概述,请注意,当我们在开发模式(wails dev)下编译时,除了允许我们热重载之外,我们不仅可以观察所做的更改(无论是在后端还是在前端)应用程序窗口本身,而且还通过地址 http://localhost:34115 在 Web 浏览器中,因为 Web 服务器已启动。这允许您使用您最喜欢的浏览器开发扩展。虽然必须说Wails本人为我们提供了一些非常有用的开发工具,但是当我们右键单击应用程序窗口(仅在开发模式下)并选择“Inspect Element”时:
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
如你所见,我已经向 Svelte 添加了 4 个 JavaScript 包(除了已经提到的 Tailwindcss Daisyui):
每个 SPA 的入口点都是 main.js(或 main.ts)文件,所以让我们从它开始:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
我突出显示了添加到由 Wails CLI 生成的框架中的内容。 svelte-i18n 库要求在 main.js/ts 文件中注册包含翻译的 JSON 文件,同时设置 fallback/initial 语言(尽管我们'你会看到,稍后将根据用户选择的偏好进行操作)。包含翻译的 JSON 文件的格式为:
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
我发现这个库的系统对于促进 Svelte 应用程序的翻译非常简单和方便(您可以查阅其文档以了解更多详细信息):
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
您还可以使用像这样的网站,它将帮助您将 JSON 文件翻译成不同的语言。然而,问题是,当您使用 $format 填充 .svelte 文件时,您必须手动跟踪它们,这是乏味且容易出错的。我不知道有什么方法可以自动完成这项任务,如果有人知道,我会很感兴趣,如果你能让我知道?...否则,我必须想出某种脚本来完成这项工作。
与任何 Svelte 应用程序一样,构建界面的下一步是 App.svelte 文件:
/* main.ts */ import { mount } from 'svelte' import './style.css' import App from './App.svelte' import { addMessages, init } from "svelte-i18n"; // ⇐ ⇐ import en from './locales/en.json'; // ⇐ ⇐ import es from './locales/es.json'; // ⇐ ⇐ addMessages('en', en); // ⇐ ⇐ addMessages('es', es); // ⇐ ⇐ init({ fallbackLocale: 'en', // ⇐ ⇐ initialLocale: 'en', // ⇐ ⇐ }); const app = mount(App, { target: document.getElementById('app')!, }) export default app
这里我们使用 GetMasterPassword,它是编译应用程序时自动生成的 绑定,并被声明为 struct App 的公共方法(请参阅本系列的第一部分)。该函数查询数据库,如果其中注册了主密码,它会认为用户已经注册(它返回一个包含布尔值的承诺),要求他输入所述密码以允许他访问其余内容的意见。如果数据库中没有主密码,则该用户被视为“新”,要求他生成自己的密码以首次进入应用程序。
最后,在安装 Login.svelte 组件时,我们做了一些对应用程序的其余部分很重要的事情。尽管 svelte-i18n 库强制我们声明初始语言代码,但正如我们已经看到的,在安装 Login.svelte 时,我们要求数据库(使用 GetLanguage 绑定)检查是否保存了语言代码。如果数据库返回空字符串,即没有配置为用户首选项的语言,svelte-i18n 将使用配置为initialLocale 的值。如果配置了一种语言,则将设置该语言 (locale.set(result);) 并发出“change_titles”事件,标题栏和应用程序本机对话框的翻译标题将传递到该事件供后端处理:
/* frontend/src/locales/en.json */ { "language": "Language", "app_title": "Nu-i uita • minimalist password store", "select_directory": "Select the directory where to save the data export", "select_file": "Select the backup file to import", "master_password": "Master Password ?", "generate": "Generate", "insert": "Insert", "login": "Login", ... } /* frontend/src/locales/es.json */ { "language": "Idioma", "app_title": "Nu-i uita • almacén de contraseñas minimalista", "select_directory": "Selecciona el directorio donde guardar los datos exportados", "select_file": "Selecciona el archivo de respaldo que deseas importar", "master_password": "Contraseña Maestra ?", "generate": "Generar", "insert": "Insertar", "login": "Inciar sesión", ... }
以下是处理登录的逻辑:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
简单地说:newPassword,绑定到获取用户输入内容的输入的状态,首先由 onLogin 检查它是否至少有 6 个字符,并且它们都是 ASCII 字符,即,通过这个小函数 const isAscii = (str: string): boolean => 它们只有 1 个字节长(请参阅本系列第一部分中的原因)。 /^[x00-x7F] $/.test(str);.如果检查失败,该函数将返回并向用户显示警告 toast。之后,如果数据库中没有保存主密码(isLogin = false),则 SaveMasterPassword 函数将保存用户输入的任何内容(Wails 生成的绑定);如果 Promise 成功解析(返回 uuid 字符串作为数据库中存储的记录的 Id),用户将被 svelte-spa-router 带到主页视图库的推送方法。相反,如果密码通过了长度检查,且不存在 非 ASCII 字符,并且数据库中存在主密码 (isLogin = true),则 CheckMasterPassword 函数将根据存储的密码验证其身份,或者将用户带到主视图(promise 为 true 解决)或显示 toast 表明输入的密码不正确。
应用程序的中心视图,同时也是最复杂的视图是主页视图。它的 HTML 实际上分为 3 个组件:一个带有搜索输入的顶部按钮栏(TopActions 组件)、一个底部按钮栏(BottomActions 组件)以及一个中心区域,其中使用以下命令显示已保存密码条目的总数或这些条目的列表:可滚动窗口(EntriesList 组件):
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
也就是说,它使搜索状态(searchTerms)成为一个空字符串,这样如果有任何搜索词,它就会被重置,从而显示整个列表。另一方面,它切换 showList 状态(props TopActions 中的 isEntriesList),以便父组件显示或隐藏列表。
正如我们在上图中看到的,两个子组件与父组件的 searchTerms 状态共享相同的 props。 TopActions 组件捕获用户的输入,并将其作为状态传递给父组件 Home,而父组件 Home 又将其作为 props 传递给其子组件 EntriesList。
显示完整列表或按用户输入的搜索词过滤的列表的主要逻辑按预期由 EntriesList 组件执行:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
正如我们所说,收到 2 个 props(listCounter 和 search)并维护一个状态(让条目:models.PasswordEntry[] = $state([]);)。当根据用户的请求安装该组件时,后端会被要求提供已保存密码条目的完整列表。如果没有搜索词,则将其存储在状态中;如果有,则对获得的数组进行简单过滤,并将其保存在状态:
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
在显示的列表中,用户可以执行 2 个操作。第一个是显示条目的详细信息,当他点击相应的按钮时执行:onclick={() =>推送(`/details/${entry.Id}`)}。基本上,我们调用路由库的 Push 方法将用户带到详细信息视图,但传递与相关项目相对应的 Id 参数。
用户可以执行的另一个操作是从列表中删除项目。如果他单击相应的按钮,将显示一个确认弹出窗口,调用 showAlert 函数。该函数依次调用 showWarning,它实际上是 sweetalert2 库的抽象层(调用 sweetalert2 库的所有函数都在 frontend/src/lib/popups/popups.ts 中)。如果用户确认删除操作,则调用DeleteEntry绑定(将其从数据库中删除),反过来,如果它返回的promise得到解析,则调用deleteItem(将其从存储在条目状态的数组中删除) :
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
主页视图的另一个组件(BottomActions)要简单得多:它不接收 props 并且仅限于将用户重定向到各种视图(Settings、About 或 AddPassword)。
AddPassword 和 EditPassword 视图的逻辑非常相似,也与 Login 视图类似。两者都不允许用户在文本输入的开头和结尾输入空格,并遵循与登录视图相同的策略,要求密码长度至少为 6 个 ASCII 字符。基本上,它们的与众不同之处在于它们调用与需要执行的操作相关的由 Wails 生成的链接:
/* main.ts */ import { mount } from 'svelte' import './style.css' import App from './App.svelte' import { addMessages, init } from "svelte-i18n"; // ⇐ ⇐ import en from './locales/en.json'; // ⇐ ⇐ import es from './locales/es.json'; // ⇐ ⇐ addMessages('en', en); // ⇐ ⇐ addMessages('es', es); // ⇐ ⇐ init({ fallbackLocale: 'en', // ⇐ ⇐ initialLocale: 'en', // ⇐ ⇐ }); const app = mount(App, { target: document.getElementById('app')!, }) export default app
另一个有点复杂的视图是“设置”。它有一个语言组件,它从其父组件(设置)接收 props languageName:
/* frontend/src/locales/en.json */ { "language": "Language", "app_title": "Nu-i uita • minimalist password store", "select_directory": "Select the directory where to save the data export", "select_file": "Select the backup file to import", "master_password": "Master Password ?", "generate": "Generate", "insert": "Insert", "login": "Login", ... } /* frontend/src/locales/es.json */ { "language": "Idioma", "app_title": "Nu-i uita • almacén de contraseñas minimalista", "select_directory": "Selecciona el directorio donde guardar los datos exportados", "select_file": "Selecciona el archivo de respaldo que deseas importar", "master_password": "Contraseña Maestra ?", "generate": "Generar", "insert": "Insertar", "login": "Inciar sesión", ... }
此组件的 HTML 是一个处理用户语言选择的 select。在其 onchange 事件中,它接收一个函数 (handleChange),该函数执行 3 件事:
返回“设置”视图,其整个操作由一系列发送到后端或从后端接收的事件控制。最简单的是退出按钮:当用户单击它时,会在后端触发并侦听退出事件,然后应用程序关闭(onclick={() => EventsEmit("quit")})。 提示 通知用户 Escape 键(快捷键)执行相同的操作,正如我们已经解释过的。
重置按钮调用显示弹出窗口的函数:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
如果用户接受该操作,则会调用 Drop 绑定,这会清除数据库中的所有 collections,如果它返回的 Promise 得到解析,则会将用户发送到 Login 视图,显示指示操作成功的模式。
剩下的另外两个操作彼此类似,所以让我们看看导入数据。
如果用户点击相应的按钮,则会发出一个事件(onclick={() => EventsEmit("import_data")}),该事件在后端监听。收到后,将打开本机选择文件对话框以允许用户选择备份文件。如果用户选择文件,包含路径(fileLocation)的变量将不包含空字符串,这将在后端触发一个事件(“enter_password”),该事件现在在前端侦听,然后显示一个新的弹出窗口询问导出时使用的主密码。同样,前端将发出另一个事件(“密码”),其中携带用户输入的主密码。当后端接收到这个新事件时,会执行 Db 包的 ImportDump 方法,该方法执行从用户选择的备份文件中读取和恢复 DB 中的数据的工作。结果,发出一个新事件(“imported_data”),该事件将其执行结果(成功或不成功)作为附加数据携带。前端收到事件后只需执行 2 个任务:
所有这些在代码逻辑中比用文字解释要容易得多?:
... . ├── index.html ├── package.json ├── package.json.md5 ├── package-lock.json ├── postcss.config.js ├── README.md ├── src │ ├── App.svelte │ ├── assets │ │ ├── fonts │ │ │ ├── nunito-v16-latin-regular.woff2 │ │ │ └── OFL.txt │ │ └── images │ │ └── logo-universal.png │ ├── lib │ │ ├── BackBtn.svelte │ │ ├── BottomActions.svelte │ │ ├── EditActions.svelte │ │ ├── EntriesList.svelte │ │ ├── Language.svelte │ │ ├── popups │ │ │ ├── alert-icons.ts │ │ │ └── popups.ts │ │ ├── ShowPasswordBtn.svelte │ │ └── TopActions.svelte │ ├── locales │ │ ├── en.json │ │ └── es.json │ ├── main.ts │ ├── pages │ │ ├── About.svelte │ │ ├── AddPassword.svelte │ │ ├── Details.svelte │ │ ├── EditPassword.svelte │ │ ├── Home.svelte │ │ ├── Login.svelte │ │ └── Settings.svelte │ ├── style.css │ └── vite-env.d.ts ├── svelte.config.js ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── wailsjs ├── go │ ├── main │ │ ├── App.d.ts │ │ └── App.js │ └── models.ts └── runtime ├── package.json ├── runtime.d.ts └── runtime.js ...
值得一提的是,在前端注册侦听器的 Wails 运行时函数 (EventsOn) 返回一个函数,该函数在调用时会取消所述侦听器。当组件被销毁时,取消所述监听器是很方便的。与 React 类似,onMount 钩子可以通过让监听器返回一个清理函数来“清理”它们,在这种情况下,该函数将调用 EventsOn 返回的所有函数,我们已采取预防措施将其保存在单独的文件中。变量:
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL // This file is automatically generated. DO NOT EDIT import {models} from '../models'; export function AddPasswordEntry(arg1:string,arg2:string,arg3:string):Promise<string>; export function CheckMasterPassword(arg1:string):Promise<boolean>; export function DeleteEntry(arg1:string):Promise<void>; export function Drop():Promise<void>; export function GetAllEntries():Promise<Array<models.PasswordEntry>>; export function GetEntryById(arg1:string):Promise<models.PasswordEntry>; export function GetLanguage():Promise<string>; export function GetMasterPassword():Promise<boolean>; export function GetPasswordCount():Promise<number>; export function SaveLanguage(arg1:string):Promise<void>; export function SaveMasterPassword(arg1:string):Promise<string>; export function UpdateEntry(arg1:models.PasswordEntry):Promise<boolean>;
为了完成对我们应用程序前端部分的审查,只需介绍一下“关于”组件即可。这几乎没有逻辑,因为它仅限于显示有关应用程序的信息,就像常见的 about 一样。然而,应该说,正如我们所看到的,该视图显示了指向应用程序存储库的链接。显然,在普通网页中,锚标记 () 将使我们导航到相应的链接,但在桌面应用程序中,如果 Wails 在运行时没有为此提供特定函数 (BrowserOpenURL),则不会发生这种情况:
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
这会将二进制文件构建到 build/bin 文件夹中。但是,要选择其他构建选项或执行交叉编译,您可能需要查看 Wails CLI 文档。
对于这个应用程序,我想我已经在本系列的第一部分中提到过,我只关注Windows和Linux的编译。为了以舒适的方式执行这些任务(由于测试,这些任务是重复的),我创建了一些小脚本和一个“协调”它们的 Makefile。
make create-bundles 命令为 Linux 版本创建一个 .tar.xz 压缩文件,其中包含应用程序和一个充当安装可执行文件的“安装程序”的 Makefile,一个用于在 开始菜单以及相应的应用程序图标。对于 Windows 版本,二进制文件只是在名为 dist/ 的文件夹中压缩为 .zip。但是,如果您更喜欢跨平台自动构建,Wails 有一个 Github Actions,允许您上传(默认选项)生成的工件到您的存储库。
请注意,如果您在运行时使用 make create-bundles 命令,它将调用 Wails 命令 wails build -clean -upx (对于 Linux)或 wails build -skipbindings -s -platform windows/amd64 - upx(对于 Windows)。 -upx 标志是指使用您应该安装在计算机上的最后,请注意,构建脚本会自动将当前存储库标签添加到“关于”视图,并在构建后将其值恢复为默认值 (DEV_VERSION)。UPX 实用程序来压缩二进制文件。可执行文件体积小的部分秘密是由于该实用程序所做的出色的压缩工作。
唷!这两篇文章比我想象的要长!但我希望您喜欢它们,最重要的是,它们可以帮助您思考新项目。在编程中学习一些东西就像这样......
请记住,您可以在此 GitHub 存储库中找到所有应用程序代码。
我相信我会在其他帖子中见到你。编码愉快?!!
以上是极简密码管理器桌面应用程序:进军 Golang 的 Wails 框架(第 2 部分)的详细内容。更多信息请关注PHP中文网其他相关文章!