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

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

View File

@ -10,7 +10,10 @@ edition = "2021"
dioxus = { version = "0.6.0", features = ["router"] }
dioxus-free-icons = { version = "0.9", features = ["bootstrap"] }
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]
default = ["dioxus/web"]

View File

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

View File

@ -562,30 +562,76 @@ video {
position: relative;
}
.right-1 {
right: 0.25rem;
}
.right-8 {
right: 2rem;
}
.top-1 {
top: 0.25rem;
}
.top-10 {
top: 2.5rem;
}
.float-end {
float: inline-end;
.top-\[0\.06rem\] {
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\] {
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 {
overflow: hidden;
text-overflow: ellipsis;
@ -596,18 +642,27 @@ video {
border-radius: 0.25rem;
}
.border-accent {
border-color: var(--accent-color);
.border-2 {
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;
background-color: rgb(254 240 138 / var(--tw-bg-opacity, 1));
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
.px-3 {
padding-left: 0.75rem;
padding-right: 0.75rem;
}
.py-3 {
@ -615,29 +670,18 @@ video {
padding-bottom: 0.75rem;
}
.text-base {
font-size: 1rem;
line-height: 1.5rem;
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity, 1));
}
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
.hover\:text-accent:hover {
color: var(--accent-color);
}
.backdrop-blur-xl {
--tw-backdrop-blur: blur(24px);
-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);
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));
.dark\:text-gray-800:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(31 41 55 / var(--tw-text-opacity, 1));
}
@media not all and (min-width: 640px) {

View File

@ -1,5 +1,5 @@
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> {
Ok(window().ok_or("浏览器window对象不存在")?)
@ -8,7 +8,18 @@ fn get_window() -> CustomResult<Window> {
fn get_storage() -> CustomResult<Storage> {
get_window()?
.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> {
@ -21,6 +32,10 @@ pub fn set_local_storage_value(key: &str, value: &str) -> CustomResult<()> {
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> {
let media_query = get_window()?
.match_media("(prefers-color-scheme: dark)")?
@ -32,13 +47,10 @@ pub fn get_media_theme() -> CustomResult<String> {
Ok("light".to_string())
}
pub fn set_element_class(ele_name: &str, class_name: &str) -> CustomResult<()> {
get_window()?
.document()
.ok_or("浏览器document对象不存在".into_custom_error())?
.query_selector(ele_name)?
.ok_or(format!("获取元素{}失败", ele_name).into_custom_error())?
.set_class_name(class_name);
Ok(())
pub fn add_element_class(ele_name: &str, class_name: &str) -> CustomResult<()> {
Ok(get_element(ele_name)?.class_list().add_1(class_name)?)
}
pub fn remove_element_class(ele_name: &str, class_name: &str) -> CustomResult<()> {
Ok(get_element(ele_name)?.class_list().remove_1(class_name)?)
}

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 error;
pub mod helps;

View File

@ -3,4 +3,4 @@ pub use navbar::Navbar;
pub mod theme_toggle;
pub use theme_toggle::Toggle;
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::helps::generate_random_string;
use crate::Route;
use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::{BsCheckCircle, BsInfoCircle, BsXCircle, BsXLg};
use dioxus_free_icons::Icon;
use dioxus_free_icons::icons::bs_icons::BsXLg;
#[derive(PartialEq, Clone)]
pub enum MessageType {
pub struct NotificationProvider {
pub notifications: Vec<NotificationProps>,
}
#[derive(PartialEq, Clone)]
pub enum NotificationType {
Info,
Warn,
Error,
Success,
}
#[derive(PartialEq, Props, Clone)]
pub struct MessageProps {
#[props(default="wight".to_string())]
color: String,
pub struct NotificationProps {
#[props(default="".to_string())]
title: String,
#[props(default="".to_string())]
message: String,
#[props(default=3)]
time:u64,
content: String,
#[props(default = 5)]
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]
pub fn Message(props: MessageProps) -> Element {
pub fn Notification() -> Element {
let notifications = use_context::<Signal<NotificationProvider>>()().notifications;
return rsx! {
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 ",
style: "background-color:{props.color}",
class: "w-[20rem] absolute right-8 top-10 max-sm:w-[12rem]",
{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:"text-lg",
div {
class:"float-end relative top-1 right-1",
Icon{
icon:BsXLg,
},
class:"mr-2 relative top-[0.06rem] hover:text-accent",
{color_matching.icon},
}
div {
class:"truncate min-w-0",
{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"
}
}
div {
class:"text-base",
"我是水水水水水水水水水ssssssssssssssssssssss水水水水内容"
}
}
}
})}
}
Outlet::<Route> {}
};
}
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_free_icons::icons::bs_icons::BsMoonStars;
use dioxus_free_icons::icons::bs_icons::BsSun;
use dioxus_free_icons::Icon;
#[derive(Debug, Clone, PartialEq)]
pub struct IsDark(pub String);
pub struct ThemeProvider(pub String);
#[derive(PartialEq, Props, Clone)]
pub struct ToggleProps {
@ -18,37 +22,34 @@ pub struct ToggleProps {
#[component]
pub fn Toggle(props: ToggleProps) -> Element {
let mut dark_context = use_context::<Signal<IsDark>>();
let mut theme_context = use_context::<Signal<ThemeProvider>>();
rsx! {
div {
onclick: move |_|
{
let theme;
if dark_context().0=="light" {
theme="dark"
let system_theme=get_media_theme().unwrap_or_else(|_|"".to_string());
let target_theme;
if theme_context().0=="light" {
target_theme="dark".to_string()
}else {
theme="light"
target_theme="light".to_string()
};
dark_context.set(IsDark(theme.to_string()));
match set_local_storage_value("theme",theme) {
Ok(_)=>{},
Err(_)=>{
tracing::error!("主题储存失败");
},
let _=remove_element_class("html", &theme_context().0);
theme_context.set(ThemeProvider(target_theme.clone()));
let _=add_element_class("html", &target_theme);
if target_theme==system_theme {
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"=>{
rsx!(
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 wasm_bindgen::prelude::*;
mod common;
use common::dom::{get_local_storage_value, get_media_theme, set_element_class};
use components::theme_toggle::IsDark;
use common::dom::{add_element_class, get_media_theme, remove_element_class};
use components::notification::{Notification, NotificationProvider};
use components::theme_toggle::{get_theme, ThemeProvider};
use components::Navbar;
use views::Home;
@ -10,12 +12,11 @@ mod components;
mod views;
#[derive(Debug, Clone, Routable, PartialEq)]
#[rustfmt::skip]
enum Route {
#[layout(Notification)]
#[layout(Navbar)]
#[route("/")]
Home {},
}
const FAVICON: Asset = asset!("/assets/favicon.ico");
@ -28,25 +29,32 @@ fn main() {
#[component]
fn App() -> Element {
use_context_provider(|| Signal::new(IsDark("light".to_string())));
let mut is_dark_context = use_context::<Signal<IsDark>>();
let theme = get_theme();
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 || {
let theme = {
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(),
}
}
}
};
is_dark_context.set(IsDark(theme.clone()));
let _ = set_element_class("html", &theme);
let _ = add_element_class("html", &theme);
let window = web_sys::window().unwrap();
let media_query = window
.match_media("(prefers-color-scheme: dark)")
.unwrap()
.unwrap();
let closure = Closure::wrap(Box::new(move |_: web_sys::MediaQueryListEvent| {
let theme = get_media_theme().unwrap_or_else(|_| "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
.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())
.unwrap();
closure.forget();
});
rsx! {

View File

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

View File

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