主要讲一下物业管理系统的一下开发难点:
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 = true
this.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 = true
this.allData[i].errorMessage = ["面積格式有誤,僅限數字"]
}
}
this.unitData = this.allData
this.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.allData
this.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.result
this.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.result
for (var i = 0; i < preResult.length; i++) {
this.allData[preResult[i].index - 1].error = true
this.allData[preResult[i].index - 1].errorMessage = preResult[i].message
}
}
this.unitData = this.allData
this.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.residentData
exportData.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 = true
let 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 = true
this.regular_calendars = this.temp_calendars = []
this.params.start_time = this.zeroTimestanp(this.days[0])
this.params.end_time = this.zeroTimestanp(this.days[6]) + 86399000
const result = await Facility.getFeeSchedule(this.facility_id, this.params)
this.temp_calendars = result.temp_calendars
const 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_sale
data.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 {
}
}
},