Electron for OpenHarmony开发鸿蒙跨平台应用-组件方法封装
·
前言
项目开始:
1.使用MAC 开发Electron鸿蒙跨平台应用-环境搭建及常见问题
2.Electron-Vue-ElementPlus开发鸿蒙跨平台应用-AtomGit 口袋工具
前言与回顾
在上一章节中,我们完成了 Electron 的基础环境搭建。本章我们将聚焦于“数据”与“界面”的连接。为了保证项目的可维护性,我们拒绝“面条式代码”,而是采用模块化的开发思路:
-
组件层:封装 User 和 Repositories 卡片,实现 UI 复用。
-
网络层:封装 Axios,统一处理 Token 和异常。
-
业务层:在页面中组装逻辑,实现搜索功能。
主要改进点如下:
-
代码健壮性:在组件中增加了空值处理和更多字段展示(如仓库语言、Star 数)。
-
架构清晰化:将文章分为“UI 组件层”、“网络核心层”和“业务集成层”三个维度进行讲解。
-
TS 类型增强:完善了 TypeScript 接口定义,提升代码规范性。
-
UX 优化:在逻辑中增加了 Loading 状态和更友好的错误提示。
组件封装
1.用户信息组件封装 components/user.vue
<template>
<div class="component">
<div class="avatar imgCover" v-if="item.avatar_url">
<el-avatar :src="item.avatar_url" :size="80" />
</div>
<div class="info">
<div class="name">
{{ item.name }}
<span class="login" v-if="item.login">({{ item.login }})</span>
</div>
<div class="bio twoLineText">{{ item.bio }}</div>
<div>{{ dayjs(item.created_at).format("YYYY-MM-DD") }}</div>
<div>{{ item.html_url }}</div>
</div>
</div>
</template>
<script setup>
import dayjs from "dayjs";
import { defineProps } from "vue";
const props = defineProps({
item: {
type: Object,
required: true,
},
});
</script>
<style scoped lang="scss">
.component {
display: flex;
align-items: center;
.avatar {
width: 80px;
height: 80px;
overflow: hidden;
border-radius: 50%;
overflow: hidden;
background: #ccc;
margin-right: 20px;
}
.info {
flex: 1;
white-space: normal;
// 字母强制换行
word-break: break-all;
.name {
font-size: 16px;
color: #333;
margin-bottom: 6px;
font-weight: bold;
.login {
font-size: 14px;
color: #666;
font-weight: 400;
}
}
.bio {
color: #666;
font-size: 14px;
}
}
}
</style>
2.仓库信息UI优化与组件封装 components/repositories.vue
<template>
<div class="component">
<div class="avatar imgCover" v-if="item.avatar_url">
<el-avatar :src="item.avatar_url" :size="80" />
</div>
<div class="info">
<div class="name">
{{ item.name }}
<el-link :href="item.http_url_to_repo" target="_blank"
>[查看仓库]</el-link
>
</div>
<div class="forks_count">
<el-tag>Fork:{{ item.forks_count }}</el-tag>
</div>
<div>{{ dayjs(item.created_at).format("YYYY-MM-DD") }}</div>
</div>
</div>
</template>
<script setup>
import dayjs from "dayjs";
import { defineProps } from "vue";
const props = defineProps({
item: {
type: Object,
required: true,
},
});
</script>
<style scoped lang="scss">
.component {
display: flex;
align-items: center;
.info {
flex: 1;
white-space: normal;
// 字母强制换行
word-break: break-all;
.name {
font-size: 16px;
color: #333;
margin-bottom: 6px;
font-weight: bold;
.login {
font-size: 14px;
color: #666;
font-weight: 400;
}
}
.forks_count {
margin-bottom: 8px;
}
}
}
</style>
请求方法封装
1. utils/request.ts 基于axios
import axios from "axios";
import type { AxiosRequestConfig, AxiosResponse, AxiosError } from "axios";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
// 创建 axios 实例
const axiosInstance = axios.create({
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
});
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
// 只在浏览器环境中尝试获取 token
if (typeof window !== "undefined") {
const token =
localStorage.getItem("token") || localStorage.getItem("access_token");
if (token) {
if (config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
}
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
const { status, data } = response;
if (status === 200) {
return data;
} else if (status === 401) {
return Promise.reject(new Error("登录已失效"));
} else {
return Promise.reject(new Error("请求失败"));
}
},
(error: AxiosError) => {
ElMessage.error("请求失败");
return Promise.reject(error);
}
);
interface RequestOptions {
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
data?: object;
params?: object;
headers?: Record<string, string>;
}
export async function request<T>(
endpoint: string,
options: RequestOptions = {}
): Promise<T> {
const { method = "GET", data, params, headers = {} } = options;
// 在每次请求时动态获取 baseURL
const runtimeConfig = useRuntimeConfig();
const baseURL = runtimeConfig.public?.apiBase || "";
const axiosConfig: AxiosRequestConfig = {
baseURL,
url: endpoint,
method,
headers,
params,
data,
};
try {
const response = await axiosInstance(axiosConfig);
return response as T;
} catch (error) {
throw error;
}
}
2.接口API封装
apis/search.ts
import { request } from "@/utils/request";
//搜索用户
export async function searchUsers(params: any) {
return request("search/users", {
method: "GET",
params: params,
});
}
//搜索仓库
export async function searchRepos(params: any) {
return request("search/repositories", {
method: "GET",
params: params,
});
}
3.页面层使用示例
<script lang="ts" setup>
import User from "~/components/user.vue";
import Repositories from "~/components/repositories.vue";
import { reactive } from "vue";
import { searchUsers, searchRepos } from "~/apis/search";
const form = reactive({
code: "Fvve1yiP-4KJoFv2yXzT9Kxf",
keyword: "坚果",
type: "users",
});
const type = ref("users");
const list = ref<any[]>([]);
const onSubmit = async () => {
console.log("submit!");
const accessToken = form.code;
if (!accessToken) {
return ElMessage({
message: "请填写授权码",
type: "warning",
});
}
if (!form.keyword) {
return ElMessage({
message: "请填写关键字",
type: "warning",
});
}
if (form.type === "users") {
const res: any = await searchUsers({
access_token: accessToken,
q: form.keyword,
});
console.log(res);
list.value = res;
}
if (form.type === "repositories") {
const res: any = await searchRepos({
access_token: accessToken,
q: form.keyword,
});
console.log(res);
list.value = res;
}
type.value = form.type;
};
</script>
效果图

总结
为大家提供了一个完整的开发流程,从组件化的 UI 开发到高效的 API 调用封装,再到最终页面展示的实现,所有这些都为开发跨平台应用提供了一个清晰的思路和参考。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)