收口创作流程统一总计划并修复等待页窄屏裁切

This commit is contained in:
2026-05-31 05:57:34 +00:00
parent 551d436919
commit c193a352df
53 changed files with 2192 additions and 161 deletions

View File

@@ -1184,7 +1184,7 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
}
if table_name == "creation_entry_type_config" {
if let Some(object) = next_value.as_object_mut() {
// 中文注释:入口分类字段晚于入口类型配置表加入,旧迁移包按未分类兼容。
// 中文注释:入口分类和统一创作契约字段晚于入口类型配置表加入,旧迁移包按空配置兼容。
object
.entry("category_id".to_string())
.or_insert(serde_json::Value::Null);
@@ -1194,6 +1194,9 @@ fn normalize_migration_row(table_name: &str, value: &serde_json::Value) -> serde
object
.entry("category_sort_order".to_string())
.or_insert_with(|| serde_json::Value::from(0));
object
.entry("unified_creation_spec_json".to_string())
.or_insert(serde_json::Value::Null);
}
}
if table_name == "user_account" {

View File

@@ -46,6 +46,8 @@ pub struct CreationEntryTypeConfig {
pub(crate) category_label: Option<String>,
#[default(0)]
pub(crate) category_sort_order: i32,
#[default(None::<String>)]
pub(crate) unified_creation_spec_json: Option<String>,
}
#[spacetimedb::procedure]
@@ -96,6 +98,7 @@ fn upsert_creation_entry_type_config_in_tx(
if input.title.trim().is_empty() {
return Err("入口标题不能为空".to_string());
}
let unified_creation_spec_json = normalize_unified_creation_spec_json(&id, &input)?;
let row = CreationEntryTypeConfig {
id: id.clone(),
title: input.title.trim().to_string(),
@@ -109,6 +112,7 @@ fn upsert_creation_entry_type_config_in_tx(
category_id: Some(normalize_category_id(&input.category_id)),
category_label: Some(normalize_category_label(&input.category_label)),
category_sort_order: input.category_sort_order,
unified_creation_spec_json,
};
if ctx.db.creation_entry_type_config().id().find(&id).is_some() {
ctx.db.creation_entry_type_config().id().update(row);
@@ -145,6 +149,7 @@ fn get_or_seed_creation_entry_config_snapshot(
category_label: normalize_optional_category_label(row.category_label.as_deref()),
category_sort_order: row.category_sort_order,
updated_at_micros: row.updated_at.to_micros_since_unix_epoch(),
unified_creation_spec_json: row.unified_creation_spec_json,
})
.collect::<Vec<_>>();
creation_types.sort_by(|left, right| {
@@ -404,10 +409,29 @@ fn default_creation_entry_type_configs(now: Timestamp) -> Vec<CreationEntryTypeC
category_id: Some(snapshot.category_id),
category_label: Some(snapshot.category_label),
category_sort_order: snapshot.category_sort_order,
unified_creation_spec_json: snapshot.unified_creation_spec_json,
})
.collect()
}
fn normalize_unified_creation_spec_json(
id: &str,
input: &CreationEntryTypeAdminUpsertInput,
) -> Result<Option<String>, String> {
let Some(spec_json) = input.unified_creation_spec_json.as_deref() else {
return Ok(None);
};
let normalized = spec_json.trim();
if normalized.is_empty() {
return Ok(None);
}
let spec =
shared_contracts::creation_entry_config::decode_unified_creation_spec_response(normalized)?;
shared_contracts::creation_entry_config::validate_unified_creation_spec_for_play(id, &spec)?;
shared_contracts::creation_entry_config::encode_unified_creation_spec_response(&spec).map(Some)
}
fn normalize_category_id(value: &str) -> String {
let normalized = value.trim();
if normalized.is_empty() {

View File

@@ -95,7 +95,7 @@ pub struct VisualNovelWorkProfileRow {
pub(crate) created_at: Timestamp,
pub(crate) updated_at: Timestamp,
pub(crate) published_at: Option<Timestamp>,
// 后台可见性开关;默认显示,隐藏后不进入公开列表。
// 管理端可见性开关;默认显示,隐藏后不进入广场列表。
#[default(WORK_VISIBLE_DEFAULT)]
pub(crate) visible: bool,
}
@@ -171,9 +171,9 @@ pub struct VisualNovelRuntimeEvent {
pub(crate) occurred_at: Timestamp,
}
/// 视觉小说公开广场列表投影。
/// 视觉小说广场列表投影。
///
/// 该 view 只暴露已发布作品卡片需要的公开字段HTTP gallery 订阅
/// 该 view 只暴露已发布作品卡片需要的展示字段HTTP gallery 缓存刷新
/// 从本地 cache 读取,避免每个列表请求调用 `list_visual_novel_works` procedure。
#[spacetimedb::view(accessor = visual_novel_gallery_view, public)]
pub fn visual_novel_gallery_view(ctx: &AnonymousViewContext) -> Vec<VisualNovelGalleryViewRow> {