Vue后台管理系统开发指南

发表时间: 2024-08-17 10:43

网上的后台管理系统模版一大堆,删起来麻烦,xjy_admin是我自己搭建的通用后台管理模板,并不断完善中......

前言

经过一段时间的打磨(具体忘了),迎来管理系统的第一个版本, 相较于其他后台管理模板,本后台管理模版只包含基本的刷新,全屏以及暗黑模式效果。本模板只供学习使用,难免会存在一些bug,还请见谅。

项目介绍

一个基于Vue3.4+Element-plus+Vite搭建的轻量级后台管理模板,本项目分一个登录接口和一个用户信息接口,并且采用mockjs模拟数据,有简单的权限划分。

首页

技术栈

技术栈

描述

官网

Vue3

渐进式JavaScript框架

https://cn.vuejs.org/

Element-plus

基于 Vue 3,面向设计师和开发者的组件库

https://element-plus.org/zh-CN/

Vite

前端构建工具

https://vitejs.cn/vite3-cn/

Pinia

符合直觉的 Vue.js 状态管理库

https://pinia.web3doc.top/

Echarts

一个基于 JavaScript 的开源可视化图表库

https://echarts.apache.org/zh/index.html

VueUse

基于Vue组合式API的实用工具集

https://www.vueusejs.com/

animate.css

一个现成的跨浏览器动画库

https://animate.style/

wangEditor

开源 Web 富文本编辑器,开箱即用,配置简单

https://www.wangeditor.com/

项目基本配置

项目全局配置

代码统一规范

  • Eslint:语法规则和代码风格检查
  • Prettier:美化代码样式
  • Stylelint: CSS 统一规范和代码检测

.eslintrc.cjs

// @see https://eslint.bootcss.com/docs/rules/module.exports = {    env: {        browser: true,        es2021: true,        node: true,        jest: true,    },    /* 指定如何解析语法 */    parser: 'vue-eslint-parser',    /** 优先级低于 parse 的语法解析配置 */    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'],    /*     * "off" 或 0    ==>  关闭规则     * "warn" 或 1   ==>  打开的规则作为警告(不影响代码执行)     * "error" 或 2  ==>  规则作为一个错误(代码不能执行,界面报错)     */    rules: {        // eslint(https://eslint.bootcss.com/docs/rules/)        'no-var': 'error', // 要求使用 let 或 const 而不是 var        '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 (https://typescript-eslint.io/rules)        '@typescript-eslint/no-unused-vars': 'off', // 禁止定义未使用的变量        '@typescript-eslint/prefer-ts-expect-error': 'off', // 禁止使用 @ts-ignore        '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型        '@typescript-eslint/no-non-null-assertion': 'off',        '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。        '@typescript-eslint/semi': 'off',        // eslint-plugin-vue (https://eslint.vuejs.org/rules/)        'vue/multi-word-component-names': 'off', // 要求组件名称始终为 “-” 链接的单词        'vue/script-setup-uses-vars': 'error', // 防止<script setup>使用的变量<template>被标记为未使用        'vue/no-mutating-props': 'off', // 不允许组件 prop的改变        'vue/attribute-hyphenation': 'off', // 对模板中的自定义组件强制执行属性命名样式    },}

.prettierrc.json

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

.stylelintrc.cjs

// @see https://stylelint.bootcss.com/// 美化css书写的样式module.exports = {    extends: [      'stylelint-config-standard', // 配置stylelint拓展插件      'stylelint-config-html/vue', // 配置 vue 中 template 样式格式化      'stylelint-config-standard-scss', // 配置stylelint scss插件      'stylelint-config-recommended-vue/scss', // 配置 vue 中 scss 样式格式化      'stylelint-config-recess-order', // 配置stylelint css属性书写顺序插件,      'stylelint-config-prettier', // 配置stylelint和prettier兼容    ],    overrides: [      {        files: ['**/*.(scss|css|vue|html)'],        customSyntax: 'postcss-scss',      },      {        files: ['**/*.(html|vue)'],        customSyntax: 'postcss-html',      },    ],    ignoreFiles: [      '**/*.js',      '**/*.jsx',      '**/*.tsx',      '**/*.ts',      '**/*.json',      '**/*.md',      '**/*.yaml',    ],    /**     * null  => 关闭该规则     * always => 必须     */    rules: {      'value-keyword-case': null, // 在 css 中使用 v-bind,不报错      'no-descending-specificity': null, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器      'function-url-quotes': 'always', // 要求或禁止 URL 的引号 "always(必须加上引号)"|"never(没有引号)"      'no-empty-source': null, // 关闭禁止空源码      'selector-class-pattern': null, // 关闭强制选择器类名的格式      'property-no-unknown': null, // 禁止未知的属性(true 为不允许)      'block-opening-brace-space-before': 'always', //大括号之前必须有一个空格或不能有空白符      'value-no-vendor-prefix': null, // 关闭 属性值前缀 --webkit-box      'property-no-vendor-prefix': null, // 关闭 属性前缀 -webkit-mask      'selector-pseudo-class-no-unknown': [        // 不允许未知的选择器        true,        {          ignorePseudoClasses: ['global', 'v-deep', 'deep'], // 忽略属性,修改element默认样式的时候能使用到        },      ],    },  }

按钮主题色

style/element/index.scss中配置

/* 只需要重写你需要的即可 */@forward 'element-plus/theme-chalk/src/common/var.scss' with ($colors: (    'primary': (// 主色      'base':#0F5197,    ),    'success': ( // 成功色      'base': #199D33,    ),    'info': ('base': #4e4f51,    ),    'warning': ( // 警告色      'base': #e26f03,    ),    'danger': ( // 危险色      'base': #de2e06,    ),    'error': ( // 危险色      'base': #de2e06,    ),  ));

vite.config.ts 配置

 css: {      preprocessorOptions: {        scss: {          javascriptEnabled: true,          // 自动导入定制化样式文件进行样式覆盖          additionalData: `            @use "@/styles/element/index.scss" as *;           @use "@/config/public.scss" as *;          `,        },      },    },

项目集成

mock.js

mock.js 生成随机数据,拦截 Ajax 请求

参考:mock.js 官网

安装

pnpm install -D vite-plugin-mock mockjs

vite.config.ts 配置

import { viteMockServe } from 'vite-plugin-mock' plugins: [      viteMockServe({        mockPath: "./src/mock",        // localEnabled: true,      }) ]

UnoCSS

UnoCSS 是即时原子 CSS 引擎,通俗易懂的讲,就是在template模版中书写css

参考:UnoCSS中文文档

安装

pnpm install -D unocss

vite.config.ts 配置

复制代码// vite.config.tsimport UnoCSS from 'unocss/vite'export default {  plugins: [    UnoCSS({ /* options */ }),  ],}

main.js中

// main.jsimport "virtual:uno.css"

uno.config.js中可自行配置

// uno.config.tsimport {  defineConfig,  presetAttributify,  presetIcons,  presetTypography,  presetUno,  presetWebFonts,  transformerDirectives,  transformerVariantGroup,} from "unocss"export default defineConfig({  shortcuts: {    "flex-center": "flex justify-center items-center",    "flex-x-center": "flex justify-center",    "flex-y-center": "flex items-center",    "wh-full": "w-full h-full",    "flex-x-between": "flex items-center justify-between",    "flex-x-end": "flex items-center justify-end",    "absolute-lt": "absolute left-0 top-0",    "absolute-rt": "absolute right-0 top-0 ",    "fixed-lt": "fixed left-0 top-0",    "b1-red": "b-1 border-solid b-red",  },  theme: {    colors: {      primary: "var(--el-color-primary)",      primary_dark: "var(--el-color-primary-light-5)",    },  },  presets: [    presetUno(),    presetAttributify(),    presetIcons(),    presetTypography(),    presetWebFonts({      fonts: {        // ...      },    }),  ],  transformers: [transformerDirectives(), transformerVariantGroup()],})

VsCode安装提示插件

插件效果预览

canvas 基本使用

这里通过一个案例对canvas展示了一个基本使用,仅供参考,以业务为主。

功能代码附上

<script setup lang="ts">import debounce from "lodash/debounce"import { message } from "@/Hooks/Element-plus"import { nextTick, onMounted, ref, watch } from "vue"import cat1 from "../../assets/image/cat1.png"import cat2 from "../../assets/image/cat2.png"import dog1 from "../../assets/image/dog1.png"import dog2 from "../../assets/image/dog2.png"import canvaas_bg from "../../assets/image/canvas.webp"const picList = [dog1, cat2, dog2, cat1] as string[]const colorList = [  "#fbd04f",  "#a8a1dc",  "#83b6da",  "#92c7ba",  "#f6a356 ",  "#5e5f61",  "#f98787 ",] as string[]// canvas实例const canvas = ref()// 画布const ctx = ref()// 初始化背景图片const initCanvas_bg = async () => {  await new Promise((resolve, reject) => {    // 初始化画布    ctx.value = canvas.value.getContext("2d")!    const width = 700    const height = 800    canvas.value.width = width    canvas.value.height = height    const img = new Image()    img.onload = () => {      ctx.value.drawImage(img, 0, 0, canvas.value.width, canvas.value.height) // 绘制第一张图片      resolve("ok")    }    img.src = canvaas_bg    img.onerror = () => {      reject("失败")    }  })}onMounted(async () => {  try {    // 初始化背景canvas    await initCanvas_bg()  } catch (err: any) {    message("error", "初始化背景失败")  }})// 选择图片const pic_val = ref("")const imgIndex = ref<number>()const selectImg = async (pic: string, i: number) => {  pic_val.value = pic  imgIndex.value = i}// 选择背景色const color_val = ref("")const colorIndex = ref<number>()const selectBg = async (color: string, i: number) => {  color_val.value = color  colorIndex.value = i}// 输入值改变const input_val = ref("")watch(  [() => pic_val.value, () => color_val.value, () => input_val.value],  debounce(async (newVal) => {    try {      // 初始化背景canvas      await initCanvas_bg()      let [newPic_val, newColor_val, newInut_val] = newVal      // 先看背景颜色有没有      if (newColor_val) {        // 设置填充色        ctx.value.fillStyle = newColor_val        // 绘制正方形        ctx.value.fillRect(99, 75, 342, 477)        // 再看文本        if (newInut_val) {          ctx.value.fillStyle = "black"          ctx.value.font = `35px 隶书`          ctx.value.fillText("名:" + newInut_val, 190, 180)        }        // 先铺背景在画图片        if (newPic_val) {          const img = new Image()          img.src = newPic_val          img.onload = () => {            ctx.value.drawImage(img, 100, 202, 350, 350) // 绘制选择的图片          }        }      } else {        // 再看文本        if (newInut_val) {          ctx.value.fillStyle = "black"          ctx.value.font = `35px 隶书`          ctx.value.fillText("名:" + newInut_val, 190, 180)        }        // 先铺背景在画图片        if (newPic_val) {          const img = new Image()          img.src = newPic_val          img.onload = () => {            ctx.value.drawImage(img, 100, 202, 350, 350) // 绘制选择的图片          }        }      }    } catch (err: any) {      message("error", "初始化背景失败")    }  }, 100),)// 导出图片const outPic = () => {  let base64 = canvas.value.toDataURL()  let link = document.createElement("a")  link.textContent = "download image"  link.href = base64  link.download = "mypainting.jpeg"  link.click()}</script>

功能封装

Svg使用

// <template><SvgIcon icon="" width="15px" height="15px" color="#fff" />    
//vite中自行调整svg图标的储存位置   createSvgIconsPlugin({        iconDirs: [path.resolve(process.cwd(), "src/assets/svgs")],        symbolId: "icon-[dir]-[name]",    }),

image.png

svg图标存放位置为 assets/svgs

WangEditor编辑器集成

官网

// 父组件中<template>  <WangEditor v-model="editVal" height="calc(100vh - 180px)" /></template><script setup lang="ts">import { ref, watchEffect } from "vue"//初始值const editVal = ref("Hello! WangEditor")watchEffect(() => {  console.log(editVal.value)})</script>
<template>  <div class="wang_edit" :class="{ dark: useSetting.dark }">    <Toolbar      id="toolbar-container"      :editor="editorRef"      :defaultConfig="toolbarConfig"      mode="default"    />    <Editor      id="editor-container"      v-model="valueHtml"      :defaultConfig="editorConfig"      mode="default"      @onCreated="handleCreated"    />  </div></template><script setup lang="ts">import { useSettingStore } from "@/stores/modules/setting"import "@wangeditor/editor/dist/css/style.css" // 引入 cssimport { onBeforeUnmount, ref, shallowRef, onMounted } from "vue"import { Editor, Toolbar } from "@wangeditor/editor-for-vue"const useSetting = useSettingStore()const props = withDefaults(  defineProps<{    height: string  }>(),  {    height: "560px",  },)const valueHtml = defineModel()// 编辑器实例,必须用 shallowRefconst editorRef = shallowRef()const toolbarConfig = {}const editorConfig = {  placeholder: "请输入内容...",  MENU_CONF: {    uploadImage: {      // 自定义图片上传      async customUpload(file: any, insertFn: any) {        console.log(file, insertFn)        // 调用后端接口,上传图片        // 拿到上传路劲插入编辑器        // insertFn(url)      },    },  },}// 组件销毁时,也及时销毁编辑器onBeforeUnmount(() => {  const editor = editorRef.value  if (editor == null) return  editor.destroy()})const handleCreated = (editor: any) => {  editorRef.value = editor // 记录 editor 实例,重要!}</script><style scoped lang="scss">.wang_edit {  border: 1px var(--el-border-color-dark) solid;  border-radius: 5px;  &.dark {    --w-e-textarea-bg-color: #333;    --w-e-toolbar-bg-color: #333;    --w-e-toolbar-color: gray;    --w-e-textarea-color: #fff;  }  #toolbar-container {    border-bottom: 1px solid var(--el-border-color-dark);    border-top-right-radius: 5px;    border-top-left-radius: 5px;  }  #editor-container {    height: v-bind("props.height") !important;    overflow: hidden;    border-bottom-right-radius: 5px;    border-bottom-left-radius: 5px;  }}</style>

项目运行

项目启动

# 安装 pnpm npm install pnpm -g # 安装依赖pnpm install # 项目运行pnpm run dev

项目语法检查

#语法检查pnpm run lint#语法修复pnpm run fix#css美化pnpm run format

项目打包

pnpm run build

✍️ 写在最后

本篇从项目规范,项目运行,项目打包,详细讲述基于Vue3.4+Element-plus技术栈0到1搭建简洁,易懂的前端后台管理框架。如有问题欢迎指出。

sjxj_世界学习室一路风雨并肩行sjxj只为兄弟情,忠肝义胆为心无愧一生只为sjxj