@nebula-skills/nebula-code-standards 0.1.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.
@@ -0,0 +1,310 @@
1
+ # 前端开发规范
2
+
3
+ > 适用于所有 Nebula IceStark 微前端子应用项目(无论独立仓库还是 monorepo 内置)。
4
+
5
+ ## 一、技术栈
6
+
7
+ | 技术 | 版本 | 说明 |
8
+ |------|------|------|
9
+ | Vue | 3.5 | 组合式 API(`<script setup>`) |
10
+ | Vite | 5 | 构建工具 |
11
+ | TypeScript | 5 | 全量类型覆盖 |
12
+ | Element Plus | 2.8 | UI 组件库 |
13
+ | IceStark | 2.8.4 | 微前端子应用 SDK(`@ice/stark-app`) |
14
+ | pnpm | 9+ | 包管理器 |
15
+ | Node | 22+ | 运行环境 |
16
+
17
+ ---
18
+
19
+ ## 二、共享包(来自 GitLab Package Registry)
20
+
21
+ 所有子应用必须使用以下共享包,**不要自行重复封装**:
22
+
23
+ | 包名 | 来源 | 说明 |
24
+ |------|------|------|
25
+ | `@nebula-web/types` | GitLab Package Registry | 共享 TypeScript 类型定义(`R<T>`、`PageResult<T>` 等)|
26
+ | `@nebula-web/utils` | GitLab Package Registry | 共享工具函数(`createRequest`、Token 工具等)|
27
+ | `@nebula-web/components` | GitLab Package Registry | 共享业务组件库 |
28
+ | `@ice/stark-app` | npm | IceStark 子应用 SDK |
29
+
30
+ **类型定义参考(来自 `@nebula-web/types`):**
31
+
32
+ ```typescript
33
+ // 统一 API 响应类型,对应后端 R<T>
34
+ interface R<T = unknown> {
35
+ code: number // 0 或 200 表示成功
36
+ message?: string // nebula 新版后端使用 message
37
+ msg?: string // 兼容旧版
38
+ data: T
39
+ }
40
+
41
+ // 分页响应类型,对应后端 PageResult<T>
42
+ // 注意:后端字段为 pageNo/pageSize(非 current/size)
43
+ interface PageResult<T> {
44
+ records: T[]
45
+ total: number
46
+ pageNo: number
47
+ pageSize: number
48
+ pages: number
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 三、项目结构
55
+
56
+ ```
57
+ src/
58
+ ├── api/
59
+ │ ├── index.ts # 创建 Axios 请求实例(只此一处,统一导出)
60
+ │ └── {域名}.ts # 业务 API 函数(如 mes.ts)
61
+ ├── pages/
62
+ │ └── {模块名}/
63
+ │ └── index.vue # 页面组件
64
+ ├── router/
65
+ │ └── routes.ts # 路由配置(路径不含 activePath 前缀)
66
+ ├── App.vue
67
+ └── main.ts # IceStark 生命周期入口
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 四、API 请求实例(`src/api/index.ts`)
73
+
74
+ **只在 `src/api/index.ts` 创建请求实例,业务文件导入使用:**
75
+
76
+ ```typescript
77
+ import { createRequest } from '@nebula-web/utils'
78
+
79
+ // 回退路径 /nebula-{domain} 由 Vite proxy 在开发环境转发到后端服务
80
+ export const {domain}Request = createRequest(import.meta.env.VITE_{DOMAIN}_API_BASE || '/nebula-{domain}')
81
+ ```
82
+
83
+ **`createRequest` 内置能力(无需业务层重复处理):**
84
+ - 请求拦截:自动注入 `Authorization: Bearer {token}`
85
+ - 响应拦截:`code === 0 || code === 200` 时自动解包,业务代码直接拿到 `T`(不是 `R<T>`)
86
+ - 401 处理:自动静默刷新 Token,失败后触发 `event.emit('401ToLogin')` 跳转登录
87
+ - 文件流响应:检测到 Excel 等文件流时直接返回 `response.data`,不解包
88
+
89
+ ---
90
+
91
+ ## 五、API 函数写法规范(`src/api/{域名}.ts`)
92
+
93
+ ```typescript
94
+ import { {domain}Request } from './index'
95
+ import type { PageResult } from '@nebula-web/types'
96
+ import type { DemoPageParam, DemoRespVO, DemoDetailVO, DemoSaveParam } from './types'
97
+
98
+ // 分页查询:POST + body
99
+ export function getDemoPage(data: DemoPageParam): Promise<PageResult<DemoRespVO>> {
100
+ return {domain}Request.post('/{domain}/demo/page', data)
101
+ }
102
+
103
+ // 查询详情:GET + pathVariable
104
+ export function getDemoById(id: number): Promise<DemoDetailVO> {
105
+ return {domain}Request.get(`/{domain}/demo/${id}`)
106
+ }
107
+
108
+ // 新增:POST + body,返回新 id
109
+ export function createDemo(data: DemoSaveParam): Promise<number> {
110
+ return {domain}Request.post('/{domain}/demo', data)
111
+ }
112
+
113
+ // 修改:PUT + body
114
+ export function updateDemo(data: DemoSaveParam): Promise<void> {
115
+ return {domain}Request.put('/{domain}/demo', data)
116
+ }
117
+
118
+ // 批量删除:DELETE + body(id 列表)
119
+ export function deleteDemo(ids: number[]): Promise<void> {
120
+ return {domain}Request.delete('/{domain}/demo', { data: ids })
121
+ }
122
+
123
+ // 启用:PATCH /{id}/v
124
+ export function enableDemo(id: number): Promise<void> {
125
+ return {domain}Request.patch(`/{domain}/demo/${id}/v`)
126
+ }
127
+
128
+ // 禁用:PATCH /{id}/x
129
+ export function disableDemo(id: number): Promise<void> {
130
+ return {domain}Request.patch(`/{domain}/demo/${id}/x`)
131
+ }
132
+ ```
133
+
134
+ **规范要点:**
135
+ - 具名导出函数(不使用默认导出对象)
136
+ - 返回类型精确标注(`Promise<PageResult<T>>` / `Promise<T>` / `Promise<void>`)
137
+ - 业务代码中**不需要**再 `.data` 解包(框架已处理)
138
+ - 不需要在业务层处理 401(框架已静默刷新)
139
+
140
+ ---
141
+
142
+ ## 六、TypeScript 类型定义规范
143
+
144
+ 在 `src/api/` 目录下或专用 `src/types/` 目录中定义业务类型:
145
+
146
+ ```typescript
147
+ // 分页查询入参(对应后端 XxxPageParamVO)
148
+ export interface DemoPageParam {
149
+ pageNo?: number // 页码,默认 1
150
+ pageSize?: number // 每页条数,默认 10
151
+ demoName?: string // 可选过滤条件
152
+ status?: number // 可选过滤条件
153
+ }
154
+
155
+ // 列表响应(对应后端 XxxRespVO)
156
+ export interface DemoRespVO {
157
+ id: number
158
+ demoName: string
159
+ demoCode: string
160
+ status: number
161
+ creator: string
162
+ createTime: string // 后端 LocalDateTime 序列化为字符串
163
+ modifyTime: string
164
+ }
165
+
166
+ // 详情响应(对应后端 XxxDetailVO)
167
+ export interface DemoDetailVO extends DemoRespVO {
168
+ remark?: string
169
+ customFields?: string
170
+ createUserId?: number
171
+ modifyUserId?: number
172
+ updater?: string
173
+ }
174
+
175
+ // 新增/修改入参(对应后端 XxxSaveParamVO)
176
+ export interface DemoSaveParam {
177
+ id?: number // 新增时不传,修改时必传
178
+ demoName: string
179
+ demoCode: string
180
+ status?: number
181
+ remark?: string
182
+ }
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 七、路由规范(`src/router/routes.ts`)
188
+
189
+ **路径不含 `/{domain}app` 等 activePath 前缀:**
190
+
191
+ ```typescript
192
+ import type { RouteRecordRaw } from 'vue-router'
193
+
194
+ /**
195
+ * 路径不含 /{domain}app 前缀。
196
+ * IceStark 通过 getBasename() 返回 activePath 作为 createWebHistory 的 base,
197
+ * Vue Router 自动将 base 从 URL 中剥除后再匹配路由。
198
+ *
199
+ * 浏览器 URL: /{domain}app/demo → Vue Router 匹配: /demo
200
+ */
201
+ const routes: RouteRecordRaw[] = [
202
+ { path: '/', redirect: '/demo' },
203
+ {
204
+ path: '/demo',
205
+ name: 'Demo',
206
+ component: () => import('@/pages/demo/index.vue'),
207
+ meta: { title: '示例管理' },
208
+ },
209
+ ]
210
+
211
+ export default routes
212
+ ```
213
+
214
+ **新增业务页面步骤:**
215
+ 1. 在 `src/pages/{模块名}/` 下新建页面组件(`.vue`)
216
+ 2. 在 `src/router/routes.ts` 注册路由(路径不含 activePath 前缀)
217
+ 3. 在后端 `nebula-system` 迁移脚本中添加菜单 INSERT(`path` 为 `/{activePath}/{路由path}`)
218
+
219
+ ---
220
+
221
+ ## 八、IceStark 子应用生命周期(`src/main.ts`)
222
+
223
+ ```typescript
224
+ import { isInIcestark, setLibraryName, getBasename } from '@ice/stark-app'
225
+ import { createApp } from 'vue'
226
+ import { createRouter, createWebHistory } from 'vue-router'
227
+ import App from './App.vue'
228
+ import routes from './router/routes'
229
+
230
+ // 全局唯一库名称,必须与主应用 microAppConfig 中配置一致
231
+ setLibraryName('nebula{PascalDomain}App')
232
+
233
+ let app: ReturnType<typeof createApp>
234
+
235
+ const mount = (props?: any) => {
236
+ const router = createRouter({
237
+ // 在 IceStark 环境中以 activePath 为 base,独立运行时为 /
238
+ history: createWebHistory(isInIcestark() ? getBasename() : '/'),
239
+ routes,
240
+ })
241
+ app = createApp(App)
242
+ app.use(router)
243
+ app.mount(props?.container?.querySelector('#app') || '#app')
244
+ }
245
+
246
+ // IceStark 环境:导出生命周期
247
+ if (isInIcestark()) {
248
+ export const bootstrap = () => Promise.resolve()
249
+ export const mount = mount
250
+ export const unmount = () => { app?.unmount() }
251
+ } else {
252
+ // 独立开发模式
253
+ mount()
254
+ }
255
+ ```
256
+
257
+ ---
258
+
259
+ ## 九、Token / Auth 工具(来自 `@nebula-web/utils`)
260
+
261
+ **统一使用共享包提供的工具,不直接操作 localStorage:**
262
+
263
+ ```typescript
264
+ import {
265
+ getToken, // 获取 AccessToken
266
+ setToken, // 存储 AccessToken
267
+ getRefreshToken, // 获取 RefreshToken
268
+ setRefreshToken, // 存储 RefreshToken
269
+ getTenantId, // 获取当前租户 ID
270
+ setTenantId,
271
+ removeToken, // 清除所有 Token(登出时调用)
272
+ isLoggedIn, // 判断是否已登录
273
+ } from '@nebula-web/utils'
274
+ ```
275
+
276
+ **LocalStorage Key 命名(仅供了解,不要直接读写):**
277
+ - `nebula_access_token`
278
+ - `nebula_refresh_token`
279
+ - `nebula_user_info`
280
+ - `nebula_tenant_id`
281
+
282
+ ---
283
+
284
+ ## 十、环境变量约定(`.env.*`)
285
+
286
+ ```bash
287
+ # .env.development — 联调本地内网
288
+ VITE_{DOMAIN}_API_BASE=http://192.168.x.x:8080
289
+ VITE_{DOMAIN}_BACKEND=http://192.168.x.x:8080 # 供 vite.config.ts proxy 使用
290
+
291
+ # .env.remote — 联调线上
292
+ VITE_{DOMAIN}_API_BASE=https://xxx.domain.cn
293
+ VITE_{DOMAIN}_BACKEND=https://xxx.domain.cn
294
+
295
+ # .env.production — 生产
296
+ VITE_{DOMAIN}_API_BASE= # 生产使用 Nginx 反向代理,留空走相对路径
297
+ ```
298
+
299
+ **切换后端地址只修改 `.env` 文件,不改代码。**
300
+
301
+ ---
302
+
303
+ ## 十一、代码规范要点
304
+
305
+ - 组件使用 `<script setup lang="ts">` 语法
306
+ - 响应式数据优先使用 `ref` / `reactive`
307
+ - Element Plus 组件按需引入(由 Vite 插件自动处理)
308
+ - 页面内不直接写请求逻辑,抽取到 `src/api/` 中
309
+ - 错误提示统一使用 `ElMessage.error(err.message)`,不 `console.error` 直接抛给用户
310
+ - TypeScript 严格模式:避免 `any`,必要时用 `unknown` 并做类型守卫