feat(chapter3): replicate styled Next.js app setup

Add the App Router page group, nested layouts, Tailwind and shadcn styling, shared header components, local fonts and backgrounds, Ant Design demo support, and compatible lint scripts and dependencies.
This commit is contained in:
jiawei
2026-08-30 16:29:58 +08:00
parent 65805be81d
commit f6d90efb0a
40 changed files with 16674 additions and 1879 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
"css": "src/app/styles/tailwind.css", "css": "src/app/styles/tailwind.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "tw" "prefix": ""
}, },
"iconLibrary": "lucide", "iconLibrary": "lucide",
"rtl": false, "rtl": false,
+22 -7
View File
@@ -3,20 +3,32 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "rimraf .next && next dev --turbopack",
"build": "next build", "build": "rimraf .next && next build",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "pnpm lint:es && pnpm lint:style",
"lint:es": "eslint . --fix",
"lint:style": "stylelint \"**/*.css\" --fix --cache --cache-location node_modules/.cache/stylelint"
}, },
"dependencies": { "dependencies": {
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@ant-design/cssinjs": "^2.1.2",
"@ant-design/nextjs-registry": "^1.3.0",
"antd": "^6.6.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"immer": "^11.1.18",
"lucide-react": "^1.37.0",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"next": "16.3.3", "next": "16.3.3",
"radix-ui": "^1.6.7",
"react": "19.2.8", "react": "19.2.8",
"react-dom": "19.2.8" "react-dom": "19.2.8",
"shadcn": "^4.19.0",
"tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0"
}, },
"devDependencies": { "devDependencies": {
"@antfu/eslint-config": "^9.3.0",
"@eslint-react/eslint-plugin": "^5.18.6", "@eslint-react/eslint-plugin": "^5.18.6",
"@eslint/eslintrc": "^3.3.6", "@eslint/eslintrc": "^3.3.6",
"@next/eslint-plugin-next": "^16.3.3", "@next/eslint-plugin-next": "^16.3.3",
@@ -26,7 +38,7 @@
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"babel-plugin-react-compiler": "1.0.0", "babel-plugin-react-compiler": "1.0.0",
"eslint": "^9", "eslint": "^9.39.5",
"eslint-config-next": "16.3.3", "eslint-config-next": "16.3.3",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-jsx-a11y": "^6.10.2",
@@ -46,5 +58,8 @@
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
}, },
"packageManager": "pnpm@10.34.5" "packageManager": "pnpm@10.34.5",
"imports": {
"#tailwind.css": "./src/app/styles/tailwind.css"
}
} }
+4011 -1732
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
/* src/app/(pages)/layout.module.css */
@reference "#tailwind.css";
.layout {
@apply min-h-full w-full flex flex-col bg-background bg-cover bg-fixed bg-center bg-no-repeat bg-[url('../styles/images/bg-light.png')] p-0 m-0;
html.dark &,
html[data-theme='dark'] & {
background-image: url('../styles/images/bg-dark.png');
}
}
+19
View File
@@ -0,0 +1,19 @@
// src/app/(pages)/layout.tsx
import type { Metadata } from 'next';
import type { FC, PropsWithChildren } from 'react';
import { Header } from '../_components/header';
import $styles from './layout.module.css';
export const metadata: Metadata = {
title: 'pincman的博客',
description:
'pincman的个人博客,提供一些ts、react、node.js、php、golang相关的技术文档以及分享一些生活琐事',
};
const AppLayout: FC<PropsWithChildren> = ({ children }) => (
<div className={$styles.layout}>
<Header />
{children}
</div>
);
export default AppLayout;
+9
View File
@@ -0,0 +1,9 @@
/* src/app/(pages)/page.module.css */
@reference "#tailwind.css";
.block {
@apply page-container flex flex-col flex-none! w-full space-y-4 p-5 items-center justify-center
rounded-sm backdrop-blur-sm shadow-nysm shadow-neutral-800 duration-300
text-lg text-center text-white bg-black/30
hover:shadow-nylg font-lxgw;
}
+22
View File
@@ -0,0 +1,22 @@
// src/app/(pages)/page.tsx
import { FC } from 'react';
import { ArrowBigRight } from 'lucide-react';
import Link from 'next/link';
import $styles from './page.module.css';
import { Button } from '../_components/shadcn/ui/button';
const App: FC = () => (
<main className={$styles.container}>
<div className={$styles.block}>
3R教室Nextjs课程的开始
<Button asChild>
<Link href="https://3rcd.com" target="_blank">
<ArrowBigRight />
</Link>
</Button>
</div>
</main>
);
export default App;
@@ -0,0 +1 @@
<svg id="图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 801 802"><defs><style>.cls-1{fill:#fff;}.cls-1,.cls-2{stroke:#000;stroke-miterlimit:10;}.cls-2{fill:#224740;}</style></defs><rect class="cls-1" x="0.5" y="0.5" width="800" height="801"/><path class="cls-2" d="M255.61,517.24,182.5,569.53c-16.57,13.4-68.15,8.09-74.37-.12-8.7-11.49-23.82-26.39-14-35.29s37.18-8.36,37.18-8.36l30.06-.58s19.51-4,27.92-4.89c11.23-1.23,13-3.43,10.56-5.93-.67.24-.48-.48-.48-.48-5.6-4.86-25.35-10.58-22.23-12.31l62-52.44c-1.87,1.12-163.29,65.48-163.29,65.48L62.67,526.27s-2.17-1.48-20.75,27.34c-2.5,3.88,37.87,28.51,37.87,28.51C209.06,671.81,328.12,475,353.3,420.12l-.24-2.29,5.15-5.58c18.53,15.86,32.57,37.32,45.87,45.14,20.4,12-.15-8.39,38.94,24.3s39.47,148.93,36.6,175.53-29.74,29.64-29.74,29.64,9.93-28.87,7.36-78.72c-2.57-49.16-81.43-51.44-83.41-51.51C471,602.21,394,738.52,394,738.52s35,4.34,93.39-56.92-25.93-226.14-20.1-253.09c2.69-12.37,95.24-19.54,95.24-19.54s117.86,1.61,120.76,5.78c5.4,7.7,9.11,22.5,4.08,46.84-5,23.83-78.09,90.15-81.16,93,2.29-.59,39.63-11.61,96.61-69.39,58.81-59.69-1.66-127.51-58.34-103.49S528,381.27,528,381.27c-7.7-8.51-11.89-18.92-13.46-30.47h0l-27.48-5.14,27.46-18.19h0c7.21-63.27,66.28-143.75,74.05-152.62,10.38-11.8,15.63-11.29,7.31-23-6.65-9.35-3-12.33-.71-13.37l.64-.2c-9.26,2.67-52.44,13.88-116.82,11.24-72-2.92-111-84.78-112-87.12l-2.21-2.86s-38.57,66-12.6,108.84c20.18,33.3,153.43,44.76,153.43,44.76s.82,14.78-9.7,21.19-32.69-2.77-72.55-15.43l-8.88-1.7c-11.23-1.51-36.36-13.58-64-40.62l-26.19,218,5.65,18-67.34,97.66c-2.6,2.47-2.6,2.47-6.91,7m68.6-122.67M407,285.14s13.38-5.44,25.85.58,19.25,21.2,19.25,21.2-13.37,5.47-25.84-.55S407,285.14,407,285.14Z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,11 @@
// src/app/_components/header/index.tsx
import type { FC } from 'react';
import { HeaderLogo } from './logo';
import $styles from './styles.module.css';
export const Header: FC = () => (
<header className={$styles.header}>
<HeaderLogo />
</header>
);
@@ -0,0 +1,17 @@
/* src/app/_components/header/logo.module.css */
@reference "#tailwind.css";
.link {
@apply w-20 h-20 block rounded-full shadow-nymd p-1 bg-white;
transition-duration: 300ms;
animation: breathe-light 4s ease-in-out infinite;
&:hover {
@apply shadow-nylg shadow-amber-400;
transform: scale(1.2) rotate(360deg);
}
& > img {
@apply rounded-full;
}
}
@@ -0,0 +1,20 @@
// src/app/_components/header/logo.tsx
import Image from 'next/image';
import Link from 'next/link';
import Avatar from './avatar.svg';
import $styles from './logo.module.css';
export const HeaderLogo = () => (
<Link href="/" className={$styles.link}>
<Image
src={Avatar}
alt="avatar logo"
sizes="100vw"
style={{
width: '100%',
height: 'auto',
}}
/>
</Link>
);
@@ -0,0 +1,6 @@
/* src/app/_components/header/styles.module.css */
@reference "#tailwind.css";
.header {
@apply flex justify-center items-center pt-6 max-h-24 flex-auto;
}
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
@@ -5,33 +5,33 @@ import { Slot } from "radix-ui"
import { cn } from "@/app/_components/shadcn/libs/utils" import { cn } from "@/app/_components/shadcn/libs/utils"
const buttonVariants = cva( const buttonVariants = cva(
"tw:group/button tw:inline-flex tw:shrink-0 tw:items-center tw:justify-center tw:rounded-lg tw:border tw:border-transparent tw:bg-clip-padding tw:text-sm tw:font-medium tw:whitespace-nowrap tw:transition-all tw:outline-none tw:select-none tw:focus-visible:border-ring tw:focus-visible:ring-3 tw:focus-visible:ring-ring/50 tw:active:not-aria-[haspopup]:translate-y-px tw:disabled:pointer-events-none tw:disabled:opacity-50 tw:aria-invalid:border-destructive tw:aria-invalid:ring-3 tw:aria-invalid:ring-destructive/20 tw:dark:aria-invalid:border-destructive/50 tw:dark:aria-invalid:ring-destructive/40 tw:[&_svg]:pointer-events-none tw:[&_svg]:shrink-0 tw:[&_svg:not([class*=size-])]:size-4", "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
variant: { variant: {
default: "tw:bg-primary tw:text-primary-foreground tw:hover:bg-primary/80", default: "bg-primary text-primary-foreground hover:bg-primary/80",
outline: outline:
"tw:border-border tw:bg-background tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:border-input tw:dark:bg-input/30 tw:dark:hover:bg-input/50", "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary: secondary:
"tw:bg-secondary tw:text-secondary-foreground tw:hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] tw:aria-expanded:bg-secondary tw:aria-expanded:text-secondary-foreground", "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost: ghost:
"tw:hover:bg-muted tw:hover:text-foreground tw:aria-expanded:bg-muted tw:aria-expanded:text-foreground tw:dark:hover:bg-muted/50", "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive: destructive:
"tw:bg-destructive/10 tw:text-destructive tw:hover:bg-destructive/20 tw:focus-visible:border-destructive/40 tw:focus-visible:ring-destructive/20 tw:dark:bg-destructive/20 tw:dark:hover:bg-destructive/30 tw:dark:focus-visible:ring-destructive/40", "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "tw:text-primary tw:underline-offset-4 tw:hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: default:
"tw:h-8 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pr-2 tw:has-data-[icon=inline-start]:pl-2", "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
xs: "tw:h-6 tw:gap-1 tw:rounded-[min(var(--radius-md),10px)] tw:px-2 tw:text-xs tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pr-1.5 tw:has-data-[icon=inline-start]:pl-1.5 tw:[&_svg:not([class*=size-])]:size-3", xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "tw:h-7 tw:gap-1 tw:rounded-[min(var(--radius-md),12px)] tw:px-2.5 tw:text-[0.8rem] tw:in-data-[slot=button-group]:rounded-lg tw:has-data-[icon=inline-end]:pr-1.5 tw:has-data-[icon=inline-start]:pl-1.5 tw:[&_svg:not([class*=size-])]:size-3.5", sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: "tw:h-9 tw:gap-1.5 tw:px-2.5 tw:has-data-[icon=inline-end]:pr-2 tw:has-data-[icon=inline-start]:pl-2", lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
icon: "tw:size-8", icon: "size-8",
"icon-xs": "icon-xs":
"tw:size-6 tw:rounded-[min(var(--radius-md),10px)] tw:in-data-[slot=button-group]:rounded-lg tw:[&_svg:not([class*=size-])]:size-3", "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "icon-sm":
"tw:size-7 tw:rounded-[min(var(--radius-md),12px)] tw:in-data-[slot=button-group]:rounded-lg", "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
"icon-lg": "tw:size-9", "icon-lg": "size-9",
}, },
}, },
defaultVariants: { defaultVariants: {
@@ -12,7 +12,7 @@ function Card({
data-slot="card" data-slot="card"
data-size={size} data-size={size}
className={cn( className={cn(
"tw:group/card tw:flex tw:flex-col tw:gap-(--card-spacing) tw:overflow-hidden tw:rounded-xl tw:bg-card tw:py-(--card-spacing) tw:text-sm tw:text-card-foreground tw:ring-1 tw:ring-foreground/10 tw:[--card-spacing:--spacing(4)] tw:has-data-[slot=card-footer]:pb-0 tw:has-[>img:first-child]:pt-0 tw:data-[size=sm]:[--card-spacing:--spacing(3)] tw:data-[size=sm]:has-data-[slot=card-footer]:pb-0 tw:*:[img:first-child]:rounded-t-xl tw:*:[img:last-child]:rounded-b-xl", "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
className className
)} )}
{...props} {...props}
@@ -25,7 +25,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
"tw:group/card-header tw:@container/card-header tw:grid tw:auto-rows-min tw:items-start tw:gap-1 tw:rounded-t-xl tw:px-(--card-spacing) tw:has-data-[slot=card-action]:grid-cols-[1fr_auto] tw:has-data-[slot=card-description]:grid-rows-[auto_auto] tw:[.border-b]:pb-(--card-spacing)", "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
className className
)} )}
{...props} {...props}
@@ -38,7 +38,7 @@ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-title" data-slot="card-title"
className={cn( className={cn(
"tw: tw:text-base tw:leading-snug tw:font-medium tw:group-data-[size=sm]/card:text-sm", "font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
className className
)} )}
{...props} {...props}
@@ -50,7 +50,7 @@ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-description" data-slot="card-description"
className={cn("tw:text-sm tw:text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
) )
@@ -61,7 +61,7 @@ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-action" data-slot="card-action"
className={cn( className={cn(
"tw:col-start-2 tw:row-span-2 tw:row-start-1 tw:self-start tw:justify-self-end", "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className className
)} )}
{...props} {...props}
@@ -73,7 +73,7 @@ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-content" data-slot="card-content"
className={cn("tw:px-(--card-spacing)", className)} className={cn("px-(--card-spacing)", className)}
{...props} {...props}
/> />
) )
@@ -84,7 +84,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card-footer" data-slot="card-footer"
className={cn( className={cn(
"tw:flex tw:items-center tw:rounded-b-xl tw:border-t tw:bg-muted/50 tw:p-(--card-spacing)", "flex items-center rounded-b-xl border-t bg-muted/50 p-(--card-spacing)",
className className
)} )}
{...props} {...props}
@@ -0,0 +1,11 @@
@reference "#tailwind.css";
.container {
@apply bg-neutral-100/40 shadow-black/20 backdrop-blur-sm shadow-md rounded-md p-5 m-5 min-w-[20rem];
}
html.dark {
.container {
@apply bg-neutral-800/40 shadow-slate-100/30;
}
}
+11
View File
@@ -0,0 +1,11 @@
/* src/app/(pages)/layout.module.css */
@reference "#tailwind.css";
.layout {
@apply min-h-full w-full flex flex-col bg-background bg-cover bg-fixed bg-center bg-no-repeat bg-[url('../styles/images/bg-light.png')] p-0 m-0;
html.dark &,
html[data-theme='dark'] & {
background-image: url('../styles/images/bg-dark.png');
}
}
+34
View File
@@ -0,0 +1,34 @@
// src/app/demo/layout.tsx
'use client';
import { FC, PropsWithChildren } from 'react';
// src/app/demo/layout.tsx
import { px2remTransformer, StyleProvider } from '@ant-design/cssinjs';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import { App as AntdApp, ConfigProvider, theme } from 'antd';
import zhCN from 'antd/locale/zh_CN';
import $styles from './layout.module.css';
const px2rem = px2remTransformer();
const DemoLayout: FC<PropsWithChildren> = ({ children }) => (
<AntdRegistry>
<ConfigProvider
locale={zhCN}
theme={{
algorithm: theme.defaultAlgorithm,
// 启用css变量
cssVar: {},
hashed: false,
token: {},
}}
>
<AntdApp>
<StyleProvider transformers={[px2rem]}>
<div className={$styles.layout}>{children}</div>
</StyleProvider>
</AntdApp>
</ConfigProvider>
</AntdRegistry>
);
export default DemoLayout;
+9
View File
@@ -0,0 +1,9 @@
@reference "#tailwind.css";
.demo {
@apply flex flex-auto flex-wrap items-center justify-center;
& > .container {
@apply bg-neutral-100/40 shadow-black/20 backdrop-blur-sm shadow-md rounded-md p-5 m-5 min-w-[20rem];
}
}
+23
View File
@@ -0,0 +1,23 @@
// src/app/demo/page.tsx
// ...
import { FC } from 'react';
import { Button } from 'antd';
import $styles from './page.module.css';
const DemoPage: FC = () => (
<div className={$styles.demo}>
<div className={$styles.container}>
<h2 className="text-center">First React App</h2>
<div className="flex flex-col items-center">
<div className="my-5 flex-auto">
<Button variant="solid" href="https://3rcd.com" target="_blank">
3R教室
</Button>
</div>
</div>
</div>
</div>
);
export default DemoPage;
-26
View File
@@ -1,26 +0,0 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}
+12 -22
View File
@@ -1,29 +1,19 @@
import type { Metadata } from "next"; // src/app/layout.tsx
import { Geist, Geist_Mono } from "next/font/google"; import type { Metadata } from 'next';
import "./globals.css";
const geistSans = Geist({ import './styles/index.css';
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({ import type { FC, PropsWithChildren } from 'react';
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: 'nextapp',
description: "Generated by create next app", description: '3r教室Next.js全栈开发课程',
}; };
export default function RootLayout({ children }: LayoutProps<"/">) { const RootLayout: FC<PropsWithChildren> = ({ children }) => (
return ( <html lang="en">
<html <body>{children}</body>
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
</html> </html>
); );
}
export default RootLayout;
-69
View File
@@ -1,69 +0,0 @@
import Image from "next/image";
export default function Home() {
return (
<div className="flex flex-col flex-1 items-center justify-center bg-zinc-50 font-sans dark:bg-black">
<main className="flex flex-1 w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
<Image
className="dark:invert h-5 w-[100px]"
src="/next.svg"
alt="Next.js logo"
width={100}
height={20}
priority
/>
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
To get started, edit the{" "}
<code className="rounded bg-black/[.06] px-1.5 py-0.5 font-mono text-[0.9em] dark:bg-white/[.08]">
page.tsx
</code>{" "}
file.
</h1>
<p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
Looking for a starting point or more instructions? Head over to{" "}
<a
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Templates
</a>{" "}
or the{" "}
<a
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
className="font-medium text-zinc-950 dark:text-zinc-50"
>
Learning
</a>{" "}
center.
</p>
</div>
<div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
<a
className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert h-[14px] w-4"
src="/vercel.svg"
alt="Vercel logomark"
width={16}
height={14}
/>
Deploy Now
</a>
<a
className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Documentation
</a>
</div>
</main>
</div>
);
}
+8
View File
@@ -0,0 +1,8 @@
/* src/app/styles/app.css */
.lucide {
@apply w-4 h-4 stroke-[1px];
}
.ant-app {
@apply h-[100vh] w-full p-0 m-0;
}
@@ -0,0 +1,55 @@
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 300;
src:
url('/fonts/firacode/woff2/FiraCode-Light.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Light.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 400;
src:
url('/fonts/firacode/woff2/FiraCode-Regular.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Regular.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 500;
src:
url('/fonts/firacode/woff2/FiraCode-Medium.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Medium.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 600;
src:
url('/fonts/firacode/woff2/FiraCode-SemiBold.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-SemiBold.woff') format('woff');
}
@font-face {
font-family: 'Fira Code';
font-style: normal;
font-weight: 700;
src:
url('/fonts/firacode/woff2/FiraCode-Bold.woff2') format('woff2'),
url('/fonts/firacode/woff/FiraCode-Bold.woff') format('woff');
}
@font-face {
font-family: 'Fira Code VF';
font-style: normal;
/* font-weight requires a range: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide#Using_a_variable_font_font-face_changes */
font-weight: 300 700;
src:
url('/fonts/firacode/woff2/FiraCode-VF.woff2') format('woff2-variations'),
url('/fonts/firacode/woff/FiraCode-VF.woff') format('woff-variations');
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

+5
View File
@@ -0,0 +1,5 @@
@import './fonts/fira-code.css';
@import './fonts/lxgwwenkai-regular.css';
@import './fonts/lxgwwenkaimono-regular.css';
@import './tailwind.css';
@import './app.css';
+118
View File
@@ -0,0 +1,118 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(100% 0 0deg);
--foreground: oklch(14.1% 0.005 285.823deg);
--card: oklch(100% 0 0deg);
--card-foreground: oklch(14.1% 0.005 285.823deg);
--popover: oklch(100% 0 0deg);
--popover-foreground: oklch(14.1% 0.005 285.823deg);
--primary: oklch(21% 0.006 285.885deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(96.7% 0.001 286.375deg);
--secondary-foreground: oklch(21% 0.006 285.885deg);
--muted: oklch(96.7% 0.001 286.375deg);
--muted-foreground: oklch(55.2% 0.016 285.938deg);
--accent: oklch(96.7% 0.001 286.375deg);
--accent-foreground: oklch(21% 0.006 285.885deg);
--destructive: oklch(57.7% 0.245 27.325deg);
--border: oklch(92% 0.004 286.32deg);
--input: oklch(92% 0.004 286.32deg);
--ring: oklch(70.5% 0.015 286.067deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
--sidebar: oklch(98.5% 0 0deg);
--sidebar-foreground: oklch(14.1% 0.005 285.823deg);
--sidebar-primary: oklch(21% 0.006 285.885deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(96.7% 0.001 286.375deg);
--sidebar-accent-foreground: oklch(21% 0.006 285.885deg);
--sidebar-border: oklch(92% 0.004 286.32deg);
--sidebar-ring: oklch(70.5% 0.015 286.067deg);
}
.dark {
--background: oklch(14.1% 0.005 285.823deg);
--foreground: oklch(98.5% 0 0deg);
--card: oklch(21% 0.006 285.885deg);
--card-foreground: oklch(98.5% 0 0deg);
--popover: oklch(21% 0.006 285.885deg);
--popover-foreground: oklch(98.5% 0 0deg);
--primary: oklch(92% 0.004 286.32deg);
--primary-foreground: oklch(21% 0.006 285.885deg);
--secondary: oklch(27.4% 0.006 286.033deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--muted: oklch(27.4% 0.006 286.033deg);
--muted-foreground: oklch(70.5% 0.015 286.067deg);
--accent: oklch(27.4% 0.006 286.033deg);
--accent-foreground: oklch(98.5% 0 0deg);
--destructive: oklch(70.4% 0.191 22.216deg);
--border: oklch(100% 0 0deg / 10%);
--input: oklch(100% 0 0deg / 15%);
--ring: oklch(55.2% 0.016 285.938deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
--sidebar: oklch(21% 0.006 285.885deg);
--sidebar-foreground: oklch(98.5% 0 0deg);
--sidebar-primary: oklch(48.8% 0.243 264.376deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(27.4% 0.006 286.033deg);
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
--sidebar-border: oklch(100% 0 0deg / 10%);
--sidebar-ring: oklch(55.2% 0.016 285.938deg);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
+136
View File
@@ -0,0 +1,136 @@
/* src/app/styles/tailwind.css */
@layer theme, base, antd, components, utilities;
@import 'tailwindcss';
@import 'tw-animate-css';
@import './tailwind/base.css';
@import './tailwind/utilities.css';
@import './tailwind/theme.css';
@import './tailwind/shadcn.css';
@custom-variant dark (&:is(.dark, .dark *, [data-theme='dark'], [data-theme='dark'] *));
@theme inline {
--font-heading: var(--font-sans);
--font-sans: var(--font-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
}
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
}
}
+50
View File
@@ -0,0 +1,50 @@
/********************************************************* 自定义tailwind基本样式 *********************************************************/
@layer base {
html {
font-size: 100%;
}
/* 以下为自定义样式 */
html,
body {
@apply font-base h-[100vh] w-full p-0 m-0;
}
body {
@apply text-sm;
}
h1 {
@apply text-3xl;
}
h2 {
@apply text-2xl;
}
h3 {
@apply text-xl;
}
h4 {
@apply text-lg;
}
h5 {
@apply text-sm;
}
h6 {
@apply text-xs;
}
button,
[role='button'] {
cursor: pointer;
}
:disabled {
cursor: default;
}
}
+118
View File
@@ -0,0 +1,118 @@
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
:root {
--radius: 0.625rem;
--background: oklch(100% 0 0deg);
--foreground: oklch(14.1% 0.005 285.823deg);
--card: oklch(100% 0 0deg);
--card-foreground: oklch(14.1% 0.005 285.823deg);
--popover: oklch(100% 0 0deg);
--popover-foreground: oklch(14.1% 0.005 285.823deg);
--primary: oklch(21% 0.006 285.885deg);
--primary-foreground: oklch(98.5% 0 0deg);
--secondary: oklch(96.7% 0.001 286.375deg);
--secondary-foreground: oklch(21% 0.006 285.885deg);
--muted: oklch(96.7% 0.001 286.375deg);
--muted-foreground: oklch(55.2% 0.016 285.938deg);
--accent: oklch(96.7% 0.001 286.375deg);
--accent-foreground: oklch(21% 0.006 285.885deg);
--destructive: oklch(57.7% 0.245 27.325deg);
--border: oklch(92% 0.004 286.32deg);
--input: oklch(92% 0.004 286.32deg);
--ring: oklch(70.5% 0.015 286.067deg);
--chart-1: oklch(64.6% 0.222 41.116deg);
--chart-2: oklch(60% 0.118 184.704deg);
--chart-3: oklch(39.8% 0.07 227.392deg);
--chart-4: oklch(82.8% 0.189 84.429deg);
--chart-5: oklch(76.9% 0.188 70.08deg);
--sidebar: oklch(98.5% 0 0deg);
--sidebar-foreground: oklch(14.1% 0.005 285.823deg);
--sidebar-primary: oklch(21% 0.006 285.885deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(96.7% 0.001 286.375deg);
--sidebar-accent-foreground: oklch(21% 0.006 285.885deg);
--sidebar-border: oklch(92% 0.004 286.32deg);
--sidebar-ring: oklch(70.5% 0.015 286.067deg);
}
.dark {
--background: oklch(14.1% 0.005 285.823deg);
--foreground: oklch(98.5% 0 0deg);
--card: oklch(21% 0.006 285.885deg);
--card-foreground: oklch(98.5% 0 0deg);
--popover: oklch(21% 0.006 285.885deg);
--popover-foreground: oklch(98.5% 0 0deg);
--primary: oklch(92% 0.004 286.32deg);
--primary-foreground: oklch(21% 0.006 285.885deg);
--secondary: oklch(27.4% 0.006 286.033deg);
--secondary-foreground: oklch(98.5% 0 0deg);
--muted: oklch(27.4% 0.006 286.033deg);
--muted-foreground: oklch(70.5% 0.015 286.067deg);
--accent: oklch(27.4% 0.006 286.033deg);
--accent-foreground: oklch(98.5% 0 0deg);
--destructive: oklch(70.4% 0.191 22.216deg);
--border: oklch(100% 0 0deg / 10%);
--input: oklch(100% 0 0deg / 15%);
--ring: oklch(55.2% 0.016 285.938deg);
--chart-1: oklch(48.8% 0.243 264.376deg);
--chart-2: oklch(69.6% 0.17 162.48deg);
--chart-3: oklch(76.9% 0.188 70.08deg);
--chart-4: oklch(62.7% 0.265 303.9deg);
--chart-5: oklch(64.5% 0.246 16.439deg);
--sidebar: oklch(21% 0.006 285.885deg);
--sidebar-foreground: oklch(98.5% 0 0deg);
--sidebar-primary: oklch(48.8% 0.243 264.376deg);
--sidebar-primary-foreground: oklch(98.5% 0 0deg);
--sidebar-accent: oklch(27.4% 0.006 286.033deg);
--sidebar-accent-foreground: oklch(98.5% 0 0deg);
--sidebar-border: oklch(100% 0 0deg / 10%);
--sidebar-ring: oklch(55.2% 0.016 285.938deg);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@@ -0,0 +1,33 @@
/********************************************************* 自定义tailwind主题扩展 *********************************************************/
@theme {
--breakpoint-xs: 30rem;
--breakpoint-sm: 36rem;
--breakpoint-md: 48rem;
--breakpoint-lg: 62rem;
--breakpoint-xl: 75rem;
--breakpoint-2xl: 87.5rem;
--shadow-nysm: 0 0 2px 0 rgb(0 0 0 / 5%);
--shadow-ny: 0 0 3px 0 rgb(0 0 0 / 10%), 0 0 2px - 1px rgb(0 0 0 / 10%);
--shadow-nymd: 0 0 6px -1px rgb(0 0 0 / 10%), 0 0 4px -2px rgb(0 0 0 / 10%);
--shadow-nylg: 0 0 15px -3px rgb(0 0 0 / 10%), 0 0 6px -4px rgb(0 0 0 / 10%);
--shadow-spread: 0 5px 40px rgb(0 0 0 / 10%);
/* 默认字体 */
--font-base:
-apple-system, blinkmacsystemfont, tahoma, arial, 'Hiragino Sans GB', 'Microsoft YaHei',
'WenQuanYi Micro Hei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
/* 霞鹜文楷字体 */
--font-lxgw:
'LXGW WenKai', '楷体', 'STKaiti', -apple-system, blinkmacsystemfont, tahoma, arial,
'Hiragino Sans GB', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* 默认代码字体 */
--font-code:
'Fira Code', -apple-system, blinkmacsystemfont, tahoma, arial, 'Hiragino Sans GB',
'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
'Segoe UI Symbol', 'Noto Color Emoji';
}
@@ -0,0 +1,107 @@
/********************************************************* 自定义tailwind工具类 *********************************************************/
@utility page-item {
@apply flex flex-auto flex-col mt-20 items-center;
}
/* 页面包装容器 */
@utility page-container {
@apply max-w-[90%] md:max-w-[80%] lg:max-w-[70%] xl:max-w-[60%] flex-auto mb-5;
}
/* 页面包装容器 */
/* 空白页样式,一般用于404和error页面 */
@utility page-blank {
@apply bg-white/80 backdrop-blur-sm h-full w-full drop-shadow-lg rounded-md flex justify-center items-center;
html.dark & {
@apply bg-black/50;
}
}
/* 全透明图标按钮 */
@utility btn-icon-transparent {
@apply block w-auto! border-none! bg-transparent! shadow-none! ring-0! hover:bg-transparent! focus-visible:ring-0!;
}
@utility page-block {
@apply bg-zinc-100/80;
html.dark & {
@apply bg-zinc-800/50;
}
}
@utility page-block-hover {
@apply hover:bg-white/90;
html.dark & {
@apply hover:bg-black/80;
}
}
/* 只显示一行文字, 溢出部分以省略号代替 */
@utility ellips {
@apply inline-block overflow-hidden max-w-full whitespace-nowrap text-ellipsis break-all;
}
/* 鼠标移动到文字或链接出现动画下划线 */
@utility animate-decoration {
padding-bottom: 2px;
background: linear-gradient(hsl(var(--foreground)), hsl(var(--foreground))) 0% 100% / 0% 1px
no-repeat;
transition: background-size ease-out 200ms;
&:not(:focus):hover {
background-size: 100% 1px;
}
}
/* 粗下划线 */
@utility animate-decoration-lg {
&:not(:focus):hover {
background-size: 100% 2px;
}
}
/* 取消下划线 */
@utility none-animate-decoration {
background-size: 0 !important;
transition: none !important;
&:hover,
&:not(:focus):hover {
background-size: 100% 0 !important;
}
}
/* 透明滚动条 */
@utility transparent-scrollbar {
&::-webkit-scrollbar {
@apply w-1 h-1;
}
&::-webkit-scrollbar-track {
@apply bg-gray-100;
}
html.dark &::-webkit-scrollbar-track {
@apply bg-zinc-800;
}
&::-webkit-scrollbar-thumb {
@apply bg-gray-300 rounded;
}
html.dark &::-webkit-scrollbar-thumb {
@apply bg-zinc-600;
}
&::-webkit-scrollbar-corner {
@apply bg-gray-100;
}
html.dark &::-webkit-scrollbar-corner {
@apply bg-zinc-800;
}
}