@elf-express/admin-net-mcp 1.0.0 → 1.1.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/index.js +83 -88
- package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
- package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
- package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
- package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
- package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
- package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
- package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
- package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
- package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
- package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
- package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
- package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
- package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
- package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
- package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
- package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
- package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
- package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
- package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
- package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
- package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
- package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
- package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
- package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
- package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
- package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
- package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
- package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
- package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
- package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
- package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
- package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
- package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
- package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
- package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
- package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
- package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
- package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
- package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
- package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
- package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
- package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
- package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
- package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
- package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
- package/knowledge/attributes.md +153 -0
- package/knowledge/config.md +147 -0
- package/knowledge/entity.md +115 -0
- package/knowledge/event.md +124 -0
- package/knowledge/plugin.md +136 -0
- package/knowledge/service.md +146 -0
- package/knowledge/sqlsugar.md +172 -0
- package/knowledge/vue-typescript.md +338 -0
- package/package.json +3 -2
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Admin.NET SqlSugar ORM 指南
|
|
2
|
+
|
|
3
|
+
## AOP 自動填充欄位
|
|
4
|
+
|
|
5
|
+
SqlSugar AOP 在 Insert/Update 時自動填入以下欄位(無需手動設定):
|
|
6
|
+
|
|
7
|
+
### Insert 時自動填充
|
|
8
|
+
| 欄位 | 來源 | 條件 |
|
|
9
|
+
|------|------|------|
|
|
10
|
+
| Id(long) | YitIdHelper.NextId() | 值為 0 或 null 時 |
|
|
11
|
+
| CreateTime | DateTime.Now | 為 null 或 MinValue 時 |
|
|
12
|
+
| CreateUserId | ClaimConst.UserId(JWT) | 為 null 或 0 時 |
|
|
13
|
+
| CreateUserName | ClaimConst.RealName(JWT) | 為空字串時 |
|
|
14
|
+
| TenantId | ClaimConst.TenantId(JWT) | 為 null 或 0,且實體實作 ITenantIdFilter |
|
|
15
|
+
|
|
16
|
+
### Update 時自動填充
|
|
17
|
+
| 欄位 | 來源 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| UpdateTime | DateTime.Now(永遠更新) |
|
|
20
|
+
| UpdateUserId | 目前使用者 ID |
|
|
21
|
+
| UpdateUserName | 目前使用者姓名 |
|
|
22
|
+
| DeleteTime | DateTime.Now(當 IsDelete=true) |
|
|
23
|
+
|
|
24
|
+
**注意**:種子資料初始化時(\_isHandlingSeedData=true)AOP 會跳過,使用明確設定的值。
|
|
25
|
+
|
|
26
|
+
## 自動查詢過濾
|
|
27
|
+
|
|
28
|
+
SqlSugar 根據實體實作的介面自動加入 WHERE 條件:
|
|
29
|
+
|
|
30
|
+
```csharp
|
|
31
|
+
// 軟刪除過濾(實作 IDeletedFilter 的實體自動套用)
|
|
32
|
+
WHERE IsDelete = 0
|
|
33
|
+
|
|
34
|
+
// 租戶過濾(實作 ITenantIdFilter 的實體自動套用)
|
|
35
|
+
WHERE TenantId = 目前使用者的 TenantId
|
|
36
|
+
|
|
37
|
+
// 組織資料權限(實作 IOrgIdFilter 的實體自動套用)
|
|
38
|
+
WHERE OrgId IN (使用者有權限的組織清單)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**SuperAdmin 例外**:DbConnectionOptions.SuperAdminIgnoreIDeletedFilter = true 時,超級管理員可看到已刪除資料。
|
|
42
|
+
|
|
43
|
+
## SqlSugarRepository<T> 使用方式
|
|
44
|
+
|
|
45
|
+
### 注入
|
|
46
|
+
```csharp
|
|
47
|
+
public class OrderService : IDynamicApiController, ITransient
|
|
48
|
+
{
|
|
49
|
+
private readonly SqlSugarRepository<Order> _orderRep;
|
|
50
|
+
|
|
51
|
+
public OrderService(SqlSugarRepository<Order> orderRep)
|
|
52
|
+
{
|
|
53
|
+
_orderRep = orderRep;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 常用查詢
|
|
59
|
+
```csharp
|
|
60
|
+
// 取得單筆
|
|
61
|
+
var order = await _orderRep.GetFirstAsync(u => u.Id == id);
|
|
62
|
+
|
|
63
|
+
// 查詢清單
|
|
64
|
+
var list = await _orderRep.GetListAsync(u => u.Status == 1);
|
|
65
|
+
|
|
66
|
+
// 分頁(推薦)
|
|
67
|
+
var paged = await _orderRep.AsQueryable()
|
|
68
|
+
.WhereIF(!string.IsNullOrWhiteSpace(keyword), u => u.Name.Contains(keyword))
|
|
69
|
+
.OrderBy(u => u.CreateTime, OrderByType.Desc)
|
|
70
|
+
.ToPagedListAsync(page, pageSize);
|
|
71
|
+
|
|
72
|
+
// 判斷是否存在
|
|
73
|
+
var exists = await _orderRep.IsAnyAsync(u => u.OrderNo == orderNo);
|
|
74
|
+
|
|
75
|
+
// 計數
|
|
76
|
+
var count = await _orderRep.CountAsync(u => u.Status == 1);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 新增/更新/刪除
|
|
80
|
+
```csharp
|
|
81
|
+
// 新增
|
|
82
|
+
await _orderRep.InsertAsync(order);
|
|
83
|
+
await _orderRep.InsertRangeAsync(orders);
|
|
84
|
+
|
|
85
|
+
// 更新(整筆)
|
|
86
|
+
await _orderRep.UpdateAsync(order);
|
|
87
|
+
|
|
88
|
+
// 部分欄位更新
|
|
89
|
+
await _orderRep.UpdateAsync(
|
|
90
|
+
u => new Order { Status = 2, UpdateTime = DateTime.Now },
|
|
91
|
+
u => u.Id == id);
|
|
92
|
+
|
|
93
|
+
// 軟刪除(需實作 IDeletedFilter)
|
|
94
|
+
await _orderRep.UpdateAsync(
|
|
95
|
+
u => new Order { IsDelete = true },
|
|
96
|
+
u => u.Id == id);
|
|
97
|
+
|
|
98
|
+
// 真實刪除
|
|
99
|
+
await _orderRep.DeleteAsync(id);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 聯表查詢
|
|
103
|
+
```csharp
|
|
104
|
+
var result = await _orderRep.AsQueryable()
|
|
105
|
+
.LeftJoin<OrderItem>((o, i) => o.Id == i.OrderId)
|
|
106
|
+
.Select((o, i) => new OrderWithItemsOutput
|
|
107
|
+
{
|
|
108
|
+
OrderNo = o.OrderNo,
|
|
109
|
+
ItemCount = SqlFunc.AggregateCount(i.Id)
|
|
110
|
+
})
|
|
111
|
+
.ToListAsync();
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 交易管理(UnitOfWork)
|
|
115
|
+
|
|
116
|
+
### 方式一:[UnitOfWork] Attribute(推薦)
|
|
117
|
+
```csharp
|
|
118
|
+
[UnitOfWork]
|
|
119
|
+
public async Task CreateOrderWithItems(CreateOrderInput input)
|
|
120
|
+
{
|
|
121
|
+
var order = input.Adapt<Order>();
|
|
122
|
+
await _orderRep.InsertAsync(order);
|
|
123
|
+
// 例外時自動回滾
|
|
124
|
+
await _itemRep.InsertRangeAsync(items);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 方式二:手動交易
|
|
129
|
+
```csharp
|
|
130
|
+
var db = _orderRep.Context;
|
|
131
|
+
try
|
|
132
|
+
{
|
|
133
|
+
db.BeginTran();
|
|
134
|
+
await _orderRep.InsertAsync(order);
|
|
135
|
+
await _itemRep.InsertRangeAsync(items);
|
|
136
|
+
db.CommitTran();
|
|
137
|
+
}
|
|
138
|
+
catch
|
|
139
|
+
{
|
|
140
|
+
db.RollbackTran();
|
|
141
|
+
throw;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 多資料庫路由
|
|
146
|
+
|
|
147
|
+
Repository 依實體 Attribute 自動選擇連線:
|
|
148
|
+
1. `[TenantAttribute]` → 租戶專屬 DB
|
|
149
|
+
2. `[LogTableAttribute]` → Log 專用 DB
|
|
150
|
+
3. `[SysTableAttribute]` → 主 DB(強制)
|
|
151
|
+
4. Request Header 中的 TenantId → 租戶 DB
|
|
152
|
+
5. 預設 → 主 DB
|
|
153
|
+
|
|
154
|
+
## 分表(Split Table)方法
|
|
155
|
+
|
|
156
|
+
用於日期分表或大型資料集:
|
|
157
|
+
```csharp
|
|
158
|
+
await _logRep.SplitTableInsertAsync(logEntry);
|
|
159
|
+
var logs = await _logRep.SplitTableGetListAsync(u => u.CreateTime > startDate);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 原始 SQL(謹慎使用)
|
|
163
|
+
```csharp
|
|
164
|
+
var result = await db.SqlQueryable<OrderOutput>("SELECT * FROM Orders WHERE Status=@status")
|
|
165
|
+
.AddParameters(new SugarParameter("@status", 1))
|
|
166
|
+
.ToListAsync();
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## 檔案位置
|
|
170
|
+
- SqlSugar 設定(AOP):`Admin.NET.Core/SqlSugar/SqlSugarSetup.cs`
|
|
171
|
+
- Repository 實作:`Admin.NET.Core/SqlSugar/SqlSugarRepository.cs`
|
|
172
|
+
- UnitOfWork:`Admin.NET.Core/SqlSugar/SqlSugarUnitOfWork.cs`
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Vue 3 + TypeScript 採坑記錄
|
|
2
|
+
|
|
3
|
+
本文記錄 Admin.NET 前端(Vue 3 + Element Plus + Vite)開發中實際踩過的坑。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 一、響應式(Reactivity)
|
|
8
|
+
|
|
9
|
+
### 坑 1:解構 reactive 物件後失去響應性
|
|
10
|
+
```typescript
|
|
11
|
+
// ❌ 錯誤:解構後 name 不是響應式
|
|
12
|
+
const state = reactive({ name: '張三', age: 18 })
|
|
13
|
+
const { name } = state // name 是普通字串,不會更新
|
|
14
|
+
|
|
15
|
+
// ✅ 正確方式一:用 toRefs
|
|
16
|
+
const { name } = toRefs(state)
|
|
17
|
+
|
|
18
|
+
// ✅ 正確方式二:直接用 ref
|
|
19
|
+
const name = ref('張三')
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 坑 2:ref 物件忘記 .value
|
|
23
|
+
```typescript
|
|
24
|
+
// ❌ 常見錯誤
|
|
25
|
+
const count = ref(0)
|
|
26
|
+
count = 5 // 這會替換掉 ref 本身!
|
|
27
|
+
|
|
28
|
+
// ✅ 正確
|
|
29
|
+
count.value = 5
|
|
30
|
+
|
|
31
|
+
// template 中不需要 .value(自動解包)
|
|
32
|
+
// <span>{{ count }}</span> ← 正確
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 坑 3:watch 監聽物件屬性需要 getter
|
|
36
|
+
```typescript
|
|
37
|
+
const user = reactive({ name: '張三' })
|
|
38
|
+
|
|
39
|
+
// ❌ 不會觸發(監聽的是初始值字串)
|
|
40
|
+
watch(user.name, (newVal) => { })
|
|
41
|
+
|
|
42
|
+
// ✅ 用 getter 函式
|
|
43
|
+
watch(() => user.name, (newVal) => { })
|
|
44
|
+
|
|
45
|
+
// ✅ 監聽整個 reactive 物件(深層)
|
|
46
|
+
watch(user, (newVal) => { }, { deep: true })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 坑 4:陣列操作不觸發更新
|
|
50
|
+
```typescript
|
|
51
|
+
const list = ref<string[]>([])
|
|
52
|
+
|
|
53
|
+
// ❌ 直接賦值 index 不觸發
|
|
54
|
+
list.value[0] = '新值'
|
|
55
|
+
|
|
56
|
+
// ✅ 用 splice
|
|
57
|
+
list.value.splice(0, 1, '新值')
|
|
58
|
+
|
|
59
|
+
// ✅ 或整個陣列替換
|
|
60
|
+
list.value = [...list.value.slice(0, 0), '新值', ...list.value.slice(1)]
|
|
61
|
+
|
|
62
|
+
// ✅ push/pop/shift/splice 等方法都能正確觸發
|
|
63
|
+
list.value.push('新項目')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 二、TypeScript 型別陷阱
|
|
69
|
+
|
|
70
|
+
### 坑 5:Event 型別錯誤
|
|
71
|
+
```typescript
|
|
72
|
+
// ❌ Event 沒有 target.value
|
|
73
|
+
const handleInput = (e: Event) => {
|
|
74
|
+
console.log(e.target.value) // TS 報錯:Object is possibly null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ✅ 明確型別
|
|
78
|
+
const handleInput = (e: Event) => {
|
|
79
|
+
const target = e.target as HTMLInputElement
|
|
80
|
+
console.log(target.value)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ✅ Element Plus 元件直接拿值
|
|
84
|
+
const handleChange = (value: string) => {
|
|
85
|
+
// el-input 的 change 事件直接傳值,不是 Event
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 坑 6:Ref 泛型推斷失敗
|
|
90
|
+
```typescript
|
|
91
|
+
// ❌ 初始 null 時 TS 無法推斷型別
|
|
92
|
+
const userInfo = ref(null)
|
|
93
|
+
userInfo.value.name // 報錯:Object is possibly null
|
|
94
|
+
|
|
95
|
+
// ✅ 明確指定泛型
|
|
96
|
+
interface UserInfo { name: string; age: number }
|
|
97
|
+
const userInfo = ref<UserInfo | null>(null)
|
|
98
|
+
|
|
99
|
+
// 使用時加判斷
|
|
100
|
+
if (userInfo.value) {
|
|
101
|
+
console.log(userInfo.value.name) // OK
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 坑 7:Snowflake ID 精度問題(Admin.NET 專屬!)
|
|
106
|
+
```typescript
|
|
107
|
+
// ❌ 後端回傳的 long ID 在 JS 中會丟失精度
|
|
108
|
+
// 例:8107821456789012345 → 8107821456789012000(最後幾位變0)
|
|
109
|
+
|
|
110
|
+
// ✅ Admin.NET 後端已設定 Long→String 序列化
|
|
111
|
+
// 前端所有 ID 欄位必須宣告為 string,不是 number
|
|
112
|
+
interface SysUser {
|
|
113
|
+
id: string // ✅ string
|
|
114
|
+
// id: number // ❌ 會丟精度
|
|
115
|
+
name: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// API 呼叫時 ID 也傳字串
|
|
119
|
+
await deleteUser({ id: '8107821456789012345' }) // ✅
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 坑 8:defineProps 解構失去響應性(Vue 3.4 以前)
|
|
123
|
+
```typescript
|
|
124
|
+
// ❌ Vue 3.3 以前解構 props 失去響應性
|
|
125
|
+
const { title, visible } = defineProps<{ title: string; visible: boolean }>()
|
|
126
|
+
// visible 變成靜態值,父元件更新不反映
|
|
127
|
+
|
|
128
|
+
// ✅ Vue 3.4+ 才支援響應式解構(withDefaults 搭配)
|
|
129
|
+
// 保險做法:不解構,直接用 props.xxx
|
|
130
|
+
const props = defineProps<{ title: string; visible: boolean }>()
|
|
131
|
+
watch(() => props.visible, ...)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 三、Element Plus 陷阱
|
|
137
|
+
|
|
138
|
+
### 坑 9:el-dialog v-model 更新父元件的方式
|
|
139
|
+
```vue
|
|
140
|
+
<!-- ❌ 在子元件直接修改 prop -->
|
|
141
|
+
<el-dialog :visible="visible" @update:visible="visible = $event">
|
|
142
|
+
<!-- visible 是 prop,直接賦值會報警告 -->
|
|
143
|
+
|
|
144
|
+
<!-- ✅ 正確:emit update:modelValue -->
|
|
145
|
+
<script setup lang="ts">
|
|
146
|
+
const props = defineProps<{ modelValue: boolean }>()
|
|
147
|
+
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
|
148
|
+
</script>
|
|
149
|
+
<template>
|
|
150
|
+
<el-dialog
|
|
151
|
+
:model-value="props.modelValue"
|
|
152
|
+
@update:model-value="emit('update:modelValue', $event)"
|
|
153
|
+
/>
|
|
154
|
+
</template>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 坑 10:el-table 選取行後刪除資料,選取狀態殘留
|
|
158
|
+
```typescript
|
|
159
|
+
// 刪除資料後需要手動清除選取
|
|
160
|
+
const tableRef = ref<InstanceType<typeof ElTable>>()
|
|
161
|
+
|
|
162
|
+
const handleDelete = async (id: string) => {
|
|
163
|
+
await deleteApi(id)
|
|
164
|
+
tableRef.value?.clearSelection() // ← 必須手動清除
|
|
165
|
+
await loadData()
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 坑 11:el-form validate 在非同步場景的時機問題
|
|
170
|
+
```typescript
|
|
171
|
+
const formRef = ref<FormInstance>()
|
|
172
|
+
|
|
173
|
+
// ❌ validate 是非同步但忘記 await
|
|
174
|
+
const handleSubmit = () => {
|
|
175
|
+
formRef.value?.validate() // 不等驗證完就繼續執行
|
|
176
|
+
submitData() // 驗證還沒完成就送出
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ✅ 正確
|
|
180
|
+
const handleSubmit = async () => {
|
|
181
|
+
const valid = await formRef.value?.validate().catch(() => false)
|
|
182
|
+
if (!valid) return
|
|
183
|
+
await submitData()
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 坑 12:el-select 選項 value 型別不匹配
|
|
188
|
+
```vue
|
|
189
|
+
<!-- ❌ v-model 是 number,但 option value 是 string -->
|
|
190
|
+
<el-select v-model="status"> <!-- status: number = 1 -->
|
|
191
|
+
<el-option :value="'1'" label="啟用" /> <!-- value 是字串 '1' -->
|
|
192
|
+
</el-select>
|
|
193
|
+
<!-- 永遠選不中! -->
|
|
194
|
+
|
|
195
|
+
<!-- ✅ 統一型別 -->
|
|
196
|
+
<el-select v-model="status">
|
|
197
|
+
<el-option :value="1" label="啟用" /> <!-- :value 綁定數字 -->
|
|
198
|
+
</el-select>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 四、Vite + 模組系統陷阱
|
|
204
|
+
|
|
205
|
+
### 坑 13:動態 import 路徑不能全動態
|
|
206
|
+
```typescript
|
|
207
|
+
// ❌ Vite 無法靜態分析完全動態的路徑
|
|
208
|
+
const modulePath = getPathFromApi()
|
|
209
|
+
const module = await import(modulePath) // 打包時報錯或執行時找不到
|
|
210
|
+
|
|
211
|
+
// ✅ 必須有靜態前綴讓 Vite 分析
|
|
212
|
+
const module = await import(`../views/\${pageName}.vue`)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 坑 14:@/ 別名在某些設定下失效
|
|
216
|
+
```typescript
|
|
217
|
+
// vite.config.ts 需要設定 resolve.alias
|
|
218
|
+
import path from 'path'
|
|
219
|
+
export default {
|
|
220
|
+
resolve: {
|
|
221
|
+
alias: {
|
|
222
|
+
'/@/': path.resolve(__dirname, 'src'), // Admin.NET 前端用 /@/
|
|
223
|
+
'@/': path.resolve(__dirname, 'src'), // 也可同時支援兩種
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// tsconfig.json 也要同步設定
|
|
229
|
+
{
|
|
230
|
+
"compilerOptions": {
|
|
231
|
+
"paths": {
|
|
232
|
+
"/@/*": ["src/*"]
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 坑 15:.env 環境變數需要 VITE_ 前綴
|
|
239
|
+
```bash
|
|
240
|
+
# ❌ 前端讀取不到(只有 Node.js 環境能讀)
|
|
241
|
+
API_URL=https://api.example.com
|
|
242
|
+
|
|
243
|
+
# ✅ 加 VITE_ 前綴才能在前端使用
|
|
244
|
+
VITE_API_URL=https://api.example.com
|
|
245
|
+
```
|
|
246
|
+
```typescript
|
|
247
|
+
// 使用
|
|
248
|
+
const apiUrl = import.meta.env.VITE_API_URL
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 五、Pinia 狀態管理
|
|
254
|
+
|
|
255
|
+
### 坑 16:在 setup() 外部使用 store 需要傳入 pinia 實例
|
|
256
|
+
```typescript
|
|
257
|
+
// ❌ 在 router guard 或工具函式中直接呼叫
|
|
258
|
+
const userStore = useUserStore() // 可能報錯:no active Pinia
|
|
259
|
+
|
|
260
|
+
// ✅ 傳入 pinia 實例(main.ts 中建立的)
|
|
261
|
+
import { pinia } from '/@/main'
|
|
262
|
+
const userStore = useUserStore(pinia)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 坑 17:storeToRefs 只解構 state 和 getters
|
|
266
|
+
```typescript
|
|
267
|
+
const store = useUserStore()
|
|
268
|
+
|
|
269
|
+
// ❌ actions 用 storeToRefs 解構後會失去方法
|
|
270
|
+
const { fetchUser } = storeToRefs(store) // fetchUser 變成 ref,不能呼叫
|
|
271
|
+
|
|
272
|
+
// ✅ actions 直接從 store 解構
|
|
273
|
+
const { fetchUser, updateUser } = store // actions 直接解構 OK
|
|
274
|
+
const { name, age } = storeToRefs(store) // state/getters 用 storeToRefs
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 六、非同步與生命週期
|
|
280
|
+
|
|
281
|
+
### 坑 18:onMounted 中的非同步初始化
|
|
282
|
+
```typescript
|
|
283
|
+
// ❌ onMounted 不能直接 async(會吞掉錯誤)
|
|
284
|
+
onMounted(async () => {
|
|
285
|
+
await fetchData() // 錯誤不會被 Vue 的錯誤處理捕獲
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
// ✅ 包一層 try/catch 或用獨立函式
|
|
289
|
+
const init = async () => {
|
|
290
|
+
try {
|
|
291
|
+
await fetchData()
|
|
292
|
+
} catch (err) {
|
|
293
|
+
ElMessage.error('載入失敗')
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
onMounted(init)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 坑 19:nextTick 取得更新後的 DOM
|
|
300
|
+
```typescript
|
|
301
|
+
// ❌ 資料改變後立即操作 DOM(DOM 還沒更新)
|
|
302
|
+
tableData.value = newData
|
|
303
|
+
tableRef.value?.doLayout() // 可能拿到舊的 DOM
|
|
304
|
+
|
|
305
|
+
// ✅ 等 DOM 更新後再操作
|
|
306
|
+
tableData.value = newData
|
|
307
|
+
await nextTick()
|
|
308
|
+
tableRef.value?.doLayout()
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## 七、Admin.NET 前端特定
|
|
314
|
+
|
|
315
|
+
### 坑 20:API 回應的統一格式
|
|
316
|
+
```typescript
|
|
317
|
+
// Admin.NET 所有 API 回應包在統一格式中
|
|
318
|
+
// { code: 200, message: "成功", data: { ... } }
|
|
319
|
+
|
|
320
|
+
// ❌ 以為 response 直接是資料
|
|
321
|
+
const user = await getUserApi(id)
|
|
322
|
+
console.log(user.name) // undefined!data 在 response.data 中
|
|
323
|
+
|
|
324
|
+
// ✅ axios interceptor 已處理,直接拿 data
|
|
325
|
+
// 查看 Web/src/utils/request.ts 的攔截器設定
|
|
326
|
+
const user = await getUserApi(id) // interceptor 已解包 data
|
|
327
|
+
console.log(user.name) // OK(視攔截器實作而定)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 坑 21:路由動態載入元件路徑大小寫問題
|
|
331
|
+
```typescript
|
|
332
|
+
// Windows 開發環境不區分大小寫,Linux 伺服器區分
|
|
333
|
+
// ❌ 本機 OK 但上線後 404
|
|
334
|
+
component: () => import('../views/System/User.vue') // 實際檔名 user.vue
|
|
335
|
+
|
|
336
|
+
// ✅ 保持檔名與 import 路徑大小寫完全一致
|
|
337
|
+
// 建議統一使用 kebab-case 檔名:user-manage.vue
|
|
338
|
+
```
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elf-express/admin-net-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Admin.NET / Furion / SqlSugar / Vue 3 framework knowledge MCP server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"admin-net-mcp": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist/"
|
|
10
|
+
"dist/",
|
|
11
|
+
"knowledge/"
|
|
11
12
|
],
|
|
12
13
|
"publishConfig": {
|
|
13
14
|
"access": "public"
|