三、Vue3 + Element-plus 搭建后台管理系统之组件实现

874 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

三、Vue3 + Element-plus 搭建后台管理系统之组件实现

3.1 ECharts可视化

本章节展示ECharts可视化的相关内容,重点实现组件封装和数据动态变更。

1.展示

echarts20221124_115253.gif

2.安装依赖包
npm install echarts
# OR
yarn add echarts
3.将echarts导入到项目中。

这里是使用app.config.globalProperties的方式将echarts挂载到项目中,通过getCurrentInstance的方式调用。当然,也可以在组件中直接调用。

# main.js

// 可视化图表 echarts
import * as echarts from 'echarts'

const app = createApp(App)
app.config.globalProperties.$echarts = echarts
...
app.mount('#app')
4.封装echarts通用组件。

内置刷新方法,可在外部调用。另外,这里有一个mixins(混入),是为了解决侧边栏收缩,Dom元素宽度的变化的时候,组件可以自适应当前的宽度。

# components/echarts/mixins/resize.js

export default {
  mounted() {
    // 监听dom宽度变化,刷新echarts
    let dom = document.getElementsByClassName("sideBar")[0];
    dom.addEventListener("transitionend", () => {
      this.chart.resize();
    });
  },
  methods: {
    resize() {
      const { chart } = this
      chart && chart.resize()
    }
  }
}
# components/echarts/EchartsComp.vue

<template>
  <div id="echartsComp" :style="{ height: height, width: width }"></div>
</template>

<script>
import { getCurrentInstance } from "vue";
import resize from "./mixins/resize";

export default {
  name: "EchartsComp",
  mixins: [resize],
  props: {
    options: {
      type: Object,
      required: true,
    },
    width: {
      type: String,
      default: "200px",
    },
    height: {
      type: String,
      default: "200px",
    },
  },
  mounted() {
    // 渲染图表
    const { proxy } = getCurrentInstance();
    this.chart = proxy.$echarts.init(document.getElementById("echartsComp"));
    this.chart.setOption(this.options);

    // 事件
    this.chart.on("click", function (params) {
      console.log(params);
    });
    // 分辨率调整刷新图表
    window.onresize = () => {
      this.chart.resize();
    };
  },
  methods: {
    Refresh() {
      console.log(this.options);
      this.chart.setOption(this.options);
    },
  },
};
</script>
5.实现一个echarts饼图组件,点击标题可以变更饼图数据。
# views/echarts/Bar.vue

<template>
  <div class="contBody">
    <h2 class="title" @click="handleTab">饼图(点击更换数据)</h2>
    <!-- echarts 饼图 -->
    <EchartsComp
      class="bar"
      ref="chart"
      :options="barOptions"
      width="100%"
      height="400px"
    />
  </div>
</template>

<script>
import EchartsComp from "@/components/echarts/EchartsComp.vue";
export default {
  name: "BmxxPage",
  data() {
    return {
      barOptions: {
        tooltip: {
          trigger: "item",
        },
        legend: {
          top: "5%",
          left: "center",
        },
        series: [
          {
            name: "Access From",
            type: "pie",
            radius: ["40%", "70%"],
            avoidLabelOverlap: false,
            itemStyle: {
              borderRadius: 10,
              borderColor: "#fff",
              borderWidth: 2,
            },
            label: {
              show: false,
              position: "center",
            },
            emphasis: {
              label: {
                show: true,
                fontSize: "40",
                fontWeight: "bold",
              },
            },
            labelLine: {
              show: false,
            },
            data: [
              { value: 1048, name: "Search Engine" },
              { value: 735, name: "Direct" },
              { value: 580, name: "Email" },
              { value: 484, name: "Union Ads" },
              { value: 300, name: "Video Ads" },
            ],
          },
        ],
      },
      barData: [
        ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
        [188, 255, 362, 455, 844, 863, 442],
      ],
    };
  },
  components: { EchartsComp },
  created() {
    this.init();
  },
  methods: {
    init() {
      // 数据处理
      this.barOptions.series[0].data = [];
      this.barData[0].forEach((ele, index) => {
        this.barOptions.series[0].data.push({
          name: ele,
          value: this.barData[1][index],
        });
      });
    },
    handleTab() {
      // 数据更新
      this.barData = [
        ["周一", "周二", "周三", "周四", "周五", "周六", "周日"],
        [
          this.getNum(8, 20),
          this.getNum(8, 20),
          this.getNum(8, 20),
          this.getNum(8, 20),
          this.getNum(8, 20),
          this.getNum(8, 20),
          this.getNum(8, 20),
        ],
      ];
      this.init();
      this.$refs.chart.Refresh();
    },
    // 生成随机数
    getNum(min, max) {
      const num = parseInt(Math.random() * (max - min + 1) + min);
      return Math.floor(num);
    },
  },
};
</script>

<style scoped>
.contBody {
  width: 100%;
  box-sizing: border-box;
}

.bar {
  width: 100%;
  height: 400px;
}
</style>

3.2 WebGis地图(Openlayers)

项目中的Gis功能通过Openlayers实现,使用的是5.3.0版本。项目中实现的功能后续会出文章详细讲解,大家如有需要,可先参考项目源码。本章节只记录了实现的一小部分功能。

1.展示

openlayers20221124_115751.gif

2.安装
npm install ol@^5.3.0
# OR
yarn add ol@^5.3.0
3.加载地图,坐标系使用EPSG:4326,图层使用天地图矢量图层和天地图矢量图层注记层。
# views/webgis/MapControls.vue

<div id="map" class="map__x"></div>

<script>
import { Map, View } from "ol"; // 地图实例方法、视图方法
import TileLayer from "ol/layer/Tile.js"; // 瓦片渲染方法
import XYZ from "ol/source/XYZ.js";
import "ol/ol.css"; // 地图样式

export default {
  name: "MapPage",
  data() {
    return {
      map: null,
    };
  },
  mounted() {
    this.initMap();
  },
  methods: {
    initMap() {
      // 地图实例
      this.map = new Map({
        target: "map", // 对应页面里 id 为 map 的元素
        layers: [
          // 图层
          new TileLayer({
            title: "天地图矢量图层",
            source: new XYZ({
              url: "http://t0.tianditu.com/DataServer?T=vec_w&x={x}&y={y}&l={z}&tk=200d3f62135e4debe4f843220883e31d",
              // attributions: "天地图的属性描述",
              wrapX: false,
            }),
            preload: Infinity,
            zIndex: 1,
            visible: true,
          }),

          new TileLayer({
            title: "天地图矢量图层注记",
            source: new XYZ({
              url: "http://t0.tianditu.com/DataServer?T=cva_w&x={x}&y={y}&l={z}&tk=200d3f62135e4debe4f843220883e31d",
              // attributions: "天地图的属性描述",
              wrapX: false,
            }),
            preload: Infinity,
            zIndex: 1,
            visible: true,
          }),
        ],
        view: new View({
          // 地图视图
          projection: "EPSG:4326", // 坐标系,有EPSG:4326和EPSG:3857
          center: [100.864943, 24.041115], // 云南坐标
          minZoom: 5, // 地图缩放最小级别
          zoom: 6, // 地图缩放级别(打开页面时默认级别)
        })
      });
    }
  },
};
</script>
4.隐藏图层,这里隐藏天地图矢量图层注记层。
# views/webgis/MapControls.vue

<el-button type="primary" @click="layerHide">图层隐藏</el-button>

export default {
    ...
    methods:{
        ...
        layerHide() {
          const layers = this.map.getLayers();
          // 图层隐藏
          layers.item(1).setVisible(false);
        }
    }
}

5.实现地图平移功能。
# views/webgis/MapControls.vue

<el-button type="primary" @click="panto">平移</el-button>

export default {
    ...
    methods:{
        ...
        panto() {
          const view = this.map.getView();
          const tam = [116.403119, 39.918034];
          //平移地图到天安门
          view.setCenter(tam);
          view.setZoom(13);
        },
    }
}
6.实现广州和北京两地的飞行动画。
# views/webgis/MapControls.vue

<el-button type="primary" @click="flyHandle([113.15, 23.08])">
    飞到广州
</el-button>
<el-button type="primary" @click="flyHandle([116.403119, 39.918034])">
    飞到北京
</el-button>

export default {
    ...
    methods:{
        ...
       flyHandle(cityName) {
          const view = this.map.getView();
          const duration = 2000; //动画的持续时间(以毫秒为单位)
          const zoom = 6;
          let parts = 2;
          let called = false;
    
          //动画完成的回调函数
          function callback(complete) {
            --parts;
            if (called) {
              return;
            }
            if (parts === 0 || !complete) {
              called = true;
              // done(complete);
            }
          }
          //第一个动画
          view.animate(
            {
              center: cityName,
              duration: duration,
            },
            callback
          );
          //第二个动画
          view.animate(
            {
              zoom: zoom - 1,
              duration: duration / 2,
            },
            {
              zoom: zoom + 5,
              duration: duration / 2,
            },
            callback
          );
        },
    }
}

3.3 编辑器使用

此模块包含了富文本编辑器、MarkDown编辑器和代码编辑器,使用的都是很优秀的开源项目。本项目中只使用部分功能。

3.3.1 富文本编辑器

TinyMCE 是一款易用、且功能强大的所见即所得的富文本编辑器。跟其他富文本编辑器相比,有着丰富的插件,支持多种语言,能够满足日常的业务需求并且免费。

1.安装版本 4.0.5
npm install @tinymce/tinymce-vue
# OR
yarn @tinymce/tinymce-vue
2.封装组件并进行初始化配置,详情请查看 components/teditor/index.vue 文件。
3.使用
# views/editor/TEditor.vue

<template>
  <div class="contBody">
    <h2 class="title">Tinymce 富文本编辑器</h2>
    <TEditor v-model:value="val" />
  </div>
</template>

<script>
import TEditor from "@/components/teditor";
export default {
  name: "TEditorPage",
  components: {
    TEditor,
  },
  data() {
    return {
      val: "Welcome to Your Vue3 project",
    };
  },
};
</script>
<style scoped>
.contBody .title{
  padding: 0 0 24px;
}
</style>

3.3.2 MarkDown编辑器

v-md-editor 是基于 Vue 开发的 markdown 编辑器组件。

1.安装支持 vue3 的版本
npm i @kangc/v-md-editor@next -S
# OR
yarn add @kangc/v-md-editor@next
2.注册
# main.js

import { createApp } from 'vue';
import VueMarkdownEditor from '@kangc/v-md-editor';
import '@kangc/v-md-editor/lib/style/base-editor.css';
import vuepressTheme from '@kangc/v-md-editor/lib/theme/vuepress.js';
import '@kangc/v-md-editor/lib/theme/style/vuepress.css';

import Prism from 'prismjs';

VueMarkdownEditor.use(vuepressTheme, {
  Prism,
});

const app = createApp(App);
app.use(VueMarkdownEditor);
app.mount('#app')
3.使用
# views/editor/MarkDownEditor.vue
<template>
  <div class="contBody">
    <h2 class="title">MarkDown 编辑器</h2>
    <MDEditor :contentMarkDown="contentMarkDown" />
  </div>
</template>

<script>
import MDEditor from "@/components/markdown";
export default {
  name: "MDPage",
  components: {
    MDEditor,
  },
  data() {
    return {
      contentMarkDown: "",
    };
  },
};
</script>

<style scoped>
.contBody .title {
  padding: 0 0 24px;
}
</style>

3.3.3 代码编辑器

vue3-ace-editor 是一款包装了ACE的Vue3插件,周下载量几千次,我感觉是挺好用的一款插件。浅浅的研究了一下,没太深入。

1.安装
npm install vue3-ace-editor
# OR
yarn add vue3-ace-editor
2.封装成组件
# views/editor/JsonEditor.vue

<template>
  <v-ace-editor
    v-model:value="content"
    @init="editorInit"
    lang="json"
    :options="{
      enableBasicAutocompletion: true,
      enableSnippets: true,
      enableLiveAutocompletion: true,
      fontSize: 14,
      tabSize: 2,
      showPrintMargin: false,
      highlightActiveLine: true,
    }"
    theme="monokai"
    style="height: 600px; background: #000; color: #fff"
    @change="handleChange"
  />
</template>

<script>
import * as ace from "ace-builds";
ace.config.set("basePath", "/static/src-min-noconflict/");

import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/snippets/sql";
import "ace-builds/src-noconflict/mode-sql";
import "ace-builds/src-noconflict/theme-monokai";
import "ace-builds/src-noconflict/mode-html";
import "ace-builds/src-noconflict/mode-html_elixir";
import "ace-builds/src-noconflict/mode-html_ruby";
import "ace-builds/src-noconflict/mode-javascript";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/snippets/less";
import "ace-builds/src-noconflict/theme-chrome";
import "ace-builds/src-noconflict/ext-static_highlight";
import "ace-builds/src-noconflict/ext-beautify";
import "ace-builds/src-noconflict/mode-json";

import { VAceEditor } from "vue3-ace-editor";
export default {
  name: "JsonEditorComp",
  components: {
    VAceEditor,
  },
  data() {
    return {
      content: "",
    };
  },
  props: {
    contentCode: {
      type: String,
      default: "",
    },
  },
  methods: {
    editorInit() {
      this.content = this.contentCode;
    },
    // 监听内容修改,并传给父级
    handleChange() {
      this.$emit("updateFun", this.content);
    },
  },
};
</script>

<style>
/* 修改光标颜色 */
.ace_cursor {
  color: #fff !important;
}
</style>
3.使用
# views/editor/CodeEditor.vue

<template>
  <div class="contBody">
    <h2 class="title">代码编辑器</h2>
    <div class="btn">
      <el-button type="primary" @click="handleSaveCode">保存</el-button>
    </div>
    <aceEditor :contentCode="contentCode" @updateFun="updateFun" />
  </div>
</template>

<script>
import aceEditor from "@/components/vue3AceEditor";

export default {
  name: "CkxxPage",
  components: { aceEditor },
  data() {
    return {
      contentCode: '',
    };
  },
  methods: {
    updateFun(code) {
      this.contentCode = code;
    },
    // 保存
    handleSaveCode() {
      alert(this.contentCode);
    },
  },
};
</script>

<style scoped>
.contBody .title {
  padding: 0 0 24px;
}
.btn {
  text-align: left;
  padding: 0 0 10px;
}
</style>