标签事件详情页面数据交互,网格员审核列表与审核功能数据交互
10 files modified
43 files added
| | |
| | | } |
| | | |
| | | //获取取保候审报事列表 |
| | | export const geteBailReportingList = (params) => { |
| | | export const getBailReportingList = (params) => { |
| | | return http.request({ |
| | | url: '/blade-taskBailReportingEvent/taskBailReportingEvent/page', |
| | | method: 'get', |
| | |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | //审核列表 |
| | | export const getAuditReportingList = (params) => { |
| | | return http.request({ |
| | | url: '/blade-task/task/page', |
| | | method: 'get', |
| | | params: { |
| | | ...params |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //获取旅馆安全详情 |
| | | export const getHotelReportingDetail = (params) => { |
| | | return http.request({ |
| | | url: '/blade-taskHotelReporting/taskHotelReporting/detail', |
| | | method: 'get', |
| | | params: { |
| | | ...params |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //获取取保候审详情 |
| | | export const getBailReportingDetail = (params) => { |
| | | return http.request({ |
| | | url: '/blade-taskBailReportingEvent/taskBailReportingEvent/detail', |
| | | method: 'get', |
| | | params: { |
| | | ...params |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //获取打金店、二手车交易、二手手机维修详情 |
| | | export const getLabelReportingDetail = (params) => { |
| | | return http.request({ |
| | | url: '/blade-taskLabelReportingEvent/taskLabelReportingEvent/detail', |
| | | method: 'get', |
| | | params: { |
| | | ...params |
| | | } |
| | | }) |
| | | } |
| | | |
| | | //修改旅店安全 |
| | | export const setHotelReporting = (data) => { |
| | | return http.request({ |
| | | url: '/blade-task/task/update', |
| | | method: 'POST', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | //修改取保候审 |
| | | export const setBailReporting = (data) => { |
| | | return http.request({ |
| | | url: '/blade-taskBailReportingEvent/taskBailReportingEvent/update', |
| | | method: 'POST', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | | //修改打金店、二手车交易、二手手机维修 |
| | | export const setLabelReporting = (data) => { |
| | | return http.request({ |
| | | url: '/blade-taskLabelReportingEvent/taskLabelReportingEvent/update', |
| | | method: 'POST', |
| | | data: data |
| | | }) |
| | | } |
| | | |
| | |
| | | // devUrl: 'https://sk.hubeishuiyi.cn', |
| | | // devUrl: 'http://192.168.1.156:9528', |
| | | // devUrl:'http://192.168.1.50:9528', |
| | | devUrl: 'http://192.168.0.101:9528', |
| | | devUrl: 'http://192.168.0.102:9528', |
| | | minioBaseUrl: "http://60.220.177.113:9000/jczz/", |
| | | // 数据中台接口url |
| | | // dataCenterUrl: 'http://10.10.2.192/services', |
| | |
| | | "uniStatistics" : { |
| | | "enable" : false |
| | | }, |
| | | "requiredPrivateInfos" : [ "getLocation" ], |
| | | "requiredPrivateInfos" : [ "getLocation","chooseLocation" ], |
| | | "permission" : { |
| | | "scope.userLocation" : { |
| | | "desc" : "定位" |
| | |
| | | "navigationBarBackgroundColor": "#fff", |
| | | "navigationBarTextStyle": "black" |
| | | } |
| | | }, |
| | | { |
| | | "path": "views/hotelReportDetail", |
| | | "style": { |
| | | "navigationBarTitleText": "审核", |
| | | "enablePullDownRefresh": false, |
| | | "navigationBarBackgroundColor": "#fff", |
| | | "navigationBarTextStyle": "black" |
| | | } |
| | | }, |
| | | { |
| | | "path": "views/bailReportDetail", |
| | | "style": { |
| | | "navigationBarTitleText": "审核", |
| | | "enablePullDownRefresh": false, |
| | | "navigationBarBackgroundColor": "#fff", |
| | | "navigationBarTextStyle": "black" |
| | | } |
| | | }, |
| | | { |
| | | "path": "views/labelReportDetail", |
| | | "style": { |
| | | "navigationBarTitleText": "审核", |
| | | "enablePullDownRefresh": false, |
| | | "navigationBarBackgroundColor": "#fff", |
| | | "navigationBarTextStyle": "black" |
| | | } |
| | | } |
| | | ] |
| | | }, |
| | |
| | | <u-swiper :list="swiperList" height="320rpx"></u-swiper>
|
| | | </view>
|
| | | <view class="" v-if="roleType != 1">
|
| | | <u-grid :border="false" :col="roleType == 2?3:4">
|
| | | <u-grid :border="false" :col="4">
|
| | | <u-grid-item v-for="(i,k) in cellList" :key="k" @click.native="toPage(i)">
|
| | | <u-icon :name="i.icon" :size="45"></u-icon>
|
| | | <text class="f-26 mt-20">{{i.text}}</text>
|
| | | </u-grid-item>
|
| | | <u-grid-item @click.native="navTo()" v-if="roleType == 3">
|
| | | <u-grid-item @click.native="navTo()" v-if="roleType == 2">
|
| | | <u-icon name="/static/icon/nav-04.png" width="90rpx" height="90rpx"></u-icon>
|
| | | <text class="f-26 mt-20">租客上报</text>
|
| | | </u-grid-item>
|
| | |
| | | </u-grid>
|
| | | </view>
|
| | |
|
| | | <view class="mt-40" v-if="roleType != 3">
|
| | | <view class="mt-40" v-if="roleType != 2">
|
| | | <view class="caption">
|
| | | <view class="flex a-i-c">
|
| | | <view class="line"></view>
|
| | |
| | | </u-cell-group>
|
| | | </view>
|
| | |
|
| | | <view class="notic" v-if="roleType == 3">
|
| | | <view class="notic" v-if="roleType == 2">
|
| | | <view class="caption flex a-i-c j-c-s-b" @click="navigatorPage('/subPackage/notice/list')">
|
| | | <view class="flex a-i-c">
|
| | | <view class="line"></view>
|
| | |
| | | status: "待处理0"
|
| | | }
|
| | | ],
|
| | | roleType: 3, // 1网格员/系统管理员 、2场所负责人、 3居民
|
| | | roleType: 2, // 1网格员/系统管理员 、 2居民
|
| | | roleTypeName: "街道社区网格",
|
| | | selectBoxShow: false,
|
| | | siteColumns: [],
|
| | |
| | | <template> |
| | | <view class=""> |
| | | <view class="list"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k" @click="navTo(i.taskId)"> |
| | | <view class="item-title flex a-i-c j-c-s-b mb-20"> |
| | | <text class="f-32 fw">{{i.checkUserName}}</text> |
| | | <u-tag v-if="i.confirmFlag == 1" text="待审批" type="warning" plain plainFill></u-tag> |
| | |
| | | console.log(res) |
| | | this.list = res.data.records; |
| | | }) |
| | | }, |
| | | |
| | | navTo(id){ |
| | | uni.navigateTo({ |
| | | url:`/subPackage/workbench/views/bailReportDetail?id=${id}` |
| | | }) |
| | | } |
| | | |
| | | } |
| | | } |
| | | </script> |
| | |
| | | <template> |
| | | <view class=""> |
| | | <view class="list"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k" @click="navTo(i.taskId)"> |
| | | <view class="item-title flex a-i-c j-c-s-b mb-20"> |
| | | <text class="f-32 fw">{{i.hotelName}}</text> |
| | | <u-tag v-if="i.confirmFlag == 1" text="待审批" type="warning" plain plainFill></u-tag> |
| | |
| | | console.log(res) |
| | | this.list = res.data.records; |
| | | }) |
| | | }, |
| | | navTo(id){ |
| | | uni.navigateTo({ |
| | | url:`/subPackage/workbench/views/bailReportDetail?id=${id}` |
| | | }) |
| | | } |
| | | } |
| | | } |
| | |
| | | <template> |
| | | <view class=""> |
| | | <view class="list"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k" @click="navTo(i.taskId)"> |
| | | <view class="item-title flex a-i-c j-c-s-b mb-20"> |
| | | <text class="f-32 fw">{{i.hotelName}}</text> |
| | | <u-tag v-if="i.confirmFlag == 1" text="待审批" type="warning" plain plainFill></u-tag> |
| | |
| | | console.log(res) |
| | | this.list = res.data.records; |
| | | }) |
| | | }, |
| | | navTo(id){ |
| | | uni.navigateTo({ |
| | | url:`/subPackage/workbench/views/hotelReportDetail?id=${id}` |
| | | }) |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | onLoad(){ |
| | | this.siteInfo = uni.getStorageSync("siteInfo"); |
| | | }, |
| | | |
| | | onShow() { |
| | | this.getCount(uni.getStorageSync("siteInfo").id); |
| | | }, |
| | | |
| | | methods:{ |
| | | getCount(code){ |
| | | getReportingCount({houseCode:code}).then(res=>{ |
| | |
| | | <u-tabs :list="tabList" :current="tabIndex" @click="changeTab" :inactiveStyle="{color:'#999999'}" :activeStyle="{color:'#017BFC'}"></u-tabs> |
| | | </view> |
| | | <view class="list"> |
| | | <view class="list-item bgc-ff mb-20" v-for="i in 3"> |
| | | <view class="list-item bgc-ff mb-20" v-for="(i,k) in list" :key="k" @click="navTo(i.name,i.id)"> |
| | | <view class="item-title flex a-i-c j-c-s-b mb-20"> |
| | | <text class="f-32 fw">名称</text> |
| | | <!-- <u-tag text="待审批" type="warning" plain plainFill></u-tag> --> |
| | | <u-tag text="审核通过" type="success" plain plainFill></u-tag> |
| | | <!-- <u-tag text="审核拒绝" type="error" plain plainFill></u-tag> --> |
| | | <text class="f-32 fw">{{i.name}}</text> |
| | | <u-tag v-if="i.status == 1" text="待审批" type="warning" plain plainFill></u-tag> |
| | | <u-tag v-if="i.status == 2" text="审核通过" type="success" plain plainFill></u-tag> |
| | | <u-tag v-if="i.status == 3" text="审核拒绝" type="error" plain plainFill></u-tag> |
| | | </view> |
| | | <view class="item-row flex a-i-c j-c-s-b"> |
| | | <text class="f-28">事件类型</text> |
| | | <text class="f-28 c-66">一次性任务</text> |
| | | </view> |
| | | <view class="item-row flex a-i-c j-c-s-b"> |
| | | <text class="f-28">标签</text> |
| | | <text class="f-28 c-66">打金店</text> |
| | | <text class="f-28">时间</text> |
| | | <text class="f-28 c-66">{{i.createTime}}</text> |
| | | </view> |
| | | <view class="item-row flex a-i-c j-c-s-b"> |
| | | <text class="f-28">地址</text> |
| | | <text class="address f-28 c-66">西市街道万达社区居民委员会滨江西路66号万达晶座11栋303室</text> |
| | | <text class="address f-28 c-66">{{i.addressName}}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | </template> |
| | | |
| | | <script> |
| | | import { getAuditReportingList } from "@/api/reporting/reporting" |
| | | export default { |
| | | data(){ |
| | | return { |
| | |
| | | status:3 |
| | | }, |
| | | ], |
| | | tabIndex:0 |
| | | tabIndex:0, |
| | | currentStatus:1, |
| | | list:[] |
| | | } |
| | | }, |
| | | |
| | | onLoad(){ |
| | | this.getList() |
| | | }, |
| | | |
| | | methods:{ |
| | | changeTab(e){ |
| | | this.tabIndex = e.index; |
| | | this.currentStatus = e.status; |
| | | this.getList(); |
| | | }, |
| | | getList(){ |
| | | getAuditReportingList({ |
| | | page:1, |
| | | size:20, |
| | | status:this.currentStatus |
| | | }).then(res=>{ |
| | | this.list = res.data.records; |
| | | }) |
| | | }, |
| | | navTo(name,id){ |
| | | if(name == "旅馆安全"){ |
| | | this.$u.func.globalNavigator(`hotelReportDetail?id=${id}`) |
| | | }else if (name == "取保候审"){ |
| | | this.$u.func.globalNavigator(`bailReportDetail?id=${id}`) |
| | | }else{ |
| | | this.$u.func.globalNavigator(`labelReportDetail?id=${id}`) |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <view class="basic"> |
| | | <box-title title="基本信息" class="box-title"></box-title> |
| | | <view class="info"> |
| | | <lineItem :dataInfo="basicData"></lineItem> |
| | | </view> |
| | | </view> |
| | | <view class="licence" v-if="locationImageUrls.length"> |
| | | <box-title title="位置图片" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in locationImageUrls" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <view class="item mt-30"> |
| | | <u-form labelPosition="left" :model="info" ref="form" labelWidth="100" |
| | | :labelStyle="{fontSize:'28rpx'}"> |
| | | <u-form-item label="审核结果" prop="confirmFlag" borderBottom @click="showPicker()"> |
| | | <u-input v-model="confirmFlag" disabled disabledColor="#ffffff" border="none" placeholder="请输入" |
| | | placeholderClass="f-28 c-99" inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | <u-form-item label="备注" borderBottom> |
| | | <u--input type="textarea" v-model="info.confirmNotion" :disabled="isDisabled" |
| | | disabledColor="#ffffff" placeholderClass="f-28 c-99" border="none" inputAlign="right"></u--input> |
| | | |
| | | </u-form-item> |
| | | <u-form-item label="审核人员" prop="checkUserName" borderBottom > |
| | | <u-input v-model="info.confirmUserName" :disabled="isDisabled" disabledColor="#ffffff" border="none" |
| | | placeholder="请输入审核人员姓名" placeholderClass="f-28 c-99" inputAlign="right"></u-input> |
| | | </u-form-item> |
| | | <u-form-item label="电话号码" prop="confirmUserTelephone" borderBottom > |
| | | <u-input v-model="info.confirmUserTelephone" :disabled="isDisabled" disabledColor="#ffffff" |
| | | border="none" placeholder="请输入审核人员电话号码" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | </u-form-item> |
| | | </u-form> |
| | | </view> |
| | | |
| | | <view v-if="status == 2 && roleType == 2"> |
| | | <u-subsection :list="tabList" :current="tabIndex" button-color="#017BFC" @change="changeTab"></u-subsection> |
| | | <u-form labelPosition="left" :model="goOutInfo" ref="form" labelWidth="100" |
| | | :labelStyle="{fontSize:'28rpx'}"> |
| | | <view class="" v-if="tabIndex == 0"> |
| | | <u-form-item label="当前时间" prop="checkUserName" borderBottom @click="showPickerDate"> |
| | | <u-input v-model="goOutInfo.startTime" disabled disabledColor="#ffffff" |
| | | border="none" placeholder="请获取当前时间" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | <u-form-item label="报备位置" prop="checkUserName" borderBottom @click="getLocation('returnLocation')"> |
| | | <u-input v-model="goOutInfo.startLocation" disabled disabledColor="#ffffff" |
| | | border="none" placeholder="请获取当前位置" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | </view> |
| | | <view class="" v-if="tabIndex == 1"> |
| | | <u-form-item label="到达时间" prop="reachTime" borderBottom @click="showPickerDate"> |
| | | <u-input v-model="goOutInfo.reachTime" disabled disabledColor="#ffffff" |
| | | border="none" placeholder="请获取当前时间" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | <u-form-item label="到达位置" prop="reachLocation" borderBottom @click="getLocation('returnLocation')"> |
| | | <u-input v-model="goOutInfo.reachLocation" disabled disabledColor="#ffffff" |
| | | border="none" placeholder="请获取当前位置" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | </view> |
| | | <view class="" v-if="tabIndex == 2"> |
| | | <u-form-item label="返回时间" prop="returnTime" borderBottom @click="showPickerDate"> |
| | | <u-input v-model="goOutInfo.returnTime" disabled disabledColor="#ffffff" |
| | | border="none" placeholder="请获取返回时间" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | <u-form-item label="返回位置" prop="returnLocation" borderBottom @click="getLocation('returnLocation')"> |
| | | <u-input v-model="goOutInfo.returnLocation" disabled disabledColor="#ffffff" |
| | | border="none" placeholder="请获取返回位置" placeholderClass="f-28 c-99" |
| | | inputAlign="right"></u-input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | </view> |
| | | </u-form> |
| | | <view class="upload bgc-ff"> |
| | | <view class="f-28 mb-20">位置图片</view> |
| | | <view class="mt-20" v-if="tabIndex == 0"> |
| | | <u-upload :fileList="startImageUrls" :previewFullImage="uploadConfig.previewFullImage" |
| | | :accept="uploadConfig.acceptImg" :multiple="uploadConfig.multiple" |
| | | :maxCount="uploadConfig.maxCount" :capture="uploadConfig.capture" @afterRead="afterReadImgs($event,'startImageUrls')" |
| | | @delete="deletePic"> |
| | | <view class="upload-item upload-icon flex_base"> |
| | | <u-icon name="/static/icon/upload.png" width="60rpx" height="60rpx"></u-icon> |
| | | </view> |
| | | </u-upload> |
| | | </view> |
| | | <view class="mt-20" v-if="tabIndex == 1"> |
| | | <u-upload :fileList="reachImageUrls" :previewFullImage="uploadConfig.previewFullImage" |
| | | :accept="uploadConfig.acceptImg" :multiple="uploadConfig.multiple" |
| | | :maxCount="uploadConfig.maxCount" :capture="uploadConfig.capture" @afterRead="afterReadImgs($event,'reachImageUrls')" |
| | | @delete="deletePic"> |
| | | <view class="upload-item upload-icon flex_base"> |
| | | <u-icon name="/static/icon/upload.png" width="60rpx" height="60rpx"></u-icon> |
| | | </view> |
| | | </u-upload> |
| | | </view> |
| | | <view class="mt-20" v-if="tabIndex == 2"> |
| | | <u-upload :fileList="returnImageUrls" :previewFullImage="uploadConfig.previewFullImage" |
| | | :accept="uploadConfig.acceptImg" :multiple="uploadConfig.multiple" |
| | | :maxCount="uploadConfig.maxCount" :capture="uploadConfig.capture" @afterRead="afterReadImgs($event,'returnImageUrls')" |
| | | @delete="deletePic"> |
| | | <view class="upload-item upload-icon flex_base"> |
| | | <u-icon name="/static/icon/upload.png" width="60rpx" height="60rpx"></u-icon> |
| | | </view> |
| | | </u-upload> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <view class="bottom-btn"> |
| | | <u-button type="primary" @click="submit">提交审核</u-button> |
| | | </view> |
| | | <u-datetime-picker ref="datetimePicker" :show="showSelectDate" v-model="goOutDate[tabIndex]" mode="datetime" |
| | | :formatter="formatter" @confirm="confirmDate" @cancel="cancelPickerDate"></u-datetime-picker> |
| | | <u-picker :closeOnClickOverlay="true" @close="isPickerShow = false" :show="isPickerShow" ref="uPicker" |
| | | :columns="columns" keyName="name" @cancel="isPickerShow = false" @confirm="handleConfirm"></u-picker> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import boxTitle from '@/components/boxTitle/index.vue' |
| | | import lineItem from '../components/lineItem.vue' |
| | | import uploadMixin from "@/mixin/uploadMixin"; |
| | | import { |
| | | getBailReportingDetail, |
| | | setBailReporting |
| | | } from '@/api/reporting/reporting' |
| | | export default { |
| | | components: { |
| | | boxTitle, |
| | | lineItem |
| | | }, |
| | | mixins: [uploadMixin], |
| | | data() { |
| | | return { |
| | | basicData: [{ |
| | | label: '姓名', |
| | | name: "checkUserName", |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '手机', |
| | | name: 'checkTelephone', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '申请名称', |
| | | name: 'applyName', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '申请时间', |
| | | name: 'applyTime', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '申请位置', |
| | | name: 'location', |
| | | value: '未完善' |
| | | } |
| | | ], |
| | | confirmFlag: "待审核", |
| | | isPickerShow: false, |
| | | columns: [ |
| | | [{ |
| | | name: "待审核", |
| | | status: 1 |
| | | }, |
| | | { |
| | | name: "审核通过", |
| | | status: 2 |
| | | }, |
| | | { |
| | | name: "审核不通过", |
| | | status: 3 |
| | | }, |
| | | ] |
| | | ], |
| | | currentId: '', |
| | | currentData: {}, |
| | | taskId: "", |
| | | locationImageUrls: [], |
| | | selectStatus: "", |
| | | remark: "", |
| | | isDisabled: false, |
| | | info: { |
| | | confirmFlag: 1, |
| | | confirmUserName: "", |
| | | confirmUserTelephone: "" |
| | | }, |
| | | goOutInfo: { |
| | | startTime: "", |
| | | startLocation: "", |
| | | reachTime: "", |
| | | reachLocation: "", |
| | | returnTime: "", |
| | | returnLocation: "" |
| | | }, |
| | | startImageUrls: [], //出发位置图片, |
| | | reachImageUrls: [], //到达位置图片 |
| | | returnImageUrls: [], //返回位置图片 |
| | | roleInfo: {}, |
| | | roleType: "", //1系统管理员/网格员 2居民 |
| | | tabList: [{ |
| | | name: '出发时' |
| | | }, |
| | | { |
| | | name: '到达时' |
| | | }, |
| | | { |
| | | name: '返程时' |
| | | } |
| | | ], |
| | | tabIndex:0, |
| | | goOutDate:["","",""], |
| | | showSelectDate:false, |
| | | status:1 ,//当前状态 1待审核 2审核通过 3审核不通过 |
| | | id:"" |
| | | |
| | | } |
| | | }, |
| | | onLoad(option) { |
| | | this.taskId = option.id; |
| | | this.getDetailInfo(option.id); |
| | | let roleInfo = uni.getStorageSync("activeRole"); |
| | | this.roleInfo = roleInfo; |
| | | if (roleInfo.roleAlias == "inhabitant") { |
| | | this.isDisabled = true; |
| | | this.roleType = 2; |
| | | } else { |
| | | this.roleType = 1; |
| | | } |
| | | }, |
| | | |
| | | methods: { |
| | | |
| | | changeTab(index){ |
| | | this.tabIndex = index; |
| | | }, |
| | | |
| | | showPickerDate(){ |
| | | if(this.roleType == 2) return; |
| | | this.showSelectDate = true; |
| | | }, |
| | | cancelPickerDate(){ |
| | | this.showSelectDate = false; |
| | | }, |
| | | showPicker(){ |
| | | if(this.roleType == 2) return; |
| | | this.isPickerShow = true |
| | | }, |
| | | |
| | | showConfirmFlag(status){ |
| | | if(status == 1){ |
| | | this.confirmFlag = "待审核" |
| | | }else if(status == 2){ |
| | | this.confirmFlag = "审核通过" |
| | | }else { |
| | | this.confirmFlag = "审核不通过" |
| | | } |
| | | }, |
| | | |
| | | confirmDate(e) { |
| | | this.showSelectDate = false; |
| | | this.goOutDate[this.tabIndex] = e.value; |
| | | if(this.tabIndex == 0){ |
| | | this.goOutInfo.startTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss') |
| | | |
| | | }else if(this.tabIndex == 1){ |
| | | this.goOutInfo.reachTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss') |
| | | }else { |
| | | this.goOutInfo.returnTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd hh:MM:ss') |
| | | } |
| | | }, |
| | | |
| | | getLocation(key){ |
| | | if(this.roleType == 2) return; |
| | | uni.chooseLocation({ |
| | | success:(res)=>{ |
| | | console.log(res); |
| | | this.$set(this.goOutInfo, key, res.address) |
| | | }, |
| | | complete:(err)=> { |
| | | console.log(err) |
| | | } |
| | | }) |
| | | }, |
| | | |
| | | async afterReadImgs(event, key) { |
| | | this.showLoading() |
| | | let lists = [].concat(event.file) |
| | | let fileListLen = this[key].length |
| | | lists.map((item) => { |
| | | this[key].push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: '上传中' |
| | | }) |
| | | }) |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const result = await this.uploadFilePromise(lists[i].url) |
| | | this[key].splice(fileListLen, 1, Object.assign({}, { |
| | | url: result.data.link, |
| | | name: result.data.name |
| | | })) |
| | | fileListLen++ |
| | | } |
| | | this.loadingClose() |
| | | }, |
| | | |
| | | getDetailInfo(id) { |
| | | getBailReportingDetail({ |
| | | taskId: id |
| | | }).then(res => { |
| | | console.log(res); |
| | | let data = res.data; |
| | | this.status = data.confirmFlag |
| | | this.id = data.id; |
| | | this.showConfirmFlag(data.confirmFlag) |
| | | if(data.confirmFlag != 1){ |
| | | this.info.confirmFlag = data.confirmFlag; |
| | | this.info.confirmUserName = data.confirmUserName; |
| | | this.info.confirmUserTelephone = data.confirmUserTelephone; |
| | | } |
| | | this.basicData.forEach(item => { |
| | | item.value = data[item.name] |
| | | }) |
| | | this.locationImageUrls = this.setImages(data.locationImageUrls); |
| | | |
| | | }) |
| | | }, |
| | | setImages(str) { |
| | | if (str) { |
| | | return str.split(",") |
| | | } else { |
| | | return [] |
| | | } |
| | | }, |
| | | |
| | | handleConfirm(e) { |
| | | this.confirmFlag = e.value[0].name; |
| | | this.info.confirmFlag = e.value[0].status; |
| | | this.isPickerShow = false |
| | | }, |
| | | |
| | | checkImages() { |
| | | if (this.startImageUrls.length) { |
| | | this.$set(this.goOutInfo, "startImageUrls", this.setGoOutImages(this.fireImageUrls)) |
| | | } |
| | | if (this.reachImageUrls.length) { |
| | | this.$set(this.goOutInfo, "reachImageUrls", this.setGoOutImages(this.reachImageUrls)) |
| | | } |
| | | if (this.returnImageUrls.length) { |
| | | this.$set(this.goOutInfo, "returnImageUrls", this.setGoOutImages(this.returnImageUrls)) |
| | | } |
| | | }, |
| | | |
| | | setGoOutImages(key) { |
| | | let urls = []; |
| | | for (let i of key) { |
| | | urls.push(i.url); |
| | | } |
| | | return urls.join(",") |
| | | }, |
| | | |
| | | submit() { |
| | | if(this.roleType == 1){ |
| | | this.submitRequest(this.info); |
| | | }else{ |
| | | this.checkImages() |
| | | setTimeout(()=>{ |
| | | this.submitRequest(this.goOutInfo); |
| | | },1000) |
| | | } |
| | | }, |
| | | |
| | | submitRequest(data){ |
| | | data.taskId = this.taskId; |
| | | data.id = this.id; |
| | | setBailReporting(data).then(res => { |
| | | if (res.code !== 200) { |
| | | uni.showToast({ |
| | | title: '更新失败', |
| | | icon: 'error' |
| | | }) |
| | | }else { |
| | | this.getDetailInfo(this.taskId) |
| | | } |
| | | // uni.navigateBack() |
| | | }) |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | padding: 0 20rpx 130rpx; |
| | | |
| | | .box-title { |
| | | margin-bottom: 15rpx; |
| | | } |
| | | |
| | | .info { |
| | | .title { |
| | | font-size: 30rpx; |
| | | padding-left: 10rpx; |
| | | } |
| | | |
| | | .images-box { |
| | | padding: 20rpx; |
| | | min-height: 100rpx; |
| | | background-color: #fff; |
| | | } |
| | | } |
| | | |
| | | .form { |
| | | background-color: #fff; |
| | | |
| | | .form-item { |
| | | padding: 0 20rpx; |
| | | |
| | | &:first-child { |
| | | border-bottom: 1rpx solid #f6f6ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .bottom-btn { |
| | | position: fixed; |
| | | left: 0; |
| | | bottom: 0; |
| | | padding: 20rpx; |
| | | width: calc(100% - 40rpx); |
| | | background-color: #fff; |
| | | z-index: 9999; |
| | | } |
| | | } |
| | | |
| | | /deep/ .u-form-item__body__left__content__label, |
| | | /deep/ .uni-input-placeholder { |
| | | font-size: 30rpx; |
| | | } |
| | | |
| | | .upload { |
| | | margin: 0 30rpx; |
| | | padding: 30rpx; |
| | | |
| | | |
| | | } |
| | | |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <view class="basic"> |
| | | <box-title title="基本信息" class="box-title"></box-title> |
| | | <view class="info"> |
| | | <lineItem :dataInfo="basicData"></lineItem> |
| | | </view> |
| | | </view> |
| | | <view class="licence" v-if="fireImages.length"> |
| | | <box-title title="灭火器图片" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in fireImages" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="planegraph" v-if="scImageUrls.length"> |
| | | <box-title title="安全通道图片" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in scImageUrls" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="planegraph" v-if="pfImageUrls.length"> |
| | | <box-title title="技防设施图片" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in pfImageUrls" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="planegraph" v-if="uanImageUrls.length"> |
| | | <box-title title="未成年人入住及照片" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in uanImageUrls" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="examine"> |
| | | <box-title title="审核信息" class="box-title"></box-title> |
| | | <view class="info"> |
| | | <u-form labelWidth="70" :model="form" ref="form" class="form"> |
| | | <u-form-item label="审核状态" @click="isPickerShow = true" class="form-item" :border-bottom="true"> |
| | | <u--input v-model="form.confirmFlag" disabled disabledColor="#ffffff" |
| | | placeholder="请选择审核状态" :border="false"></u--input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | <u-form-item label="备注" class="form-item" v-if="selectStatus == 3"> |
| | | <u--input type="textarea" v-model="remark"></u--input> |
| | | </u-form-item> |
| | | </u-form> |
| | | </view> |
| | | </view> |
| | | <view class="bottom-btn"> |
| | | <u-button type="primary" @click="submit">提交审核</u-button> |
| | | </view> |
| | | <u-picker :closeOnClickOverlay="true" @close="isPickerShow = false" :show="isPickerShow" ref="uPicker" |
| | | :columns="columns" keyName="name" @cancel="isPickerShow = false" @confirm="handleConfirm"></u-picker> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import boxTitle from '@/components/boxTitle/index.vue' |
| | | import lineItem from '../components/lineItem.vue' |
| | | import { |
| | | getHotelReportingDetail, |
| | | setHotelReporting |
| | | } from '@/api/reporting/reporting' |
| | | export default { |
| | | components: { |
| | | boxTitle, |
| | | lineItem |
| | | }, |
| | | data() { |
| | | return { |
| | | basicData: [{ |
| | | label: '社区名称', |
| | | name: "districtName", |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '酒店名称', |
| | | name: 'hotelName', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '自查时间', |
| | | name: 'checkTime', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '自查人姓名', |
| | | name: 'checkUserName', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '自查人手机', |
| | | name: 'checkTelephone', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '自查位置', |
| | | name: 'location', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '自查数量', |
| | | name: 'fireNums', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '灭火器状态', |
| | | name: 'fireStatus', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '安全通道有无', |
| | | name: 'scFlag', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '安全通道状态', |
| | | name: 'scStatus', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '防设施有无', |
| | | name: 'pfFlag', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '技防设施名称', |
| | | name: 'pfName', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '是否完全实名制登记', |
| | | name: 'realName', |
| | | value: '未完善' |
| | | }, |
| | | ], |
| | | personnelData: [ |
| | | [{ |
| | | label: '姓名', |
| | | value: '未完善', |
| | | }, |
| | | { |
| | | label: '电话号码', |
| | | value: '未完善', |
| | | }, |
| | | { |
| | | label: '暂住地', |
| | | value: '未完善', |
| | | } |
| | | ] |
| | | ], |
| | | form: { |
| | | confirmFlag: '待审核', |
| | | confirmNotion: '' |
| | | }, |
| | | isPickerShow: false, |
| | | columns: [ |
| | | [{ |
| | | name: "待审核", |
| | | status: 1 |
| | | }, |
| | | { |
| | | name: "审核通过", |
| | | status: 2 |
| | | }, |
| | | { |
| | | name: "审核不通过", |
| | | status: 3 |
| | | }, |
| | | ] |
| | | ], |
| | | currentId: '', |
| | | currentData: {}, |
| | | id: "", |
| | | fireImages: [], |
| | | scImageUrls: [], |
| | | pfImageUrls: [], |
| | | uanImageUrls: [], |
| | | selectStatus: "", |
| | | remark: "" |
| | | |
| | | } |
| | | }, |
| | | onLoad(option) { |
| | | this.id = option.id; |
| | | this.getDetailInfo(option.id); |
| | | }, |
| | | |
| | | methods: { |
| | | |
| | | getDetailInfo(id) { |
| | | getHotelReportingDetail({ |
| | | taskId: id |
| | | }).then(res => { |
| | | console.log(res); |
| | | let data = res.data; |
| | | this.basicData.forEach(item => { |
| | | item.value = data[item.name] |
| | | }) |
| | | this.fireImages = this.setImages(data.fireImageUrls); |
| | | this.scImageUrls = this.setImages(data.scImageUrls); |
| | | this.pfImageUrls = this.setImages(data.pfImageUrls); |
| | | this.uanImageUrls = this.setImages(data.uanImageUrls); |
| | | |
| | | }) |
| | | }, |
| | | setImages(str) { |
| | | if (str) { |
| | | return str.split(",") |
| | | } else { |
| | | return [] |
| | | } |
| | | }, |
| | | |
| | | handleConfirm(e) { |
| | | this.form.confirmFlag = e.value[0].name; |
| | | this.selectStatus = e.value[0].status; |
| | | this.isPickerShow = false |
| | | }, |
| | | submit() { |
| | | let data = { |
| | | id: this.id, |
| | | status: this.selectStatus, |
| | | remark: this.remark |
| | | } |
| | | if (this.selectStatus == 3 && !this.remark) { |
| | | uni.showToast({ |
| | | title: "请输入拒绝原因", |
| | | icon: "none" |
| | | }) |
| | | return; |
| | | } |
| | | setHotelReporting(data).then(res=>{ |
| | | if (res.code !== 200) { |
| | | uni.showToast({ |
| | | title: '更新失败', |
| | | icon: 'error' |
| | | }) |
| | | return |
| | | } |
| | | uni.navigateBack() |
| | | }) |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | padding:0 20rpx 130rpx; |
| | | .box-title { |
| | | margin-bottom: 15rpx; |
| | | } |
| | | .info { |
| | | .title { |
| | | font-size: 30rpx; |
| | | padding-left: 10rpx; |
| | | } |
| | | .images-box { |
| | | padding: 20rpx; |
| | | min-height: 100rpx; |
| | | background-color: #fff; |
| | | } |
| | | } |
| | | .form { |
| | | background-color: #fff; |
| | | .form-item { |
| | | padding: 0 20rpx; |
| | | |
| | | &:first-child { |
| | | border-bottom: 1rpx solid #f6f6ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .bottom-btn { |
| | | position: fixed; |
| | | left: 0; |
| | | bottom: 0; |
| | | padding: 20rpx; |
| | | width: calc(100% - 40rpx); |
| | | background-color: #fff; |
| | | z-index: 9999; |
| | | } |
| | | } |
| | | |
| | | /deep/ .u-form-item__body__left__content__label, |
| | | /deep/ .uni-input-placeholder { |
| | | font-size: 30rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <template> |
| | | <view class="container"> |
| | | <view class="basic"> |
| | | <box-title title="基本信息" class="box-title"></box-title> |
| | | <view class="info"> |
| | | <lineItem :dataInfo="basicData"></lineItem> |
| | | </view> |
| | | </view> |
| | | <view class="licence" v-if="imageUrls.length"> |
| | | <box-title title="手持身份证" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in imageUrls" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="planegraph" v-if="goodsImageUrls.length"> |
| | | <box-title title="物品照片" class="box-title"></box-title> |
| | | <view class="flex-wrap"> |
| | | <view v-for="(i,k) in goodsImageUrls" :key="k" class="ml-20 mt-20"> |
| | | <u-image :src="i" width="80" height="80"></u-image> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="examine"> |
| | | <box-title title="审核信息" class="box-title"></box-title> |
| | | <view class="info"> |
| | | <u-form labelWidth="70" :model="form" ref="form" class="form"> |
| | | <u-form-item label="审核状态" @click="isPickerShow = true" class="form-item" :border-bottom="true"> |
| | | <u--input v-model="form.confirmFlag" disabled disabledColor="#ffffff" |
| | | placeholder="请选择审核状态" :border="false"></u--input> |
| | | <u-icon slot="right" name="arrow-right"></u-icon> |
| | | </u-form-item> |
| | | <u-form-item label="备注" class="form-item" v-if="selectStatus == 3"> |
| | | <u--input type="textarea" v-model="remark"></u--input> |
| | | </u-form-item> |
| | | </u-form> |
| | | </view> |
| | | </view> |
| | | <view class="bottom-btn"> |
| | | <u-button type="primary" @click="submit">提交审核</u-button> |
| | | </view> |
| | | <u-picker :closeOnClickOverlay="true" @close="isPickerShow = false" :show="isPickerShow" ref="uPicker" |
| | | :columns="columns" keyName="name" @cancel="isPickerShow = false" @confirm="handleConfirm"></u-picker> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import boxTitle from '@/components/boxTitle/index.vue' |
| | | import lineItem from '../components/lineItem.vue' |
| | | import { |
| | | getLabelReportingDetail, |
| | | setLabelReporting |
| | | } from '@/api/reporting/reporting' |
| | | export default { |
| | | components: { |
| | | boxTitle, |
| | | lineItem |
| | | }, |
| | | data() { |
| | | return { |
| | | basicData: [{ |
| | | label: '对象电话', |
| | | name: "transactionObjectTel", |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '交易金额', |
| | | name: 'transactionMoney', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '物品数量', |
| | | name: 'goodsNums', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '交易位置', |
| | | name: 'location', |
| | | value: '未完善' |
| | | }, |
| | | { |
| | | label: '交易过程', |
| | | name: 'transactionProcess', |
| | | value: '未完善' |
| | | } |
| | | ], |
| | | form: { |
| | | confirmFlag: '待审核', |
| | | confirmNotion: '' |
| | | }, |
| | | isPickerShow: false, |
| | | columns: [ |
| | | [{ |
| | | name: "待审核", |
| | | status: 1 |
| | | }, |
| | | { |
| | | name: "审核通过", |
| | | status: 2 |
| | | }, |
| | | { |
| | | name: "审核不通过", |
| | | status: 3 |
| | | }, |
| | | ] |
| | | ], |
| | | currentId: '', |
| | | currentData: {}, |
| | | id: "", |
| | | imageUrls:[], |
| | | goodsImageUrls:[], |
| | | selectStatus: "", |
| | | remark: "" |
| | | } |
| | | }, |
| | | onLoad(option) { |
| | | this.id = option.id; |
| | | this.getDetailInfo(option.id); |
| | | }, |
| | | |
| | | methods: { |
| | | getDetailInfo(id) { |
| | | getLabelReportingDetail({ |
| | | taskId: id |
| | | }).then(res => { |
| | | console.log(res); |
| | | let data = res.data; |
| | | this.basicData.forEach(item => { |
| | | item.value = data[item.name] |
| | | }) |
| | | this.imageUrls = this.setImages(data.imageUrls); |
| | | this.goodsImageUrls = this.setImages(data.goodsImageUrls); |
| | | }) |
| | | }, |
| | | setImages(str) { |
| | | if (str) { |
| | | return str.split(",") |
| | | } else { |
| | | return [] |
| | | } |
| | | }, |
| | | |
| | | handleConfirm(e) { |
| | | this.form.confirmFlag = e.value[0].name; |
| | | this.selectStatus = e.value[0].status; |
| | | this.isPickerShow = false |
| | | }, |
| | | submit() { |
| | | let data = { |
| | | id: this.id, |
| | | status: this.selectStatus, |
| | | remark: this.remark |
| | | } |
| | | if (this.selectStatus == 3 && !this.remark) { |
| | | uni.showToast({ |
| | | title: "请输入拒绝原因", |
| | | icon: "none" |
| | | }) |
| | | return; |
| | | } |
| | | setLabelReporting(data).then(res=>{ |
| | | if (res.code !== 200) { |
| | | uni.showToast({ |
| | | title: '更新失败', |
| | | icon: 'error' |
| | | }) |
| | | return |
| | | } |
| | | uni.navigateBack() |
| | | }) |
| | | }, |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | .container { |
| | | padding:0 20rpx 130rpx; |
| | | .box-title { |
| | | margin-bottom: 15rpx; |
| | | } |
| | | .info { |
| | | .title { |
| | | font-size: 30rpx; |
| | | padding-left: 10rpx; |
| | | } |
| | | .images-box { |
| | | padding: 20rpx; |
| | | min-height: 100rpx; |
| | | background-color: #fff; |
| | | } |
| | | } |
| | | .form { |
| | | background-color: #fff; |
| | | .form-item { |
| | | padding: 0 20rpx; |
| | | |
| | | &:first-child { |
| | | border-bottom: 1rpx solid #f6f6ff; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .bottom-btn { |
| | | position: fixed; |
| | | left: 0; |
| | | bottom: 0; |
| | | padding: 20rpx; |
| | | width: calc(100% - 40rpx); |
| | | background-color: #fff; |
| | | z-index: 9999; |
| | | } |
| | | } |
| | | |
| | | /deep/ .u-form-item__body__left__content__label, |
| | | /deep/ .uni-input-placeholder { |
| | | font-size: 30rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | ## 2.6.2(2023-10-31) |
| | | 1.`修复` 在源码中有异常字符导致的在vue3中编译报错的问题。 |
| | | 2.`修复` 在微信小程序中`z-paging-refresh`的height无效的问题(by xiaohe0601)。 |
| | | 近期更新: |
| | | ============================= |
| | | 1.`新增` 手动更新自定义下拉刷新view高度方法。 |
| | | 2.`新增` 点击返回顶部按钮添加事件监听&支持拦截。 |
| | | 3.`新增` 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况。 |
| | | 4.`新增` 支持配置网络请求失败触发`reject`。 |
| | | 5.`修复` 显示空数据图时,滚动到底部依然可以加载更多的问题。 |
| | | 6.`修复` 在vue2中底部加载更多相关`slot`使用`template`插入无效的问题。 |
| | | 7.`修复` `complete`的`Promise`可能无效的问题。 |
| | | 8.`优化` `hooks`判断`z-paging`为空则不调用。 |
| | | |
| | | |
| | | |
| New file |
| | |
| | | <!-- z-paging --> |
| | | <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> |
| | | <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> |
| | | <!-- 反馈QQ群:790460711 --> |
| | | |
| | | <!-- z-paging-cell,用于在nvue中使用cell包裹,vue中使用view包裹 --> |
| | | <template> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <cell :style="[cellStyle]"> |
| | | <slot /> |
| | | </cell> |
| | | <!-- #endif --> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <view :style="[cellStyle]"> |
| | | <slot /> |
| | | </view> |
| | | <!-- #endif --> |
| | | </template> |
| | | |
| | | <script> |
| | | export default { |
| | | name: "z-paging-cell", |
| | | props: { |
| | | //cellStyle |
| | | cellStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {} |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| New file |
| | |
| | | <!-- z-paging --> |
| | | <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> |
| | | <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> |
| | | <!-- 反馈QQ群:790460711 --> |
| | | |
| | | <!-- 空数据占位view,此组件支持easycom规范,可以在项目中直接引用 --> |
| | | <template> |
| | | <view :class="{'zp-container':true,'zp-container-fixed':emptyViewFixed}" :style="[finalEmptyViewStyle]" @click="emptyViewClick"> |
| | | <view class="zp-main"> |
| | | <image v-if="!emptyViewImg.length" class="zp-main-image" :style="[emptyViewImgStyle]" :src="emptyImg" /> |
| | | <image v-else class="zp-main-image" mode="aspectFit" :style="[emptyViewImgStyle]" :src="emptyViewImg" /> |
| | | <text class="zp-main-title" :style="[emptyViewTitleStyle]">{{emptyViewText}}</text> |
| | | <text v-if="showEmptyViewReload" class="zp-main-error-btn" :style="[emptyViewReloadStyle]" @click.stop="reloadClick">{{emptyViewReloadText}}</text> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import zStatic from '../z-paging/js/z-paging-static' |
| | | export default { |
| | | name: "z-paging-empty-view", |
| | | data() { |
| | | return { |
| | | |
| | | }; |
| | | }, |
| | | props: { |
| | | //空数据描述文字 |
| | | emptyViewText: { |
| | | type: String, |
| | | default: '没有数据哦~' |
| | | }, |
| | | //空数据图片 |
| | | emptyViewImg: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | //是否显示空数据图重新加载按钮 |
| | | showEmptyViewReload: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | //空数据点击重新加载文字 |
| | | emptyViewReloadText: { |
| | | type: String, |
| | | default: '重新加载' |
| | | }, |
| | | //是否是加载失败 |
| | | isLoadFailed: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | //空数据图样式 |
| | | emptyViewStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {} |
| | | } |
| | | }, |
| | | //空数据图img样式 |
| | | emptyViewImgStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {} |
| | | } |
| | | }, |
| | | //空数据图描述文字样式 |
| | | emptyViewTitleStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {} |
| | | } |
| | | }, |
| | | //空数据图重新加载按钮样式 |
| | | emptyViewReloadStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {} |
| | | } |
| | | }, |
| | | //空数据图z-index |
| | | emptyViewZIndex: { |
| | | type: Number, |
| | | default: 9 |
| | | }, |
| | | //空数据图片是否使用fixed布局并铺满z-paging |
| | | emptyViewFixed: { |
| | | type: Boolean, |
| | | default: true |
| | | } |
| | | }, |
| | | computed: { |
| | | emptyImg() { |
| | | return this.isLoadFailed ? zStatic.base64Error : zStatic.base64Empty; |
| | | }, |
| | | finalEmptyViewStyle(){ |
| | | this.emptyViewStyle['z-index'] = this.emptyViewZIndex; |
| | | return this.emptyViewStyle; |
| | | } |
| | | }, |
| | | methods: { |
| | | reloadClick() { |
| | | this.$emit('reload'); |
| | | }, |
| | | emptyViewClick() { |
| | | this.$emit('viewClick'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .zp-container{ |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .zp-container-fixed { |
| | | /* #ifndef APP-NVUE */ |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | height: 100%; |
| | | /* #endif */ |
| | | /* #ifdef APP-NVUE */ |
| | | flex: 1; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-main{ |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | align-items: center; |
| | | padding: 50rpx 0rpx; |
| | | } |
| | | |
| | | .zp-main-image { |
| | | width: 200rpx; |
| | | height: 200rpx; |
| | | } |
| | | |
| | | .zp-main-title { |
| | | font-size: 26rpx; |
| | | color: #aaaaaa; |
| | | text-align: center; |
| | | margin-top: 10rpx; |
| | | } |
| | | |
| | | .zp-main-error-btn { |
| | | font-size: 26rpx; |
| | | padding: 8rpx 24rpx; |
| | | border: solid 1px #dddddd; |
| | | border-radius: 6rpx; |
| | | color: #aaaaaa; |
| | | margin-top: 50rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <!-- z-paging --> |
| | | <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> |
| | | <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> |
| | | <!-- 反馈QQ群:790460711 --> |
| | | |
| | | <!-- 滑动切换选项卡swiper-item,此组件支持easycom规范,可以在项目中直接引用 --> |
| | | <template> |
| | | <view class="zp-swiper-item-container"> |
| | | <z-paging ref="paging" :fixed="false" |
| | | :auto="false" :useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle" |
| | | :preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol" |
| | | @query="_queryList" @listChange="_updateList" style="height: 100%;"> |
| | | <slot /> |
| | | <template #header> |
| | | <slot name="header"/> |
| | | </template> |
| | | <template #cell="{item,index}"> |
| | | <slot name="cell" :item="item" :index="index"/> |
| | | </template> |
| | | <template #footer> |
| | | <slot name="footer"/> |
| | | </template> |
| | | </z-paging> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import zPaging from '../z-paging/z-paging' |
| | | export default { |
| | | name: "z-paging-swiper-item", |
| | | components: { zPaging }, |
| | | data() { |
| | | return { |
| | | firstLoaded: false |
| | | } |
| | | }, |
| | | props: { |
| | | //当前组件的index,也就是当前组件是swiper中的第几个 |
| | | tabIndex: { |
| | | type: Number, |
| | | default: function() { |
| | | return 0 |
| | | } |
| | | }, |
| | | //当前swiper切换到第几个index |
| | | currentIndex: { |
| | | type: Number, |
| | | default: function() { |
| | | return 0 |
| | | } |
| | | }, |
| | | //是否使用虚拟列表,默认为否 |
| | | useVirtualList: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | //是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true |
| | | useInnerList: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | //内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项 |
| | | cellKeyName: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | //innerList样式 |
| | | innerListStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {}; |
| | | } |
| | | }, |
| | | //预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题 |
| | | preloadPage: { |
| | | type: [Number, String], |
| | | default: 12 |
| | | }, |
| | | //虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。 |
| | | cellHeightMode: { |
| | | type: String, |
| | | default: 'fixed' |
| | | }, |
| | | //虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2 |
| | | virtualListCol: { |
| | | type: [Number, String], |
| | | default: 1 |
| | | }, |
| | | //虚拟列表scroll取样帧率,默认为60,过高可能出现卡顿等问题 |
| | | virtualScrollFps: { |
| | | type: [Number, String], |
| | | default: 60 |
| | | }, |
| | | }, |
| | | watch: { |
| | | currentIndex: { |
| | | handler(newVal, oldVal) { |
| | | if (newVal === this.tabIndex) { |
| | | //懒加载,当滑动到当前的item时,才去加载 |
| | | if (!this.firstLoaded) { |
| | | this.$nextTick(()=>{ |
| | | let delay = 5; |
| | | // #ifdef MP-TOUTIAO |
| | | delay = 100; |
| | | // #endif |
| | | setTimeout(() => { |
| | | this.$refs.paging.reload().catch(() => {}); |
| | | }, delay); |
| | | }) |
| | | } |
| | | } |
| | | }, |
| | | immediate: true |
| | | } |
| | | }, |
| | | methods: { |
| | | reload(data) { |
| | | return this.$refs.paging.reload(data); |
| | | }, |
| | | complete(data) { |
| | | this.firstLoaded = true; |
| | | return this.$refs.paging.complete(data); |
| | | }, |
| | | _queryList(pageNo, pageSize, from) { |
| | | this.$emit('query', pageNo, pageSize, from); |
| | | }, |
| | | _updateList(list) { |
| | | this.$emit('updateList', list); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .zp-swiper-item-container { |
| | | /* #ifndef APP-NVUE */ |
| | | height: 100%; |
| | | /* #endif */ |
| | | /* #ifdef APP-NVUE */ |
| | | flex: 1; |
| | | /* #endif */ |
| | | } |
| | | </style> |
| New file |
| | |
| | | <!-- z-paging --> |
| | | <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> |
| | | <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> |
| | | <!-- 反馈QQ群:790460711 --> |
| | | |
| | | <!-- 滑动切换选项卡swiper容器,此组件支持easycom规范,可以在项目中直接引用 --> |
| | | <template> |
| | | <view :class="fixed?'zp-swiper-container zp-swiper-container-fixed':'zp-swiper-container'" :style="[finalSwiperStyle]"> |
| | | <!-- #ifndef APP-PLUS --> |
| | | <view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view> |
| | | <!-- #endif --> |
| | | <slot v-if="zSlots.top" name="top" /> |
| | | <view class="zp-swiper-super"> |
| | | <view v-if="zSlots.left" :class="{'zp-swiper-left':true,'zp-absoulte':isOldWebView}"> |
| | | <slot name="left" /> |
| | | </view> |
| | | <view :class="{'zp-swiper':true,'zp-absoulte':isOldWebView}" :style="[swiperContentStyle]"> |
| | | <slot /> |
| | | </view> |
| | | <view v-if="zSlots.right" :class="{'zp-swiper-right':true,'zp-absoulte zp-right':isOldWebView}"> |
| | | <slot name="right" /> |
| | | </view> |
| | | </view> |
| | | <slot v-if="zSlots.bottom" name="bottom" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script> |
| | | import commonLayoutModule from '../z-paging/js/modules/common-layout' |
| | | |
| | | export default { |
| | | name: "z-paging-swiper", |
| | | mixins: [commonLayoutModule], |
| | | data() { |
| | | return { |
| | | swiperContentStyle: {} |
| | | }; |
| | | }, |
| | | props: { |
| | | //是否使用fixed布局,默认为是 |
| | | fixed: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | //是否开启底部安全区域适配 |
| | | safeAreaInsetBottom: { |
| | | type: Boolean, |
| | | default: false |
| | | }, |
| | | //z-paging-swiper样式 |
| | | swiperStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return {}; |
| | | }, |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.$nextTick(() => { |
| | | this.systemInfo = uni.getSystemInfoSync(); |
| | | }) |
| | | // #ifndef APP-PLUS |
| | | this._getCssSafeAreaInsetBottom(); |
| | | // #endif |
| | | this.updateLeftAndRightWidth(); |
| | | |
| | | this.swiperContentStyle = { 'flex': '1' }; |
| | | // #ifndef APP-NVUE |
| | | this.swiperContentStyle = { width: '100%',height: '100%' }; |
| | | // #endif |
| | | }, |
| | | computed: { |
| | | finalSwiperStyle() { |
| | | const swiperStyle = this.swiperStyle; |
| | | if (!this.systemInfo) return swiperStyle; |
| | | const windowTop = this.windowTop; |
| | | const windowBottom = this.systemInfo.windowBottom; |
| | | if (this.fixed) { |
| | | if (windowTop && !swiperStyle.top) { |
| | | swiperStyle.top = windowTop + 'px'; |
| | | } |
| | | if (!swiperStyle.bottom) { |
| | | let bottom = windowBottom || 0; |
| | | bottom += this.safeAreaInsetBottom ? this.safeAreaBottom : 0; |
| | | if (bottom > 0) { |
| | | swiperStyle.bottom = bottom + 'px'; |
| | | } |
| | | } |
| | | } |
| | | return swiperStyle; |
| | | } |
| | | }, |
| | | methods: { |
| | | //更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用 |
| | | updateLeftAndRightWidth() { |
| | | if (!this.isOldWebView) return; |
| | | this.$nextTick(() => this._updateLeftAndRightWidth(this.swiperContentStyle, 'zp-swiper')); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .zp-swiper-container { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | flex: 1; |
| | | } |
| | | |
| | | .zp-swiper-container-fixed { |
| | | position: fixed; |
| | | /* #ifndef APP-NVUE */ |
| | | height: auto; |
| | | width: auto; |
| | | /* #endif */ |
| | | top: 0; |
| | | left: 0; |
| | | bottom: 0; |
| | | right: 0; |
| | | } |
| | | |
| | | .zp-safe-area-inset-bottom { |
| | | position: absolute; |
| | | /* #ifndef APP-PLUS */ |
| | | height: env(safe-area-inset-bottom); |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-swiper-super { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | } |
| | | |
| | | .zp-swiper-left,.zp-swiper-right{ |
| | | /* #ifndef APP-NVUE */ |
| | | height: 100%; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-swiper { |
| | | flex: 1; |
| | | /* #ifndef APP-NVUE */ |
| | | height: 100%; |
| | | width: 100%; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-absoulte { |
| | | /* #ifndef APP-NVUE */ |
| | | position: absolute; |
| | | top: 0; |
| | | width: auto; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-right{ |
| | | right: 0; |
| | | } |
| | | |
| | | .zp-swiper-item { |
| | | height: 100%; |
| | | } |
| | | </style> |
| New file |
| | |
| | | <!-- [z-paging]上拉加载更多view --> |
| | | <template> |
| | | <view class="zp-l-container" :style="[c.customStyle]" @click="doClick"> |
| | | <template v-if="!c.hideContent"> |
| | | <text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" class="zp-l-line" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" /> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <image v-if="finalStatus===M.Loading&&!!c.loadingIconCustomImage" |
| | | :src="c.loadingIconCustomImage" :style="[c.iconCustomStyle]" :class="{'zp-l-line-loading-custom-image':true,'zp-l-line-loading-custom-image-animated':c.loadingAnimated}" /> |
| | | <image v-if="finalStatus===M.Loading&&finalLoadingIconType==='flower'&&!c.loadingIconCustomImage.length" |
| | | class="zp-line-loading-image" :style="[c.iconCustomStyle]" :src="zTheme.flower[ts]" /> |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <view> |
| | | <loading-indicator v-if="finalStatus===M.Loading&&finalLoadingIconType!=='circle'" class="zp-line-loading-image" :style="[{color:zTheme.indicator[ts]}]" :animating="true" /> |
| | | </view> |
| | | <!-- #endif --> |
| | | <text v-if="finalStatus===M.Loading&&finalLoadingIconType==='circle'&&!c.loadingIconCustomImage.length" |
| | | class="zp-l-circle-loading-view" :style="[{borderColor:zTheme.circleBorder[ts],borderTopColor:zTheme.circleBorderTop[ts]},c.iconCustomStyle]" /> |
| | | <text class="zp-l-text" :style="[{color:zTheme.title[ts]},c.titleCustomStyle]">{{ownLoadingMoreText}}</text> |
| | | <text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" class="zp-l-line" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" /> |
| | | </template> |
| | | </view> |
| | | </template> |
| | | <script> |
| | | import zStatic from '../js/z-paging-static' |
| | | import Enum from '../js/z-paging-enum' |
| | | export default { |
| | | name: 'z-paging-load-more', |
| | | data() { |
| | | return { |
| | | M: Enum.More, |
| | | zTheme: { |
| | | title: { white: '#efefef', black: '#a4a4a4' }, |
| | | line: { white: '#efefef', black: '#eeeeee' }, |
| | | circleBorder: { white: '#aaaaaa', black: '#c8c8c8' }, |
| | | circleBorderTop: { white: '#ffffff', black: '#444444' }, |
| | | flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower }, |
| | | indicator: { white: '#eeeeee', black: '#777777' } |
| | | } |
| | | }; |
| | | }, |
| | | props: ['zConfig'], |
| | | computed: { |
| | | ts() { |
| | | return this.c.defaultThemeStyle; |
| | | }, |
| | | c() { |
| | | return this.zConfig || {}; |
| | | }, |
| | | ownLoadingMoreText() { |
| | | const statusTextArr = [this.c.defaultText,this.c.loadingText,this.c.noMoreText,this.c.failText]; |
| | | return statusTextArr[this.finalStatus]; |
| | | }, |
| | | finalStatus() { |
| | | if (this.c.defaultAsLoading && this.c.status === this.M.Default) return this.M.Loading; |
| | | return this.c.status; |
| | | }, |
| | | finalLoadingIconType() { |
| | | // #ifdef APP-NVUE |
| | | return 'flower'; |
| | | // #endif |
| | | return this.c.loadingIconType; |
| | | } |
| | | }, |
| | | methods: { |
| | | doClick() { |
| | | this.$emit('doClick'); |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | @import "../css/z-paging-static.css"; |
| | | |
| | | .zp-l-container { |
| | | height: 80rpx; |
| | | font-size: 27rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | clear: both; |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .zp-l-line-loading-custom-image { |
| | | color: #a4a4a4; |
| | | margin-right: 8rpx; |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | } |
| | | |
| | | .zp-l-line-loading-custom-image-animated{ |
| | | /* #ifndef APP-NVUE */ |
| | | animation: loading-circle 1s linear infinite; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-l-circle-loading-view { |
| | | margin-right: 8rpx; |
| | | width: 23rpx; |
| | | height: 23rpx; |
| | | border: 3rpx solid #dddddd; |
| | | border-radius: 50%; |
| | | /* #ifndef APP-NVUE */ |
| | | animation: loading-circle 1s linear infinite; |
| | | /* #endif */ |
| | | /* #ifdef APP-NVUE */ |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-l-text { |
| | | /* #ifdef APP-NVUE */ |
| | | font-size: 30rpx; |
| | | margin: 0rpx 10rpx; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-l-line { |
| | | height: 1px; |
| | | width: 100rpx; |
| | | margin: 0rpx 10rpx; |
| | | } |
| | | |
| | | /* #ifndef APP-NVUE */ |
| | | @keyframes loading-circle { |
| | | 0% { |
| | | -webkit-transform: rotate(0deg); |
| | | transform: rotate(0deg); |
| | | } |
| | | 100% { |
| | | -webkit-transform: rotate(360deg); |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | /* #endif */ |
| | | </style> |
| New file |
| | |
| | | <!-- [z-paging]下拉刷新view --> |
| | | <template> |
| | | <view style="height: 100%;"> |
| | | <view :class="showUpdateTime?'zp-r-container zp-r-container-padding':'zp-r-container'"> |
| | | <view class="zp-r-left"> |
| | | <image v-if="status!==R.Loading" :class="leftImageClass" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" /> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <image v-else :class="{'zp-line-loading-image':refreshingAnimated,'zp-r-left-image':true}" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" /> |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <view v-else :style="[{'margin-right':showUpdateTime?'18rpx':'12rpx'}]"> |
| | | <loading-indicator :class="isIos?'zp-loading-image-ios':'zp-loading-image-android'" |
| | | :style="[{color:zTheme.indicator[ts]},imgStyle]" :animating="true" /> |
| | | </view> |
| | | <!-- #endif --> |
| | | </view> |
| | | <view class="zp-r-right"> |
| | | <text class="zp-r-right-text" :style="[rightTextStyle,titleStyle]">{{currentTitle}}</text> |
| | | <text v-if="showUpdateTime&&refresherTimeText.length" class="zp-r-right-text zp-r-right-time-text" :style="[rightTextStyle,updateTimeStyle]"> |
| | | {{refresherTimeText}} |
| | | </text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | <script> |
| | | import zStatic from '../js/z-paging-static' |
| | | import u from '../js/z-paging-utils' |
| | | import Enum from '../js/z-paging-enum' |
| | | |
| | | export default { |
| | | name: 'z-paging-refresh', |
| | | data() { |
| | | return { |
| | | R: Enum.Refresher, |
| | | isIos: uni.getSystemInfoSync().platform === 'ios', |
| | | refresherTimeText: '', |
| | | zTheme: { |
| | | title: { white: '#efefef', black: '#555555' }, |
| | | arrow: { white: zStatic.base64ArrowWhite, black: zStatic.base64Arrow }, |
| | | flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower }, |
| | | success: { white: zStatic.base64SuccessWhite, black: zStatic.base64Success }, |
| | | indicator: { white: '#eeeeee', black: '#777777' } |
| | | } |
| | | }; |
| | | }, |
| | | props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'defaultImg', 'pullingImg', |
| | | 'refreshingImg', 'completeImg', 'refreshingAnimated', 'showUpdateTime', 'updateTimeKey', 'imgStyle', 'titleStyle', 'updateTimeStyle', 'updateTimeTextMap' |
| | | ], |
| | | computed: { |
| | | ts() { |
| | | return this.defaultThemeStyle; |
| | | }, |
| | | statusTextArr() { |
| | | this.updateTime(); |
| | | return [this.defaultText,this.pullingText,this.refreshingText,this.completeText]; |
| | | }, |
| | | currentTitle() { |
| | | return this.statusTextArr[this.status] || this.defaultText; |
| | | }, |
| | | leftImageClass() { |
| | | if (this.status === this.R.Complete) return 'zp-r-left-image-pre-size'; |
| | | return `zp-r-left-image zp-r-left-image-pre-size ${this.status === this.R.Default ? 'zp-r-arrow-down' : 'zp-r-arrow-top'}`; |
| | | }, |
| | | leftImageStyle() { |
| | | const showUpdateTime = this.showUpdateTime; |
| | | const size = showUpdateTime ? '36rpx' : '30rpx'; |
| | | return {width: size,height: size,'margin-right': showUpdateTime ? '20rpx' : '9rpx'}; |
| | | }, |
| | | leftImageSrc() { |
| | | const R = this.R; |
| | | const status = this.status; |
| | | if (status === R.Default) { |
| | | if (!!this.defaultImg) return this.defaultImg; |
| | | return this.zTheme.arrow[this.ts]; |
| | | } else if (status === R.ReleaseToRefresh) { |
| | | if (!!this.pullingImg) return this.pullingImg; |
| | | if (!!this.defaultImg) return this.defaultImg; |
| | | return this.zTheme.arrow[this.ts]; |
| | | } else if (status === R.Loading) { |
| | | if (!!this.refreshingImg) return this.refreshingImg; |
| | | return this.zTheme.flower[this.ts];; |
| | | } else if (status === R.Complete) { |
| | | if (!!this.completeImg) return this.completeImg; |
| | | return this.zTheme.success[this.ts];; |
| | | } |
| | | return ''; |
| | | }, |
| | | rightTextStyle() { |
| | | let stl = {}; |
| | | // #ifdef APP-NVUE |
| | | const textHeight = this.showUpdateTime ? '40rpx' : '80rpx'; |
| | | stl = {'height': textHeight, 'line-height': textHeight} |
| | | // #endif |
| | | stl['color'] = this.zTheme.title[this.ts]; |
| | | return stl; |
| | | } |
| | | }, |
| | | methods: { |
| | | updateTime() { |
| | | if (this.showUpdateTime) { |
| | | this.refresherTimeText = u.getRefesrherFormatTimeByKey(this.updateTimeKey, this.updateTimeTextMap); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <style scoped> |
| | | @import "../css/z-paging-static.css"; |
| | | |
| | | .zp-r-container { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | height: 100%; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | justify-content: center; |
| | | align-items: center; |
| | | } |
| | | |
| | | .zp-r-container-padding { |
| | | /* #ifdef APP-NVUE */ |
| | | padding: 15rpx 0rpx; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-r-left { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | /* #ifdef MP-ALIPAY */ |
| | | margin-top: -4rpx; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-r-left-image { |
| | | transition-duration: .2s; |
| | | transition-property: transform; |
| | | color: #666666; |
| | | } |
| | | |
| | | .zp-r-left-image-pre-size{ |
| | | /* #ifndef APP-NVUE */ |
| | | width: 30rpx; |
| | | height: 30rpx; |
| | | overflow: hidden; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-r-arrow-top { |
| | | transform: rotate(0deg); |
| | | } |
| | | |
| | | .zp-r-arrow-down { |
| | | transform: rotate(180deg); |
| | | } |
| | | |
| | | .zp-r-right { |
| | | font-size: 27rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .zp-r-right-text { |
| | | /* #ifdef APP-NVUE */ |
| | | font-size: 28rpx; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-r-right-time-text { |
| | | margin-top: 10rpx; |
| | | font-size: 24rpx; |
| | | } |
| | | </style> |
| New file |
| | |
| | | // z-paging全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置 |
| | | |
| | | export default {} |
| New file |
| | |
| | | /* [z-paging]公共css*/ |
| | | |
| | | .z-paging-content { |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .z-paging-content-fixed, .zp-loading-fixed { |
| | | position: fixed; |
| | | /* #ifndef APP-NVUE */ |
| | | height: auto; |
| | | width: auto; |
| | | /* #endif */ |
| | | top: 0; |
| | | left: 0; |
| | | bottom: 0; |
| | | right: 0; |
| | | } |
| | | |
| | | .zp-page-top,.zp-page-bottom { |
| | | /* #ifndef APP-NVUE */ |
| | | width: auto; |
| | | /* #endif */ |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | z-index: 999; |
| | | } |
| | | |
| | | .zp-page-left,.zp-page-right{ |
| | | /* #ifndef APP-NVUE */ |
| | | height: 100%; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-scroll-view-super { |
| | | flex: 1; |
| | | overflow: hidden; |
| | | position: relative; |
| | | } |
| | | |
| | | .zp-view-super{ |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | } |
| | | |
| | | .zp-scroll-view-container,.zp-scroll-view { |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | | height: 100%; |
| | | width: 100%; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-absoulte{ |
| | | /* #ifndef APP-NVUE */ |
| | | position: absolute; |
| | | top: 0; |
| | | width: auto; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-right{ |
| | | right: 0; |
| | | } |
| | | |
| | | .zp-scroll-view-absolute { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | } |
| | | |
| | | /* #ifndef APP-NVUE */ |
| | | .zp-scroll-view-hide-scrollbar ::-webkit-scrollbar { |
| | | display: none; |
| | | -webkit-appearance: none; |
| | | width: 0 !important; |
| | | height: 0 !important; |
| | | background: transparent; |
| | | } |
| | | /* #endif */ |
| | | |
| | | .zp-paging-touch-view { |
| | | width: 100%; |
| | | height: 100%; |
| | | position: relative; |
| | | } |
| | | |
| | | .zp-fixed-bac-view { |
| | | position: absolute; |
| | | width: 100%; |
| | | top: 0; |
| | | left: 0; |
| | | height: 200px; |
| | | } |
| | | |
| | | .zp-paging-main { |
| | | height: 100%; |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .zp-paging-container { |
| | | flex: 1; |
| | | position: relative; |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .zp-chat-record-loading-container { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | width: 100%; |
| | | /* #endif */ |
| | | /* #ifdef APP-NVUE */ |
| | | width: 750rpx; |
| | | /* #endif */ |
| | | align-items: center; |
| | | justify-content: center; |
| | | height: 60rpx; |
| | | font-size: 26rpx; |
| | | } |
| | | |
| | | .zp-chat-record-loading-custom-image { |
| | | width: 35rpx; |
| | | height: 35rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | animation: loading-flower 1s linear infinite; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-custom-refresher-container { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .zp-custom-refresher-refresh { |
| | | /* #ifndef APP-NVUE */ |
| | | display: block; |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-back-to-top { |
| | | width: 76rpx; |
| | | height: 76rpx; |
| | | z-index: 999; |
| | | position: absolute; |
| | | bottom: 0rpx; |
| | | right: 25rpx; |
| | | transition-duration: .3s; |
| | | transition-property: opacity; |
| | | } |
| | | |
| | | .zp-back-to-top-show { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .zp-back-to-top-hide { |
| | | opacity: 0; |
| | | } |
| | | |
| | | .zp-back-to-top-img { |
| | | /* #ifndef APP-NVUE */ |
| | | width: 100%; |
| | | height: 100%; |
| | | /* #endif */ |
| | | /* #ifdef APP-NVUE */ |
| | | flex: 1; |
| | | /* #endif */ |
| | | z-index: 999; |
| | | } |
| | | |
| | | .zp-empty-view { |
| | | /* #ifdef APP-NVUE */ |
| | | height: 100%; |
| | | /* #endif */ |
| | | flex: 1; |
| | | } |
| | | |
| | | .zp-empty-view-center { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .zp-loading-fixed { |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .zp-safe-area-inset-bottom { |
| | | position: absolute; |
| | | /* #ifndef APP-PLUS */ |
| | | height: env(safe-area-inset-bottom); |
| | | /* #endif */ |
| | | } |
| | | |
| | | .zp-n-refresh-container { |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | justify-content: center; |
| | | width: 750rpx; |
| | | } |
| | | |
| | | .zp-n-list-container{ |
| | | /* #ifndef APP-NVUE */ |
| | | display: flex; |
| | | /* #endif */ |
| | | flex-direction: row; |
| | | flex: 1; |
| | | } |
| New file |
| | |
| | | /* [z-paging]公用的静态css资源 */ |
| | | |
| | | .zp-line-loading-image { |
| | | margin-right: 8rpx; |
| | | width: 28rpx; |
| | | height: 28rpx; |
| | | /* #ifndef APP-NVUE */ |
| | | animation: loading-flower 1s steps(12) infinite; |
| | | /* #endif */ |
| | | color: #666666; |
| | | } |
| | | |
| | | .zp-loading-image-ios{ |
| | | width: 20px; |
| | | height: 20px; |
| | | } |
| | | |
| | | .zp-loading-image-android{ |
| | | width: 32rpx; |
| | | height: 32rpx; |
| | | } |
| | | |
| | | /* #ifndef APP-NVUE */ |
| | | @keyframes loading-flower { |
| | | 0% { |
| | | -webkit-transform: rotate(0deg); |
| | | transform: rotate(0deg); |
| | | } |
| | | to { |
| | | -webkit-transform: rotate(1turn); |
| | | transform: rotate(1turn); |
| | | } |
| | | } |
| | | /* #endif */ |
| | | |
| New file |
| | |
| | | { |
| | | "zp.refresher.default": "Pull down to refresh", |
| | | "zp.refresher.pulling": "Release to refresh", |
| | | "zp.refresher.refreshing": "Refreshing...", |
| | | "zp.refresher.complete": "Refresh succeeded", |
| | | |
| | | "zp.loadingMore.default": "Click to load more", |
| | | "zp.loadingMore.loading": "Loading...", |
| | | "zp.loadingMore.noMore": "No more data", |
| | | "zp.loadingMore.fail": "Load failed,click to reload", |
| | | |
| | | "zp.emptyView.title": "No data", |
| | | "zp.emptyView.reload": "Reload", |
| | | "zp.emptyView.error": "Sorry,load failed", |
| | | |
| | | "zp.refresherUpdateTime.title": "Last update: ", |
| | | "zp.refresherUpdateTime.none": "None", |
| | | "zp.refresherUpdateTime.today": "Today", |
| | | "zp.refresherUpdateTime.yesterday": "Yesterday", |
| | | |
| | | "zp.systemLoading.title": "Loading..." |
| | | } |
| New file |
| | |
| | | import en from './en.json' |
| | | import zhHans from './zh-Hans.json' |
| | | import zhHant from './zh-Hant.json' |
| | | export default { |
| | | en, |
| | | 'zh-Hans': zhHans, |
| | | 'zh-Hant': zhHant |
| | | } |
| New file |
| | |
| | | { |
| | | "zp.refresher.default": "继续下拉刷新", |
| | | "zp.refresher.pulling": "松开立即刷新", |
| | | "zp.refresher.refreshing": "正在刷新...", |
| | | "zp.refresher.complete": "刷新成功", |
| | | |
| | | "zp.loadingMore.default": "点击加载更多", |
| | | "zp.loadingMore.loading": "正在加载...", |
| | | "zp.loadingMore.noMore": "没有更多了", |
| | | "zp.loadingMore.fail": "加载失败,点击重新加载", |
| | | |
| | | "zp.emptyView.title": "没有数据哦~", |
| | | "zp.emptyView.reload": "重新加载", |
| | | "zp.emptyView.error": "很抱歉,加载失败", |
| | | |
| | | "zp.refresherUpdateTime.title": "最后更新:", |
| | | "zp.refresherUpdateTime.none": "无", |
| | | "zp.refresherUpdateTime.today": "今天", |
| | | "zp.refresherUpdateTime.yesterday": "昨天", |
| | | |
| | | "zp.systemLoading.title": "加载中..." |
| | | } |
| New file |
| | |
| | | { |
| | | "zp.refresher.default": "繼續下拉重繪", |
| | | "zp.refresher.pulling": "鬆開立即重繪", |
| | | "zp.refresher.refreshing": "正在重繪...", |
| | | "zp.refresher.complete": "重繪成功", |
| | | |
| | | "zp.loadingMore.default": "點擊加載更多", |
| | | "zp.loadingMore.loading": "正在加載...", |
| | | "zp.loadingMore.noMore": "沒有更多了", |
| | | "zp.loadingMore.fail": "加載失敗,點擊重新加載", |
| | | |
| | | "zp.emptyView.title": "沒有數據哦~", |
| | | "zp.emptyView.reload": "重新加載", |
| | | "zp.emptyView.error": "很抱歉,加載失敗", |
| | | |
| | | "zp.refresherUpdateTime.title": "最後更新:", |
| | | "zp.refresherUpdateTime.none": "無", |
| | | "zp.refresherUpdateTime.today": "今天", |
| | | "zp.refresherUpdateTime.yesterday": "昨天", |
| | | |
| | | "zp.systemLoading.title": "加載中..." |
| | | } |
| New file |
| | |
| | | // [z-paging]useZPaging hooks |
| | | |
| | | import { onPageScroll, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app'; |
| | | |
| | | function useZPaging(paging) { |
| | | const cPaging = !!paging ? paging.value || paging : null; |
| | | |
| | | onPullDownRefresh(() => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.reload().catch(() => {}); |
| | | }) |
| | | |
| | | onPageScroll(e => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.updatePageScrollTop(e.scrollTop); |
| | | e.scrollTop < 10 && cPaging.value.doChatRecordLoadMore(); |
| | | }) |
| | | |
| | | onReachBottom(() => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.pageReachBottom(); |
| | | }) |
| | | } |
| | | |
| | | export default useZPaging |
| New file |
| | |
| | | // [z-paging]useZPagingComp hooks |
| | | |
| | | function useZPagingComp(paging) { |
| | | const cPaging = !!paging ? paging.value || paging : null; |
| | | |
| | | const reload = () => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.reload().catch(() => {}); |
| | | } |
| | | const updatePageScrollTop = scrollTop => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.updatePageScrollTop(scrollTop); |
| | | } |
| | | const doChatRecordLoadMore = () => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.doChatRecordLoadMore(); |
| | | } |
| | | const pageReachBottom = () => { |
| | | if (!cPaging || !cPaging.value) return; |
| | | cPaging.value.pageReachBottom(); |
| | | } |
| | | return { reload, updatePageScrollTop, doChatRecordLoadMore, pageReachBottom }; |
| | | } |
| | | |
| | | export default useZPagingComp |
| New file |
| | |
| | | // [z-paging]点击返回顶部view模块 |
| | | import u from '.././z-paging-utils' |
| | | |
| | | export default { |
| | | props: { |
| | | //自动显示点击返回顶部按钮,默认为否 |
| | | autoShowBackToTop: { |
| | | type: Boolean, |
| | | default: u.gc('autoShowBackToTop', false) |
| | | }, |
| | | //点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx |
| | | backToTopThreshold: { |
| | | type: [Number, String], |
| | | default: u.gc('backToTopThreshold', '400rpx') |
| | | }, |
| | | //点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片 |
| | | backToTopImg: { |
| | | type: String, |
| | | default: u.gc('backToTopImg', '') |
| | | }, |
| | | //点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是 |
| | | backToTopWithAnimate: { |
| | | type: Boolean, |
| | | default: u.gc('backToTopWithAnimate', true) |
| | | }, |
| | | //点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx |
| | | backToTopBottom: { |
| | | type: [Number, String], |
| | | default: u.gc('backToTopBottom', '160rpx') |
| | | }, |
| | | //点击返回顶部按钮的自定义样式 |
| | | backToTopStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('backToTopStyle', {}); |
| | | }, |
| | | }, |
| | | //iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是 |
| | | enableBackToTop: { |
| | | type: Boolean, |
| | | default: u.gc('enableBackToTop', true) |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | backToTopClass: 'zp-back-to-top zp-back-to-top-hide', |
| | | lastBackToTopShowTime: 0, |
| | | showBackToTopClass: false, |
| | | } |
| | | }, |
| | | computed: { |
| | | finalEnableBackToTop() { |
| | | return this.usePageScroll ? false : this.enableBackToTop; |
| | | }, |
| | | finalBackToTopThreshold() { |
| | | return u.convertToPx(this.backToTopThreshold); |
| | | }, |
| | | finalBackToTopStyle() { |
| | | const backToTopStyle = this.backToTopStyle; |
| | | if (!backToTopStyle.bottom) { |
| | | backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottom) + 'px'; |
| | | } |
| | | if(!backToTopStyle.position){ |
| | | backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute'; |
| | | } |
| | | return backToTopStyle; |
| | | }, |
| | | }, |
| | | methods: { |
| | | //点击返回顶部 |
| | | _backToTopClick() { |
| | | let callbacked = false; |
| | | this.$emit('backToTopClick', toTop => { |
| | | (toTop === undefined || toTop === true) && this._handleToTop(); |
| | | callbacked = true; |
| | | }); |
| | | this.$nextTick(() => { |
| | | !callbacked && this._handleToTop(); |
| | | }) |
| | | }, |
| | | //处理滚动到顶部 |
| | | _handleToTop() { |
| | | !this.backToTopWithAnimate && this._checkShouldShowBackToTop(0); |
| | | this.scrollToTop(this.backToTopWithAnimate); |
| | | }, |
| | | //判断是否要显示返回顶部按钮 |
| | | _checkShouldShowBackToTop(scrollTop) { |
| | | if (!this.autoShowBackToTop) { |
| | | this.showBackToTopClass = false; |
| | | return; |
| | | } |
| | | if (scrollTop > this.finalBackToTopThreshold) { |
| | | if (!this.showBackToTopClass) { |
| | | this.showBackToTopClass = true; |
| | | this.lastBackToTopShowTime = new Date().getTime(); |
| | | u.delay(() => { |
| | | this.backToTopClass = 'zp-back-to-top zp-back-to-top-show'; |
| | | }, 300) |
| | | } |
| | | } else { |
| | | if (this.showBackToTopClass) { |
| | | this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide'; |
| | | u.delay(() => { |
| | | this.showBackToTopClass = false; |
| | | }, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300) |
| | | } |
| | | } |
| | | }, |
| | | } |
| | | } |
| | | |
| New file |
| | |
| | | // [z-paging]通用布局相关模块 |
| | | |
| | | // #ifdef APP-NVUE |
| | | const weexDom = weex.requireModule('dom'); |
| | | // #endif |
| | | |
| | | export default { |
| | | data() { |
| | | return { |
| | | systemInfo: null, |
| | | cssSafeAreaInsetBottom: -1, |
| | | } |
| | | }, |
| | | computed: { |
| | | windowTop() { |
| | | if (!this.systemInfo) return 0; |
| | | //暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634 |
| | | //感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25 |
| | | // #ifdef VUE3 && H5 |
| | | const pageHeadNode = document.getElementsByTagName("uni-page-head"); |
| | | if (!pageHeadNode.length) return 0; |
| | | // #endif |
| | | return this.systemInfo.windowTop || 0; |
| | | }, |
| | | safeAreaBottom() { |
| | | if (!this.systemInfo) return 0; |
| | | let safeAreaBottom = 0; |
| | | // #ifdef APP-PLUS |
| | | safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ; |
| | | // #endif |
| | | // #ifndef APP-PLUS |
| | | safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0); |
| | | // #endif |
| | | return safeAreaBottom; |
| | | }, |
| | | isOldWebView() { |
| | | // #ifndef APP-NVUE || MP-KUAISHOU |
| | | try { |
| | | const systemInfos = uni.getSystemInfoSync().system.split(' '); |
| | | const deviceType = systemInfos[0]; |
| | | const version = parseInt(systemInfos[1]); |
| | | if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) { |
| | | return true; |
| | | } |
| | | } catch(e) { |
| | | return false; |
| | | } |
| | | // #endif |
| | | return false; |
| | | }, |
| | | zSlots() { |
| | | // #ifdef VUE2 |
| | | |
| | | // #ifdef MP-ALIPAY |
| | | return this.$slots; |
| | | // #endif |
| | | |
| | | return this.$scopedSlots || this.$slots; |
| | | // #endif |
| | | |
| | | return this.$slots; |
| | | } |
| | | }, |
| | | methods: { |
| | | //获取节点尺寸 |
| | | _getNodeClientRect(select, inDom = true, scrollOffset = false) { |
| | | // #ifdef APP-NVUE |
| | | select = select.replace(/[.|#]/g, ''); |
| | | const ref = this.$refs[select]; |
| | | return new Promise((resolve, reject) => { |
| | | if (ref) { |
| | | weexDom.getComponentRect(ref, option => { |
| | | resolve(option && option.result ? [option.size] : false); |
| | | }) |
| | | } else { |
| | | resolve(false); |
| | | } |
| | | }); |
| | | return; |
| | | // #endif |
| | | //#ifdef MP-ALIPAY |
| | | inDom = false; |
| | | //#endif |
| | | let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery(); |
| | | scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect(); |
| | | return new Promise((resolve, reject) => { |
| | | res.exec(data => { |
| | | resolve((data && data != '' && data != undefined && data.length) ? data : false); |
| | | }); |
| | | }); |
| | | }, |
| | | //获取slot="left"和slot="right"宽度并且更新布局 |
| | | _updateLeftAndRightWidth(targetStyle, parentNodePrefix) { |
| | | this.$nextTick(() => { |
| | | let delayTime = 0; |
| | | // #ifdef MP-BAIDU |
| | | delayTime = 10; |
| | | // #endif |
| | | setTimeout(() => { |
| | | ['left','right'].map(position => { |
| | | this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => { |
| | | this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px'); |
| | | }); |
| | | }) |
| | | }, delayTime) |
| | | }) |
| | | }, |
| | | //通过获取css设置的底部安全区域占位view高度设置bottom距离 |
| | | _getCssSafeAreaInsetBottom(success) { |
| | | this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => { |
| | | this.cssSafeAreaInsetBottom = res ? res[0].height : -1; |
| | | res && success && success(); |
| | | }); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]数据处理模块 |
| | | import u from '.././z-paging-utils' |
| | | import c from '.././z-paging-constant' |
| | | import Enum from '.././z-paging-enum' |
| | | import interceptor from '../z-paging-interceptor' |
| | | |
| | | export default { |
| | | props: { |
| | | //自定义初始的pageNo,默认为1 |
| | | defaultPageNo: { |
| | | type: [Number, String], |
| | | default: u.gc('defaultPageNo', 1), |
| | | observer: function(newVal) { |
| | | this.pageNo = newVal; |
| | | }, |
| | | }, |
| | | //自定义pageSize,默认为10 |
| | | defaultPageSize: { |
| | | type: [Number, String], |
| | | default: u.gc('defaultPageSize', 10), |
| | | validator: (value) => { |
| | | if (value <= 0) u.consoleErr('default-page-size必须大于0!'); |
| | | return value > 0; |
| | | } |
| | | }, |
| | | //为保证数据一致,设置当前tab切换时的标识key,并在complete中传递相同key,若二者不一致,则complete将不会生效 |
| | | dataKey: { |
| | | type: [Number, String, Object], |
| | | default: function() { |
| | | return u.gc('dataKey', null); |
| | | }, |
| | | }, |
| | | //使用缓存,若开启将自动缓存第一页的数据,默认为否。请注意,因考虑到切换tab时不同tab数据不同的情况,默认仅会缓存组件首次加载时第一次请求到的数据,后续的下拉刷新操作不会更新缓存。 |
| | | useCache: { |
| | | type: Boolean, |
| | | default: u.gc('useCache', false) |
| | | }, |
| | | //使用缓存时缓存的key,用于区分不同列表的缓存数据,useCache为true时必须设置,否则缓存无效 |
| | | cacheKey: { |
| | | type: String, |
| | | default: u.gc('cacheKey', null) |
| | | }, |
| | | //缓存模式,默认仅会缓存组件首次加载时第一次请求到的数据,可设置为always,即代表总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 |
| | | cacheMode: { |
| | | type: String, |
| | | default: u.gc('cacheMode', Enum.CacheMode.Default) |
| | | }, |
| | | //自动注入的list名,可自动修改父view(包含ref="paging")中对应name的list值 |
| | | autowireListName: { |
| | | type: String, |
| | | default: u.gc('autowireListName', '') |
| | | }, |
| | | //自动注入的query名,可自动调用父view(包含ref="paging")中的query方法 |
| | | autowireQueryName: { |
| | | type: String, |
| | | default: u.gc('autowireQueryName', '') |
| | | }, |
| | | //z-paging mounted后自动调用reload方法(mounted后自动调用接口),默认为是 |
| | | auto: { |
| | | type: Boolean, |
| | | default: u.gc('auto', true) |
| | | }, |
| | | //用户下拉刷新时是否触发reload方法,默认为是 |
| | | reloadWhenRefresh: { |
| | | type: Boolean, |
| | | default: u.gc('reloadWhenRefresh', true) |
| | | }, |
| | | //reload时自动滚动到顶部,默认为是 |
| | | autoScrollToTopWhenReload: { |
| | | type: Boolean, |
| | | default: u.gc('autoScrollToTopWhenReload', true) |
| | | }, |
| | | //reload时立即自动清空原list,默认为是,若立即自动清空,则在reload之后、请求回调之前页面是空白的 |
| | | autoCleanListWhenReload: { |
| | | type: Boolean, |
| | | default: u.gc('autoCleanListWhenReload', true) |
| | | }, |
| | | //列表刷新时自动显示下拉刷新view,默认为否 |
| | | showRefresherWhenReload: { |
| | | type: Boolean, |
| | | default: u.gc('showRefresherWhenReload', false) |
| | | }, |
| | | //列表刷新时自动显示加载更多view,且为加载中状态,默认为否 |
| | | showLoadingMoreWhenReload: { |
| | | type: Boolean, |
| | | default: u.gc('showLoadingMoreWhenReload', false) |
| | | }, |
| | | //组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题),auto为true时有效。为否时将在mounted+nextTick后触发reload,默认为否 |
| | | createdReload: { |
| | | type: Boolean, |
| | | default: u.gc('createdReload', false) |
| | | }, |
| | | //本地分页时上拉加载更多延迟时间,单位为毫秒,默认200毫秒 |
| | | localPagingLoadingTime: { |
| | | type: [Number, String], |
| | | default: u.gc('localPagingLoadingTime', 200) |
| | | }, |
| | | //使用聊天记录模式,默认为否 |
| | | useChatRecordMode: { |
| | | type: Boolean, |
| | | default: u.gc('useChatRecordMode', false) |
| | | }, |
| | | //使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是 |
| | | autoHideKeyboardWhenChat: { |
| | | type: Boolean, |
| | | default: u.gc('autoHideKeyboardWhenChat', true) |
| | | }, |
| | | //自动拼接complete中传过来的数组(使用聊天记录模式时无效) |
| | | concat: { |
| | | type: Boolean, |
| | | default: u.gc('concat', true) |
| | | }, |
| | | //请求失败是否触发reject,默认为是 |
| | | callNetworkReject: { |
| | | type: Boolean, |
| | | default: u.gc('callNetworkReject', true) |
| | | }, |
| | | //父组件v-model所绑定的list的值 |
| | | value: { |
| | | type: Array, |
| | | default: function() { |
| | | return []; |
| | | } |
| | | }, |
| | | // #ifdef VUE3 |
| | | modelValue: { |
| | | type: Array, |
| | | default: function() { |
| | | return []; |
| | | } |
| | | } |
| | | // #endif |
| | | }, |
| | | data (){ |
| | | return { |
| | | currentData: [], |
| | | totalData: [], |
| | | realTotalData: [], |
| | | totalLocalPagingList: [], |
| | | dataPromiseResultMap: { |
| | | reload: null, |
| | | complete: null, |
| | | localPaging: null |
| | | }, |
| | | isSettingCacheList: false, |
| | | pageNo: 1, |
| | | currentRefreshPageSize: 0, |
| | | isLocalPaging: false, |
| | | isAddedData: false, |
| | | isTotalChangeFromAddData: false, |
| | | privateConcat: true, |
| | | myParentQuery: -1, |
| | | firstPageLoaded: false, |
| | | pagingLoaded: false, |
| | | loaded: false, |
| | | isUserReload: true, |
| | | fromEmptyViewReload: false, |
| | | queryFrom: '', |
| | | listRendering: false, |
| | | isHandlingRefreshToPage: false |
| | | } |
| | | }, |
| | | computed: { |
| | | pageSize() { |
| | | return this.defaultPageSize; |
| | | }, |
| | | finalConcat() { |
| | | return this.concat && this.privateConcat; |
| | | }, |
| | | finalUseCache() { |
| | | if (this.useCache && !this.cacheKey) { |
| | | u.consoleErr('use-cache为true时,必须设置cache-key,否则缓存无效!'); |
| | | } |
| | | return this.useCache && !!this.cacheKey; |
| | | }, |
| | | finalCacheKey() { |
| | | return this.cacheKey ? `${c.cachePrefixKey}-${this.cacheKey}` : null; |
| | | }, |
| | | isFirstPage() { |
| | | return this.pageNo === this.defaultPageNo; |
| | | } |
| | | }, |
| | | watch: { |
| | | totalData(newVal, oldVal) { |
| | | this._totalDataChange(newVal, oldVal); |
| | | }, |
| | | currentData(newVal, oldVal) { |
| | | this._currentDataChange(newVal, oldVal); |
| | | }, |
| | | useChatRecordMode(newVal, oldVal) { |
| | | if (newVal) { |
| | | this.nLoadingMoreFixedHeight = false; |
| | | } |
| | | }, |
| | | value: { |
| | | handler(newVal) { |
| | | this.realTotalData = newVal; |
| | | }, |
| | | immediate: true |
| | | }, |
| | | // #ifdef VUE3 |
| | | modelValue: { |
| | | handler(newVal) { |
| | | this.realTotalData = newVal; |
| | | }, |
| | | immediate: true |
| | | } |
| | | // #endif |
| | | }, |
| | | methods: { |
| | | //请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否成功(默认是是) |
| | | complete(data, success = true) { |
| | | this.customNoMore = -1; |
| | | return this.addData(data, success); |
| | | }, |
| | | //【保证数据一致】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为dataKey,需与:data-key绑定的一致,第三个参数为是否成功(默认为是) |
| | | completeByKey(data, dataKey = null, success = true) { |
| | | if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) { |
| | | this.isFirstPage && this.endRefresh(); |
| | | return new Promise(resolve => resolve()); |
| | | } |
| | | this.customNoMore = -1; |
| | | return this.addData(data, success); |
| | | }, |
| | | //【通过total判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为total(列表总数),第三个参数为是否成功(默认为是) |
| | | completeByTotal(data, total, success = true) { |
| | | if (total == 'undefined') { |
| | | this.customNoMore = -1; |
| | | } else { |
| | | const dataTypeRes = this._checkDataType(data, success, false); |
| | | data = dataTypeRes.data; |
| | | success = dataTypeRes.success; |
| | | if (total >= 0 && success) { |
| | | return new Promise((resolve, reject) => { |
| | | this.$nextTick(() => { |
| | | let nomore = false; |
| | | const realTotalDataCount = this.pageNo == this.defaultPageNo ? 0 : this.realTotalData.length; |
| | | const dataLength = this.privateConcat ? data.length : 0; |
| | | let exceedCount = realTotalDataCount + dataLength - total; |
| | | if (exceedCount >= 0) { |
| | | nomore = true; |
| | | exceedCount = this.defaultPageSize - exceedCount; |
| | | if (this.privateConcat && exceedCount > 0 && exceedCount < data.length) { |
| | | data = data.splice(0, exceedCount); |
| | | } |
| | | } |
| | | this.completeByNoMore(data, nomore, success).then(res => resolve(res)).catch(() => reject()); |
| | | }) |
| | | }); |
| | | } |
| | | } |
| | | return this.addData(data, success); |
| | | }, |
| | | //【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否有更多数据,第三个参数为是否成功(默认是是) |
| | | completeByNoMore(data, nomore, success = true) { |
| | | if (nomore != 'undefined') { |
| | | this.customNoMore = nomore == true ? 1 : 0; |
| | | } |
| | | return this.addData(data, success); |
| | | }, |
| | | //与上方complete方法功能一致,新版本中设置服务端回调数组请使用complete方法 |
| | | addData(data, success = true) { |
| | | if (!this.fromCompleteEmit) { |
| | | this.disabledCompleteEmit = true; |
| | | this.fromCompleteEmit = false; |
| | | } |
| | | const currentTimeStamp = u.getTime(); |
| | | const disTime = currentTimeStamp - this.requestTimeStamp; |
| | | let minDelay = this.minDelay; |
| | | if (this.isFirstPage && this.finalShowRefresherWhenReload) { |
| | | minDelay = Math.max(400, minDelay); |
| | | } |
| | | const addDataDalay = (this.requestTimeStamp > 0 && disTime < minDelay) ? minDelay - disTime : 0; |
| | | this.$nextTick(() => { |
| | | u.delay(() => { |
| | | this._addData(data, success, false); |
| | | }, this.delay > 0 ? this.delay : addDataDalay) |
| | | }) |
| | | |
| | | return new Promise((resolve, reject) => { |
| | | this.dataPromiseResultMap.complete = { resolve, reject }; |
| | | }); |
| | | }, |
| | | //从顶部添加数据,不会影响分页的pageNo和pageSize |
| | | addDataFromTop(data, toTop = true, toTopWithAnimate = true) { |
| | | data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data.reverse(); |
| | | // #ifndef APP-NVUE |
| | | this.finalUseVirtualList && this._setCellIndex(data, 'top') |
| | | // #endif |
| | | this.totalData = [...data, ...this.totalData]; |
| | | if (toTop) { |
| | | u.delay(() => this._scrollToTop(toTopWithAnimate)); |
| | | } |
| | | }, |
| | | //重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求。适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时,请使用此方法,请勿直接修改page中:list.sync绑定的数组) |
| | | resetTotalData(data) { |
| | | this.isTotalChangeFromAddData = true; |
| | | data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; |
| | | this.totalData = data; |
| | | }, |
| | | //添加聊天记录 |
| | | addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) { |
| | | data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; |
| | | if (!this.useChatRecordMode) return; |
| | | this.isTotalChangeFromAddData = true; |
| | | //#ifndef APP-NVUE |
| | | this.totalData = [...this.totalData, ...data]; |
| | | //#endif |
| | | //#ifdef APP-NVUE |
| | | this.totalData = this.nIsFirstPageAndNoMore ? [...this.totalData, ...data] : [...data, ...this.totalData]; |
| | | //#endif |
| | | if (toBottom) { |
| | | u.delay(() => { |
| | | //#ifndef APP-NVUE |
| | | this._scrollToBottom(toBottomWithAnimate); |
| | | //#endif |
| | | //#ifdef APP-NVUE |
| | | this.nIsFirstPageAndNoMore ? this._scrollToBottom(toBottomWithAnimate) : this._scrollToTop(toBottomWithAnimate); |
| | | //#endif |
| | | }) |
| | | } |
| | | }, |
| | | //设置本地分页数据,请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging作分页处理(若调用了此方法,则上拉加载更多时内部会自动分页,不会触发@query所绑定的事件) |
| | | setLocalPaging(data, success = true) { |
| | | this.isLocalPaging = true; |
| | | this.$nextTick(() => { |
| | | this._addData(data, success, true); |
| | | }) |
| | | return new Promise((resolve, reject) => { |
| | | this.dataPromiseResultMap.localPaging = { resolve, reject }; |
| | | }); |
| | | }, |
| | | //重新加载分页数据,pageNo会恢复为默认值,相当于下拉刷新的效果(animate为true时会展示下拉刷新动画,默认为false) |
| | | reload(animate = this.showRefresherWhenReload) { |
| | | if (animate) { |
| | | this.privateShowRefresherWhenReload = animate; |
| | | this.isUserPullDown = true; |
| | | } |
| | | if (!this.showLoadingMoreWhenReload) { |
| | | this.listRendering = true; |
| | | } |
| | | this.$nextTick(() => { |
| | | this._preReload(animate, false); |
| | | }) |
| | | return new Promise((resolve, reject) => { |
| | | this.dataPromiseResultMap.reload = { resolve, reject }; |
| | | }); |
| | | }, |
| | | //刷新列表数据,pageNo和pageSize不会重置,列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 |
| | | refresh() { |
| | | return this._handleRefreshWithDisPageNo(this.pageNo - this.defaultPageNo + 1); |
| | | }, |
| | | //刷新列表数据至指定页,例如pageNo=5时则代表刷新列表至第5页,此时pageNo会变为5,列表会展示前5页的数据。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 |
| | | refreshToPage(pageNo) { |
| | | this.isHandlingRefreshToPage = true; |
| | | return this._handleRefreshWithDisPageNo(pageNo + this.defaultPageNo - 1); |
| | | }, |
| | | //手动更新列表缓存数据,将自动截取v-model绑定的list中的前pageSize条覆盖缓存,请确保在list数据更新到预期结果后再调用此方法 |
| | | updateCache() { |
| | | if (this.finalUseCache && this.totalData.length) { |
| | | this._saveLocalCache(this.totalData.slice(0, Math.min(this.totalData.length, this.pageSize))); |
| | | } |
| | | }, |
| | | //清空分页数据 |
| | | clean() { |
| | | this._reload(true); |
| | | this._addData([], true, false); |
| | | }, |
| | | //清空分页数据 |
| | | clear() { |
| | | this.clean(); |
| | | }, |
| | | //手动触发滚动到顶部加载更多,聊天记录模式时有效 |
| | | doChatRecordLoadMore() { |
| | | this.useChatRecordMode && this._onLoadingMore('click'); |
| | | }, |
| | | //reload之前的一些处理 |
| | | _preReload(animate = this.showRefresherWhenReload, isFromMounted = true) { |
| | | const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher; |
| | | // #ifndef APP-NVUE |
| | | if (this.customRefresherHeight === -1 && showRefresher) { |
| | | u.delay(() => this._preReload(animate, isFromMounted), c.delayTime / 2); |
| | | return; |
| | | } |
| | | // #endif |
| | | this.isUserReload = true; |
| | | this.loadingType = Enum.LoadingType.Refresher; |
| | | if (animate) { |
| | | this.privateShowRefresherWhenReload = animate; |
| | | // #ifndef APP-NVUE |
| | | if (this.useCustomRefresher) { |
| | | this._doRefresherRefreshAnimate(); |
| | | } else { |
| | | this.refresherTriggered = true; |
| | | } |
| | | // #endif |
| | | // #ifdef APP-NVUE |
| | | this.refresherStatus = Enum.Refresher.Loading; |
| | | this.refresherRevealStackCount ++; |
| | | u.delay(() => { |
| | | this._getNodeClientRect('zp-n-refresh-container', false).then((node) => { |
| | | if (node) { |
| | | let nodeHeight = node[0].height; |
| | | this.nShowRefresherReveal = true; |
| | | this.nShowRefresherRevealHeight = nodeHeight; |
| | | u.delay(() => { |
| | | this._nDoRefresherEndAnimation(0, -nodeHeight, false, false); |
| | | u.delay(() => { |
| | | this._nDoRefresherEndAnimation(nodeHeight, 0); |
| | | }, 10) |
| | | }, 10) |
| | | } |
| | | this._reload(false, isFromMounted); |
| | | this._doRefresherLoad(false); |
| | | }); |
| | | }, this.pagingLoaded ? 10 : 100) |
| | | return; |
| | | // #endif |
| | | } else { |
| | | this._refresherEnd(false, false, false, false); |
| | | } |
| | | this._reload(false, isFromMounted); |
| | | }, |
| | | //重新加载分页数据 |
| | | _reload(isClean = false, isFromMounted = false, isUserPullDown = false) { |
| | | this.isAddedData = false; |
| | | this.insideOfPaging = -1; |
| | | this.cacheScrollNodeHeight = -1; |
| | | this.pageNo = this.defaultPageNo; |
| | | this._cleanRefresherEndTimeout(); |
| | | !this.privateShowRefresherWhenReload && !isClean && this._startLoading(true); |
| | | this.firstPageLoaded = true; |
| | | this.isTotalChangeFromAddData = false; |
| | | if (!this.isSettingCacheList) { |
| | | this.totalData = []; |
| | | } |
| | | if (!isClean) { |
| | | this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload); |
| | | let delay = 0; |
| | | // #ifdef MP-TOUTIAO |
| | | delay = 5; |
| | | // #endif |
| | | u.delay(this._callMyParentQuery, delay); |
| | | if (!isFromMounted && this.autoScrollToTopWhenReload) { |
| | | let checkedNRefresherLoading = true; |
| | | // #ifdef APP-NVUE |
| | | checkedNRefresherLoading = !this.nRefresherLoading; |
| | | // #endif |
| | | checkedNRefresherLoading && this._scrollToTop(false); |
| | | } |
| | | } |
| | | // #ifdef APP-NVUE |
| | | this.$nextTick(() => { |
| | | this.nShowBottom = this.realTotalData.length > 0; |
| | | }) |
| | | // #endif |
| | | }, |
| | | //处理服务端返回的数组 |
| | | _addData(data, success, isLocal) { |
| | | this.isAddedData = true; |
| | | this.fromEmptyViewReload = false; |
| | | this.isTotalChangeFromAddData = true; |
| | | this.refresherTriggered = false; |
| | | this._endSystemLoadingAndRefresh(); |
| | | const tempIsUserPullDown = this.isUserPullDown; |
| | | if (this.showRefresherUpdateTime && this.isFirstPage) { |
| | | u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey); |
| | | this.$refs.refresh && this.$refs.refresh.updateTime(); |
| | | } |
| | | if (!isLocal && tempIsUserPullDown && this.isFirstPage) { |
| | | this.isUserPullDown = false; |
| | | } |
| | | if (!this.isFirstPage) { |
| | | this.listRendering = true; |
| | | this.$nextTick(() => { |
| | | u.delay(() => this.listRendering = false); |
| | | }) |
| | | } else { |
| | | this.listRendering = false; |
| | | } |
| | | let dataTypeRes = this._checkDataType(data, success, isLocal); |
| | | data = dataTypeRes.data; |
| | | success = dataTypeRes.success; |
| | | let delayTime = c.delayTime; |
| | | // #ifdef APP-NVUE |
| | | if (this.useChatRecordMode) delayTime = 0; |
| | | // #endif |
| | | this.loadingForNow = false; |
| | | u.delay(() => { |
| | | this.pagingLoaded = true; |
| | | this.$nextTick(()=>{ |
| | | !isLocal && this._refresherEnd(delayTime > 0, true, tempIsUserPullDown); |
| | | }) |
| | | }) |
| | | if (this.isFirstPage) { |
| | | this.isLoadFailed = !success; |
| | | this.$emit('isLoadFailedChange', this.isLoadFailed); |
| | | if (this.finalUseCache && success && (this.cacheMode === Enum.CacheMode.Always ? true : this.isSettingCacheList)) { |
| | | this._saveLocalCache(data); |
| | | } |
| | | } |
| | | this.isSettingCacheList = false; |
| | | if (success) { |
| | | if (!(this.privateConcat === false && this.loadingStatus === Enum.More.NoMore)) { |
| | | this.loadingStatus = Enum.More.Default; |
| | | } |
| | | if (isLocal) { |
| | | this.totalLocalPagingList = data; |
| | | const localPageNo = this.defaultPageNo; |
| | | const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize; |
| | | this._localPagingQueryList(localPageNo, localPageSize, 0, res => { |
| | | this.completeByTotal(res, this.totalLocalPagingList.length); |
| | | }) |
| | | } else { |
| | | let dataChangeDelayTime = 0; |
| | | // #ifdef APP-NVUE |
| | | if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') { |
| | | dataChangeDelayTime = 150; |
| | | } |
| | | // #endif |
| | | u.delay(() => { |
| | | this._currentDataChange(data, this.currentData); |
| | | this._callDataPromise(true, this.totalData); |
| | | }, dataChangeDelayTime) |
| | | } |
| | | if (this.isHandlingRefreshToPage) { |
| | | this.isHandlingRefreshToPage = false; |
| | | this.pageNo = this.defaultPageNo + Math.ceil(data.length / this.pageSize) - 1; |
| | | if (data.length % this.pageSize !== 0) { |
| | | this.customNoMore = 1; |
| | | } |
| | | } |
| | | } else { |
| | | this._currentDataChange(data, this.currentData); |
| | | this._callDataPromise(false); |
| | | this.loadingStatus = Enum.More.Fail; |
| | | this.isHandlingRefreshToPage = false; |
| | | if (this.loadingType === Enum.LoadingType.LoadingMore) { |
| | | this.pageNo --; |
| | | } |
| | | } |
| | | }, |
| | | //所有数据改变时调用 |
| | | _totalDataChange(newVal, oldVal, eventThrow=true) { |
| | | if ((!this.isUserReload || !this.autoCleanListWhenReload) && this.firstPageLoaded && !newVal.length && oldVal.length) { |
| | | return; |
| | | } |
| | | this._doCheckScrollViewShouldFullHeight(newVal); |
| | | if(!this.realTotalData.length && !newVal.length){ |
| | | eventThrow = false; |
| | | } |
| | | this.realTotalData = newVal; |
| | | if (eventThrow) { |
| | | this.$emit('input', newVal); |
| | | // #ifdef VUE3 |
| | | this.$emit('update:modelValue', newVal); |
| | | // #endif |
| | | this.$emit('update:list', newVal); |
| | | this.$emit('listChange', newVal); |
| | | this._callMyParentList(newVal); |
| | | } |
| | | this.firstPageLoaded = false; |
| | | this.isTotalChangeFromAddData = false; |
| | | this.$nextTick(() => { |
| | | u.delay(()=>{ |
| | | this._getNodeClientRect('.zp-paging-container-content').then(res => { |
| | | res && this.$emit('contentHeightChanged', res[0].height); |
| | | }); |
| | | }, c.delayTime * (this.isIos ? 1 : 3)) |
| | | // #ifdef APP-NVUE |
| | | if (this.useChatRecordMode && this.nIsFirstPageAndNoMore && this.isFirstPage && !this.nFirstPageAndNoMoreChecked) { |
| | | this.nFirstPageAndNoMoreChecked = true; |
| | | this._scrollToBottom(false); |
| | | } |
| | | u.delay(() => { |
| | | this.nShowBottom = true; |
| | | }, c.delayTime * 6, 'nShowBottomDelay'); |
| | | // #endif |
| | | }) |
| | | }, |
| | | //当前数据改变时调用 |
| | | _currentDataChange(newVal, oldVal) { |
| | | newVal = [...newVal]; |
| | | // #ifndef APP-NVUE |
| | | this.finalUseVirtualList && this._setCellIndex(newVal, 'bottom'); |
| | | this.useChatRecordMode && newVal.reverse(); |
| | | // #endif |
| | | if (this.isFirstPage && this.finalConcat) { |
| | | this.totalData = []; |
| | | } |
| | | if (this.customNoMore !== -1) { |
| | | if (this.customNoMore === 1 || !newVal.length) { |
| | | this.loadingStatus = Enum.More.NoMore; |
| | | } |
| | | } else { |
| | | if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) { |
| | | this.loadingStatus = Enum.More.NoMore; |
| | | } |
| | | } |
| | | if (!this.totalData.length) { |
| | | if (this.finalConcat) { |
| | | // #ifdef APP-NVUE |
| | | if (this.useChatRecordMode && this.isFirstPage && this.loadingStatus === Enum.More.NoMore) { |
| | | newVal.reverse(); |
| | | } |
| | | // #endif |
| | | this.totalData = newVal; |
| | | } |
| | | if (this.useChatRecordMode) { |
| | | // #ifndef APP-NVUE |
| | | this.$nextTick(() => { |
| | | this._scrollToBottom(false); |
| | | }) |
| | | // #endif |
| | | } |
| | | } else { |
| | | if (this.useChatRecordMode) { |
| | | // #ifdef APP-NVUE |
| | | this.totalData = [...this.totalData, ...newVal]; |
| | | // #endif |
| | | //#ifndef APP-NVUE |
| | | const idIndex = newVal.length; |
| | | let idIndexStr = `z-paging-${idIndex}`; |
| | | this.totalData = [...newVal, ...this.totalData]; |
| | | if (this.pageNo !== this.defaultPageNo) { |
| | | this.privateScrollWithAnimation = 0; |
| | | this.$emit('update:chatIndex', idIndex); |
| | | this.$nextTick(() => { |
| | | this._scrollIntoView(idIndexStr, 30 + Math.max(0, this.cacheTopHeight), false, () => { |
| | | this.$emit('update:chatIndex', 0); |
| | | }); |
| | | }) |
| | | } else { |
| | | this.$nextTick(() => { |
| | | this._scrollToBottom(false); |
| | | }) |
| | | } |
| | | //#endif |
| | | |
| | | } else { |
| | | if (this.finalConcat) { |
| | | const currentScrollTop = this.oldScrollTop; |
| | | this.totalData = [...this.totalData, ...newVal]; |
| | | // #ifdef MP-WEIXIN |
| | | if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) { |
| | | this.loadingMoreTimeStamp = u.getTime(); |
| | | this.$nextTick(() => { |
| | | this.scrollToY(currentScrollTop); |
| | | }) |
| | | } |
| | | // #endif |
| | | } else { |
| | | this.totalData = newVal; |
| | | } |
| | | } |
| | | } |
| | | this.privateConcat = true; |
| | | }, |
| | | //根据pageNo处理refresh操作 |
| | | _handleRefreshWithDisPageNo(pageNo) { |
| | | if (!this.realTotalData.length) return this.reload(); |
| | | if (pageNo >= 1) { |
| | | this.loading = true; |
| | | this.privateConcat = false; |
| | | const totalPageSize = pageNo * this.pageSize; |
| | | this.currentRefreshPageSize = totalPageSize; |
| | | this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh); |
| | | this._callMyParentQuery(this.defaultPageNo, totalPageSize); |
| | | } |
| | | return new Promise((resolve, reject) => { |
| | | this.dataPromiseResultMap.reload = { resolve, reject }; |
| | | }); |
| | | }, |
| | | //本地分页请求 |
| | | _localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) { |
| | | pageNo = Math.max(1, pageNo); |
| | | pageSize = Math.max(1, pageSize); |
| | | const totalPagingList = [...this.totalLocalPagingList]; |
| | | const pageNoIndex = (pageNo - 1) * pageSize; |
| | | const finalPageNoIndex = Math.min(totalPagingList.length, pageNoIndex + pageSize); |
| | | const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex); |
| | | u.delay(() => callback(resultPagingList), localPagingLoadingTime) |
| | | }, |
| | | //存储列表缓存数据 |
| | | _saveLocalCache(data) { |
| | | uni.setStorageSync(this.finalCacheKey, data); |
| | | }, |
| | | //通过缓存数据填充列表数据 |
| | | _setListByLocalCache() { |
| | | this.totalData = uni.getStorageSync(this.finalCacheKey) || []; |
| | | this.isSettingCacheList = true; |
| | | }, |
| | | //修改父view的list |
| | | _callMyParentList(newVal) { |
| | | if (this.autowireListName.length) { |
| | | const myParent = u.getParent(this.$parent); |
| | | if (myParent && myParent[this.autowireListName]) { |
| | | myParent[this.autowireListName] = newVal; |
| | | } |
| | | } |
| | | }, |
| | | //调用父view的query |
| | | _callMyParentQuery(customPageNo = 0, customPageSize = 0) { |
| | | if (this.autowireQueryName) { |
| | | if (this.myParentQuery === -1) { |
| | | const myParent = u.getParent(this.$parent); |
| | | if (myParent && myParent[this.autowireQueryName]) { |
| | | this.myParentQuery = myParent[this.autowireQueryName]; |
| | | } |
| | | } |
| | | if (this.myParentQuery !== -1) { |
| | | customPageSize > 0 ? this.myParentQuery(customPageNo, customPageSize) : this.myParentQuery(this.pageNo, this.defaultPageSize); |
| | | } |
| | | } |
| | | }, |
| | | //emit query事件 |
| | | _emitQuery(pageNo, pageSize, from){ |
| | | this.queryFrom = from; |
| | | this.requestTimeStamp = u.getTime(); |
| | | const [lastItem] = this.realTotalData.slice(-1); |
| | | this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null)); |
| | | }, |
| | | //触发数据改变promise |
| | | _callDataPromise(success, totalList) { |
| | | for (const key in this.dataPromiseResultMap) { |
| | | const obj = this.dataPromiseResultMap[key]; |
| | | if (!obj) continue; |
| | | success ? obj.resolve({ totalList, noMore: this.loadingStatus === Enum.More.NoMore }) : this.callNetworkReject && obj.reject(`z-paging-${key}-error`); |
| | | } |
| | | }, |
| | | //检查complete data的类型 |
| | | _checkDataType(data, success, isLocal) { |
| | | const dataType = Object.prototype.toString.call(data); |
| | | if (dataType === '[object Boolean]') { |
| | | success = data; |
| | | data = []; |
| | | } else if (dataType !== '[object Array]') { |
| | | data = []; |
| | | if (dataType !== '[object Undefined]' && dataType !== '[object Null]') { |
| | | u.consoleErr(`${isLocal ? 'setLocalPaging' : 'complete'}参数类型不正确,第一个参数类型必须为Array!`); |
| | | } |
| | | } |
| | | return { data, success }; |
| | | }, |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]空数据图view模块 |
| | | import u from '.././z-paging-utils' |
| | | |
| | | export default { |
| | | props: { |
| | | //是否强制隐藏空数据图,默认为否 |
| | | hideEmptyView: { |
| | | type: Boolean, |
| | | default: u.gc('hideEmptyView', false) |
| | | }, |
| | | //空数据图描述文字,默认为“没有数据哦~” |
| | | emptyViewText: { |
| | | type: [String, Object], |
| | | default: u.gc('emptyViewText', null) |
| | | }, |
| | | //是否显示空数据图重新加载按钮(无数据时),默认为否 |
| | | showEmptyViewReload: { |
| | | type: Boolean, |
| | | default: u.gc('showEmptyViewReload', false) |
| | | }, |
| | | //加载失败时是否显示空数据图重新加载按钮,默认为是 |
| | | showEmptyViewReloadWhenError: { |
| | | type: Boolean, |
| | | default: u.gc('showEmptyViewReloadWhenError', true) |
| | | }, |
| | | //空数据图点击重新加载文字,默认为“重新加载” |
| | | emptyViewReloadText: { |
| | | type: [String, Object], |
| | | default: u.gc('emptyViewReloadText', null) |
| | | }, |
| | | //空数据图图片,默认使用z-paging内置的图片 |
| | | emptyViewImg: { |
| | | type: String, |
| | | default: u.gc('emptyViewImg', '') |
| | | }, |
| | | //空数据图“加载失败”描述文字,默认为“很抱歉,加载失败” |
| | | emptyViewErrorText: { |
| | | type: [String, Object], |
| | | default: u.gc('emptyViewErrorText', null) |
| | | }, |
| | | //空数据图“加载失败”图片,默认使用z-paging内置的图片 |
| | | emptyViewErrorImg: { |
| | | type: String, |
| | | default: u.gc('emptyViewErrorImg', '') |
| | | }, |
| | | //空数据图样式 |
| | | emptyViewStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('emptyViewStyle', {}); |
| | | } |
| | | }, |
| | | //空数据图容器样式 |
| | | emptyViewSuperStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('emptyViewSuperStyle', {}); |
| | | } |
| | | }, |
| | | //空数据图img样式 |
| | | emptyViewImgStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('emptyViewImgStyle', {}); |
| | | } |
| | | }, |
| | | //空数据图描述文字样式 |
| | | emptyViewTitleStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('emptyViewTitleStyle', {}); |
| | | } |
| | | }, |
| | | //空数据图重新加载按钮样式 |
| | | emptyViewReloadStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('emptyViewReloadStyle', {}); |
| | | } |
| | | }, |
| | | //空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging |
| | | emptyViewFixed: { |
| | | type: Boolean, |
| | | default: u.gc('emptyViewFixed', false) |
| | | }, |
| | | //空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效 |
| | | emptyViewCenter: { |
| | | type: Boolean, |
| | | default: u.gc('emptyViewCenter', true) |
| | | }, |
| | | //加载中时是否自动隐藏空数据图,默认为是 |
| | | autoHideEmptyViewWhenLoading: { |
| | | type: Boolean, |
| | | default: u.gc('autoHideEmptyViewWhenLoading', true) |
| | | }, |
| | | //用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是 |
| | | autoHideEmptyViewWhenPull: { |
| | | type: Boolean, |
| | | default: u.gc('autoHideEmptyViewWhenPull', true) |
| | | }, |
| | | //空数据view的z-index,默认为9 |
| | | emptyViewZIndex: { |
| | | type: Number, |
| | | default: u.gc('emptyViewZIndex', 9) |
| | | }, |
| | | }, |
| | | computed: { |
| | | finalEmptyViewImg() { |
| | | return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg; |
| | | }, |
| | | finalShowEmptyViewReload() { |
| | | return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload; |
| | | }, |
| | | showEmpty() { |
| | | if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false; |
| | | if (this.autoHideEmptyViewWhenLoading) { |
| | | if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true; |
| | | } else { |
| | | return true; |
| | | } |
| | | return !this.autoHideEmptyViewWhenPull && !this.isUserReload; |
| | | }, |
| | | }, |
| | | methods: { |
| | | //点击了空数据view重新加载按钮 |
| | | _emptyViewReload() { |
| | | let callbacked = false; |
| | | this.$emit('emptyViewReload', reload => { |
| | | if (reload === undefined || reload === true) { |
| | | this.fromEmptyViewReload = true; |
| | | this.reload().catch(() => {}); |
| | | } |
| | | callbacked = true; |
| | | }); |
| | | this.$nextTick(() => { |
| | | if (!callbacked) { |
| | | this.fromEmptyViewReload = true; |
| | | this.reload().catch(() => {}); |
| | | } |
| | | }) |
| | | }, |
| | | //点击了空数据view |
| | | _emptyViewClick() { |
| | | this.$emit('emptyViewClick'); |
| | | }, |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]i18n模块 |
| | | import { initVueI18n } from '@dcloudio/uni-i18n' |
| | | import messages from '../../i18n/index.js' |
| | | const { t } = initVueI18n(messages) |
| | | |
| | | import u from '.././z-paging-utils' |
| | | import c from '.././z-paging-constant' |
| | | import interceptor from '../z-paging-interceptor' |
| | | |
| | | const language = uni.getSystemInfoSync().language; |
| | | export default { |
| | | data() { |
| | | return { |
| | | language |
| | | } |
| | | }, |
| | | computed: { |
| | | finalLanguage() { |
| | | try { |
| | | const local = uni.getLocale(); |
| | | const language = this.language; |
| | | return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local; |
| | | } catch (e) { |
| | | return 'zh-Hans'; |
| | | } |
| | | }, |
| | | finalRefresherDefaultText() { |
| | | return this._getI18nText('zp.refresher.default', this.refresherDefaultText); |
| | | }, |
| | | finalRefresherPullingText() { |
| | | return this._getI18nText('zp.refresher.pulling', this.refresherPullingText); |
| | | }, |
| | | finalRefresherRefreshingText() { |
| | | return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText); |
| | | }, |
| | | finalRefresherCompleteText() { |
| | | return this._getI18nText('zp.refresher.complete', this.refresherCompleteText); |
| | | }, |
| | | finalRefresherUpdateTimeTextMap() { |
| | | return { |
| | | title: t('zp.refresherUpdateTime.title'), |
| | | none: t('zp.refresherUpdateTime.none'), |
| | | today: t('zp.refresherUpdateTime.today'), |
| | | yesterday: t('zp.refresherUpdateTime.yesterday') |
| | | }; |
| | | }, |
| | | finalLoadingMoreDefaultText() { |
| | | return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText); |
| | | }, |
| | | finalLoadingMoreLoadingText() { |
| | | return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText); |
| | | }, |
| | | finalLoadingMoreNoMoreText() { |
| | | return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText); |
| | | }, |
| | | finalLoadingMoreFailText() { |
| | | return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText); |
| | | }, |
| | | finalEmptyViewText() { |
| | | return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText); |
| | | }, |
| | | finalEmptyViewReloadText() { |
| | | return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText); |
| | | }, |
| | | finalEmptyViewErrorText() { |
| | | return this._getI18nText('zp.emptyView.error', this.emptyViewErrorText); |
| | | }, |
| | | finalSystemLoadingText() { |
| | | return this._getI18nText('zp.systemLoading.title', this.systemLoadingText); |
| | | }, |
| | | }, |
| | | methods: { |
| | | //获取当前z-paging的语言 |
| | | getLanguage() { |
| | | return this.finalLanguage; |
| | | }, |
| | | //获取国际化转换后的文本 |
| | | _getI18nText(key, value) { |
| | | const dataType = Object.prototype.toString.call(value); |
| | | if (dataType === '[object Object]') { |
| | | const nextValue = value[this.finalLanguage]; |
| | | if (nextValue) return nextValue; |
| | | } else if (dataType === '[object String]') { |
| | | return value; |
| | | } |
| | | return t(key); |
| | | }, |
| | | //系统language转i18n local |
| | | _language2Local(language) { |
| | | const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-'); |
| | | if (formatedLanguage.indexOf('zh') !== -1) { |
| | | if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) { |
| | | return 'zh-Hans'; |
| | | } |
| | | return 'zh-Hant'; |
| | | } |
| | | if (formatedLanguage.indexOf('en') !== -1) return 'en'; |
| | | return language; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]滚动到底部加载更多模块 |
| | | import u from '.././z-paging-utils' |
| | | import Enum from '.././z-paging-enum' |
| | | |
| | | export default { |
| | | props: { |
| | | //自定义底部加载更多样式 |
| | | loadingMoreCustomStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('loadingMoreCustomStyle', {}); |
| | | } |
| | | }, |
| | | //自定义底部加载更多文字样式 |
| | | loadingMoreTitleCustomStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('loadingMoreTitleCustomStyle', {}); |
| | | } |
| | | }, |
| | | //自定义底部加载更多加载中动画样式 |
| | | loadingMoreLoadingIconCustomStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('loadingMoreLoadingIconCustomStyle', {}); |
| | | } |
| | | }, |
| | | //自定义底部加载更多加载中动画图标类型,可选flower或circle,默认为flower |
| | | loadingMoreLoadingIconType: { |
| | | type: String, |
| | | default: u.gc('loadingMoreLoadingIconType', 'flower') |
| | | }, |
| | | //自定义底部加载更多加载中动画图标图片 |
| | | loadingMoreLoadingIconCustomImage: { |
| | | type: String, |
| | | default: u.gc('loadingMoreLoadingIconCustomImage', '') |
| | | }, |
| | | //底部加载更多加载中view是否展示旋转动画,默认为是 |
| | | loadingMoreLoadingAnimated: { |
| | | type: Boolean, |
| | | default: u.gc('loadingMoreLoadingAnimated', true) |
| | | }, |
| | | //是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是 |
| | | loadingMoreEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('loadingMoreEnabled', true) |
| | | }, |
| | | //是否启用滑动到底部加载更多数据,默认为是 |
| | | toBottomLoadingMoreEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('toBottomLoadingMoreEnabled', true) |
| | | }, |
| | | //滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】 |
| | | loadingMoreDefaultAsLoading: { |
| | | type: [Boolean], |
| | | default: u.gc('loadingMoreDefaultAsLoading', false) |
| | | }, |
| | | //滑动到底部"默认"文字,默认为【点击加载更多】 |
| | | loadingMoreDefaultText: { |
| | | type: [String, Object], |
| | | default: u.gc('loadingMoreDefaultText', null) |
| | | }, |
| | | //滑动到底部"加载中"文字,默认为【正在加载...】 |
| | | loadingMoreLoadingText: { |
| | | type: [String, Object], |
| | | default: u.gc('loadingMoreLoadingText', null) |
| | | }, |
| | | //滑动到底部"没有更多"文字,默认为【没有更多了】 |
| | | loadingMoreNoMoreText: { |
| | | type: [String, Object], |
| | | default: u.gc('loadingMoreNoMoreText', null) |
| | | }, |
| | | //滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】 |
| | | loadingMoreFailText: { |
| | | type: [String, Object], |
| | | default: u.gc('loadingMoreFailText', null) |
| | | }, |
| | | //当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view,默认为否 |
| | | hideNoMoreInside: { |
| | | type: Boolean, |
| | | default: u.gc('hideNoMoreInside', false) |
| | | }, |
| | | //当没有更多数据且分页数组长度少于这个值时,隐藏没有更多数据的view,默认为0,代表不限制。 |
| | | hideNoMoreByLimit: { |
| | | type: Number, |
| | | default: u.gc('hideNoMoreByLimit', 0) |
| | | }, |
| | | //是否显示默认的加载更多text,默认为是 |
| | | showDefaultLoadingMoreText: { |
| | | type: Boolean, |
| | | default: u.gc('showDefaultLoadingMoreText', true) |
| | | }, |
| | | //是否显示没有更多数据的view |
| | | showLoadingMoreNoMoreView: { |
| | | type: Boolean, |
| | | default: u.gc('showLoadingMoreNoMoreView', true) |
| | | }, |
| | | //是否显示没有更多数据的分割线,默认为是 |
| | | showLoadingMoreNoMoreLine: { |
| | | type: Boolean, |
| | | default: u.gc('showLoadingMoreNoMoreLine', true) |
| | | }, |
| | | //自定义底部没有更多数据的分割线样式 |
| | | loadingMoreNoMoreLineCustomStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('loadingMoreNoMoreLineCustomStyle', {}); |
| | | }, |
| | | }, |
| | | //当分页未满一屏时,是否自动加载更多,默认为否(nvue无效) |
| | | insideMore: { |
| | | type: Boolean, |
| | | default: u.gc('insideMore', false) |
| | | }, |
| | | //距底部/右边多远时(单位px),触发 scrolltolower 事件,默认为100rpx |
| | | lowerThreshold: { |
| | | type: [Number, String], |
| | | default: u.gc('lowerThreshold', '100rpx') |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | M: Enum.More, |
| | | //底部加载更多状态 |
| | | loadingStatus: Enum.More.Default, |
| | | loadingStatusAfterRender: Enum.More.Default, |
| | | loadingMoreTimeStamp: 0, |
| | | loadingMoreDefaultSlot: null, |
| | | showLoadingMore: false, |
| | | customNoMore: -1, |
| | | } |
| | | }, |
| | | computed: { |
| | | zLoadMoreConfig() { |
| | | return { |
| | | status: this.loadingStatusAfterRender, |
| | | defaultAsLoading: this.loadingMoreDefaultAsLoading, |
| | | defaultThemeStyle: this.finalLoadingMoreThemeStyle, |
| | | customStyle: this.loadingMoreCustomStyle, |
| | | titleCustomStyle: this.loadingMoreTitleCustomStyle, |
| | | iconCustomStyle: this.loadingMoreLoadingIconCustomStyle, |
| | | loadingIconType: this.loadingMoreLoadingIconType, |
| | | loadingIconCustomImage: this.loadingMoreLoadingIconCustomImage, |
| | | loadingAnimated: this.loadingMoreLoadingAnimated, |
| | | showNoMoreLine: this.showLoadingMoreNoMoreLine, |
| | | noMoreLineCustomStyle: this.loadingMoreNoMoreLineCustomStyle, |
| | | defaultText: this.finalLoadingMoreDefaultText, |
| | | loadingText: this.finalLoadingMoreLoadingText, |
| | | noMoreText: this.finalLoadingMoreNoMoreText, |
| | | failText: this.finalLoadingMoreFailText, |
| | | hideContent: !this.loadingMoreDefaultAsLoading && this.listRendering, |
| | | }; |
| | | }, |
| | | finalLoadingMoreThemeStyle() { |
| | | return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle; |
| | | }, |
| | | showLoadingMoreDefault() { |
| | | return this._showLoadingMore('Default'); |
| | | }, |
| | | showLoadingMoreLoading() { |
| | | return this._showLoadingMore('Loading'); |
| | | }, |
| | | showLoadingMoreNoMore() { |
| | | return this._showLoadingMore('NoMore'); |
| | | }, |
| | | showLoadingMoreFail() { |
| | | return this._showLoadingMore('Fail'); |
| | | }, |
| | | showLoadingMoreCustom() { |
| | | return this._showLoadingMore('Custom'); |
| | | } |
| | | }, |
| | | methods: { |
| | | //页面滚动到底部时通知z-paging进行进一步处理 |
| | | pageReachBottom() { |
| | | !this.useChatRecordMode && this._onLoadingMore('toBottom'); |
| | | }, |
| | | //手动触发上拉加载更多(非必须,可依据具体需求使用) |
| | | doLoadMore(type) { |
| | | this._onLoadingMore(type); |
| | | }, |
| | | //通过@scroll事件检测是否滚动到了底部 |
| | | _checkScrolledToBottom(scrollDiff, checked = false) { |
| | | if (this.cacheScrollNodeHeight === -1) { |
| | | this._getNodeClientRect('.zp-scroll-view').then((res) => { |
| | | if (res) { |
| | | const pageScrollNodeHeight = res[0].height; |
| | | this.cacheScrollNodeHeight = pageScrollNodeHeight; |
| | | if (scrollDiff - pageScrollNodeHeight <= this.finalLowerThreshold) { |
| | | this._onLoadingMore('toBottom'); |
| | | } |
| | | } |
| | | }); |
| | | } else { |
| | | if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) { |
| | | this._onLoadingMore('toBottom'); |
| | | } else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) { |
| | | u.delay(() => { |
| | | this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => { |
| | | this.oldScrollTop = res[0].scrollTop; |
| | | const newScrollDiff = res[0].scrollHeight - this.oldScrollTop; |
| | | this._checkScrolledToBottom(newScrollDiff, true); |
| | | }) |
| | | }, 150, 'checkScrolledToBottomDelay') |
| | | } |
| | | } |
| | | }, |
| | | //触发加载更多时调用,from:toBottom-滑动到底部触发;1、click-点击加载更多触发 |
| | | _onLoadingMore(from = 'click') { |
| | | if (this.isIos && from === 'toBottom' && !this.scrollToBottomBounceEnabled && this.scrollEnable) { |
| | | this.scrollEnable = false; |
| | | this.$nextTick(() => { |
| | | this.scrollEnable = true; |
| | | }) |
| | | } |
| | | this.$emit('scrolltolower', from); |
| | | if (from === 'toBottom' && (!this.toBottomLoadingMoreEnabled || this.useChatRecordMode)) return; |
| | | if (this.refresherOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return; |
| | | // #ifdef MP-WEIXIN |
| | | if (!this.isIos && !this.refresherOnly && !this.usePageScroll) { |
| | | const currentTimestamp = u.getTime(); |
| | | if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) { |
| | | this.loadingMoreTimeStamp = 0; |
| | | return; |
| | | } |
| | | } |
| | | // #endif |
| | | this._doLoadingMore(); |
| | | }, |
| | | //处理开始加载更多 |
| | | _doLoadingMore() { |
| | | if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) { |
| | | this.pageNo ++; |
| | | this._startLoading(false); |
| | | if (this.isLocalPaging) { |
| | | this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => { |
| | | this.completeByTotal(res, this.totalLocalPagingList.length); |
| | | }) |
| | | } else { |
| | | this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadingMore); |
| | | this._callMyParentQuery(); |
| | | } |
| | | this.loadingType = Enum.LoadingType.LoadingMore; |
| | | } |
| | | }, |
| | | //(预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view |
| | | _preCheckShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode) { |
| | | if (this.loadingStatus === Enum.More.NoMore && this.hideNoMoreByLimit > 0 && newVal.length) { |
| | | this.showLoadingMore = newVal.length > this.hideNoMoreByLimit; |
| | | } else if ((this.loadingStatus === Enum.More.NoMore && this.hideNoMoreInside && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) { |
| | | this.$nextTick(() => { |
| | | this._checkShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode); |
| | | }) |
| | | if (this.insideMore && this.insideOfPaging !== false && newVal.length) { |
| | | this.showLoadingMore = newVal.length; |
| | | } |
| | | } else { |
| | | this.showLoadingMore = newVal.length; |
| | | } |
| | | }, |
| | | //判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view |
| | | async _checkShowNoMoreInside(totalData, oldScrollViewNode, oldPagingContainerNode) { |
| | | try { |
| | | const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); |
| | | if (this.usePageScroll) { |
| | | if (scrollViewNode) { |
| | | const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height; |
| | | this.insideOfPaging = scrollViewTotalH < this.windowHeight; |
| | | if (this.hideNoMoreInside) { |
| | | this.showLoadingMore = !this.insideOfPaging; |
| | | } |
| | | this._updateInsideOfPaging(); |
| | | } |
| | | } else { |
| | | const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content'); |
| | | const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; |
| | | const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; |
| | | this.insideOfPaging = pagingContainerH < scrollViewH; |
| | | if (this.hideNoMoreInside) { |
| | | this.showLoadingMore = !this.insideOfPaging; |
| | | } |
| | | this._updateInsideOfPaging(); |
| | | } |
| | | } catch (e) { |
| | | this.insideOfPaging = !totalData.length; |
| | | if (this.hideNoMoreInside) { |
| | | this.showLoadingMore = !this.insideOfPaging; |
| | | } |
| | | this._updateInsideOfPaging(); |
| | | } |
| | | }, |
| | | //是否要展示上拉加载更多view |
| | | _showLoadingMore(type) { |
| | | if (!this.showLoadingMoreWhenReload && (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.realTotalData.length)) return false; |
| | | if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) || |
| | | (!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.refresherOnly) { |
| | | return false; |
| | | } |
| | | if (this.useChatRecordMode && type !== 'Loading') return false; |
| | | if (!this.zSlots) return false; |
| | | if (type === 'Custom') { |
| | | return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView); |
| | | } |
| | | const res = this.loadingStatus === Enum.More[type] && this.zSlots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true); |
| | | if (res) { |
| | | // #ifdef APP-NVUE |
| | | if (!this.isIos) { |
| | | this.nLoadingMoreFixedHeight = false; |
| | | } |
| | | // #endif |
| | | } |
| | | return res; |
| | | }, |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]loading相关模块 |
| | | import u from '.././z-paging-utils' |
| | | import Enum from '.././z-paging-enum' |
| | | |
| | | export default { |
| | | props: { |
| | | //第一次加载后自动隐藏loading slot,默认为是 |
| | | autoHideLoadingAfterFirstLoaded: { |
| | | type: Boolean, |
| | | default: u.gc('autoHideLoadingAfterFirstLoaded', true) |
| | | }, |
| | | //loading slot是否铺满屏幕并固定,默认为否 |
| | | loadingFullFixed: { |
| | | type: Boolean, |
| | | default: u.gc('loadingFullFixed', false) |
| | | }, |
| | | //是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。 |
| | | autoShowSystemLoading: { |
| | | type: Boolean, |
| | | default: u.gc('autoShowSystemLoading', false) |
| | | }, |
| | | //显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效) |
| | | systemLoadingMask: { |
| | | type: Boolean, |
| | | default: u.gc('systemLoadingMask', true) |
| | | }, |
| | | //显示系统Loading时显示的文字,默认为"加载中" |
| | | systemLoadingText: { |
| | | type: [String, Object], |
| | | default: u.gc('systemLoadingText', null) |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | loading: false, |
| | | loadingForNow: false, |
| | | } |
| | | }, |
| | | watch: { |
| | | loadingStatus(newVal) { |
| | | this.$emit('loadingStatusChange', newVal); |
| | | this.$nextTick(() => { |
| | | this.loadingStatusAfterRender = newVal; |
| | | }) |
| | | // #ifdef APP-NVUE |
| | | if (this.useChatRecordMode) { |
| | | if (this.pageNo === this.defaultPageNo && newVal === Enum.More.NoMore) { |
| | | this.nIsFirstPageAndNoMore = true; |
| | | return; |
| | | } |
| | | } |
| | | this.nIsFirstPageAndNoMore = false; |
| | | // #endif |
| | | }, |
| | | loading(newVal){ |
| | | if (newVal) { |
| | | this.loadingForNow = newVal; |
| | | } |
| | | }, |
| | | }, |
| | | computed: { |
| | | showLoading() { |
| | | if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false; |
| | | if (this.finalShowSystemLoading){ |
| | | uni.showLoading({ |
| | | title: this.finalSystemLoadingText, |
| | | mask: this.systemLoadingMask |
| | | }) |
| | | } |
| | | return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher; |
| | | }, |
| | | finalShowSystemLoading() { |
| | | return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher; |
| | | } |
| | | }, |
| | | methods: { |
| | | //处理开始加载更多状态 |
| | | _startLoading(isReload = false) { |
| | | if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) { |
| | | this.loadingStatus = Enum.More.Loading; |
| | | } |
| | | this.loading = true; |
| | | }, |
| | | //停止系统loading和refresh |
| | | _endSystemLoadingAndRefresh(){ |
| | | this.finalShowSystemLoading && uni.hideLoading(); |
| | | !this.useCustomRefresher && uni.stopPullDownRefresh(); |
| | | // #ifdef APP-NVUE |
| | | this.usePageScroll && uni.stopPullDownRefresh(); |
| | | // #endif |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]nvue独有部分模块 |
| | | import u from '.././z-paging-utils' |
| | | import c from '.././z-paging-constant' |
| | | import Enum from '.././z-paging-enum' |
| | | |
| | | // #ifdef APP-NVUE |
| | | const weexAnimation = weex.requireModule('animation'); |
| | | // #endif |
| | | export default { |
| | | props: { |
| | | // #ifdef APP-NVUE |
| | | //nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list |
| | | nvueListIs: { |
| | | type: String, |
| | | default: u.gc('nvueListIs', 'list') |
| | | }, |
| | | //nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall |
| | | nvueWaterfallConfig: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('nvueWaterfallConfig', {}); |
| | | } |
| | | }, |
| | | //nvue 控制是否回弹效果,iOS不支持动态修改 |
| | | nvueBounce: { |
| | | type: Boolean, |
| | | default: u.gc('nvueBounce', true) |
| | | }, |
| | | //nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否 |
| | | nvueFastScroll: { |
| | | type: Boolean, |
| | | default: u.gc('nvueFastScroll', false) |
| | | }, |
| | | //nvue中list的id |
| | | nvueListId: { |
| | | type: String, |
| | | default: u.gc('nvueListId', '') |
| | | }, |
| | | //nvue中refresh组件的样式 |
| | | nvueRefresherStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('nvueRefresherStyle', {}); |
| | | } |
| | | }, |
| | | //nvue中是否按分页模式(类似竖向swiper)显示List,默认为false |
| | | nvuePagingEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('nvuePagingEnabled', false) |
| | | }, |
| | | //是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否 |
| | | hideNvueBottomTag: { |
| | | type: Boolean, |
| | | default: u.gc('hideNvueBottomTag', false) |
| | | }, |
| | | //nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能 |
| | | offsetAccuracy: { |
| | | type: Number, |
| | | default: u.gc('offsetAccuracy', 10) |
| | | }, |
| | | // #endif |
| | | }, |
| | | data() { |
| | | return { |
| | | nRefresherLoading: false, |
| | | nListIsDragging: false, |
| | | nShowBottom: true, |
| | | nFixFreezing: false, |
| | | nShowRefresherReveal: false, |
| | | nIsFirstPageAndNoMore: false, |
| | | nFirstPageAndNoMoreChecked: false, |
| | | nLoadingMoreFixedHeight: false, |
| | | nShowRefresherRevealHeight: 0, |
| | | nOldShowRefresherRevealHeight: -1, |
| | | nRefresherWidth: uni.upx2px(750), |
| | | } |
| | | }, |
| | | watch: { |
| | | // #ifdef APP-NVUE |
| | | nIsFirstPageAndNoMore: { |
| | | handler(newVal) { |
| | | const cellStyle = !this.useChatRecordMode || newVal ? {} : { transform: 'rotate(180deg)' }; |
| | | this.$emit('update:cellStyle', cellStyle); |
| | | this.$emit('cellStyleChange', cellStyle); |
| | | }, |
| | | immediate: true |
| | | }, |
| | | // #endif |
| | | }, |
| | | computed: { |
| | | // #ifdef APP-NVUE |
| | | nScopedSlots() { |
| | | // #ifdef VUE2 |
| | | return this.$scopedSlots; |
| | | // #endif |
| | | // #ifdef VUE3 |
| | | return null; |
| | | // #endif |
| | | }, |
| | | nWaterfallColumnCount() { |
| | | if (this.finalNvueListIs !== 'waterfall') return 0; |
| | | return this._nGetWaterfallConfig('column-count', 2); |
| | | }, |
| | | nWaterfallColumnWidth() { |
| | | return this._nGetWaterfallConfig('column-width', 'auto'); |
| | | }, |
| | | nWaterfallColumnGap() { |
| | | return this._nGetWaterfallConfig('column-gap', 'normal'); |
| | | }, |
| | | nWaterfallLeftGap() { |
| | | return this._nGetWaterfallConfig('left-gap', 0); |
| | | }, |
| | | nWaterfallRightGap() { |
| | | return this._nGetWaterfallConfig('right-gap', 0); |
| | | }, |
| | | nViewIs() { |
| | | const is = this.finalNvueListIs; |
| | | return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell'; |
| | | }, |
| | | nSafeAreaBottomHeight() { |
| | | return this.safeAreaInsetBottom ? this.safeAreaBottom : 0; |
| | | }, |
| | | nChatRecordRotateStyle() { |
| | | return this.useChatRecordMode ? { transform: this.nIsFirstPageAndNoMore ? 'rotate(0deg)' : 'rotate(180deg)' } : {}; |
| | | }, |
| | | finalNvueListIs() { |
| | | if (this.usePageScroll) return 'view'; |
| | | const nvueListIsLowerCase = this.nvueListIs.toLowerCase(); |
| | | if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase; |
| | | return 'list'; |
| | | }, |
| | | finalNvueSuperListIs() { |
| | | return this.usePageScroll ? 'view' : 'scroller'; |
| | | }, |
| | | finalNvueRefresherEnabled() { |
| | | return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode; |
| | | }, |
| | | // #endif |
| | | }, |
| | | mounted(){ |
| | | // #ifdef APP-NVUE |
| | | //旋转屏幕时更新宽度 |
| | | uni.onWindowResize((res) => { |
| | | // this._nUpdateRefresherWidth(); |
| | | }) |
| | | // #endif |
| | | }, |
| | | methods: { |
| | | // #ifdef APP-NVUE |
| | | //列表滚动时触发 |
| | | _nOnScroll(e) { |
| | | this.$emit('scroll', e); |
| | | const contentOffsetY = -e.contentOffset.y; |
| | | this.oldScrollTop = contentOffsetY; |
| | | this.nListIsDragging = e.isDragging; |
| | | this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1); |
| | | }, |
| | | //下拉刷新刷新中 |
| | | _nOnRrefresh() { |
| | | if (this.nShowRefresherReveal) return; |
| | | this.nRefresherLoading = true; |
| | | this.refresherStatus = Enum.Refresher.Loading; |
| | | this._doRefresherLoad(); |
| | | }, |
| | | //下拉刷新下拉中 |
| | | _nOnPullingdown(e) { |
| | | if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return; |
| | | this._emitTouchmove(e); |
| | | const { viewHeight, pullingDistance } = e; |
| | | this.refresherStatus = pullingDistance >= viewHeight ? Enum.Refresher.ReleaseToRefresh : Enum.Refresher.Default; |
| | | }, |
| | | //下拉刷新结束 |
| | | _nRefresherEnd(doEnd = true) { |
| | | if (doEnd) { |
| | | this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight); |
| | | !this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore(); |
| | | this.nRefresherLoading = false; |
| | | } |
| | | }, |
| | | //执行主动触发下拉刷新动画 |
| | | _nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) { |
| | | this._cleanRefresherCompleteTimeout(); |
| | | this._cleanRefresherEndTimeout(); |
| | | |
| | | if (!this.finalShowRefresherWhenReload) { |
| | | this.refresherEndTimeout = u.delay(() => { |
| | | this.refresherStatus = Enum.Refresher.Default; |
| | | }, this.refresherCompleteDuration); |
| | | return; |
| | | } |
| | | const stackCount = this.refresherRevealStackCount; |
| | | if (height === 0 && checkStack) { |
| | | this.refresherRevealStackCount --; |
| | | if (stackCount > 1) return; |
| | | this.refresherEndTimeout = u.delay(() => { |
| | | this.refresherStatus = Enum.Refresher.Default; |
| | | }, this.refresherCompleteDuration); |
| | | } |
| | | if (stackCount > 1) { |
| | | this.refresherStatus = Enum.Refresher.Loading; |
| | | } |
| | | |
| | | const duration = animate ? 200 : 0; |
| | | if (this.nOldShowRefresherRevealHeight !== height) { |
| | | if (height > 0) { |
| | | this.nShowRefresherReveal = true; |
| | | } |
| | | weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], { |
| | | styles: { |
| | | height: `${height}px`, |
| | | transform: `translateY(${translateY}px)`, |
| | | }, |
| | | duration, |
| | | timingFunction: 'linear', |
| | | needLayout: true, |
| | | delay: 0 |
| | | }) |
| | | } |
| | | u.delay(() => { |
| | | if (animate) { |
| | | this.nShowRefresherReveal = height > 0; |
| | | } |
| | | }, duration > 0 ? duration - 60 : 0); |
| | | this.nOldShowRefresherRevealHeight = height; |
| | | }, |
| | | //滚动到底部加载更多 |
| | | _nOnLoadmore() { |
| | | if (this.nShowRefresherReveal || !this.totalData.length) return; |
| | | this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom'); |
| | | }, |
| | | //获取nvue waterfall单项配置 |
| | | _nGetWaterfallConfig(key, defaultValue) { |
| | | return this.nvueWaterfallConfig[key] || defaultValue; |
| | | }, |
| | | //更新nvue 下拉刷新view容器的宽度 |
| | | _nUpdateRefresherWidth() { |
| | | u.delay(() => { |
| | | this.$nextTick(()=>{ |
| | | this._getNodeClientRect('.zp-n-list').then(node => { |
| | | if (node) { |
| | | this.nRefresherWidth = node[0].width || this.nRefresherWidth; |
| | | } |
| | | }) |
| | | }) |
| | | }) |
| | | } |
| | | // #endif |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]下拉刷新view模块 |
| | | import u from '.././z-paging-utils' |
| | | import c from '.././z-paging-constant' |
| | | import Enum from '.././z-paging-enum' |
| | | |
| | | export default { |
| | | props: { |
| | | //下拉刷新的主题样式,支持black,white,默认black |
| | | refresherThemeStyle: { |
| | | type: String, |
| | | default: u.gc('refresherThemeStyle', '') |
| | | }, |
| | | //自定义下拉刷新中左侧图标的样式 |
| | | refresherImgStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('refresherImgStyle', {}); |
| | | } |
| | | }, |
| | | //自定义下拉刷新中右侧状态描述文字的样式 |
| | | refresherTitleStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('refresherTitleStyle', {}); |
| | | } |
| | | }, |
| | | //自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效) |
| | | refresherUpdateTimeStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('refresherUpdateTimeStyle', {}); |
| | | } |
| | | }, |
| | | //在微信小程序和QQ小程序中,是否实时监听下拉刷新中进度,默认为否 |
| | | watchRefresherTouchmove: { |
| | | type: Boolean, |
| | | default: u.gc('watchRefresherTouchmove', false) |
| | | }, |
| | | //底部加载更多的主题样式,支持black,white,默认black |
| | | loadingMoreThemeStyle: { |
| | | type: String, |
| | | default: u.gc('loadingMoreThemeStyle', '') |
| | | }, |
| | | //是否只使用下拉刷新,设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图。默认为否 |
| | | refresherOnly: { |
| | | type: Boolean, |
| | | default: u.gc('refresherOnly', false) |
| | | }, |
| | | //自定义下拉刷新默认状态下回弹动画时间,单位为毫秒,默认为100毫秒,nvue无效 |
| | | refresherDefaultDuration: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherDefaultDuration', 100) |
| | | }, |
| | | //自定义下拉刷新结束以后延迟回弹的时间,单位为毫秒,默认为0 |
| | | refresherCompleteDelay: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherCompleteDelay', 0) |
| | | }, |
| | | //自定义下拉刷新结束回弹动画时间,单位为毫秒,默认为300毫秒(refresherEndBounceEnabled为false时,refresherCompleteDuration为设定值的1/3),nvue无效 |
| | | refresherCompleteDuration: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherCompleteDuration', 300) |
| | | }, |
| | | //自定义下拉刷新结束状态下是否允许列表滚动,默认为否 |
| | | refresherCompleteScrollable: { |
| | | type: Boolean, |
| | | default: u.gc('refresherCompleteScrollable', false) |
| | | }, |
| | | //是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新 |
| | | useCustomRefresher: { |
| | | type: Boolean, |
| | | default: u.gc('useCustomRefresher', true) |
| | | }, |
| | | //自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题 |
| | | refresherFps: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherFps', 40) |
| | | }, |
| | | //自定义下拉刷新允许触发的最大下拉角度,默认为40度,当下拉角度小于设定值时,自定义下拉刷新动画不会被触发 |
| | | refresherMaxAngle: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherMaxAngle', 40) |
| | | }, |
| | | //自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否 |
| | | refresherAngleEnableChangeContinued: { |
| | | type: Boolean, |
| | | default: u.gc('refresherAngleEnableChangeContinued', false) |
| | | }, |
| | | //自定义下拉刷新默认状态下的文字 |
| | | refresherDefaultText: { |
| | | type: [String, Object], |
| | | default: u.gc('refresherDefaultText', null) |
| | | }, |
| | | //自定义下拉刷新松手立即刷新状态下的文字 |
| | | refresherPullingText: { |
| | | type: [String, Object], |
| | | default: u.gc('refresherPullingText', null) |
| | | }, |
| | | //自定义下拉刷新刷新中状态下的文字 |
| | | refresherRefreshingText: { |
| | | type: [String, Object], |
| | | default: u.gc('refresherRefreshingText', null) |
| | | }, |
| | | //自定义下拉刷新刷新结束状态下的文字 |
| | | refresherCompleteText: { |
| | | type: [String, Object], |
| | | default: u.gc('refresherCompleteText', null) |
| | | }, |
| | | //自定义下拉刷新默认状态下的图片 |
| | | refresherDefaultImg: { |
| | | type: String, |
| | | default: u.gc('refresherDefaultImg', null) |
| | | }, |
| | | //自定义下拉刷新松手立即刷新状态下的图片,默认与refresherDefaultImg一致 |
| | | refresherPullingImg: { |
| | | type: String, |
| | | default: u.gc('refresherPullingImg', null) |
| | | }, |
| | | //自定义下拉刷新刷新中状态下的图片 |
| | | refresherRefreshingImg: { |
| | | type: String, |
| | | default: u.gc('refresherRefreshingImg', null) |
| | | }, |
| | | //自定义下拉刷新刷新结束状态下的图片 |
| | | refresherCompleteImg: { |
| | | type: String, |
| | | default: u.gc('refresherCompleteImg', null) |
| | | }, |
| | | //自定义下拉刷新刷新中状态下是否展示旋转动画 |
| | | refresherRefreshingAnimated: { |
| | | type: Boolean, |
| | | default: u.gc('refresherRefreshingAnimated', true) |
| | | }, |
| | | //是否开启自定义下拉刷新刷新结束回弹效果,默认为是 |
| | | refresherEndBounceEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('refresherEndBounceEnabled', true) |
| | | }, |
| | | //是否开启自定义下拉刷新,默认为是 |
| | | refresherEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('refresherEnabled', true) |
| | | }, |
| | | //设置自定义下拉刷新阈值,默认为80rpx |
| | | refresherThreshold: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherThreshold', '80rpx') |
| | | }, |
| | | //设置系统下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式,默认为black |
| | | refresherDefaultStyle: { |
| | | type: String, |
| | | default: u.gc('refresherDefaultStyle', 'black') |
| | | }, |
| | | //设置自定义下拉刷新区域背景 |
| | | refresherBackground: { |
| | | type: String, |
| | | default: u.gc('refresherBackground', 'transparent') |
| | | }, |
| | | //设置固定的自定义下拉刷新区域背景 |
| | | refresherFixedBackground: { |
| | | type: String, |
| | | default: u.gc('refresherFixedBackground', 'transparent') |
| | | }, |
| | | //设置固定的自定义下拉刷新区域高度,默认为0 |
| | | refresherFixedBacHeight: { |
| | | type: [Number, String], |
| | | default: u.gc('refresherFixedBacHeight', 0) |
| | | }, |
| | | //设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例,范围0-1,值越大代表衰减越多。默认为0.65(nvue无效) |
| | | refresherOutRate: { |
| | | type: Number, |
| | | default: u.gc('refresherOutRate', 0.65) |
| | | }, |
| | | //设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值,默认为0.75,即代表若用户下拉10px,则实际位移为7.5px(nvue无效) |
| | | refresherPullRate: { |
| | | type: Number, |
| | | default: u.gc('refresherPullRate', 0.75) |
| | | }, |
| | | //是否显示最后更新时间,默认为否 |
| | | showRefresherUpdateTime: { |
| | | type: Boolean, |
| | | default: u.gc('showRefresherUpdateTime', false) |
| | | }, |
| | | //如果需要区别不同页面的最后更新时间,请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串 |
| | | refresherUpdateTimeKey: { |
| | | type: String, |
| | | default: u.gc('refresherUpdateTimeKey', 'default') |
| | | }, |
| | | //下拉刷新时下拉到“松手立即刷新”状态时是否使手机短振动,默认为否(h5无效) |
| | | refresherVibrate: { |
| | | type: Boolean, |
| | | default: u.gc('refresherVibrate', false) |
| | | }, |
| | | //下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动,默认为否。注意此属性只是禁止下拉刷新view移动,其他下拉刷新逻辑依然会正常触发 |
| | | refresherNoTransform: { |
| | | type: Boolean, |
| | | default: u.gc('refresherNoTransform', false) |
| | | }, |
| | | //是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否 |
| | | useRefresherStatusBarPlaceholder: { |
| | | type: Boolean, |
| | | default: u.gc('useRefresherStatusBarPlaceholder', false) |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | R: Enum.Refresher, |
| | | //下拉刷新状态 |
| | | refresherStatus: Enum.Refresher.Default, |
| | | refresherTouchstartY: 0, |
| | | lastRefresherTouchmove: null, |
| | | refresherReachMaxAngle: true, |
| | | refresherTransform: 'translateY(0px)', |
| | | refresherTransition: '', |
| | | finalRefresherDefaultStyle: 'black', |
| | | refresherRevealStackCount: 0, |
| | | refresherCompleteTimeout: null, |
| | | refresherCompleteSubTimeout: null, |
| | | refresherEndTimeout: null, |
| | | isTouchmovingTimeout: null, |
| | | refresherTriggered: false, |
| | | isTouchmoving: false, |
| | | isTouchEnded: false, |
| | | isUserPullDown: false, |
| | | privateRefresherEnabled: -1, |
| | | privateShowRefresherWhenReload: false, |
| | | customRefresherHeight: -1, |
| | | showCustomRefresher: false, |
| | | doRefreshAnimateAfter: false, |
| | | isRefresherInComplete: false, |
| | | pullDownTimeStamp: 0, |
| | | moveDis: 0, |
| | | oldMoveDis: 0, |
| | | currentDis: 0, |
| | | oldCurrentMoveDis: 0, |
| | | oldRefresherTouchmoveY: 0, |
| | | oldTouchDirection: '', |
| | | oldEmitedTouchDirection: '', |
| | | oldPullingDistance: -1, |
| | | refresherThresholdUpdateTag: 0 |
| | | } |
| | | }, |
| | | watch: { |
| | | refresherDefaultStyle: { |
| | | handler(newVal) { |
| | | if (newVal.length) { |
| | | this.finalRefresherDefaultStyle = newVal; |
| | | } |
| | | }, |
| | | immediate: true |
| | | }, |
| | | refresherStatus(newVal) { |
| | | newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout(); |
| | | this.refresherVibrate && newVal === Enum.Refresher.ReleaseToRefresh && this._doVibrateShort(); |
| | | this.$emit('refresherStatusChange', newVal); |
| | | this.$emit('update:refresherStatus', newVal); |
| | | }, |
| | | refresherEnabled(newVal) { |
| | | !newVal && this.endRefresh(); |
| | | } |
| | | }, |
| | | computed: { |
| | | pullDownDisTimeStamp() { |
| | | return 1000 / this.refresherFps; |
| | | }, |
| | | finalRefresherEnabled() { |
| | | if (this.useChatRecordMode) return false; |
| | | if (this.privateRefresherEnabled === -1) return this.refresherEnabled; |
| | | return this.privateRefresherEnabled === 1; |
| | | }, |
| | | finalRefresherThreshold() { |
| | | let refresherThreshold = this.refresherThreshold; |
| | | let idDefault = false; |
| | | if (refresherThreshold === '80rpx') { |
| | | idDefault = true; |
| | | if (this.showRefresherUpdateTime) { |
| | | refresherThreshold = '120rpx'; |
| | | } |
| | | } |
| | | if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder; |
| | | return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder; |
| | | }, |
| | | finalRefresherThresholdPlaceholder() { |
| | | return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0; |
| | | }, |
| | | finalRefresherFixedBacHeight() { |
| | | return u.convertToPx(this.refresherFixedBacHeight); |
| | | }, |
| | | finalRefresherThemeStyle() { |
| | | return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle; |
| | | }, |
| | | finalRefresherOutRate() { |
| | | let rate = this.refresherOutRate; |
| | | rate = Math.max(0,rate); |
| | | rate = Math.min(1,rate); |
| | | return rate; |
| | | }, |
| | | finalRefresherPullRate() { |
| | | let rate = this.refresherPullRate; |
| | | rate = Math.max(0,rate); |
| | | return rate; |
| | | }, |
| | | finalRefresherTransform() { |
| | | if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none'; |
| | | return this.refresherTransform; |
| | | }, |
| | | finalShowRefresherWhenReload() { |
| | | return this.showRefresherWhenReload || this.privateShowRefresherWhenReload; |
| | | }, |
| | | finalRefresherTriggered() { |
| | | if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false; |
| | | return this.refresherTriggered; |
| | | }, |
| | | showRefresher() { |
| | | const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher; |
| | | // #ifndef APP-NVUE |
| | | this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight(); |
| | | // #endif |
| | | return showRefresher; |
| | | }, |
| | | hasTouchmove(){ |
| | | // #ifdef VUE2 |
| | | // #ifdef APP-VUE || H5 |
| | | if (this.$listeners && !this.$listeners.refresherTouchmove) return false; |
| | | // #endif |
| | | // #ifdef MP-WEIXIN || MP-QQ |
| | | return this.watchRefresherTouchmove; |
| | | // #endif |
| | | return true; |
| | | // #endif |
| | | return this.watchRefresherTouchmove; |
| | | }, |
| | | }, |
| | | methods: { |
| | | //终止下拉刷新状态 |
| | | endRefresh() { |
| | | this.totalData = this.realTotalData; |
| | | this._refresherEnd(); |
| | | this._endSystemLoadingAndRefresh(); |
| | | this._handleScrollViewDisableBounce({ bounce: true }); |
| | | this.$nextTick(() => { |
| | | this.refresherTriggered = false; |
| | | }) |
| | | }, |
| | | handleRefresherStatusChanged(func) { |
| | | this.refresherStatusChangedFunc = func; |
| | | }, |
| | | //手动更新自定义下拉刷新view高度 |
| | | updateCustomRefresherHeight() { |
| | | u.delay(() => this.$nextTick(this._updateCustomRefresherHeight)); |
| | | }, |
| | | //自定义下拉刷新被触发 |
| | | _onRefresh(fromScrollView = false,isUserPullDown = true) { |
| | | if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return; |
| | | this.$emit('onRefresh'); |
| | | this.$emit('Refresh'); |
| | | // #ifdef APP-NVUE |
| | | if (this.loading) { |
| | | u.delay(this._nRefresherEnd, 500) |
| | | return; |
| | | } |
| | | // #endif |
| | | if (this.loading || this.isRefresherInComplete) return; |
| | | this.loadingType = Enum.LoadingType.Refresher; |
| | | if (this.nShowRefresherReveal) return; |
| | | this.isUserPullDown = isUserPullDown; |
| | | this.isUserReload = !isUserPullDown; |
| | | this._startLoading(true); |
| | | this.refresherTriggered = true; |
| | | if(this.reloadWhenRefresh && isUserPullDown){ |
| | | this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown); |
| | | } |
| | | }, |
| | | //自定义下拉刷新被复位 |
| | | _onRestore() { |
| | | this.refresherTriggered = 'restore'; |
| | | this.$emit('onRestore'); |
| | | this.$emit('Restore'); |
| | | }, |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | //拖拽开始 |
| | | _refresherTouchstart(e) { |
| | | this._handleListTouchstart(); |
| | | if (this._touchDisabled()) return; |
| | | this._handleRefresherTouchstart(u.getTouch(e)); |
| | | }, |
| | | // #endif |
| | | //进一步处理拖拽开始结果 |
| | | _handleRefresherTouchstart(touch) { |
| | | if (!this.loading && this.isTouchEnded) { |
| | | this.isTouchmoving = false; |
| | | } |
| | | this.loadingType = Enum.LoadingType.Refresher; |
| | | this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); |
| | | this.isTouchEnded = false; |
| | | this.refresherTransition = ''; |
| | | this.refresherTouchstartY = touch.touchY; |
| | | this.$emit('refresherTouchstart', this.refresherTouchstartY); |
| | | this.lastRefresherTouchmove = touch; |
| | | this._cleanRefresherCompleteTimeout(); |
| | | this._cleanRefresherEndTimeout(); |
| | | }, |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | //拖拽中 |
| | | _refresherTouchmove(e) { |
| | | const currentTimeStamp = u.getTime(); |
| | | let touch = null; |
| | | let refresherTouchmoveY = 0; |
| | | if (this.watchTouchDirectionChange) { |
| | | touch = u.getTouch(e); |
| | | refresherTouchmoveY = touch.touchY; |
| | | const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom'; |
| | | if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) { |
| | | this._handleTouchDirectionChange({ direction }); |
| | | this.oldEmitedTouchDirection = direction; |
| | | } |
| | | this.oldTouchDirection = direction; |
| | | this.oldRefresherTouchmoveY = refresherTouchmoveY; |
| | | } |
| | | if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return; |
| | | if (this._touchDisabled()) return; |
| | | this.pullDownTimeStamp = Number(currentTimeStamp); |
| | | touch = u.getTouch(e); |
| | | refresherTouchmoveY = touch.touchY; |
| | | let moveDis = refresherTouchmoveY - this.refresherTouchstartY; |
| | | if (moveDis < 0) return; |
| | | if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) { |
| | | if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return; |
| | | const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX); |
| | | const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY); |
| | | const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); |
| | | if ((x || y) && x > 1) { |
| | | const angle = Math.asin(y / z) / Math.PI * 180; |
| | | if (angle < this.refresherMaxAngle) { |
| | | this.lastRefresherTouchmove = touch; |
| | | this.refresherReachMaxAngle = false; |
| | | return; |
| | | } |
| | | } |
| | | } |
| | | moveDis = this._getFinalRefresherMoveDis(moveDis); |
| | | this._handleRefresherTouchmove(moveDis, touch); |
| | | if (!this.disabledBounce) { |
| | | if(this.isIos){ |
| | | // #ifndef MP-LARK |
| | | this._handleScrollViewDisableBounce({ bounce: false }); |
| | | // #endif |
| | | } |
| | | this.disabledBounce = true; |
| | | } |
| | | this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis }); |
| | | }, |
| | | // #endif |
| | | //进一步处理拖拽中结果 |
| | | _handleRefresherTouchmove(moveDis, touch) { |
| | | this.refresherReachMaxAngle = true; |
| | | this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); |
| | | this.isTouchmoving = true; |
| | | this.isTouchEnded = false; |
| | | this.refresherStatus = moveDis >= this.finalRefresherThreshold ? Enum.Refresher.ReleaseToRefresh : this.refresherStatus = Enum.Refresher.Default; |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | // this.scrollEnable = false; |
| | | this.refresherTransform = `translateY(${moveDis}px)`; |
| | | this.lastRefresherTouchmove = touch; |
| | | // #endif |
| | | this.moveDis = moveDis; |
| | | }, |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | //拖拽结束 |
| | | _refresherTouchend(e) { |
| | | if (this._touchDisabled() || !this.isTouchmoving) return; |
| | | const touch = u.getTouch(e); |
| | | let refresherTouchendY = touch.touchY; |
| | | let moveDis = refresherTouchendY - this.refresherTouchstartY; |
| | | moveDis = this._getFinalRefresherMoveDis(moveDis); |
| | | this._handleRefresherTouchend(moveDis); |
| | | this._handleScrollViewDisableBounce({bounce: true}); |
| | | this.disabledBounce = false; |
| | | }, |
| | | // #endif |
| | | //进一步处理拖拽结束结果 |
| | | _handleRefresherTouchend(moveDis) { |
| | | // #ifndef APP-PLUS || H5 || MP-WEIXIN |
| | | if (!this.isTouchmoving) return; |
| | | // #endif |
| | | this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); |
| | | this.refresherReachMaxAngle = true; |
| | | this.isTouchEnded = true; |
| | | const refresherThreshold = this.finalRefresherThreshold; |
| | | if (moveDis >= refresherThreshold && this.refresherStatus === Enum.Refresher.ReleaseToRefresh) { |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | this.refresherTransform = `translateY(${refresherThreshold}px)`; |
| | | this.refresherTransition = 'transform .1s linear'; |
| | | // #endif |
| | | u.delay(() => { |
| | | this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold }); |
| | | }, 0.1); |
| | | this.moveDis = refresherThreshold; |
| | | this.refresherStatus = Enum.Refresher.Loading; |
| | | this._doRefresherLoad(); |
| | | } else { |
| | | this._refresherEnd(); |
| | | this.isTouchmovingTimeout = u.delay(() => { |
| | | this.isTouchmoving = false; |
| | | }, this.refresherDefaultDuration); |
| | | } |
| | | this.scrollEnable = true; |
| | | this.$emit('refresherTouchend', moveDis); |
| | | }, |
| | | //处理列表触摸开始事件 |
| | | _handleListTouchstart() { |
| | | if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) { |
| | | uni.hideKeyboard(); |
| | | this.$emit('hidedKeyboard'); |
| | | } |
| | | }, |
| | | //处理scroll-view bounce是否生效 |
| | | _handleScrollViewDisableBounce({ bounce }) { |
| | | if (!this.usePageScroll && !this.scrollToTopBounceEnabled && this.wxsScrollTop <= 5) { |
| | | // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | this.refresherTransition = ''; |
| | | // #endif |
| | | this.scrollEnable = bounce; |
| | | } |
| | | }, |
| | | //wxs正在下拉状态改变处理 |
| | | _handleWxsPullingDownStatusChange(onPullingDown) { |
| | | this.wxsOnPullingDown = onPullingDown; |
| | | if (onPullingDown && !this.useChatRecordMode) { |
| | | this.renderPropScrollTop = 0; |
| | | } |
| | | }, |
| | | //wxs正在下拉处理 |
| | | _handleWxsPullingDown({ moveDis, diffDis }){ |
| | | this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis }); |
| | | }, |
| | | //wxs触摸方向改变 |
| | | _handleTouchDirectionChange({ direction }) { |
| | | this.$emit('touchDirectionChange',direction); |
| | | }, |
| | | //wxs通知更新其props |
| | | _handlePropUpdate(){ |
| | | this.wxsPropType = u.getTime().toString(); |
| | | }, |
| | | //下拉刷新结束 |
| | | _refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) { |
| | | if (this.loadingType === Enum.LoadingType.Refresher) { |
| | | const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0; |
| | | const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default; |
| | | if (this.finalShowRefresherWhenReload) { |
| | | const stackCount = this.refresherRevealStackCount; |
| | | this.refresherRevealStackCount --; |
| | | if (stackCount > 1) return; |
| | | } |
| | | this._cleanRefresherEndTimeout(); |
| | | this.refresherEndTimeout = u.delay(() => { |
| | | this.refresherStatus = refresherStatus; |
| | | }, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0); |
| | | |
| | | // #ifndef APP-NVUE |
| | | if (refresherCompleteDelay > 0) { |
| | | this.isRefresherInComplete = true; |
| | | } |
| | | // #endif |
| | | this._cleanRefresherCompleteTimeout(); |
| | | this.refresherCompleteTimeout = u.delay(() => { |
| | | let animateDuration = 1; |
| | | const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear'; |
| | | if (fromAddData) { |
| | | animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000; |
| | | } |
| | | this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`; |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | this.refresherTransform = 'translateY(0px)'; |
| | | this.currentDis = 0; |
| | | // #endif |
| | | // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | this.wxsPropType = this.refresherTransition + 'end' + u.getTime(); |
| | | // #endif |
| | | // #ifdef APP-NVUE |
| | | this._nRefresherEnd(); |
| | | // #endif |
| | | this.moveDis = 0; |
| | | // #ifndef APP-NVUE |
| | | if (refresherStatus === Enum.Refresher.Complete) { |
| | | if (this.refresherCompleteSubTimeout) { |
| | | clearTimeout(this.refresherCompleteSubTimeout); |
| | | this.refresherCompleteSubTimeout = null; |
| | | } |
| | | this.refresherCompleteSubTimeout = u.delay(() => { |
| | | this.$nextTick(() => { |
| | | this.refresherStatus = Enum.Refresher.Default; |
| | | this.isRefresherInComplete = false; |
| | | }) |
| | | }, animateDuration * 800); |
| | | } |
| | | // #endif |
| | | this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis }); |
| | | }, refresherCompleteDelay); |
| | | } |
| | | if (setLoading) { |
| | | u.delay(() => this.loading = false, shouldEndLoadingDelay ? c.delayTime : 0); |
| | | isUserPullDown && this._onRestore(); |
| | | } |
| | | }, |
| | | //模拟用户手动触发下拉刷新 |
| | | _doRefresherRefreshAnimate() { |
| | | this._cleanRefresherCompleteTimeout(); |
| | | // #ifndef APP-NVUE |
| | | const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this |
| | | .customRefresherHeight === -1 && this.refresherThreshold === '80rpx'; |
| | | if (doRefreshAnimateAfter) { |
| | | this.doRefreshAnimateAfter = true; |
| | | return; |
| | | } |
| | | // #endif |
| | | this.refresherRevealStackCount ++; |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`; |
| | | // #endif |
| | | // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | this.wxsPropType = 'begin' + u.getTime(); |
| | | // #endif |
| | | this.moveDis = this.finalRefresherThreshold; |
| | | this.refresherStatus = Enum.Refresher.Loading; |
| | | this.isTouchmoving = true; |
| | | this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); |
| | | this._doRefresherLoad(false); |
| | | }, |
| | | //触发下拉刷新 |
| | | _doRefresherLoad(isUserPullDown = true) { |
| | | this._onRefresh(false,isUserPullDown); |
| | | this.loading = true; |
| | | }, |
| | | // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 |
| | | //获取处理后的moveDis |
| | | _getFinalRefresherMoveDis(moveDis) { |
| | | let diffDis = moveDis - this.oldCurrentMoveDis; |
| | | this.oldCurrentMoveDis = moveDis; |
| | | if (diffDis > 0) { |
| | | diffDis = diffDis * this.finalRefresherPullRate; |
| | | if (this.currentDis > this.finalRefresherThreshold) { |
| | | diffDis = diffDis * (1 - this.finalRefresherOutRate); |
| | | } |
| | | } |
| | | diffDis = diffDis > 100 ? diffDis / 100 : diffDis; |
| | | this.currentDis += diffDis; |
| | | this.currentDis = Math.max(0, this.currentDis); |
| | | return this.currentDis; |
| | | }, |
| | | //判断touch手势是否要触发 |
| | | _touchDisabled() { |
| | | const checkOldScrollTop = this.oldScrollTop > 5; |
| | | return this.loading || this.isRefresherInComplete || this.useChatRecordMode || !this.refresherEnabled || !this.useCustomRefresher ||(this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop); |
| | | }, |
| | | // #endif |
| | | //更新自定义下拉刷新view高度 |
| | | _updateCustomRefresherHeight() { |
| | | this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => { |
| | | this.customRefresherHeight = res ? res[0].height : 0; |
| | | this.showCustomRefresher = this.customRefresherHeight > 0; |
| | | if (this.doRefreshAnimateAfter) { |
| | | this.doRefreshAnimateAfter = false; |
| | | this._doRefresherRefreshAnimate(); |
| | | } |
| | | }); |
| | | }, |
| | | //发射pullingDown事件 |
| | | _emitTouchmove(e) { |
| | | // #ifndef APP-NVUE |
| | | e.viewHeight = this.finalRefresherThreshold; |
| | | // #endif |
| | | e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0; |
| | | this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e); |
| | | this.oldPullingDistance = e.pullingDistance; |
| | | }, |
| | | //清除refresherCompleteTimeout |
| | | _cleanRefresherCompleteTimeout() { |
| | | this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout); |
| | | // #ifdef APP-NVUE |
| | | this._nRefresherEnd(false); |
| | | // #endif |
| | | }, |
| | | //清除refresherEndTimeout |
| | | _cleanRefresherEndTimeout() { |
| | | this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout); |
| | | }, |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]scroll相关模块 |
| | | import u from '.././z-paging-utils' |
| | | import Enum from '.././z-paging-enum' |
| | | |
| | | // #ifdef APP-NVUE |
| | | const weexDom = weex.requireModule('dom'); |
| | | // #endif |
| | | |
| | | export default { |
| | | props: { |
| | | //使用页面滚动,默认为否,当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动,使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高,但配置会略微繁琐 |
| | | usePageScroll: { |
| | | type: Boolean, |
| | | default: u.gc('usePageScroll', false) |
| | | }, |
| | | //是否可以滚动,使用内置scroll-view和nvue时有效,默认为是 |
| | | scrollable: { |
| | | type: Boolean, |
| | | default: u.gc('scrollable', true) |
| | | }, |
| | | //控制是否出现滚动条,默认为是 |
| | | showScrollbar: { |
| | | type: Boolean, |
| | | default: u.gc('showScrollbar', true) |
| | | }, |
| | | //是否允许横向滚动,默认为否 |
| | | scrollX: { |
| | | type: Boolean, |
| | | default: u.gc('scrollX', false) |
| | | }, |
| | | //iOS设备上滚动到顶部时是否允许回弹效果,默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯,但是有吸顶view时滚动到顶部时可能出现抖动。 |
| | | scrollToTopBounceEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('scrollToTopBounceEnabled', false) |
| | | }, |
| | | //iOS设备上滚动到底部时是否允许回弹效果,默认为是。 |
| | | scrollToBottomBounceEnabled: { |
| | | type: Boolean, |
| | | default: u.gc('scrollToBottomBounceEnabled', true) |
| | | }, |
| | | //在设置滚动条位置时使用动画过渡,默认为否 |
| | | scrollWithAnimation: { |
| | | type: Boolean, |
| | | default: u.gc('scrollWithAnimation', false) |
| | | }, |
| | | //值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 |
| | | scrollIntoView: { |
| | | type: String, |
| | | default: u.gc('scrollIntoView', '') |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | scrollTop: 0, |
| | | oldScrollTop: 0, |
| | | scrollViewStyle: {}, |
| | | scrollViewContainerStyle: {}, |
| | | scrollViewInStyle: {}, |
| | | pageScrollTop: -1, |
| | | scrollEnable: true, |
| | | privateScrollWithAnimation: -1, |
| | | cacheScrollNodeHeight: -1 |
| | | } |
| | | }, |
| | | watch: { |
| | | oldScrollTop(newVal) { |
| | | !this.usePageScroll && this._scrollTopChange(newVal,false); |
| | | }, |
| | | pageScrollTop(newVal) { |
| | | this.usePageScroll && this._scrollTopChange(newVal,true); |
| | | }, |
| | | usePageScroll: { |
| | | handler(newVal) { |
| | | this.loaded && this.autoHeight && this._setAutoHeight(!newVal); |
| | | // #ifdef H5 |
| | | if (newVal) { |
| | | this.$nextTick(() => { |
| | | const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main; |
| | | if (mainScrollRef) { |
| | | mainScrollRef.style = {}; |
| | | } |
| | | }) |
| | | } |
| | | // #endif |
| | | }, |
| | | immediate: true |
| | | }, |
| | | finalScrollTop(newVal) { |
| | | if (!this.useChatRecordMode) { |
| | | this.renderPropScrollTop = newVal < 6 ? 0 : 10; |
| | | } |
| | | }, |
| | | }, |
| | | computed: { |
| | | finalScrollWithAnimation() { |
| | | if (this.privateScrollWithAnimation !== -1) { |
| | | const scrollWithAnimation = this.privateScrollWithAnimation === 1; |
| | | this.privateScrollWithAnimation = -1; |
| | | return scrollWithAnimation; |
| | | } |
| | | return this.scrollWithAnimation; |
| | | }, |
| | | finalScrollViewStyle() { |
| | | if (this.superContentZIndex != 1) { |
| | | this.scrollViewStyle['z-index'] = this.superContentZIndex; |
| | | this.scrollViewStyle['position'] = 'relative'; |
| | | } |
| | | return this.scrollViewStyle; |
| | | }, |
| | | finalScrollTop() { |
| | | return this.usePageScroll ? this.pageScrollTop : this.oldScrollTop; |
| | | }, |
| | | finalIsOldWebView() { |
| | | return this.isOldWebView && !this.usePageScroll; |
| | | } |
| | | }, |
| | | methods: { |
| | | //滚动到顶部,animate为是否展示滚动动画,默认为是 |
| | | scrollToTop(animate, checkReverse = true) { |
| | | // #ifdef APP-NVUE |
| | | if (checkReverse && this.useChatRecordMode) { |
| | | if (!this.nIsFirstPageAndNoMore) { |
| | | this.scrollToBottom(animate, false); |
| | | return; |
| | | } |
| | | } |
| | | // #endif |
| | | this.$nextTick(() => { |
| | | this._scrollToTop(animate, false); |
| | | // #ifdef APP-NVUE |
| | | if (this.nvueFastScroll && animate) { |
| | | u.delay(() => { |
| | | this._scrollToTop(false, false); |
| | | }); |
| | | } |
| | | // #endif |
| | | }) |
| | | }, |
| | | //滚动到底部,animate为是否展示滚动动画,默认为是 |
| | | scrollToBottom(animate, checkReverse = true) { |
| | | // #ifdef APP-NVUE |
| | | if (checkReverse && this.useChatRecordMode) { |
| | | if (!this.nIsFirstPageAndNoMore) { |
| | | this.scrollToTop(animate, false); |
| | | return; |
| | | } |
| | | } |
| | | // #endif |
| | | this.$nextTick(() => { |
| | | this._scrollToBottom(animate); |
| | | // #ifdef APP-NVUE |
| | | if (this.nvueFastScroll && animate) { |
| | | u.delay(() => { |
| | | this._scrollToBottom(false); |
| | | }); |
| | | } |
| | | // #endif |
| | | }) |
| | | }, |
| | | //滚动到指定view(vue中有效)。sel为需要滚动的view的id值,不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 |
| | | scrollIntoViewById(sel, offset, animate) { |
| | | this._scrollIntoView(sel, offset, animate); |
| | | }, |
| | | //滚动到指定view(vue中有效)。nodeTop为需要滚动的view的top值(通过uni.createSelectorQuery()获取);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 |
| | | scrollIntoViewByNodeTop(nodeTop, offset, animate) { |
| | | this.scrollTop = this.oldScrollTop; |
| | | this.$nextTick(() => { |
| | | this._scrollIntoViewByNodeTop(nodeTop, offset, animate); |
| | | }) |
| | | }, |
| | | //滚动到指定位置(vue中有效)。y为与顶部的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 |
| | | scrollToY(y, offset, animate) { |
| | | this.scrollTop = this.oldScrollTop; |
| | | this.$nextTick(() => { |
| | | this._scrollToY(y, offset, animate); |
| | | }) |
| | | }, |
| | | //滚动到指定view(nvue中有效)。index为需要滚动的view的index(第几个);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 |
| | | scrollIntoViewByIndex(index, offset, animate) { |
| | | this._scrollIntoView(index, offset, animate); |
| | | }, |
| | | //滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 |
| | | scrollIntoViewByView(view, offset, animate) { |
| | | this._scrollIntoView(view, offset, animate); |
| | | }, |
| | | //当使用页面滚动并且自定义下拉刷新时,请在页面的onPageScroll中调用此方法,告知z-paging当前的pageScrollTop,否则会导致在任意位置都可以下拉刷新 |
| | | updatePageScrollTop(value) { |
| | | this.pageScrollTop = value; |
| | | }, |
| | | //当使用页面滚动并且设置了slot="top"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="top"的view高度动态改变时,在其高度需要更新时调用此方法 |
| | | updatePageScrollTopHeight() { |
| | | this._updatePageScrollTopOrBottomHeight('top'); |
| | | }, |
| | | //当使用页面滚动并且设置了slot="bottom"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="bottom"的view高度动态改变时,在其高度需要更新时调用此方法 |
| | | updatePageScrollBottomHeight() { |
| | | this._updatePageScrollTopOrBottomHeight('bottom'); |
| | | }, |
| | | //更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用 |
| | | updateLeftAndRightWidth() { |
| | | if (!this.finalIsOldWebView) return; |
| | | this.$nextTick(() => this._updateLeftAndRightWidth(this.scrollViewContainerStyle, 'zp-page')); |
| | | }, |
| | | //更新z-paging内置scroll-view的scrollTop |
| | | updateScrollViewScrollTop(scrollTop, animate = true) { |
| | | this.privateScrollWithAnimation = animate ? 1 : 0; |
| | | this.scrollTop = this.oldScrollTop; |
| | | this.$nextTick(() => { |
| | | this.scrollTop = scrollTop; |
| | | this.oldScrollTop = this.scrollTop; |
| | | }); |
| | | }, |
| | | |
| | | //当滚动到顶部时 |
| | | _onScrollToUpper() { |
| | | this.$emit('scrolltoupper'); |
| | | this.$emit('scrollTopChange', 0); |
| | | this.$nextTick(() => { |
| | | this.oldScrollTop = 0; |
| | | }) |
| | | this.useChatRecordMode && this.loadingStatus !== Enum.More.NoMore && this._onLoadingMore('click'); |
| | | }, |
| | | //当滚动到底部时 |
| | | _onScrollToLower(e) { |
| | | (!e.detail || !e.detail.direction || e.detail.direction === 'bottom') && this._onLoadingMore('toBottom') |
| | | }, |
| | | //滚动到顶部 |
| | | _scrollToTop(animate = true, isPrivate = true) { |
| | | // #ifdef APP-NVUE |
| | | const el = this.$refs['zp-n-list-top-tag']; |
| | | if (this.usePageScroll) { |
| | | this._getNodeClientRect('zp-page-scroll-top', false).then(node => { |
| | | const nodeHeight = node ? node[0].height : 0; |
| | | weexDom.scrollToElement(el, { |
| | | offset: -nodeHeight, |
| | | animated: animate |
| | | }); |
| | | }); |
| | | } else { |
| | | if (!this.isIos && this.nvueListIs === 'scroller') { |
| | | this._getNodeClientRect('zp-n-refresh-container', false).then(node => { |
| | | const nodeHeight = node ? node[0].height : 0; |
| | | weexDom.scrollToElement(el, { |
| | | offset: -nodeHeight, |
| | | animated: animate |
| | | }); |
| | | }); |
| | | } else { |
| | | weexDom.scrollToElement(el, { |
| | | offset: 0, |
| | | animated: animate |
| | | }); |
| | | } |
| | | } |
| | | return; |
| | | // #endif |
| | | if (this.usePageScroll) { |
| | | this.$nextTick(() => { |
| | | uni.pageScrollTo({ |
| | | scrollTop: 0, |
| | | duration: animate ? 100 : 0, |
| | | }); |
| | | }); |
| | | return; |
| | | } |
| | | this.privateScrollWithAnimation = animate ? 1 : 0; |
| | | this.scrollTop = this.oldScrollTop; |
| | | this.$nextTick(() => { |
| | | this.scrollTop = 0; |
| | | this.oldScrollTop = this.scrollTop; |
| | | }); |
| | | }, |
| | | //滚动到底部 |
| | | async _scrollToBottom(animate = true) { |
| | | // #ifdef APP-NVUE |
| | | const el = this.$refs['zp-n-list-bottom-tag']; |
| | | if (el) { |
| | | weexDom.scrollToElement(el, { |
| | | offset: 0, |
| | | animated: animate |
| | | }); |
| | | } else { |
| | | u.consoleErr('滚动到底部失败,因为您设置了hideNvueBottomTag为true'); |
| | | } |
| | | return; |
| | | // #endif |
| | | if (this.usePageScroll) { |
| | | this.$nextTick(() => { |
| | | uni.pageScrollTo({ |
| | | scrollTop: Number.MAX_VALUE, |
| | | duration: animate ? 100 : 0, |
| | | }); |
| | | }); |
| | | return; |
| | | } |
| | | try { |
| | | this.privateScrollWithAnimation = animate ? 1 : 0; |
| | | const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container'); |
| | | const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); |
| | | const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; |
| | | const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; |
| | | if (pagingContainerH > scrollViewH) { |
| | | this.scrollTop = this.oldScrollTop; |
| | | this.$nextTick(() => { |
| | | this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight; |
| | | this.oldScrollTop = this.scrollTop; |
| | | }); |
| | | } |
| | | } catch (e) {} |
| | | }, |
| | | //滚动到指定view |
| | | _scrollIntoView(sel, offset = 0, animate = false, finishCallback) { |
| | | try { |
| | | this.scrollTop = this.oldScrollTop; |
| | | this.$nextTick(() => { |
| | | // #ifdef APP-NVUE |
| | | const refs = this.$parent.$refs; |
| | | if (!refs) return; |
| | | const dataType = Object.prototype.toString.call(sel); |
| | | let el = null; |
| | | if (dataType === '[object Number]') { |
| | | const els = refs[`z-paging-${sel}`]; |
| | | el = els ? els[0] : null; |
| | | } else if (dataType === '[object Array]') { |
| | | el = sel[0]; |
| | | } else { |
| | | el = sel; |
| | | } |
| | | if (el) { |
| | | weexDom.scrollToElement(el, { |
| | | offset: -offset, |
| | | animated: animate |
| | | }); |
| | | } else { |
| | | u.consoleErr('在nvue中滚动到指定位置,cell必须设置 :ref="`z-paging-${index}`"'); |
| | | } |
| | | return; |
| | | // #endif |
| | | this._getNodeClientRect('#' + sel.replace('#', ''), this.$parent).then((node) => { |
| | | if (node) { |
| | | let nodeTop = node[0].top; |
| | | this._scrollIntoViewByNodeTop(nodeTop, offset, animate); |
| | | finishCallback && finishCallback(); |
| | | } |
| | | }); |
| | | }); |
| | | } catch (e) {} |
| | | }, |
| | | //通过nodeTop滚动到指定view |
| | | _scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) { |
| | | this._scrollToY(nodeTop, offset, animate, true); |
| | | }, |
| | | //滚动到指定位置 |
| | | _scrollToY(y, offset = 0, animate = false, addScrollTop = false) { |
| | | this.privateScrollWithAnimation = animate ? 1 : 0; |
| | | if (this.usePageScroll) { |
| | | uni.pageScrollTo({ |
| | | scrollTop: y - offset, |
| | | duration: animate ? 100 : 0 |
| | | }); |
| | | } else { |
| | | if(addScrollTop){ |
| | | y += this.oldScrollTop; |
| | | } |
| | | this.scrollTop = y - offset; |
| | | this.oldScrollTop = this.scrollTop; |
| | | } |
| | | }, |
| | | //scroll-view滚动中 |
| | | _scroll(e) { |
| | | this.$emit('scroll', e); |
| | | const scrollTop = e.detail.scrollTop; |
| | | // #ifndef APP-NVUE |
| | | this.finalUseVirtualList && this._updateVirtualScroll(scrollTop, this.oldScrollTop - scrollTop); |
| | | // #endif |
| | | this.oldScrollTop = scrollTop; |
| | | const scrollDiff = e.detail.scrollHeight - this.oldScrollTop; |
| | | !this.isIos && this._checkScrolledToBottom(scrollDiff); |
| | | }, |
| | | //检测scrollView是否要铺满屏幕 |
| | | _doCheckScrollViewShouldFullHeight(totalData) { |
| | | if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) { |
| | | // #ifndef APP-NVUE |
| | | this.$nextTick(() => { |
| | | this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => { |
| | | this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode) |
| | | }); |
| | | }) |
| | | // #endif |
| | | // #ifdef APP-NVUE |
| | | this._preCheckShowNoMoreInside(totalData) |
| | | // #endif |
| | | } else { |
| | | this._preCheckShowNoMoreInside(totalData) |
| | | } |
| | | }, |
| | | //检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时,默认z-paging需要铺满全屏,避免数据过少时内部的empty-view无法正确展示) |
| | | async _checkScrollViewShouldFullHeight(callback) { |
| | | try { |
| | | const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); |
| | | const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content'); |
| | | if (!scrollViewNode || !pagingContainerNode) return; |
| | | const scrollViewHeight = pagingContainerNode[0].height; |
| | | const scrollViewTop = scrollViewNode[0].top; |
| | | if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) { |
| | | this._setAutoHeight(true, scrollViewNode); |
| | | callback(scrollViewNode, pagingContainerNode); |
| | | } else { |
| | | this._setAutoHeight(false); |
| | | callback(null, null); |
| | | } |
| | | } catch (e) { |
| | | callback(null, null); |
| | | } |
| | | }, |
| | | //scrollTop改变时触发 |
| | | _scrollTopChange(newVal, isPageScrollTop){ |
| | | this.$emit('scrollTopChange', newVal); |
| | | this.$emit('update:scrollTop', newVal); |
| | | this._checkShouldShowBackToTop(newVal); |
| | | const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : (newVal > 105 ? 106 : (newVal > 5 ? 6 : 0)); |
| | | if (isPageScrollTop && this.wxsPageScrollTop !== scrollTop) { |
| | | this.wxsPageScrollTop = scrollTop; |
| | | } else if (!isPageScrollTop && this.wxsScrollTop !== scrollTop) { |
| | | this.wxsScrollTop = scrollTop; |
| | | if (scrollTop > 6) { |
| | | this.scrollEnable = true; |
| | | } |
| | | } |
| | | }, |
| | | //更新使用页面滚动时slot="top"或"bottom"插入view的高度 |
| | | _updatePageScrollTopOrBottomHeight(type) { |
| | | // #ifndef APP-NVUE |
| | | if (!this.usePageScroll) return; |
| | | // #endif |
| | | this._doCheckScrollViewShouldFullHeight(this.realTotalData); |
| | | const node = `.zp-page-${type}`; |
| | | const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`; |
| | | let safeAreaInsetBottomAdd = this.safeAreaInsetBottom; |
| | | this.$nextTick(() => { |
| | | let delayTime = 0; |
| | | // #ifdef MP-BAIDU || APP-NVUE |
| | | delayTime = 50; |
| | | // #endif |
| | | u.delay(() => { |
| | | this._getNodeClientRect(node).then((res) => { |
| | | if (res) { |
| | | let pageScrollNodeHeight = res[0].height; |
| | | if (type === 'bottom') { |
| | | if (safeAreaInsetBottomAdd) { |
| | | pageScrollNodeHeight += this.safeAreaBottom; |
| | | } |
| | | } else { |
| | | this.cacheTopHeight = pageScrollNodeHeight; |
| | | } |
| | | this.$set(this.scrollViewStyle, marginText, `${pageScrollNodeHeight}px`); |
| | | } else if (safeAreaInsetBottomAdd) { |
| | | this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`); |
| | | } |
| | | }); |
| | | }, delayTime) |
| | | }) |
| | | }, |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]虚拟列表模块 |
| | | import u from '.././z-paging-utils' |
| | | import c from '.././z-paging-constant' |
| | | import Enum from '.././z-paging-enum' |
| | | |
| | | export default { |
| | | props: { |
| | | //是否使用虚拟列表,默认为否 |
| | | useVirtualList: { |
| | | type: Boolean, |
| | | default: u.gc('useVirtualList', false) |
| | | }, |
| | | //在使用虚拟列表时,是否使用兼容模式,默认为否 |
| | | useCompatibilityMode: { |
| | | type: Boolean, |
| | | default: u.gc('useCompatibilityMode', false) |
| | | }, |
| | | //使用兼容模式时传递的附加数据 |
| | | extraData: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('extraData', {}); |
| | | } |
| | | }, |
| | | //是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true |
| | | useInnerList: { |
| | | type: Boolean, |
| | | default: u.gc('useInnerList', false) |
| | | }, |
| | | //强制关闭inner-list,默认为false,如果为true将强制关闭innerList,适用于开启了虚拟列表后需要强制关闭inner-list的情况 |
| | | forceCloseInnerList: { |
| | | type: Boolean, |
| | | default: u.gc('forceCloseInnerList', false) |
| | | }, |
| | | //内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项 |
| | | cellKeyName: { |
| | | type: String, |
| | | default: u.gc('cellKeyName', '') |
| | | }, |
| | | //innerList样式 |
| | | innerListStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('innerListStyle', {}); |
| | | } |
| | | }, |
| | | //innerCell样式 |
| | | innerCellStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('innerCellStyle', {}); |
| | | } |
| | | }, |
| | | //预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题 |
| | | preloadPage: { |
| | | type: [Number, String], |
| | | default: u.gc('preloadPage', 12), |
| | | validator: (value) => { |
| | | if (value <= 0) u.consoleErr('preload-page必须大于0!'); |
| | | return value > 0; |
| | | } |
| | | }, |
| | | //虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。 |
| | | cellHeightMode: { |
| | | type: String, |
| | | default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed) |
| | | }, |
| | | //虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2 |
| | | virtualListCol: { |
| | | type: [Number, String], |
| | | default: u.gc('virtualListCol', 1) |
| | | }, |
| | | //虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题 |
| | | virtualScrollFps: { |
| | | type: [Number, String], |
| | | default: u.gc('virtualScrollFps', 80) |
| | | }, |
| | | }, |
| | | data() { |
| | | return { |
| | | virtualListKey: u.getInstanceId(), |
| | | virtualPageHeight: 0, |
| | | virtualCellHeight: 0, |
| | | virtualScrollTimeStamp: 0, |
| | | |
| | | virtualList: [], |
| | | virtualPlaceholderTopHeight: 0, |
| | | virtualPlaceholderBottomHeight: 0, |
| | | virtualTopRangeIndex: 0, |
| | | virtualBottomRangeIndex: 0, |
| | | lastVirtualTopRangeIndex: 0, |
| | | lastVirtualBottomRangeIndex: 0, |
| | | virtualItemInsertedCount: 0, |
| | | |
| | | virtualHeightCacheList: [], |
| | | |
| | | getCellHeightRetryCount: { |
| | | fixed: 0, |
| | | dynamic: 0 |
| | | }, |
| | | pagingOrgTop: -1, |
| | | updateVirtualListFromDataChange: false |
| | | } |
| | | }, |
| | | watch: { |
| | | realTotalData(newVal) { |
| | | // #ifndef APP-NVUE |
| | | if (this.finalUseVirtualList) { |
| | | this.updateVirtualListFromDataChange = true; |
| | | this.$nextTick(() => { |
| | | this.getCellHeightRetryCount.fixed = 0; |
| | | !newVal.length && this._resetDynamicListState(!this.isUserPullDown); |
| | | newVal.length && this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight(); |
| | | this._updateVirtualScroll(this.oldScrollTop); |
| | | }) |
| | | } |
| | | // #endif |
| | | }, |
| | | virtualList(newVal){ |
| | | this.$emit('update:virtualList', newVal); |
| | | this.$emit('virtualListChange', newVal); |
| | | } |
| | | }, |
| | | computed: { |
| | | virtualCellIndexKey() { |
| | | return c.listCellIndexKey; |
| | | }, |
| | | finalUseVirtualList() { |
| | | if (this.useVirtualList && this.usePageScroll){ |
| | | u.consoleErr('使用页面滚动时,开启虚拟列表无效!'); |
| | | } |
| | | return this.useVirtualList && !this.usePageScroll; |
| | | }, |
| | | finalUseInnerList() { |
| | | return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList); |
| | | }, |
| | | finalCellKeyName() { |
| | | // #ifdef APP-NVUE |
| | | if (this.finalUseVirtualList && !this.cellKeyName.length){ |
| | | u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name,否则将可能导致列表渲染错误!'); |
| | | } |
| | | // #endif |
| | | return this.cellKeyName; |
| | | }, |
| | | finalVirtualPageHeight(){ |
| | | return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight; |
| | | }, |
| | | virtualRangePageHeight(){ |
| | | return this.finalVirtualPageHeight * this.preloadPage; |
| | | }, |
| | | virtualScrollDisTimeStamp() { |
| | | return 1000 / this.virtualScrollFps; |
| | | }, |
| | | }, |
| | | methods: { |
| | | //在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法;item:需要插入的item,index:插入的cell位置,若index为2,则插入的item在原list的index=1之后,index从0开始 |
| | | doInsertVirtualListItem(item, index) { |
| | | if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; |
| | | this.virtualItemInsertedCount ++; |
| | | if (!item || Object.prototype.toString.call(item) !== '[object Object]') { |
| | | item = { item }; |
| | | } |
| | | const cellIndexKey = this.virtualCellIndexKey; |
| | | item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`; |
| | | item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; |
| | | this.totalData.splice(index, 0, item); |
| | | this.$nextTick(async () => { |
| | | let retryCount = 0; |
| | | while (retryCount <= 10) { |
| | | await u.wait(c.delayTime); |
| | | |
| | | const cellNode = await this._getNodeClientRect(`#zp-id-${item[cellIndexKey]}`, this.finalUseInnerList); |
| | | if (!cellNode) { |
| | | retryCount ++; |
| | | continue; |
| | | } |
| | | |
| | | const currentHeight = cellNode ? cellNode[0].height : 0; |
| | | const lastHeightCache = this.virtualHeightCacheList[index - 1]; |
| | | const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; |
| | | this.virtualHeightCacheList.splice(index, 0, { |
| | | height: currentHeight, |
| | | lastTotalHeight, |
| | | totalHeight: lastTotalHeight + currentHeight |
| | | }); |
| | | |
| | | for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { |
| | | const thisNode = this.virtualHeightCacheList[i]; |
| | | thisNode.lastTotalHeight += currentHeight; |
| | | thisNode.totalHeight += currentHeight; |
| | | } |
| | | |
| | | this._updateVirtualScroll(this.oldScrollTop); |
| | | break; |
| | | } |
| | | }) |
| | | }, |
| | | //在使用动态高度虚拟列表时,手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变时调用);index:需要更新的cell在列表中的位置,从0开始 |
| | | didUpdateVirtualListCell(index) { |
| | | if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; |
| | | const currentNode = this.virtualHeightCacheList[index]; |
| | | this.$nextTick(() => { |
| | | this._getNodeClientRect(`#zp-id-${index}`, this.finalUseInnerList).then(cellNode => { |
| | | const cellNodeHeight = cellNode ? cellNode[0].height : 0; |
| | | const heightDis = cellNodeHeight - currentNode.height; |
| | | currentNode.height = cellNodeHeight; |
| | | currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight; |
| | | |
| | | for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { |
| | | const thisNode = this.virtualHeightCacheList[i]; |
| | | thisNode.totalHeight += heightDis; |
| | | thisNode.lastTotalHeight += heightDis; |
| | | } |
| | | }); |
| | | }) |
| | | }, |
| | | //在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组;index:删除的cell在列表中的位置,从0开始 |
| | | didDeleteVirtualListCell(index) { |
| | | if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; |
| | | const currentNode = this.virtualHeightCacheList[index]; |
| | | for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { |
| | | const thisNode = this.virtualHeightCacheList[i]; |
| | | thisNode.totalHeight -= currentNode.height; |
| | | thisNode.lastTotalHeight -= currentNode.height; |
| | | } |
| | | this.virtualHeightCacheList.splice(index, 1); |
| | | }, |
| | | //初始化虚拟列表 |
| | | _virtualListInit() { |
| | | this.$nextTick(() => { |
| | | u.delay(() => { |
| | | this._getNodeClientRect('.zp-scroll-view').then(node => { |
| | | if (node) { |
| | | this.pagingOrgTop = node[0].top; |
| | | this.virtualPageHeight = node[0].height; |
| | | } |
| | | }); |
| | | }); |
| | | }) |
| | | }, |
| | | //cellHeightMode为fixed时获取第一个cell高度 |
| | | _updateFixedCellHeight() { |
| | | this.$nextTick(() => { |
| | | u.delay(() => { |
| | | this._getNodeClientRect(`#zp-id-${0}`,this.finalUseInnerList).then(cellNode => { |
| | | if (!cellNode) { |
| | | if (this.getCellHeightRetryCount.fixed > 10) return; |
| | | this.getCellHeightRetryCount.fixed ++; |
| | | this._updateFixedCellHeight(); |
| | | } else { |
| | | this.virtualCellHeight = cellNode[0].height; |
| | | this._updateVirtualScroll(this.oldScrollTop); |
| | | } |
| | | }); |
| | | }, c.delayTime, 'updateFixedCellHeightDelay'); |
| | | }) |
| | | }, |
| | | //cellHeightMode为dynamic时获取每个cell高度 |
| | | _updateDynamicCellHeight(list, dataFrom = 'bottom') { |
| | | const dataFromTop = dataFrom === 'top'; |
| | | const heightCacheList = this.virtualHeightCacheList; |
| | | const currentCacheList = dataFromTop ? [] : heightCacheList; |
| | | let listTotalHeight = 0; |
| | | this.$nextTick(() => { |
| | | u.delay(async () => { |
| | | for (let i = 0; i < list.length; i++) { |
| | | const cellNode = await this._getNodeClientRect(`#zp-id-${list[i][this.virtualCellIndexKey]}`, this.finalUseInnerList); |
| | | const currentHeight = cellNode ? cellNode[0].height : 0; |
| | | if (!cellNode) { |
| | | if (this.getCellHeightRetryCount.dynamic <= 10) { |
| | | heightCacheList.splice(heightCacheList.length - i, i); |
| | | this.getCellHeightRetryCount.dynamic ++; |
| | | this._updateDynamicCellHeight(list, dataFrom); |
| | | } |
| | | return; |
| | | } |
| | | const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null; |
| | | const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; |
| | | currentCacheList.push({ |
| | | height: currentHeight, |
| | | lastTotalHeight, |
| | | totalHeight: lastTotalHeight + currentHeight |
| | | }); |
| | | if (dataFromTop) { |
| | | listTotalHeight += currentHeight; |
| | | } |
| | | } |
| | | if (dataFromTop && list.length) { |
| | | for (let i = 0; i < heightCacheList.length; i++) { |
| | | const heightCacheItem = heightCacheList[i]; |
| | | heightCacheItem.lastTotalHeight += listTotalHeight; |
| | | heightCacheItem.totalHeight += listTotalHeight; |
| | | } |
| | | this.virtualHeightCacheList = currentCacheList.concat(heightCacheList); |
| | | } |
| | | this._updateVirtualScroll(this.oldScrollTop); |
| | | }, c.delayTime, 'updateDynamicCellHeightDelay') |
| | | }) |
| | | }, |
| | | //设置cellItem的index |
| | | _setCellIndex(list, dataFrom = 'bottom') { |
| | | let currentItemIndex = 0; |
| | | const cellIndexKey = this.virtualCellIndexKey; |
| | | if (this.totalData.length) { |
| | | if (dataFrom === 'bottom') { |
| | | currentItemIndex = this.realTotalData.length; |
| | | const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null; |
| | | if (lastItem && lastItem[cellIndexKey] !== undefined) { |
| | | currentItemIndex = lastItem[cellIndexKey] + 1; |
| | | } |
| | | } else if (dataFrom === 'top') { |
| | | const firstItem = this.realTotalData.length ? this.realTotalData[0] : null; |
| | | if (firstItem && firstItem[cellIndexKey] !== undefined) { |
| | | currentItemIndex = firstItem[cellIndexKey] - list.length; |
| | | } |
| | | } |
| | | } else { |
| | | this._resetDynamicListState(); |
| | | } |
| | | for (let i = 0; i < list.length; i++) { |
| | | let item = list[i]; |
| | | if (!item || Object.prototype.toString.call(item) !== '[object Object]') { |
| | | item = { item }; |
| | | } |
| | | item[cellIndexKey] = currentItemIndex + i; |
| | | item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; |
| | | list[i] = item; |
| | | } |
| | | this.getCellHeightRetryCount.dynamic = 0; |
| | | this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom); |
| | | }, |
| | | //更新scroll滚动 |
| | | _updateVirtualScroll(scrollTop, scrollDiff = 0) { |
| | | const currentTimeStamp = u.getTime(); |
| | | scrollTop === 0 && this._resetTopRange(); |
| | | if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) { |
| | | return; |
| | | } |
| | | this.virtualScrollTimeStamp = currentTimeStamp; |
| | | |
| | | let scrollIndex = 0; |
| | | const cellHeightMode = this.cellHeightMode; |
| | | if (cellHeightMode === Enum.CellHeightMode.Fixed) { |
| | | scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0; |
| | | this._updateFixedTopRangeIndex(scrollIndex); |
| | | this._updateFixedBottomRangeIndex(scrollIndex); |
| | | } else if(cellHeightMode === Enum.CellHeightMode.Dynamic) { |
| | | const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom'; |
| | | const rangePageHeight = this.virtualRangePageHeight; |
| | | const topRangePageOffset = scrollTop - rangePageHeight; |
| | | const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight; |
| | | |
| | | let virtualBottomRangeIndex = 0; |
| | | let virtualPlaceholderBottomHeight = 0; |
| | | let reachedLimitBottom = false; |
| | | const heightCacheList = this.virtualHeightCacheList; |
| | | const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null; |
| | | |
| | | let startTopRangeIndex = this.virtualTopRangeIndex; |
| | | if (scrollDirection === 'bottom') { |
| | | for (let i = startTopRangeIndex; i < heightCacheList.length; i++){ |
| | | const heightCacheItem = heightCacheList[i]; |
| | | if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) { |
| | | this.virtualTopRangeIndex = i; |
| | | this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; |
| | | break; |
| | | } |
| | | } |
| | | } else { |
| | | let topRangeMatched = false; |
| | | for (let i = startTopRangeIndex; i >= 0; i--){ |
| | | const heightCacheItem = heightCacheList[i]; |
| | | if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) { |
| | | this.virtualTopRangeIndex = i; |
| | | this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; |
| | | topRangeMatched = true; |
| | | break; |
| | | } |
| | | } |
| | | !topRangeMatched && this._resetTopRange(); |
| | | } |
| | | for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){ |
| | | const heightCacheItem = heightCacheList[i]; |
| | | if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) { |
| | | virtualBottomRangeIndex = i; |
| | | virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight; |
| | | reachedLimitBottom = true; |
| | | break; |
| | | } |
| | | } |
| | | if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) { |
| | | this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize; |
| | | this.virtualPlaceholderBottomHeight = 0; |
| | | } else { |
| | | this.virtualBottomRangeIndex = virtualBottomRangeIndex; |
| | | this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight; |
| | | } |
| | | this._updateVirtualList(); |
| | | } |
| | | }, |
| | | //更新fixedCell模式下topRangeIndex&placeholderTopHeight |
| | | _updateFixedTopRangeIndex(scrollIndex) { |
| | | let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage; |
| | | virtualTopRangeIndex *= this.virtualListCol; |
| | | virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex); |
| | | this.virtualTopRangeIndex = virtualTopRangeIndex; |
| | | this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight; |
| | | }, |
| | | //更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight |
| | | _updateFixedBottomRangeIndex(scrollIndex) { |
| | | let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1); |
| | | virtualBottomRangeIndex *= this.virtualListCol; |
| | | virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex); |
| | | this.virtualBottomRangeIndex = virtualBottomRangeIndex; |
| | | this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol; |
| | | this._updateVirtualList(); |
| | | }, |
| | | //更新virtualList |
| | | _updateVirtualList() { |
| | | const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex); |
| | | if (shouldUpdateList) { |
| | | this.updateVirtualListFromDataChange = false; |
| | | this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex; |
| | | this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex; |
| | | this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1); |
| | | } |
| | | }, |
| | | //重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态 |
| | | _resetDynamicListState(resetVirtualList = false) { |
| | | this.virtualHeightCacheList = []; |
| | | if (resetVirtualList) { |
| | | this.virtualList = []; |
| | | } |
| | | this.virtualTopRangeIndex = 0; |
| | | this.virtualPlaceholderTopHeight = 0; |
| | | }, |
| | | //重置topRangeIndex和placeholderTopHeight |
| | | _resetTopRange() { |
| | | this.virtualTopRangeIndex = 0; |
| | | this.virtualPlaceholderTopHeight = 0; |
| | | this._updateVirtualList(); |
| | | }, |
| | | //检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题) |
| | | _checkVirtualListScroll() { |
| | | if (this.finalUseVirtualList) { |
| | | this.$nextTick(() => { |
| | | this._getNodeClientRect('.zp-paging-touch-view').then(node => { |
| | | const currentTop = node ? node[0].top : 0; |
| | | if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) { |
| | | this._updateVirtualScroll(0); |
| | | } |
| | | }); |
| | | }) |
| | | } |
| | | }, |
| | | //处理使用内置列表时点击了cell事件 |
| | | _innerCellClick(item, index) { |
| | | this.$emit('innerCellClick', item, index); |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]处理main.js中的配置信息工具 |
| | | |
| | | let config = null; |
| | | let getedStorage = false; |
| | | const storageKey = 'Z-PAGING-CONFIG-STORAGE-KEY' |
| | | |
| | | function setConfig(value) { |
| | | uni.setStorageSync(storageKey, value); |
| | | } |
| | | |
| | | function getConfig() { |
| | | if (getedStorage) return config; |
| | | config = uni.getStorageSync(storageKey); |
| | | getedStorage = true; |
| | | return config; |
| | | } |
| | | |
| | | export default { |
| | | setConfig, |
| | | getConfig |
| | | }; |
| New file |
| | |
| | | // [z-paging]常量 |
| | | |
| | | export default { |
| | | version: '2.6.2', |
| | | delayTime: 100, |
| | | errorUpdateKey: 'z-paging-error-emit', |
| | | completeUpdateKey: 'z-paging-complete-emit', |
| | | cachePrefixKey: 'z-paging-cache', |
| | | |
| | | listCellIndexKey: 'zp_index', |
| | | listCellIndexUniqueKey: 'zp_unique_index' |
| | | } |
| New file |
| | |
| | | // [z-paging]枚举 |
| | | |
| | | export default { |
| | | //当前加载类型 0.下拉刷新 1.上拉加载更多 |
| | | LoadingType: { |
| | | Refresher: 0, |
| | | LoadingMore: 1 |
| | | }, |
| | | //下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束 |
| | | Refresher: { |
| | | Default: 0, |
| | | ReleaseToRefresh: 1, |
| | | Loading: 2, |
| | | Complete: 3 |
| | | }, |
| | | //底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败 |
| | | More: { |
| | | Default: 0, |
| | | Loading: 1, |
| | | NoMore: 2, |
| | | Fail: 3 |
| | | }, |
| | | //@query触发来源 0.用户主动下拉刷新 1.通过reload触发 2.通过refresh触发 3.通过滚动到底部加载更多或点击底部加载更多触发 |
| | | QueryFrom: { |
| | | UserPullDown: 0, |
| | | Reload: 1, |
| | | Refresh: 2, |
| | | LoadingMore: 3 |
| | | }, |
| | | //虚拟列表cell高度模式 |
| | | CellHeightMode: { |
| | | //固定高度 |
| | | Fixed: 'fixed', |
| | | //动态高度 |
| | | Dynamic: 'dynamic' |
| | | }, |
| | | //列表缓存模式 |
| | | CacheMode: { |
| | | //默认模式,只会缓存一次 |
| | | Default: 'default', |
| | | //总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 |
| | | Always: 'always' |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]拦截器 |
| | | |
| | | //拦截&处理@query事件 |
| | | function handleQuery(callback) { |
| | | try { |
| | | setTimeout(function() { |
| | | _getApp().globalData.zp_handleQueryCallback = callback; |
| | | }, 1); |
| | | } catch (e) {} |
| | | } |
| | | |
| | | //拦截&处理@query事件(私有,请勿调用) |
| | | function _handleQuery(pageNo, pageSize, from, lastItem){ |
| | | const callback = _getApp().globalData.zp_handleQueryCallback; |
| | | return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from]; |
| | | } |
| | | |
| | | //拦截&处理系统language转i18n local |
| | | function handleLanguage2Local(callback) { |
| | | try { |
| | | setTimeout(function() { |
| | | _getApp().globalData.zp_handleLanguage2LocalCallback = callback; |
| | | }, 1); |
| | | } catch (e) {} |
| | | } |
| | | |
| | | //拦截&处理系统language转i18n local(私有,请勿调用) |
| | | function _handleLanguage2Local(language, local){ |
| | | const callback = _getApp().globalData.zp_handleLanguage2LocalCallback; |
| | | return callback ? callback(language, local) : local; |
| | | } |
| | | |
| | | //获取当前app对象 |
| | | function _getApp(){ |
| | | // #ifndef APP-NVUE |
| | | return getApp(); |
| | | // #endif |
| | | // #ifdef APP-NVUE |
| | | return getApp({ allowDefault: true }); |
| | | // #endif |
| | | } |
| | | |
| | | export default { |
| | | handleQuery, |
| | | _handleQuery, |
| | | handleLanguage2Local, |
| | | _handleLanguage2Local |
| | | }; |
| New file |
| | |
| | | // [z-paging]核心js |
| | | |
| | | import zStatic from './z-paging-static' |
| | | import c from './z-paging-constant' |
| | | import u from './z-paging-utils' |
| | | |
| | | import zPagingRefresh from '../components/z-paging-refresh' |
| | | import zPagingLoadMore from '../components/z-paging-load-more' |
| | | import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view' |
| | | |
| | | // modules |
| | | import commonLayoutModule from './modules/common-layout' |
| | | import dataHandleModule from './modules/data-handle' |
| | | import i18nModule from './modules/i18n' |
| | | import nvueModule from './modules/nvue' |
| | | import emptyModule from './modules/empty' |
| | | import refresherModule from './modules/refresher' |
| | | import loadMoreModule from './modules/load-more' |
| | | import loadingModule from './modules/loading' |
| | | import scrollerModule from './modules/scroller' |
| | | import backToTopModule from './modules/back-to-top' |
| | | import virtualListModule from './modules/virtual-list' |
| | | |
| | | import Enum from './z-paging-enum' |
| | | |
| | | const systemInfo = uni.getSystemInfoSync(); |
| | | |
| | | export default { |
| | | name: "z-paging", |
| | | components: { |
| | | zPagingRefresh, |
| | | zPagingLoadMore, |
| | | zPagingEmptyView |
| | | }, |
| | | mixins: [ |
| | | commonLayoutModule, |
| | | dataHandleModule, |
| | | i18nModule, |
| | | nvueModule, |
| | | emptyModule, |
| | | refresherModule, |
| | | loadMoreModule, |
| | | loadingModule, |
| | | scrollerModule, |
| | | backToTopModule, |
| | | virtualListModule |
| | | ], |
| | | data() { |
| | | return { |
| | | //--------------静态资源--------------- |
| | | base64Arrow: zStatic.base64Arrow, |
| | | base64Flower: zStatic.base64Flower, |
| | | base64BackToTop: zStatic.base64BackToTop, |
| | | |
| | | //-------------全局数据相关-------------- |
| | | //当前加载类型 |
| | | loadingType: Enum.LoadingType.Refresher, |
| | | requestTimeStamp: 0, |
| | | chatRecordLoadingMoreText: '', |
| | | wxsPropType: '', |
| | | renderPropScrollTop: -1, |
| | | checkScrolledToBottomTimeOut: null, |
| | | cacheTopHeight: -1, |
| | | statusBarHeight: systemInfo.statusBarHeight, |
| | | |
| | | //--------------状态&判断--------------- |
| | | insideOfPaging: -1, |
| | | isLoadFailed: false, |
| | | isIos: systemInfo.platform === 'ios', |
| | | disabledBounce: false, |
| | | fromCompleteEmit: false, |
| | | disabledCompleteEmit: false, |
| | | pageLaunched: false, |
| | | |
| | | //---------------wxs相关--------------- |
| | | wxsIsScrollTopInTopRange: true, |
| | | wxsScrollTop: 0, |
| | | wxsPageScrollTop: 0, |
| | | wxsOnPullingDown: false, |
| | | }; |
| | | }, |
| | | props: { |
| | | //调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay |
| | | delay: { |
| | | type: [Number, String], |
| | | default: u.gc('delay', 0), |
| | | }, |
| | | //触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400 |
| | | minDelay: { |
| | | type: [Number, String], |
| | | default: u.gc('minDelay', 0), |
| | | }, |
| | | //设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 |
| | | pagingStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('pagingStyle', {}); |
| | | }, |
| | | }, |
| | | //z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100% |
| | | height: { |
| | | type: String, |
| | | default: u.gc('height', '') |
| | | }, |
| | | //z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100% |
| | | width: { |
| | | type: String, |
| | | default: u.gc('width', '') |
| | | }, |
| | | //z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff" |
| | | bgColor: { |
| | | type: String, |
| | | default: u.gc('bgColor', '') |
| | | }, |
| | | //设置z-paging的容器(插槽的父view)的style |
| | | pagingContentStyle: { |
| | | type: Object, |
| | | default: function() { |
| | | return u.gc('pagingContentStyle', {}); |
| | | }, |
| | | }, |
| | | //z-paging是否自动高度,若自动高度则会自动铺满屏幕 |
| | | autoHeight: { |
| | | type: Boolean, |
| | | default: u.gc('autoHeight', false) |
| | | }, |
| | | //z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数 |
| | | autoHeightAddition: { |
| | | type: [Number, String], |
| | | default: u.gc('autoHeightAddition', '0px') |
| | | }, |
| | | //loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black |
| | | defaultThemeStyle: { |
| | | type: String, |
| | | default: u.gc('defaultThemeStyle', 'black') |
| | | }, |
| | | //z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) |
| | | fixed: { |
| | | type: Boolean, |
| | | default: u.gc('fixed', true) |
| | | }, |
| | | //是否开启底部安全区域适配 |
| | | safeAreaInsetBottom: { |
| | | type: Boolean, |
| | | default: u.gc('safeAreaInsetBottom', false) |
| | | }, |
| | | //开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域 |
| | | useSafeAreaPlaceholder: { |
| | | type: Boolean, |
| | | default: u.gc('useSafeAreaPlaceholder', false) |
| | | }, |
| | | //slot="top"的view的z-index,默认为99,仅使用页面滚动时有效 |
| | | topZIndex: { |
| | | type: Number, |
| | | default: u.gc('topZIndex', 99) |
| | | }, |
| | | //z-paging内容容器父view的z-index,默认为1 |
| | | superContentZIndex: { |
| | | type: Number, |
| | | default: u.gc('superContentZIndex', 1) |
| | | }, |
| | | //z-paging内容容器部分的z-index,默认为10 |
| | | contentZIndex: { |
| | | type: Number, |
| | | default: u.gc('contentZIndex', 10) |
| | | }, |
| | | //使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是 |
| | | autoFullHeight: { |
| | | type: Boolean, |
| | | default: u.gc('autoFullHeight', true) |
| | | }, |
| | | //是否监听列表触摸方向改变,默认为否 |
| | | watchTouchDirectionChange: { |
| | | type: Boolean, |
| | | default: u.gc('watchTouchDirectionChange', false) |
| | | }, |
| | | }, |
| | | created(){ |
| | | if (this.createdReload && !this.refresherOnly && this.auto) { |
| | | this._startLoading(); |
| | | this.$nextTick(this._preReload); |
| | | } |
| | | }, |
| | | mounted() { |
| | | this.wxsPropType = u.getTime().toString(); |
| | | this.renderJsIgnore; |
| | | if (!this.createdReload && !this.refresherOnly && this.auto) { |
| | | this.$nextTick(this._preReload); |
| | | } |
| | | this.finalUseCache && this._setListByLocalCache(); |
| | | let delay = 0; |
| | | // #ifdef H5 || MP |
| | | delay = c.delayTime; |
| | | // #endif |
| | | this.$nextTick(() => { |
| | | this.systemInfo = uni.getSystemInfoSync(); |
| | | !this.usePageScroll && this.autoHeight && this._setAutoHeight(); |
| | | this.loaded = true; |
| | | }) |
| | | this.updatePageScrollTopHeight(); |
| | | this.updatePageScrollBottomHeight(); |
| | | this.updateLeftAndRightWidth(); |
| | | if (this.finalRefresherEnabled && this.useCustomRefresher) { |
| | | this.$nextTick(() => { |
| | | this.isTouchmoving = true; |
| | | }) |
| | | } |
| | | this._onEmit(); |
| | | // #ifdef APP-NVUE |
| | | if (!this.isIos && !this.useChatRecordMode) { |
| | | this.nLoadingMoreFixedHeight = true; |
| | | } |
| | | this._nUpdateRefresherWidth(); |
| | | // #endif |
| | | // #ifndef APP-NVUE |
| | | this.finalUseVirtualList && this._virtualListInit(); |
| | | // #endif |
| | | // #ifndef APP-PLUS |
| | | this.$nextTick(() => { |
| | | setTimeout(() => { |
| | | this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight()); |
| | | }, delay) |
| | | }) |
| | | // #endif |
| | | }, |
| | | destroyed() { |
| | | this._offEmit(); |
| | | }, |
| | | // #ifdef VUE3 |
| | | unmounted() { |
| | | this._offEmit(); |
| | | }, |
| | | // #endif |
| | | watch: { |
| | | defaultThemeStyle: { |
| | | handler(newVal) { |
| | | if (newVal.length) { |
| | | this.finalRefresherDefaultStyle = newVal; |
| | | } |
| | | }, |
| | | immediate: true |
| | | }, |
| | | autoHeight(newVal) { |
| | | this.loaded && !this.usePageScroll && this._setAutoHeight(newVal); |
| | | }, |
| | | autoHeightAddition(newVal) { |
| | | this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal); |
| | | }, |
| | | }, |
| | | computed: { |
| | | finalPagingStyle() { |
| | | const pagingStyle = this.pagingStyle; |
| | | if (!this.systemInfo) return pagingStyle; |
| | | const { windowTop, windowBottom } = this; |
| | | if (!this.usePageScroll && this.fixed) { |
| | | if (windowTop && !pagingStyle.top) { |
| | | pagingStyle.top = windowTop + 'px'; |
| | | } |
| | | if (windowBottom && !pagingStyle.bottom) { |
| | | pagingStyle.bottom = windowBottom + 'px'; |
| | | } |
| | | } |
| | | if (this.bgColor.length && !pagingStyle['background']) { |
| | | pagingStyle['background'] = this.bgColor; |
| | | } |
| | | if (this.height.length && !pagingStyle['height']) { |
| | | pagingStyle['height'] = this.height; |
| | | } |
| | | if (this.width.length && !pagingStyle['width']) { |
| | | pagingStyle['width'] = this.width; |
| | | } |
| | | return pagingStyle; |
| | | }, |
| | | finalLowerThreshold() { |
| | | return u.convertToPx(this.lowerThreshold); |
| | | }, |
| | | finalPagingContentStyle() { |
| | | if (this.contentZIndex != 1) { |
| | | this.pagingContentStyle['z-index'] = this.contentZIndex; |
| | | this.pagingContentStyle['position'] = 'relative'; |
| | | } |
| | | return this.pagingContentStyle; |
| | | }, |
| | | renderJsIgnore() { |
| | | if ((this.usePageScroll && this.useChatRecordMode) || !this.refresherEnabled || !this.useCustomRefresher) { |
| | | this.$nextTick(() => { |
| | | this.renderPropScrollTop = 10; |
| | | }) |
| | | } |
| | | return 0; |
| | | }, |
| | | windowHeight() { |
| | | if (!this.systemInfo) return 0; |
| | | return this.systemInfo.windowHeight || 0; |
| | | }, |
| | | windowBottom() { |
| | | if (!this.systemInfo) return 0; |
| | | let windowBottom = this.systemInfo.windowBottom || 0; |
| | | if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder) { |
| | | windowBottom += this.safeAreaBottom; |
| | | } |
| | | return windowBottom; |
| | | }, |
| | | isIosAndH5() { |
| | | // #ifndef H5 |
| | | return false; |
| | | // #endif |
| | | return this.isIos; |
| | | } |
| | | }, |
| | | methods: { |
| | | //当前版本号 |
| | | getVersion() { |
| | | return `z-paging v${c.version}`; |
| | | }, |
| | | //设置nvue List的specialEffects |
| | | setSpecialEffects(args) { |
| | | this.setListSpecialEffects(args); |
| | | }, |
| | | //与setSpecialEffects等效,兼容旧版本 |
| | | setListSpecialEffects(args) { |
| | | this.nFixFreezing = args && Object.keys(args).length; |
| | | if (this.isIos) { |
| | | this.privateRefresherEnabled = 0; |
| | | } |
| | | !this.usePageScroll && this.$refs['zp-n-list'].setSpecialEffects(args); |
| | | }, |
| | | // #ifdef APP-VUE |
| | | //当app长时间进入后台后进入前台,因系统内存管理导致app重新加载时,进行一些适配处理 |
| | | _handlePageLaunch() { |
| | | // 首次触发不进行处理,只有进入后台后打开app重新加载时才处理 |
| | | if (this.pageLaunched) { |
| | | // 解决在vue3+ios中,app ReLaunch时顶部下拉刷新展示位置向下偏移的问题 |
| | | // #ifdef VUE3 |
| | | this.refresherThresholdUpdateTag = 1; |
| | | this.$nextTick(() => { |
| | | this.refresherThresholdUpdateTag = 0; |
| | | }) |
| | | // #endif |
| | | // 解决使用虚拟列表时,app ReLaunch时白屏问题 |
| | | this._checkVirtualListScroll(); |
| | | } |
| | | this.pageLaunched = true; |
| | | }, |
| | | // #endif |
| | | //使手机发生较短时间的振动(15ms) |
| | | _doVibrateShort() { |
| | | // #ifndef H5 |
| | | |
| | | // #ifdef APP-PLUS |
| | | if (this.isIos) { |
| | | const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator'); |
| | | const feedbackGenerator = new UISelectionFeedbackGenerator(); |
| | | feedbackGenerator.init(); |
| | | setTimeout(() => { |
| | | feedbackGenerator.selectionChanged(); |
| | | }, 0) |
| | | } else { |
| | | plus.device.vibrate(15); |
| | | } |
| | | // #endif |
| | | // #ifndef APP-PLUS |
| | | uni.vibrateShort(); |
| | | // #endif |
| | | |
| | | // #endif |
| | | }, |
| | | //设置z-paging高度 |
| | | async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) { |
| | | let heightKey = 'min-height'; |
| | | // #ifndef APP-NVUE |
| | | heightKey = 'min-height'; |
| | | // #endif |
| | | try { |
| | | if (shouldFullHeight) { |
| | | let finalScrollViewNode = scrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); |
| | | let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom'); |
| | | if (finalScrollViewNode) { |
| | | const scrollViewTop = finalScrollViewNode[0].top; |
| | | let scrollViewHeight = this.windowHeight - scrollViewTop; |
| | | scrollViewHeight -= finalScrollBottomNode ? finalScrollBottomNode[0].height : 0; |
| | | const additionHeight = u.convertToPx(this.autoHeightAddition); |
| | | const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px !important'; |
| | | this.$set(this.scrollViewStyle, heightKey, finalHeight); |
| | | this.$set(this.scrollViewInStyle, heightKey, finalHeight); |
| | | } |
| | | } else { |
| | | this.$delete(this.scrollViewStyle, heightKey); |
| | | this.$delete(this.scrollViewInStyle, heightKey); |
| | | } |
| | | } catch (e) {} |
| | | }, |
| | | //触发更新是否超出页面状态 |
| | | _updateInsideOfPaging() { |
| | | this.insideMore && this.insideOfPaging === true && setTimeout(this.doLoadMore, 200) |
| | | }, |
| | | //清除timeout |
| | | _cleanTimeout(timeout) { |
| | | if (timeout) { |
| | | clearTimeout(timeout); |
| | | timeout = null; |
| | | } |
| | | return timeout; |
| | | }, |
| | | //添加全局emit监听 |
| | | _onEmit() { |
| | | uni.$on(c.errorUpdateKey, () => { |
| | | this.loading && this.complete(false).catch(() => {}); |
| | | }) |
| | | uni.$on(c.completeUpdateKey, (data) => { |
| | | setTimeout(() => { |
| | | if (this.loading) { |
| | | if (!this.disabledCompleteEmit) { |
| | | const type = data.type || 'normal'; |
| | | const list = data.list || data; |
| | | const rule = data.rule; |
| | | this.fromCompleteEmit = true; |
| | | switch (type){ |
| | | case 'normal': |
| | | this.complete(list); |
| | | break; |
| | | case 'total': |
| | | this.completeByTotal(list, rule); |
| | | break; |
| | | case 'nomore': |
| | | this.completeByNoMore(list, rule); |
| | | break; |
| | | case 'key': |
| | | this.completeByKey(list, rule); |
| | | break; |
| | | default: |
| | | break; |
| | | } |
| | | } else { |
| | | this.disabledCompleteEmit = false; |
| | | } |
| | | } |
| | | }, 1); |
| | | }) |
| | | }, |
| | | //销毁全局emit和listener监听 |
| | | _offEmit(){ |
| | | uni.$off(c.errorUpdateKey); |
| | | uni.$off(c.completeUpdateKey); |
| | | } |
| | | }, |
| | | }; |
| New file |
| | |
| | | // [z-paging]使用页面滚动时引入此mixin,用于监听和处理onPullDownRefresh等页面生命周期方法 |
| | | |
| | | export default { |
| | | onPullDownRefresh() { |
| | | if (this.isPagingRefNotFound()) return; |
| | | this.$refs.paging.reload().catch(() => {}); |
| | | }, |
| | | onPageScroll(e) { |
| | | if (this.isPagingRefNotFound()) return; |
| | | this.$refs.paging.updatePageScrollTop(e.scrollTop); |
| | | e.scrollTop < 10 && this.$refs.paging.doChatRecordLoadMore(); |
| | | }, |
| | | onReachBottom() { |
| | | if (this.isPagingRefNotFound()) return; |
| | | this.$refs.paging.pageReachBottom(); |
| | | }, |
| | | methods: { |
| | | isPagingRefNotFound() { |
| | | return !this.$refs.paging; |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | // [z-paging]公用的静态图片资源 |
| | | |
| | | export default { |
| | | base64Arrow: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAD1BMVEVHcExRUVFMTExRUVFRUVE9CdWsAAAABHRSTlMAjjrY9ZnUjwAAAQFJREFUWMPt2MsNgzAMgGEEE1B1gKJmAIRYoCH7z9RCXrabh33iYktcIv35EEg5ZBh07pvxJU6MFSPOSRnjnBUjUsaciRUjMsb4xIoRCWNiYsUInzE5sWKEyxiYWDbyefqHx1zIeiYTk7mQYziTYecxHvEJjwmIT3hMQELCYSISEg4TkZj0mYTEpM8kJCU9JiMp6TEZyUmbAUhO2gxAQNJiIAKSFgMRmNQZhMCkziAEJTUGIyipMRjBSZkhCE7KDEFIUmTeGCHJxWz0zXaE0GTCG8ZFtEaS347r/1fe11YyHYVfubxayfjoHmc0YYwmmmiiiSaaaKLJ7ckyz5ve+dw3Xw2emdwm9xSbAAAAAElFTkSuQmCC', |
| | | base64ArrowWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVHcEz///////////////////+IGTx/AAAABnRSTlMA/dAkXZOhASU/AAABYElEQVRYw+2YwXLCIBCGsdAHWGbyAKZ4zxi9O017rxLf/1UaWFAgA1m8dcpedNSPf/l/Vh0Ya/Wn6hN0JcGvoCqRM4C8VBFiDwBqqNuJKV0rAnCgy3AUqZE57x0iqTL8Br4U3WBf/YWaIlTKfAcELU/h9w72CSVPa3C3OCDvhpHbRp/s2vq4fHhCeiCl2A3m4Qd71DQR257mFBlMcTlbFnFWzNtHxewYEfSiaLS4el8d8nyhmKJd1CF4eOS0keLMAuSxubLBIeIGQW8YHCFFo7EH9+YDcQt9FMZEswTheaNxTHwHT8SZorJjMrEVwo4Zo0U8HSEyZvJMOg4RjnmmRr8nDYeIz3OMkbfE/QhBo+U9RnZJxjGCRh/WKmHEMWLNkfPKsGh/CWJk1JjG0kcuJggTt34VDP8aWAFhp4nybVb5+9qQhjSkIQ1pSEMa8k+Q5U9rV3dF8MpFBK+/7miVq1/HZ2qmo9D+pAAAAABJRU5ErkJggg==', |
| | | base64Flower: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC', |
| | | base64FlowerWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEX///9HcEz///////////////84chYNAAAABnRSTlP/AGzCOYZj5g1nAAACfklEQVRYw+2YTVPDIBCGtza9Jw25a0bvcax30o73OOr//yvma2F3YWlpPTijXNpAHrK8LLALVPFium2vNIFSbwGKTGQA2GUiHcD29yDNy3sMIdUBQl7r2H8mOEVqAHgPkYZUS6Qc2zYhQqtjyDZEximCZwWZLIBeIgYShs2NzxKpSUehYpMJhURGb+O+w5BpMCAREKPnCDHbIY20SzhM5yxziAXpOiBXydrekT9i5XDEq4NIIHHgyU5mRGqviII4mREJJA4QJzMiILwlRJzpKxJKvCBm8OsBBbLux0tsPl4RKYm5aPu6jw1U4mGxEUR9g8M1PcqBEp/WJliNgYOXueBzS4jZSIcgY5lCtevgDSgyzE+rAfuOTQMq0yzvoGH18qju27Mayzs4fPyMziCx81NJa5RNfW7vPYK9KOfDiVkBxFHG8hAj9txuoBuSWORsFfkpBf7xKFLSeaOefEojh5jz22DJEqMP8fUyaKdQx+RnG+yXMpe8Aars8ueR1pVH/bW3FyyvPRw90upLDHwpgBDtg4aUBNkxRLXMAi03IhcZtr1m+FeI/O/JNyDmmL1djLOauSlNflBpW18RQ2bPqXI22MXXEk75KRHTnkPkYbESbdKP2ZFk0r5sIwffAjy1lx+vx7NLjB6/E7Jfv5ERKhzpN0w8IDE8IGFDv5dhz10s7GFiXRZcUeLCEG5P5nDq9k4PFDcoMpE3GY4OuxuCXhmuyNB6k0RsLIAvqp9NE5r8ZCSS8gxnUp7ODdYhZTqxuiJ9uyJJtPmpqJ7wVj+XVieS903iViHziqAhchLEJAyb7jWU647EpUofQ0ziUuXXXhDddtlllSwjgSQu7r4BRWhQqfDPMVwAAAAASUVORK5CYII=', |
| | | base64Success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVRUVFHcExTU1NRUVFRUVFRUVFOSlSUAAAABnRSTlP/AI6+VySB3ZENAAACcElEQVRYw+2YyYKCMAyGI8hdpdxdZu7gcpdZ7jL6/s8yYheSNi0aPdqbwOffpGmaFOYPD3gj4bisN7vddv17N/JVgxn5x12IWgIaWTuO/IE3PseQbwjGPo2cgRmHFLJwdm/X643zwiqOKPPJ1nj3sjEP2iiifZWj5bhopSyGaEO2HX5fbQJzwJ+W7x/jw5ZFjsEU0PMph9xE8i5EqprKALW95eJQURkgzw98uJ/JvwGecR7bIjWWsUgVrrIfFZ2HlLy3sKETD1mmRLRMRhGVssRa0xJkdn3SpJBymBkM8+pSSDXMDNyDaToVHd2fgpNt0sjwiUZO19+jGQ+gQEg9Oq+bufmAVGihomNmjQG7UG3020vrlm7lkFnKFGU3kZ0KGAdmKe821pipQ+qEKcrZeTL2g5FsUks4cStjEZWwXg0b0n4GxmEpkWwIs5VBynjgK7xZaz1/0D7OxkVuLpsY5BQNFyLS84VBjjbg0iL2r2EQHBOxBhikuUOkdxODVF1cxHoWtPPsiyXO455Iv34hssCO8EV4ZIYTjS8SR4qYSHRiTiYQ4ZFbHi0iIhhBTi6dTCgSWRcnw4h4yGTuyTAiOGBIWGoZTgSHJQl+LcOJ4OCnW6yX2bMnJ9pidCOXtkTkTrIGpYuOynAiOF14SamMiOCk5Ke+mq8BcOrrvym8d0zKIQnWT+M1WwOQNO4fFiWb18hhERxJPx2fblbPHHyC41VyiAtKBUFBIih7JMWVoIQTFIr3lKPN80WvoLSWFPC653ioTZA0I0FrQ7qU6asaK0H7JmkSJa2ooOGVtNUsc3j9FYHkIkJy3SG6VHnfXKXGP9t4N9Q4Ye98AAAAAElFTkSuQmCC', |
| | | base64SuccessWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAGFBMVEVHcEz///////////////////////////8dS1W+AAAAB3RSTlMAiVYk6KvDHLfaegAAAo1JREFUWMPtWEtzmzAQNhCTq910ytXpiyvxTNOr60zrayepx9d02gnX4sTm7xcEiJX2gdnkGJ1A4tOnfWqXyeR1vMRYzrcPD9v5h5MBl3/Ldvx4cxIg/FWC8X0xjLjalM54uhhCfCrRuJURX0pi3EmIqZV7O59vrRZmguStHL9b7S7ftfLwOtiZDw7AHMtmquAQ12b5Wwbnordm8g9zLLO49qc/m2n6aKnhwPOGZ08hAiNHhheiHae1lOUPGZpQkPKa3q0mOUjaRzSRaGUjpy/mmWSwySSpllcEteBKAT52KEnSbblA51pJEPxBQoiH1FP4E3s5+FJv07h6/ylD6ui7B+9fq/ehrFB98ghec9EoVtyjK8pqCHLmCBOwMWSCeWFNN4MbPAk55NhsvoFHSSVR0k5TCTTEzlUGcqV/nVp7n9oIVkmtaqbAEqEgfdgHJPwsEAyZ9r4VAZXFjpEwyaw3+H2v42KYxKhs1XvY/gSSGv+IHyUSuHXCeZhLAgVI3EjgSGo1Fb3xO0tGGU9S2/KAIbtjxpJASG73qox6w5LUq0cEOa+iIONIWIilQSQ0pPa2jgaRQAgQP7c0mITRWGxpMAmEQFN2NAQJNCV0mI6GIIEO47hlQ0ORQLd0nL+hoUjg1m6I1TRr8uYEAriBHLcVFQ5UEMiBe3XkTBEG04WXlGKGxPnMS305XQPA1Ocn2JiuAZwE66fxnKwBnDTuXxZTMq85lwW6kt5ndLqZPefiU1yvmktcUSooChJF2aMprhQlnKJQ5FxRKkcVRa+itNYU8Io2oVkY14w0NMWYlqft91Bj9VHq+ca3b43BxjWJmla0sfKohlfTVpPN+93L/yLQ/IjQ/O5Q/VR5HdL4D7mlxmjwVdELAAAAAElFTkSuQmCC', |
| | | base64Empty: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAbUExURdvc3EdwTMLBwcjIyLSzs/Hx8ff39////19dXXz7IJEAAAAFdFJOU/4A6J9QDyyutAAAB5VJREFUeNrtnM1z4jYYxhUHkR4hdu9eU7Z75Ct7jgPbs9ZZmSuTrUWPmXTA186e+LMrf0uWLMtf2WkHXQgzln88et5XeiVMwPQdGrhCrpAr5Aq5Qv4TkJ07OGQFMLp1B4VYCz+kDblDQhJGeH4eEDLBYdLOHwaDWNBPIeHLYJAJ3meQ83IoCMTHDBKOBoKYGOeM8G0gyD0LObnDQB5ZSCtTNCBfsM9AboaBPLCQcDAIM1zht/dQEkMsd1DjI4hpw2YzMtBJeBbydWpCTJs3YDKGX62YgfGoVwi9KwtZJAzcYHHRm7sYCKD390nQSIoO5JGZIEOYxNoZ4+deISYLyeL5hLHbJ2QK98W0kudMgJe9Qh73odhO+KZHyNYGvgQS9gmJKhUigwSj3iBPUhXxePWmxBqHw0Mej9WQ3qILVjLC177yxNxXQ/7uK+Mn1aNVLsGsBTaWrSAPobYl0aUHt2fIs2Rgz7c9QYL0pSTkSzILLFtAJMH1cidN998T9E0/Sg73/pEEwrgkYRh86wlC949gJsR6EobBcz8hHOVgKYi2m6kZtodIkjEQvF3QjbGpmplB4/lRgJhxgRS2N15iijAvPmByDtCxfQhPJ8J4CR82rgCCBILarScw6X0OcMUyYrFVmbxErl0ZacFIoloOLdJAO42qY+NMDss2kKS8xmiZxcCpFKXWvpRGbQqJp5ixyRfJMmR6x0Fk+z29kmgWDYI5ziFbdug/84HxvduhWhLOJ2StPDQrMJPSjNANklh8QhB7dBO0yTGRwn1fkOk8rbQjiB8Ymww+JuiuN0icmSccK4naLMWYa/euL0+m23GyM8kgAc6sYeL4z04Qa4WjGepcKIliO8EUGSk7d9OGWOsoK31OSdy8TQZ59Y/hWbaV1IVs5/Ed6UzGK4nANAJiyGhRsZPUg2yzLe9hLyiJIyCaDU7udC2uy9pnkKvidlBUEltzFAqxRhBrBZm7HfZnjEQI3boqTsJq15PUDEaKZLgiJYc8OZtCtnM/4G93OFYooXpvdy0guwWWNQkEHl/j7Jw1XRmtlS9HYJkSPjk1IUnyyRqUKQn45NSDlP1mcg9i6En1ZU2IADnEtHF1Q+JwIcS/d5YakPuDUamEShGUHHikAz9oQCaE0CsrpYjDBVkEHQYdyK+EkKPhVErxqh1xbJ/oQf4gEeVsOIEc41WJNAwcd9GBfCZJezXsJhAvH+ImEEIOzlwXgpw5wQ0gH3MIOcsiQAahZuSD69/UQyxcQEggiQARQseVFO/ASAMCgM9gjkHZmhLENzi1AOhA7ullkMWUrfHKfpMiDBHtDIx6yCS6jseEnDUe7zcT6DGCtnrIY3olZw1hrPHkfucIAJa1EDu/lsVEyVmGGA67coKijeogFnMxlEaAV5ghRdDm1kDuuatZTJBGgJdOthIzsvZbDWRRuh6ScgR4EQLgagQvRQIxxQ4sxqcR4GE+c4CkjZQQW9YF89Y4OFAjOCki5KmiDxsBL3PlSJWlAFVogaoIePlYi2ClCJAHRa/cmre5eqTii4uvisqQJxqnip6pNd68DhEvyEs5xIyHBNdh4thCKhU++10kD7Gy1Up1A/o56FKuRJQWSFCuf8dpbisxhqHSKlSSgvG7VTaFKO5TzYD5VMPUxEB2YJNiqq3xYJ0KrroH8mq7xpoXqEZgfgNRUQsDtTVvUOk3sLUKbqrBr7YGvkCkQNC/9SA+vTYtvERrxiKEmcogk4ZqCLUd59MIEiFYHlIoxelCaJWDMmtOPIa80XVLbkb6hzaEwwTcPEmV4AIRlBGNIEmuJBFwLAZoHClJ36J8h+wxihpCqJosAnJrSKwEcQOFAFeWN4RQMYc0Ao4Jhg5gpASzyWcDvjpuDIlTkrGGJEro1rHIjHKR3wJCAj+z5oyi11gJBkXy9QFJIiAu78d+pgSjuWhGN0gUAZAcEncSJf4LRrZ8I94WEmcNCJJqBWYjVbE9bg2JxiyrViBWty6QvO56D8jPVWLA4ZX8dfkxvJJPl8t8aCX+pU/Iz1SCf7lc4OBK0OWfQaKLP0TKjj96VvIp+/BDZjwNKF2ItV2vN7sWStAl87oWkm3dZ+k3lEMoYXe8cT1eq2TOePJDD8KfQdxu6iEPxanUZa4HmZRq3dunGsj3BzFq6yD3wnZNX4n2emI2hXyXQpi6RRZdfSgxHNuxVZBFdyVeBPDmCsiksxKUiDAUEKuzkvRUEs0V08pjVyU2/yqFmF2VZGYop3peitdUiQd1pnrL7qTE01tPzE6eaEKm23dQwh2jNlbiay+/245zl94abw45CzNPyqYQ2++kxHGV1crWzg4A2yvR+BY7wziwnRLN7+O36aA54+ZKGjxZYK3txJpxQyUNn5GwtquII4+ACiWtnvawduu1A3SVtH5uhTvAVSpBG7fDYz6RQ+M6JWjmKm6g+RvTla9UMtspu+s+37VbVCupNqPx43CsNawSb1PbtcmDfQWmUILW7rRXSPHtSq5k5ur0a/hb7DQCUiW3G71ejX/wvV1kSoyNbp8Wvyqn1lCIKvl6gNDkNBYzt0GHdr+Pt9xGl1//ncAVcoVcIVfIFXKFXCFXyP8I8i8SyTW4yTz2lwAAAABJRU5ErkJggg==', |
| | | base64Error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAJFBMVEVHcEzo6Oibm5ukpKSbm5uampqbm5ubm5u5ubnn5+fm5ub6+vpGpDPdAAAAC3RSTlMA/v4hb+u20dq8aQhnHL4AAATwSURBVHja7ZvNb+JGGMbdjjdVe3NPodzeMhj1ZMUGujkh28B9wKR7iwwBqafWSbPqsbm0uTWtVlrTS9v0Et9yqLQS/1zHNiTZMMB4bO92d+dRUITt8c/PvPPx2h4URUpKSkpKSkpK6m3K6lFN73Q+S/+es3W3fzabTh2Dl0FAXEM+BgrgeXxRsdILTDU9n7J0vz/+EsCYC9KEiSFc06pf5zouqAkjkGWo0OG5Fq6j2IwZ6I4/4DhyTxc20oUjAj5PRTTrwvGAZ9p+ADzlD4RDctCoLhb7JUP87xeLxS3BZUIQ+YNCFi8wRwc4GIqGRK/GkM+5ILV8kFflOmkkkH/LddJ4c05eSSfSyTvrZPEmnCw+UCeuk84QvTSnUF0uCL68fBle/swF+RL1QZ/EpU6gHtOsAM64pnjPwxgTLsjoBM58ODNaAXYBT5QeGdr0KwcEA8He0TkPBLCjUDNHpG4qlg8eTCzXht1FVd1MxTPHj5LTtUiSrKK+7iDf8wBGxk4If3arLv/HF4Tox0A2nlFIGp+CIA+LzSamgbp4TNvZjECtDAjyMcB5HybLO6NxsRA1vmFCNjguNXDGWygbRPXh/B+zn9zPWK5RCkT18QxA57YgAqE+HGS6/tAoD4JO0ts+M2tbyQJpc95a5oI0xXNhCZGQ/x8E0VSCkUZY6Z6CIE/qdO5eL+yPlW6tMMgefPs3o7Bdt8iguJj4DThlZSY/rJ0yB+RraLDK2jAQCHyr4zIhT9mQ7vowygMZjpgQG+CYUV2E1EWqixyzICr8eFFnBB5ba1Y4IIfeKQvysW7ssZrwQLEFmvCBM2U6oRkdI5Wgm1QnO8RUTDlASoiESIiESMh7BDHKhlhu/LbSNUuEqL3lu1p945vO3BArADiaum7vBGDUKQfSIjBykooyrRPQnTIglHFfSagHeqd4iErwa9duEebpckGQjx/VT4v5fC0XhJHAd1mPRvJAVMZiAeQzTpgHYrPKqkGnUEiX+dCoNS4UgniHMTnUS4iESIiE7IS0x+mnVAidglDwVcmQJpy2WQ8VC4UgogfbA1RE4Nuw3UghEBV2rKl7V5ygAJPSY9KGQbP01mVjA5Fa2f1kQN2U3k+M9POWB8gnJUNMZJioWTMzKwOklyxgDrCXVcMMEF90tXM9C2TiCqmfCdIRi/jeewNpyerKok9WkGuzfCdYC+fXRsmBxxpVGG2zY0ZBbieJKvPrDQce3lxppBhIjGFWGkVoxUEoZt0Mukn2XBQH0bTHZpaMIp2sU/6qasU70W6/eHjM09VmYSc6C6Jpvz+orKvVxot8kL3HkMr9IZ9qeZ2o6RrO9mOI9ufdIR9peZ2gNIW31yC/MpyI9ngUDNIsezPks3vIsWDGdYA7cZa9pbqUVeCr/neiaR3U3R4BfXPg75vwb8I/b7HjxChobDZCO+Ny4wuxxaVxPPowcoNnrzPmzGFlX3RJHz2FafbhJ41n8PLx2DCM7KkwQgpqka1DVzKdJNHfJwBe9l/n0eSZFsIPjVSY8xZKZpSXnogwled98wAx3xRcdBNq1f1fhFVdIcL5tvaDolC7XaqaWStEtLOJHkbhlSauMLrma4yHEa03AVUoIUs/M2NQFkchBZiGUPeKonAnqhLOo4hrKf0WTyZ1FcU0Ki0hVrSr+Mucnvya7jYUKSkpKSkpKSmpD0f/AXq+Umj5XnXDAAAAAElFTkSuQmCC', |
| | | base64BackToTop: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAElBMVEVRUVH+/v5HcEyZmZlRUVFRUVGm1ByOAAAABnRSTlPMzADMTZAJBBGsAAAEnElEQVR42t2cS27jMAyGf7/2U+QCQeDsbeQCgZDujaC5/1UmkzaJn+JDFGcw3LdfflKibJkkDnxrL7dbg7sNt6+L4O8OYBM+B0ys+QrGkHZG+OEEQ8g6go8Bx1GIGMdpNOQyIG6XdMgnSPtKhLQDGEZFBgYMkhKFtGBb0EIEjDgFRowoBVaMGAWpMedEfxMiZtwpUsgZCqtlkCNUdpVAWigtCCCDFtLwIWeoreZCWiRYYEKGFEjDg+yRZCUH0iLRAgNyToXUNCRZyMqWhGnUN2IPm3wSlwJ7IUspyCBkIQUZhCykIIeQuRTkEDKXAuM9srrtYbrZN7Y98giZSoFd+t1OxmMITG0dcrSFXFchZ1tIvQZpYWxhBbK3hpQrkMEa0iwh5t4a+QvZvDXyF7J5a+Qv5PPW21/I5623v5DPW29/IaO3Xv5Clrw1y1/Ikrdm+Qs5svw83yNnSJ5BQb4F/F7EIEJSnThGBAXxkFQfLOviQUE8JAUPsosHBfGQfDAtHhREQ1JxIV00KIgmrnRI84S0yAd5BAXxxJUck0f6Qnwr9qmr6xF5xLMjcwn/iudIEAdWnyjkEXlQKZiRVzoqRyLbgeUKKR8Q4alY7cSnoxzSf2ggsqehKr6YVpcXpOd7H93f60cKhOd7Re2LteUF4eLqiVS1mr0ge4io6C2+soaFkJ7MuuuQs1yITEp9hwwKISIpzR2iESKSIoT0rLNwuVHQqoSIpAQJpGce60vIUSdEIuUqgPTsJ5QFZK8UIpBS8iG94GFrDjlrhfCl8CG96Llxmle4kEr6vKWBPIVo9kqDQSRk9/3cWoikcCFPAd33v4dIChPyEvLzBA6RlEYWke4JEUnhKXkLeUEKxRHJFfKCQHGucIW8IdZSRkLeEGMpYyEjiK2UsZARxFTKRMgYYillImQMMZQyFTKB2EmZCplAuFLIHT8TMoWwpQwiIVMIUwqpZP5bp5CCvCTiQKr5f5lCQN+tPCBn2ZvVDFJwIDUP0m1BYAfZYRNSsCB7BqTbhoARePIxtZ9tgwWkoJcwCalmv3MBAemtO4R6dah2HaKQqj8Zvp9sQDjvJ21+SPCBHPJDDk6QITekEV7gqCC19CpKAym9IMfckKv4olMBCeIrWwVEfvkshzQekO9r9P1/ALk+IG1eSPCDiCJfyG+FyU+A6ZCa/piZDinpz7LpkCv5gdkAEshP5emQhv7onw6pGeULyZCSUYiRDAmMkpJkCKs4JhFSq8p8hJBSVbAkhARV6ZUQoisik0FqXTmcDHLVFfbJIEFXoiiCNMpiSxGkVJaNiiBBWQArgTTaUl4JpNQWJUsgQVteXQg+AKkLxQWFGKW+5J2+eVp4S168X3CF1CltCKdTJ8lb84YK2bUBO+wZW0Pqv9nk4tKu49N45NJC5dMM5tLW5tOg59Jq6NM06dL+abFXwr/RkuvTXJwae1abtE/Dt0/ruksTvs84AZ/BCC4jHnyGVfiM3VBQFANEXEah+Ax18RlP4zNox2dkkM/wI58xTn8yDCXGYCDV3W5RGSajtXyGhG1jbpbjzpwGt/0MJft8jqC7iUbQ/QZaxdnKqcIftwAAAABJRU5ErkJggg==', |
| | | } |
| New file |
| | |
| | | // [z-paging]工具类 |
| | | |
| | | import zConfig from './z-paging-config' |
| | | import zLocalConfig from '../config/index' |
| | | import c from './z-paging-constant' |
| | | |
| | | const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY'; |
| | | let config = null; |
| | | const timeoutMap = {}; |
| | | |
| | | /* |
| | | 当z-paging未使用uni_modules管理时,控制台会有警告:WARNING: Module not found: Error: Can't resolve '@/uni_modules/z-paging'... |
| | | 此时注释下方try中的代码即可 |
| | | */ |
| | | // #ifdef VUE2 |
| | | try { |
| | | const contextKeys = require.context('@/uni_modules/z-paging', false, /\z-paging-config$/).keys(); |
| | | if (contextKeys.length) { |
| | | const suffix = '.js'; |
| | | config = require('@/uni_modules/z-paging/z-paging-config' + suffix); |
| | | } |
| | | } catch (e) {} |
| | | // #endif |
| | | |
| | | //获取默认配置信息 |
| | | function gc(key, defaultValue) { |
| | | if (!config) { |
| | | if (zLocalConfig && Object.keys(zLocalConfig).length) { |
| | | config = zLocalConfig; |
| | | } else { |
| | | const tempConfig = zConfig.getConfig(); |
| | | if (zConfig && tempConfig) { |
| | | config = tempConfig; |
| | | } |
| | | } |
| | | } |
| | | if (!config) return defaultValue; |
| | | const value = config[_toKebab(key)]; |
| | | return value === undefined ? defaultValue : value; |
| | | } |
| | | |
| | | |
| | | //获取最终的touch位置 |
| | | function getTouch(e) { |
| | | let touch = null; |
| | | if (e.touches && e.touches.length) { |
| | | touch = e.touches[0]; |
| | | } else if (e.changedTouches && e.changedTouches.length) { |
| | | touch = e.changedTouches[0]; |
| | | } else if (e.datail && e.datail != {}) { |
| | | touch = e.datail; |
| | | } else { |
| | | return { |
| | | touchX: 0, |
| | | touchY: 0 |
| | | } |
| | | } |
| | | return { |
| | | touchX: touch.clientX, |
| | | touchY: touch.clientY |
| | | }; |
| | | } |
| | | |
| | | //判断当前手势是否在z-paging内触发 |
| | | function getTouchFromZPaging(target) { |
| | | if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') { |
| | | const classList = target.classList; |
| | | if (classList && classList.contains('z-paging-content')) { |
| | | return { |
| | | isFromZp: true, |
| | | isPageScroll: classList.contains('z-paging-content-page'), |
| | | isReachedTop: classList.contains('z-paging-reached-top') |
| | | }; |
| | | } else { |
| | | return getTouchFromZPaging(target.parentNode); |
| | | } |
| | | } else { |
| | | return { isFromZp: false }; |
| | | } |
| | | } |
| | | |
| | | //获取z-paging所在的parent |
| | | function getParent(parent) { |
| | | if (!parent) return null; |
| | | if (parent.$refs.paging) return parent; |
| | | return getParent(parent.$parent); |
| | | } |
| | | |
| | | //打印错误信息 |
| | | function consoleErr(err) { |
| | | console.error(`[z-paging]${err}`); |
| | | } |
| | | |
| | | //延时操作,如果key存在,调用时根据key停止之前的延时操作 |
| | | function delay(callback, ms = c.delayTime, key) { |
| | | const timeout = setTimeout(callback, ms);; |
| | | if (!!key) { |
| | | timeoutMap[key] && clearTimeout(timeoutMap[key]); |
| | | timeoutMap[key] = timeout; |
| | | } |
| | | return timeout; |
| | | } |
| | | |
| | | //设置下拉刷新时间 |
| | | function setRefesrherTime(time, key) { |
| | | const datas = getRefesrherTime() || {}; |
| | | datas[key] = time; |
| | | uni.setStorageSync(storageKey, datas); |
| | | } |
| | | |
| | | //获取下拉刷新时间 |
| | | function getRefesrherTime() { |
| | | return uni.getStorageSync(storageKey); |
| | | } |
| | | |
| | | //通过下拉刷新标识key获取下拉刷新时间 |
| | | function getRefesrherTimeByKey(key) { |
| | | const datas = getRefesrherTime(); |
| | | return datas && datas[key] ? datas[key] : null; |
| | | } |
| | | |
| | | //通过下拉刷新标识key获取下拉刷新时间(格式化之后) |
| | | function getRefesrherFormatTimeByKey(key, textMap) { |
| | | const time = getRefesrherTimeByKey(key); |
| | | const timeText = time ? _timeFormat(time, textMap) : textMap.none; |
| | | return `${textMap.title}${timeText}`; |
| | | } |
| | | |
| | | //将文本的px或者rpx转为px的值 |
| | | function convertToPx(text) { |
| | | const dataType = Object.prototype.toString.call(text); |
| | | if (dataType === '[object Number]') return text; |
| | | let isRpx = false; |
| | | if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) { |
| | | text = text.replace('rpx', '').replace('upx', ''); |
| | | isRpx = true; |
| | | } else if (text.indexOf('px') !== -1) { |
| | | text = text.replace('px', ''); |
| | | } |
| | | if (!isNaN(text)) { |
| | | if (isRpx) return Number(uni.upx2px(text)); |
| | | return Number(text); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | //获取当前时间 |
| | | function getTime() { |
| | | return (new Date()).getTime(); |
| | | } |
| | | |
| | | //获取z-paging实例id |
| | | function getInstanceId() { |
| | | const s = []; |
| | | const hexDigits = "0123456789abcdef"; |
| | | for (let i = 0; i < 10; i++) { |
| | | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); |
| | | } |
| | | return s.join('') + getTime(); |
| | | } |
| | | |
| | | // 等待一段时间 |
| | | function wait(ms) { |
| | | return new Promise(resolve => { |
| | | setTimeout(resolve, ms); |
| | | }); |
| | | } |
| | | |
| | | //------------------ 私有方法 ------------------------ |
| | | //时间格式化 |
| | | function _timeFormat(time, textMap) { |
| | | const date = new Date(time); |
| | | const currentDate = new Date(); |
| | | const dateDay = new Date(time).setHours(0, 0, 0, 0); |
| | | const currentDateDay = new Date().setHours(0, 0, 0, 0); |
| | | const disTime = dateDay - currentDateDay; |
| | | let dayStr = ''; |
| | | const timeStr = _dateTimeFormat(date); |
| | | if (disTime === 0) { |
| | | dayStr = textMap.today; |
| | | } else if (disTime === -86400000) { |
| | | dayStr = textMap.yesterday; |
| | | } else { |
| | | dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear()); |
| | | } |
| | | return `${dayStr} ${timeStr}`; |
| | | } |
| | | |
| | | //date格式化为年月日 |
| | | function _dateDayFormat(date, showYear = true) { |
| | | const year = date.getFullYear(); |
| | | const month = date.getMonth() + 1; |
| | | const day = date.getDate(); |
| | | return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`; |
| | | } |
| | | |
| | | //data格式化为时分 |
| | | function _dateTimeFormat(date) { |
| | | const hour = date.getHours(); |
| | | const minute = date.getMinutes(); |
| | | return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`; |
| | | } |
| | | |
| | | //不满2位在前面填充0 |
| | | function _fullZeroToTwo(str) { |
| | | str = str.toString(); |
| | | return str.length === 1 ? '0' + str : str; |
| | | } |
| | | |
| | | //驼峰转短横线 |
| | | function _toKebab(value) { |
| | | return value.replace(/([A-Z])/g, "-$1").toLowerCase(); |
| | | } |
| | | |
| | | export default { |
| | | gc, |
| | | setRefesrherTime, |
| | | getRefesrherFormatTimeByKey, |
| | | getTouch, |
| | | getTouchFromZPaging, |
| | | getParent, |
| | | convertToPx, |
| | | getTime, |
| | | getInstanceId, |
| | | consoleErr, |
| | | delay, |
| | | wait |
| | | }; |
| New file |
| | |
| | | // [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理 |
| | | |
| | | import u from '../js/z-paging-utils' |
| | | const data = { |
| | | startY: 0, |
| | | isTouchFromZPaging: false, |
| | | isUsePageScroll: false, |
| | | isReachedTop: true, |
| | | isIosAndH5: false, |
| | | appLaunched: false |
| | | } |
| | | |
| | | export default { |
| | | mounted() { |
| | | if (window) { |
| | | this._handleTouch(); |
| | | // #ifdef APP-VUE |
| | | this.$ownerInstance.callMethod('_handlePageLaunch'); |
| | | // #endif |
| | | } |
| | | }, |
| | | methods: { |
| | | //接收逻辑层发送的数据 |
| | | renderPropIsIosAndH5Change(newVal) { |
| | | if (newVal === -1) return; |
| | | data.isIosAndH5 = newVal; |
| | | }, |
| | | //拦截处理touch事件 |
| | | _handleTouch() { |
| | | if (!window.$zPagingRenderJsInited) { |
| | | window.$zPagingRenderJsInited = true; |
| | | window.addEventListener('touchstart', this._handleTouchstart, { passive: true }) |
| | | window.addEventListener('touchmove', this._handleTouchmove, { passive: false }) |
| | | } |
| | | }, |
| | | _handleTouchstart(e) { |
| | | const touch = u.getTouch(e); |
| | | data.startY = touch.touchY; |
| | | const touchResult = u.getTouchFromZPaging(e.target); |
| | | data.isTouchFromZPaging = touchResult.isFromZp; |
| | | data.isUsePageScroll = touchResult.isPageScroll; |
| | | data.isReachedTop = touchResult.isReachedTop; |
| | | }, |
| | | _handleTouchmove(e) { |
| | | const touch = u.getTouch(e); |
| | | const moveY = touch.touchY - data.startY; |
| | | if (data.isTouchFromZPaging && ((data.isReachedTop && moveY > 0) || (data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) { |
| | | if (e.cancelable && !e.defaultPrevented) { |
| | | e.preventDefault(); |
| | | } |
| | | } |
| | | }, |
| | | _removeAllEventListener(){ |
| | | window.removeEventListener('touchstart'); |
| | | window.removeEventListener('touchmove'); |
| | | } |
| | | } |
| | | }; |
| New file |
| | |
| | | // [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新,降低逻辑层与视图层的通信折损,提升性能 |
| | | |
| | | var currentDis = 0; |
| | | var isPCFlag = -1; |
| | | var startY = -1; |
| | | |
| | | function propObserver(newValue, oldValue, ownerIns, ins) { |
| | | var state = ownerIns.getState() || {}; |
| | | state.currentIns = ins; |
| | | var dataset = ins.getDataset(); |
| | | var loading = dataset.loading == true; |
| | | if (newValue && newValue.indexOf('end') != -1) { |
| | | var transition = newValue.split('end')[0]; |
| | | _setTransform('translateY(0px)', ins, false, transition); |
| | | state.moveDis = 0; |
| | | state.oldMoveDis = 0; |
| | | currentDis = 0; |
| | | } else if (newValue && newValue.indexOf('begin') != -1) { |
| | | var refresherThreshold = ins.getDataset().refresherthreshold; |
| | | _setTransformValue(refresherThreshold, ins, state, false); |
| | | } |
| | | } |
| | | |
| | | function touchstart(e, ownerIns) { |
| | | var ins = _getIns(ownerIns); |
| | | var state = {}; |
| | | var dataset = {}; |
| | | ownerIns.callMethod('_handleListTouchstart'); |
| | | if (ins) { |
| | | state = ins.getState(); |
| | | dataset = ins.getDataset(); |
| | | if (_touchDisabled(e, ins, 0)) return; |
| | | } |
| | | var isTouchEnded = state.isTouchEnded; |
| | | state.oldMoveDis = 0; |
| | | var touch = _getTouch(e); |
| | | var loading = _isTrue(dataset.loading); |
| | | state.startY = touch.touchY; |
| | | startY = state.startY; |
| | | state.lastTouch = touch; |
| | | if (!loading && isTouchEnded) { |
| | | state.isTouchmoving = false; |
| | | } |
| | | state.isTouchEnded = false; |
| | | ownerIns.callMethod('_handleRefresherTouchstart', touch); |
| | | } |
| | | |
| | | function touchmove(e, ownerIns) { |
| | | var touch = _getTouch(e); |
| | | var ins = _getIns(ownerIns); |
| | | var dataset = ins.getDataset(); |
| | | var refresherThreshold = dataset.refresherthreshold; |
| | | var isIos = _isTrue(dataset.isios); |
| | | var state = ins.getState(); |
| | | var watchTouchDirectionChange = _isTrue(dataset.watchtouchdirectionchange); |
| | | var moveDisObj = {}; |
| | | var moveDis = 0; |
| | | var prevent = false; |
| | | if (watchTouchDirectionChange) { |
| | | moveDisObj = _getMoveDis(e, ins); |
| | | moveDis = moveDisObj.currentDis; |
| | | prevent = moveDisObj.isDown; |
| | | var direction = prevent ? 'top' : 'bottom'; |
| | | if (prevent == state.oldTouchDirection && prevent != state.oldEmitedTouchDirection) { |
| | | ownerIns.callMethod('_handleTouchDirectionChange', { direction: direction }); |
| | | state.oldEmitedTouchDirection = prevent; |
| | | } |
| | | state.oldTouchDirection = prevent; |
| | | } |
| | | if (_touchDisabled(e, ins, 1)) { |
| | | _handlePullingDown(state, ownerIns, false); |
| | | return true; |
| | | } |
| | | if (!_getAngleIsInRange(e, touch, state, dataset)) { |
| | | _handlePullingDown(state, ownerIns, false); |
| | | return true; |
| | | } |
| | | moveDisObj = _getMoveDis(e, ins); |
| | | moveDis = moveDisObj.currentDis; |
| | | prevent = moveDisObj.isDown; |
| | | if (moveDis < 0) { |
| | | _setTransformValue(0, ins, state, false); |
| | | _handlePullingDown(state, ownerIns, false); |
| | | return true; |
| | | } |
| | | if (prevent && !state.disabledBounce) { |
| | | ownerIns.callMethod('_handleScrollViewDisableBounce', {bounce: false}); |
| | | state.disabledBounce = true; |
| | | _handlePullingDown(state, ownerIns, prevent); |
| | | return !prevent; |
| | | } |
| | | _setTransformValue(moveDis, ins, state, false); |
| | | var oldRefresherStatus = state.refresherStatus; |
| | | var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving); |
| | | var hasTouchmove = _isTrue(dataset.hastouchmove); |
| | | var isTouchmoving = state.isTouchmoving; |
| | | state.refresherStatus = moveDis >= refresherThreshold ? 1 : 0; |
| | | if (!isTouchmoving) { |
| | | state.isTouchmoving = true; |
| | | isTouchmoving = true; |
| | | } |
| | | if (state.isTouchEnded) { |
| | | state.isTouchEnded = false; |
| | | } |
| | | if (hasTouchmove) { |
| | | ownerIns.callMethod('_handleWxsPullingDown', { moveDis:moveDis, diffDis:moveDisObj.diffDis }); |
| | | } |
| | | if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) { |
| | | ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch); |
| | | } |
| | | _handlePullingDown(state, ownerIns, prevent); |
| | | return !prevent; |
| | | } |
| | | |
| | | function touchend(e, ownerIns) { |
| | | var touch = _getTouch(e); |
| | | var ins = _getIns(ownerIns); |
| | | var dataset = ins.getDataset(); |
| | | var state = ins.getState(); |
| | | if (_touchDisabled(e, ins, 2)) return; |
| | | state.reachMaxAngle = true; |
| | | state.hitReachMaxAngleCount = 0; |
| | | state.disabledBounce = false; |
| | | state.fixedIsTopHitCount = 0; |
| | | if (!state.isTouchmoving) return; |
| | | var oldRefresherStatus = state.refresherStatus; |
| | | var oldMoveDis = state.moveDis; |
| | | var refresherThreshold = ins.getDataset().refresherthreshold |
| | | var moveDis = _getMoveDis(e, ins).currentDis; |
| | | if (!(moveDis >= refresherThreshold && oldRefresherStatus === 1)) { |
| | | state.isTouchmoving = false; |
| | | } |
| | | ownerIns.callMethod('_handleRefresherTouchend', moveDis); |
| | | state.isTouchEnded = true; |
| | | if (oldMoveDis < refresherThreshold) return; |
| | | var animate = false; |
| | | if (moveDis >= refresherThreshold) { |
| | | moveDis = refresherThreshold; |
| | | animate = true; |
| | | } |
| | | _setTransformValue(moveDis, ins, state, animate); |
| | | } |
| | | |
| | | // #ifdef H5 |
| | | function isPC() { |
| | | if (!navigator) return false; |
| | | if (isPCFlag != -1) return isPCFlag; |
| | | var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; |
| | | isPCFlag = agents.every(function(item) { return navigator.userAgent.indexOf(item) < 0 }); |
| | | return isPCFlag; |
| | | } |
| | | |
| | | var movable = false; |
| | | |
| | | function mousedown(e, ins) { |
| | | if (!isPC()) return; |
| | | touchstart(e, ins); |
| | | movable = true; |
| | | } |
| | | |
| | | function mousemove(e, ins) { |
| | | if (!isPC() || !movable) return; |
| | | touchmove(e, ins); |
| | | } |
| | | |
| | | function mouseup(e, ins) { |
| | | if (!isPC()) return; |
| | | touchend(e, ins); |
| | | movable = false; |
| | | } |
| | | |
| | | function mouseleave(e, ins) { |
| | | if (!isPC()) return; |
| | | movable = false; |
| | | } |
| | | // #endif |
| | | |
| | | |
| | | function _setTransformValue(value, ins, state, animate) { |
| | | value = value || 0; |
| | | if (state.moveDis == value) return; |
| | | state.moveDis = value; |
| | | _setTransform('translateY(' + value + 'px)', ins, animate, ''); |
| | | } |
| | | |
| | | function _setTransform(transform, ins, animate, transition) { |
| | | var dataset = ins.getDataset(); |
| | | if (_isTrue(dataset.refreshernotransform)) return; |
| | | transform = transform == 'translateY(0px)' ? 'none' : transform; |
| | | ins.requestAnimationFrame(function() { |
| | | var stl = { 'transform': transform }; |
| | | if (animate) { |
| | | stl['transition'] = 'transform .1s linear'; |
| | | } |
| | | if (transition.length) { |
| | | stl['transition'] = transition; |
| | | } |
| | | ins.setStyle(stl); |
| | | }) |
| | | } |
| | | |
| | | function _getMoveDis(e, ins) { |
| | | var state = ins.getState(); |
| | | var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold); |
| | | var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate); |
| | | var refresherPullRate = parseFloat(ins.getDataset().refresherpullrate); |
| | | var touch = _getTouch(e); |
| | | var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY; |
| | | var moveDis = touch.touchY - currentStartY; |
| | | var oldMoveDis = state.oldMoveDis || 0; |
| | | state.oldMoveDis = moveDis; |
| | | var diffDis = moveDis - oldMoveDis; |
| | | if (diffDis > 0) { |
| | | diffDis = diffDis * refresherPullRate; |
| | | if (currentDis > refresherThreshold) { |
| | | diffDis = diffDis * (1 - refresherOutRate); |
| | | } |
| | | } |
| | | diffDis = diffDis > 100 ? diffDis / 100 : (diffDis > 20 ? diffDis / 1.2 : diffDis); |
| | | currentDis += diffDis; |
| | | currentDis = Math.max(0, currentDis); |
| | | return { |
| | | currentDis: currentDis, |
| | | diffDis: diffDis, |
| | | isDown: diffDis > 0 |
| | | }; |
| | | } |
| | | |
| | | function _getTouch(e) { |
| | | var touch = e; |
| | | if (e.touches && e.touches.length) { |
| | | touch = e.touches[0]; |
| | | } else if (e.changedTouches && e.changedTouches.length) { |
| | | touch = e.changedTouches[0]; |
| | | } else if (e.datail && e.datail != {}) { |
| | | touch = e.datail; |
| | | } |
| | | return { |
| | | touchX: touch.clientX, |
| | | touchY: touch.clientY |
| | | }; |
| | | } |
| | | |
| | | function _getIns(ownerIns) { |
| | | var ins = ownerIns.getState().currentIns; |
| | | if (!ins) { |
| | | ownerIns.callMethod('_handlePropUpdate'); |
| | | } |
| | | return ins; |
| | | } |
| | | |
| | | function _touchDisabled(e, ins, processTag) { |
| | | var dataset = ins.getDataset(); |
| | | var state = ins.getState(); |
| | | var loading = _isTrue(dataset.loading); |
| | | var useChatRecordMode = _isTrue(dataset.usechatrecordmode); |
| | | var refresherEnabled = _isTrue(dataset.refresherenabled); |
| | | var useCustomRefresher = _isTrue(dataset.usecustomrefresher); |
| | | var usePageScroll = _isTrue(dataset.usepagescroll); |
| | | var pageScrollTop = parseFloat(dataset.pagescrolltop); |
| | | var scrollTop = parseFloat(dataset.scrolltop); |
| | | var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop; |
| | | var fixedIsTop = false; |
| | | var isIos = _isTrue(dataset.isios); |
| | | if (!isIos && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) { |
| | | fixedIsTop = true; |
| | | } |
| | | var fixedIsTopHitCount = state.fixedIsTopHitCount || 0; |
| | | if (fixedIsTop) { |
| | | fixedIsTopHitCount ++; |
| | | if (fixedIsTopHitCount <= 2) { |
| | | fixedIsTop = false; |
| | | } |
| | | state.fixedIsTopHitCount = fixedIsTopHitCount; |
| | | } else { |
| | | state.fixedIsTopHitCount = 0; |
| | | } |
| | | if (!isIos && processTag === 0) { |
| | | state.startScrollTop = finalScrollTop || 0; |
| | | } |
| | | if (!isIos && processTag === 2) { |
| | | fixedIsTop = true; |
| | | } |
| | | return loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher || |
| | | ((usePageScroll && useCustomRefresher && pageScrollTop > 5) && !fixedIsTop) || |
| | | ((!usePageScroll && useCustomRefresher && scrollTop > 5) && !fixedIsTop); |
| | | } |
| | | |
| | | function _getAngleIsInRange(e, touch, state, dataset) { |
| | | var maxAngle = dataset.refreshermaxangle; |
| | | var refresherAecc = _isTrue(dataset.refresheraecc); |
| | | var lastTouch = state.lastTouch; |
| | | var reachMaxAngle = state.reachMaxAngle; |
| | | var moveDis = state.oldMoveDis; |
| | | if (!lastTouch) return true; |
| | | if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) { |
| | | if ((!moveDis || moveDis < 1) && !refresherAecc && reachMaxAngle != null && !reachMaxAngle) return false; |
| | | var x = Math.abs(touch.touchX - lastTouch.touchX); |
| | | var y = Math.abs(touch.touchY - lastTouch.touchY); |
| | | var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); |
| | | if ((x || y) && x > 1) { |
| | | var angle = Math.asin(y / z) / Math.PI * 180; |
| | | if (angle < maxAngle) { |
| | | var hitReachMaxAngleCount = state.hitReachMaxAngleCount || 0; |
| | | state.hitReachMaxAngleCount = ++hitReachMaxAngleCount; |
| | | if (state.hitReachMaxAngleCount > 2) { |
| | | state.lastTouch = touch; |
| | | state.reachMaxAngle = false; |
| | | } |
| | | return false; |
| | | } |
| | | } |
| | | } |
| | | state.lastTouch = touch; |
| | | return true; |
| | | } |
| | | |
| | | function _handlePullingDown(state, ins, onPullingDown) { |
| | | var oldOnPullingDown = state.onPullingDown || false; |
| | | if (oldOnPullingDown != onPullingDown) { |
| | | ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown); |
| | | } |
| | | state.onPullingDown = onPullingDown; |
| | | } |
| | | |
| | | function _isTrue(value) { |
| | | value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false; |
| | | return value == true || value == 'true'; |
| | | } |
| | | |
| | | module.exports = { |
| | | touchstart: touchstart, |
| | | touchmove: touchmove, |
| | | touchend: touchend, |
| | | mousedown: mousedown, |
| | | mousemove: mousemove, |
| | | mouseup: mouseup, |
| | | mouseleave: mouseleave, |
| | | propObserver: propObserver |
| | | } |
| New file |
| | |
| | | <!-- _ |
| | | ____ _ __ __ _ __ _(_)_ __ __ _ |
| | | |_ /____| '_ \ / _` |/ _` | | '_ \ / _` | |
| | | / /_____| |_) | (_| | (_| | | | | | (_| | |
| | | /___| | .__/ \__,_|\__, |_|_| |_|\__, | |
| | | |_| |___/ |___/ |
| | | v2.6.2 (2023-10-31) |
| | | by ZXLee |
| | | --> |
| | | <!-- 文档地址:https://z-paging.zxlee.cn --> |
| | | <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> |
| | | <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> |
| | | <!-- 反馈QQ群:790460711(已满)、371624008 --> |
| | | |
| | | <template name="z-paging"> |
| | | <!-- #ifndef APP-NVUE --> |
| | | <view :class="{'z-paging-content':true,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1}" :style="[finalPagingStyle]"> |
| | | <!-- #ifndef APP-PLUS --> |
| | | <view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view> |
| | | <!-- #endif --> |
| | | <!-- 顶部固定的slot --> |
| | | <slot v-if="!usePageScroll&&zSlots.top" name="top" /> |
| | | <view class="zp-page-top" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]"> |
| | | <slot name="top" /> |
| | | </view> |
| | | <view :class="{'zp-view-super':true,'zp-scroll-view-super':!usePageScroll}" :style="[finalScrollViewStyle]"> |
| | | <view v-if="zSlots.left" :class="{'zp-page-left':true,'zp-absoulte':finalIsOldWebView}"> |
| | | <slot name="left" /> |
| | | </view> |
| | | <view :class="{'zp-scroll-view-container':true,'zp-absoulte':finalIsOldWebView}" :style="[scrollViewContainerStyle]"> |
| | | <scroll-view |
| | | ref="zp-scroll-view" :class="{'zp-scroll-view':true,'zp-scroll-view-absolute':!usePageScroll,'zp-scroll-view-hide-scrollbar':!showScrollbar}" |
| | | :scroll-top="scrollTop" :scroll-x="scrollX" |
| | | :scroll-y="scrollable&&!usePageScroll&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==R.Complete)" :enable-back-to-top="finalEnableBackToTop" |
| | | :show-scrollbar="showScrollbar" :scroll-with-animation="finalScrollWithAnimation" |
| | | :scroll-into-view="scrollIntoView" :lower-threshold="finalLowerThreshold" :upper-threshold="5" |
| | | :refresher-enabled="finalRefresherEnabled&&!useCustomRefresher" :refresher-threshold="finalRefresherThreshold" |
| | | :refresher-default-style="finalRefresherDefaultStyle" :refresher-background="refresherBackground" |
| | | :refresher-triggered="finalRefresherTriggered" @scroll="_scroll" @scrolltolower="_onScrollToLower" |
| | | @scrolltoupper="_onScrollToUpper" @refresherrestore="_onRestore" @refresherrefresh="_onRefresh(true)" |
| | | > |
| | | <view class="zp-paging-touch-view" |
| | | <!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> |
| | | @touchstart="_refresherTouchstart" @touchmove="_refresherTouchmove" @touchend="_refresherTouchend" @touchcancel="_refresherTouchend" |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> |
| | | @touchstart="pagingWxs.touchstart" @touchmove="pagingWxs.touchmove" @touchend="pagingWxs.touchend" @touchcancel="pagingWxs.touchend" |
| | | @mousedown="pagingWxs.mousedown" @mousemove="pagingWxs.mousemove" @mouseup="pagingWxs.mouseup" @mouseleave="pagingWxs.mouseleave" |
| | | <!-- #endif --> |
| | | > |
| | | <view v-if="finalRefresherFixedBacHeight>0" class="zp-fixed-bac-view" :style="[{'background': refresherFixedBackground,'height': `${finalRefresherFixedBacHeight}px`}]"></view> |
| | | <view class="zp-paging-main" :style="[scrollViewInStyle,{'transform': finalRefresherTransform,'transition': refresherTransition}]" |
| | | <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> |
| | | :change:prop="pagingWxs.propObserver" :prop="wxsPropType" |
| | | :data-refresherThreshold="finalRefresherThreshold" :data-isIos="isIos" |
| | | :data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode" |
| | | :data-refresherEnabled="refresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop" |
| | | :data-scrollTop="wxsScrollTop" :data-refresherMaxAngle="refresherMaxAngle" :data-refresherNoTransform="refresherNoTransform" |
| | | :data-refresherAecc="refresherAngleEnableChangeContinued" :data-usePageScroll="usePageScroll" :data-watchTouchDirectionChange="watchTouchDirectionChange" |
| | | :data-oldIsTouchmoving="isTouchmoving" :data-refresherOutRate="finalRefresherOutRate" :data-refresherPullRate="finalRefresherPullRate" :data-hasTouchmove="hasTouchmove" |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-VUE || H5 --> |
| | | :change:renderPropIsIosAndH5="pagingRenderjs.renderPropIsIosAndH5Change" :renderPropIsIosAndH5="isIosAndH5" |
| | | <!-- #endif --> |
| | | > |
| | | <view v-if="showRefresher" class="zp-custom-refresher-view" :style="[{'margin-top': `-${finalRefresherThreshold+refresherThresholdUpdateTag}px`,'background': refresherBackground,'opacity': isTouchmoving ? 1 : 0}]"> |
| | | <view class="zp-custom-refresher-container" :style="[{'height': `${finalRefresherThreshold}px`,'background': refresherBackground}]"> |
| | | <view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" /> |
| | | <!-- 下拉刷新view --> |
| | | <view class="zp-custom-refresher-slot-view"> |
| | | <slot v-if="!(zSlots.refresherComplete&&refresherStatus===R.Complete)" :refresherStatus="refresherStatus" name="refresher" /> |
| | | </view> |
| | | <slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" /> |
| | | <z-paging-refresh ref="refresh" v-else-if="!showCustomRefresher" class="zp-custom-refresher-refresh" :style="[{'height': `${finalRefresherThreshold - finalRefresherThresholdPlaceholder}px`}]" :status="refresherStatus" |
| | | :defaultThemeStyle="finalRefresherThemeStyle" :defaultText="finalRefresherDefaultText" |
| | | :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" |
| | | :defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated" |
| | | :showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap" |
| | | :imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" /> |
| | | </view> |
| | | </view> |
| | | <view class="zp-paging-container"> |
| | | <slot v-if="useChatRecordMode&&zSlots.chatLoading&&loadingStatus!==M.NoMore&&realTotalData.length" name="chatLoading" /> |
| | | <view v-else-if="useChatRecordMode&&loadingStatus!==M.NoMore&&realTotalData.length" class="zp-chat-record-loading-container"> |
| | | <text v-if="loadingStatus!==M.Loading" @click="_onScrollToUpper" |
| | | :class="defaultThemeStyle==='white'?'zp-loading-more-text zp-loading-more-text-white':'zp-loading-more-text zp-loading-more-text-black'">{{chatRecordLoadingMoreText}}</text> |
| | | <image v-else :src="base64Flower" class="zp-chat-record-loading-custom-image" /> |
| | | </view> |
| | | <!-- 全屏Loading --> |
| | | <slot v-if="showLoading&&zSlots.loading&&!loadingFullFixed" name="loading" /> |
| | | <!-- 主体内容 --> |
| | | <view class="zp-paging-container-content" :style="[{transform:virtualPlaceholderTopHeight>0?`translateY(${virtualPlaceholderTopHeight}px)`:'none'},finalPagingContentStyle]"> |
| | | <slot /> |
| | | <!-- 内置列表&虚拟列表 --> |
| | | <template v-if="finalUseInnerList"> |
| | | <slot name="header"/> |
| | | <view class="zp-list-container" :style="[innerListStyle]"> |
| | | <template v-if="finalUseVirtualList"> |
| | | <view class="zp-list-cell" :style="[innerCellStyle]" :id="`zp-id-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)"> |
| | | <view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第100行中注释这一行,并打开下面一行注释</view> |
| | | <!-- <zp-public-virtual-cell v-if="useCompatibilityMode" :extraData="extraData" :item="item" :index="virtualTopRangeIndex+index" /> --> |
| | | <slot v-else name="cell" :item="item" :index="virtualTopRangeIndex+index"/> |
| | | </view> |
| | | </template> |
| | | <template v-else> |
| | | <view class="zp-list-cell" v-for="(item,index) in realTotalData" :key="index" @click="_innerCellClick(item,index)"> |
| | | <slot name="cell" :item="item" :index="index"/> |
| | | </view> |
| | | </template> |
| | | </view> |
| | | <slot name="footer"/> |
| | | </template> |
| | | <view v-if="useVirtualList" class="zp-virtual-placeholder" :style="[{height:virtualPlaceholderBottomHeight+'px'}]"/> |
| | | <!-- 上拉加载更多view --> |
| | | <!-- #ifndef MP-ALIPAY --> |
| | | <slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" /> |
| | | <slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" /> |
| | | <slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" /> |
| | | <slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" /> |
| | | <z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" /> |
| | | <!-- #endif --> |
| | | <!-- #ifdef MP-ALIPAY --> |
| | | <slot v-if="loadingStatus===M.Default&&zSlots.loadingMoreDefault&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreDefault" /> |
| | | <slot v-else-if="loadingStatus===M.Loading&&zSlots.loadingMoreLoading&&showLoadingMore&&loadingMoreEnabled" name="loadingMoreLoading" /> |
| | | <slot v-else-if="loadingStatus===M.NoMore&&zSlots.loadingMoreNoMore&&showLoadingMore&&showLoadingMoreNoMoreView&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreNoMore" /> |
| | | <slot v-else-if="loadingStatus===M.Fail&&zSlots.loadingMoreFail&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreFail" /> |
| | | <z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMore&&showDefaultLoadingMoreText&&!(loadingStatus===M.NoMore&&!showLoadingMoreNoMoreView)&&loadingMoreEnabled&&!useChatRecordMode" :zConfig="zLoadMoreConfig" /> |
| | | <!-- #endif --> |
| | | <view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" /> |
| | | </view> |
| | | <!-- 空数据图 --> |
| | | <view v-if="showEmpty" :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}" :style="[emptyViewSuperStyle]"> |
| | | <slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed"/> |
| | | <z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload" |
| | | :emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle" |
| | | :emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" |
| | | @reload="_emptyViewReload" @viewClick="_emptyViewClick" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | <view v-if="zSlots.right" :class="{'zp-page-right':true,'zp-absoulte zp-right':finalIsOldWebView}"> |
| | | <slot name="right" /> |
| | | </view> |
| | | </view> |
| | | <!-- 底部固定的slot --> |
| | | <slot v-if="!usePageScroll&&zSlots.bottom" name="bottom" /> |
| | | <view class="zp-page-bottom" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.bottom" :style="[{'bottom': `${windowBottom}px`}]"> |
| | | <slot name="bottom" /> |
| | | </view> |
| | | <!-- 点击返回顶部view --> |
| | | <view v-if="showBackToTopClass" :class="backToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick"> |
| | | <slot v-if="zSlots.backToTop" name="backToTop" /> |
| | | <image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" /> |
| | | </view> |
| | | <!-- 全屏Loading(铺满z-paging并固定) --> |
| | | <view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed"> |
| | | <slot name="loading" /> |
| | | </view> |
| | | </view> |
| | | <!-- #endif --> |
| | | <!-- #ifdef APP-NVUE --> |
| | | <component :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false"> |
| | | <!-- 顶部固定的slot --> |
| | | <view ref="zp-page-top" v-if="zSlots.top" :class="{'zp-page-top':usePageScroll}" :style="[usePageScroll?{'top':`${windowTop}px`,'z-index':topZIndex}:{}]"> |
| | | <slot name="top" /> |
| | | </view> |
| | | <component :is="finalNvueSuperListIs" class="zp-n-list-container" :scrollable="false"> |
| | | <view v-if="zSlots.left" class="zp-page-left"> |
| | | <slot name="left" /> |
| | | </view> |
| | | <component :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},nChatRecordRotateStyle]" :alwaysScrollableVertical="true" |
| | | :fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar&&!useChatRecordMode" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop" |
| | | :scrollable="scrollable&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==R.Complete)" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth" |
| | | :column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled" :offset-accuracy="offsetAccuracy" |
| | | @loadmore="_nOnLoadmore" @scroll="_nOnScroll"> |
| | | <refresh v-if="(zSlots.top?cacheTopHeight!==-1:true)&&finalNvueRefresherEnabled" class="zp-n-refresh" :style="[nvueRefresherStyle]" :display="nRefresherLoading?'show':'hide'" @refresh="_nOnRrefresh" @pullingdown="_nOnPullingdown"> |
| | | <view ref="zp-n-refresh-container" class="zp-n-refresh-container" :style="[{background:refresherBackground,width:nRefresherWidth}]" id="zp-n-refresh-container"> |
| | | <view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" /> |
| | | <!-- 下拉刷新view --> |
| | | <slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" /> |
| | | <slot v-else-if="(nScopedSlots?nScopedSlots:zSlots).refresher" :refresherStatus="refresherStatus" name="refresher" /> |
| | | <z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle" |
| | | :defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" |
| | | :defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated" |
| | | :showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap" |
| | | :imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" /> |
| | | </view> |
| | | </refresh> |
| | | <component :is="nViewIs" v-if="isIos&&!useChatRecordMode?oldScrollTop>10:true" ref="zp-n-list-top-tag" class="zp-n-list-top-tag" style="margin-top: -1rpx;" :style="[{height:finalNvueRefresherEnabled?'0px':'1px'}]"></component> |
| | | <component :is="nViewIs" v-if="nShowRefresherReveal" ref="zp-n-list-refresher-reveal" :style="[{transform:`translateY(-${nShowRefresherRevealHeight}px)`},{background:refresherBackground}]"> |
| | | <view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" /> |
| | | <!-- 下拉刷新view --> |
| | | <slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" /> |
| | | <slot v-else-if="(nScopedSlots?nScopedSlots:$slots).refresher" :refresherStatus="R.Loading" name="refresher" /> |
| | | <z-paging-refresh ref="refresh" v-else :status="R.Loading" :defaultThemeStyle="finalRefresherThemeStyle" |
| | | :defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" |
| | | :defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated" |
| | | :showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap" |
| | | :imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" /> |
| | | </component> |
| | | <template v-if="finalUseInnerList"> |
| | | <component :is="nViewIs"> |
| | | <slot name="header"/> |
| | | </component> |
| | | <component :is="nViewIs" class="zp-list-cell" v-for="(item,index) in realTotalData" :key="finalCellKeyName.length?item[finalCellKeyName]:index"> |
| | | <slot name="cell" :item="item" :index="index"/> |
| | | </component> |
| | | <component :is="nViewIs"> |
| | | <slot name="footer"/> |
| | | </component> |
| | | </template> |
| | | <template v-else> |
| | | <slot /> |
| | | </template> |
| | | <!-- 全屏Loading --> |
| | | <component :is="nViewIs" v-if="showLoading&&zSlots.loading&&!loadingFullFixed" :class="{'z-paging-content-fixed':usePageScroll}" style="flex:1" :style="[nChatRecordRotateStyle]"> |
| | | <slot name="loading" /> |
| | | </component> |
| | | <!-- 上拉加载更多view --> |
| | | <component :is="nViewIs" v-if="!refresherOnly&&loadingMoreEnabled&&!showEmpty"> |
| | | <view v-if="useChatRecordMode"> |
| | | <view v-if="loadingStatus!==M.NoMore&&realTotalData.length"> |
| | | <slot v-if="zSlots.chatLoading" name="chatLoading" /> |
| | | <view v-else class="zp-chat-record-loading-container"> |
| | | <text v-if="loadingStatus!==M.Loading" @click="_onScrollToUpper" |
| | | :class="defaultThemeStyle==='white'?'zp-loading-more-text zp-loading-more-text-white':'zp-loading-more-text zp-loading-more-text-black'">{{chatRecordLoadingMoreText}}</text> |
| | | <view> |
| | | <loading-indicator v-if="loadingStatus===M.Loading" class="zp-line-loading-image" animating /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:'80rpx'}:{}"> |
| | | <slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" /> |
| | | <slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" /> |
| | | <slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" /> |
| | | <slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" /> |
| | | <z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" /> |
| | | <view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" /> |
| | | </view> |
| | | </component> |
| | | <!-- 空数据图 --> |
| | | <component :is="nViewIs" v-if="showEmpty" :class="{'z-paging-content-fixed':usePageScroll}" :style="[{flex:emptyViewCenter?1:0},emptyViewSuperStyle,nChatRecordRotateStyle]"> |
| | | <view :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}"> |
| | | <slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed" /> |
| | | <z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload" |
| | | :emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle" |
| | | :emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" |
| | | @reload="_emptyViewReload" @viewClick="_emptyViewClick" /> |
| | | </view> |
| | | </component> |
| | | <component is="header" v-if="!hideNvueBottomTag" ref="zp-n-list-bottom-tag" class="zp-n-list-bottom-tag"></component> |
| | | </component> |
| | | <view v-if="zSlots.right" class="zp-page-right"> |
| | | <slot name="right" /> |
| | | </view> |
| | | </component> |
| | | <!-- 底部固定的slot --> |
| | | <slot name="bottom" /> |
| | | <!-- 点击返回顶部view --> |
| | | <view v-if="showBackToTopClass" :class="backToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick"> |
| | | <slot v-if="zSlots.backToTop" name="backToTop" /> |
| | | <image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" /> |
| | | </view> |
| | | <!-- 全屏Loading(铺满z-paging并固定) --> |
| | | <view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed"> |
| | | <slot name="loading" /> |
| | | </view> |
| | | </component> |
| | | <!-- #endif --> |
| | | </template> |
| | | <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> |
| | | <script src="./wxs/z-paging-wxs.wxs" module="pagingWxs" lang="wxs"></script> |
| | | <!-- #endif --> |
| | | <script module="pagingRenderjs" lang="renderjs"> |
| | | import pagingRenderjs from './wxs/z-paging-renderjs.js'; |
| | | /** |
| | | * z-paging 分页组件 |
| | | * @description 高性能,全平台兼容。支持虚拟列表,支持nvue、vue3 |
| | | * @tutorial https://z-paging.zxlee.cn |
| | | * @notice 以下仅为部分常用属性、方法和事件,完整文档请查阅z-paging官网 |
| | | * @property {Number|String} default-page-no 自定义初始的pageNo,默认为1 |
| | | * @property {Number|String} default-page-size 自定义pageSize,默认为10 |
| | | * @property {Object} paging-style 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 |
| | | * @property {String} height z-paging的高度,优先级低于pagingStyle中设置的height,传字符串,如100px、100rpx、100% |
| | | * @property {String} width z-paging的宽度,优先级低于pagingStyle中设置的width,传字符串,如100px、100rpx、100% |
| | | * @property {Boolean} use-page-scroll 使用页面滚动,默认为否 |
| | | * @property {Boolean} use-virtual-list 是否使用虚拟列表,默认为否 |
| | | * @property {Boolean} fixed z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) |
| | | * @property {Boolean} auto [z-paging]mounted后是否自动调用reload方法(mounted后自动调用接口),默认为是 |
| | | * @property {Boolean} use-chat-record-mode 使用聊天记录模式,默认为否 |
| | | * @event {Function} query 下拉刷新或滚动到底部时会自动触发此方法。z-paging加载时也会触发(若要禁止,请设置:auto="false")。pageNo和pageSize会自动计算好,直接传给服务器即可。 |
| | | * @example <z-paging ref="paging" v-model="dataList" @query="queryList"></z-paging> |
| | | */ |
| | | export default { |
| | | name:"z-paging", |
| | | // #ifdef APP-VUE || H5 |
| | | mixins: [pagingRenderjs], |
| | | // #endif |
| | | } |
| | | </script> |
| | | <script src="./js/z-paging-main.js" /> |
| | | |
| | | <style scoped> |
| | | @import "./css/z-paging-main.css"; |
| | | @import "./css/z-paging-static.css"; |
| | | </style> |
| New file |
| | |
| | | { |
| | | "id": "z-paging", |
| | | "name": "z-paging", |
| | | "displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,支持nvue、vue3", |
| | | "version": "2.6.2", |
| | | "description": "超简单、低耦合!使用wxs+renderjs实现。支持长列表优化,支持自定义下拉刷新、上拉加载更多,支持自动管理空数据图、点击返回顶部,支持聊天分页、本地分页,支持国际化等100+项配置", |
| | | "keywords": [ |
| | | "下拉刷新", |
| | | "上拉加载", |
| | | "分页器", |
| | | "nvue", |
| | | "虚拟列表" |
| | | ], |
| | | "repository": "https://github.com/SmileZXLee/uni-z-paging", |
| | | "engines": { |
| | | "HBuilderX": "^3.0.7" |
| | | }, |
| | | "dcloudext": { |
| | | "sale": { |
| | | "regular": { |
| | | "price": "0.00" |
| | | }, |
| | | "sourcecode": { |
| | | "price": "0.00" |
| | | } |
| | | }, |
| | | "contact": { |
| | | "qq": "393727164" |
| | | }, |
| | | "declaration": { |
| | | "ads": "无", |
| | | "data": "无", |
| | | "permissions": "无" |
| | | }, |
| | | "npmurl": "https://www.npmjs.com/package/z-paging", |
| | | "type": "component-vue" |
| | | }, |
| | | "uni_modules": { |
| | | "dependencies": [], |
| | | "encrypt": [], |
| | | "platforms": { |
| | | "cloud": { |
| | | "tcb": "y", |
| | | "aliyun": "y" |
| | | }, |
| | | "client": { |
| | | "App": { |
| | | "app-vue": "y", |
| | | "app-nvue": "y" |
| | | }, |
| | | "H5-mobile": { |
| | | "Safari": "y", |
| | | "Android Browser": "y", |
| | | "微信浏览器(Android)": "y", |
| | | "QQ浏览器(Android)": "y" |
| | | }, |
| | | "H5-pc": { |
| | | "Chrome": "y", |
| | | "IE": "y", |
| | | "Edge": "y", |
| | | "Firefox": "y", |
| | | "Safari": "y" |
| | | }, |
| | | "小程序": { |
| | | "微信": "y", |
| | | "阿里": "y", |
| | | "百度": "y", |
| | | "字节跳动": "y", |
| | | "QQ": "y", |
| | | "钉钉": "y", |
| | | "快手": "y", |
| | | "飞书": "y", |
| | | "京东": "y" |
| | | }, |
| | | "快应用": { |
| | | "华为": "y", |
| | | "联盟": "y" |
| | | }, |
| | | "Vue": { |
| | | "vue2": "y", |
| | | "vue3": "y" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| New file |
| | |
| | | # z-paging |
| | | |
| | | <p align="center"> |
| | | <img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;"> |
| | | </p> |
| | | |
| | | [](https://github.com/SmileZXLee/uni-z-paging) |
| | | [](https://en.wikipedia.org/wiki/MIT_License) |
| | | |
| | | ### 文档地址:[https://z-paging.zxlee.cn](https://z-paging.zxlee.cn) |
| | | |
| | | ### 更新组件前,请注意[版本差异](https://z-paging.zxlee.cn/start/upgrade-guide.html) |
| | | |
| | | *** |
| | | ### 功能&特点 |
| | | * 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。 |
| | | * 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。 |
| | | * 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。 |
| | | * 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部等诸多功能。 |
| | | * 【全平台兼容】支持vue、nvue,vue2、vue3,支持h5、app及各家小程序。 |
| | | * 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs从视图层实现下拉刷新;支持虚拟列表,轻松渲染万级数据! |
| | | |
| | | *** |
| | | ### 反馈qq群 |
| | | * 官方1群`已满`:[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH) |
| | | |
| | | * 官方2群:[371624008](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=avPmibADf2TNi4LxkIwjCE5vbfXpa-r1&authKey=dQ%2FVDAR87ONxI4b32Py%2BvmXbhnopjHN7%2FJPtdsqJdsCPFZB6zDQ17L06Uh0kITUZ&noverify=0&group_code=371624008) |
| | | |
| | | *** |
| | | |
| | | ### 预览 |
| | | |
| | | *** |
| | | |
| | | | 自定义下拉刷新效果演示 | 滑动切换选项卡+吸顶演示 | |
| | | | :----------------------------------------------------------: | :----------------------------------------------------------: | |
| | | |  |  | |
| | | |
| | | | 聊天记录模式演示 | 虚拟列表(流畅渲染1万+条)演示 | |
| | | | :----------------------------------------------------------: | :----------------------------------------------------------: | |
| | | |  |  | |
| | | |
| | | ### 在线demo体验地址: |
| | | |
| | | * [https://demo.z-paging.zxlee.cn](https://demo.z-paging.zxlee.cn) |
| | | |
| | | | 扫码体验 | |
| | | | ------------------------------------------------------------ | |
| | | |  | |
| | | |
| | | ### demo下载 |
| | | * 支持vue2&vue3的`选项式api`写法demo下载,请点击页面右上角的【使用HBuilderX导入示例项目】或【下载示例项目ZIP】。 |
| | | * 支持vue3的`组合式api`写法demo下载,请访问[github](https://github.com/SmileZXLee/uni-z-paging)。 |