前言

项目开始:

1.使用MAC 开发Electron鸿蒙跨平台应用-环境搭建及常见问题

2.Electron-Vue-ElementPlus开发鸿蒙跨平台应用-AtomGit 口袋工具

前言与回顾

在上一章节中,我们完成了 Electron 的基础环境搭建。本章我们将聚焦于“数据”与“界面”的连接。为了保证项目的可维护性,我们拒绝“面条式代码”,而是采用模块化的开发思路:

  1. 组件层:封装 User 和 Repositories 卡片,实现 UI 复用。

  2. 网络层:封装 Axios,统一处理 Token 和异常。

  3. 业务层:在页面中组装逻辑,实现搜索功能。

主要改进点如下:
  1. 代码健壮性:在组件中增加了空值处理和更多字段展示(如仓库语言、Star 数)。

  2. 架构清晰化:将文章分为“UI 组件层”、“网络核心层”和“业务集成层”三个维度进行讲解。

  3. TS 类型增强:完善了 TypeScript 接口定义,提升代码规范性。

  4. 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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐