组件封装
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>
效果预览
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>
效果预览
图标选择器
组件封装
</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>
效果预览
规范配置
代码统一规范
【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
解决方案
根据 Eslint 官方解决方案描述,解析器使用 vue-eslint-parser v9.0.0 + 版本
安装 vue-eslint-parser 解析器
npm install -D vue-eslint-parser
1
.eslintrc.js 关键配置( v9.0.0 及以上版本无需配置编译宏 vue/setup-compiler-macros)如下 :
parser: 'vue-eslint-parser',
extends: [
'eslint:recommended',
// ...
],
重启 VSCode 已无报错提示
2: Vite 首屏加载慢(白屏久)
问题描述
Vite 项目启动很快,但首次打开界面加载慢?
参考文章:为什么有人说 vite 快,有人却说 vite 慢
vite 启动时,并不像 webpack 那样做一个全量的打包构建,所以启动速度非常快。启动以后,浏览器发起请求时, Dev Server 要把请求需要的资源发送给浏览器,中间需要经历预构建、对请求文件做路径解析、加载源文件、对源文件做转换,然后才能把内容返回给浏览器,这个时间耗时蛮久的,导致白屏时间较长。
解决方案升级 vite 4.3 版本
https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md
结语
本篇从项目介绍、环境准备、VSCode 的代码规范配置 、整合各种框架 、再到最后的启动部署,完整讲述如何基于 Vue3 + Vite4 + TypeScript + Element Plus 等主流技术栈从 0 到 1构建一个企业应用级管理前端框架。
项目有问题建议 issue 或者可以通过项目 关于我们 加入交流群反馈。