- 实现了 DELETE /api/dict/:id 接口,用于软删除字典项。 - 支持 `cascade=true` 查询参数,用于级联软删除所有子孙节点。 - 添加了 DeleteDictSchema 用于请求验证。 - 添加了 DeleteDictResponsesSchema 用于 API 文档。 - 服务层实现包含对系统字典的保护、以及对有子节点的非级联删除的防护。 - 所有数据库查询和更新均使用原生 SQL 以规避 ORM 类型问题。 - 在控制器中添加了新路由。 - 在 dict.test.md 中为删除接口添加了全面的测试用例。
420 lines
12 KiB
TypeScript
420 lines
12 KiB
TypeScript
/**
|
||
* @file 字典模块响应格式定义
|
||
* @author AI Assistant
|
||
* @date 2024-12-19
|
||
* @lastEditor AI Assistant
|
||
* @lastEditTime 2025-01-07
|
||
* @description 定义字典模块的响应格式,包括创建字典项等
|
||
*/
|
||
|
||
import { t, type Static } from 'elysia';
|
||
import { responseWrapperSchema } from '@/utils/responseFormate';
|
||
|
||
/**
|
||
* 创建字典项成功响应数据结构
|
||
*/
|
||
export const CreateDictSuccessSchema = t.Object({
|
||
id: t.String({
|
||
description: '字典项ID(bigint类型以字符串返回防止精度丢失)',
|
||
examples: ['1', '2', '100'],
|
||
}),
|
||
code: t.String({
|
||
description: '字典代码',
|
||
examples: ['user_status'],
|
||
}),
|
||
name: t.String({
|
||
description: '字典名称',
|
||
examples: ['用户状态'],
|
||
}),
|
||
value: t.Optional(
|
||
t.String({
|
||
description: '字典值',
|
||
examples: ['active'],
|
||
}),
|
||
),
|
||
description: t.Optional(
|
||
t.String({
|
||
description: '字典描述',
|
||
examples: ['用户状态字典'],
|
||
}),
|
||
),
|
||
icon: t.Optional(
|
||
t.String({
|
||
description: '图标',
|
||
examples: ['icon-user'],
|
||
}),
|
||
),
|
||
pid: t.String({
|
||
description: '父级ID(bigint类型以字符串返回)',
|
||
examples: ['0', '1'],
|
||
}),
|
||
level: t.Number({
|
||
description: '层级深度',
|
||
examples: [1, 2],
|
||
}),
|
||
sortOrder: t.Number({
|
||
description: '排序号',
|
||
examples: [0, 1, 10],
|
||
}),
|
||
status: t.String({
|
||
description: '状态',
|
||
examples: ['active', 'inactive'],
|
||
}),
|
||
isSystem: t.Boolean({
|
||
description: '是否系统字典',
|
||
examples: [true, false],
|
||
}),
|
||
color: t.Optional(
|
||
t.String({
|
||
description: '颜色标识',
|
||
examples: ['#1890ff'],
|
||
}),
|
||
),
|
||
extra: t.Optional(
|
||
t.Record(t.String(), t.Any(), {
|
||
description: '扩展字段',
|
||
examples: [{ key1: 'value1' }],
|
||
}),
|
||
),
|
||
createdAt: t.String({
|
||
description: '创建时间',
|
||
examples: ['2024-12-19T10:30:00Z'],
|
||
}),
|
||
updatedAt: t.String({
|
||
description: '更新时间',
|
||
examples: ['2024-12-19T10:30:00Z'],
|
||
}),
|
||
});
|
||
|
||
/**
|
||
* 创建字典项接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const CreateDictResponsesSchema = {
|
||
200: responseWrapperSchema(CreateDictSuccessSchema),
|
||
409: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '唯一性冲突',
|
||
examples: ['字典代码已存在', '字典名称在同级下已存在'],
|
||
}),
|
||
}),
|
||
),
|
||
400: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '参数错误',
|
||
examples: ['参数校验失败', '父级字典状态非active', '字典层级超过限制'],
|
||
}),
|
||
}),
|
||
),
|
||
404: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '资源不存在',
|
||
examples: ['父级字典不存在'],
|
||
}),
|
||
}),
|
||
),
|
||
500: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '服务器错误',
|
||
examples: ['内部服务器错误'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 创建字典项成功响应数据类型 */
|
||
export type CreateDictSuccessType = Static<(typeof CreateDictResponsesSchema)[200]>;
|
||
|
||
/**
|
||
* 获取字典项成功响应数据结构
|
||
* @description 获取单个字典项的成功响应数据结构,与创建成功响应结构相同
|
||
*/
|
||
export const GetDictByIdSuccessSchema = CreateDictSuccessSchema;
|
||
|
||
/**
|
||
* 获取字典项接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const GetDictByIdResponsesSchema = {
|
||
200: responseWrapperSchema(GetDictByIdSuccessSchema),
|
||
404: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '资源不存在',
|
||
examples: ['字典项不存在'],
|
||
}),
|
||
}),
|
||
),
|
||
500: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '服务器错误',
|
||
examples: ['内部服务器错误'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 获取字典项成功响应数据类型 */
|
||
export type GetDictByIdSuccessType = Static<(typeof GetDictByIdResponsesSchema)[200]>;
|
||
|
||
/**
|
||
* 字典树节点数据结构
|
||
* @description 描述字典树中单个节点的数据结构,包含一个可选的children数组用于表示子节点
|
||
*/
|
||
export const DictTreeNodeSchema = t.Recursive(
|
||
(self) =>
|
||
t.Object({
|
||
id: t.String({
|
||
description: '字典项ID(bigint类型以字符串返回防止精度丢失)',
|
||
examples: ['1', '2', '100'],
|
||
}),
|
||
code: t.String({
|
||
description: '字典代码',
|
||
examples: ['user_status'],
|
||
}),
|
||
name: t.String({
|
||
description: '字典名称',
|
||
examples: ['用户状态'],
|
||
}),
|
||
value: t.Optional(
|
||
t.String({
|
||
description: '字典值',
|
||
examples: ['active'],
|
||
}),
|
||
),
|
||
description: t.Optional(
|
||
t.String({
|
||
description: '字典描述',
|
||
examples: ['用户状态字典'],
|
||
}),
|
||
),
|
||
icon: t.Optional(
|
||
t.String({
|
||
description: '图标',
|
||
examples: ['icon-user'],
|
||
}),
|
||
),
|
||
pid: t.String({
|
||
description: '父级ID(bigint类型以字符串返回)',
|
||
examples: ['0', '1'],
|
||
}),
|
||
level: t.Number({
|
||
description: '层级深度',
|
||
examples: [1, 2],
|
||
}),
|
||
sortOrder: t.Number({
|
||
description: '排序号',
|
||
examples: [0, 1, 10],
|
||
}),
|
||
status: t.String({
|
||
description: '状态',
|
||
examples: ['active', 'inactive'],
|
||
}),
|
||
isSystem: t.Boolean({
|
||
description: '是否系统字典',
|
||
examples: [true, false],
|
||
}),
|
||
color: t.Optional(
|
||
t.String({
|
||
description: '颜色标识',
|
||
examples: ['#1890ff'],
|
||
}),
|
||
),
|
||
extra: t.Optional(
|
||
t.Record(t.String(), t.Any(), {
|
||
description: '扩展字段',
|
||
examples: [{ key1: 'value1' }],
|
||
}),
|
||
),
|
||
createdAt: t.String({
|
||
description: '创建时间',
|
||
examples: ['2024-12-19T10:30:00Z'],
|
||
}),
|
||
updatedAt: t.String({
|
||
description: '更新时间',
|
||
examples: ['2024-12-19T10:30:00Z'],
|
||
}),
|
||
children: t.Optional(t.Array(self)),
|
||
}),
|
||
{
|
||
$id: 'DictTreeNode',
|
||
description: '字典树节点',
|
||
},
|
||
);
|
||
|
||
/**
|
||
* 获取完整字典树接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const GetDictTreeResponsesSchema = {
|
||
200: responseWrapperSchema(t.Array(DictTreeNodeSchema)),
|
||
500: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '服务器错误',
|
||
examples: ['内部服务器错误'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 获取完整字典树成功响应数据类型 */
|
||
export type GetDictTreeSuccessType = Static<(typeof GetDictTreeResponsesSchema)[200]>;
|
||
|
||
/**
|
||
* 获取指定字典树接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const GetDictTreeByCodeResponsesSchema = {
|
||
...GetDictTreeResponsesSchema,
|
||
404: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '资源不存在',
|
||
examples: ['字典代码不存在'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 获取指定字典树成功响应数据类型 */
|
||
export type GetDictTreeByCodeSuccessType = Static<(typeof GetDictTreeByCodeResponsesSchema)[200]>;
|
||
|
||
/**
|
||
* 更新字典项成功响应数据结构
|
||
* @description 更新字典项的成功响应数据结构,与获取成功响应结构相同
|
||
*/
|
||
export const UpdateDictSuccessSchema = GetDictByIdSuccessSchema;
|
||
|
||
/**
|
||
* 更新字典项接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const UpdateDictResponsesSchema = {
|
||
200: responseWrapperSchema(UpdateDictSuccessSchema),
|
||
404: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '资源不存在',
|
||
examples: ['字典项不存在'],
|
||
}),
|
||
}),
|
||
),
|
||
409: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '唯一性冲突',
|
||
examples: ['字典代码已存在', '字典名称在同级下已存在'],
|
||
}),
|
||
}),
|
||
),
|
||
500: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '服务器错误',
|
||
examples: ['内部服务器错误'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 更新字典项成功响应数据类型 */
|
||
export type UpdateDictSuccessType = Static<(typeof UpdateDictResponsesSchema)[200]>;
|
||
|
||
/**
|
||
* 字典项排序接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const SortDictResponsesSchema = {
|
||
200: responseWrapperSchema(t.Null(), {
|
||
description: '排序成功',
|
||
examples: {
|
||
code: 0,
|
||
message: '字典项排序成功',
|
||
data: null,
|
||
},
|
||
}),
|
||
400: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '参数错误',
|
||
examples: ['参数校验失败', '排序项列表为空'],
|
||
}),
|
||
}),
|
||
),
|
||
404: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '资源不存在',
|
||
examples: ['部分字典项不存在', '父级字典不存在'],
|
||
}),
|
||
}),
|
||
),
|
||
500: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '服务器错误',
|
||
examples: ['内部服务器错误'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 字典项排序成功响应数据类型 */
|
||
export type SortDictSuccessType = Static<(typeof SortDictResponsesSchema)[200]>;
|
||
|
||
/**
|
||
* 删除字典项接口响应组合
|
||
* @description 用于Controller中定义所有可能的响应格式
|
||
*/
|
||
export const DeleteDictResponsesSchema = {
|
||
200: responseWrapperSchema(t.Null(), {
|
||
description: '删除成功',
|
||
examples: {
|
||
code: 0,
|
||
message: '字典项删除成功',
|
||
data: null,
|
||
},
|
||
}),
|
||
400: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '参数错误或业务规则冲突',
|
||
examples: ['存在子字典项,无法删除,请先删除子项或使用级联删除'],
|
||
}),
|
||
}),
|
||
),
|
||
404: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '资源不存在',
|
||
examples: ['字典项不存在'],
|
||
}),
|
||
}),
|
||
),
|
||
409: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '冲突',
|
||
examples: ['系统字典不允许删除'],
|
||
}),
|
||
}),
|
||
),
|
||
500: responseWrapperSchema(
|
||
t.Object({
|
||
error: t.String({
|
||
description: '服务器错误',
|
||
examples: ['内部服务器错误'],
|
||
}),
|
||
}),
|
||
),
|
||
};
|
||
|
||
/** 删除字典项成功响应数据类型 */
|
||
export type DeleteDictSuccessType = Static<(typeof DeleteDictResponsesSchema)[200]>;
|