Vue 3.3 + Vite 4.3 + TypeScript 5+ Element-Plus:从零到一构建企业级后台管理系统(前后端开源)(四)

简介: Vue 3.3 + Vite 4.3 + TypeScript 5+ Element-Plus:从零到一构建企业级后台管理系统(前后端开源)(四)

组件封装

wangEditor 富文本

参考: wangEditor 官方文档


安装 wangEditor


npm install @wangeditor/editor @wangeditor/editor-for-vue@next

1

wangEditor 组件封装

</code></div><div><code>import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';</code></div><div><code>import { Editor, Toolbar } from '@wangeditor/editor-for-vue';</code></div><div><code>// API 引用</code></div><div><code>import { uploadFileApi } from '@/api/file';</code></div><div><code>const props = defineProps({</code></div><div><code>  modelValue: {</code></div><div><code>    type: [String],</code></div><div><code>    default: ''</code></div><div><code>  }</code></div><div><code>});</code></div><div><code>const emit = defineEmits(['update:modelValue']);</code></div><div><code>// 编辑器实例,必须用 shallowRef</code></div><div><code>const editorRef = shallowRef();</code></div><div><code>const state = reactive({</code></div><div><code>  toolbarConfig: {},</code></div><div><code>  editorConfig: {</code></div><div><code>    placeholder: '请输入内容...',</code></div><div><code>    MENU_CONF: {</code></div><div><code>      uploadImage: {</code></div><div><code>        // 自定义图片上传</code></div><div><code>        async customUpload(file: any, insertFn: any) {</code></div><div><code>          uploadFileApi(file).then(response => {</code></div><div><code>            const url = response.data.url;</code></div><div><code>            insertFn(url);</code></div><div><code>          });</code></div><div><code>        }</code></div><div><code>      }</code></div><div><code>    }</code></div><div><code>  },</code></div><div><code>  defaultHtml: props.modelValue,</code></div><div><code>  mode: 'default'</code></div><div><code>});</code></div><div><code>const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state);</code></div><div><code>const handleCreated = (editor: any) => {</code></div><div><code>  editorRef.value = editor; // 记录 editor 实例,重要!</code></div><div><code>};</code></div><div><code>function handleChange(editor: any) {</code></div><div><code>  emit('update:modelValue', editor.getHtml());</code></div><div><code>}</code></div><div><code>// 组件销毁时,也及时销毁编辑器</code></div><div><code>onBeforeUnmount(() => {</code></div><div><code>  const editor = editorRef.value;</code></div><div><code>  if (editor == null) return;</code></div><div><code>  editor.destroy();</code></div><div><code>});</code></div><div><code>


使用案例

</code></div><div><code>import Editor from '@/components/WangEditor/index.vue';</code></div><div><code>const value = ref('初始内容');</code></div><div><code>



效果预览

微信图片_20230706152812.png



Echarts 图表

参考:📊 Echarts 官方示例


安装 Echarts


npm install echarts

1

组件封装

 

</code></div><div><code>import * as echarts from 'echarts';</code></div><div><code>const props = defineProps({</code></div><div><code>  id: {</code></div><div><code>    type: String,</code></div><div><code>    default: 'barChart'</code></div><div><code>  },</code></div><div><code>  className: {</code></div><div><code>    type: String,</code></div><div><code>    default: ''</code></div><div><code>  },</code></div><div><code>  width: {</code></div><div><code>    type: String,</code></div><div><code>    default: '200px',</code></div><div><code>    required: true</code></div><div><code>  },</code></div><div><code>  height: {</code></div><div><code>    type: String,</code></div><div><code>    default: '200px',</code></div><div><code>    required: true</code></div><div><code>  }</code></div><div><code>});</code></div><div><code>const options = {</code></div><div><code>  grid: {</code></div><div><code>    left: '2%',</code></div><div><code>    right: '2%',</code></div><div><code>    bottom: '10%',</code></div><div><code>    containLabel: true</code></div><div><code>  },</code></div><div><code>  tooltip: {</code></div><div><code>    trigger: 'axis',</code></div><div><code>    axisPointer: {</code></div><div><code>      type: 'cross',</code></div><div><code>      crossStyle: {</code></div><div><code>        color: '#999'</code></div><div><code>      }</code></div><div><code>    }</code></div><div><code>  },</code></div><div><code>  legend: {</code></div><div><code>    x: 'center',</code></div><div><code>    y: 'bottom',</code></div><div><code>    data: ['收入', '毛利润', '收入增长率', '利润增长率'],</code></div><div><code>    textStyle: {</code></div><div><code>      color: '#999'</code></div><div><code>    }</code></div><div><code>  },</code></div><div><code>  xAxis: [</code></div><div><code>    {</code></div><div><code>      type: 'category',</code></div><div><code>      data: ['浙江', '北京', '上海', '广东', '深圳'],</code></div><div><code>      axisPointer: {</code></div><div><code>        type: 'shadow'</code></div><div><code>      }</code></div><div><code>    }</code></div><div><code>  ],</code></div><div><code>  yAxis: [</code></div><div><code>    {</code></div><div><code>      type: 'value',</code></div><div><code>      min: 0,</code></div><div><code>      max: 10000,</code></div><div><code>      interval: 2000,</code></div><div><code>      axisLabel: {</code></div><div><code>        formatter: '{value} '</code></div><div><code>      }</code></div><div><code>    },</code></div><div><code>    {</code></div><div><code>      type: 'value',</code></div><div><code>      min: 0,</code></div><div><code>      max: 100,</code></div><div><code>      interval: 20,</code></div><div><code>      axisLabel: {</code></div><div><code>        formatter: '{value}%'</code></div><div><code>      }</code></div><div><code>    }</code></div><div><code>  ],</code></div><div><code>  series: [</code></div><div><code>    {</code></div><div><code>      name: '收入',</code></div><div><code>      type: 'bar',</code></div><div><code>      data: [7000, 7100, 7200, 7300, 7400],</code></div><div><code>      barWidth: 20,</code></div><div><code>      itemStyle: {</code></div><div><code>        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [</code></div><div><code>          { offset: 0, color: '#83bff6' },</code></div><div><code>          { offset: 0.5, color: '#188df0' },</code></div><div><code>          { offset: 1, color: '#188df0' }</code></div><div><code>        ])</code></div><div><code>      }</code></div><div><code>    },</code></div><div><code>    {</code></div><div><code>      name: '毛利润',</code></div><div><code>      type: 'bar',</code></div><div><code>      data: [8000, 8200, 8400, 8600, 8800],</code></div><div><code>      barWidth: 20,</code></div><div><code>      itemStyle: {</code></div><div><code>        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [</code></div><div><code>          { offset: 0, color: '#25d73c' },</code></div><div><code>          { offset: 0.5, color: '#1bc23d' },</code></div><div><code>          { offset: 1, color: '#179e61' }</code></div><div><code>        ])</code></div><div><code>      }</code></div><div><code>    },</code></div><div><code>    {</code></div><div><code>      name: '收入增长率',</code></div><div><code>      type: 'line',</code></div><div><code>      yAxisIndex: 1,</code></div><div><code>      data: [60, 65, 70, 75, 80],</code></div><div><code>      itemStyle: {</code></div><div><code>        color: '#67C23A'</code></div><div><code>      }</code></div><div><code>    },</code></div><div><code>    {</code></div><div><code>      name: '利润增长率',</code></div><div><code>      type: 'line',</code></div><div><code>      yAxisIndex: 1,</code></div><div><code>      data: [70, 75, 80, 85, 90],</code></div><div><code>      itemStyle: {</code></div><div><code>        color: '#409EFF'</code></div><div><code>      }</code></div><div><code>    }</code></div><div><code>  ]</code></div><div><code>};</code></div><div><code>onMounted(() => {</code></div><div><code>  // 图表初始化</code></div><div><code>  const chart = echarts.init(</code></div><div><code>    document.getElementById(props.id) as HTMLDivElement</code></div><div><code>  );</code></div><div><code>  chart.setOption(options);</code></div><div><code>  // 大小自适应</code></div><div><code>  window.addEventListener('resize', () => {</code></div><div><code>    chart.resize();</code></div><div><code>  });</code></div><div><code>});</code></div><div><code>

组件使用

</code></div><div><code>import BarChart from './components/BarChart.vue';</code></div><div><code>


效果预览


微信图片_20230706152858.png


图标选择器

组件封装


</code></div><div><code>const props = defineProps({</code></div><div><code>  modelValue: {</code></div><div><code>    type: String,</code></div><div><code>    require: false</code></div><div><code>  }</code></div><div><code>});</code></div><div><code>const emit = defineEmits(['update:modelValue']);</code></div><div><code>const inputValue = toRef(props, 'modelValue');</code></div><div><code>const visible = ref(false); // 弹窗显示状态</code></div><div><code>const iconNames: string[] = []; // 所有的图标名称集合</code></div><div><code>const filterValue = ref(''); // 筛选的值</code></div><div><code>const filterIconNames = ref<string[]>([]); // 过滤后的图标名称集合</code></div><div><code>const iconSelectorRef = ref(null);</code></div><div><code>/**</code></div><div><code> * 加载 ICON</code></div><div><code> */</code></div><div><code>function loadIcons() {</code></div><div><code>  const icons = import.meta.glob('../../assets/icons/*.svg');</code></div><div><code>  for (const icon in icons) {</code></div><div><code>    const iconName = icon.split('assets/icons/')[1].split('.svg')[0];</code></div><div><code>    iconNames.push(iconName);</code></div><div><code>  }</code></div><div><code>  filterIconNames.value = iconNames;</code></div><div><code>}</code></div><div><code>/**</code></div><div><code> * 筛选图标</code></div><div><code> */</code></div><div><code>function handleFilter() {</code></div><div><code>  if (filterValue.value) {</code></div><div><code>    filterIconNames.value = iconNames.filter(iconName =></code></div><div><code>      iconName.includes(filterValue.value)</code></div><div><code>    );</code></div><div><code>  } else {</code></div><div><code>    filterIconNames.value = iconNames;</code></div><div><code>  }</code></div><div><code>}</code></div><div><code>/**</code></div><div><code> * 选择图标</code></div><div><code> */</code></div><div><code>function handleSelect(iconName: string) {</code></div><div><code>  emit('update:modelValue', iconName);</code></div><div><code>  visible.value = false;</code></div><div><code>}</code></div><div><code>/**</code></div><div><code> * 点击容器外的区域关闭弹窗 VueUse onClickOutside</code></div><div><code> */</code></div><div><code>onClickOutside(iconSelectorRef, () => (visible.value = false));</code></div><div><code>onMounted(() => {</code></div><div><code>  loadIcons();</code></div><div><code>});</code></div><div><code>





组件使用

</code></div><div><code>const iconName = ref('edit');</code></div><div><code>



效果预览

微信图片_20230706152918.gif



规范配置

代码统一规范

【vue3-element-admin】ESLint+Prettier+Stylelint+EditorConfig 约束和统一前端代码规范


Git 提交规范

【vue3-element-admin】Husky + Lint-staged + Commitlint + Commitizen + cz-git 配置 Git 提交规范


启动部署

项目启动

# 安装 pnpm

npm install pnpm -g

/

# 安装依赖

pnpm install


# 项目运行

pnpm run dev


项目部署

# 项目打包

pnpm run build:prod

1

2

生成的静态文件在工程根目录 dist 文件夹


FAQ

1: defineProps is not defined

问题描述


‘defineProps’ is not defined.eslint no-undef


微信图片_20230706152934.png


解决方案


根据 Eslint 官方解决方案描述,解析器使用 vue-eslint-parser v9.0.0 + 版本


微信图片_20230706152937.png


安装 vue-eslint-parser 解析器


npm install -D vue-eslint-parser

1

微信图片_20230706152942.png


.eslintrc.js 关键配置( v9.0.0 及以上版本无需配置编译宏 vue/setup-compiler-macros)如下 :


 parser: 'vue-eslint-parser',

 extends: [

   'eslint:recommended',

// ...  

 ],


重启 VSCode 已无报错提示


微信图片_20230706153023.png


2: Vite 首屏加载慢(白屏久)

问题描述


Vite 项目启动很快,但首次打开界面加载慢?


参考文章:为什么有人说 vite 快,有人却说 vite 慢


vite 启动时,并不像 webpack 那样做一个全量的打包构建,所以启动速度非常快。启动以后,浏览器发起请求时, Dev Server 要把请求需要的资源发送给浏览器,中间需要经历预构建、对请求文件做路径解析、加载源文件、对源文件做转换,然后才能把内容返回给浏览器,这个时间耗时蛮久的,导致白屏时间较长。


解决方案升级 vite 4.3 版本

https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md

微信图片_20230706153034.png


结语

本篇从项目介绍、环境准备、VSCode 的代码规范配置 、整合各种框架 、再到最后的启动部署,完整讲述如何基于 Vue3 + Vite4 + TypeScript + Element Plus 等主流技术栈从 0 到 1构建一个企业应用级管理前端框架。


项目有问题建议 issue 或者可以通过项目 关于我们 加入交流群反馈。

相关文章
|
4天前
|
JavaScript API
如何使用Vue 3和Type Script进行组件化设计
【8月更文挑战第16天】如何使用Vue 3和Type Script进行组件化设计
12 3
|
4天前
|
JavaScript API
如何使用Vue 3和Type Script进行组件化设计
【8月更文挑战第16天】如何使用Vue 3和Type Script进行组件化设计
10 1
|
17天前
|
资源调度 JavaScript 前端开发
Vue3+TypeScript前端项目新纪元:揭秘高效事件总线Mitt,轻松驾驭组件间通信的艺术!
【8月更文挑战第3天】Vue3结合TypeScript强化了类型安全与组件化开发。面对大型应用中复杂的组件通信挑战,可通过引入轻量级事件发射器Mitt实现事件总线模式。Mitt易于集成,通过简单几步即可完成安装与配置:安装Mitt、创建事件总线实例、并在组件中使用`emit`与`on`方法发送及监听事件。此外,利用TypeScript的强大类型系统确保事件处理器正确无误。这种方式有助于保持代码整洁、解耦组件,同时提高应用的可维护性和扩展性。不过,在大规模项目中需谨慎使用,以防事件流过于复杂难以管理。
33 1
|
11天前
|
JavaScript 测试技术 API
Vue 3 与 TypeScript:最佳实践详解
Vue 3 与 TypeScript:最佳实践详解
|
1月前
|
前端开发 JavaScript 安全
TypeScript在React Hooks中的应用:提升React开发的类型安全与可维护性
【7月更文挑战第17天】TypeScript在React Hooks中的应用极大地提升了React应用的类型安全性和可维护性。通过为状态、依赖项和自定义Hooks指定明确的类型,开发者可以编写更加健壮、易于理解和维护的代码。随着React和TypeScript的不断发展,结合两者的优势将成为构建现代Web应用的标准做法。
|
7天前
|
JavaScript
TypeScript——不能将类型“HTMLElement | null”分配给类型“HTMLElement”
TypeScript——不能将类型“HTMLElement | null”分配给类型“HTMLElement”
19 4
|
12天前
|
JavaScript 编译器
typescript 解决变量多类型访问属性报错--工作随记
typescript 解决变量多类型访问属性报错--工作随记
|
6天前
|
JavaScript
TypeScript——Record类型
TypeScript——Record类型
15 0
|
12天前
|
JavaScript 前端开发 编译器
Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧
Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧
|
1月前
|
JavaScript 开发者 索引
TypeScript接口与类型别名:深入解析与应用实践
【7月更文挑战第10天】TypeScript的接口和类型别名是定义类型的关键工具。接口描述对象结构,用于类、对象和函数参数的形状约束,支持可选、只读属性及继承。类型别名则为复杂类型提供新名称,便于重用和简化。接口适合面向对象场景,类型别名在类型重用和复杂类型简化时更有优势。选择时要考虑场景和灵活性。