Vue3.4+Element-plus+Vite通用后台管理系统

🔥 前言

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

😀 项目介绍

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

✨ 效果展示

在线预览

xjy_admin

仓库地址Gitee

xjy_dmin

首页

image.png

🏅 技术栈

技术栈描述官网
Vue3 渐进式JavaScript框架 cn.vuejs.org/
Element-plus 基于 Vue 3,面向设计师和开发者的组件库 element-plus.org/zh-CN/
Vite 前端构建工具 vitejs.cn/vite3-cn/
Pinia 符合直觉的 Vue.js 状态管理库 pinia.web3doc.top/
Echarts 一个基于 JavaScript 的开源可视化图表库 echarts.apache.org/zh/index.ht…
VueUse 基于Vue组合式API的实用工具集 www.vueusejs.com/
animate.css 一个现成的跨浏览器动画库 animate.style/
wangEditor 开源 Web 富文本编辑器,开箱即用,配置简单 www.wangeditor.com/

🌈 项目基本配置

项目全局配置

image.png

代码统一规范

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

.eslintrc.cjs

 
js
代码解读
复制代码


// @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

 
js
代码解读
复制代码
{
  "singleQuote": false,
  "semi": false,
  "bracketSpacing": true,
  "htmlWhitespaceSensitivity": "ignore",
  "endOfLine": "auto",
  "trailingComma": "all",
  "tabWidth": 2
}

.stylelintrc.cjs

 
js
代码解读
复制代码
// @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中配置

 
js
代码解读
复制代码
/* 只需要重写你需要的即可 */
@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 配置

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

🔑 项目集成

mock.js

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

参考:mock.js 官网

安装

 
js
代码解读
复制代码
pnpm install -D vite-plugin-mock mockjs

vite.config.ts 配置

 
js
代码解读
复制代码
import { viteMockServe } from 'vite-plugin-mock'

 plugins: [
      viteMockServe({
        mockPath: "./src/mock",
        // localEnabled: true,
      })
 ]

UnoCSS

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

参考:UnoCSS中文文档

安装

 
js
代码解读
复制代码
pnpm install -D unocss

vite.config.ts 配置

 
js
代码解读
复制代码
复制代码
// vite.config.ts
import UnoCSS from 'unocss/vite'

export default {
  plugins: [
    UnoCSS({ /* options */ }),
  ],
}

main.js中

 
js
代码解读
复制代码
// main.js
import "virtual:uno.css"

uno.config.js中可自行配置

 
js
代码解读
复制代码
// uno.config.ts
import {
  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安装提示插件

image.png

插件效果预览

image.png

canvas 基本使用

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

Snipaste_2024-06-26_10-21-57.png

功能代码附上

 
js
代码解读
复制代码
<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使用

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

image.png

svg图标存放位置为 assets/svgs

WangEditor编辑器集成

官网

 
js
代码解读
复制代码
// 父组件中
<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>
 
js
代码解读
复制代码
<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" // 引入 css
import { 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()
// 编辑器实例,必须用 shallowRef
const 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>

👻 项目运行

项目启动

 
js
代码解读
复制代码
# 安装 
pnpm npm install pnpm -g 

# 安装依赖
pnpm install 

# 项目运行
pnpm run dev

项目语法检查

 
js
代码解读
复制代码
#语法检查
pnpm run lint

#语法修复
pnpm run fix

#css美化
pnpm run format

项目打包

 
js
代码解读
复制代码
pnpm run build

✍️ 写在最后

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

posted @   码农小哥。  阅读(354)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示