@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,353 @@
1
+ # Ant Design Vue 实战示例
2
+
3
+ ## 完整数据表格(搜索 + 分页 + 操作列)
4
+
5
+ ```vue
6
+ <!-- components/UserTable.vue -->
7
+ <script setup lang="ts">
8
+ import { ref, reactive, watch } from 'vue'
9
+ import { message } from 'ant-design-vue'
10
+ import type { User } from '@/shared/types/user'
11
+
12
+ interface Filters {
13
+ search: string
14
+ role: string | undefined
15
+ }
16
+
17
+ const props = defineProps<{
18
+ users: User[]
19
+ total: number
20
+ loading: boolean
21
+ }>()
22
+
23
+ const emit = defineEmits<{
24
+ search: [filters: Filters]
25
+ pageChange: [page: number, pageSize: number]
26
+ edit: [user: User]
27
+ delete: [userId: string]
28
+ }>()
29
+
30
+ const filters = reactive<Filters>({
31
+ search: '',
32
+ role: undefined,
33
+ })
34
+
35
+ const pagination = reactive({
36
+ current: 1,
37
+ pageSize: 20,
38
+ })
39
+
40
+ // 列定义
41
+ const columns = [
42
+ { title: '用户名', dataIndex: 'username', key: 'username' },
43
+ { title: '邮箱', dataIndex: 'email', key: 'email' },
44
+ { title: '角色', dataIndex: 'role', key: 'role' },
45
+ { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt' },
46
+ {
47
+ title: '操作',
48
+ key: 'action',
49
+ customRender: ({ record }: { record: User }) => (
50
+ <span>
51
+ <a onClick={() => emit('edit', record)}>编辑</a>
52
+ <a-divider type="vertical" />
53
+ <a-popconfirm title="确定删除?" onConfirm={() => emit('delete', record.id)}>
54
+ <a>删除</a>
55
+ </a-popconfirm>
56
+ </span>
57
+ ),
58
+ },
59
+ ]
60
+
61
+ // 搜索
62
+ let searchTimer: ReturnType<typeof setTimeout>
63
+ function handleSearch(value: string) {
64
+ clearTimeout(searchTimer)
65
+ searchTimer = setTimeout(() => {
66
+ filters.search = value
67
+ pagination.current = 1
68
+ emit('search', { ...filters })
69
+ }, 300)
70
+ }
71
+
72
+ // 分页变化
73
+ function handleTableChange(pag: { current: number; pageSize: number }) {
74
+ pagination.current = pag.current
75
+ pagination.pageSize = pag.pageSize
76
+ emit('pageChange', pag.current, pag.pageSize)
77
+ }
78
+
79
+ // 批量删除
80
+ const selectedRowKeys = ref<string[]>([])
81
+
82
+ function handleBatchDelete() {
83
+ if (selectedRowKeys.value.length === 0) {
84
+ message.warning('请先选择要删除的用户')
85
+ return
86
+ }
87
+ Modal.confirm({
88
+ title: '批量删除',
89
+ content: `确定要删除选中的 ${selectedRowKeys.value.length} 名用户吗?`,
90
+ onOk: async () => {
91
+ // 批量删除逻辑
92
+ message.success(`已删除 ${selectedRowKeys.value.length} 名用户`)
93
+ selectedRowKeys.value = []
94
+ },
95
+ })
96
+ }
97
+ </script>
98
+
99
+ <template>
100
+ <div class="space-y-4">
101
+ <!-- 搜索栏 -->
102
+ <div class="flex justify-between items-center">
103
+ <a-input-search
104
+ placeholder="搜索用户名/邮箱"
105
+ style="width: 300px"
106
+ @search="handleSearch"
107
+ />
108
+ <div class="flex gap-2">
109
+ <a-button
110
+ danger
111
+ :disabled="selectedRowKeys.length === 0"
112
+ @click="handleBatchDelete"
113
+ >
114
+ 批量删除
115
+ </a-button>
116
+ <a-button type="primary">新增用户</a-button>
117
+ </div>
118
+ </div>
119
+
120
+ <!-- 表格 -->
121
+ <a-table
122
+ :data-source="users"
123
+ :columns="columns"
124
+ :loading="loading"
125
+ :pagination="{
126
+ ...pagination,
127
+ total,
128
+ showSizeChanger: true,
129
+ showTotal: (total: number) => `共 ${total} 条`,
130
+ }"
131
+ :row-selection="{
132
+ selectedRowKeys,
133
+ onChange: (keys: string[]) => { selectedRowKeys = keys },
134
+ }"
135
+ row-key="id"
136
+ :locale="{ emptyText: '暂无用户数据' }"
137
+ @change="handleTableChange"
138
+ />
139
+ </div>
140
+ </template>
141
+ ```
142
+
143
+ ## 表单模态框(创建/编辑)
144
+
145
+ ```vue
146
+ <!-- components/UserFormModal.vue -->
147
+ <script setup lang="ts">
148
+ import { reactive, ref, watch } from 'vue'
149
+ import { message, Modal } from 'ant-design-vue'
150
+ import type { User } from '@/shared/types/user'
151
+
152
+ interface FormData {
153
+ username: string
154
+ email: string
155
+ displayName: string
156
+ role: string
157
+ active: boolean
158
+ }
159
+
160
+ const props = defineProps<{
161
+ visible: boolean
162
+ user: User | null // null 表示创建模式
163
+ }>()
164
+
165
+ const emit = defineEmits<{
166
+ close: []
167
+ saved: []
168
+ }>()
169
+
170
+ const formRef = ref()
171
+ const loading = ref(false)
172
+ const isEdit = ref(false)
173
+
174
+ const formData = reactive<FormData>({
175
+ username: '',
176
+ email: '',
177
+ displayName: '',
178
+ role: 'EDITOR',
179
+ active: true,
180
+ })
181
+
182
+ // 编辑模式填充数据
183
+ watch(() => props.user, (user) => {
184
+ isEdit.value = !!user
185
+ if (user) {
186
+ formData.username = user.username
187
+ formData.email = user.email
188
+ formData.displayName = user.displayName
189
+ formData.role = user.role
190
+ formData.active = user.active
191
+ } else {
192
+ formData.username = ''
193
+ formData.email = ''
194
+ formData.displayName = ''
195
+ formData.role = 'EDITOR'
196
+ formData.active = true
197
+ }
198
+ }, { immediate: true })
199
+
200
+ const rules = {
201
+ username: [
202
+ { required: true, message: '请输入用户名', trigger: 'blur' },
203
+ { min: 3, max: 20, message: '用户名长度 3-20 个字符', trigger: 'blur' },
204
+ ],
205
+ email: [
206
+ { required: true, message: '请输入邮箱', trigger: 'blur' },
207
+ { type: 'email', message: '请输理有效的邮箱地址', trigger: 'blur' },
208
+ ],
209
+ displayName: [
210
+ { required: true, message: '请输入显示名称', trigger: 'blur' },
211
+ ],
212
+ role: [
213
+ { required: true, message: '请选择角色', trigger: 'change' },
214
+ ],
215
+ }
216
+
217
+ async function handleOk() {
218
+ try {
219
+ await formRef.value?.validate()
220
+ loading.value = true
221
+
222
+ const url = isEdit.value ? `/api/v1/users/${props.user!.id}` : '/api/v1/users'
223
+ const method = isEdit.value ? 'PUT' : 'POST'
224
+
225
+ const res = await fetch(url, {
226
+ method,
227
+ headers: { 'Content-Type': 'application/json' },
228
+ body: JSON.stringify(formData),
229
+ })
230
+
231
+ if (!res.ok) throw new Error(isEdit.value ? '更新失败' : '创建失败')
232
+
233
+ message.success(isEdit.value ? '更新成功' : '创建成功')
234
+ emit('saved')
235
+ } catch (e) {
236
+ if (e instanceof Error) {
237
+ message.error(e.message)
238
+ }
239
+ } finally {
240
+ loading.value = false
241
+ }
242
+ }
243
+
244
+ function handleCancel() {
245
+ emit('close')
246
+ }
247
+ </script>
248
+
249
+ <template>
250
+ <a-modal
251
+ :visible="visible"
252
+ :title="isEdit ? '编辑用户' : '新建用户'"
253
+ :confirm-loading="loading"
254
+ destroy-on-close
255
+ @ok="handleOk"
256
+ @cancel="handleCancel"
257
+ >
258
+ <a-form
259
+ ref="formRef"
260
+ :model="formData"
261
+ :rules="rules"
262
+ :label-col="{ span: 6 }"
263
+ :wrapper-col="{ span: 16 }"
264
+ >
265
+ <a-form-item label="用户名" name="username">
266
+ <a-input
267
+ v-model:value="formData.username"
268
+ :disabled="isEdit"
269
+ placeholder="请输入用户名"
270
+ />
271
+ </a-form-item>
272
+
273
+ <a-form-item label="邮箱" name="email">
274
+ <a-input v-model:value="formData.email" placeholder="请输入邮箱" />
275
+ </a-form-item>
276
+
277
+ <a-form-item label="显示名称" name="displayName">
278
+ <a-input v-model:value="formData.displayName" placeholder="请输入显示名称" />
279
+ </a-form-item>
280
+
281
+ <a-form-item label="角色" name="role">
282
+ <a-select v-model:value="formData.role">
283
+ <a-select-option value="ADMIN">管理员</a-select-option>
284
+ <a-select-option value="EDITOR">编辑</a-select-option>
285
+ <a-select-option value="VIEWER">查看者</a-select-option>
286
+ </a-select>
287
+ </a-form-item>
288
+
289
+ <a-form-item label="状态" name="active">
290
+ <a-switch v-model:checked="formData.active" />
291
+ </a-form-item>
292
+ </a-form>
293
+ </a-modal>
294
+ </template>
295
+ ```
296
+
297
+ ## 响应式布局仪表板
298
+
299
+ ```vue
300
+ <!-- views/Dashboard.vue -->
301
+ <script setup lang="ts">
302
+ import { ref, onMounted } from 'vue'
303
+
304
+ const stats = ref([
305
+ { title: '用户总数', value: 1284, icon: 'users' },
306
+ { title: '今日活跃', value: 356, icon: 'activity' },
307
+ { title: '待处理工单', value: 23, icon: 'clock' },
308
+ { title: '系统消息', value: 7, icon: 'message' },
309
+ ])
310
+ </script>
311
+
312
+ <template>
313
+ <div class="p-6 space-y-6">
314
+ <!-- 统计卡片 -->
315
+ <a-row :gutter="[16, 16]">
316
+ <a-col :xs="24" :sm="12" :lg="6" v-for="stat in stats" :key="stat.title">
317
+ <a-card hoverable>
318
+ <div class="flex items-center justify-between">
319
+ <div>
320
+ <p class="text-gray-500 text-sm">{{ stat.title }}</p>
321
+ <p class="text-2xl font-bold mt-1">{{ stat.value }}</p>
322
+ </div>
323
+ <div class="text-3xl text-blue-500">
324
+ <!-- 根据 stat.icon 渲染对应图标 -->
325
+ </div>
326
+ </div>
327
+ </a-card>
328
+ </a-col>
329
+ </a-row>
330
+
331
+ <!-- 图表区域 -->
332
+ <a-row :gutter="[16, 16]">
333
+ <a-col :xs="24" :lg="16">
334
+ <a-card title="访问趋势">
335
+ <div style="height: 300px">
336
+ <!-- 图表组件 -->
337
+ </div>
338
+ </a-card>
339
+ </a-col>
340
+ <a-col :xs="24" :lg="8">
341
+ <a-card title="最近操作">
342
+ <a-timeline>
343
+ <a-timeline-item color="green">新建用户 alice</a-timeline-item>
344
+ <a-timeline-item color="blue">更新配置</a-timeline-item>
345
+ <a-timeline-item color="red">删除过期数据</a-timeline-item>
346
+ <a-timeline-item>系统备份完成</a-timeline-item>
347
+ </a-timeline>
348
+ </a-card>
349
+ </a-col>
350
+ </a-row>
351
+ </div>
352
+ </template>
353
+ ```
@@ -0,0 +1,267 @@
1
+ # Ant Design Vue 4.x 组件使用规范
2
+
3
+ ## 通用原则
4
+
5
+ - Ant Design Vue 4.x 兼容 Vue 3,组件需通过 `unplugin-vue-components` 配合 `AntDesignVueResolver` 实现自动按需导入
6
+ - 模板中使用 `a-` 前缀组件:`<a-button>`、`<a-table>`、`<a-form>`
7
+ - 程序化 API(`message`、`Modal`、`notification`)需从 `ant-design-vue` 包手动导入
8
+ - 主题定制通过 `<a-config-provider>` 的 `:theme` 属性配置
9
+ - 本地化通过 `<a-config-provider>` 的 `:locale` 属性设置,导入 `ant-design-vue/es/locale/zh_CN`
10
+
11
+ ### Vite 自动导入配置
12
+
13
+ ```typescript
14
+ // vite.config.ts
15
+ import Components from 'unplugin-vue-components/vite'
16
+ import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'
17
+
18
+ export default {
19
+ plugins: [
20
+ Components({
21
+ resolvers: [AntDesignVueResolver()],
22
+ }),
23
+ ],
24
+ }
25
+ ```
26
+
27
+ ## 常用组件规范
28
+
29
+ ### Button(按钮)
30
+ - 主要操作使用 `type="primary"`,次要操作默认
31
+ - 危险操作使用 `danger` 属性
32
+ - 异步操作必须设置 `:loading` 状态防重复提交
33
+ - 纯图标按钮必须有 `aria-label` 提供无障碍说明
34
+
35
+ ```vue
36
+ <a-button type="primary" :loading="submitting" @click="handleSubmit">
37
+ 提交
38
+ </a-button>
39
+ <a-button danger @click="handleDelete">删除</a-button>
40
+ <a-button aria-label="搜索">
41
+ <template #icon><SearchOutlined /></template>
42
+ </a-button>
43
+ ```
44
+
45
+ ### Table(表格)
46
+ - 始终指定 `row-key` 为唯一标识字段(如 `id`)
47
+ - 列表格使用 `:pagination` 属性控制分页
48
+ - 空数据使用 `:locale` 自定义空状态提示:`{ emptyText: '暂无数据' }`
49
+ - 后端分页使用 `@change` 事件 + `:pagination.current/pageSize` 受控
50
+
51
+ ```vue
52
+ <a-table
53
+ :data-source="users"
54
+ :columns="columns"
55
+ row-key="id"
56
+ :loading="loading"
57
+ :pagination="{
58
+ current: page,
59
+ pageSize: 20,
60
+ total,
61
+ showSizeChanger: true,
62
+ showTotal: (total: number) => `共 ${total} 条`,
63
+ }"
64
+ @change="(pagination: any) => {
65
+ page = pagination.current
66
+ pageSize = pagination.pageSize
67
+ }"
68
+ :locale="{ emptyText: '暂无数据' }"
69
+ />
70
+ ```
71
+
72
+ ### Form(表单)
73
+ - 使用 `layout="vertical"` 默认垂直布局
74
+ - 所有校验规则集中在 `:rules` 中定义,避免使用自定义 validator(除非复杂业务)
75
+ - `<a-form-item>` 的 `name` 必须与提交数据结构的字段名一致
76
+ - 编辑表单用 `ref.value?.setFieldsValue()` 填充初始值
77
+ - 表单数据通过 `v-model:value` 双向绑定
78
+ - 获取表单引用:`const formRef = ref()`
79
+
80
+ ```vue
81
+ <script setup lang="ts">
82
+ import { reactive, ref } from 'vue'
83
+
84
+ const formState = reactive({
85
+ email: '',
86
+ name: '',
87
+ })
88
+ const formRef = ref()
89
+
90
+ const rules = {
91
+ email: [
92
+ { required: true, message: '请输入邮箱', trigger: 'blur' },
93
+ { type: 'email', message: '邮箱格式不正确', trigger: 'blur' },
94
+ ],
95
+ name: [
96
+ { required: true, message: '请输入姓名', trigger: 'blur' },
97
+ ],
98
+ }
99
+
100
+ async function handleSubmit() {
101
+ try {
102
+ await formRef.value?.validate()
103
+ // 提交逻辑
104
+ } catch (e) {
105
+ // 校验失败
106
+ }
107
+ }
108
+ </script>
109
+
110
+ <template>
111
+ <a-form
112
+ ref="formRef"
113
+ :model="formState"
114
+ :rules="rules"
115
+ layout="vertical"
116
+ @finish="handleSubmit"
117
+ >
118
+ <a-form-item name="email" label="邮箱">
119
+ <a-input v-model:value="formState.email" placeholder="请输入邮箱" />
120
+ </a-form-item>
121
+ </a-form>
122
+ </template>
123
+ ```
124
+
125
+ ### Modal(弹窗)
126
+ - 表单弹窗用 `destroy-on-close` 确保关闭后重置状态
127
+ - `@cancel` 用于关闭回调
128
+ - 确认弹窗用 `Modal.confirm()` 快捷方法
129
+
130
+ ```vue
131
+ <a-modal
132
+ title="编辑用户"
133
+ :visible="isOpen"
134
+ @ok="handleSave"
135
+ @cancel="handleCancel"
136
+ :confirm-loading="saving"
137
+ destroy-on-close
138
+ >
139
+ <UserForm :user="editingUser" />
140
+ </a-modal>
141
+
142
+ <!-- 程序化确认弹窗 -->
143
+ <script setup lang="ts">
144
+ import { Modal } from 'ant-design-vue'
145
+
146
+ Modal.confirm({
147
+ title: '确认删除',
148
+ content: '确定要删除该用户吗?此操作不可恢复。',
149
+ okText: '确认删除',
150
+ okType: 'danger',
151
+ onOk: () => deleteUser(id),
152
+ })
153
+ </script>
154
+ ```
155
+
156
+ ### Select(选择器)
157
+ - 选项少时直接写 `<a-select-option>`;选项多时使用 `:options` 数组属性
158
+ - 远程搜索使用 `show-search` + `@search` + `:filter-option` 自定义
159
+ - 多选用 `mode="multiple"`
160
+ - 使用 `v-model:value` 双向绑定
161
+
162
+ ```vue
163
+ <a-select v-model:value="formState.role" placeholder="请选择角色">
164
+ <a-select-option value="ADMIN">管理员</a-select-option>
165
+ <a-select-option value="EDITOR">编辑</a-select-option>
166
+ </a-select>
167
+
168
+ <!-- 远程搜索 -->
169
+ <a-select
170
+ v-model:value="selectedUser"
171
+ show-search
172
+ :filter-option="false"
173
+ @search="handleSearch"
174
+ >
175
+ <a-select-option v-for="u in users" :key="u.id" :value="u.id">
176
+ {{ u.username }}
177
+ </a-select-option>
178
+ </a-select>
179
+ ```
180
+
181
+ ### DatePicker(日期选择器)
182
+ - 使用 `format` 指定显示格式
183
+ - 表单中配合 `<a-form-item>` 使用,值类型为 `dayjs` 对象
184
+ - 使用 `v-model:value` 双向绑定
185
+ - 范围选择用 `<a-range-picker>`
186
+
187
+ ```vue
188
+ <a-date-picker v-model:value="date" format="YYYY-MM-DD" />
189
+ <a-range-picker v-model:value="dateRange" format="YYYY-MM-DD" />
190
+ ```
191
+
192
+ ### Space(间距)
193
+ - 组件间间距优先使用 `<a-space>` 包裹,避免手动 `margin`
194
+ - 默认间距为 `size="small"`(8px),可根据上下文调整
195
+
196
+ ### message / notification(消息提示)
197
+ - 需从 `ant-design-vue` 包手动导入
198
+ - 操作反馈使用 `message.success()` / `message.error()` 轻量提示
199
+ - 重要通知使用 `notification.open()` / `notification.info()`
200
+
201
+ ```vue
202
+ <script setup lang="ts">
203
+ import { message } from 'ant-design-vue'
204
+
205
+ function handleSuccess() {
206
+ message.success('操作成功')
207
+ }
208
+
209
+ function handleError() {
210
+ message.error('操作失败')
211
+ }
212
+ </script>
213
+ ```
214
+
215
+ ### Spin(加载中)
216
+ - 页面级加载使用 `<a-spin>` 包裹内容区域
217
+ - 组件级加载使用 Spin 组件的 `:spinning` 属性
218
+
219
+ ```vue
220
+ <a-spin :spinning="loading">
221
+ <div>内容区域</div>
222
+ </a-spin>
223
+ ```
224
+
225
+ ## 主题定制
226
+
227
+ ```vue
228
+ <template>
229
+ <a-config-provider
230
+ :locale="zhCN"
231
+ :theme="{
232
+ token: {
233
+ colorPrimary: '#1677ff',
234
+ borderRadius: 6,
235
+ },
236
+ components: {
237
+ Table: {
238
+ headerBg: '#fafafa',
239
+ rowHoverBg: '#f5f5f5',
240
+ },
241
+ },
242
+ }"
243
+ >
244
+ <RouterView />
245
+ </a-config-provider>
246
+ </template>
247
+
248
+ <script setup lang="ts">
249
+ import zhCN from 'ant-design-vue/es/locale/zh_CN'
250
+ import dayjs from 'dayjs'
251
+ import 'dayjs/locale/zh-cn'
252
+
253
+ dayjs.locale('zh-cn')
254
+ </script>
255
+ ```
256
+
257
+ ## Ant Design Vue + Tailwind CSS 共存
258
+
259
+ - Ant Design Vue 负责组件内部样式,Tailwind 负责页面/组件外部的布局和间距
260
+ - 避免在 Ant Design Vue 组件上直接用 Tailwind 覆盖内联样式
261
+ - 布局使用 Tailwind 的 `flex`/`grid` 类 + `<a-row>` / `<a-col>` 搭配
262
+ - 使用 Tailwind 的 `dark:` 变体时,需同时配置 `a-config-provider` 的暗色主题
263
+
264
+ ## 性能注意
265
+
266
+ - 大列表(>500 行)考虑分页或虚拟滚动
267
+ - 避免频繁调用 `message` / `notification`