From 12b60e74dffc9f8a9972cd274476f2bb437a69a6 Mon Sep 17 00:00:00 2001 From: luorijun Date: Tue, 31 Mar 2026 10:56:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=9D=83=E9=99=90=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=B8=8E=E6=9D=83=E9=99=90=E6=B8=B2=E6=9F=93=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bun.lock | 3 ++ package.json | 1 + src/actions/auth.ts | 4 +-- src/app/login/page.tsx | 6 +++- src/components/auth/index.ts | 13 ++++++++ src/lib/scopes.ts | 65 ++++++++++++++++++++++++++++++++++++ src/lib/stores/scopes.ts | 5 +++ 7 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 src/components/auth/index.ts create mode 100644 src/lib/scopes.ts create mode 100644 src/lib/stores/scopes.ts diff --git a/bun.lock b/bun.lock index eddbacb..aaadc6e 100644 --- a/bun.lock +++ b/bun.lock @@ -18,6 +18,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "jotai": "^2.19.0", "lucide-react": "^0.562.0", "next": "^16.0.10", "next-themes": "^0.4.6", @@ -359,6 +360,8 @@ "jiti": ["jiti@2.6.1", "https://registry.npmmirror.com/jiti/-/jiti-2.6.1.tgz", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "jotai": ["jotai@2.19.0", "https://registry.npmmirror.com/jotai/-/jotai-2.19.0.tgz", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ=="], + "lightningcss": ["lightningcss@1.30.2", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.2.tgz", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], diff --git a/package.json b/package.json index 50b0667..a698d17 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "jotai": "^2.19.0", "lucide-react": "^0.562.0", "next": "^16.0.10", "next-themes": "^0.4.6", diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 41919a3..62eefa4 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -16,7 +16,7 @@ export async function login(params: { username: string password: string remember: boolean -}): Promise { +}): Promise> { const resp = await callByDevice("/api/auth/token", { grant_type: "password", login_type: "password", @@ -43,7 +43,7 @@ export async function login(params: { return { success: true, - data: undefined, + data: data.scope?.split(" ") || [], } } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 82d0554..f9229e0 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -1,5 +1,6 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" +import { useSetAtom } from "jotai" import { useRouter } from "next/navigation" import { Controller, useForm } from "react-hook-form" import { toast } from "sonner" @@ -16,6 +17,7 @@ import { FieldLegend, } from "@/components/ui/field" import { Input } from "@/components/ui/input" +import { scopesAtom } from "@/lib/stores/scopes" const schema = z.object({ username: z.string().min(4).max(50), @@ -36,7 +38,7 @@ export default function LoginPage() { }) const router = useRouter() - + const setScopes = useSetAtom(scopesAtom) const onSubmit = async (data: Schema) => { try { const resp = await login(data) @@ -45,6 +47,8 @@ export default function LoginPage() { } // 登录成功后跳转到首页 + console.log("用户权限列表", resp.data) + setScopes(resp.data) router.push("/") } catch (e) { toast.error("登录失败", { diff --git a/src/components/auth/index.ts b/src/components/auth/index.ts new file mode 100644 index 0000000..a707e58 --- /dev/null +++ b/src/components/auth/index.ts @@ -0,0 +1,13 @@ +import { useAtomValue } from "jotai" +import type { ReactNode } from "react" +import { scopesAtom } from "@/lib/stores/scopes" + +export function Auth(props: { scope: string; children: ReactNode }) { + const scopes = useAtomValue(scopesAtom) + if (!scopes.length) return props.children + + const hasScope = scopes.some(s => props.scope.startsWith(s)) + if (!hasScope) return null + + return props.children +} diff --git a/src/lib/scopes.ts b/src/lib/scopes.ts new file mode 100644 index 0000000..03b868e --- /dev/null +++ b/src/lib/scopes.ts @@ -0,0 +1,65 @@ +// 权限 +export const ScopePermission = "permission"; +export const ScopePermissionRead = "permission:read"; // 读取权限列表 +export const ScopePermissionWrite = "permission:write"; // 写入权限 + +// 管理员角色 +export const ScopeAdminRole = "admin_role"; +export const ScopeAdminRoleRead = "admin_role:read"; // 读取管理员角色列表 +export const ScopeAdminRoleWrite = "admin_role:write"; // 写入管理员角色 + +// 管理员 +export const ScopeAdmin = "admin"; +export const ScopeAdminRead = "admin:read"; // 读取管理员列表 +export const ScopeAdminWrite = "admin:write"; // 写入管理员 + +// 产品 +export const ScopeProduct = "product"; +export const ScopeProductRead = "product:read"; // 读取产品列表 +export const ScopeProductWrite = "product:write"; // 写入产品 + +// 产品套餐 +export const ScopeProductSku = "product_sku"; +export const ScopeProductSkuRead = "product_sku:read"; // 读取产品套餐列表 +export const ScopeProductSkuWrite = "product_sku:write"; // 写入产品套餐 + +// 折扣 +export const ScopeDiscount = "discount"; +export const ScopeDiscountRead = "discount:read"; // 读取折扣列表 +export const ScopeDiscountWrite = "discount:write"; // 写入折扣 + +// 用户套餐 +export const ScopeResource = "resource"; +export const ScopeResourceRead = "resource:read"; // 读取用户套餐列表 +export const ScopeResourceWrite = "resource:write"; // 写入用户套餐 + +// 用户 +export const ScopeUser = "user"; +export const ScopeUserRead = "user:read"; // 读取用户列表 +export const ScopeUserWrite = "user:write"; // 写入用户 +export const ScopeUserWriteBalance = "user:write:balance"; // 写入用户余额 + +// 优惠券 +export const ScopeCoupon = "coupon"; +export const ScopeCouponRead = "coupon:read"; // 读取优惠券列表 +export const ScopeCouponWrite = "coupon:write"; // 写入优惠券 + +// 批次 +export const ScopeBatch = "batch"; +export const ScopeBatchRead = "batch:read"; // 读取批次列表 +export const ScopeBatchWrite = "batch:write"; // 写入批次 + +// IP +export const ScopeChannel = "channel"; +export const ScopeChannelRead = "channel:read"; // 读取 IP 列表 +export const ScopeChannelWrite = "channel:write"; // 写入 IP + +// 交易 +export const ScopeTrade = "trade"; +export const ScopeTradeRead = "trade:read"; // 读取交易列表 +export const ScopeTradeWrite = "trade:write"; // 写入交易 + +// 账单 +export const ScopeBill = "bill"; +export const ScopeBillRead = "bill:read"; // 读取账单列表 +export const ScopeBillWrite = "bill:write"; // 写入账单 diff --git a/src/lib/stores/scopes.ts b/src/lib/stores/scopes.ts new file mode 100644 index 0000000..2e85352 --- /dev/null +++ b/src/lib/stores/scopes.ts @@ -0,0 +1,5 @@ +import { atom } from "jotai" + +const scopesAtom = atom([]) + +export { scopesAtom }