各位程序員,大家好!在這個(gè)簡(jiǎn)短系列的第一部分中,我們看到了桌面應(yīng)用程序的創(chuàng)建和操作,用于存儲(chǔ)和加密使用 Wails 框架創(chuàng)建的密碼。我們還描述了 Go 后端以及如何將其綁定到前端。
在這一部分中,我們將處理用戶界面。正如我們?cè)谀瞧恼轮兴?,Wails 允許我們使用任何我們喜歡的 Web 框架,甚至 Vanilla JS,來構(gòu)建我們的 GUI。正如我所說,Wails 的創(chuàng)作者似乎偏愛 Svelte,因?yàn)樗麄兛偸菍⑵渥鳛槭走x。當(dāng)我們要求使用 Svelte Typescript (wails init -n myproject -t svelte-ts) 創(chuàng)建項(xiàng)目時(shí),Wails CLI(當(dāng)前版本)會(huì)使用 Svelte3 生成腳手架。正如我已經(jīng)告訴過您的,如果您更喜歡使用 Svelte5(及其新功能),我有一個(gè) bash 腳本可以自動(dòng)創(chuàng)建它(無論如何,您必須安裝 Wails CLI)。此外,它還添加了 Taildwindcss Daisyui,這在我看來是界面設(shè)計(jì)的完美組合。
事實(shí)是,我首先使用 Vanilla Js 和 Vue,然后使用 React,甚至使用那個(gè)對(duì)于許多人來說是React 的奇怪庫。 ??>HTMX(我不得不說我喜歡??)。但是Svelte
讓你從一開始就愛上它,我不得不說,我是在嘗試Wails時(shí)第一次使用它的(我保證會(huì)繼續(xù)使用它......)。但是,盡管 Web 框架很舒服,但我們必須提醒后端開發(fā)人員,前端并不那么容易???!但是讓我們進(jìn)入正題吧。
I - 前端結(jié)構(gòu)概覽
如果您使用過任何 Web 框架,您很快就會(huì)認(rèn)識(shí)到 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 框架,您不會(huì)對(duì)其配置文件感到驚訝。這里我使用 Svelte5 (加上 Taildwindcss Daisyui 的配置),這就是生成我自己的 bash 腳本的原因,正如我已經(jīng)告訴過你的。我們還使用了TypeScript
,這樣會(huì)方便前端的開發(fā),所以你也可以看到它的配置。
但是這個(gè)解釋中最重要的是wailsjs文件夾的內(nèi)容。這就是Wails 所做的編譯發(fā)揮其魔力的地方。 go 子文件夾是存儲(chǔ)必須與前端交互的后端部分的“翻譯”為 Js/Ts
的方法的位置。例如,在 main/App.js(或其 TypeScript 版本 main/App.d.ts)中,有 App 結(jié)構(gòu)的所有導(dǎo)出(公共)方法:
... . ├── 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 ...
他們都返回一個(gè)承諾。如果 Promise“包裝”了一些用作返回類型的 Go 結(jié)構(gòu),或者相應(yīng)的函數(shù)采用參數(shù)類型,則會(huì)有一個(gè)模塊(models.ts,在本例中為類型,因?yàn)槲覀兪褂?TypeScript),其中包含與 Go 對(duì)應(yīng)的類命名空間中的結(jié)構(gòu)及其構(gòu)造函數(shù)。
此外,runtime 子文件夾包含 Go 運(yùn)行時(shí)包中的所有方法,這些方法允許我們分別操作窗口以及從后端偵聽或發(fā)出的事件。
src 文件夾包含將由 Vite 編譯的文件,并將它們保存在“frontend/dist”中(并嵌入到最終的可執(zhí)行文件中),就像在任何 Web 應(yīng)用程序中一樣。請(qǐng)注意,由于我們使用 Tailwindcss,因此 style.css 包含基本的 Tailwind 配置以及我們需要使用的任何 CSS 類。此外,作為界面使用網(wǎng)絡(luò)技術(shù)的一個(gè)優(yōu)勢(shì),我們可以輕松地使用一種或多種字體(文件夾資產(chǎn)/字體)或交換它們。
為了完成這個(gè)概述,請(qǐng)注意,當(dāng)我們?cè)陂_發(fā)模式(wails dev)下編譯時(shí),除了允許我們熱重載之外,我們不僅可以觀察所做的更改(無論是在后端還是在前端)應(yīng)用程序窗口本身,而且還通過地址 http://localhost:34115 在 Web 瀏覽器中,因?yàn)?Web 服務(wù)器已啟動(dòng)。這允許您使用您最喜歡的瀏覽器開發(fā)擴(kuò)展。雖然必須說Wails本人為我們提供了一些非常有用的開發(fā)工具,但是當(dāng)我們右鍵單擊應(yīng)用程序窗口(僅在開發(fā)模式下)并選擇“Inspect Element”時(shí):
II - 現(xiàn)在……深入研究 HTML、CSS 和 JavaScript?
// 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>;
如你所見,我已經(jīng)向 Svelte 添加了 4 個(gè) JavaScript 包(除了已經(jīng)提到的 Tailwindcss Daisyui):
- svelte-copy,可以更輕松地將用戶名和密碼復(fù)制到剪貼板。
- svelte-i18n,用于 i18n 處理,即允許用戶更改應(yīng)用程序的語言。
- svelte-spa-router,Svelte 的一個(gè)小型路由庫,它可以更輕松地更改應(yīng)用程序窗口中的視圖,因?yàn)樵谶@種情況下,使用由SvelteKit。
- sweetalert2,基本上用它來輕松快速地創(chuàng)建模態(tài)框/對(duì)話框。
每個(gè) SPA 的入口點(diǎn)都是 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 生成的框架中的內(nèi)容。 svelte-i18n 庫要求在 main.js/ts 文件中注冊(cè)包含翻譯的 JSON 文件,同時(shí)設(shè)置 fallback/initial 語言(盡管我們'你會(huì)看到,稍后將根據(jù)用戶選擇的偏好進(jìn)行操作)。包含翻譯的 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>;
我發(fā)現(xiàn)這個(gè)庫的系統(tǒng)對(duì)于促進(jìn) Svelte 應(yīng)用程序的翻譯非常簡(jiǎn)單和方便(您可以查閱其文檔以了解更多詳細(xì)信息):
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
您還可以使用像這樣的網(wǎng)站,它將幫助您將 JSON 文件翻譯成不同的語言。然而,問題是,當(dāng)您使用 $format 填充 .svelte 文件時(shí),您必須手動(dòng)跟蹤它們,這是乏味且容易出錯(cuò)的。我不知道有什么方法可以自動(dòng)完成這項(xiàng)任務(wù),如果有人知道,我會(huì)很感興趣,如果你能讓我知道?...否則,我必須想出某種腳本來完成這項(xiàng)工作。
與任何 Svelte 應(yīng)用程序一樣,構(gòu)建界面的下一步是 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,它是編譯應(yīng)用程序時(shí)自動(dòng)生成的 綁定,并被聲明為 struct App 的公共方法(請(qǐng)參閱本系列的第一部分)。該函數(shù)查詢數(shù)據(jù)庫,如果其中注冊(cè)了主密碼,它會(huì)認(rèn)為用戶已經(jīng)注冊(cè)(它返回一個(gè)包含布爾值的承諾),要求他輸入所述密碼以允許他訪問其余內(nèi)容的意見。如果數(shù)據(jù)庫中沒有主密碼,則該用戶被視為“新”,要求他生成自己的密碼以首次進(jìn)入應(yīng)用程序。
最后,在安裝 Login.svelte 組件時(shí),我們做了一些對(duì)應(yīng)用程序的其余部分很重要的事情。盡管 svelte-i18n 庫強(qiáng)制我們聲明初始語言代碼,但正如我們已經(jīng)看到的,在安裝 Login.svelte 時(shí),我們要求數(shù)據(jù)庫(使用 GetLanguage 綁定)檢查是否保存了語言代碼。如果數(shù)據(jù)庫返回空字符串,即沒有配置為用戶首選項(xiàng)的語言,svelte-i18n 將使用配置為initialLocale 的值。如果配置了一種語言,則將設(shè)置該語言 (locale.set(result);) 并發(fā)出“change_titles”事件,標(biāo)題欄和應(yīng)用程序本機(jī)對(duì)話框的翻譯標(biāo)題將傳遞到該事件供后端處理:
/* 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 ...
簡(jiǎn)單地說:newPassword,綁定到獲取用戶輸入內(nèi)容的輸入的狀態(tài),首先由 onLogin 檢查它是否至少有 6 個(gè)字符,并且它們都是 ASCII 字符,即,通過這個(gè)小函數(shù) const isAscii = (str: string): boolean => 它們只有 1 個(gè)字節(jié)長(請(qǐng)參閱本系列第一部分中的原因)。 /^[x00-x7F] $/.test(str);.如果檢查失敗,該函數(shù)將返回并向用戶顯示警告 toast。之后,如果數(shù)據(jù)庫中沒有保存主密碼(isLogin = false),則 SaveMasterPassword 函數(shù)將保存用戶輸入的任何內(nèi)容(Wails 生成的綁定);如果 Promise 成功解析(返回 uuid 字符串作為數(shù)據(jù)庫中存儲(chǔ)的記錄的 Id),用戶將被 svelte-spa-router 帶到主頁視圖庫的推送方法。相反,如果密碼通過了長度檢查,且不存在 非 ASCII 字符,并且數(shù)據(jù)庫中存在主密碼 (isLogin = true),則 CheckMasterPassword 函數(shù)將根據(jù)存儲(chǔ)的密碼驗(yàn)證其身份,或者將用戶帶到主視圖(promise 為 true 解決)或顯示 toast 表明輸入的密碼不正確。
應(yīng)用程序的中心視圖,同時(shí)也是最復(fù)雜的視圖是主頁視圖。它的 HTML 實(shí)際上分為 3 個(gè)組件:一個(gè)帶有搜索輸入的頂部按鈕欄(TopActions 組件)、一個(gè)底部按鈕欄(BottomActions 組件)以及一個(gè)中心區(qū)域,其中使用以下命令顯示已保存密碼條目的總數(shù)或這些條目的列表:可滾動(dòng)窗口(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>;
也就是說,它使搜索狀態(tài)(searchTerms)成為一個(gè)空字符串,這樣如果有任何搜索詞,它就會(huì)被重置,從而顯示整個(gè)列表。另一方面,它切換 showList 狀態(tài)(props TopActions 中的 isEntriesList),以便父組件顯示或隱藏列表。
正如我們?cè)谏蠄D中看到的,兩個(gè)子組件與父組件的 searchTerms 狀態(tài)共享相同的 props。 TopActions 組件捕獲用戶的輸入,并將其作為狀態(tài)傳遞給父組件 Home,而父組件 Home 又將其作為 props 傳遞給其子組件 EntriesList。
顯示完整列表或按用戶輸入的搜索詞過濾的列表的主要邏輯按預(yù)期由 EntriesList 組件執(zhí)行:
... . ├── 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 個(gè) props(listCounter 和 search)并維護(hù)一個(gè)狀態(tài)(讓條目:models.PasswordEntry[] = $state([]);)。當(dāng)根據(jù)用戶的請(qǐng)求安裝該組件時(shí),后端會(huì)被要求提供已保存密碼條目的完整列表。如果沒有搜索詞,則將其存儲(chǔ)在狀態(tài)中;如果有,則對(duì)獲得的數(shù)組進(jìn)行簡(jiǎn)單過濾,并將其保存在狀態(tài):
// 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>;
在顯示的列表中,用戶可以執(zhí)行 2 個(gè)操作。第一個(gè)是顯示條目的詳細(xì)信息,當(dāng)他點(diǎn)擊相應(yīng)的按鈕時(shí)執(zhí)行:onclick={() =>推送(`/details/${entry.Id}`)}?;旧?,我們調(diào)用路由庫的 Push 方法將用戶帶到詳細(xì)信息視圖,但傳遞與相關(guān)項(xiàng)目相對(duì)應(yīng)的 Id 參數(shù)。
用戶可以執(zhí)行的另一個(gè)操作是從列表中刪除項(xiàng)目。如果他單擊相應(yīng)的按鈕,將顯示一個(gè)確認(rèn)彈出窗口,調(diào)用 showAlert 函數(shù)。該函數(shù)依次調(diào)用 showWarning,它實(shí)際上是 sweetalert2 庫的抽象層(調(diào)用 sweetalert2 庫的所有函數(shù)都在 frontend/src/lib/popups/popups.ts 中)。如果用戶確認(rèn)刪除操作,則調(diào)用DeleteEntry綁定(將其從數(shù)據(jù)庫中刪除),反過來,如果它返回的promise得到解析,則調(diào)用deleteItem(將其從存儲(chǔ)在條目狀態(tài)的數(shù)組中刪除) :
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
主頁視圖的另一個(gè)組件(BottomActions)要簡(jiǎn)單得多:它不接收 props 并且僅限于將用戶重定向到各種視圖(Settings、About 或 AddPassword)。
AddPassword 和 EditPassword 視圖的邏輯非常相似,也與 Login 視圖類似。兩者都不允許用戶在文本輸入的開頭和結(jié)尾輸入空格,并遵循與登錄視圖相同的策略,要求密碼長度至少為 6 個(gè) ASCII 字符?;旧希鼈兊呐c眾不同之處在于它們調(diào)用與需要執(zhí)行的操作相關(guān)的由 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
另一個(gè)有點(diǎn)復(fù)雜的視圖是“設(shè)置”。它有一個(gè)語言組件,它從其父組件(設(shè)置)接收 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 是一個(gè)處理用戶語言選擇的 select。在其 onchange 事件中,它接收一個(gè)函數(shù) (handleChange),該函數(shù)執(zhí)行 3 件事:
- 使用 svelte-i18n 庫設(shè)置前端語言
- 發(fā)出一個(gè)事件(“change_titles”),以便Wails運(yùn)行時(shí)更改應(yīng)用程序標(biāo)題欄的標(biāo)題以及相關(guān)的選擇目錄和選擇文件對(duì)話框的標(biāo)題到上一個(gè)操作
- 將用戶選擇的語言保存在數(shù)據(jù)庫中,以便下次啟動(dòng)應(yīng)用程序時(shí),它將打開配置為該語言的。
返回“設(shè)置”視圖,其整個(gè)操作由一系列發(fā)送到后端或從后端接收的事件控制。最簡(jiǎn)單的是退出按鈕:當(dāng)用戶單擊它時(shí),會(huì)在后端觸發(fā)并偵聽退出事件,然后應(yīng)用程序關(guān)閉(onclick={() => EventsEmit("quit")})。 提示 通知用戶 Escape 鍵(快捷鍵)執(zhí)行相同的操作,正如我們已經(jīng)解釋過的。
重置按鈕調(diào)用顯示彈出窗口的函數(shù):
... . ├── 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 ...
如果用戶接受該操作,則會(huì)調(diào)用 Drop 綁定,這會(huì)清除數(shù)據(jù)庫中的所有 collections,如果它返回的 Promise 得到解析,則會(huì)將用戶發(fā)送到 Login 視圖,顯示指示操作成功的模式。
剩下的另外兩個(gè)操作彼此類似,所以讓我們看看導(dǎo)入數(shù)據(jù)。
如果用戶點(diǎn)擊相應(yīng)的按鈕,則會(huì)發(fā)出一個(gè)事件(onclick={() => EventsEmit("import_data")}),該事件在后端監(jiān)聽。收到后,將打開本機(jī)選擇文件對(duì)話框以允許用戶選擇備份文件。如果用戶選擇文件,包含路徑(fileLocation)的變量將不包含空字符串,這將在后端觸發(fā)一個(gè)事件(“enter_password”),該事件現(xiàn)在在前端偵聽,然后顯示一個(gè)新的彈出窗口詢問導(dǎo)出時(shí)使用的主密碼。同樣,前端將發(fā)出另一個(gè)事件(“密碼”),其中攜帶用戶輸入的主密碼。當(dāng)后端接收到這個(gè)新事件時(shí),會(huì)執(zhí)行 Db 包的 ImportDump 方法,該方法執(zhí)行從用戶選擇的備份文件中讀取和恢復(fù) DB 中的數(shù)據(jù)的工作。結(jié)果,發(fā)出一個(gè)新事件(“imported_data”),該事件將其執(zhí)行結(jié)果(成功或不成功)作為附加數(shù)據(jù)攜帶。前端收到事件后只需執(zhí)行 2 個(gè)任務(wù):
- 如果結(jié)果成功,請(qǐng)?jiān)O(shè)置備份文件中保存的語言并顯示指示操作成功的模式
- 如果由于某種原因無法完成導(dǎo)入,請(qǐng)顯示錯(cuò)誤及其原因。
所有這些在代碼邏輯中比用文字解釋要容易得多?:
... . ├── 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 ...
值得一提的是,在前端注冊(cè)偵聽器的 Wails 運(yùn)行時(shí)函數(shù) (EventsOn) 返回一個(gè)函數(shù),該函數(shù)在調(diào)用時(shí)會(huì)取消所述偵聽器。當(dāng)組件被銷毀時(shí),取消所述監(jiān)聽器是很方便的。與 React 類似,onMount 鉤子可以通過讓監(jiān)聽器返回一個(gè)清理函數(shù)來“清理”它們,在這種情況下,該函數(shù)將調(diào)用 EventsOn 返回的所有函數(shù),我們已采取預(yù)防措施將其保存在單獨(dú)的文件中。變量:
// 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>;
為了完成對(duì)我們應(yīng)用程序前端部分的審查,只需介紹一下“關(guān)于”組件即可。這幾乎沒有邏輯,因?yàn)樗鼉H限于顯示有關(guān)應(yīng)用程序的信息,就像常見的 about 一樣。然而,應(yīng)該說,正如我們所看到的,該視圖顯示了指向應(yīng)用程序存儲(chǔ)庫的鏈接。顯然,在普通網(wǎng)頁中,錨標(biāo)記 () 將使我們導(dǎo)航到相應(yīng)的鏈接,但在桌面應(yīng)用程序中,如果 Wails 在運(yùn)行時(shí)沒有為此提供特定函數(shù) (BrowserOpenURL),則不會(huì)發(fā)生這種情況:
/* package.json */ ... }, "dependencies": { "svelte-copy": "^2.0.0", "svelte-i18n": "^4.0.1", "svelte-spa-router": "^4.0.1", "sweetalert2": "^11.14.5" } ...
這會(huì)將二進(jìn)制文件構(gòu)建到 build/bin 文件夾中。但是,要選擇其他構(gòu)建選項(xiàng)或執(zhí)行交叉編譯,您可能需要查看 Wails CLI 文檔。
對(duì)于這個(gè)應(yīng)用程序,我想我已經(jīng)在本系列的第一部分中提到過,我只關(guān)注Windows和Linux的編譯。為了以舒適的方式執(zhí)行這些任務(wù)(由于測(cè)試,這些任務(wù)是重復(fù)的),我創(chuàng)建了一些小腳本和一個(gè)“協(xié)調(diào)”它們的 Makefile。
make create-bundles 命令為 Linux 版本創(chuàng)建一個(gè) .tar.xz 壓縮文件,其中包含應(yīng)用程序和一個(gè)充當(dāng)安裝可執(zhí)行文件的“安裝程序”的 Makefile,一個(gè)用于在 開始菜單以及相應(yīng)的應(yīng)用程序圖標(biāo)。對(duì)于 Windows 版本,二進(jìn)制文件只是在名為 dist/ 的文件夾中壓縮為 .zip。但是,如果您更喜歡跨平臺(tái)自動(dòng)構(gòu)建,Wails 有一個(gè) Github Actions,允許您上傳(默認(rèn)選項(xiàng))生成的工件到您的存儲(chǔ)庫。
請(qǐng)注意,如果您在運(yùn)行時(shí)使用 make create-bundles 命令,它將調(diào)用 Wails 命令 wails build -clean -upx (對(duì)于 Linux)或 wails build -skipbindings -s -platform windows/amd64 - upx(對(duì)于 Windows)。 -upx 標(biāo)志是指使用您應(yīng)該安裝在計(jì)算機(jī)上的最后,請(qǐng)注意,構(gòu)建腳本會(huì)自動(dòng)將當(dāng)前存儲(chǔ)庫標(biāo)簽添加到“關(guān)于”視圖,并在構(gòu)建后將其值恢復(fù)為默認(rèn)值 (DEV_VERSION)。UPX 實(shí)用程序來壓縮二進(jìn)制文件??蓤?zhí)行文件體積小的部分秘密是由于該實(shí)用程序所做的出色的壓縮工作。
?。∵@兩篇文章比我想象的要長!但我希望您喜歡它們,最重要的是,它們可以幫助您思考新項(xiàng)目。在編程中學(xué)習(xí)一些東西就像這樣......
請(qǐng)記住,您可以在此 GitHub 存儲(chǔ)庫中找到所有應(yīng)用程序代碼。
我相信我會(huì)在其他帖子中見到你。編碼愉快?!!
以上是極簡(jiǎn)密碼管理器桌面應(yīng)用程序:進(jìn)軍 Golang 的 Wails 框架(第 2 部分)的詳細(xì)內(nèi)容。更多信息請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

熱AI工具

Undress AI Tool
免費(fèi)脫衣服圖片

Undresser.AI Undress
人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover
用于從照片中去除衣服的在線人工智能工具。

Clothoff.io
AI脫衣機(jī)

Video Face Swap
使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費(fèi)的代碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
功能強(qiáng)大的PHP集成開發(fā)環(huán)境

Dreamweaver CS6
視覺化網(wǎng)頁開發(fā)工具

SublimeText3 Mac版
神級(jí)代碼編輯軟件(SublimeText3)

Golangofferssuperiorperformance,nativeconcurrencyviagoroutines,andefficientresourceusage,makingitidealforhigh-traffic,low-latencyAPIs;2.Python,whileslowerduetointerpretationandtheGIL,provideseasierdevelopment,arichecosystem,andisbettersuitedforI/O-bo

Golang主要用于后端開發(fā),但也能在前端領(lǐng)域間接發(fā)揮作用。其設(shè)計(jì)目標(biāo)聚焦高性能、并發(fā)處理和系統(tǒng)級(jí)編程,適合構(gòu)建API服務(wù)器、微服務(wù)、分布式系統(tǒng)、數(shù)據(jù)庫操作及CLI工具等后端應(yīng)用。雖然Golang不是網(wǎng)頁前端的主流語言,但可通過GopherJS編譯成JavaScript、通過TinyGo運(yùn)行于WebAssembly,或搭配模板引擎生成HTML頁面來參與前端開發(fā)。然而,現(xiàn)代前端開發(fā)仍需依賴JavaScript/TypeScript及其生態(tài)。因此,Golang更適合以高性能后端為核心的技術(shù)棧選擇。

安裝Go的關(guān)鍵在于選擇正確版本、配置環(huán)境變量并驗(yàn)證安裝。1.前往官網(wǎng)下載對(duì)應(yīng)系統(tǒng)的安裝包,Windows使用.msi文件,macOS使用.pkg文件,Linux使用.tar.gz文件并解壓至/usr/local目錄;2.配置環(huán)境變量,在Linux/macOS中編輯~/.bashrc或~/.zshrc添加PATH和GOPATH,Windows則在系統(tǒng)屬性中設(shè)置PATH為Go的安裝路徑;3.使用goversion命令驗(yàn)證安裝,并運(yùn)行測(cè)試程序hello.go確認(rèn)編譯執(zhí)行正常。整個(gè)流程中PATH設(shè)置和環(huán)

要構(gòu)建一個(gè)GraphQLAPI在Go語言中,推薦使用gqlgen庫以提高開發(fā)效率。1.首先選擇合適的庫,如gqlgen,它支持根據(jù)schema自動(dòng)生成代碼;2.接著定義GraphQLschema,描述API的結(jié)構(gòu)和查詢?nèi)肟?,如定義Post類型和查詢方法;3.然后初始化項(xiàng)目并生成基礎(chǔ)代碼,實(shí)現(xiàn)resolver中的業(yè)務(wù)邏輯;4.最后將GraphQLhandler接入HTTPserver,通過內(nèi)置Playground測(cè)試API。注意事項(xiàng)包括字段命名規(guī)范、錯(cuò)誤處理、性能優(yōu)化及安全設(shè)置等,確保項(xiàng)目可維護(hù)性

Golang在構(gòu)建Web服務(wù)時(shí)CPU和內(nèi)存消耗通常低于Python。1.Golang的goroutine模型調(diào)度高效,并發(fā)請(qǐng)求處理能力強(qiáng),CPU使用率更低;2.Go編譯為原生代碼,運(yùn)行時(shí)不依賴虛擬機(jī),內(nèi)存占用更?。?.Python因GIL和解釋執(zhí)行機(jī)制,在并發(fā)場(chǎng)景下CPU和內(nèi)存開銷更大;4.雖然Python開發(fā)效率高、生態(tài)豐富,但資源消耗較高,適合并發(fā)要求不高的場(chǎng)景。

sync.WaitGroup用于等待一組goroutine完成任務(wù),其核心是通過Add、Done、Wait三個(gè)方法協(xié)同工作。1.Add(n)設(shè)置需等待的goroutine數(shù)量;2.Done()在每個(gè)goroutine結(jié)束時(shí)調(diào)用,計(jì)數(shù)減一;3.Wait()阻塞主協(xié)程直到所有任務(wù)完成。使用時(shí)需注意:Add應(yīng)在goroutine外調(diào)用、避免重復(fù)Wait、務(wù)必確保Done被調(diào)用,推薦配合defer使用。常見于并發(fā)抓取網(wǎng)頁、批量數(shù)據(jù)處理等場(chǎng)景,能有效控制并發(fā)流程。

使用Go的embed包可以方便地將靜態(tài)資源嵌入二進(jìn)制,適合Web服務(wù)打包HTML、CSS、圖片等文件。1.聲明嵌入資源需在變量前加//go:embed注釋,如嵌入單個(gè)文件hello.txt;2.可嵌入整個(gè)目錄如static/*,通過embed.FS實(shí)現(xiàn)多文件打包;3.開發(fā)時(shí)建議通過buildtag或環(huán)境變量切換磁盤加載模式以提高效率;4.注意路徑正確性、文件大小限制及嵌入資源的只讀特性。合理使用embed能簡(jiǎn)化部署并優(yōu)化項(xiàng)目結(jié)構(gòu)。

WhenchoosingbetweenGolangandPythonforcodereadabilityandmaintainability,thedecisionhingesonteampriorities.1.Golangoffersstrictconsistencywithminimal,opinionatedsyntaxandbuilt-intoolinglikegofmt,ensuringuniformcodestyleandearlyerrordetection.2.Pythonpr
