diff --git a/.env.default b/.env.default new file mode 100644 index 0000000..de2ebe5 --- /dev/null +++ b/.env.default @@ -0,0 +1,3 @@ +APP_SITENAME=萝心花园 +APP_APIURL=https://paul.ren +APP_FOOTER_EXTRA= diff --git a/app/components/layout/footer.tsx b/app/components/layout/footer.tsx new file mode 100644 index 0000000..b506241 --- /dev/null +++ b/app/components/layout/footer.tsx @@ -0,0 +1,14 @@ +const { APP_SITENAME, APP_FOOTER_EXTRA } = import.meta.env; + +function Footer() { + return ( + + ); +} + +export default Footer; diff --git a/app/root.tsx b/app/root.tsx index 3f5f33c..3826963 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -12,6 +12,7 @@ import Header from "./components/layout/header"; import Spinner from "./components/common/spinner"; import "./index.css"; +import Footer from "./components/layout/footer"; export const links: LinksFunction = () => [ ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), @@ -31,6 +32,7 @@ export default function App() { + diff --git a/app/routes/gallery.$/route.tsx b/app/routes/gallery.$/route.tsx new file mode 100644 index 0000000..9ae6d64 --- /dev/null +++ b/app/routes/gallery.$/route.tsx @@ -0,0 +1,71 @@ +import { NavLink, useLoaderData } from "@remix-run/react"; +import { json, LoaderFunctionArgs, type MetaFunction } from "@remix-run/node"; +import { clsn, siteTitle } from "~/utils"; + +import styles from "./styles.module.less"; + +export const meta: MetaFunction = ({ data }) => { + return [ + { title: siteTitle(data?.currentCategory?.name || "相册") }, + { name: "description", content: "奇趣保罗的日常笔记" }, + ]; +}; + +export async function loader({ request, params }: LoaderFunctionArgs) { + const url = new URL(request.url); + const page = url.searchParams.get("page") || "1"; + + const searchParams = new URLSearchParams(); + searchParams.append("page", page); + + const category = await fetch(`https://paul.ren/api/gallery`).then((res) => res.json()) as unknown as API.PageResponse; + let cateIndex = -1; + + if (params["*"]) { + cateIndex = category.data.findIndex((item) => item.slug === params["*"]); + + if (cateIndex === -1) { + throw json("Not Found", { status: 404 }); + } + + searchParams.append("cate", String(category.data[cateIndex].id)); + } + + const media = await fetch(`https://paul.ren/api/media/?${searchParams.toString()}`).then((res) => res.json()) as unknown as API.PageResponse; + const currentCategory = cateIndex > -1 ? category.data[cateIndex]: undefined; + + return json({ media, category, currentCategory }); +} + +export default function Gallery() { + const { media, category } = useLoaderData(); + + return ( + + + 相册 + + clsn("inline-block px-3 py-1 ml-auto mr-3", isActive && "text-pink-400 font-bold")}> + 所有 + + {category.data.map((item) => ( + clsn("inline-block px-3 py-1 mr-3 last:mr-auto", isActive && "text-pink-400 font-bold")}> + {item.name} + + ))} + + + + {media.data.map((item) => ( + + + + {item.take_time.substring(0, 10)} + {item.title} + + + ))} + + + ); +} diff --git a/app/routes/gallery.$/styles.module.less b/app/routes/gallery.$/styles.module.less new file mode 100644 index 0000000..ca03899 --- /dev/null +++ b/app/routes/gallery.$/styles.module.less @@ -0,0 +1,3 @@ +.image { + clip-path: polygon(0 0, 100% 0%, 100% 100%, 0 75%); +} diff --git a/app/types/api.d.ts b/app/types/api.d.ts index 882a18f..739da96 100644 --- a/app/types/api.d.ts +++ b/app/types/api.d.ts @@ -4,4 +4,8 @@ declare namespace API { msg: string; data: D; } + + interface PageResponse extends Response { + count: number; + } } diff --git a/app/types/api.gallery.d.ts b/app/types/api.gallery.d.ts new file mode 100644 index 0000000..d2c83e6 --- /dev/null +++ b/app/types/api.gallery.d.ts @@ -0,0 +1,12 @@ +declare namespace API { + namespace Gallery { + export interface ICateData { + id: number + name: string + slug: string + url: string + description: string + hidden: boolean + } + } +} diff --git a/app/types/api.media.d.ts b/app/types/api.media.d.ts new file mode 100644 index 0000000..186f1bf --- /dev/null +++ b/app/types/api.media.d.ts @@ -0,0 +1,37 @@ +declare namespace API { + namespace Media { + // 数据库本来的数据 + export interface IMediaBaseData { + title: string + origin_name: string + content: string + cate: number + key: string + hidden: boolean + hidden_ref: boolean + is_sensitive: boolean + take_time: string + modified: number | string // 文件修改时间 + photo?: File + } + + // 后端加的数据 + export interface IMediaExtraData extends IMediaBaseData { + file_name: string + type: string + meta: unknown + path: string + + likes: number + created_at: string + updated_at: string + + url: string + thumb_url: string + } + + export interface IMediaData extends IMediaExtraData { + id: number + } + } +}