Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

vue-element-admin User Guide

Generated May 2026 from PanJiaChen/vue-element-admin at 6858a9a

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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-provider flag due to OpenSSL 3.0 changes. If you encounter hash-related build errors, set NODE_OPTIONS=--openssl-legacy-provider before 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, and sidebarLogo at runtime using the settings panel (the gear icon in the top-right corner). These runtime changes are managed through the Vuex settings store 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, returns res.data
  • 50008 — Illegal token
  • 50012 — Other client logged in
  • 50014 — 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: true on the parent to always show the parent wrapper
  • Multiple children — Renders as a nested sub-menu automatically

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.

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 name property on your view component. The name must match the route's name for <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.js expects all successful responses to have code: 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-permission directive 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, use v-if with checkPermission() 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):

  1. Backend/Mock: Return the new role from the user info endpoint
  2. Routes: Add 'viewer' to the meta.roles arrays of routes the new role should access
  3. Elements: Use v-permission="['viewer']" or checkPermission(['viewer']) where needed
  4. Mock user: Add a new user entry in mock/user.js that returns the viewer role

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:

  1. Fetching Element UI's compiled CSS
  2. Replacing color values with regex
  3. 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

  1. Create src/api/user-management.js with list, create, update, delete functions
  2. Add mock data in mock/user-management.js
  3. Create src/views/user-management/index.vue with el-table, filters, and pagination
  4. Add the route to asyncRoutes with meta.roles: ['admin']
  5. Add an Edit dialog using el-dialog with form validation
  6. Use v-permission="['admin']" on the delete button

19.2 Adding a Dashboard Widget

  1. Create a new component in src/views/dashboard/components/
  2. Import and register it in the dashboard index page
  3. Add an API call in src/api/ for the widget data
  4. Add corresponding mock data
  5. Use ECharts if it is a chart widget (import the resize mixin)

19.3 Implementing File Upload with Preview

  1. Use the el-upload component from Element UI
  2. Configure action to point to your upload endpoint
  3. Use :before-upload for file validation (size, type)
  4. Use :on-success to handle the response and store the file URL
  5. Display uploaded files using el-image or a custom preview component

19.4 Adding a Multi-Tab Detail Page

  1. Create a view with el-tabs containing multiple el-tab-pane components
  2. Use route query parameters to track the active tab: this.$route.query.tab
  3. Set noCache: true if tab state should reset on re-entry
  4. Use meta.activeMenu to keep the parent menu highlighted

19.5 Creating a Wizard/Stepper Form

  1. Use el-steps component for the progress indicator
  2. Track current step in component data
  3. Use v-show (not v-if) for each step's form fields to preserve validation state
  4. Validate each step with this.$refs.form.validateField(fields) before advancing
  5. Submit all collected data on the final step

19.6 Building a Tree-Based Navigation

  1. Use el-tree with default-expand-all or lazy loading
  2. Handle @node-click to load detail in a right panel
  3. Use el-tree props: data, props, node-key, highlight-current
  4. Combine with SplitPane component for resizable tree + detail layout

The HeaderSearch component already provides fuzzy route search. To add data search:

  1. Create a search API endpoint
  2. Use el-autocomplete or el-select with remote search
  3. Debounce the search input (use the debounce utility from src/utils/index.js)
  4. Navigate to the result on selection

19.8 Adding Keyboard Shortcuts

  1. Listen for keydown events in your component's mounted hook
  2. Clean up in beforeDestroy
  3. Use the existing pattern from HeaderSearch (which uses keyup events)
  4. Consider using a library like mousetrap for complex shortcuts

19.9 Batch Operations on Table Data

  1. Add selection type column to el-table: <el-table-column type="selection" />
  2. Handle @selection-change to track selected rows
  3. Add batch action buttons above the table
  4. Confirm with this.$confirm before destructive batch operations
  5. Show loading state during the batch API call

19.10 Implementing Infinite Scroll

  1. Use el-table with a fixed height
  2. Listen for the table's scroll event
  3. When scroll reaches bottom, load the next page
  4. Append new data to the existing array (do not replace)
  5. 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

  1. Install: npm install library-name
  2. If the library has a Vue wrapper, register it globally in main.js or locally in the component
  3. If it requires DOM access, initialize in mounted() and clean up in beforeDestroy()
  4. For large libraries, use dynamic import to keep the main bundle small: const lib = () => import('library-name')
  5. 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

  1. Use el-badge for unread count in the navbar
  2. Create a notification dropdown with el-popover
  3. Poll the notification API on a timer (or use WebSocket)
  4. Mark as read on click, update the badge count
  5. Use Notification component from Element UI for real-time push alerts

19.18 Exporting Filtered Table Data

  1. Apply filters to the table data
  2. Use the Export2Excel utility 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

  1. Create a dialog with old password, new password, and confirm password fields
  2. Validate that new password matches confirmation
  3. Validate minimum length and complexity
  4. Call the update password API
  5. 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

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:

  1. Open browser DevTools (F12)
  2. Check the Console tab for errors
  3. 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:

  1. Restart the dev server
  2. On Linux, increase file watchers: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
  3. 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:

  1. Check that the URL in src/api/*.js matches the url field in the corresponding mock/*.js file
  2. Mock URLs must match exactly, including the path prefix
  3. 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:

  1. Use CDN for large libraries by configuring externals in vue.config.js
  2. Check bundle analysis: add --report flag to the build command
  3. Lazy-load routes (already done by default with () => import(...))
  4. 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:

  1. Clear cookies and log in again
  2. If using a real backend, verify the token TTL and refresh mechanism
  3. Check that the backend sends code: 20000 for valid responses

Login redirects back to login page

Cause: Token is being set but getInfo() fails, causing the route guard to redirect.

Solution:

  1. Check the Network tab for the user info API response
  2. Verify the response contains roles data
  3. Ensure code: 20000 is returned

After login, sidebar shows no menu items

Cause: The user's role does not match any asyncRoutes.

Solution:

  1. Verify the roles returned by getInfo() match the role strings in route meta.roles
  2. Remember: roles are case-sensitive
  3. 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:

  1. Set meta.noCache: true on the route
  2. Or use a different component name than the route name
  3. 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:

  1. Check the CDN URL in the Tinymce component
  2. Try an alternative CDN or host Tinymce locally
  3. Check browser DevTools Network tab for failed script loads
  4. 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:

  1. Import and use the resize mixin: mixins: [resize]
  2. Store the chart instance as this.chart (the mixin expects this property name)
  3. 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:

  1. Check the Element UI size selector in the navbar
  2. Reset to medium or default
  3. The size is stored in the app Vuex 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:

  1. Import the directive: import permission from '@/directive/permission'
  2. Register it: directives: { permission }
  3. Or use the globally registered v-permission (which should be available in all components)
  4. 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;
}

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.

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:

  1. Set noCache: true on routes with heavy components
  2. Limit the maximum number of cached views by customizing the tagsView store module
  3. 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:

  1. Use server-side pagination (do not load all rows at once)
  2. Use virtual scrolling for very large datasets (third-party plugin required)
  3. Reduce the number of reactive properties in each row object

Bundle size is over 2MB

Cause: Large dependencies included in the main bundle.

Solution:

  1. Load Tinymce from CDN (already default)
  2. Load ECharts on-demand: import('echarts/lib/chart/bar') instead of import echarts from 'echarts'
  3. Use vue.config.js externals to exclude Element UI and Vue from the bundle when serving from CDN
  4. 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:

  1. Verify mock/index.js is imported in the entry file
  2. Check that mock url fields match the API request URLs exactly
  3. 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?

  1. Delete the view folder from src/views/ (e.g., src/views/charts/)
  2. Remove the corresponding route from src/router/index.js
  3. Remove the corresponding API module from src/api/ if any
  4. Remove the corresponding mock module from mock/
  5. Run npm run lint to 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?

  1. Replace the logo image in src/assets/ or the sidebar logo component
  2. The sidebar logo component is in src/layout/components/Sidebar/Logo.vue
  3. Set sidebarLogo: true in src/settings.js to 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?

  1. Set publicPath in vue.config.js to your CDN URL
  2. Run npm run build:prod
  3. Upload the dist/ contents to your CDN
  4. Configure your web server to serve index.html locally 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 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.vue with sub-components alongside it
  • API modules mirror views — src/api/article.js serves src/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 noCache wisely — 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 debounce utility 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 html2Text from 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 lint before committing
  • Use scoped styles in single-file components
  • Use @/ path alias for imports (maps to src/)

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)