Skip to content

Commit

Permalink
Merge pull request #61 from meetqy/58-批量打印
Browse files Browse the repository at this point in the history
58 批量打印
  • Loading branch information
meetqy authored Mar 22, 2024
2 parents 1e8f34f + b9fbfa2 commit e60cd0b
Show file tree
Hide file tree
Showing 11 changed files with 423 additions and 256 deletions.
8 changes: 8 additions & 0 deletions src/app/[lang]/components/menu/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ArrowUpRightIcon,
Check,
GithubIcon,
Printer,
Rocket,
Rows2,
SendIcon,
Expand Down Expand Up @@ -94,6 +95,13 @@ export function Content({
variant: /^(\/tag)/.test(pathname) ? "default" : "ghost",
href: `/${lang}/tag`,
},
{
title: dict.menu.print,
icon: Printer,
label: <ArrowRight className="h-4 w-4 text-destructive" />,
variant: /^(\/print)/.test(pathname) ? "default" : "ghost",
href: `/tools/print`,
},
]}
/>

Expand Down
9 changes: 9 additions & 0 deletions src/app/tools/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Root from "../root";

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<Root lang="zh-Hans" languageComponent={false}>
{children}
</Root>
);
}
14 changes: 14 additions & 0 deletions src/app/tools/print/components/aside.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ToggleOption } from "./toggle-option";
import { SelectPoem } from "./select-poem";

export const Aside = () => {
return (
<>
<aside className="fixed left-0 top-0 flex h-full w-72 flex-col space-y-8 bg-muted/50 p-4">
<SelectPoem />
<ToggleOption />
</aside>
<aside className="w-72"></aside>
</>
);
};
205 changes: 205 additions & 0 deletions src/app/tools/print/components/preview-print.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
"use client";

import { chunk } from "lodash-es";
import { useRef } from "react";

import { cn } from "~/utils";
import { type Author, type Poem } from "@prisma/client";
import { useSearchParams } from "next/navigation";
import { useReactToPrint } from "react-to-print";
import { Button } from "~/components/ui/button";

const Row = ({
text,
align = "center",
className,
border,
}: {
text: string;
align?: "left" | "right" | "center";
className?: string;
border?: boolean;
}) => {
const num = 12;
const left = Math.floor((num - text.length) / 2) + text.length;

let data = text.padStart(left).padEnd(num).split("");
if (align === "right") {
data = text.padStart(num).split("");
}

if (align === "left") {
data = text.padEnd(num).split("");
}

return (
<div
className={cn(
"grid w-full grid-cols-12 border-y",
!border && "border-transparent",
)}
>
{data.map((item, index) => (
<div
key={index}
className={cn(
"flex items-center justify-center border-r",
index === 0 && "border-l",
!border && "border-transparent",
className,
)}
>
{item}
</div>
))}
</div>
);
};

const PyRow = ({
py,
className,
border,
align,
}: {
py: string;
className?: string;
align?: "left" | "right" | "center";
border?: boolean;
}) => {
const num = 12;
const arr = py.split(" ");

let left = Math.floor((num - arr.length) / 2);

if (align === "right") {
left = num - arr.length;
}

const data = new Array(12).fill("").map((_, index) => {
return index < left ? "" : arr[index - left];
});

return (
<div
className={cn(
"mt-4 grid h-14 grid-cols-12 border-t text-2xl text-neutral-500",
className,
!border && "border-transparent",
)}
>
{data.map((item, index) => (
<div
key={index}
className="relative flex w-full items-center justify-center"
>
{border && (
<>
<div className="absolute top-1 h-3 w-full border-b-2 border-dashed"></div>
<div className="absolute bottom-3 h-3 w-full border-b-2 border-dashed"></div>
</>
)}
<span className="relative z-10 font-serif font-light">{item}</span>
</div>
))}
</div>
);
};

export default function PreviewPrint({
poem,
}: {
poem: Poem & { author: Author };
}) {
const componentRef = useRef<HTMLDivElement>(null);
const searchParams = useSearchParams();

const opts = {
translation: searchParams.get("translation") === "true",
py: searchParams.get("py") === "true",
border: searchParams.get("border") === "true",
};

const handlePrint = useReactToPrint({
content: () => componentRef.current,
pageStyle: `padding:24px`,
});

const title = poem.title;
const author = `${poem.author.dynasty}·${poem.author.name}`;

const content = poem.content
.replaceAll("\n", "")
.match(/[^。|!|?|,|;]+[。|!|?|,|;]+/g);

if (!content) return null;

const translation = chunk(
poem.translation?.replaceAll("\n", "").split(""),
12,
);

const arr = [title, author, ...content];
const py = [
poem.titlePinYin,
poem.author.namePinYin,
...(poem.contentPinYin ?? "").split("."),
];

return (
<>
<Button
className="fixed bottom-8 left-4 w-64"
onClick={() => handlePrint()}
>
打印
</Button>
<div className="relative m-auto min-h-[1754px] w-[938px]">
<div
className="font-cursive w-[938px] space-y-4 text-5xl"
ref={componentRef}
>
<div
className={cn(
opts.py ? "min-h-[1334px] space-y-8" : "h-auto space-y-4",
)}
>
{arr.map((item, index) => (
<div key={index}>
<PyRow
py={py[index] ?? ""}
align={index === 1 ? "right" : "center"}
border={opts.border}
className={cn(!opts.py && "hidden")}
/>
<Row
border={opts.border}
className="h-20 w-20"
text={item}
align={index === 1 ? "right" : "center"}
/>
</div>
))}
</div>

{opts.translation && (
<p className="flex h-20 items-center justify-between text-2xl text-neutral-400">
<span className="text-5xl text-black">译文</span>
aspoem.com | 现代化中国诗词学习网站
</p>
)}

{opts.translation &&
translation.map((item) =>
Row({
text: item.join(""),
border: opts.border,
align: "left",
className: "h-20 w-20",
}),
)}
</div>
</div>
</>
);
}
115 changes: 115 additions & 0 deletions src/app/tools/print/components/select-poem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use client";

import { Button } from "~/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "~/components/ui/command";
import { useDebounce } from "@react-hook/debounce";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "~/components/ui/popover";
import { api } from "~/trpc/react";
import { useEffect, useState } from "react";
import { X } from "lucide-react";
import { useRouter } from "next/navigation";

export function SelectPoem(props: {
selected?: {
author: {
name: string;
id: number;
};
id: number;
title: string;
content: string;
};
}) {
const [value, setValue] = useState("");
const [query, setQuery] = useDebounce(value, 500);
const router = useRouter();

const { data = [] } = api.poem.search.useQuery(query, {
refetchOnWindowFocus: false,
});

useEffect(() => {
setQuery(value);
}, [setQuery, value]);

const [open, setOpen] = useState(false);
const [selected, setSelected] = useState<(typeof data)[number] | undefined>(
props.selected || undefined,
);

return (
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
className="w-full justify-between overflow-hidden"
>
{selected ? (
<>
<span className="inline-block flex-1 truncate text-left">
{selected.title}
</span>
<div
className="flex h-8 w-8 shrink-0 items-center justify-end text-muted-foreground"
onClick={(e) => {
e.stopPropagation();
setSelected(undefined);
}}
>
<X className="h-4 w-4" />
</div>
</>
) : (
<>+ 选择诗词</>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-64 p-0" align="start">
<Command className="w-full">
<CommandInput
placeholder="搜索标题、作者、内容"
value={value}
onValueChange={setValue}
/>
<CommandList>
<CommandEmpty>没有找到诗词。</CommandEmpty>
<CommandGroup>
{data.map((item) => {
const value = `${item.title} | ${item.author.name}`;

return (
<CommandItem
key={item.id}
value={value}
onSelect={(e) => {
setSelected(
data.find(
(item) => `${item.title} | ${item.author.name}` === e,
) || undefined,
);
setOpen(false);
router.push("?id=" + item.id);
}}
>
{item.title} | {item.author.name}
</CommandItem>
);
})}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
Loading

0 comments on commit e60cd0b

Please sign in to comment.