@wzyjs/cli 0.3.32 → 0.3.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +350 -35
- package/package.json +6 -4
- package/template/web/base/Dockerfile +37 -0
- package/template/web/base/_env +2 -0
- package/template/web/base/eslint.config.js +94 -0
- package/template/web/base/next.config.js +29 -0
- package/template/web/base/package.json +53 -0
- package/template/web/base/postcss.config.js +5 -0
- package/template/web/base/prisma/models/demo/DemoItem.zmodel +8 -0
- package/template/web/base/prisma/schema.prisma +29 -0
- package/template/web/base/prisma/schema.zmodel +1 -0
- package/template/web/base/src/app/api/trpc/[trpc]/route.ts +24 -0
- package/template/web/base/src/app/auth/disabled/page.tsx +29 -0
- package/template/web/base/src/app/auth/error/page.tsx +47 -0
- package/template/web/base/src/app/auth/signin/page.tsx +53 -0
- package/template/web/base/src/app/auth/unauthorized/page.tsx +60 -0
- package/template/web/base/src/app/demo/page.tsx +59 -0
- package/template/web/base/src/app/layout.tsx +32 -0
- package/template/web/base/src/app/not-found.tsx +21 -0
- package/template/web/base/src/app/page.tsx +92 -0
- package/template/web/base/src/components/base/Layout/Header/context.tsx +56 -0
- package/template/web/base/src/components/base/Layout/Header/index.tsx +32 -0
- package/template/web/base/src/components/base/Layout/index.tsx +27 -0
- package/template/web/base/src/components/base/Providers/TRPCReactProvider.tsx +25 -0
- package/template/web/base/src/components/base/Providers/index.tsx +31 -0
- package/template/web/base/src/components/base/display/DisplayProvider.tsx +44 -0
- package/template/web/base/src/components/base/display/consts.ts +10 -0
- package/template/web/base/src/components/base/display/context.ts +6 -0
- package/template/web/base/src/components/base/display/index.ts +4 -0
- package/template/web/base/src/components/base/display/types.ts +12 -0
- package/template/web/base/src/components/base/display/useDisplay.ts +9 -0
- package/template/web/base/src/components/base/display/utils.ts +18 -0
- package/template/web/base/src/components/base/theme/ThemeProvider.tsx +83 -0
- package/template/web/base/src/components/base/theme/ThemeToggle.tsx +26 -0
- package/template/web/base/src/components/base/theme/consts.tsx +59 -0
- package/template/web/base/src/components/base/theme/context.ts +14 -0
- package/template/web/base/src/components/base/theme/useAppTheme.ts +15 -0
- package/template/web/base/src/components/base/theme/utils.ts +17 -0
- package/template/web/base/src/components/index.ts +1 -0
- package/template/web/base/src/consts/index.ts +6 -0
- package/template/web/base/src/enums/index.ts +1 -0
- package/template/web/base/src/env.js +44 -0
- package/template/web/base/src/hooks/index.ts +1 -0
- package/template/web/base/src/server/db/client.ts +19 -0
- package/template/web/base/src/server/routers/index.ts +3 -0
- package/template/web/base/src/server/trpc/context.ts +14 -0
- package/template/web/base/src/server/trpc/index.ts +1 -0
- package/template/web/base/src/server/trpc/init.ts +27 -0
- package/template/web/base/src/server/trpc/procedures.ts +5 -0
- package/template/web/base/src/server/trpc/router.ts +11 -0
- package/template/web/base/src/server/utils/index.ts +1 -0
- package/template/web/base/src/styles/globals.css +3 -0
- package/template/web/base/src/types/index.ts +1 -0
- package/template/web/base/src/utils/index.ts +4 -0
- package/template/web/base/src/utils/query-client/index.ts +31 -0
- package/template/web/base/src/utils/trpc/index.ts +23 -0
- package/template/web/base/src/utils/trpc/utils.ts +61 -0
- package/template/web/base/tailwind.config.cjs +7 -0
- package/template/web/base/tsconfig.json +46 -0
- package/template/web/google/_env +8 -0
- package/template/web/google/package.json +55 -0
- package/template/web/google/prisma/models/auth/Account.zmodel +26 -0
- package/template/web/google/prisma/models/auth/Session.zmodel +17 -0
- package/template/web/google/prisma/models/auth/User.zmodel +22 -0
- package/template/web/google/prisma/schema.zmodel +2 -0
- package/template/web/google/src/app/api/auth/[...nextauth]/route.ts +7 -0
- package/template/web/google/src/app/auth/error/page.tsx +56 -0
- package/template/web/google/src/app/auth/signin/page.tsx +83 -0
- package/template/web/google/src/app/layout.tsx +35 -0
- package/template/web/google/src/components/base/AuthGuard/Loading.tsx +19 -0
- package/template/web/google/src/components/base/AuthGuard/index.tsx +45 -0
- package/template/web/google/src/components/base/Layout/Header/UserAuthStatus.tsx +93 -0
- package/template/web/google/src/components/base/Layout/Header/index.tsx +34 -0
- package/template/web/google/src/components/base/Providers/index.tsx +34 -0
- package/template/web/google/src/env.js +52 -0
- package/template/web/google/src/server/auth/next-auth/adapter.ts +77 -0
- package/template/web/google/src/server/auth/next-auth/options.ts +53 -0
- package/template/web/google/src/server/trpc/context.ts +21 -0
- package/template/web/google/src/server/trpc/procedures.ts +16 -0
- package/template/web/middle/_env +10 -0
- package/template/web/middle/package.json +55 -0
- package/template/web/middle/src/app/api/auth/[...nextauth]/route.ts +7 -0
- package/template/web/middle/src/app/auth/disabled/page.tsx +71 -0
- package/template/web/middle/src/app/auth/error/page.tsx +56 -0
- package/template/web/middle/src/app/auth/signin/page.tsx +91 -0
- package/template/web/middle/src/app/auth/unauthorized/page.tsx +88 -0
- package/template/web/middle/src/app/layout.tsx +35 -0
- package/template/web/middle/src/app/middle/page.tsx +10 -0
- package/template/web/middle/src/app/page.tsx +20 -0
- package/template/web/middle/src/components/base/AuthGuard/Loading.tsx +19 -0
- package/template/web/middle/src/components/base/AuthGuard/index.tsx +45 -0
- package/template/web/middle/src/components/base/Layout/Header/UserAuthStatus.tsx +93 -0
- package/template/web/middle/src/components/base/Layout/Header/index.tsx +34 -0
- package/template/web/middle/src/components/base/Layout/Sidebar/index.tsx +88 -0
- package/template/web/middle/src/components/base/Layout/index.tsx +35 -0
- package/template/web/middle/src/components/base/Providers/MiddleProvider.tsx +33 -0
- package/template/web/middle/src/components/base/Providers/index.tsx +37 -0
- package/template/web/middle/src/env.js +57 -0
- package/template/web/middle/src/server/auth/next-auth/options.ts +26 -0
- package/template/web/middle/src/server/trpc/context.ts +22 -0
- package/template/web/middle/src/server/trpc/procedures.ts +16 -0
- package/template/web/middle/src/server/utils/index.ts +2 -0
- package/template/web/middle/src/server/utils/middle.ts +34 -0
- package/template/web/middle/src/types/index.ts +3 -0
- package/template/web/middle/src/utils/index.ts +5 -0
- package/template/web/middle/src/utils/middle.ts +28 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { createEnv } from '@t3-oss/env-nextjs'
|
|
3
|
+
|
|
4
|
+
export const env = createEnv({
|
|
5
|
+
/**
|
|
6
|
+
* 在此处指定服务器端环境变量模式。
|
|
7
|
+
* 这样可以确保应用程序不会使用无效的环境变量构建。
|
|
8
|
+
*/
|
|
9
|
+
server: {
|
|
10
|
+
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
|
11
|
+
// 数据库
|
|
12
|
+
DATABASE_URL: z.string().url(),
|
|
13
|
+
NEXTAUTH_URL: z.string().url(),
|
|
14
|
+
NEXTAUTH_SECRET: z.string().min(1),
|
|
15
|
+
GOOGLE_CLIENT_ID: z.string().min(1),
|
|
16
|
+
GOOGLE_CLIENT_SECRET: z.string().min(1),
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 在此处指定客户端环境变量模式。
|
|
21
|
+
* 这样可以确保应用程序不会使用无效的环境变量构建。
|
|
22
|
+
* 要将它们暴露给客户端,请使用 `NEXT_PUBLIC_` 前缀。
|
|
23
|
+
*/
|
|
24
|
+
client: {},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 在这里显式传入运行时环境变量的实际值。
|
|
28
|
+
* `server` / `client` 负责声明校验规则,
|
|
29
|
+
* `runtimeEnv` 负责把 `process.env` 中对应的值提供给 `createEnv`。
|
|
30
|
+
*/
|
|
31
|
+
runtimeEnv: {
|
|
32
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
33
|
+
// 数据库
|
|
34
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
35
|
+
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
|
36
|
+
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
|
37
|
+
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
|
38
|
+
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 使用 `SKIP_ENV_VALIDATION` 运行 `build` 或 `dev` 可以跳过环境变量验证。
|
|
43
|
+
* 这在 Docker 构建时特别有用。
|
|
44
|
+
*/
|
|
45
|
+
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 使空字符串被视为未定义。
|
|
49
|
+
* 例如:`SOME_VAR: z.string()` 和 `SOME_VAR=''` 将抛出错误。
|
|
50
|
+
*/
|
|
51
|
+
emptyStringAsUndefined: true,
|
|
52
|
+
})
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { PrismaAdapter } from '@auth/prisma-adapter'
|
|
2
|
+
|
|
3
|
+
import type { Adapter, AdapterAccount, AdapterSession, AdapterUser } from 'next-auth/adapters'
|
|
4
|
+
|
|
5
|
+
import { db } from '@/server/db/client'
|
|
6
|
+
|
|
7
|
+
const prismaAdapter = PrismaAdapter(db)
|
|
8
|
+
|
|
9
|
+
export const nextAuthAdapter: Adapter = {
|
|
10
|
+
...prismaAdapter,
|
|
11
|
+
createUser: async (data: Omit<AdapterUser, 'id'>): Promise<AdapterUser> => {
|
|
12
|
+
return db.authUser.create({ data })
|
|
13
|
+
},
|
|
14
|
+
getUser: async (id: string): Promise<AdapterUser | null> => {
|
|
15
|
+
return db.authUser.findUnique({ where: { id } })
|
|
16
|
+
},
|
|
17
|
+
getUserByEmail: async (email: string): Promise<AdapterUser | null> => {
|
|
18
|
+
return db.authUser.findUnique({ where: { email } })
|
|
19
|
+
},
|
|
20
|
+
getUserByAccount: async (providerData: Pick<AdapterAccount, 'provider' | 'providerAccountId'>): Promise<AdapterUser | null> => {
|
|
21
|
+
const account = await db.authAccount.findUnique({
|
|
22
|
+
select: { user: true },
|
|
23
|
+
where: {
|
|
24
|
+
provider_providerAccountId: {
|
|
25
|
+
provider: providerData.provider,
|
|
26
|
+
providerAccountId: providerData.providerAccountId,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return account?.user ?? null
|
|
32
|
+
},
|
|
33
|
+
updateUser: async (data: Partial<AdapterUser>): Promise<AdapterUser> => {
|
|
34
|
+
if (!data.id) {
|
|
35
|
+
throw new Error('Adapter user id is required for updateUser')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { id, ...userData } = data
|
|
39
|
+
|
|
40
|
+
return db.authUser.update({
|
|
41
|
+
where: { id },
|
|
42
|
+
data: userData,
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
linkAccount: async (data: AdapterAccount): Promise<void> => {
|
|
46
|
+
await db.authAccount.create({ data })
|
|
47
|
+
},
|
|
48
|
+
createSession: async (data: AdapterSession): Promise<AdapterSession> => {
|
|
49
|
+
return db.authSession.create({ data })
|
|
50
|
+
},
|
|
51
|
+
getSessionAndUser: async (sessionToken: string): Promise<{ session: AdapterSession; user: AdapterUser } | null> => {
|
|
52
|
+
const session = await db.authSession.findUnique({
|
|
53
|
+
where: { sessionToken },
|
|
54
|
+
include: { user: true },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!session) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
session,
|
|
63
|
+
user: session.user,
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
updateSession: async (data: Partial<AdapterSession> & Pick<AdapterSession, 'sessionToken'>): Promise<AdapterSession> => {
|
|
67
|
+
const { sessionToken, ...sessionData } = data
|
|
68
|
+
|
|
69
|
+
return db.authSession.update({
|
|
70
|
+
where: { sessionToken },
|
|
71
|
+
data: sessionData,
|
|
72
|
+
})
|
|
73
|
+
},
|
|
74
|
+
deleteSession: async (sessionToken: string): Promise<void> => {
|
|
75
|
+
await db.authSession.delete({ where: { sessionToken } })
|
|
76
|
+
},
|
|
77
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import GoogleProvider from 'next-auth/providers/google'
|
|
2
|
+
|
|
3
|
+
import type { NextAuthOptions } from 'next-auth'
|
|
4
|
+
|
|
5
|
+
import { env } from '@/env'
|
|
6
|
+
|
|
7
|
+
import { nextAuthAdapter } from './adapter'
|
|
8
|
+
|
|
9
|
+
declare module 'next-auth' {
|
|
10
|
+
interface Session {
|
|
11
|
+
user: {
|
|
12
|
+
id: string
|
|
13
|
+
name?: string | null
|
|
14
|
+
email?: string | null
|
|
15
|
+
image?: string | null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const auth: NextAuthOptions = {
|
|
21
|
+
adapter: nextAuthAdapter,
|
|
22
|
+
secret: env.NEXTAUTH_SECRET,
|
|
23
|
+
providers: [
|
|
24
|
+
GoogleProvider({
|
|
25
|
+
clientId: env.GOOGLE_CLIENT_ID ?? '',
|
|
26
|
+
clientSecret: env.GOOGLE_CLIENT_SECRET ?? '',
|
|
27
|
+
}),
|
|
28
|
+
],
|
|
29
|
+
pages: {
|
|
30
|
+
signIn: '/auth/signin',
|
|
31
|
+
error: '/auth/error',
|
|
32
|
+
},
|
|
33
|
+
callbacks: {
|
|
34
|
+
async session({ session, user }) {
|
|
35
|
+
if (session.user) {
|
|
36
|
+
session.user.id = user.id
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return session
|
|
40
|
+
},
|
|
41
|
+
async redirect({ url, baseUrl }) {
|
|
42
|
+
if (url.startsWith(baseUrl)) {
|
|
43
|
+
return url
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (url.startsWith('/')) {
|
|
47
|
+
return `${baseUrl}${url}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return baseUrl
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getServerSession } from 'next-auth'
|
|
2
|
+
|
|
3
|
+
import { auth } from '@/server/auth/next-auth/options'
|
|
4
|
+
import { db } from '@/server/db/client'
|
|
5
|
+
|
|
6
|
+
import { enhance } from '../generated/enhancer/enhance'
|
|
7
|
+
|
|
8
|
+
export const createTRPCContext = async (opts?: { req?: Request }) => {
|
|
9
|
+
const session = await getServerSession(auth)
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
session,
|
|
13
|
+
req: opts?.req,
|
|
14
|
+
headers: opts?.req?.headers,
|
|
15
|
+
prisma: enhance(db, {
|
|
16
|
+
user: session?.user?.id ? { id: session.user.id } : undefined,
|
|
17
|
+
}),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TRPCContext = Awaited<ReturnType<typeof createTRPCContext>>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TRPCError } from '@trpc/server'
|
|
2
|
+
|
|
3
|
+
import { createTRPCProcedure } from './init'
|
|
4
|
+
|
|
5
|
+
export const publicProcedure = createTRPCProcedure
|
|
6
|
+
|
|
7
|
+
export const procedure = createTRPCProcedure.use(({ ctx, next }) => {
|
|
8
|
+
if (!ctx.session?.user?.id) {
|
|
9
|
+
throw new TRPCError({
|
|
10
|
+
code: 'UNAUTHORIZED',
|
|
11
|
+
message: '请先登录',
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return next()
|
|
16
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# 数据库配置
|
|
2
|
+
DATABASE_URL=postgresql://root:password.@localhost:5432/demo
|
|
3
|
+
|
|
4
|
+
# 中台登录
|
|
5
|
+
NEXTAUTH_URL=http://localhost:4000
|
|
6
|
+
NEXTAUTH_SECRET=
|
|
7
|
+
NEXT_PUBLIC_MIDDLE_API_URL=http://localhost:3000
|
|
8
|
+
MIDDLE_OIDC_CLIENT_ID=
|
|
9
|
+
MIDDLE_OIDC_CLIENT_SECRET=
|
|
10
|
+
NEXT_PUBLIC_MIDDLE_APPID=
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__WZYJS_APP_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev --turbopack",
|
|
8
|
+
"build": "NODE_ENV=production next build --turbopack",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"generate": "rm -rf src/server/generated && zenstack generate --output ./src/server/generated/.zenstack --no-compile --schema ./prisma/schema.zmodel",
|
|
11
|
+
"db:push": "prisma db push",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"check": "bun run lint && bun run typecheck",
|
|
15
|
+
"postinstall": "bun run generate"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@prisma/client": "^6.14.0",
|
|
19
|
+
"@t3-oss/env-nextjs": "^0.10.1",
|
|
20
|
+
"@tanstack/react-query": "^5.50.0",
|
|
21
|
+
"@trpc/client": "^11.7.0",
|
|
22
|
+
"@trpc/next": "^11.7.0",
|
|
23
|
+
"@trpc/react-query": "^11.7.0",
|
|
24
|
+
"@trpc/server": "^11.7.0",
|
|
25
|
+
"@wzyjs/hooks": "workspace:*",
|
|
26
|
+
"@wzyjs/middle-sdk": "workspace:*",
|
|
27
|
+
"@wzyjs/next": "workspace:*",
|
|
28
|
+
"@wzyjs/utils": "workspace:*",
|
|
29
|
+
"@wzyjs/uis": "workspace:*",
|
|
30
|
+
"@zenstackhq/runtime": "2.22.1",
|
|
31
|
+
"@zenstackhq/trpc": "2.22.1",
|
|
32
|
+
"antd": "^6.3.1",
|
|
33
|
+
"next": "^16.2.4",
|
|
34
|
+
"next-auth": "^4.24.11",
|
|
35
|
+
"react": "^19.1.0",
|
|
36
|
+
"react-dom": "^19.1.0",
|
|
37
|
+
"server-only": "^0.0.1",
|
|
38
|
+
"superjson": "^2.2.1",
|
|
39
|
+
"zenstack": "2.22.1",
|
|
40
|
+
"zod": "^3.23.3",
|
|
41
|
+
"zod-validation-error": "^3.4.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^20",
|
|
45
|
+
"@types/react": "19.1.10",
|
|
46
|
+
"@types/react-dom": "^19",
|
|
47
|
+
"eslint": "^9",
|
|
48
|
+
"eslint-config-next": "^16.2.4",
|
|
49
|
+
"postcss": "^8.4.39",
|
|
50
|
+
"prisma": "^6.14.0",
|
|
51
|
+
"prisma-json-types-generator": "^3.3.0",
|
|
52
|
+
"tailwindcss": "^3.4.3",
|
|
53
|
+
"typescript": "^5"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Suspense } from 'react'
|
|
4
|
+
import { signOut, useSession } from 'next-auth/react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
LogoutOutlined,
|
|
9
|
+
PanelPage,
|
|
10
|
+
Result,
|
|
11
|
+
Spin,
|
|
12
|
+
StopOutlined,
|
|
13
|
+
Typography,
|
|
14
|
+
} from '@/components'
|
|
15
|
+
|
|
16
|
+
const DisabledContent = () => {
|
|
17
|
+
const { data: session } = useSession()
|
|
18
|
+
|
|
19
|
+
const onSignOut = async () => {
|
|
20
|
+
await signOut({ callbackUrl: '/auth/signin' })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<PanelPage eyebrow='账号不可用' title='暂时不能访问'>
|
|
25
|
+
<Result
|
|
26
|
+
status='403'
|
|
27
|
+
icon={<StopOutlined style={{ color: '#f5222d' }} />}
|
|
28
|
+
title='这个账号已被停用'
|
|
29
|
+
subTitle={(
|
|
30
|
+
<div className='mt-4 space-y-4 text-left'>
|
|
31
|
+
<Typography.Paragraph className='!mb-0 text-center !text-sm !leading-6 !text-slate-600 dark:!text-slate-300'>
|
|
32
|
+
你已经登录成功,但这个账号目前不能使用该应用。请联系管理员重新启用,或换一个有权限的账号登录。
|
|
33
|
+
</Typography.Paragraph>
|
|
34
|
+
|
|
35
|
+
<div className='rounded-lg border border-slate-200/70 bg-white/70 px-4 py-3 dark:border-white/10 dark:bg-white/5'>
|
|
36
|
+
<div className='text-xs font-semibold uppercase text-slate-500 dark:text-slate-400'>
|
|
37
|
+
当前登录账号
|
|
38
|
+
</div>
|
|
39
|
+
<div className='mt-2 break-all text-sm font-medium text-slate-900 dark:text-slate-100'>
|
|
40
|
+
{session?.user?.email || '暂未读取到账号信息'}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
extra={(
|
|
46
|
+
<div className='flex justify-center'>
|
|
47
|
+
<Button
|
|
48
|
+
type='primary'
|
|
49
|
+
danger
|
|
50
|
+
icon={<LogoutOutlined />}
|
|
51
|
+
className='!rounded-lg'
|
|
52
|
+
onClick={() => {
|
|
53
|
+
void onSignOut()
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
换个账号登录
|
|
57
|
+
</Button>
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
/>
|
|
61
|
+
</PanelPage>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default function DisabledPage() {
|
|
66
|
+
return (
|
|
67
|
+
<Suspense fallback={<Spin />}>
|
|
68
|
+
<DisabledContent />
|
|
69
|
+
</Suspense>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { Suspense } from 'react'
|
|
5
|
+
import { useSearchParams } from 'next/navigation'
|
|
6
|
+
|
|
7
|
+
import { Button, PanelPage, Result, Spin, Typography } from '@/components'
|
|
8
|
+
|
|
9
|
+
const errorMessageMap: Record<string, string> = {
|
|
10
|
+
OAuthSignin: '暂时无法打开中台登录页,请稍后再试。',
|
|
11
|
+
OAuthCallback: '中台没有返回有效的登录结果,请重新登录一次。',
|
|
12
|
+
OAuthCreateAccount: '账号信息暂时无法同步,请稍后再试。',
|
|
13
|
+
EmailCreateAccount: '账号信息暂时无法同步,请稍后再试。',
|
|
14
|
+
Callback: '登录过程被中断,请重新登录一次。',
|
|
15
|
+
OAuthAccountNotLinked: '这个邮箱已经绑定了其他登录方式,请换一种方式登录。',
|
|
16
|
+
EmailSignin: '邮件登录没有完成,请重新尝试。',
|
|
17
|
+
CredentialsSignin: '账号或授权信息校验失败,请确认后再试。',
|
|
18
|
+
SessionRequired: '请先登录,再继续访问当前页面。',
|
|
19
|
+
default: '这次登录没有成功,请重新尝试。',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AuthErrorContent = () => {
|
|
23
|
+
const searchParams = useSearchParams()
|
|
24
|
+
const error = searchParams.get('error') || 'default'
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<PanelPage eyebrow='登录遇到问题' title='没能完成登录'>
|
|
28
|
+
<Result
|
|
29
|
+
status='error'
|
|
30
|
+
title='登录没有完成'
|
|
31
|
+
subTitle={errorMessageMap[error] ?? errorMessageMap.default}
|
|
32
|
+
extra={[
|
|
33
|
+
<Link href='/auth/signin' key='signin'>
|
|
34
|
+
<Button type='primary' className='!rounded-lg px-6'>
|
|
35
|
+
重新登录
|
|
36
|
+
</Button>
|
|
37
|
+
</Link>,
|
|
38
|
+
]}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
<div className='mx-4 mb-4 rounded-lg bg-white/70 p-5 dark:bg-white/5 sm:mx-6'>
|
|
42
|
+
<Typography.Paragraph className='!mb-0 text-center !text-sm !leading-6 !text-slate-500 dark:!text-slate-300'>
|
|
43
|
+
如果多次重试仍然失败,请联系管理员,并说明你是在登录当前项目时遇到的问题。
|
|
44
|
+
</Typography.Paragraph>
|
|
45
|
+
</div>
|
|
46
|
+
</PanelPage>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default function AuthErrorPage() {
|
|
51
|
+
return (
|
|
52
|
+
<Suspense fallback={<Spin />}>
|
|
53
|
+
<AuthErrorContent />
|
|
54
|
+
</Suspense>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Suspense, useEffect } from 'react'
|
|
4
|
+
import { signIn, useSession } from 'next-auth/react'
|
|
5
|
+
import { useRouter, useSearchParams } from 'next/navigation'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
App,
|
|
9
|
+
Button,
|
|
10
|
+
LoginOutlined,
|
|
11
|
+
PanelPage,
|
|
12
|
+
Space,
|
|
13
|
+
Spin,
|
|
14
|
+
Typography,
|
|
15
|
+
} from '@/components'
|
|
16
|
+
import { getSafeCallbackPath } from '@/utils'
|
|
17
|
+
|
|
18
|
+
const { Text } = Typography
|
|
19
|
+
|
|
20
|
+
const SignInPage = () => {
|
|
21
|
+
const { message } = App.useApp()
|
|
22
|
+
const { data: session, status } = useSession()
|
|
23
|
+
const router = useRouter()
|
|
24
|
+
const searchParams = useSearchParams()
|
|
25
|
+
|
|
26
|
+
const callbackUrl = getSafeCallbackPath(searchParams.get('callbackUrl'), '/')
|
|
27
|
+
const error = searchParams.get('error')
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (status === 'authenticated' && session) {
|
|
31
|
+
router.push(callbackUrl)
|
|
32
|
+
}
|
|
33
|
+
}, [callbackUrl, router, session, status])
|
|
34
|
+
|
|
35
|
+
const onMiddleSignIn = async () => {
|
|
36
|
+
try {
|
|
37
|
+
await signIn('middle', { callbackUrl })
|
|
38
|
+
} catch (signinError) {
|
|
39
|
+
console.error(666, '登录失败:', signinError)
|
|
40
|
+
void message.error('暂时无法跳转登录,请稍后再试')
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<PanelPage
|
|
46
|
+
eyebrow='继续访问'
|
|
47
|
+
title='请先登录'
|
|
48
|
+
description='使用已获授权的中台账号登录,完成后会自动回到刚才的页面。'
|
|
49
|
+
>
|
|
50
|
+
<Space orientation='vertical' size='large' className='w-full'>
|
|
51
|
+
{error ? (
|
|
52
|
+
<div className='rounded-lg border border-red-200 bg-red-50/90 p-4 text-left dark:border-red-500/30 dark:bg-red-500/10'>
|
|
53
|
+
<div className='text-sm font-semibold text-red-700 dark:text-red-300'>
|
|
54
|
+
登录没有完成
|
|
55
|
+
</div>
|
|
56
|
+
<p className='mt-2 text-sm leading-6 text-red-600 dark:text-red-200'>
|
|
57
|
+
可能是登录页面被关闭或授权已过期。你可以重新登录一次。
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
) : null}
|
|
61
|
+
|
|
62
|
+
<Button
|
|
63
|
+
type='primary'
|
|
64
|
+
size='large'
|
|
65
|
+
icon={<LoginOutlined />}
|
|
66
|
+
className='!h-12 !rounded-lg !text-sm !font-semibold shadow-sm'
|
|
67
|
+
onClick={() => {
|
|
68
|
+
void onMiddleSignIn()
|
|
69
|
+
}}
|
|
70
|
+
block
|
|
71
|
+
>
|
|
72
|
+
登录并继续
|
|
73
|
+
</Button>
|
|
74
|
+
|
|
75
|
+
<div className='rounded-lg bg-white/70 p-5 text-center dark:bg-white/5'>
|
|
76
|
+
<Text className='text-sm leading-6 text-slate-500 dark:text-slate-300'>
|
|
77
|
+
我们会带你到中台完成身份确认,不会在当前项目保存你的登录密码。
|
|
78
|
+
</Text>
|
|
79
|
+
</div>
|
|
80
|
+
</Space>
|
|
81
|
+
</PanelPage>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default function SignIn() {
|
|
86
|
+
return (
|
|
87
|
+
<Suspense fallback={<Spin />}>
|
|
88
|
+
<SignInPage />
|
|
89
|
+
</Suspense>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { signOut, useSession } from 'next-auth/react'
|
|
4
|
+
import { useRouter, useSearchParams } from 'next/navigation'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ArrowRightOutlined,
|
|
8
|
+
Button,
|
|
9
|
+
LogoutOutlined,
|
|
10
|
+
PanelPage,
|
|
11
|
+
Result,
|
|
12
|
+
RetweetOutlined,
|
|
13
|
+
StopOutlined,
|
|
14
|
+
Typography,
|
|
15
|
+
} from '@/components'
|
|
16
|
+
|
|
17
|
+
import { getSafeCallbackPath } from '@/utils'
|
|
18
|
+
|
|
19
|
+
export default function UnauthorizedPage() {
|
|
20
|
+
const { data: session } = useSession()
|
|
21
|
+
const router = useRouter()
|
|
22
|
+
const searchParams = useSearchParams()
|
|
23
|
+
|
|
24
|
+
const retryUrl = getSafeCallbackPath(searchParams.get('callbackUrl'), '/')
|
|
25
|
+
|
|
26
|
+
const onSignOut = async () => {
|
|
27
|
+
await signOut({ callbackUrl: '/auth/signin' })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<PanelPage eyebrow='需要授权' title='暂时不能访问'>
|
|
32
|
+
<Result
|
|
33
|
+
status='403'
|
|
34
|
+
title='你还没有这个页面的访问权限'
|
|
35
|
+
icon={<StopOutlined style={{ color: '#f5222d' }} />}
|
|
36
|
+
subTitle={(
|
|
37
|
+
<div className='mt-4 space-y-4'>
|
|
38
|
+
<Typography.Paragraph className='!mb-0 !text-sm !leading-6 !text-slate-600 dark:!text-slate-300'>
|
|
39
|
+
请联系管理员开通权限。权限生效后,你可以回来重试,或切换到已经授权的账号。
|
|
40
|
+
</Typography.Paragraph>
|
|
41
|
+
<div className='rounded-lg border border-slate-200/70 bg-white/70 px-4 py-3 text-left dark:border-white/10 dark:bg-white/5'>
|
|
42
|
+
<div className='text-xs font-semibold uppercase text-slate-500 dark:text-slate-400'>
|
|
43
|
+
当前登录账号
|
|
44
|
+
</div>
|
|
45
|
+
<div className='mt-2 break-all text-sm font-medium text-slate-900 dark:text-slate-100'>
|
|
46
|
+
{session?.user?.email || '暂未读取到账号信息'}
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
extra={(
|
|
52
|
+
<div className='flex w-full flex-col gap-3 sm:flex-row sm:justify-center'>
|
|
53
|
+
<Button
|
|
54
|
+
type='primary'
|
|
55
|
+
icon={<RetweetOutlined />}
|
|
56
|
+
className='!rounded-lg'
|
|
57
|
+
onClick={() => {
|
|
58
|
+
router.push(retryUrl)
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
重试访问
|
|
62
|
+
</Button>
|
|
63
|
+
<Button
|
|
64
|
+
icon={<ArrowRightOutlined />}
|
|
65
|
+
className='!rounded-lg'
|
|
66
|
+
onClick={() => {
|
|
67
|
+
router.push('/')
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
返回首页
|
|
71
|
+
</Button>
|
|
72
|
+
<Button
|
|
73
|
+
type='primary'
|
|
74
|
+
danger
|
|
75
|
+
icon={<LogoutOutlined />}
|
|
76
|
+
className='!rounded-lg'
|
|
77
|
+
onClick={() => {
|
|
78
|
+
void onSignOut()
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
换个账号登录
|
|
82
|
+
</Button>
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
/>
|
|
86
|
+
</PanelPage>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
import { type Metadata } from 'next'
|
|
3
|
+
|
|
4
|
+
import { AuthGuard } from '@/components/base/AuthGuard'
|
|
5
|
+
import { Providers } from '@/components/base/Providers'
|
|
6
|
+
import { Layout } from '@/components/base/Layout'
|
|
7
|
+
|
|
8
|
+
import '@/styles/globals.css'
|
|
9
|
+
|
|
10
|
+
export const metadata: Metadata = {
|
|
11
|
+
title: '__WZYJS_APP_DISPLAY_NAME__',
|
|
12
|
+
icons: [{ rel: 'icon', url: '/favicon.ico' }],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RootLayoutProps {
|
|
16
|
+
children: ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default function RootLayout(props: RootLayoutProps) {
|
|
20
|
+
const { children } = props
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<html lang='zh-CN' suppressHydrationWarning>
|
|
24
|
+
<body suppressHydrationWarning>
|
|
25
|
+
<Providers>
|
|
26
|
+
<AuthGuard>
|
|
27
|
+
<Layout>
|
|
28
|
+
{children}
|
|
29
|
+
</Layout>
|
|
30
|
+
</AuthGuard>
|
|
31
|
+
</Providers>
|
|
32
|
+
</body>
|
|
33
|
+
</html>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useSession } from 'next-auth/react'
|
|
4
|
+
|
|
5
|
+
export default function HomePage() {
|
|
6
|
+
const { data: session } = useSession()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className='min-h-full bg-slate-50 px-5 py-8 text-slate-950 dark:bg-[#14171c] dark:text-[#e8edf5]'>
|
|
10
|
+
<section className='mx-auto max-w-4xl border-b border-slate-200 pb-6 dark:border-[#303846]'>
|
|
11
|
+
<h1 className='text-2xl font-semibold tracking-normal'>
|
|
12
|
+
__WZYJS_APP_DISPLAY_NAME__
|
|
13
|
+
</h1>
|
|
14
|
+
<p className='mt-3 text-sm leading-6 text-slate-600 dark:text-[#aab4c2]'>
|
|
15
|
+
{session?.user?.name || session?.user?.email || '当前账号'} 已登录。
|
|
16
|
+
</p>
|
|
17
|
+
</section>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|