完善弹窗样式,完善主题更改,如果储存主题和当前主题相同就不储存主题使用默认主题

This commit is contained in:
lsy 2024-12-25 23:26:35 +08:00
parent e52c49a346
commit 1f885b54f3
13 changed files with 351 additions and 131 deletions

5
frontend/Cargo.lock generated
View File

@ -1474,6 +1474,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"dioxus", "dioxus",
"dioxus-free-icons", "dioxus-free-icons",
"getrandom 0.2.15",
"js-sys",
"rand 0.8.5",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
@ -1736,8 +1739,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
] ]
[[package]] [[package]]

View File

@ -10,7 +10,10 @@ 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"
web-sys = { version = "0.3.76", features = ["Window","Storage","MediaQueryList","Document"] } 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"] }
[features] [features]
default = ["dioxus/web"] default = ["dioxus/web"]

View File

@ -1,11 +1,18 @@
.light{ .light{
--accent-color:#3F51B5; --accent-color:#3F51B5;
} color:black;
.light .box{
background:
} }
.dark{ .dark{
--accent-color:#4d648d; --accent-color:#4d648d;
color:white;
}
html.dark{
background-color:black;
}
html.light{
background-color:white;
} }

View File

@ -562,30 +562,76 @@ video {
position: relative; position: relative;
} }
.right-1 {
right: 0.25rem;
}
.right-8 { .right-8 {
right: 2rem; right: 2rem;
} }
.top-1 {
top: 0.25rem;
}
.top-10 { .top-10 {
top: 2.5rem; top: 2.5rem;
} }
.float-end { .top-\[0\.06rem\] {
float: inline-end; top: 0.06rem;
}
.m-2 {
margin: 0.5rem;
}
.ml-1 {
margin-left: 0.25rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.mt-2 {
margin-top: 0.5rem;
}
.flex {
display: flex;
}
.h-1 {
height: 0.25rem;
} }
.w-\[20rem\] { .w-\[20rem\] {
width: 20rem; width: 20rem;
} }
.min-w-0 {
min-width: 0px;
}
.flex-shrink-0 {
flex-shrink: 0;
}
@keyframes progress {
0% {
width: 0%;
}
100% {
width: 100%;
}
}
.animate-progress {
animation: 5s progress linear infinite;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.truncate { .truncate {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -596,18 +642,27 @@ video {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
.border-accent { .border-2 {
border-color: var(--accent-color); border-width: 2px;
} }
.bg-yellow-200 { .\!border-none {
border-style: none !important;
}
.border-red-500 {
--tw-border-opacity: 1;
border-color: rgb(239 68 68 / var(--tw-border-opacity, 1));
}
.bg-white {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(254 240 138 / var(--tw-bg-opacity, 1)); background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
} }
.px-2 { .px-3 {
padding-left: 0.5rem; padding-left: 0.75rem;
padding-right: 0.5rem; padding-right: 0.75rem;
} }
.py-3 { .py-3 {
@ -615,29 +670,18 @@ video {
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.text-base { .text-gray-300 {
font-size: 1rem; --tw-text-opacity: 1;
line-height: 1.5rem; color: rgb(209 213 219 / var(--tw-text-opacity, 1));
} }
.text-lg { .hover\:text-accent:hover {
font-size: 1.125rem; color: var(--accent-color);
line-height: 1.75rem;
} }
.backdrop-blur-xl { .dark\:text-gray-800:is(.dark *) {
--tw-backdrop-blur: blur(24px); --tw-text-opacity: 1;
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); color: rgb(31 41 55 / var(--tw-text-opacity, 1));
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
}
.hover\:border-2:hover {
border-width: 2px;
}
.dark\:bg-sky-500:is(.dark *) {
--tw-bg-opacity: 1;
background-color: rgb(14 165 233 / var(--tw-bg-opacity, 1));
} }
@media not all and (min-width: 640px) { @media not all and (min-width: 640px) {

View File

@ -1,5 +1,5 @@
use super::error::{CustomErrorInto, CustomResult}; use super::error::{CustomErrorInto, CustomResult};
use web_sys::{window, Document, MediaQueryList, Storage, Window}; use web_sys::{window, Document, Element, Storage, Window};
fn get_window() -> CustomResult<Window> { fn get_window() -> CustomResult<Window> {
Ok(window().ok_or("浏览器window对象不存在")?) Ok(window().ok_or("浏览器window对象不存在")?)
@ -8,7 +8,18 @@ fn get_window() -> CustomResult<Window> {
fn get_storage() -> CustomResult<Storage> { fn get_storage() -> CustomResult<Storage> {
get_window()? get_window()?
.local_storage()? .local_storage()?
.ok_or("浏览器不支持localStorage".into_custom_error()) .ok_or("获取浏览器Storge对象失败".into_custom_error())
}
fn get_document() -> CustomResult<Document> {
get_window()?
.document()
.ok_or("获取浏览器Document对象失败".into_custom_error())
}
fn get_element(ele_name: &str) -> CustomResult<Element> {
get_document()?
.query_selector(ele_name)?
.ok_or(format!("获取元素{}失败", ele_name).into_custom_error())
} }
pub fn get_local_storage_value(key: &str) -> CustomResult<String> { pub fn get_local_storage_value(key: &str) -> CustomResult<String> {
@ -21,6 +32,10 @@ pub fn set_local_storage_value(key: &str, value: &str) -> CustomResult<()> {
Ok(get_storage()?.set_item(key, value)?) Ok(get_storage()?.set_item(key, value)?)
} }
pub fn remove_local_storage_value(key: &str) -> CustomResult<()> {
Ok(get_storage()?.remove_item(key)?)
}
pub fn get_media_theme() -> CustomResult<String> { pub fn get_media_theme() -> CustomResult<String> {
let media_query = get_window()? let media_query = get_window()?
.match_media("(prefers-color-scheme: dark)")? .match_media("(prefers-color-scheme: dark)")?
@ -32,13 +47,10 @@ pub fn get_media_theme() -> CustomResult<String> {
Ok("light".to_string()) Ok("light".to_string())
} }
pub fn set_element_class(ele_name: &str, class_name: &str) -> CustomResult<()> { pub fn add_element_class(ele_name: &str, class_name: &str) -> CustomResult<()> {
get_window()? Ok(get_element(ele_name)?.class_list().add_1(class_name)?)
.document() }
.ok_or("浏览器document对象不存在".into_custom_error())?
.query_selector(ele_name)? pub fn remove_element_class(ele_name: &str, class_name: &str) -> CustomResult<()> {
.ok_or(format!("获取元素{}失败", ele_name).into_custom_error())? Ok(get_element(ele_name)?.class_list().remove_1(class_name)?)
.set_class_name(class_name);
Ok(())
} }

View File

@ -0,0 +1,9 @@
use rand::seq::SliceRandom;
pub fn generate_random_string(length: usize) -> String {
let charset = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::thread_rng();
(0..length)
.map(|_| *charset.choose(&mut rng).unwrap() as char)
.collect()
}

View File

@ -1,2 +1,3 @@
pub mod dom; pub mod dom;
pub mod error; pub mod error;
pub mod helps;

View File

@ -3,4 +3,4 @@ pub use navbar::Navbar;
pub mod theme_toggle; pub mod theme_toggle;
pub use theme_toggle::Toggle; pub use theme_toggle::Toggle;
pub mod notification; pub mod notification;
pub use notification::Message; pub use notification::Toast;

View File

@ -1,55 +1,155 @@
use crate::common::error::{CustomErrorInto, CustomResult}; use crate::common::error::{CustomErrorInto, CustomResult};
use crate::common::helps::generate_random_string;
use crate::Route;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg};
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::bs_icons::BsXLg;
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum MessageType { pub struct NotificationProvider {
pub notifications: Vec<NotificationProps>,
}
#[derive(PartialEq, Clone)]
pub enum NotificationType {
Info, Info,
Warn,
Error, Error,
Success, Success,
} }
#[derive(PartialEq, Props, Clone)] #[derive(PartialEq, Props, Clone)]
pub struct MessageProps { pub struct NotificationProps {
#[props(default="wight".to_string())]
color: String,
#[props(default="".to_string())] #[props(default="".to_string())]
title: String, title: String,
#[props(default="".to_string())] #[props(default="".to_string())]
message: String, content: String,
#[props(default=3)] #[props(default = 5)]
time:u64, time: u64,
#[props(default=NotificationType::Info)]
r#type: NotificationType,
}
#[derive(PartialEq, Clone)]
struct NoticationColorMatching {
color: String,
icon: Element,
}
fn get_color_matching(notification_type: &NotificationType) -> NoticationColorMatching {
match notification_type {
NotificationType::Info => NoticationColorMatching {
color: String::from("rgba(0,168,91,0.85)"),
icon: rsx! {
Icon {
icon: BsInfoCircle,
width:18,
height:18,
}
},
},
NotificationType::Error => NoticationColorMatching {
color: String::from("rgba(225,45,57,0.85)"),
icon: rsx! {
Icon {
icon: BsXCircle,
width:18,
height:18,
}
},
},
NotificationType::Success => NoticationColorMatching {
color: String::from("rgba(38,131,255,0.85)"),
icon: rsx! {
Icon {
icon: BsCheckCircle,
width:18,
height:18,
}
},
},
}
} }
#[component] #[component]
pub fn Message(props: MessageProps) -> Element { pub fn Notification() -> Element {
let notifications = use_context::<Signal<NotificationProvider>>()().notifications;
return rsx! { return rsx! {
div { div {
class: "w-[20rem] px-2 py-3 absolute right-8 top-10 backdrop-blur-xl max-sm:w-[12rem] hover:border-2 border-accent rounded ", class: "w-[20rem] absolute right-8 top-10 max-sm:w-[12rem]",
style: "background-color:{props.color}", {notifications.iter().map(|item| {
let color_matching=get_color_matching(&item.r#type);
rsx!{
div {
id:format!("notification-{}",generate_random_string(10)),
class:"rounded px-3 py-3 m-2 !border-none text-gray-300 dark:text-gray-800 ",
style:format!("background-color:{}",color_matching.color),
div {
div {
class:"flex items-center justify-between",
div {
onclick:|e|{
div { },
class:"mr-2 relative top-[0.06rem] hover:text-accent",
class:"text-lg", {color_matching.icon},
div { }
class:"float-end relative top-1 right-1", div {
Icon{ class:"truncate min-w-0",
icon:BsXLg, {item.title.clone()}
}
div {
class:"ml-1 flex-shrink-0",
Icon{
icon:BsXLg,
height:18,
width:18
}
}
}
div {
class:"mt-2 h-1 bg-white animate-progress",
style: format!("animation-duration: {}s", item.time),
}
}
} }
} }
div { })}
class:"truncate", }
"我是标题水水水水水水水水水水水水水水水水水水水水水sssssssssssss水水水ssssssssssssssss" Outlet::<Route> {}
}
}
div {
class:"text-base",
"我是水水水水水水水水水ssssssssssssssssssssss水水水水内容"
}
}
}; };
} }
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();
new_provider.notifications.push(props);
notification_context.set(new_provider);
}
pub fn info(title: String, content: String) {
Self::show(NotificationProps {
title,
content,
time: 5,
r#type: NotificationType::Info,
});
}
pub fn error(title: String, content: String) {
Self::show(NotificationProps {
title,
content,
time: 5,
r#type: NotificationType::Error,
});
}
pub fn success(title: String, content: String) {
Self::show(NotificationProps {
title,
content,
time: 5,
r#type: NotificationType::Success,
});
}
}

View File

@ -1,10 +1,14 @@
use crate::common::dom::{set_element_class, set_local_storage_value}; use crate::common::dom::{
add_element_class, get_local_storage_value, get_media_theme, remove_element_class,
remove_local_storage_value, set_local_storage_value,
};
use dioxus::{logger::tracing, prelude::*}; use dioxus::{logger::tracing, prelude::*};
use dioxus_free_icons::icons::bs_icons::BsMoonStars; use dioxus_free_icons::icons::bs_icons::BsMoonStars;
use dioxus_free_icons::icons::bs_icons::BsSun; use dioxus_free_icons::icons::bs_icons::BsSun;
use dioxus_free_icons::Icon; use dioxus_free_icons::Icon;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct IsDark(pub String); pub struct ThemeProvider(pub String);
#[derive(PartialEq, Props, Clone)] #[derive(PartialEq, Props, Clone)]
pub struct ToggleProps { pub struct ToggleProps {
@ -18,37 +22,34 @@ pub struct ToggleProps {
#[component] #[component]
pub fn Toggle(props: ToggleProps) -> Element { pub fn Toggle(props: ToggleProps) -> Element {
let mut dark_context = use_context::<Signal<IsDark>>(); let mut theme_context = use_context::<Signal<ThemeProvider>>();
rsx! { rsx! {
div { div {
onclick: move |_| onclick: move |_|
{ {
let theme; let system_theme=get_media_theme().unwrap_or_else(|_|"".to_string());
if dark_context().0=="light" { let target_theme;
theme="dark"
if theme_context().0=="light" {
target_theme="dark".to_string()
}else { }else {
theme="light" target_theme="light".to_string()
}; };
dark_context.set(IsDark(theme.to_string())); let _=remove_element_class("html", &theme_context().0);
match set_local_storage_value("theme",theme) { theme_context.set(ThemeProvider(target_theme.clone()));
Ok(_)=>{}, let _=add_element_class("html", &target_theme);
Err(_)=>{ if target_theme==system_theme {
tracing::error!("主题储存失败"); let _=remove_local_storage_value("theme");
}, }else{
let _=set_local_storage_value("theme", &target_theme);
} }
match set_element_class("html",theme) {
Ok(_)=>{},
Err(e)=>{
tracing::error!("主题类名设置失败:{}",e);
},
}
} }
, ,
{ {
match {dark_context().0.as_str()} { match {theme_context().0.as_str()} {
"dark"=>{ "dark"=>{
rsx!( rsx!(
Icon{ Icon{
@ -78,3 +79,17 @@ pub fn Toggle(props: ToggleProps) -> Element {
} }
} }
pub fn get_theme() -> String {
let storage_theme = get_local_storage_value("theme");
match storage_theme {
Ok(s) => s,
Err(_) => {
let device_theme = get_media_theme();
match device_theme {
Ok(s) => s,
Err(_) => "light".to_string(),
}
}
}
}

View File

@ -1,7 +1,9 @@
use dioxus::{logger::tracing, prelude::*}; use dioxus::{logger::tracing, prelude::*};
use wasm_bindgen::prelude::*;
mod common; mod common;
use common::dom::{get_local_storage_value, get_media_theme, set_element_class}; use common::dom::{add_element_class, get_media_theme, remove_element_class};
use components::theme_toggle::IsDark; use components::notification::{Notification, NotificationProvider};
use components::theme_toggle::{get_theme, ThemeProvider};
use components::Navbar; use components::Navbar;
use views::Home; use views::Home;
@ -10,12 +12,11 @@ mod components;
mod views; mod views;
#[derive(Debug, Clone, Routable, PartialEq)] #[derive(Debug, Clone, Routable, PartialEq)]
#[rustfmt::skip]
enum Route { enum Route {
#[layout(Notification)]
#[layout(Navbar)] #[layout(Navbar)]
#[route("/")] #[route("/")]
Home {}, Home {},
} }
const FAVICON: Asset = asset!("/assets/favicon.ico"); const FAVICON: Asset = asset!("/assets/favicon.ico");
@ -28,25 +29,32 @@ fn main() {
#[component] #[component]
fn App() -> Element { fn App() -> Element {
use_context_provider(|| Signal::new(IsDark("light".to_string()))); let theme = get_theme();
let mut is_dark_context = use_context::<Signal<IsDark>>(); use_context_provider(|| Signal::new(ThemeProvider(theme.clone())));
use_context_provider(|| {
Signal::new(NotificationProvider {
notifications: Vec::new(),
})
});
let mut is_dark_context = use_context::<Signal<ThemeProvider>>();
use_effect(move || { use_effect(move || {
let theme = { let _ = add_element_class("html", &theme);
let storage_theme = get_local_storage_value("theme"); let window = web_sys::window().unwrap();
match storage_theme { let media_query = window
Ok(s) => s, .match_media("(prefers-color-scheme: dark)")
Err(_) => { .unwrap()
let device_theme = get_media_theme(); .unwrap();
match device_theme { let closure = Closure::wrap(Box::new(move |_: web_sys::MediaQueryListEvent| {
Ok(s) => s, let theme = get_media_theme().unwrap_or_else(|_| "light".to_string());
Err(_) => "light".to_string(), let _ = remove_element_class("html", &is_dark_context().0);
} let _ = add_element_class("html", &theme);
} is_dark_context.set(ThemeProvider(theme));
} }) as Box<dyn FnMut(_)>);
}; media_query
is_dark_context.set(IsDark(theme.clone())); .add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())
let _ = set_element_class("html", &theme); .unwrap();
closure.forget();
}); });
rsx! { rsx! {

View File

@ -1,16 +1,21 @@
use crate::components::Toast;
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::components::Message;
#[component] #[component]
pub fn Home() -> Element { pub fn Home() -> Element {
rsx! { rsx! {
Message{},
div { div {
class:"bg-yellow-200 dark:bg-sky-500", class:"border-2 border-red-500",
"hello,world" "hello,world"
ul { ul {
li { "nihao" } li { "nihao" }
}
} }
button {
onclick: move |_| {
Toast::info("我是标sasadc我想二次TV要不以后牛魔题".to_string(), "我是内容".to_string())
},
"Show Notification"
}
}
} }
} }

View File

@ -7,6 +7,17 @@ module.exports = {
colors: { colors: {
accent: 'var(--accent-color)', accent: 'var(--accent-color)',
}, },
animation:{
progress:"5s progress linear infinite"
},
keyframes:{
progress:{
'0%':{width:'0%'},
'100%':{width:'100%'}
}
}
}, },
}, },
plugins: [], plugins: [],