前端:构建好了主题切换

This commit is contained in:
lsy 2024-12-20 23:45:25 +08:00
commit 351ee3f675
23 changed files with 6215 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Generated by Cargo
# will have compiled files and executables
**/target
.DS_Store
**/.idea
# These are backup files generated by rustfmt
**/*.rs.bk

73
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,73 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "backend",
"type": "shell",
"command": "cargo run",
"options": {
"cwd": "${workspaceFolder}/backend"
},
"problemMatcher": [
"$armcc5"
]
},
{
"label": "backend-fmt",
"type": "shell",
"command": "cargo fmt",
"options": {
"cwd": "${workspaceFolder}/backend"
},
"problemMatcher": []
},
{
"label": "frontend-dev",
"type": "shell",
"command": "dx serve --platform web",
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"problemMatcher": []
},
{
"label": "frontend-build",
"type": "shell",
"command": "dx build --platform web",
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"problemMatcher": []
},
{
"label": "frontend-fmt",
"type": "shell",
"command": "cargo fmt",
"options": {
"cwd": "${workspaceFolder}/frontend"
},
"problemMatcher": []
},
{
"label": "format-all",
"dependsOn": [
"backend-fmt",
"frontend-fmt"
],
"dependsOrder": "parallel",
"problemMatcher": []
},
{
"label": "run-all",
"dependsOn": [
"backend",
"frontend-dev"
],
"dependsOrder": "parallel",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

1
README.md Normal file
View File

@ -0,0 +1 @@
## 由rust构建的前后端博客

5193
frontend/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

31
frontend/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
[package]
name = "frontend"
version = "0.1.0"
authors = ["lsy <lsy22@vip.qq.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[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"] }
[features]
default = []
web = ["dioxus/web"]
desktop = ["dioxus/desktop"]
mobile = ["dioxus/mobile"]
[profile]
[profile.wasm-dev]
inherits = "dev"
opt-level = 1
[profile.server-dev]
inherits = "dev"
[profile.android-dev]
inherits = "dev"

24
frontend/Dioxus.toml Normal file
View File

@ -0,0 +1,24 @@
[application]
# App (Project) Name
name = "frontend"
[web.app]
# HTML title tag content
title = "frontend"
# include `assets` in web platform
[web.resource]
# Additional CSS style files
style = []
# Additional JavaScript files
script = []
[web.resource.dev]
# Javascript code file
# serve: [dev-server] only
script = []

BIN
frontend/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

View File

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

View File

@ -0,0 +1,38 @@
:root {
--transition-duration: 150ms;
--transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
--hljs-theme: 'github';
}
:root[class~="dark"] {
--hljs-theme: 'github-dark';
}
/* 确保 Radix UI 主题类包裹整个应用 */
.radix-themes {
transition:
background-color var(--transition-duration) var(--transition-easing),
color var(--transition-duration) var(--transition-easing);
min-height: 100%;
}
/* 基础布局样式 */
html,
body {
height: 100%;
}
/* 添加暗色模式支持 */
.radix-themes-dark {
@apply dark;
}
/* 隐藏不活跃的主题样式 */
[data-theme="light"] .hljs-dark {
display: none;
}
[data-theme="dark"] .hljs-light {
display: none;
}

View File

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

View File

@ -0,0 +1,574 @@
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
--tw-contain-size: ;
--tw-contain-layout: ;
--tw-contain-paint: ;
--tw-contain-style: ;
}
/*
! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
letter-spacing: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
input:where([type='button']),
input:where([type='reset']),
input:where([type='submit']) {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden]:where(:not([hidden="until-found"])) {
display: none;
}
.static {
position: static;
}
.bg-sky-700 {
--tw-bg-opacity: 1;
background-color: rgb(3 105 161 / var(--tw-bg-opacity, 1));
}
.bg-white {
--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));
}

3
frontend/input.css Normal file
View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,27 @@
#[derive(Debug)]
pub struct CustomError(String);
impl std::fmt::Display for CustomError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
pub trait CustomErrorInto {
fn into_custom_error(self) -> CustomError;
}
impl CustomErrorInto for &str {
fn into_custom_error(self) -> CustomError {
CustomError(self.to_string())
}
}
impl<E: std::error::Error> From<E> for CustomError {
fn from(error: E) -> Self {
CustomError(error.to_string())
}
}
pub type CustomResult<T> = Result<T, CustomError>;

View File

@ -0,0 +1 @@
pub mod error;

View File

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

View File

@ -0,0 +1,25 @@
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",
Link {
to: Route::Home {},
"home"
}
Toggle{}
}
Outlet::<Route> {}
}
}

View File

@ -0,0 +1,64 @@
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(PartialEq, Props, Clone)]
pub struct ToggleProps {
#[props(default = 20)]
pub height: u32,
#[props(default = 20)]
pub width: u32,
#[props(default = "currentColor".to_string())]
pub fill: String,
}
#[component]
pub fn Toggle(props: ToggleProps) -> Element {
let mut dark_context=use_context::<Signal<IsDark>>();
rsx! {
div {
onclick: move |_|
{
dark_context.set(IsDark(!dark_context().0));
match set_local_storage_value("theme", if !dark_context().0 { "true" } else { "false" }) {
Ok(_)=>{},
Err(_)=>{
tracing::error!("主题储存失败");
},
}
}
,
{
match {dark_context().0} {
false=>{
rsx!(
Icon{
width:props.width,
height:props.height,
fill:props.fill,
icon:BsMoonStars,
}
)
},
true=>{
rsx!{
Icon{
width:props.width,
height:props.height,
fill:props.fill,
icon:BsSun,
}
}
}
}
}
}
}
}

4
frontend/src/input.css Normal file
View File

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

93
frontend/src/main.rs Normal file
View File

@ -0,0 +1,93 @@
use dioxus::{logger::tracing, prelude::*};
use web_sys::{window, MediaQueryList, Storage};
mod common;
use common::error::{CustomErrorInto, CustomResult};
use components::Navbar;
use views::Home;
mod components;
mod views;
#[derive(Debug, Clone, Routable, PartialEq)]
#[rustfmt::skip]
enum Route {
#[layout(Navbar)]
#[route("/")]
Home {},
}
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)
}
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
use_context_provider(|| Signal::new(IsDark(false)));
let mut is_dark_context = use_context::<Signal<IsDark>>();
#[cfg(target_arch = "wasm32")]
{
let _ = use_memo(move || {
let storage_theme = get_local_storage_value("theme");
match storage_theme {
Ok(b) => is_dark_context.set(IsDark(b == "true")),
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))
}
}
}
});
}
rsx! {
// Global app resources
document::Link { rel: "icon", href: FAVICON }
document::Stylesheet{ href: GLOBAL_CSS }
document::Stylesheet { href: TAILWIND_CSS }
Router::<Route> {}
}
}

View File

@ -0,0 +1,8 @@
use dioxus::prelude::*;
#[component]
pub fn Home() -> Element {
rsx! {
div { "hello,world" }
}
}

View File

@ -0,0 +1,2 @@
mod home;
pub use home::Home;

View File

@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
mode: "all",
content: ["./src/**/*.{rs,html,css}", "./dist/**/*.html"],
theme: {
extend: {},
},
plugins: [],
};