From fdcb8bd06507f109ea4f0e3251e04b4859c8d7f4 Mon Sep 17 00:00:00 2001 From: lsy Date: Sat, 4 Jan 2025 00:02:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E7=9A=84=E8=BF=9B=E5=BA=A6=E6=9D=A1=E5=92=8C=E9=85=8D=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/Cargo.lock | 2 +- frontend/Cargo.toml | 3 +- frontend/assets/styling/tailwind.css | 97 ++++++++++-- frontend/src/common/dom.rs | 8 +- frontend/src/common/hooks.rs | 6 +- frontend/src/common/mod.rs | 2 +- frontend/src/components/notification.rs | 192 ++++++++++++++++-------- frontend/src/main.rs | 6 +- frontend/src/views/home.rs | 2 +- 9 files changed, 222 insertions(+), 96 deletions(-) diff --git a/frontend/Cargo.lock b/frontend/Cargo.lock index 169f034..5b14871 100644 --- a/frontend/Cargo.lock +++ b/frontend/Cargo.lock @@ -1477,8 +1477,8 @@ dependencies = [ "getrandom 0.2.15", "js-sys", "rand 0.8.5", - "tokio", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", ] diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index cc9b333..dfaaf31 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -10,11 +10,12 @@ edition = "2021" dioxus = { version = "0.6.0", features = ["router"] } dioxus-free-icons = { version = "0.9", features = ["bootstrap"] } wasm-bindgen = "0.2.99" +wasm-bindgen-futures = "0.4" web-sys = { version = "0.3.76", features = ["Window","Storage","MediaQueryList","Document","DomTokenList","Element","MediaQueryListEvent"] } js-sys = "0.3.76" rand = "0.8.5" getrandom = { version = "0.2", features = ["js"] } -tokio = "1.42.0" + [features] default = ["dioxus/web"] diff --git a/frontend/assets/styling/tailwind.css b/frontend/assets/styling/tailwind.css index 3b18f50..5f74ffa 100644 --- a/frontend/assets/styling/tailwind.css +++ b/frontend/assets/styling/tailwind.css @@ -554,6 +554,10 @@ video { display: none; } +.static { + position: static; +} + .absolute { position: absolute; } @@ -566,8 +570,8 @@ video { right: 2rem; } -.top-10 { - top: 2.5rem; +.top-5 { + top: 1.25rem; } .top-\[0\.06rem\] { @@ -598,10 +602,6 @@ video { height: 0.25rem; } -.h-full { - height: 100%; -} - .w-\[20rem\] { width: 20rem; } @@ -636,12 +636,34 @@ video { border-radius: 0.25rem; } +.rounded-md { + border-radius: 0.375rem; +} + +.border { + border-width: 1px; +} + .border-2 { border-width: 2px; } -.\!border-none { - border-style: none !important; +.border-solid { + border-style: solid; +} + +.border-none { + border-style: none; +} + +.border-blue-500 { + --tw-border-opacity: 1; + border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)); +} + +.border-green-500 { + --tw-border-opacity: 1; + border-color: rgb(34 197 94 / var(--tw-border-opacity, 1)); } .border-red-500 { @@ -649,14 +671,34 @@ video { border-color: rgb(239 68 68 / var(--tw-border-opacity, 1)); } +.bg-blue-200 { + --tw-bg-opacity: 1; + background-color: rgb(191 219 254 / var(--tw-bg-opacity, 1)); +} + .bg-blue-500 { --tw-bg-opacity: 1; background-color: rgb(59 130 246 / var(--tw-bg-opacity, 1)); } -.bg-slate-400 { +.bg-green-200 { --tw-bg-opacity: 1; - background-color: rgb(148 163 184 / var(--tw-bg-opacity, 1)); + background-color: rgb(187 247 208 / var(--tw-bg-opacity, 1)); +} + +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity, 1)); +} + +.bg-red-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 202 202 / var(--tw-bg-opacity, 1)); +} + +.bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); } .px-3 { @@ -669,21 +711,44 @@ video { padding-bottom: 0.75rem; } +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-blue-800 { + --tw-text-opacity: 1; + color: rgb(30 64 175 / var(--tw-text-opacity, 1)); +} + .text-gray-800 { --tw-text-opacity: 1; color: rgb(31 41 55 / var(--tw-text-opacity, 1)); } -.opacity-75 { - opacity: 0.75; +.text-green-800 { + --tw-text-opacity: 1; + color: rgb(22 101 52 / var(--tw-text-opacity, 1)); } -.filter { - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +.text-red-800 { + --tw-text-opacity: 1; + color: rgb(153 27 27 / var(--tw-text-opacity, 1)); } -.hover\:text-accent:hover { - color: var(--accent-color); +.transition-all { + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.ease-linear { + transition-timing-function: linear; } .dark\:text-gray-200:is(.dark *) { diff --git a/frontend/src/common/dom.rs b/frontend/src/common/dom.rs index 0890567..253b195 100644 --- a/frontend/src/common/dom.rs +++ b/frontend/src/common/dom.rs @@ -55,8 +55,8 @@ pub fn remove_element_class(ele_name: &str, class_name: &str) -> CustomResult<() Ok(get_element(ele_name)?.class_list().remove_1(class_name)?) } -pub fn remove_element(ele_name: &str)-> CustomResult<()> { - let e=get_element(ele_name)?; - let _=e.parent_node().ok_or("无法获取父节点")?.remove_child(&e)?; +pub fn remove_element(ele_name: &str) -> CustomResult<()> { + let e = get_element(ele_name)?; + let _ = e.parent_node().ok_or("无法获取父节点")?.remove_child(&e)?; Ok(()) -} \ No newline at end of file +} diff --git a/frontend/src/common/hooks.rs b/frontend/src/common/hooks.rs index d2b2cf9..a686bb2 100644 --- a/frontend/src/common/hooks.rs +++ b/frontend/src/common/hooks.rs @@ -1,5 +1 @@ -use tokio; - -pub fn set_interval(time:u32,callback:fn()){ - -} \ No newline at end of file +pub fn set_interval(time: u32, callback: fn()) {} diff --git a/frontend/src/common/mod.rs b/frontend/src/common/mod.rs index 6eda43c..0b065ef 100644 --- a/frontend/src/common/mod.rs +++ b/frontend/src/common/mod.rs @@ -1,4 +1,4 @@ pub mod dom; pub mod error; pub mod helps; -pub mod hooks; \ No newline at end of file +pub mod hooks; diff --git a/frontend/src/components/notification.rs b/frontend/src/components/notification.rs index 93dca57..e48e1cd 100644 --- a/frontend/src/components/notification.rs +++ b/frontend/src/components/notification.rs @@ -1,15 +1,27 @@ +use std::format; + use crate::common::error::{CustomErrorInto, CustomResult}; use crate::common::helps::generate_random_string; use crate::Route; use dioxus::{logger::tracing, prelude::*}; use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg}; use dioxus_free_icons::Icon; +use wasm_bindgen::prelude::*; +use web_sys::window; #[derive(PartialEq, Clone)] pub struct NotificationProvider { pub notifications: Vec, } +impl Default for NotificationProvider { + fn default() -> Self { + Self { + notifications: Vec::new(), + } + } +} + #[derive(PartialEq, Clone)] pub enum NotificationType { Info, @@ -40,14 +52,16 @@ impl Default for NotificationProps { #[derive(PartialEq, Clone)] struct NoticationColorMatching { - color: String, icon: Element, + bg_color: &'static str, + border_color: &'static str, + text_color: &'static str, + progress_color: &'static str, } fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMatching { match notification_type { NotificationType::Info => NoticationColorMatching { - color: String::from("rgb(38,131,255)"), icon: rsx! { Icon { icon: BsInfoCircle, @@ -55,9 +69,12 @@ fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMa height:18, } }, + bg_color: "bg-blue-200", + border_color: "border-blue-500", + text_color: "text-blue-800", + progress_color: "bg-blue-500", }, NotificationType::Error => NoticationColorMatching { - color: String::from("rgb(225,45,57)"), icon: rsx! { Icon { icon: BsXCircle, @@ -65,9 +82,12 @@ fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMa height:18, } }, + bg_color: "bg-red-200", + border_color: "border-red-500", + text_color: "text-red-800", + progress_color: "bg-red-500", }, NotificationType::Success => NoticationColorMatching { - color: String::from("rgb(0,168,91)"), icon: rsx! { Icon { icon: BsCheckCircle, @@ -75,23 +95,113 @@ fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMa height:18, } }, + bg_color: "bg-green-200", + border_color: "border-green-500", + text_color: "text-green-800", + progress_color: "bg-green-500", }, } } pub fn remove_notification(id: String) { let mut notifications_context = use_context::>(); - let new_notifications:Vec = notifications_context.clone()() - .notifications - .into_iter() - .filter(|i| i.id != id ) - .collect(); - - notifications_context.set(NotificationProvider { - notifications: new_notifications, + 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); }); } +#[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()), + } + + } + } + + } + } +} + #[component] pub fn Notification() -> Element { let notifications_context = use_context::>(); @@ -99,50 +209,11 @@ pub fn Notification() -> Element { return rsx! { div { - class: "w-[20rem] absolute right-8 top-10 max-sm:w-[12rem]", + class: "w-[20rem] absolute right-8 top-5 max-sm:w-[12rem] ", {notifications.notifications.iter().map(move |item| { - let color_matching=get_color_matching(&item.r#type); - let id = item.id.clone(); - rsx!{ - div { - id: id.clone(), - class:"rounded px-3 py-3 m-2 !border-none text-gray-800 dark:text-gray-200 opacity-75 ", - style:format!("background-color:{}",color_matching.color), - div { - div { - class:"flex items-center justify-between", - div { - class:"mr-2 relative top-[0.06rem] hover:text-accent", - {color_matching.icon}, - } - div { - class:"truncate min-w-0", - {item.title.clone()} - } - div { - onclick:move |e|{ - e.prevent_default(); - remove_notification(id.clone()); - }, - class:"ml-1 flex-shrink-0", - Icon{ - icon:BsXLg, - height:18, - width:18 - } - } - } - div { - class:"mt-2 h-1 relative", - div { - class:"absolute bg-slate-400 w-full h-full", - } - div { - class:"absolute w-full h-full ", - style:"width:80%" - } - } - } + rsx! { + NotificationCard { + ..item.clone() } } })} @@ -158,35 +229,32 @@ impl Toast { let mut notification_context = use_context::>(); let mut new_provider = notification_context().clone(); let mut new_props = props; - new_props.id = format!("notification-{}",generate_random_string(10)); + new_props.id = format!("notification-{}", generate_random_string(10)); new_provider.notifications.push(new_props); notification_context.set(new_provider); } pub fn info(title: String, content: String) { Self::show(NotificationProps { - id: Default::default(), title, content, - time: Default::default(), r#type: NotificationType::Info, + ..NotificationProps::default() }); } pub fn error(title: String, content: String) { Self::show(NotificationProps { - id: Default::default(), title, content, - time: Default::default(), r#type: NotificationType::Error, + ..NotificationProps::default() }); } pub fn success(title: String, content: String) { Self::show(NotificationProps { - id: Default::default(), title, content, - time: Default::default(), r#type: NotificationType::Success, + ..NotificationProps::default() }); } } diff --git a/frontend/src/main.rs b/frontend/src/main.rs index b06962a..1926c78 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -30,11 +30,7 @@ fn main() { fn App() -> Element { let theme = get_theme(); use_context_provider(|| Signal::new(ThemeProvider(theme.clone()))); - use_context_provider(|| { - Signal::new(NotificationProvider { - notifications: Vec::new(), - }) - }); + use_context_provider(|| Signal::new(NotificationProvider::default())); let mut is_dark_context = use_context::>(); use_effect(move || { diff --git a/frontend/src/views/home.rs b/frontend/src/views/home.rs index 2e14a63..b5b5684 100644 --- a/frontend/src/views/home.rs +++ b/frontend/src/views/home.rs @@ -29,7 +29,7 @@ pub fn Home() -> Element { "success" } - + } } }