Building a Vue 3 Admin Panel: Setup and Configuration

This project utilizes Vue 3, TypeScript, Vue Router, Pinia, Element Plus, Axios, and ECharts.

Project Initialization

This guide covers the process of setting up a Vue 3-based admin management system from scratch. Establishing consistent project standards is crucial. We'll integrate ESLint, Stylelint, and Prettier for code quality checks and auto-formatting. Husky will be used for commit message validation, and Pre-install will enforce the use of a specific package manager.

Environment Setup

  • Node.js version: v16.14.2
  • pnpm version: 8.0.0

Project Scaffolding

Vite is the build tool of choice for this project. Refer to the official Vite documentation for details: Vite Official Documentation.

pnpm (performant npm) is a fast, disk-space-efficient package manager that offers significant performance improvements and solves potential issues found in npm and yarn. It's considered a highly advanced package management tool.

To install pnpm:

npm i -g pnpm

To initialize the project:

pnpm create vite

Navigate to the project's root directory and install dependencies:

pnpm install

To run the development server:

pnpm run dev

The application will be accessible at http://127.0.0.1:5173/.

Cleaning Unnecessary Files

In the src directory:

  • Delete SVG files from the assets folder.
  • Remove style imports from main.ts.
  • Delete style.css.
  • Modify App.vue to contain only the basic structure:
<template>
  <div>
    <h1>App Root Component</h1>
  </div>
</template>

<script setup lang="ts">
</script>

<style scoped></style>

  • Delete the HelloWorld.vue component.

Project Configuration

Opening Project Automatically

To have the browser automatically open the application when running the development server, modify the dev script in package.json:

"dev": "vite --open",

ESLint Configuration

ESLint is a pluggable JavaScript linter tool created by Nicholas C. Zakas in 2013. It helps identify and report on problematic patterns in JavaScript code.

Install ESLint:

pnpm i eslint -D

Generate the ESLint configuration file (.eslint.cjs):

npx eslint --init

A basic .eslint.cjs configuration:

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:vue/vue3-essential",
    "plugin:@typescript-eslint/recommended"
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    ecmaVersion: "latest",
    sourceType: "module"
  },
  plugins: [
    "vue",
    "@typescript-eslint"
  ],
  rules: {
  }
}

Additional ESLint plugins and configurations for Prettier integration:

pnpm install -D eslint-plugin-import eslint-plugin-vue eslint-plugin-node eslint-plugin-prettier eslint-config-prettier eslint-plugin-node @babel/eslint-parser

Update .eslintrc.cjs for comprehensive linting:

// .eslintrc.cjs
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
    jest: true,
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module',
    parser: '@typescript-eslint/parser',
    jsxPragma: 'React',
    ecmaFeatures: {
      jsx: true,
    },
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  plugins: ['vue', '@typescript-eslint'],
  rules: {
    // Eslint rules
    'no-var': 'error',
    'no-multiple-empty-lines': ['warn', { max: 1 }],
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-unexpected-multiline': 'error',
    'no-useless-escape': 'off',

    // TypeScript rules
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/prefer-ts-expect-error': 'error',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/no-namespace': 'off',
    '@typescript-eslint/semi': 'off',

    // Vue rules
    'vue/multi-word-component-names': 'off',
    'vue/script-setup-uses-vars': 'error',
    'vue/no-mutating-props': 'off',
    'vue/attribute-hyphenation': 'off',
  },
}

Create a .eslintignore file to specify files and directories to be ignored by ESLint:

dist
node_modules

Add scripts to package.json for linting and fixing:

"scripts": {
    "lint": "eslint src",
    "fix": "eslint src --fix",
}

Run the scripts:

pnpm run lint  // Check code quality
pnpm run fix   // Automatically fix code style issues

Prettier Configuration

While ESLint focuses on JavaScript code quality and potential errors, Prettier is dedicated to code formatting. It ensures a consistent visual style across the project, supporting various languages.

Install Prettier dependencies:

pnpm install -D eslint-plugin-prettier prettier eslint-config-prettier

Create a .prettierrc.json file with formatting rules:

{
  "singleQuote": true,
  "semi": false,
  "bracketSpacing": true,
  "htmlWhitespaceSensitivity": "ignore",
  "endOfLine": "auto",
  "trailingComma": "all",
  "tabWidth": 2
}

Create a .prettierignore file to specify files to be ignored by Prettier:

/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*

Running pnpm run lint checks syntax, and pnpm run fix attempts to correct formatting issues.

Stylelint Configuration

Stylelint is a linter for CSS, helping to format CSS code, catch syntax errors, and enforce consistent writing styles. This project uses SCSS as its preprocessor.

Install Stylelint and related dependencies:

pnpm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D

Create a .stylelintrc.cjs configuration file:

// .stylelintrc.cjs
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-html/vue',
    'stylelint-config-standard-scss',
    'stylelint-config-recommended-vue/scss',
    'stylelint-config-recess-order',
    'stylelint-config-prettier',
  ],
  overrides: [
    {
      files: ['**/*.(scss|css|vue|html)'],
      customSyntax: 'postcss-scss',
    },
    {
      files: ['**/*.(html|vue)'],
      customSyntax: 'postcss-html',
    },
  ],
  ignoreFiles: [
    '**/*.js',
    '**/*.jsx',
    '**/*.tsx',
    '**/*.ts',
    '**/*.json',
    '**/*.md',
    '**/*.yaml',
  ],
  rules: {
    'value-keyword-case': null,
    'no-descending-specificity': null,
    'function-url-quotes': 'always',
    'no-empty-source': null,
    'selector-class-pattern': null,
    'property-no-unknown': null,
    'block-opening-brace-space-before': 'always',
    'value-no-vendor-prefix': null,
    'property-no-vendor-prefix': null,
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['global', 'v-deep', 'deep'],
      },
    ],
  },
}

Create a .stylelintignore file:

/node_modules/*
/dist/*
/html/*
/public/*

Add a script to package.json for style linting:

"scripts": {
 "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
}

To apply consistent formatting across JavaScript, CSS, and HTML, update the scripts section in package.json:

"scripts": {
    "dev": "vite --open",
    "build": "vue-tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint src",
    "fix": "eslint src --fix",
    "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
    "lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
    "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix"
  },

Running pnpm run format will format all supported files.

Husky for Git Hooks

To ensure code quality standards are maintained, we'll use Husky to automatically run formatting checks before commits.

Install Husky:

pnpm install -D husky

Initialize Husky:

npx husky-init

This creates a .husky directory with a pre-commit file. Add the following command to .husky/pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm run format

Now, before each commit, the pnpm run format command will execute, ensuring code is formatted.

Commitlint for Commit Message Standards

To enforce a standardized format for commit messages, we'll use Commitlint.

Install Commitlint dependencies:

pnpm add @commitlint/config-conventional @commitlint/cli -D

Create a commitlint.config.cjs file:

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert', 'build',
      ],
    ],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 72],
  },
}

Add a Commitlint script to package.json:

"scripts": {
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
},

Commit message types include:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • style: Code style changes (formatting, etc.)
  • refactor: Code refactoring
  • perf: Performance improvements
  • test: Adding or modifying tests
  • chore: Other changes (build process, dependencies)
  • revert: Reverting a previous commit
  • build: Build system or external dependency changes

Configure Husky to use Commitlint:

npx husky add .husky/commit-msg

Add the following command to the generated .husky/commit-msg file:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm commitlint

Now, commit messages must follow the specified format (e.g., git commit -m 'fix: Resolve login issue'). Note the colon and space after the type.

Enforcing pnpm Package Manager

To ensure all team members use the same package manager, preventing potential version conflicts, we'll enforce pnpm.

Create a scripts/preinstall.js file:

if (!/pnpm/.test(process.env.npm_execpath || '')) {
  console.warn(
    `\u001b[33mThis repository must using pnpm as the package manager ` +
    ` for scripts to work properly.\u001b[39m\n`,
  )
  process.exit(1)
}

Add the preinstall script to package.json:

"scripts": {
 "preinstall": "node ./scripts/preinstall.js"
}

This script will execute before any installation command (e.g., npm install or yarn install), throwing an error if pnpm is not used.

Project Integration

Element Plus Integration

Element Plus is the chosen UI component library. Install it along with its icons:

pnpm install element-plus @element-plus/icons-vue

Globally register Element Plus in main.ts, setting the locale to Chinese:

import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
// @ts-ignore: Ignore TypeScript type checking for this line
import zhCn from 'element-plus/dist/locale/zh-cn.mjs';

app.use(ElementPlus, {
    locale: zhCn
});

To resolve potential build errors related to locale imports, the //@ts-ignore comment is used.

Add global component type declarations to tsconfig.json:

// tsconfig.json
{
  "compilerOptions": {
    // ... other options
    "types": ["element-plus/global"]
  }
}

Source Directory Aliases

To simplify imports within the project, configure a path alias for the src directory.

Update vite.config.ts:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            "@": path.resolve("./src") // Alias for src directory
        }
    }
});

Configure TypeScript path mapping in tsconfig.json:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

After setting up aliases, you might encounter linting issues in VS Code. Disabling or uninstalling the Vetur extension and restarting the IDE should resolve this.

Environment Variable Configuration

Projects typically require different configurations for development, testing, and production environments (e.g., API endpoints). Environment variables simplify managing these differences.

  • .env.development: For the development environment.
  • .env.production: For the production environment.
  • .env.test: For the testing environment.

Example .env.development:

NODE_ENV = 'development'
VITE_APP_TITLE = 'Vue 3 Admin Panel'
VITE_APP_BASE_API = '/dev-api'

Example .env.production:

NODE_ENV = 'production'
VITE_APP_TITLE = 'Vue 3 Admin Panel'
VITE_APP_BASE_API = '/prod-api'

Example .env.test:

NODE_ENV = 'test'
VITE_APP_TITLE = 'Vue 3 Admin Panel'
VITE_APP_BASE_API = '/test-api'

Note: All Vite environment variables must be prefixed with VITE_.

Update the scripts section in package.json to handle different build modes:

"scripts": {
    "dev": "vite --open",
    "build:test": "vue-tsc && vite build --mode test",
    "build:pro": "vue-tsc && vite build --mode production",
    "preview": "vite preview"
  },

Environment variables can be accessed using import.meta.env.

SVG Icon Configuration

Using SVG icons can significantly improve page performance by reducing image requests and file sizes.

Install the SVG icons plugin:

pnpm install vite-plugin-svg-icons -D

Configure the plugin in vite.config.ts:

import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';

export default defineConfig((config) => {
  return {
    plugins: [
      vue(),
      createSvgIconsPlugin({
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        symbolId: 'icon-[dir]-[name]',
      }),
    ],
    css: {
        preprocessorOptions: {
          scss: {
            additionalData: '@import "./src/styles/variable.scss";',
          },
        },
      },
  };
});

Import the SVG icons register in your entry point (e.g., main.ts):

import 'virtual:svg-icons-register';

Creating a Global SVG Icon Component

Create a reusable SvgIcon.vue component in src/components/:

<template>
  <svg :style="{ width: width, height: height }">
    <use :xlink:href="prefix + name" :fill="color"></use>
  </svg>
</template>

<script setup lang="ts">
defineProps({
  prefix: {
    type: String,
    default: '#icon-'
  },
  name: String,
  color: {
    type: String,
    default: ""
  },
  width: {
    type: String,
    default: '16px'
  },
  height: {
    type: String,
    default: '16px'
  }
})
</script>
<style scoped></style>

Create an index.ts file in src/components/ to register global components:

import SvgIcon from './SvgIcon.vue';
import type { App, Component } from 'vue';

const components: { [name: string]: Component } = { SvgIcon };

export default {
    install(app: App) {
        Object.keys(components).forEach((key: string) => {
            app.component(key, components[key]);
        })
    }
}

Register the global component plugin in main.ts:

import gloablComponent from './components/index';
app.use(gloablComponent);

Now, you can use the <svg-icon> component throughout your application.

SCSS Integration

SCSS support is already available due to the installation of sass and sass-loader during Stylelint setup. You can use SCSS in your component styles by adding lang="scss" to the <style> tag.

Create a global SCSS file (e.g., src/styles/index.scss) and import necessary resets:

// src/styles/index.scss
@import 'reset.scss';

Import the global styles in main.ts:

import '@/styles/index.scss';

To use global SCSS variables, define them in src/styles/variable.scss and configure Vite to import them:

// vite.config.ts
export default defineConfig((config) => {
  return {
    // ... other configurations
    css: {
      preprocessorOptions: {
        scss: {
          javascriptEnabled: true,
          additionalData: '@import "./src/styles/variable.scss";',
        },
      },
    },
  };
});

Ensure the semicolon at the end of the additionalData path is present.

Mock Data Integration

Mocking API requests allows for frontend development without a backend. Install the necessary plugins:

pnpm install -D vite-plugin-mock mockjs

Enable the mock server plugin in vite.config.js:

import { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'

export default ({ command })=> {
  return {
    plugins: [
      vue(),
      viteMockServe({
        localEnabled: command === 'serve', // Enable mock only in development server
      }),
    ],
  }
}

Create mock API definitions in a mock/ directory. For example, mock/user.ts:

// mock/user.ts
function createUserList() {
    return [
        {
            userId: 1,
            avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            username: 'admin',
            password: '111111',
            desc: 'Platform Administrator',
            roles: ['Platform Administrator'],
            buttons: ['cuser.detail'],
            routes: ['home'],
            token: 'Admin Token',
        },
        {
            userId: 2,
            avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            username: 'system',
            password: '111111',
            desc: 'System Administrator',
            roles: ['System Administrator'],
            buttons: ['cuser.detail', 'cuser.user'],
            routes: ['home'],
            token: 'System Token',
        },
    ]
}

export default [
    // User login API
    {
        url: '/api/user/login',
        method: 'post',
        response: ({ body }) => {
            const { username, password } = body;
            const checkUser = createUserList().find(
                (item) => item.username === username && item.password === password,
            )
            if (!checkUser) {
                return { code: 201, data: { message: 'Incorrect username or password' } }
            }
            const { token } = checkUser
            return { code: 200, data: { token } }
        },
    },
    // Get user info API
    {
        url: '/api/user/info',
        method: 'get',
        response: (request) => {
            const token = request.headers.token;
            const checkUser = createUserList().find((item) => item.token === token)
            if (!checkUser) {
                return { code: 201, data: { message: 'Failed to get user information' } }
            }
            return { code: 200, data: { checkUser } }
        },
    },
]

Install Axios for making HTTP requests:

pnpm install axios

You can test the mock APIs by making requests using Axios, for example, in main.ts:

import axios from 'axios';
// Test login mock API
axios({
    url:'/api/user/login',
    method:"post",
    data:{
        username:'admin',
        password:'111111'
    }
});

Axios Instance Configuration

Axios is used for making HTTP requests. We'll create a reusable Axios instance with interceptors for request and response handling.

Create utils/request.ts:

// utils/request.ts
import axios from "axios";
import { ElMessage } from "element-plus";

const request = axios.create({
    baseURL: import.meta.env.VITE_APP_BASE_API,
    timeout: 5000
});

// Request interceptor
request.interceptors.request.use(config => {
    // Add common headers or token here
    return config;
});

// Response interceptor
request.interceptors.response.use((response) => {
    // Return response data directly
    return response.data;
}, (error) => {
    let message = '';
    const status = error.response.status;
    switch (status) {
        case 401:
            message = "Token expired";
            break;
        case 403:
            message = "Unauthorized access";
            break;
        case 404:
            message = "Request address error";
            break;
        case 500:
            message = "Server error";
            break;
        default:
            message = "Network error";
    }
    ElMessage({
        type: 'error',
        message: message
    });
    return Promise.reject(error);
});

export default request;

Ensure your .env.development file has VITE_APP_BASE_API = '/api'.

API Interface Management

Centralize API definitions in a dedicated api/ directory. For example, create api/user/index.ts:

// api/user/index.ts
import request from '@/utils/request';
import type { loginFormData, loginResponseData, userInfoResponseData } from './type';

enum API {
    LOGIN_URL = '/api/user/login',
    USERINFO_URL = '/api/user/info',
    LOGOUT_URL = '/api/user/logout',
}

// User login API
export const reqLogin = (data: loginFormData) =>
    request.post<any loginresponsedata="">(API.LOGIN_URL, data);

// Get user info API
export const reqUserInfo = () =>
    request.get<any userinforesponsedata="">(API.USERINFO_URL);

// Logout API
export const reqLogout = () => request.post<any any="">(API.LOGOUT_URL);
</any></any></any>

Define corresponding TypeScript types in api/user/type.ts:

// api/user/type.ts
export interface loginFormData {
    username: string,
    password: string
}

interface dataType {
    token: string
}

export interface loginResponseData {
    code: number,
    data: dataType
}

interface userInfo {
    userId: number,
    avatar: string,
    username: string,
    password: string,
    desc: string,
    roles: string[],
    buttons: string[],
    routes: string[],
    token: string
}

interface user {
    checkUser: userInfo
}

export interface userInfoResponseData {
    code: number,
    data: user
}

Test the API calls in app.vue:

<script setup lang="ts">
import { onMounted } from 'vue';
import { reqLogin } from './api/user';

onMounted(() => {
  reqLogin({
    username:'admin',
    password:'111111'
  });
});
</script>

Vue Router Configuration

Install Vue Router:

pnpm install vue-router

Create a views/ directory for your route components (e.g., login/index.vue, home/index.vue, 404/index.vue).

Create a router/index.ts file:

// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router';
import { constantRouter } from './routers';

const router = createRouter({
    history: createWebHashHistory(),
    routes: constantRouter,
    scrollBehavior() {
        return {
            left: 0,
            top: 0,
        };
    },
});

export default router;

Define routes in router/routers.ts:

// router/routers.ts
export const constantRouter = [
    {
        path: '/login',
        component: () => import('@/views/login/index.vue'),
        name: 'login',
    },
    {
        path: '/',
        component: () => import('@/views/home/index.vue'),
        name: 'layout',
    },
    {
        path: '/404',
        component: () => import('@/views/404/index.vue'),
        name: '404',
    },
    {
        path: '/:pathMatch(.*)*',
        redirect: '404',
        name: 'Any',
    },
];

Register the router in main.ts:

import router from './router';
app.use(router);

Ensure your root app.vue component includes <router-view></router-view>:

<template>
  <div>
    <router-view></router-view>
  </div>
</template>

Tags: Vue.js TypeScript Vite ESLint Prettier

Posted on Sat, 18 Jul 2026 16:04:45 +0000 by ghost007