前端:优化错误处理,提取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",
|
"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
1
frontend/Cargo.lock
generated
@ -1474,6 +1474,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-free-icons",
|
"dioxus-free-icons",
|
||||||
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -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"]
|
||||||
|
@ -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;
|
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));
|
|
||||||
}
|
}
|
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)]
|
#[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>;
|
||||||
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
|
pub mod dom;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
@ -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;
|
@ -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",
|
||||||
|
@ -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,
|
||||||
|
@ -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 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> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,4 +6,5 @@ module.exports = {
|
|||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
darkMode: ['class'],
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user