项目初始化
本节重点
从 0 开始搭建后端和前端项目,会后续正式开发项目打好基础。
包括:
- 后端项目初始化
- 前端项目初始化
本节教程的后端和前端互不影响,可以按需独立学习,建议优先学习自己求职方向的内容。
一、后端项目初始化
环境准备
1)安装的 JDK 版本必须 >= 17,推荐使用 21 版本!
可参考视频安装 JDK:https://www.bilibili.com/video/BV14SUNYREv8
2)MySQL 数据库最好安装 8.x 版本,或者 5.7 版本。
新建项目
在 IDEA 中新建项目,选择 Spring Boot 模板、Maven、JDK 21,配置如图:
选择 Spring Boot 3.5.x 版本,必须添加的依赖包括 Spring Web、MySQL、Lombok,Spring Boot DevTools 可以按需选取:
当然,后续通过修改 Maven 配置添加依赖也是可以的。
💡 小提示,如果 Lombok 依赖报错的话,可以手动指定 Lombok 的版本,修改 pom.xml 的代码:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.36</version>
<optional>true</optional>
</dependency>
点击创建,就得到了一个 Spring Boot 项目,需要等待 Maven 为我们安装依赖。
修改资源目录下的配置文件为 application.yml,指定项目启动的端口号和访问地址前缀、项目名称,代码如下:
spring:
application:
name: yu-ai-code-mother-backend
server:
port: 8123
servlet:
context-path: /api
然后尝试启动项目,确保能正常启动再进行后续操作。
整合依赖
接下来我们要整合一些开发项目常用的依赖。
Hutool 工具库
Hutool 是主流的 Java 工具类库,集合了丰富的工具类,涵盖字符串处理、日期操作、文件处理、加解密、反射、正则匹配等常见功能。它的轻量化和无侵入性让开发者能够专注于业务逻辑而不必编写重复的工具代码。例如,DateUtil.formatDate(new Date()) 可以快速将当前日期格式化为字符串。
参考官方文档引入:https://doc.hutool.cn/pages/index/#🍊maven
在 Maven 的 pom.xml 中添加依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.38</version>
</dependency>
Knife4j 接口文档
Knife4j 是基于 Swagger 接口文档的增强工具,提供了更加友好的 API 文档界面和功能扩展,例如动态参数调试、分组文档等。它适合用于 Spring Boot 项目中,能够通过简单的配置自动生成接口文档,让开发者和前端快速了解和调试接口,提高协作效率。
参考官方文档引入:https://doc.xiaominfo.com/docs/quick-start#spring-boot-3
由于使用的是 Spring Boot 3.x,注意要选择 OpenAPI 3 的版本!
1)在 Maven 的 pom.xml 中添加依赖:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
2)在 application.yml 中追加接口文档配置,重点是指定扫描 Controller 包的路径:
# springdoc-openapi
springdoc:
group-configs:
- group: 'default'
packages-to-scan: com.yupi.yuaicodemother.controller
# knife4j
knife4j:
enable: true
setting:
language: zh_cn
3)在 controller 包下新建一个测试接口:
@RestController
@RequestMapping("/health")
public class HealthController {
@GetMapping("/")
public String healthCheck() {
return "ok";
}
}
重启项目,访问 http://localhost:8123/api/doc.html 能够看到接口文档,可以测试调用接口:
其他依赖
可以按需引入其他依赖,比如 AOP 切面编程:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
给启动类添加注解(可选):
@EnableAspectJAutoProxy(exposeProxy = true)
解释一下 exposeProxy = true 的作用:通过 Spring AOP 提供对当前代理对象的访问,使得可以在业务逻辑中访问到当前的代理对象。你可以在方法执行时通过 AopContext.currentProxy() 获取当前的代理对象。
还有更多的依赖,后续我们随用随装。
通用基础代码
通用基础代码是指:无论在任何后端项目中,都可以复用的代码。这种代码一般 “一辈子只用写一次”,了解作用之后复制粘贴即可,无需记忆。
目录结构如下:
1、自定义异常
自定义错误码,对错误进行收敛,便于前端统一处理。
💡 这里有 2 个小技巧:
- 自定义错误码时,建议跟主流的错误码(比如 HTTP 错误码)的含义保持一致,比如 “未登录” 定义为 40100,和 HTTP 401 错误(用户需要进行身份认证)保持一致,会更容易理解。
- 错误码不要完全连续,预留一些间隔,便于后续扩展。
在 exception 包下新建错误码枚举类:
@Getter
public enum ErrorCode {
SUCCESS(0, "ok"),
PARAMS_ERROR(40000, "请求参数错误"),
NOT_LOGIN_ERROR(40100, "未登录"),
NO_AUTH_ERROR(40101, "无权限"),
NOT_FOUND_ERROR(40400, "请求数据不存在"),
FORBIDDEN_ERROR(40300, "禁止访问"),
SYSTEM_ERROR(50000, "系统内部异常"),
OPERATION_ERROR(50001, "操作失败");
/**
* 状态码
*/
private final int code;
/**
* 信息
*/
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
一般不建议直接抛出 Java 内置的 RuntimeException,而是自定义一个业务异常,和内置的异常类区分开,便于定制化输出错误信息:
@Getter
public class BusinessException extends RuntimeException {
/**
* 错误码
*/
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public BusinessException(ErrorCode errorCode, String message) {
super(message);
this.code = errorCode.getCode();
}
}
为了更方便地根据情况抛出异常,可以封装一个 ThrowUtils,类似断言类,简化抛异常的代码:
public class ThrowUtils {
/**
* 条件成立则抛异常
*
* @param condition 条件
* @param runtimeException 异常
*/
public static void throwIf(boolean condition, RuntimeException runtimeException) {
if (condition) {
throw runtimeException;
}
}
/**
* 条件成立则抛异常
*
* @param condition 条件
* @param errorCode 错误码
*/
public static void throwIf(boolean condition, ErrorCode errorCode) {
throwIf(condition, new BusinessException(errorCode));
}
/**
* 条件成立则抛异常
*
* @param condition 条件
* @param errorCode 错误码
* @param message 错误信息
*/
public static void throwIf(boolean condition, ErrorCode errorCode, String message) {
throwIf(condition, new BusinessException(errorCode, message));
}
}
2、响应包装类
一般情况下,每个后端接口都要返回调用码、数据、调用信息等,前端可以根据这些信息进行相应的处理。
我们可以封装统一的响应结果类,便于前端统一获取这些信息。
通用响应类:
@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String message;
public BaseResponse(int code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
public BaseResponse(int code, T data) {
this(code, data, "");
}
public BaseResponse(ErrorCode errorCode) {
this(errorCode.getCode(), null, errorCode.getMessage());
}
}
但之后每次接口返回值时,都要手动 new 一个 BaseResponse 对象并传入参数,比较麻烦,我们可以新建一个工具类,提供成功调用和失败调用的方法,支持灵活地传参,简化调用。
public class ResultUtils {
/**
* 成功
*
* @param data 数据
* @param <T> 数据类型
* @return 响应
*/
public static <T> BaseResponse<T> success(T data) {
return new BaseResponse<>(0, data, "ok");
}
/**
* 失败
*
* @param errorCode 错误码
* @return 响应
*/
public static BaseResponse<?> error(ErrorCode errorCode) {
return new BaseResponse<>(errorCode);
}
/**
* 失败
*
* @param code 错误码
* @param message 错误信息
* @return 响应
*/
public static BaseResponse<?> error(int code, String message) {
return new BaseResponse<>(code, null, message);
}
/**
* 失败
*
* @param errorCode 错误码
* @return 响应
*/
public static BaseResponse<?> error(ErrorCode errorCode, String message) {
return new BaseResponse<>(errorCode.getCode(), null, message);
}
}
3、全局异常处理器
为了防止意料之外的异常,利用 AOP 切面全局对业务异常和 RuntimeException 进行捕获:
@Hidden
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
log.error("BusinessException", e);
return ResultUtils.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(RuntimeException.class)
public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
log.error("RuntimeException", e);
return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
}
}
注意!由于本项目使用的 Spring Boot 版本 >= 3.4、并且是 OpenAPI 3 版本的 Knife4j,这会导致 @RestControllerAdvice 注解不兼容,所以必须给这个类加上 @Hidden 注解,不被 Swagger 加载。虽然网上也有其他的解决方案,但这种方法是最直接有效的。
4、请求包装类
对于 “分页”、“删除某条数据” 这类通用的请求,可以封装统一的请求包装类,用于接受前端传来的参数,之后相同参数的请求就不用专门再新建一个类了。
分页请求包装类,包括当前页号、页面大小、排序字段、排序顺序参数:
@Data
public class PageRequest {
/**
* 当前页号
*/
private int pageNum = 1;
/**
* 页面大小
*/
private int pageSize = 10;
/**
* 排序字段
*/
private String sortField;
/**
* 排序顺序(默认降序)
*/
private String sortOrder = "descend";
}
删除请求包装类,接受要删除数据的 id 作为参数:
@Data
public class DeleteRequest implements Serializable {
/**
* id
*/
private Long id;
private static final long serialVersionUID = 1L;
}
5、全局跨域配置
跨域是指浏览器访问的 URL(前端地址)和后端接口地址的域名(或端口号)不一致导致的,浏览器为了安全,默认禁止跨域请求访问。
后端接口:http://localhost:8123/api
这就是跨域!
为了开发调试方便,我们可以通过全局跨域配置,让整个项目所有的接口支持跨域,解决跨域报错。
新建 config 包,用于存放所有的配置相关代码。全局跨域配置代码如下:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 覆盖所有请求
registry.addMapping("/**")
// 允许发送 Cookie
.allowCredentials(true)
// 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("*");
}
}
测试验证
最后让我们修改示例接口的返回值,然后再次验证接口文档的效果:
@RestController
@RequestMapping("/health")
public class HealthController {
@GetMapping("/")
public BaseResponse<String> healthCheck() {
return ResultUtils.success( "ok");
}
}
访问接口文档,能够正常响应:
后端项目初始化完成,此时的项目结构如图
二、前端项目初始化
环境准备
前端 Node.js 版本必须 >= 20,鱼皮教程中使用 20 版本。在官网安装好 Node 后会自动安装 NPM 前端包管理器。
可参考视频安装 Node.js:https://www.bilibili.com/video/BV14SUNYREv8
创建项目
使用 Vue 官方推荐的脚手架 create-vue 快速创建 Vue3 的项目:https://cn.vuejs.org/guide/quick-start.html
💡 Vue 提供了在线编码测试,可以通过 Playground 来学习 Vue:https://play.vuejs.org/
为了便于项目代码的统一管理,我们直接在后端项目根目录 yu-ai-code-mother 下新建前端项目 yu-ai-code-mother-frontend。
在终端进入后端项目根目录,输入命令:
npm create vue@latest
NPM 会自动安装 create-vue 工具并执行命令:
注意本教程使用的版本号是 3.17.0,如果之后版本更新导致跟鱼皮的教程不一致,记得安装特定版本的工具,而不是 latest 最新版!
接下来按照如下选项创建项目,脚手架会自动帮我们安装 Vue Router 路由、Pinia 全局状态管理等实用类库:


然后用 WebStorm 打开项目,先在终端执行 npm install 安装依赖,然后执行 npm run dev 能访问网页就成功了。
💡 可以看到 Vue 脚手架提供了一个 调试工具 devtools,你可以使用它来调试分析项目:
前端工程化配置
脚手架已经帮我们整合了 Prettier 代码美化、ESLint 自动校验、TypeScript 类型校验,无需再自行整合。
但是需要在 WebStorm 里开启代码美化插件:
在 vue 文件中执行格式化快捷键,不报错,表示配置工程化成功。
如果发现格式化效果不好,也没关系,之后可以使用另外一种格式化快捷键:
为了开发效率更高,你可能想关闭由于 ESLint 校验导致的编译错误,同样可以在开发工具中禁用 ESLint:
修改 eslint.config.js、.prettierrc.json、tsconfig.json 文件可以改变校验规则。
如果不使用脚手架,就需要自己整合这些工具:
- 代码规范:https://eslint.org/docs/latest/use/getting-started
- 代码美化:https://prettier.io/docs/en/install.html
- 直接整合:https://github.com/prettier/eslint-plugin-prettier#recommended-configuration(包括了 https://github.com/prettier/eslint-config-prettier#installation)
对于前端新手来说,你不需要深入了解这些,纯当工具去使用即可,应该尽快上手项目。
引入组件库
引入 Ant Design Vue 组件库,参考 官方文档 快速上手。
注意,本教程使用的是 v4.2.6 的组件库版本,如果后续阅读本教程中发现有组件或语法不一致,以官方文档为主,或者在网站右上角切换对应版本的文档即可:
执行安装:
npm i --save ant-design-vue@4.x
改变主入口文件 main.ts,为了方便,选择全局注册组件:
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(Antd)
app.mount('#app')
随便引入一个组件,如果显示出来,就表示引入成功。比如在 App.vue 中引入按钮:
<a-button type="primary">Primary Button</a-button>
效果如图:
开发规范
建议遵循 Vue3 的 组合式 API (Composition API),而不是 选项式 API,开发更自由高效一些。
示例代码:
<template>
<div id="xxPage">
</div>
</template>
#xxPage {
}
页面基本信息
可以修改项目根目录下的 index.html 文件,来定义页面的元信息,比如修改标题:
<!DOCTYPE html>
<html lang="">
<head>
<title>鱼皮 AI 零代码应用生成平台</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
还可以替换 public 目录下默认的 ico 图标为自己的。先用 AI 生成了个 Logo,保存到 assets 目录下 logo.png:
然后用 现成的网站 可以制作 ico 图标,替换 public 目录下的 favicon.ico 文件,效果如图:
全局通用布局 - Vibe Coding
由于这个项目包含多个页面,每个页面都包含导航栏、内容和底部栏,可以准备一套全局通用布局,给各页面复用。
如果是以前,我们需要自己利用组件库编写布局代码;但现在有了 AI,我们只需把需求描述清楚,剩下的交给 AI 就好,Vibe Coding 启动!
如果你想自己开发布局,可以参考 编程导航智能协同云图库项目 的前端项目初始化部分。
我们预期的布局大概是这样的:
使用 Cursor 打开前端项目,编写给 AI 的提示词 ,其实就是把自己开发的思路告诉 AI:
你是一位前端程序员专家,帮我给项目生成通用的全局基础布局,要求如下:
1)文件名 layouts/BasicLayout.vue,并且在 App.vue 全局页面入口文件中引入
2)移除 main.css 默认样式文件,以及对该文件的引用
3)整体结构为上中下布局,支持响应式,使用 Ant Design Vue 组件库的 Layout 组件实现
- 上方展示导航栏:独立创建 GlobalHeader 组件。左侧展示 logo.png 和网站标题,然后是菜单项,右侧展示登录用户的头像和昵称(暂时先用登录按钮替代)。导航栏使用 Menu 组件实现,支持通过配置设置菜单项。
- 中间展示内容区域:根据路由切换页面
- 下方展示版权信息:独立创建 GlobalFooter 组件。位置始终固定在底部,内容为:编程导航原创项目 by 程序员鱼皮(添加超链接 <https://www.codefather.cn>)
注意,由于 AI 生成的代码是随机的,本项目可能会出现视频教程跟鱼皮提供的源码不同的情况,完全不用担心,以鱼皮最终提供的代码为准就好。
对前端比较熟悉的同学,建议使用 Cursor Rules 指定自己理想的规范,确保每次生成的代码风格一致。
可以到 Cursor 官方网站找一些规范:https://cursor.directory/rules
举个例子,配置下面这个规则:
生成前端 Vue 代码时,使用 Vue3 的 组合式 API 语法
配置过程如图:
会在项目下生成 .cursor/rules 目录和规则文件:
这里鱼皮就先不配置了,简单一点,Cursor 也支持在对话过程中自动帮你生成规则。
💡 建议执行 AI 生成前先提交一下当前代码,便于观察 AI 生成了哪些代码,出了问题也好及时回滚。
执行生成:
效果如图,可以尝试缩小浏览器窗口观察导航条的变化:
建议在生成后看一下 AI 的思考过程和生成的代码,学习一下,如果有不满意的地方,可以自己修改。
生成的代码:
import BasicLayout from '@/layouts/BasicLayout.vue'
import { healthCheck } from '@/api/healthController.ts'
healthCheck().then((res) => {
console.log(res)
})
<template>
<BasicLayout />
</template>
<template>
<a-layout-footer class="footer">
<div class="footer-content">
<p class="copyright">
<a
href="<https://www.codefather.cn>"
target="_blank"
rel="noopener noreferrer"
class="author-link"
>
编程导航原创项目 by 程序员鱼皮
</a>
</p>
</div>
</a-layout-footer>
</template>
// 无需额外的响应式数据
.footer {
background: #efefef;
text-align: center;
padding: 20px;
position: fixed;
bottom: 0;
left: 0;
right: 0;
}
.copyright {
margin: 0;
color: #666;
font-size: 14px;
}
<template>
<a-layout-header class="header">
<a-row :wrap="false">
<!-- 左侧:Logo和标题 -->
<a-col flex="200px">
<RouterLink to="/">
<div class="header-left">
<img class="logo" src="@/assets/logo.png" alt="Logo" />
<h1 class="site-title">鱼皮应用生成</h1>
</div>
</RouterLink>
</a-col>
<!-- 中间:导航菜单 -->
<a-col flex="auto">
<a-menu
v-model:selectedKeys="selectedKeys"
mode="horizontal"
:items="menuItems"
@click="handleMenuClick"
/>
</a-col>
<!-- 右侧:用户操作区域 -->
<a-col>
<div class="user-login-status">
<a-button type="primary">登录</a-button>
</div>
</a-col>
</a-row>
</a-layout-header>
</template>
import { h, ref } from 'vue'
import { useRouter } from 'vue-router'
import type { MenuProps } from 'ant-design-vue'
const router = useRouter()
// 当前选中菜单
const selectedKeys = ref<string[]>(['/'])
// 监听路由变化,更新当前选中菜单
router.afterEach((to, from, next) => {
selectedKeys.value = [to.path]
})
// 菜单配置项
const menuItems = ref([
{
key: '/',
label: '首页',
title: '首页',
},
{
key: '/about',
label: '关于',
title: '关于我们',
},
{
key: 'others',
label: h('a', { href: '<https://www.codefather.cn>', target: '_blank' }, '编程导航'),
title: '编程导航',
},
])
// 处理菜单点击
const handleMenuClick: MenuProps['onClick'] = (e) => {
const key = e.key as string
selectedKeys.value = [key]
// 跳转到对应页面
if (key.startsWith('/')) {
router.push(key)
}
}
.header {
background: #fff;
padding: 0 24px;
}
.header-left {
display: flex;
align-items: center;
gap: 12px;
}
.logo {
height: 48px;
width: 48px;
}
.site-title {
margin: 0;
font-size: 18px;
color: #1890ff;
}
.ant-menu-horizontal {
border-bottom: none !important;
}
<template>
<a-layout class="basic-layout">
<!-- 顶部导航栏 -->
<GlobalHeader />
<!-- 主要内容区域 -->
<a-layout-content class="main-content">
<router-view />
</a-layout-content>
<!-- 底部版权信息 -->
<GlobalFooter />
</a-layout>
</template>
import GlobalHeader from '@/components/GlobalHeader.vue'
import GlobalFooter from '@/components/GlobalFooter.vue'
.basic-layout {
background: none;
}
.main-content {
max-width: 1200px;
padding: 24px;
background: white;
margin: 16px auto 56px;
}
路由
所谓路由,就是根据 URL 展示对应的页面。我们希望点击菜单项后,可以跳转到对应的页面;并且刷新页面后,对应的菜单自动高亮。
1、修改路由配置
按需修改 router/index.ts 文件的 routes 配置,定义我们需要的页面路由,每个 path 对应一个 component(要加载的组件):
routes: [
{
path: '/',
name: 'home',
component: HomeView,
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue'),
},
],
观察上述代码,会发现 component 支持直接传入组件、或者使用 import 按需懒加载组件,按需加载是一种优化首次打开站点性能的方式。
2、路由跳转
给 GlobalHeader 的菜单组件绑定跳转事件:
import { useRouter } from "vue-router";
const router = useRouter();
// 处理菜单点击
const handleMenuClick: MenuProps['onClick'] = (e) => {
const key = e.key as string
selectedKeys.value = [key]
// 跳转到对应页面
if (key.startsWith('/')) {
router.push(key)
}
}
修改 HTML 模板,绑定事件:
<a-menu
v-model:selectedKeys="selectedKeys"
mode="horizontal"
:items="items"
@click="handleMenuClick"
/>
3、高亮同步
刷新页面后,你会发现当前菜单项并没有高亮,所以需要同步路由的更新到菜单项高亮。
同步高亮原理:
- 点击菜单时,Ant Design 组件已经通过 v-model 绑定 current 变量实现了高亮。
- 刷新页面时,需要获取到当前 URL 路径,然后修改 current 变量的值,从而实现同步。
使用 Vue Router 的 afterEach 路由钩子实现,每次改变路由或刷新页面时都会自动更新 current 的值,从而实现高亮:
const router = useRouter()
// 当前选中菜单
const selectedKeys = ref<string[]>(['/'])
// 监听路由变化,更新当前选中菜单
router.afterEach((to, from, next) => {
selectedKeys.value = [to.path]
})
💡思考:大家有没有发现,路由和菜单配置中,有一些是重复的呢?有没有更好地方式来配置路由和菜单项,不用每次修改时都要改两边的代码呢?答案就是将路由配置数组传递给菜单组件,大家可以尝试自行实现。(鱼皮编程导航的 OJ 判题系统项目、鱼答答 AI 答题应用平台项目 中有讲过)
请求
一般情况下,前端只负责界面展示和动效交互,尽量避免写复杂的逻辑;当需要获取数据时,通常是向后端提供的接口发送请求,由后端执行操作(比如保存数据)并响应数据给前端。
前端如何向后端发送请求呢?
最传统的方式是使用 AJAX 技术。但其代码有些复杂,我们可以使用第三方的封装库,来简化发送请求的代码,比如主流的请求工具库 Axios。
1、请求工具库
安装请求工具类 Axios,参考官方文档 执行命令:
npm install axios
2、全局自定义请求
需要自定义全局请求地址等,参考 Axios 官方文档,编写请求配置文件 request.ts。包括全局接口请求地址、超时时间、自定义请求响应拦截器等。
响应拦截器的应用场景:我们需要对接口的 通用响应 进行统一处理,比如从 response 中取出 data;或者根据 code 去集中处理错误。这样不用在每个接口请求中都去写相同的逻辑。
比如可以在全局响应拦截器中,读取出结果中的 data,并校验 code 是否合法,如果是未登录状态,则自动登录。
示例代码如下,其中 withCredentials: true 一定要写,否则无法在发请求时携带 Cookie,就无法完成登录。
代码如下:
import axios from 'axios'
import { message } from 'ant-design-vue'
// 创建 Axios 实例
const myAxios = axios.create({
baseURL: '<http://localhost:8123/api>',
timeout: 60000,
withCredentials: true,
})
// 全局请求拦截器
myAxios.interceptors.request.use(
function (config) {
// Do something before request is sent
return config
},
function (error) {
// Do something with request error
return Promise.reject(error)
},
)
// 全局响应拦截器
myAxios.interceptors.response.use(
function (response) {
const { data } = response
// 未登录
if (data.code === 40100) {
// 不是获取用户信息的请求,并且用户目前不是已经在用户登录页面,则跳转到登录页面
if (
!response.request.responseURL.includes('user/get/login') &&
!window.location.pathname.includes('/user/login')
) {
message.warning('请先登录')
window.location.href = `/user/login?redirect=${window.location.href}`
}
}
return response
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error)
},
)
export default myAxios
3、自动生成请求代码
如果采用传统开发方式,针对每个请求都要单独编写代码,很麻烦。
推荐使用 OpenAPI 工具,直接根据后端接口文档自动生成前端请求代码即可,这种方式会比 AI 生成更可控。
按照官方文档的步骤,先安装:
npm i --save-dev @umijs/openap
还需要安装依赖库:
npm i --save-dev tslib
在 前端项目根目录 新建 openapi2ts.config.ts,根据自己的需要定制生成的代码:
export default {
requestLibPath: "import request from '@/request'",
schemaPath: '<http://localhost:8123/api/v3/api-docs>',
serversPath: './src',
}
注意,要将 schemaPath 改为自己后端服务提供的 Swagger 接口文档的地址,生成前确保后端已启动!
在 package.json 的 scripts 中添加 "openapi2ts": "openapi2ts"。
执行脚本即可生成请求代码,还包括 TypeScript 类型:
以后每次后端接口变更时,只需要重新生成一遍就好,非常方便~
4、测试请求
可以尝试在任意页面代码中调用 API:
import { healthCheck } from '@/api/healthController.ts'
healthCheck().then((res) => {
console.log(res)
})
按 F12 打开开发者工具查看请求,由于我们后端已经添加了全局跨域配置,正常情况下应该能看到如下响应:
清理项目
最后,我们需要移除脚手架自动生成的多余内容,并且把 views 目录重命名为 pages 目录(便于理解):
移除内容后,修改主页名称为 HomePage,之后所有页面名称都用 Page 作为结尾,更语义化。还需要相应地移除主页和路由中已经删除的组件引用,让项目能正常运行。
至此项目就初始化完成了。
扩展思路
在后续开发中你会发现,Ant Design Vue 默认使用的是英文文案,如果需要替换为中文,可以参考 国际化文档,只需给整个应用包裹一层组件即可完成。
本节教程到这里就结束了,建议大家从 0 实操下项目初始化,以后自己搭建一个新项目,也不会觉得困难啦~
项目分区导航:⬅️ 03-AI_零代码应用生成平台大纲 | 04-项目初始化 | ➡️ 05-Mynotes
💬 评论