mirror of
https://github.com/zuiidea/antd-admin.git
synced 2026-10-19 16:14:37 +00:00
chore: add root .githooks dispatcher and sync with-lingui
Wire monorepo pre-commit by staged paths; document setup; update Lingui template. Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# Git hooks(仓库根)
|
||||
|
||||
本目录为 **monorepo 根级** Git 钩子。提交时根据暂存文件路径,在对应子项目目录执行检查(例如 `apps/with-lingui` 下执行 `vp staged`,以便正确读取该目录的 `vite.config.ts`)。
|
||||
|
||||
模板应用目录内的 `.vite-hooks/`(如 `apps/with-lingui/.vite-hooks/`)留给 **模板单独拷贝出去使用**;在本仓库开发时请使用根级钩子,避免在仓库根执行 `vp staged` 找不到 `vite.config.ts`。
|
||||
|
||||
## 启用方式(一次性)
|
||||
|
||||
在仓库根目录执行:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
或使用:
|
||||
|
||||
```bash
|
||||
pnpm run setup:git-hooks
|
||||
```
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
git config core.hooksPath
|
||||
# 应输出:.githooks
|
||||
```
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env sh
|
||||
# Monorepo pre-commit: run Vite+ / checks in the app that owns staged files.
|
||||
# Template copies under apps/*/ keep their own .vite-hooks for standalone use; this repo uses root hooksPath instead.
|
||||
set -e
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
cd "$ROOT"
|
||||
|
||||
STAGED=$(git diff --cached --name-only --diff-filter=ACM)
|
||||
FAILED=0
|
||||
|
||||
run_in_dir() {
|
||||
dir="$1"
|
||||
shift
|
||||
(cd "$ROOT/$dir" && "$@") || FAILED=1
|
||||
}
|
||||
|
||||
if echo "$STAGED" | grep -q '^apps/with-lingui/'; then
|
||||
run_in_dir apps/with-lingui vp staged
|
||||
fi
|
||||
|
||||
if echo "$STAGED" | grep -q '^apps/basic/'; then
|
||||
run_in_dir apps/basic vp staged
|
||||
fi
|
||||
|
||||
if echo "$STAGED" | grep -q '^packages/create/'; then
|
||||
run_in_dir . pnpm --dir packages/create run check-types
|
||||
fi
|
||||
|
||||
if echo "$STAGED" | grep -q '^apps/docs/'; then
|
||||
run_in_dir apps/docs pnpm run lint
|
||||
fi
|
||||
|
||||
exit "$FAILED"
|
||||
@@ -74,6 +74,16 @@ Core Playwright flows (from the app you are working in):
|
||||
cd apps/basic && pnpm run test:e2e:core
|
||||
```
|
||||
|
||||
### Git hooks
|
||||
|
||||
提交前检查由仓库根目录 **`.githooks/pre-commit`** 按暂存路径分发到各子项目(在对应目录执行 `vp staged`,避免在仓库根找不到 `vite.config.ts`)。一次性启用:
|
||||
|
||||
```bash
|
||||
pnpm run setup:git-hooks
|
||||
```
|
||||
|
||||
说明见 [.githooks/README.md](./.githooks/README.md)。模板目录内的 `.vite-hooks` 仍保留给模板单独拷贝使用;在本仓库开发请使用上述根级钩子。
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -1 +1 @@
|
||||
pnpm lint && pnpm fmt
|
||||
vp staged
|
||||
@@ -13,7 +13,12 @@ interface UserMenuProps {
|
||||
accountMenuTriggerAriaLabel: string;
|
||||
}
|
||||
|
||||
export function UserMenu({ collapsed, user, userMenuItems, accountMenuTriggerAriaLabel }: UserMenuProps) {
|
||||
export function UserMenu({
|
||||
collapsed,
|
||||
user,
|
||||
userMenuItems,
|
||||
accountMenuTriggerAriaLabel,
|
||||
}: UserMenuProps) {
|
||||
const { token } = theme.useToken();
|
||||
|
||||
const avatarSrc = (user?.avatar ?? "").trim() || undefined;
|
||||
|
||||
@@ -52,10 +52,10 @@ export type CrudResult<TListData, TCreateValues, TUpdateValues> = ResourceCRUDRe
|
||||
|
||||
type RollbackCtx<TListData> = { snapshot: TListData | undefined };
|
||||
|
||||
export function applyOptimisticListUpdate<TItem extends { id: string }, TUpdate extends { id: string }>(
|
||||
data: ResourceListData<TItem>,
|
||||
values: TUpdate,
|
||||
): { list: TItem[]; total: number } {
|
||||
export function applyOptimisticListUpdate<
|
||||
TItem extends { id: string },
|
||||
TUpdate extends { id: string },
|
||||
>(data: ResourceListData<TItem>, values: TUpdate): { list: TItem[]; total: number } {
|
||||
const { id, ...patch } = values as TUpdate & Record<string, unknown>;
|
||||
return {
|
||||
list: data.list.map((row) =>
|
||||
@@ -147,7 +147,10 @@ export function useResourceCRUD<
|
||||
if (applied) {
|
||||
queryClient.setQueryData<TListData>(
|
||||
queryKey,
|
||||
applyOptimisticListDelete(previous as ResourceListData<{ id: string }>, id) as unknown as TListData,
|
||||
applyOptimisticListDelete(
|
||||
previous as ResourceListData<{ id: string }>,
|
||||
id,
|
||||
) as unknown as TListData,
|
||||
);
|
||||
}
|
||||
options.deleteLifecycle?.onMutate?.(id);
|
||||
|
||||
@@ -12,8 +12,16 @@ export type UseTableFitHeightArgs = {
|
||||
};
|
||||
|
||||
export function useTableFitHeight(args: UseTableFitHeightArgs) {
|
||||
const { pageShellRef, toolbarRef, middleRef, tableFrameRef, marginLG, rowCount, isLoading, showPagination } =
|
||||
args;
|
||||
const {
|
||||
pageShellRef,
|
||||
toolbarRef,
|
||||
middleRef,
|
||||
tableFrameRef,
|
||||
marginLG,
|
||||
rowCount,
|
||||
isLoading,
|
||||
showPagination,
|
||||
} = args;
|
||||
|
||||
const tableAvailableRef = useRef(0);
|
||||
const [tableAreaMaxHeight, setTableAreaMaxHeight] = useState<number | undefined>(() =>
|
||||
@@ -48,7 +56,10 @@ export function useTableFitHeight(args: UseTableFitHeightArgs) {
|
||||
? mainRect.bottom
|
||||
: Math.min(shell.getBoundingClientRect().bottom, viewportBottom);
|
||||
|
||||
const maxMiddleFromShell = Math.max(0, Math.floor(clipBottom - toolbarRect.bottom - marginLG));
|
||||
const maxMiddleFromShell = Math.max(
|
||||
0,
|
||||
Math.floor(clipBottom - toolbarRect.bottom - marginLG),
|
||||
);
|
||||
|
||||
const midHRaw = mid.clientHeight;
|
||||
const midH = midHRaw > 0 ? Math.min(midHRaw, maxMiddleFromShell) : maxMiddleFromShell;
|
||||
@@ -103,7 +114,16 @@ export function useTableFitHeight(args: UseTableFitHeightArgs) {
|
||||
ro.observe(shell);
|
||||
ro.observe(mid);
|
||||
return () => ro.disconnect();
|
||||
}, [showPagination, rowCount, isLoading, marginLG, pageShellRef, toolbarRef, middleRef, tableFrameRef]);
|
||||
}, [
|
||||
showPagination,
|
||||
rowCount,
|
||||
isLoading,
|
||||
marginLG,
|
||||
pageShellRef,
|
||||
toolbarRef,
|
||||
middleRef,
|
||||
tableFrameRef,
|
||||
]);
|
||||
|
||||
return {
|
||||
tableAreaMaxHeight,
|
||||
|
||||
@@ -26,10 +26,7 @@ async function enableMocking() {
|
||||
|
||||
enableMocking()
|
||||
.then(async () => {
|
||||
await Promise.all([
|
||||
useSettingsStore.persist.rehydrate(),
|
||||
useAuthStore.persist.rehydrate(),
|
||||
]);
|
||||
await Promise.all([useSettingsStore.persist.rehydrate(), useAuthStore.persist.rehydrate()]);
|
||||
|
||||
const initialLocale = useSettingsStore.getState().locale;
|
||||
const messages = await loadLocaleCatalog(initialLocale);
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("paginatedListSchema / paginatedWithSchema", () => {
|
||||
permissions: [] as string[],
|
||||
};
|
||||
const res = paginatedWithSchema(UserSchema, { list: [row], total: 1 });
|
||||
const body = (await res.json()) as { data: { list: typeof row[]; total: number } };
|
||||
const body = (await res.json()) as { data: { list: (typeof row)[]; total: number } };
|
||||
expect(body.data.total).toBe(1);
|
||||
expect(body.data.list).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -51,11 +51,7 @@ export function successWithNullBody(message: string = "ok") {
|
||||
return successWithSchema(NullBodySchema, null, message);
|
||||
}
|
||||
|
||||
export function errorResponse(
|
||||
code: number,
|
||||
message: string,
|
||||
data: unknown = null,
|
||||
) {
|
||||
export function errorResponse(code: number, message: string, data: unknown = null) {
|
||||
return HttpResponse.json({
|
||||
code,
|
||||
data,
|
||||
|
||||
@@ -256,21 +256,21 @@ function UsersPage() {
|
||||
() =>
|
||||
showPagination
|
||||
? {
|
||||
total: data?.total ?? 0,
|
||||
current: currentPage,
|
||||
pageSize: search.limit,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => t`${total} rows`,
|
||||
onChange: (page, pageSize) => {
|
||||
void navigate({
|
||||
search: {
|
||||
...search,
|
||||
limit: pageSize ?? search.limit,
|
||||
offset: (page - 1) * (pageSize ?? search.limit),
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
total: data?.total ?? 0,
|
||||
current: currentPage,
|
||||
pageSize: search.limit,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => t`${total} rows`,
|
||||
onChange: (page, pageSize) => {
|
||||
void navigate({
|
||||
search: {
|
||||
...search,
|
||||
limit: pageSize ?? search.limit,
|
||||
offset: (page - 1) * (pageSize ?? search.limit),
|
||||
},
|
||||
});
|
||||
},
|
||||
}
|
||||
: false,
|
||||
[showPagination, data?.total, currentPage, search, navigate, t],
|
||||
);
|
||||
|
||||
@@ -163,7 +163,10 @@ async function request<T>(
|
||||
|
||||
export const httpClient = {
|
||||
get: <T>(path: string, options?: RequestOptions) => request<T>("GET", path, undefined, options),
|
||||
post: <T>(path: string, body?: unknown, options?: RequestOptions) => request<T>("POST", path, body, options),
|
||||
put: <T>(path: string, body?: unknown, options?: RequestOptions) => request<T>("PUT", path, body, options),
|
||||
delete: <T>(path: string, options?: RequestOptions) => request<T>("DELETE", path, undefined, options),
|
||||
post: <T>(path: string, body?: unknown, options?: RequestOptions) =>
|
||||
request<T>("POST", path, body, options),
|
||||
put: <T>(path: string, body?: unknown, options?: RequestOptions) =>
|
||||
request<T>("PUT", path, body, options),
|
||||
delete: <T>(path: string, options?: RequestOptions) =>
|
||||
request<T>("DELETE", path, undefined, options),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
]
|
||||
"rewrites": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"destination": "/index.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import { lingui } from "@lingui/vite-plugin";
|
||||
const VENDOR_CHUNKS: Array<{ chunk: string; test: (id: string) => boolean }> = [
|
||||
{
|
||||
chunk: "vendor-antd",
|
||||
test: (id) =>
|
||||
id.includes("node_modules/antd") || id.includes("node_modules/rc-"),
|
||||
test: (id) => id.includes("node_modules/antd") || id.includes("node_modules/rc-"),
|
||||
},
|
||||
{
|
||||
chunk: "vendor-tanstack",
|
||||
@@ -19,8 +18,7 @@ const VENDOR_CHUNKS: Array<{ chunk: string; test: (id: string) => boolean }> = [
|
||||
},
|
||||
{
|
||||
chunk: "vendor-i18n",
|
||||
test: (id) =>
|
||||
id.includes("node_modules/@lingui/") || id.includes("/@lingui/"),
|
||||
test: (id) => id.includes("node_modules/@lingui/") || id.includes("/@lingui/"),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+2
-1
@@ -7,7 +7,8 @@
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"lint": "turbo run lint",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||
"setup:git-hooks": "git config core.hooksPath .githooks && git config core.hooksPath"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0",
|
||||
|
||||
Reference in New Issue
Block a user