This commit is contained in:
2026-04-08 21:26:18 +08:00
commit 8fdc7ac0c3
401 changed files with 53093 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
{
"name": "@cslab-dcs/schema",
"type": "module",
"version": "1.0.0",
"private": true,
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts"
},
"./*": {
"types": "./src/*.ts",
"import": "./src/*.ts"
}
},
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"typecheck": "tsc --noEmit"
},
"dependencies": {
"zod": "^3.24.1"
},
"devDependencies": {
"typescript": "^5.9.3"
}
}

View File

@@ -0,0 +1,164 @@
/**
* 画布基础类型 Schema
* 定义位置、尺寸、样式等基础类型
*/
import { z } from 'zod'
// ============================================
// 基础几何类型
// ============================================
/**
* 位置 Schema
*/
export const positionSchema = z.object({
x: z.number().describe('X 坐标'),
y: z.number().describe('Y 坐标'),
})
export type Position = z.infer<typeof positionSchema>
/**
* 尺寸 Schema
*/
export const sizeSchema = z.object({
width: z.number().positive('宽度必须为正数').describe('宽度'),
height: z.number().positive('高度必须为正数').describe('高度'),
})
export type Size = z.infer<typeof sizeSchema>
/**
* 边界框 Schema
*/
export const boundingBoxSchema = z.object({
x: z.number(),
y: z.number(),
width: z.number().positive(),
height: z.number().positive(),
})
export type BoundingBox = z.infer<typeof boundingBoxSchema>
// ============================================
// 样式类型
// ============================================
/**
* 边框样式 Schema
*/
export const borderStyleSchema = z.object({
color: z.string().optional().describe('边框颜色'),
width: z.number().nonnegative().optional().describe('边框宽度'),
style: z.enum(['solid', 'dashed', 'dotted', 'none']).optional().describe('边框样式'),
radius: z.number().nonnegative().optional().describe('圆角半径'),
})
export type BorderStyle = z.infer<typeof borderStyleSchema>
/**
* 填充样式 Schema
*/
export const fillStyleSchema = z.object({
color: z.string().optional().describe('填充颜色'),
opacity: z.number().min(0).max(1).optional().describe('不透明度'),
gradient: z
.object({
type: z.enum(['linear', 'radial']),
colors: z.array(z.string()),
stops: z.array(z.number().min(0).max(1)).optional(),
angle: z.number().optional(),
})
.optional()
.describe('渐变填充'),
image: z
.object({
src: z.string().describe('图片数据base64'),
fit: z.enum(['cover', 'contain', 'fill']).default('cover').describe('缩放方式'),
opacity: z.number().min(0).max(1).default(1).describe('图片透明度'),
})
.optional()
.describe('图片填充'),
})
export type FillStyle = z.infer<typeof fillStyleSchema>
/**
* 文本样式 Schema
*/
export const textStyleSchema = z.object({
fontSize: z.number().positive().optional().describe('字号'),
fontWeight: z.union([z.number(), z.enum(['normal', 'bold', 'lighter', 'bolder'])]).optional().describe('字重'),
fontStyle: z.enum(['normal', 'italic']).optional().describe('字体样式'),
textDecoration: z.enum(['none', 'underline']).optional().describe('文本装饰'),
color: z.string().optional().describe('文本颜色'),
align: z.enum(['left', 'center', 'right']).optional().describe('水平对齐'),
verticalAlign: z.enum(['top', 'middle', 'bottom']).optional().describe('垂直对齐'),
})
export type TextStyle = z.infer<typeof textStyleSchema>
/**
* 阴影样式 Schema
*/
export const shadowStyleSchema = z.object({
color: z.string().optional().describe('阴影颜色'),
blur: z.number().nonnegative().optional().describe('模糊半径'),
offsetX: z.number().optional().describe('X 偏移'),
offsetY: z.number().optional().describe('Y 偏移'),
opacity: z.number().min(0).max(1).optional().describe('阴影透明度'),
inset: z.boolean().optional().describe('是否内阴影'),
enabled: z.boolean().optional().describe('是否启用'),
})
export type ShadowStyle = z.infer<typeof shadowStyleSchema>
/**
* 文本阴影样式 Schema
*/
export const textShadowStyleSchema = z.object({
color: z.string().optional().describe('文本阴影颜色'),
blur: z.number().nonnegative().optional().describe('模糊半径'),
offsetX: z.number().optional().describe('X 偏移'),
offsetY: z.number().optional().describe('Y 偏移'),
enabled: z.boolean().optional().describe('是否启用'),
})
export type TextShadowStyle = z.infer<typeof textShadowStyleSchema>
/**
* 组件通用样式 Schema
*/
export const componentStyleSchema = z.object({
fill: fillStyleSchema.optional(),
border: borderStyleSchema.optional(),
text: textStyleSchema.optional(),
shadow: shadowStyleSchema.optional(),
textShadow: textShadowStyleSchema.optional(),
})
export type ComponentStyle = z.infer<typeof componentStyleSchema>
// ============================================
// 条件样式
// ============================================
/**
* 条件样式覆盖属性(扁平结构,与 runtime 对齐)
*/
export const conditionalStyleOverrideSchema = z.record(z.string(), z.unknown())
.describe('条件样式覆盖属性(扁平 key-value')
/**
* 条件样式规则 Schema
*/
export const conditionalStyleSchema = z.object({
id: z.string().describe('规则 ID'),
name: z.string().optional().describe('规则名称'),
condition: z.string().describe('条件表达式'),
style: conditionalStyleOverrideSchema.describe('满足条件时应用的样式'),
priority: z.number().int().default(0).describe('优先级,数值越大优先级越高'),
enabled: z.boolean().optional().describe('是否启用该条件样式'),
})
export type ConditionalStyle = z.infer<typeof conditionalStyleSchema>

View File

@@ -0,0 +1,165 @@
/**
* 画布 Schema
* 定义画布主结构、背景、连接线等
*/
import { z } from 'zod'
import { canvasComponentSchema } from './component'
import { variableSchema } from './variable'
// 安全的 UUID 生成(兼容非安全上下文)
function generateUUID(): string {
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
return crypto.randomUUID()
}
// eslint-disable-next-line e18e/prefer-static-regex
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
})
}
// ============================================
// 背景配置
// ============================================
/**
* 背景类型
*/
export const backgroundTypeSchema = z.enum(['image', 'color', 'gradient', 'none'])
export type BackgroundType = z.infer<typeof backgroundTypeSchema>
/**
* 背景配置 Schema
*/
export const backgroundSchema = z.object({
type: backgroundTypeSchema.describe('背景类型'),
color: z.string().optional().describe('背景颜色'),
src: z.string().optional().describe('背景图片源'),
width: z.number().positive().optional().describe('背景逻辑宽度'),
height: z.number().positive().optional().describe('背景逻辑高度'),
lockAspectRatio: z.boolean().optional().describe('是否锁定宽高比'),
repeat: z.enum(['no-repeat', 'repeat', 'repeat-x', 'repeat-y']).optional().describe('重复方式'),
position: z.enum(['center', 'top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right']).optional().describe('位置'),
size: z.enum(['cover', 'contain', 'auto']).optional().describe('尺寸模式'),
})
export type Background = z.infer<typeof backgroundSchema>
// ============================================
// 视口配置
// ============================================
/**
* 视口配置 Schema
*/
export const viewportSchema = z.object({
x: z.number().default(0).describe('X 偏移'),
y: z.number().default(0).describe('Y 偏移'),
zoom: z.number().min(0.1).max(5).default(1).describe('缩放比例'),
})
export type Viewport = z.infer<typeof viewportSchema>
// ============================================
// 画布主结构
// ============================================
/**
* 画布类型
*/
export const canvasTypeSchema = z.enum(['canvas', 'svg', 'custom'])
export type CanvasType = z.infer<typeof canvasTypeSchema>
/**
* 画布主题
*/
export const canvasThemeSchema = z.enum([
'industrial-dark', // 工业深蓝
'industrial-light', // 工业浅色
'classic', // 经典
'modern', // 现代
'custom', // 自定义
])
export type CanvasTheme = z.infer<typeof canvasThemeSchema>
/**
* 画布样式 Schema
*/
export const canvasStyleSchema = z.object({
background: backgroundSchema.optional().describe('背景配置'),
})
export type CanvasStyle = z.infer<typeof canvasStyleSchema>
/**
* 画布配置 Schema
*/
export const canvasConfigSchema = z.object({
viewport: viewportSchema.optional().describe('视口配置'),
style: canvasStyleSchema.optional().describe('画布样式'),
})
export type CanvasConfig = z.infer<typeof canvasConfigSchema>
/**
* 画布 Schema
*/
export const canvasSchema = z.object({
id: z.string().uuid().describe('画布唯一标识'),
name: z.string().min(1, '画布名不能为空').describe('画布名称'),
type: canvasTypeSchema.default('canvas').describe('画布类型'),
version: z.string().regex(/^\d+\.\d+\.\d+$/, '版本号格式应为 x.x.x').describe('版本号'),
width: z.number().positive().default(1920).describe('画布宽度'),
height: z.number().positive().default(1080).describe('画布高度'),
theme: canvasThemeSchema.optional().describe('主题'),
config: canvasConfigSchema.optional().describe('画布配置'),
components: z.array(canvasComponentSchema).describe('组件列表'),
vars: z.array(variableSchema).optional().describe('变量列表'),
})
export type Canvas = z.infer<typeof canvasSchema>
// ============================================
// 画布验证工具
// ============================================
/**
* 验证画布数据
*/
export function validateCanvas(data: unknown): Canvas {
return canvasSchema.parse(data)
}
/**
* 安全验证画布数据
*/
export function safeParseCanvas(data: unknown) {
return canvasSchema.safeParse(data)
}
/**
* 创建空画布
*/
export function createEmptyCanvas(options: {
id?: string
name?: string
width?: number
height?: number
} = {}): Canvas {
return {
id: options.id || generateUUID(),
name: options.name || '新建画布',
type: 'canvas',
version: '1.0.0',
width: options.width || 1920,
height: options.height || 1080,
config: {
viewport: { x: 0, y: 0, zoom: 1 },
style: { background: { type: 'color', color: '#1a2634' } },
},
components: [],
}
}

View File

@@ -0,0 +1,304 @@
/**
* 组件类型 Schema
* 定义各种画布组件的数据结构
*/
import { z } from 'zod'
import {
componentStyleSchema,
conditionalStyleSchema,
positionSchema,
sizeSchema,
} from './base'
import { componentBindingsSchema } from './variable'
// ============================================
// 组件类型枚举
// ============================================
/**
* 组件类型
*/
export const componentTypeSchema = z.enum([
'rect',
'number',
'text',
'bar',
'button',
'pidController',
'valveController',
'canvasSwitcher',
'custom',
])
export type ComponentType = z.infer<typeof componentTypeSchema>
// ============================================
// 组件基础 Schema
// ============================================
/**
* 组件事件触发类型
*/
export const componentEventTriggerSchema = z.enum([
'click',
'dblclick',
])
export type ComponentEventTrigger = z.infer<typeof componentEventTriggerSchema>
/**
* 组件事件动作 Schema
*/
export const componentEventActionSchema = z.object({
type: z.string().min(1).describe('动作类型'),
payload: z.record(z.unknown()).optional().describe('动作参数'),
})
export type ComponentEventAction = z.infer<typeof componentEventActionSchema>
/**
* 组件事件 Schema
*/
export const componentEventSchema = z.object({
id: z.string().min(1).describe('事件 ID'),
trigger: componentEventTriggerSchema.describe('触发类型'),
condition: z.string().optional().describe('触发条件'),
action: componentEventActionSchema.describe('动作'),
})
export type ComponentEvent = z.infer<typeof componentEventSchema>
/**
* 组件基础属性 Schema
*/
export const componentBaseSchema = z.object({
id: z.string().uuid().describe('组件唯一标识'),
type: componentTypeSchema.describe('组件类型'),
name: z.string().min(1, '组件名不能为空').describe('组件名称/位号'),
position: positionSchema.describe('组件位置'),
size: sizeSchema.describe('组件尺寸'),
zIndex: z.number().int().default(0).describe('层级'),
opacity: z.number().min(0).max(1).default(1).describe('不透明度'),
visible: z.boolean().default(true).describe('是否可见'),
tagNumber: z.string().optional().describe('设备位号'),
bindings: componentBindingsSchema.optional().describe('变量绑定'),
style: componentStyleSchema.optional().describe('样式'),
conditionalStyles: z.array(conditionalStyleSchema).optional().describe('条件样式'),
events: z.array(componentEventSchema).optional().describe('事件列表'),
metadata: z.record(z.unknown()).optional().describe('元数据'),
})
export type ComponentBase = z.infer<typeof componentBaseSchema>
// ============================================
// 具体组件类型
// ============================================
/**
* 矩形组件配置
*/
export const rectComponentConfigSchema = z.object({
fillColor: z.string().optional().describe('填充颜色'),
})
export type RectComponentConfig = z.infer<typeof rectComponentConfigSchema>
/**
* 矩形组件 Schema
*/
export const rectComponentSchema = componentBaseSchema.extend({
type: z.literal('rect'),
config: rectComponentConfigSchema.optional(),
})
export type RectComponent = z.infer<typeof rectComponentSchema>
/**
* 数字组件配置
*/
export const numberComponentConfigSchema = z.object({
fillColor: z.string().optional().describe('填充颜色'),
textColor: z.string().optional().describe('文字颜色'),
decimals: z.number().int().min(0).max(10).default(2).describe('小数位数'),
prefix: z.string().optional().describe('前缀'),
suffix: z.string().optional().describe('后缀/单位'),
thousandsSeparator: z.boolean().default(false).describe('千分位分隔'),
showTrend: z.boolean().default(false).describe('显示趋势箭头'),
})
export type NumberComponentConfig = z.infer<typeof numberComponentConfigSchema>
/**
* 数字组件 Schema
*/
export const numberComponentSchema = componentBaseSchema.extend({
type: z.literal('number'),
config: numberComponentConfigSchema.optional(),
})
export type NumberComponent = z.infer<typeof numberComponentSchema>
/**
* 文本组件配置
*/
export const textComponentConfigSchema = z.object({
fillColor: z.string().optional().describe('填充颜色'),
textColor: z.string().optional().describe('文字颜色'),
content: z.string().default('').describe('静态文本内容'),
isDynamic: z.boolean().default(false).describe('是否动态文本'),
expression: z.string().optional().describe('动态表达式'),
})
export type TextComponentConfig = z.infer<typeof textComponentConfigSchema>
/**
* 文本组件 Schema
*/
export const textComponentSchema = componentBaseSchema.extend({
type: z.literal('text'),
config: textComponentConfigSchema.optional(),
})
export type TextComponent = z.infer<typeof textComponentSchema>
/**
* 棒图组件配置
*/
export const barComponentConfigSchema = z.object({
value: z.number().optional().describe('当前值'),
direction: z.enum(['horizontal', 'vertical']).default('vertical').describe('方向'),
min: z.number().default(0).describe('最小值'),
max: z.number().default(100).describe('最大值'),
showValue: z.boolean().default(true).describe('显示数值'),
foregroundColor: z.string().default('#00ff00').describe('前景色(填充条颜色)'),
valueColor: z.string().default('#ffffff').describe('数值文字颜色'),
valueFontSize: z.number().positive().default(14).describe('数值字号'),
valueFontWeight: z.union([z.number(), z.enum(['normal', 'bold'])]).default(600).describe('数值字重'),
valueContent: z.string().optional().describe('数值显示内容(为空时显示百分比)'),
colors: z.array(z.object({
threshold: z.number(),
color: z.string(),
})).optional().describe('阈值颜色(覆盖前景色)'),
})
export type BarComponentConfig = z.infer<typeof barComponentConfigSchema>
/**
* 棒图组件 Schema
*/
export const barComponentSchema = componentBaseSchema.extend({
type: z.literal('bar'),
config: barComponentConfigSchema.optional(),
})
export type BarComponent = z.infer<typeof barComponentSchema>
/**
* 按钮组件配置
*/
export const buttonComponentConfigSchema = z.object({
label: z.string().default('按钮').describe('按钮文字'),
buttonType: z.enum(['trigger', 'toggle', 'navigate']).default('trigger').describe('按钮类型'),
targetMethod: z.string().optional().describe('触发的方法名'),
targetCanvas: z.string().optional().describe('导航目标画布 ID'),
confirmRequired: z.boolean().default(false).describe('是否需要确认'),
confirmMessage: z.string().optional().describe('确认提示文字'),
})
export type ButtonComponentConfig = z.infer<typeof buttonComponentConfigSchema>
/**
* 按钮组件 Schema
*/
export const buttonComponentSchema = componentBaseSchema.extend({
type: z.literal('button'),
config: buttonComponentConfigSchema.optional(),
})
export type ButtonComponent = z.infer<typeof buttonComponentSchema>
/**
* 控制仪表组件配置
*/
export const pidControllerComponentConfigSchema = z.object({
headerColor: z.string().default('#0055ff').describe('标题栏背景色'),
labelColor: z.string().default('#00cc00').describe('字段标签颜色'),
valueColor: z.string().default('#ffffff').describe('数值颜色'),
bgColor: z.string().default('#1a1a2e').describe('面板背景色'),
decimalPlaces: z.number().int().min(0).max(6).default(2).describe('PV/SV 小数位数'),
opDecimalPlaces: z.number().int().min(0).max(6).default(2).describe('OP 小数位数'),
description: z.string().default('').describe('设备描述'),
})
export type PidControllerComponentConfig = z.infer<typeof pidControllerComponentConfigSchema>
export const pidControllerComponentSchema = componentBaseSchema.extend({
type: z.literal('pidController'),
config: pidControllerComponentConfigSchema.optional(),
})
export type PidControllerComponent = z.infer<typeof pidControllerComponentSchema>
/**
* 阀门控制器组件配置
*/
export const valveControllerComponentConfigSchema = z.object({
bgColor: z.string().default('transparent').describe('背景色'),
textColor: z.string().default('#ffffff').describe('文字颜色'),
decimalPlaces: z.number().int().min(0).max(6).default(2).describe('OP 小数位数'),
description: z.string().default('').describe('设备描述'),
})
export type ValveControllerComponentConfig = z.infer<typeof valveControllerComponentConfigSchema>
export const valveControllerComponentSchema = componentBaseSchema.extend({
type: z.literal('valveController'),
config: valveControllerComponentConfigSchema.optional(),
})
export type ValveControllerComponent = z.infer<typeof valveControllerComponentSchema>
/**
* 切页按钮组件配置
*/
export const canvasSwitcherComponentConfigSchema = z.object({
items: z.array(z.object({
canvasId: z.string().describe('目标画布 ID'),
})).default([]).describe('切换项列表'),
activeColor: z.string().default('#E1E1E1').describe('激活项背景色'),
inactiveColor: z.string().default('#C8C8C8').describe('未激活项背景色'),
activeTextColor: z.string().default('#333333').describe('激活项文字颜色'),
inactiveTextColor: z.string().default('#333333').describe('未激活项文字颜色'),
})
export type CanvasSwitcherComponentConfig = z.infer<typeof canvasSwitcherComponentConfigSchema>
export const canvasSwitcherComponentSchema = componentBaseSchema.extend({
type: z.literal('canvasSwitcher'),
config: canvasSwitcherComponentConfigSchema.optional(),
})
export type CanvasSwitcherComponent = z.infer<typeof canvasSwitcherComponentSchema>
// ============================================
// 联合类型
// ============================================
/**
* 画布组件联合类型 Schema
*/
export const canvasComponentSchema: z.ZodType<ComponentBase> = z.lazy(() =>
z.discriminatedUnion('type', [
rectComponentSchema,
numberComponentSchema,
textComponentSchema,
barComponentSchema,
buttonComponentSchema,
pidControllerComponentSchema,
valveControllerComponentSchema,
canvasSwitcherComponentSchema,
componentBaseSchema.extend({ type: z.literal('custom') }),
]),
) as z.ZodType<ComponentBase>
export type CanvasComponent = z.infer<typeof canvasComponentSchema>

View File

@@ -0,0 +1,18 @@
/**
* 画布 Schema 统一导出
*/
// 基础类型
export * from './base'
// 画布主结构
export * from './canvas'
// 组件类型
export * from './component'
// 图层数据模型
export * from './layer'
// 变量绑定
export * from './variable'

View File

@@ -0,0 +1,72 @@
import type { ComponentEvent, ComponentType } from './component'
/**
* 图层数据模型
* 编辑器运行时使用的图层类型定义
*/
import type { BindingExpression } from './variable'
/**
* 图层绑定定义BindingExpression 的别名,保持向后兼容)
*/
export type LayerBindingDefinition = BindingExpression
/**
* 图层绑定值:支持简单字符串绑定、复杂绑定、或多条规则
*/
export type LayerBindingValue = string | LayerBindingDefinition | Array<string | LayerBindingDefinition>
/**
* 图层
*/
export interface Layer {
id: string
type: ComponentType
x: number
y: number
width: number
height: number
config?: Record<string, any>
style?: Record<string, any>
bindings?: Record<string, LayerBindingValue>
tagNumber?: string
conditionalStyles?: Array<{
id: string
condition: string
style: Record<string, any>
priority?: number
enabled?: boolean
}>
events?: ComponentEvent[]
}
/**
* 图层分组
*/
export interface LayerGroup {
groupId: string
groupName: string
layerIds: string[]
layers: Layer[]
x: number
y: number
width: number
height: number
}
/**
* 画布视图状态
*/
export interface CanvasView {
canvasWidth: number
canvasHeight: number
imageWidth: number
imageHeight: number
imageNaturalWidth: number
imageNaturalHeight: number
backgroundLockAspectRatio: boolean
backgroundAverageColor: string | null
offsetX: number
offsetY: number
scale: number
background: string
}

View File

@@ -0,0 +1,88 @@
/**
* 变量绑定 Schema
* 定义变量和绑定相关类型
*/
import { z } from 'zod'
// ============================================
// 变量类型
// ============================================
/**
* 变量值类型
*/
export const variableValueTypeSchema = z.enum([
'analog', // 模拟量(数值)
'digital', // 数字量(布尔)
'string', // 字符串
'enum', // 枚举
])
export type VariableValueType = z.infer<typeof variableValueTypeSchema>
/**
* 变量定义 Schema
*/
export const variableSchema = z.object({
name: z.string().min(1, '变量名不能为空').describe('变量名'),
type: variableValueTypeSchema.describe('变量类型'),
description: z.string().optional().describe('变量描述'),
unit: z.string().optional().describe('单位'),
source: z.enum(['dynamic_project', 'manual', 'calculated']).optional().describe('数据来源'),
sourceId: z.string().optional().describe('来源 ID'),
defaultValue: z.unknown().optional().describe('默认值'),
})
export type Variable = z.infer<typeof variableSchema>
// ============================================
// 变量绑定
// ============================================
/**
* 绑定表达式 Schema
* 支持直接绑定变量名或计算表达式
*/
export const bindingExpressionSchema = z.object({
id: z.string().optional().describe('绑定规则 ID'),
type: z.enum(['variable', 'expression']).describe('绑定类型'),
value: z.string().describe('变量名或表达式'),
variables: z.array(z.string()).optional().describe('表达式中引用的变量列表'),
priority: z.number().optional().describe('权重,数值越大优先级越高'),
enabled: z.boolean().optional().describe('是否启用该绑定规则'),
})
export type BindingExpression = z.infer<typeof bindingExpressionSchema>
/**
* 组件变量绑定 Schema
*/
export const componentBindingsSchema = z.record(
z.string(), // 绑定属性名
z.union([
z.string(), // 简单绑定:直接使用变量名
bindingExpressionSchema, // 复杂绑定:表达式
z.array(z.union([
z.string(),
bindingExpressionSchema,
])).describe('同一属性的多条绑定规则'),
]),
)
export type ComponentBindings = z.infer<typeof componentBindingsSchema>
// ============================================
// 变量值(运行时)
// ============================================
/**
* 变量快照 Schema
*/
export const variableSnapshotSchema = z.object({
name: z.string(),
value: z.unknown(),
quality: z.enum(['good', 'bad', 'uncertain']).optional(),
timestamp: z.string().datetime().optional(),
})
export type VariableSnapshot = z.infer<typeof variableSnapshotSchema>

View File

@@ -0,0 +1,99 @@
/**
* DCS 配置相关类型和验证器
*/
import { z } from 'zod'
/**
* DCS 配置项类型
*/
export type DCSValueType = 'string' | 'number' | 'boolean' | 'array' | 'object'
/**
* DCS 配置项
*/
export interface DCSConfigItem {
key: string
value: unknown
type: DCSValueType
description?: string
required?: boolean
children?: DCSConfigItem[]
}
/**
* DCS 配置文件
*/
export interface DCSConfigFile {
name: string
path: string
version: string
description?: string
items: DCSConfigItem[]
createdAt: string
updatedAt: string
}
/**
* DCS 项目
*/
export interface DCSProject {
id: string
name: string
description?: string
configs: DCSConfigFile[]
createdAt: string
updatedAt: string
}
/**
* DCS 配置项验证器
*/
export const dcsConfigItemSchema: z.ZodType<DCSConfigItem> = z.lazy(() =>
z.object({
key: z.string().min(1, '配置键不能为空'),
value: z.unknown(),
type: z.enum(['string', 'number', 'boolean', 'array', 'object']),
description: z.string().optional(),
required: z.boolean().optional(),
children: z.array(dcsConfigItemSchema).optional(),
}),
) as z.ZodType<DCSConfigItem>
/**
* DCS 配置文件验证器
*/
export const dcsConfigFileSchema = z.object({
name: z.string().min(1, '文件名不能为空'),
path: z.string().min(1, '文件路径不能为空'),
version: z.string().regex(/^\d+\.\d+\.\d+$/, '版本号格式应为 x.x.x'),
description: z.string().optional(),
items: z.array(dcsConfigItemSchema),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
})
/**
* DCS 项目验证器
*/
export const dcsProjectSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1, '项目名不能为空'),
description: z.string().optional(),
configs: z.array(dcsConfigFileSchema),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
})
/**
* 验证 DCS 配置
*/
export function validateDCSConfig(data: unknown): DCSConfigFile {
return dcsConfigFileSchema.parse(data)
}
/**
* 安全验证 DCS 配置
*/
export function safeParseDCSConfig(data: unknown) {
return dcsConfigFileSchema.safeParse(data)
}

View File

@@ -0,0 +1,9 @@
/**
* @cslab-dcs/schema
* 数据模型和验证器统一导出
*/
export * from './canvas'
export * from './dcs'
export * from './types'
export * from './validators'

View File

@@ -0,0 +1,81 @@
/**
* 通用类型定义
*/
/**
* API 响应结构
*/
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
success: boolean
}
/**
* 分页请求参数
*/
export interface PaginationParams {
page: number
pageSize: number
}
/**
* 分页响应结构
*/
export interface PaginatedData<T> {
list: T[]
total: number
page: number
pageSize: number
totalPages: number
}
/**
* 树形结构节点
*/
export interface TreeNode<T = unknown> {
id: string
label: string
children?: TreeNode<T>[]
data?: T
disabled?: boolean
isLeaf?: boolean
}
/**
* 键值对
*/
export interface KeyValue<T = string> {
key: string
value: T
}
/**
* 选项结构
*/
export interface SelectOption<T = string | number> {
label: string
value: T
disabled?: boolean
}
/**
* 用户信息
*/
export interface UserInfo {
id: string
username: string
nickname?: string
avatar?: string
email?: string
}
/**
* 应用状态
*/
export interface AppState {
theme: 'light' | 'dark' | 'system'
language: string
sidebarCollapsed: boolean
}

View File

@@ -0,0 +1,57 @@
/**
* Zod 验证器
*/
import { z } from 'zod'
/**
* 通用验证规则
*/
export const requiredString = z.string().min(1, '此字段为必填项')
export const optionalString = z.string().optional()
export const email = z.string().email('请输入有效的邮箱地址')
export const phone = z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号')
export const url = z.string().url('请输入有效的 URL')
export const positiveNumber = z.number().positive('请输入正数')
export const nonNegativeNumber = z.number().nonnegative('请输入非负数')
/**
* API 响应验证
*/
export const apiResponseSchema = z.object({
code: z.number(),
message: z.string(),
data: z.unknown(),
success: z.boolean(),
})
/**
* 分页参数验证
*/
export const paginationSchema = z.object({
page: z.number().int().min(1).default(1),
pageSize: z.number().int().min(1).max(100).default(20),
})
/**
* 用户信息验证
*/
export const userInfoSchema = z.object({
id: z.string(),
username: z.string().min(2, '用户名至少2个字符'),
nickname: z.string().optional(),
avatar: z.string().url().optional(),
email: z.string().email().optional(),
})
/**
* 创建可选字段的 schema
*/
export function createPartialSchema<T extends z.ZodRawShape>(schema: z.ZodObject<T>) {
return schema.partial()
}
/**
* 重新导出 zod
*/
export { z }
export type { ZodError, ZodSchema, ZodType } from 'zod'

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}