当前为游客模式。公开章节: 00, 01 。关注「公众号」后可解锁: 03, 04 。其余章节仍需登录。
目录导航

02 搭建前端骨架

这篇目标:创建一个能跑、能请求后端的 Vue3 工程。

1. 环境准备

  1. Node.js 20+
  2. pnpm

2. 创建 Vue3 项目

pnpm create vue@latest data-agent-frontend

推荐选项:

  1. TypeScript: yes
  2. Router: 可选
  3. Pinia: 可选
  4. ESLint/Prettier: 按团队习惯

3. 安装依赖并启动

cd data-agent-frontend
pnpm install
pnpm add axios fs-extra adm-zip uuid

4. 先在后端补一个 HelloController

前端联调不要一开始就手写请求地址,先让后端提供一个最小可验证接口。

@RestController
class HelloController {
    data class Message(val message: String)

    @GetMapping("/hello")
    fun hello(): Message {
        return Message("QiFan")
    }
}

后面前端会通过生成出来的 API SDK 调这个接口,所以这里先把后端最小闭环补齐。

5. 执行 pnpm run api

接下来不要直接在页面里写 axios.get('/api/hello'),而是先生成前端 API 代码。

因为 scripts/generate-api.js 会用到额外依赖,所以在执行前先确认下面这些包已经安装:

  1. fs-extra
  2. adm-zip
  3. uuid

package.json

{
  "scripts": {
    "api": "node scripts/generate-api.js"
  }
}

执行:

pnpm run api

6. 介绍 data-agent-frontend/scripts/generate-api.js

这个脚本的职责很简单:从后端下载接口定义压缩包,解压到 src/apis/__generated,然后顺手把生成代码做一遍兼容处理。

以你当前项目里的实现为例,它做了这几件事:

  1. 从后端地址下载 ts.zip
  2. 清空旧的 src/apis/__generated
  3. 把压缩包解压到 src/apis/__generated
  4. 遍历生成的 modelservices
  5. 替换 readonlyReadonlyArrayReadonlyMap 等类型,减少前端使用时的阻力

这里会直接使用这些依赖:

  1. fs-extra:删除旧的生成目录
  2. adm-zip:解压后端返回的 ts.zip
  3. uuid:生成临时压缩包文件名

如果你的后端端口不是脚本里的默认值,记得先改 sourceUrl,例如改成:

const sourceUrl = 'http://localhost:9933/ts.zip'

执行完成后,前端会拿到一份生成好的 Api 类和对应的类型定义。

7. 配置 request.ts、环境变量和 Vite 代理

建议不要在页面里直接散落 fetch('/api/...'),而是先收敛一个统一请求实例。

先加环境变量:

.env.development

VITE_API_PREFIX=/api

.env.production

VITE_API_PREFIX=/api

然后新增 src/utils/request.ts

import axios from 'axios'
const BASE_URL = import.meta.env.VITE_API_PREFIX

export const request = axios.create({
    baseURL: BASE_URL,
    timeout: 30000,
})
request.interceptors.response.use(
    (res) => {
        return res.data
    },
    ({ response }) => {
        return Promise.reject(response.data)
    },
)

骨架阶段先保持这个最小版本就够了。后面如果接入登录态、统一错误提示、token 刷新,再继续补拦截器。

vite.config.ts 里把 /api 代理到后端:

export default defineConfig({
    // ...
    server: {
        host: '0.0.0.0',
        port: 3500,
        proxy: {
            '/api': {
                target: 'http://localhost:9933',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, ''),
            },
        },
    },
})

8. 配置 src/utils/api-instance.ts

有了生成代码和统一请求实例之后,再补一层 API 实例,把两边接起来。

import { Api } from '@/apis/__generated'
import { request } from '@/utils/request'

export const api = new Api(async ({ uri, method, body }) => {
  return await request({ url: uri, method, data: body })
})

这一步的意义是:以后页面和 store 都不直接关心接口 URL,而是统一通过 api.xxxController.xxx() 调用。

9. 前端调用 api.helloController.hello()

到这里,前端就可以直接通过生成好的 SDK 调后端接口了:

<script setup lang="ts">
  import { onMounted, ref } from 'vue'
  import { api } from '@/utils/api-instance.ts'

  const hello = ref('')
  const loadHello = async () => {
    const res = await api.helloController.hello()
    hello.value = res.message
  }
  onMounted(() => {
    loadHello()
  })
</script>

<template>
  <h1>Hello, {{ hello }}</h1>
</template>

<style scoped></style>

如果这里能成功拿到后端返回值,说明这一条链路已经通了:

HelloController -> ts.zip -> pnpm run api -> request.ts -> api-instance.ts -> api.helloController.hello()

最后再启动前端:

pnpm dev

默认会在 http://localhost:3500 启动。

10. 常见问题

  1. 前端 404 /api/...
    检查代理 target 与 rewrite。

  2. 浏览器跨域错误
    优先通过 Vite 代理解决,不要直接改后端跨域策略。

  3. pnpm run api 失败
    先确认后端是否已经提供 ts.zip 导出能力,再检查 generate-api.js 里的下载地址、后端端口和目标目录。

  4. 前端没有生成 helloController
    先确认后端 HelloController 是否已经被接口导出工具扫描到,然后重新执行一次 pnpm run api