@lark-apaas/coding-templates 0.1.38 → 0.1.39-alpha.4
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/package.json +1 -1
- package/template-apex/README.md +59 -251
- package/template-apex/client/src/app.tsx +2 -2
- package/template-vite-react/README.md +53 -161
- package/template-vite-react/client/src/app.tsx +2 -2
- package/template-vite-react/client/src/lib/utils.ts +3 -3
- package/template-apex/client/src/lib/utils.ts +0 -6
- package/template-apex/client/src/pages/HomePage/HomePage.tsx +0 -12
- package/template-vite-react/client/src/pages/HomePage/HomePage.tsx +0 -12
package/package.json
CHANGED
package/template-apex/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 项目技术规范
|
|
2
2
|
|
|
3
3
|
## 技术栈
|
|
4
4
|
|
|
@@ -8,28 +8,29 @@
|
|
|
8
8
|
- 样式: Tailwind CSS v4
|
|
9
9
|
- UI 组件: shadcn/ui `import { Button } from "@/components/ui/button";`
|
|
10
10
|
- 图标: lucide-react `import { SearchIcon } from "lucide-react";`
|
|
11
|
-
- 图表:
|
|
11
|
+
- 图表: echarts-for-react `import ReactECharts from "echarts-for-react";`
|
|
12
12
|
- 动画: framer-motion `import { motion } from "framer-motion";`
|
|
13
13
|
- 路由: react-router-dom `import { Link, useNavigate } from "react-router-dom";`
|
|
14
14
|
- 校验: zod `import { z } from "zod";`
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
##
|
|
18
|
+
## 目录结构
|
|
19
19
|
|
|
20
20
|
```
|
|
21
21
|
├── client/src/ # 前端代码
|
|
22
|
-
│ ├── index.tsx #
|
|
23
|
-
│ ├── app.tsx #
|
|
22
|
+
│ ├── index.tsx # 入口(勿修改)
|
|
23
|
+
│ ├── app.tsx # 路由配置(仅在 <Routes> 内增删 <Route>)
|
|
24
24
|
│ ├── index.css # 全局样式 + 主题变量
|
|
25
25
|
│ ├── api/ # API 请求封装
|
|
26
26
|
│ │ └── index.ts
|
|
27
27
|
│ ├── components/ # 基础 UI 组件(禁止存放业务组件)
|
|
28
|
-
│ │
|
|
28
|
+
│ │ ├── layout.tsx # 全局布局容器(含 <Outlet />)
|
|
29
|
+
│ │ └── ui/ # shadcn/ui 内置组件(勿修改)
|
|
29
30
|
│ ├── pages/ # 页面模块(每个页面一个目录)
|
|
30
|
-
│ │ ├──
|
|
31
|
-
│ │ │
|
|
32
|
-
│ │ │ └── components/
|
|
31
|
+
│ │ ├── <PageName>/ # 页面目录示例
|
|
32
|
+
│ │ │ ├── PageName.tsx # 页面入口文件与目录同名
|
|
33
|
+
│ │ │ └── components/ # 页面专属组件
|
|
33
34
|
│ │ └── NotFoundPage/
|
|
34
35
|
│ │ └── NotFoundPage.tsx
|
|
35
36
|
│ ├── hooks/ # 自定义 Hooks
|
|
@@ -38,208 +39,84 @@
|
|
|
38
39
|
│ ├── index.ts # Express 入口
|
|
39
40
|
│ ├── routes/ # 路由
|
|
40
41
|
│ │ ├── index.ts # 路由注册
|
|
41
|
-
│ │ └── view.ts #
|
|
42
|
+
│ │ └── view.ts # 页面渲染(勿修改)
|
|
42
43
|
│ └── db/ # 数据库层
|
|
43
|
-
│
|
|
44
|
+
│ └── schema.ts # Drizzle schema(自动生成,勿修改)
|
|
44
45
|
├── shared/ # 前后端共享(不依赖 client 或 server)
|
|
45
46
|
│ ├── types.ts # 数据模型类型
|
|
46
|
-
│
|
|
47
|
+
│ ├── api.interface.ts # zod schema + API 入参/出参类型
|
|
48
|
+
│ └── static/ # 静态资源
|
|
49
|
+
│ ├── data/ # 数据文件(JSON)
|
|
50
|
+
│ └── images/ # 图片资源
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
---
|
|
50
54
|
|
|
51
|
-
##
|
|
55
|
+
## 模板初始状态
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
export interface Post {
|
|
59
|
-
id: number;
|
|
60
|
-
title: string;
|
|
61
|
-
content: string | null;
|
|
62
|
-
createdAt: string;
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`shared/api.interface.ts` 增加 zod schema 和 API 类型:
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
export const createPostSchema = z.object({
|
|
70
|
-
title: z.string().min(1).max(200),
|
|
71
|
-
content: z.string().optional(),
|
|
72
|
-
});
|
|
73
|
-
export type CreatePostRequest = z.infer<typeof createPostSchema>;
|
|
74
|
-
export type CreatePostResponse = Post;
|
|
75
|
-
export type ListPostsResponse = Post[];
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 2. server/ — 数据库和路由
|
|
79
|
-
|
|
80
|
-
`server/db/schema.ts` 由 `npm run gen:db-schema` 自动生成,无需手动编写。运行命令后会根据数据库表结构自动生成如下定义:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
export const posts = pgTable("posts", {
|
|
84
|
-
id: serial("id").primaryKey(),
|
|
85
|
-
title: text("title").notNull(),
|
|
86
|
-
content: text("content"),
|
|
87
|
-
createdAt: timestamp("created_at").notNull().defaultNow(),
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
`server/routes/posts.ts` 编写 CRUD 路由:
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
import { Router } from "express";
|
|
95
|
-
import { db } from "@lark-apaas/express-core";
|
|
96
|
-
import { posts } from "../db/schema";
|
|
97
|
-
import { createPostSchema } from "@shared/api.interface";
|
|
98
|
-
|
|
99
|
-
const router = Router();
|
|
100
|
-
router.get("/", async (_req, res) => {
|
|
101
|
-
const list = await db.select().from(posts);
|
|
102
|
-
res.json(list);
|
|
103
|
-
});
|
|
104
|
-
router.post("/", async (req, res) => {
|
|
105
|
-
const parsed = createPostSchema.safeParse(req.body);
|
|
106
|
-
if (!parsed.success) { res.status(400).json({ error: parsed.error.flatten() }); return; }
|
|
107
|
-
const [post] = await db.insert(posts).values(parsed.data).returning();
|
|
108
|
-
res.status(201).json(post);
|
|
109
|
-
});
|
|
110
|
-
export default router;
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
`server/routes/index.ts` 注册:
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
import postsRouter from "./posts";
|
|
117
|
-
app.use("/api/posts", postsRouter);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### 3. client/ — API 封装和页面
|
|
121
|
-
|
|
122
|
-
> ⚠️ **客户端所有 HTTP 请求必须使用 `axiosForBackend`**
|
|
123
|
-
>
|
|
124
|
-
> `axiosForBackend` 由 `@lark-apaas/client-toolkit-lite` 提供,内置平台鉴权、CSRF token 注入和请求上下文。**禁止使用** `fetch`、`axios`、`XMLHttpRequest` 或其他 HTTP 客户端直接发起请求。
|
|
125
|
-
|
|
126
|
-
`client/src/api/index.ts` 增加封装:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
import { axiosForBackend } from "@lark-apaas/client-toolkit-lite";
|
|
130
|
-
import type { ListPostsResponse } from "@shared/api.interface";
|
|
131
|
-
|
|
132
|
-
export async function listPosts(): Promise<ListPostsResponse> {
|
|
133
|
-
try {
|
|
134
|
-
const response = await axiosForBackend({
|
|
135
|
-
url: '/api/posts',
|
|
136
|
-
method: 'GET',
|
|
137
|
-
});
|
|
138
|
-
return response.data;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
throw error;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
`client/src/pages/PostsPage/PostsPage.tsx` 编写页面,`client/src/app.tsx` 注册路由。
|
|
57
|
+
- `app.tsx` 首页路由指向平台内置的 `<Welcome />` 组件
|
|
58
|
+
- 开发时需将 `index` 路由替换为业务首页,并在 `pages/` 下创建对应页面目录
|
|
59
|
+
- `layout.tsx` 为空壳容器(仅 `<Outlet />`),需根据需求实现导航和布局
|
|
146
60
|
|
|
147
61
|
---
|
|
148
62
|
|
|
149
|
-
##
|
|
150
|
-
|
|
151
|
-
**页面文件只做骨架编排,不包含具体 UI 实现。**
|
|
152
|
-
|
|
153
|
-
```tsx
|
|
154
|
-
// client/src/pages/DashboardPage/DashboardPage.tsx
|
|
155
|
-
import { StatsSection } from "./components/stats-section";
|
|
156
|
-
import { DataTableSection } from "./components/data-table-section";
|
|
157
|
-
|
|
158
|
-
export default function DashboardPage() {
|
|
159
|
-
return (
|
|
160
|
-
<div className="space-y-8">
|
|
161
|
-
<StatsSection />
|
|
162
|
-
<DataTableSection />
|
|
163
|
-
</div>
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
```
|
|
63
|
+
## 禁止修改的文件
|
|
167
64
|
|
|
168
|
-
|
|
65
|
+
| 文件 | 原因 |
|
|
66
|
+
|------|------|
|
|
67
|
+
| `client/src/index.tsx` | Provider 层级 + 样式引入,由模板管理 |
|
|
68
|
+
| `client/src/components/ui/*` | shadcn/ui 内置组件,版本锁定 |
|
|
69
|
+
| `server/routes/view.ts` | 页面渲染 catch-all,由模板管理 |
|
|
70
|
+
| `server/db/schema.ts` | 由 `npm run gen:db-schema` 自动生成 |
|
|
169
71
|
|
|
170
|
-
|
|
171
|
-
- 单个组件文件不超过 **150 行**,超出时进一步拆分子组件
|
|
172
|
-
- 页面专属组件放在 `pages/<page>/components/`
|
|
173
|
-
- `client/src/components/` 仅存放基础 UI 组件(如 shadcn/ui),**禁止存放业务组件**
|
|
174
|
-
- 文件名 kebab-case(`stat-card.tsx`),组件名 PascalCase(`StatCard`)
|
|
175
|
-
- 组件之间**禁止循环引用**
|
|
176
|
-
|
|
177
|
-
### Section 独立性(并行开发规范)
|
|
178
|
-
|
|
179
|
-
每个 Section 级组件(如 `StatsSection`、`DataTableSection`)必须做到**完全自包含**:
|
|
180
|
-
|
|
181
|
-
**1. 页面文件 = 纯布局组合器**
|
|
182
|
-
|
|
183
|
-
- 页面入口文件(如 `DashboardPage.tsx`)只做 import + JSX 排列,**禁止包含** `useState`、`useEffect`、数据请求或业务逻辑
|
|
184
|
-
- 页面文件无 props 接口定义,不承担任何数据协调职责
|
|
185
|
-
|
|
186
|
-
**2. Section 自包含原则**
|
|
187
|
-
|
|
188
|
-
每个 Section 组件独立拥有自己的:
|
|
189
|
-
- **数据获取**(API 调用、fetch)— 即使多个 Section 需要同一份数据,各自获取
|
|
190
|
-
- **状态管理**(useState、useReducer)
|
|
191
|
-
- **类型定义**(写在同文件或同目录下的 `types.ts`)
|
|
192
|
-
- **子组件**(如需拆分,平铺在 `components/` 目录下,禁止嵌套子目录)
|
|
72
|
+
---
|
|
193
73
|
|
|
194
|
-
|
|
74
|
+
## 文件放置规则
|
|
195
75
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
76
|
+
| 内容类型 | 放置位置 |
|
|
77
|
+
|---------|---------|
|
|
78
|
+
| 数据模型类型 | `shared/types.ts` |
|
|
79
|
+
| zod schema + API 类型 | `shared/api.interface.ts` |
|
|
80
|
+
| 新页面 | `client/src/pages/<PageName>/PageName.tsx` |
|
|
81
|
+
| 页面专属组件 | `client/src/pages/<PageName>/components/` |
|
|
82
|
+
| API 请求封装 | `client/src/api/index.ts` |
|
|
83
|
+
| 后端路由 | `server/routes/<resource>.ts` |
|
|
84
|
+
| 路由注册 | `server/routes/index.ts` |
|
|
85
|
+
| 静态数据文件 | `shared/static/data/` |
|
|
86
|
+
| 静态图片 | `shared/static/images/` |
|
|
199
87
|
|
|
200
88
|
---
|
|
201
89
|
|
|
202
|
-
##
|
|
90
|
+
## 导入路径
|
|
203
91
|
|
|
204
|
-
|
|
92
|
+
```typescript
|
|
93
|
+
// @/ 别名 → client/src/
|
|
94
|
+
import { cn } from "@/lib/utils";
|
|
205
95
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
96
|
+
// @shared/ 别名 → shared/
|
|
97
|
+
import type { Post } from "@shared/types";
|
|
98
|
+
import { createPostSchema } from "@shared/api.interface";
|
|
99
|
+
import heroImage from "@shared/static/images/hero.png";
|
|
209
100
|
|
|
210
|
-
|
|
101
|
+
// 后端导入 db 实例(唯一方式)
|
|
102
|
+
import { db } from "@lark-apaas/express-core";
|
|
211
103
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
{/* 将 index 路由指向真实的业务首页 */}
|
|
215
|
-
<Route index element={<DashboardPage />} />
|
|
216
|
-
<Route path="todos" element={<TodosPage />} />
|
|
217
|
-
<Route path="*" element={<NotFoundPage />} />
|
|
218
|
-
</Route>
|
|
104
|
+
// 后端导入表定义
|
|
105
|
+
import { posts } from "../db/schema";
|
|
219
106
|
```
|
|
220
107
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
**新增页面步骤:**
|
|
224
|
-
|
|
225
|
-
1. 在 `client/src/pages/` 下新建页面目录(如 `SettingsPage`)和 `SettingsPage.tsx`
|
|
226
|
-
2. 在 `app.tsx` 的 `<Routes>` 内添加 `<Route>` 配置
|
|
108
|
+
---
|
|
227
109
|
|
|
228
|
-
|
|
110
|
+
## 路由配置
|
|
229
111
|
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
- **禁止使用** `<a href="/">` 或 `window.location` 进行页面内跳转
|
|
112
|
+
- 新增页面需在 `client/src/app.tsx` 的 `<Routes>` 内注册 `<Route>`
|
|
113
|
+
- `BrowserRouter` 已在 `index.tsx` 中配置,`app.tsx` 中**禁止**再包裹 Router
|
|
233
114
|
|
|
234
115
|
---
|
|
235
116
|
|
|
236
|
-
##
|
|
117
|
+
## 主题变量
|
|
237
118
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
主题色定义在 `client/src/index.css` 中,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
241
|
-
|
|
242
|
-
**语义化颜色对照:**
|
|
119
|
+
主题色定义在 `client/src/index.css`,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
243
120
|
|
|
244
121
|
| 用途 | Tailwind 类 | CSS 变量 |
|
|
245
122
|
|------|------------|----------|
|
|
@@ -251,75 +128,6 @@ export default function DashboardPage() {
|
|
|
251
128
|
| 强调色 | `bg-accent` | `--accent` |
|
|
252
129
|
| 边框 | `border-border` | `--border` |
|
|
253
130
|
| 危险色 | `text-destructive` | `--destructive` |
|
|
254
|
-
| 成功色 | `text-success-foreground` | `--success-foreground` |
|
|
255
|
-
| 警告色 | `text-warning-foreground` | `--warning-foreground` |
|
|
256
131
|
| 图表色 | `bg-chart-1` ~ `bg-chart-5` | `--chart-1` ~ `--chart-5` |
|
|
257
132
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
- 主题色(背景、文本、主色、边框等)**必须使用语义化变量类**
|
|
261
|
-
- 灰阶辅助色(细节装饰、次要分隔线)可使用 Tailwind 原生色(如 `text-gray-500`)
|
|
262
|
-
- 类名合并使用 `cn()`:`import { cn } from "@/lib/utils"`
|
|
263
|
-
|
|
264
|
-
### 主题增量修改规范
|
|
265
|
-
|
|
266
|
-
修改主题时,**只覆盖需要变更的变量**:
|
|
267
|
-
|
|
268
|
-
```css
|
|
269
|
-
/* 正确:仅修改需要的变量 */
|
|
270
|
-
:root {
|
|
271
|
-
--primary: hsl(150, 60%, 40%);
|
|
272
|
-
--primary-foreground: hsl(0, 0%, 100%);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/* 禁止:复制整个 :root 块后修改 */
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
- 新增自定义颜色变量时,必须同时在 `:root` 和 `@theme inline` 中注册
|
|
279
|
-
- 禁止直接修改 `@theme inline` 中已有的 `--color-*` 映射关系
|
|
280
|
-
- 禁止删除已有的主题变量(可能被 shadcn/ui 组件依赖)
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## 布局与交互
|
|
285
|
-
|
|
286
|
-
### 响应式布局
|
|
287
|
-
|
|
288
|
-
- 容器使用 `max-w-*` + `mx-auto` 居中,禁止内容在大屏贴边延伸
|
|
289
|
-
- 多列布局使用 `grid` + 断点类:`grid-cols-1 md:grid-cols-2 lg:grid-cols-3`
|
|
290
|
-
- flex 子元素设置 `min-w-0`,多元素横排时加 `flex-wrap`
|
|
291
|
-
- 禁止固定像素宽度作为主容器(如 `w-[720px]`)
|
|
292
|
-
|
|
293
|
-
### 内容自适应
|
|
294
|
-
|
|
295
|
-
- 区块高度由内容撑开,禁止固定 `h-` 值(图表容器除外)
|
|
296
|
-
- 图片:`max-w-full h-auto`
|
|
297
|
-
- 长文本:`break-words`
|
|
298
|
-
- 单行截断:`truncate`
|
|
299
|
-
- 表格/代码块:`overflow-x-auto`
|
|
300
|
-
|
|
301
|
-
### 交互规范
|
|
302
|
-
|
|
303
|
-
- 所有交互元素(按钮、链接、标签页等)必须有**实际交互逻辑**和**可见反馈**
|
|
304
|
-
- 禁止空函数(`onClick={() => {}}`)或仅 `console.log` 的响应
|
|
305
|
-
- 禁止 `href="#"` 占位链接、无内容切换的标签页、空下拉菜单
|
|
306
|
-
- 禁止"导出"、"分享"等无法真正执行的操作按钮
|
|
307
|
-
- 如功能暂未实现,**删除该入口**,不实现假按钮
|
|
308
|
-
|
|
309
|
-
---
|
|
310
|
-
|
|
311
|
-
## 自检清单
|
|
312
|
-
|
|
313
|
-
| 检查项 | 验收标准 |
|
|
314
|
-
|--------|---------|
|
|
315
|
-
| 页面拆分 | 页面文件只做骨架编排(无 state/effect/逻辑);每个 Section 自包含数据+状态+类型;兄弟 Section 间无互相 import;单文件 ≤150 行 |
|
|
316
|
-
| 命名规范 | 页面目录 PascalCase,页面入口文件与目录同名(PascalCase),组件文件名 kebab-case,组件名 PascalCase |
|
|
317
|
-
| 路由注册 | 默认 `HomePage` 已替换为业务首页;新页面已在 `app.tsx` 注册;跳转使用 `<Link>` / `useNavigate()`,无 `<a href>` |
|
|
318
|
-
| API 调用 | 统一在 `api/` 封装;必须使用 `axiosForBackend` 发起请求,禁止 `fetch`/`axios`;使用 `@shared` 类型 |
|
|
319
|
-
| 输入校验 | zod schema 定义在 `shared/api.interface.ts`;server 和 client 共用 |
|
|
320
|
-
| 主题色 | 使用语义化变量类(`bg-background`、`text-primary` 等);未硬编码颜色值 |
|
|
321
|
-
| 主题修改 | 仅增量覆盖变更的变量;新增色同时注册 `:root` 和 `@theme inline` |
|
|
322
|
-
| 响应式 | 容器 `max-w-*` + `mx-auto`;多列布局窄屏退化单列;flex 有 `min-w-0` |
|
|
323
|
-
| 内容自适应 | 无固定 `h-`(图表除外);长文本 `break-words`;表格 `overflow-x-auto` |
|
|
324
|
-
| 交互完整性 | 所有按钮/链接有实际处理器和可见反馈;无空响应、无假按钮 |
|
|
325
|
-
| 无循环引用 | client ↔ server 不直接 import;通过 shared/ 共享类型 |
|
|
133
|
+
HSL 格式使用**空格分隔**:`--primary: hsl(150 60% 40%);`
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Routes, Route } from "react-router-dom";
|
|
2
|
+
import { Welcome } from "@lark-apaas/client-toolkit-lite";
|
|
2
3
|
import { Layout } from "@/components/layout";
|
|
3
|
-
import HomePage from "@/pages/HomePage/HomePage";
|
|
4
4
|
import NotFoundPage from "@/pages/NotFoundPage/NotFoundPage";
|
|
5
5
|
|
|
6
6
|
export default function App() {
|
|
7
7
|
return (
|
|
8
8
|
<Routes>
|
|
9
9
|
<Route element={<Layout />}>
|
|
10
|
-
<Route index element={<
|
|
10
|
+
<Route index element={<Welcome />} />
|
|
11
11
|
<Route path="*" element={<NotFoundPage />} />
|
|
12
12
|
</Route>
|
|
13
13
|
</Routes>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 项目技术规范
|
|
2
2
|
|
|
3
3
|
## 技术栈
|
|
4
4
|
|
|
@@ -6,135 +6,93 @@
|
|
|
6
6
|
- 样式: Tailwind CSS v4
|
|
7
7
|
- UI 组件: shadcn/ui `import { Button } from "@/components/ui/button";`
|
|
8
8
|
- 图标: lucide-react `import { SearchIcon } from "lucide-react";`
|
|
9
|
-
- 图表:
|
|
9
|
+
- 图表: echarts-for-react `import ReactECharts from "echarts-for-react";`
|
|
10
10
|
- 动画: framer-motion `import { motion } from "framer-motion";`
|
|
11
11
|
- 路由: react-router-dom `import { Link, useNavigate } from "react-router-dom";`
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 目录结构
|
|
16
16
|
|
|
17
17
|
```
|
|
18
18
|
client/src/
|
|
19
|
-
├── index.tsx #
|
|
20
|
-
├── app.tsx #
|
|
19
|
+
├── index.tsx # 入口(勿修改)
|
|
20
|
+
├── app.tsx # 路由配置(仅在 <Routes> 内增删 <Route>)
|
|
21
21
|
├── index.css # 全局样式 + 主题变量
|
|
22
22
|
├── components/ # 基础 UI 组件(禁止存放业务组件)
|
|
23
|
-
│
|
|
23
|
+
│ ├── layout.tsx # 全局布局容器(含 <Outlet />)
|
|
24
|
+
│ └── ui/ # shadcn/ui 内置组件(勿修改)
|
|
24
25
|
├── pages/ # 页面模块(每个页面一个目录)
|
|
25
|
-
│
|
|
26
|
-
│
|
|
27
|
-
│
|
|
26
|
+
│ ├── <PageName>/ # 页面目录示例
|
|
27
|
+
│ │ ├── PageName.tsx # 页面入口文件与目录同名
|
|
28
|
+
│ │ └── components/ # 页面专属组件
|
|
29
|
+
│ └── NotFoundPage/
|
|
30
|
+
│ └── NotFoundPage.tsx
|
|
28
31
|
├── hooks/ # 自定义 Hooks
|
|
29
32
|
└── lib/ # 工具函数(cn() 等)
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 页面与组件规范
|
|
35
|
-
|
|
36
|
-
**页面文件只做骨架编排,不包含具体 UI 实现。**
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export default function DashboardPage() {
|
|
44
|
-
return (
|
|
45
|
-
<div className="space-y-8">
|
|
46
|
-
<StatsSection />
|
|
47
|
-
<DataTableSection />
|
|
48
|
-
</div>
|
|
49
|
-
);
|
|
50
|
-
}
|
|
34
|
+
shared/
|
|
35
|
+
└── static/ # 静态资源
|
|
36
|
+
├── data/ # 数据文件(JSON)
|
|
37
|
+
└── images/ # 图片资源
|
|
51
38
|
```
|
|
52
39
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- 每个视觉上独立的区块拆为一个组件文件,即使只出现一次
|
|
56
|
-
- 单个组件文件不超过 **150 行**,超出时进一步拆分子组件
|
|
57
|
-
- 页面专属组件放在 `pages/<page>/components/`
|
|
58
|
-
- `client/src/components/` 仅存放基础 UI 组件(如 shadcn/ui),**禁止存放业务组件**
|
|
59
|
-
- 文件名 kebab-case(`stat-card.tsx`),组件名 PascalCase(`StatCard`)
|
|
60
|
-
- 组件之间**禁止循环引用**
|
|
40
|
+
---
|
|
61
41
|
|
|
62
|
-
|
|
42
|
+
## 模板初始状态
|
|
63
43
|
|
|
64
|
-
|
|
44
|
+
- `app.tsx` 首页路由指向平台内置的 `<Welcome />` 组件
|
|
45
|
+
- 开发时需将 `index` 路由替换为业务首页,并在 `pages/` 下创建对应页面目录
|
|
46
|
+
- `layout.tsx` 为空壳容器(仅 `<Outlet />`),需根据需求实现导航和布局
|
|
65
47
|
|
|
66
|
-
|
|
48
|
+
---
|
|
67
49
|
|
|
68
|
-
|
|
69
|
-
- 页面文件无 props 接口定义,不承担任何数据协调职责
|
|
50
|
+
## 禁止修改的文件
|
|
70
51
|
|
|
71
|
-
|
|
52
|
+
| 文件 | 原因 |
|
|
53
|
+
|------|------|
|
|
54
|
+
| `client/src/index.tsx` | Provider 层级 + 样式引入,由模板管理 |
|
|
55
|
+
| `client/src/components/ui/*` | shadcn/ui 内置组件,版本锁定 |
|
|
72
56
|
|
|
73
|
-
|
|
74
|
-
- **数据获取**(API 调用、fetch)— 即使多个 Section 需要同一份数据,各自获取
|
|
75
|
-
- **状态管理**(useState、useReducer)
|
|
76
|
-
- **类型定义**(写在同文件或同目录下的 `types.ts`)
|
|
77
|
-
- **子组件**(如需拆分,平铺在 `components/` 目录下,禁止嵌套子目录)
|
|
57
|
+
---
|
|
78
58
|
|
|
79
|
-
|
|
59
|
+
## 文件放置规则
|
|
80
60
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
61
|
+
| 内容类型 | 放置位置 |
|
|
62
|
+
|---------|---------|
|
|
63
|
+
| 新页面 | `client/src/pages/<PageName>/PageName.tsx` |
|
|
64
|
+
| 页面专属组件 | `client/src/pages/<PageName>/components/` |
|
|
65
|
+
| 自定义 Hooks | `client/src/hooks/` |
|
|
66
|
+
| 工具函数 | `client/src/lib/` |
|
|
67
|
+
| 静态数据文件 | `shared/static/data/` |
|
|
68
|
+
| 静态图片 | `shared/static/images/` |
|
|
84
69
|
|
|
85
70
|
---
|
|
86
71
|
|
|
87
|
-
##
|
|
88
|
-
|
|
89
|
-
新增页面在 `client/src/app.tsx` 中注册。
|
|
90
|
-
|
|
91
|
-
> ⚠️ **首页路由替换(必做)**
|
|
92
|
-
>
|
|
93
|
-
> 模板默认的 `HomePage` 是占位示例页,**不是业务首页**。开发时必须将 `index` 路由替换为真实的业务首页组件(如 `DashboardPage`),并删除 `HomePage` 目录。
|
|
94
|
-
|
|
95
|
-
**替换后的路由示例:**
|
|
96
|
-
|
|
97
|
-
```tsx
|
|
98
|
-
import { Routes, Route } from "react-router-dom";
|
|
99
|
-
import { Layout } from "@/components/layout";
|
|
100
|
-
import DashboardPage from "@/pages/DashboardPage/DashboardPage";
|
|
101
|
-
import NotFoundPage from "@/pages/NotFoundPage/NotFoundPage";
|
|
102
|
-
|
|
103
|
-
export default function App() {
|
|
104
|
-
return (
|
|
105
|
-
<Routes>
|
|
106
|
-
<Route element={<Layout />}>
|
|
107
|
-
{/* 将 index 路由指向真实的业务首页 */}
|
|
108
|
-
<Route index element={<DashboardPage />} />
|
|
109
|
-
<Route path="*" element={<NotFoundPage />} />
|
|
110
|
-
</Route>
|
|
111
|
-
</Routes>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
```
|
|
72
|
+
## 导入路径
|
|
115
73
|
|
|
116
|
-
|
|
74
|
+
```typescript
|
|
75
|
+
// @/ 别名 → client/src/
|
|
76
|
+
import { cn } from "@/lib/utils";
|
|
77
|
+
import { useIsMobile } from "@/hooks/use-mobile";
|
|
117
78
|
|
|
118
|
-
|
|
79
|
+
// @shared/ 别名 → shared/
|
|
80
|
+
import heroImage from "@shared/static/images/hero.png";
|
|
81
|
+
import configData from "@shared/static/config.json";
|
|
82
|
+
```
|
|
119
83
|
|
|
120
|
-
|
|
121
|
-
2. 在 `app.tsx` 的 `<Routes>` 内添加 `<Route>` 配置
|
|
84
|
+
---
|
|
122
85
|
|
|
123
|
-
|
|
86
|
+
## 路由配置
|
|
124
87
|
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
- **禁止使用** `<a href="/">` 或 `window.location` 进行页面内跳转
|
|
88
|
+
- 新增页面需在 `client/src/app.tsx` 的 `<Routes>` 内注册 `<Route>`
|
|
89
|
+
- `BrowserRouter` 已在 `index.tsx` 中配置,`app.tsx` 中**禁止**再包裹 Router
|
|
128
90
|
|
|
129
91
|
---
|
|
130
92
|
|
|
131
|
-
##
|
|
93
|
+
## 主题变量
|
|
132
94
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
主题色定义在 `client/src/index.css` 中,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
136
|
-
|
|
137
|
-
**语义化颜色对照:**
|
|
95
|
+
主题色定义在 `client/src/index.css`,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
138
96
|
|
|
139
97
|
| 用途 | Tailwind 类 | CSS 变量 |
|
|
140
98
|
|------|------------|----------|
|
|
@@ -146,72 +104,6 @@ export default function App() {
|
|
|
146
104
|
| 强调色 | `bg-accent` | `--accent` |
|
|
147
105
|
| 边框 | `border-border` | `--border` |
|
|
148
106
|
| 危险色 | `text-destructive` | `--destructive` |
|
|
149
|
-
| 成功色 | `text-success-foreground` | `--success-foreground` |
|
|
150
|
-
| 警告色 | `text-warning-foreground` | `--warning-foreground` |
|
|
151
107
|
| 图表色 | `bg-chart-1` ~ `bg-chart-5` | `--chart-1` ~ `--chart-5` |
|
|
152
108
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
- 主题色(背景、文本、主色、边框等)**必须使用语义化变量类**
|
|
156
|
-
- 灰阶辅助色(细节装饰、次要分隔线)可使用 Tailwind 原生色(如 `text-gray-500`)
|
|
157
|
-
- 类名合并使用 `cn()`:`import { cn } from "@/lib/utils"`
|
|
158
|
-
|
|
159
|
-
### 主题增量修改规范
|
|
160
|
-
|
|
161
|
-
修改主题时,**只覆盖需要变更的变量**:
|
|
162
|
-
|
|
163
|
-
```css
|
|
164
|
-
/* 正确:仅修改需要的变量 */
|
|
165
|
-
:root {
|
|
166
|
-
--primary: hsl(150, 60%, 40%);
|
|
167
|
-
--primary-foreground: hsl(0, 0%, 100%);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/* 禁止:复制整个 :root 块后修改 */
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
- 新增自定义颜色变量时,必须同时在 `:root` 和 `@theme inline` 中注册
|
|
174
|
-
- 禁止直接修改 `@theme inline` 中已有的 `--color-*` 映射关系
|
|
175
|
-
- 禁止删除已有的主题变量(可能被 shadcn/ui 组件依赖)
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## 布局与交互
|
|
180
|
-
|
|
181
|
-
### 响应式布局
|
|
182
|
-
|
|
183
|
-
- 容器使用 `max-w-*` + `mx-auto` 居中,禁止内容在大屏贴边延伸
|
|
184
|
-
- 多列布局使用 `grid` + 断点类:`grid-cols-1 md:grid-cols-2 lg:grid-cols-3`
|
|
185
|
-
- flex 子元素设置 `min-w-0`,多元素横排时加 `flex-wrap`
|
|
186
|
-
- 禁止固定像素宽度作为主容器(如 `w-[720px]`)
|
|
187
|
-
|
|
188
|
-
### 内容自适应
|
|
189
|
-
|
|
190
|
-
- 区块高度由内容撑开,禁止固定 `h-` 值(图表容器除外)
|
|
191
|
-
- 图片:`max-w-full h-auto`
|
|
192
|
-
- 长文本:`break-words`
|
|
193
|
-
- 单行截断:`truncate`
|
|
194
|
-
- 表格/代码块:`overflow-x-auto`
|
|
195
|
-
|
|
196
|
-
### 交互规范
|
|
197
|
-
|
|
198
|
-
- 所有交互元素(按钮、链接、标签页等)必须有**实际交互逻辑**和**可见反馈**
|
|
199
|
-
- 禁止空函数(`onClick={() => {}}`)或仅 `console.log` 的响应
|
|
200
|
-
- 禁止 `href="#"` 占位链接、无内容切换的标签页、空下拉菜单
|
|
201
|
-
- 禁止"导出"、"分享"等无法真正执行的操作按钮
|
|
202
|
-
- 如功能暂未实现,**删除该入口**,不实现假按钮
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## 自检清单
|
|
207
|
-
|
|
208
|
-
| 检查项 | 验收标准 |
|
|
209
|
-
|--------|---------|
|
|
210
|
-
| 页面拆分 | 页面文件只做骨架编排(无 state/effect/逻辑);每个 Section 自包含数据+状态+类型;兄弟 Section 间无互相 import;单文件 ≤150 行 |
|
|
211
|
-
| 命名规范 | 文件名 kebab-case,组件名 PascalCase |
|
|
212
|
-
| 路由注册 | 默认 `HomePage` 已替换为业务首页;新页面已在 `app.tsx` 注册;跳转使用 `<Link>` / `useNavigate()`,无 `<a href>` |
|
|
213
|
-
| 主题色 | 使用语义化变量类(`bg-background`、`text-primary` 等);未硬编码颜色值 |
|
|
214
|
-
| 主题修改 | 仅增量覆盖变更的变量;新增色同时注册 `:root` 和 `@theme inline` |
|
|
215
|
-
| 响应式 | 容器 `max-w-*` + `mx-auto`;多列布局窄屏退化单列;flex 有 `min-w-0` |
|
|
216
|
-
| 内容自适应 | 无固定 `h-`(图表除外);长文本 `break-words`;表格 `overflow-x-auto` |
|
|
217
|
-
| 交互完整性 | 所有按钮/链接有实际处理器和可见反馈;无空响应、无假按钮 |
|
|
109
|
+
HSL 格式使用**空格分隔**:`--primary: hsl(150 60% 40%);`
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Routes, Route } from "react-router-dom";
|
|
2
|
+
import { Welcome } from "@lark-apaas/client-toolkit-lite";
|
|
2
3
|
import { Layout } from "@/components/layout";
|
|
3
|
-
import HomePage from "@/pages/HomePage/HomePage";
|
|
4
4
|
import NotFoundPage from "@/pages/NotFoundPage/NotFoundPage";
|
|
5
5
|
|
|
6
6
|
export default function App() {
|
|
7
7
|
return (
|
|
8
8
|
<Routes>
|
|
9
9
|
<Route element={<Layout />}>
|
|
10
|
-
<Route index element={<
|
|
10
|
+
<Route index element={<Welcome />} />
|
|
11
11
|
<Route path="*" element={<NotFoundPage />} />
|
|
12
12
|
</Route>
|
|
13
13
|
</Routes>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { clsx, type ClassValue } from "clsx"
|
|
2
|
-
import { twMerge } from "tailwind-merge"
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
3
|
|
|
4
4
|
export function cn(...inputs: ClassValue[]) {
|
|
5
|
-
return twMerge(clsx(inputs))
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
6
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export default function HomePage() {
|
|
2
|
-
return (
|
|
3
|
-
<div className="flex flex-col items-center justify-center py-24">
|
|
4
|
-
<h1 className="text-5xl font-bold tracking-tight sm:text-7xl mb-4">
|
|
5
|
-
👋 Hello OpenClaw
|
|
6
|
-
</h1>
|
|
7
|
-
<p className="text-lg text-muted-foreground">
|
|
8
|
-
Start building your app by editing <code className="px-1.5 py-0.5 rounded bg-muted font-mono text-sm">client/src/pages/HomePage/HomePage.tsx</code>
|
|
9
|
-
</p>
|
|
10
|
-
</div>
|
|
11
|
-
);
|
|
12
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export default function HomePage() {
|
|
2
|
-
return (
|
|
3
|
-
<div className="flex flex-col items-center justify-center py-24">
|
|
4
|
-
<h1 className="text-5xl font-bold tracking-tight sm:text-7xl mb-4">
|
|
5
|
-
👋 Hello OpenClaw
|
|
6
|
-
</h1>
|
|
7
|
-
<p className="text-lg text-muted-foreground">
|
|
8
|
-
Start building your app by editing <code className="px-1.5 py-0.5 rounded bg-muted font-mono text-sm">client/src/pages/HomePage/HomePage.tsx</code>
|
|
9
|
-
</p>
|
|
10
|
-
</div>
|
|
11
|
-
);
|
|
12
|
-
}
|