Frontend Development 28 min read

General Architecture Plan for a Vue‑based Backend Management System

This article presents a comprehensive guide to building a generic backend management system with Vue, covering project initialization using Vite or Vue‑CLI, code standards, CSS architecture (ITCSS, BEM, ACSS), JWT authentication, dynamic menu and route generation, RBAC, caching, and component templating.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
General Architecture Plan for a Vue‑based Backend Management System

General Architecture Plan for a Vue‑based Backend Management System

The article outlines a complete solution for creating a generic admin backend system using Vue, focusing on frontend tooling, code quality, CSS architecture, authentication, dynamic routing, permission control, caching, and automated scaffolding.

Project Creation and Scaffold Choice

Two main scaffolds are compared: vue-cli (webpack‑based, powerful but complex) and vite (esbuild dev server, rollup bundler, fast cold‑start and HMR). The author recommends vite for a smoother development experience.

$ npm init @vitejs/app
$ cd
$ npm install
$ npm run dev

Basic Setup – Code Standards (ESLint + Prettier)

Install linting and formatting tools in VSCode:

yarn add --dev eslint eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add --dev prettier eslint-config-prettier eslint-plugin-prettier

Example .prettierrc.js configuration:

module.exports = {
  printWidth: 180,
  tabWidth: 4,
  useTabs: false,
  singleQuote: true,
  semi: false,
  trailingComma: 'none',
  bracketSpacing: true,
  jsxSingleQuote: true,
  endOfLine: 'auto'
};

Example .eslintrc.js configuration:

module.exports = {
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: { jsx: true }
  },
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:prettier/recommended'
  ]
};

CSS Architecture – ITCSS + BEM + ACSS

The author advocates a layered CSS approach using ITCSS (Settings, Tools, Generic, Base, Objects, Components, Trumps) combined with BEM naming and atomic‑class concepts (ACSS) to avoid style leakage and duplication.

Directory example for styles:

styles/
├── acss
├── generic
├── theme
├── tools
└── transition

JWT Authentication

JWT is introduced as a cross‑domain authentication solution. The token contains Header, Payload, and Signature; the payload should not store sensitive data.

Backend example (Koa + koa‑jwt):

const router = require('koa-router')();
const jwt = require('jsonwebtoken');
router.post('/login', async (ctx) => {
  const { userName, userPwd } = ctx.request.body;
  const res = await User.findOne({ userName, userPwd });
  const token = jwt.sign({ data: res._doc }, 'secret', { expiresIn: '1h' });
  ctx.body = { ...res._doc, token };
});

const app = new Koa();
app.use(require('koa-jwt')({ secret: 'secret' }));
app.use(async (ctx, next) => {
  try { await next(); } catch (err) {
    if (err.status === 401) ctx.body.msg = 'token authentication failed';
  }
});

Frontend interceptor (Axios) adds the token to request headers:

service.interceptors.request.use(request => {
  const token = Cookies.get('token');
  if (token) request.headers['Authorization'] = token;
  return request;
}, error => { Message.error(error); });

Menu Design and Dynamic Routing

Menus are defined in menu.json with fields such as title , name , type (MODULE, MENU, BUTTON), path , icon , component , hidden , noCache , etc. The JSON can be converted to SQL for database seeding using a Node script.

// createMenu.js (simplified)
const fs = require('fs');
const path = require('path');
const md5 = require('md5');
const INPUT_PATH = path.join(__dirname, 'src/router/menu.json');
const OUTPUT_PATH = path.join(__dirname, 'menu.sql');
function createSQL(data, name = '', pid, arr = []) {
  data.forEach(v => {
    if (v.children && v.children.length) createSQL(v.children, name + '-' + v.name, v.id, arr);
    arr.push({
      id: v.id || md5(v.name),
      name: v.name,
      title: v.title,
      code: (name + '-' + v.name).slice(1),
      parent_id: pid,
      type: v.type,
      component: v.component,
      path: v.path,
      hidden: !!v.hidden,
      no_cache: !!v.noCache
    });
  });
  return arr;
}
fs.readFile(INPUT_PATH, 'utf-8', (err, data) => {
  if (err) return console.error(err);
  const menuList = createSQL(JSON.parse(data));
  // generate INSERT statements …
});

Dynamic import for Vue‑CLI (webpack) uses import() , while Vite 2+ can use import.meta.glob to load components based on the path.

// dynamicImport.ts (Vite)
export default function dynamicImport(component: string) {
  const modules = import.meta.glob('../../views/**/*.{vue,tsx}');
  const match = Object.keys(modules).find(k => k.endsWith(`${component}.vue`) || k.endsWith(`${component}.tsx`));
  return match ? modules[match] : null;
}

Route generation function (TypeScript) creates RouteRecordRaw objects, handling hidden routes, full‑screen, and caching flags.

export default function generateRoutes(routes: IResource[], cname = '', level = 1): RouteRecordRaw[] {
  return routes.reduce((prev, { type, path, component, name, title, icon, redirect, hidden, fullscreen, noCache, children = [] }) => {
    if (type === 'MENU') {
      const route = {
        path,
        component: component ? dynamicImport(component) : () => import('@/layouts/dashboard'),
        name: (cname + '-' + name).slice(1),
        meta: { title, icon, hidden: !!hidden, fullscreen: !!fullscreen, noCache: !!noCache },
        children: children.length ? generateRoutes(children, cname + '-' + name, level + 1) : []
      };
      prev.push(route);
    }
    return prev;
  }, []);
}

Permission Control (RBAC)

Roles are linked to menus; each button is a BUTTON type under a MENU . A custom Vue directive v-permission removes elements whose permission name is not present in the store.

Vue.directive('permission', {
  mounted(el, binding, vnode) {
    const { context: vm } = vnode;
    const name = vm.$options.name + '-' + binding.value;
    const { permissionBtns } = store.state;
    if (!permissionBtns.includes(name)) el.parentNode.removeChild(el);
  }
});

Page Caching Strategy

Vue’s keep-alive is used with the include prop, which expects the component’s name . The author aligns route names with component names (derived from the menu hierarchy) so caching decisions can be driven by the noCache flag stored in the menu.

Component Encapsulation and Template Generation

Components should be reusable and testable (Jest + Storybook). For repetitive CRUD pages, the author uses plop with Handlebars templates to generate API modules and page scaffolds automatically.

// api.hbs (template example)
import request from '../request';
{{#if create}}
export const create{{properCase name}} = (data) => request.post('{{camelCase name}}/', data);
{{/if}}
{{#if update}}
export const update{{properCase name}} = (id, data) => request.put(`{{camelCase name}}/${id}`, data);
{{/if}}
// … other CRUD methods based on selected types …

Running plop prompts the developer to select a target directory, input the API name, and choose which CRUD methods to generate, producing ready‑to‑use TypeScript files.

Project Repository

The complete source code is available at the levi-vue-admin repository.

Vuefrontend architectureJWTDynamic RoutingRBACCSS ArchitectureAdmin System
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.