create-dwy 0.5.1 → 0.6.1
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/bin/index.js +6 -15
- package/package.json +3 -3
- package/src/create.js +61 -37
- package/src/sync.js +206 -61
- package/templates/claude-global/CLAUDE.md +92 -0
- package/templates/claude-global/rules/dwy-android-core.md +610 -0
- package/templates/claude-global/rules/dwy-api-design.md +110 -0
- package/templates/claude-global/rules/dwy-db-migration.md +46 -0
- package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
- package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
- package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
- package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
- package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
- package/templates/claude-global/rules/dwy-git-security.md +53 -0
- package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
- package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
- package/templates/claude-global/rules/dwy-project-structure.md +181 -0
- package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +292 -358
- package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
- package/templates/claude-global/rules/dwy-python-orm.md +61 -0
- package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +99 -72
- package/templates/claude-global/rules/dwy-python-testing.md +53 -0
- package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
- package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
- package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
- package/templates/claude-global/rules/dwy-vue-api.md +146 -0
- package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +338 -152
- package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
- package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
- package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
- package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
- package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
- package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
- package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
- package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
- package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
- package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
- package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
- package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
- package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
- package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
- package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
- package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
- package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
- package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
- package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
- package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
- package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
- package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
- package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
- package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
- package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
- package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
- package/templates/claude-global/skills/{dwy-release → dwy-publish}/SKILL.md +42 -18
- package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
- package/templates/project/backend/.dwy.hbs +5 -0
- package/templates/project/backend/.python-version +1 -0
- package/templates/project/backend/CLAUDE.md.hbs +40 -0
- package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
- package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
- package/templates/project/backend/deploy.sh.hbs +18 -0
- package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
- package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
- package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
- package/templates/project/backend/src/app/database.py +10 -0
- package/templates/project/backend/src/app/dependencies.py +5 -0
- package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
- package/templates/project/backend/src/app/models/base.py +5 -0
- package/templates/project/web/CLAUDE.md.hbs +29 -36
- package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
- package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
- package/templates/project/web/apps/web/src/App.vue +9 -0
- package/templates/project/web/apps/web/src/api/client.ts +5 -0
- package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
- package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
- package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
- package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
- package/templates/project/web/apps/web/src/style.css +2 -0
- package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
- package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
- package/templates/project/web/apps/web/tsconfig.app.json +18 -0
- package/templates/project/web/apps/web/tsconfig.json +6 -0
- package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
- package/templates/project/web/package.json.hbs +16 -0
- package/templates/project/web/packages/.gitkeep +0 -0
- package/templates/project/web/pnpm-workspace.yaml +3 -0
- package/templates/claude-global/hooks/post-sync-claude-global.sh +0 -72
- package/templates/claude-global/rules/agent-team-execution.md +0 -138
- package/templates/claude-global/rules/agent-team-patterns.md +0 -157
- package/templates/claude-global/rules/git-commit-convention.md +0 -106
- package/templates/claude-global/rules/git-security.md +0 -144
- package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
- package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
- package/templates/project/web/.claude/commands/deploy.md +0 -4
- package/templates/project/web/backend/src/app/models/base.py +0 -5
- package/templates/project/web/deploy.sh.hbs +0 -113
- package/templates/project/web/frontend/src/App.vue +0 -3
- package/templates/project/web/frontend/src/api/client.ts +0 -13
- package/templates/project/web/frontend/src/style.css +0 -1
- package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
- package/templates/project/web/frontend/tsconfig.app.json +0 -14
- package/templates/project/web/frontend/tsconfig.json +0 -4
- /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
- /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
- /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
- /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
- /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
- /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
- /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
- /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
- /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 后端 API URL/响应格式/状态码/Schema 命名规范
|
|
3
|
+
paths:
|
|
4
|
+
- "**/routers/**/*.py"
|
|
5
|
+
- "**/schemas/**/*.py"
|
|
6
|
+
- "**/services/**/*.py"
|
|
7
|
+
- "**/dependencies.py"
|
|
8
|
+
- "**/main.py"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# API 设计规范
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 一、URL 命名
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
基础格式: /api/{resource}
|
|
19
|
+
|
|
20
|
+
GET /api/users # 列表(支持分页)
|
|
21
|
+
POST /api/users # 创建
|
|
22
|
+
GET /api/users/{id} # 详情
|
|
23
|
+
PATCH /api/users/{id} # 部分更新
|
|
24
|
+
DELETE /api/users/{id} # 删除
|
|
25
|
+
|
|
26
|
+
# 子资源
|
|
27
|
+
GET /api/users/{id}/orders # 用户的订单列表
|
|
28
|
+
|
|
29
|
+
# 动作(非 CRUD,用 POST + 动词)
|
|
30
|
+
POST /api/users/{id}/activate
|
|
31
|
+
POST /api/tasks/{id}/cancel
|
|
32
|
+
POST /api/tasks/{id}/retry
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**规则:**
|
|
36
|
+
- 资源名用复数、kebab-case:`/api/upload-tasks`(不是 `uploadTasks` 或 `upload_task`)
|
|
37
|
+
- 路径参数用 `{id}` 不用 `{user_id}`(除非有歧义)
|
|
38
|
+
- 查询参数用 snake_case:`?page_size=20&task_type=export`
|
|
39
|
+
- 所有路由加 `/api` 前缀
|
|
40
|
+
- 禁止在 URL 中包含动词(用 HTTP 方法区分),但非 CRUD 操作例外
|
|
41
|
+
|
|
42
|
+
## 二、统一响应格式
|
|
43
|
+
|
|
44
|
+
所有 API 使用 eapi response 模块的标准格式:
|
|
45
|
+
|
|
46
|
+
**成功响应(`response.success()`):**
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"code": 200,
|
|
50
|
+
"message": "success",
|
|
51
|
+
"data": { ... },
|
|
52
|
+
"timestamp": 1712467200
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**分页响应(`response.paginated()`):**
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"code": 200,
|
|
60
|
+
"message": "success",
|
|
61
|
+
"data": {
|
|
62
|
+
"items": [ ... ],
|
|
63
|
+
"total": 100,
|
|
64
|
+
"page": 1,
|
|
65
|
+
"page_size": 20
|
|
66
|
+
},
|
|
67
|
+
"timestamp": 1712467200
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**错误响应(eapi 异常处理器自动生成):**
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"code": "NOT_FOUND",
|
|
75
|
+
"message": "用户不存在"
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**禁止:** 自造响应格式、在 router 中手动构造 `{"success": true, ...}` 等非标准格式。
|
|
80
|
+
|
|
81
|
+
## 三、HTTP 状态码
|
|
82
|
+
|
|
83
|
+
| 场景 | 状态码 | 说明 |
|
|
84
|
+
|------|--------|------|
|
|
85
|
+
| 查询/更新成功 | 200 | GET / PATCH 默认 |
|
|
86
|
+
| 创建成功 | 201 | POST 创建资源 |
|
|
87
|
+
| 删除成功 | 204 | DELETE,无响应体 |
|
|
88
|
+
| 参数错误 | 400 | 请求格式错误 |
|
|
89
|
+
| 未认证 | 401 | Token 缺失或过期 |
|
|
90
|
+
| 无权限 | 403 | 认证通过但权限不足 |
|
|
91
|
+
| 资源不存在 | 404 | — |
|
|
92
|
+
| 业务错误 | 422 | 参数合法但业务规则不允许 |
|
|
93
|
+
|
|
94
|
+
## 四、Pydantic Schema 命名约定
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# 创建请求
|
|
98
|
+
class UserCreate(BaseModel): ...
|
|
99
|
+
|
|
100
|
+
# 更新请求(所有字段可选)
|
|
101
|
+
class UserUpdate(BaseModel): ...
|
|
102
|
+
|
|
103
|
+
# 响应
|
|
104
|
+
class UserResponse(BaseModel): ...
|
|
105
|
+
|
|
106
|
+
# 列表项(精简字段)
|
|
107
|
+
class UserBrief(BaseModel): ...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
请求和响应**必须分离**,禁止同一个 Schema 同时用于请求和响应。
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Alembic 数据库迁移规范
|
|
3
|
+
paths:
|
|
4
|
+
- "**/alembic/**"
|
|
5
|
+
- "**/alembic.ini"
|
|
6
|
+
- "**/migrations/**"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Alembic 数据库迁移规范
|
|
10
|
+
|
|
11
|
+
## 一、初始化
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd backend
|
|
15
|
+
alembic init alembic
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 二、配置
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
# alembic/env.py — 必须配置项
|
|
22
|
+
from app.database import Base # 导入 ORM Base
|
|
23
|
+
from app.models import * # 导入所有模型(确保被扫描到)
|
|
24
|
+
target_metadata = Base.metadata
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 三、工作流
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 模型变更后生成迁移
|
|
31
|
+
alembic revision --autogenerate -m "add user table"
|
|
32
|
+
|
|
33
|
+
# 执行迁移(dev 和 prod 相同)
|
|
34
|
+
alembic upgrade head
|
|
35
|
+
|
|
36
|
+
# 回滚一步
|
|
37
|
+
alembic downgrade -1
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## 四、强制规则
|
|
41
|
+
|
|
42
|
+
- 每次 model 变更**必须**生成 migration 文件,禁止手动 `CREATE TABLE`
|
|
43
|
+
- 迁移文件**必须**提交到 git,dev 和 prod 共用同一套迁移
|
|
44
|
+
- 生产数据库只通过 `alembic upgrade head` 变更 schema
|
|
45
|
+
- 迁移文件命名:Alembic 自动生成 revision ID + 人类可读描述
|
|
46
|
+
- 禁止在迁移文件中写业务数据操作(seed data 放 scripts/ 目录)
|
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Flutter + Dart 基础风格(结构/Riverpod/命名/模型/Dart 语言/Lint)
|
|
3
|
+
paths:
|
|
4
|
+
- "**/*.dart"
|
|
5
|
+
- "**/pubspec.yaml"
|
|
6
|
+
- "**/analysis_options.yaml"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Flutter + Dart 基础风格规范
|
|
10
|
+
|
|
11
|
+
## 一、项目结构
|
|
12
|
+
|
|
13
|
+
### 1.1 标准目录
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
project_name/
|
|
17
|
+
├── lib/
|
|
18
|
+
│ ├── main.dart # runApp + ProviderScope
|
|
19
|
+
│ ├── app.dart # MaterialApp.router + GoRouter + Theme
|
|
20
|
+
│ │
|
|
21
|
+
│ ├── core/ # 全局基础设施(不含业务逻辑)
|
|
22
|
+
│ │ ├── config/
|
|
23
|
+
│ │ │ └── env.dart # 环境配置(API 地址、OSS 端点等)
|
|
24
|
+
│ │ ├── network/
|
|
25
|
+
│ │ │ ├── dio_client.dart # Dio 实例 + 全部拦截器
|
|
26
|
+
│ │ │ ├── api_response.dart # 统一响应模型(对齐 eapi 格式)
|
|
27
|
+
│ │ │ └── interceptors/
|
|
28
|
+
│ │ │ ├── token_interceptor.dart
|
|
29
|
+
│ │ │ ├── refresh_interceptor.dart
|
|
30
|
+
│ │ │ ├── unwrap_interceptor.dart
|
|
31
|
+
│ │ │ └── error_interceptor.dart
|
|
32
|
+
│ │ ├── storage/
|
|
33
|
+
│ │ │ └── token_storage.dart # FlutterSecureStorage 封装
|
|
34
|
+
│ │ ├── theme/
|
|
35
|
+
│ │ │ ├── app_theme.dart # ThemeData(亮色 + 暗色)
|
|
36
|
+
│ │ │ └── app_colors.dart # 设计 tokens
|
|
37
|
+
│ │ └── utils/ # 通用工具(格式化、校验等)
|
|
38
|
+
│ │
|
|
39
|
+
│ ├── features/ # 功能模块(feature-first 拆分)
|
|
40
|
+
│ │ ├── auth/
|
|
41
|
+
│ │ │ ├── data/
|
|
42
|
+
│ │ │ │ ├── auth_repository.dart
|
|
43
|
+
│ │ │ │ └── auth_dto.dart
|
|
44
|
+
│ │ │ ├── domain/
|
|
45
|
+
│ │ │ │ └── user_entity.dart
|
|
46
|
+
│ │ │ └── presentation/
|
|
47
|
+
│ │ │ ├── login_page.dart
|
|
48
|
+
│ │ │ ├── auth_provider.dart
|
|
49
|
+
│ │ │ └── widgets/
|
|
50
|
+
│ │ │ └── login_form.dart
|
|
51
|
+
│ │ ├── home/
|
|
52
|
+
│ │ │ └── ...
|
|
53
|
+
│ │ └── {feature_name}/
|
|
54
|
+
│ │ ├── data/ # 数据层:Repository 实现 + DTO
|
|
55
|
+
│ │ ├── domain/ # 领域层:Entity(可选,简单场景直接用 DTO)
|
|
56
|
+
│ │ └── presentation/ # 表现层:Page + Provider + Widget
|
|
57
|
+
│ │
|
|
58
|
+
│ └── shared/ # 跨功能共享
|
|
59
|
+
│ ├── providers/ # 全局 Provider(auth、theme、dio)
|
|
60
|
+
│ ├── widgets/ # 共享 Widget(AppBar、ErrorView 等)
|
|
61
|
+
│ └── models/ # 共享数据模型
|
|
62
|
+
│
|
|
63
|
+
├── test/
|
|
64
|
+
│ ├── core/
|
|
65
|
+
│ ├── features/
|
|
66
|
+
│ │ └── {feature_name}/
|
|
67
|
+
│ └── shared/
|
|
68
|
+
│
|
|
69
|
+
├── pubspec.yaml
|
|
70
|
+
├── analysis_options.yaml # Lint 规则
|
|
71
|
+
├── build.yaml # build_runner 配置
|
|
72
|
+
├── .env.example
|
|
73
|
+
└── TEST_CASES.md
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 1.2 目录规则
|
|
77
|
+
|
|
78
|
+
| 规则 | 说明 |
|
|
79
|
+
|------|------|
|
|
80
|
+
| features/ 按功能拆分 | 每个功能自包含 data/domain/presentation,不跨功能引用 data/ |
|
|
81
|
+
| core/ 禁止业务逻辑 | 只放基础设施(网络、存储、主题、工具) |
|
|
82
|
+
| shared/ 放跨功能共享 | 两个以上 feature 共用的 provider/widget/model |
|
|
83
|
+
| domain/ 可选 | 简单 CRUD 场景可省略 domain/,直接 DTO 传递 |
|
|
84
|
+
| 一个文件一个公开类 | 文件名与主要类名对应(snake_case) |
|
|
85
|
+
|
|
86
|
+
### 1.3 入口文件
|
|
87
|
+
|
|
88
|
+
```dart
|
|
89
|
+
// main.dart
|
|
90
|
+
void main() {
|
|
91
|
+
runApp(const ProviderScope(child: App()));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// app.dart
|
|
95
|
+
class App extends ConsumerWidget {
|
|
96
|
+
const App({super.key});
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
100
|
+
final router = ref.watch(routerProvider);
|
|
101
|
+
return MaterialApp.router(
|
|
102
|
+
routerConfig: router,
|
|
103
|
+
theme: AppTheme.light,
|
|
104
|
+
darkTheme: AppTheme.dark,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 二、Riverpod 状态管理
|
|
113
|
+
|
|
114
|
+
### 2.1 Provider 类型选择
|
|
115
|
+
|
|
116
|
+
使用 `riverpod_generator`(@riverpod 注解)自动生成 Provider,禁止手写 Provider 声明。
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
# pubspec.yaml
|
|
120
|
+
dependencies:
|
|
121
|
+
flutter_riverpod: ^2.6.0
|
|
122
|
+
riverpod_annotation: ^2.6.0
|
|
123
|
+
|
|
124
|
+
dev_dependencies:
|
|
125
|
+
riverpod_generator: ^2.6.0
|
|
126
|
+
build_runner: ^2.4.0
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**类型选择规则:**
|
|
130
|
+
|
|
131
|
+
| 场景 | 用法 | 示例 |
|
|
132
|
+
|------|------|------|
|
|
133
|
+
| 同步计算/常量 | `@riverpod` 函数 | 主题、配置、计算值 |
|
|
134
|
+
| 异步一次性获取 | `@riverpod` async 函数 | 获取用户详情 |
|
|
135
|
+
| 带参数的查询 | `@riverpod` 函数 + 参数 | 按 ID 获取数据 |
|
|
136
|
+
| 可变状态(同步) | `@riverpod` class extends _$Xxx | 表单状态、开关 |
|
|
137
|
+
| 可变状态(异步) | `@riverpod` class + AsyncValue | 列表 CRUD、分页 |
|
|
138
|
+
| 全局长驻 | `@Riverpod(keepAlive: true)` | 认证状态、Dio 实例 |
|
|
139
|
+
|
|
140
|
+
### 2.2 代码生成模式
|
|
141
|
+
|
|
142
|
+
```dart
|
|
143
|
+
// 推荐:使用 @riverpod 注解(自动生成 Provider)
|
|
144
|
+
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
145
|
+
part 'user_provider.g.dart';
|
|
146
|
+
|
|
147
|
+
// 只读 Provider(函数式)
|
|
148
|
+
@riverpod
|
|
149
|
+
Future<User> userDetail(Ref ref, {required int userId}) async {
|
|
150
|
+
final dio = ref.watch(dioProvider);
|
|
151
|
+
final response = await dio.get('/api/users/$userId');
|
|
152
|
+
return User.fromJson(response.data);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 可变状态 Provider(类式)
|
|
156
|
+
@riverpod
|
|
157
|
+
class UserList extends _$UserList {
|
|
158
|
+
@override
|
|
159
|
+
Future<List<User>> build() async {
|
|
160
|
+
final dio = ref.watch(dioProvider);
|
|
161
|
+
final response = await dio.get('/api/users');
|
|
162
|
+
return (response.data['items'] as List).map((e) => User.fromJson(e)).toList();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Future<void> refresh() async {
|
|
166
|
+
state = const AsyncLoading();
|
|
167
|
+
state = await AsyncValue.guard(() => build());
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
Future<void> deleteUser(int id) async {
|
|
171
|
+
final dio = ref.read(dioProvider);
|
|
172
|
+
await dio.delete('/api/users/$id');
|
|
173
|
+
ref.invalidateSelf(); // 重新获取列表
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 全局长驻 Provider
|
|
178
|
+
@Riverpod(keepAlive: true)
|
|
179
|
+
class Auth extends _$Auth {
|
|
180
|
+
@override
|
|
181
|
+
AuthState build() {
|
|
182
|
+
return const AuthState.unauthenticated();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Future<void> login(String username, String password) async { ... }
|
|
186
|
+
void logout() { ... }
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 2.3 ref 使用规则
|
|
191
|
+
|
|
192
|
+
| 方法 | 用途 | 使用场景 |
|
|
193
|
+
|------|------|---------|
|
|
194
|
+
| `ref.watch(provider)` | 响应式监听,值变化时重建 | build() 方法中 |
|
|
195
|
+
| `ref.read(provider)` | 一次性读取,不监听变化 | 事件处理(onPressed、onSubmit) |
|
|
196
|
+
| `ref.listen(provider, callback)` | 监听变化执行副作用 | 显示 SnackBar、导航 |
|
|
197
|
+
| `ref.invalidate(provider)` | 强制刷新 Provider | 数据变更后刷新列表 |
|
|
198
|
+
|
|
199
|
+
**禁止:**
|
|
200
|
+
- 禁止在 `build()` 中用 `ref.read()`(应该用 `ref.watch()`)
|
|
201
|
+
- 禁止在 `onPressed` 回调中用 `ref.watch()`(应该用 `ref.read()`)
|
|
202
|
+
|
|
203
|
+
### 2.4 Widget 选择
|
|
204
|
+
|
|
205
|
+
```dart
|
|
206
|
+
// ConsumerWidget — 无状态,优先使用
|
|
207
|
+
class UserListPage extends ConsumerWidget {
|
|
208
|
+
const UserListPage({super.key});
|
|
209
|
+
|
|
210
|
+
@override
|
|
211
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
212
|
+
final users = ref.watch(userListProvider);
|
|
213
|
+
return users.when(
|
|
214
|
+
data: (list) => ListView(...),
|
|
215
|
+
loading: () => const CircularProgressIndicator(),
|
|
216
|
+
error: (err, stack) => ErrorView(error: err),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ConsumerStatefulWidget — 需要 initState/dispose 或动画控制器时使用
|
|
222
|
+
class AnimatedPage extends ConsumerStatefulWidget { ... }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**选择规则:** 优先 `ConsumerWidget`,只有需要 `StatefulWidget` 生命周期时才用 `ConsumerStatefulWidget`。
|
|
226
|
+
|
|
227
|
+
### 2.5 禁止事项
|
|
228
|
+
|
|
229
|
+
| 禁止 | 说明 |
|
|
230
|
+
|------|------|
|
|
231
|
+
| `StateNotifier` / `StateNotifierProvider` | 已废弃,使用 `Notifier` / `AsyncNotifier` |
|
|
232
|
+
| `ChangeNotifier` / `ChangeNotifierProvider` | Riverpod 不推荐,使用 Notifier |
|
|
233
|
+
| 手写 Provider 声明 | 必须用 `@riverpod` 注解 + code generation |
|
|
234
|
+
| `ref.watch()` 在事件回调中 | 事件回调用 `ref.read()` |
|
|
235
|
+
| Provider 间循环依赖 | 重新设计数据流方向 |
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 三、命名规范
|
|
240
|
+
|
|
241
|
+
### 3.1 文件命名
|
|
242
|
+
|
|
243
|
+
全部 `snake_case`,文件名反映内容:
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
user_profile_page.dart # 页面
|
|
247
|
+
user_list_provider.dart # Provider
|
|
248
|
+
auth_repository.dart # Repository
|
|
249
|
+
user_entity.dart # 实体
|
|
250
|
+
user_dto.dart # DTO
|
|
251
|
+
dio_client.dart # 工具类
|
|
252
|
+
login_form.dart # Widget
|
|
253
|
+
token_interceptor.dart # 拦截器
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### 3.2 类命名
|
|
257
|
+
|
|
258
|
+
| 类型 | 命名规则 | 示例 |
|
|
259
|
+
|------|---------|------|
|
|
260
|
+
| Page | `{Feature}{Action}Page` | `UserListPage`, `LoginPage` |
|
|
261
|
+
| Widget | 描述性 PascalCase | `LoginForm`, `UserCard`, `ErrorView` |
|
|
262
|
+
| Provider (函数式) | `{entity}{action}Provider` (自动生成) | `userDetailProvider`, `userListProvider` |
|
|
263
|
+
| Provider (类式) | `{Entity}{Action}` + `Provider` (自动生成) | `UserList` → `userListProvider` |
|
|
264
|
+
| Notifier | `{Entity}{Action}` | `UserList`, `Auth`, `ThemeMode` |
|
|
265
|
+
| Repository | `{Feature}Repository` | `AuthRepository`, `UserRepository` |
|
|
266
|
+
| Entity | `{Name}` | `User`, `Order`, `Factor` |
|
|
267
|
+
| DTO | `{Name}Dto` 或 `{Name}Response` | `UserDto`, `LoginResponse` |
|
|
268
|
+
| Service | `{Feature}Service` | `OssService`, `NotificationService` |
|
|
269
|
+
|
|
270
|
+
### 3.3 变量和函数命名
|
|
271
|
+
|
|
272
|
+
```dart
|
|
273
|
+
// 变量:camelCase
|
|
274
|
+
final userName = 'John';
|
|
275
|
+
final isLoading = false;
|
|
276
|
+
final userList = <User>[];
|
|
277
|
+
|
|
278
|
+
// 函数:camelCase,动词开头
|
|
279
|
+
Future<User> fetchUser(int id) async { ... }
|
|
280
|
+
void deleteUser(int id) { ... }
|
|
281
|
+
bool isValidEmail(String email) { ... }
|
|
282
|
+
|
|
283
|
+
// 常量:lowerCamelCase(Dart 官方风格,不用 UPPER_SNAKE)
|
|
284
|
+
const defaultPageSize = 20;
|
|
285
|
+
const maxRetryCount = 3;
|
|
286
|
+
|
|
287
|
+
// 私有成员:_前缀
|
|
288
|
+
final _cache = <String, dynamic>{};
|
|
289
|
+
void _handleError(Object error) { ... }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 3.4 禁止魔法字符串
|
|
293
|
+
|
|
294
|
+
同一个字符串字面量在文件内出现 2 次及以上时,**必须**提取为常量。
|
|
295
|
+
|
|
296
|
+
```dart
|
|
297
|
+
// ❌ 魔法字符串散落多处
|
|
298
|
+
await secureStorage.write(key: 'access_token', value: token);
|
|
299
|
+
final t = await secureStorage.read(key: 'access_token');
|
|
300
|
+
|
|
301
|
+
// ✅ 提取为常量
|
|
302
|
+
const _accessTokenKey = 'access_token';
|
|
303
|
+
await secureStorage.write(key: _accessTokenKey, value: token);
|
|
304
|
+
final t = await secureStorage.read(key: _accessTokenKey);
|
|
305
|
+
|
|
306
|
+
// ✅ 跨文件共享的 key 放到常量文件
|
|
307
|
+
// core/constants/storage_keys.dart
|
|
308
|
+
class StorageKeys {
|
|
309
|
+
StorageKeys._();
|
|
310
|
+
static const accessToken = 'access_token';
|
|
311
|
+
static const refreshToken = 'refresh_token';
|
|
312
|
+
static const theme = 'theme';
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 3.5 目录和包命名
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
features/user_management/ # snake_case
|
|
320
|
+
core/network/ # snake_case
|
|
321
|
+
shared/widgets/ # snake_case
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 四、数据模型与序列化
|
|
327
|
+
|
|
328
|
+
### 4.1 freezed + json_serializable
|
|
329
|
+
|
|
330
|
+
```yaml
|
|
331
|
+
# pubspec.yaml
|
|
332
|
+
dependencies:
|
|
333
|
+
freezed_annotation: ^2.4.0
|
|
334
|
+
json_annotation: ^4.9.0
|
|
335
|
+
|
|
336
|
+
dev_dependencies:
|
|
337
|
+
freezed: ^2.5.0
|
|
338
|
+
json_serializable: ^6.8.0
|
|
339
|
+
build_runner: ^2.4.0
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
```dart
|
|
343
|
+
// features/user/domain/user_entity.dart
|
|
344
|
+
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
345
|
+
part 'user_entity.freezed.dart';
|
|
346
|
+
part 'user_entity.g.dart';
|
|
347
|
+
|
|
348
|
+
@freezed
|
|
349
|
+
class User with _$User {
|
|
350
|
+
const factory User({
|
|
351
|
+
required int id,
|
|
352
|
+
required String username,
|
|
353
|
+
required String role,
|
|
354
|
+
@JsonKey(name: 'is_active') required bool isActive,
|
|
355
|
+
@JsonKey(name: 'created_at') required DateTime createdAt,
|
|
356
|
+
}) = _User;
|
|
357
|
+
|
|
358
|
+
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### 4.2 规则
|
|
363
|
+
|
|
364
|
+
| 规则 | 说明 |
|
|
365
|
+
|------|------|
|
|
366
|
+
| 数据模型用 `@freezed` | 不手写 `fromJson/toJson/copyWith/==` |
|
|
367
|
+
| 后端字段 snake_case → 前端 camelCase | 用 `@JsonKey(name: 'snake_case')` 映射 |
|
|
368
|
+
| 禁止手写序列化 | 必须用 json_serializable 代码生成 |
|
|
369
|
+
| 运行代码生成 | `dart run build_runner build --delete-conflicting-outputs` |
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## 五、Dart 语言规范
|
|
374
|
+
|
|
375
|
+
### 5.1 类型标注
|
|
376
|
+
|
|
377
|
+
```dart
|
|
378
|
+
// 显式标注公开 API 的返回类型
|
|
379
|
+
Future<List<User>> fetchUsers() async { ... }
|
|
380
|
+
String formatDate(DateTime date) { ... }
|
|
381
|
+
|
|
382
|
+
// 局部变量可用 final/var 推断
|
|
383
|
+
final users = await fetchUsers();
|
|
384
|
+
var count = 0;
|
|
385
|
+
|
|
386
|
+
// 禁止 dynamic(除非与原生 JSON 交互的边界点)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### 5.2 Null Safety
|
|
390
|
+
|
|
391
|
+
```dart
|
|
392
|
+
// 优先用非空类型
|
|
393
|
+
String name = 'default'; // 非空
|
|
394
|
+
|
|
395
|
+
// 只在真正可能为 null 时用 ?
|
|
396
|
+
String? middleName;
|
|
397
|
+
|
|
398
|
+
// 禁止滥用 !(bang operator),先 null check
|
|
399
|
+
if (user != null) {
|
|
400
|
+
print(user.name); // 安全
|
|
401
|
+
}
|
|
402
|
+
// 或用 ?.
|
|
403
|
+
user?.name;
|
|
404
|
+
|
|
405
|
+
// 禁止
|
|
406
|
+
print(user!.name); // 可能运行时崩溃
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 5.3 异步
|
|
410
|
+
|
|
411
|
+
```dart
|
|
412
|
+
// async/await 优先
|
|
413
|
+
final user = await fetchUser(id);
|
|
414
|
+
|
|
415
|
+
// 多个独立异步操作用 Future.wait 并行
|
|
416
|
+
final (users, orders) = await (
|
|
417
|
+
fetchUsers(),
|
|
418
|
+
fetchOrders(),
|
|
419
|
+
).wait;
|
|
420
|
+
|
|
421
|
+
// 禁止 .then() 链式调用(可读性差)
|
|
422
|
+
// fetchUser(id).then((user) => fetchOrders(user.id)).then(...) // 禁止
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 5.4 集合操作
|
|
426
|
+
|
|
427
|
+
```dart
|
|
428
|
+
// 推荐:collection-if / collection-for
|
|
429
|
+
final widgets = [
|
|
430
|
+
const Header(),
|
|
431
|
+
if (showBanner) const Banner(),
|
|
432
|
+
for (final item in items) ItemCard(item: item),
|
|
433
|
+
];
|
|
434
|
+
|
|
435
|
+
// 推荐:级联操作符
|
|
436
|
+
final paint = Paint()
|
|
437
|
+
..color = Colors.blue
|
|
438
|
+
..strokeWidth = 2.0;
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### 5.5 构造函数
|
|
442
|
+
|
|
443
|
+
```dart
|
|
444
|
+
// 推荐:命名参数 + required
|
|
445
|
+
class UserCard extends StatelessWidget {
|
|
446
|
+
const UserCard({
|
|
447
|
+
super.key,
|
|
448
|
+
required this.user,
|
|
449
|
+
this.onTap,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
final User user;
|
|
453
|
+
final VoidCallback? onTap;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 禁止位置参数超过 2 个
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
## 六、Lint 规则
|
|
462
|
+
|
|
463
|
+
### 6.1 analysis_options.yaml
|
|
464
|
+
|
|
465
|
+
```yaml
|
|
466
|
+
include: package:flutter_lints/flutter.yaml
|
|
467
|
+
|
|
468
|
+
linter:
|
|
469
|
+
rules:
|
|
470
|
+
- prefer_const_constructors
|
|
471
|
+
- prefer_const_declarations
|
|
472
|
+
- prefer_final_locals
|
|
473
|
+
- avoid_print # 禁止 print
|
|
474
|
+
- require_trailing_commas # 尾逗号(方便 diff)
|
|
475
|
+
- sort_constructors_first
|
|
476
|
+
- prefer_single_quotes # 统一单引号
|
|
477
|
+
- always_use_package_imports # 禁止相对导入
|
|
478
|
+
|
|
479
|
+
analyzer:
|
|
480
|
+
errors:
|
|
481
|
+
missing_return: error
|
|
482
|
+
dead_code: warning
|
|
483
|
+
exclude:
|
|
484
|
+
- "**/*.g.dart"
|
|
485
|
+
- "**/*.freezed.dart"
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### 6.2 Import 规则
|
|
489
|
+
|
|
490
|
+
```dart
|
|
491
|
+
// 使用 package import(禁止相对导入)
|
|
492
|
+
import 'package:my_app/features/auth/data/auth_repository.dart'; // 正确
|
|
493
|
+
// import '../data/auth_repository.dart'; // 禁止
|
|
494
|
+
|
|
495
|
+
// 导入顺序(dart > package > 项目 > 相对路径)
|
|
496
|
+
import 'dart:async';
|
|
497
|
+
import 'dart:convert';
|
|
498
|
+
|
|
499
|
+
import 'package:flutter/material.dart';
|
|
500
|
+
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
501
|
+
|
|
502
|
+
import 'package:my_app/core/network/dio_client.dart';
|
|
503
|
+
import 'package:my_app/features/auth/data/auth_repository.dart';
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 七、代码自检清单
|
|
509
|
+
|
|
510
|
+
提交前逐条确认:
|
|
511
|
+
|
|
512
|
+
- [ ] 文件名全部 snake_case
|
|
513
|
+
- [ ] 公开类/函数有类型标注
|
|
514
|
+
- [ ] Provider 用 @riverpod 注解生成,不手写
|
|
515
|
+
- [ ] 无 StateNotifier / ChangeNotifier
|
|
516
|
+
- [ ] ref.watch 只在 build() 中,ref.read 只在事件回调中
|
|
517
|
+
- [ ] 路由全部用 GoRouter,无 Navigator.push
|
|
518
|
+
- [ ] 数据模型用 @freezed,无手写 fromJson
|
|
519
|
+
- [ ] HTTP 请求通过 DioClient,无裸 http.get / Dio()
|
|
520
|
+
- [ ] 无 print(),调试用 debugPrint 或 logger
|
|
521
|
+
- [ ] 无 dynamic 类型(JSON 边界除外)
|
|
522
|
+
- [ ] 无 ! bang operator(先 null check)
|
|
523
|
+
- [ ] 常量构造函数加 const
|
|
524
|
+
- [ ] 尾逗号(trailing comma)
|
|
525
|
+
- [ ] package import,无相对导入
|
|
526
|
+
- [ ] 代码生成文件已更新(`dart run build_runner build`)
|
|
527
|
+
- [ ] 新增功能有对应测试
|
|
528
|
+
- [ ] TEST_CASES.md 已同步
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
## 八、环境变量管理(Flutter)
|
|
533
|
+
|
|
534
|
+
使用编译时注入或 `.env` + `flutter_dotenv`:
|
|
535
|
+
|
|
536
|
+
```bash
|
|
537
|
+
# .env
|
|
538
|
+
API_BASE_URL=http://localhost:8000/api
|
|
539
|
+
|
|
540
|
+
# 或编译时注入
|
|
541
|
+
flutter run --dart-define=API_BASE_URL=http://localhost:8000/api
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**强制规则:**
|
|
545
|
+
|
|
546
|
+
| 规则 | 说明 |
|
|
547
|
+
|------|------|
|
|
548
|
+
| 每个项目必须有 `.env.example` | 列出所有变量名 + 注释说明,值用占位符 |
|
|
549
|
+
| `.env` 文件禁止提交 git | 已在 git-security.md 约束 |
|
|
550
|
+
| 禁止硬编码 | 数据库连接、密钥、API 地址等必须走环境变量 |
|
|
551
|
+
|
|
552
|
+
---
|
|
553
|
+
|
|
554
|
+
## 九、状态管理(跨端共识)
|
|
555
|
+
|
|
556
|
+
| 平台 | 方案 | 详细规范 |
|
|
557
|
+
|------|------|---------|
|
|
558
|
+
| Vue | Pinia Setup Store | 见 `vue-code-style.md` 状态管理章节 |
|
|
559
|
+
| Flutter | Riverpod | 见本文件第二章 |
|
|
560
|
+
|
|
561
|
+
**跨端共识:**
|
|
562
|
+
- 认证状态全局管理(Vue: `useAuthStore`,Flutter: `authProvider`)
|
|
563
|
+
- 页面级状态就近管理,不上提到全局
|
|
564
|
+
- 缓存策略:列表数据按需刷新,不做客户端持久化缓存(除离线场景)
|