对招聘公司做评价的网站,济南润滑油网站制作,大鹏网站建设公司,访问国外的网站很慢由于多种原因#xff0c;Electron 曾经#xff08;并且仍然#xff09;大受欢迎。首先#xff0c;其跨平台功能使开发人员能够从单个代码库支持 Linux、Windows 和 macOS。最重要的是#xff0c;它对于熟悉 Javascript 的开发人员来说有一个精简的学习曲线。
尽管它有其缺…由于多种原因Electron 曾经并且仍然大受欢迎。首先其跨平台功能使开发人员能够从单个代码库支持 Linux、Windows 和 macOS。最重要的是它对于熟悉 Javascript 的开发人员来说有一个精简的学习曲线。
尽管它有其缺点其中应用程序大小和内存消耗最为突出但它为创建跨平台桌面应用程序提供了丰富的可能性。
然而自其发布以来许多替代品也加入了竞争。本文探讨了这样一种替代方案 - Wails该项目使得使用 Go 和 Web 技术例如 React 和 Vue编写桌面应用程序成为可能。Wails 的一个主要卖点是它不嵌入浏览器而是使用平台的本机渲染引擎。这使其成为Electron 的轻量级替代品。
为了熟悉 Wails您将构建一个 GitHub 桌面客户端它将与GitHub API交互并提供以下功能
查看公共存储库和要点查看经过身份验证的用户的私有存储库和要点为经过身份验证的用户创建一个新的要点。
后端将用 Go 编写前端将使用React和Vite 。UI 组件将使用Ant Design (AntD)创建。
怎么运行的 如前所述Wails 的工作原理是将用 Go 编写的后端与使用 Javascript 库/框架或使用 Vanilla HTML 和 Javascript 编写的前端相结合。即使您的函数和数据类型是在后端声明的Wails 也可以在前端调用它们。更重要的是当在后端声明一个结构体时Wails 能够生成一个TypeScript模型以在前端使用。其结果是前端和后端之间的无缝通信。您可以在此处阅读有关 Wails 如何工作的更多信息。
先决条件 要学习本教程您将需要以下内容
对Go和React的基本了解go1.19新项目管理Wails的最新安装
入门 通过运行以下命令创建一个新的 Wails 项目
wails init -n github_demo -t react这搭建了一个新项目后端使用 Go前端使用 React Vite。脚手架过程完成后通过运行以下命令导航到新创建的文件夹并运行项目。
cd github_demo
wails dev这将运行应用程序如下图所示。 关闭应用程序并在您喜欢的编辑器或 IDE 中打开项目目录开始向应用程序添加功能。
构建后端 添加 API 请求功能 应用程序首先需要具备向 GitHub API 发送 GET 和 POST 请求的能力。在应用程序的根目录中创建一个名为api.go的新文件。在此文件中添加以下代码。
package main
import (bytesfmtionet/http
)
func makeRequest(requestType, url, token string, payload []byte ) ([]byte, error){client : http.Client{}var request *http.Requestif payload ! nil {requestBody : bytes.NewReader(payload)request, _ http.NewRequest(requestType, url, requestBody)} else {request, _ http.NewRequest(requestType, url, nil)}request.Header.Set(Accept, application/vnd.githubjson)if token ! {request.Header.Set(Authorization, fmt.Sprintf(Bearer %s, token))}response, err : client.Do(request)if err ! nil {return nil, fmt.Errorf(request failed: %w, err)}body, _ : io.ReadAll(response.Body)return body, nil
}func MakeGetRequest(url string, token string) ([]byte, error) {return makeRequest(GET, url, token, nil)
}func MakePostRequest(url, token string, payload []byte) ([]byte, error){return makeRequest(POST, url, token, payload)
}
该makeRequest()函数在内部用于向指定的 URL 发出请求。除了指定 URL 之外请求类型、令牌和负载也会传递给该函数。使用这些可以准备请求并与函数返回的 API 响应一起发送。
和函数分别包裹该函数以MakeGetRequest()发送GET 和 POST 请求。MakePostRequest()makeRequest()
将辅助函数绑定到应用程序 有了 API 功能您可以声明一些将绑定到前端的辅助函数。这是通过为结构添加接收器函数来完成的App。
您可以在app.go末尾看到一个示例其中Greet()声明了一个名为 的接收器函数。
func (a *App) Greet(name string) string {return fmt.Sprintf(Hello %s, Its show time!, name)
}
现在将以下代码添加到app.go。
type APIResponse []interface{}
type Gist struct {Description string json:descriptionPublic bool json:publicFiles interface{} json:files
}const BaseUrl https://api.github.comvar githubResponse APIResponsefunc (a *App) GetPublicRepositories() (APIResponse, error) {url : fmt.Sprintf(%s/repositories, BaseUrl)response, err : MakeGetRequest(url, )if err ! nil {return nil, err}json.Unmarshal(response, githubResponse)return githubResponse, nil
}func (a *App) GetPublicGists() (APIResponse, error) {url : fmt.Sprintf(%s/gists/public, BaseUrl)response, err : MakeGetRequest(url, )if err ! nil {return nil, err}json.Unmarshal(response, githubResponse)return githubResponse, nil
}func (a *App) GetRepositoriesForAuthenticatedUser(token string) (APIResponse, error) {url : fmt.Sprintf(%s/user/repos?typeprivate, BaseUrl)response, err : MakeGetRequest(url, token)if err ! nil {return nil, err}json.Unmarshal(response, githubResponse)return githubResponse, nil
}func (a *App) GetGistsForAuthenticatedUser(token string) (APIResponse, error) {url : fmt.Sprintf(%s/gists, BaseUrl)response, err : MakeGetRequest(url, token)if err ! nil {return nil, err}json.Unmarshal(response, githubResponse)return githubResponse, nil
}func (a *App) GetMoreInformationFromURL(url, token string) (APIResponse, error) {response, err : MakeGetRequest(url, token)if err ! nil {return nil, err}json.Unmarshal(response, githubResponse)return githubResponse, nil
}func (a *App) GetGistContent(url, token string) (string, error) {githubResponse, err : MakeGetRequest(url, token)if err ! nil {return , err}return string(githubResponse), nil
}func (a *App) CreateNewGist(gist Gist, token string) (interface{}, error) {var githubResponse interface{}requestBody, _ : json.Marshal(gist)url : fmt.Sprintf(%s/gists, BaseUrl)response, err : MakePostRequest(url, token, requestBody)if err ! nil {return nil, err}json.Unmarshal(response, githubResponse)return githubResponse, nil
}
然后如果您的文本编辑器或 IDE 没有自动为您执行此操作请将“encoding/json”添加到文件顶部的导入列表中。
除了现有代码之外它还声明了两种新类型APIResponse和Gist。这些将分别用于对来自 API 的响应和 Gist 的结构进行建模。接下来它声明该App结构的接收器函数
该GetPublicRepositories()函数通过 GET 请求从 GitHub API 检索公共存储库列表。由于此路由不需要身份验证因此将传递一个空字符串作为令牌。 该GetPublicGists()函数通过 GET 请求从 GitHub API 检索公共要点列表。也不需要身份验证因此将空字符串作为令牌传递。 该GetRepositoriesForAuthenticatedUser()函数用于获取经过身份验证的用户的私有存储库的列表。该函数将令牌作为参数。 该GetGistsForAuthenticatedUser()函数用于检索经过身份验证的用户的要点。该函数还采用令牌作为参数。 该GetMoreInformationFromURL()函数用于获取有关存储库的更多信息。此信息可以是提交历史记录、贡献者列表或已为存储库添加星标的用户列表。它需要两个参数即要调用的 url 和身份验证令牌。对于公共存储库令牌将为空字符串。 该GetGistContent()函数用于获取 Gist 的内容。该函数采用 Gist 原始内容的 URL 和身份验证令牌公共 Gists 为空字符串。它返回与 Gist 内容相对应的字符串。 该CreateNewGist()函数用于为经过身份验证的用户创建新的要点。该函数采用两个参数即要创建的要点以及用户的身份验证令牌。
构建前端 前端的所有代码都存储在frontend文件夹中。但在编写任何代码之前请使用以下命令添加 JavaScript 依赖项。
cd frontend
npm install antd ant-design/icons react-router-dom prismjs
依赖关系如下
Ant Design - 这可以帮助设计师/开发人员轻松构建美观且灵活的产品 Ant-design 图标- 这使您可以访问 AntD 的 SVG 图标集 React-router - 这将用于实现客户端路由 Prismjs - 这将用于实现 Gists 的语法突出显示
接下来在frontend/src文件夹中创建一个名为Components的文件夹。
添加身份验证 为了进行身份验证用户需要提供 GitHub个人访问令牌。该令牌包含在对需要身份验证的端点的请求标头中。如果您没有请创建一个 - 但是您必须为您的令牌设置以下权限才能用于此项目。 对于此项目React Context API将用于存储令牌一小时之后用户必须再次提供令牌来重新进行身份验证。
在frontend/src/components文件夹中创建一个名为context的新文件夹。在该文件夹中创建一个名为AuthModal.jsx的新文件并向其中添加以下代码。
import {Form, Input, Modal} from antd;
import {EyeInvisibleOutlined, EyeTwoTone} from ant-design/icons;const AuthModal ({shouldShowModal, onSubmit, onCancel}) {const [form] Form.useForm();const onFormSubmit () {form.validateFields().then((values) {onSubmit(values.token);});};return (ModaltitleProvide Github Authentication TokencenteredokTextSavecancelTextCancelopen{shouldShowModal}onOk{onFormSubmit}onCancel{onCancel}Formform{form}nameauth_forminitialValues{{token: ,}}Form.ItemnametokenlabelTokenrules{[{required: true, message: Please provide your Github Token!,},]}Input.PasswordplaceholderGithub TokeniconRender{(visible) visible ? EyeTwoTone/ : EyeInvisibleOutlined/}//Form.Item/Form/Modal);
};export default AuthModal;
该组件呈现身份验证表单。该表单有一个字段供用户粘贴和保存令牌。propshouldShowModal用于有条件地渲染表单而onSubmit和onCancelprop 用于响应用户的操作。
接下来再次在context文件夹中创建一个名为AuthContext.jsx的新文件并向其中添加以下代码。
import {Button, Result} from antd;
import React, {createContext, useContext, useEffect, useState} from react;
import AuthModal from ./AuthModal;
import {useNavigate} from react-router-dom;const AuthContext createContext({});const AuthContextProvider ({children}) {const [token, setToken] useState(null);const [shouldShowModal, setShouldShowModal] useState(true);const navigate useNavigate();useEffect(() {const timer setTimeout(() {if (token ! null) {setToken(null);setShouldShowModal(true);}}, 3600000);return () clearTimeout(timer);}, [token]);const onSubmit (token) {setToken(token);setShouldShowModal(false);};const onCancel () {setShouldShowModal(false);};if (!shouldShowModal !token) {return (ResultstatuserrortitleAuthentication FailedsubTitleA Github token is required to view this pageextra{[ButtontypelinkkeyhomeonClick{() {navigate(/);}}Public Section/Button,ButtonkeyretrytypeprimaryonClick{() {setShouldShowModal(true);}}Try Again/Button,]}/);}return ({shouldShowModal (AuthModalshouldShowModal{shouldShowModal}onSubmit{onSubmit}onCancel{onCancel}/)}AuthContext.Provider value{{token}}{children}/AuthContext.Provider/);
};export const useAuthContext () {const context useContext(AuthContext);if (context undefined) {throw new Error(useAuthContext must be used within a AuthContextProvider);}return context;
};export default AuthContextProvider;
exports这个文件里有两个。第一个是useAuthContext钩子。该钩子将用于检索保存在 中的令牌Context。第二个是AuthContextProvider组件。该组件负责呈现身份验证表单在页面加载时或令牌在 1 小时后“过期”时。
如果用户单击身份验证表单上的“取消”它还会呈现错误页面。该组件采用 JSX 元素名为children作为 prop并用上下文提供程序将其包装起来 — 从而使子元素能够访问令牌的值。
添加主从布局 为了显示存储库和要点将使用主从布局。将呈现项目列表单击其中一项将在列表旁边显示有关所选项目的更多信息。
在Components文件夹中创建一个名为ListItem.jsx的新文件并向其中添加以下代码。
import { useEffect, useState } from react;
import { Avatar, Card, Skeleton } from antd;const ListItem ({ item, onSelect, selectedItem, title }) {const [loading, setLoading] useState(true);const [gridStyle, setGridStyle] useState({margin: 3%,width: 94%,});useEffect(() {const isSelected selectedItem?.id item.id;setGridStyle({margin: 3%,width: 94%,...(isSelected { backgroundColor: lightblue }),});}, [selectedItem]);const onClickHandler () {onSelect(item);};useEffect(() {setTimeout(() {setLoading(false);}, 3000);}, []);return (Card.Grid hoverable{true} style{gridStyle} onClick{onClickHandler}Skeleton loading{loading} avatar activeCard.Metaavatar{Avatar src{item.owner.avatar_url} /}title{title}description{Authored by ${item.owner.login}}//Skeleton/Card.Grid);
};export default ListItem;
该组件使用 AntD Card组件呈现列表中的单个项目。卡片的标题作为组件道具提供。除了标题之外该组件还接收其他三个属性
该onSelect道具用于通知父项该卡已被点击 item对应于将在卡上呈现的要点或存储库 selectedItem组件使用它来确定用户是否单击了呈现的项目在这种情况下浅蓝色背景将添加到卡片样式中。 接下来在组件文件夹中创建一个名为MasterDetail.jsx的新文件并向其中添加以下代码。
import {useState} from react;
import {Affix, Card, Col, Row, Typography} from antd;
import ListItem from ./ListItem;const MasterDetail ({title, items, getItemDescription, detailLayout}) {const [selectedItem, setSelectedItem] useState(null);return (Row justifycenterColTypography.Title level{3}{title}/Typography.Title/Col/RowRowCol span{6}Affix offsetTop{20}dividscrollableDivstyle{{height: 80vh, overflow: auto, padding: 0 5px,}}Card bordered{false} style{{boxShadow: none}}{items.map((item, index) (ListItemkey{index}item{item}onSelect{setSelectedItem}selectedItem{selectedItem}title{getItemDescription(item)}/))}/Card/div/Affix/ColCol span{18}{selectedItem detailLayout(selectedItem)}/Col/Row/);
};export default MasterDetail;
该组件负责呈现一列中的项目列表以及另一列中所选项目的详细信息。要渲染的项目作为组件的道具提供。
除此之外getItemDescription()prop 是一个获取用户头像下显示内容的函数这是存储库名称或要点描述。
propdetailLayout()是父组件提供的函数它根据提供的项目返回详细信息部分的 JSX 内容。这允许 Gists 和存储库在使用相同的子组件进行渲染时具有完全不同的布局。
添加存储库相关组件 接下来在组件文件夹中创建一个名为Repository的新文件夹来保存与存储库相关的组件。然后创建一个名为RepositoryDetails.jsx的新文件并向其中添加以下代码。
import {useEffect, useState} from react;
import {Avatar, Card, Divider, List, Spin, Timeline, Typography} from antd;
import {GetMoreInformationFromURL} from ../../../wailsjs/go/main/App;const UserGrid ({users}) (Listgrid{{gutter: 16, column: 4}}dataSource{users}renderItem{(item, index) (List.Item key{index} style{{marginTop: 5px}}Card.Metaavatar{Avatar src{item.avatar_url}/}title{item.login}//List.Item)}
/);const RepositoryDetails ({repository, token }) {const [commits, setCommits] useState([]);const [contributors, setContributors] useState([]);const [stargazers, setStargazers] useState([]);const [isLoading, setIsLoading] useState(true);useEffect(() {const getRepositoryDetails async () {setIsLoading(true);const stargazers await GetMoreInformationFromURL(repository.stargazers_url, token);const commits await GetMoreInformationFromURL(repository.commits_url.replace(/{\/[a-z]*}/, ), token);const contributors await GetMoreInformationFromURL(repository.contributors_url, token);setCommits(commits);setContributors(contributors);setStargazers(stargazers);setIsLoading(false);};getRepositoryDetails();}, [repository]);return (Cardtitle{repository.name}bordered{false}style{{margin: 1%,}}{repository.description}Divider/Spin tipLoading spinning{isLoading}Typography.Title level{5} style{{margin: 10}}Contributors/Typography.TitleUserGrid users{contributors}/Divider/Typography.Title level{5} style{{marginBottom: 15}}Stargazers/Typography.TitleUserGrid users{stargazers}/Divider/Typography.Title level{5} style{{marginBottom: 15}}Commits/Typography.TitleTimeline modealternate{commits.map((commit, index) (Timeline.Item key{index}{commit.commit?.message}/Timeline.Item))}/Timeline/Spin/Card);
};export default RepositoryDetails;
接下来创建用于渲染公共存储库的组件。在Components/Repository文件夹中创建一个名为PublicRepositories.jsx的新文件并向其中添加以下代码。
import {useEffect, useState} from react;
import {GetPublicRepositories} from ../../../wailsjs/go/main/App;
import RepositoryDetails from ./RepositoryDetails;
import MasterDetail from ../MasterDetail;
import {message} from antd;const PublicRepositories () {const [repositories, setRepositories] useState([]);const [messageApi, contextHolder] message.useMessage();useEffect(() {const getRepositories async () {GetPublicRepositories().then((repositories) {setRepositories(repositories);}).catch((error) {messageApi.open({type: error, content: error,});});};getRepositories();}, []);const title Public Repositories;const getItemDescription (repository) repository.name;const detailLayout (repository) (RepositoryDetails repository{repository}/);return ({contextHolder}MasterDetailtitle{title}items{repositories}getItemDescription{getItemDescription}detailLayout{detailLayout}//);
};export default PublicRepositories;
该组件进行调用以从 GitHub API 检索公共存储库。它使用app.goGetPublicRepositories()中声明的函数来执行此操作该函数由 Wails 自动绑定到前端。
以这种方式导出的函数是异步的并返回Promise。使用MasterDetail和RepositoryDetails组件将相应地呈现返回的响应。
接下来在Repository文件夹中创建另一个名为PrivateRepositories.jsx的文件并向其中添加以下代码。
import { useEffect, useState } from react;
import { useAuthContext } from ../context/AuthContext;
import { GetRepositoriesForAuthenticatedUser } from ../../../wailsjs/go/main/App;
import RepositoryDetails from ./RepositoryDetails;
import MasterDetail from ../MasterDetail;
import { message } from antd;const PrivateRepositories () {const { token } useAuthContext();const [repositories, setRepositories] useState([]);const [messageApi, contextHolder] message.useMessage();useEffect(() {const getRepositories async () {if (token) {GetRepositoriesForAuthenticatedUser(token).then((repositories) {setRepositories(repositories);}).catch((error) {messageApi.open({type: error,content: error,});});}};getRepositories();}, [token]);const title Private Repositories;const getItemDescription (repository) repository.name;const detailLayout (repository) (RepositoryDetails repository{repository} token{token}/);return ({contextHolder}MasterDetailtitle{title}items{repositories}getItemDescription{getItemDescription}detailLayout{detailLayout}//);
};export default PrivateRepositories;
该组件与 组件非常相似PublicRepositories但有两个关键点。首先该组件将用 包装AuthContextProvider这使得可以通过useAuthContext钩子检索保存的令牌。其次它使用另一个绑定函数GetRepositoriesForAuthenticatedUser()来获取提供令牌的用户的存储库。
添加Gist相关组件 接下来在组件文件夹中创建一个名为Gist的新文件夹来保存与 Gist 相关的组件。然后在该新文件夹中创建一个名为GistDetails.jsx的新文件并向其中添加以下代码。
import { Carousel, Col, Row, Spin, Typography } from antd;
import React, { useEffect, useState } from react;
import prismjs/themes/prism-okaidia.min.css;
import Prism from prismjs;
import { GetGistContent } from ../../../wailsjs/go/main/App;const GistDetails ({ gist }) {const [snippets, setSnippets] useState([]);const [isLoading, setIsLoading] useState(true);useEffect(() {Prism.highlightAll();}, [snippets]);useEffect(() {const getSnippets async () {setIsLoading(true);const snippets await Promise.all(Object.values(gist.files).map(async (file) {const fileContent await GetGistContent(file.raw_url, );return {language: file.language?.toLowerCase() || text,content: fileContent,};}));setSnippets(snippets);setIsLoading(false);};getSnippets();}, [gist]);return (Spin tipLoading spinning{isLoading}Row justifycenterCol{gist.description (Typography.Text strong{gist.description}/Typography.Text)}/Col/RowdivCarouselautoplaystyle{{ backgroundColor: #272822, height: 100% }}{snippets.map((snippet, index) (pre key{index}code className{language-${snippet.language}}{snippet.content}/code/pre))}/Carousel/div/Spin);
};export default GistDetails;
该组件呈现文件中给定要点的代码。每个 Gist 响应都带有一个files密钥。这是一个包含 Gist 所有文件的对象。每个文件对象都包含文件原始内容的 URL 以及与文件关联的语言。该组件使用该函数检索所有文件GetGistContent()并将它们呈现在轮播中。Prism用于呈现 IDE 中的代码。
接下来在 Gist 文件夹中创建一个名为PublicGists.jsx的文件并向其中添加以下代码。
import { useEffect, useState } from react;
import GistDetails from ./GistDetails;
import { GetPublicGists } from ../../../wailsjs/go/main/App;
import MasterDetail from ../MasterDetail;
import { message } from antd;const PublicGists () {const [gists, setGists] useState([]);const [messageApi, contextHolder] message.useMessage();useEffect(() {const getGists async () {GetPublicGists().then((gists) {setGists(gists);}).catch((error) {messageApi.open({type: error,content: error,});});};getGists();}, []);const title Public Gists;const getItemDescription (gist) gist.description || No description provided;const detailLayout (gist) GistDetails gist{gist} /;return ({contextHolder}MasterDetailtitle{title}items{gists}getItemDescription{getItemDescription}detailLayout{detailLayout}//);
};export default PublicGists;
正如公共存储库的渲染一样app.goGetPublicGists()中声明的函数用于从 Github API 检索公共 Gist 并将其传递给组件以及获取 Gist 描述和显示有关该 Gist 的更多信息的函数。选择时要点。MasterDetail
接下来在Gist文件夹中创建一个名为PrivateGists.jsx的新文件并向其中添加以下代码。
import { useEffect, useState } from react;
import { useAuthContext } from ../context/AuthContext;
import { GetGistsForAuthenticatedUser } from ../../../wailsjs/go/main/App;
import MasterDetail from ../MasterDetail;
import GistDetails from ./GistDetails;
import { message } from antd;const PrivateGists () {const [gists, setGists] useState([]);const { token } useAuthContext();const [messageApi, contextHolder] message.useMessage();useEffect(() {const getGists async () {if (token) {GetGistsForAuthenticatedUser(token).then((gists) {setGists(gists);}).catch((error) {messageApi.open({type: error,content: error,});});}};getGists();}, [token]);const title Private Gists;const getItemDescription (gist) gist.description || No description provided;const detailLayout (gist) GistDetails gist{gist} /;return ({contextHolder}MasterDetailtitle{title}items{gists}getItemDescription{getItemDescription}detailLayout{detailLayout}//);
};
export default PrivateGists;
该组件将用一个AuthContextProvider组件包装从而使其能够访问所提供的令牌。使用令牌通过函数对 GitHub API 进行异步调用GetGistsForAuthenticatedUser()。然后将结果MasterDetail与其他所需的 props 一起传递给组件以进行适当的渲染。
最后要构建的 Gist 相关组件是创建新 Gist 的表单。为此请在Gist文件夹中创建一个名为CreateGist.jsx的新文件并向其中添加以下代码。
import { useAuthContext } from ../context/AuthContext;
import { Button, Card, Divider, Form, Input, message, Switch } from antd;
import { DeleteTwoTone, PlusOutlined } from ant-design/icons;
import { CreateNewGist } from ../../../wailsjs/go/main/App;
import { useNavigate } from react-router-dom;const CreateGist () {const { token } useAuthContext();const [messageApi, contextHolder] message.useMessage();const navigate useNavigate();const onFinish async (values) {const { description, files, isPublic } values;const gist {description,public: !!isPublic,files: files.reduce((accumulator, { filename, content }) Object.assign(accumulator, {[filename]: { content },}),{}),};CreateNewGist(gist, token).then((gist) {messageApi.open({type: success,content: Gist ${gist.id} created successfully,});navigate(/gists/private);}).catch((error) {messageApi.open({type: error,content: error,});});};const onFinishFailed (errorInfo) {console.log(Failed:, errorInfo);};return ({contextHolder}Card titleCreate a new GistFormnamegistonFinish{onFinish}onFinishFailed{onFinishFailed}autoCompleteoffForm.Item namedescriptionInput placeholderGist description... //Form.ItemForm.ItemlabelMake gist publicvaluePropNamecheckednameisPublicSwitch //Form.ItemForm.Listnamefilesrules{[{validator: async (_, files) {if (!files || files.length 1) {return Promise.reject(new Error(At least 1 file is required to create a Gist));}},},]}{(fields, { add, remove }, { errors }) ({fields.map((field) (div key{field.key}Form.ItemshouldUpdate{(prevValues, curValues) prevValues.area ! curValues.area ||prevValues.sights ! curValues.sights}{() (divDivider /Form.Item{...field}name{[field.name, filename]}rules{[{required: true,message: Missing filename,},]}noStyleInputplaceholderFilename including extension...style{{ width: 90%, marginRight: 5px }}//Form.ItemDeleteTwoTonestyle{{fontSize: 30px,verticalAlign: middle,}}twoToneColor#eb2f96onClick{() remove(field.name)}//div)}/Form.ItemForm.Item{...field}name{[field.name, content]}rules{[{required: true,message: Missing content,},]}Input.TextArea rows{20} placeholderGist content //Form.Item/div))}Form.ItemwrapperCol{{offset: 10,}}ButtontypedashedonClick{() add()}icon{PlusOutlined /}Add file/ButtonForm.ErrorList errors{errors} //Form.Item/)}/Form.ListForm.ItemwrapperCol{{offset: 10,}}Button typeprimary htmlTypesubmitSubmit/Button/Form.Item/Form/Card/);
};export default CreateGist;
创建新 Gist 的请求包含三个字段
description如果提供的话这将描述要点中的代码旨在实现的目标。该字段是可选的并在表单中由输入字段表示 public这是必填字段决定 Gist 是否具有公共访问权限。在您创建的表单中这由默认设置为关闭的开关表示。这意味着除非用户另有指定否则创建的要点将是秘密的并且仅对拥有其链接的用户可用。 files这是另一个必填字段。它是一个对象对于对象中的每个条目键是文件的名称包括扩展名值是文件的内容。 这以您创建的动态列表的形式表示其中每个列表项都包含文件名的文本字段和文件内容的文本区域。通过单击“添加文件”按钮您可以添加多个文件。您还可以删除文件。请注意您将需要至少有一个文件如果没有将显示一条错误消息。 当表单正确填写并提交后该onFinish()函数用于创建一个符合app.goGist中声明的结构的对象并调用接收器函数。CreateNewGist()
因为该组件是用 包装的AuthContextProvider所以可以根据函数的需要检索保存的令牌并与 Gist 一起传递。收到成功响应后应用程序将重定向到经过身份验证的用户的要点列表。
将各个部分放在一起 添加导航 所有单独的组件就位后接下来要添加的是导航 - 用户可以在应用程序中移动的一种方式。要添加此内容请在组件文件夹中创建一个名为NavBar.jsx的新文件并向其中添加以下代码。
import { LockOutlined, UnlockOutlined } from ant-design/icons;
import { Layout, Menu } from antd;
import { Link } from react-router-dom;
import logo from ../assets/images/logo-universal.png;function getItem(label, key, icon, children, type) {return {key,icon,children,label,type,};
}
const items [getItem(Public Actions, sub1, UnlockOutlined /, [getItem(Repositories,g1,null,[getItem(Link to{repositories/public}View all repositories/Link,1),],group),getItem(Gists,g2,null,[getItem(Link to{gists/public}View all gists/Link, 3)],group),]),getItem(Private Actions, sub2, LockOutlined /, [getItem(Repositories,g3,null,[getItem(Link to{repositories/private}View my repositories/Link,5),],group),getItem(Gists,g4,null,[getItem(Link to{gists/private}View my gists/Link, 6),getItem(Link to{gist/new}Create new gist/Link, 7),],group),]),
];const NavBar () {return (Layout.Header themelight style{{ background: white }}divclassNamelogostyle{{float: left,marginRight: 200px,padding: 1%,}}Link to/img src{logo} style{{ width: 50px }} //Link/divMenudefaultSelectedKeys{[1]}modehorizontalitems{items}style{{position: relative,}}//Layout.Header);
};export default NavBar;
该组件在窗口顶部呈现一个导航栏其中包含两个主要项目 - Public Actions和Private Actions。然后每个项目都有子项目这些子项目是最终将呈现与子项目关联的组件的链接。完成此操作后您可以将路由添加到您的应用程序中。
添加路由 在frontend/src文件夹中创建一个名为routes.jsx的新文件并向其中添加以下代码。
import App from ./App;import CreateGist from ./components/Gist/CreateGist;
import PrivateGists from ./components/Gist/PrivateGists;
import PublicGists from ./components/Gist/PublicGists;import PrivateRepositories from ./components/Repository/PrivateRepositories;
import PublicRepositories from ./components/Repository/PublicRepositories;
import AuthContextProvider from ./components/context/AuthContext;const routes [{path: /,element: App /,children: [{ index: true, element: PublicRepositories / },{path: repositories/public,element: PublicRepositories /,},{path: gists/public,element: PublicGists /,},{path: gist/new,element: (AuthContextProviderCreateGist //AuthContextProvider),},{path: repositories/private,element: (AuthContextProviderPrivateRepositories //AuthContextProvider),},{path: gists/private,element: (AuthContextProviderPrivateGists //AuthContextProvider),},],},
];export default routes;
在这里您指定了应用程序中的路由以及要为每个路径呈现的组件。除此之外您还包装了需要用户为组件提供令牌的组件AuthContextProvider。
接下来打开App.jsx并更新文件的代码以匹配以下内容。
import NavBar from ./components/NavBar;
import { FloatButton, Layout } from antd;
import { Outlet } from react-router-dom;const { Content } Layout;const App () {return (Layoutstyle{{minHeight: 100vh,}}NavBar /Layout classNamesite-layoutContentstyle{{background: white,padding: 0 50px,}}divstyle{{padding: 24,}}Outlet /FloatButton.BackTop //div/Content/Layout/Layout);
};export default App;
在这里您已经包含了NavBar之前声明的组件。您还声明了一个Outlet由 提供的组件react-router-dom来渲染子路由元素。
最后更新main.jsx中的代码以匹配以下内容。
import React from react
import {createRoot} from react-dom/client
import { createHashRouter, RouterProvider } from react-router-dom
import routes from ./routesconst container document.getElementById(root)const root createRoot(container)const router createHashRouter(routes, {basename:/})root.render(React.StrictModeRouterProvider router{router}//React.StrictMode
)
HashRouter是官方推荐的路由方法。这是通过createHashRouter()函数创建的。使用routes您之前声明的对象所有路由器对象都会传递到此组件以呈现您的应用程序并启用其余 API。完成此操作后您的应用程序将在加载后呈现索引页面。
测试应用程序是否有效 您已经使用Wails成功构建了您的第一个应用程序。再次运行应用程序并通过从项目的顶级文件夹运行以下命令来试用它。
wails dev
默认情况下当应用程序加载时您将看到一个公共存储库列表。使用导航菜单您可以通过单击相应的菜单项来查看公共和私有存储库和要点。
当您选择私有存储库或私有 Gist 的菜单项时将显示一个弹出窗口询问您的 GitHub 令牌如下所示。 粘贴您的个人访问令牌 (PAT) 并单击“保存”。然后将呈现您的存储库或 Gists视情况而定。您将能够在应用程序的私人部分中导航而无需在几分钟内重新输入令牌。
这就是如何使用 Go 和 Wails 构建跨平台桌面应用程序