mirror of
https://gh-proxy.org/https://github.com/klinsmaya/portal-template-default.git
synced 2026-11-08 00:57:57 +08:00
fix: improve starter resource routing and filters
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
# NocoBase REST API root (the /api suffix is required)
|
||||
NOCOBASE_API_URL=/api
|
||||
NOCOBASE_AUTHENTICATOR=basic
|
||||
|
||||
# Public base path for Vite assets and React Router when deployed under NocoBase.
|
||||
# Use / for root-local development, or /x/admin/ when building for storage/dist-client/x/admin.
|
||||
@@ -10,6 +9,3 @@ NOCOBASE_PORTAL_BASE=/x/admin/
|
||||
# API_CLIENT_STORAGE_PREFIX=NOCOBASE_
|
||||
# API_CLIENT_STORAGE_TYPE=localStorage
|
||||
# API_CLIENT_SHARE_TOKEN=false
|
||||
|
||||
# Optional for local development. Do not commit real access tokens.
|
||||
# NOCOBASE_API_TOKEN=
|
||||
|
||||
@@ -16,6 +16,8 @@ Components copied from shadcn/ui are owned and maintained by this project; upstr
|
||||
|
||||
Put application-owned business routes in `src/routes.tsx` with `defineAppRoutes`. A route with a `resource` entry contributes its Refine resource and navigation item, while the same definition generates its React Router route. Mark create, edit, and show children with `resourceAction` so their paths populate the same Refine resource instead of being repeated. Use `access.roles` for route-level role constraints; nested routes inherit parent constraints, and the runtime applies the complete chain to both menu visibility and direct URL access. Do not repeat those roles in `resource.meta.acl` or a manually written route guard.
|
||||
|
||||
Resource action children are rendered automatically through the resource route's outlet. Keep list pages focused on list content; do not add a wrapper solely to render `<Outlet />`, and do not replace the shared Create, Edit, or Show buttons with hard-coded `navigate()` calls. Set `outlet: "manual"` when a specialized layout renders `<Outlet />` or consumes `useOutlet()` itself, when a child should replace the list instead of overlaying it, or when the child must stay inside a parent provider or boundary. If an action button has no target, fix the route definition by ensuring the resource route name matches the API resource and the corresponding child declares `resourceAction`.
|
||||
|
||||
The bundled Registry routes are examples, not application structure. When beginning the real application, set `registryRoutesEnabled` to `false` in `src/routes.tsx` and define the application's own routes there. This removes Registry-contributed main routes, resources, and navigation without modifying installed extension source; extension providers, authentication adapters, and `/dev` showcases remain available.
|
||||
|
||||
Route access currently centralizes role constraints only. Keep NocoBase resource/action, region, field, and record checks in the existing `CanAccess` and ACL boundaries close to the protected query or UI. Server ACL remains authoritative.
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AccessDenied } from "@/components/access-control/access-denied";
|
||||
import { CanAccess } from "@/components/access-control/can-access";
|
||||
import { UserCreate } from "./create";
|
||||
import { UserEdit } from "./edit";
|
||||
import { UserResourceLayout } from "./layout";
|
||||
import { UserList } from "./list";
|
||||
import { RoleDetailRoute } from "./role-detail";
|
||||
import { userRoutes } from "./routes";
|
||||
import { UserShow } from "./show";
|
||||
@@ -14,7 +14,11 @@ export const usersExampleRoutes = defineAppRoutes([
|
||||
{
|
||||
name: "users",
|
||||
path: userRoutes.list,
|
||||
element: <UserResourceLayout />,
|
||||
element: (
|
||||
<CanAccess resource="users" action="list" fallback={<AccessDenied />}>
|
||||
<UserList />
|
||||
</CanAccess>
|
||||
),
|
||||
resource: {
|
||||
meta: {
|
||||
label: "Users",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
export * from "./app-routes";
|
||||
export * from "./create";
|
||||
export * from "./edit";
|
||||
export * from "./layout";
|
||||
export * from "./list";
|
||||
export * from "./role-detail";
|
||||
export * from "./routes";
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { CanAccess } from "@/components/access-control/can-access";
|
||||
import { Outlet } from "react-router";
|
||||
|
||||
import { AccessDenied } from "@/components/access-control/access-denied";
|
||||
import { UserList } from "./list";
|
||||
|
||||
export function UserResourceLayout() {
|
||||
return (
|
||||
<>
|
||||
<CanAccess resource="users" action="list" fallback={<AccessDenied />}>
|
||||
<UserList />
|
||||
</CanAccess>
|
||||
<Outlet />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { ResourceProps } from "@refinedev/core";
|
||||
import { createElement, type ReactElement, type ReactNode } from "react";
|
||||
import {
|
||||
createElement,
|
||||
Fragment,
|
||||
type ReactElement,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { Outlet, Route } from "react-router";
|
||||
|
||||
import type { ResourceAcl, RouteAccessConstraint } from "@/lib/nocobase/acl";
|
||||
@@ -18,6 +23,7 @@ type AppRouteBase = {
|
||||
name: string;
|
||||
element?: ReactNode;
|
||||
access?: RouteAccessConstraint;
|
||||
outlet?: "auto" | "manual";
|
||||
};
|
||||
|
||||
type AppRouteResourceBinding =
|
||||
@@ -147,9 +153,30 @@ export function buildRouteResources(
|
||||
});
|
||||
}
|
||||
|
||||
const hasResourceActionRoute = (routes: AppRouteDefinition[]): boolean =>
|
||||
routes.some(
|
||||
(route) =>
|
||||
Boolean(route.resourceAction) ||
|
||||
hasResourceActionRoute(route.children ?? [])
|
||||
);
|
||||
|
||||
export function renderAppRoutes(routes: AppRouteDefinition[]): ReactElement[] {
|
||||
return routes.map((route) => {
|
||||
const content = route.element ?? createElement(Outlet);
|
||||
// Resource actions are nested so the list stays mounted behind a drawer or
|
||||
// dialog. Own that required outlet here unless a layout deliberately
|
||||
// renders or consumes the outlet itself.
|
||||
const content =
|
||||
route.element &&
|
||||
route.resource &&
|
||||
route.outlet !== "manual" &&
|
||||
hasResourceActionRoute(route.children ?? [])
|
||||
? createElement(
|
||||
Fragment,
|
||||
null,
|
||||
route.element,
|
||||
createElement(Outlet)
|
||||
)
|
||||
: route.element ?? createElement(Outlet);
|
||||
const element = route.access
|
||||
? createElement(RouteAccessGuard, { access: route.access }, content)
|
||||
: content;
|
||||
|
||||
@@ -647,6 +647,13 @@ export function DataTableFilterInput<TData>({
|
||||
return defaultOperatorFromProps || "eq";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
columnFromProps.columnDef.meta = {
|
||||
...columnFromProps.columnDef.meta,
|
||||
filterOperator: operator,
|
||||
};
|
||||
}, [columnFromProps, operator]);
|
||||
|
||||
const handleApply = () => {
|
||||
columnFromProps.setFilterValue(filterValue);
|
||||
};
|
||||
@@ -658,10 +665,6 @@ export function DataTableFilterInput<TData>({
|
||||
|
||||
const handleOperatorChange = (value: CrudOperators) => {
|
||||
setOperator(value);
|
||||
columnFromProps.columnDef.meta = {
|
||||
...columnFromProps.columnDef.meta,
|
||||
filterOperator: value,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user