2025-01-04 00:02:23 +08:00
|
|
|
|
use std::format;
|
|
|
|
|
|
2024-12-23 00:41:55 +08:00
|
|
|
|
use crate::common::error::{CustomErrorInto, CustomResult};
|
2024-12-25 23:26:35 +08:00
|
|
|
|
use crate::common::helps::generate_random_string;
|
|
|
|
|
use crate::Route;
|
2025-01-01 23:56:37 +08:00
|
|
|
|
use dioxus::{logger::tracing, prelude::*};
|
2024-12-25 23:26:35 +08:00
|
|
|
|
use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg};
|
2024-12-23 00:41:55 +08:00
|
|
|
|
use dioxus_free_icons::Icon;
|
2025-01-04 00:02:23 +08:00
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
|
|
|
use web_sys::window;
|
2024-12-23 00:41:55 +08:00
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Clone)]
|
2024-12-25 23:26:35 +08:00
|
|
|
|
pub struct NotificationProvider {
|
|
|
|
|
pub notifications: Vec<NotificationProps>,
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-04 00:02:23 +08:00
|
|
|
|
impl Default for NotificationProvider {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
notifications: Vec::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-25 23:26:35 +08:00
|
|
|
|
#[derive(PartialEq, Clone)]
|
|
|
|
|
pub enum NotificationType {
|
2024-12-23 00:41:55 +08:00
|
|
|
|
Info,
|
|
|
|
|
Error,
|
|
|
|
|
Success,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Props, Clone)]
|
2024-12-25 23:26:35 +08:00
|
|
|
|
pub struct NotificationProps {
|
2025-01-01 23:56:37 +08:00
|
|
|
|
id: String,
|
2024-12-23 00:41:55 +08:00
|
|
|
|
title: String,
|
2024-12-25 23:26:35 +08:00
|
|
|
|
content: String,
|
|
|
|
|
time: u64,
|
|
|
|
|
r#type: NotificationType,
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-01 23:56:37 +08:00
|
|
|
|
impl Default for NotificationProps {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
id: "".to_string(),
|
|
|
|
|
title: "".to_string(),
|
|
|
|
|
content: "".to_string(),
|
|
|
|
|
time: 5,
|
|
|
|
|
r#type: NotificationType::Info,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-25 23:26:35 +08:00
|
|
|
|
#[derive(PartialEq, Clone)]
|
|
|
|
|
struct NoticationColorMatching {
|
|
|
|
|
icon: Element,
|
2025-01-04 00:02:23 +08:00
|
|
|
|
bg_color: &'static str,
|
|
|
|
|
border_color: &'static str,
|
|
|
|
|
text_color: &'static str,
|
|
|
|
|
progress_color: &'static str,
|
2024-12-25 23:26:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMatching {
|
|
|
|
|
match notification_type {
|
|
|
|
|
NotificationType::Info => NoticationColorMatching {
|
|
|
|
|
icon: rsx! {
|
|
|
|
|
Icon {
|
|
|
|
|
icon: BsInfoCircle,
|
|
|
|
|
width:18,
|
|
|
|
|
height:18,
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-01-04 00:02:23 +08:00
|
|
|
|
bg_color: "bg-blue-200",
|
|
|
|
|
border_color: "border-blue-500",
|
|
|
|
|
text_color: "text-blue-800",
|
|
|
|
|
progress_color: "bg-blue-500",
|
2024-12-25 23:26:35 +08:00
|
|
|
|
},
|
|
|
|
|
NotificationType::Error => NoticationColorMatching {
|
|
|
|
|
icon: rsx! {
|
|
|
|
|
Icon {
|
|
|
|
|
icon: BsXCircle,
|
|
|
|
|
width:18,
|
|
|
|
|
height:18,
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-01-04 00:02:23 +08:00
|
|
|
|
bg_color: "bg-red-200",
|
|
|
|
|
border_color: "border-red-500",
|
|
|
|
|
text_color: "text-red-800",
|
|
|
|
|
progress_color: "bg-red-500",
|
2024-12-25 23:26:35 +08:00
|
|
|
|
},
|
|
|
|
|
NotificationType::Success => NoticationColorMatching {
|
|
|
|
|
icon: rsx! {
|
|
|
|
|
Icon {
|
|
|
|
|
icon: BsCheckCircle,
|
|
|
|
|
width:18,
|
|
|
|
|
height:18,
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-01-04 00:02:23 +08:00
|
|
|
|
bg_color: "bg-green-200",
|
|
|
|
|
border_color: "border-green-500",
|
|
|
|
|
text_color: "text-green-800",
|
|
|
|
|
progress_color: "bg-green-500",
|
2024-12-25 23:26:35 +08:00
|
|
|
|
},
|
|
|
|
|
}
|
2024-12-23 00:41:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-01 23:56:37 +08:00
|
|
|
|
pub fn remove_notification(id: String) {
|
|
|
|
|
let mut notifications_context = use_context::<Signal<NotificationProvider>>();
|
2025-01-04 00:02:23 +08:00
|
|
|
|
tracing::info!("开始删除通知,ID: {}", id);
|
|
|
|
|
tracing::info!(
|
|
|
|
|
"当前通知列表长度: {}",
|
|
|
|
|
notifications_context().notifications.len()
|
|
|
|
|
);
|
|
|
|
|
notifications_context.with_mut(|state| {
|
|
|
|
|
let before_len = state.notifications.len();
|
|
|
|
|
state
|
|
|
|
|
.notifications
|
|
|
|
|
.retain(|notification| notification.id != id);
|
|
|
|
|
let after_len = state.notifications.len();
|
|
|
|
|
tracing::info!("删除通知后,列表长度从 {} 变为 {}", before_len, after_len);
|
2025-01-01 23:56:37 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-04 00:02:23 +08:00
|
|
|
|
#[component]
|
|
|
|
|
pub fn NotificationCard(props: NotificationProps) -> Element {
|
|
|
|
|
let color_matching = get_color_matching(&props.r#type);
|
|
|
|
|
let mut progress = use_signal(|| 0);
|
|
|
|
|
let mut hover = use_signal(|| false);
|
|
|
|
|
let id_for_future = props.id.clone();
|
|
|
|
|
use_future(move || {
|
|
|
|
|
let id = id_for_future.clone();
|
|
|
|
|
let progress_time = (props.time * 10) as i32;
|
|
|
|
|
async move {
|
|
|
|
|
loop {
|
|
|
|
|
if progress() >= 100 {
|
|
|
|
|
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
|
|
|
|
window()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, 150)
|
|
|
|
|
.unwrap();
|
|
|
|
|
});
|
|
|
|
|
wasm_bindgen_futures::JsFuture::from(promise).await.unwrap();
|
|
|
|
|
remove_notification(id.clone());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
progress.set(progress() + 1);
|
|
|
|
|
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
|
|
|
|
window()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_timeout_with_callback_and_timeout_and_arguments_0(
|
|
|
|
|
&resolve,
|
|
|
|
|
progress_time,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
});
|
|
|
|
|
wasm_bindgen_futures::JsFuture::from(promise).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
rsx! {
|
|
|
|
|
div {
|
|
|
|
|
id: props.id.clone(),
|
|
|
|
|
class:format!("rounded px-3 py-3 m-2 border-none text-gray-800 dark:text-gray-200 {} hover:{} border-2 text-base",color_matching.bg_color,color_matching.border_color),
|
|
|
|
|
div {
|
|
|
|
|
div {
|
|
|
|
|
class:format!("flex items-center justify-between {}",color_matching.text_color),
|
|
|
|
|
div {
|
|
|
|
|
class:"mr-2 relative top-[0.06rem]",
|
|
|
|
|
{color_matching.icon},
|
|
|
|
|
}
|
|
|
|
|
div {
|
|
|
|
|
class:"truncate min-w-0",
|
|
|
|
|
{props.title.clone()}
|
|
|
|
|
}
|
|
|
|
|
div {
|
|
|
|
|
onclick:move |e|{
|
|
|
|
|
e.prevent_default();
|
|
|
|
|
remove_notification(props.id.clone());
|
|
|
|
|
},
|
|
|
|
|
class:"ml-1 flex-shrink-0",
|
|
|
|
|
Icon{
|
|
|
|
|
icon:BsXLg,
|
|
|
|
|
height:18,
|
|
|
|
|
width:18
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
div {
|
|
|
|
|
class:format!("text-sm {}",color_matching.text_color),
|
|
|
|
|
{props.content.clone()}
|
|
|
|
|
}
|
|
|
|
|
div {
|
|
|
|
|
class:format!("mt-2 border border-solid rounded-md {}",color_matching.border_color),
|
|
|
|
|
div {
|
|
|
|
|
class:format!("w-full h-1 transition-all ease-linear {}",color_matching.progress_color),
|
|
|
|
|
style:format!("width:{}%",progress()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-23 00:41:55 +08:00
|
|
|
|
#[component]
|
2024-12-25 23:26:35 +08:00
|
|
|
|
pub fn Notification() -> Element {
|
2025-01-01 23:56:37 +08:00
|
|
|
|
let notifications_context = use_context::<Signal<NotificationProvider>>();
|
|
|
|
|
let notifications = notifications_context().clone();
|
|
|
|
|
|
2024-12-23 00:41:55 +08:00
|
|
|
|
return rsx! {
|
|
|
|
|
div {
|
2025-01-04 00:02:23 +08:00
|
|
|
|
class: "w-[20rem] absolute right-8 top-5 max-sm:w-[12rem] ",
|
2025-01-01 23:56:37 +08:00
|
|
|
|
{notifications.notifications.iter().map(move |item| {
|
2025-01-04 00:02:23 +08:00
|
|
|
|
rsx! {
|
|
|
|
|
NotificationCard {
|
|
|
|
|
..item.clone()
|
2024-12-23 00:41:55 +08:00
|
|
|
|
}
|
2024-12-25 23:26:35 +08:00
|
|
|
|
}
|
|
|
|
|
})}
|
|
|
|
|
}
|
|
|
|
|
Outlet::<Route> {}
|
2024-12-23 00:41:55 +08:00
|
|
|
|
};
|
|
|
|
|
}
|
2024-12-25 23:26:35 +08:00
|
|
|
|
|
|
|
|
|
pub struct Toast();
|
|
|
|
|
|
|
|
|
|
impl Toast {
|
|
|
|
|
pub fn show(props: NotificationProps) {
|
|
|
|
|
let mut notification_context = use_context::<Signal<NotificationProvider>>();
|
|
|
|
|
let mut new_provider = notification_context().clone();
|
2025-01-01 23:56:37 +08:00
|
|
|
|
let mut new_props = props;
|
2025-01-04 00:02:23 +08:00
|
|
|
|
new_props.id = format!("notification-{}", generate_random_string(10));
|
2025-01-01 23:56:37 +08:00
|
|
|
|
new_provider.notifications.push(new_props);
|
2024-12-25 23:26:35 +08:00
|
|
|
|
notification_context.set(new_provider);
|
|
|
|
|
}
|
|
|
|
|
pub fn info(title: String, content: String) {
|
|
|
|
|
Self::show(NotificationProps {
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
r#type: NotificationType::Info,
|
2025-01-04 00:02:23 +08:00
|
|
|
|
..NotificationProps::default()
|
2024-12-25 23:26:35 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
pub fn error(title: String, content: String) {
|
|
|
|
|
Self::show(NotificationProps {
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
r#type: NotificationType::Error,
|
2025-01-04 00:02:23 +08:00
|
|
|
|
..NotificationProps::default()
|
2024-12-25 23:26:35 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
pub fn success(title: String, content: String) {
|
|
|
|
|
Self::show(NotificationProps {
|
|
|
|
|
title,
|
|
|
|
|
content,
|
|
|
|
|
r#type: NotificationType::Success,
|
2025-01-04 00:02:23 +08:00
|
|
|
|
..NotificationProps::default()
|
2024-12-25 23:26:35 +08:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|