api.ts.ftl 和 types.ts.ftl(TypeScript 模板参考示例1)

以下是符合 现代前端工程化规范、遵循 OpenAPI 3.0 契约 的两个 TypeScript 模板文件,专为与 Java 后端(特别是基于 MyBatis-Plus + SpringDoc 的项目)协同开发而设计:

  • api.ts.ftl:API 请求函数封装(基于 Axios)
  • types.ts.ftl:TypeScript 类型定义(与后端 DTO/VO 严格对齐)

这两个模板均包含详尽的中文注释,支持 自动生成类型安全的前端 API 客户端,可直接用于代码生成器(如 MyBatis-Plus Generator 扩展或自定义脚本)。


✅ 1. types.ts.ftl(TypeScript 类型定义模板)

/**
 * ${table.comment!} 相关类型定义
 *
 * 【设计目标】
 * 1. 与后端 Java 的 DTO/VO 结构严格对齐
 * 2. 提供完整的 TypeScript 类型安全
 * 3. 支持 OpenAPI 生成工具(如 openapi-typescript)的补充或替代
 * 4. 字段注释保留业务语义,便于前端理解
 *
 * 【关键原则】
 * - 类型命名清晰:CreateDTO → CreateReq,VO → Res
 * - 时间字段统一使用 string(ISO 8601 格式),避免 Date 对象时区问题
 * - 枚举使用联合类型("0" | "1")而非数字,提升可读性
 * - 敏感字段(如 password)绝不包含
 *
 * @author ${author}
 * @since ${date}
 */

/**
 * ${table.comment!} 创建请求参数
 */
export interface ${entity}CreateReq {
<#list table.fields as field>
<#-- 跳过主键、基类字段和敏感字段 -->
<#if !(field.name == 'id' || field.name == 'create_time' || field.name == 'update_time' || field.name == 'deleted' || field.name == 'password' || field.name == 'salt')>
  /**
   * ${field.comment!}
   */
  ${field.propertyName}: 
  <#if field.propertyType == "String">string
  <#elseif field.propertyType == "Integer" || field.propertyType == "Long">number
  <#elseif field.propertyType == "Boolean">boolean
  <#elseif field.propertyType == "LocalDateTime" || field.propertyType == "LocalDate">string // ISO 8601 格式,如 "2023-01-01T12:00:00"
  <#else>any</#if>;
</#if>
</#list>
}

/**
 * ${table.comment!} 更新请求参数
 */
export interface ${entity}UpdateReq {
  /**
   * 主键 ID
   */
  id: number;
<#list table.fields as field>
<#-- 跳过基类字段、敏感字段,保留可更新字段 -->
<#if !(field.name == 'id' || field.name == 'create_time' || field.name == 'update_time' || field.name == 'deleted' || field.name == 'password' || field.name == 'salt')>
  /**
   * ${field.comment!}
   */
  ${field.propertyName}: 
  <#if field.propertyType == "String">string | null
  <#elseif field.propertyType == "Integer" || field.propertyType == "Long">number | null
  <#elseif field.propertyType == "Boolean">boolean | null
  <#elseif field.propertyType == "LocalDateTime" || field.propertyType == "LocalDate">string | null // ISO 8601 格式
  <#else>any</#if>;
</#if>
</#list>
}

/**
 * ${table.comment!} 查询条件
 */
export interface ${entity}Query {
  /**
   * 页码(从1开始)
   * @default 1
   */
  page?: number;

  /**
   * 每页数量
   * @default 10
   */
  size?: number;
<#list table.fields as field>
<#-- 仅包含适合查询的字段(通常是 String、Integer 等) -->
<#if !(field.name == 'id' || field.name == 'create_time' || field.name == 'update_time' || field.name == 'deleted' || field.name == 'password' || field.name == 'salt')>
  <#if field.propertyType == "String" || field.propertyType == "Integer" || field.propertyType == "Boolean">
  /**
   * ${field.comment!}(模糊/精确查询)
   */
  ${field.propertyName}?: 
  <#if field.propertyType == "String">string
  <#elseif field.propertyType == "Integer" || field.propertyType == "Long">number
  <#elseif field.propertyType == "Boolean">boolean</#if>;
  </#if>
</#if>
</#list>
}

/**
 * ${table.comment!} 详情响应
 */
export interface ${entity}DetailRes {
  /**
   * 主键 ID
   */
  id: number;
<#list table.fields as field>
<#-- 跳过敏感字段和逻辑删除字段 -->
<#if !(field.name == 'password' || field.name == 'salt' || field.name == 'deleted')>
  /**
   * ${field.comment!}
   */
  ${field.propertyName}: 
  <#if field.propertyType == "String">string
  <#elseif field.propertyType == "Integer" || field.propertyType == "Long">number
  <#elseif field.propertyType == "Boolean">boolean
  <#elseif field.propertyType == "LocalDateTime" || field.propertyType == "LocalDate">string // ISO 8601 格式
  <#else>any</#if>;
</#if>
</#list>
}

/**
 * ${table.comment!} 列表项响应
 */
export type ${entity}ListItemRes = ${entity}DetailRes;

/**
 * 分页响应包装
 */
export interface PageRes<T> {
  /**
   * 总记录数
   */
  total: number;

  /**
   * 当前页数据列表
   */
  list: T[];

  /**
   * 当前页码
   */
  page: number;

  /**
   * 每页数量
   */
  size: number;
}

/**
 * 【扩展建议】以下为常见场景示例(按需取消注释)
 */

/*
// 示例:状态枚举(与后端保持一致)
export type UserStatus = '0' | '1'; // '0' - 禁用, '1' - 启用

// 示例:格式化字段(前端计算)
export interface UserDetailResWithFormat extends UserDetailRes {
  formattedCreateTime: string; // 如 "2023年01月01日 12:00"
}
*/

✅ 2. api.ts.ftl(API 请求函数模板)

/**
 * ${table.comment!} API 接口封装
 *
 * 【设计目标】
 * 1. 基于 Axios 封装类型安全的 HTTP 请求
 * 2. 与后端 Controller 路径严格对齐(/${table.mappingPath})
 * 3. 返回 Promise,便于 async/await 使用
 * 4. 集成统一错误处理(通过 axios 拦截器)
 *
 * 【关键原则】
 * - 函数命名清晰:create${entity}、update${entity} 等
 * - 参数类型严格匹配 types.ts 中的定义
 * - 响应类型使用后端统一包装格式(ApiResponse<T>)
 * - 路径常量集中管理,避免硬编码
 *
 * @author ${author}
 * @since ${date}
 */

import axios, { AxiosPromise } from 'axios';
<#-- 导入类型定义 -->
import {
  ${entity}CreateReq,
  ${entity}UpdateReq,
  ${entity}Query,
  ${entity}DetailRes,
  ${entity}ListItemRes,
  PageRes
} from './types';

/**
 * API 路径常量
 */
const API_PREFIX = '/${table.mappingPath}';

/**
 * 统一响应包装(与后端 ApiResponse<T> 对齐)
 */
interface ApiResponse<T = any> {
  code: number;
  message: string;
  data: T;
}

/**
 * 创建 ${table.comment!}
 * @param data 创建请求参数
 * @returns 创建成功的详情
 */
export function create${entity}(data: ${entity}CreateReq): AxiosPromise<ApiResponse<${entity}DetailRes>> {
  return axios.post<ApiResponse<${entity}DetailRes>>(API_PREFIX, data);
}

/**
 * 更新 ${table.comment!}
 * @param data 更新请求参数
 * @returns 更新是否成功
 */
export function update${entity}(data: ${entity}UpdateReq): AxiosPromise<ApiResponse<boolean>> {
  return axios.put<ApiResponse<boolean>>(API_PREFIX, data);
}

/**
 * 根据 ID 删除 ${table.comment!}
 * @param id 主键 ID
 * @returns 删除是否成功
 */
export function delete${entity}(id: number): AxiosPromise<ApiResponse<boolean>> {
  return axios.delete<ApiResponse<boolean>>(`${API_PREFIX}/${id}`);
}

/**
 * 根据 ID 获取 ${table.comment!} 详情
 * @param id 主键 ID
 * @returns 详情数据
 */
export function get${entity}Detail(id: number): AxiosPromise<ApiResponse<${entity}DetailRes>> {
  return axios.get<ApiResponse<${entity}DetailRes>>(`${API_PREFIX}/${id}`);
}

/**
 * 获取 ${table.comment!} 列表(不分页)
 * @returns 列表数据
 */
export function list${entity}(): AxiosPromise<ApiResponse<${entity}ListItemRes[]>> {
  return axios.get<ApiResponse<${entity}ListItemRes[]>>(API_PREFIX);
}

/**
 * 分页查询 ${table.comment!} 列表
 * @param params 查询条件
 * @returns 分页数据
 */
export function page${entity}(params: ${entity}Query): AxiosPromise<ApiResponse<PageRes<${entity}ListItemRes>>> {
  return axios.get<ApiResponse<PageRes<${entity}ListItemRes>>>(API_PREFIX, { params });
}

/**
 * 【扩展建议】以下为常见场景示例(按需取消注释)
 */

/*
// 示例:批量删除
export function batchDelete${entity}(ids: number[]): AxiosPromise<ApiResponse<boolean>> {
  return axios.delete<ApiResponse<boolean>>(`${API_PREFIX}/batch`, { data: { ids } });
}

// 示例:导出
export function export${entity}(params: ${entity}Query): AxiosPromise<Blob> {
  return axios.get(`${API_PREFIX}/export`, {
    params,
    responseType: 'blob'
  });
}
*/

🔍 关键设计说明与最佳实践

1. 前后端类型对齐策略
后端 Java 类型前端 TypeScript 类型说明
Stringstring基础字符串
Integer/LongnumberJavaScript Number 足够
Booleanboolean布尔值
LocalDateTimestring必须用 ISO 8601 字符串,避免 Date 时区问题
List<T>T[]数组类型
Page<T>PageRes<T>自定义分页结构

⚠️ 重要:时间字段绝不使用 Date 对象
原因:new Date("2023-01-01T12:00:00") 在不同浏览器/时区解析结果不一致,应由前端 UI 库(如 dayjs)按需格式化。

2. API 函数设计规范
  • 命名清晰createUsergetUserDetail
  • 参数单一:每个函数只接收一个参数对象(便于扩展)
  • 返回 AxiosPromise:保留 Axios 的链式调用能力
  • 路径集中管理API_PREFIX 避免路径硬编码
3. 安全与健壮性
  • 🔒 敏感字段过滤:模板中已排除 passwordsaltdeleted
  • 🔒 可选字段处理:查询参数使用 ? 标记可选
  • 🔒 空值处理:更新请求字段允许 null(表示清空)
4. 工程化集成建议
  • 代码生成流程

    MyBatis-Plus Generator
    MyBatis-Plus Generator
    Java Entity/DTO
    types.ts
    api.ts
    前端项目
  • 类型安全验证:配合 openapi-typescript 双重校验

  • Mock 支持:在开发环境可 mock api.ts 函数


🛠️ 配套使用示例

(1) 前端调用示例(Vue/React)
// Vue 3 + Composition API
import { create${entity}, get${entity}Detail } from '@/api/${table.mappingPath}';

const handleCreate = async () => {
  try {
    const res = await create${entity}({
      username: 'zhangsan',
      email: 'zhangsan@example.com',
      age: 25
    });
    ElMessage.success('创建成功');
    // res.data.code === 200
    // res.data.data 包含详情
  } catch (error) {
    // 通过 axios 拦截器统一处理
  }
};
(2) Axios 拦截器(统一错误处理)
// request.ts
axios.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.data?.message) {
      ElMessage.error(error.response.data.message);
    }
    return Promise.reject(error);
  }
);

📦 项目结构建议

src/
├── api/
│   └── user/
│       ├── types.ts      ← 类型定义
│       └── index.ts      ← API 函数(即 api.ts)
├── views/
└── utils/

并在 vite.config.tswebpack 中配置路径别名:

// vite.config.ts
resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src')
  }
}

✅ 这两份模板体现了 “前后端契约驱动开发(CDC)”“类型安全优先” 的现代前端工程思想:

  • 类型定义与后端 DTO/VO 严格对齐
  • API 函数封装完整、可维护
  • 时间处理、枚举、分页等高频场景全覆盖
  • 安全红线明确,杜绝敏感信息泄露

可作为团队标准模板,在代码生成流程中自动生成,大幅提升前后端协作效率与代码质量。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值