@deepstorm/cli 0.1.0 → 0.2.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/dist/config-schema.json +1 -1
- package/dist/registry.json +65 -5
- package/dist/skills/reef-gen-frontend/variants/react/steps.md +31 -0
- package/dist/skills/reef-gen-frontend/variants/vue/steps.md +34 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +283 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +152 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +457 -0
- package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +186 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +257 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +166 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +353 -0
- package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +267 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +124 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +195 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +127 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +251 -0
- package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +226 -0
- package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +118 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +162 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +270 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +145 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +231 -0
- package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +224 -0
- package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +145 -0
- package/package.json +3 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Vue 数据获取层 + 路由配置示例
|
|
2
|
+
|
|
3
|
+
## 组合式函数(数据获取层)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
// composables/useUsers.ts
|
|
7
|
+
import { ref, computed, watch } from 'vue'
|
|
8
|
+
import type { User, PageResponse } from '@/shared/types/user'
|
|
9
|
+
|
|
10
|
+
interface UseUsersOptions {
|
|
11
|
+
page?: number
|
|
12
|
+
size?: number
|
|
13
|
+
search?: string
|
|
14
|
+
role?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 使用 URLSearchParams 构建查询参数
|
|
18
|
+
function buildQuery(params: UseUsersOptions): string {
|
|
19
|
+
const searchParams = new URLSearchParams()
|
|
20
|
+
if (params.page !== undefined) searchParams.set('page', String(params.page))
|
|
21
|
+
if (params.size !== undefined) searchParams.set('size', String(params.size))
|
|
22
|
+
if (params.search) searchParams.set('search', params.search)
|
|
23
|
+
if (params.role) searchParams.set('role', params.role)
|
|
24
|
+
const qs = searchParams.toString()
|
|
25
|
+
return qs ? `?${qs}` : ''
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function useUsers(options: UseUsersOptions = {}) {
|
|
29
|
+
const users = ref<User[]>([])
|
|
30
|
+
const total = ref(0)
|
|
31
|
+
const loading = ref(true)
|
|
32
|
+
const error = ref<Error | null>(null)
|
|
33
|
+
const refreshKey = ref(0)
|
|
34
|
+
|
|
35
|
+
async function fetchUsers() {
|
|
36
|
+
loading.value = true
|
|
37
|
+
error.value = null
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch(`/api/v1/users${buildQuery(options)}`)
|
|
41
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
42
|
+
const data: PageResponse<User> = await res.json()
|
|
43
|
+
users.value = data.content
|
|
44
|
+
total.value = data.totalElements
|
|
45
|
+
} catch (e) {
|
|
46
|
+
error.value = e as Error
|
|
47
|
+
} finally {
|
|
48
|
+
loading.value = false
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
watch([() => options.page, () => options.size, () => options.search, () => options.role, refreshKey], () => {
|
|
53
|
+
fetchUsers()
|
|
54
|
+
}, { immediate: true })
|
|
55
|
+
|
|
56
|
+
function refresh() {
|
|
57
|
+
refreshKey.value++
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { users, total, loading, error, refresh }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 写操作组合式函数
|
|
64
|
+
export function useCreateUser() {
|
|
65
|
+
const loading = ref(false)
|
|
66
|
+
|
|
67
|
+
async function create(data: CreateUserRequest): Promise<User> {
|
|
68
|
+
loading.value = true
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch('/api/v1/users', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify(data),
|
|
74
|
+
})
|
|
75
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
76
|
+
return await res.json()
|
|
77
|
+
} finally {
|
|
78
|
+
loading.value = false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { create, loading }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Pinia Store 示例
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// stores/auth.ts
|
|
90
|
+
import { defineStore } from 'pinia'
|
|
91
|
+
import { ref, computed } from 'vue'
|
|
92
|
+
import type { User } from '@/shared/types/user'
|
|
93
|
+
|
|
94
|
+
export const useAuthStore = defineStore('auth', () => {
|
|
95
|
+
// state
|
|
96
|
+
const user = ref<User | null>(null)
|
|
97
|
+
const token = ref<string | null>(localStorage.getItem('auth_token'))
|
|
98
|
+
const loading = ref(false)
|
|
99
|
+
|
|
100
|
+
// getters
|
|
101
|
+
const isAuthenticated = computed(() => !!token.value)
|
|
102
|
+
const isAdmin = computed(() => user.value?.role === 'ADMIN')
|
|
103
|
+
|
|
104
|
+
// actions
|
|
105
|
+
async function login(username: string, password: string) {
|
|
106
|
+
loading.value = true
|
|
107
|
+
try {
|
|
108
|
+
const res = await fetch('/api/v1/auth/login', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { 'Content-Type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ username, password }),
|
|
112
|
+
})
|
|
113
|
+
const data = await res.json()
|
|
114
|
+
token.value = data.token
|
|
115
|
+
user.value = data.user
|
|
116
|
+
localStorage.setItem('auth_token', data.token)
|
|
117
|
+
} finally {
|
|
118
|
+
loading.value = false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function logout() {
|
|
123
|
+
user.value = null
|
|
124
|
+
token.value = null
|
|
125
|
+
localStorage.removeItem('auth_token')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { user, token, loading, isAuthenticated, isAdmin, login, logout }
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 路由配置
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// router/index.ts
|
|
136
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
137
|
+
import { useAuthStore } from '@/stores/auth'
|
|
138
|
+
|
|
139
|
+
const router = createRouter({
|
|
140
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
141
|
+
routes: [
|
|
142
|
+
{
|
|
143
|
+
path: '/',
|
|
144
|
+
name: 'dashboard',
|
|
145
|
+
component: () => import('@/views/Dashboard.vue'),
|
|
146
|
+
meta: { requiresAuth: true },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
path: '/users',
|
|
150
|
+
name: 'users',
|
|
151
|
+
component: () => import('@/views/UserList.vue'),
|
|
152
|
+
meta: { requiresAuth: true },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
path: '/users/new',
|
|
156
|
+
name: 'create-user',
|
|
157
|
+
component: () => import('@/views/CreateUser.vue'),
|
|
158
|
+
meta: { requiresAuth: true },
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
path: '/users/:id',
|
|
162
|
+
name: 'user-detail',
|
|
163
|
+
component: () => import('@/views/UserDetail.vue'),
|
|
164
|
+
meta: { requiresAuth: true },
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
path: '/login',
|
|
168
|
+
name: 'login',
|
|
169
|
+
component: () => import('@/views/Login.vue'),
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
path: '/:pathMatch(.*)*',
|
|
173
|
+
name: 'not-found',
|
|
174
|
+
component: () => import('@/views/NotFound.vue'),
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
// 导航守卫
|
|
180
|
+
router.beforeEach((to, from) => {
|
|
181
|
+
const auth = useAuthStore()
|
|
182
|
+
if (to.meta.requiresAuth && !auth.isAuthenticated) {
|
|
183
|
+
return { name: 'login', query: { redirect: to.fullPath } }
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
export default router
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## 主应用入口
|
|
191
|
+
|
|
192
|
+
```vue
|
|
193
|
+
<!-- App.vue -->
|
|
194
|
+
<script setup lang="ts">
|
|
195
|
+
import { ConfigProvider } from 'ant-design-vue'
|
|
196
|
+
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
|
197
|
+
import dayjs from 'dayjs'
|
|
198
|
+
import 'dayjs/locale/zh-cn'
|
|
199
|
+
|
|
200
|
+
dayjs.locale('zh-cn')
|
|
201
|
+
</script>
|
|
202
|
+
|
|
203
|
+
<template>
|
|
204
|
+
<ConfigProvider
|
|
205
|
+
:locale="zhCN"
|
|
206
|
+
:theme="{ token: { colorPrimary: '#1677ff', borderRadius: 6 } }"
|
|
207
|
+
>
|
|
208
|
+
<RouterView />
|
|
209
|
+
</ConfigProvider>
|
|
210
|
+
</template>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// main.ts
|
|
215
|
+
import { createApp } from 'vue'
|
|
216
|
+
import { createPinia } from 'pinia'
|
|
217
|
+
import App from './App.vue'
|
|
218
|
+
import router from './router'
|
|
219
|
+
|
|
220
|
+
const app = createApp(App)
|
|
221
|
+
app.use(createPinia())
|
|
222
|
+
app.use(router)
|
|
223
|
+
app.mount('#app')
|
|
224
|
+
```
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# 前端编码快速参考 (Vue 3)
|
|
2
|
+
|
|
3
|
+
按需加载。仅当你需要编写对应组件类型时阅读相关章节。
|
|
4
|
+
|
|
5
|
+
> **完整示例代码见 `examples/` 目录。** 已安装子维度的规范,通过该维度的 `{value}.md` 文件阅读。
|
|
6
|
+
|
|
7
|
+
## 速查
|
|
8
|
+
|
|
9
|
+
| 场景 | 决策 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 新建组件 | `<script setup lang="ts">` + `defineProps<Props>()` + 组合式 API |
|
|
12
|
+
| 新建表单 | Ant Design Vue `<a-form>` + `v-model` + `:rules` 校验 |
|
|
13
|
+
| 读数据 | 组合式函数(`useUsers`)封装 fetch/axios |
|
|
14
|
+
| 写数据 | 组合式函数 + `ref()` loading/error/data 三态 |
|
|
15
|
+
| 组合式函数职责 | 只负责业务逻辑和数据获取,不管理 UI 渲染 |
|
|
16
|
+
| 路由 | 动态 `import()` + `createRouter` 懒加载 |
|
|
17
|
+
|
|
18
|
+
## 新建组件
|
|
19
|
+
|
|
20
|
+
```vue
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
interface UserTableProps {
|
|
23
|
+
users: User[]
|
|
24
|
+
loading?: boolean
|
|
25
|
+
onSelect?: (user: User) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = defineProps<UserTableProps>()
|
|
29
|
+
const emit = defineEmits<{
|
|
30
|
+
select: [user: User]
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
function handleClick(user: User) {
|
|
34
|
+
emit('select', user)
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<a-table
|
|
40
|
+
:data-source="props.users"
|
|
41
|
+
:loading="props.loading"
|
|
42
|
+
row-key="id"
|
|
43
|
+
@click="handleClick"
|
|
44
|
+
/>
|
|
45
|
+
</template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| 规则 | 要求 |
|
|
49
|
+
| --- | --- |
|
|
50
|
+
| `<script setup lang="ts">` | 禁止 Options API,使用组合式 API |
|
|
51
|
+
| `defineProps<Type>()` | 使用泛型语法定义 Props,`type` 定义联合类型 |
|
|
52
|
+
| 状态提升 | 组件间共享状态提升到最近公共父组件,或使用 Pinia |
|
|
53
|
+
| 默认导出 | 页面级组件使用 `export default`(路由组件),通用组件使用具名导出 |
|
|
54
|
+
|
|
55
|
+
## 组合式 API 规则
|
|
56
|
+
|
|
57
|
+
- **顶层调用**:`ref()`、`computed()`、`watch()` 等组合式 API 在 `<script setup>` 顶层调用,禁止在条件/循环中
|
|
58
|
+
- **ref 默认首选**:优先使用 `ref()`,`reactive()` 仅用于不需要整体替换的固定结构对象
|
|
59
|
+
- **生命周期**:注册的监听器和定时器必须在 `onUnmounted` 中清理
|
|
60
|
+
- **组合式函数**:以 `use` 开头命名,返回值为对象
|
|
61
|
+
|
|
62
|
+
```vue
|
|
63
|
+
<script setup lang="ts">
|
|
64
|
+
import { ref, onMounted, onUnmounted } from 'vue'
|
|
65
|
+
|
|
66
|
+
const users = ref<User[]>([])
|
|
67
|
+
const loading = ref(true)
|
|
68
|
+
const error = ref<Error | null>(null)
|
|
69
|
+
|
|
70
|
+
onMounted(async () => {
|
|
71
|
+
try {
|
|
72
|
+
const res = await fetch('/api/v1/users')
|
|
73
|
+
users.value = await res.json()
|
|
74
|
+
} catch (e) {
|
|
75
|
+
error.value = e as Error
|
|
76
|
+
} finally {
|
|
77
|
+
loading.value = false
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
</script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Ant Design Vue 组件
|
|
84
|
+
|
|
85
|
+
- **自动按需导入**:通过 `unplugin-vue-components` 配合 `AntDesignVueResolver` 实现,模板中使用 `a-` 前缀(`<a-button>`、`<a-table>`)
|
|
86
|
+
- **Form**:`<a-form-item>` 的 `name` 必须与数据模型字段一致,使用 `:rules` 定义校验
|
|
87
|
+
- **Table**:必须指定 `row-key`,空数据时设置 `locale.emptyText`
|
|
88
|
+
- **Modal**:关闭时使用 `destroyOnClose` 或 `@afterClose` 重置内部状态
|
|
89
|
+
- **布局**:使用 `<a-row>` / `<a-col>` 栅格系统或 Flex 布局
|
|
90
|
+
- **程序化 API**:`message.success()`、`Modal.confirm()` 等从 `ant-design-vue` 手动导入
|
|
91
|
+
|
|
92
|
+
## 错误处理
|
|
93
|
+
|
|
94
|
+
- 错误边界使用 `onErrorCaptured` 生命周期钩子捕获子组件异常
|
|
95
|
+
- API 错误在组合式函数层捕获并暴露错误状态,UI 层通过 Ant Design Vue 组件展示反馈
|
|
96
|
+
- 表单校验使用 Ant Design Vue Form 内置校验规则,非必要不自定义 validator
|
|
97
|
+
- Error Boundary 组件使用 `<script setup>` + `onErrorCaptured` 实现
|
|
98
|
+
|
|
99
|
+
## 路由
|
|
100
|
+
|
|
101
|
+
- 所有页面组件使用动态 `import()` 实现懒加载
|
|
102
|
+
- 路由配置集中定义在路由文件中,不在组件内嵌路由
|
|
103
|
+
- 权限通过导航守卫(`beforeEach`)控制
|
|
104
|
+
- 组件内路由操作使用 `useRouter()` / `useRoute()` 组合式函数
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<script setup lang="ts">
|
|
108
|
+
import { createRouter, createWebHistory } from 'vue-router'
|
|
109
|
+
|
|
110
|
+
const router = createRouter({
|
|
111
|
+
history: createWebHistory(import.meta.env.BASE_URL),
|
|
112
|
+
routes: [
|
|
113
|
+
{
|
|
114
|
+
path: '/users',
|
|
115
|
+
name: 'users',
|
|
116
|
+
component: () => import('@/views/UserList.vue'),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
path: '/users/:id',
|
|
120
|
+
name: 'user-detail',
|
|
121
|
+
component: () => import('@/views/UserDetail.vue'),
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
})
|
|
125
|
+
</script>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 代码风格
|
|
129
|
+
|
|
130
|
+
- 控制流语句必须使用大括号,禁止无大括号的早期返回
|
|
131
|
+
- 代码折行规则(90 列限制)详见 `examples/code-wrapping.md`
|
|
132
|
+
- 模板属性超过 3 个时换行排列
|
|
133
|
+
- 自闭合标签:无子元素时使用 `<Component />` 而非 `<Component></Component>`
|
|
134
|
+
- 模板表达式保持简洁,复杂逻辑提取到 `<script setup>` 中
|
|
135
|
+
|
|
136
|
+
## 常见坑
|
|
137
|
+
|
|
138
|
+
| 场景 | 问题 | 正确做法 |
|
|
139
|
+
|------|------|---------|
|
|
140
|
+
| `reactive` 整体赋值 | 直接 `state = newVal` 破坏响应式 | 使用 `Object.assign()` 或改用 `ref()` |
|
|
141
|
+
| `v-for` 缺少 `:key` | 列表渲染异常/性能下降 | 绑定唯一 `:key="item.id"` |
|
|
142
|
+
| 响应式解构丢失 | `const { name } = reactive(obj)` 解构后 `name` 失去响应性 | 使用 `toRefs()` 或保持 `obj.name` 访问 |
|
|
143
|
+
| 生命周期清理遗漏 | 事件监听/定时器在组件卸载后仍然运行 | `onUnmounted` 中清理 |
|
|
144
|
+
| `watch` 旧值误用 | 以为 `watch` 第一个回调参数是新值 | 签名是 `watch(source, (newVal, oldVal) => ...)` |
|
|
145
|
+
| Ant Design Vue 手动导入遗漏 | `message` / `Modal` 等程序化 API 未导入 | 从 `ant-design-vue` 包手动导入 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstorm/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "DeepStorm CLI — 一键配置项目开发环境",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "billkang",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "node scripts/build.mjs && node dist/build-registry.js",
|
|
15
|
+
"publish": "node scripts/publish.mjs",
|
|
15
16
|
"test": "vitest run --coverage",
|
|
16
17
|
"prepublishOnly": "npm run build",
|
|
17
18
|
"prepack": "npm run build"
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"@clack/prompts": "^0.9.0",
|
|
21
22
|
"commander": "^12.0.0",
|
|
23
|
+
"dotenv": "^17.4.2",
|
|
22
24
|
"handlebars": "^4.7.8",
|
|
23
25
|
"js-yaml": "^4.1.0"
|
|
24
26
|
},
|