新增: Rust 数据模型 + 颜色纯函数 + ts-rs 类型同步(B1)
- model.rs: 全部数据结构(Deck/Slide/Element/Style/Theme/AiCfg/LibItem/PageTemplate/ChatMessage/Op/RichSegment) 所有结构体 derive Serialize/Deserialize/TS,自动生成 TS 类型到 bindings/types.ts - color.rs: 颜色解析纯函数(resolve_color/resolve_bg/is_dark_bg/shade/hex_to_rgb) 对应 store.ts 的颜色逻辑,23 个单元测试全部通过 - lib.rs: 新增 app_info IPC 命令 - ts-rs 自动生成的 bindings/types.ts 与手写 types.ts 类型一致
This commit is contained in:
307
src-tauri/src/model.rs
Normal file
307
src-tauri/src/model.rs
Normal file
@@ -0,0 +1,307 @@
|
||||
/* =====================================================================
|
||||
* model.rs — 数据模型(对应 src/core/types.ts)
|
||||
*
|
||||
* 所有结构体 derive Serialize/Deserialize/TS,确保:
|
||||
* - serde: JSON 序列化(Rust ↔ IPC ↔ JS)
|
||||
* - ts-rs: 自动生成 TypeScript 类型定义(Rust ↔ TS 类型同步)
|
||||
*
|
||||
* 运行 `cargo test --features export-types` 会在 ../bindings/ 生成 .ts 文件。
|
||||
* ===================================================================== */
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
/* ---------- 颜色 / 背景 / 动画 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub enum ChartType {
|
||||
#[serde(rename = "bar")]
|
||||
Bar,
|
||||
#[serde(rename = "line")]
|
||||
Line,
|
||||
#[serde(rename = "pie")]
|
||||
Pie,
|
||||
#[serde(rename = "area")]
|
||||
Area,
|
||||
#[serde(rename = "doughnut")]
|
||||
Doughnut,
|
||||
#[serde(rename = "radar")]
|
||||
Radar,
|
||||
#[serde(rename = "hbar")]
|
||||
Hbar,
|
||||
#[serde(rename = "progress")]
|
||||
Progress,
|
||||
}
|
||||
|
||||
impl Default for ChartType {
|
||||
fn default() -> Self {
|
||||
ChartType::Bar
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub enum ShapeType {
|
||||
#[serde(rename = "rect")]
|
||||
Rect,
|
||||
#[serde(rename = "circle")]
|
||||
Circle,
|
||||
#[serde(rename = "triangle")]
|
||||
Triangle,
|
||||
}
|
||||
|
||||
impl Default for ShapeType {
|
||||
fn default() -> Self {
|
||||
ShapeType::Rect
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub enum AnimType {
|
||||
#[serde(rename = "fade-up")]
|
||||
FadeUp,
|
||||
#[serde(rename = "fade")]
|
||||
Fade,
|
||||
#[serde(rename = "scale")]
|
||||
Scale,
|
||||
#[serde(rename = "zoom")]
|
||||
Zoom,
|
||||
#[serde(rename = "slide-l")]
|
||||
SlideL,
|
||||
#[serde(rename = "slide-r")]
|
||||
SlideR,
|
||||
#[serde(rename = "pop")]
|
||||
Pop,
|
||||
#[serde(rename = "rotate")]
|
||||
Rotate,
|
||||
#[serde(rename = "bounce")]
|
||||
Bounce,
|
||||
#[serde(rename = "flip")]
|
||||
Flip,
|
||||
#[serde(rename = "blur")]
|
||||
Blur,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub enum ElementType {
|
||||
#[serde(rename = "title")]
|
||||
Title,
|
||||
#[serde(rename = "text")]
|
||||
Text,
|
||||
#[serde(rename = "list")]
|
||||
List,
|
||||
#[serde(rename = "stat")]
|
||||
Stat,
|
||||
#[serde(rename = "quote")]
|
||||
Quote,
|
||||
#[serde(rename = "image")]
|
||||
Image,
|
||||
#[serde(rename = "shape")]
|
||||
Shape,
|
||||
#[serde(rename = "chart")]
|
||||
Chart,
|
||||
#[serde(rename = "card")]
|
||||
Card,
|
||||
#[serde(rename = "table")]
|
||||
Table,
|
||||
#[serde(rename = "code")]
|
||||
Code,
|
||||
#[serde(rename = "formula")]
|
||||
Formula,
|
||||
}
|
||||
|
||||
/* ---------- 主题 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct Theme {
|
||||
pub name: String,
|
||||
pub primary: String,
|
||||
pub accent: String,
|
||||
pub bg: String,
|
||||
pub panel: String,
|
||||
pub text: String,
|
||||
pub muted: String,
|
||||
}
|
||||
|
||||
/* ---------- 富文本 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct RichSegment {
|
||||
pub text: String,
|
||||
pub bold: Option<bool>,
|
||||
pub italic: Option<bool>,
|
||||
pub underline: Option<bool>,
|
||||
pub strike: Option<bool>,
|
||||
pub color: Option<String>,
|
||||
pub highlight: Option<bool>,
|
||||
pub code: Option<bool>,
|
||||
#[serde(rename = "sup")]
|
||||
pub sup_: Option<bool>,
|
||||
#[serde(rename = "sub")]
|
||||
pub sub_: Option<bool>,
|
||||
pub font_size: Option<f64>,
|
||||
pub link: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct RichLine {
|
||||
pub segments: Vec<RichSegment>,
|
||||
}
|
||||
|
||||
/* ---------- 元素 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct ElementStyle {
|
||||
pub font_size: Option<f64>,
|
||||
pub color: Option<String>,
|
||||
pub align: Option<String>,
|
||||
pub bold: Option<bool>,
|
||||
pub italic: Option<bool>,
|
||||
pub anim: Option<String>,
|
||||
// stat
|
||||
pub label: Option<String>,
|
||||
pub label_color: Option<String>,
|
||||
pub label_size: Option<f64>,
|
||||
// image
|
||||
pub fit: Option<String>,
|
||||
// shape
|
||||
pub shape_type: Option<ShapeType>,
|
||||
pub fill: Option<String>,
|
||||
pub gradient: Option<bool>,
|
||||
pub opacity: Option<f64>,
|
||||
pub radius: Option<f64>,
|
||||
// chart
|
||||
pub max: Option<f64>,
|
||||
pub chart_type: Option<ChartType>,
|
||||
pub legend: Option<bool>,
|
||||
pub stack: Option<bool>,
|
||||
pub grid: Option<bool>,
|
||||
// table
|
||||
pub header: Option<bool>,
|
||||
// code
|
||||
pub lang: Option<String>,
|
||||
// formula
|
||||
#[serde(rename = "inline")]
|
||||
pub inline_: Option<bool>,
|
||||
// card
|
||||
pub icon: Option<String>,
|
||||
pub accent: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct SlideElement {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub element_type: String,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub w: f64,
|
||||
pub h: f64,
|
||||
pub content: String,
|
||||
pub style: ElementStyle,
|
||||
pub segments: Option<Vec<RichLine>>,
|
||||
}
|
||||
|
||||
/* ---------- 幻灯片 / Deck ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct Slide {
|
||||
pub id: String,
|
||||
pub background: String,
|
||||
pub elements: Vec<SlideElement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct Deck {
|
||||
pub v: i32,
|
||||
pub theme: String,
|
||||
pub slides: Vec<Slide>,
|
||||
pub chat_id: Option<String>,
|
||||
}
|
||||
|
||||
/* ---------- AI 配置 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct AiCfg {
|
||||
pub preset: String,
|
||||
pub protocol: String,
|
||||
pub base: String,
|
||||
pub key: String,
|
||||
pub model: String,
|
||||
pub proxy: String,
|
||||
pub img_base: Option<String>,
|
||||
pub img_key: Option<String>,
|
||||
pub img_model: Option<String>,
|
||||
}
|
||||
|
||||
/* ---------- 文库 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct LibItem {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub deck: Deck,
|
||||
pub created_at: f64,
|
||||
pub updated_at: f64,
|
||||
}
|
||||
|
||||
/* ---------- 模板 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct PageTemplate {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
pub background: String,
|
||||
pub elements: Vec<SlideElement>,
|
||||
pub created_at: Option<f64>,
|
||||
}
|
||||
|
||||
/* ---------- 会话 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct ChatMessage {
|
||||
pub role: String,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
/* ---------- Op Log(对应 src/core/op.ts) ---------- */
|
||||
|
||||
/// Op 的 payload 是任意 JSON(不导出 TS,因为 payload 是动态的)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Op {
|
||||
#[serde(rename = "type")]
|
||||
pub op_type: String,
|
||||
pub client_id: Option<String>,
|
||||
pub timestamp: Option<f64>,
|
||||
#[serde(flatten)]
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HistoryEntry {
|
||||
pub forward: Op,
|
||||
pub backward: Op,
|
||||
}
|
||||
|
||||
/* ---------- 图表数据 ---------- */
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "../bindings/types.ts")]
|
||||
pub struct ChartItem {
|
||||
pub label: Option<String>,
|
||||
pub value: Option<f64>,
|
||||
}
|
||||
Reference in New Issue
Block a user