all
This commit is contained in:
27
packages/schema/package.json
Normal file
27
packages/schema/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
164
packages/schema/src/canvas/base.ts
Normal file
164
packages/schema/src/canvas/base.ts
Normal 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>
|
||||
165
packages/schema/src/canvas/canvas.ts
Normal file
165
packages/schema/src/canvas/canvas.ts
Normal 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: [],
|
||||
}
|
||||
}
|
||||
304
packages/schema/src/canvas/component.ts
Normal file
304
packages/schema/src/canvas/component.ts
Normal 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>
|
||||
18
packages/schema/src/canvas/index.ts
Normal file
18
packages/schema/src/canvas/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 画布 Schema 统一导出
|
||||
*/
|
||||
|
||||
// 基础类型
|
||||
export * from './base'
|
||||
|
||||
// 画布主结构
|
||||
export * from './canvas'
|
||||
|
||||
// 组件类型
|
||||
export * from './component'
|
||||
|
||||
// 图层数据模型
|
||||
export * from './layer'
|
||||
|
||||
// 变量绑定
|
||||
export * from './variable'
|
||||
72
packages/schema/src/canvas/layer.ts
Normal file
72
packages/schema/src/canvas/layer.ts
Normal 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
|
||||
}
|
||||
88
packages/schema/src/canvas/variable.ts
Normal file
88
packages/schema/src/canvas/variable.ts
Normal 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>
|
||||
99
packages/schema/src/dcs.ts
Normal file
99
packages/schema/src/dcs.ts
Normal 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)
|
||||
}
|
||||
9
packages/schema/src/index.ts
Normal file
9
packages/schema/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @cslab-dcs/schema
|
||||
* 数据模型和验证器统一导出
|
||||
*/
|
||||
|
||||
export * from './canvas'
|
||||
export * from './dcs'
|
||||
export * from './types'
|
||||
export * from './validators'
|
||||
81
packages/schema/src/types.ts
Normal file
81
packages/schema/src/types.ts
Normal 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
|
||||
}
|
||||
57
packages/schema/src/validators.ts
Normal file
57
packages/schema/src/validators.ts
Normal 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'
|
||||
10
packages/schema/tsconfig.json
Normal file
10
packages/schema/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user