开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
三、Vue3 + Element-plus 搭建后台管理系统之组件实现
3.1 ECharts可视化
本章节展示ECharts可视化的相关内容,重点实现组件封装和数据动态变更。
1.展示
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.展示
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>