智能巡检系统前端-管理端部分是采用目前非常流行的VUE+ElementUI实现,其中图形展示采用eCharts相关技术实现。智能巡检系统使用最新的前端技术栈,提供各类经过各种项目验证的实用的组件方便在业务开发时的调用。智能巡检系统的核心业务功能是班组管理、路线管理、日常巡检和上报记录,感兴趣或者有相关技术能力的公司或个人可以基于此项目开发出适合自己的衍生项目。
本文档的作用只是起到抛砖引玉的作用,供广大爱好者或者相关行业工作者学习或借鉴。
rcs-front ├── package.json #node.js项目的配置文件,管理各种第三方包 ├── public #存放静态文件的目录 | ├── element-theme #相关的elementUI主题 | ├── favicon.ico #网站标识的图标文件 | └── index.html #项目入口页面 ├── README.md #项目介绍 ├── src #存放项目源码的目录 | ├── App.vue #vue的根组件 | ├── assets #静态资源(会被打包) | ├── components #组件库 | ├── echartsTheme #echarts主题 | ├── element-ui #当前element-ui主题样式 | ├── i18n #多语言配置 | ├── icons #图标目录 | ├── main.js #应用程序的入口文件 | ├── mixins #存放混入的文件,方便类似功能复用 | ├── router #存放路由相关的文件 | ├── store #VUEX管理应用程序的状态 | ├── utils #公共的自定义的通用函数库 | └── views #业务相关功能的视图 ├── vue.config.js #vue-cli配置
├── main.vue #登录后主页面视图 ├── modules #业务模块 | ├── pointmgt #巡检点管理 | | ├── point-bmap.vue #巡检点获取位置页面 | | ├── routinecheckpoint.vue #巡检点管理主页面 | | ├── routinecheckpoint-add-or-update.vue #巡检点增加或修改 | | └── routinecheckpoint-view-info.vue #巡检点查看页面 | ├── recordmgt #巡检记录管理 | | ├── inspection-track-bmap.vue #查看行程轨迹页面 | | ├── routinecheckrecord.vue #巡检记录管理主页面 | | └── routinecheckrecord-view-info.vue #巡检记录查看页面 | ├── routingmgt #巡检路线管理 | | ├── location-bmap.vue #巡检路线获取位置页面 | | ├── routinecheckrouting.vue #巡检路线管理主页面 | | ├── routinecheckrouting-add-or-update.vue #巡检路线增加或修改 | | ├── routinecheckrouting-select-component.vue #选择巡检路线组件 | | ├── routinecheckrouting-view-info.vue #巡检路线查看页面 | | └── routing-bmap.vue #查看路线轨迹页面 | ├── teamsmgt #班组管理 | | ├── routinecheckteams.vue ##班组管理主页面 | | ├── routinecheckteams-add-or-update.vue ##班组增加或修改 | | ├── routinecheckteams-select-component.vue #选择班组页面 | | └── routinecheckteams-view-info.vue #班组查看页面 | └── sys #系统管理 | | ├── choose-dept-tree.vue #部门树 | | ├── dept-add-or-update.vue #部门增加或修改 | | ├── dept-set-leader-list.vue #设置部门负责人 | | ├── dept.vue #部门管理 | | ├── dict-data.vue #字典管理 | | ├── dict-type.vue #字典类型管理 | | ├── log-error.vue #错误日志 | | ├── log-login.vue #登录日志 | | ├── log-operation.vue #操作日志 | | ├── menu.vue #菜单管理 | | ├── params.vue #全局参数管理 | | ├── region.vue #区域管理 | | ├── role.vue #角色管理 | | └── sysuser.vue #用户管理 | └── home.vue #登录后工作区域内容视图 └── pages ├── 404.vue #404页面视图 ├── login.vue #登录页面视图 └── wxLogin.vue #微信登录页面视图
一个成熟的项目包含很多技术框架类的内容,例如组件库,自定义函数库,路由处理,多语言,UI样式等等。由于智能巡检系统是一个完整的项目,虽然业务功能点不多,但技术框架该有的基础功能已经基本涵盖(工作流相关实现没有包括进来),以下从两个个功能点代码进行讲解,起到抛砖引玉的作用,完整的代码学习还需要自行在项目中或者自行debug进行学习。
一般大部分的应用系统都有登录功能,为了安全性或者不同角色使用不同的功能使用。互联网上的应用很多使用了第三方的一键登录,例如微信扫码登录、关注公众号登录、支付宝账号登录、短信息验证登录等等。但企业内部的应用系统,基本还是采用用户名及密码登录,为了防止暴力破解,一般会加一个验证码进行验证。前端代码如下:
<template> <div class="aui-wrapper aui-page__login"> <div class="aui-content__wrapper"> <main class="aui-content"> <div class="login-header"> <h2 class="login-brand">{{ $t('brand.lg') }}</h2> </div> <div class="login-body"> <h3 class="login-title">{{ $t('login.title') }}</h3> <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmitHandle()" status-icon> <el-form-item prop="username"> <el-input v-model="dataForm.username" :placeholder="$t('login.username')"> <span slot="prefix" class="el-input__icon"> <svg class="icon-svg" aria-hidden="true"><use xlink:href="#icon-user"></use></svg> </span> </el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="dataForm.password" type="password" :placeholder="$t('login.password')"> <span slot="prefix" class="el-input__icon"> <svg class="icon-svg" aria-hidden="true"><use xlink:href="#icon-lock"></use></svg> </span> </el-input> </el-form-item> <el-form-item prop="captcha"> <el-row :gutter="20"> <el-col :span="14"> <el-input v-model="dataForm.captcha" :placeholder="$t('login.captcha')"> <span slot="prefix" class="el-input__icon"> <svg class="icon-svg" aria-hidden="true"><use xlink:href="#icon-safetycertificate"></use></svg> </span> </el-input> </el-col> <el-col :span="10" class="login-captcha"> <img :src="captchaPath" @click="getCaptcha()"> </el-col> </el-row> </el-form-item> <el-form-item> <el-button type="primary" @click="dataFormSubmitHandle()" class="w-percent-100">{{ $t('login.title') }}</el-button> </el-form-item> </el-form> </div> <div class="login-footer"> <p>{{ $t('login.copyright') }} 2014 - 2025 © </p> </div> </main> </div> </div> </template> <script> import Cookies from 'js-cookie' import debounce from 'lodash/debounce' import { messages } from '@/i18n' import { getUUID } from '@/utils' export default { data () { return { i18nMessages: messages, captchaPath: '', dataForm: { username: '', password: '', uuid: '', captcha: '' } } }, computed: { dataRule () { return { username: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], password: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], captcha: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ] } } }, created () { this.getCaptcha() }, mounted() { this.bgSwitch(); }, methods: { // 背景轮播 bgSwitch() { // 获取 class 为 aui-page__login 的标签 var logins = document.querySelector('.aui-content__wrappers'); let bgswitch = () => { // 设置一个随机1-3的正整数 let random = Math.floor(Math.random() * 3 + 1); logins.style.backgroundImage = 'url(' + require('@/assets/img/login_dark' + random + '.jpg') + ')'; }; // 设置一个定时器,每隔5秒钟切换一次背景图片 let timer = setInterval(bgswitch, 5000); // 监听页面可见性变化,并在页面隐藏时暂停动画,在页面显示时恢复动画 document.visibilitychange = () => { if (document.hidden) { // 停止定时器 clearInterval(timer); } else { // 重新调用定时器函数,实现自动轮播功能 timer = setInterval(bgswitch, 5000); } }; }, // 获取验证码 getCaptcha () { this.dataForm.uuid = getUUID() this.captchaPath = `${window.SITE_CONFIG['apiURL']}/captcha?uuid=${this.dataForm.uuid}` }, // 表单提交 dataFormSubmitHandle: debounce(function () { this.$refs['dataForm'].validate((valid) => { if (!valid) { return false } this.$http.post('/login', this.dataForm).then(({ data: res }) => { if (res.code !== 0) { this.getCaptcha() return this.$message.error(res.msg) } Cookies.set('token', res.data.token) this.$router.replace({ name: 'home' }) }).catch(() => {}) }) }, 1000, { 'leading': true, 'trailing': false }) } } </script>
页面效果如下:
以上代码包含了登录页面的布局和逻辑。在模板部分,使用了Element UI库的组件来构建登录表单。其中包括了用户名、密码和动态验证码的输入框,以及一个提交按钮。通过v-model指令将输入框的值与data中的dataForm对象进行双向绑定,使得输入框的值可以实时更新到dataForm对象中。
在脚本部分,首先引入了一些依赖,包括js-cookie库和lodash库的debounce函数。然后定义了组件的data属性,包括了多语言消息、验证码图片和表单数据。其中,dataForm对象包含了用户名、密码、uuid和验证码。
computed属性定义了表单的验证规则,主要是用户名、密码的必填规则。
在created生命周期中,调用了getCaptcha方法来获取验证码图片。
在mounted生命周期中,调用了bgSwitch方法实现背景轮播。
dataFormSubmitHandle方法是表单提交的处理函数,使用了debounce函数来防止抖动。this.http.post方法向服务器发送登录请求,根据服务器返回的结果进行处理,如果登录成功则将token保存到Cookies中,并跳转到首页,否则重新获取验证码并显示错误信息。
<template> <div class="aui-wrapper aui-page__login"> <div class="aui-content__wrappers"> <div class="logo"> <div class="logo-login-img"> <img src="~@/assets/img/logo-login.png" alt=""/> <div class="logo-login--txt">为全球的中小微企业信息化转型提供服务</div> <div class="logo-login--txt">使信息化价格平民化</div> <div class="logo-login--txt">让全球有需求的企业大胆的转型</div> <div class="logo-login--txt">尽早通过信息化转型实现业务新的起航!</div> </div> </div> </div> <div class="aui-content__wrapper" style="display: flex;justify-content: space-around;align-items:center;"> <div class="aui-contents"> <h2 class="login-brand">微信登录</h2> <div class="login-body"> <img src="~@/assets/img/login-blue.png" alt="" class="img-login"/> <h3 class="login-title">扫码登录</h3> <div class="login-QRcode"> <el-image :src="imgUrl" class="login-QRcode-img" v-if="!login_timeout" ></el-image> <img src="../../assets/img/timeouts.png" class="login-QRcode-img" v-if="login_timeout" @click="loginRefresh" /> </div> <div>{{ this.login_timeout_text }}</div> </div> </div> </div> <div class="login-footer"> <p>巡检系统 QIQIQI RCS 2014 - 2025 ©</p> </div> </div> </template> <script> import Cookies from 'js-cookie' import { messages } from '@/i18n' export default { data () { return { i18nMessages: messages, imgUrl: '', ticketId: '', timer: null, wxUser: null, login_timeout: false, login_timeout_text: '请使用微信扫描二维码登录', } }, created () { this.getWxQrcode() }, mounted () { this.logintimeout() }, methods: { getWxQrcode () { this.$http.get('https://wxcms.iqiqiqi.cn/wx/wxAuth/getLoginQrcode?type=qiqiqi_rcs').then(({ data: res }) => { if (res.code !== 200) { return this.$message.error(res.msg) } this.imgUrl = res.data.qrcodeUrl this.ticketId = res.data.ticketId this.getWxUser() }).catch(() => { }) }, getWxUser () { this.timer = setInterval(() => { this.$http.get('https://wxcms.iqiqiqi.cn/wx/wxAuth/getWxUser?ticket=' + this.ticketId).then(({ data: res }) => { if (res.code === 500) { return this.$message.error(res.msg) } if (res.code === 200) { clearInterval(this.timer) this.wxUser = JSON.parse(res.data) this.wxLogin() } }).catch(() => { clearInterval(this.timer) }) }, 1000) }, wxLogin () { this.$http.post('/wxLogin', this.wxUser).then(({ data: res }) => { if (res.code !== 0) { return this.$message.error(res.msg) } Cookies.set('token', res.data.token) this.$router.replace({ name: 'home' }) }).catch(() => { }) }, logintimeout () { setTimeout(() => { this.login_timeout = true this.login_timeout_text = '获取二维码超时,点击刷新' clearInterval(this.timer) }, 60000) }, loginRefresh () { this.login_timeout = false this.login_timeout_text = '请使用微信扫描二维码登录' this.getWxQrcode() this.logintimeout() }, } } </script>
页面效果如下:
以上代码包含了登录页面的布局和逻辑。在模板部分,使用了生成的二维码来扫码登录表单。其中二维码token的有效期为1分钟,二维码失效后,点击刷新重新生成二维码。
在脚本部分,首先引入了一些依赖,包括js-cookie库和多语言i18n的messages函数。然后定义了组件的data属性,包括了多语言消息、二维码图片、接口访问凭证、定时器、微信用户等表单数据。
在created生命周期中,调用了getWxQrcode方法,设置一个定时器timer来处重复获取二维码图片,并调用getWxUser方法获取微信登录用户信息。
在mounted生命周期中,调用了logintimeout方法实现一个60秒的定时器,并清空定时器timer。
getWxUser方法,是获取微信用户登录时的用户信息,需要传入参数ticketId。
loginRefresh方法,是二维码失效时, 重新调用getWxQrcode方法生成二维码,并重启定时器。
wxLogin方法是请求后台的微信登录接口,this.http.post方法向服务器发送登录请求,根据服务器返回的结果进行处理,如果登录成功则将token保存到Cookies中,并跳转到首页,否则显示错误信息。
维护、查询巡检路线时,首先需要新增保存巡检路线,其次输入名称、起止位置、状态等查询条件,然后点击查询返回对应的巡检路线数据。以下是巡检路线管理列表的前端代码:
<template> <el-card shadow="never" class="aui-card--fill"> <div class="mod-routingmgt__routinecheckrouting}"> <el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()"> <el-form-item> <el-input v-model="dataForm.routingName" placeholder="路线名称" clearable></el-input> </el-form-item> <el-form-item> <el-input v-model="dataForm.startPoint" placeholder="起始位置" clearable></el-input> </el-form-item> <el-form-item> <el-input v-model="dataForm.endPoint" placeholder="终止位置" clearable></el-input> </el-form-item> <el-form-item> <my-select v-model="dataForm.enableflag" dict-type="enableflag" placeholder="是否可用 " clearable></my-select> </el-form-item> <el-form-item> <el-button @click="getDataList()" icon="el-icon-search">{{ $t('query') }}</el-button> </el-form-item> <el-form-item> <el-button type="info" @click="exportExcelHandle()" icon="el-icon-download">{{ $t('export') }}</el-button> </el-form-item> <el-form-item> <el-button v-if="$hasPermission('routingmgt:routinecheckrouting:save')" type="primary" @click="addOrUpdateHandle()" icon="el-icon-plus">{{ $t('add') }}</el-button> </el-form-item> <el-form-item> <el-button v-if="$hasPermission('routingmgt:routinecheckrouting:delete')" type="danger" @click="deleteHandle()" icon="el-icon-delete">{{ $t('deleteBatch') }}</el-button> </el-form-item> </el-form> <el-table stripe @sort-change="dataListSortChangeHandle" v-loading="dataListLoading" :data="dataList" border @selection-change="dataListSelectionChangeHandle" style="width: 100%;"> <el-table-column type="selection" header-align="center" align="center" width="50"></el-table-column> <el-table-column prop="routingName" label="路线名称" sortable="custom" header-align="center" align="center" min-width="160" show-overflow-tooltip> <template slot-scope="scope"> <a @click="viewInfoHandle(scope.row.id)" style="cursor: pointer" title="查看详情">{{scope.row.routingName}}</a> </template> </el-table-column> <el-table-column prop="teamsName" label="分配班组" header-align="center" align="center" min-width="120" show-overflow-tooltip></el-table-column> <el-table-column prop="startPoint" label="起始位置" header-align="center" align="center" min-width="140" show-overflow-tooltip></el-table-column> <el-table-column prop="endPoint" label="终止位置" header-align="center" align="center" min-width="140" show-overflow-tooltip></el-table-column> <el-table-column prop="enableflag" label="是否可用 " sortable="custom" header-align="center" align="center" min-width="60" show-overflow-tooltip> <template slot-scope="scope"> <el-tag effect="dark" type="info"> {{ $getDictLabel("enableflag", scope.row.enableflag) }} </el-tag> </template> </el-table-column> <el-table-column prop="createDate" label="创建时间" sortable="custom" header-align="center" align="center" min-width="100" show-overflow-tooltip></el-table-column> <el-table-column :label="$t('handle')" fixed="right" header-align="center" align="center" width="200"> <template slot-scope="scope"> <el-button v-if="$hasPermission('routingmgt:routinecheckrouting:info')" type="text" size="small" @click="showRoutingHandle(scope.row.id)" icon="el-icon-view">查看路线</el-button> <el-button v-if="$hasPermission('routingmgt:routinecheckrouting:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.id)" icon="el-icon-edit">{{ $t('update') }}</el-button> <el-button v-if="$hasPermission('routingmgt:routinecheckrouting:delete')" type="text" size="small" @click="deleteHandle(scope.row.id)" icon="el-icon-delete">{{ $t('delete') }}</el-button> </template> </el-table-column> </el-table> <el-pagination :current-page="page" :page-sizes="[10, 20, 50, 100]" :page-size="limit" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="pageSizeChangeHandle" @current-change="pageCurrentChangeHandle"> </el-pagination> <!-- 弹窗, 新增 / 修改 --> <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update> <view-info v-if="viewInfoVisible" ref="viewInfo" @refreshDataList="getDataList"></view-info> <!-- 巡检路线地图 --> <routing-bmap v-if="routingTrackVisible" ref="routingBmap" @refreshDataList="getDataList"></routing-bmap> </div> </el-card> </template> <script> import mixinViewModule from '@/mixins/view-module' import AddOrUpdate from './routinecheckrouting-add-or-update' import ViewInfo from './routinecheckrouting-view-info' import RoutingBmap from './routing-bmap' import Cookies from 'js-cookie' export default { mixins: [mixinViewModule], data () { return { mixinViewModuleOptions: { getDataListURL: '/routingmgt/routinecheckrouting/page', getDataListIsPage: true, exportURL: '/routingmgt/routinecheckrouting/export', deleteURL: '/routingmgt/routinecheckrouting', deleteIsBatch: true }, dataForm: { routingName: '', startPoint: '', endPoint: '', enableflag: '' }, routineCheckPointList: [], //所属路线添加的巡检点集合 routingTrackVisible: false, //查看路线图 } }, components: { AddOrUpdate, ViewInfo, RoutingBmap }, methods: { //查看巡检路线 showRoutingHandle(id){ //根据路线id,获取所有添加的巡检点集合routineCheckPointList this.$http.get(`/routingmgt/routinecheckrouting/getRoutineCheckPointList/${id}`).then(({ data: res }) => { if (res.code !== 0) { return this.$message.error(res.msg) } this.routineCheckPointList = res.data if(this.routineCheckPointList.length < 1){ return this.$message({ message: "请设置路线的巡检点!", type: 'warning', duration: 2000 }) } //查看路线地图 this.routingTrackVisible = true this.$nextTick(() => { this.$refs.routingBmap.routineCheckRoutingId = id this.$refs.routingBmap.init() }) }).catch(() => {}) }, } } </script>
页面效果如下:
从以上代码可以看出,使用了Element UI库的常用组件,包括el-card、el-form、el-input、el-row、el-col、el-button、el-table、el-table-column和el-pagination等。其中,el-card是一个卡片组件,el-form是一个表单组件,el-input是一个输入框组件,el-row和el-col是用来布局的组件,el-button是一个按钮组件,el-table是一个表格组件,el-table-column是表格的列组件,el-pagination是一个分页组件。
<el-form>是一个表单组件,用于展示文件搜索的表单。@keyup.enter.native="getDataList()"表示在按下回车键时触发相关搜索的方法。
<el-row>和<el-col>是用于布局的组件,:span属性表示占据的列数,:gutter属性表示列之间的间隔。
<el-table>用于展示文件列表。:data属性绑定了dataList属性,表示表格的数据来源。
<el-table>中,使用了<el-table-column>组件定义了表格的列。prop属性表示列对应的数据字段,label属性表示列的标题。
methods定义了showRoutingHandle方法,用于查看巡检路线轨迹。首先,根据路线id,获取所有添加的巡检点集合routineCheckPointList;如果routineCheckPointList为空,提示请先添加路线的巡检点。如果routineCheckPointList不为空,打开routing-bmap.vue弹出页面,展示路线的轨迹。
点击新增按钮时,弹出新增文件的dialog,代码如下:
<template> <el-dialog :visible.sync="visible" :title="!dataForm.id ? $t('add') : $t('update')" :close-on-click-modal="false" :close-on-press-escape="false"> <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmitHandle()" :label-width="$i18n.locale === 'en-US' ? '120px' : '80px'"> <el-row> <el-col :span="10"> <el-form-item label="路线名称" prop="routingName"> <el-input v-model="dataForm.routingName" placeholder="路线名称" maxlength="100"></el-input> </el-form-item> </el-col> <el-col :span="10"> <el-form-item label="选择班组" prop="teamsName"> <routinecheckteams-select-component v-if="visible" v-model="dataForm.teamsName" ref="routinecheckteamsSelectComponent" :routineCheckTeamsName="dataForm.teamsName" :type.toLowerCase="'dialog'" @input="inputTeam" placeholder="选择指标模板"></routinecheckteams-select-component> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="10"> <el-form-item label="起始位置" prop="startPoint"> <el-input v-model="dataForm.startPoint" placeholder="起始位置" maxlength="100"></el-input> </el-form-item> </el-col> <el-col :span="10"> <el-form-item label="终止位置" prop="endPoint"> <el-input v-model="dataForm.endPoint" placeholder="终止位置" maxlength="100"></el-input> </el-form-item> </el-col> <el-col :span="4"> <div class="showLocationDiv"> <a href="#" @click="getPositionInfo()"><i class="el-icon-location"></i>获取位置</a> </div> </el-col> </el-row> <el-form-item label="描述信息" prop="remark"> <el-input v-model="dataForm.remark" type="textarea" placeholder="描述信息" maxlength="65535"></el-input> </el-form-item> <el-form-item label="是否可用 " prop="enableflag"> <my-radio-group v-model="dataForm.enableflag" dict-type="enableflag"></my-radio-group> </el-form-item> </el-form> <template slot="footer"> <el-button @click="visible = false" icon="el-icon-close">{{ $t('cancel') }}</el-button> <el-button type="primary" @click="dataFormSubmitHandle()" icon="el-icon-check">{{ $t('confirm') }}</el-button> </template> <!-- 获取地图位置 --> <location-map v-if="locationMapVisible" ref="locationMap" @clickMarkEvent="clickMarkEvent"></location-map> </el-dialog> </template> <script> import debounce from 'lodash/debounce' import LocationMap from './location-bmap' import RoutinecheckteamsSelectComponent from '@/views/modules/teamsmgt/routinecheckteams-select-component' export default { components: { LocationMap,RoutinecheckteamsSelectComponent }, data () { return { visible: false, locationMapVisible: '', //地图位置组件 dataForm: { id: '', routingName: '', startPoint: '', endPoint: '', remark: '', enableflag: 1, //默认可用 creator: '', createDate: '', updater: '', updateDate: '', teamsId: '', //班组ID teamsName: '', //班组名称 routingTeamsId: '', //线路班组ID } } }, computed: { dataRule () { return { routingName: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], startPoint: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], endPoint: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], enableflag: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], teamsName: [ { required: true, message: this.$t('validate.required'), trigger: 'blur' } ], } } }, methods: { init () { this.visible = true this.$nextTick(() => { this.$refs['dataForm'].resetFields() if (this.dataForm.id) { this.getInfo() } }) }, //选择班组组件 inputTeam(data){ //由于这里是单选,直接赋值 (如果是多选,需要以逗号分割,存到列表中) this.dataForm.teamsId = data.finalIds this.dataForm.teamsName = data.finalRoutineCheckTeamsName //清除必填校验 if(data.finalRoutineCheckTeamsName){ this.$refs["dataForm"].clearValidate("teamsName") }else{ this.$refs['dataForm'].validateField("teamsName") } }, //获取位置 getPositionInfo(){ this.locationMapVisible = true this.$nextTick(() => { // this.$refs.locationMap.longitude = this.dataForm.longitude // this.$refs.locationMap.latitude = this.dataForm.latitude this.$refs.locationMap.init() }) }, //标记位置事件 clickMarkEvent(positionInfo){ console.log(positionInfo) if(positionInfo.startPointAddress && positionInfo.endPointAddress){ this.dataForm.startPoint = positionInfo.startPointAddress this.dataForm.endPoint = positionInfo.endPointAddress //清除必填校验 this.$refs["dataForm"].clearValidate("startPoint") this.$refs["dataForm"].clearValidate("endPoint") } }, // 获取信息 getInfo () { this.$http.get(`/routingmgt/routinecheckrouting/${this.dataForm.id}`).then(({ data: res }) => { if (res.code !== 0) { return this.$message.error(res.msg) } this.dataForm = { ...this.dataForm, ...res.data } }).catch(() => {}) }, // 表单提交 dataFormSubmitHandle: debounce(function () { this.$refs['dataForm'].validate((valid) => { if (!valid) { return false } this.$http[!this.dataForm.id ? 'post' : 'put']('/routingmgt/routinecheckrouting/', this.dataForm).then(({ data: res }) => { if (res.code !== 0) { return this.$message.error(res.msg) } this.$message({ message: this.$t('prompt.success'), type: 'success', duration: 500, onClose: () => { this.visible = false this.$emit('refreshDataList') } }) }).catch(() => {}) }) }, 1000, { 'leading': true, 'trailing': false }) } } </script>
页面效果如下:
以上代码的主要实现了新增巡检路线功能,以下是部分代码解释:
<el-dialog>是一个对话框组件,用于展示文件上传的弹窗。:visible.sync="visible"表示通过visible属性控制对话框的显示和隐藏,:title属性根据dataForm.id的值来确定对话框的标题是"添加"还是"更新",:close-on-click-modal="false"和:close-on-press-escape="false"表示点击对话框外部和按下ESC键时不关闭对话框。
<routinecheckteams-select-component>是一个选择班组组件,用于选择路线巡检所指定的班组。v-model="dataForm.teamsName"是表示选择班组返回的结果,绑定到dataForm中的teamsName属性;而
:type.toLowerCase="'dialog'" 表示选择班组组件是以dialog方式打开页面;@input="inputTeam",表示选择班组组件选择成功的回调事件。
methods中的方法解释:
init方法用于初始化对话框,设置对话框的显示状态,并重置表单的字段。
inputTeam方法,选择班组成功的回调事件,在inputTeam事件中,可以处理字段检验等逻辑。
getPositionInfo方法,打开标记地图位置页面,用于标记路线的起始位置和终止位置。
clickMarkEvent方法,标记起止位置的回调方法,可用来绑定表单dataForm中的起始位置和终止位置,并且进行清楚校验格式的操作。
getInfo方法用于获取文件的相关信息(在修改时),发送HTTP请求并根据返回结果进行相应的操作。
dataFormSubmitHandle方法用于表单的提交,先进行表单的校验,然后发送HTTP请求进行数据的添加或更新。
在巡检路线列表页面,点击列表中的查看路线,将弹出对应的巡检路线图dialog,代码如下:
<template> <el-dialog :visible.sync="visible" title="巡检路线图" :close-on-click-modal="false" :close-on-press-escape="false" append-to-body :fullscreen="true" width="95%"> <el-form :model="dataForm" ref="dataForm"> <div class ="emptyDiv"></div> <!-- 地图组件 --> <div class="bmView" id="baiduMapView"> </div> </el-form> <template slot="footer"> <el-button @click="closeEvent" icon="el-icon-close">{{ $t('cancel') }}</el-button> </template> </el-dialog> </template> <script> import {baiduMap} from '@/utils/map.js'; import '@/utils/GeoUtil.js'; import stakeIconUrl from '@/assets/img/logo.png'; var bmap = null; export default { data () { return { ak: 'u3Vb70MniNmG2KVFL8f2LasZ77sD5bWM', visible: false, routineCheckRoutingId: '', //巡检路线id dataForm: { routingId: '', }, routineCheckPointList: [], //巡检点集合 zoom: 15, //缩放级别 searchKey: '', } }, methods: { //初始化 init () { var _that = this this.visible = true //路线id this.dataForm.routingId = this.routineCheckRoutingId this.$nextTick(() => { Promise.all([ _that.getRoutineCheckPointList() ]).then(() => { _that.initMap() }) }) }, //关闭弹窗 closeEvent(){ this.visible = false this.routineCheckPointList =[] bmap = null; }, /*============== 百度地图相关 ===================*/ //初始地图 initMap() { var that = this //**************初始化百度地图************************** baiduMap(this.ak).then((baiduMap) => { // 创建百度地图实例 bmap = new BMapGL.Map("baiduMapView"); console.log('获取当前定位并初始化百度地图') //获取当前定位 let geolocation = new BMapGL.Geolocation(); // 开启SDK辅助定位 geolocation.enableSDKLocation(); geolocation.getCurrentPosition(function(r) { console.log(r) if (this.getStatus() == BMAP_STATUS_SUCCESS) { let point = new BMapGL.Point(r.point.lng, r.point.lat); bmap.centerAndZoom(point, 16); //设置缩放级别 bmap.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放 let marker = new BMapGL.Marker(point); // 创建标注 bmap.addOverlay(marker); // 创建信息窗口 let opts = { width: 200, height: 100, }; let infoWindowStr = '经度:'+ r.point.lng +'\<br>' + '纬度:'+ r.point.lat ; // 给marker点标记添加点击事件 marker.addEventListener('click', function() { // 获取我的位置地址 let myGeo = new BMapGL.Geocoder(); // 根据坐标得到地址描述 myGeo.getLocation(r.point, function(res){ if (res){ infoWindowStr += '\<br>' + '地址:'+ res.address; let infoWindow = new BMapGL.InfoWindow(infoWindowStr, opts); bmap.openInfoWindow(infoWindow, point); // 开启信息窗口 } }); }); //创建label let label = new BMapGL.Label('我的位置', { position: new BMapGL.Point(r.point.lng, r.point.lat), offset: new BMapGL.Size(0, 0), }); label.lableId = "lableId"; label.setStyle({ color: "#fff", backgroundColor: "#396DFF", borderRadius: "10px", padding: "2px 6px", border: "0", }); bmap.addOverlay(label); } }) //创建定位控件 let locationControl = new BMapGL.LocationControl({ // 控件的停靠位置(可选,默认左上角) anchor: BMAP_ANCHOR_TOP_LEFT, // 控件基于停靠位置的偏移量(可选) offset: new BMapGL.Size(20, 20) }); //将控件添加到地图上 bmap.addControl(locationControl); //添加巡检标注、轨迹动画等 setTimeout(function() { console.log(that.routineCheckPointList) that.addInspectionMark(that.routineCheckPointList); }, 1000); }); }, //添加标注、连线 (接收逻辑层传来的数据:dataList巡检数据) addInspectionMark(newValue) { var _that = this if(!newValue){ return ; } setTimeout(function() { //1.创建并描点 巡检数据 if(bmap && newValue.length && newValue.length > 0){ //先清除地图上标记的所有覆盖物 bmap.clearOverlays(); for(let i=0;i<newValue.length;i++){ var pointItem = newValue[i]; var point = new BMapGL.Point(pointItem.longitude, pointItem.latitude); //这个新坐标是getCurrentPosition传过来的 var myIcon = new BMapGL.Icon(stakeIconUrl, new BMapGL.Size(25, 25), { // 设置图片偏移。 // 当您需要从一幅较大的图片中截取某部分作为标注图标时,您 // 需要指定大图的偏移位置,此做法与css sprites技术类似。 imageOffset: new BMapGL.Size(0, 0) // 设置图片偏移 }); // // 创建标注对象并添加到地图 var marker = new BMapGL.Marker(point, { icon: myIcon }); marker.disableMassClear(); bmap.addOverlay(marker); // 将标注添加到地图中 // 给marker点标记添加点击事件 //给label添加点击事件 _that.clickInspectionHandler(pointItem,marker); _that.clickInspectionLabelHandler(pointItem,i,newValue.length); } //2.开始连线,轨迹效果 _that.openDrawLine(newValue); } }, 1000); }, //点marker击事件 clickInspectionHandler(pointItem,marker){ marker.addEventListener("click",function(e){ let inspectionPoint = new BMapGL.Point(pointItem.longitude, pointItem.latitude); //巡检点 // 创建信息窗口 let opts = { width: 280, height: 100, }; let infoWindowStr = '经度:'+ pointItem.longitude +'\<br>' + '纬度:'+ pointItem.latitude + '地址:'+ pointItem.positionName; let infoWindow = new BMapGL.InfoWindow(infoWindowStr, opts); bmap.openInfoWindow(infoWindow, inspectionPoint); // 开启信息窗口 }); }, //点击label事件 clickInspectionLabelHandler(pointItem,index,arrLength){ var _that = this let pointLabel = pointItem.name; let point = new BMapGL.Point(pointItem.longitude, pointItem.latitude); //设置起点标桩 和 终点标桩 if(index == 0){ pointLabel = "起点:" + pointLabel } if(index == (arrLength -1)){ pointLabel = "终点:" + pointLabel } const label = new BMapGL.Label(pointLabel, { position: point, offset: new BMapGL.Size(0, 0), }); label.setStyle({ color: "#fff", backgroundColor: "#396DFF", borderRadius: "10px", padding: "2px 6px", border: "0", }); //给label添加点击事件 label.addEventListener("click", function(e) { //点击触发 //思路:此处点击label,展示巡检的基本信息 let opts = { width: 250, // 信息窗口宽度 height: 300, // 信息窗口高度 title: "巡检信息" // 信息窗口标题 } let labelStr ='经度:'+ pointItem.longitude +'\<br>' + '纬度:'+ pointItem.latitude +'\<br>' + '地址:'+ pointItem.positionName; let infoWindow = new BMapGL.InfoWindow(labelStr, opts); // 创建信息窗口对象 bmap.openInfoWindow(infoWindow, point); }); bmap.addOverlay(label); }, //开启动画效果 openDrawLine(dataList) { // GL版命名空间为BMapGL // 按住鼠标右键,修改倾斜角和角度 var path = [] for(let i=0;i<dataList.length;i++){ if(dataList.length > 0){ let item = {} item.lng = dataList[i].longitude; item.lat = dataList[i].latitude; path.push(item); } } var point = []; for (var i = 0; i < path.length; i++) { point.push(new BMapGL.Point(path[i].lng, path[i].lat)); } var pl = new BMapGL.Polyline(point); this.start(pl) // setTimeout(this.start(pl), 1000); }, start(pl) { console.log('开始画图****************') let trackAni = new BMapGLLib.TrackAnimation(bmap, pl, { overallView: true, tilt: 30, duration:0, delay: 300 }); trackAni.start(); }, /*==============巡检数据处理 ===================*/ //获取巡检记录集合 getRoutineCheckPointList(){ let that = this this.$http.get(`/routingmgt/routinecheckrouting/getRoutineCheckPointList/${this.dataForm.routingId}`).then(({ data: res }) => { if (res.code !== 0) { return this.$message.error(res.msg) } //组装巡检点数据 let routineCheckPointList = res.data this.routineCheckPointList = routineCheckPointList }).catch(() => {}) }, } } </script>
页面效果图如下:
以上代码是展示对应巡检路线的地图组件页面,形似地图导航的路线效果,主要功能就是调用百度地图组件SDK展示地图,然后根据路线id获取路线上所有关联的巡检点数据,根据巡检点的经纬度信息,通过百度地图SDK的相关方法,进行描点、标记信息、连线;最后展示巡检路线图效果。
<el-dialog>是一个对话框组件,用于展示聊天对话框。:visible.sync="visible"表示通过visible属性控制对话框的显示和隐藏,:title属性设置对话框的标题,width属性设置对话框的宽度。
在对话框中,使用了一些<div>元素来布局聊天内容和用户输入框。v-for指令用于循环渲染messages数组中的每个消息,:key属性指定了每个消息的唯一标识,:class属性根据消息的类型设置不同的样式。
用户输入框和发送按钮位于<form>元素中,@submit.prevent指令用于阻止表单的默认提交行为,v-model指令用于双向绑定用户输入的内容。
methods定义了一些方法:
init方法用于初始化对话框,设置对话框的显示状态。
initMap方法,初始化地图,创建定位控件,获取当前定位;并在对应的div区域展示。
addInspectionMark方法,根据查询后台返回的routineCheckPointList巡检点数据集合,在地图上添加标记点、添加标注信息、连线。
clickInspectionHandler方法,点标记点marker,展示经纬度、位置等信息。
openDrawLine方法,开启标记点marker连线的动画效果。
getInfo方法用于获取文件的信息,发送HTTP请求并根据返回结果进行相应的操作。
扫码关注不迷路!!!
郑州升龙商业广场B座25层
service@iqiqiqi.cn
联系电话:400-8049-474
联系电话:187-0363-0315
联系电话:199-3777-5101