@elf-express/admin-net-mcp 1.0.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/README.md +72 -0
- package/dist/index.js +152 -0
- package/dist/knowledge/attributes.js +158 -0
- package/dist/knowledge/config.js +152 -0
- package/dist/knowledge/entity.js +120 -0
- package/dist/knowledge/event.js +129 -0
- package/dist/knowledge/plugin.js +141 -0
- package/dist/knowledge/service.js +151 -0
- package/dist/knowledge/sqlsugar.js +177 -0
- package/dist/knowledge/vue-typescript.js +343 -0
- package/package.json +34 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.vueTypescriptGuide = void 0;
|
|
4
|
+
exports.vueTypescriptGuide = `
|
|
5
|
+
# Vue 3 + TypeScript 採坑記錄
|
|
6
|
+
|
|
7
|
+
本文記錄 Admin.NET 前端(Vue 3 + Element Plus + Vite)開發中實際踩過的坑。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 一、響應式(Reactivity)
|
|
12
|
+
|
|
13
|
+
### 坑 1:解構 reactive 物件後失去響應性
|
|
14
|
+
\`\`\`typescript
|
|
15
|
+
// ❌ 錯誤:解構後 name 不是響應式
|
|
16
|
+
const state = reactive({ name: '張三', age: 18 })
|
|
17
|
+
const { name } = state // name 是普通字串,不會更新
|
|
18
|
+
|
|
19
|
+
// ✅ 正確方式一:用 toRefs
|
|
20
|
+
const { name } = toRefs(state)
|
|
21
|
+
|
|
22
|
+
// ✅ 正確方式二:直接用 ref
|
|
23
|
+
const name = ref('張三')
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
### 坑 2:ref 物件忘記 .value
|
|
27
|
+
\`\`\`typescript
|
|
28
|
+
// ❌ 常見錯誤
|
|
29
|
+
const count = ref(0)
|
|
30
|
+
count = 5 // 這會替換掉 ref 本身!
|
|
31
|
+
|
|
32
|
+
// ✅ 正確
|
|
33
|
+
count.value = 5
|
|
34
|
+
|
|
35
|
+
// template 中不需要 .value(自動解包)
|
|
36
|
+
// <span>{{ count }}</span> ← 正確
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
### 坑 3:watch 監聽物件屬性需要 getter
|
|
40
|
+
\`\`\`typescript
|
|
41
|
+
const user = reactive({ name: '張三' })
|
|
42
|
+
|
|
43
|
+
// ❌ 不會觸發(監聽的是初始值字串)
|
|
44
|
+
watch(user.name, (newVal) => { })
|
|
45
|
+
|
|
46
|
+
// ✅ 用 getter 函式
|
|
47
|
+
watch(() => user.name, (newVal) => { })
|
|
48
|
+
|
|
49
|
+
// ✅ 監聽整個 reactive 物件(深層)
|
|
50
|
+
watch(user, (newVal) => { }, { deep: true })
|
|
51
|
+
\`\`\`
|
|
52
|
+
|
|
53
|
+
### 坑 4:陣列操作不觸發更新
|
|
54
|
+
\`\`\`typescript
|
|
55
|
+
const list = ref<string[]>([])
|
|
56
|
+
|
|
57
|
+
// ❌ 直接賦值 index 不觸發
|
|
58
|
+
list.value[0] = '新值'
|
|
59
|
+
|
|
60
|
+
// ✅ 用 splice
|
|
61
|
+
list.value.splice(0, 1, '新值')
|
|
62
|
+
|
|
63
|
+
// ✅ 或整個陣列替換
|
|
64
|
+
list.value = [...list.value.slice(0, 0), '新值', ...list.value.slice(1)]
|
|
65
|
+
|
|
66
|
+
// ✅ push/pop/shift/splice 等方法都能正確觸發
|
|
67
|
+
list.value.push('新項目')
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 二、TypeScript 型別陷阱
|
|
73
|
+
|
|
74
|
+
### 坑 5:Event 型別錯誤
|
|
75
|
+
\`\`\`typescript
|
|
76
|
+
// ❌ Event 沒有 target.value
|
|
77
|
+
const handleInput = (e: Event) => {
|
|
78
|
+
console.log(e.target.value) // TS 報錯:Object is possibly null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ✅ 明確型別
|
|
82
|
+
const handleInput = (e: Event) => {
|
|
83
|
+
const target = e.target as HTMLInputElement
|
|
84
|
+
console.log(target.value)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ✅ Element Plus 元件直接拿值
|
|
88
|
+
const handleChange = (value: string) => {
|
|
89
|
+
// el-input 的 change 事件直接傳值,不是 Event
|
|
90
|
+
}
|
|
91
|
+
\`\`\`
|
|
92
|
+
|
|
93
|
+
### 坑 6:Ref 泛型推斷失敗
|
|
94
|
+
\`\`\`typescript
|
|
95
|
+
// ❌ 初始 null 時 TS 無法推斷型別
|
|
96
|
+
const userInfo = ref(null)
|
|
97
|
+
userInfo.value.name // 報錯:Object is possibly null
|
|
98
|
+
|
|
99
|
+
// ✅ 明確指定泛型
|
|
100
|
+
interface UserInfo { name: string; age: number }
|
|
101
|
+
const userInfo = ref<UserInfo | null>(null)
|
|
102
|
+
|
|
103
|
+
// 使用時加判斷
|
|
104
|
+
if (userInfo.value) {
|
|
105
|
+
console.log(userInfo.value.name) // OK
|
|
106
|
+
}
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
### 坑 7:Snowflake ID 精度問題(Admin.NET 專屬!)
|
|
110
|
+
\`\`\`typescript
|
|
111
|
+
// ❌ 後端回傳的 long ID 在 JS 中會丟失精度
|
|
112
|
+
// 例:8107821456789012345 → 8107821456789012000(最後幾位變0)
|
|
113
|
+
|
|
114
|
+
// ✅ Admin.NET 後端已設定 Long→String 序列化
|
|
115
|
+
// 前端所有 ID 欄位必須宣告為 string,不是 number
|
|
116
|
+
interface SysUser {
|
|
117
|
+
id: string // ✅ string
|
|
118
|
+
// id: number // ❌ 會丟精度
|
|
119
|
+
name: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// API 呼叫時 ID 也傳字串
|
|
123
|
+
await deleteUser({ id: '8107821456789012345' }) // ✅
|
|
124
|
+
\`\`\`
|
|
125
|
+
|
|
126
|
+
### 坑 8:defineProps 解構失去響應性(Vue 3.4 以前)
|
|
127
|
+
\`\`\`typescript
|
|
128
|
+
// ❌ Vue 3.3 以前解構 props 失去響應性
|
|
129
|
+
const { title, visible } = defineProps<{ title: string; visible: boolean }>()
|
|
130
|
+
// visible 變成靜態值,父元件更新不反映
|
|
131
|
+
|
|
132
|
+
// ✅ Vue 3.4+ 才支援響應式解構(withDefaults 搭配)
|
|
133
|
+
// 保險做法:不解構,直接用 props.xxx
|
|
134
|
+
const props = defineProps<{ title: string; visible: boolean }>()
|
|
135
|
+
watch(() => props.visible, ...)
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 三、Element Plus 陷阱
|
|
141
|
+
|
|
142
|
+
### 坑 9:el-dialog v-model 更新父元件的方式
|
|
143
|
+
\`\`\`vue
|
|
144
|
+
<!-- ❌ 在子元件直接修改 prop -->
|
|
145
|
+
<el-dialog :visible="visible" @update:visible="visible = $event">
|
|
146
|
+
<!-- visible 是 prop,直接賦值會報警告 -->
|
|
147
|
+
|
|
148
|
+
<!-- ✅ 正確:emit update:modelValue -->
|
|
149
|
+
<script setup lang="ts">
|
|
150
|
+
const props = defineProps<{ modelValue: boolean }>()
|
|
151
|
+
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
|
152
|
+
</script>
|
|
153
|
+
<template>
|
|
154
|
+
<el-dialog
|
|
155
|
+
:model-value="props.modelValue"
|
|
156
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
157
|
+
/>
|
|
158
|
+
</template>
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
### 坑 10:el-table 選取行後刪除資料,選取狀態殘留
|
|
162
|
+
\`\`\`typescript
|
|
163
|
+
// 刪除資料後需要手動清除選取
|
|
164
|
+
const tableRef = ref<InstanceType<typeof ElTable>>()
|
|
165
|
+
|
|
166
|
+
const handleDelete = async (id: string) => {
|
|
167
|
+
await deleteApi(id)
|
|
168
|
+
tableRef.value?.clearSelection() // ← 必須手動清除
|
|
169
|
+
await loadData()
|
|
170
|
+
}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
### 坑 11:el-form validate 在非同步場景的時機問題
|
|
174
|
+
\`\`\`typescript
|
|
175
|
+
const formRef = ref<FormInstance>()
|
|
176
|
+
|
|
177
|
+
// ❌ validate 是非同步但忘記 await
|
|
178
|
+
const handleSubmit = () => {
|
|
179
|
+
formRef.value?.validate() // 不等驗證完就繼續執行
|
|
180
|
+
submitData() // 驗證還沒完成就送出
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ✅ 正確
|
|
184
|
+
const handleSubmit = async () => {
|
|
185
|
+
const valid = await formRef.value?.validate().catch(() => false)
|
|
186
|
+
if (!valid) return
|
|
187
|
+
await submitData()
|
|
188
|
+
}
|
|
189
|
+
\`\`\`
|
|
190
|
+
|
|
191
|
+
### 坑 12:el-select 選項 value 型別不匹配
|
|
192
|
+
\`\`\`vue
|
|
193
|
+
<!-- ❌ v-model 是 number,但 option value 是 string -->
|
|
194
|
+
<el-select v-model="status"> <!-- status: number = 1 -->
|
|
195
|
+
<el-option :value="'1'" label="啟用" /> <!-- value 是字串 '1' -->
|
|
196
|
+
</el-select>
|
|
197
|
+
<!-- 永遠選不中! -->
|
|
198
|
+
|
|
199
|
+
<!-- ✅ 統一型別 -->
|
|
200
|
+
<el-select v-model="status">
|
|
201
|
+
<el-option :value="1" label="啟用" /> <!-- :value 綁定數字 -->
|
|
202
|
+
</el-select>
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 四、Vite + 模組系統陷阱
|
|
208
|
+
|
|
209
|
+
### 坑 13:動態 import 路徑不能全動態
|
|
210
|
+
\`\`\`typescript
|
|
211
|
+
// ❌ Vite 無法靜態分析完全動態的路徑
|
|
212
|
+
const modulePath = getPathFromApi()
|
|
213
|
+
const module = await import(modulePath) // 打包時報錯或執行時找不到
|
|
214
|
+
|
|
215
|
+
// ✅ 必須有靜態前綴讓 Vite 分析
|
|
216
|
+
const module = await import(\`../views/\${pageName}.vue\`)
|
|
217
|
+
\`\`\`
|
|
218
|
+
|
|
219
|
+
### 坑 14:@/ 別名在某些設定下失效
|
|
220
|
+
\`\`\`typescript
|
|
221
|
+
// vite.config.ts 需要設定 resolve.alias
|
|
222
|
+
import path from 'path'
|
|
223
|
+
export default {
|
|
224
|
+
resolve: {
|
|
225
|
+
alias: {
|
|
226
|
+
'/@/': path.resolve(__dirname, 'src'), // Admin.NET 前端用 /@/
|
|
227
|
+
'@/': path.resolve(__dirname, 'src'), // 也可同時支援兩種
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// tsconfig.json 也要同步設定
|
|
233
|
+
{
|
|
234
|
+
"compilerOptions": {
|
|
235
|
+
"paths": {
|
|
236
|
+
"/@/*": ["src/*"]
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
### 坑 15:.env 環境變數需要 VITE_ 前綴
|
|
243
|
+
\`\`\`bash
|
|
244
|
+
# ❌ 前端讀取不到(只有 Node.js 環境能讀)
|
|
245
|
+
API_URL=https://api.example.com
|
|
246
|
+
|
|
247
|
+
# ✅ 加 VITE_ 前綴才能在前端使用
|
|
248
|
+
VITE_API_URL=https://api.example.com
|
|
249
|
+
\`\`\`
|
|
250
|
+
\`\`\`typescript
|
|
251
|
+
// 使用
|
|
252
|
+
const apiUrl = import.meta.env.VITE_API_URL
|
|
253
|
+
\`\`\`
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 五、Pinia 狀態管理
|
|
258
|
+
|
|
259
|
+
### 坑 16:在 setup() 外部使用 store 需要傳入 pinia 實例
|
|
260
|
+
\`\`\`typescript
|
|
261
|
+
// ❌ 在 router guard 或工具函式中直接呼叫
|
|
262
|
+
const userStore = useUserStore() // 可能報錯:no active Pinia
|
|
263
|
+
|
|
264
|
+
// ✅ 傳入 pinia 實例(main.ts 中建立的)
|
|
265
|
+
import { pinia } from '/@/main'
|
|
266
|
+
const userStore = useUserStore(pinia)
|
|
267
|
+
\`\`\`
|
|
268
|
+
|
|
269
|
+
### 坑 17:storeToRefs 只解構 state 和 getters
|
|
270
|
+
\`\`\`typescript
|
|
271
|
+
const store = useUserStore()
|
|
272
|
+
|
|
273
|
+
// ❌ actions 用 storeToRefs 解構後會失去方法
|
|
274
|
+
const { fetchUser } = storeToRefs(store) // fetchUser 變成 ref,不能呼叫
|
|
275
|
+
|
|
276
|
+
// ✅ actions 直接從 store 解構
|
|
277
|
+
const { fetchUser, updateUser } = store // actions 直接解構 OK
|
|
278
|
+
const { name, age } = storeToRefs(store) // state/getters 用 storeToRefs
|
|
279
|
+
\`\`\`
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## 六、非同步與生命週期
|
|
284
|
+
|
|
285
|
+
### 坑 18:onMounted 中的非同步初始化
|
|
286
|
+
\`\`\`typescript
|
|
287
|
+
// ❌ onMounted 不能直接 async(會吞掉錯誤)
|
|
288
|
+
onMounted(async () => {
|
|
289
|
+
await fetchData() // 錯誤不會被 Vue 的錯誤處理捕獲
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
// ✅ 包一層 try/catch 或用獨立函式
|
|
293
|
+
const init = async () => {
|
|
294
|
+
try {
|
|
295
|
+
await fetchData()
|
|
296
|
+
} catch (err) {
|
|
297
|
+
ElMessage.error('載入失敗')
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
onMounted(init)
|
|
301
|
+
\`\`\`
|
|
302
|
+
|
|
303
|
+
### 坑 19:nextTick 取得更新後的 DOM
|
|
304
|
+
\`\`\`typescript
|
|
305
|
+
// ❌ 資料改變後立即操作 DOM(DOM 還沒更新)
|
|
306
|
+
tableData.value = newData
|
|
307
|
+
tableRef.value?.doLayout() // 可能拿到舊的 DOM
|
|
308
|
+
|
|
309
|
+
// ✅ 等 DOM 更新後再操作
|
|
310
|
+
tableData.value = newData
|
|
311
|
+
await nextTick()
|
|
312
|
+
tableRef.value?.doLayout()
|
|
313
|
+
\`\`\`
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 七、Admin.NET 前端特定
|
|
318
|
+
|
|
319
|
+
### 坑 20:API 回應的統一格式
|
|
320
|
+
\`\`\`typescript
|
|
321
|
+
// Admin.NET 所有 API 回應包在統一格式中
|
|
322
|
+
// { code: 200, message: "成功", data: { ... } }
|
|
323
|
+
|
|
324
|
+
// ❌ 以為 response 直接是資料
|
|
325
|
+
const user = await getUserApi(id)
|
|
326
|
+
console.log(user.name) // undefined!data 在 response.data 中
|
|
327
|
+
|
|
328
|
+
// ✅ axios interceptor 已處理,直接拿 data
|
|
329
|
+
// 查看 Web/src/utils/request.ts 的攔截器設定
|
|
330
|
+
const user = await getUserApi(id) // interceptor 已解包 data
|
|
331
|
+
console.log(user.name) // OK(視攔截器實作而定)
|
|
332
|
+
\`\`\`
|
|
333
|
+
|
|
334
|
+
### 坑 21:路由動態載入元件路徑大小寫問題
|
|
335
|
+
\`\`\`typescript
|
|
336
|
+
// Windows 開發環境不區分大小寫,Linux 伺服器區分
|
|
337
|
+
// ❌ 本機 OK 但上線後 404
|
|
338
|
+
component: () => import('../views/System/User.vue') // 實際檔名 user.vue
|
|
339
|
+
|
|
340
|
+
// ✅ 保持檔名與 import 路徑大小寫完全一致
|
|
341
|
+
// 建議統一使用 kebab-case 檔名:user-manage.vue
|
|
342
|
+
\`\`\`
|
|
343
|
+
`;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elf-express/admin-net-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Admin.NET / Furion / SqlSugar / Vue 3 framework knowledge MCP server",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"admin-net-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/elf-express/platfrom-admin.git"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "ts-node src/index.ts",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"prepublishOnly": "npm run build && node -e \"const fs=require('fs');const f='dist/index.js';const c=fs.readFileSync(f,'utf8');if(!c.startsWith('#!/'))fs.writeFileSync(f,'#!/usr/bin/env node\\n'+c);\""
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
27
|
+
"zod": "^3.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.0.0",
|
|
31
|
+
"ts-node": "^10.9.0",
|
|
32
|
+
"typescript": "^5.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|