dk-frontend-skills 1.0.2 → 1.0.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.
@@ -47,6 +47,10 @@
47
47
  "security-review": {
48
48
  "enabled": true,
49
49
  "description": "待变更代码安全审查"
50
+ },
51
+ "fe-biz-patterns": {
52
+ "enabled": true,
53
+ "description": "前端业务模式库,loading/滚动加载/导入导出/批量操作/表单联动/大列表渲染/Service层封装/Pinia Store模式/分页数据管理等常见业务场景"
50
54
  }
51
55
  },
52
56
  "always_apply_skills": [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: fe-biz-patterns
3
- description: 前端业务模式库,收录 XiaoMa 在实际业务中沉淀的前端方案和最佳实践。当用户要求实现包含以下特征的功能时使用:滚动加载/无限滚动/触底加载、数据导入导出、批量操作、表单联动、权限控制、大列表渲染、文件上传、实时搜索、拖拽排序等常见前端业务场景。
3
+ description: 前端业务模式库,收录 XiaoMa 在实际业务中沉淀的前端方案和最佳实践。当用户要求实现包含以下特征的功能时使用:loading/加载/加载态、滚动加载/无限滚动/触底加载、数据导入导出、批量操作、表单联动、权限控制、大列表渲染、文件上传、实时搜索、拖拽排序、Service层封装/请求层/axios封装、Pinia Store模式/状态管理/分页数据管理等常见前端业务场景。
4
4
  ---
5
5
 
6
6
  # 前端业务模式库
@@ -13,9 +13,12 @@ XiaoMa 实战沉淀的前端业务方案集合,覆盖日常开发中高频出
13
13
 
14
14
  | 方案 | 适用场景 | 参考文档 |
15
15
  |------|---------|---------|
16
+ | 通用 loading 管理 | 表单提交、数据加载、异步操作的 loading 状态控制 | [use-loading.md](references/use-loading.md) |
16
17
  | 滚动触底自动加载 | 列表滚动加载更多、无限滚动、分页拉取 | [infinite-scroll.md](references/infinite-scroll.md) |
18
+ | Service 层封装 | axios 实例封装、API 模块组织、拦截器管理、多 API 源隔离 | [service-layer.md](references/service-layer.md) |
19
+ | Pinia Store 模式 | Setup Store 格局、分页数据管理、storeToRefs 解构规范 | [pinia-store.md](references/pinia-store.md) |
17
20
 
18
- > **使用方式**:根据用户需求匹配上方方案,点击对应链接阅读完整实现文档,按文档中的模式生成代码。
21
+ > **使用方式**:根据用户需求匹配上方方案,点击对应链接阅读完整实现文档,按文档中的模式生成代码。选择方案后**必须向用户确认**再执行。
19
22
 
20
23
  ## 扩展指南
21
24
 
@@ -25,7 +25,8 @@
25
25
  ## 自定义 Hook
26
26
 
27
27
  ```ts
28
- import { ref, onMounted, onUnmounted, type Ref } from 'vue'
28
+ import { ref, nextTick, onMounted, onUnmounted, type Ref } from 'vue'
29
+ import { useLoading } from '@/composables/useLoading'
29
30
 
30
31
  interface UseInfiniteScrollOptions<T> {
31
32
  /** 分页请求函数,返回当前页的数据数组。返回空数组时视为加载完毕 */
@@ -44,7 +45,7 @@ export function useInfiniteScroll<T>(options: UseInfiniteScrollOptions<T>) {
44
45
  const { fetcher, root, rootMargin = '100px', threshold = 0, immediate = true } = options
45
46
 
46
47
  const data = ref<T[]>([]) as Ref<T[]>
47
- const loading = ref(false)
48
+ const { loading, withLoading } = useLoading() // 详见 [use-loading.md](use-loading.md)
48
49
  const error = ref<Error | null>(null)
49
50
  const isFinished = ref(false)
50
51
  const page = ref(0)
@@ -57,28 +58,25 @@ export function useInfiniteScroll<T>(options: UseInfiniteScrollOptions<T>) {
57
58
  async function loadMore() {
58
59
  if (loading.value || isFinished.value) return
59
60
 
60
- loading.value = true
61
61
  error.value = null
62
62
  const currentPage = page.value + 1
63
63
 
64
64
  try {
65
- const res = await fetcher(currentPage)
66
- // 卸载后丢弃结果
67
- if (stopLoading) return
68
-
69
- if (res.length === 0) {
70
- isFinished.value = true
71
- } else {
72
- data.value.push(...res)
73
- page.value = currentPage
74
- }
65
+ await withLoading(async () => {
66
+ const res = await fetcher(currentPage)
67
+ // 卸载后丢弃结果
68
+ if (stopLoading) return
69
+
70
+ if (res.length === 0) {
71
+ isFinished.value = true
72
+ } else {
73
+ data.value.push(...res)
74
+ page.value = currentPage
75
+ }
76
+ })
75
77
  } catch (e) {
76
78
  if (stopLoading) return
77
79
  error.value = e as Error
78
- } finally {
79
- if (!stopLoading) {
80
- loading.value = false
81
- }
82
80
  }
83
81
  }
84
82
 
@@ -93,7 +91,6 @@ export function useInfiniteScroll<T>(options: UseInfiniteScrollOptions<T>) {
93
91
  page.value = 0
94
92
  isFinished.value = false
95
93
  error.value = null
96
- loading.value = false
97
94
 
98
95
  if (immediate) {
99
96
  await loadMore()
@@ -0,0 +1,174 @@
1
+ # Pinia Store 封装方案
2
+
3
+ ## 概述
4
+
5
+ 基于 Pinia Setup Store(Composition API 风格)的状态管理方案。
6
+
7
+ 核心模式:
8
+ 1. **Setup Store 格局** — `defineStore` + Composition API,天然支持响应式
9
+ 2. **异步 action + loading 分离** — store 只管理数据,loading 状态交给 view 层的 `useLoading`
10
+ 3. **分页数据管理** — 首屏替换 / 翻页追加的通用模式
11
+
12
+ ## 目录结构
13
+
14
+ ```
15
+ src/stores/
16
+ home.ts -- 首页 store
17
+ city.ts -- 城市 store
18
+ ```
19
+
20
+ 每个文件对应一个业务域,与 service/modules 一一对应。
21
+
22
+ ## Setup Store 格局
23
+
24
+ ```ts
25
+ export const useHomeStore = defineStore('home', () => {
26
+ // --- 状态 ---
27
+ const xxx = ref<T>(...)
28
+
29
+ // --- 计算属性(可选) ---
30
+ const xxxName = computed(() => ...)
31
+
32
+ // --- 异步操作 ---
33
+ const fetchXxx = async () => {
34
+ const res = await getXxx()
35
+ xxx.value = res.data || []
36
+ }
37
+
38
+ // --- 同步操作 ---
39
+ const setXxx = (val: T) => { xxx.value = val }
40
+
41
+ return { xxx, xxxName, fetchXxx, setXxx }
42
+ })
43
+ ```
44
+
45
+ ### 命名规范
46
+
47
+ | 项目 | 规范 |
48
+ |------|------|
49
+ | Store 变量 | `use[Name]Store` |
50
+ | Store ID | 小写英文,与文件名一致 |
51
+ | 导出方式 | `export const`(统一使用命名导出) |
52
+ | 状态 | `ref()` 定义原始数据 |
53
+ | 派生 | `computed()` 定义计算属性 |
54
+ | 异步操作 | `fetch` / `load` 前缀 |
55
+ | 同步操作 | `set` / `select` 前缀,动词主导 |
56
+
57
+ ## 异步 Action 模式
58
+
59
+ ### 原则:Store 只管数据,不管 UI 状态
60
+
61
+ store 中的异步函数只负责获取数据、更新状态,不管理 loading/error:
62
+
63
+ ```ts
64
+ const fetchHotSuggests = async () => {
65
+ const res = await getHotSuggests()
66
+ hotSuggests.value = res.data || []
67
+ }
68
+ ```
69
+
70
+ View 层通过 `useLoading` 组合多个 action:
71
+
72
+ ```ts
73
+ // Home.vue
74
+ const { loading: initLoading, withLoading } = useLoading()
75
+
76
+ onMounted(() => {
77
+ withLoading(async () => {
78
+ await fetchHotSuggests()
79
+ await fetchCategories()
80
+ await fetchHouseList()
81
+ })
82
+ })
83
+ ```
84
+
85
+ 这样 store 保持纯数据逻辑,view 控制 UI 状态粒度(可以多个 action 共享一个 loading,也可以各自独立)。
86
+
87
+ ## 分页数据管理
88
+
89
+ ### 核心状态
90
+
91
+ ```ts
92
+ const list = ref<T[]>([]) // 列表数据
93
+ const currentPage = ref(1) // 当前页码
94
+ const hasMore = ref(true) // 是否还有更多
95
+ ```
96
+
97
+ ### 加载函数
98
+
99
+ ```ts
100
+ const fetchList = async (page: number = 1) => {
101
+ const res = await getList(page)
102
+ // 假设后端返回 { errcode: 0, data: [...] }
103
+ if (res.errcode === 0) {
104
+ if (page === 1) {
105
+ list.value = res.data || [] // 首页:替换
106
+ } else {
107
+ list.value.push(...(res.data || [])) // 翻页:追加
108
+ }
109
+ currentPage.value = page
110
+ hasMore.value = (res.data || []).length > 0
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### View 层使用
116
+
117
+ ```vue
118
+ <script setup>
119
+ const { list, currentPage, hasMore } = storeToRefs(store)
120
+ const { fetchList } = store
121
+
122
+ // 首次加载
123
+ onMounted(() => fetchList())
124
+
125
+ // 加载更多
126
+ function onLoadMore() {
127
+ if (!hasMore.value) return
128
+ fetchList(currentPage.value + 1)
129
+ }
130
+ </script>
131
+
132
+ <template>
133
+ <div v-for="item in list" :key="item.id">{{ item.name }}</div>
134
+ <div v-if="hasMore" @click="onLoadMore">加载更多</div>
135
+ </template>
136
+ ```
137
+
138
+ ### 边界处理
139
+
140
+ | 场景 | 处理 |
141
+ |------|------|
142
+ | 空数据 | `res.data || []` 保底,避免 `.push(undefined)` |
143
+ | 已加载完毕 | `hasMore` 为 false 时拦截加载请求 |
144
+ | 搜索条件变化 | 重置 page = 1,重新 fetchList |
145
+ | 后端页码不从 1 开始 | 调整 `page === 1` 的判断逻辑 |
146
+
147
+ ## View 层消费 Store 的方式
148
+
149
+ ### 推荐:解构响应式状态 + 保留 action
150
+
151
+ ```ts
152
+ import { useHomeStore } from '@/stores/home'
153
+ import { storeToRefs } from 'pinia'
154
+
155
+ const store = useHomeStore()
156
+ // 响应式状态:必须用 storeToRefs 包裹,否则会丢失响应性
157
+ const { hotSuggests, houseList, hasMore } = storeToRefs(store)
158
+ // 非响应式方法/action:直接从 store 实例解构
159
+ const { fetchHotSuggests, fetchHouseList } = store
160
+ ```
161
+
162
+ ### storeToRefs 规则
163
+
164
+ | 类型 | 解构方式 | 原因 |
165
+ |------|---------|------|
166
+ | `ref` / `computed` | `storeToRefs(store)` | 保持响应性 |
167
+ | 函数(action) | `store.xxx` 或解构 | 本身就是普通函数,无响应式问题 |
168
+ | 普通值 | `store.xxx` | 非响应式属性 |
169
+
170
+ ## 注意事项
171
+
172
+ 1. **不要在 store 外直接修改 `storeToRefs` 的值**:`storeToRefs` 返回的是 ref,改 `xxx.value = ...` 会直接修改 store 状态。如果只是想取值不改值,用 `toRef(store, 'xxx')` 只读
173
+ 2. **避免 store 循环依赖**:Store A 调用 Store B 的 action 时,在 action 内部 `useXxxStore()`,不要在模块顶层调用
174
+ 3. **SSR 兼容**:`useStore()` 调用要在组件的 `setup` 或 `pinia` 实例已挂载后执行
@@ -0,0 +1,198 @@
1
+ # Service 层封装方案
2
+
3
+ ## 概述
4
+
5
+ 基于 axios 的请求层封装,核心模式:
6
+
7
+ 1. **Request 类 + 单例** — 封装 axios 实例,统一配置和拦截器
8
+ 2. **按业务域拆分模块** — `modules/[domain].ts`,类型与函数共置
9
+ 3. **config 集中管理** — baseURL、超时、状态码映射统一维护
10
+
11
+ ## 目录结构
12
+
13
+ ```
14
+ src/service/
15
+ index.ts -- 统一导出
16
+ request/
17
+ config.ts -- 配置集中管理
18
+ index.ts -- Request 类(axios 封装)
19
+ modules/
20
+ home.ts -- 首页 API
21
+ city.ts -- 城市 API
22
+ ```
23
+
24
+ ## Request 类封装
25
+
26
+ ```ts
27
+ class Request {
28
+ private instance: AxiosInstance
29
+
30
+ constructor() {
31
+ this.instance = axios.create({
32
+ baseURL: config.baseURL,
33
+ timeout: config.timeout,
34
+ headers: config.headers,
35
+ })
36
+ }
37
+
38
+ public get<T>(url, params?, options?): Promise<T>
39
+ public post<T>(url, data?, options?): Promise<T>
40
+ public put<T>(url, data?, options?): Promise<T>
41
+ public delete<T>(url, params?, options?): Promise<T>
42
+ }
43
+
44
+ export const request = new Request()
45
+ ```
46
+
47
+ 关键设计点:
48
+
49
+ - **单例模式**:整个应用共享一个 Request 实例,避免重复创建
50
+ - **泛型方法**:`get<T>` 返回 `Promise<T>`,调用方通过类型参数控制返回值类型
51
+ - **RequestOptions**:扩展 `AxiosRequestConfig`,预留 `showLoading`、`showError` 等业务字段
52
+
53
+ ## 拦截器(可选启用)
54
+
55
+ 拦截器默认不激活,需要时显式调用 `request.enableInterceptors()`:
56
+
57
+ ```ts
58
+ class Request {
59
+ /** 启用拦截器:token 注入 + 统一错误处理 */
60
+ enableInterceptors() {
61
+ // 请求拦截器:自动注入 token
62
+ this.instance.interceptors.request.use((config) => {
63
+ const token = localStorage.getItem('token')
64
+ if (token) {
65
+ config.headers.Authorization = `Bearer ${token}`
66
+ }
67
+ return config
68
+ })
69
+
70
+ // 响应拦截器:解包数据 + 统一错误提示
71
+ this.instance.interceptors.response.use(
72
+ (response) => {
73
+ const { code, message, data } = response.data
74
+ if (code === 0) return data // 成功:直接返回业务数据
75
+ console.error('请求失败:', message)
76
+ return Promise.reject(new Error(message))
77
+ },
78
+ (error) => {
79
+ if (error.response) {
80
+ console.error(statusCodeMap[error.response.status] || '网络请求失败')
81
+ } else {
82
+ console.error('网络连接失败')
83
+ }
84
+ return Promise.reject(error)
85
+ },
86
+ )
87
+ }
88
+ }
89
+ ```
90
+
91
+ 启用后,API 函数的写法会简化——不再需要手动解 `res.data`,因为响应拦截器已经解了一层。
92
+
93
+ ## Config 集中管理
94
+
95
+ ```ts
96
+ // src/service/request/config.ts
97
+ export interface RequestConfig {
98
+ baseURL: string
99
+ timeout: number
100
+ headers?: Record<string, string>
101
+ }
102
+
103
+ export const config: RequestConfig = {
104
+ baseURL: 'http://xxx/api',
105
+ timeout: 10000,
106
+ headers: { 'Content-Type': 'application/json' },
107
+ }
108
+
109
+ export const statusCodeMap: Record<number, string> = {
110
+ 400: '请求参数错误',
111
+ 401: '未授权,请登录',
112
+ 403: '拒绝访问',
113
+ 404: '请求地址不存在',
114
+ 500: '服务器内部错误',
115
+ }
116
+ ```
117
+
118
+ 多 API 源时,在 config 中添加对应配置对象,Request 类中用单独 instance 隔离:
119
+
120
+ ```ts
121
+ export const mapConfig = {
122
+ baseURL: '/api/map',
123
+ key: 'xxx',
124
+ }
125
+
126
+ // request/index.ts
127
+ class Request {
128
+ private mapInstance: AxiosInstance | null = null
129
+
130
+ public mapGet<T>(url, params?): Promise<T> {
131
+ if (!this.mapInstance) {
132
+ this.mapInstance = axios.create({ baseURL: mapConfig.baseURL, timeout: config.timeout })
133
+ }
134
+ return this.mapInstance.get(url, { params })
135
+ }
136
+ }
137
+ ```
138
+
139
+ ## API 模块组织
140
+
141
+ ### 按业务域拆分
142
+
143
+ ```ts
144
+ // src/service/modules/home.ts
145
+ import { request } from '../request'
146
+
147
+ // 类型定义与 API 函数共置
148
+ export interface HouseListItem { /* ... */ }
149
+
150
+ export const getHotSuggests = async () => {
151
+ const res = await request.get('/home/hotSuggests')
152
+ return res.data
153
+ }
154
+
155
+ export const getHouseList = async (page = 1) => {
156
+ const res = await request.get('/home/houselist', { page })
157
+ return res.data
158
+ }
159
+ ```
160
+
161
+ ### 规则
162
+
163
+ | 原则 | 说明 |
164
+ |------|------|
165
+ | 一域一文件 | 每个业务模块一个文件,命名与后端资源对应 |
166
+ | 类型共置 | 接口响应类型定义在 API 函数同一文件,就近维护 |
167
+ | 函数即接口 | 每个 API 导出一个 async 函数,参数即业务参数 |
168
+ | 统一导出 | `service/index.ts` 只导出 request 实例和核心类型,modules 由消费方按需 import |
169
+
170
+ ## View 层消费方式
171
+
172
+ ```ts
173
+ import { getHotSuggests } from '@/service/modules/home'
174
+
175
+ onMounted(async () => {
176
+ const res = await getHotSuggests()
177
+ hotSuggests.value = res.data || []
178
+ })
179
+ ```
180
+
181
+ 或通过 store 间接调用:
182
+
183
+ ```ts
184
+ // store 中调用 service
185
+ const fetchHotSuggests = async () => {
186
+ const res = await getHotSuggests()
187
+ hotSuggests.value = res.data || []
188
+ }
189
+
190
+ // view 中调用 store action
191
+ onMounted(() => store.fetchHotSuggests())
192
+ ```
193
+
194
+ ## 注意事项
195
+
196
+ 1. **拦截器启用时机**:`enableInterceptors()` 应在应用初始化时(如 `main.ts`)调用一次,避免重复注册
197
+ 2. **响应拦截器副作用**:启用后 API 函数返回的是解包后的业务数据,而非完整 AxiosResponse,与之配套的 store 逻辑需要同步调整
198
+ 3. **泛型参数**:`request.get<T>` 的 `T` 只是类型标注,不会在运行时做校验,后端返回格式异常仍需前端容错
@@ -0,0 +1,114 @@
1
+ # 通用 Loading 管理方案
2
+
3
+ ## 概述
4
+
5
+ 异步操作的 loading 状态管理是前端最基础的需求之一。`useLoading` 是一个极简的通用 loading 包装器,用最小的代码量解决 loading 状态控制问题。
6
+
7
+ ## 源码
8
+
9
+ ```ts
10
+ // src/composables/useLoading.ts
11
+ import { ref } from 'vue'
12
+
13
+ export function useLoading(initial = false) {
14
+ const loading = ref(initial)
15
+
16
+ async function withLoading<T>(fn: () => Promise<T>): Promise<T> {
17
+ loading.value = true
18
+ try {
19
+ return await fn()
20
+ } finally {
21
+ loading.value = false
22
+ }
23
+ }
24
+
25
+ return { loading, withLoading }
26
+ }
27
+ ```
28
+
29
+ 核心逻辑只有一行:执行前设 `true`,finally 中设回 `false`,保证无论成功还是失败 loading 都能复位。
30
+
31
+ ## 使用示例
32
+
33
+ ### 表单提交
34
+
35
+ ```vue
36
+ <script setup lang="ts">
37
+ const { loading, withLoading } = useLoading()
38
+
39
+ const onSubmit = async () => {
40
+ await withLoading(() => updateUser(form.value))
41
+ }
42
+ </script>
43
+
44
+ <template>
45
+ <van-button :loading="loading" @click="onSubmit">提交</van-button>
46
+ </template>
47
+ ```
48
+
49
+ ### 初始数据加载
50
+
51
+ ```vue
52
+ <script setup lang="ts">
53
+ const { loading, withLoading } = useLoading(true) // 初始就为 true
54
+
55
+ onMounted(() => {
56
+ withLoading(async () => {
57
+ await fetchUserInfo()
58
+ await fetchPermissions()
59
+ })
60
+ })
61
+ </script>
62
+
63
+ <template>
64
+ <BaseLoading :loading="loading" type="spinner" text="正在加载..." vertical />
65
+ <template v-if="!loading">
66
+ <!-- 主体内容 -->
67
+ </template>
68
+ </template>
69
+ ```
70
+
71
+ ### 多个独立 loading(初始加载 + 加载更多)
72
+
73
+ ```vue
74
+ <script setup lang="ts">
75
+ // 首次加载
76
+ const { loading: initLoading, withLoading: withInitLoading } = useLoading()
77
+ // 加载更多
78
+ const { loading: moreLoading, withLoading: withMoreLoading } = useLoading()
79
+
80
+ const onLoadMore = () => {
81
+ withMoreLoading(() => fetchHouseList(page + 1))
82
+ }
83
+
84
+ onMounted(() => {
85
+ withInitLoading(async () => {
86
+ await fetchHotSuggests()
87
+ await fetchCategories()
88
+ await fetchHouseList()
89
+ })
90
+ })
91
+ </script>
92
+
93
+ <template>
94
+ <!-- 首次加载用全屏 loading -->
95
+ <BaseLoading :loading="initLoading" type="spinner" vertical />
96
+ <!-- 加载更多只影响底部 -->
97
+ <div v-if="moreLoading">加载更多...</div>
98
+ </template>
99
+ ```
100
+
101
+ ## 常见问题
102
+
103
+ | 场景 | 处理方式 |
104
+ |------|---------|
105
+ | 重复点击提交 | `loading` 为 true 时按钮置灰 / `:loading` 属性,无需额外逻辑 |
106
+ | 多个请求并行 | 需要分别管理 loading 时创建多个 `useLoading` 实例 |
107
+ | 组件卸载 | 不影响,`withLoading` 的 finally 中赋值不会有问题(Vue 会处理) |
108
+ | 需要 loading 之外的状态 | 搭配 `error`、`data` 等额外 ref 使用,`useLoading` 只负责 loading |
109
+
110
+ ## 注意事项
111
+
112
+ 1. **不要滥用**:简单的 Boolean 控制直接写模板里就行,不需要每个异步操作都抽成一个 composable
113
+ 2. **区分 loading 粒度**:页面级 loading 用一个实例,列表加载更多用另一个实例,互不干扰
114
+ 3. **`withLoading` 的返回值**:它返回的是 `fn()` 的 Promise 结果,可以用它链式处理后续逻辑
package/CLAUDE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dk-engineer
3
- description: 幽默的沉稳靠谱秘书,专注于高质量代码输出和清晰的任务执行。
3
+ description: 幽默的沉稳靠谱助手,专注于高质量代码输出和清晰的任务执行。
4
4
  ---
5
5
 
6
6
  # 开发助手配置说明
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dk-frontend-skills",
3
- "version": "1.0.2",
4
- "description": "dk-engineer - 幽默沉稳靠谱的 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
3
+ "version": "1.0.4",
4
+ "description": "dk-engineer - 幽默沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
5
5
  "author": "XiaoMa",
6
6
  "license": "MIT",
7
7
  "private": false,
@@ -1,5 +1,6 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
+ const { execSync } = require("child_process");
3
4
 
4
5
  // 获取用户项目根目录
5
6
  const projectRoot = path.resolve(__dirname, "..", "..", "..");
@@ -66,3 +67,7 @@ console.log("📁 生成文件:");
66
67
  console.log(" - .claude/settings.json");
67
68
  console.log(" - .claude/skills/ (含所有技能)");
68
69
  console.log(" - CLAUDE.md");
70
+
71
+ // 拷贝完成,不再需要本包,自动卸载
72
+ console.log("🧹 清理安装包...");
73
+ execSync("npm uninstall dk-frontend-skills", { cwd: projectRoot, stdio: "inherit" });