feat(write): support edit mode with data backfill

This commit is contained in:
xfy 2026-06-05 17:45:54 +08:00
parent 0c3a084c9b
commit 2ecdc09138

View File

@ -3,13 +3,26 @@ use dioxus::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
use crate::api::posts::{create_post, CreatePostResponse};
use crate::api::posts::{create_post, get_post_by_id, update_post, CreatePostResponse, SinglePostResponse};
use crate::components::write_skeleton::WriteSkeleton;
use crate::models::post::PostStatus;
use crate::router::Route;
#[component]
#[allow(unused_mut, unused_variables)]
pub fn Write() -> Element {
write_editor(None)
}
#[component]
#[allow(unused_mut, unused_variables)]
pub fn WriteEdit(id: i32) -> Element {
write_editor(Some(id))
}
fn write_editor(post_id: Option<i32>) -> Element {
let is_edit = post_id.is_some();
let mut title = use_signal(|| "".to_string());
let mut summary = use_signal(|| "".to_string());
let mut slug = use_signal(|| "".to_string());
@ -21,11 +34,47 @@ pub fn Write() -> Element {
let mut saving = use_signal(|| false);
let mut error = use_signal(|| None::<String>);
let mut success = use_signal(|| false);
let mut editor_content_set = use_signal(|| false);
// 编辑模式:加载文章数据
let post_res = use_resource(move || async move {
if let Some(id) = post_id {
match get_post_by_id(id).await {
Ok(SinglePostResponse { post }) => post,
Err(_) => None,
}
} else {
None
}
});
// 数据回填 effect
use_effect(move || {
if !is_edit {
return;
}
if let Some(Some(post)) = post_res.read().as_ref() {
if title().is_empty() {
title.set(post.title.clone());
summary.set(post.summary.clone().unwrap_or_default());
slug.set(post.slug.clone());
tags.set(post.tags.join(", "));
cover_image.set(post.cover_image.clone().unwrap_or_default());
status.set(post.status.as_str().to_string());
content.set(post.content_md.clone());
}
}
});
// 初始化 Tiptap 编辑器
use_effect(move || {
#[cfg(target_arch = "wasm32")]
{
// 编辑模式:等数据加载完再初始化
if is_edit && post_res.read().is_none() {
return;
}
let _ = js_sys::eval(
r#"
(function initEditor() {
@ -90,6 +139,11 @@ pub fn Write() -> Element {
use_effect(move || {
#[cfg(target_arch = "wasm32")]
{
// 编辑模式:等数据加载完再开始轮询
if is_edit && post_res.read().is_none() {
return;
}
wasm_bindgen_futures::spawn_local(async move {
for i in 0..100 {
if let Ok(promise_val) = js_sys::eval("new Promise(r => setTimeout(r, 100))") {
@ -99,6 +153,19 @@ pub fn Write() -> Element {
}
if let Ok(ready) = js_sys::eval("window.__tiptap_ready") {
if ready.as_bool().unwrap_or(false) {
// 编辑模式:回填编辑器内容
if is_edit && !editor_content_set() {
let md = content();
if !md.is_empty() {
let md_json = serde_json::to_string(&md).unwrap_or_default();
let script = format!(
"(function() {{ var editor = window.TiptapEditor && window.TiptapEditor._instances && window.TiptapEditor._instances.get('tiptap-editor'); if (editor) {{ editor.setMarkdown({}); }} }})()",
md_json
);
let _ = js_sys::eval(&script);
}
editor_content_set.set(true);
}
loading.set(false);
return;
}
@ -160,45 +227,85 @@ pub fn Write() -> Element {
saving.set(true);
error.set(None);
spawn(async move {
match create_post(
title().trim().to_string(),
slug_opt,
summary_opt,
md,
status(),
tags_list,
cover_image_opt,
)
.await
{
Ok(CreatePostResponse { success: true, .. }) => {
saving.set(false);
success.set(true);
// Delay navigation slightly so user sees success message
#[cfg(target_arch = "wasm32")]
{
let _ = js_sys::eval("new Promise(r => setTimeout(r, 800))");
if let Some(id) = post_id {
// 编辑模式:调用 update_post
spawn(async move {
match update_post(
id,
title().trim().to_string(),
slug_opt,
summary_opt,
md,
status(),
tags_list,
cover_image_opt,
)
.await
{
Ok(CreatePostResponse { success: true, .. }) => {
saving.set(false);
success.set(true);
#[cfg(target_arch = "wasm32")]
{
let _ = js_sys::eval("new Promise(r => setTimeout(r, 800))");
}
let _ = dioxus::router::navigator().push(Route::Posts {});
}
Ok(CreatePostResponse { success: false, message, .. }) => {
saving.set(false);
error.set(Some(message));
}
Err(e) => {
saving.set(false);
error.set(Some(format!("更新失败: {}", e)));
}
let _ = dioxus::router::navigator().push(Route::Admin {});
}
Ok(CreatePostResponse {
success: false,
message,
..
}) => {
saving.set(false);
error.set(Some(message));
});
} else {
// 新建模式:调用 create_post
spawn(async move {
match create_post(
title().trim().to_string(),
slug_opt,
summary_opt,
md,
status(),
tags_list,
cover_image_opt,
)
.await
{
Ok(CreatePostResponse { success: true, .. }) => {
saving.set(false);
success.set(true);
#[cfg(target_arch = "wasm32")]
{
let _ = js_sys::eval("new Promise(r => setTimeout(r, 800))");
}
let _ = dioxus::router::navigator().push(Route::Admin {});
}
Ok(CreatePostResponse { success: false, message, .. }) => {
saving.set(false);
error.set(Some(message));
}
Err(e) => {
saving.set(false);
error.set(Some(format!("保存失败: {}", e)));
}
}
Err(e) => {
saving.set(false);
error.set(Some(format!("保存失败: {}", e)));
}
}
});
});
}
}
};
let save_button_text = if saving() {
"保存中..."
} else if is_edit {
"更新"
} else {
"保存"
};
rsx! {
div { class: "relative",
if loading() {
@ -277,7 +384,7 @@ pub fn Write() -> Element {
button {
class: "px-5 py-2.5 text-sm bg-gray-200 dark:bg-[#333] text-gray-700 dark:text-[#dadadb] rounded-full font-medium hover:opacity-80 transition-opacity cursor-pointer",
onclick: move |_| {
let _ = dioxus::router::navigator().push(Route::Admin {});
let _ = dioxus::router::navigator().push(Route::Posts {});
},
"取消"
}
@ -314,7 +421,7 @@ pub fn Write() -> Element {
},
disabled: saving(),
onclick: on_submit,
if saving() { "保存中..." } else { "保存" }
"{save_button_text}"
}
}
}