@bovinphang/frontend-craft 2.5.0
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/.claude-plugin/marketplace.json +61 -0
- package/.claude-plugin/plugin.json +32 -0
- package/.mcp.json +44 -0
- package/CHANGELOG.md +229 -0
- package/CHANGELOG.zh-CN.md +228 -0
- package/CODE_OF_CONDUCT.md +34 -0
- package/CODE_OF_CONDUCT.zh-CN.md +34 -0
- package/CONTRIBUTING.md +356 -0
- package/CONTRIBUTING.zh-CN.md +356 -0
- package/LICENSE +21 -0
- package/README.md +467 -0
- package/README.openclaw.md +262 -0
- package/README.openclaw.zh-CN.md +262 -0
- package/README.zh-CN.md +467 -0
- package/SECURITY.md +44 -0
- package/SECURITY.zh-CN.md +44 -0
- package/agents/fec-architect.md +125 -0
- package/agents/fec-code-reviewer.md +152 -0
- package/agents/fec-debugger.md +44 -0
- package/agents/fec-design-token-mapper.md +75 -0
- package/agents/fec-doc-updater.md +30 -0
- package/agents/fec-e2e-runner.md +107 -0
- package/agents/fec-figma-implementer.md +104 -0
- package/agents/fec-performance-optimizer.md +293 -0
- package/agents/fec-refactor-cleaner.md +32 -0
- package/agents/fec-security-reviewer.md +116 -0
- package/agents/fec-test-planner.md +50 -0
- package/agents/fec-typescript-reviewer.md +142 -0
- package/agents/fec-ui-checker.md +120 -0
- package/commands/fec-debug.md +30 -0
- package/commands/fec-doc-sync.md +18 -0
- package/commands/fec-init.md +70 -0
- package/commands/fec-plan.md +94 -0
- package/commands/fec-refactor-clean.md +19 -0
- package/commands/fec-review.md +66 -0
- package/commands/fec-scaffold.md +205 -0
- package/commands/fec-tdd.md +21 -0
- package/dist/bin/frontend-craft.js +82 -0
- package/dist/hooks/fec-cleanup-claude-cache.js +4 -0
- package/dist/hooks/fec-format-changed-file.js +1 -0
- package/dist/hooks/fec-notify.js +1 -0
- package/dist/hooks/fec-run-tests.js +1 -0
- package/dist/hooks/fec-security-check.js +2 -0
- package/dist/hooks/fec-session-start.js +1 -0
- package/docs/example-prompts.md +124 -0
- package/docs/ja-JP/README.md +467 -0
- package/docs/ko-KR/README.md +467 -0
- package/docs/project-structure.md +119 -0
- package/docs/runtimes/antigravity.md +10 -0
- package/docs/runtimes/augment.md +10 -0
- package/docs/runtimes/claude.md +171 -0
- package/docs/runtimes/claude.zh-CN.md +169 -0
- package/docs/runtimes/cline.md +10 -0
- package/docs/runtimes/codebuddy.md +10 -0
- package/docs/runtimes/codex.md +10 -0
- package/docs/runtimes/copilot.md +10 -0
- package/docs/runtimes/cursor.md +10 -0
- package/docs/runtimes/gemini.md +10 -0
- package/docs/runtimes/kilo.md +10 -0
- package/docs/runtimes/openclaw.md +10 -0
- package/docs/runtimes/opencode.md +10 -0
- package/docs/runtimes/qoder.md +19 -0
- package/docs/runtimes/trae.md +10 -0
- package/docs/runtimes/windsurf.md +10 -0
- package/docs/superpowers/plans/2026-05-29-fec-debug.md +491 -0
- package/docs/superpowers/specs/2026-05-29-fec-debug-design.md +340 -0
- package/docs/zh-CN/example-prompts.md +124 -0
- package/docs/zh-CN/project-structure.md +119 -0
- package/docs/zh-TW/README.md +467 -0
- package/hooks/hooks.json +71 -0
- package/openclaw.plugin.json +26 -0
- package/package.json +101 -0
- package/skills/TEMPLATE.md +155 -0
- package/skills/eval_queries.json +890 -0
- package/skills/fec-accessibility-check/SKILL.md +39 -0
- package/skills/fec-accessibility-check/references/report-template.md +23 -0
- package/skills/fec-accessibility-check/references/screen-reader-testing.md +33 -0
- package/skills/fec-api-integration/SKILL.md +78 -0
- package/skills/fec-api-integration/references/integration-patterns.md +80 -0
- package/skills/fec-browser-storage/SKILL.md +34 -0
- package/skills/fec-browser-storage/references/storage-patterns.md +65 -0
- package/skills/fec-canvas-threejs/SKILL.md +66 -0
- package/skills/fec-canvas-threejs/references/rendering-patterns.md +67 -0
- package/skills/fec-canvas-threejs/references/shader-webgl-patterns.md +67 -0
- package/skills/fec-code-review/SKILL.md +135 -0
- package/skills/fec-code-review/references/report-template.md +29 -0
- package/skills/fec-component-testing/SKILL.md +119 -0
- package/skills/fec-data-fetching/SKILL.md +62 -0
- package/skills/fec-data-fetching/references/query-patterns.md +77 -0
- package/skills/fec-debug-framework/SKILL.md +162 -0
- package/skills/fec-dependency-upgrade/SKILL.md +34 -0
- package/skills/fec-dependency-upgrade/references/dependency-upgrade-workflow.md +56 -0
- package/skills/fec-doc-sync/SKILL.md +37 -0
- package/skills/fec-e2e-testing/SKILL.md +40 -0
- package/skills/fec-e2e-testing/references/e2e-advanced.md +176 -0
- package/skills/fec-e2e-testing/references/playwright-patterns.md +95 -0
- package/skills/fec-form-handling/SKILL.md +76 -0
- package/skills/fec-form-handling/references/advanced-form-patterns.md +72 -0
- package/skills/fec-implement-from-design/SKILL.md +122 -0
- package/skills/fec-implement-from-design/references/design-plan-template.md +36 -0
- package/skills/fec-legacy-to-modern-migration/SKILL.md +180 -0
- package/skills/fec-legacy-to-modern-migration/references/migration-report-template.md +41 -0
- package/skills/fec-legacy-web-standard/SKILL.md +35 -0
- package/skills/fec-legacy-web-standard/references/legacy-patterns.md +85 -0
- package/skills/fec-list-virtualization/SKILL.md +34 -0
- package/skills/fec-list-virtualization/references/virtualization-patterns.md +52 -0
- package/skills/fec-monorepo-project-standard/SKILL.md +142 -0
- package/skills/fec-motion-interaction/SKILL.md +66 -0
- package/skills/fec-motion-interaction/references/motion-patterns.md +112 -0
- package/skills/fec-nextjs-project-standard/SKILL.md +117 -0
- package/skills/fec-nuxt-project-standard/SKILL.md +122 -0
- package/skills/fec-performance-optimization/SKILL.md +72 -0
- package/skills/fec-pwa-implementation/SKILL.md +36 -0
- package/skills/fec-pwa-implementation/references/manifest-and-icons.md +53 -0
- package/skills/fec-pwa-implementation/references/service-worker-workbox.md +68 -0
- package/skills/fec-react-project-standard/SKILL.md +83 -0
- package/skills/fec-react-project-standard/references/react-project-details.md +642 -0
- package/skills/fec-refactor-clean/SKILL.md +48 -0
- package/skills/fec-responsive-layout/SKILL.md +34 -0
- package/skills/fec-responsive-layout/references/responsive-layout-patterns.md +59 -0
- package/skills/fec-route-protection/SKILL.md +135 -0
- package/skills/fec-security-review/SKILL.md +38 -0
- package/skills/fec-security-review/references/report-template.md +27 -0
- package/skills/fec-security-review/references/security-checklist.md +64 -0
- package/skills/fec-source-driven-development/SKILL.md +61 -0
- package/skills/fec-state-management/SKILL.md +140 -0
- package/skills/fec-state-management/references/state-patterns.md +109 -0
- package/skills/fec-storybook-component-doc/SKILL.md +37 -0
- package/skills/fec-storybook-component-doc/references/story-patterns.md +118 -0
- package/skills/fec-svg-animation/SKILL.md +148 -0
- package/skills/fec-tailwind-design-system/SKILL.md +34 -0
- package/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +55 -0
- package/skills/fec-tdd-workflow/SKILL.md +52 -0
- package/skills/fec-testing-strategy/SKILL.md +45 -0
- package/skills/fec-typescript-type-safety/SKILL.md +157 -0
- package/skills/fec-typescript-type-safety/references/type-safety-patterns.md +102 -0
- package/skills/fec-ui-design/SKILL.md +90 -0
- package/skills/fec-ui-design/data/product-rules.json +110 -0
- package/skills/fec-ui-design/data/stack-ui-rules.json +14 -0
- package/skills/fec-ui-design/data/style-archetypes.json +74 -0
- package/skills/fec-ui-design/data/ux-quality-rules.json +56 -0
- package/skills/fec-ui-design/references/design-intelligence.md +37 -0
- package/skills/fec-ui-design/references/master-page-overrides.md +30 -0
- package/skills/fec-ui-design/references/pre-delivery-checklist.md +33 -0
- package/skills/fec-ui-design/scripts/design-system.mjs +530 -0
- package/skills/fec-validation-fix/SKILL.md +48 -0
- package/skills/fec-vite-project-standard/SKILL.md +83 -0
- package/skills/fec-vue3-project-standard/SKILL.md +49 -0
- package/skills/fec-vue3-project-standard/references/vue3-project-details.md +547 -0
- package/skills/fec-web-workers/SKILL.md +36 -0
- package/skills/fec-web-workers/references/advanced-workers.md +54 -0
- package/skills/fec-web-workers/references/basic-worker.md +67 -0
- package/skills/metadata.json +1422 -0
- package/skills/relations.json +644 -0
- package/templates/claude/CLAUDE.md +106 -0
- package/templates/claude/settings.json +62 -0
- package/templates/codex/AGENTS.md +105 -0
- package/templates/codex/config.toml +32 -0
- package/templates/openclaw/AGENTS.md +101 -0
- package/templates/openclaw/OPENCLAW-CONFIG.md +12 -0
- package/templates/shared/rules/fec-agent-workflow.md +54 -0
- package/templates/shared/rules/fec-api-layer.md +99 -0
- package/templates/shared/rules/fec-ci-cd.md +128 -0
- package/templates/shared/rules/fec-code-comments.md +33 -0
- package/templates/shared/rules/fec-design-system.md +248 -0
- package/templates/shared/rules/fec-error-handling.md +200 -0
- package/templates/shared/rules/fec-git-conventions.md +73 -0
- package/templates/shared/rules/fec-i18n.md +112 -0
- package/templates/shared/rules/fec-naming-conventions.md +120 -0
- package/templates/shared/rules/fec-performance.md +101 -0
- package/templates/shared/rules/fec-react.md +82 -0
- package/templates/shared/rules/fec-refactoring.md +43 -0
- package/templates/shared/rules/fec-rendering-patterns.md +183 -0
- package/templates/shared/rules/fec-responsive-design.md +227 -0
- package/templates/shared/rules/fec-source-driven-development.md +31 -0
- package/templates/shared/rules/fec-state-management.md +163 -0
- package/templates/shared/rules/fec-testing.md +98 -0
- package/templates/shared/rules/fec-typescript.md +355 -0
- package/templates/shared/rules/fec-vue.md +64 -0
- package/templates/shared/rules/fec-working-modes.md +50 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-responsive-layout
|
|
3
|
+
description: Use when designing, implementing, or reviewing responsive frontend layouts, mobile-first breakpoints, container queries, fluid grids, data-dense tables, touch targets, safe areas, orientation changes, viewport overflow, or cross-device UI behavior; Chinese triggers include 响应式布局, 移动端适配, 断点, 容器查询, 横竖屏, 触摸目标.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 响应式布局
|
|
7
|
+
|
|
8
|
+
适用于需要让页面、工具界面、表格、仪表盘或组件在多设备上稳定工作的前端任务。需要布局模式和检查清单时加载 [references/responsive-layout-patterns.md](references/responsive-layout-patterns.md)。
|
|
9
|
+
|
|
10
|
+
## Purpose
|
|
11
|
+
|
|
12
|
+
用内容优先、移动优先和容器感知的方式设计响应式布局,避免只靠固定断点修补溢出。
|
|
13
|
+
|
|
14
|
+
## Procedure
|
|
15
|
+
|
|
16
|
+
1. 明确信息优先级:小屏先保留核心任务、关键操作和反馈;次要信息折叠、延后或移入详情层。
|
|
17
|
+
2. 选择布局策略:简单页面用 mobile-first 断点;嵌套组件、卡片网格和侧栏优先考虑 container query。
|
|
18
|
+
3. 设计流式尺寸:用 `minmax()`、`clamp()`、`aspect-ratio`、`max-width`、`min-width: 0` 和稳定网格轨道控制伸缩。
|
|
19
|
+
4. 处理数据密集界面:表格、列表、看板和编辑器需要明确横向滚动、列优先级、冻结列或移动端摘要视图。
|
|
20
|
+
5. 保证触摸与键盘:触摸目标、焦点路径、hover 替代、虚拟键盘、安全区域和横竖屏切换都要复核。
|
|
21
|
+
6. 关联性能与资源:移动端减少首屏大图、重型图表、同步动画和不必要列渲染。
|
|
22
|
+
7. 验证断点之间:检查 375px、768px、1024px、1440px,以及断点中间值是否溢出、遮挡或跳变。
|
|
23
|
+
|
|
24
|
+
## Constraints
|
|
25
|
+
|
|
26
|
+
- 不把桌面版缩小当作移动端设计。
|
|
27
|
+
- 不让关键功能只依赖 hover 或宽屏侧栏。
|
|
28
|
+
- 不用固定高度掩盖内容变化;需要固定格式时用稳定约束和可滚动区域。
|
|
29
|
+
- 不用 viewport 字体缩放替代真实排版层级。
|
|
30
|
+
- 不让移动端横向溢出成为默认解决方案;只有数据表、画布或编辑器等明确场景可以局部横滚。
|
|
31
|
+
|
|
32
|
+
## Expected Output
|
|
33
|
+
|
|
34
|
+
输出响应式信息优先级、断点或容器策略、关键组件布局模式、触摸/键盘要求、数据密集区域处理方案和验证结果。完成后页面在常见视口和输入方式下不溢出、不遮挡、可操作。
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 响应式布局模式
|
|
2
|
+
|
|
3
|
+
## 策略选择
|
|
4
|
+
|
|
5
|
+
| 场景 | 策略 |
|
|
6
|
+
| ---- | ---- |
|
|
7
|
+
| 页面级结构 | 移动优先断点 |
|
|
8
|
+
| 会被放进未知父容器的复用组件 | 容器查询 |
|
|
9
|
+
| 卡片网格 | `repeat(auto-fit, minmax())`,并设置合理最小宽度 |
|
|
10
|
+
| 数据表格 | 列优先级、横向滚动或移动端摘要行 |
|
|
11
|
+
| 编辑器 / 画布 / dashboard | 稳定外壳 + 可滚动工作区 |
|
|
12
|
+
|
|
13
|
+
## CSS 模式
|
|
14
|
+
|
|
15
|
+
```css
|
|
16
|
+
.card-grid {
|
|
17
|
+
display: grid;
|
|
18
|
+
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
|
|
19
|
+
gap: 1rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.panel {
|
|
23
|
+
min-width: 0;
|
|
24
|
+
container-type: inline-size;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@container (min-width: 42rem) {
|
|
28
|
+
.panel-body {
|
|
29
|
+
display: grid;
|
|
30
|
+
grid-template-columns: 16rem minmax(0, 1fr);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
当 grid/flex 子项包含长文本、表格、代码、图表或媒体时,给子项设置 `min-width: 0`。
|
|
36
|
+
|
|
37
|
+
## 数据密集型界面
|
|
38
|
+
|
|
39
|
+
- 编码前先确定移动端列优先级。
|
|
40
|
+
- 主标识、状态和主要操作保持可见。
|
|
41
|
+
- 低优先级元数据移入详情行、抽屉或可展开面板。
|
|
42
|
+
- 小屏仍要保留排序、筛选、分页和选择能力。
|
|
43
|
+
- 虚拟滚动只解决 DOM 数量问题,不能解决信息层级问题。
|
|
44
|
+
|
|
45
|
+
## 触控与视口检查
|
|
46
|
+
|
|
47
|
+
- 空间允许时,交互目标至少 44x44px。
|
|
48
|
+
- 移动端输入框字号避免低于 16px。
|
|
49
|
+
- 固定底部栏和操作条必须考虑 `env(safe-area-inset-bottom)`。
|
|
50
|
+
- 对话框和抽屉需要适配虚拟键盘导致的视口变化。
|
|
51
|
+
- 只在 hover 时出现的内容必须提供点击、焦点或常驻可见替代方案。
|
|
52
|
+
|
|
53
|
+
## 验证清单
|
|
54
|
+
|
|
55
|
+
- 320-375px:页面级没有横向溢出。
|
|
56
|
+
- 768px:布局切换后阅读顺序仍然合理。
|
|
57
|
+
- 1024-1440px:内容宽度有约束,扫描路径清晰。
|
|
58
|
+
- 长标签、翻译文本、数字以及 empty/error/loading 状态不会撑坏固定格式控件。
|
|
59
|
+
- 横竖屏切换不会困住焦点或隐藏主操作。
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-route-protection
|
|
3
|
+
description: Use when implementing or reviewing frontend route protection, auth guards, RBAC, permission routes, login state handling, redirects, middleware, React Router, Next.js, Vue Router, or Nuxt route middleware; Chinese triggers include 路由保护, 权限路由, 登录态.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 路由保护
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
为前端应用建立清晰的认证、授权和重定向边界,避免越权访问与闪烁渲染。
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- 页面需要登录后访问,或不同角色看到不同路由。
|
|
15
|
+
- 需要实现 React Router、Next.js、Nuxt 或 Vue Router 的路由守卫。
|
|
16
|
+
- 登录过期、权限不足、组织/租户切换需要统一处理。
|
|
17
|
+
- 不用于替代服务端授权;前端路由保护只能改善体验,不能作为唯一安全边界。
|
|
18
|
+
|
|
19
|
+
## Procedure
|
|
20
|
+
|
|
21
|
+
### 1. 定义认证与授权状态
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export type AuthStatus = "loading" | "anonymous" | "authenticated";
|
|
25
|
+
|
|
26
|
+
export interface CurrentUser {
|
|
27
|
+
id: string;
|
|
28
|
+
roles: string[];
|
|
29
|
+
permissions: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function canAccess(user: CurrentUser, required: string[]) {
|
|
33
|
+
return required.every((permission) => user.permissions.includes(permission));
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. React Router 使用布局守卫
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
|
41
|
+
|
|
42
|
+
interface ProtectedRouteProps {
|
|
43
|
+
requiredPermissions?: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function ProtectedRoute({ requiredPermissions = [] }: ProtectedRouteProps) {
|
|
47
|
+
const location = useLocation();
|
|
48
|
+
const { status, user } = useAuth();
|
|
49
|
+
|
|
50
|
+
if (status === "loading") return <RouteLoading />;
|
|
51
|
+
if (status === "anonymous") {
|
|
52
|
+
return <Navigate to="/login" replace state={{ from: location }} />;
|
|
53
|
+
}
|
|
54
|
+
if (requiredPermissions.length > 0 && !canAccess(user, requiredPermissions)) {
|
|
55
|
+
return <Navigate to="/403" replace />;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return <Outlet />;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
const router = createBrowserRouter([
|
|
64
|
+
{
|
|
65
|
+
element: <ProtectedRoute requiredPermissions={["orders:read"]} />,
|
|
66
|
+
children: [{ path: "/orders", element: <OrdersPage /> }],
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Next.js 优先在服务端边界处理
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
// middleware.ts
|
|
75
|
+
import { NextResponse, type NextRequest } from "next/server";
|
|
76
|
+
|
|
77
|
+
export function middleware(request: NextRequest) {
|
|
78
|
+
const token = request.cookies.get("session")?.value;
|
|
79
|
+
const isPrivateRoute = request.nextUrl.pathname.startsWith("/dashboard");
|
|
80
|
+
|
|
81
|
+
if (isPrivateRoute && !token) {
|
|
82
|
+
const loginUrl = new URL("/login", request.url);
|
|
83
|
+
loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
|
|
84
|
+
return NextResponse.redirect(loginUrl);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return NextResponse.next();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const config = {
|
|
91
|
+
matcher: ["/dashboard/:path*"],
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
对需要精细权限的 App Router 页面,在 server component 或 server action 中重新校验权限,不依赖客户端状态。
|
|
96
|
+
|
|
97
|
+
### 4. Vue Router / Nuxt 使用导航守卫
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
router.beforeEach(async (to) => {
|
|
101
|
+
const auth = useAuthStore();
|
|
102
|
+
if (auth.status === "unknown") await auth.fetchCurrentUser();
|
|
103
|
+
|
|
104
|
+
if (to.meta.requiresAuth && !auth.user) {
|
|
105
|
+
return { path: "/login", query: { redirect: to.fullPath } };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const required = to.meta.permissions as string[] | undefined;
|
|
109
|
+
if (required?.length && !auth.canAccess(required)) {
|
|
110
|
+
return { path: "/403" };
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 5. 统一失败与跳转体验
|
|
116
|
+
|
|
117
|
+
- `401`:清理过期会话,跳转登录页并保留 `redirect`。
|
|
118
|
+
- `403`:进入无权限页,不反复重试。
|
|
119
|
+
- `loading`:渲染稳定骨架屏,避免先显示私有内容再跳转。
|
|
120
|
+
- 登录成功:只跳转到同源且允许的路径,防止 open redirect。
|
|
121
|
+
- RBAC:权限矩阵来自可信会话或后端返回;前端只用于路由体验和 UI 裁剪,不能替代接口授权。
|
|
122
|
+
- 会话刷新:401 刷新失败后统一清理状态;避免多个并发请求各自触发跳转或刷新风暴。
|
|
123
|
+
|
|
124
|
+
## Constraints
|
|
125
|
+
|
|
126
|
+
- 前端路由保护不是安全边界;API、SSR loader、server action 必须重复做授权校验。
|
|
127
|
+
- 不要在组件渲染后用 `useEffect` 才跳转私有页面,否则会出现敏感内容闪烁。
|
|
128
|
+
- redirect 参数必须限制为站内路径,不能直接信任 URL 输入。
|
|
129
|
+
- 权限信息应来自可信会话或后端响应,不要只依赖 localStorage 中的角色字段。
|
|
130
|
+
- 多租户应用必须把 tenant/org 上下文纳入权限判断。
|
|
131
|
+
- 不把菜单隐藏视为授权完成;用户直接访问 URL、刷新页面、篡改 localStorage 和替换 tenant 都必须验证。
|
|
132
|
+
|
|
133
|
+
## Expected Output
|
|
134
|
+
|
|
135
|
+
产出一套路由守卫实现,覆盖 loading、未登录、权限不足、登录后回跳和会话过期。验证时直接访问私有 URL、刷新页面、切换角色、篡改 redirect 参数,确认行为稳定且 API 仍有服务端授权。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-security-review
|
|
3
|
+
description: Use when reviewing frontend security risks such as XSS, CSRF, sensitive data exposure, unsafe DOM APIs, untrusted user input, authentication/token handling, payment flows, file upload, CSP, dependency risk, or third-party scripts; Chinese triggers include 安全审查, 安全检查.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 前端安全审查
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
识别前端代码中的客户端安全风险,并给出可执行修复建议。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. 先确认审查面:用户输入、动态 HTML、URL 跳转、认证态、RBAC、文件上传、支付/删除等敏感操作、第三方脚本和依赖。
|
|
15
|
+
2. 搜索高危模式:`dangerouslySetInnerHTML`、`v-html`、`innerHTML`、`document.write`、动态 script、未校验 redirect、明文 token。
|
|
16
|
+
3. 按风险类型审查:XSS、CSP、敏感数据、CSRF、依赖、输入校验、文件上传、开放重定向、认证授权和第三方脚本。
|
|
17
|
+
4. 用边界模型判断责任:客户端只能改善体验和减少误用,鉴权、授权、上传信任和敏感操作必须由服务端最终裁决。
|
|
18
|
+
5. 高危问题标记为阻塞合并;前端校验只能改善体验,不能作为唯一安全边界。
|
|
19
|
+
6. 输出分级安全报告;报告格式见 [references/report-template.md](references/report-template.md)。
|
|
20
|
+
|
|
21
|
+
## Detailed References
|
|
22
|
+
|
|
23
|
+
- Load [references/security-checklist.md](references/security-checklist.md) for XSS, CSP, sensitive data, CSRF, dependency, and input validation details.
|
|
24
|
+
- Load [references/report-template.md](references/report-template.md) when writing the security review report.
|
|
25
|
+
|
|
26
|
+
## Constraints
|
|
27
|
+
|
|
28
|
+
- 不要为了方便开发而绕过安全机制。
|
|
29
|
+
- 不要依赖前端校验作为唯一安全防线。
|
|
30
|
+
- 不要信任任何来自客户端的数据。
|
|
31
|
+
- 发现高危问题时必须标记为阻塞合并。
|
|
32
|
+
- 与通用代码质量 review 分工:本 skill 关注威胁、攻击面和数据泄露。
|
|
33
|
+
- 不把依赖审计结果机械等同为可利用漏洞;需要结合运行路径、暴露面和修复成本判断。
|
|
34
|
+
- 不把隐藏按钮、前端路由守卫或本地角色字段当作授权边界;API、SSR loader、server action 和敏感操作必须有服务端裁决。
|
|
35
|
+
|
|
36
|
+
## Expected Output
|
|
37
|
+
|
|
38
|
+
输出 CRITICAL/HIGH/MEDIUM/LOW 分级安全审查报告,每个问题关联具体文件和行号,给出修复建议;报告保存为 `reports/security-review-YYYY-MM-DD-HHmmss.md`。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 安全审查报告模板
|
|
2
|
+
|
|
3
|
+
```markdown
|
|
4
|
+
# 安全审查报告
|
|
5
|
+
|
|
6
|
+
> 生成时间: YYYY-MM-DD HH:mm
|
|
7
|
+
> 评审工具: frontend-craft
|
|
8
|
+
|
|
9
|
+
## CRITICAL / 高危 (N项)
|
|
10
|
+
- **[文件:行号]** 风险描述 -> 修复建议
|
|
11
|
+
|
|
12
|
+
## HIGH / 中高危 (N项)
|
|
13
|
+
- ...
|
|
14
|
+
|
|
15
|
+
## MEDIUM / 中危 (N项)
|
|
16
|
+
- ...
|
|
17
|
+
|
|
18
|
+
## LOW / 低危或建议 (N项)
|
|
19
|
+
- ...
|
|
20
|
+
|
|
21
|
+
## 已通过的安全检查
|
|
22
|
+
- ...
|
|
23
|
+
|
|
24
|
+
**整体安全等级**: 安全 / 存在风险 / 高危需修复
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
审查完成后,将报告保存到 `reports/security-review-YYYY-MM-DD-HHmmss.md`,并告知用户报告路径。
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# 前端安全审查清单
|
|
2
|
+
|
|
3
|
+
## XSS
|
|
4
|
+
|
|
5
|
+
- `dangerouslySetInnerHTML` 和 `v-html` 必须有明确理由和输入净化。
|
|
6
|
+
- 用户输入不得直接插入 DOM、`innerHTML`、`document.write`。
|
|
7
|
+
- URL 参数不得直接用于页面渲染。
|
|
8
|
+
- 动态生成的 `<script>` 标签必须审查来源。
|
|
9
|
+
- 富文本使用 DOMPurify 等库,并配置标签、属性、协议白名单。
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import DOMPurify from "dompurify";
|
|
13
|
+
|
|
14
|
+
const clean = DOMPurify.sanitize(dirtyHtml, {
|
|
15
|
+
ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "ul", "ol", "li", "p", "br"],
|
|
16
|
+
ALLOWED_ATTR: ["href", "title"],
|
|
17
|
+
ALLOWED_URI_REGEXP: /^(https?|mailto):/i,
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 安全重定向
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
function safeRedirect(url: string): string {
|
|
25
|
+
if (url.startsWith("/") && !url.startsWith("//")) return url;
|
|
26
|
+
return "/dashboard";
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## CSP
|
|
31
|
+
|
|
32
|
+
- 推荐先用 `Content-Security-Policy-Report-Only` 验证。
|
|
33
|
+
- `default-src 'self'`、`object-src 'none'`、`frame-ancestors 'none'`、`base-uri 'self'` 是常见底线。
|
|
34
|
+
- 避免 `'unsafe-eval'`;内联脚本优先 nonce。
|
|
35
|
+
|
|
36
|
+
```http
|
|
37
|
+
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 敏感数据
|
|
41
|
+
|
|
42
|
+
- 禁止前端硬编码 API Key、Secret、密码。
|
|
43
|
+
- 禁止 localStorage/sessionStorage/IndexedDB 存明文 token、密码、信用卡信息。
|
|
44
|
+
- 禁止 URL query 参数传 token 或密码。
|
|
45
|
+
- 禁止 console.log 或错误上报携带隐私数据。
|
|
46
|
+
- Token 优先 httpOnly + Secure + SameSite cookie。
|
|
47
|
+
|
|
48
|
+
## CSRF
|
|
49
|
+
|
|
50
|
+
- 变更操作必须携带 CSRF token 或使用同等后端防护。
|
|
51
|
+
- 关键操作不要使用 GET。
|
|
52
|
+
- 检查后端是否校验 `Origin` / `Referer`。
|
|
53
|
+
|
|
54
|
+
## 依赖与第三方脚本
|
|
55
|
+
|
|
56
|
+
- 定期审查依赖安全公告。
|
|
57
|
+
- 禁止从非官方 CDN 加载脚本,除非有 SRI 和来源审查。
|
|
58
|
+
- 动态加载第三方脚本必须有业务必要性和降级策略。
|
|
59
|
+
|
|
60
|
+
## 输入校验与文件上传
|
|
61
|
+
|
|
62
|
+
- 前端校验不是安全边界,后端必须二次校验。
|
|
63
|
+
- 文件上传校验 MIME、大小、扩展名和内容;不能只看扩展名。
|
|
64
|
+
- 正则校验注意 ReDoS 风险。
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-source-driven-development
|
|
3
|
+
description: Use when frontend decisions depend on framework, library, browser, runtime, package, API, or platform behavior that may have changed, including official documentation checks, version-sensitive patterns, migration choices, or public interface assumptions; Chinese triggers include 查官方文档, 以源码为准, source-driven, 版本差异.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 来源驱动开发
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
在前端实现、评审和架构决策中优先依据项目内事实、官方文档和当前版本行为,避免沿用过时经验或未经验证的假设。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. 判断是否需要查源
|
|
15
|
+
- 涉及框架新版本、路由、渲染模式、缓存、构建工具、浏览器 API、包发布格式、鉴权协议或公共接口时必须查源。
|
|
16
|
+
- 涉及项目约定时先读本仓库 README、配置、模板、已有实现、测试和迁移记录。
|
|
17
|
+
- 只是稳定语言语法或明显局部重构时,可使用项目现有模式作为事实来源。
|
|
18
|
+
|
|
19
|
+
2. 收集权威材料
|
|
20
|
+
- 首选官方文档、规范、源码、release notes、migration guide、类型定义和项目内测试。
|
|
21
|
+
- 第三方文章只能作为线索,不能作为最终依据。
|
|
22
|
+
- 对快速变化的库记录包名、版本、文档日期或页面标题。
|
|
23
|
+
|
|
24
|
+
3. 提炼决策
|
|
25
|
+
- 写清当前版本推荐做法、弃用做法、限制条件和迁移注意事项。
|
|
26
|
+
- 对多个可行方案说明选择理由、兼容性、维护成本和回退方式。
|
|
27
|
+
- 若文档和项目现状冲突,优先解释冲突并给出渐进调整,不直接推翻已运行系统。
|
|
28
|
+
- 对依赖升级、框架迁移和公共 API 变化,记录 release notes、migration guide、版本范围、破坏性变更和回滚方案。
|
|
29
|
+
|
|
30
|
+
4. 落到实现
|
|
31
|
+
- 将外部行为隔离在适配层、配置层或小型工具函数中,避免在组件里散落版本假设。
|
|
32
|
+
- 公共接口变化要保持命名、错误形状、默认值和兼容策略清晰。
|
|
33
|
+
- 代码注释只记录非显而易见的来源约束,不粘贴大段文档。
|
|
34
|
+
|
|
35
|
+
5. 验证假设
|
|
36
|
+
- 用类型检查、最小复现、单元测试、组件测试、E2E、构建或浏览器验证证明关键假设。
|
|
37
|
+
- 如果无法验证,报告不确定性、风险范围和需要用户或团队确认的信息。
|
|
38
|
+
6. 留下轻量决策记录
|
|
39
|
+
- 当结论会影响公共接口、依赖版本、目录规范或团队工作流时,补充 ADR/变更记录或交给文档同步流程。
|
|
40
|
+
|
|
41
|
+
## Source Priority
|
|
42
|
+
|
|
43
|
+
| Priority | Source | Use |
|
|
44
|
+
| -------- | ------ | --- |
|
|
45
|
+
| 1 | 项目内配置、测试、已有实现 | 判断本仓库真实约定 |
|
|
46
|
+
| 2 | 官方文档、源码、类型定义 | 判断当前推荐行为 |
|
|
47
|
+
| 3 | 标准规范、浏览器兼容数据 | 判断平台能力和兼容边界 |
|
|
48
|
+
| 4 | Issue、讨论、第三方文章 | 辅助排查,不能单独定案 |
|
|
49
|
+
|
|
50
|
+
## Constraints
|
|
51
|
+
|
|
52
|
+
- 不凭记忆给版本敏感结论。
|
|
53
|
+
- 不把过时教程当作当前最佳实践。
|
|
54
|
+
- 不引用无法复核的来源作为关键依据。
|
|
55
|
+
- 不为追新而改动稳定项目约定;必须说明收益和迁移成本。
|
|
56
|
+
- 不在交付中复制长篇外部文档;只保留必要结论和链接或文件路径。
|
|
57
|
+
- 不把“查过文档”停留在口头;版本敏感决策需要能追溯到具体版本、路径或官方页面。
|
|
58
|
+
|
|
59
|
+
## Expected Output
|
|
60
|
+
|
|
61
|
+
输出来源清单、版本或项目事实、决策结论、实现影响、验证命令和未决风险。若修改代码,关键行为应有测试或构建验证支撑。
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-state-management
|
|
3
|
+
description: Use when choosing, implementing, reviewing, or refactoring frontend state ownership across React, Vue, Next.js, Nuxt, URL state, server state, form state, browser persistence, or global stores. Prefer narrower skills for TanStack Query cache details, browser storage persistence, or form validation internals; Chinese triggers include 状态管理, 状态归属, store 选型.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 前端状态管理
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
为前端状态确定清晰归属,避免全局 store 膨胀、重复缓存和派生状态同步错误。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
### 1. 先分类状态来源
|
|
15
|
+
|
|
16
|
+
不要先选 Redux、Zustand、Pinia 或 Context。先把每个状态标到唯一归属,再决定工具。
|
|
17
|
+
|
|
18
|
+
| 状态类型 | 典型例子 | 默认归属 |
|
|
19
|
+
| ---------------- | ------------------------------------ | ------------------------ |
|
|
20
|
+
| 本地 UI 状态 | 弹窗开关、tab、展开行、hover 编辑态 | 组件内 state / ref |
|
|
21
|
+
| 表单状态 | 输入值、脏字段、校验错误、提交中 | 表单库或表单组件 |
|
|
22
|
+
| 服务端状态 | 列表、详情、分页结果、远程错误 | 请求缓存库 |
|
|
23
|
+
| URL 状态 | 搜索词、筛选、排序、页码、选中 tab | 路由参数 / search params |
|
|
24
|
+
| 全局客户端状态 | 登录用户、主题、权限快照、购物车草稿 | 全局 store |
|
|
25
|
+
| 浏览器持久化状态 | 跨刷新保留的草稿、偏好、离线队列 | 存储层 + 状态适配器 |
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
type ReportsStateMap = {
|
|
29
|
+
search: "url";
|
|
30
|
+
selectedReportId: "url";
|
|
31
|
+
reports: "server-state-cache";
|
|
32
|
+
isFilterPanelOpen: "local-ui";
|
|
33
|
+
draftColumns: "browser-persistence";
|
|
34
|
+
};
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. 保持最小状态
|
|
38
|
+
|
|
39
|
+
可从 props、server state、URL 或已有 state 推导出来的值,不要另存一份。
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
interface Invoice {
|
|
43
|
+
id: string;
|
|
44
|
+
status: "draft" | "sent" | "paid";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function InvoiceList({ invoices }: { invoices: Invoice[] }) {
|
|
48
|
+
const paidInvoices = invoices.filter((invoice) => invoice.status === "paid");
|
|
49
|
+
|
|
50
|
+
return <span>{paidInvoices.length}</span>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. 选择 React 全局状态方案
|
|
55
|
+
|
|
56
|
+
React 中优先本地化和组合;只有跨页面、跨 feature 或需要统一动作时才引入全局 store。
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { create } from "zustand";
|
|
60
|
+
|
|
61
|
+
interface WorkspaceState {
|
|
62
|
+
activeWorkspaceId: string | null;
|
|
63
|
+
setActiveWorkspaceId: (workspaceId: string) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const useWorkspaceStore = create<WorkspaceState>((set) => ({
|
|
67
|
+
activeWorkspaceId: null,
|
|
68
|
+
setActiveWorkspaceId: (activeWorkspaceId) => set({ activeWorkspaceId }),
|
|
69
|
+
}));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
决策顺序:
|
|
73
|
+
|
|
74
|
+
1. 只在一个组件或一个小子树使用:`useState` / `useReducer`。
|
|
75
|
+
2. 低频全局配置或依赖注入:Context。
|
|
76
|
+
3. 中等复杂业务状态:Zustand 或 Jotai,按仓库现有选型优先。
|
|
77
|
+
4. 大型应用、严格动作流、审计或时间旅行调试:Redux Toolkit。
|
|
78
|
+
5. 远程数据:使用数据获取 skill 管理 query key、缓存和失效策略。
|
|
79
|
+
|
|
80
|
+
### 4. 选择 Vue 全局状态方案
|
|
81
|
+
|
|
82
|
+
Vue 3 中,局部跨层级传递使用 `provide/inject`;全局业务状态使用 Pinia 或项目既有 store。
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { computed, readonly, ref } from "vue";
|
|
86
|
+
import { defineStore } from "pinia";
|
|
87
|
+
|
|
88
|
+
export const useSessionStore = defineStore("session", () => {
|
|
89
|
+
const userId = ref<string | null>(null);
|
|
90
|
+
const isSignedIn = computed(() => userId.value !== null);
|
|
91
|
+
|
|
92
|
+
function signIn(nextUserId: string) {
|
|
93
|
+
userId.value = nextUserId;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
userId: readonly(userId),
|
|
98
|
+
isSignedIn,
|
|
99
|
+
signIn,
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 5. 明确状态边界和迁移步骤
|
|
105
|
+
|
|
106
|
+
重构状态时先做状态清单,再逐步移动读写入口。每一步都应保持行为可验证。
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
interface StateMigrationItem {
|
|
110
|
+
name: string;
|
|
111
|
+
currentOwner: "component" | "context" | "store" | "query-cache" | "url";
|
|
112
|
+
targetOwner: "component" | "context" | "store" | "query-cache" | "url";
|
|
113
|
+
verification: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const migrationPlan: StateMigrationItem[] = [
|
|
117
|
+
{
|
|
118
|
+
name: "dashboard filters",
|
|
119
|
+
currentOwner: "store",
|
|
120
|
+
targetOwner: "url",
|
|
121
|
+
verification: "refreshing the page preserves filters through search params",
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Detailed References
|
|
127
|
+
|
|
128
|
+
需要 Store 形状示例、选择器模式、URL 状态同步、持久化适配器、SSR 边界或审查清单时,加载 [references/state-patterns.md](references/state-patterns.md)。
|
|
129
|
+
|
|
130
|
+
## Constraints
|
|
131
|
+
|
|
132
|
+
- 不把服务端响应复制到全局 store;缓存、失效和重试归数据获取层。
|
|
133
|
+
- 不把表单每个字段提升到全局 store;跨步骤共享也优先由表单上下文或提交草稿适配器处理。
|
|
134
|
+
- 不用 Context 承载高频变化的大对象;会扩大重渲染范围。
|
|
135
|
+
- 持久化状态必须明确字段白名单、版本号、过期策略和敏感数据排除。
|
|
136
|
+
- SSR 场景不要在模块顶层创建带用户数据的单例 store。
|
|
137
|
+
|
|
138
|
+
## Expected Output
|
|
139
|
+
|
|
140
|
+
产出状态归属清单、选型理由、store/API 边界和验证步骤。实现时应保留 loading/error/empty、刷新、回退、跨路由和权限变化等关键行为。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# 状态管理模式
|
|
2
|
+
|
|
3
|
+
## 所有权审查清单
|
|
4
|
+
|
|
5
|
+
- 每个状态字段有唯一归属:本地状态、表单状态、服务端缓存、URL 状态、全局 Store 或浏览器持久化。
|
|
6
|
+
- 派生值由源状态计算得出,而不是通过 effect 或 watcher 手动同步。
|
|
7
|
+
- URL 状态用于可分享、刷新后需保留的视图状态,如筛选、搜索、排序和分页。
|
|
8
|
+
- 服务端数据保留在请求缓存中;全局 Store 仅存放选中的 ID、本地偏好或乐观更新提示。
|
|
9
|
+
- 持久化的客户端状态需有白名单、schema 版本号、迁移路径和敏感字段排除规则。
|
|
10
|
+
- Store 的 action 以领域事件命名,而非组件处理器命名。
|
|
11
|
+
|
|
12
|
+
## React Store 形状示例
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { create } from "zustand";
|
|
16
|
+
import { subscribeWithSelector } from "zustand/middleware";
|
|
17
|
+
|
|
18
|
+
interface CartLine {
|
|
19
|
+
sku: string;
|
|
20
|
+
quantity: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CartState {
|
|
24
|
+
lines: CartLine[];
|
|
25
|
+
addLine: (line: CartLine) => void;
|
|
26
|
+
removeLine: (sku: string) => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const useCartStore = create<CartState>()(
|
|
30
|
+
subscribeWithSelector((set) => ({
|
|
31
|
+
lines: [],
|
|
32
|
+
addLine: (line) =>
|
|
33
|
+
set((state) => ({
|
|
34
|
+
lines: [...state.lines.filter((item) => item.sku !== line.sku), line],
|
|
35
|
+
})),
|
|
36
|
+
removeLine: (sku) =>
|
|
37
|
+
set((state) => ({
|
|
38
|
+
lines: state.lines.filter((line) => line.sku !== sku),
|
|
39
|
+
})),
|
|
40
|
+
})),
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
选择器应尽可能返回稳定的原始值或经过 memo 的切片:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
const itemCount = useCartStore((state) =>
|
|
48
|
+
state.lines.reduce((total, line) => total + line.quantity, 0),
|
|
49
|
+
);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## URL 状态边界
|
|
53
|
+
|
|
54
|
+
当另一个用户应能打开相同视图,或刷新后需要保留视图时,使用 URL 状态。
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { useSearchParams } from "react-router-dom";
|
|
58
|
+
|
|
59
|
+
export function useReportFilters() {
|
|
60
|
+
const [params, setParams] = useSearchParams();
|
|
61
|
+
const status = params.get("status") ?? "open";
|
|
62
|
+
|
|
63
|
+
function setStatus(nextStatus: string) {
|
|
64
|
+
setParams((next) => {
|
|
65
|
+
next.set("status", nextStatus);
|
|
66
|
+
next.set("page", "1");
|
|
67
|
+
return next;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { status, setStatus };
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 持久化适配器边界
|
|
76
|
+
|
|
77
|
+
仅持久化稳定的客户端偏好设置或非敏感草稿。序列化逻辑放在组件外部。
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
interface PersistedGridState {
|
|
81
|
+
version: 1;
|
|
82
|
+
visibleColumns: string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const storageKey = "reports:grid";
|
|
86
|
+
|
|
87
|
+
export function readGridState(): PersistedGridState | null {
|
|
88
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
89
|
+
if (!raw) return null;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const parsed = JSON.parse(raw) as PersistedGridState;
|
|
93
|
+
return parsed.version === 1 ? parsed : null;
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function writeGridState(state: PersistedGridState): void {
|
|
100
|
+
window.localStorage.setItem(storageKey, JSON.stringify(state));
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## SSR 与请求隔离
|
|
105
|
+
|
|
106
|
+
- 当状态可能包含用户数据时,每次请求需创建独立的 Store。
|
|
107
|
+
- 服务端渲染期间不要读取 `window`、`localStorage` 或 `document`。
|
|
108
|
+
- 水合后再加载持久化偏好,并在水合前提供确定性默认值。
|
|
109
|
+
- 服务端缓存脱水与纯客户端全局 Store 分开处理。
|