前言

环境搭建与准备工作:使用MAC 开发Electron鸿蒙跨平台应用-环境搭建及常见问题

在上一篇[《环境搭建与准备工作》]中,我们成功在 Mac 环境下配置了 Electron 与鸿蒙跨平台开发环境。本篇将正式进入功能开发阶段,基于 Vue 3 + Element Plus 技术栈,实现 AtomGit 开放 API 的核心搜索功能,涵盖用户鉴权、数据请求封装及 UI 交互实现。

前言与技术选型

功能开发是应用的核心。为了构建一个高效、美观且易于维护的搜索界面,我们采用了以下主流技术栈:

  • Vue 3 (Composition API): 利用 setup 语法糖,实现逻辑复用与更清晰的代码组织。

  • Element Plus: 也就是 Vue 3 版本的 Element UI,提供成熟的表单与列表组件,快速构建 UI。

  • Axios: 处理 HTTP 请求,负责与 AtomGit API 进行数据交互。

  • Day.js: 轻量级的时间处理库,用于将时间戳格式化为易读日期。

核心功能实现

我们将开发过程分为三步走:API 接口封装 -> UI 界面构建 -> 业务逻辑实现

1. API 接口层封装 (apis/search.ts)

为了保持代码的整洁和可维护性,我们将 HTTP 请求独立封装。这里定义了两个核心接口:搜索用户和搜索仓库。

功能开发

技术栈:Vue+Element-Plus+axios+dayjs

页面实现:

<template>
  <div class="page">
    <div class="warnLine">
      <el-text class="mx-1" type="primary"
        >输入关键字+access token,立即查询
        AtomGit用户或仓库(搜索接口需授权)。</el-text
      >
    </div>

    <div class="form">
      <el-form :model="form" label-width="auto" style="max-width: 600px">
        <el-form-item label="授权码">
          <el-input v-model="form.code" type="password" />
        </el-form-item>
        <el-form-item label="搜索">
          <el-select v-model="form.type" placeholder="选择搜索内容">
            <el-option label="用户" value="users" />
            <el-option label="仓库" value="repositories" />
          </el-select>
        </el-form-item>
        <el-form-item label="关键字">
          <el-input v-model="form.keyword" />
        </el-form-item>

        <el-form-item>
          <el-button type="primary" @click="onSubmit">搜索</el-button>
          <el-button @click="onReset">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="resultBox">
      <div class="resultItem" v-for="(item, index) in list" :key="index">
        <div class="avatar imgCover" v-if="item.avatar_url">
          <!-- <NuxtImg :src="item.avatar_url" :alt="item.name" /> -->
          <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>
    </div>
  </div>
</template>

<script lang="ts" setup>
import dayjs from "dayjs";
import { reactive } from "vue";
import { searchUsers, searchRepos } from "~/apis/search";
const form = reactive({
  code: "",
  keyword: "坚果",
  type: "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;
  }
};
const onReset = () => {
  form.code = "";
  form.keyword = "";
  form.type = "users";
};
</script>

<style lang="scss" scoped>
.page {
  .warnLine {
    margin: 30px;
  }
  .form {
    margin: 30px;
    background: #f5f5f5;
    border-radius: 10px;
    padding: 30px;
  }

  .resultBox {
    margin: 30px;
    .resultItem {
      margin-bottom: 30px;
      background-color: #f5f5f5;
      border-radius: 10px;
      font-size: 14px;
      display: flex;
      align-items: center;
      padding: 15px;
      box-sizing: border-box;
      .avatar {
        width: 80px;
        height: 80px;
        overflow: hidden;
        border-radius: 50%;
        overflow: hidden;
        background: #ccc;
        margin-right: 20px;
      }
      .info {
        flex: 1;
        .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>

使用dayjs对接口返回的数据进行格式化处理

请求代码:request.ts

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;
  }
}

API封装: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,
  });
}

运行效果如下:

功能介绍

1. 授权码必填,自行获取,个人令牌

2. 类型可选用户/仓库

3. 搜索:调用接口,返回数据展示,重置:清空表单内容

总结

通过 Electron 与 Vue 开发鸿蒙跨平台应用,并使用 AtomGit API 进行用户和仓库的搜索。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

赋能鸿蒙PC开发者,共建全场景原生生态,共享一次开发多端部署创新价值。

更多推荐