vue-element-admin is a production-ready front-end solution for building admin interfaces. Built on Vue 2.6 and Element UI 2.13, it provides a complete admin dashboard framework with 30+ reusable components, a role-based permission system, mock server for development, and built-in support for Excel/ZIP/PDF export, rich text editing, drag-and-drop interfaces, and data visualization. Version 4.4.0, MIT licensed, by PanJiaChen.
This guide covers everything you need to install, configure, and use vue-element-admin for your projects. For architecture details, deployment strategies, and internals, see the Admin Guide.
1. Quick Start
Get a working admin dashboard running in under five minutes.
1.1 Prerequisites
| Requirement | Minimum Version | Recommended |
|---|---|---|
| Node.js | 8.9+ | 12.x or 14.x |
| npm | 3.0.0+ | 6.x+ |
| Git | Any recent version | Latest |
Note
vue-element-admin is built with Vue CLI 4.4.4. Node versions above 16 may require the
--openssl-legacy-providerflag due to OpenSSL 3.0 changes. If you encounter hash-related build errors, setNODE_OPTIONS=--openssl-legacy-providerbefore running dev or build commands.
1.2 Clone and Run
# Clone the repository
git clone https://github.com/PanJiaChen/vue-element-admin.git
# Enter the project directory
cd vue-element-admin
# Install dependencies
npm install
# Start the development server
npm run dev
The dev server starts on port 9527. Open http://localhost:9527 in your browser.
1.3 First Login
The project ships with a mock server (MockJS) that intercepts HTTP requests — no backend needed for development.
| Username | Password | Role | Access Level |
|---|---|---|---|
admin |
any password | admin | Full access to all routes and features |
editor |
any password | editor | Limited access (restricted routes) |
Type admin as the username, enter anything for the password, and click Login. You'll land on the Dashboard with the full sidebar menu available.
1.4 Project Structure at a Glance
vue-element-admin/
├── build/ # Build scripts (preview server)
├── mock/ # Mock server data and routes
├── plop-templates/ # Scaffolding templates (npm run new)
├── public/ # Static assets (index.html, favicon)
├── src/
│ ├── api/ # API request modules (one per resource)
│ ├── assets/ # Static assets (images, custom themes)
│ ├── components/ # 30+ reusable components
│ ├── directive/ # Custom Vue directives
│ ├── filters/ # Global template filters
│ ├── icons/ # SVG icons (auto-registered)
│ ├── layout/ # App layout (sidebar, navbar, tags)
│ ├── router/ # Route definitions (constant + async)
│ ├── store/ # Vuex state management
│ ├── styles/ # Global SCSS (variables, mixins, transitions)
│ ├── utils/ # Utility functions
│ └── views/ # Page components (one folder per feature)
├── tests/ # Jest unit tests
├── .env.development # Dev environment config
├── .env.production # Production environment config
├── .env.staging # Staging environment config
└── vue.config.js # Vue CLI configuration
2. Installation
2.1 Fresh Installation
git clone https://github.com/PanJiaChen/vue-element-admin.git
cd vue-element-admin
npm install
2.2 Handling npm Install Failures
If npm install fails (a known issue, see GitHub #3988), try the following in order:
Clear npm cache and retry
npm cache clean --force
rm -rf node_modules
rm package-lock.json
npm install
Use a specific npm registry
npm install --registry=https://registry.npmmirror.com
Pin Node version
# Use Node 14.x for best compatibility
nvm install 14
nvm use 14
npm install
2.3 Development Server
npm run dev
This runs vue-cli-service serve, which:
- Starts a webpack dev server on port 9527
- Enables hot module replacement (HMR)
- Activates the MockJS interceptor for all API calls
- Opens the browser automatically
Custom port
Override the default port via environment variable:
# Using port env var
port=8080 npm run dev
# Using npm_config_port
npm_config_port=8080 npm run dev
2.4 Production Build
# Production build
npm run build:prod
# Staging build
npm run build:stage
Both commands run vue-cli-service build. The staging build uses --mode staging to load .env.staging variables. Output lands in dist/.
2.5 Preview the Production Build
npm run preview
This runs node build/index.js --preview, which serves the dist/ directory locally so you can verify the production build before deploying.
2.6 Other NPM Scripts
| Script | Command | Purpose |
|---|---|---|
npm run lint |
eslint --ext .js,.vue src |
Lint all source files |
npm run new |
plop |
Scaffold a new view, component, or store module |
npm run svgo |
svgo -f src/icons/svg |
Optimize SVG icon files |
npm run test:unit |
jest --clearCache && vue-cli-service test:unit |
Run unit tests |
npm run test:ci |
lint + test:unit | Full CI check |
3. Configuration
3.1 Application Settings
The primary configuration file is src/settings.js:
module.exports = {
title: 'Vue Element Admin', // Page title (shown in browser tab)
showSettings: true, // Show the settings gear panel
tagsView: true, // Show the tags/tabs navigation bar
fixedHeader: false, // Fix the navbar header to the top
sidebarLogo: false, // Show a logo above the sidebar
errorLog: 'production' // Which environments collect error logs
}
| Setting | Type | Default | Effect |
|---|---|---|---|
title |
String | 'Vue Element Admin' |
Browser tab title; combined with route meta.title |
showSettings |
Boolean | true |
Toggles the gear icon that opens the right-side settings panel |
tagsView |
Boolean | true |
Toggles the tabbed navigation bar below the header |
fixedHeader |
Boolean | false |
When true, the navbar stays pinned while scrolling content |
sidebarLogo |
Boolean | false |
When true, displays a logo image above the sidebar menu |
errorLog |
String/Array | 'production' |
Environments where error logs are captured. Use ['production', 'development'] to enable in multiple environments |
Tip
You can change
showSettings,tagsView,fixedHeader, andsidebarLogoat runtime using the settings panel (the gear icon in the top-right corner). These runtime changes are managed through the Vuexsettingsstore module and persist only for the session.
3.2 Environment Variables
Three environment files control the API base URL:
| File | Build Mode | Typical VUE_APP_BASE_API Value |
|---|---|---|
.env.development |
npm run dev |
/dev-api |
.env.production |
npm run build:prod |
/prod-api |
.env.staging |
npm run build:stage |
/stage-api |
The VUE_APP_BASE_API variable is used by src/utils/request.js as the Axios baseURL. In development mode, MockJS intercepts requests before they reach the network, so the actual base URL value does not matter — any path works. In production, you must point this to your real backend.
Adding Custom Environment Variables
Any variable prefixed with VUE_APP_ is embedded into the client bundle by Vue CLI and accessible via process.env.VUE_APP_*:
# In .env.production
VUE_APP_TITLE=My Admin Panel
VUE_APP_CDN_URL=https://cdn.example.com
// In your code
console.log(process.env.VUE_APP_TITLE)
3.3 Vue CLI Configuration
The vue.config.js file controls build behavior:
- Dev server proxy — Configure proxy rules for forwarding API requests to a real backend during development
- Source maps — Disabled in production by default for performance
- Webpack chain — SVG sprite loader for the icon system, script preloading
- Build optimization — CDN externals, chunk splitting
3.4 Axios Request Configuration
All HTTP requests go through src/utils/request.js, which creates a configured Axios instance:
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000 // 5-second timeout
})
Request interceptor: Attaches the X-Token header from the cookie store on every outgoing request.
Response interceptor: Checks res.data.code:
20000— Success, returnsres.data50008— Illegal token50012— Other client logged in50014— Token expired
Codes 50008, 50012, and 50014 trigger a re-login confirmation dialog that clears the token and redirects to /login.
Important
When connecting to a real backend, your API must return responses in the format
{ code: 20000, data: ... }for the interceptor to treat them as successful. Any other code triggers an error message. Adjust the interceptor if your backend uses a different response structure.
3.5 Theme Configuration
Element UI Theme
The project includes a built-in theme picker component accessible from the navbar. It dynamically replaces Element UI's CSS variables to apply a new primary color across the entire application.
To set a permanent default theme, modify the Element UI SCSS variables in src/styles/element-variables.scss:
$--color-primary: #1890ff;
Custom SCSS Variables
Global style variables live in src/styles/variables.scss:
| Variable | Purpose | Default |
|---|---|---|
$menuText |
Sidebar menu text color | #bfcbd9 |
$menuActiveText |
Active sidebar menu text | #409EFF |
$subMenuActiveText |
Active sub-menu text | #f4f4f5 |
$menuBg |
Sidebar background | #304156 |
$menuHover |
Sidebar hover background | #263445 |
$subMenuBg |
Sub-menu background | #1f2d3d |
$subMenuHover |
Sub-menu hover background | #001528 |
$sideBarWidth |
Sidebar width | 210px |
These variables are available in any .vue component without importing, as they are injected globally via vue.config.js.
3.6 Internationalization (i18n)
vue-element-admin supports internationalization via vue-i18n. The i18n branch (i18n branch on GitHub) contains the full implementation. The main branch does not include i18n by default — if you need multi-language support, merge or cherry-pick from the i18n branch.
4. Core Usage
4.1 Layout System
The application layout consists of four main regions:
┌──────────────────────────────────────────┐
│ Navbar │
├──────────┬───────────────────────────────┤
│ │ Tags View │
│ Sidebar ├───────────────────────────────┤
│ │ │
│ │ Main Content │
│ │ (AppMain / router-view) │
│ │ │
└──────────┴───────────────────────────────┘
Sidebar — Collapsible navigation menu, auto-generated from the route table. Supports nested menus, icons, and external links. Toggle with the hamburger icon.
Navbar — Top bar with hamburger toggle, breadcrumb navigation, and right-side controls (search, screenfull, Element UI size selector, user dropdown).
Tags View — Tab bar showing open pages. Right-click any tab for options: Close, Close Others, Close All, Refresh. Pages listed with meta.affix: true cannot be closed (Dashboard and Documentation are affixed by default).
AppMain — The <router-view> that renders the current page. Uses <keep-alive> to cache views listed in the Tags View cachedViews array.
4.2 Sidebar Navigation
The sidebar is auto-generated from the route configuration in src/router/index.js. Every route object maps to a menu item.
Route Meta Fields That Control the Sidebar
| Meta Field | Type | Effect |
|---|---|---|
title |
String | Display text in the sidebar menu |
icon |
String | SVG icon name (from src/icons/svg/) |
hidden |
Boolean | When true, the route is hidden from the sidebar |
alwaysShow |
Boolean | When true, always shows the parent menu even with one child |
noCache |
Boolean | When true, the page is not cached by <keep-alive> |
affix |
Boolean | When true, the tag is permanently pinned in Tags View |
breadcrumb |
Boolean | When false, hidden from breadcrumb |
activeMenu |
String | Highlights a different sidebar item (e.g., for detail pages) |
roles |
Array | Which roles can access this route (e.g., ['admin', 'editor']) |
Nesting Rules
- No children — Renders as a single menu item
- One child — By default, shows only the child (flattened). Set
alwaysShow: trueon the parent to always show the parent wrapper - Multiple children — Renders as a nested sub-menu automatically
External Links
Add an external URL as the path to create a sidebar link that opens in a new tab:
{
path: 'https://github.com/PanJiaChen/vue-element-admin',
component: Layout,
children: [{
path: 'https://github.com/PanJiaChen/vue-element-admin',
meta: { title: 'External Link', icon: 'link' }
}]
}
4.3 Tags View (Tabs Navigation)
The Tags View bar shows each visited route as a tab. It is managed by the tagsView Vuex store module.
Features:
- Click a tab to navigate to that page
- Right-click a tab to see the context menu (Refresh, Close, Close Other, Close All)
- Affixed tabs (like Dashboard) cannot be closed
- Cached views persist their state when switching between tabs
- Scroll horizontally when many tabs are open
Disabling Tags View: Set tagsView: false in src/settings.js or toggle it via the settings panel.
Disabling cache for a specific page: Set noCache: true in the route's meta to prevent <keep-alive> from caching that page:
{
path: 'my-page',
component: () => import('@/views/my-page'),
meta: { title: 'My Page', noCache: true }
}
4.4 Breadcrumb Navigation
Breadcrumbs auto-generate from the current route's matched hierarchy. They appear in the navbar, to the right of the hamburger toggle.
Routes with meta.breadcrumb: false are excluded from the breadcrumb trail. The last breadcrumb (current page) is not clickable.
4.5 Header Search
Press the search icon in the navbar (or use keyboard shortcut) to open a search dialog. It uses Fuse.js for fuzzy matching against all route titles. Type a few characters and select from the filtered results to navigate instantly.
The search indexes all sidebar-visible routes. Hidden routes (meta.hidden: true) are excluded from search results.
4.6 Screenfull (Fullscreen Mode)
Click the expand icon in the navbar to toggle fullscreen mode using the browser's Fullscreen API. This uses the screenfull library for cross-browser compatibility.
4.7 Element UI Size Selector
The navbar includes a size selector dropdown that lets you switch Element UI's global component size between medium, small, mini, and default. This changes the size of all Element UI components (buttons, inputs, tables, etc.) across the entire application. The selected size is stored in the app Vuex module.
5. Common Tasks
5.1 Adding a New Page
Method 1: Using the Scaffold Generator
npm run new
Select view from the prompt. Enter a name, and plop creates a new view file from the template in plop-templates/view/.
Method 2: Manual Creation
Step 1: Create the view component
mkdir -p src/views/my-feature
Create src/views/my-feature/index.vue:
<template>
<div class="my-feature-container">
<h2>My Feature</h2>
</div>
</template>
<script>
export default {
name: 'MyFeature'
}
</script>
<style scoped>
.my-feature-container {
padding: 20px;
}
</style>
Tip
Always set a
nameproperty on your view component. Thenamemust match the route'snamefor<keep-alive>caching to work correctly with Tags View.
Step 2: Add the route
In src/router/index.js, add to constantRoutes (for all users) or asyncRoutes (for role-restricted access):
// In constantRoutes (accessible to everyone)
{
path: '/my-feature',
component: Layout,
children: [
{
path: 'index',
name: 'MyFeature',
component: () => import('@/views/my-feature/index'),
meta: { title: 'My Feature', icon: 'example' }
}
]
}
// In asyncRoutes (role-restricted)
{
path: '/my-feature',
component: Layout,
meta: { roles: ['admin'] }, // Only admin can see this
children: [
{
path: 'index',
name: 'MyFeature',
component: () => import('@/views/my-feature/index'),
meta: { title: 'My Feature', icon: 'example', roles: ['admin'] }
}
]
}
Step 3 (Optional): Add the API module
If the page needs API calls, create src/api/my-feature.js:
import request from '@/utils/request'
export function getList(params) {
return request({
url: '/my-feature/list',
method: 'get',
params
})
}
export function createItem(data) {
return request({
url: '/my-feature/create',
method: 'post',
data
})
}
Step 4 (Optional): Add mock data
Create mock/my-feature.js and register it in mock/index.js:
// mock/my-feature.js
const Mock = require('mockjs')
const List = Mock.mock({
'items|30': [{
id: '@id',
title: '@sentence(5, 10)',
'status|1': ['published', 'draft'],
timestamp: +Mock.Random.date('T')
}]
})
module.exports = [
{
url: '/my-feature/list',
type: 'get',
response: config => {
return { code: 20000, data: { items: List.items } }
}
}
]
5.2 Adding a New Component
Using the scaffold
npm run new
Select component, enter a name. Plop creates the component in src/components/.
Manual creation
Create src/components/MyComponent/index.vue:
<template>
<div class="my-component">
<slot />
</div>
</template>
<script>
export default {
name: 'MyComponent',
props: {
value: {
type: String,
default: ''
}
}
}
</script>
Use it in any view:
<template>
<my-component value="hello">Content</my-component>
</template>
<script>
import MyComponent from '@/components/MyComponent'
export default {
components: { MyComponent }
}
</script>
5.3 Adding a Vuex Store Module
Using the scaffold
npm run new
Select store, enter the module name. Plop generates the module file and auto-registers it.
Manual creation
Create src/store/modules/my-module.js:
const state = {
items: []
}
const mutations = {
SET_ITEMS: (state, items) => {
state.items = items
}
}
const actions = {
fetchItems({ commit }) {
// Call API, then commit
commit('SET_ITEMS', [])
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
Register in src/store/index.js:
import myModule from './modules/my-module'
const store = new Vuex.Store({
modules: {
// ...existing modules,
myModule
}
})
5.4 Adding SVG Icons
vue-element-admin uses an SVG sprite system. All SVG files in src/icons/svg/ are auto-registered as icon components.
Step 1: Place your SVG file in src/icons/svg/:
src/icons/svg/my-icon.svg
Step 2: Optimize it (optional but recommended):
npm run svgo
Step 3: Use it in templates:
<svg-icon icon-class="my-icon" />
The SvgIcon component is globally registered — no import needed. The icon-class value matches the SVG filename without the .svg extension.
Using icons in sidebar routes
Set the icon meta field to the SVG filename:
meta: { title: 'My Page', icon: 'my-icon' }
5.5 Working with the Mock Server
The mock server is powered by MockJS and intercepts Axios requests during development. Mock definitions live in the mock/ directory.
Mock file structure
mock/
├── article.js # Article CRUD endpoints
├── index.js # Auto-registers all mock modules
├── mock-server.js # Express-based mock server for preview
├── remote-search.js # Search endpoint
├── role/ # Role management endpoints
├── table.js # Table data
└── user.js # Login, logout, user info
How mock routing works
Each mock module exports an array of route objects:
module.exports = [
{
url: '/vue-element-admin/article/list',
type: 'get',
response: config => {
const { page = 1, limit = 20 } = config.query
return {
code: 20000,
data: {
total: articles.length,
items: articles.slice((page - 1) * limit, page * limit)
}
}
}
}
]
The config parameter contains query (GET params), body (POST body), and other request metadata.
Disabling the mock server
To connect to a real backend during development, remove or comment out the mock setup in main.js or mock/index.js, then update VUE_APP_BASE_API in .env.development to point to your backend URL. You may also need to configure a proxy in vue.config.js to avoid CORS issues.
5.6 Connecting to a Real Backend
To transition from the mock server to a real backend:
Step 1: Update .env.development:
VUE_APP_BASE_API = 'http://localhost:3000/api'
Step 2: Remove mock imports from the entry point (check main.js or the Webpack dev config).
Step 3: Configure the dev server proxy in vue.config.js if your backend runs on a different port:
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
Step 4: Update .env.production to point to your production API:
VUE_APP_BASE_API = 'https://api.yourdomain.com'
Step 5: Adjust the response interceptor in src/utils/request.js if your backend does not return { code: 20000, data: ... } format.
Warning
The Axios response interceptor in
src/utils/request.jsexpects all successful responses to havecode: 20000. If your backend returns standard HTTP status codes (200, 201, etc.) with a different body structure, you must rewrite the response interceptor or your app will treat every response as an error.
6. Permission System
6.1 Overview
vue-element-admin implements permission control at three levels:
| Level | Mechanism | Granularity |
|---|---|---|
| Route-level | Route meta roles array + dynamic route generation |
Entire pages |
| Element-level | v-permission directive |
Individual UI elements |
| Programmatic | checkPermission() utility |
Conditional logic in code |
6.2 Authentication Flow
The authentication flow is controlled by the route guard in src/permission.js:
User navigates to a route
├── Has token (cookie 'Admin-Token')?
│ ├── Route is /login → Redirect to /
│ └── Route is not /login
│ ├── Has roles in Vuex? → Allow navigation
│ └── No roles yet
│ ├── Call getInfo() → Get roles from server
│ ├── Call generateRoutes(roles) → Filter asyncRoutes
│ ├── router.addRoutes() → Add permitted routes
│ └── Allow navigation
└── No token
├── Route is in whitelist (/login, /auth-redirect)? → Allow
└── Route is not in whitelist → Redirect to /login
The token is stored in a cookie named Admin-Token via the js-cookie library. Cookie access is centralized in src/utils/auth.js with getToken(), setToken(), and removeToken() functions.
6.3 Login and Logout
Login: The user/login Vuex action sends credentials to the mock endpoint, receives a token, and stores it in the cookie. The mock server accepts admin or editor as usernames with any password.
Logout: The user/logout action calls the logout endpoint, resets the token cookie, clears roles from Vuex, and resets the dynamically added routes via resetRouter().
Token-based re-login: If the backend returns error codes 50008, 50012, or 50014, the response interceptor shows a confirmation dialog asking the user to re-login. Confirming clears the token and reloads the page.
6.4 Route-Level Permissions
Routes in asyncRoutes are dynamically filtered based on the user's roles. The filtering logic is in the permission Vuex store module.
Admin role: Gets all asyncRoutes without filtering.
Other roles: Each route's meta.roles array is checked. If the user's role is included, the route is accessible. If a parent route has meta.roles, all its children inherit that restriction unless they define their own.
Example from the source:
// Only admin can access
{
path: '/permission',
component: Layout,
meta: { roles: ['admin'] },
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
meta: { title: 'Page Permission', roles: ['admin'] }
},
{
path: 'directive',
component: () => import('@/views/permission/directive'),
meta: { title: 'Directive Permission' }
// No roles = accessible to admin AND editor (inherits from parent being in asyncRoutes)
},
{
path: 'role',
component: () => import('@/views/permission/role'),
meta: { title: 'Role Permission', roles: ['admin'] }
}
]
}
6.5 Element-Level Permissions with v-permission
The v-permission directive controls visibility of individual elements:
<template>
<div>
<!-- Only visible to admin -->
<el-button v-permission="['admin']">Admin Action</el-button>
<!-- Visible to admin and editor -->
<el-button v-permission="['admin','editor']">Edit</el-button>
</div>
</template>
The directive compares the provided roles against the user's roles from the Vuex store. If the user lacks the required role, the element is removed from the DOM (not hidden — completely removed).
Caution
The
v-permissiondirective removes elements from the DOM. Once removed, they cannot be toggled back without a re-render. For elements that need to toggle visibility based on dynamic conditions, usev-ifwithcheckPermission()instead.
6.6 Programmatic Permission Checks
For cases where a directive is not suitable (computed properties, method logic), use the checkPermission utility:
import checkPermission from '@/utils/permission'
export default {
methods: {
hasAdminAccess() {
return checkPermission(['admin'])
}
}
}
This function returns true if the current user has any of the specified roles.
6.7 Adding a New Role
To add a new role (e.g., viewer):
- Backend/Mock: Return the new role from the user info endpoint
- Routes: Add
'viewer'to themeta.rolesarrays of routes the new role should access - Elements: Use
v-permission="['viewer']"orcheckPermission(['viewer'])where needed - Mock user: Add a new user entry in
mock/user.jsthat returns theviewerrole
7. Components
vue-element-admin ships with 30+ reusable components in src/components/. Each is self-contained and can be imported individually.
7.1 Rich Text Editors
Tinymce
A full-featured WYSIWYG editor loaded from CDN. Located at src/components/Tinymce/.
<template>
<tinymce v-model="content" :height="400" />
</template>
<script>
import Tinymce from '@/components/Tinymce'
export default {
components: { Tinymce },
data() {
return { content: '' }
}
}
</script>
Features: formatting toolbar, image upload, fullscreen editing, HTML source view. The editor script is loaded from a CDN URL — it is not bundled locally.
Demo: Navigate to Components > Tinymce in the sidebar.
Markdown Editor
Based on tui-editor (Toast UI Editor). Located at src/components/MarkdownEditor/.
<template>
<markdown-editor v-model="content" height="500px" />
</template>
<script>
import MarkdownEditor from '@/components/MarkdownEditor'
export default {
components: { MarkdownEditor },
data() {
return { content: '# Hello' }
}
}
</script>
Supports both Markdown and WYSIWYG editing modes, live preview, and standard Markdown syntax.
Demo: Navigate to Components > Markdown in the sidebar.
JSON Editor
Powered by CodeMirror. Located at src/components/JsonEditor/.
<template>
<json-editor v-model="jsonValue" />
</template>
<script>
import JsonEditor from '@/components/JsonEditor'
export default {
components: { JsonEditor },
data() {
return { jsonValue: { key: 'value' } }
}
}
</script>
Provides syntax highlighting, auto-formatting, and validation for JSON content.
Demo: Navigate to Components > JSON Editor in the sidebar.
7.2 Upload Components
Avatar Upload
An image cropper and uploader component with preview. Located at src/components/ImageCropper/.
Features: drag-and-drop upload, crop area selection, zoom, rotation, and preview in multiple sizes (circle and square).
Demo: Navigate to Components > Avatar Upload in the sidebar.
Dropzone
Drag-and-drop file upload area. Located at src/components/Dropzone/.
Features: drag files onto the drop area, progress display, file type filtering.
Demo: Navigate to Components > Dropzone in the sidebar.
Excel Upload
Upload .xlsx or .xls files and parse them into JSON. Located at src/components/UploadExcel/.
<template>
<upload-excel :on-success="handleSuccess" :before-upload="beforeUpload" />
</template>
<script>
import UploadExcel from '@/components/UploadExcel'
export default {
components: { UploadExcel },
methods: {
handleSuccess({ results, header }) {
// results = array of row objects
// header = array of column names
this.tableData = results
this.tableHeader = header
},
beforeUpload(file) {
const isLt1M = file.size / 1024 / 1024 < 1
if (!isLt1M) {
this.$message.error('File must be smaller than 1MB')
}
return isLt1M
}
}
}
</script>
Uses the xlsx library to parse spreadsheet files client-side.
Demo: Navigate to Excel > Upload Excel in the sidebar.
7.3 Drag-and-Drop Components
Drag Dialog
Make any el-dialog draggable by adding the v-el-drag-dialog directive:
<template>
<el-dialog v-el-drag-dialog title="Draggable Dialog" :visible.sync="visible">
<p>You can drag this dialog by its title bar.</p>
</el-dialog>
</template>
<script>
import elDragDialog from '@/directive/el-drag-dialog'
export default {
directives: { elDragDialog },
data() {
return { visible: true }
}
}
</script>
Demo: Navigate to Components > Drag Dialog in the sidebar.
Drag Select
Select multiple items by dragging a selection area. Located at src/components/DragSelect/.
Demo: Navigate to Components > Drag Select in the sidebar.
DnD List
Drag-and-drop sortable list using sortablejs. Located at src/components/DndList/.
Demo: Navigate to Components > DnD List in the sidebar.
Kanban Board
Drag-and-drop Kanban board. Located at src/components/Kanban/.
Demo: Navigate to Components > Drag Kanban in the sidebar.
7.4 Data Display Components
Pagination
Standardized pagination wrapper for Element UI's el-pagination. Located at src/components/Pagination/.
<template>
<pagination
:total="total"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
</template>
<script>
import Pagination from '@/components/Pagination'
export default {
components: { Pagination },
data() {
return {
total: 0,
listQuery: { page: 1, limit: 20 }
}
},
methods: {
getList() {
// Fetch data with this.listQuery.page and this.listQuery.limit
}
}
}
</script>
CountTo
Animated number counter. Smoothly transitions from 0 to a target value.
Demo: Navigate to Components > Count To in the sidebar.
BackToTop
Scroll-to-top button that appears when the user scrolls down. Located at src/components/BackToTop/.
<template>
<div>
<!-- Page content -->
<back-to-top :visibility-height="300" :back-position="0" />
</div>
</template>
<script>
import BackToTop from '@/components/BackToTop'
export default {
components: { BackToTop }
}
</script>
Demo: Navigate to Components > Back To Top in the sidebar.
7.5 UI Enhancement Components
Sticky
Make any element stick to the top of the viewport on scroll. Located at src/components/Sticky/.
<template>
<sticky :sticky-top="0" class-name="sticky-header">
<div>This sticks to the top when scrolling</div>
</sticky>
</template>
<script>
import Sticky from '@/components/Sticky'
export default {
components: { Sticky }
}
</script>
Demo: Navigate to Components > Sticky in the sidebar.
SplitPane
Resizable split-panel layout. Drag the divider to resize.
Demo: Navigate to Components > SplitPane in the sidebar.
PanThumb
Thumbnail component with hover pan effect (image moves on mouse hover).
GithubCorner
The GitHub corner ribbon component shown in the top-right of the application.
RightPanel
The sliding settings panel triggered by the gear icon. Used internally for the settings panel but can be reused for any slide-out panel need.
ErrorLog
Displays a badge in the navbar showing the count of JavaScript errors. Click to see error details. Controlled by the errorLog setting in src/settings.js.
7.6 Charts (ECharts)
The project integrates ECharts 4.2.1 for data visualization. Chart components are in src/views/charts/ (view-level) and src/components/Charts/ (reusable wrappers).
Three demo charts are included:
| Chart | Path | Type |
|---|---|---|
| Keyboard Chart | src/views/charts/keyboard |
Custom interactive |
| Line Chart | src/views/charts/line |
Line chart |
| Mix Chart | src/views/charts/mix |
Mixed bar + line |
Charts auto-resize on window resize via a mixin. Navigate to Charts in the sidebar to see the demos.
To add a new chart:
<template>
<div ref="chart" style="width: 100%; height: 400px;" />
</template>
<script>
import echarts from 'echarts'
import resize from '@/components/Charts/mixins/resize'
export default {
mixins: [resize],
mounted() {
this.chart = echarts.init(this.$refs.chart)
this.chart.setOption({
// ECharts option config
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'bar' }]
})
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
this.chart = null
}
}
}
</script>
The resize mixin handles window.addEventListener('resize', ...) cleanup automatically.
8. Dashboard
The Dashboard is the landing page after login. It is affixed in the Tags View and cannot be closed.
The dashboard page is located at src/views/dashboard/index.vue and typically includes:
- Panel Group — Summary cards showing key metrics (new visits, messages, purchases, shopping count) with animated counters
- Line Chart — Trend data visualization
- Bar Chart — Category comparisons
- Table — Recent transaction or order data
- Todo List — Task management widget
All dashboard data comes from the mock server during development. Replace the API calls in src/api/ with real endpoints when connecting to a backend.
9. Tables
Tables are a core feature of any admin panel. vue-element-admin includes four table implementations demonstrating different patterns.
9.1 Dynamic Table
Configurable columns that can be shown/hidden and reordered at runtime. Users can check/uncheck columns from a dropdown, and drag to reorder them.
Navigate to Table > Dynamic Table in the sidebar.
9.2 Drag Table
Table rows that can be reordered via drag-and-drop using sortablejs. Useful for priority lists, ordering, and manual sorting.
Navigate to Table > Drag Table in the sidebar.
9.3 Inline Edit Table
Click a cell to edit it in-place. Each row has Edit/Cancel buttons that toggle between display and edit modes.
Navigate to Table > Inline Edit Table in the sidebar.
9.4 Complex Table
Full-featured table with:
- Search and filter controls
- Column sorting
- Pagination
- Row status tags (published, draft, deleted)
- Action buttons (Edit, Publish, Delete)
- Create/Edit dialogs
- CSV export
Navigate to Table > Complex Table in the sidebar.
9.5 Table Patterns for Your Own Pages
The most common table pattern from the source:
<template>
<div class="app-container">
<!-- Filters -->
<div class="filter-container">
<el-input v-model="listQuery.title" placeholder="Title" style="width: 200px;" @keyup.enter.native="handleFilter" />
<el-button type="primary" icon="el-icon-search" @click="handleFilter">Search</el-button>
</div>
<!-- Table -->
<el-table :data="list" v-loading="listLoading" border>
<el-table-column label="Title" prop="title" />
<el-table-column label="Status" prop="status">
<template slot-scope="{row}">
<el-tag :type="row.status | statusFilter">{{ row.status }}</el-tag>
</template>
</el-table-column>
<el-table-column label="Actions" width="200">
<template slot-scope="{row}">
<el-button size="mini" @click="handleEdit(row)">Edit</el-button>
<el-button size="mini" type="danger" @click="handleDelete(row)">Delete</el-button>
</template>
</el-table-column>
</el-table>
<!-- Pagination -->
<pagination :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="getList" />
</div>
</template>
<script>
import { fetchList } from '@/api/article'
import Pagination from '@/components/Pagination'
export default {
components: { Pagination },
data() {
return {
list: [],
total: 0,
listLoading: true,
listQuery: { page: 1, limit: 20, title: '' }
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listLoading = true
fetchList(this.listQuery).then(response => {
this.list = response.data.items
this.total = response.data.total
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleEdit(row) { /* ... */ },
handleDelete(row) { /* ... */ }
}
}
</script>
10. Excel, ZIP, and PDF Export
10.1 Export Table to Excel
Navigate to Excel > Export Excel in the sidebar.
The export feature uses the xlsx library (file-saver + xlsx). Export functions are in src/vendor/Export2Excel.js.
import('@/vendor/Export2Excel').then(excel => {
const tHeader = ['Id', 'Title', 'Author', 'Date']
const data = this.list.map(v => [v.id, v.title, v.author, v.date])
excel.export_json_to_excel({
header: tHeader,
data,
filename: 'my-export', // Default: 'excel-list'
autoWidth: true, // Auto-fit column widths
bookType: 'xlsx' // 'xlsx', 'csv', 'txt'
})
})
Export Selected Rows
Navigate to Excel > Export Selected. Select rows using table checkboxes, then click Export. The export function receives only the selected rows.
Merge Header Export
Navigate to Excel > Merge Header. Demonstrates multi-level column headers in the exported spreadsheet.
10.2 Upload and Parse Excel
Navigate to Excel > Upload Excel. Uses the UploadExcel component to parse .xlsx files and display data in a table. See Section 7.2 for the component usage.
10.3 Export to ZIP
Navigate to ZIP > Export Zip. Uses jszip + file-saver to bundle data into a .zip download:
import('@/vendor/Export2Zip').then(zip => {
const tHeader = ['Id', 'Title']
const data = this.list.map(v => [v.id, v.title])
zip.export_txt_to_zip(tHeader, data, 'my-export', 'my-file')
})
10.4 Export to PDF
Navigate to PDF in the sidebar. Uses html2canvas + jspdf to capture the current view as a PDF. The export is purely client-side — it renders the DOM to a canvas and converts it to a PDF document.
11. Example: Article CRUD
The Example section in the sidebar demonstrates a complete CRUD workflow for articles. This serves as a reference implementation for building your own CRUD pages.
11.1 Article List
Path: Example > Article List
Features:
- Table with title, author, reviewer, importance rating, status, and timestamp
- Sortable columns
- Status filter (published, draft, deleted)
- Importance filter (1-3 stars)
- Full-text search by title
- Pagination
- Inline edit button opens a detail page
11.2 Create Article
Path: Example > Create Article
Features:
- Title input
- Rich text editor (Tinymce) for content body
- Remark input
- Source URL field
- Importance rating (1-3 stars)
- Publish and Draft buttons
- Form validation
11.3 Edit Article
Click Edit on any article in the list. The edit page pre-fills all fields. The route uses a dynamic parameter (/example/edit/:id) with meta.activeMenu set to highlight the article list in the sidebar.
This pattern of list + create + edit pages with shared API calls is the most common CRUD pattern in the codebase.
12. Custom Directives
12.1 v-permission
Role-based element visibility. See Section 6.5 for details.
<el-button v-permission="['admin']">Admin Only</el-button>
12.2 v-clipboard
Copy or cut text to the clipboard:
<template>
<div>
<el-button v-clipboard:copy="inputData" v-clipboard:success="clipboardSuccess">
Copy
</el-button>
</div>
</template>
<script>
import clipboard from '@/directive/clipboard'
export default {
directives: { clipboard },
data() {
return { inputData: 'Text to copy' }
},
methods: {
clipboardSuccess() {
this.$message.success('Copied!')
}
}
}
</script>
Modifiers: v-clipboard:copy (copy to clipboard), v-clipboard:cut (cut to clipboard).
Demo: Navigate to Clipboard in the sidebar.
12.3 v-el-drag-dialog
Makes any el-dialog draggable by its title bar. See Section 7.3 for usage.
12.4 v-el-table (Adaptive)
Auto-adjusts el-table height to fill available viewport space. Useful for full-page tables that should resize with the window.
12.5 v-waves
Adds a Material Design ripple effect on click:
<el-button v-waves>Click Me</el-button>
The ripple emanates from the click position. Import the directive:
import waves from '@/directive/waves'
export default {
directives: { waves }
}
12.6 v-sticky
Makes an element stick to the viewport edge on scroll. An alternative to the Sticky component for simpler use cases.
13. Global Filters
Filters registered globally in src/filters/index.js can be used in any template:
13.1 Date and Time Filters
parseTime
Formats a timestamp into a formatted date string:
<!-- Output: "2024-10-24 15:30:00" -->
{{ 1729783800000 | parseTime('{y}-{m}-{d} {h}:{i}:{s}') }}
<!-- Short format -->
{{ timestamp | parseTime('{y}-{m}-{d}') }}
Format tokens: {y} year, {m} month, {d} day, {h} hour, {i} minute, {s} second, {a} day of week.
formatTime
Returns a human-readable relative time for recent dates:
<!-- Output: "3 minutes ago", "1 hour ago", "1 day ago", or "10-24" -->
{{ timestamp | formatTime }}
Returns relative times for durations under 30 days; returns a date string for older dates.
timeAgo
Returns relative time strings:
<!-- Output: "3 minutes ago" -->
{{ timestamp | timeAgo }}
13.2 Number Filters
numberFormatter
Abbreviates large numbers:
<!-- Output: "10k" -->
{{ 10000 | numberFormatter }}
<!-- Output: "9.9k" -->
{{ 9900 | numberFormatter }}
Numbers below 10,000 display as-is.
toThousandFilter
Adds comma separators:
<!-- Output: "10,000,000" -->
{{ 10000000 | toThousandFilter }}
13.3 String Filters
uppercaseFirst
Capitalizes the first character:
<!-- Output: "Hello" -->
{{ 'hello' | uppercaseFirst }}
14. Utility Functions
The src/utils/ directory provides shared utility functions used throughout the application.
14.1 src/utils/index.js
| Function | Purpose | Example |
|---|---|---|
parseTime(time, cFormat) |
Format timestamp to string | parseTime(Date.now(), '{y}-{m}-{d}') |
formatTime(time) |
Relative time string | formatTime(someTimestamp) |
getQueryObject(url) |
Parse URL query string to object | getQueryObject(window.location.href) |
byteLength(str) |
String byte length (CJK = 2) | byteLength('hello') → 5 |
cleanArray(arr) |
Remove falsy values | cleanArray([0, 1, null, 3]) → [1, 3] |
param(json) |
Object to URL query string | param({a: 1, b: 2}) → 'a=1&b=2' |
param2Obj(url) |
URL query string to object | param2Obj('?a=1&b=2') → {a: '1', b: '2'} |
html2Text(val) |
Strip HTML tags | html2Text('<p>hi</p>') → 'hi' |
objectMerge(target, source) |
Deep merge objects | objectMerge(defaults, overrides) |
debounce(func, wait) |
Debounce a function | debounce(handleResize, 300) |
deepClone(source) |
Deep clone an object | deepClone(complexObject) |
uniqueArr(arr) |
Deduplicate array | uniqueArr([1, 2, 2, 3]) → [1, 2, 3] |
createUniqueString() |
Generate unique string | Used for element keys |
toggleClass(el, cls) |
Toggle CSS class | toggleClass(element, 'active') |
hasClass(el, cls) |
Check if element has class | hasClass(element, 'active') |
addClass(el, cls) |
Add CSS class | addClass(element, 'active') |
removeClass(el, cls) |
Remove CSS class | removeClass(element, 'active') |
14.2 src/utils/request.js
Axios instance configuration. See Section 3.4.
14.3 src/utils/auth.js
Token management:
| Function | Purpose |
|---|---|
getToken() |
Read Admin-Token from cookie |
setToken(token) |
Write token to cookie |
removeToken() |
Delete token cookie |
14.4 src/utils/validate.js
Validation helpers:
| Function | Purpose | Example |
|---|---|---|
validUsername(str) |
Check if username is valid | Mock users only: admin, editor |
validURL(url) |
Validate URL format | Returns boolean |
validLowerCase(str) |
Check all lowercase | Returns boolean |
validUpperCase(str) |
Check all uppercase | Returns boolean |
validAlphabets(str) |
Check all alphabetic | Returns boolean |
validEmail(str) |
Validate email format | Returns boolean |
isString(str) |
Check if value is string | Returns boolean |
isArray(arg) |
Check if value is array | Returns boolean |
14.5 src/utils/permission.js
The checkPermission(value) function for programmatic role checks. See Section 6.6.
14.6 src/utils/scroll-to.js
Smooth scroll utility used by the BackToTop component:
import { scrollTo } from '@/utils/scroll-to'
// Scroll to top
scrollTo(0, 800) // scrollTo(targetY, duration)
14.7 src/utils/clipboard.js
Clipboard copy utility function (used internally by the v-clipboard directive):
import clip from '@/utils/clipboard'
clip(text, event) // Copies text to clipboard
15. Nested Routes (Multi-Level Menus)
vue-element-admin supports multi-level nested menus. The sidebar auto-generates sub-menus from nested route children.
Navigate to Nested in the sidebar to see a three-level deep menu:
Nested
├── Menu 1
│ ├── Menu 1-1
│ ├── Menu 1-2
│ │ ├── Menu 1-2-1
│ │ └── Menu 1-2-2
│ └── Menu 1-3
└── Menu 2
Each level corresponds to a nested children array in the route definition. There is no hard limit on nesting depth, but more than three levels becomes unwieldy for sidebar navigation.
16. Error Handling
16.1 Error Log
The ErrorLog component captures JavaScript errors using Vue.config.errorHandler and displays them via a badge icon in the navbar. This is controlled by the errorLog setting in src/settings.js.
// src/settings.js
errorLog: 'production' // Only capture in production
// To enable in development too:
errorLog: ['production', 'development']
Click the bug icon in the navbar to view captured errors with:
- Error message
- Stack trace
- URL where the error occurred
- Timestamp
Navigate to Error Log in the sidebar to see the error log demo page. It deliberately throws an error to demonstrate the capture mechanism.
16.2 Error Pages
Two error pages are included:
- 401 Unauthorized — Shown when a user tries to access a page without permission
- 404 Not Found — Shown for any unrecognized route (the
*catch-all route redirects to/404)
Both pages feature illustrated graphics. Navigate to Error Pages > 401 or Error Pages > 404 in the sidebar to preview them.
17. Theme Customization
17.1 Runtime Theme Switching
Click the theme color circle in the navbar to open the theme picker. Select a new primary color, and all Element UI components update immediately. This works by:
- Fetching Element UI's compiled CSS
- Replacing color values with regex
- Appending a new
<style>tag with the recolored CSS
The selected theme color persists in Vuex for the session but resets on page reload unless you implement persistence (e.g., localStorage).
17.2 SCSS Variable Customization
For permanent theme changes, edit src/styles/element-variables.scss:
/* Primary theme color */
$--color-primary: #1890ff;
/* Override other Element UI variables */
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;
/* Font */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
Then rebuild the project for changes to take effect.
17.3 Sidebar Theme
Edit src/styles/variables.scss to change sidebar colors:
$menuBg: #304156; // Sidebar background
$menuHover: #263445; // Sidebar hover color
$menuText: #bfcbd9; // Menu text color
$menuActiveText: #409EFF; // Active menu item text
$subMenuBg: #1f2d3d; // Sub-menu background
$subMenuHover: #001528; // Sub-menu hover color
$subMenuActiveText: #f4f4f5; // Active sub-menu text
$sideBarWidth: 210px; // Sidebar width
Note
Both runtime theme switching and SCSS variable customization are available, but they operate independently. Runtime switching only affects Element UI component colors; sidebar and custom styles require SCSS changes. See GitHub issue #3606 for the known limitation that the two methods cannot fully coexist.
18. Settings Panel
The settings panel is the gear icon in the top-right corner. When showSettings: true in src/settings.js, clicking it slides out a right-side panel with toggle switches for:
| Toggle | Setting | Effect |
|---|---|---|
| Show Tags View | tagsView |
Show/hide the tab navigation bar |
| Fixed Header | fixedHeader |
Pin/unpin the navbar during scroll |
| Show Sidebar Logo | sidebarLogo |
Show/hide the logo above the sidebar |
Changes take effect immediately and are stored in the Vuex settings module. They reset on page reload.
To make settings persistent across reloads, save them to localStorage in the Vuex settings module:
// In src/store/modules/settings.js
const actions = {
changeSetting({ commit }, data) {
commit('CHANGE_SETTING', data)
// Persist to localStorage
localStorage.setItem(`setting_${data.key}`, JSON.stringify(data.value))
}
}
19. Workflows and Recipes
19.1 Building a User Management Page
- Create
src/api/user-management.jswith list, create, update, delete functions - Add mock data in
mock/user-management.js - Create
src/views/user-management/index.vuewith el-table, filters, and pagination - Add the route to
asyncRouteswithmeta.roles: ['admin'] - Add an Edit dialog using
el-dialogwith form validation - Use
v-permission="['admin']"on the delete button
19.2 Adding a Dashboard Widget
- Create a new component in
src/views/dashboard/components/ - Import and register it in the dashboard index page
- Add an API call in
src/api/for the widget data - Add corresponding mock data
- Use ECharts if it is a chart widget (import the resize mixin)
19.3 Implementing File Upload with Preview
- Use the
el-uploadcomponent from Element UI - Configure
actionto point to your upload endpoint - Use
:before-uploadfor file validation (size, type) - Use
:on-successto handle the response and store the file URL - Display uploaded files using
el-imageor a custom preview component
19.4 Adding a Multi-Tab Detail Page
- Create a view with
el-tabscontaining multipleel-tab-panecomponents - Use route query parameters to track the active tab:
this.$route.query.tab - Set
noCache: trueif tab state should reset on re-entry - Use
meta.activeMenuto keep the parent menu highlighted
19.5 Creating a Wizard/Stepper Form
- Use
el-stepscomponent for the progress indicator - Track current step in component data
- Use
v-show(notv-if) for each step's form fields to preserve validation state - Validate each step with
this.$refs.form.validateField(fields)before advancing - Submit all collected data on the final step
19.6 Building a Tree-Based Navigation
- Use
el-treewithdefault-expand-allor lazy loading - Handle
@node-clickto load detail in a right panel - Use
el-treeprops:data,props,node-key,highlight-current - Combine with
SplitPanecomponent for resizable tree + detail layout
19.7 Implementing Global Search
The HeaderSearch component already provides fuzzy route search. To add data search:
- Create a search API endpoint
- Use
el-autocompleteorel-selectwith remote search - Debounce the search input (use the
debounceutility fromsrc/utils/index.js) - Navigate to the result on selection
19.8 Adding Keyboard Shortcuts
- Listen for keydown events in your component's
mountedhook - Clean up in
beforeDestroy - Use the existing pattern from HeaderSearch (which uses
keyupevents) - Consider using a library like
mousetrapfor complex shortcuts
19.9 Batch Operations on Table Data
- Add
selectiontype column toel-table:<el-table-column type="selection" /> - Handle
@selection-changeto track selected rows - Add batch action buttons above the table
- Confirm with
this.$confirmbefore destructive batch operations - Show loading state during the batch API call
19.10 Implementing Infinite Scroll
- Use
el-tablewith a fixed height - Listen for the table's scroll event
- When scroll reaches bottom, load the next page
- Append new data to the existing array (do not replace)
- Show a loading indicator at the bottom
19.11 Form Validation Patterns
Element UI's el-form with rules:
<template>
<el-form ref="form" :model="form" :rules="rules">
<el-form-item label="Name" prop="name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Email" prop="email">
<el-input v-model="form.email" />
</el-form-item>
<el-button @click="submitForm">Submit</el-button>
</el-form>
</template>
<script>
import { validEmail } from '@/utils/validate'
export default {
data() {
const validateEmail = (rule, value, callback) => {
if (!validEmail(value)) {
callback(new Error('Invalid email'))
} else {
callback()
}
}
return {
form: { name: '', email: '' },
rules: {
name: [{ required: true, message: 'Name is required', trigger: 'blur' }],
email: [{ required: true, trigger: 'blur' }, { validator: validateEmail, trigger: 'blur' }]
}
}
},
methods: {
submitForm() {
this.$refs.form.validate(valid => {
if (valid) {
// Submit
}
})
}
}
}
</script>
19.12 Conditional Form Fields
Show/hide form fields based on selections:
<el-form-item label="Type" prop="type">
<el-select v-model="form.type">
<el-option label="Basic" value="basic" />
<el-option label="Advanced" value="advanced" />
</el-select>
</el-form-item>
<el-form-item v-if="form.type === 'advanced'" label="Config" prop="config">
<json-editor v-model="form.config" />
</el-form-item>
19.13 Auto-Saving Form Drafts
Persist form state to localStorage to prevent data loss:
export default {
watch: {
form: {
handler(val) {
localStorage.setItem('draft_article', JSON.stringify(val))
},
deep: true
}
},
created() {
const draft = localStorage.getItem('draft_article')
if (draft) {
this.form = JSON.parse(draft)
}
}
}
19.14 Dynamic Column Configuration
Let users pick which table columns to show:
<template>
<div>
<el-checkbox-group v-model="visibleColumns">
<el-checkbox v-for="col in allColumns" :key="col.prop" :label="col.prop">
{{ col.label }}
</el-checkbox>
</el-checkbox-group>
<el-table :data="list">
<el-table-column v-for="col in filteredColumns" :key="col.prop" :label="col.label" :prop="col.prop" />
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
visibleColumns: ['id', 'title', 'status'],
allColumns: [
{ prop: 'id', label: 'ID' },
{ prop: 'title', label: 'Title' },
{ prop: 'author', label: 'Author' },
{ prop: 'status', label: 'Status' },
{ prop: 'date', label: 'Date' }
]
}
},
computed: {
filteredColumns() {
return this.allColumns.filter(c => this.visibleColumns.includes(c.prop))
}
}
}
</script>
19.15 Integrating a Third-Party Library
- Install:
npm install library-name - If the library has a Vue wrapper, register it globally in
main.jsor locally in the component - If it requires DOM access, initialize in
mounted()and clean up inbeforeDestroy() - For large libraries, use dynamic import to keep the main bundle small:
const lib = () => import('library-name') - If the library has CSS, import it in the component or globally
19.16 Responsive Sidebar Behavior
The sidebar automatically collapses on mobile-width screens. The app Vuex module tracks the device state (desktop or mobile). On mobile:
- The sidebar opens as an overlay
- Clicking outside the sidebar closes it
- The hamburger toggle controls the overlay
This is handled automatically by the layout system. Breakpoint detection happens in src/layout/mixin/ResizeHandler.js.
19.17 Adding a Notification System
- Use
el-badgefor unread count in the navbar - Create a notification dropdown with
el-popover - Poll the notification API on a timer (or use WebSocket)
- Mark as read on click, update the badge count
- Use
Notificationcomponent from Element UI for real-time push alerts
19.18 Exporting Filtered Table Data
- Apply filters to the table data
- Use the
Export2Excelutility on the filtered dataset:
handleExport() {
import('@/vendor/Export2Excel').then(excel => {
const tHeader = this.filteredColumns.map(c => c.label)
const filterVal = this.filteredColumns.map(c => c.prop)
const data = this.filteredList.map(v => filterVal.map(j => v[j]))
excel.export_json_to_excel({
header: tHeader,
data,
filename: `export_${parseTime(Date.now(), '{y}{m}{d}')}`
})
})
}
19.19 Password Change Flow
- Create a dialog with old password, new password, and confirm password fields
- Validate that new password matches confirmation
- Validate minimum length and complexity
- Call the update password API
- On success, force logout and redirect to login
19.20 Role-Specific Dashboard Views
Show different dashboard content based on user role:
<template>
<div>
<admin-dashboard v-if="isAdmin" />
<editor-dashboard v-else />
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['roles']),
isAdmin() {
return this.roles.includes('admin')
}
}
}
</script>
19.21 Page Loading States
Use Element UI's v-loading directive for loading states:
<el-table v-loading="listLoading" :data="list">
<!-- columns -->
</el-table>
For full-page loading, use a loading overlay:
const loading = this.$loading({
lock: true,
text: 'Processing...',
background: 'rgba(0, 0, 0, 0.7)'
})
// When done:
loading.close()
19.22 Custom Error Boundaries
Wrap unreliable components in error boundaries using Vue's errorCaptured hook:
export default {
errorCaptured(err, vm, info) {
console.error('Component error:', err, info)
this.hasError = true
return false // Prevent propagation
}
}
19.23 Printing Specific Sections
Use window.print() with print-specific CSS:
<el-button @click="handlePrint">Print</el-button>
@media print {
.sidebar-container,
.navbar,
.tags-view-container,
.right-panel {
display: none !important;
}
.app-main {
margin-left: 0 !important;
padding: 0 !important;
}
}
19.24 Deep Linking to Tabs
Preserve active tab state in the URL for shareable links:
computed: {
activeTab: {
get() {
return this.$route.query.tab || 'first'
},
set(val) {
this.$router.replace({ query: { ...this.$route.query, tab: val } })
}
}
}
20. Troubleshooting
20.1 Installation Issues
Error: "node-sass" build failures
Cause: node-sass requires native compilation and frequently fails on newer Node versions.
Solution:
# Option 1: Use a compatible Node version
nvm use 14
# Option 2: Replace node-sass with dart-sass
npm uninstall node-sass
npm install sass --save-dev
Prevention: Use Node 12.x or 14.x for best compatibility with this project's dependency tree.
Error: "gyp ERR! build error" during npm install
Cause: Missing native build tools (Python 2.7, Visual C++ Build Tools on Windows, Xcode Command Line Tools on macOS).
Solution (Windows):
npm install --global windows-build-tools
Solution (macOS):
xcode-select --install
Solution (Linux):
sudo apt-get install build-essential python
Error: "ERESOLVE unable to resolve dependency tree"
Cause: npm 7+ enforces strict peer dependency resolution.
Solution:
npm install --legacy-peer-deps
Error: "ERR_OSSL_EVP_UNSUPPORTED" or hash-related build failures
Cause: Node 17+ uses OpenSSL 3.0 which removed support for some legacy hash algorithms used by webpack.
Solution:
# Set environment variable before running dev or build
export NODE_OPTIONS=--openssl-legacy-provider
# Windows (PowerShell)
$env:NODE_OPTIONS="--openssl-legacy-provider"
# Windows (CMD)
set NODE_OPTIONS=--openssl-legacy-provider
npm run dev
Error: "Cannot find module" after git pull
Cause: New dependencies were added but not installed.
Solution:
rm -rf node_modules
npm install
20.2 Development Server Issues
Error: "Port 9527 is already in use"
Cause: Another process is using port 9527.
Solution:
# Use a different port
port=8080 npm run dev
# Or kill the process using 9527
# Linux/macOS:
lsof -ti:9527 | xargs kill -9
# Windows:
netstat -ano | findstr :9527
taskkill /PID <pid> /F
Dev server starts but page is blank
Cause: Usually a JavaScript error in the console preventing Vue from mounting.
Solution:
- Open browser DevTools (F12)
- Check the Console tab for errors
- Common causes: missing environment variable, import path typo, syntax error in a recently edited file
Hot Module Replacement (HMR) not working
Cause: File system watchers may have limits, or the file is outside the watched directory.
Solution:
- Restart the dev server
- On Linux, increase file watchers:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - Verify the edited file is under
src/
Error: "Request failed with status code 404" on API calls
Cause: Mock server route does not match the request URL.
Solution:
- Check that the URL in
src/api/*.jsmatches theurlfield in the correspondingmock/*.jsfile - Mock URLs must match exactly, including the path prefix
- Check the Network tab in DevTools to see the actual requested URL
20.3 Build Issues
Error: "JavaScript heap out of memory"
Cause: Large project exceeds Node's default memory limit.
Solution:
# Increase Node memory limit
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build:prod
Build succeeds but app shows blank page in production
Cause: Usually a publicPath issue when deploying to a subdirectory.
Solution:
In vue.config.js, set publicPath to match your deploy path:
module.exports = {
publicPath: '/admin/' // If deploying to example.com/admin/
}
Also ensure your web server is configured to serve index.html for all routes (SPA fallback).
Build output is too large
Cause: Large dependencies bundled without optimization.
Solution:
- Use CDN for large libraries by configuring
externalsinvue.config.js - Check bundle analysis: add
--reportflag to the build command - Lazy-load routes (already done by default with
() => import(...)) - Remove unused components and dependencies
20.4 Authentication Issues
Error: "Illegal token" / "Token expired" popup keeps appearing
Cause: Response interceptor detected error codes 50008, 50012, or 50014 from the backend.
Solution:
- Clear cookies and log in again
- If using a real backend, verify the token TTL and refresh mechanism
- Check that the backend sends
code: 20000for valid responses
Login redirects back to login page
Cause: Token is being set but getInfo() fails, causing the route guard to redirect.
Solution:
- Check the Network tab for the user info API response
- Verify the response contains
rolesdata - Ensure
code: 20000is returned
After login, sidebar shows no menu items
Cause: The user's role does not match any asyncRoutes.
Solution:
- Verify the roles returned by
getInfo()match the role strings in routemeta.roles - Remember: roles are case-sensitive
- Admin role gets all async routes; other roles need explicit listing
20.5 Routing Issues
Error: "Duplicate named routes" warning in console
Cause: router.addRoutes() called multiple times (e.g., on page refresh while already logged in).
Solution: The project includes resetRouter() in src/router/index.js which creates a fresh matcher. Ensure it is called before addRoutes().
Page cached when it should not be (stale data)
Cause: <keep-alive> caches the page. The component's name matches the route's name, enabling caching.
Solution:
- Set
meta.noCache: trueon the route - Or use a different component
namethan the routename - Or manually remove from cache:
this.$store.dispatch('tagsView/delCachedView', view)
404 page shows for a valid route
Cause: The * catch-all route is evaluated before async routes are added.
Solution: The * catch-all must be the last route. It is placed at the end of asyncRoutes in the source code, so it is only added after addRoutes(). Do not add it to constantRoutes.
20.6 Component Issues
Tinymce editor does not load
Cause: CDN URL for Tinymce script is unreachable (network issues, CDN down).
Solution:
- Check the CDN URL in the Tinymce component
- Try an alternative CDN or host Tinymce locally
- Check browser DevTools Network tab for failed script loads
- GitHub issue #4335 reports broken githubusercontent links
ECharts chart does not resize with the window
Cause: The resize mixin is not included or the chart instance variable name does not match.
Solution:
- Import and use the resize mixin:
mixins: [resize] - Store the chart instance as
this.chart(the mixin expects this property name) - Make sure the chart container has a percentage-based or responsive width
Element UI components are too large/small
Cause: Global size setting may have been changed.
Solution:
- Check the Element UI size selector in the navbar
- Reset to
mediumordefault - The size is stored in the
appVuex module and can be set programmatically
v-permission directive not working
Cause: Component is rendered before roles are loaded, or the directive is not imported.
Solution:
- Import the directive:
import permission from '@/directive/permission' - Register it:
directives: { permission } - Or use the globally registered
v-permission(which should be available in all components) - Verify roles are loaded in Vuex store
20.7 Styling Issues
Custom styles are overridden by Element UI
Cause: Element UI styles have higher specificity or are loaded after custom styles.
Solution:
/* Use deep selector for scoped styles */
::v-deep .el-button {
background-color: #1890ff;
}
/* Or increase specificity */
.my-container .el-button {
background-color: #1890ff;
}
Sidebar width changes cause layout shift
Cause: $sideBarWidth variable changed but transitions not adjusted.
Solution: Update $sideBarWidth in src/styles/variables.scss. The sidebar collapse animation uses CSS transitions, so extreme width changes may look jarring. Keep the width between 180px and 260px for best results.
Print output includes sidebar and navbar
Cause: No print-specific CSS rules.
Solution: See Recipe 19.23 for print CSS media query rules.
20.8 Performance Issues
Page loads slowly with many tabs open in Tags View
Cause: All cached views remain in memory due to <keep-alive>.
Solution:
- Set
noCache: trueon routes with heavy components - Limit the maximum number of cached views by customizing the
tagsViewstore module - Close unused tabs via right-click context menu
Large table (1000+ rows) is sluggish
Cause: Element UI's el-table renders all rows by default.
Solution:
- Use server-side pagination (do not load all rows at once)
- Use virtual scrolling for very large datasets (third-party plugin required)
- Reduce the number of reactive properties in each row object
Bundle size is over 2MB
Cause: Large dependencies included in the main bundle.
Solution:
- Load Tinymce from CDN (already default)
- Load ECharts on-demand:
import('echarts/lib/chart/bar')instead ofimport echarts from 'echarts' - Use
vue.config.jsexternals to exclude Element UI and Vue from the bundle when serving from CDN - Enable gzip compression on your web server
20.9 Deployment Issues
Deployed app shows 404 on page refresh
Cause: The web server does not fall back to index.html for SPA routes.
Solution:
Nginx:
location / {
try_files $uri $uri/ /index.html;
}
Apache (.htaccess):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
Assets not loading (CSS/JS 404)
Cause: publicPath in vue.config.js does not match the deployment path.
Solution: Set publicPath to / for root deployment or /your-path/ for subdirectory deployment.
CORS errors in production
Cause: Backend and frontend are on different domains, and CORS headers are not configured.
Solution: Configure CORS on your backend, or use a reverse proxy (like Nginx) to serve both frontend and API from the same domain.
20.10 Mock Server Issues
Mock data returns but table shows empty
Cause: Response format mismatch. The table expects response.data.items but mock returns a different structure.
Solution: Check the mock response structure matches what the component expects. The standard pattern is { code: 20000, data: { items: [...], total: N } }.
Mock server not intercepting requests
Cause: Mock setup was removed or the URL path does not match.
Solution:
- Verify
mock/index.jsis imported in the entry file - Check that mock
urlfields match the API request URLs exactly - MockJS intercepts XMLHttpRequest — if you are using
fetch, MockJS will not intercept it
21. FAQ
Q: What version of Vue does vue-element-admin use? Vue 2.6.10. It is not compatible with Vue 3.x. Migrating to Vue 3 would require rewriting components, directives, filters (removed in Vue 3), and the Vuex store.
Q: What version of Element UI is included? Element UI 2.13.2. This is the Vue 2 version. For Vue 3, the equivalent library is Element Plus, but it is not a drop-in replacement.
Q: Can I use this with TypeScript?
Not out of the box. The project is entirely JavaScript. To add TypeScript, you would need to configure tsconfig.json, add Vue type declarations, and gradually convert .js files. This is a non-trivial migration for a project of this size.
Q: Is there a simplified version without all the demo pages?
Yes. PanJiaChen maintains vue-admin-template, a minimal version with only the core layout, login, and a blank dashboard. Use it as a starting point if you do not need the 30+ demo components.
Q: How do I remove demo pages I do not need?
- Delete the view folder from
src/views/(e.g.,src/views/charts/) - Remove the corresponding route from
src/router/index.js - Remove the corresponding API module from
src/api/if any - Remove the corresponding mock module from
mock/ - Run
npm run lintto catch any broken imports
Q: How do I change the app title?
Edit title in src/settings.js. The title appears in the browser tab, combined with each page's meta.title via the route afterEach guard in src/permission.js.
Q: How do I change the logo?
- Replace the logo image in
src/assets/or the sidebar logo component - The sidebar logo component is in
src/layout/components/Sidebar/Logo.vue - Set
sidebarLogo: trueinsrc/settings.jsto display it
Q: How do I add a new sidebar menu with an icon?
Add a route with meta.icon set to the name of an SVG file in src/icons/svg/. See Section 5.1 for the full process.
Q: Can I have a route visible in the sidebar but not accessible to certain roles?
Yes. Add the route to asyncRoutes with meta.roles specifying which roles can access it. Roles not listed will not see the route in their sidebar.
Q: How do I make a page always accessible regardless of role?
Add the route to constantRoutes instead of asyncRoutes. Constant routes are available to all authenticated users.
Q: How do I hide a page from the sidebar but still make it accessible?
Set meta.hidden: true on the route. The route remains functional via direct URL or programmatic navigation, but it will not appear in the sidebar.
Q: How do I redirect from one route to another?
Use the redirect property on a route:
{
path: '/old-path',
redirect: '/new-path'
}
Q: How do I open an external URL from the sidebar?
Add the full URL (starting with http:// or https://) as the route path. The sidebar renders it as an <a> tag with target="_blank".
Q: How do I add breadcrumb navigation to a nested page?
Breadcrumbs are automatic. They generate from the route's matched array. Ensure each parent route has meta.title set. Set meta.breadcrumb: false to hide a specific level.
Q: How do I keep a page's state when switching between tabs?
By default, pages with matching name properties are cached by <keep-alive>. Ensure your component's name matches the route's name. If caching does not work, check that meta.noCache is not set to true.
Q: How do I force a page to reload when its tab is clicked?
Right-click the tab and select "Refresh" from the context menu. Programmatically, dispatch tagsView/delCachedView and then re-navigate.
Q: How do I customize the login page?
Edit src/views/login/index.vue. The login form, background, and validation are all in this single file. The login API call goes to src/api/user.js.
Q: Can I use a different authentication method (JWT, OAuth)?
Yes. Replace the token handling in src/utils/auth.js and the login/getInfo actions in src/store/modules/user.js. The route guard in src/permission.js only checks for the presence of a token — it does not care how the token was obtained.
Q: How do I add a loading spinner to a page?
Use Element UI's v-loading directive on any container element. For full-page loading, use this.$loading(). See Recipe 19.21.
Q: How do I handle 401/403 errors from a real backend?
The response interceptor in src/utils/request.js handles codes 50008/50012/50014. For standard HTTP 401/403 status codes, add handling in the response interceptor's error block:
service.interceptors.response.use(
response => { /* ... */ },
error => {
if (error.response && error.response.status === 401) {
// Handle unauthorized
store.dispatch('user/resetToken').then(() => {
location.reload()
})
}
return Promise.reject(error)
}
)
Q: How do I add Google Analytics or similar tracking?
Add the tracking script to public/index.html, or install the Vue plugin (vue-analytics, vue-gtag) and configure it in main.js.
Q: How do I change the default language of Element UI?
In main.js, import the locale and set it:
import locale from 'element-ui/lib/locale/lang/en'
Vue.use(ElementUI, { locale })
Replace en with the desired language code (e.g., zh-CN, ja, ko).
Q: How do I deploy to a CDN?
- Set
publicPathinvue.config.jsto your CDN URL - Run
npm run build:prod - Upload the
dist/contents to your CDN - Configure your web server to serve
index.htmllocally and reference CDN assets
Q: How do I add a watermark to pages? Create a canvas-based watermark utility and apply it as a directive or in the layout component. The project does not include a watermark feature by default.
Q: Why does my page flash/flicker on load?
This is often caused by route guard redirects. The route guard checks the token, then fetches user info, then generates routes — all before showing the page. If getInfo() is slow, add a full-page loading indicator in src/permission.js.
Q: How do I debug Vuex state changes? Install Vue DevTools browser extension. It provides a Vuex inspector showing all state, mutations, and actions in real-time. Works with Vue 2.6.
Q: Can I use this project without the mock server?
Yes. Remove the mock imports and point VUE_APP_BASE_API to your real backend. See Section 5.6 for the full process.
Q: What IDE setup is recommended? VSCode with these extensions:
- Vetur (Vue tooling)
- ESLint (code quality)
- Prettier (formatting — configure to match the project's ESLint rules)
The project includes .eslintrc.js for consistent code style.
Q: How do I update Element UI to a newer version?
npm update element-ui
Then check for breaking changes in the Element UI changelog. Minor updates within 2.x should be safe. Do not upgrade to Element Plus (that is for Vue 3).
22. Route Reference
22.1 Constant Routes (All Roles)
These routes are available to every authenticated user:
| Path | Page | Notes |
|---|---|---|
/ |
Redirect to /dashboard |
|
/dashboard |
Dashboard | Affix (pinned tab) |
/documentation |
Documentation | Affix (pinned tab) |
/guide |
Guide | Interactive guide page |
/profile |
User Profile | Hidden from sidebar |
/login |
Login | Hidden from sidebar |
/auth-redirect |
Auth Redirect | Hidden from sidebar |
/401 |
401 Error | Hidden from sidebar |
/404 |
404 Error | Hidden from sidebar |
22.2 Async Routes (Role-Filtered)
These routes are dynamically added based on user roles:
| Path | Page | Required Roles | Children |
|---|---|---|---|
/permission |
Permission | admin, editor | Page (admin), Directive, Role (admin) |
/icon |
Icons | all | Icon gallery |
/components |
Components | all | 14 component demos |
/charts |
Charts | all | Keyboard, Line, Mix |
/nested |
Nested | all | Multi-level menus |
/table |
Table | all | Dynamic, Drag, Inline Edit, Complex |
/example |
Example | all | Article List, Create, Edit |
/tab |
Tab | all | Tab demo |
/error |
Error Pages | all | 401, 404 |
/error-log |
Error Log | all | Error log page |
/excel |
Excel | all | Export, Selected, Merge, Upload |
/zip |
Zip | all | Export Zip |
/pdf |
all | PDF export | |
/theme |
Theme | all | Theme picker |
/clipboard |
Clipboard | all | Clipboard demo |
| (external link) | External Link | all | Opens in new tab |
23. Vuex Store Reference
23.1 Modules
| Module | State | Key Actions | Purpose |
|---|---|---|---|
app |
sidebar.opened, sidebar.withoutAnimation, device, size |
toggleSideBar, closeSideBar, toggleDevice, setSize |
App-level UI state |
errorLog |
logs |
addErrorLog, clearErrorLog |
JavaScript error collection |
permission |
routes, addRoutes |
generateRoutes(roles) |
Dynamic route generation |
settings |
theme, showSettings, tagsView, fixedHeader, sidebarLogo |
changeSetting({ key, value }) |
UI settings panel state |
tagsView |
visitedViews, cachedViews |
addView, delView, delCachedView, delOthersViews, delAllViews, updateVisitedView |
Tab navigation state |
user |
token, name, avatar, introduction, roles |
login, getInfo, logout, resetToken, changeRoles |
Authentication and user data |
23.2 Getters
Centralized getters in src/store/getters.js:
| Getter | Source | Returns |
|---|---|---|
sidebar |
app.sidebar |
Sidebar state object |
size |
app.size |
Element UI component size |
device |
app.device |
'desktop' or 'mobile' |
visitedViews |
tagsView.visitedViews |
Array of visited route objects |
cachedViews |
tagsView.cachedViews |
Array of cached component names |
token |
user.token |
Auth token string |
avatar |
user.avatar |
User avatar URL |
name |
user.name |
User display name |
introduction |
user.introduction |
User bio/introduction |
roles |
user.roles |
Array of role strings |
permission_routes |
permission.routes |
All accessible routes |
errorLogs |
errorLog.logs |
Array of error log entries |
24. Keyboard Shortcuts and Navigation Tips
24.1 Built-in Shortcuts
| Shortcut | Action |
|---|---|
| Click search icon in navbar | Open header search (fuzzy route finder) |
| Type in header search | Filter routes by title (Fuse.js fuzzy match) |
| Click screenfull icon | Toggle browser fullscreen |
| Right-click a tag/tab | Open context menu (Refresh, Close, Close Others, Close All) |
24.2 Navigation Tips
- Quick navigation: Use the header search to jump to any page by typing part of its title
- Organize tabs: Right-click and "Close Others" to clear clutter
- Refresh a cached page: Right-click its tab and select "Refresh"
- Mobile mode: The sidebar auto-collapses below the mobile breakpoint; tap the hamburger to toggle
- Collapse sidebar: Click the hamburger icon for more content space; the sidebar collapses to icon-only mode
25. Best Practices
25.1 Project Organization
- One view per folder — Each page lives in
src/views/{feature}/index.vuewith sub-components alongside it - API modules mirror views —
src/api/article.jsservessrc/views/article/ - Component naming — Use PascalCase for component names, match the folder name
- Lazy load all routes — Use
() => import(...)for every route component to enable code splitting
25.2 Performance
- Avoid large Vuex state — Do not store entire API responses if only a subset is needed
- Use
noCachewisely — Cache pages that are expensive to render; skip caching for pages with rapidly changing data - Lazy load heavy components — Tinymce, ECharts, and JSON editor are already lazy-loaded via dynamic imports. Follow this pattern for any large dependency
- Debounce search inputs — Use the
debounceutility for search fields that trigger API calls
25.3 Security
- Never store sensitive data in Vuex or localStorage — Tokens in cookies (with httpOnly in production) are safer
- Validate on the server — Client-side permission checks (v-permission, route guards) are UX features, not security boundaries. Always validate roles and permissions on your backend
- Sanitize user input — Use
html2Textfrom utils when displaying user-generated content to prevent XSS
Warning
The mock server is for development only. It provides no real authentication or authorization. Do not deploy the mock server to production. All permission checks in the frontend are for UX purposes — your real backend must enforce authorization independently.
25.4 Code Style
- Follow the ESLint configuration in
.eslintrc.js - Run
npm run lintbefore committing - Use
scopedstyles in single-file components - Use
@/path alias for imports (maps tosrc/)
26. Version Information
| Package | Version |
|---|---|
| vue-element-admin | 4.4.0 |
| Vue.js | 2.6.10 |
| Element UI | 2.13.2 |
| Vuex | 3.1.0 |
| Vue Router | 3.0.2 |
| Axios | 0.18.1 |
| ECharts | 4.2.1 |
| MockJS | 1.0.1-beta3 |
| Vue CLI | 4.4.4 |
| Webpack | 4.x (via Vue CLI) |