Vue

Element开发总结

Posted by Wh0ami-hy on February 3, 2023

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下的skinsthemes文件夹复制粘贴到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

Font Awesome

安装


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;绝对定位是相对于最近的有定位属性的父级进行定位。 如果所有的祖先元素都没有定位属性,就相对于浏览器的窗口进行定位

绝对定位也会完全脱离文档流。在设置相对定位时,一般给父元素设置相对定位,再子元素设置绝对定位。设置偏移量后子元素就相对与父元素偏移,而元素设置相对定位没有完全脱离文档流,这样也就不会影响父元素


本站总访问量