+
仪表盘
+
当前时间(上海):{formatDateTime(new Date())}
+
金额示例(数据库存分,界面显示元):{formatCNYFromFen(sampleFen)}
+
+ 示例用户
+
+ {users.map((u) => (
+ -
+ {u.name || '—'}({u.email})· 创建于 {formatDateTime(u.createdAt)}
+
+ ))}
+
+ {users.length === 0 && (
+ 暂无用户。运行“yarn prisma:migrate && yarn seed”以创建示例数据。
+ )}
+
+
+ );
+}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..9db5fa1
--- /dev/null
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,5 @@
+import NextAuth from 'next-auth';
+import { authOptions } from '@/auth.config';
+
+const handler = NextAuth(authOptions);
+export { handler as GET, handler as POST };
diff --git a/src/app/api/hello/route.ts b/src/app/api/hello/route.ts
new file mode 100644
index 0000000..0874c39
--- /dev/null
+++ b/src/app/api/hello/route.ts
@@ -0,0 +1,5 @@
+import { NextResponse } from 'next/server';
+
+export async function GET() {
+ return NextResponse.json({ message: '你好,API 正常工作!' });
+}
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..5fae5fb
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,22 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --background: #0b0f19;
+ --foreground: #e5e7eb;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+body {
+ color: var(--foreground);
+ background: var(--background);
+}
+
+a {
+ color: #93c5fd;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..cf6f553
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,15 @@
+import './globals.css';
+import type { Metadata } from 'next';
+
+export const metadata: Metadata = {
+ title: '示例应用',
+ description: 'Next.js 14 中文(中国)模板,包含 Prisma + SQLite、next-intl、Tailwind 等配置'
+};
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/auth.config.ts b/src/auth.config.ts
new file mode 100644
index 0000000..b7ad775
--- /dev/null
+++ b/src/auth.config.ts
@@ -0,0 +1,31 @@
+import type { NextAuthOptions } from 'next-auth';
+import Credentials from 'next-auth/providers/credentials';
+import EmailProvider from 'next-auth/providers/email';
+
+export const authOptions: NextAuthOptions = {
+ providers: [
+ Credentials({
+ name: 'Credentials',
+ credentials: {
+ email: { label: 'Email', type: 'text' },
+ password: { label: 'Password', type: 'password' }
+ },
+ async authorize(credentials) {
+ if (!credentials?.email) return null;
+ // Placeholder auth: accept any non-empty email and password
+ return { id: '1', email: credentials.email, name: '测试用户' } as any;
+ }
+ }),
+ ...(process.env.EMAIL_SERVER && process.env.EMAIL_FROM
+ ? [
+ EmailProvider({
+ server: process.env.EMAIL_SERVER,
+ from: process.env.EMAIL_FROM
+ })
+ ]
+ : [])
+ ],
+ session: {
+ strategy: 'jwt'
+ }
+};
diff --git a/src/lib/dayjs.ts b/src/lib/dayjs.ts
new file mode 100644
index 0000000..e821d4b
--- /dev/null
+++ b/src/lib/dayjs.ts
@@ -0,0 +1,7 @@
+import dayjs from 'dayjs';
+import 'dayjs/locale/zh-cn';
+
+// Use Chinese locale
+dayjs.locale('zh-cn');
+
+export default dayjs;
diff --git a/src/lib/format.ts b/src/lib/format.ts
new file mode 100644
index 0000000..f98d204
--- /dev/null
+++ b/src/lib/format.ts
@@ -0,0 +1,33 @@
+import Big from 'big.js';
+
+export const CNYFormatter = new Intl.NumberFormat('zh-CN', {
+ style: 'currency',
+ currency: 'CNY',
+ currencyDisplay: 'symbol',
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+});
+
+// Convert fen (integer, 1/100 yuan) to yuan string with currency
+export function formatCNYFromFen(fen: number | string | bigint): string {
+ const fenBig = new Big(fen as any);
+ const yuan = fenBig.div(100);
+ return CNYFormatter.format(Number(yuan.toString()));
+}
+
+export function fenToYuanNumber(fen: number | string | bigint): number {
+ const fenBig = new Big(fen as any);
+ return Number(fenBig.div(100).toString());
+}
+
+export function yuanToFenNumber(yuan: number | string): number {
+ const fen = new Big(yuan).times(100);
+ return Number(fen.round(0, 0 /* RoundDown */).toString());
+}
+
+export function formatNumberZH(n: number, fractionDigits = 2): string {
+ return new Intl.NumberFormat('zh-CN', {
+ minimumFractionDigits: fractionDigits,
+ maximumFractionDigits: fractionDigits
+ }).format(n);
+}
diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts
new file mode 100644
index 0000000..b8d2078
--- /dev/null
+++ b/src/lib/prisma.ts
@@ -0,0 +1,11 @@
+import { PrismaClient } from '@prisma/client';
+
+const globalForPrisma = global as unknown as { prisma: PrismaClient | undefined };
+
+export const prisma =
+ globalForPrisma.prisma ||
+ new PrismaClient({
+ log: ['error', 'warn']
+ });
+
+if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
diff --git a/src/lib/time.ts b/src/lib/time.ts
new file mode 100644
index 0000000..1492ac4
--- /dev/null
+++ b/src/lib/time.ts
@@ -0,0 +1,13 @@
+import dayjs from './dayjs';
+
+export function formatDateTime(date: Date | string | number, format = 'YYYY-MM-DD HH:mm') {
+ return dayjs(date).format(format);
+}
+
+export function formatTime(date: Date | string | number) {
+ return dayjs(date).format('HH:mm');
+}
+
+export function nowISO() {
+ return new Date().toISOString();
+}
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..bae1261
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,14 @@
+import createMiddleware from 'next-intl/middleware';
+
+export default createMiddleware({
+ locales: ['zh-CN'],
+ defaultLocale: 'zh-CN',
+ localePrefix: 'as-needed'
+});
+
+export const config = {
+ matcher: [
+ // Skip all internal paths (_next)
+ '/((?!_next|.*\\..*|api).*)'
+ ]
+};
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..f65c862
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,12 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ './src/pages/**/*.{js,ts,jsx,tsx,mdx}',
+ './src/components/**/*.{js,ts,jsx,tsx,mdx}',
+ './src/app/**/*.{js,ts,jsx,tsx,mdx}'
+ ],
+ theme: {
+ extend: {}
+ },
+ plugins: []
+};
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..ff364ca
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"]
+ },
+ "types": ["node"]
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}