点击这里,查看扫码入库系统演示的网址
一、界面样式
主要功能:
- 扫码入库,无线,有线扫码枪均可。
- 断点后自动续存编号,比如之前1号服务站入库了2单,换成8号服务站入库,当切换回1号服务站继续入库时,快递编号自动从第3单开始向后续。
- 能够把入库数据转换成EXCEL表格,以供下载使用。
- 手动输入,倒计时12秒后自动检测单号是否合规,并需要用户确认后存入浏览器本地存储
- 本地存储的几个主要用途。 ①计算时间,当第一次打开网站会存入一个毫秒时间,每次刷新或者重新打开页面都会比对当前时间与存储时间的差值,大于13小时,即弹出对话框让用户选择是否清理页面缓存信息。同时存入当前毫秒时间。 ②断点续号,将服务站编号与他的入库数,作为key:value保存于本地存储中,当点击切换服务站时,保存旧服务站的信息,调出新服务站的信息,以达到序号不乱的目的。 ③显示列表信息和下载EXCEL信息都来源于本地存储
二、扫码入库和手动入库
2.1 扫码入库,扫码枪获取单号的时间一般不超过20毫秒,为了保险起见,设置了300毫秒后,开始验证数据。
- 2.1.1 methods的首个函数,主要功能一些开关和计时
`this.flag 为 ture 时把毫秒时间存放于数组中,单号有15位就会存放15次毫秒时间`
`this.istrue 为 ture 时立即锁定并执行 this.timerSet() 函数`
`this.isFalse 用于切换提示信息与手动输入提示信息有关`
scanWrite() {if (this.flag) {let time = Date.now();this.timeArr.push(time);}if (this.istrue) {this.istrue = false;this.isFalse = false;this.timerSet();}
}
- 2.1.2 自动分通道检测单号
`this.isShow 为 ture 时,是扫码入库,反之是手动入库`
timerSet() {if (this.isShow) {this.checkNumA();} else {this.checkNumB();}}
- 2.1.3 检测单号的有效性
` this.istrue 为 false 时 300毫秒后执行数据检测 ``第一步计算了时间,展示在网页顶部右侧的提示信息框内``第二步检测单号是否已经存在,并展示提示信息,开启或关闭某些开关``第三部检测单号属于哪个快递公司``第四步统计保存数据``重复或者错误都将执行清除函数 this.remove()`checkNumA() {if (!this.istrue) {setTimeout(async () => {this.timeArr.push(Date.now());this.timess = '用时:' + (this.timeArr[this.timeArr.length - 1] - this.timeArr[0]) + '毫秒';/*检测快递单号是否重复*/let myforArr = await this.forArr(this.numA);if (!myforArr) {this.remove();this.countdown = `${this.numA}号码重复`;return;}/*检测快递单号长度*/let checkLength = await this.checkLength(this.numA);if (!checkLength) {this.remove();this.countdown = `单号长度有误,应当在11~19位之间`;return;} else {this.totalCount(this.numA);}}, 300);}}
2.2 手动入库,当点击输入框,倒计时启动,然后验证单号有效性。
- 2.2.1 倒计时。
`倒计时开启后运行倒计时音乐``倒计时结束后运行 this.timerSet()``this.right用于锁定,当前只能运行一个timer()函数`timer() {if(this.isRight){this.isRight =false;this.isHandleShow = true;this.timer_audio = new Audio('../../static/yx/10s.mp3');this.timer_audio.play();let num = 12;this.countdown = `将在${num}秒后自动入库`;this.myTimerNum = setInterval(() => {num -= 1;if (num <= 1) {clearInterval(this.myTimerNum);this.isShow = false;this.timerSet();} else {this.countdown = `将在${num}秒后自动入库`;}}, 1000);}else{return}}
- 2.2.2 自动分通道检测单号,同上
this.timerSet()
- 2.2.3 手动输入单号检测验证区
`第一步验证单号是否重复``第二步匹配对应的快递公司``第三步统计并保存数据``重复或者错误都将执行清除函数 this.remove()`async checkNumB() {/*检测快递单号是否重复*/let myforArr = await this.forArr(this.numB);if (!myforArr) {this.remove(this.numB);this.isHandleShow = false;return;}/*检测快递单号长度*/let checkLength = await this.checkLength(this.numB);if (!checkLength) {this.isRight = true;this.remove(this.numB);return;} else {if (this.timer_audio) {this.timer_audio.pause();}const that = this;uni.showModal({title: '无法匹配快递公司',content: '确定要保存当前单号吗?',success: function(res) {if (res.confirm) {that.isRight = true;that.totalCount(that.numB);that.remove(that.numB);return} else if (res.cancel) {that.countdown = '单号有误,已经删除';that.isRight = true;that.remove(that.numB);return}}});}}
三、统计并本地保存数据
`this.countNum 是当前服务站的快递排序编号``this.totalNums 是当前总入库数 ``this.expressNum 当前单号,用于首页展示``this.totalJsonData() 整合汇总数据 并存入EXCEL表所需数组中 ``把EXCEL表所需数组存入浏览器本地存储``计算用时,运行清理函数`async totalCount(e) {this.countNum++;this.totalNums++;this.expressNum = e;this.buffNumArr.push(e);let buffResult = await this.totalJsonData();uni.setStorage({key: 'expressJsonArr',data: buffResult});this.timeArr.push(Date.now());this.timess = '用时:' + (this.timeArr[this.timeArr.length - 1] - this.timeArr[0]) + '毫秒';this.remove(e);}
四、打开网页时的初始化数据
- 4.1
onLoad()
页面生命周期
`this.getDate().slice(0, 10)获取当前时间`
`初始化EXCEL表下载时所用的文件名`
`this.timess 顶部显示的时间`
`获取本地存储的时间数据,如果大于13小时,就显示模态框,并存入新的时间`
`this.checkStorageObj() 获取本地存储 更新页面数据`onLoad() {let str = this.getDate().slice(0, 10);this.fileName = str + '快递单.xls';this.timess = this.getDate();uni.getStorage({key: 'dateNow',success: res => {this.starTimer = res.data;let H = Math.round((Date.now() - this.starTimer) / 3600000);if (H > 13) {uni.setStorage({key: 'dateNow',data: Date.now()});this.clearArr();}},fail: err => {uni.setStorage({key: 'dateNow',data: Date.now()});}});this.checkStorageObj();}
- 4.2
this.getDate()
获取当前年月日时分秒
`时间格式为 2021/04/09/09:07:58`getDate(e) {if (typeof e === Number) {var D = new Date(e);} else {var D = new Date();}let Y = D.getFullYear();let M = D.getMonth() + 1;M = M < 10 ? '0' + M : M;let R = D.getDate();R = R < 10 ? '0' + R : R;let H = D.getHours();H = H < 10 ? '0' + H : H;let F = D.getMinutes();F = F < 10 ? '0' + F : F;let S = D.getSeconds();S = S < 10 ? '0' + S : S;return Y + '/' + M + '/' + R + '/' + H + ':' + F + ':' + S;}
- 4.3
this.checkStorageObj()
获取本地存储数据
`this.ServStaNumObj 包含服务站编号和该服务站入库单号统计数``arr.map()用于计算已入库的快递总数`checkStorageObj() {uni.getStorage({key: 'ServStaNumObj',success: res => {this.ServStaNumObj = res.data;let arr = Object.values(res.data);let num = 0;arr.map(val=>{num+=Number(val);})this.totalNums = num;},fail: err => {console.log('当前还没有服务站的数据缓存信息');}});}
五、页面中的几个点击事件
- 5.1
handleXL(e)
选择服务站点后的事件
`e.newVal 是新站点编号``this.defaultVal 是旧站点编号``this.myIndex 是站点编号加了 - 后的字符串``this.forStorageObj(newIndex,oldIndex,oldVal) 用于对比数据交换数据`handleXL(e) {let my_index = e.newVal + '-';if (this.myIndex != my_index) {let newIndex = e.newVal;let oldIndex = this.defaultVal;let oldVal = this.countNum;this.countNum = 0;this.forStorageObj(newIndex,oldIndex,oldVal);this.myIndex = my_index;this.defaultVal = newIndex;} else {return;}}
- 5.2
this.forStorageObj()
对比、交换、存储数据
`当旧服务站入库数不为 0 时,把当前的入库数更新到 this.ServStaNumObj 中``获取 this.ServStaNumObj 对象中是否有新服务站的数据,把入库数赋值给 this.countNum ``最后进行本地存储,这个存储就是用于,onLoad()时调用`forStorageObj(newIndex,oldIndex,oldVal) {/*保存旧值*/if(oldVal!=0){this.ServStaNumObj[oldIndex] = `${oldVal}`;}/*更新新值*/for (let k in this.ServStaNumObj) {if(k === newIndex){this.countNum = this.ServStaNumObj[k];break;}}uni.setStorage({key: 'ServStaNumObj',data: this.ServStaNumObj});}
- 5.3 更换入库输入方式
changeDemo()
`打开或者关闭开关``改变当前按钮的形态,颜色,提示信息等``关闭定时器,关闭音乐`changeDemo() {this.isShow = !this.isShow;this.flag = true;this.istrue = true;if (this.isShow) {this.countdown = '现在已经是扫码输入模式了';this.type = 'primary';this.btnShow = '尝试手动输入?';if (this.timer_audio) {this.timer_audio.pause();}clearInterval(this.myTimerNum);} else {this.countdown = '您选择了手动输入单号';this.isRight = true;this.type = 'warn';this.btnShow = '返回扫码输入?';}
}
- 5.4 清除页面缓存
clearArr() {let that = this;uni.showModal({title: '确定要清除',content: '保存在浏览器内的快递单号吗?',success: function(res) {if (res.confirm) {this.ServStaNumObj = {};uni.removeStorage({key: 'expressJsonArr',success: function(res) {that.countdown = '清除成功';}});uni.removeStorage({key: 'ServStaNumObj',success: function(res) {that.countdown = '清除成功';}});window.location.reload();} else if (res.cancel) {that.countdown = '取消清除';}}});
},
- 5.5 获取全部快递单号列表
`that.buffTotal是 excel表的数据源`getList() {this.isListShow = !this.isListShow;if (this.isListShow) {var that = this;that.buffTotal = [];uni.getStorage({key: 'expressJsonArr',success: function(resa) {that.buffTotal.push(...resa.data);}});}
}
- 5.6 EXCEL表下载,使用的是组件
<template><view @click.stop="downExcel"><slot></slot></view>
</template><script>
export default {name: 'min-excel',data() {return {thName: `编号,快递公司,快递单号,入库时间\n`};},props: {excelList: {type: Array},fileName: {type: String,default: '快递单.xls'}},methods: {downExcel() {if (this.excelList.length === 0) {uni.showModal({title: '抱歉不能下载',content: '当前没有可用的数据',showCancel: false});}else{let str = this.thName;let arrList = this.excelList;for (let i = 0; i < arrList.length; i++) {for (let key in arrList[i]) {str += `${arrList[i][key] + '\t'},`;}str += '\n';}const uri = 'data:text/csv;charset=utf-8,\ufeff' + encodeURIComponent(str);const link = window.document.createElement('a');link.href = uri;link.download = this.fileName;link.click();}}}
};
</script><style></style>
- 5.7 选择服务站点的下拉列表组件
<template><view class="xl-content" @click.stop.prevent="downUp"><!--列表分为三个部分--><view class="xl-show"><!--1.默认显示栏--><input disabled="true" type="text" :value="inputStr === '' ? defaultVal : inputStr" /></view><view class="xl-sj" :class="isClick ? 'xl-sj-up' : 'xl-sj-down'"><!--2.倒三角按钮--><image src="@/static/components/sj.png"></image></view><view class="xl-lb" v-show="isClick"><!--3.数据列表--><view class="xl-lb-view" v-for="(item, index) in list" :key="item" :data-index="index" hover-class="color-blue" @click.stop="choose(item, $event)">{{ item }}</view></view></view>
</template><script>
export default {data() {return {isClick: false,inputStr: '',oldIndex: 0,eve: '',timer:null};},props: {list: {type: Array},defaultVal:{type:String}},watch: {list: function() {this.inputStr = '';},/*监听点击事件,如果打开了列表就开始监听点击事件,如果列表关闭就移除监听事件*/isClick:function(){if(this.isClick){window.addEventListener('click', (e)=>{this.endClick(e);});}else{clearTimeout(this.timer);window.removeEventListener('click',this.endClick);}}},methods: {/*endClick需要传的参数*/endClick(e){if (this.isClick && this.eve === '') {this.eve = e.path[0];} else if(this.eve === e.path[0]){return;}else{this.isClick = false;}},downUp() {this.isClick = !this.isClick;if (this.isClick) {this.timer = setTimeout(() => {this.isClick = false;}, 8000);}},choose(newVal, event) {if (this.inputStr === '') {this.inputStr = this.list[0];}let oldVal = this.inputStr;this.inputStr = newVal;let oldIndex = this.oldIndex;let newIndex = event.target.dataset.index;let timer = setTimeout(() => {this.isClick = false;clearTimeout(timer);}, 200);this.$emit('btns', { oldVal, newVal, newIndex, oldIndex, event });this.oldIndex = event.target.dataset.index;}}
};
</script><style lang="scss">---样式就不写了--
</style>
六、其他衔接函数
- 6.1 清除输入框数据的函数
remove(e)
- 6.2 判断单号是否重复的函数
forArr(e)
- 6.3 根据单号,匹配快递公司的函数
checkLength(e)
- 6.4 生成EXCEL表数据源的函数
totalJsonData()
- 6.5 监听数字变化就播放音乐的函数
watch
`点击输入框、错误、重复、完成后都要运行的清除输入框数据的函数`remove(e) {if (e === this.numB || e === 'numB') {this.countdown = '您正在手动输入';this.isFalse = false;this.timeArr = [];this.flag = true;this.istrue = true;this.numB = '';this.timer();} else {this.numA = '';this.timeArr = [];this.flag = true;this.istrue = true;}}
`检测单号是否重复的函数` forArr(e) {if (this.buffNumArr.length === 0) {return true;}var arr = this.buffNumArr.filter(k => {return k === e;});if (arr.length !== 0) {this.isFalse = true;this.num++;this.wrong;return false;} else {return true;}}
`检测单号长度,匹配快递公司的函数`checkLength(e) {var that = this;this.timess = '单号长度为:'+ e.length;if (e.length < 11) {this.wrong++;this.countdown = `快递单号长度有误${this.wrong}次`;this.istrue = true;return false;}let str1 = e.slice(0, 2);let str2 = e.slice(0, 1);let strLength = e.length;if (str1 == 'SN') {that.expressName = '苏宁快递';that.countdown = `快递公司 : 苏宁快递 快递单号:${e}`;return true;} else if (str1 === 'JT') {that.expressName = '极兔快递';that.countdown = `快递公司:极兔快递 快递单号:${e}`;return true;} else {that.expressName = '-未知-';that.countdown = `快递公司:-未知- 快递单号:${e}`;return '-未知-';}}
`对数据进行合并分发处理,排序处理,给EXCEL表提供数据源`
totalJsonData() {let flag = true;let index = Number(this.countNum);let myIndex = this.myIndex + index;let oldMyIndex = this.myIndex + (index - 1);const time = Date.now();let buffObj = { expressIndex: myIndex,index:index,expressName: this.expressName,expressNum: this.expressNum,inputTimer: this.getDate(time)};this.buffTotal.forEach((k,i)=>{if(k.expressIndex === oldMyIndex ){++i;this.buffTotal.splice(i,0,buffObj);flag = false;}})if(flag){this.buffTotal.push(buffObj);}return this.buffTotal;}
`监听数字变化,播放对应的音效`watch: {totalNums: function() {const audio = new Audio('../../static/yx/ok.wav');audio.play();},wrong: function() {const audio = new Audio('../../static/yx/wrong.wav');audio.play();},num: function() {const audio = new Audio('../../static/yx/wrong.mp3');audio.play();}}
七、其他系统设置
- 7.1
manifest.json
H5的自定义设置。官方文档地址 - 7.2 网站小图标的设置,在项目根目录下创建html文件,在H5配置内,关联此html文件
"template":"link_index.html"
"h5": {//网站小图标需要使用到的link_index.html文件,在此关联"template":"link_index.html",//网站的名称"title": "快递扫码入库",//反向代理相关设置,是否允许自己的网站去访问其他的 https 网站"devServer": {"https": true},//网络地址中不显示 # 号 "router": {"mode": "history"}}
<!DOCTYPE html>
<!--在项目根目录下生成 link_index.html文件 把小图标的引用 link rel 放进去-->
<!-- <link rel="shortcut icon" href="https://static.oschina.net/new-osc/img/favicon.ico"> -->
<html lang="zh-CN"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><title><%= htmlWebpackPlugin.options.title %></title><script>var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')</script><link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" /><link rel="shortcut icon" href="https://static.oschina.net/new-osc/img/favicon.ico"></head><body><noscript><strong>Please enable JavaScript to continue.</strong></noscript><div id="app"></div><!-- built files will be auto injected --></body>
</html>