1. 双向绑定数据的修改问题
<el-form>
中 <el-input>
双向绑定数据后,无法再修改编辑的问题。Vue实例创建时,form的属性名并未声明,因此Vue就无法对属性执行 getter/setter 转化过程,导致form属性不是响应式的,因此无法触发视图更新。显式地声明form这个对象的属性即可解决
data () {
return {
formInline: {
id: '',
name: '',
pwd: '',
}
}
}
而不是
data () {
return {
formInline:{}
}
}
2. Element UI的上传功能
<el-upload
class="avatar-uploader"
action="http://localhost:8888/upload/picture"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
name = "photo"
>
<!--这个name的值对应后端接收的参数名-->
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
仅需关心如下参数
action:后端接收图片的地址
name:photo,该值对应后端接收图片方法中的参数名称
在Java中 public Result picture(MultipartFile photo){
....
}
3. Element UI的分页
修改<el-table>
中的 :data
:data="tableData.slice((pages.currentPage-1)*pages.size,pages.currentPage*pages.size)"里面的tableData是接到的所有数据
完整代码(仅分页功能)
<template>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[5, 10, 15, 20]"
:page-size=pages.size
layout="total, sizes, prev, pager, next"
:total=pages.total>
</el-pagination>
</template>
<script>
import { show } from "@/api/student.js";
export default {
data() {
return {
pages:{
total: 0,
size: 5,
currentPage: 1,
},
}
},
methods: {
loadData(){
show().then((response)=>{
this.tableData = response.data.data;
this.pages.total = response.data.data.length
})
},
handleSizeChange(val) {
this.pages.size = val;
},
handleCurrentChange(val) {
this.pages.currentPage = val;
}
}
}
</script>
4. 引入高德地图
https://lbs.amap.com/api/javascript-api/guide/abc/prepare
<template>
<div id="container"></div>
</template>
<script>
import AMapLoader from '@amap/amap-jsapi-loader'
import {createMap} from "@/api/node.js";
window._AMapSecurityConfig = {
// 申请时分配的
securityJsCode: 'ceb74446c9ecaabe6020720a5bf9a26d'
}
export default {
data() {
return {
map: null,
mapData : []
}
},
methods: {
loadData() {
createMap().then((response)=>{
this.mapData = response.data.data;
})
},
initMap() {
AMapLoader.load({
key: '5625def45684788ad4cd785a05538cdf', // 申请好的Web端开发者Key,首次调用 load 时必填
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: [''] // 需要使用的的插件列表,如比例尺'AMap.Scale'等
})
.then(AMap => {
this.map = new AMap.Map('container', {
//设置地图容器id
viewMode: '3D', //是否为3D地图模式
zoom: 10, //初始化地图级别
center: [121.473667, 31.230525] //初始化地图中心点位置
})
// 批量添加圆点标记 绿色:#22dc19 、红色:#c80539
const circleMarkers = this.mapData
circleMarkers.forEach(marker => {
marker.radius = 20
const circleMarker = new AMap.CircleMarker(
{
map: this.map,
center: new AMap.LngLat(marker.center[0], marker.center[1]), radius: marker.radius, fillColor: marker.fillColor })
circleMarker.setMap(this.map)
})
})
.catch(e => { console.log(e) })
},
},
created() {
this.loadData()
},
mounted() { //DOM初始化完成进行地图初始化
this.initMap()
}
}
</script>
<style lang="less" scoped>
#container {
padding: 0px;
margin: 0px;
width: 100vmax;
height: 100vmax;
}
</style>
5. 基于element UI组件实现分组表格(合并单元格)
<template>
<div>
<el-main class="contailer">
<el-table :data="tableData" :span-method="spanMethod">
<el-table-column prop="classId" label="班级ID"></el-table-column>
<el-table-column prop="className" label="班级名"></el-table-column>
<el-table-column prop="stuId" label="学号"></el-table-column>
<el-table-column prop="stuName" label="姓名"></el-table-column>
<el-table-column label="操作">
<template slot-scope="{row}">
<span @click="sendMsg(row.classId)">群发短信</span>
</template></el-table-column>
</el-table>
</el-main>
</div>
</template>
<script>
export default {
data() {
return {
tableData:[ //班级学生信息mock数据,该数据假设已经按着班级信息进行排序
{classId:'001',className:'一班',stuId:1001,stuName:'zhangsan001'},
{classId:'001',className:'一班',stuId:1002,stuName:'zhangsan001'},
{classId:'001',className:'一班',stuId:1003,stuName:'zhangsan001'},
{classId:'002',className:'二班',stuId:1004,stuName:'zhangsan002'},
{classId:'002',className:'二班',stuId:1005,stuName:'zhangsan002'},
{classId:'003',className:'三班',stuId:1006,stuName:'zhangsan003'},
{classId:'003',className:'三班',stuId:1007,stuName:'zhangsan003'},
{classId:'003',className:'三班',stuId:1008,stuName:'zhangsan003'},
{classId:'004',className:'四班',stuId:1009,stuName:'zhangsan004'},
]
};
},
computed:{
groupNum(){ //获取班级列表数组
return new Set(this.tableData.map(o => o.className));
}
},
methods:{
classGroup(name){ //根据班级名称获取学生数量
return this.tableData.filter(o => o.className == name).length;
},
classNameLen(name){ //根据班级名称获取该班级第一个学生在全量学生中的偏移位置
const tmp = Array.from(this.groupNum);
const index = tmp.indexOf(name); //某班级在全班级中的偏移位置
let len = 0;
for (let i = 0;i < index;i++){
len += this.classGroup(tmp[i]);
}
return len;
},
sendMsg(classId){ //do something },
spanMethod(data) { //对于表格数据进行分组合并操作,UI组件回调函数
const {row,column,rowIndex,columnIndex} = data;
if (columnIndex < 2 || columnIndex > 3) { //班级合并展示区
const len = this.classGroup(row.className);
const lenName = this.classNameLen(row.className);
if (rowIndex === lenName) { //某班级首位学生行
return {
rowspan:len,
colspan:1
}
} else return { //某班级非首位学生行
rowspan: 0,
colspan: 0
};
} else { //学生信息展示区
return {
rowspan: 1,
colspan: 1
};
}
}
}
};
</script>
6. 引入Tinymce富文本
http://tinymce.ax-z.cn/
安装
npm install tinymce@5.1.0 --save npm install @tinymce/tinymce-vue@3.0.1
在node_modules
下找到tinymce
下的skins
和themes
文件夹复制粘贴到public/tinymce
下
封装一个tinymce
富文本组件
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
如果上面引入tinymce/themes/silver/theme
报错可以尝试引入下面这两个
import 'tinymce/themes/modern/theme';
import "tinymce/icons/default";
完整富文本组件代码
<template>
<div class="tinymce-editor">
<Editor v-model="myValue" :init="init" :disabled="disabled" ref="editorRef"
:key="timestamp" @onClick="onClick">
</Editor>
</div>
</template>
<script>
import tinymce from 'tinymce/tinymce'
import Editor from '@tinymce/tinymce-vue'
import { uploadFiles } from '@/api/common.js'
import 'tinymce/themes/silver/theme'
// 列表插件
import 'tinymce/plugins/lists'
import 'tinymce/plugins/advlist'
// 上传图片插件
import 'tinymce/plugins/image'
import 'tinymce/plugins/imagetools'
// 表格插件
import 'tinymce/plugins/table'
// 自动识别链接插件
import 'tinymce/plugins/autolink'
// 预览插件
import 'tinymce/plugins/preview'
export default {
name: 'TinymceEditor',
components: {
Editor
},
props: {
// 传入一个value,使组件支持v-model绑定
value: {
type: String,
default: ''
},
disabled: {
type: Boolean,
default: false
},
menubar: { // 菜单栏
type: String,
default: 'file edit insert view format table'
},
// 相关插件配置
plugins: {
type: [String, Array],
default:
'lists advlist image imagetools table autolink preview'
},
// 工具栏配置
toolbar: {
type: [String, Array],
default:
'undo redo | formatselect | fontsizeselect | bold italic underline forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists image table preview'
},
// 富文本高度
height: {
type: Number,
default: 500
}
},
data () {
return {
// 当前时间戳,是为了解决回显问题
timestamp: 0,
//初始化配置
init: {
language_url: '/tinymce/langs/zh_CN.js', // 根据自己文件的位置,填写正确的路径,注意/可以直接访问到public文件
language: 'zh_CN',
skin_url: '/tinymce/skins/ui/oxide', // 根据自己文件的位置,填写正确的路径。路径不对会报错
height: this.height,
plugins: this.plugins,
toolbar: this.toolbar,
branding: false,
menubar: false,
// 如需ajax上传可参考https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_handler
// 配置了此方法,即可手动选择图片
images_upload_handler: (blobInfo, success, failure) => {
const formData = new FormData()
formData.append('files', blobInfo.blob());
uploadFiles(formData)
.then(res => {
success(res.url)
})
.catch(err => {
failure(err)
})
},
resize: false,
},
myValue: this.value
}
},
methods: {
// 需要什么事件可以自己增加 可用的事件参照文档=> https://github.com/tinymce/tinymce-vue
onClick (e) {
this.$emit('onClick', e, tinymce)
},
// 可以添加一些自己的自定义事件,如清空内容
clear () {
this.myValue = ''
},
// 解决切换页签后数据回显问题
setTinymceContent () {
setTimeout(() => {
this.timestamp = new Date().getTime()
}, 10)
}
},
watch: {
value: {
handler (newValue) {
this.myValue = newValue
},
immediate: true
},
myValue (newValue) {
this.$emit('input', newValue)
}
}
}
</script>
使用组件
<TinymceEditor v-model="form.noticeContent" ref="tinymceEditorRef"></TinymceEditor>
7. 引入ECharts
安装
npm install echarts --save
封装一个ECharts组件
<template>
<!-- :id表示从父组件中动态获取,chartId表示父组件中调用时应是chartId=""的形式 -->
<div :id="chartId" :data="chartData" style="height: 420px;width:320px;" />
</template>
<script>
import * as echarts from "echarts";
export default {
// 对应父组件中传递的数据的名称
props: {
chartId: {
type: String,
required: true
},
chartData: {
type: Array,
required: true
}
},
// 封装的组件的名称
name: "EChartsPie",
mounted() {
this.getList();
},
methods: {
getList() {
this.chart = echarts.init(this.$el, "macarons");
this.chart.setOption({
// 移动鼠标展示
tooltip: {
trigger: 'item'
},
// 指示颜色的含义
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
type: 'pie',
radius: '50%',
data: this.chartData
}
]
});
},
// 打开加载层
openLoading() {
this.$modal.loading("正在加载缓存监控数据,请稍候!");
},
goTarget(href) {
window.open(href, "_blank");
}
}
};
</script>
调用封装的ECharts子组件
<template>
<!-- 首页可视化大屏 -->
<div class="floor">
<!-- 楼栋单元可视化 -->
<el-row class="buildings" v-if="showFlag.buildings" v-for="building in buildings" :key="building.buildingId">
<el-col :span="24" class="buildingContainer" >
<div class="building-name" style="font-family: 'Helvetica Neue', sans-serif; font-size: 18px; color: #333; padding-left: 10px">
<div class="building">
<font-awesome-icon icon="building" size="2xl" style="color: #133c72;" @click="showRooms(building)"/>
</div>
<div class="total-num" style="font-family: 'Helvetica Neue', sans-serif; font-size: 14px; color: #333;">
可用:<br>
已用:
</div>
<!-- 可视化组件 -->
<EChartsPie :chartId="'pie' + building.buildingId" :chartData="pieBuilding(building)" style="width:320px;"/>
</div>
</el-col>
</el-row>
<!-- 宿舍人员信息可视化 -->
<div class="button" style="position: absolute;">
<font-awesome-icon icon="arrow-left" style="color: #1f3051;" size="xl" @click="showBuildings" v-if="showFlag.rooms" />
</div>
<el-row class="rooms" v-if="showFlag.rooms" v-for="room in rooms" :key="room.roomId" style="margin-top: 34px;">
<el-col :span="6" class="roomContainer">
<div class="room-name" style="font-family: 'Helvetica Neue', sans-serif; font-size: 18px; color: #333; padding-left: 10px">
<div class="room">
<font-awesome-icon
v-for="i in room.roomOccupied"
:key="i"
icon="user"
size="lg"
style="color: #1c4792"
/>
<font-awesome-icon
v-for="i in room.roomAvailable"
:key="i"
icon="user-plus"
size="lg"
style="color: #079711"
/>
</div>
<div class="bed-num" style="font-family: 'Helvetica Neue', sans-serif; font-size: 14px; color: #333;">
可用:<br>
已用:
</div>
<!-- 可视化组件 -->
<EChartsPie :chartId="'pie' + room.roomId" :chartData="pieRoom(room)" style="width:350px"/>
</div>
</el-col>
</el-row>
</div>
</template>
<script>
import {listRoomInfo,listBuildingInfo} from '@/api/visual'
import EChartsPie from './EChartsPie.vue'
export default {
components: {
EChartsPie
},
data() {
return {
// 控制显示顺序
showFlag: {
rooms: false,
buildings: true
},
rooms: [],
buildings: []
}
},
created() {
this.getListBuilding();
},
methods: {
showRooms(building){
this.showFlag.rooms = true;
this.showFlag.buildings = false;
listRoomInfo(building.buildingId).then(response => {
this.rooms = response.data;
});
},
showBuildings(){
this.showFlag.rooms = false;
this.showFlag.buildings = true;
},
getListBuilding(){
listBuildingInfo().then(response => {
this.buildings = response.data;
});
},
//可视化数据
pieBuilding(building){
return [
{value: building.totalOccupied, name:"已用"},
{value: building.totalAvailable, name:"可用"}
]
},
//可视化数据
pieRoom(room){
return [
{value: room.roomOccupied, name:"已用"},
{value: room.roomAvailable, name:"可用"}
]
}
}
}
</script>
<style>
.floor,.back {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
width: 98%;
margin: 10px 1%;
}
/* 控制宿舍人员的样式 */
.rooms {
width: 45%;
margin-bottom: 10px;
/** 设置边框阴影*/
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
/* 设置边角圆润 */
border-radius: 20px;
}
/* 控制楼栋单元的样式 */
.buildings {
width: 25%;
margin-bottom: 20px;
/** 设置边框阴影*/
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
/* 设置边角圆润 */
border-radius: 20px;
}
.buildingContainer {
}
.roomContainer {
}
.button {
}
</style>
8. 引入图形库-fontawesome
安装
main.js
全局引入
// 图形库
/* import the fontawesome core */
import { library } from '@fortawesome/fontawesome-svg-core'
/* import font awesome icon component */
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
/* import specific icons */
import { faBed,faPerson,faUser,faUserPlus,faBuilding,faArrowLeft } from '@fortawesome/free-solid-svg-icons'
/* add icons to the library */
library.add(faBed,faPerson,faUser,faUserPlus,faBuilding,faArrowLeft)
/* add font awesome icon component */
Vue.component('font-awesome-icon', FontAwesomeIcon)
9. Element布局组件 el-row el-col
分栏布局
将网页划分成若干行,然后每行等分为若干列,称为栅栏布局
element可将每行划分为24个分栏
每行使用<el-row>
标签标识,然后每行内的列使用<el-col>
标识
行内每列占整行的宽度比例,则使用:span
属性进行设置
分栏间隔
为不同分栏之间设定一定的间隔,可以使用<el-row>
标签的:gutter
属性,默认间隔为0,要在分栏里面有新增元素,才能实现分栏间隔
分栏偏移
想让某个分栏不从左边显示,而是直接显示到中间或者右侧,可以借助offset
属性来实现,表示偏移量
对齐方式
想让整个行居左、居中、居右对齐,则可以直接设置<el-row>
的对齐方式。
此时需要先设置type="flex"
来启用对齐方式,然后通过justify
属性来设置具体的对齐方式
响应式布局
element的响应式布局,通过为列设置不同分辨率下的占用宽度比例来实现的
实现在电脑和手机上都能自适应正常显示
10. 浮动和定位
10.1. 浮动
标准文档流。所谓标准文档流就是文档的排列方式。浏览器会将块级元素从上到下排列,行内元素从左到右排列
浮动属性值:
float: left;
浮动的元素会脱离标准文档流;元素浮动后,就不会占据文档流中的位置,那么在他后面的元素就会顶上来
清除浮动 clear:both
同时清除前面的左右浮动
10.2. 定位
固定定位
position: fixed;
相对于浏览器窗口是固定位置。在不设置偏移量的时候是相对与body是固定位置。
设置固定定位后元素完全脱离标准文档流
相对定位
position: relative;
相对定位也可以通过偏移量移动位置。相对定位是相对与他本身在标准文档流中的位置进行定位
绝对定位
position: absolute;
绝对定位是相对于最近的有定位属性的父级进行定位。 如果所有的祖先元素都没有定位属性,就相对于浏览器的窗口进行定位
绝对定位也会完全脱离文档流。在设置相对定位时,一般给父元素设置相对定位,再子元素设置绝对定位。设置偏移量后子元素就相对与父元素偏移,而元素设置相对定位没有完全脱离文档流,这样也就不会影响父元素