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
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>