diff --git a/app/components/common/article/article.module.less b/app/components/common/article/article.module.less
new file mode 100644
index 0000000..f0f3f45
--- /dev/null
+++ b/app/components/common/article/article.module.less
@@ -0,0 +1,30 @@
+.article {
+ line-height: 1.7;
+
+ > * {
+ margin-bottom: 1rem;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ pre {
+ tab-size: 4;
+ padding: 1em;
+ color: #fff;
+ overflow: auto;
+ border-radius: .75rem;
+ background-color: #333;
+ }
+
+ ul {
+ line-height: 2;
+ list-style: disc;
+ margin-left: 1.25rem;
+
+ ::marker {
+ color: rgb(244 114 182 / var(--tw-bg-opacity));
+ }
+ }
+}
diff --git a/app/components/common/article/index.tsx b/app/components/common/article/index.tsx
new file mode 100644
index 0000000..edd1dd5
--- /dev/null
+++ b/app/components/common/article/index.tsx
@@ -0,0 +1,15 @@
+import { clsn } from "~/utils";
+import styles from "./article.module.less";
+
+interface ArticleProps {
+ className?: string;
+ html: string;
+}
+
+function Article({ className, html }: ArticleProps) {
+ return (
+
+ );
+}
+
+export default Article;
diff --git a/app/components/common/spinner.tsx b/app/components/common/spinner.tsx
new file mode 100644
index 0000000..cd5fe68
--- /dev/null
+++ b/app/components/common/spinner.tsx
@@ -0,0 +1,9 @@
+import { useNavigation } from "@remix-run/react";
+
+export default function SpinnerBar() {
+ const navigation = useNavigation();
+
+ return navigation.state === "loading" ? (
+
+ ) : null;
+}
diff --git a/app/components/layout/header.tsx b/app/components/layout/header.tsx
new file mode 100644
index 0000000..cfeec0a
--- /dev/null
+++ b/app/components/layout/header.tsx
@@ -0,0 +1,31 @@
+import { Link, NavLink } from "@remix-run/react";
+
+const navItems = [
+ { name: "首页", to: "/" },
+ { name: "日记", to: "/note" },
+ { name: "相册", to: "/gallery" },
+ { name: "关于我", to: "/about" },
+];
+
+const inheritCls = "inline-block py-2 px-5";
+const activeCls = "inline-block py-2 px-5 bg-orange-200 text-pink-400 rounded-xl";
+
+function Header() {
+ return (
+
+
+
+ );
+}
+
+export default Header;
diff --git a/app/root.tsx b/app/root.tsx
index c7f9883..3f5f33c 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -8,22 +8,28 @@ import {
Scripts,
ScrollRestoration,
} from "@remix-run/react";
+import Header from "./components/layout/header";
+import Spinner from "./components/common/spinner";
import "./index.css";
+
export const links: LinksFunction = () => [
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
+ { rel: "stylesheet", href: "https://cdn-font.hyperos.mi.com/font/css?family=MiSans:100,200,300,400,450,500,600,650,700,900:Chinese_Simplify,Latin&display=swap" },
];
export default function App() {
return (
-
+
-
+
+
+
diff --git a/app/routes/note.$year.$id.tsx b/app/routes/note.$year.$id.tsx
new file mode 100644
index 0000000..ab16e7e
--- /dev/null
+++ b/app/routes/note.$year.$id.tsx
@@ -0,0 +1,33 @@
+import { LoaderFunctionArgs, json } from "@remix-run/node";
+import { useLoaderData } from "@remix-run/react";
+import Article from "~/components/common/article";
+
+export async function loader({ params }: LoaderFunctionArgs) {
+ if (Number.isNaN(Number(params.year)) || Number.isNaN(Number(params.id))) {
+ throw json("Not Found", { status: 404 });
+ }
+
+ const note = await fetch(`https://paul.ren/api/note/get?id=${params.id}&year=${params.year}`).then((res) => res.json()) as API.Response;
+
+ if (note.status === "Failed") {
+ throw json("Not Found", { status: 404 });
+ }
+
+ return json(note);
+}
+
+export default function Detail() {
+ const note = useLoaderData();
+
+ return (
+
+
+ {note.data.title}
+ {note.data.date}
+
+
+
+ );
+};
diff --git a/app/routes/note._index.tsx b/app/routes/note._index.tsx
new file mode 100644
index 0000000..bf85c07
--- /dev/null
+++ b/app/routes/note._index.tsx
@@ -0,0 +1,51 @@
+import { useEffect } from "react";
+import { Link, useLoaderData } from "@remix-run/react";
+import { json, type MetaFunction } from "@remix-run/node";
+
+export const meta: MetaFunction = () => {
+ return [
+ { title: "日记" },
+ { name: "description", content: "奇趣保罗的日常笔记" },
+ ];
+};
+
+export async function loader() {
+ const note = await fetch("https://paul.ren/api/note").then((res) => res.json()) as API.Response;
+
+ return json(note);
+}
+
+export default function Note() {
+ const note = useLoaderData();
+
+ useEffect(() => {
+ console.log(note);
+ }, []);
+
+ return (
+
+
+ {note.data.map((item) => {
+ const year = item.date.substring(0, 4);
+
+ return (
+
+
{item.title}
+
{item.except}
+
+
{item.date}
+
+ 继续阅读
+
+
+
+ );
+ })}
+
+ );
+}
diff --git a/app/types/api.d.ts b/app/types/api.d.ts
new file mode 100644
index 0000000..882a18f
--- /dev/null
+++ b/app/types/api.d.ts
@@ -0,0 +1,7 @@
+declare namespace API {
+ interface Response {
+ status: "Success" | "Failed";
+ msg: string;
+ data: D;
+ }
+}
diff --git a/app/types/api.note.d.ts b/app/types/api.note.d.ts
new file mode 100644
index 0000000..345e66e
--- /dev/null
+++ b/app/types/api.note.d.ts
@@ -0,0 +1,69 @@
+declare namespace API {
+ namespace Note {
+ enum NoteType {
+ Private,
+ Friends,
+ Limited,
+ Public,
+ }
+
+ interface INoteMusic {
+ id: number;
+ type?: "netease";
+ title: string;
+ artist: string;
+ album: string;
+ cover: string;
+ }
+
+ interface INoteQuery {
+ page: number;
+ year?: number;
+ month?: number;
+ search?: string;
+ }
+
+ interface INoteDetailQuery {
+ id: number;
+ year?: number;
+ }
+
+ // 旧数据兼容
+ interface INotePhotoData {
+ year: number | string;
+ name: string;
+ type: string;
+ url: string;
+ }
+
+ interface INoteData {
+ id: number;
+ title: string;
+ content: string;
+ except: string;
+ content_html: string;
+
+ date: string;
+ mood: number;
+ weather: number;
+ status: number;
+ type: NoteType;
+ time_spent: number;
+ music?: INoteMusic;
+ starred: boolean;
+ unlocked?: boolean;
+
+ media: any[];
+ photo?: INotePhotoData[];
+
+ year?: string;
+
+ time: number;
+
+ likes: number;
+
+ created_at: string;
+ updated_at: string;
+ }
+ }
+}
diff --git a/app/utils/index.ts b/app/utils/index.ts
new file mode 100644
index 0000000..7a86661
--- /dev/null
+++ b/app/utils/index.ts
@@ -0,0 +1,4 @@
+// Classnames
+export const clsn = (...clsn: (string | undefined | null | false)[]) => {
+ return clsn.filter(item => item).join(" ");
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index ea446d6..7360ae8 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -5,7 +5,20 @@ export default {
"./app/**/*.{js,ts,jsx,tsx}",
],
theme: {
- extend: {},
+ extend: {
+ fontFamily: {
+ "mi": "MiSans",
+ },
+ animation: {
+ 'spinner-bar': 'spinnerBar 6s linear infinite',
+ },
+ keyframes: {
+ spinnerBar: {
+ "0%": { width: "0%" },
+ "100%": { width: "100%" },
+ }
+ }
+ },
},
plugins: [],
}