更新了弹窗的进度条和配色

This commit is contained in:
lsy 2025-01-04 00:02:23 +08:00
parent 995a3262ed
commit fdcb8bd065
9 changed files with 222 additions and 96 deletions

2
frontend/Cargo.lock generated
View File

@ -1477,8 +1477,8 @@ dependencies = [
"getrandom 0.2.15", "getrandom 0.2.15",
"js-sys", "js-sys",
"rand 0.8.5", "rand 0.8.5",
"tokio",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures",
"web-sys", "web-sys",
] ]

View File

@ -10,11 +10,12 @@ edition = "2021"
dioxus = { version = "0.6.0", features = ["router"] } dioxus = { version = "0.6.0", features = ["router"] }
dioxus-free-icons = { version = "0.9", features = ["bootstrap"] } dioxus-free-icons = { version = "0.9", features = ["bootstrap"] }
wasm-bindgen = "0.2.99" wasm-bindgen = "0.2.99"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3.76", features = ["Window","Storage","MediaQueryList","Document","DomTokenList","Element","MediaQueryListEvent"] } web-sys = { version = "0.3.76", features = ["Window","Storage","MediaQueryList","Document","DomTokenList","Element","MediaQueryListEvent"] }
js-sys = "0.3.76" js-sys = "0.3.76"
rand = "0.8.5" rand = "0.8.5"
getrandom = { version = "0.2", features = ["js"] } getrandom = { version = "0.2", features = ["js"] }
tokio = "1.42.0"
[features] [features]
default = ["dioxus/web"] default = ["dioxus/web"]

View File

@ -554,6 +554,10 @@ video {
display: none; display: none;
} }
.static {
position: static;
}
.absolute { .absolute {
position: absolute; position: absolute;
} }
@ -566,8 +570,8 @@ video {
right: 2rem; right: 2rem;
} }
.top-10 { .top-5 {
top: 2.5rem; top: 1.25rem;
} }
.top-\[0\.06rem\] { .top-\[0\.06rem\] {
@ -598,10 +602,6 @@ video {
height: 0.25rem; height: 0.25rem;
} }
.h-full {
height: 100%;
}
.w-\[20rem\] { .w-\[20rem\] {
width: 20rem; width: 20rem;
} }
@ -636,12 +636,34 @@ video {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.rounded-md {
border-radius: 0.375rem;
}
.border {
border-width: 1px;
}
.border-2 { .border-2 {
border-width: 2px; border-width: 2px;
} }
.\!border-none { .border-solid {
border-style: none !important; 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 { .border-red-500 {
@ -649,14 +671,34 @@ video {
border-color: rgb(239 68 68 / var(--tw-border-opacity, 1)); 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 { .bg-blue-500 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--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; --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 { .px-3 {
@ -669,21 +711,44 @@ video {
padding-bottom: 0.75rem; 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 { .text-gray-800 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity, 1)); color: rgb(31 41 55 / var(--tw-text-opacity, 1));
} }
.opacity-75 { .text-green-800 {
opacity: 0.75; --tw-text-opacity: 1;
color: rgb(22 101 52 / var(--tw-text-opacity, 1));
} }
.filter { .text-red-800 {
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); --tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity, 1));
} }
.hover\:text-accent:hover { .transition-all {
color: var(--accent-color); 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 *) { .dark\:text-gray-200:is(.dark *) {

View File

@ -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)?) Ok(get_element(ele_name)?.class_list().remove_1(class_name)?)
} }
pub fn remove_element(ele_name: &str)-> CustomResult<()> { pub fn remove_element(ele_name: &str) -> CustomResult<()> {
let e=get_element(ele_name)?; let e = get_element(ele_name)?;
let _=e.parent_node().ok_or("无法获取父节点")?.remove_child(&e)?; let _ = e.parent_node().ok_or("无法获取父节点")?.remove_child(&e)?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1 @@
use tokio; pub fn set_interval(time: u32, callback: fn()) {}
pub fn set_interval(time:u32,callback:fn()){
}

View File

@ -1,4 +1,4 @@
pub mod dom; pub mod dom;
pub mod error; pub mod error;
pub mod helps; pub mod helps;
pub mod hooks; pub mod hooks;

View File

@ -1,15 +1,27 @@
use std::format;
use crate::common::error::{CustomErrorInto, CustomResult}; use crate::common::error::{CustomErrorInto, CustomResult};
use crate::common::helps::generate_random_string; use crate::common::helps::generate_random_string;
use crate::Route; use crate::Route;
use dioxus::{logger::tracing, prelude::*}; use dioxus::{logger::tracing, prelude::*};
use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg}; use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg};
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use wasm_bindgen::prelude::*;
use web_sys::window;
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub struct NotificationProvider { pub struct NotificationProvider {
pub notifications: Vec<NotificationProps>, pub notifications: Vec<NotificationProps>,
} }
impl Default for NotificationProvider {
fn default() -> Self {
Self {
notifications: Vec::new(),
}
}
}
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum NotificationType { pub enum NotificationType {
Info, Info,
@ -40,14 +52,16 @@ impl Default for NotificationProps {
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
struct NoticationColorMatching { struct NoticationColorMatching {
color: String,
icon: Element, 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 { fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMatching {
match notification_type { match notification_type {
NotificationType::Info => NoticationColorMatching { NotificationType::Info => NoticationColorMatching {
color: String::from("rgb(38,131,255)"),
icon: rsx! { icon: rsx! {
Icon { Icon {
icon: BsInfoCircle, icon: BsInfoCircle,
@ -55,9 +69,12 @@ fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMa
height:18, 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 { NotificationType::Error => NoticationColorMatching {
color: String::from("rgb(225,45,57)"),
icon: rsx! { icon: rsx! {
Icon { Icon {
icon: BsXCircle, icon: BsXCircle,
@ -65,9 +82,12 @@ fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMa
height:18, 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 { NotificationType::Success => NoticationColorMatching {
color: String::from("rgb(0,168,91)"),
icon: rsx! { icon: rsx! {
Icon { Icon {
icon: BsCheckCircle, icon: BsCheckCircle,
@ -75,23 +95,113 @@ fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMa
height:18, 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) { pub fn remove_notification(id: String) {
let mut notifications_context = use_context::<Signal<NotificationProvider>>(); let mut notifications_context = use_context::<Signal<NotificationProvider>>();
let new_notifications:Vec<NotificationProps> = notifications_context.clone()() tracing::info!("开始删除通知ID: {}", id);
.notifications tracing::info!(
.into_iter() "当前通知列表长度: {}",
.filter(|i| i.id != id ) notifications_context().notifications.len()
.collect(); );
notifications_context.with_mut(|state| {
notifications_context.set(NotificationProvider { let before_len = state.notifications.len();
notifications: new_notifications, 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] #[component]
pub fn Notification() -> Element { pub fn Notification() -> Element {
let notifications_context = use_context::<Signal<NotificationProvider>>(); let notifications_context = use_context::<Signal<NotificationProvider>>();
@ -99,50 +209,11 @@ pub fn Notification() -> Element {
return rsx! { return rsx! {
div { 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| { {notifications.notifications.iter().map(move |item| {
let color_matching=get_color_matching(&item.r#type); rsx! {
let id = item.id.clone(); NotificationCard {
rsx!{ ..item.clone()
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%"
}
}
}
} }
} }
})} })}
@ -158,35 +229,32 @@ impl Toast {
let mut notification_context = use_context::<Signal<NotificationProvider>>(); let mut notification_context = use_context::<Signal<NotificationProvider>>();
let mut new_provider = notification_context().clone(); let mut new_provider = notification_context().clone();
let mut new_props = props; 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); new_provider.notifications.push(new_props);
notification_context.set(new_provider); notification_context.set(new_provider);
} }
pub fn info(title: String, content: String) { pub fn info(title: String, content: String) {
Self::show(NotificationProps { Self::show(NotificationProps {
id: Default::default(),
title, title,
content, content,
time: Default::default(),
r#type: NotificationType::Info, r#type: NotificationType::Info,
..NotificationProps::default()
}); });
} }
pub fn error(title: String, content: String) { pub fn error(title: String, content: String) {
Self::show(NotificationProps { Self::show(NotificationProps {
id: Default::default(),
title, title,
content, content,
time: Default::default(),
r#type: NotificationType::Error, r#type: NotificationType::Error,
..NotificationProps::default()
}); });
} }
pub fn success(title: String, content: String) { pub fn success(title: String, content: String) {
Self::show(NotificationProps { Self::show(NotificationProps {
id: Default::default(),
title, title,
content, content,
time: Default::default(),
r#type: NotificationType::Success, r#type: NotificationType::Success,
..NotificationProps::default()
}); });
} }
} }

View File

@ -30,11 +30,7 @@ fn main() {
fn App() -> Element { fn App() -> Element {
let theme = get_theme(); let theme = get_theme();
use_context_provider(|| Signal::new(ThemeProvider(theme.clone()))); use_context_provider(|| Signal::new(ThemeProvider(theme.clone())));
use_context_provider(|| { use_context_provider(|| Signal::new(NotificationProvider::default()));
Signal::new(NotificationProvider {
notifications: Vec::new(),
})
});
let mut is_dark_context = use_context::<Signal<ThemeProvider>>(); let mut is_dark_context = use_context::<Signal<ThemeProvider>>();
use_effect(move || { use_effect(move || {

View File

@ -29,7 +29,7 @@ pub fn Home() -> Element {
"success" "success"
} }
} }
} }
} }