主要讲一下物业管理系统的一下开发难点:
CMS端
1. 批量导入功能的实现
系统中,存在批量导入住户,批量导入单位,批量导入账单等功能模块,这里以批量导入单位为例子,单位数据文件的xlsx格式。
1.1 下载模板
批量导入数据的第一步是下载指定的模板
// 下载模板的方法downloadFileClick() {let url = 'https://yoov-1257906831.cos.ap-hongkong.myqcloud.com/file/batch_unit_template.xlsx'// 在本页打开窗口window.open(url, "_self")},
1.2 上传xlsx文件
vue导入xlsx文件需要安装第三方插件xlsx
// 安装npm install xlsx --save// 使用import XLSX from 'xlsx'
// 点击上传按钮使用el-upload,在on-change事件中调用importExcel方法<el-upload class="upload-demo" ref="upload" action="/" :show-file-list="false" :on-change="importExcel":auto-upload="false"><el-button class="download-button" size="small" type="primary"><i class="iconfont icon-shangchuan"></i>{{$t('Button.Upload')}}</el-button></el-upload>// 導入Excel表格數據importExcel(file) {this.allData = []this.batchFile = file.raw// let file = file.files[0] // 使用传统的input方法需要加上这一步const types = file.name.split('.')[1]const fileType = ['xlsx', 'xls'].some(item => item === types)if (!fileType) {this.$message.error('格式错误!请重新选择')return}this.excel_loading = truethis.file2Xce(file).then(tabJson => {if (tabJson && tabJson.length > 0) {this.gridData = tabJson[0].sheet// console.log(this.gridData)// this.gridData = tabJson[0].sheet.slice(1)// this.gridData = this.gridData.filter(item => item.length > 0)for (var i = 0; i < this.gridData.length; i++) {this.allData.push({"index": i + 1,"building_name": this.gridData[i].大廈,"floor_name": this.gridData[i].樓層,"unit_name": this.gridData[i].單位.toString(),"area": this.gridData[i].面積,"type": this.gridData[i].單位類型,})}for (var i = 0; i < this.allData.length; i++) {// // console.log(typeof this.allData[i].type)if (isNaN(Number(this.allData[i].area))) {this.allData[i].error = truethis.allData[i].errorMessage = ["面積格式有誤,僅限數字"]}}this.unitData = this.allDatathis.errorData = this.unitData.filter(item => item.error == true)// var testData = this.unitData.filter(item => item.error != true)if (this.errorData.length == 0) {this.postData = this.allDatathis.pretest()}setTimeout(() => {this.excel_loading = false}, 500)}})},file2Xce(file) {return new Promise(function(resolve, reject) {const reader = new FileReader()reader.onload = function(e) {const data = e.target.resultthis.wb = XLSX.read(data, {type: 'binary'})const result = []this.wb.SheetNames.forEach((sheetName) => {result.push({sheetName: sheetName,sheet: XLSX.utils.sheet_to_json(this.wb.Sheets[sheetName])})})resolve(result)}reader.readAsBinaryString(file.raw)})}
1.3 数据解析
对打开的xlsx文件进行数据解析,并且把解析的数据转成json格式,与后台进行校验,返回的错误提示渲染到表格上,用户按提示修改xlsx文件内容,然后再重新上传就可以了。
代码
// 数据校验async pretest() {var data = {"data": this.allData}var preResult = []const result = await Unit.pretestUnitByBatch(this.condo_id, data)if (result.pass == false) {preResult = result.resultfor (var i = 0; i < preResult.length; i++) {this.allData[preResult[i].index - 1].error = truethis.allData[preResult[i].index - 1].errorMessage = preResult[i].message}}this.unitData = this.allDatathis.errorData = this.allData.filter(item => item.error == true)},
// 数据提交async submit() {var data = {"data": this.postData}const result = await Unit.doUnitByBatch(this.condo_id, data)if (result.code == 1) {// console.log('success')this.$emit('uploadSuccess')} else {// this.$message.success("提交成功")this.$message.error("請先按要求修改Excel表格數據,然後再重新上傳提交!")}},
1.4 改进
目前做的版本,导入的xlsx数据与后台验证格式之后,返回错误提示,只能在xlsx源文件中修改,改进的话,可以直接在表单中修改提交的话就更好了。
2. 批量导入功能的实现
系统中,账单、活动信息、住户信息、维修信息等需要批量导出数据,这里以批量导出住户数据为例,导出的文件格式是xlsx文件。
2.1 引入第三方js文件
首先,需要引入2个第三方的js文件(Blob.js和Export2Excel.js)
model文件夹新建exportExcel.js文件
export default {// 列表选中下载 excelData导出的数据,tHeader字段名称,filterVal字段downloadExcel(excelData,tHeader,filterVal,excelName) {require.ensure([], () => {let { export_json_to_excel } = require("@/excel/Export2Excel");let data = excelData.map(v => filterVal.map(j => v[j]));// 这是表格的名称。合同列表+当前日期,dateFormatStr是一个方法,格式化了日期// let excelName='合同列表'+this.dateFormatStr(new Date)export_json_to_excel(tHeader, data, excelName); // 导出的表格名称,根据需要自己命名});}}
main.js文件中添加引用
import excel from './model/exportExcel.js'//注册到vue原型上Vue.prototype.excel = excel
2.2 调用download方法下载xlsx文件
fieldTitle定义xlsx文件里面的表头,fieldName定义json数据的字段名称,fieldTitle和fieldName是对应的。
// 導出列表downloadExcel() {let exportData = this.residentDataexportData.map(item => {item._wallet_money = item.wallet_money == null ? '0.00' : item.wallet_money})let fieldTitle = ["住戶姓名","聯絡電話","電郵","身份證件號","錢包預存金額",]let fieldName = ["name","phone","email","id_no","_wallet_money",]let excelName = '住户列表' + '-' + new Date().getTime()this.excel.downloadExcel(exportData,fieldTitle,fieldName,excelName)},
3. 会所设施时段收费表
3.1 收费表渲染
第一步,先获取当前七天的时间,转成数组week。
initData(type, cur) {this.loading = truelet date = ''if (cur) {date = new Date(cur)} else {date = new Date()}// 獲取當前七天的時間this.nowTime = new Date()this.currentDay = date.getDate() // 今日日期 几号this.currentYear = date.getFullYear() // 当前年份this.currentMonth = date.getMonth() + 1 // 当前月份this.currentWeek = date.getDay() // 1...6,0 // 星期几// console.log(this.currentWeek)if (this.currentWeek === 0) {this.currentWeek = 7}const str = this.formatDate(this.currentYear, this.currentMonth, this.currentDay) // 今日日期 年-月-日// console.log(str);this.days = []for (let i = 0; i < 7; i++) {const d = new Date(str)if (type == 'prex') {d.setDate(d.getDate() - i - 1)this.days.unshift(d) //获取未来7天内的日期} else if (type == 'next') {d.setDate(d.getDate() + i + 1)this.days.push(d) //获取未来7天内的日期} else {d.setDate(d.getDate() + i)this.days.push(d) //获取未来7天内的日期}}// console.log(this.days);this.week = this.days.map(item => {return {"dateTime": item,"date": this.formatDate(item.getFullYear(), item.getMonth() + 1, item.getDate()),"dayOfWeek": item.getDay() == 0 ? this.weeks[6] : this.weeks[item.getDay() - 1],"day": item.getDay() == 0 ? 7 : item.getDay()}})this.getFeeSchedule()this.loading = false},
第二步,通过第一步获得时段请求数据,常规时段收费的dayOfWeek把常规时段按周一到周日分组,获得当周的时段收费表。
第三步,遍历临时时段收费数组,如果不为空,则根据dayOfWeek把临时收费的时段插入对应的当周时段收费表,从而实现常规时段和临时时段合并。
// 获取时段收费表async getFeeSchedule() {// this.loading = truethis.regular_calendars = this.temp_calendars = []this.params.start_time = this.zeroTimestanp(this.days[0])this.params.end_time = this.zeroTimestanp(this.days[6]) + 86399000const result = await Facility.getFeeSchedule(this.facility_id, this.params)this.temp_calendars = result.temp_calendarsconst sortResult = result.regular_calendars.sort(this.compare('start_time_of_day'))this.regular_calendars = FormatData.formatSchedule(sortResult)console.log(this.regular_calendars);console.log(this.temp_calendars);for(var i=0; i<this.temp_calendars.length; i++) {var day = this.temp_calendars[i].day_of_week[0]var realDay = this.regular_calendars[day-1]for(var j=0; j<realDay.length; j++) {if(realDay[j].start_time_of_day == this.temp_calendars[i].start_time_of_day) {// console.log('存在临时收费表');this.regular_calendars[day-1][j] = this.temp_calendars[i]}}}// console.log(this.regular_calendars);},
3.2 真实时间有效性
后台请求返回的时间,只要在有效期,都会返回对应的时段收费比如我请求2022-02-11到2022年-02-17这个时段 的收费表,如果其中一个时段收费是08:00-10:00,循环周期是周一到周日,有效期是2022-01-01到2022-02-15,其中2022-02-15是周二,理论上2022-02-16(周三)和2022-02-17(周四)是没有数据的。实际上是渲染到这个时段的数据的。
解决方法:
在每个box渲染前判断真实的时间时间戳与请求时段返回的收费表的valid_start_time和vaild_end_time进行对比,不在范围内的就取消。
// 判断显示的日期是否在有效期checkValided() {var currenTime = this.dateToTimeStamp(this.day.dateTime)for(var i=0; i<this.daySchedule.length; i++) {let startTime = this.dateToTimeStamp(this.daySchedule[i].valid_start_time)let endTime = this.dateToTimeStamp(this.daySchedule[i].valid_end_time)if(currenTime > endTime || currenTime < startTime) {this.daySchedule.splice(i,1)}}},// 时间转时间戳dateToTimeStamp(date) {return new Date(new Date(date).toLocaleDateString()).getTime()},
3.3 添加临时收费
添加临时收费目前只支持修改当前时段的收费金额,不支持修改时段。

即把点击的时间段的参数赋值到一个新的对象,并且把这个对象的temp设置为true
async addSchedule() {console.log(this.boxData)var dayOfWeek = []var list = []dayOfWeek.push(this.dayOfWeek + 1)list[0] = {"day_of_week": dayOfWeek,"start_time_of_day": this.stringToTime(this.startTime),"end_time_of_day": this.stringToTime(this.endTime),"valid_start_time": this.zeroTimestanp(this.choiceDate) + this.stringToTime(this.startTime),"valid_end_time": this.zeroTimestanp(this.choiceDate) + this.stringToTime(this.endTime),"price": parseFloat(this.price),"on_sale": true,"temp": true}var data = {"facility_id": parseInt(this.$route.params.id),"list": list}console.log(data)const result = await Facility.addFeeSchedule(data)if (result.code == 1) {this.$message.success("添加成功")this.$emit('success')} else {this.$message.error("時間段衝突,請檢查修改再提交")}},
3.4 修改临时收费
async updateSchedule() {console.log('更新')var dayOfWeek = []var list = []dayOfWeek.push(this.dayOfWeek + 1)const data = {"id": this.boxData.id,"facility_id": parseInt(this.$route.params.id),"day_of_week": dayOfWeek,"start_time_of_day": this.stringToTime(this.startTime),"end_time_of_day": this.stringToTime(this.endTime),"valid_start_time": this.zeroTimestanp(this.choiceDate) + this.stringToTime(this.startTime),"valid_end_time": this.zeroTimestanp(this.choiceDate) + this.stringToTime(this.endTime),"price": parseFloat(this.price),"on_sale": true,"temp": true}console.log(data);const result = await Facility.updateFeeSchedule(data)if (result.code == 2) {this.$message.success("更新成功")this.$emit('success')} else {this.$message.error("時間段衝突,請檢查修改再提交")}},
3.5 临时下架收费
如果是临时时段收费的上架或者下架,则修改on_sale参数为true或者false即可;
如果是常规时段收费的下架相当于添加一个临时时段,temp为true,on_sale为false;
常规时段收费的上架则相当于把创建的临时时段删除即可;
// 上架或者下架async changeOnSale(item, day) {// console.log(item)// 臨時時段上下架if(item.temp == true) {if(item.on_sale == true) {var data = JSON.parse(JSON.stringify(item))console.log(data)data.on_sale = !data.on_saledata.facility_id = Number(this.$route.params.id)data.valid_start_time = new Date(data.valid_start_time).getTime()data.valid_end_time = new Date(data.valid_end_time).getTime()console.log(data)const result = await Facility.updateFeeSchedule(data)console.log(result)if(result.code == 2) {this.$message.success(item.on_sale ? '下架成功' : '上架成功')this.$emit('changeOnSaleSuccess')} else {this.$message.error(item.on_sale ? '下架失敗' : '上架失敗')}} else {this.deleteSchedule(item)}}// 常規時段上下架else {// 下架console.log(item)console.log(day)var dayOfWeek = []var list=[]dayOfWeek.push(day.day)if(item.on_sale == true) {list[0] = {"day_of_week": dayOfWeek,"start_time_of_day": item.start_time_of_day,"end_time_of_day": item.end_time_of_day,"valid_start_time": this.zeroTimestanp(day.date) + item.start_time_of_day,"valid_end_time": this.zeroTimestanp(day.date) + item.end_time_of_day,"price": item.price,"on_sale": false,"temp": true}var data = {"facility_id": parseInt(this.$route.params.id),"list": list}console.log(data);const result = await Facility.addFeeSchedule(data)if(result.code == 1) {this.$message.success("下架成功")this.$emit('changeOnSaleSuccess')} else {this.$message.error("下架失敗")}} else {}}},
