前端:优化错误处理,提取web_sys操作,完善主题切换,
This commit is contained in:
parent
351ee3f675
commit
e7ebf60586
22
.vscode/tasks.json
vendored
22
.vscode/tasks.json
vendored
@ -24,16 +24,34 @@
|
||||
{
|
||||
"label": "frontend-dev",
|
||||
"type": "shell",
|
||||
"command": "dx serve --platform web",
|
||||
"command": "dx serve",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "frontend-start",
|
||||
"type": "shell",
|
||||
"command": "serve .",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/frontend/target/dx/frontend/release/web/public"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "frontend-build",
|
||||
"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": {
|
||||
"cwd": "${workspaceFolder}/frontend"
|
||||
},
|
||||
|
1
frontend/Cargo.lock
generated
1
frontend/Cargo.lock
generated
@ -1474,6 +1474,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"dioxus",
|
||||
"dioxus-free-icons",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
|
@ -9,11 +9,11 @@ edition = "2021"
|
||||
[dependencies]
|
||||
dioxus = { version = "0.6.0", features = ["router"] }
|
||||
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]
|
||||
default = []
|
||||
default = ["dioxus/web"]
|
||||
web = ["dioxus/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
mobile = ["dioxus/mobile"]
|
||||
|
@ -1,8 +0,0 @@
|
||||
#blog {
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
#blog a {
|
||||
color: #ffffff;
|
||||
margin-top: 50px;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
#navbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
@ -554,21 +554,12 @@ video {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.bg-sky-700 {
|
||||
.bg-yellow-200 {
|
||||
--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;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
||||
}
|
||||
|
||||
.text-slate-100 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(241 245 249 / var(--tw-text-opacity, 1));
|
||||
background-color: rgb(14 165 233 / var(--tw-bg-opacity, 1));
|
||||
}
|
44
frontend/src/common/dom.rs
Normal file
44
frontend/src/common/dom.rs
Normal 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(())
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CustomError(String);
|
||||
|
||||
@ -17,11 +19,26 @@ impl CustomErrorInto for &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: std::error::Error> From<E> for CustomError {
|
||||
fn from(error: E) -> Self {
|
||||
impl From<std::io::Error> for CustomError {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
pub type CustomResult<T> = Result<T, CustomError>;
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod dom;
|
||||
pub mod error;
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod navbar;
|
||||
pub mod navbar;
|
||||
pub use navbar::Navbar;
|
||||
mod theme_toggle;
|
||||
pub use theme_toggle::Toggle;
|
||||
pub mod theme_toggle;
|
||||
pub use theme_toggle::Toggle;
|
||||
|
@ -1,13 +1,10 @@
|
||||
use super::Toggle;
|
||||
use crate::Route;
|
||||
use dioxus::prelude::*;
|
||||
const NAVBAR_CSS: Asset = asset!("/assets/styling/navbar.css");
|
||||
use super::Toggle;
|
||||
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: NAVBAR_CSS }
|
||||
|
||||
div {
|
||||
id: "navbar",
|
||||
|
@ -1,8 +1,10 @@
|
||||
use crate::common::dom::{set_element_class, 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;
|
||||
use crate::{IsDark,set_local_storage_value};
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct IsDark(pub String);
|
||||
|
||||
#[derive(PartialEq, Props, Clone)]
|
||||
pub struct ToggleProps {
|
||||
@ -16,24 +18,38 @@ pub struct ToggleProps {
|
||||
|
||||
#[component]
|
||||
pub fn Toggle(props: ToggleProps) -> Element {
|
||||
let mut dark_context=use_context::<Signal<IsDark>>();
|
||||
let mut dark_context = use_context::<Signal<IsDark>>();
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
onclick: move |_|
|
||||
onclick: move |_|
|
||||
{
|
||||
dark_context.set(IsDark(!dark_context().0));
|
||||
match set_local_storage_value("theme", if !dark_context().0 { "true" } else { "false" }) {
|
||||
let theme;
|
||||
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(_)=>{},
|
||||
Err(_)=>{
|
||||
tracing::error!("主题储存失败");
|
||||
},
|
||||
}
|
||||
match set_element_class("html",theme) {
|
||||
Ok(_)=>{},
|
||||
Err(e)=>{
|
||||
tracing::error!("主题类名设置失败:{}",e);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
,
|
||||
{
|
||||
match {dark_context().0} {
|
||||
false=>{
|
||||
match {dark_context().0.as_str()} {
|
||||
"dark"=>{
|
||||
rsx!(
|
||||
Icon{
|
||||
width:props.width,
|
||||
@ -43,7 +59,7 @@ pub fn Toggle(props: ToggleProps) -> Element {
|
||||
}
|
||||
)
|
||||
},
|
||||
true=>{
|
||||
_=>{
|
||||
rsx!{
|
||||
Icon{
|
||||
width:props.width,
|
||||
|
@ -1,4 +0,0 @@
|
||||
@import "@radix-ui/themes/styles.css";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
@ -1,7 +1,7 @@
|
||||
use dioxus::{logger::tracing, prelude::*};
|
||||
use web_sys::{window, MediaQueryList, Storage};
|
||||
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 views::Home;
|
||||
@ -20,41 +20,7 @@ enum Route {
|
||||
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const GLOBAL_CSS: Asset = asset!("/assets/styling/global.css");
|
||||
const TAILWIND_CSS: Asset = asset!("/assets/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)
|
||||
}
|
||||
const TAILWIND_CSS: Asset = asset!("/assets/styling/tailwind.css");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(App);
|
||||
@ -62,32 +28,33 @@ fn main() {
|
||||
|
||||
#[component]
|
||||
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>>();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let _ = use_memo(move || {
|
||||
use_effect(move || {
|
||||
let theme = {
|
||||
let storage_theme = get_local_storage_value("theme");
|
||||
match storage_theme {
|
||||
Ok(b) => is_dark_context.set(IsDark(b == "true")),
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
let device_theme = get_media_theme();
|
||||
match device_theme {
|
||||
Ok(b) => is_dark_context.set(IsDark(b)),
|
||||
Err(_) => is_dark_context.set(IsDark(false))
|
||||
Ok(s) => s,
|
||||
Err(_) => "light".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
is_dark_context.set(IsDark(theme.clone()));
|
||||
let _ = set_element_class("html", &theme);
|
||||
});
|
||||
|
||||
rsx! {
|
||||
// Global app resources
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Stylesheet{ href: GLOBAL_CSS }
|
||||
document::Stylesheet { href: TAILWIND_CSS }
|
||||
|
||||
Router::<Route> {}
|
||||
div {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Stylesheet{ href: GLOBAL_CSS }
|
||||
document::Stylesheet { href: TAILWIND_CSS }
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,12 @@ use dioxus::prelude::*;
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
rsx! {
|
||||
div { "hello,world" }
|
||||
div {
|
||||
class:"bg-yellow-200 dark:bg-sky-500",
|
||||
"hello,world"
|
||||
ul {
|
||||
li { "nihao" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,5 @@ module.exports = {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
darkMode: ['class'],
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user