vue-intercept-plugin 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 +293 -0
- package/dist/__tests__/index.test.d.ts +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/vue-intercept-plugin.cjs.js +115 -0
- package/dist/vue-intercept-plugin.esm.js +108 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# vue-intercept-plugin
|
|
2
|
+
|
|
3
|
+
[中文](README_ZH.md) | English
|
|
4
|
+
|
|
5
|
+
A lightweight Vue plugin providing the `v-intercept` custom directive for intercepting DOM events. Defaults to click, with support for any event type via directive arguments.
|
|
6
|
+
|
|
7
|
+
> The plugin does not handle permission checking — it does one thing: intercepts an event and passes it to your function.
|
|
8
|
+
|
|
9
|
+
## How It Works
|
|
10
|
+
|
|
11
|
+
### 🎯 Event Interception Mechanism
|
|
12
|
+
|
|
13
|
+
1. **Directive Parsing**: Resolves the `v-intercept` value at runtime, supporting both function and array formats
|
|
14
|
+
2. **Auto Binding**: Uses `addEventListener` to bind to the specified event type (default: click)
|
|
15
|
+
3. **Parameter Forwarding**: When using array syntax, the event object is automatically appended as the last argument
|
|
16
|
+
4. **Auto Cleanup**: Removes event listeners via `removeEventListener` on component unmount to prevent memory leaks
|
|
17
|
+
|
|
18
|
+
### 📋 Directive Format Reference
|
|
19
|
+
|
|
20
|
+
| Syntax | Description |
|
|
21
|
+
|--------|-------------|
|
|
22
|
+
| `v-intercept="handleFn"` | Pass a function directly. Receives the event object automatically |
|
|
23
|
+
| `v-intercept="[handleFn, arg1, arg2]"` | Array syntax. First item is the function, rest are arguments. **Event object auto-appended to the end** |
|
|
24
|
+
| `v-intercept:change="handleFn"` | Specify event type via directive argument. Defaults to `click` |
|
|
25
|
+
| `v-intercept:change="[handleFn, arg1]"` | Custom event + array arguments, work together |
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install vue-intercept-plugin
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { createApp } from 'vue'
|
|
37
|
+
import VueInterceptPlugin from 'vue-intercept-plugin'
|
|
38
|
+
|
|
39
|
+
// Register plugin (no configuration needed)
|
|
40
|
+
const app = createApp(App)
|
|
41
|
+
app.use(VueInterceptPlugin)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Vue 2
|
|
46
|
+
import Vue from 'vue'
|
|
47
|
+
import VueInterceptPlugin from 'vue-intercept-plugin'
|
|
48
|
+
|
|
49
|
+
Vue.use(VueInterceptPlugin)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<template>
|
|
54
|
+
<!-- No arguments: pass function directly -->
|
|
55
|
+
<el-button v-intercept="handleDelete" type="danger">Delete</el-button>
|
|
56
|
+
|
|
57
|
+
<!-- Array syntax: [function, arg1, arg2, ...] -->
|
|
58
|
+
<el-button v-intercept="[handleDeleteById, 1001]" type="warning">
|
|
59
|
+
Delete Order #1001
|
|
60
|
+
</el-button>
|
|
61
|
+
|
|
62
|
+
<!-- Custom event type via directive argument -->
|
|
63
|
+
<select v-intercept:change="handleChange">
|
|
64
|
+
<option value="">Select...</option>
|
|
65
|
+
<option value="1">Option 1</option>
|
|
66
|
+
<option value="2">Option 2</option>
|
|
67
|
+
</select>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
const handleDelete = () => {
|
|
72
|
+
if (!hasPermission) {
|
|
73
|
+
ElMessage.warning('No permission')
|
|
74
|
+
return // Intercept
|
|
75
|
+
}
|
|
76
|
+
// Execute delete logic
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const handleDeleteById = (id: number, event: MouseEvent) => {
|
|
80
|
+
if (!hasPermission) return
|
|
81
|
+
deleteApi(id)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const handleChange = (event: Event) => {
|
|
85
|
+
if (!canChange) return
|
|
86
|
+
const value = (event.target as HTMLSelectElement).value
|
|
87
|
+
// Handle selection logic
|
|
88
|
+
}
|
|
89
|
+
</script>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API
|
|
93
|
+
|
|
94
|
+
### `app.use(VueInterceptPlugin)`
|
|
95
|
+
|
|
96
|
+
Registers the plugin. No arguments required. Internally auto-detects the Vue version (Vue 3 uses `mounted`/`updated`/`unmounted` hooks; Vue 2 uses `bind`/`update`/`unbind` hooks).
|
|
97
|
+
|
|
98
|
+
### v-intercept Directive
|
|
99
|
+
|
|
100
|
+
**Directive Argument (Event Type):**
|
|
101
|
+
|
|
102
|
+
| Syntax | Description |
|
|
103
|
+
|--------|-------------|
|
|
104
|
+
| `v-intercept="handler"` | Default: click |
|
|
105
|
+
| `v-intercept:click="handler"` | Explicit click |
|
|
106
|
+
| `v-intercept:change="handler"` | change event |
|
|
107
|
+
| `v-intercept:submit="handler"` | Form submit |
|
|
108
|
+
| `v-intercept:contextmenu="handler"` | Right-click menu |
|
|
109
|
+
| `v-intercept:dblclick="handler"` | Double-click |
|
|
110
|
+
| `v-intercept:dragstart="handler"` | Drag start |
|
|
111
|
+
| `v-intercept:copy="handler"` | Copy |
|
|
112
|
+
| `...` | Any native DOM event |
|
|
113
|
+
|
|
114
|
+
**Directive Value (Handler):**
|
|
115
|
+
|
|
116
|
+
| Format | Example | Description |
|
|
117
|
+
|--------|---------|-------------|
|
|
118
|
+
| `handler` | `v-intercept="handleDelete"` | Pass function directly. Receives event object |
|
|
119
|
+
| `[handler, ...args]` | `v-intercept="[handleDelete, 1001]"` | Array syntax. **Event object auto-appended to arguments** |
|
|
120
|
+
| `[handler]` | `v-intercept="[handleDelete]"` | Function only, no extra args. Equivalent to passing function directly |
|
|
121
|
+
|
|
122
|
+
**Function Signature Reference:**
|
|
123
|
+
|
|
124
|
+
| Template Syntax | Actual Arguments Received |
|
|
125
|
+
|----------------|-------------------------|
|
|
126
|
+
| `v-intercept="handle"` | `handle(event)` |
|
|
127
|
+
| `v-intercept="[handle, id]"` | `handle(id, event)` |
|
|
128
|
+
| `v-intercept="[handle, id, name]"` | `handle(id, name, event)` |
|
|
129
|
+
| `v-intercept:change="handle"` | `handle(event)` |
|
|
130
|
+
| `v-intercept:change="[handle, id]"` | `handle(id, event)` |
|
|
131
|
+
|
|
132
|
+
## Advanced Usage
|
|
133
|
+
|
|
134
|
+
### Permission-Guarded Buttons
|
|
135
|
+
|
|
136
|
+
```vue
|
|
137
|
+
<el-button v-intercept="handleDelete" type="danger">Delete</el-button>
|
|
138
|
+
<el-button v-intercept="[handleApprove, 'order_001']" type="primary">Approve</el-button>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const handleDelete = () => {
|
|
143
|
+
if (!userHasPermission('delete')) {
|
|
144
|
+
ElMessage.warning('No permission to delete')
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
deleteApi()
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Right-Click Menu Guard
|
|
152
|
+
|
|
153
|
+
```vue
|
|
154
|
+
<div v-intercept:contextmenu="handleContextMenu" class="table-row">
|
|
155
|
+
Right-click this area
|
|
156
|
+
</div>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const handleContextMenu = (event: MouseEvent) => {
|
|
161
|
+
event.preventDefault()
|
|
162
|
+
if (!hasRightClickPermission) return
|
|
163
|
+
showContextMenu(event.clientX, event.clientY)
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Select Change Guard
|
|
168
|
+
|
|
169
|
+
```vue
|
|
170
|
+
<el-select v-intercept:change="[handleRoleChange, 'User Management']" placeholder="Select role">
|
|
171
|
+
<el-option label="Admin" value="admin" />
|
|
172
|
+
<el-option label="Editor" value="editor" />
|
|
173
|
+
<el-option label="Guest" value="guest" />
|
|
174
|
+
</el-select>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const handleRoleChange = (section: string, event: Event) => {
|
|
179
|
+
if (!canChangeRole) {
|
|
180
|
+
ElMessage.warning(`Cannot change role for 「${section}」`)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
updateRole()
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Toggle Switch Guard
|
|
188
|
+
|
|
189
|
+
```vue
|
|
190
|
+
<el-switch v-intercept:change="[handleFeatureToggle, 'Export']" />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const handleFeatureToggle = (feature: string, event: Event) => {
|
|
195
|
+
const checked = (event.target as HTMLInputElement).checked
|
|
196
|
+
if (checked && !canEnableFeature(feature)) {
|
|
197
|
+
ElMessage.warning(`Cannot enable 「${feature}」`)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
toggleFeature(feature, checked)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Double-Click Edit Guard
|
|
205
|
+
|
|
206
|
+
```vue
|
|
207
|
+
<div v-intercept:dblclick="[handleDoubleClick, item.id]">
|
|
208
|
+
{{ item.name }}
|
|
209
|
+
</div>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
const handleDoubleClick = (id: number, event: MouseEvent) => {
|
|
214
|
+
if (!canEdit) return
|
|
215
|
+
enterEditMode(id)
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Drag Guard
|
|
220
|
+
|
|
221
|
+
```vue
|
|
222
|
+
<div v-intercept:dragstart="[handleDragStart, file.name]" draggable="true">
|
|
223
|
+
{{ file.name }}
|
|
224
|
+
</div>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const handleDragStart = (fileName: string, event: DragEvent) => {
|
|
229
|
+
if (!canDrag) {
|
|
230
|
+
event.preventDefault()
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
event.dataTransfer?.setData('text/plain', fileName)
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Build Artifacts
|
|
238
|
+
|
|
239
|
+
| File | Format | Description |
|
|
240
|
+
|------|--------|-------------|
|
|
241
|
+
| `dist/vue-intercept-plugin.cjs.js` | CJS | CommonJS for Node.js require |
|
|
242
|
+
| `dist/vue-intercept-plugin.esm.js` | ESM | ES Module, Tree-shakable |
|
|
243
|
+
|
|
244
|
+
## Technical Details
|
|
245
|
+
|
|
246
|
+
### Why Not Wrap Components?
|
|
247
|
+
|
|
248
|
+
When using third-party UI libraries like Element Plus, wrapping every permission-sensitive component (e.g., `PermissionButton`) leads to:
|
|
249
|
+
- High wrapping cost — requires handling extensive prop forwarding
|
|
250
|
+
- Poor coverage — hard to cover all UI components that need permission control (buttons, selects, switches, etc.)
|
|
251
|
+
- Duplicated effort — different UI libraries need separate wrappers
|
|
252
|
+
|
|
253
|
+
Vue custom directives operate directly on native DOM events — zero component wrapping, one line of code.
|
|
254
|
+
|
|
255
|
+
### Vue 2 / Vue 3 Compatibility
|
|
256
|
+
|
|
257
|
+
The plugin auto-detects the Vue version via `app.version` and selects the appropriate directive hooks:
|
|
258
|
+
|
|
259
|
+
| Vue Version | Bind Hook | Update Hook | Unbind Hook |
|
|
260
|
+
|-------------|-----------|-------------|-------------|
|
|
261
|
+
| Vue 3 | `mounted` | `updated` | `unmounted` |
|
|
262
|
+
| Vue 2 | `bind` | `update` | `unbind` |
|
|
263
|
+
|
|
264
|
+
### Memory Safety
|
|
265
|
+
|
|
266
|
+
- Old listeners are removed before re-binding to prevent duplicate handlers
|
|
267
|
+
- Listeners are automatically removed on component unmount, references are cleaned up
|
|
268
|
+
- Different event types use separate storage keys (`_intcpt_click` / `_intcpt_change`), keeping them isolated
|
|
269
|
+
|
|
270
|
+
## Development
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Install dependencies
|
|
274
|
+
npm install
|
|
275
|
+
|
|
276
|
+
# Build
|
|
277
|
+
npm run build
|
|
278
|
+
|
|
279
|
+
# Run tests
|
|
280
|
+
npm test
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Testing
|
|
284
|
+
|
|
285
|
+
Uses Vitest + jsdom with 28 test cases covering:
|
|
286
|
+
|
|
287
|
+
- `resolveHandler` — direct function passthrough, array argument forwarding with event auto-append, multiple arguments, invalid value warnings, null/undefined edge cases
|
|
288
|
+
- `bindHandler / unbindHandler` — click binding, change binding, auto-replacement on re-bind, unbind cleanup, event type isolation, contextmenu/dblclick special events
|
|
289
|
+
- Plugin install — Vue3/Vue2 directive registration, correct hook names, mounted event binding, custom event types, array arguments with event auto-append, unmounted cleanup, graceful handling of invalid values
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析指令值,生成实际的事件处理函数
|
|
3
|
+
* 支持两种格式:
|
|
4
|
+
* 1. 函数:v-intercept="handleDelete"
|
|
5
|
+
* 2. 数组:v-intercept="[handleDelete, 1001]"
|
|
6
|
+
*/
|
|
7
|
+
declare function resolveHandler(value: unknown): EventListener | null;
|
|
8
|
+
/**
|
|
9
|
+
* 在元素上绑定事件
|
|
10
|
+
*/
|
|
11
|
+
declare function bindHandler(el: HTMLElement, eventType: string, handler: EventListener): void;
|
|
12
|
+
/**
|
|
13
|
+
* 移除元素上的事件监听
|
|
14
|
+
*/
|
|
15
|
+
declare function unbindHandler(el: HTMLElement, eventType: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* Vue 事件拦截插件
|
|
18
|
+
*
|
|
19
|
+
* 提供 v-intercept 自定义指令,用于拦截事件。
|
|
20
|
+
* 默认拦截 click 事件,可通过指令参数指定其他事件类型。
|
|
21
|
+
* 开发者在使用时,在函数内部自行判断权限并决定是否放行。
|
|
22
|
+
*/
|
|
23
|
+
declare const VueInterceptPlugin: {
|
|
24
|
+
install(app: any): void;
|
|
25
|
+
};
|
|
26
|
+
export default VueInterceptPlugin;
|
|
27
|
+
export { resolveHandler, bindHandler, unbindHandler };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 用 WeakMap 存储元素 → 事件类型 → 监听函数的映射
|
|
7
|
+
* 避免在 DOM 元素上挂载自定义属性(el._intcpt_xxx)
|
|
8
|
+
*/
|
|
9
|
+
const handlerMap = new WeakMap();
|
|
10
|
+
/**
|
|
11
|
+
* 获取元素上指定事件类型的监听函数
|
|
12
|
+
*/
|
|
13
|
+
function getHandler(el, eventType) {
|
|
14
|
+
var _a;
|
|
15
|
+
return (_a = handlerMap.get(el)) === null || _a === void 0 ? void 0 : _a.get(eventType);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 设置元素上指定事件类型的监听函数
|
|
19
|
+
*/
|
|
20
|
+
function setHandler(el, eventType, handler) {
|
|
21
|
+
let typeMap = handlerMap.get(el);
|
|
22
|
+
if (!typeMap) {
|
|
23
|
+
typeMap = new Map();
|
|
24
|
+
handlerMap.set(el, typeMap);
|
|
25
|
+
}
|
|
26
|
+
typeMap.set(eventType, handler);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 删除元素上指定事件类型的监听函数
|
|
30
|
+
*/
|
|
31
|
+
function deleteHandler(el, eventType) {
|
|
32
|
+
var _a, _b;
|
|
33
|
+
(_a = handlerMap.get(el)) === null || _a === void 0 ? void 0 : _a.delete(eventType);
|
|
34
|
+
if (((_b = handlerMap.get(el)) === null || _b === void 0 ? void 0 : _b.size) === 0) {
|
|
35
|
+
handlerMap.delete(el);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 解析指令值,生成实际的事件处理函数
|
|
40
|
+
* 支持两种格式:
|
|
41
|
+
* 1. 函数:v-intercept="handleDelete"
|
|
42
|
+
* 2. 数组:v-intercept="[handleDelete, 1001]"
|
|
43
|
+
*/
|
|
44
|
+
function resolveHandler(value) {
|
|
45
|
+
if (typeof value === 'function') {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
const [fn, ...args] = value;
|
|
50
|
+
if (typeof fn !== 'function') {
|
|
51
|
+
console.warn('[vue-intercept-plugin] 数组第一项必须是一个函数');
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return (event) => { fn(...args, event); };
|
|
55
|
+
}
|
|
56
|
+
console.warn('[vue-intercept-plugin] v-intercept 的值必须是函数或 [函数, ...参数] 数组');
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* 在元素上绑定事件
|
|
61
|
+
*/
|
|
62
|
+
function bindHandler(el, eventType, handler) {
|
|
63
|
+
const oldHandler = getHandler(el, eventType);
|
|
64
|
+
if (oldHandler) {
|
|
65
|
+
el.removeEventListener(eventType, oldHandler);
|
|
66
|
+
}
|
|
67
|
+
el.addEventListener(eventType, handler);
|
|
68
|
+
setHandler(el, eventType, handler);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 移除元素上的事件监听
|
|
72
|
+
*/
|
|
73
|
+
function unbindHandler(el, eventType) {
|
|
74
|
+
const handler = getHandler(el, eventType);
|
|
75
|
+
if (handler) {
|
|
76
|
+
el.removeEventListener(eventType, handler);
|
|
77
|
+
deleteHandler(el, eventType);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Vue 事件拦截插件
|
|
82
|
+
*
|
|
83
|
+
* 提供 v-intercept 自定义指令,用于拦截事件。
|
|
84
|
+
* 默认拦截 click 事件,可通过指令参数指定其他事件类型。
|
|
85
|
+
* 开发者在使用时,在函数内部自行判断权限并决定是否放行。
|
|
86
|
+
*/
|
|
87
|
+
const VueInterceptPlugin = {
|
|
88
|
+
install(app) {
|
|
89
|
+
// SSR 安全:非浏览器环境直接跳过
|
|
90
|
+
if (typeof window === 'undefined') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const isVue3 = typeof app.version === 'string';
|
|
94
|
+
function bind(el, binding) {
|
|
95
|
+
const handler = resolveHandler(binding.value);
|
|
96
|
+
if (!handler)
|
|
97
|
+
return;
|
|
98
|
+
const eventType = binding.arg || 'click';
|
|
99
|
+
bindHandler(el, eventType, handler);
|
|
100
|
+
}
|
|
101
|
+
function unbind(el, binding) {
|
|
102
|
+
const eventType = binding.arg || 'click';
|
|
103
|
+
unbindHandler(el, eventType);
|
|
104
|
+
}
|
|
105
|
+
const directive = isVue3
|
|
106
|
+
? { mounted: bind, updated: bind, unmounted: unbind }
|
|
107
|
+
: { bind, update: bind, unbind };
|
|
108
|
+
app.directive('intercept', directive);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
exports.bindHandler = bindHandler;
|
|
113
|
+
exports.default = VueInterceptPlugin;
|
|
114
|
+
exports.resolveHandler = resolveHandler;
|
|
115
|
+
exports.unbindHandler = unbindHandler;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 用 WeakMap 存储元素 → 事件类型 → 监听函数的映射
|
|
3
|
+
* 避免在 DOM 元素上挂载自定义属性(el._intcpt_xxx)
|
|
4
|
+
*/
|
|
5
|
+
const handlerMap = new WeakMap();
|
|
6
|
+
/**
|
|
7
|
+
* 获取元素上指定事件类型的监听函数
|
|
8
|
+
*/
|
|
9
|
+
function getHandler(el, eventType) {
|
|
10
|
+
var _a;
|
|
11
|
+
return (_a = handlerMap.get(el)) === null || _a === void 0 ? void 0 : _a.get(eventType);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 设置元素上指定事件类型的监听函数
|
|
15
|
+
*/
|
|
16
|
+
function setHandler(el, eventType, handler) {
|
|
17
|
+
let typeMap = handlerMap.get(el);
|
|
18
|
+
if (!typeMap) {
|
|
19
|
+
typeMap = new Map();
|
|
20
|
+
handlerMap.set(el, typeMap);
|
|
21
|
+
}
|
|
22
|
+
typeMap.set(eventType, handler);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 删除元素上指定事件类型的监听函数
|
|
26
|
+
*/
|
|
27
|
+
function deleteHandler(el, eventType) {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
(_a = handlerMap.get(el)) === null || _a === void 0 ? void 0 : _a.delete(eventType);
|
|
30
|
+
if (((_b = handlerMap.get(el)) === null || _b === void 0 ? void 0 : _b.size) === 0) {
|
|
31
|
+
handlerMap.delete(el);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 解析指令值,生成实际的事件处理函数
|
|
36
|
+
* 支持两种格式:
|
|
37
|
+
* 1. 函数:v-intercept="handleDelete"
|
|
38
|
+
* 2. 数组:v-intercept="[handleDelete, 1001]"
|
|
39
|
+
*/
|
|
40
|
+
function resolveHandler(value) {
|
|
41
|
+
if (typeof value === 'function') {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
const [fn, ...args] = value;
|
|
46
|
+
if (typeof fn !== 'function') {
|
|
47
|
+
console.warn('[vue-intercept-plugin] 数组第一项必须是一个函数');
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return (event) => { fn(...args, event); };
|
|
51
|
+
}
|
|
52
|
+
console.warn('[vue-intercept-plugin] v-intercept 的值必须是函数或 [函数, ...参数] 数组');
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 在元素上绑定事件
|
|
57
|
+
*/
|
|
58
|
+
function bindHandler(el, eventType, handler) {
|
|
59
|
+
const oldHandler = getHandler(el, eventType);
|
|
60
|
+
if (oldHandler) {
|
|
61
|
+
el.removeEventListener(eventType, oldHandler);
|
|
62
|
+
}
|
|
63
|
+
el.addEventListener(eventType, handler);
|
|
64
|
+
setHandler(el, eventType, handler);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 移除元素上的事件监听
|
|
68
|
+
*/
|
|
69
|
+
function unbindHandler(el, eventType) {
|
|
70
|
+
const handler = getHandler(el, eventType);
|
|
71
|
+
if (handler) {
|
|
72
|
+
el.removeEventListener(eventType, handler);
|
|
73
|
+
deleteHandler(el, eventType);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Vue 事件拦截插件
|
|
78
|
+
*
|
|
79
|
+
* 提供 v-intercept 自定义指令,用于拦截事件。
|
|
80
|
+
* 默认拦截 click 事件,可通过指令参数指定其他事件类型。
|
|
81
|
+
* 开发者在使用时,在函数内部自行判断权限并决定是否放行。
|
|
82
|
+
*/
|
|
83
|
+
const VueInterceptPlugin = {
|
|
84
|
+
install(app) {
|
|
85
|
+
// SSR 安全:非浏览器环境直接跳过
|
|
86
|
+
if (typeof window === 'undefined') {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const isVue3 = typeof app.version === 'string';
|
|
90
|
+
function bind(el, binding) {
|
|
91
|
+
const handler = resolveHandler(binding.value);
|
|
92
|
+
if (!handler)
|
|
93
|
+
return;
|
|
94
|
+
const eventType = binding.arg || 'click';
|
|
95
|
+
bindHandler(el, eventType, handler);
|
|
96
|
+
}
|
|
97
|
+
function unbind(el, binding) {
|
|
98
|
+
const eventType = binding.arg || 'click';
|
|
99
|
+
unbindHandler(el, eventType);
|
|
100
|
+
}
|
|
101
|
+
const directive = isVue3
|
|
102
|
+
? { mounted: bind, updated: bind, unmounted: unbind }
|
|
103
|
+
: { bind, update: bind, unbind };
|
|
104
|
+
app.directive('intercept', directive);
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export { bindHandler, VueInterceptPlugin as default, resolveHandler, unbindHandler };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vue-intercept-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "v-intercept 自定义指令,纯点击拦截,兼容 Vue 2 & Vue 3。",
|
|
5
|
+
"main": "dist/vue-intercept-plugin.cjs.js",
|
|
6
|
+
"module": "dist/vue-intercept-plugin.esm.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "rollup -c rollup.config.mjs",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vue",
|
|
17
|
+
"vue2",
|
|
18
|
+
"vue3",
|
|
19
|
+
"intercept",
|
|
20
|
+
"directive",
|
|
21
|
+
"plugin"
|
|
22
|
+
],
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"homepage": "https://github.com/wangkai000/vue-intercept-plugin",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/wangkai000/vue-intercept-plugin.git"
|
|
28
|
+
},
|
|
29
|
+
"author": "wangkai000",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
33
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
34
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
35
|
+
"jsdom": "^29.1.1",
|
|
36
|
+
"rollup": "^4.0.0",
|
|
37
|
+
"tslib": "^2.6.0",
|
|
38
|
+
"typescript": "^5.3.0",
|
|
39
|
+
"vitest": "^4.1.6"
|
|
40
|
+
}
|
|
41
|
+
}
|