feat(flows): 新增流程编辑器基础功能与相关组件

feat(backend): 添加流程模型与服务支持
feat(frontend): 实现流程编辑器UI与交互
feat(assets): 添加流程节点图标资源
feat(plugins): 实现上下文菜单和运行时插件
feat(components): 新增基础节点和侧边栏组件
feat(routes): 添加流程相关路由配置
feat(models): 创建流程和运行日志数据模型
feat(services): 实现流程服务层逻辑
feat(migration): 添加流程相关数据库迁移
feat(config): 更新前端配置支持流程编辑器
feat(utils): 增强axios错误处理和工具函数
This commit is contained in:
2025-09-15 00:27:13 +08:00
parent 9da3978f91
commit b0963e5e37
291 changed files with 17947 additions and 86 deletions

View File

@ -11,6 +11,18 @@ mod m20220101_000008_add_keep_alive_to_menus;
mod m20220101_000009_create_request_logs;
// 新增岗位与用户岗位关联
mod m20220101_000010_create_positions;
// 占位:历史上已应用但缺失的工作流相关迁移
mod m20220101_000011_create_workflows;
mod m20220101_000012_create_workflow_executions;
mod m20220101_000013_create_workflow_execution_logs;
mod m20220101_000014_create_flows;
// 新增 flows 的 code 与 remark 列
mod m20220101_000015_add_code_and_remark_to_flows;
mod m20220101_000016_dedup_flows_code;
mod m20220101_000016_add_unique_index_to_flows_code;
mod m20220101_000017_create_flow_run_logs;
// 新增:为 flow_run_logs 添加 flow_code 列
mod m20220101_000018_add_flow_code_to_flow_run_logs;
pub struct Migrator;
@ -29,6 +41,20 @@ impl MigratorTrait for Migrator {
Box::new(m20220101_000009_create_request_logs::Migration),
// 注册岗位迁移
Box::new(m20220101_000010_create_positions::Migration),
// 占位:历史上已应用但缺失的工作流相关迁移
Box::new(m20220101_000011_create_workflows::Migration),
Box::new(m20220101_000012_create_workflow_executions::Migration),
Box::new(m20220101_000013_create_workflow_execution_logs::Migration),
// 新增 flows 表
Box::new(m20220101_000014_create_flows::Migration),
// 新增 flows 的 code 与 remark 列
Box::new(m20220101_000015_add_code_and_remark_to_flows::Migration),
// 先去重再建唯一索引
Box::new(m20220101_000016_dedup_flows_code::Migration),
Box::new(m20220101_000016_add_unique_index_to_flows_code::Migration),
Box::new(m20220101_000017_create_flow_run_logs::Migration),
// 新增:为 flow_run_logs 添加 flow_code 列
Box::new(m20220101_000018_add_flow_code_to_flow_run_logs::Migration),
]
}
}

View File

@ -24,9 +24,10 @@ impl MigrationTrait for Migration {
)
.await?;
// seed admin user (cross-DB)
// seed admin user for all DB backends
// NOTE: test default password is fixed to '123456' for local/dev testing
let salt = SaltString::generate(&mut rand::thread_rng());
let hash = Argon2::default().hash_password("Admin@123".as_bytes(), &salt).unwrap().to_string();
let hash = Argon2::default().hash_password("123456".as_bytes(), &salt).unwrap().to_string();
let backend = manager.get_database_backend();
let conn = manager.get_connection();
match backend {

View File

@ -0,0 +1,16 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// 占位迁移:历史上已应用,但当前代码库缺失实际文件
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
Ok(())
}
}

View File

@ -0,0 +1,16 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// 占位迁移:历史上已应用,但当前代码库缺失实际文件
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
Ok(())
}
}

View File

@ -0,0 +1,16 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// 占位迁移:历史上已应用,但当前代码库缺失实际文件
Ok(())
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
Ok(())
}
}

View File

@ -0,0 +1,40 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Flows::Table)
.if_not_exists()
.col(ColumnDef::new(Flows::Id).string_len(64).not_null().primary_key())
.col(ColumnDef::new(Flows::Name).string().null())
.col(ColumnDef::new(Flows::Yaml).text().null())
.col(ColumnDef::new(Flows::DesignJson).text().null())
.col(ColumnDef::new(Flows::CreatedAt).timestamp().not_null().default(Expr::current_timestamp()))
.col(ColumnDef::new(Flows::UpdatedAt).timestamp().not_null().default(Expr::current_timestamp()))
.to_owned()
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop().table(Flows::Table).to_owned()).await
}
}
#[derive(Iden)]
enum Flows {
Table,
Id,
Name,
Yaml,
DesignJson,
CreatedAt,
UpdatedAt,
}

View File

@ -0,0 +1,56 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// SQLite 不支持单条 ALTER 语句包含多个操作,拆分为两次执行
manager
.alter_table(
Table::alter()
.table(Flows::Table)
.add_column(ColumnDef::new(Flows::Code).string().null())
.to_owned()
)
.await?;
manager
.alter_table(
Table::alter()
.table(Flows::Table)
.add_column(ColumnDef::new(Flows::Remark).string().null())
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// 与 up 相反顺序,依然需要拆分为两次执行
manager
.alter_table(
Table::alter()
.table(Flows::Table)
.drop_column(Flows::Remark)
.to_owned()
)
.await?;
manager
.alter_table(
Table::alter()
.table(Flows::Table)
.drop_column(Flows::Code)
.to_owned()
)
.await
}
}
#[derive(DeriveIden)]
enum Flows {
Table,
Code,
Remark,
}

View File

@ -0,0 +1,37 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_index(
Index::create()
.name("idx-unique-flows-code")
.table(Flows::Table)
.col(Flows::Code)
.unique()
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_index(
Index::drop()
.name("idx-unique-flows-code")
.table(Flows::Table)
.to_owned(),
)
.await
}
}
#[derive(DeriveIden)]
enum Flows {
Table,
Code,
}

View File

@ -0,0 +1,65 @@
use sea_orm_migration::prelude::*;
use sea_orm_migration::sea_orm::Statement;
use sea_orm_migration::sea_orm::DatabaseBackend;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
match manager.get_database_backend() {
DatabaseBackend::MySql => {
// 将重复的 code 置为 NULL仅保留每组中 id 最小的一条
let sql = r#"
UPDATE flows f
JOIN (
SELECT code, MIN(id) AS min_id
FROM flows
WHERE code IS NOT NULL
GROUP BY code
HAVING COUNT(*) > 1
) t ON f.code = t.code AND f.id <> t.min_id
SET f.code = NULL;
"#;
db.execute(Statement::from_string(DatabaseBackend::MySql, sql.to_string())).await?;
Ok(())
}
DatabaseBackend::Postgres => {
let sql = r#"
WITH d AS (
SELECT id, ROW_NUMBER() OVER(PARTITION BY code ORDER BY id) AS rn
FROM flows
WHERE code IS NOT NULL
)
UPDATE flows AS f
SET code = NULL
FROM d
WHERE f.id = d.id AND d.rn > 1;
"#;
db.execute(Statement::from_string(DatabaseBackend::Postgres, sql.to_string())).await?;
Ok(())
}
DatabaseBackend::Sqlite => {
let sql = r#"
WITH d AS (
SELECT id, ROW_NUMBER() OVER(PARTITION BY code ORDER BY id) AS rn
FROM flows
WHERE code IS NOT NULL
)
UPDATE flows
SET code = NULL
WHERE id IN (SELECT id FROM d WHERE rn > 1);
"#;
db.execute(Statement::from_string(DatabaseBackend::Sqlite, sql.to_string())).await?;
Ok(())
}
}
}
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
// 数据清洗不可逆
Ok(())
}
}

View File

@ -0,0 +1,50 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(FlowRunLogs::Table)
.if_not_exists()
.col(ColumnDef::new(FlowRunLogs::Id).big_integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(FlowRunLogs::FlowId).string_len(64).not_null())
.col(ColumnDef::new(FlowRunLogs::Input).text().null())
.col(ColumnDef::new(FlowRunLogs::Output).text().null())
.col(ColumnDef::new(FlowRunLogs::Ok).boolean().not_null().default(false))
.col(ColumnDef::new(FlowRunLogs::Logs).text().null())
.col(ColumnDef::new(FlowRunLogs::UserId).big_integer().null())
.col(ColumnDef::new(FlowRunLogs::Username).string().null())
.col(ColumnDef::new(FlowRunLogs::StartedAt).timestamp().not_null().default(Expr::current_timestamp()))
.col(ColumnDef::new(FlowRunLogs::DurationMs).big_integer().not_null().default(0))
.col(ColumnDef::new(FlowRunLogs::CreatedAt).timestamp().not_null().default(Expr::current_timestamp()))
.to_owned()
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.drop_table(Table::drop().table(FlowRunLogs::Table).to_owned()).await
}
}
#[derive(Iden)]
enum FlowRunLogs {
Table,
Id,
FlowId,
Input,
Output,
Ok,
Logs,
UserId,
Username,
StartedAt,
DurationMs,
CreatedAt,
}

View File

@ -0,0 +1,37 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(FlowRunLogs::Table)
.add_column(ColumnDef::new(FlowRunLogs::FlowCode).string().null())
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(FlowRunLogs::Table)
.drop_column(FlowRunLogs::FlowCode)
.to_owned()
)
.await
}
}
#[derive(DeriveIden)]
enum FlowRunLogs {
Table,
#[sea_orm(iden = "flow_run_logs")]
__N, // dummy to ensure table name when not default; but using Table alias is standard
FlowCode,
}