vue2server7 7.0.45 → 7.0.46
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.
|
@@ -153,7 +153,7 @@ const props = withDefaults(defineProps<OrgTreeSelectProps>(), {
|
|
|
153
153
|
labelKey: 'name',
|
|
154
154
|
childrenKey: 'children',
|
|
155
155
|
defaultExpandedKeys: () => [],
|
|
156
|
-
defaultExpandAll:
|
|
156
|
+
defaultExpandAll: true
|
|
157
157
|
})
|
|
158
158
|
|
|
159
159
|
const emit = defineEmits<OrgTreeSelectEmits>()
|
|
@@ -265,20 +265,65 @@ function openDialog(): void {
|
|
|
265
265
|
selectedValue.value = props.modelValue
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
/**
|
|
269
|
+
* 获取所有节点 ID(递归)
|
|
270
|
+
* @param data - 树数据
|
|
271
|
+
* @returns 所有节点 ID 数组
|
|
272
|
+
*/
|
|
273
|
+
function getAllNodeIds(data: OrgTreeNode[]): (string | number)[] {
|
|
274
|
+
const ids: (string | number)[] = []
|
|
275
|
+
for (const item of data) {
|
|
276
|
+
ids.push(item.id)
|
|
277
|
+
if (item.children && item.children.length > 0) {
|
|
278
|
+
ids.push(...getAllNodeIds(item.children))
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return ids
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 分批展开节点(避免卡顿)
|
|
286
|
+
* @param ids - 节点 ID 数组
|
|
287
|
+
* @param batchSize - 每批处理的节点数
|
|
288
|
+
*/
|
|
289
|
+
async function expandNodesBatch(ids: (string | number)[], batchSize: number = 50): Promise<void> {
|
|
290
|
+
if (!treeRef.value) return
|
|
291
|
+
|
|
292
|
+
for (let i = 0; i < ids.length; i += batchSize) {
|
|
293
|
+
const batch = ids.slice(i, i + batchSize)
|
|
294
|
+
batch.forEach(id => {
|
|
295
|
+
treeRef.value?.expandNode(id as string)
|
|
296
|
+
})
|
|
297
|
+
// 每批处理后让出主线程,避免阻塞UI
|
|
298
|
+
if (i + batchSize < ids.length) {
|
|
299
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
268
304
|
/**
|
|
269
305
|
* 弹窗打开后的回调
|
|
270
306
|
*/
|
|
271
307
|
function onDialogOpened(): void {
|
|
272
|
-
nextTick(() => {
|
|
308
|
+
nextTick(async () => {
|
|
273
309
|
updateSelectedNodes()
|
|
274
310
|
if (!treeRef.value) return
|
|
275
311
|
|
|
312
|
+
// 先恢复选中状态
|
|
276
313
|
if (props.multiple) {
|
|
277
314
|
const keys = Array.isArray(selectedValue.value) ? selectedValue.value : []
|
|
278
315
|
treeRef.value.setCheckedKeys(keys as (string | number)[])
|
|
279
316
|
} else if (selectedValue.value !== null) {
|
|
280
317
|
treeRef.value.setCurrentKey(selectedValue.value as string | number)
|
|
281
318
|
}
|
|
319
|
+
|
|
320
|
+
// 异步展开节点,避免弹窗卡顿
|
|
321
|
+
if (props.defaultExpandAll) {
|
|
322
|
+
const allIds = getAllNodeIds(props.data)
|
|
323
|
+
await expandNodesBatch(allIds, 50)
|
|
324
|
+
} else if (props.defaultExpandedKeys.length > 0) {
|
|
325
|
+
await expandNodesBatch(props.defaultExpandedKeys, 50)
|
|
326
|
+
}
|
|
282
327
|
})
|
|
283
328
|
}
|
|
284
329
|
|
|
@@ -103,165 +103,72 @@ import { ref } from 'vue'
|
|
|
103
103
|
import OrgTreeSelect, { type OrgTreeNode } from '../components/OrgTreeSelect.vue'
|
|
104
104
|
import OrgTreeSelectCascade from '../components/OrgTreeSelectCascade.vue'
|
|
105
105
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
106
|
+
// 机构树数据(模拟)- 生成600条数据
|
|
107
|
+
function generateOrgData(): OrgTreeNode[] {
|
|
108
|
+
const orgNames = [
|
|
109
|
+
'北京银行', '工商银行', '建设银行', '农业银行', '中国银行',
|
|
110
|
+
'交通银行', '招商银行', '浦发银行', '兴业银行', '民生银行',
|
|
111
|
+
'中信银行', '光大银行', '华夏银行', '广发银行', '平安银行'
|
|
112
|
+
]
|
|
113
|
+
const branchTypes = ['分行', '支行', '营业部', '分理处', '储蓄所']
|
|
114
|
+
const districts = [
|
|
115
|
+
'东城', '西城', '朝阳', '丰台', '石景山', '海淀', '门头沟', '房山', '通州', '顺义',
|
|
116
|
+
'昌平', '大兴', '怀柔', '平谷', '密云', '延庆', '天津', '河北', '山西', '内蒙古',
|
|
117
|
+
'辽宁', '吉林', '黑龙江', '上海', '江苏', '浙江', '安徽', '福建', '江西', '山东'
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
const data: OrgTreeNode[] = []
|
|
121
|
+
let idCounter = 10000
|
|
122
|
+
|
|
123
|
+
// 生成10个总行
|
|
124
|
+
for (let i = 0; i < 10; i++) {
|
|
125
|
+
const bankName = orgNames[i % orgNames.length]
|
|
126
|
+
const bankId = String(idCounter++)
|
|
127
|
+
const bank: OrgTreeNode = {
|
|
128
|
+
id: bankId,
|
|
129
|
+
code: bankId,
|
|
130
|
+
name: `${bankName}总行`,
|
|
131
|
+
icon: 'OfficeBuilding',
|
|
132
|
+
children: []
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 每个总行下生成5-8个分行
|
|
136
|
+
const branchCount = 5 + Math.floor(Math.random() * 4)
|
|
137
|
+
for (let j = 0; j < branchCount; j++) {
|
|
138
|
+
const district = districts[j % districts.length]
|
|
139
|
+
const branchId = String(idCounter++)
|
|
140
|
+
const branch: OrgTreeNode = {
|
|
141
|
+
id: branchId,
|
|
142
|
+
code: branchId,
|
|
143
|
+
name: `${bankName}${district}${branchTypes[0]}`,
|
|
139
144
|
icon: 'OfficeBuilding',
|
|
140
145
|
children: []
|
|
141
146
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
children: []
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: '30001',
|
|
159
|
-
code: '30001',
|
|
160
|
-
name: '天津营业部',
|
|
161
|
-
icon: 'Document',
|
|
162
|
-
children: []
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
id: '30011',
|
|
166
|
-
code: '30011',
|
|
167
|
-
name: '天津河西支行营业部',
|
|
168
|
-
icon: 'Document',
|
|
169
|
-
children: []
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
id: '30021',
|
|
173
|
-
code: '30021',
|
|
174
|
-
name: '天津开发区支行营业部',
|
|
175
|
-
icon: 'Document',
|
|
176
|
-
children: []
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
id: '30031',
|
|
180
|
-
code: '30031',
|
|
181
|
-
name: '天津南开支行营业部',
|
|
182
|
-
icon: 'Document',
|
|
183
|
-
children: []
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
id: '30041',
|
|
187
|
-
code: '30041',
|
|
188
|
-
name: '北京银行股份有限公司天津八纬路支行',
|
|
189
|
-
icon: 'Document',
|
|
190
|
-
children: []
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
id: '30051',
|
|
194
|
-
code: '30051',
|
|
195
|
-
name: '天津梅江支行营业部',
|
|
196
|
-
icon: 'Document',
|
|
197
|
-
children: []
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: '30061',
|
|
201
|
-
code: '30061',
|
|
202
|
-
name: '天津津南支行营业部',
|
|
203
|
-
icon: 'Document',
|
|
204
|
-
children: []
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: '30071',
|
|
208
|
-
code: '30071',
|
|
209
|
-
name: '天津河北支行营业部',
|
|
210
|
-
icon: 'Document',
|
|
211
|
-
children: []
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
id: '30081',
|
|
215
|
-
code: '30081',
|
|
216
|
-
name: '北京银行股份有限公司天津河东支行',
|
|
217
|
-
icon: 'Document',
|
|
218
|
-
children: []
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
id: '30091',
|
|
222
|
-
code: '30091',
|
|
223
|
-
name: '北京银行股份有限公司天津和平支行',
|
|
224
|
-
icon: 'Document',
|
|
225
|
-
children: []
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
id: '30101',
|
|
229
|
-
code: '30101',
|
|
230
|
-
name: '天津滨海支行营业部',
|
|
231
|
-
icon: 'Document',
|
|
232
|
-
children: []
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
id: '30111',
|
|
236
|
-
code: '30111',
|
|
237
|
-
name: '天津西青支行营业部',
|
|
238
|
-
icon: 'Document',
|
|
239
|
-
children: []
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
id: '30121',
|
|
243
|
-
code: '30121',
|
|
244
|
-
name: '北京银行股份有限公司天津科创支行',
|
|
245
|
-
icon: 'Document',
|
|
246
|
-
children: []
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
id: '30131',
|
|
250
|
-
code: '30131',
|
|
251
|
-
name: '北京银行股份有限公司天津北辰支行',
|
|
252
|
-
icon: 'Document',
|
|
253
|
-
children: []
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
id: '30141',
|
|
257
|
-
code: '30141',
|
|
258
|
-
name: '北京银行天津中新生态城绿色支行',
|
|
259
|
-
icon: 'Document',
|
|
260
|
-
children: []
|
|
147
|
+
|
|
148
|
+
// 每个分行下生成8-12个支行
|
|
149
|
+
const subBranchCount = 8 + Math.floor(Math.random() * 5)
|
|
150
|
+
for (let k = 0; k < subBranchCount; k++) {
|
|
151
|
+
const subDistrict = districts[(j + k) % districts.length]
|
|
152
|
+
const subBranchId = String(idCounter++)
|
|
153
|
+
branch.children!.push({
|
|
154
|
+
id: subBranchId,
|
|
155
|
+
code: subBranchId,
|
|
156
|
+
name: `${bankName}${subDistrict}${branchTypes[1]}${k + 1}`,
|
|
157
|
+
icon: 'Document',
|
|
158
|
+
children: []
|
|
159
|
+
})
|
|
261
160
|
}
|
|
262
|
-
|
|
161
|
+
|
|
162
|
+
bank.children!.push(branch)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
data.push(bank)
|
|
263
166
|
}
|
|
264
|
-
|
|
167
|
+
|
|
168
|
+
return data
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const orgData = ref<OrgTreeNode[]>(generateOrgData())
|
|
265
172
|
|
|
266
173
|
const singleValue = ref<string | null>(null)
|
|
267
174
|
const multipleValue = ref<string[]>([])
|
package/package.json
CHANGED
package/test/111
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/directives/permission.ts
|
|
2
|
+
import type { Directive } from 'vue'
|
|
3
|
+
|
|
4
|
+
function getPermissions(): string[] {
|
|
5
|
+
return JSON.parse(localStorage.getItem('permissions') || '[]')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const permissionDirective: Directive = {
|
|
9
|
+
mounted(el, binding) {
|
|
10
|
+
const { value } = binding
|
|
11
|
+
const permissions = getPermissions()
|
|
12
|
+
|
|
13
|
+
if (!value) return
|
|
14
|
+
|
|
15
|
+
const hasPermission = Array.isArray(value)
|
|
16
|
+
? value.some(p => permissions.includes(p))
|
|
17
|
+
: permissions.includes(value)
|
|
18
|
+
|
|
19
|
+
if (!hasPermission) {
|
|
20
|
+
el.parentNode?.removeChild(el)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// main.ts
|
|
27
|
+
import { createApp } from 'vue'
|
|
28
|
+
import App from './App.vue'
|
|
29
|
+
import { permissionDirective } from '@/directives/permission'
|
|
30
|
+
|
|
31
|
+
const app = createApp(App)
|
|
32
|
+
|
|
33
|
+
app.directive('permission', permissionDirective)
|
|
34
|
+
|
|
35
|
+
app.mount('#app')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
<el-button v-permission="'user:add'">新增</el-button>
|
|
39
|
+
|
|
40
|
+
<el-button v-permission="['user:edit', 'user:update']">
|
|
41
|
+
编辑
|
|
42
|
+
</el-button>
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
<el-button v-permission="'user:add'">新增</el-button>
|
|
48
|
+
|
|
49
|
+
<el-button v-permission="['user:edit', 'user:update']">
|
|
50
|
+
编辑
|
|
51
|
+
</el-button>
|
|
52
|
+
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Vue3 按钮权限管理(进阶版:自定义指令)
|
|
2
|
+
|
|
3
|
+
## 📌 目标
|
|
4
|
+
实现一套通用的按钮权限控制方案,支持:
|
|
5
|
+
|
|
6
|
+
- ✅ 单权限 / 多权限
|
|
7
|
+
- ✅ AND / OR 权限判断
|
|
8
|
+
- ✅ 隐藏 或 禁用 控制
|
|
9
|
+
- ✅ 动态更新权限
|
|
10
|
+
- ✅ 可扩展(Pinia / 后端权限)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🧩 一、指令实现
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
// src/directives/permission.ts
|
|
18
|
+
import type { Directive } from 'vue'
|
|
19
|
+
|
|
20
|
+
function getPermissions(): string[] {
|
|
21
|
+
return JSON.parse(localStorage.getItem('permissions') || '[]')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function checkPermission(value: any, permissions: string[]) {
|
|
25
|
+
if (typeof value === 'string') {
|
|
26
|
+
return permissions.includes(value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value.some(p => permissions.includes(p))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof value === 'object') {
|
|
34
|
+
const { code, mode = 'or' } = value
|
|
35
|
+
|
|
36
|
+
if (Array.isArray(code)) {
|
|
37
|
+
return mode === 'and'
|
|
38
|
+
? code.every(p => permissions.includes(p))
|
|
39
|
+
: code.some(p => permissions.includes(p))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return permissions.includes(code)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function handleEl(el: HTMLElement, hasPermission: boolean, effect = 'remove') {
|
|
49
|
+
if (hasPermission) return
|
|
50
|
+
|
|
51
|
+
if (effect === 'disable') {
|
|
52
|
+
el.setAttribute('disabled', 'true')
|
|
53
|
+
el.classList.add('is-disabled')
|
|
54
|
+
el.style.pointerEvents = 'none'
|
|
55
|
+
} else {
|
|
56
|
+
el.parentNode?.removeChild(el)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const permissionDirective: Directive = {
|
|
61
|
+
mounted(el, binding) {
|
|
62
|
+
const permissions = getPermissions()
|
|
63
|
+
const { value, arg } = binding
|
|
64
|
+
|
|
65
|
+
const hasPermission = checkPermission(value, permissions)
|
|
66
|
+
|
|
67
|
+
handleEl(el, hasPermission, arg)
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
updated(el, binding) {
|
|
71
|
+
const permissions = getPermissions()
|
|
72
|
+
const { value, arg } = binding
|
|
73
|
+
|
|
74
|
+
const hasPermission = checkPermission(value, permissions)
|
|
75
|
+
|
|
76
|
+
handleEl(el, hasPermission, arg)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 🚀 二、全局注册
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// main.ts
|
|
87
|
+
import { createApp } from 'vue'
|
|
88
|
+
import App from './App.vue'
|
|
89
|
+
import { permissionDirective } from '@/directives/permission'
|
|
90
|
+
|
|
91
|
+
const app = createApp(App)
|
|
92
|
+
|
|
93
|
+
app.directive('permission', permissionDirective)
|
|
94
|
+
|
|
95
|
+
app.mount('#app')
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 🎯 三、使用方式
|
|
101
|
+
|
|
102
|
+
### 1️⃣ 单权限
|
|
103
|
+
```vue
|
|
104
|
+
<el-button v-permission="'user:add'">新增</el-button>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 2️⃣ 多权限(满足一个)
|
|
110
|
+
```vue
|
|
111
|
+
<el-button v-permission="['user:add','user:create']">
|
|
112
|
+
新增
|
|
113
|
+
</el-button>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### 3️⃣ 多权限(必须全部满足)
|
|
119
|
+
```vue
|
|
120
|
+
<el-button v-permission="{ code: ['user:add','user:vip'], mode: 'and' }">
|
|
121
|
+
高级新增
|
|
122
|
+
</el-button>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### 4️⃣ 无权限 → 禁用按钮
|
|
128
|
+
```vue
|
|
129
|
+
<el-button v-permission:disable="'user:add'">
|
|
130
|
+
新增
|
|
131
|
+
</el-button>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 🧠 四、推荐优化(生产环境必做)
|
|
137
|
+
|
|
138
|
+
### ✔ 使用 Pinia 管理权限
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// stores/user.ts
|
|
142
|
+
import { defineStore } from 'pinia'
|
|
143
|
+
|
|
144
|
+
export const useUserStore = defineStore('user', {
|
|
145
|
+
state: () => ({
|
|
146
|
+
permissions: [] as string[]
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### ✔ 修改指令权限来源
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { useUserStore } from '@/stores/user'
|
|
155
|
+
|
|
156
|
+
const permissions = useUserStore().permissions
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 📦 五、权限格式规范
|
|
162
|
+
|
|
163
|
+
建议统一格式:
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
user:add
|
|
167
|
+
user:delete
|
|
168
|
+
order:list
|
|
169
|
+
order:create
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 🔐 六、后端权限校验(必须)
|
|
175
|
+
|
|
176
|
+
前端权限只是 UI 控制,后端必须校验:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
if (!user.permissions.includes('user:add')) {
|
|
180
|
+
throw new Error('Forbidden')
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## ⚠️ 七、常见问题
|
|
187
|
+
|
|
188
|
+
### ❌ 权限变化 UI 不更新
|
|
189
|
+
✔ 已通过 `updated` 生命周期解决
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### ❌ Element Plus 禁用无效
|
|
194
|
+
✔ 必须加:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
el.style.pointerEvents = 'none'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### ❌ 使用 v-if 控制权限
|
|
203
|
+
不推荐,会导致:
|
|
204
|
+
- 模板混乱
|
|
205
|
+
- 逻辑分散
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 🏁 总结
|
|
210
|
+
|
|
211
|
+
推荐最终方案:
|
|
212
|
+
|
|
213
|
+
👉 自定义指令(控制 UI)
|
|
214
|
+
👉 Pinia(存权限)
|
|
215
|
+
👉 后端校验(保证安全)
|
|
216
|
+
|
|
217
|
+
这样你的权限系统:
|
|
218
|
+
- 清晰
|
|
219
|
+
- 可维护
|
|
220
|
+
- 可扩展
|