@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.
Files changed (25) hide show
  1. package/dist/config-schema.json +1 -1
  2. package/dist/registry.json +65 -5
  3. package/dist/skills/reef-gen-frontend/variants/react/steps.md +31 -0
  4. package/dist/skills/reef-gen-frontend/variants/vue/steps.md +34 -0
  5. package/dist/skills/reef-style-frontend/fragments/test/vitest-react/examples/testing.md +283 -0
  6. package/dist/skills/reef-style-frontend/fragments/test/vitest-react/quick-reference.md +152 -0
  7. package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/examples/testing.md +457 -0
  8. package/dist/skills/reef-style-frontend/fragments/test/vitest-vue/quick-reference.md +186 -0
  9. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/examples/ui-components.md +257 -0
  10. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd/quick-reference.md +166 -0
  11. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/examples/ui-components.md +353 -0
  12. package/dist/skills/reef-style-frontend/fragments/ui-lib/antd-vue/quick-reference.md +267 -0
  13. package/dist/skills/reef-style-frontend/variants/react/examples/code-wrapping.md +124 -0
  14. package/dist/skills/reef-style-frontend/variants/react/examples/component-types-pipes.md +195 -0
  15. package/dist/skills/reef-style-frontend/variants/react/examples/entity-types.md +127 -0
  16. package/dist/skills/reef-style-frontend/variants/react/examples/forms-layer.md +251 -0
  17. package/dist/skills/reef-style-frontend/variants/react/examples/service-routing.md +226 -0
  18. package/dist/skills/reef-style-frontend/variants/react/quick-reference.md +118 -0
  19. package/dist/skills/reef-style-frontend/variants/vue/examples/code-wrapping.md +162 -0
  20. package/dist/skills/reef-style-frontend/variants/vue/examples/component-types-pipes.md +270 -0
  21. package/dist/skills/reef-style-frontend/variants/vue/examples/entity-types.md +145 -0
  22. package/dist/skills/reef-style-frontend/variants/vue/examples/forms-layer.md +231 -0
  23. package/dist/skills/reef-style-frontend/variants/vue/examples/service-routing.md +224 -0
  24. package/dist/skills/reef-style-frontend/variants/vue/quick-reference.md +145 -0
  25. package/package.json +3 -1
@@ -0,0 +1,270 @@
1
+ # Vue 组件模式示例
2
+
3
+ Vue 3 中不直接存在 Pipe 的概念,通过 computed 或工具函数实现数据转换。
4
+
5
+ ## 数据转换(替代 Pipe)
6
+
7
+ ```vue
8
+ <script setup lang="ts">
9
+ // ✅ 方案1: 纯工具函数(无状态转换)
10
+ function formatDate(date: string | Date, style: 'short' | 'long' = 'short'): string {
11
+ const d = typeof date === 'string' ? new Date(date) : date
12
+ if (style === 'short') {
13
+ return d.toLocaleDateString('zh-CN')
14
+ }
15
+ return d.toLocaleDateString('zh-CN', {
16
+ year: 'numeric', month: 'long', day: 'numeric',
17
+ hour: '2-digit', minute: '2-digit',
18
+ })
19
+ }
20
+
21
+ function formatCurrency(amount: number, currency = 'CNY'): string {
22
+ return new Intl.NumberFormat('zh-CN', {
23
+ style: 'currency',
24
+ currency,
25
+ }).format(amount)
26
+ }
27
+
28
+ // ✅ 方案2: computed 属性(有状态转换 — 如搜索/过滤)
29
+ const props = defineProps<{ users: User[] }>()
30
+ const search = ref('')
31
+
32
+ const filteredUsers = computed(() => {
33
+ if (!search.value.trim()) return props.users
34
+ const q = search.value.toLowerCase()
35
+ return props.users.filter(
36
+ u => u.username.toLowerCase().includes(q) || u.email.toLowerCase().includes(q)
37
+ )
38
+ })
39
+ </script>
40
+
41
+ <template>
42
+ <a-table
43
+ :data-source="filteredUsers"
44
+ :columns="[
45
+ { title: '创建时间', dataIndex: 'createdAt', customRender: ({ text }) => formatDate(text) },
46
+ { title: '金额', dataIndex: 'amount', customRender: ({ text }) => formatCurrency(text) },
47
+ ]"
48
+ />
49
+ </template>
50
+ ```
51
+
52
+ ## 插槽模式(Slots)
53
+
54
+ ### 命名插槽 + 默认内容
55
+
56
+ ```vue
57
+ <!-- components/Card.vue -->
58
+ <template>
59
+ <div class="card">
60
+ <header v-if="$slots.header">
61
+ <slot name="header" />
62
+ </header>
63
+ <main>
64
+ <slot />
65
+ </main>
66
+ <footer v-if="$slots.footer">
67
+ <slot name="footer" />
68
+ </footer>
69
+ </div>
70
+ </template>
71
+ ```
72
+
73
+ ### 作用域插槽
74
+
75
+ ```vue
76
+ <!-- components/DataList.vue -->
77
+ <script setup lang="ts">
78
+ interface Item {
79
+ id: number
80
+ name: string
81
+ email: string
82
+ }
83
+
84
+ defineProps<{ items: Item[] }>()
85
+ </script>
86
+
87
+ <template>
88
+ <ul>
89
+ <li v-for="item in items" :key="item.id">
90
+ <slot name="item" :item="item">
91
+ <!-- 默认内容:显示名称 -->
92
+ {{ item.name }}
93
+ </slot>
94
+ </li>
95
+ </ul>
96
+ </template>
97
+ ```
98
+
99
+ ```vue
100
+ <!-- 使用作用域插槽 -->
101
+ <DataList :items="users">
102
+ <template #item="{ item }">
103
+ <strong>{{ item.name }}</strong>
104
+ <span class="text-gray-500">({{ item.email }})</span>
105
+ </template>
106
+ </DataList>
107
+ ```
108
+
109
+ ## 动态组件
110
+
111
+ ```vue
112
+ <script setup lang="ts">
113
+ import { shallowRef } from 'vue'
114
+ import UserList from '@/views/UserList.vue'
115
+ import UserDetail from '@/views/UserDetail.vue'
116
+ import Settings from '@/views/Settings.vue'
117
+
118
+ const currentView = shallowRef(UserList)
119
+
120
+ function switchView(view: 'list' | 'detail' | 'settings') {
121
+ const views = { list: UserList, detail: UserDetail, settings: Settings }
122
+ currentView.value = views[view]
123
+ }
124
+ </script>
125
+
126
+ <template>
127
+ <KeepAlive>
128
+ <component :is="currentView" />
129
+ </KeepAlive>
130
+ </template>
131
+ ```
132
+
133
+ ## KeepAlive 缓存组件
134
+
135
+ ```vue
136
+ <template>
137
+ <!-- 缓存已激活的视图,避免切换时重新渲染 -->
138
+ <KeepAlive :include="['UserList', 'Dashboard']" :max="10">
139
+ <RouterView />
140
+ </KeepAlive>
141
+ </template>
142
+ ```
143
+
144
+ ## Transition 动画
145
+
146
+ ```vue
147
+ <template>
148
+ <a-button @click="show = !show">
149
+ {{ show ? '隐藏' : '显示' }}
150
+ </a-button>
151
+
152
+ <Transition name="fade" mode="out-in">
153
+ <div v-if="show" key="content">
154
+ 内容区域
155
+ </div>
156
+ </Transition>
157
+ </template>
158
+
159
+ <style scoped>
160
+ .fade-enter-active,
161
+ .fade-leave-active {
162
+ transition: opacity 0.3s ease;
163
+ }
164
+ .fade-enter-from,
165
+ .fade-leave-to {
166
+ opacity: 0;
167
+ }
168
+ </style>
169
+ ```
170
+
171
+ ## 异步组件
172
+
173
+ ```typescript
174
+ // components/AsyncWidget.ts
175
+ import { defineAsyncComponent } from 'vue'
176
+ import { Spin } from 'ant-design-vue'
177
+
178
+ export default defineAsyncComponent({
179
+ loader: () => import('./HeavyWidget.vue'),
180
+ loadingComponent: Spin,
181
+ delay: 200,
182
+ timeout: 8000,
183
+ })
184
+
185
+ // 路由中的异步组件 — 通过动态 import 实现
186
+ // router/index.ts
187
+ const routes = [
188
+ {
189
+ path: '/dashboard',
190
+ component: () => import('@/views/Dashboard.vue'),
191
+ },
192
+ ]
193
+ ```
194
+
195
+ ## Provide/Inject 模式
196
+
197
+ ```typescript
198
+ // injection-keys.ts
199
+ import type { InjectionKey, Ref } from 'vue'
200
+
201
+ export const ThemeKey: InjectionKey<{
202
+ theme: Ref<'light' | 'dark'>
203
+ toggleTheme: () => void
204
+ }> = Symbol('theme')
205
+ ```
206
+
207
+ ```vue
208
+ <!-- 祖先组件:provide -->
209
+ <script setup lang="ts">
210
+ import { provide, ref } from 'vue'
211
+ import { ThemeKey } from '@/injection-keys'
212
+
213
+ const theme = ref<'light' | 'dark'>('light')
214
+
215
+ function toggleTheme() {
216
+ theme.value = theme.value === 'light' ? 'dark' : 'light'
217
+ }
218
+
219
+ provide(ThemeKey, { theme, toggleTheme })
220
+ </script>
221
+
222
+ <template>
223
+ <slot />
224
+ </template>
225
+ ```
226
+
227
+ ```vue
228
+ <!-- 后代组件:inject -->
229
+ <script setup lang="ts">
230
+ import { inject } from 'vue'
231
+ import { ThemeKey } from '@/injection-keys'
232
+
233
+ const { theme, toggleTheme } = inject(ThemeKey)!
234
+ </script>
235
+
236
+ <template>
237
+ <div :class="theme">
238
+ 当前主题: {{ theme }}
239
+ <a-button @click="toggleTheme">切换主题</a-button>
240
+ </div>
241
+ </template>
242
+ ```
243
+
244
+ ## 权限控制(导航守卫 + 组件封装)
245
+
246
+ ```vue
247
+ <!-- components/AuthGuard.vue -->
248
+ <script setup lang="ts">
249
+ import { useAuthStore } from '@/stores/auth'
250
+ import { useRouter } from 'vue-router'
251
+
252
+ const auth = useAuthStore()
253
+ const router = useRouter()
254
+
255
+ if (!auth.isAuthenticated) {
256
+ router.replace({ name: 'login', query: { redirect: router.currentRoute.value.fullPath } })
257
+ }
258
+ </script>
259
+
260
+ <template>
261
+ <slot v-if="auth.isAuthenticated" />
262
+ </template>
263
+ ```
264
+
265
+ ```vue
266
+ <!-- 使用 -->
267
+ <AuthGuard>
268
+ <Dashboard />
269
+ </AuthGuard>
270
+ ```
@@ -0,0 +1,145 @@
1
+ # Vue 实体类型定义示例
2
+
3
+ ## 基础实体层次
4
+
5
+ ```typescript
6
+ // shared/types/entity.ts
7
+ export interface ImmutableEntity {
8
+ readonly id: string
9
+ readonly createdAt: string
10
+ readonly updatedAt: string
11
+ }
12
+
13
+ export interface Entity extends ImmutableEntity {
14
+ version: number
15
+ }
16
+
17
+ export interface Auditable<T> extends Entity {
18
+ createdBy: T
19
+ updatedBy: T
20
+ }
21
+
22
+ export interface ImmutableAuditable<T> extends ImmutableEntity {
23
+ createdBy: T
24
+ }
25
+ ```
26
+
27
+ ## API 响应类型
28
+
29
+ ```typescript
30
+ // shared/types/api.ts
31
+ export interface PageResponse<T> {
32
+ content: T[]
33
+ totalElements: number
34
+ totalPages: number
35
+ number: number
36
+ size: number
37
+ first: boolean
38
+ last: boolean
39
+ }
40
+
41
+ export interface ApiError {
42
+ code: string
43
+ message: string
44
+ details?: Record<string, string[]>
45
+ }
46
+ ```
47
+
48
+ ## 业务实体示例
49
+
50
+ ```typescript
51
+ // shared/types/user.ts
52
+ export interface User extends Auditable<string> {
53
+ username: string
54
+ email: string
55
+ displayName: string
56
+ avatar?: string
57
+ role: UserRole
58
+ active: boolean
59
+ }
60
+
61
+ export enum UserRole {
62
+ Admin = 'ADMIN',
63
+ Editor = 'EDITOR',
64
+ Viewer = 'VIEWER',
65
+ }
66
+
67
+ export interface CreateUserRequest {
68
+ username: string
69
+ email: string
70
+ displayName: string
71
+ role: UserRole
72
+ }
73
+
74
+ export interface UpdateUserRequest {
75
+ email?: string
76
+ displayName?: string
77
+ role?: UserRole
78
+ }
79
+ ```
80
+
81
+ ## 组件 Props 类型(Vue 方式)
82
+
83
+ ```vue
84
+ <!-- components/UserAvatar.vue -->
85
+ <script setup lang="ts">
86
+ import type { User } from '@/shared/types/user'
87
+
88
+ interface UserAvatarProps {
89
+ user: Pick<User, 'username' | 'avatar'>
90
+ size?: 'sm' | 'md' | 'lg'
91
+ onClick?: () => void
92
+ }
93
+
94
+ const props = withDefaults(defineProps<UserAvatarProps>(), {
95
+ size: 'md',
96
+ })
97
+
98
+ const emit = defineEmits<{
99
+ click: [user: User]
100
+ }>()
101
+
102
+ function handleClick() {
103
+ if (props.onClick) {
104
+ props.onClick()
105
+ }
106
+ }
107
+ </script>
108
+
109
+ <template>
110
+ <a-avatar
111
+ :src="user.avatar"
112
+ :alt="user.username"
113
+ :size="size === 'sm' ? 24 : size === 'md' ? 32 : 48"
114
+ :style="{ cursor: onClick ? 'pointer' : 'default' }"
115
+ @click="handleClick"
116
+ />
117
+ </template>
118
+ ```
119
+
120
+ ```vue
121
+ <!-- views/UserListPage.vue — 页面组件:默认导出 -->
122
+ <script setup lang="ts">
123
+ import { useUsers } from '@/composables/useUsers'
124
+
125
+ const { users, loading } = useUsers()
126
+ </script>
127
+
128
+ <template>
129
+ <UserTable :users="users" :loading="loading" />
130
+ </template>
131
+ ```
132
+
133
+ ## Discriminated Union 类型
134
+
135
+ ```typescript
136
+ // 用于多状态组件
137
+ export type NotificationType = 'success' | 'error' | 'warning' | 'info'
138
+
139
+ export interface NotificationConfig {
140
+ type: NotificationType
141
+ title: string
142
+ message: string
143
+ duration?: number
144
+ }
145
+ ```
@@ -0,0 +1,231 @@
1
+ # Vue 表单处理示例(Ant Design Vue)
2
+
3
+ ## 创建用户表单
4
+
5
+ ```vue
6
+ <!-- components/CreateUserForm.vue -->
7
+ <script setup lang="ts">
8
+ import { reactive, ref } from 'vue'
9
+ import { message } from 'ant-design-vue'
10
+ import type { UserRole } from '@/shared/types/user'
11
+
12
+ interface FormState {
13
+ username: string
14
+ email: string
15
+ displayName: string
16
+ role: UserRole | undefined
17
+ }
18
+
19
+ const emit = defineEmits<{
20
+ success: [userId: string]
21
+ }>()
22
+
23
+ const formState = reactive<FormState>({
24
+ username: '',
25
+ email: '',
26
+ displayName: '',
27
+ role: undefined,
28
+ })
29
+
30
+ const loading = ref(false)
31
+ const formRef = ref()
32
+
33
+ // 校验规则
34
+ const rules = {
35
+ username: [
36
+ { required: true, message: '请输入用户名', trigger: 'blur' },
37
+ { min: 3, max: 20, message: '用户名长度 3-20 个字符', trigger: 'blur' },
38
+ ],
39
+ email: [
40
+ { required: true, message: '请输入邮箱', trigger: 'blur' },
41
+ { type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' },
42
+ ],
43
+ displayName: [
44
+ { required: true, message: '请输入显示名称', trigger: 'blur' },
45
+ ],
46
+ role: [
47
+ { required: true, message: '请选择角色', trigger: 'change' },
48
+ ],
49
+ }
50
+
51
+ async function handleSubmit() {
52
+ try {
53
+ await formRef.value?.validate()
54
+ loading.value = true
55
+
56
+ const res = await fetch('/api/v1/users', {
57
+ method: 'POST',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify(formState),
60
+ })
61
+
62
+ if (!res.ok) throw new Error('创建失败')
63
+ const user = await res.json()
64
+
65
+ message.success('用户创建成功')
66
+ emit('success', user.id)
67
+ formRef.value?.resetFields()
68
+ } catch (e) {
69
+ if (e instanceof Error) {
70
+ message.error(e.message)
71
+ }
72
+ } finally {
73
+ loading.value = false
74
+ }
75
+ }
76
+
77
+ function handleReset() {
78
+ formRef.value?.resetFields()
79
+ }
80
+ </script>
81
+
82
+ <template>
83
+ <a-form
84
+ ref="formRef"
85
+ :model="formState"
86
+ :rules="rules"
87
+ :label-col="{ span: 6 }"
88
+ :wrapper-col="{ span: 16 }"
89
+ @finish="handleSubmit"
90
+ >
91
+ <a-form-item label="用户名" name="username">
92
+ <a-input v-model:value="formState.username" placeholder="请输入用户名" />
93
+ </a-form-item>
94
+
95
+ <a-form-item label="邮箱" name="email">
96
+ <a-input v-model:value="formState.email" placeholder="请输入邮箱" />
97
+ </a-form-item>
98
+
99
+ <a-form-item label="显示名称" name="displayName">
100
+ <a-input v-model:value="formState.displayName" placeholder="请输入显示名称" />
101
+ </a-form-item>
102
+
103
+ <a-form-item label="角色" name="role">
104
+ <a-select v-model:value="formState.role" placeholder="请选择角色">
105
+ <a-select-option value="ADMIN">管理员</a-select-option>
106
+ <a-select-option value="EDITOR">编辑</a-select-option>
107
+ <a-select-option value="VIEWER">查看者</a-select-option>
108
+ </a-select>
109
+ </a-form-item>
110
+
111
+ <a-form-item :wrapper-col="{ offset: 6, span: 16 }">
112
+ <a-button type="primary" html-type="submit" :loading="loading">
113
+ {{ loading ? '创建中...' : '创建' }}
114
+ </a-button>
115
+ <a-button style="margin-left: 8px" @click="handleReset">
116
+ 重置
117
+ </a-button>
118
+ </a-form-item>
119
+ </a-form>
120
+ </template>
121
+ ```
122
+
123
+ ## 编辑用户信息 Modal
124
+
125
+ ```vue
126
+ <!-- components/EditUserModal.vue -->
127
+ <script setup lang="ts">
128
+ import { reactive, ref, watch } from 'vue'
129
+ import { message, Modal } from 'ant-design-vue'
130
+ import type { User } from '@/shared/types/user'
131
+
132
+ interface EditForm {
133
+ email: string
134
+ displayName: string
135
+ active: boolean
136
+ }
137
+
138
+ const props = defineProps<{
139
+ visible: boolean
140
+ user: User | null
141
+ }>()
142
+
143
+ const emit = defineEmits<{
144
+ close: []
145
+ saved: []
146
+ }>()
147
+
148
+ const formState = reactive<EditForm>({
149
+ email: '',
150
+ displayName: '',
151
+ active: true,
152
+ })
153
+
154
+ const formRef = ref()
155
+ const loading = ref(false)
156
+
157
+ // 监听 user 变化填充表单
158
+ watch(() => props.user, (user) => {
159
+ if (user) {
160
+ formState.email = user.email
161
+ formState.displayName = user.displayName
162
+ formState.active = user.active
163
+ }
164
+ }, { immediate: true })
165
+
166
+ const rules = {
167
+ email: [
168
+ { required: true, message: '请输入邮箱', trigger: 'blur' },
169
+ { type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' },
170
+ ],
171
+ displayName: [
172
+ { required: true, message: '请输入显示名称', trigger: 'blur' },
173
+ ],
174
+ }
175
+
176
+ async function handleOk() {
177
+ try {
178
+ await formRef.value?.validate()
179
+ loading.value = true
180
+
181
+ const res = await fetch(`/api/v1/users/${props.user!.id}`, {
182
+ method: 'PUT',
183
+ headers: { 'Content-Type': 'application/json' },
184
+ body: JSON.stringify({
185
+ email: formState.email,
186
+ displayName: formState.displayName,
187
+ }),
188
+ })
189
+
190
+ if (!res.ok) throw new Error('更新失败')
191
+ message.success('更新成功')
192
+ emit('saved')
193
+ } catch (e) {
194
+ if (e instanceof Error) {
195
+ message.error(e.message)
196
+ }
197
+ } finally {
198
+ loading.value = false
199
+ }
200
+ }
201
+
202
+ function handleCancel() {
203
+ emit('close')
204
+ }
205
+ </script>
206
+
207
+ <template>
208
+ <a-modal
209
+ :visible="visible"
210
+ title="编辑用户"
211
+ :confirm-loading="loading"
212
+ destroy-on-close
213
+ @ok="handleOk"
214
+ @cancel="handleCancel"
215
+ >
216
+ <a-form ref="formRef" :model="formState" :rules="rules" :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
217
+ <a-form-item label="邮箱" name="email">
218
+ <a-input v-model:value="formState.email" />
219
+ </a-form-item>
220
+
221
+ <a-form-item label="显示名称" name="displayName">
222
+ <a-input v-model:value="formState.displayName" />
223
+ </a-form-item>
224
+
225
+ <a-form-item label="状态" name="active">
226
+ <a-switch v-model:checked="formState.active" />
227
+ </a-form-item>
228
+ </a-form>
229
+ </a-modal>
230
+ </template>
231
+ ```