前端:优化错误处理,提取web_sys操作,完善主题切换,

This commit is contained in:
lsy 2024-12-22 00:43:10 +08:00
parent 351ee3f675
commit e7ebf60586
17 changed files with 148 additions and 106 deletions

22
.vscode/tasks.json vendored
View File

@ -24,16 +24,34 @@
{ {
"label": "frontend-dev", "label": "frontend-dev",
"type": "shell", "type": "shell",
"command": "dx serve --platform web", "command": "dx serve",
"options": { "options": {
"cwd": "${workspaceFolder}/frontend" "cwd": "${workspaceFolder}/frontend"
}, },
"problemMatcher": [] "problemMatcher": []
},
{
"label": "frontend-start",
"type": "shell",
"command": "serve .",
"options": {
"cwd": "${workspaceFolder}/frontend/target/dx/frontend/release/web/public"
},
"problemMatcher": []
}, },
{ {
"label": "frontend-build", "label": "frontend-build",
"type": "shell", "type": "shell",
"command": "dx build --platform web", "command": "dx build --release",
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"problemMatcher": []
},
{
"label": "frontend-tailwind-build",
"type": "shell",
"command": "npx tailwindcss -i ./tailwind.css -o ./assets/styling/tailwind.css",
"options": { "options": {
"cwd": "${workspaceFolder}/frontend" "cwd": "${workspaceFolder}/frontend"
}, },

1
frontend/Cargo.lock generated
View File

@ -1474,6 +1474,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"dioxus", "dioxus",
"dioxus-free-icons", "dioxus-free-icons",
"wasm-bindgen",
"web-sys", "web-sys",
] ]

View File

@ -9,11 +9,11 @@ edition = "2021"
[dependencies] [dependencies]
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"] }
web-sys = { version = "0.3.76", features = ["Window","Storage","MediaQueryList"] } wasm-bindgen = "0.2.99"
web-sys = { version = "0.3.76", features = ["Window","Storage","MediaQueryList","Document"] }
[features] [features]
default = [] default = ["dioxus/web"]
web = ["dioxus/web"] web = ["dioxus/web"]
desktop = ["dioxus/desktop"] desktop = ["dioxus/desktop"]
mobile = ["dioxus/mobile"] mobile = ["dioxus/mobile"]

View File

@ -1,8 +0,0 @@
#blog {
margin-top: 50px;
}
#blog a {
color: #ffffff;
margin-top: 50px;
}

View File

@ -1,5 +0,0 @@
#navbar {
display: flex;
flex-direction: row;
}

View File

@ -554,21 +554,12 @@ video {
display: none; display: none;
} }
.static { .bg-yellow-200 {
position: static;
}
.bg-sky-700 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(3 105 161 / var(--tw-bg-opacity, 1)); background-color: rgb(254 240 138 / var(--tw-bg-opacity, 1));
} }
.bg-white { .dark\:bg-sky-500:is(.dark *) {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); background-color: rgb(14 165 233 / var(--tw-bg-opacity, 1));
}
.text-slate-100 {
--tw-text-opacity: 1;
color: rgb(241 245 249 / var(--tw-text-opacity, 1));
} }

View File

@ -0,0 +1,44 @@
use super::error::{CustomErrorInto, CustomResult};
use web_sys::{window, Document, MediaQueryList, Storage, Window};
fn get_window() -> CustomResult<Window> {
Ok(window().ok_or("浏览器window对象不存在")?)
}
fn get_storage() -> CustomResult<Storage> {
get_window()?
.local_storage()?
.ok_or("浏览器不支持localStorage".into_custom_error())
}
pub fn get_local_storage_value(key: &str) -> CustomResult<String> {
get_storage()?
.get_item(key)?
.ok_or(format!("localStorage中不存在键'{}'", key).into_custom_error())
}
pub fn set_local_storage_value(key: &str, value: &str) -> CustomResult<()> {
Ok(get_storage()?.set_item(key, value)?)
}
pub fn get_media_theme() -> CustomResult<String> {
let media_query = get_window()?
.match_media("(prefers-color-scheme: dark)")?
.ok_or("查询media时发生错误".into_custom_error())?
.matches();
if media_query {
return Ok("dark".to_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(())
}

View File

@ -1,3 +1,5 @@
use wasm_bindgen::JsValue;
#[derive(Debug)] #[derive(Debug)]
pub struct CustomError(String); pub struct CustomError(String);
@ -17,11 +19,26 @@ impl CustomErrorInto for &str {
} }
} }
impl<E: std::error::Error> From<E> for CustomError { impl From<std::io::Error> for CustomError {
fn from(error: E) -> Self { fn from(error: std::io::Error) -> Self {
CustomError(error.to_string())
}
}
impl From<JsValue> for CustomError {
fn from(error: JsValue) -> Self {
CustomError(
error
.as_string()
.unwrap_or_else(|| String::from("Unknown JS error")),
)
}
}
impl From<&str> for CustomError {
fn from(error: &str) -> Self {
CustomError(error.to_string()) CustomError(error.to_string())
} }
} }
pub type CustomResult<T> = Result<T, CustomError>; pub type CustomResult<T> = Result<T, CustomError>;

View File

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

View File

@ -1,4 +1,4 @@
mod navbar; pub mod navbar;
pub use navbar::Navbar; pub use navbar::Navbar;
mod theme_toggle; pub mod theme_toggle;
pub use theme_toggle::Toggle; pub use theme_toggle::Toggle;

View File

@ -1,13 +1,10 @@
use super::Toggle;
use crate::Route; use crate::Route;
use dioxus::prelude::*; use dioxus::prelude::*;
const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
use super::Toggle;
#[component] #[component]
pub fn Navbar() -> Element { pub fn Navbar() -> Element {
rsx! { rsx! {
document::Link { rel: "stylesheet", href: NAVBAR_CSS }
div { div {
id: "navbar", id: "navbar",

View File

@ -1,8 +1,10 @@
use crate::common::dom::{set_element_class, 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;
use crate::{IsDark,set_local_storage_value}; #[derive(Debug, Clone, PartialEq)]
pub struct IsDark(pub String);
#[derive(PartialEq, Props, Clone)] #[derive(PartialEq, Props, Clone)]
pub struct ToggleProps { pub struct ToggleProps {
@ -22,18 +24,32 @@ pub fn Toggle(props: ToggleProps) -> Element {
div { div {
onclick: move |_| onclick: move |_|
{ {
dark_context.set(IsDark(!dark_context().0)); let theme;
match set_local_storage_value("theme", if !dark_context().0 { "true" } else { "false" }) { if dark_context().0=="light" {
theme="dark"
}else {
theme="light"
};
dark_context.set(IsDark(theme.to_string()));
match set_local_storage_value("theme",theme) {
Ok(_)=>{}, Ok(_)=>{},
Err(_)=>{ Err(_)=>{
tracing::error!("主题储存失败"); tracing::error!("主题储存失败");
}, },
} }
match set_element_class("html",theme) {
Ok(_)=>{},
Err(e)=>{
tracing::error!("主题类名设置失败:{}",e);
},
}
} }
, ,
{ {
match {dark_context().0} { match {dark_context().0.as_str()} {
false=>{ "dark"=>{
rsx!( rsx!(
Icon{ Icon{
width:props.width, width:props.width,
@ -43,7 +59,7 @@ pub fn Toggle(props: ToggleProps) -> Element {
} }
) )
}, },
true=>{ _=>{
rsx!{ rsx!{
Icon{ Icon{
width:props.width, width:props.width,

View File

@ -1,4 +0,0 @@
@import "@radix-ui/themes/styles.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,7 +1,7 @@
use dioxus::{logger::tracing, prelude::*}; use dioxus::{logger::tracing, prelude::*};
use web_sys::{window, MediaQueryList, Storage};
mod common; mod common;
use common::error::{CustomErrorInto, CustomResult}; use common::dom::{get_local_storage_value, get_media_theme, set_element_class};
use components::theme_toggle::IsDark;
use components::Navbar; use components::Navbar;
use views::Home; use views::Home;
@ -20,41 +20,7 @@ enum Route {
const FAVICON: Asset = asset!("/assets/favicon.ico"); const FAVICON: Asset = asset!("/assets/favicon.ico");
const GLOBAL_CSS: Asset = asset!("/assets/styling/global.css"); const GLOBAL_CSS: Asset = asset!("/assets/styling/global.css");
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css"); const TAILWIND_CSS: Asset = asset!("/assets/styling/tailwind.css");
#[derive(Debug, Clone, PartialEq)]
pub struct IsDark(bool);
fn get_storage() -> CustomResult<Storage> {
window()
.ok_or("浏览器window对象不存在".into_custom_error())?
.local_storage()
.map_err(|_| "无法访问localStorage API".into_custom_error())?
.ok_or("浏览器不支持localStorage".into_custom_error())
}
pub fn get_local_storage_value(key: &str) -> CustomResult<String> {
get_storage()?
.get_item(key)
.map_err(|_| "读取localStorage时发生错误".into_custom_error())?
.ok_or(format!("localStorage中不存在键'{}'", key).into_custom_error())
}
pub fn set_local_storage_value(key: &str, value: &str) -> CustomResult<()> {
get_storage()?
.set_item(key, value)
.map_err(|_| format!("无法将值写入localStorage的'{}'键", key).into_custom_error())
}
pub fn get_media_theme() -> CustomResult<bool> {
let media_query = window()
.ok_or("浏览器window对象不存在".into_custom_error())?
.match_media("(prefers-color-scheme: dark)")
.map_err(|_| format!("读取媒体查询时发生错误").into_custom_error())?
.ok_or("查询media时发生错误".into_custom_error())?
.matches();
Ok(media_query)
}
fn main() { fn main() {
dioxus::launch(App); dioxus::launch(App);
@ -62,32 +28,33 @@ fn main() {
#[component] #[component]
fn App() -> Element { fn App() -> Element {
use_context_provider(|| Signal::new(IsDark(false))); use_context_provider(|| Signal::new(IsDark("light".to_string())));
let mut is_dark_context = use_context::<Signal<IsDark>>(); let mut is_dark_context = use_context::<Signal<IsDark>>();
#[cfg(target_arch = "wasm32")] use_effect(move || {
{ let theme = {
let _ = use_memo(move || {
let storage_theme = get_local_storage_value("theme"); let storage_theme = get_local_storage_value("theme");
match storage_theme { match storage_theme {
Ok(b) => is_dark_context.set(IsDark(b == "true")), Ok(s) => s,
Err(_) => { Err(_) => {
let device_theme = get_media_theme(); let device_theme = get_media_theme();
match device_theme { match device_theme {
Ok(b) => is_dark_context.set(IsDark(b)), Ok(s) => s,
Err(_) => is_dark_context.set(IsDark(false)) Err(_) => "light".to_string(),
} }
} }
} }
};
is_dark_context.set(IsDark(theme.clone()));
let _ = set_element_class("html", &theme);
}); });
}
rsx! { rsx! {
// Global app resources div {
document::Link { rel: "icon", href: FAVICON } document::Link { rel: "icon", href: FAVICON }
document::Stylesheet{ href: GLOBAL_CSS } document::Stylesheet{ href: GLOBAL_CSS }
document::Stylesheet { href: TAILWIND_CSS } document::Stylesheet { href: TAILWIND_CSS }
Router::<Route> {} Router::<Route> {}
} }
} }
}

View File

@ -3,6 +3,12 @@ use dioxus::prelude::*;
#[component] #[component]
pub fn Home() -> Element { pub fn Home() -> Element {
rsx! { rsx! {
div { "hello,world" } div {
class:"bg-yellow-200 dark:bg-sky-500",
"hello,world"
ul {
li { "nihao" }
}
}
} }
} }

View File

@ -6,4 +6,5 @@ module.exports = {
extend: {}, extend: {},
}, },
plugins: [], plugins: [],
darkMode: ['class'],
}; };