Plugin System β
Plugin System Overview
The 3.0
frontend natively supports a plugin system at its core. Compared to 2.0
which wasn't designed with plugin functionality in mind, where modifying system interfaces or behaviors required source code changesβleading to upgrade difficulties and increasing divergence from official codeβthe later addition of an app store feature allowed forced plugin support, though plugins still had to modify source code. Moreover, plugins couldn't extend initialization points and had to modify main.js
directly.
Now all these issues are resolved. The frontend plugin system provides robust support, allowing seamless integration of interface replacements, feature additions, third-party components, or custom-developed components into the system. It also offers various hooks
that can even influence and alter frontend operations.
Plugin System Architecture Overview β
The plugin system is designed based on modern frontend architecture, providing complete lifecycle management and extension capabilities:
Core Features β
- Zero-intrusion design: Plugin development doesn't require modifying core code
- Dynamic loading: Supports dynamic enabling and disabling of plugins
- Lifecycle management: Complete plugin lifecycle hooks
- Type safety: Comprehensive TypeScript type definitions
- Performance optimization: Supports lazy loading and on-demand loading
- Error isolation: Plugin errors don't affect main application operation
Plugin Data Types Introduction β
Type Definition File
Type definitions are located in types/global.d.ts
Click to view complete type definitions
declare namespace Plugin {
/**
* Basic plugin information
*/
interface Info {
/** Plugin name in format: author-namespace/plugin-name */
name: string
/** Plugin version following semantic versioning */
version: string
/** Plugin author */
author: string
/** Plugin description */
description: string
/** Plugin startup order - higher values start first (default: 0) */
order?: number
/** Plugin dependencies list */
dependencies?: string[]
/** Plugin keywords for search */
keywords?: string[]
/** Plugin homepage URL */
homepage?: string
/** Plugin license */
license?: string
/** Minimum system version requirement */
minSystemVersion?: string
}
/**
* Plugin configuration
*/
interface Config {
/** Basic plugin information */
info: Info
/** Whether plugin is enabled */
enable: boolean
/** Plugin development mode for debugging */
devMode?: boolean
/** Plugin custom settings */
settings?: Record<string, any>
}
/**
* Plugin view route definitions
*/
interface Views extends Route.RouteRecordRaw {
/** Extended route meta information */
meta?: {
/** Page title */
title?: string
/** Internationalization key */
i18n?: string
/** Page icon */
icon?: string
/** Whether authentication is required */
requireAuth?: boolean
/** Required permissions list */
permissions?: string[]
/** Whether to cache the page */
keepAlive?: boolean
/** Whether the page is hidden */
hidden?: boolean
/** Menu order */
order?: number
}
}
/**
* Hook function type definitions
*/
interface HookHandlers {
/** Plugin startup hook for initialization validation */
start?: (config: Config) => Promise<boolean | void> | boolean | void
/** System initialization complete hook with Vue context access */
setup?: () => Promise<void> | void
/** Route registration hook for modifying route configurations */
registerRoute?: (router: Router, routesRaw: Route.RouteRecordRaw[] | Views[] | MineRoute.routeRecord[]) => Promise<void> | void
/** User login hook */
login?: (formInfo: LoginFormData) => Promise<void> | void
/** User logout hook */
logout?: () => Promise<void> | void
/** Get user information hook */
getUserInfo?: (userInfo: UserInfo) => Promise<void> | void
/** Route redirect hook (external links invalid) */
routerRedirect?: (context: { from: RouteLocationNormalized, to: RouteLocationNormalized }, router: Router) => Promise<void> | void
/** Network request interception hook */
networkRequest?: <T = any>(request: AxiosRequestConfig) => Promise<AxiosRequestConfig> | AxiosRequestConfig
/** Network response interception hook */
networkResponse?: <T = any>(response: AxiosResponse<T>) => Promise<AxiosResponse<T>> | AxiosResponse<T>
/** Error handling hook */
error?: (error: Error, context?: string) => Promise<void> | void
/** Page load complete hook */
mounted?: () => Promise<void> | void
/** Page destruction hook */
beforeDestroy?: () => Promise<void> | void
}
/**
* Main plugin configuration interface
*/
interface PluginConfig {
/** Plugin installation function - registers components, directives, etc. */
install: (app: App<Element>) => Promise<void> | void
/** Plugin configuration information */
config: Config
/** Plugin route definitions */
views?: Views[]
/** Plugin hook functions */
hooks?: HookHandlers
/** Plugin custom properties */
[key: string]: any
}
/**
* Plugin storage state
*/
interface PluginStore {
/** List of installed plugins */
plugins: Map<string, PluginConfig>
/** Plugin enable status */
enabledPlugins: Set<string>
/** Plugin loading status */
loadingPlugins: Set<string>
/** Plugin error information */
pluginErrors: Map<string, Error>
}
/**
* Plugin manager interface
*/
interface PluginManager {
/** Register plugin */
register(name: string, plugin: PluginConfig): Promise<boolean>
/** Uninstall plugin */
unregister(name: string): Promise<boolean>
/** Enable plugin */
enable(name: string): Promise<boolean>
/** Disable plugin */
disable(name: string): Promise<boolean>
/** Get plugin information */
getPlugin(name: string): PluginConfig | null
/** Get all plugins */
getAllPlugins(): Map<string, PluginConfig>
/** Check plugin dependencies */
checkDependencies(name: string): Promise<boolean>
}
}
/**
* Login form data type
*/
interface LoginFormData {
username: string
password: string
captcha?: string
remember?: boolean
}
/**
* User information type
*/
interface UserInfo {
id: number
username: string
nickname: string
email: string
avatar: string
roles: string[]
permissions: string[]
[key: string]: any
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
Creating Plugins β
Directory Structure and Naming Conventions β
All plugins are placed in the src/plugins
directory, with the alias $
pointing to this directory. Plugins follow the same structure as the backend, consisting of developer-namespace/plugin-name
to form the plugin directory. The left side of the slash is the author namespace (configurable on MineAdmin website), while the right side is the plugin name, which must be unique within that author namespace.
Standard Plugin Directory Structure β
src/plugins/
βββ mine-admin/ # Official plugin namespace
β βββ app-store/ # App Store plugin
β βββ basic-ui/ # Basic UI library plugin
β βββ demo/ # Official demo plugin
βββ author-name/ # Third-party developer namespace
β βββ plugin-name/ # Specific plugin directory
β βββ index.ts # Plugin entry file (required)
β βββ config.ts # Plugin config file (optional)
β βββ package.json # Plugin package info (recommended)
β βββ README.md # Plugin documentation (recommended)
β βββ views/ # Page components directory
β β βββ index.vue
β β βββ components/
β βββ components/ # Reusable components
β βββ composables/ # Composable functions
β βββ utils/ # Utility functions
β βββ assets/ # Static assets
β βββ locales/ # Internationalization files
β β βββ zh.json
β β βββ en.json
β β βββ ja.json
β βββ types/ # TypeScript type definitions
β βββ tests/ # Test files
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Naming Convention Suggestions β
- Plugin name: Use lowercase letters and hyphens, e.g.,
file-manager
,data-export
- Author namespace: Use lowercase letters and hyphens, avoid special characters
- File naming: Follow kebab-case convention
- Component names: Use PascalCase, e.g.,
FileUploader.vue
Best Practices
- Locally developed plugins can also be recognized by the system but cannot be uploaded to MineAdmin App Market
- Adding a
package.json
is recommended for dependency and version management - Using TypeScript provides better type hints and error checking
- Follow Vue 3 Composition API best practices
Important Notes
- Plugin names must be unique within the same author namespace
- Avoid using system reserved words as plugin names
- Avoid changing plugin directory names after creation
Plugin Lifecycle β
Plugin Development Guide β
Basic Plugin Example β
Let's examine a complete file manager plugin to understand the full plugin development process:
1. Create Plugin Entry File index.ts
β
// src/plugins/zhang-san/file-manager/index.ts
import type { App } from 'vue'
import type { Router, RouteRecordRaw } from 'vue-router'
import type { Plugin } from '#/global'
import { ElMessage, ElNotification } from 'element-plus'
// Import plugin components
import FileManagerComponent from './components/FileManager.vue'
import FileUploader from './components/FileUploader.vue'
// Import utility functions
import { formatFileSize, validateFileType } from './utils/fileUtils'
// Plugin configuration
const pluginConfig: Plugin.PluginConfig = {
// Plugin installation method - register global components, directives, etc.
async install(app: App) {
try {
// Register global components
app.component('FileManager', FileManagerComponent)
app.component('FileUploader', FileUploader)
// Register global directive
app.directive('file-drop', {
mounted(el, binding) {
el.addEventListener('dragover', (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
})
el.addEventListener('drop', async (e: DragEvent) => {
e.preventDefault()
e.stopPropagation()
const files = Array.from(e.dataTransfer?.files || [])
await binding.value(files)
})
}
})
// Add global properties
app.config.globalProperties.$fileUtils = {
formatSize: formatFileSize,
validateType: validateFileType
}
console.log('File manager plugin installed successfully')
} catch (error) {
console.error('File manager plugin installation failed:', error)
throw error
}
},
// Basic plugin configuration
config: {
enable: import.meta.env.NODE_ENV !== 'production', // Disable in production
devMode: import.meta.env.DEV,
info: {
name: 'zhang-san/file-manager',
version: '2.1.0',
author: 'Zhang San',
description: 'Enterprise file management plugin with upload, download, preview, and permission control',
keywords: ['file management', 'file upload', 'permission control'],
homepage: 'https://github.com/zhang-san/file-manager',
license: 'MIT',
minSystemVersion: '3.0.0',
dependencies: ['mine-admin/basic-ui'],
order: 10 // Higher priority
},
settings: {
maxFileSize: 50 * 1024 * 1024, // 50MB
allowedTypes: ['image/*', 'application/pdf', '.docx', '.xlsx'],
uploadChunkSize: 1024 * 1024, // 1MB
enablePreview: true,
enableVersionControl: false
}
},
// Plugin hook functions
hooks: {
// Plugin startup validation
async start(config) {
console.log('File manager plugin starting...', config.info.name)
// Check required permissions
const hasPermission = await checkFilePermissions()
if (!hasPermission) {
ElMessage.error('File manager plugin requires file operation permissions')
return false // Prevent plugin startup
}
// Initialize plugin settings
await initializeSettings(config.settings)
return true
},
// Executed after system initialization
async setup() {
// Initialize file storage
await initFileStorage()
// Register file type mappings
registerFileTypes()
// Listen to system events
window.addEventListener('beforeunload', handleBeforeUnload)
},
// Route registration hook
async registerRoute(router: Router, routesRaw) {
// Dynamically add file management routes
const adminRoutes = routesRaw.find(route => route.path === '/admin')
if (adminRoutes && adminRoutes.children) {
adminRoutes.children.push({
path: 'files',
name: 'FileManagement',
component: () => import('./views/FileManagement.vue'),
meta: {
title: 'File Management',
icon: 'FolderOpened',
requireAuth: true,
permissions: ['file:read'],
keepAlive: true
}
})
}
console.log('File management routes registered')
},
// Post-login hook
async login(formInfo) {
console.log('User logged in, initializing file permissions')
await refreshFilePermissions(formInfo.username)
},
// Logout hook
async logout() {
console.log('User logged out, cleaning file cache')
await clearFileCache()
},
// Post-user info hook
async getUserInfo(userInfo) {
// Set file permissions based on user roles
await setFilePermissions(userInfo.roles, userInfo.permissions)
},
// Network request interception
async networkRequest(config) {
// Special handling for file upload requests
if (config.url?.includes('/upload')) {
config.timeout = 300000 // 5-minute timeout
config.headers = {
...config.headers,
'X-File-Plugin': 'zhang-san/file-manager'
}
}
return config
},
// Network response interception
async networkResponse(response) {
// Handle file download responses
if (response.headers['content-type']?.includes('application/octet-stream')) {
const contentDisposition = response.headers['content-disposition']
if (contentDisposition) {
const filename = extractFilename(contentDisposition)
response.metadata = { filename }
}
}
return response
},
// Error handling
async error(error, context) {
if (context === 'file-upload') {
ElNotification.error({
title: 'File upload failed',
message: error.message,
duration: 5000
})
}
},
// Pre-destruction cleanup
async beforeDestroy() {
console.log('File manager plugin about to destroy, cleaning resources...')
// Cancel ongoing uploads
await cancelAllUploads()
// Clean up event listeners
window.removeEventListener('beforeunload', handleBeforeUnload)
// Clean up temporary files
await cleanupTempFiles()
}
},
// Plugin route definitions
views: [
{
name: 'zhangsan:filemanager:index',
path: '/plugins/file-manager',
component: () => import('./views/FileManagerIndex.vue'),
meta: {
title: 'File Manager',
i18n: 'plugin.fileManager.title',
icon: 'FolderOpened',
requireAuth: true,
permissions: ['file:read'],
keepAlive: true,
hidden: false
}
},
{
name: 'zhangsan:filemanager:upload',
path: '/plugins/file-manager/upload',
component: () => import('./views/FileUpload.vue'),
meta: {
title: 'File Upload',
i18n: 'plugin.fileManager.upload',
icon: 'Upload',
requireAuth: true,
permissions: ['file:create'],
keepAlive: false
}
}
]
}
// Helper functions
async function checkFilePermissions(): Promise<boolean>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233