Experience in the development of vue mobile terminal

Introducing Mint UI on demand Used in this project mint-ui As a basic ui framework, it has encountered many problems in use. The official website doc ...
Realization
Use
Code
Use
Code
Use
Effect
Introducing Mint UI on demand

Used in this project mint-ui As a basic ui framework, it has encountered many problems in use. The official website doc still can't visit. But thanks to the mint ui team.
Here we recommend a vue mobile ui Library vant

  • Introduction on demand
* mint-ui import 'mint-ui/lib/style.css' import { Navbar, TabItem, TabContainer, TabContainerItem, Radio, Actionsheet, Switch, Popup, Button, DatetimePicker, Toast, Picker, MessageBox, loadmore, Range, Progress, Indicator, } from 'mint-ui' Vue.component(Navbar.name, Navbar) Vue.component(TabItem.name, TabItem) Vue.component(TabContainer.name, TabContainer) Vue.component(TabContainerItem.name, TabContainerItem) Vue.component(Radio.name, Radio) Vue.component(Actionsheet.name, Actionsheet) Vue.component(Popup.name, Popup) Vue.component(Button.name, Button) Vue.component(DatetimePicker.name, DatetimePicker) Vue.component(Picker.name, Picker); Vue.component(loadmore.name, loadmore); Vue.component(Range.name, Range); Vue.component(Progress.name, Progress); Vue.component(Switch.name, Switch);
Secondary encapsulation of MT loadmore components

The pull-down refresh and pull-up load of the list are more necessary components of the mobile terminal. But there is something wrong with mt's loadmore component, so I have a layer to make it more
Clear, easy to use

Secondary packaging features

  • Simulation iphone click on the top to scroll through the list to the top.
  • No need to write dead height, and compatible with iPhone x
  • It provides a more concise and easy-to-use method for refreshing, returning to the top, obtaining and setting the scroll bar position
  • Unified UI prompt, without repeating css code.

Code

<template> <div ref="loadBox"> <mt-loadmore :topMethod="topMethod" :bottomMethod="bottomMethod" :topPullText="`Drop-down refresh`" :bottomPullText="`Pull up to load more`" :autoFill="false" :bottomDistance="40" :topDistance="60" :bottomAllLoaded="bottomAllLoaded" ref="loadmore"> <ul v-if="rows.length>0"> <slot v-for="(item,index) in rows" v-bind=""></slot> </ul> <ul v-else> <li>{}</li> </ul> </mt-loadmore> </div> </template> <script> import Bus from "../common/bus.js" export default { data: function () { return { rows: [], loadingText: '', total: 0, bottomAllLoaded:false, timer:null, search: { page: 1, size: 10, }, } }, props: { top:{ type:[Number,String], default:0 }, bottom:{ type:[Number,String], default:0 }, itemProcess:{ //List item handler type:Function, default:null }, url:{ type:String, default:"" }, param:{ //Query parameters type:Object, default:{} }, type:{ //Configuring ajax method types type:String, default:"get" }, dataKey:{ //key to read interface data type:String, default:"content" }, clickToTop:{ //Do you want to click the top to return to the start type:Boolean, default:true, }, }, watch:{ rows(val){ this.$emit('change',val); } }, mounted(){ setTimeout( ()=>{ var myDiv = document.getElementsByClassName('mobile-top')[0]; //Get style through different methods by judging whether it supports currentStyle (ie or not) var finalStyle = myDiv.currentStyle ? myDiv.currentStyle : document.defaultView.getComputedStyle(myDiv, null); //More paddingTop for iPhone x var iphoneXPT = parseInt(finalStyle.paddingTop)==20?0:parseInt(finalStyle.paddingTop)-20; this.$refs.loadBox.style.top = parseInt(this.top) + iphoneXPT +"px"; this.$refs.loadBox.style.bottom = parseInt(this.bottom) + iphoneXPT +"px"; },100) //Delayed execution, fixed can't get the bug of paddingTop this.search = Object.assign(this.search,this.param); this.upData(); if(this.clickToTop){ Bus.$on('toTop', () => { this.toTop(); }) } }, watch:{ param(val){ this.search = Object.assign(this.search,val); } }, methods:{ upData(data) { /*If the parameter is an object, the watch update param will update the method and then execute, resulting in inaccurate bug of parameter merging*/ return new Promise((resolve,reject)=>{ setTimeout(()=>{ this.loadingText = "Loading..."; var query = Object.assign(this.search, data); return this.$http({ url: this.url, data: query, type:this.type, loading:false, }).then(res => { let rows = res[this.dataKey]; this.total = res.total; if (rows.length > 0) { if(typeof this.itemProcess == 'function'){ rows = this.itemProcess(rows); } this.rows = this.rows.concat(rows); } if (this.rows.length == 0) { this.loadingText = "No data" } resolve(true) }) },100) }) }, //Drop-down refresh topMethod() { this.bottomAllLoaded = false; this.rows = []; this.upData({ page: 1 }).then(res => { if (res) { this.ToastTip("Refresh success", 'suc'); this.$refs.loadmore.onTopLoaded(); } }) }, //Pull up to load more bottomMethod() { if (this.rows.length < this.total) { this.bottomAllLoaded = false; this.upData({ page: ++this.search.page }).then(()=>{ this.$refs.loadmore.onBottomLoaded(); }) } else { this.bottomAllLoaded = true; this.ToastTip("No more data!") this.$refs.loadmore.onBottomLoaded(); } }, refresh(){ this.bottomAllLoaded = false; this.rows = []; this.upData({ page: 1 }).then(res => { if (res) { this.$refs.loadmore.onTopLoaded(); } }) }, //External control pull-up refresh allLoad(bool){ this.bottomAllLoaded = bool; }, //wipe data clearData(){ this.rows = []; }, //Handle item functions to facilitate the parent component's operation on list items processData(callBack){ callBack(this.rows); }, //Click the top title to scroll to the beginning of the list toTop(){ var app = document.getElementsByClassName('scrolling')[0]||document.getElementsByTagName('body')[0]; app.className ="";/*fix bug of page shaking caused by inertial sliding of mobile terminal*/ clearInterval(this.timer); this.timer =setInterval(()=>{ var scrollTop= this.$el.scrollTop; var ispeed=Math.floor(-scrollTop/8); if(scrollTop==0){ app.className ="scrolling"; clearInterval(this.timer); } this.$el.scrollTop = scrollTop+ispeed; },10); /*fix When the pull-up is not completed, the list will be pulled, resulting in repeated bug s*/ document.addEventListener('touchstart',(ev)=>{ if(this.$refs['loadBox']&&this.$refs['loadBox'].contains(ev.changedTouches[0].target)){ app.className ="scrolling"; clearInterval(this.timer); } }) }, //Get current scroll position getPosition(){ return this.$el.scrollTop; }, //Set scroll position setPosition(position=0){ this.$el.scrollTop = position; } } } </script> <style lang="scss" scoped> .loader-more { padding-bottom: 0.2rem; background-color: #fff; overflow-y: auto; /*position: fixed;*/ position: absolute; left: 0; right: 0; box-sizing: border-box; } </style>

Use

<myLoadMore :url="ajaxApi.docSearch.draft" :param="param" top="65px" ref="myLoadMore" :itemProcess="itemProcess"> <li slot-scope="" :key="item.id" @click="toDetail(item.id,item.serviceCode)"> <div>{}</div> </li> </myLoadMore> //List out functions itemProcess(rows) { rows.forEach(item => { item.time= new Date().getTime(); }) return rows },
mySelect component

The mobile select component is actually the combination of pop.bottom + picker;

Code

<template> <div> <div @click="show"> <span style="margin-right: 10px;">{}</span> <v-icon name="chevron-down"></v-icon> </div> <mt-popup v-model="popupVisible" position="bottom" style="width: 100%;" :closeOnClickModal="false"> <div> <span @click="cancel">cancel</span> <span @click="selected">Determine</span> </div> <mt-picker v-show="popupVisible" :slots="slots" @change="onValuesChange" :value-key="keyName" ref="picker" :visibleItemCount="visibleItemCount"> </mt-picker> </mt-popup> </div> </template> <script> export default { data: function () { return { popupVisible: false, name:'', value:'', oldName:'', oldValue:'', defaultItem:null, slots: [{ values:[], defaultIndex: 0, }], } }, model:{ prop:'selectValue', event:'change' }, props: { selectValue:{ type:[Number,String] }, dataArr: { type: Array, default: function () { return [] } }, keyName:{ //Display name type:String, default:'name' }, keyValue:{ type:String, default:'value' }, visibleItemCount:{ type:Number, default:5 }, defaultIndex:{//Default selection type:Number, default:0 } }, watch:{ popupVisible(val){ var bottom = document.getElementsByClassName("mobile-bottom"); if(val){ for(var i=0;i<bottom.length;i++){ bottom[i].style.display = "none"; } } else { for(var i=0;i<bottom.length;i++){ bottom[i].style.display = "flex"; } } }, }, created() { this.slots[0].values = this.dataArr; this.slots[0].defaultIndex = this.defaultIndex; this.defaultItem = { name:this.slots[0].values[this.defaultIndex][this.keyName], value:this.slots[0].values[this.defaultIndex][this.keyValue], }; }, methods: { show(){ this.oldName = this.name; this.oldValue = this.value; this.noScrollAfter.open(this,`popupVisible`) }, cancel(){ this.name = this.oldName; this.value = this.oldValue; this.popupVisible=false; }, selected(){ this.noScrollAfter.close(this,`popupVisible`) this.oldName = this.name; this.oldValue = this.value; this.$emit('change',this.value);//Pass value to the father this.$emit('select',) }, onValuesChange(picker, values) { this.name = values[0][this.keyName]; this.value = values[0][this.keyValue]; }, set(index){ //Set the selected value index let theIndex = index || this.defaultIndex; this.name = this.slots[0].values[theIndex][this.keyName]; this.value = this.slots[0].values[theIndex][this.keyValue]; this.slots[0].defaultIndex = index; this.selected();//Synchronize parent component data; }, } } </script> <style lang="scss" scoped> .selected{ padding: 0.1rem; text-align: right; display: flex; align-items: center; justify-content: flex-end; } .selected-box{ user-select: none; z-index: 3000!important; position:fixed; right: 0; bottom: 0; } .picker-toolbar{ height: 40px; border-bottom: solid 1px #eaeaea; color: #26a2ff; } </style>

Use

<my-select :dataArr="leaveTypeData" keyName="enumerationName" keyValue="enumerationCode" v-model="leaveType" ref="mySelect" @select="select"> </my-select> //Set select this.$refs['mySelect'].setTime(index);
Encapsulate the pop component

The pop-up component is generally configured with position to slide in or out of the bottom or pop-up window in the middle. The only harm is that if your page has a lot of pop ups, you have to set many variables true/false to control pop ups. So here I encapsulate it.

  • Reduce css code and component configuration
  • Reduce declaration controls hidden variables

Realization

<!--encapsulation mint-ui It does not need to define variables and methods one by one to control the display and hiding of the pop-up window * position: right Draw the pop-up window from the right * radius: Whether to fillet the pop-up window * To open a pop-up window: this.$refs[`What you define popup Of ref`].open() * Close the pop-up window: this.$refs[`What you define popup Of ref`].close() --> <template> <mt-popup v-model="visible" :class="" :modal="radius" :closeOnClickModal="false" :popup-transition="radius?`popup-fade`:``" :position="position"> <slot></slot> </mt-popup> </template> <script> export default { data: function () { return { visible: false } }, props:{ position:{ type:String, default:"" }, radius:{ type:Boolean, default:true } }, methods:{ open(){ this.noScrollAfter.open(this,`visible`) }, close(){ this.noScrollAfter.close(this,`visible`) }, state(){ return this.visible; } } } </script> <style lang="scss" scoped> </style>

Use

<popup ref="exceptionFlow" position="right" :radius="false"> xxxx </popup> //open this.$refs['exceptionFlow'].open(); //Close this.$refs['exceptionFlow'].close();

The value of post is the same as that of mint

Time control encapsulation

The time control of mint is also cumbersome to use, and it has also made a secondary encapsulation, which mainly has the following characteristics

  • Get the time value string directly
  • Automatic binding of open and close methods
  • Cancel and save function added
  • Support initialization time and dynamically set time value

Code

<template> <div> <div> <div v-show="confirmTimeStart" @click="open"> <v-icon v-if="delTime" v-show="confirmTimeStart" name="x-circle" @click.native.stop="confirmTimeStart = false"></v-icon> {} </div> <div v-show="!confirmTimeStart" @click="open"></div> <v-icon name="calendar" @click.native="open"></v-icon> </div> <mt-datetime-picker ref="timePicker" :type="dateType" @cancel=" timeStart = oldTimeStart;close();" @visible-change="oldTimeStart = timeStart;$emit(`timeChange`)" @confirm="confirmTime" v-model="timeStart"> </mt-datetime-picker> </div> </template> <script> export default { data: function () { return { timeStart:new Date(), confirmTimeStart:false, } }, model:{ prop:'time', events:'change', }, props:{ dateType:{ //Time control type type:String, default:"date", }, initDate:{//Default to initialize and confirm today type:Boolean, default:false, }, time:{ type:String, default:'' }, delTime:{ //Whether to display the clear time button type:Boolean, default:true, } }, watch:{ //Confirm selection time and cancel confirmTimeStart(val){ if(val){ this.$emit("confirm",this.timeStartFmt); }else{ this.$emit("confirm",""); } } }, computed: { //Format time timeStartFmt() { let fmt = this.dateType=="date"?"yyyy-MM-dd":null; return this.tools.dateFmt(this.timeStart,fmt); }, }, mounted(){ if(this.initDate){ this.confirmTime(); } }, methods:{ //When changing time; confirmTime(){ this.confirmTimeStart = true; this.$emit("confirm",this.timeStartFmt); this.close(); }, /** * Author: lzh * Function: set the time for the method called by the parent component to cooperate with ref; * Parameter: val dateobj * Return value: */ setTime(val){ this.timeStart = val; this.confirmTimeStart =val!==""?true:false; }, open(){ var bottom = document.getElementsByClassName("mobile-bottom"); this.$refs[`timePicker`].open(); for(var i=0;i<bottom.length;i++){ bottom[i].style.display = "none"; } }, close(){ var bottom = document.getElementsByClassName("mobile-bottom"); for(var i=0;i<bottom.length;i++){ bottom[i].style.display = "flex"; } }, } } </script> <style lang="scss" scoped> .timer{ .item-content{ width: 100%; height: 30px; display: flex; justify-content: space-between; align-items: center; .item-content-div{ flex:10; border: 1px solid #eaeaea; padding: 5px 25px 5px 5px; box-sizing: border-box; height: 100%; position:relative; .item-content-icon{ position:absolute; right:5px; color: #d8d8d8; } } .icon { margin-left: 10px; width: 17px; height: 17px; } } } </style>

Use

<timer @confirm="(val)=>"></timer>
Package upload picture component

Uploading pictures is also a common component, which is implemented here.

Code

<!--Upload attachments--> <template> <div> <form-card-item itemTitle="Upload attachment:" :required="required"> <input ref="uploadInput" type="file" @change="upload" style="padding-right: 0.5rem;"> <v-icon v-show="uploading" name="x-circle" @click.native.stop="clearFile"></v-icon> <progressDom ref="progressId"></progressDom> </form-card-item> <adjunct ref="list" @delFile="del"></adjunct> </div> </template> <script> import qs from "qs" import axios from "axios" export default { data: function () { return { all:'all', pic:["jpg","jpeg","gif","png"], gzip:["zip","rar"], uploading:false, } }, model:{ prop:'adjunct', event:'change' }, props:{ adjunct:{ //Number of uploaded attachments type:Number, default:0, }, data:{ type:Object, default:()=> } }, types:{ type:String, default:"all" }, required:{ type:Boolean, default:false, }, saveParam:{ type:Object, default:()=> } } }, methods: { upload() { let file = this.$refs[`uploadInput`].files[0]; if (!file){ this.$emit('change',false); return; }; let type = this[this.types]; if(type!=='all'&&type.indexOf(file.type.split(`/`)[1])==-1){ this.ToastTip("Please upload the following types of attachments: "+type.join(","), "warn",5000); this.$refs[`uploadInput`].value = ""; return; } if (file.size /(1024*1024) > 50) { //size is BT unit 1kb = 1024bt; this.ToastTip("Please upload 50 M Pictures up to", "warn"); this.$refs[`uploadInput`].value = ""; return; } let form = new FormData(); form.append("file", file); let actionUrl = process.env.proxyString + this.ajaxApi.attachment.upload + '?' + qs.stringify(this.saveParam); this.$refs[`progressId`].start(); this.uploading = true; axios.post(actionUrl, form).then((res) => { if (res.status==200&&res.data) { this.ToastTip("Attachment uploaded successfully","suc"); this.updateList(); this.$refs[`uploadInput`].value = ""; let num = this.adjunct+1; this.$emit('change',num); this.$emit("success"); } else { let msg = data.msg||data.messages||"Upload error"; this.$refs[`uploadInput`].value = ""; this.ToastTip(msg, "warn"); } this.$refs[`progressId`].stop(); this.uploading = false; }).catch(res=>{ console.log(res) }) }, clearFile(){ this.$refs[`uploadInput`].value = ""; this.$refs[`progressId`].stop(); this.uploading = false; }, del(length){ this.$emit('change',length);//Number of overwriting attachments }, updateList(){ if(this.saveParam&&this.saveParam.docid){ this.$refs['list'].updateList({ url:this.ajaxApi.attachment.attachmentList, type:'post', data: { docid:this.saveParam.docid, tid:this.saveParam.taskId, device:'mobile', service:this.saveParam.service } }); } } } } </script> <style lang="scss" scoped> .box.form-item{ padding-top: 16px; padding-bottom: 16px; } .box /deep/{ .form-item-value{ position: relative; } .stop { margin-left: 10px; width: 17px; height: 17px; position:absolute; right: 18px; top: 12px; color: #d8d8d8; } } </style> * adjunct.vue <!--Document attachment--> <template> <form-card :title="title" v-show="list.length>0"> <v-icon name="paperclip" slot="title-icon" style="color:#8a8a8a;margin-right: 0.1rem;"></v-icon> <form-card-item v-if="list.length>0" v-for="item in list" :itemTitle="item.name" :key="item.id"> <icon icon-class="icon-huixingzhen" color="#59a5ff" size="20" slot="before-title-icon"></icon> <icon v-show="icon==`download`" icon-class="icon-xiazai1" color="#306bd3" size="28" @click.native="download(item)"></icon> <v-icon v-show="icon==`del`" name="trash-2" style="color:#8a8a8a;margin-top: 10px;" @click.native="del(item)"></v-icon> </form-card-item> </form-card> </template> <script> export default { data: function () { return { list:[] } }, props:{ title:{ type:String, default:'Document attachment' }, icon:{ type:String, default:'del' } }, methods:{ updateList(param){ this.$http(param).then(res=>{ this.list = res.files; this.$emit('delFile',this.list.length); }) }, del(item){ this.MessageBox({ closeOnClickModal:false, showCancelButton:true, confirmButtonText:'Determine', title:'Delete files', message:'Are you sure you want to delete this file?', }).then((res)=>{ if(res=="confirm"){ this.$http({ url:this.ajaxApi.attachment.delAttachment, type:"post", data:{ docid:item.documentId, fileId:item.id } }).then(res=>{ this.ToastTip(res.result,'suc'); this.list.splice(this.list.findIndex(o=>{ return o.id == item.id }),1); this.$emit('delFile',this.list.length); }) }; }) }, download(item){ } }, } </script> <style lang="scss" scoped> .mb20 /deep/ .form-item{ .form-item-value{ width: auto; } } .list{ /deep/ .form-item-title{ word-break: break-all; max-width: 6rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; } } </style>

Use

<!--Upload attachments--> <uploadFile ref="uploadFile" :saveParam="saveParam" v-model="adjunct" :required="true"> </uploadFile>

Effect

4 December 2019, 06:12 | Views: 7970

Add new comment

For adding a comment, please log in
or create account

0 comments