1547 lines
49 KiB
Rust
1547 lines
49 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use sea_query::{Expr, PostgresQueryBuilder, Query};
|
|
use sea_query_binder::SqlxBinder;
|
|
use sqlx::{PgPool, Row};
|
|
use thiserror::Error;
|
|
use tracing::error;
|
|
use uuid::Uuid;
|
|
|
|
use crate::models::page_block::*;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum PageBlockError {
|
|
#[error("数据库错误: {0}")]
|
|
DatabaseError(#[from] sqlx::Error),
|
|
#[error("未找到: {0}")]
|
|
NotFound(String),
|
|
#[error("验证错误: {0}")]
|
|
ValidationError(String),
|
|
#[error("权限错误: {0}")]
|
|
PermissionError(String),
|
|
}
|
|
|
|
pub struct PageBlockService {
|
|
pool: PgPool,
|
|
}
|
|
|
|
impl PageBlockService {
|
|
pub fn new(pool: PgPool) -> Self {
|
|
Self { pool }
|
|
}
|
|
|
|
/// 创建新页面
|
|
pub async fn create_page(
|
|
&self,
|
|
input: CreatePageInput,
|
|
user_id: Uuid,
|
|
) -> Result<Page, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
INSERT INTO pages (title, slug, description, is_active, created_by)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
"#,
|
|
input.title,
|
|
input.slug,
|
|
input.description,
|
|
input.is_active.unwrap_or(true),
|
|
user_id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
})
|
|
}
|
|
|
|
/// 根据ID获取页面
|
|
pub async fn get_page_by_id(&self, page_id: Uuid) -> Result<Page, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
SELECT id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
FROM pages WHERE id = $1 AND is_active = true
|
|
"#,
|
|
page_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
}),
|
|
None => Err(PageBlockError::NotFound(format!("页面 {} 未找到", page_id))),
|
|
}
|
|
}
|
|
|
|
/// 根据slug获取页面
|
|
pub async fn get_page_by_slug(&self, slug: &str) -> Result<Page, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
SELECT id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
FROM pages WHERE slug = $1 AND is_active = true
|
|
"#,
|
|
slug
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
}),
|
|
None => Err(PageBlockError::NotFound(format!(
|
|
"页面 slug {} 未找到",
|
|
slug
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// 获取页面列表(使用 sea-query 构建动态查询)
|
|
pub async fn get_pages(
|
|
&self,
|
|
filter: Option<PageFilterInput>,
|
|
limit: Option<i64>,
|
|
offset: Option<i64>,
|
|
) -> Result<Vec<Page>, PageBlockError> {
|
|
// 使用 sea-query 构建查询
|
|
let mut query = Query::select();
|
|
use Pages::Pages;
|
|
query
|
|
.columns([
|
|
(Pages::Table, Pages::Id),
|
|
(Pages::Table, Pages::Title),
|
|
(Pages::Table, Pages::Slug),
|
|
(Pages::Table, Pages::Description),
|
|
(Pages::Table, Pages::IsActive),
|
|
(Pages::Table, Pages::CreatedAt),
|
|
(Pages::Table, Pages::UpdatedAt),
|
|
(Pages::Table, Pages::CreatedBy),
|
|
(Pages::Table, Pages::UpdatedBy),
|
|
])
|
|
.from(Pages::Table)
|
|
.and_where(Expr::col((Pages::Table, Pages::IsActive)).eq(true));
|
|
|
|
// 添加过滤条件
|
|
if let Some(filter) = filter {
|
|
if let Some(title) = filter.title {
|
|
query.and_where(
|
|
Expr::col((Pages::Table, Pages::Title)).like(format!("%{}%", title)),
|
|
);
|
|
}
|
|
if let Some(search) = filter.search {
|
|
query.and_where(
|
|
Expr::col((Pages::Table, Pages::Title))
|
|
.like(format!("%{}%", search))
|
|
.or(Expr::col((Pages::Table, Pages::Description))
|
|
.like(format!("%{}%", search))),
|
|
);
|
|
}
|
|
}
|
|
|
|
// 添加排序
|
|
query.order_by((Pages::Table, Pages::CreatedAt), sea_query::Order::Desc);
|
|
|
|
// 添加分页
|
|
if let Some(limit) = limit {
|
|
query.limit(limit as u64);
|
|
}
|
|
if let Some(offset) = offset {
|
|
query.offset(offset as u64);
|
|
}
|
|
|
|
// 构建并执行查询
|
|
let (sql, values) = query.build_sqlx(PostgresQueryBuilder);
|
|
let rows = sqlx::query_with(&sql, values).fetch_all(&self.pool).await?;
|
|
|
|
let pages = rows
|
|
.into_iter()
|
|
.map(|row| Page {
|
|
id: row.get("id"),
|
|
title: row.get("title"),
|
|
slug: row.get("slug"),
|
|
description: row.get("description"),
|
|
is_active: row.get("is_active"),
|
|
created_at: chrono::DateTime::from(
|
|
row.get::<chrono::DateTime<chrono::Utc>, _>("created_at"),
|
|
),
|
|
updated_at: chrono::DateTime::from(
|
|
row.get::<chrono::DateTime<chrono::Utc>, _>("updated_at"),
|
|
),
|
|
created_by: row.get("created_by"),
|
|
updated_by: row.get("updated_by"),
|
|
})
|
|
.collect();
|
|
|
|
Ok(pages)
|
|
}
|
|
|
|
/// 获取页面的所有块
|
|
pub async fn get_page_blocks(&self, page_id: Uuid) -> Result<Vec<Block>, PageBlockError> {
|
|
let mut blocks = Vec::new();
|
|
|
|
// 获取文本块
|
|
let text_blocks = sqlx::query!(
|
|
r#"
|
|
SELECT id, page_id, block_order, title, markdown, is_active, created_at as "created_at: DateTime<Utc>", updated_at as "updated_at: DateTime<Utc>"
|
|
FROM text_blocks WHERE page_id = $1 AND is_active = true ORDER BY block_order
|
|
"#,
|
|
page_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
for row in text_blocks {
|
|
blocks.push(Block::TextBlock(TextBlock {
|
|
id: row.id,
|
|
page_id: row.page_id,
|
|
block_order: row.block_order,
|
|
title: row.title,
|
|
markdown: row.markdown,
|
|
is_active: row.is_active,
|
|
created_at: chrono::DateTime::from(row.created_at),
|
|
updated_at: chrono::DateTime::from(row.updated_at),
|
|
}));
|
|
}
|
|
|
|
// 获取图表块
|
|
let chart_blocks = sqlx::query!(
|
|
r#"
|
|
SELECT id, page_id, block_order, title, chart_type, config, is_active, created_at as "created_at: DateTime<Utc>", updated_at as "updated_at: DateTime<Utc>"
|
|
FROM chart_blocks WHERE page_id = $1 AND is_active = true ORDER BY block_order
|
|
"#,
|
|
page_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
for row in chart_blocks {
|
|
// 获取数据点
|
|
let data_points = sqlx::query!(
|
|
r#"
|
|
SELECT id, chart_block_id, x, y, label, color
|
|
FROM data_points WHERE chart_block_id = $1 ORDER BY x
|
|
"#,
|
|
row.id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let series = data_points
|
|
.into_iter()
|
|
.map(|dp| DataPoint {
|
|
id: dp.id,
|
|
chart_block_id: dp.chart_block_id,
|
|
x: dp.x,
|
|
y: dp.y,
|
|
label: dp.label,
|
|
color: dp.color,
|
|
})
|
|
.collect();
|
|
|
|
blocks.push(Block::ChartBlock(ChartBlock {
|
|
id: row.id,
|
|
page_id: row.page_id,
|
|
block_order: row.block_order,
|
|
title: row.title,
|
|
chart_type: row.chart_type,
|
|
series,
|
|
config: row.config,
|
|
is_active: row.is_active,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
}));
|
|
}
|
|
|
|
// 获取设置块
|
|
let settings_blocks = sqlx::query!(
|
|
r#"
|
|
SELECT id, page_id, block_order, title, category, editable, display_mode, is_active, created_at as "created_at: DateTime<Utc>", updated_at as "updated_at: DateTime<Utc>"
|
|
FROM settings_blocks WHERE page_id = $1 AND is_active = true ORDER BY block_order
|
|
"#,
|
|
page_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
for row in settings_blocks {
|
|
blocks.push(Block::SettingsBlock(SettingsBlock {
|
|
id: row.id,
|
|
page_id: row.page_id,
|
|
block_order: row.block_order,
|
|
title: row.title,
|
|
category: row.category,
|
|
editable: row.editable,
|
|
display_mode: row.display_mode,
|
|
is_active: row.is_active,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
}));
|
|
}
|
|
|
|
// 获取表格块
|
|
let table_blocks = sqlx::query!(
|
|
r#"
|
|
SELECT id, page_id, block_order, title, data_source, data_config, is_active, created_at as "created_at: DateTime<Utc>", updated_at as "updated_at: DateTime<Utc>"
|
|
FROM table_blocks WHERE page_id = $1 AND is_active = true ORDER BY block_order
|
|
"#,
|
|
page_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
for row in table_blocks {
|
|
// 获取表格列
|
|
let columns = sqlx::query!(
|
|
r#"
|
|
SELECT id, table_block_id, name, label, data_type, is_sortable, is_filterable, width, "order"
|
|
FROM table_columns WHERE table_block_id = $1 ORDER BY "order"
|
|
"#,
|
|
row.id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let table_columns = columns
|
|
.into_iter()
|
|
.map(|col| TableColumn {
|
|
id: col.id,
|
|
table_block_id: col.table_block_id,
|
|
name: col.name,
|
|
label: col.label,
|
|
data_type: col.data_type,
|
|
is_sortable: col.is_sortable,
|
|
is_filterable: col.is_filterable,
|
|
width: col.width,
|
|
order: col.order,
|
|
})
|
|
.collect();
|
|
|
|
blocks.push(Block::TableBlock(TableBlock {
|
|
id: row.id,
|
|
page_id: row.page_id,
|
|
block_order: row.block_order,
|
|
title: row.title,
|
|
columns: table_columns,
|
|
data_source: row.data_source,
|
|
data_config: row.data_config,
|
|
is_active: row.is_active,
|
|
created_at: chrono::DateTime::from(row.created_at),
|
|
updated_at: chrono::DateTime::from(row.updated_at),
|
|
}));
|
|
}
|
|
|
|
// 获取英雄块
|
|
let hero_blocks = sqlx::query!(
|
|
r#"
|
|
SELECT id, page_id, block_order, title, subtitle, background_image, background_color, text_color, cta_text, cta_link, is_active, created_at as "created_at: DateTime<Utc>", updated_at as "updated_at: DateTime<Utc>"
|
|
FROM hero_blocks WHERE page_id = $1 AND is_active = true ORDER BY block_order
|
|
"#,
|
|
page_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
for row in hero_blocks {
|
|
blocks.push(Block::HeroBlock(HeroBlock {
|
|
id: row.id,
|
|
page_id: row.page_id,
|
|
block_order: row.block_order,
|
|
title: row.title,
|
|
subtitle: row.subtitle,
|
|
background_image: row.background_image,
|
|
background_color: row.background_color,
|
|
text_color: row.text_color,
|
|
cta_text: row.cta_text,
|
|
cta_link: row.cta_link,
|
|
is_active: row.is_active,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at,
|
|
}));
|
|
}
|
|
|
|
Ok(blocks)
|
|
}
|
|
|
|
/// 创建文本块
|
|
pub async fn create_text_block(
|
|
&self,
|
|
input: CreateTextBlockInput,
|
|
) -> Result<TextBlock, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
TextBlock,
|
|
r#"
|
|
INSERT INTO text_blocks (page_id, block_order, title, markdown, is_active)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING id, page_id, block_order, title, markdown, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.page_id,
|
|
input.block_order,
|
|
input.title,
|
|
input.markdown,
|
|
input.is_active.unwrap_or(true)
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(TextBlock {
|
|
id: result.id,
|
|
page_id: result.page_id,
|
|
block_order: result.block_order,
|
|
title: result.title,
|
|
markdown: result.markdown,
|
|
is_active: result.is_active,
|
|
created_at: chrono::DateTime::from(result.created_at),
|
|
updated_at: chrono::DateTime::from(result.updated_at),
|
|
})
|
|
}
|
|
|
|
/// 创建图表块
|
|
pub async fn create_chart_block(
|
|
&self,
|
|
input: CreateChartBlockInput,
|
|
) -> Result<ChartBlock, PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
// 创建图表块
|
|
let chart_result = sqlx::query!(
|
|
r#"
|
|
INSERT INTO chart_blocks (page_id, block_order, title, chart_type, config, is_active)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING id, page_id, block_order, title, chart_type, config, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.page_id,
|
|
input.block_order,
|
|
input.title,
|
|
input.chart_type,
|
|
input.config,
|
|
input.is_active.unwrap_or(true)
|
|
)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
// 创建数据点
|
|
for data_point in input.series {
|
|
sqlx::query!(
|
|
r#"
|
|
INSERT INTO data_points (chart_block_id, x, y, label, color)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
"#,
|
|
chart_result.id,
|
|
data_point.x,
|
|
data_point.y,
|
|
data_point.label,
|
|
data_point.color
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
}
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
|
|
// 获取完整的数据点
|
|
let data_points = sqlx::query!(
|
|
r#"
|
|
SELECT id, chart_block_id, x, y, label, color
|
|
FROM data_points WHERE chart_block_id = $1 ORDER BY x
|
|
"#,
|
|
chart_result.id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let series = data_points
|
|
.into_iter()
|
|
.map(|dp| DataPoint {
|
|
id: dp.id,
|
|
chart_block_id: dp.chart_block_id,
|
|
x: dp.x,
|
|
y: dp.y,
|
|
label: dp.label,
|
|
color: dp.color,
|
|
})
|
|
.collect();
|
|
|
|
Ok(ChartBlock {
|
|
id: chart_result.id,
|
|
page_id: chart_result.page_id,
|
|
block_order: chart_result.block_order,
|
|
title: chart_result.title,
|
|
chart_type: chart_result.chart_type,
|
|
series,
|
|
config: chart_result.config,
|
|
is_active: chart_result.is_active,
|
|
created_at: chrono::DateTime::from(chart_result.created_at),
|
|
updated_at: chrono::DateTime::from(chart_result.updated_at),
|
|
})
|
|
}
|
|
|
|
/// 创建设置块
|
|
pub async fn create_settings_block(
|
|
&self,
|
|
input: CreateSettingsBlockInput,
|
|
) -> Result<SettingsBlock, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
SettingsBlock,
|
|
r#"
|
|
INSERT INTO settings_blocks (page_id, block_order, title, category, editable, display_mode, is_active)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
RETURNING id, page_id, block_order, title, category, editable, display_mode, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.page_id,
|
|
input.block_order,
|
|
input.title,
|
|
input.category,
|
|
input.editable,
|
|
input.display_mode,
|
|
input.is_active.unwrap_or(true)
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(SettingsBlock {
|
|
id: result.id,
|
|
page_id: result.page_id,
|
|
block_order: result.block_order,
|
|
title: result.title,
|
|
category: result.category,
|
|
editable: result.editable,
|
|
display_mode: result.display_mode,
|
|
is_active: result.is_active,
|
|
created_at: chrono::DateTime::from(result.created_at),
|
|
updated_at: chrono::DateTime::from(result.updated_at),
|
|
})
|
|
}
|
|
|
|
/// 删除页面
|
|
pub async fn delete_page(&self, page_id: Uuid) -> Result<(), PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
// 删除所有相关的块
|
|
sqlx::query!("DELETE FROM text_blocks WHERE page_id = $1", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query!("DELETE FROM chart_blocks WHERE page_id = $1", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query!("DELETE FROM data_points WHERE chart_block_id IN (SELECT id FROM chart_blocks WHERE page_id = $1)", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query!("DELETE FROM settings_blocks WHERE page_id = $1", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query!("DELETE FROM table_blocks WHERE page_id = $1", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query!("DELETE FROM table_columns WHERE table_block_id IN (SELECT id FROM table_blocks WHERE page_id = $1)", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query!("DELETE FROM hero_blocks WHERE page_id = $1", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
// 删除页面
|
|
sqlx::query!("DELETE FROM pages WHERE id = $1", page_id)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 更新页面
|
|
pub async fn update_page(
|
|
&self,
|
|
page_id: Uuid,
|
|
input: UpdatePageInput,
|
|
user_id: Uuid,
|
|
) -> Result<Page, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
UPDATE pages
|
|
SET title = COALESCE($1, title),
|
|
slug = COALESCE($2, slug),
|
|
description = COALESCE($3, description),
|
|
is_active = COALESCE($4, is_active),
|
|
updated_at = NOW(),
|
|
updated_by = $5
|
|
WHERE id = $6
|
|
RETURNING id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
"#,
|
|
input.title,
|
|
input.slug,
|
|
input.description,
|
|
input.is_active,
|
|
user_id,
|
|
page_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
}),
|
|
None => Err(PageBlockError::NotFound(format!("页面 {} 未找到", page_id))),
|
|
}
|
|
}
|
|
|
|
/// 更新文本块
|
|
pub async fn update_text_block(
|
|
&self,
|
|
block_id: Uuid,
|
|
input: UpdateTextBlockInput,
|
|
) -> Result<TextBlock, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
TextBlock,
|
|
r#"
|
|
UPDATE text_blocks
|
|
SET title = COALESCE($1, title),
|
|
markdown = COALESCE($2, markdown),
|
|
block_order = COALESCE($3, block_order),
|
|
is_active = COALESCE($4, is_active),
|
|
updated_at = NOW()
|
|
WHERE id = $5
|
|
RETURNING id, page_id, block_order, title, markdown, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.title,
|
|
input.markdown,
|
|
input.block_order,
|
|
input.is_active,
|
|
block_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(TextBlock {
|
|
id: result.id,
|
|
page_id: result.page_id,
|
|
block_order: result.block_order,
|
|
title: result.title,
|
|
markdown: result.markdown,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
}),
|
|
None => Err(PageBlockError::NotFound(format!(
|
|
"文本块 {} 未找到",
|
|
block_id
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// 更新图表块
|
|
pub async fn update_chart_block(
|
|
&self,
|
|
block_id: Uuid,
|
|
input: UpdateChartBlockInput,
|
|
) -> Result<ChartBlock, PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
// 更新图表块基本信息
|
|
let chart_result = sqlx::query!(
|
|
r#"
|
|
UPDATE chart_blocks
|
|
SET title = COALESCE($1, title),
|
|
chart_type = COALESCE($2, chart_type),
|
|
config = COALESCE($3, config),
|
|
block_order = COALESCE($4, block_order),
|
|
is_active = COALESCE($5, is_active),
|
|
updated_at = NOW()
|
|
WHERE id = $6
|
|
RETURNING id, page_id, block_order, title, chart_type, config, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.title,
|
|
input.chart_type,
|
|
input.config,
|
|
input.block_order,
|
|
input.is_active,
|
|
block_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
let chart_block = match chart_result {
|
|
Some(result) => result,
|
|
None => {
|
|
tx.rollback().await?;
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"图表块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
};
|
|
|
|
// 如果提供了新的数据系列,则更新数据点
|
|
if let Some(series) = input.series {
|
|
// 删除旧的数据点
|
|
sqlx::query!(
|
|
"DELETE FROM data_points WHERE chart_block_id = $1",
|
|
block_id
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
// 插入新的数据点
|
|
for data_point in series {
|
|
sqlx::query!(
|
|
r#"
|
|
INSERT INTO data_points (chart_block_id, x, y, label, color)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
"#,
|
|
block_id,
|
|
data_point.x,
|
|
data_point.y,
|
|
data_point.label,
|
|
data_point.color
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
|
|
// 获取完整的数据点
|
|
let data_points = sqlx::query!(
|
|
r#"
|
|
SELECT id, chart_block_id, x, y, label, color
|
|
FROM data_points WHERE chart_block_id = $1 ORDER BY x
|
|
"#,
|
|
block_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let series = data_points
|
|
.into_iter()
|
|
.map(|dp| DataPoint {
|
|
id: dp.id,
|
|
chart_block_id: dp.chart_block_id,
|
|
x: dp.x,
|
|
y: dp.y,
|
|
label: dp.label,
|
|
color: dp.color,
|
|
})
|
|
.collect();
|
|
|
|
Ok(ChartBlock {
|
|
id: chart_block.id,
|
|
page_id: chart_block.page_id,
|
|
block_order: chart_block.block_order,
|
|
title: chart_block.title,
|
|
chart_type: chart_block.chart_type,
|
|
series,
|
|
config: chart_block.config,
|
|
is_active: chart_block.is_active,
|
|
created_at: chart_block.created_at,
|
|
updated_at: chart_block.updated_at,
|
|
})
|
|
}
|
|
|
|
/// 更新设置块
|
|
pub async fn update_settings_block(
|
|
&self,
|
|
block_id: Uuid,
|
|
input: UpdateSettingsBlockInput,
|
|
) -> Result<SettingsBlock, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
SettingsBlock,
|
|
r#"
|
|
UPDATE settings_blocks
|
|
SET title = COALESCE($1, title),
|
|
category = COALESCE($2, category),
|
|
editable = COALESCE($3, editable),
|
|
display_mode = COALESCE($4, display_mode),
|
|
block_order = COALESCE($5, block_order),
|
|
is_active = COALESCE($6, is_active),
|
|
updated_at = NOW()
|
|
WHERE id = $7
|
|
RETURNING id, page_id, block_order, title, category, editable, display_mode, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.title,
|
|
input.category,
|
|
input.editable,
|
|
input.display_mode,
|
|
input.block_order,
|
|
input.is_active,
|
|
block_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(SettingsBlock {
|
|
id: result.id,
|
|
page_id: result.page_id,
|
|
block_order: result.block_order,
|
|
title: result.title,
|
|
category: result.category,
|
|
editable: result.editable,
|
|
display_mode: result.display_mode,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
}),
|
|
None => Err(PageBlockError::NotFound(format!(
|
|
"设置块 {} 未找到",
|
|
block_id
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// 更新表格块
|
|
pub async fn update_table_block(
|
|
&self,
|
|
block_id: Uuid,
|
|
input: UpdateTableBlockInput,
|
|
) -> Result<TableBlock, PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
// 创建临时结构体来接收更新后的基本信息
|
|
#[derive(sqlx::FromRow)]
|
|
struct TableBlockUpdate {
|
|
id: Uuid,
|
|
page_id: Uuid,
|
|
block_order: i32,
|
|
title: Option<String>,
|
|
data_source: String,
|
|
data_config: Option<serde_json::Value>,
|
|
is_active: bool,
|
|
created_at: DateTime<Utc>,
|
|
updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
// 更新表格块基本信息
|
|
let table_result = sqlx::query_as!(
|
|
TableBlockUpdate,
|
|
r#"
|
|
UPDATE table_blocks
|
|
SET title = COALESCE($1, title),
|
|
data_source = COALESCE($2, data_source),
|
|
data_config = COALESCE($3, data_config),
|
|
block_order = COALESCE($4, block_order),
|
|
is_active = COALESCE($5, is_active),
|
|
updated_at = NOW()
|
|
WHERE id = $6
|
|
RETURNING id, page_id, block_order, title, data_source, data_config, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.title,
|
|
input.data_source,
|
|
input.data_config,
|
|
input.block_order,
|
|
input.is_active,
|
|
block_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
let table_block = match table_result {
|
|
Some(result) => result,
|
|
None => {
|
|
tx.rollback().await?;
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"表格块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
};
|
|
|
|
// 如果提供了新的列配置,则更新列
|
|
if let Some(columns) = input.columns {
|
|
// 删除旧的列
|
|
sqlx::query!(
|
|
"DELETE FROM table_columns WHERE table_block_id = $1",
|
|
block_id
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
// 插入新的列
|
|
for column in columns {
|
|
sqlx::query!(
|
|
r#"
|
|
INSERT INTO table_columns (table_block_id, name, label, data_type, is_sortable, is_filterable, width, "order")
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
"#,
|
|
block_id,
|
|
column.name,
|
|
column.label,
|
|
column.data_type,
|
|
column.is_sortable,
|
|
column.is_filterable,
|
|
column.width,
|
|
column.order
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
|
|
// 获取完整的列配置
|
|
let columns = sqlx::query!(
|
|
r#"
|
|
SELECT id, table_block_id, name, label, data_type, is_sortable, is_filterable, width, "order"
|
|
FROM table_columns WHERE table_block_id = $1 ORDER BY "order"
|
|
"#,
|
|
block_id
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let table_columns = columns
|
|
.into_iter()
|
|
.map(|col| TableColumn {
|
|
id: col.id,
|
|
table_block_id: col.table_block_id,
|
|
name: col.name,
|
|
label: col.label,
|
|
data_type: col.data_type,
|
|
is_sortable: col.is_sortable,
|
|
is_filterable: col.is_filterable,
|
|
width: col.width,
|
|
order: col.order,
|
|
})
|
|
.collect();
|
|
|
|
Ok(TableBlock {
|
|
id: table_block.id,
|
|
page_id: table_block.page_id,
|
|
block_order: table_block.block_order,
|
|
title: table_block.title,
|
|
columns: table_columns,
|
|
data_source: table_block.data_source,
|
|
data_config: table_block.data_config,
|
|
is_active: table_block.is_active,
|
|
created_at: table_block.created_at,
|
|
updated_at: table_block.updated_at,
|
|
})
|
|
}
|
|
|
|
/// 更新英雄块
|
|
pub async fn update_hero_block(
|
|
&self,
|
|
block_id: Uuid,
|
|
input: UpdateHeroBlockInput,
|
|
) -> Result<HeroBlock, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
HeroBlock,
|
|
r#"
|
|
UPDATE hero_blocks
|
|
SET title = COALESCE($1, title),
|
|
subtitle = COALESCE($2, subtitle),
|
|
background_image = COALESCE($3, background_image),
|
|
background_color = COALESCE($4, background_color),
|
|
text_color = COALESCE($5, text_color),
|
|
cta_text = COALESCE($6, cta_text),
|
|
cta_link = COALESCE($7, cta_link),
|
|
block_order = COALESCE($8, block_order),
|
|
is_active = COALESCE($9, is_active),
|
|
updated_at = NOW()
|
|
WHERE id = $10
|
|
RETURNING id, page_id, block_order, title, subtitle, background_image, background_color, text_color, cta_text, cta_link, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>"
|
|
"#,
|
|
input.title,
|
|
input.subtitle,
|
|
input.background_image,
|
|
input.background_color,
|
|
input.text_color,
|
|
input.cta_text,
|
|
input.cta_link,
|
|
input.block_order,
|
|
input.is_active,
|
|
block_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(HeroBlock {
|
|
id: result.id,
|
|
page_id: result.page_id,
|
|
block_order: result.block_order,
|
|
title: result.title,
|
|
subtitle: result.subtitle,
|
|
background_image: result.background_image,
|
|
background_color: result.background_color,
|
|
text_color: result.text_color,
|
|
cta_text: result.cta_text,
|
|
cta_link: result.cta_link,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
}),
|
|
None => Err(PageBlockError::NotFound(format!(
|
|
"英雄块 {} 未找到",
|
|
block_id
|
|
))),
|
|
}
|
|
}
|
|
|
|
/// 删除文本块
|
|
pub async fn delete_text_block(&self, block_id: Uuid) -> Result<(), PageBlockError> {
|
|
let result = sqlx::query!(
|
|
"DELETE FROM text_blocks WHERE id = $1 RETURNING id",
|
|
block_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
if result.is_none() {
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"文本块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 删除图表块
|
|
pub async fn delete_chart_block(&self, block_id: Uuid) -> Result<(), PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
// 删除数据点
|
|
sqlx::query!(
|
|
"DELETE FROM data_points WHERE chart_block_id = $1",
|
|
block_id
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
// 删除图表块
|
|
let result = sqlx::query!(
|
|
"DELETE FROM chart_blocks WHERE id = $1 RETURNING id",
|
|
block_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
if result.is_none() {
|
|
tx.rollback().await?;
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"图表块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// 删除设置块
|
|
pub async fn delete_settings_block(&self, block_id: Uuid) -> Result<(), PageBlockError> {
|
|
let result = sqlx::query!(
|
|
"DELETE FROM settings_blocks WHERE id = $1 RETURNING id",
|
|
block_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
if result.is_none() {
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"设置块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 删除表格块
|
|
pub async fn delete_table_block(&self, block_id: Uuid) -> Result<(), PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
// 删除表格列
|
|
sqlx::query!(
|
|
"DELETE FROM table_columns WHERE table_block_id = $1",
|
|
block_id
|
|
)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
// 删除表格块
|
|
let result = sqlx::query!(
|
|
"DELETE FROM table_blocks WHERE id = $1 RETURNING id",
|
|
block_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
|
|
if result.is_none() {
|
|
tx.rollback().await?;
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"表格块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// 删除英雄块
|
|
pub async fn delete_hero_block(&self, block_id: Uuid) -> Result<(), PageBlockError> {
|
|
let result = sqlx::query!(
|
|
"DELETE FROM hero_blocks WHERE id = $1 RETURNING id",
|
|
block_id
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
if result.is_none() {
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"英雄块 {} 未找到",
|
|
block_id
|
|
)));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 重新排序页面块
|
|
pub async fn reorder_page_blocks(
|
|
&self,
|
|
page_id: Uuid,
|
|
block_orders: Vec<(Uuid, i32)>,
|
|
) -> Result<(), PageBlockError> {
|
|
// 开始事务
|
|
let mut tx = self.pool.begin().await?;
|
|
|
|
for (block_id, new_order) in block_orders {
|
|
// 尝试更新各种类型的块
|
|
let mut updated = false;
|
|
|
|
// 更新文本块
|
|
let result = sqlx::query!(
|
|
"UPDATE text_blocks SET block_order = $1, updated_at = NOW() WHERE id = $2 AND page_id = $3 RETURNING id",
|
|
new_order, block_id, page_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
if result.is_some() {
|
|
updated = true;
|
|
continue;
|
|
}
|
|
|
|
// 更新图表块
|
|
let result = sqlx::query!(
|
|
"UPDATE chart_blocks SET block_order = $1, updated_at = NOW() WHERE id = $2 AND page_id = $3 RETURNING id",
|
|
new_order, block_id, page_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
if result.is_some() {
|
|
updated = true;
|
|
continue;
|
|
}
|
|
|
|
// 更新设置块
|
|
let result = sqlx::query!(
|
|
"UPDATE settings_blocks SET block_order = $1, updated_at = NOW() WHERE id = $2 AND page_id = $3 RETURNING id",
|
|
new_order, block_id, page_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
if result.is_some() {
|
|
updated = true;
|
|
continue;
|
|
}
|
|
|
|
// 更新表格块
|
|
let result = sqlx::query!(
|
|
"UPDATE table_blocks SET block_order = $1, updated_at = NOW() WHERE id = $2 AND page_id = $3 RETURNING id",
|
|
new_order, block_id, page_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
if result.is_some() {
|
|
updated = true;
|
|
continue;
|
|
}
|
|
|
|
// 更新英雄块
|
|
let result = sqlx::query!(
|
|
"UPDATE hero_blocks SET block_order = $1, updated_at = NOW() WHERE id = $2 AND page_id = $3 RETURNING id",
|
|
new_order, block_id, page_id
|
|
)
|
|
.fetch_optional(&mut *tx)
|
|
.await?;
|
|
if result.is_some() {
|
|
updated = true;
|
|
continue;
|
|
}
|
|
|
|
if !updated {
|
|
tx.rollback().await?;
|
|
return Err(PageBlockError::NotFound(format!(
|
|
"块 {} 未找到或不属于页面 {}",
|
|
block_id, page_id
|
|
)));
|
|
}
|
|
}
|
|
|
|
// 提交事务
|
|
tx.commit().await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// 获取页面的统计信息
|
|
pub async fn get_page_stats(&self, page_id: Uuid) -> Result<PageStats, PageBlockError> {
|
|
let text_count = sqlx::query!(
|
|
"SELECT COUNT(*) as count FROM text_blocks WHERE page_id = $1 AND is_active = true",
|
|
page_id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count;
|
|
|
|
let chart_count = sqlx::query!(
|
|
"SELECT COUNT(*) as count FROM chart_blocks WHERE page_id = $1 AND is_active = true",
|
|
page_id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count;
|
|
|
|
let settings_count = sqlx::query!(
|
|
"SELECT COUNT(*) as count FROM settings_blocks WHERE page_id = $1 AND is_active = true",
|
|
page_id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count;
|
|
|
|
let table_count = sqlx::query!(
|
|
"SELECT COUNT(*) as count FROM table_blocks WHERE page_id = $1 AND is_active = true",
|
|
page_id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count;
|
|
|
|
let hero_count = sqlx::query!(
|
|
"SELECT COUNT(*) as count FROM hero_blocks WHERE page_id = $1 AND is_active = true",
|
|
page_id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count;
|
|
|
|
Ok(PageStats {
|
|
text_blocks: text_count.unwrap_or(0) as i32,
|
|
chart_blocks: chart_count.unwrap_or(0) as i32,
|
|
settings_blocks: settings_count.unwrap_or(0) as i32,
|
|
table_blocks: table_count.unwrap_or(0) as i32,
|
|
hero_blocks: hero_count.unwrap_or(0) as i32,
|
|
total_blocks: (text_count.unwrap_or(0)
|
|
+ chart_count.unwrap_or(0)
|
|
+ settings_count.unwrap_or(0)
|
|
+ table_count.unwrap_or(0)
|
|
+ hero_count.unwrap_or(0)) as i32,
|
|
})
|
|
}
|
|
|
|
/// 检查页面slug是否唯一
|
|
pub async fn is_slug_unique(
|
|
&self,
|
|
slug: &str,
|
|
exclude_id: Option<Uuid>,
|
|
) -> Result<bool, PageBlockError> {
|
|
let count = match exclude_id {
|
|
Some(id) => {
|
|
sqlx::query!(
|
|
"SELECT COUNT(*) as count FROM pages WHERE slug = $1 AND id != $2",
|
|
slug,
|
|
id
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count
|
|
}
|
|
None => {
|
|
sqlx::query!("SELECT COUNT(*) as count FROM pages WHERE slug = $1", slug)
|
|
.fetch_one(&self.pool)
|
|
.await?
|
|
.count
|
|
}
|
|
};
|
|
|
|
Ok(count.unwrap_or(0) == 0)
|
|
}
|
|
|
|
/// 批量获取页面
|
|
pub async fn get_pages_by_ids(&self, page_ids: &[Uuid]) -> Result<Vec<Page>, PageBlockError> {
|
|
if page_ids.is_empty() {
|
|
return Ok(Vec::new());
|
|
}
|
|
|
|
let mut pages = Vec::new();
|
|
for page_id in page_ids {
|
|
match self.get_page_by_id(*page_id).await {
|
|
Ok(page) => pages.push(page),
|
|
Err(PageBlockError::NotFound(_)) => continue, // 跳过不存在的页面
|
|
Err(e) => return Err(e),
|
|
}
|
|
}
|
|
|
|
Ok(pages)
|
|
}
|
|
|
|
/// 搜索页面
|
|
pub async fn search_pages(
|
|
&self,
|
|
query: &str,
|
|
limit: Option<i64>,
|
|
) -> Result<Vec<Page>, PageBlockError> {
|
|
let limit = limit.unwrap_or(10);
|
|
|
|
let rows = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
SELECT id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
FROM pages
|
|
WHERE is_active = true
|
|
AND (title ILIKE $1 OR description ILIKE $1 OR slug ILIKE $1)
|
|
ORDER BY
|
|
CASE
|
|
WHEN title ILIKE $1 THEN 1
|
|
WHEN slug ILIKE $1 THEN 2
|
|
ELSE 3
|
|
END,
|
|
updated_at DESC
|
|
LIMIT $2
|
|
"#,
|
|
format!("%{}%", query),
|
|
limit
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let pages = rows
|
|
.into_iter()
|
|
.map(|result| Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
})
|
|
.collect();
|
|
|
|
Ok(pages)
|
|
}
|
|
|
|
/// 根据配置分类获取页面
|
|
pub async fn get_page_by_category(
|
|
&self,
|
|
category: &str,
|
|
) -> Result<Option<Page>, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
SELECT id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
FROM pages WHERE slug = $1 AND is_active = true
|
|
"#,
|
|
format!("{}-settings", category)
|
|
)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
match result {
|
|
Some(result) => Ok(Some(Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
})),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
/// 根据配置分类获取页面和统计信息
|
|
pub async fn get_category_page_with_stats(
|
|
&self,
|
|
category: &str,
|
|
) -> Result<(Option<Page>, i32, i32, i32), PageBlockError> {
|
|
// 获取页面信息
|
|
let page = self.get_page_by_category(category).await?;
|
|
|
|
// 获取该分类下的配置项统计
|
|
let stats_result = sqlx::query!(
|
|
r#"
|
|
SELECT
|
|
COUNT(*) as total_count,
|
|
COUNT(CASE WHEN is_system = true THEN 1 END) as system_count,
|
|
COUNT(CASE WHEN is_editable = true THEN 1 END) as editable_count
|
|
FROM settings
|
|
WHERE category = $1
|
|
"#,
|
|
category
|
|
)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
let total_count = stats_result.total_count.unwrap_or(0) as i32;
|
|
let system_count = stats_result.system_count.unwrap_or(0) as i32;
|
|
let editable_count = stats_result.editable_count.unwrap_or(0) as i32;
|
|
|
|
Ok((page, total_count, system_count, editable_count))
|
|
}
|
|
|
|
/// 获取所有配置分类页面
|
|
pub async fn get_all_category_pages(&self) -> Result<Vec<Page>, PageBlockError> {
|
|
let result = sqlx::query_as!(
|
|
Page,
|
|
r#"
|
|
SELECT id, title, slug, description, is_active,
|
|
created_at as "created_at: DateTime<Utc>",
|
|
updated_at as "updated_at: DateTime<Utc>",
|
|
created_by, updated_by
|
|
FROM pages WHERE slug LIKE '%-settings' AND is_active = true
|
|
ORDER BY title
|
|
"#
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
let pages = result
|
|
.into_iter()
|
|
.map(|result| Page {
|
|
id: result.id,
|
|
title: result.title,
|
|
slug: result.slug,
|
|
description: result.description,
|
|
is_active: result.is_active,
|
|
created_at: result.created_at,
|
|
updated_at: result.updated_at,
|
|
created_by: result.created_by,
|
|
updated_by: result.updated_by,
|
|
})
|
|
.collect();
|
|
|
|
Ok(pages)
|
|
}
|
|
}
|
|
|
|
// 定义表结构常量
|
|
mod Pages {
|
|
use sea_query::Iden;
|
|
#[derive(Iden)]
|
|
pub enum Pages {
|
|
#[iden = "pages"]
|
|
Table,
|
|
Id,
|
|
Title,
|
|
Slug,
|
|
Description,
|
|
IsActive,
|
|
CreatedAt,
|
|
UpdatedAt,
|
|
CreatedBy,
|
|
UpdatedBy,
|
|
}
|
|
}
|