a7835847 by 华明祺

Merge branch 'master' of https://code.itechtop.cn/yangyang/ztx_wx_admin

# Conflicts:
#	pages/index/index.vue
2 parents 7145d1c6 c49a1142
Showing 54 changed files with 4327 additions and 195 deletions
...@@ -253,8 +253,41 @@ ...@@ -253,8 +253,41 @@
253 "navigationBarTitleText": "注册", 253 "navigationBarTitleText": "注册",
254 "enablePullDownRefresh": false 254 "enablePullDownRefresh": false
255 } 255 }
256 }, {
257 "path": "pages/index/msgList",
258 "style": {
259 "navigationBarTitleText": "待办列表",
260 "enablePullDownRefresh": false
261 }
262 }, {
263 "path": "pages/personalVip/addVip",
264 "style": {
265 "navigationBarTitleText": "添加会员",
266 "enablePullDownRefresh": false,
267 "backgroundColor": "#ffffff",
268 "navigationBarTextStyle": "black",
269 "navigationBarBackgroundColor": "#ffffff"
270 }
256 } 271 }
257 ], 272 ,{
273 "path" : "pages/personalVip/renew",
274 "style" :
275 {
276 "navigationBarTitleText": "会员缴费",
277 "enablePullDownRefresh": false
278 }
279
280 }
281 ,{
282 "path" : "pages/personalVip/vipList",
283 "style" :
284 {
285 "navigationBarTitleText": "会员列表",
286 "enablePullDownRefresh": false
287 }
288
289 }
290 ],
258 "globalStyle": { 291 "globalStyle": {
259 "navigationStyle": "custom", 292 "navigationStyle": "custom",
260 "navigationBarTextStyle": "white", 293 "navigationBarTextStyle": "white",
......
1 <template> 1 <template>
2 <view class="page"> 2 <view class="page">
3 <view> 3 <view>
4 <view class="welcome">您好!<br />欢迎使用中跆协会员管理系统</view> 4 <view class="welcome">您好!<br />欢迎使用中跆协会员管理系统</view>
5 <view class="flexbox"> 5 <view class="flexbox">
6 <view>添加会员</view> 6 <view @click="goAddVip">添加会员</view>
7 <view>会员缴费</view> 7 <view @click="goRenew">会员缴费</view>
8 <view>缴费审核</view> 8 <view>缴费审核</view>
9 </view> 9 </view>
10 10
11 </view> 11 </view>
12 <uni-section padding> 12 <uni-section padding>
13 <uni-grid :column="4" :show-border="false" :square="false" @change="change"> 13 <uni-grid :column="4" :show-border="false" :square="false" @change="change">
14 <uni-grid-item v-for="(item ,index) in list" :index="index" :key="index"> 14 <uni-grid-item v-for="(item ,index) in list" :index="index" :key="index">
15 <view class="grid-item-box"> 15 <view class="grid-item-box">
16 <image class="image" :src="item.url" mode="aspectFill" /> 16 <image class="image" :src="item.url" mode="aspectFill" />
17 <text class="text">{{item.text}}</text> 17 <text class="text">{{item.text}}</text>
18 <view v-if="item.badge" class="grid-dot"> 18 <view v-if="item.badge" class="grid-dot">
19 <uni-badge :text="item.badge" :type="item.type" /> 19 <uni-badge :text="item.badge" :type="item.type" />
20 </view> 20 </view>
21 </view> 21 </view>
22 </uni-grid-item> 22 </uni-grid-item>
23 </uni-grid> 23 </uni-grid>
24 </uni-section> 24 </uni-section>
25 <uni-section title="待办提醒" padding> 25 <uni-section title="待办提醒" padding>
26 <template v-slot:right> 26 <template v-slot:right>
27 <text @click="goMsgList" class="more">更多></text> 27 <text @click="goMsgList" class="more">更多></text>
28 </template> 28 </template>
29 <view class="msglist"> 29 <view class="msglist">
30 <view class="msgitem" v-for="n in 4"> 30 <view class="msgitem" v-for="n in 4">
31 <text class="dot" :class="n.isRead?'done':''"></text> 31 <text class="dot" :class="n.isRead?'done':''"></text>
32 <view class="tt esp">你有一条会员缴费等待审批,点击前去处理!</view> 32 <view class="tt esp">你有一条会员缴费等待审批,点击前去处理!</view>
33 <view class="date">2023-09-23</view> 33 <view class="date">2023-09-23</view>
34 </view> 34 </view>
35 </view> 35 </view>
36 </uni-section> 36 </uni-section>
37 </view> 37 </view>
38 </template> 38 </template>
39 39
40 <script setup> 40 <script setup>
41 import * as api from '@/common/api.js'; 41 import * as api from '@/common/api.js';
42 import * as loginServer from '@/common/login.js'; 42 import * as loginServer from '@/common/login.js';
43 import { 43 import {
44 getWxUser, 44 getWxUser,
45 getWxUserPhone 45 getWxUserPhone
46 } from '@/common/login.js'; 46 } from '@/common/login.js';
47 import { 47 import {
48 onLoad, 48 onLoad,
49 onShow, 49 onShow,
50 onReady, 50 onReady,
51 onShareAppMessage, 51 onShareAppMessage,
52 onShareTimeline, 52 onShareTimeline,
53 onPullDownRefresh 53 onPullDownRefresh
54 } from '@dcloudio/uni-app'; 54 } from '@dcloudio/uni-app';
55 import { 55 import {
56 ref, 56 ref,
57 getCurrentInstance 57 getCurrentInstance
58 } from 'vue'; 58 } from 'vue';
59 59
60 60
61 const { 61 const {
62 proxy 62 proxy
63 } = getCurrentInstance() 63 } = getCurrentInstance()
64 const app = getApp(); 64 const app = getApp();
65 65
66 66
67 let proId; 67 let proId;
68 let goPath; 68 let goPath;
69 const svId = ref(null); 69 const svId = ref(null);
70 const list = ref([{ 70 const list = ref([{
71 url: '/static/c1.png', 71 url: '/static/c1.png',
72 text: '会员调动', 72 text: '会员调动',
73 badge: '0', 73 badge: '0',
74 type: "primary" 74 type: "primary"
75 }, 75 },
76 { 76 {
77 url: '/static/c2.png', 77 url: '/static/c2.png',
78 text: '调动审核', 78 text: '调动审核',
79 badge: '1', 79 badge: '1',
80 type: "success" 80 type: "success"
81 }, 81 },
82 { 82 {
83 url: '/static/c3.png', 83 url: '/static/c3.png',
84 text: '级位考试申请', 84 text: '级位考试申请',
85 badge: '99', 85 badge: '99',
86 type: "warning" 86 type: "warning"
87 }, 87 },
88 { 88 {
89 url: '/static/c4.png', 89 url: '/static/c4.png',
90 text: '级位考试审核', 90 text: '级位考试审核',
91 badge: '2', 91 badge: '2',
92 type: "error" 92 type: "error"
93 }, 93 },
94 { 94 {
95 url: '/static/c5.png', 95 url: '/static/c5.png',
96 text: '段位考试申请' 96 text: '段位考试申请'
97 }, 97 },
98 { 98 {
99 url: '/static/c6.png', 99 url: '/static/c6.png',
100 text: '段位考试审核' 100 text: '段位考试审核'
101 }, 101 },
102 { 102 {
103 url: '/static/c7.png', 103 url: '/static/c7.png',
104 text: '级位证书发送' 104 text: '级位证书发送'
105 }, 105 },
106 { 106 {
107 url: '/static/c8.png', 107 url: '/static/c8.png',
108 text: '段位证书发送' 108 text: '段位证书发送'
109 } 109 }
110 ]); 110 ]);
111 111
112 onShow(() => { 112 onShow(() => {
113 if (app.globalData.isLogin) { 113 if (app.globalData.isLogin) {
114 init(); 114 init();
115 } else { 115 } else {
116 116
117 app.firstLoadCallback = () => { 117 app.firstLoadCallback = () => {
118 init(); 118 init();
119 }; 119 };
120 } 120 }
121 }) 121 })
122 onLoad(option => { 122 onLoad(option => {
123 if (option.scene) { 123 if (option.scene) {
124 proId = decodeURIComponent(option.scene); 124 proId = decodeURIComponent(option.scene);
125 } else { 125 } else {
126 proId = option.proId; 126 proId = option.proId;
127 } 127 }
128 128
129 }); 129 });
130 130
131 function gologin(){ 131 function gologin() {
132 let path = '/pages/index/login'; 132 let path = '/pages/index/login';
133 uni.navigateTo({ 133 uni.navigateTo({
134 url: path 134 url: path
135 }); 135 });
136 } 136 }
137 137
138 function goCenter() { 138 function goCenter() {
139 let path = '/pages/usercenter/usercenter'; 139 let path = '/pages/usercenter/usercenter';
140 if (checkUserPhone(path)) { 140 uni.navigateTo({
141 uni.navigateTo({ 141 url: path
142 url: path 142 });
143 }); 143
144 } 144 }
145 } 145
146 146 function goAddVip() {
147 function init() { 147 let path = '/pages/personalVip/addVip';
148 uni.showToast({ 148 uni.navigateTo({
149 title:uni.getStorageSync('userType') 149 url: path
150 }) 150 });
151 } 151 }
152 152
153 function goMsgList(){ 153 function goRenew() {
154 154 let path = '/pages/personalVip/renew';
155 } 155 uni.navigateTo({
156 156 url: path
157 function goItem(item) { 157 });
158 if (item.path) { 158 }
159 let path = item.path 159
160 // if (checkUserPhone(path)) { 160 function init() {
161 uni.navigateTo({ 161
162 url: item.path 162 }
163 }); 163
164 // } 164 function goMsgList() {
165 } else { 165 let path = '/pages/index/msgList';
166 uni.showToast({ 166 uni.navigateTo({
167 title: `暂未开放`, 167 url: path
168 icon: 'none' 168 });
169 }); 169 }
170 } 170
171 171 function goItem(item) {
172 } 172 if (item.path) {
173 let path = item.path
174 // if (checkUserPhone(path)) {
175 uni.navigateTo({
176 url: item.path
177 });
178 // }
179 } else {
180 uni.showToast({
181 title: `暂未开放`,
182 icon: 'none'
183 });
184 }
185
186 }
173 </script> 187 </script>
174 <style scope lang="scss"> 188 <style scope lang="scss">
175 .welcome{padding: 55rpx; 189 .welcome {
176 line-height: 55rpx; 190 padding: 55rpx;
177 font-size: 36rpx;} 191 line-height: 55rpx;
192 font-size: 36rpx;
193 }
194
178 .flexbox { 195 .flexbox {
179 display: flex; 196 display: flex;
180 justify-content: space-around; 197 justify-content: space-around;
...@@ -213,27 +230,14 @@ font-size: 36rpx;} ...@@ -213,27 +230,14 @@ font-size: 36rpx;}
213 position: absolute; 230 position: absolute;
214 top: 5px; 231 top: 5px;
215 right: 15px; 232 right: 15px;
216 } 233 }
217 :deep(.uni-section){ 234
218 background-color: transparent; 235 :deep(.uni-section) {
219 } 236 background-color: transparent;
220 :deep(.uni-section .uni-section-header__content){font-size: 36rpx;color: #29343C;} 237 }
221 .more{color: #929AA0;font-size: 24rpx;} 238
222 .msglist{ 239 :deep(.uni-section .uni-section-header__content) {
223 .msgitem{background: #FFFFFF;border-radius: 10rpx;margin-bottom: 25rpx;padding: 30rpx 20rpx 30rpx 50rpx; 240 font-size: 36rpx;
224 position: relative; 241 color: #29343C;
225 .dot{width: 12rpx;display: block;position: absolute;left: 20rpx;top: 40rpx;
226 height: 12rpx;
227 background: #C40F18;
228 border-radius: 50%;}
229 .dot.done{
230 background: #C7C7CD;}
231 .tt{
232 font-size: 28rpx;
233 color: #29343C;}
234 .date{
235 color: #929AA0;
236 font-size: 24rpx;}
237 }
238 } 242 }
239 </style> 243 </style>
...\ No newline at end of file ...\ No newline at end of file
......
1 <template>
2 <view>
3 <z-paging ref="paging" v-model="dataList" @query="queryList">
4 <!-- z-paging默认铺满全屏,此时页面所有view都应放在z-paging标签内,否则会被盖住 -->
5 <!-- 需要固定在页面顶部的view请通过slot="top"插入,包括自定义的导航栏 -->
6 <uni-section title="待办提醒" padding>
7 <view class="msglist">
8 <view class="msgitem" v-for="n in dataList">
9 <text class="dot" :class="n.isRead?'done':''"></text>
10 <view class="tt esp">你有一条会员缴费等待审批,点击前去处理!</view>
11 <view class="date">2023-09-23</view>
12 </view>
13 </view>
14 </uni-section>
15 </z-paging>
16 </view>
17 </template>
18
19 <script setup>
20 import {
21 ref
22 } from 'vue'
23 const dataList = ref([])
24 const paging = ref(null)
25 const total = ref(50)
26 const current = ref(2)
27 function queryList(pageNo, pageSize){
28 paging.value.complete([{},{},{},{},{},{},{},{},{}]);
29 }
30 </script>
31
32 <style lang="scss" scoped>
33 :deep(.uni-section) {
34 background-color: transparent;
35 }
36
37 :deep(.uni-section .uni-section-header__content) {
38 font-size: 36rpx;
39 color: #29343C;
40 }
41 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <template>
2 <view>
3 <uni-segmented-control class="whitebg" :current="current" :values="items" @clickItem="onClickItem"
4 styleType="text" activeColor="#C40F18"></uni-segmented-control>
5 <view class="hasfixedbottom">
6 <view>
7 <uni-forms ref="baseForm" :border="true" :modelValue="baseFormData" label-width="80">
8
9 <view class="nolineform">
10
11 <uni-forms-item label="姓名" required name="realName" v-show="current === 0">
12 <uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
13 v-model="baseFormData.realName" placeholder="请输入姓名" />
14 </uni-forms-item>
15 <uni-forms-item label="证件类型" required name="idcType">
16 <uni-data-select v-model="baseFormData.idcType" :localdata="idcTypeList"
17 @change="changeidcType"></uni-data-select>
18 </uni-forms-item>
19 <uni-forms-item label="证件照" required name="picUrl" v-show="current === 1">
20 <view class="upCard">
21 <uni-file-picker :class="baseFormData.idcFrontImg?'':'op0'"
22 v-model="baseFormData.idcFrontImgObj" @delete="delimgFont" return-type="object"
23 limit="1" @select="upIdCardImgFront"
24 :image-styles="imageStylesZJ"></uni-file-picker>
25 <image v-if="!baseFormData.idcFrontImg" class="sfz" src="@/static/login/sfz.png">
26 </image>
27 </view>
28 </uni-forms-item>
29 <uni-forms-item label="姓名" required name="realName" v-show="current === 1">
30 <uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
31 v-model="baseFormData.realName" placeholder="请输入姓名" />
32 </uni-forms-item>
33 <uni-forms-item label="证件号码" required name="idcCode">
34 <uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
35 v-model="baseFormData.idcCode" @blur="giveBirthDay" placeholder="请输入证件号码" />
36 </uni-forms-item>
37 <uni-forms-item label="性别" required name="sex">
38 <uni-data-checkbox v-model="baseFormData.sex" @change="changeSex" :localdata="sexs" />
39 </uni-forms-item>
40 <uni-forms-item label="出生日期" required name="birth">
41 <uni-datetime-picker type="date" placeholder="YYYY-MM-DD" :border='false'
42 :clear-icon="false" v-model="baseFormData.birth" />
43 </uni-forms-item>
44 <uni-forms-item label="联系方式" name="phone">
45 <uni-easyinput :styles="inputstyle" :placeholderStyle="placeholderStyle"
46 v-model="baseFormData.phone" placeholder="请输入联系方式" />
47 </uni-forms-item>
48
49 <uni-forms-item label="所在地区">
50 <uni-data-picker class="fixUniFormItemStyle" v-model="baseFormData.regionId"
51 :localdata="regionsList" popup-title="请选择所在地区"></uni-data-picker>
52 </uni-forms-item>
53 <uni-forms-item label="详细地址"><uni-easyinput :styles="inputstyle"
54 :placeholderStyle="placeholderStyle" v-model="baseFormData.address"
55 placeholder="请输入详细地址" /></uni-forms-item>
56
57 <uni-forms-item label="头像" required name="picUrl">
58 <uni-file-picker v-model="baseFormData.idcFrontImgObj" @delete="delimgFont"
59 return-type="object" limit="1" @select="upIdCardImgFront"
60 :image-styles="imageStylesTx"></uni-file-picker>
61 </uni-forms-item>
62
63
64
65 </view>
66
67
68
69 </uni-forms>
70
71 </view>
72 <view class="agreeline">
73 <image @click="changeAgree(agree)" v-if="agree" src="@/static/login/xz_dwn@2x.png"></image>
74 <image v-else src="@/static/login/xz2@2x.png"></image>
75 <view>我已阅读<text @click="openpopup">《入会须知》</text></view>
76 </view>
77 </view>
78 <view class="fixedBottom"><button class="btn-red" @click="goSubmit">确 定</button></view>
79
80 <!-- 会员须知 -->
81 <uni-popup ref="popup" type="bottom" background-color="#fff" animation>
82 <view class="tt">入会须知</view>
83 <view class="popBody">
84 ______欢迎您申请成为中国跆拳道协会(以下简称中国跆协)会员,请确保本次申请是经过您本人或监护人授权同意后的自愿行为,请您务必仔细阅读本入会须知。
85 <br />
86 一、中国跆协会员分为个人会员和单位会员。
87 <br />
88 二、成为本协会会员条件:遵守中国跆协章程和协会各项规章制度及相关决议,按期交纳会费,积极支持和参与中国跆拳道事业发展的社会各届人士或地方跆拳道协会、俱乐部、培训机构等,均可自愿申请成为中国跆协会员。
89 <br />
90 三、个人会员为在中国工作和生活的跆拳道爱好者,16 周岁以下应有监护人协助申请,会员须为中国公民。
91 <br />
92 四、会员入会需向所在区域内中国跆协单位会员提出入会申请,并按程序报中国跆协批准,按规定交纳会费。
93 <br />
94 五、会员享有《中国跆拳道协会会员管理办法》规定的会员权利。
95 <br />
96 六、会员应履行《中国跆拳道协会会员管理办法》规定的会员义务。
97 <br />
98 七、凡中国跆协会员,须按照《中国跆拳道协会会员会费标准(2021 版)》按时交纳年度会费。
99 <br />
100 八、会员行为违反《中国跆拳道协会会员管理办法》中规定的,按照相关处罚规定进行处理。
101 <br />
102 九、会员有退会的自由。会员自愿退会应至少提前 3 个月以书面形式通知本协会,并在此之前,妥善解决与本协会及其他会员之间的财务等问题,方可退会。
103 <br />
104 十、其它会员相关内容请查看《中国跆拳道协会会员管理办法》。
105
106 <button @click="closepopup" class="btn-red">我已阅读</button>
107 </view>
108 </uni-popup>
109 </view>
110 </template>
111
112 <script setup>
113 import {
114 ref
115 } from 'vue'
116 const current = ref(0)
117 const popup = ref(null)
118 const agree = ref(true)
119 const regionsList = ref([])
120 const baseFormData = ref({
121 sex: '0'
122 })
123 const baseFormData2 = ref({})
124 const items = ref(['手动录入', '自动录入'])
125 const idcTypeList = ref([{
126 value: '0',
127 text: "身份证"
128 },
129 {
130 value: '1',
131 text: "护照"
132 },
133 {
134 value: '2',
135 text: "其他"
136 },
137 ])
138 const sexs = ref([{
139 text: '女',
140 value: '0'
141 }, {
142 text: '男',
143 value: '1'
144 }])
145 const placeholderStyle = ref('text-align: right;font-size:30rpx')
146 const inputstyle = ref({
147 borderColor: '#fff',
148 fontSize:'30rpx'
149 })
150 const imageStylesTx = ref({
151 width: '210rpx',
152 height: '280rpx',
153 background: {
154 color: '#F4F6FA'
155 },
156 border: {
157 radius: '2px'
158 }
159 });
160
161 const imageStylesZJ = ref({
162 width: '500rpx',
163 height: '316rpx'
164 });
165
166 function onClickItem(e) {
167 if (current.value != e.currentIndex) {
168 current.value = e.currentIndex
169 }
170 }
171
172 function changeAgree(item) {
173 item = !item
174 }
175
176 function upIdCardImgFront(e) {
177 // api.uploadImg(e).then(data => {
178 // baseFormData.value.idcFrontImg = data.data;
179 // });
180 baseFormData.value.idcFrontImg = e
181 }
182
183 function delimgFont(n) {
184 baseFormData.value.idcFrontImg = '';
185 }
186
187 function giveBirthDay() {
188 // 判断身份证正确性/赋值生日
189 if (baseFormData.value.idcType == 0) {
190 if (!(/(^\d{15}$)|(^\d{17}([0-9]|X)$)/.test(baseFormData.value.idcCode))) {
191 uni.showToast({
192 title: '请输入正确的身份证号码',
193 duration: 2000,
194 icon: 'none'
195 })
196 } else {
197 let tmpStr = "";
198 if (baseFormData.value.idcCode.length == 15) {
199 tmpStr = baseFormData.value.idcCode.substring(6, 12);
200 tmpStr = "19" + tmpStr;
201 tmpStr = tmpStr.substring(0, 4) + "-" + tmpStr.substring(4, 6) + "-" + tmpStr.substring(6)
202
203 } else {
204 tmpStr = baseFormData.value.idcCode.substring(6, 14);
205 tmpStr = tmpStr.substring(0, 4) + "-" + tmpStr.substring(4, 6) + "-" + tmpStr.substring(6)
206
207 }
208
209 baseFormData.value.birth = tmpStr
210 const res = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X)$/
211 if (baseFormData.value.idcCode && res.test(baseFormData.value.idcCode)) {
212 const genderCode = baseFormData.value.idcCode.charAt(16)
213 if (parseInt(genderCode) % 2 == 0) {
214 baseFormData.value.sex = '0'
215 } else {
216 baseFormData.value.sex = '1'
217 }
218 }
219 }
220 }
221 }
222
223 function changeSex(e) {
224 console.log("性别:", e.detail.value)
225 }
226
227 function changeidcType(e) {
228 console.log("证件:", e)
229 if (e) {
230 baseFormData.value.idcTypeStr = idcTypeList[e].text
231 }
232 }
233
234 function openpopup() {
235 popup.value.open()
236 }
237
238 function closepopup() {
239 popup.value.close()
240 }
241
242 function goSubmit() {
243 if(!agree.value){
244 uni.showToast({
245 title: '请阅知入会须知',
246 duration: 2000
247 });
248 }
249 }
250 </script>
251
252 <style lang="scss" scoped>
253 :deep(.segmented-control){height:100rpx;}
254 :deep(.segmented-control__text){line-height: 2;font-size: 30rpx;}
255 .tt{text-align: center;font-size: 30rpx;padding: 40rpx 0 0;}
256 .popBody {
257 font-size: 28rpx;
258 line-height: 1.5;
259 font-family: 华文仿宋;
260 height: 80vh;
261 overflow: auto;padding: 30rpx;
262 .btn-red{margin: 50rpx 0 30rpx;}
263 }
264
265 .agreeline {
266 padding: 20rpx 40rpx;
267 box-sizing: border-box;
268 display: flex;
269 font-size: 30rpx;
270
271 text {
272 color: #014A9F;
273 }
274
275 image {
276 width: 40rpx;
277 height: 40rpx;
278 margin-right: 20rpx;
279 }
280 }
281
282 .upCard {
283 position: relative;
284 width: 500rpx;
285 height: 316rpx;
286
287 .uni-file-picker {
288 position: absolute;
289 z-index: 1;
290 }
291
292 .sfz {
293 width: 500rpx;
294 height: 316rpx;
295 }
296 }
297
298 .op0 {
299 opacity: 0;
300 }
301 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <template>
2 <view>
3 <view class="searchbar">
4 <uni-easyinput placeholderStyle="font-size:30rpx" :input-border="false" prefixIcon="search" v-model="query.name" placeholder="搜索姓名或证件号码" @iconClick="iconClick">
5 </uni-easyinput>
6 <view class="invertedbtn-red" @click="goVipList">+ 添加会员</view>
7 </view>
8
9 <uni-swipe-action>
10 <uni-swipe-action-item class="personitem" v-for="n in choseList">
11 <view class="content-box">
12 <view class="flexbox">
13 <view class="colorful">{{n.name.slice(0,1)}}</view>
14 {{n.name}}
15 </view>
16 <view class="flexbox">
17 <uni-number-box :min="1" v-model="n.year" :max="5"></uni-number-box>
18 </view>
19
20 </view>
21 <template v-slot:right>
22 <view class="slot-button">
23 <view>
24 <uni-icons type="trash-filled" color="#fff" size="20"></uni-icons>
25 <text class="slot-button-text">删除</text>
26 </view>
27 </view>
28 </template>
29 </uni-swipe-action-item>
30 </uni-swipe-action>
31
32 <view class="nodata" v-if="choseList.length==0">
33 <image mode="aspectFit" src="/static/nodata.png"></image>
34 <button class="btn-red" @click="goVipList">+ 添加会员</button>
35 </view>
36 </view>
37 </template>
38
39 <script setup>
40 import {
41 ref
42 } from 'vue'
43 const query = ref({})
44 const choseList = ref([{name:'张三',year:'1'},{name:'李三',year:'1'}])
45 const yearlist = ref([{text:'一年',value:'1'},{text:'2年',value:'2'}])
46
47 function goVipList(){
48 let path = '/pages/personalVip/vipList';
49 uni.navigateTo({
50 url: path
51 });
52 }
53 function changeYear(){
54
55 }
56 </script>
57
58 <style scoped lang="scss">
59 .searchbar{display: flex;align-items: center;padding: 25rpx;box-sizing: border-box;
60 .invertedbtn-red{margin-left: 15rpx;font-size: 30rpx;
61 padding: 16rpx 20rpx;box-sizing: border-box;border-radius: 50rpx;background-color: #fff;}
62 :deep(.uni-easyinput .uni-easyinput__content){border-radius: 35rpx;border: none;
63 height: 70rpx;}
64 :deep(.uni-easyinput__content-input){
65 font-size: 26rpx;}
66 }
67
68 .slot-button {display: flex;align-items: center;
69 padding: 0 20px;text-align: center;
70 background-color: #E60012;
71 }
72
73 .slot-button-text {
74 color: #ffffff;display: block;
75 font-size: 14px;
76 }
77 .personitem{background: #fff;box-sizing: border-box;
78 margin-bottom: 30rpx;
79 .content-box{display: flex;align-items: center;padding:16rpx;border-radius: 15rpx;
80 justify-content: space-between;
81 .noborder{border: none;
82 :deep(.uni-select){border: none;text-align: right;}
83 }
84 }
85 .flexbox{align-items: center;}
86 &:nth-child(3n) .colorful{background: #014A9F;}
87 &:nth-child(3n+1) .colorful{background: #AD181F;}
88 &:nth-child(3n+2) .colorful{background: #D3B267;}
89 }
90 .colorful{width: 100rpx;margin-right: 14rpx;
91 height: 100rpx;line-height: 100rpx;
92 font-size: 44rpx;
93 color: #fff;text-align: center;
94 border-radius: 50%;}
95 </style>
1 <template>
2 <view>
3 <view class="searchbar">
4 <uni-easyinput placeholderStyle="font-size:30rpx" :input-border="false" prefixIcon="search"
5 v-model="query.name" placeholder="搜索姓名或证件号码" @iconClick="iconClick">
6 </uni-easyinput>
7 </view>
8 <view>会员列表</view>
9 <view class="indexboxre">
10 <uni-indexed-list :options="list" :showSelect="true" @click="bindClick"></uni-indexed-list>
11 </view>
12
13 <view class="fixedBottom">
14
15 <button class="btn-red" @click="goAddRenew">添 加</button>
16 </view>
17
18 </view>
19 </template>
20
21 <script setup>
22 import {
23 ref
24 } from 'vue'
25 const query = ref({})
26 const list = ref([{
27 "letter": "A",
28 "data": [
29 "阿克苏机场",
30 "阿拉山口机场",
31 "阿勒泰机场",
32 "阿里昆莎机场",
33 "安庆天柱山机场",
34 "澳门国际机场"
35 ]
36 }, {
37 "letter": "B",
38 "data": [
39 "保山机场",
40 "包头机场",
41 "北海福成机场",
42 "北京南苑机场",
43 "北京首都国际机场"
44 ]
45 }, {
46 "letter": "C",
47 "data": [
48 "保山机场",
49 "包头机场",
50 "北海福成机场",
51 "北京南苑机场",
52 "北京首都国际机场"
53 ]
54 }, {
55 "letter": "D",
56 "data": [
57 "保山机场",
58 "北京首都国际机场"
59 ]
60 }, {
61 "letter": "G",
62 "data": [
63 "保山机场",
64 "北京首都国际机场"
65 ]
66 }, {
67 "letter": "Z",
68 "data": [
69 "保山机场",
70 "北京首都国际机场"
71 ]
72 }])
73
74 function bindClick(e) {
75 console.log('点击item,返回数据' + JSON.stringify(e))
76 }
77 function goAddRenew(){
78 uni.navigateBack()
79 }
80 </script>
81
82 <style scoped lang="scss">
83 .indexboxre{position: relative;height:calc(100vh - 300rpx);}
84 .searchbar{display: flex;align-items: center;padding: 25rpx;box-sizing: border-box;
85 :deep(.uni-easyinput .uni-easyinput__content){border-radius: 35rpx;border: none;
86 height: 70rpx;}
87 :deep(.uni-easyinput__content-input){
88 font-size: 26rpx;}
89 }
90 </style>
...\ No newline at end of file ...\ No newline at end of file

17.7 KB | W: | H:

73.1 KB | W: | H:

static/nodata.png
static/nodata.png
static/nodata.png
static/nodata.png
  • 2-up
  • Swipe
  • Onion skin
1 ## 1.2.1(2021-11-22)
2 - 修复 vue3中某些scss变量无法找到的问题
3 ## 1.2.0(2021-11-19)
4 - 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
5 - 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-indexed-list](https://uniapp.dcloud.io/component/uniui/uni-indexed-list)
6 ## 1.1.0(2021-07-30)
7 - 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
8 ## 1.0.11(2021-05-12)
9 - 新增 组件示例地址
10 ## 1.0.10(2021-04-21)
11 - 优化 添加依赖 uni-icons, 导入后自动下载依赖
12 ## 1.0.9(2021-02-05)
13 - 优化 组件引用关系,通过uni_modules引用组件
14
15 ## 1.0.8(2021-02-05)
16 - 调整为uni_modules目录规范
17 - 新增 支持 PC 端
1 <template>
2 <view>
3 <!-- <view v-if="loaded || list.itemIndex < 15" class="uni-indexed-list__title-wrapper">
4 <text v-if="list.items && list.items.length > 0" class="uni-indexed-list__title">{{ list.key }}</text>
5 </view> -->
6 <view v-if="(loaded || list.itemIndex < 15) && list.items && list.items.length > 0" class="uni-indexed-list__list">
7 <view v-for="(item, index) in list.items" :key="index" class="uni-indexed-list__item" hover-class="uni-indexed-list__item--hover">
8 <view class="uni-indexed-list__item-container" @click="onClick(idx, index)">
9 <view class="uni-indexed-list__item-border" :class="{'uni-indexed-list__item-border--last':index===list.items.length-1}">
10 <view v-if="showSelect" style="margin-right: 20rpx;">
11 <uni-icons :type="item.checked ? 'checkbox-filled' : 'circle'" :color="item.checked ? '#AD181F' : '#C0C0C0'" size="24" />
12 </view>
13
14 <text class="uni-indexed-list__item-content">{{ item.name }}</text>
15 </view>
16 </view>
17 </view>
18 </view>
19 </view>
20 </template>
21
22 <script>
23 export default {
24 name: 'UniIndexedList',
25 emits:['itemClick'],
26 props: {
27 loaded: {
28 type: Boolean,
29 default: false
30 },
31 idx: {
32 type: Number,
33 default: 0
34 },
35 list: {
36 type: Object,
37 default () {
38 return {}
39 }
40 },
41 showSelect: {
42 type: Boolean,
43 default: false
44 }
45 },
46 methods: {
47 onClick(idx, index) {
48 this.$emit("itemClick", {
49 idx,
50 index
51 })
52 }
53 }
54 }
55 </script>
56
57 <style lang="scss" scoped>
58 .uni-indexed-list__list {
59 // background-color: $uni-bg-color;
60 /* #ifndef APP-NVUE */
61 display: flex;
62 /* #endif */
63 flex-direction: column;
64
65 }
66
67 .uni-indexed-list__item {
68 font-size: 14px;
69 /* #ifndef APP-NVUE */
70 display: flex;
71 /* #endif */
72 flex: 1;
73 flex-direction: row;
74 justify-content: space-between;
75 align-items: center;
76 background-color: #fff;
77 margin-bottom: 26rpx;
78 }
79
80 .uni-indexed-list__item-container {
81 padding-left: 15px;
82 flex: 1;
83 position: relative;
84 /* #ifndef APP-NVUE */
85 display: flex;
86 box-sizing: border-box;
87 /* #endif */
88 flex-direction: row;
89 justify-content: space-between;
90 align-items: center;
91 /* #ifdef H5 */
92 cursor: pointer;
93 /* #endif */
94 }
95
96 .uni-indexed-list__item-border {
97 flex: 1;
98 position: relative;
99 /* #ifndef APP-NVUE */
100 display: flex;
101 box-sizing: border-box;
102 /* #endif */
103 flex-direction: row;
104 justify-content: space-between;
105 align-items: center;
106 height: 50px;
107 padding: 25px;
108 padding-left: 0;
109 // border-bottom-style: solid;
110 // border-bottom-width: 1px;
111 // border-bottom-color: #DEDEDE;
112 }
113
114 .uni-indexed-list__item-border--last {
115 border-bottom-width: 0px;
116 }
117
118 .uni-indexed-list__item-content {
119 flex: 1;
120 font-size: 14px;
121 color: #191919;
122 }
123
124 .uni-indexed-list {
125 /* #ifndef APP-NVUE */
126 display: flex;
127 /* #endif */
128 flex-direction: row;
129 }
130
131 .uni-indexed-list__title-wrapper {
132 /* #ifndef APP-NVUE */
133 display: flex;
134 width: 100%;
135 /* #endif */
136 background-color: #f7f7f7;
137 }
138
139 .uni-indexed-list__title {
140 padding: 6px 12px;
141 line-height: 24px;
142 font-size: 16px;
143 font-weight: 500;
144 }
145 </style>
1 <template>
2 <view class="uni-indexed-list" ref="list" id="list">
3 <!-- #ifdef APP-NVUE -->
4 <list class="uni-indexed-list__scroll" scrollable="true" show-scrollbar="false">
5 <cell v-for="(list, idx) in lists" :key="idx" :ref="'uni-indexed-list-' + idx">
6 <!-- #endif -->
7 <!-- #ifndef APP-NVUE -->
8 <scroll-view :scroll-into-view="scrollViewId" class="uni-indexed-list__scroll" scroll-y>
9 <view v-for="(list, idx) in lists" :key="idx" :id="'uni-indexed-list-' + idx">
10 <!-- #endif -->
11 <indexed-list-item :list="list" :loaded="loaded" :idx="idx" :showSelect="showSelect"
12 @itemClick="onClick"></indexed-list-item>
13 <!-- #ifndef APP-NVUE -->
14 </view>
15 </scroll-view>
16 <!-- #endif -->
17 <!-- #ifdef APP-NVUE -->
18 </cell>
19 </list>
20 <!-- #endif -->
21 <view class="uni-indexed-list__menu" @touchstart="touchStart" @touchmove.stop.prevent="touchMove"
22 @touchend="touchEnd" @mousedown.stop="mousedown" @mousemove.stop.prevent="mousemove"
23 @mouseleave.stop="mouseleave">
24 <view v-for="(list, key) in lists" :key="key" class="uni-indexed-list__menu-item"
25 :class="touchmoveIndex == key ? 'uni-indexed-list__menu--active' : ''">
26 <text class="uni-indexed-list__menu-text"
27 :class="touchmoveIndex == key ? 'uni-indexed-list__menu-text--active' : ''">{{ list.key }}</text>
28 </view>
29 </view>
30 <view v-if="touchmove" class="uni-indexed-list__alert-wrapper">
31 <text class="uni-indexed-list__alert">{{ lists[touchmoveIndex].key }}</text>
32 </view>
33 </view>
34 </template>
35 <script>
36 import indexedListItem from './uni-indexed-list-item.vue'
37 // #ifdef APP-NVUE
38 const dom = weex.requireModule('dom');
39 // #endif
40 // #ifdef APP-PLUS
41 function throttle(func, delay) {
42 var prev = Date.now();
43 return function() {
44 var context = this;
45 var args = arguments;
46 var now = Date.now();
47 if (now - prev >= delay) {
48 func.apply(context, args);
49 prev = Date.now();
50 }
51 }
52 }
53
54 function touchMove(e) {
55 let pageY = e.touches[0].pageY
56 let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
57 if (this.touchmoveIndex === index) {
58 return false
59 }
60 let item = this.lists[index]
61 if (item) {
62 // #ifndef APP-NVUE
63 this.scrollViewId = 'uni-indexed-list-' + index
64 this.touchmoveIndex = index
65 // #endif
66 // #ifdef APP-NVUE
67 dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
68 animated: false
69 })
70 this.touchmoveIndex = index
71 // #endif
72 }
73 }
74 const throttleTouchMove = throttle(touchMove, 40)
75 // #endif
76
77 /**
78 * IndexedList 索引列表
79 * @description 用于展示索引列表
80 * @tutorial https://ext.dcloud.net.cn/plugin?id=375
81 * @property {Boolean} showSelect = [true|false] 展示模式
82 * @value true 展示模式
83 * @value false 选择模式
84 * @property {Object} options 索引列表需要的数据对象
85 * @event {Function} click 点击列表事件 ,返回当前选择项的事件对象
86 * @example <uni-indexed-list options="" showSelect="false" @click=""></uni-indexed-list>
87 */
88 export default {
89 name: 'UniIndexedList',
90 components: {
91 indexedListItem
92 },
93 emits: ['click'],
94 props: {
95 options: {
96 type: Array,
97 default () {
98 return []
99 }
100 },
101 showSelect: {
102 type: Boolean,
103 default: false
104 }
105 },
106 data() {
107 return {
108 lists: [],
109 winHeight: 0,
110 itemHeight: 0,
111 winOffsetY: 0,
112 touchmove: false,
113 touchmoveIndex: -1,
114 scrollViewId: '',
115 touchmovable: true,
116 loaded: false,
117 isPC: false
118 }
119 },
120 watch: {
121 options: {
122 handler: function() {
123 this.setList()
124 },
125 deep: true
126 }
127 },
128 mounted() {
129 // #ifdef H5
130 this.isPC = this.IsPC()
131 // #endif
132 setTimeout(() => {
133 this.setList()
134 }, 50)
135 setTimeout(() => {
136 this.loaded = true
137 }, 300);
138 },
139 methods: {
140 setList() {
141 let index = 0;
142 this.lists = []
143 this.options.forEach((value, index) => {
144 if (value.data.length === 0) {
145 return
146 }
147 let indexBefore = index
148 let items = value.data.map(item => {
149 let obj = {}
150 obj['key'] = value.letter
151 obj['name'] = item
152 obj['itemIndex'] = index
153 index++
154 obj.checked = item.checked ? item.checked : false
155 return obj
156 })
157 this.lists.push({
158 title: value.letter,
159 key: value.letter,
160 items: items,
161 itemIndex: indexBefore
162 })
163 })
164 // #ifndef APP-NVUE
165 uni.createSelectorQuery()
166 .in(this)
167 .select('#list')
168 .boundingClientRect()
169 .exec(ret => {
170 this.winOffsetY = ret[0].top
171 this.winHeight = ret[0].height
172 this.itemHeight = this.winHeight / this.lists.length
173 })
174 // #endif
175 // #ifdef APP-NVUE
176 dom.getComponentRect(this.$refs['list'], (res) => {
177 this.winOffsetY = res.size.top
178 this.winHeight = res.size.height
179 this.itemHeight = this.winHeight / this.lists.length
180 })
181 // #endif
182 },
183 touchStart(e) {
184 this.touchmove = true
185 let pageY = this.isPC ? e.pageY : e.touches[0].pageY
186 let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
187 let item = this.lists[index]
188 if (item) {
189 this.scrollViewId = 'uni-indexed-list-' + index
190 this.touchmoveIndex = index
191 // #ifdef APP-NVUE
192 dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
193 animated: false
194 })
195 // #endif
196 }
197 },
198 touchMove(e) {
199 // #ifndef APP-PLUS
200 let pageY = this.isPC ? e.pageY : e.touches[0].pageY
201 let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
202 if (this.touchmoveIndex === index) {
203 return false
204 }
205 let item = this.lists[index]
206 if (item) {
207 this.scrollViewId = 'uni-indexed-list-' + index
208 this.touchmoveIndex = index
209 }
210 // #endif
211 // #ifdef APP-PLUS
212 throttleTouchMove.call(this, e)
213 // #endif
214 },
215 touchEnd() {
216 this.touchmove = false
217 // this.touchmoveIndex = -1
218 },
219
220 /**
221 * 兼容 PC @tian
222 */
223
224 mousedown(e) {
225 if (!this.isPC) return
226 this.touchStart(e)
227 },
228 mousemove(e) {
229 if (!this.isPC) return
230 this.touchMove(e)
231 },
232 mouseleave(e) {
233 if (!this.isPC) return
234 this.touchEnd(e)
235 },
236
237 // #ifdef H5
238 IsPC() {
239 var userAgentInfo = navigator.userAgent;
240 var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
241 var flag = true;
242 for (let v = 0; v < Agents.length - 1; v++) {
243 if (userAgentInfo.indexOf(Agents[v]) > 0) {
244 flag = false;
245 break;
246 }
247 }
248 return flag;
249 },
250 // #endif
251
252
253 onClick(e) {
254 let {
255 idx,
256 index
257 } = e
258 let obj = {}
259 for (let key in this.lists[idx].items[index]) {
260 obj[key] = this.lists[idx].items[index][key]
261 }
262 let select = []
263 if (this.showSelect) {
264 this.lists[idx].items[index].checked = !this.lists[idx].items[index].checked
265 this.lists.forEach((value, idx) => {
266 value.items.forEach((item, index) => {
267 if (item.checked) {
268 let obj = {}
269 for (let key in this.lists[idx].items[index]) {
270 obj[key] = this.lists[idx].items[index][key]
271 }
272 select.push(obj)
273 }
274 })
275 })
276 }
277 this.$emit('click', {
278 item: obj,
279 select: select
280 })
281 }
282 }
283 }
284 </script>
285 <style lang="scss" scoped>
286 .uni-indexed-list {
287 position: absolute;
288 left: 0;
289 top: 0;
290 right: 0;
291 bottom: 0;
292 /* #ifndef APP-NVUE */
293 display: flex;
294 /* #endif */
295 flex-direction: row;
296 }
297
298 .uni-indexed-list__scroll {
299 flex: 1;
300 }
301
302 .uni-indexed-list__menu {
303 width: 24px;box-shadow: 0 0 2px #333;z-index: 1;
304 /* #ifndef APP-NVUE */
305 display: flex;
306 /* #endif */
307 flex-direction: column;
308 background: #FFFFFF;
309 border-radius: 15rpx;
310 // height: fit-content;
311 }
312
313 .uni-indexed-list__menu-item {
314 /* #ifndef APP-NVUE */
315 display: flex;
316 /* #endif */
317 flex: 1;
318 align-items: center;
319 justify-content: center;
320 /* #ifdef H5 */
321 cursor: pointer;
322 /* #endif */
323 margin: 15px 0;
324 }
325
326 .uni-indexed-list__menu-text {
327 font-size: 12px;
328 text-align: center;
329 color: #434343;
330 }
331
332 .uni-indexed-list__menu--active {
333 // background-color: rgb(200, 200, 200);
334 }
335
336 .uni-indexed-list__menu--active {}
337
338 .uni-indexed-list__menu-text--active {
339 border-radius: 16px;
340 width: 16px;
341 height: 16px;
342 line-height: 16px;
343 color: #AD181F;
344 }
345
346 .uni-indexed-list__alert-wrapper {
347 position: absolute;
348 left: 0;
349 top: 0;
350 right: 0;
351 bottom: 0;
352 /* #ifndef APP-NVUE */
353 display: flex;
354 /* #endif */
355 flex-direction: row;
356 align-items: center;
357 justify-content: center;
358 }
359
360 .uni-indexed-list__alert {
361 width: 80px;
362 height: 80px;
363 border-radius: 80px;
364 text-align: center;
365 line-height: 80px;
366 font-size: 35px;
367 color: #fff;
368 background-color: rgba(0, 0, 0, 0.5);
369 }
370 </style>
1 {
2 "id": "uni-indexed-list",
3 "displayName": "uni-indexed-list 索引列表",
4 "version": "1.2.1",
5 "description": "索引列表组件,右侧带索引的列表,方便快速定位到具体内容,通常用于城市/机场选择等场景",
6 "keywords": [
7 "uni-ui",
8 "索引列表",
9 "索引",
10 "列表"
11 ],
12 "repository": "https://github.com/dcloudio/uni-ui",
13 "engines": {
14 "HBuilderX": ""
15 },
16 "directories": {
17 "example": "../../temps/example_temps"
18 },
19 "dcloudext": {
20 "category": [
21 "前端组件",
22 "通用组件"
23 ],
24 "sale": {
25 "regular": {
26 "price": "0.00"
27 },
28 "sourcecode": {
29 "price": "0.00"
30 }
31 },
32 "contact": {
33 "qq": ""
34 },
35 "declaration": {
36 "ads": "无",
37 "data": "无",
38 "permissions": "无"
39 },
40 "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
41 },
42 "uni_modules": {
43 "dependencies": [
44 "uni-scss",
45 "uni-icons"
46 ],
47 "encrypt": [],
48 "platforms": {
49 "cloud": {
50 "tcb": "y",
51 "aliyun": "y"
52 },
53 "client": {
54 "App": {
55 "app-vue": "y",
56 "app-nvue": "y"
57 },
58 "H5-mobile": {
59 "Safari": "y",
60 "Android Browser": "y",
61 "微信浏览器(Android)": "y",
62 "QQ浏览器(Android)": "y"
63 },
64 "H5-pc": {
65 "Chrome": "y",
66 "IE": "y",
67 "Edge": "y",
68 "Firefox": "y",
69 "Safari": "y"
70 },
71 "小程序": {
72 "微信": "y",
73 "阿里": "y",
74 "百度": "y",
75 "字节跳动": "y",
76 "QQ": "y"
77 },
78 "快应用": {
79 "华为": "u",
80 "联盟": "u"
81 },
82 "Vue": {
83 "vue2": "y",
84 "vue3": "y"
85 }
86 }
87 }
88 }
89 }
1
2
3 ## IndexedList 索引列表
4 > **组件名:uni-indexed-list**
5 > 代码块: `uIndexedList`
6
7
8 用于展示索引列表。
9
10 ### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-indexed-list)
11 #### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
1 ## 2.5.9(2023-09-25)
2 1.`新增` `doInsertVirtualListItem`方法,支持在非固定高度虚拟列表新插入数据。
3 2.`新增` `refreshToPage`方法,支持刷新列表数据至指定页。
4 3.`新增` 请求失败Promise在reject时添加具体错误信息,组件内部调用reload时添加catch处理。
5 4.`修复` 滑动切换选项卡+吸顶模式在抖音小程序中下拉刷新状态无法结束的问题。
6 5.`修复` `slot="left"`&`slot="right"`可能出现的高度过高超出页面的问题。
7 6.`修复` nvue中可能出现的切换空数据图后切换回列表数据时底部加载更多在列表顶部的问题。
8 7.`修复` 虚拟列表固定高度cell超出屏幕高度时,底部有异常空白的问题。
9 8.`修复` i18n国际化可能导致的下拉刷新view未能正常显示的问题。
10 9.`修复` 设置`show-refresher-when-reload``refresher-threshold`且自定义插入下拉刷新view后可能出现的闪现旧的view的问题。
11 10.`修复` 基于z-paging封装个性化分页组件demo在vue3中列表不展示的问题。
12 11.`修复` `didUpdateVirtualListCell``didDeleteVirtualListCell`高度缓存计算不正确的问题。
13 12.`修复` `empty-view-super-style`无效的问题。
14 13.`修复` vue3+ios中长时间进入后台打开后可能出现的下拉刷新展示位置向下偏移的问题。
15 14.`修复` 在安卓+nvue中,z-paging高度比较低时,空数据图被裁剪的问题。
16 15.`优化` `endRefresh`触发时,也终止系统默认下拉刷新状态。
17 16.`优化` slot插入的上下view禁止touchmove事件冒泡。
18 17.`优化` `addDataFromTop`方法传入的数组倒序处理。
19 18.`优化` 列表滚动性能,减少js层和wxs层信息交互次数。
20 19.`优化` 从列表滚动过渡到下拉刷新时的细节处理以解决由此可能引发的下拉刷新抖动问题。
21 20.`优化` 聊天记录模式demo细节。
22
1 <!-- z-paging -->
2 <!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
3 <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
4 <!-- 反馈QQ群:790460711 -->
5
6 <!-- z-paging-cell,用于在nvue中使用cell包裹,vue中使用view包裹 -->
7 <template>
8 <!-- #ifdef APP-NVUE -->
9 <cell :style="[cellStyle]">
10 <slot />
11 </cell>
12 <!-- #endif -->
13 <!-- #ifndef APP-NVUE -->
14 <view :style="[cellStyle]">
15 <slot />
16 </view>
17 <!-- #endif -->
18 </template>
19
20 <script>
21 export default {
22 name: "z-paging-cell",
23 props: {
24 //cellStyle
25 cellStyle: {
26 type: Object,
27 default: function() {
28 return {}
29 }
30 }
31 }
32 }
33 </script>
34
1 <!-- z-paging -->
2 <!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
3 <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
4 <!-- 反馈QQ群:790460711 -->
5
6 <!-- 空数据占位view,此组件支持easycom规范,可以在项目中直接引用 -->
7 <template>
8 <view :class="{'zp-container':true,'zp-container-fixed':emptyViewFixed}" :style="[finalEmptyViewStyle]" @click="emptyViewClick">
9 <view class="zp-main">
10 <image v-if="!emptyViewImg.length" class="zp-main-image" :style="[emptyViewImgStyle]" :src="emptyImg" />
11 <image v-else class="zp-main-image" mode="aspectFit" :style="[emptyViewImgStyle]" :src="emptyViewImg" />
12 <text class="zp-main-title" :style="[emptyViewTitleStyle]">{{emptyViewText}}</text>
13 <text v-if="showEmptyViewReload" class="zp-main-error-btn" :style="[emptyViewReloadStyle]" @click.stop="reloadClick">{{emptyViewReloadText}}</text>
14 </view>
15 </view>
16 </template>
17
18 <script>
19 import zStatic from '../z-paging/js/z-paging-static'
20 export default {
21 name: "z-paging-empty-view",
22 data() {
23 return {
24
25 };
26 },
27 props: {
28 //空数据描述文字
29 emptyViewText: {
30 type: String,
31 default: '没有数据哦~'
32 },
33 //空数据图片
34 emptyViewImg: {
35 type: String,
36 default: ''
37 },
38 //是否显示空数据图重新加载按钮
39 showEmptyViewReload: {
40 type: Boolean,
41 default: false
42 },
43 //空数据点击重新加载文字
44 emptyViewReloadText: {
45 type: String,
46 default: '重新加载'
47 },
48 //是否是加载失败
49 isLoadFailed: {
50 type: Boolean,
51 default: false
52 },
53 //空数据图样式
54 emptyViewStyle: {
55 type: Object,
56 default: function() {
57 return {}
58 }
59 },
60 //空数据图img样式
61 emptyViewImgStyle: {
62 type: Object,
63 default: function() {
64 return {}
65 }
66 },
67 //空数据图描述文字样式
68 emptyViewTitleStyle: {
69 type: Object,
70 default: function() {
71 return {}
72 }
73 },
74 //空数据图重新加载按钮样式
75 emptyViewReloadStyle: {
76 type: Object,
77 default: function() {
78 return {}
79 }
80 },
81 //空数据图z-index
82 emptyViewZIndex: {
83 type: Number,
84 default: 9
85 },
86 //空数据图片是否使用fixed布局并铺满z-paging
87 emptyViewFixed: {
88 type: Boolean,
89 default: true
90 }
91 },
92 computed: {
93 emptyImg() {
94 return this.isLoadFailed ? zStatic.base64Error : zStatic.base64Empty;
95 },
96 finalEmptyViewStyle(){
97 this.emptyViewStyle['z-index'] = this.emptyViewZIndex;
98 return this.emptyViewStyle;
99 }
100 },
101 methods: {
102 reloadClick() {
103 this.$emit('reload');
104 },
105 emptyViewClick() {
106 this.$emit('viewClick');
107 }
108 }
109 }
110 </script>
111
112 <style scoped>
113 .zp-container{
114 /* #ifndef APP-NVUE */
115 display: flex;
116 /* #endif */
117 align-items: center;
118 justify-content: center;
119 }
120 .zp-container-fixed {
121 /* #ifndef APP-NVUE */
122 position: absolute;
123 top: 0;
124 left: 0;
125 width: 100%;
126 height: 100%;
127 /* #endif */
128 /* #ifdef APP-NVUE */
129 flex: 1;
130 /* #endif */
131 }
132
133 .zp-main{
134 /* #ifndef APP-NVUE */
135 display: flex;
136 /* #endif */
137 flex-direction: column;
138 align-items: center;
139 padding: 50rpx 0rpx;
140 }
141
142 .zp-main-image {
143 width: 200rpx;
144 height: 200rpx;
145 }
146
147 .zp-main-title {
148 font-size: 26rpx;
149 color: #aaaaaa;
150 text-align: center;
151 margin-top: 10rpx;
152 }
153
154 .zp-main-error-btn {
155 font-size: 26rpx;
156 padding: 8rpx 24rpx;
157 border: solid 1px #dddddd;
158 border-radius: 6rpx;
159 color: #aaaaaa;
160 margin-top: 50rpx;
161 }
162 </style>
1 <!-- z-paging -->
2 <!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
3 <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
4 <!-- 反馈QQ群:790460711 -->
5
6 <!-- 滑动切换选项卡swiper-item,此组件支持easycom规范,可以在项目中直接引用 -->
7 <template>
8 <view class="zp-swiper-item-container">
9 <z-paging ref="paging" :fixed="false"
10 :auto="false" :useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle"
11 :preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol"
12 @query="_queryList" @listChange="_updateList" style="height: 100%;">
13 <slot />
14 <template #header>
15 <slot name="header"/>
16 </template>
17 <template #cell="{item,index}">
18 <slot name="cell" :item="item" :index="index"/>
19 </template>
20 <template #footer>
21 <slot name="footer"/>
22 </template>
23 </z-paging>
24 </view>
25 </template>
26
27 <script>
28 import zPaging from '../z-paging/z-paging'
29 export default {
30 name: "z-paging-swiper-item",
31 components: { zPaging },
32 data() {
33 return {
34 firstLoaded: false
35 }
36 },
37 props: {
38 //当前组件的index,也就是当前组件是swiper中的第几个
39 tabIndex: {
40 type: Number,
41 default: function() {
42 return 0
43 }
44 },
45 //当前swiper切换到第几个index
46 currentIndex: {
47 type: Number,
48 default: function() {
49 return 0
50 }
51 },
52 //是否使用虚拟列表,默认为否
53 useVirtualList: {
54 type: Boolean,
55 default: false
56 },
57 //是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true
58 useInnerList: {
59 type: Boolean,
60 default: false
61 },
62 //内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项
63 cellKeyName: {
64 type: String,
65 default: ''
66 },
67 //innerList样式
68 innerListStyle: {
69 type: Object,
70 default: function() {
71 return {};
72 }
73 },
74 //预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题
75 preloadPage: {
76 type: [Number, String],
77 default: 12
78 },
79 //虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。
80 cellHeightMode: {
81 type: String,
82 default: 'fixed'
83 },
84 //虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2
85 virtualListCol: {
86 type: [Number, String],
87 default: 1
88 },
89 //虚拟列表scroll取样帧率,默认为60,过高可能出现卡顿等问题
90 virtualScrollFps: {
91 type: [Number, String],
92 default: 60
93 },
94 },
95 watch: {
96 currentIndex: {
97 handler(newVal, oldVal) {
98 if (newVal === this.tabIndex) {
99 //懒加载,当滑动到当前的item时,才去加载
100 if (!this.firstLoaded) {
101 this.$nextTick(()=>{
102 let delay = 5;
103 // #ifdef MP-TOUTIAO
104 delay = 100;
105 // #endif
106 setTimeout(() => {
107 this.$refs.paging.reload().catch(() => {});
108 }, delay);
109 })
110 }
111 }
112 },
113 immediate: true
114 }
115 },
116 methods: {
117 reload(data) {
118 return this.$refs.paging.reload(data);
119 },
120 complete(data) {
121 this.firstLoaded = true;
122 return this.$refs.paging.complete(data);
123 },
124 _queryList(pageNo, pageSize, from) {
125 this.$emit('query', pageNo, pageSize, from);
126 },
127 _updateList(list) {
128 this.$emit('updateList', list);
129 }
130 }
131 }
132 </script>
133
134 <style scoped>
135 .zp-swiper-item-container {
136 /* #ifndef APP-NVUE */
137 height: 100%;
138 /* #endif */
139 /* #ifdef APP-NVUE */
140 flex: 1;
141 /* #endif */
142 }
143 </style>
1 <!-- z-paging -->
2 <!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
3 <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
4 <!-- 反馈QQ群:790460711 -->
5
6 <!-- 滑动切换选项卡swiper容器,此组件支持easycom规范,可以在项目中直接引用 -->
7 <template>
8 <view :class="fixed?'zp-swiper-container zp-swiper-container-fixed':'zp-swiper-container'" :style="[finalSwiperStyle]">
9 <!-- #ifndef APP-PLUS -->
10 <view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
11 <!-- #endif -->
12 <slot v-if="zSlots.top" name="top" />
13 <view class="zp-swiper-super">
14 <view v-if="zSlots.left" :class="{'zp-swiper-left':true,'zp-absoulte':isOldWebView}">
15 <slot name="left" />
16 </view>
17 <view :class="{'zp-swiper':true,'zp-absoulte':isOldWebView}" :style="[swiperContentStyle]">
18 <slot />
19 </view>
20 <view v-if="zSlots.right" :class="{'zp-swiper-right':true,'zp-absoulte zp-right':isOldWebView}">
21 <slot name="right" />
22 </view>
23 </view>
24 <slot v-if="zSlots.bottom" name="bottom" />
25 </view>
26 </template>
27
28 <script>
29 import commonLayoutModule from '../z-paging/js/modules/common-layout'
30
31 export default {
32 name: "z-paging-swiper",
33 mixins: [commonLayoutModule],
34 data() {
35 return {
36 swiperContentStyle: {}
37 };
38 },
39 props: {
40 //是否使用fixed布局,默认为是
41 fixed: {
42 type: Boolean,
43 default: true
44 },
45 //是否开启底部安全区域适配
46 safeAreaInsetBottom: {
47 type: Boolean,
48 default: false
49 },
50 //z-paging-swiper样式
51 swiperStyle: {
52 type: Object,
53 default: function() {
54 return {};
55 },
56 }
57 },
58 mounted() {
59 this.$nextTick(() => {
60 this.systemInfo = uni.getSystemInfoSync();
61 })
62 // #ifndef APP-PLUS
63 this._getCssSafeAreaInsetBottom();
64 // #endif
65 this.updateLeftAndRightWidth();
66
67 this.swiperContentStyle = { 'flex': '1' };
68 // #ifndef APP-NVUE
69 this.swiperContentStyle = { width: '100%',height: '100%' };
70 // #endif
71 },
72 computed: {
73 finalSwiperStyle() {
74 const swiperStyle = this.swiperStyle;
75 if (!this.systemInfo) return swiperStyle;
76 const windowTop = this.windowTop;
77 const windowBottom = this.systemInfo.windowBottom;
78 if (this.fixed) {
79 if (windowTop && !swiperStyle.top) {
80 swiperStyle.top = windowTop + 'px';
81 }
82 if (!swiperStyle.bottom) {
83 let bottom = windowBottom || 0;
84 bottom += this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
85 if (bottom > 0) {
86 swiperStyle.bottom = bottom + 'px';
87 }
88 }
89 }
90 return swiperStyle;
91 }
92 },
93 methods: {
94 //更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用
95 updateLeftAndRightWidth() {
96 if (!this.isOldWebView) return;
97 this.$nextTick(() => this._updateLeftAndRightWidth(this.swiperContentStyle, 'zp-swiper'));
98 }
99 }
100 }
101 </script>
102
103 <style scoped>
104 .zp-swiper-container {
105 /* #ifndef APP-NVUE */
106 display: flex;
107 /* #endif */
108 flex-direction: column;
109 flex: 1;
110 }
111
112 .zp-swiper-container-fixed {
113 position: fixed;
114 /* #ifndef APP-NVUE */
115 height: auto;
116 width: auto;
117 /* #endif */
118 top: 0;
119 left: 0;
120 bottom: 0;
121 right: 0;
122 }
123
124 .zp-safe-area-inset-bottom {
125 position: absolute;
126 /* #ifndef APP-PLUS */
127 height: env(safe-area-inset-bottom);
128 /* #endif */
129 }
130
131 .zp-swiper-super {
132 flex: 1;
133 overflow: hidden;
134 position: relative;
135 /* #ifndef APP-NVUE */
136 display: flex;
137 /* #endif */
138 flex-direction: row;
139 }
140
141 .zp-swiper-left,.zp-swiper-right{
142 /* #ifndef APP-NVUE */
143 height: 100%;
144 /* #endif */
145 }
146
147 .zp-swiper {
148 flex: 1;
149 /* #ifndef APP-NVUE */
150 height: 100%;
151 width: 100%;
152 /* #endif */
153 }
154
155 .zp-absoulte {
156 /* #ifndef APP-NVUE */
157 position: absolute;
158 top: 0;
159 width: auto;
160 /* #endif */
161 }
162
163 .zp-right{
164 right: 0;
165 }
166
167 .zp-swiper-item {
168 height: 100%;
169 }
170 </style>
1 <!-- [z-paging]上拉加载更多view -->
2 <template>
3 <view class="zp-l-container" :style="[c.customStyle]" @click="doClick">
4 <template v-if="!c.hideContent">
5 <text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" class="zp-l-line" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" />
6 <!-- #ifndef APP-NVUE -->
7 <image v-if="finalStatus===M.Loading&&!!c.loadingIconCustomImage"
8 :src="c.loadingIconCustomImage" :style="[c.iconCustomStyle]" :class="{'zp-l-line-loading-custom-image':true,'zp-l-line-loading-custom-image-animated':c.loadingAnimated}" />
9 <image v-if="finalStatus===M.Loading&&finalLoadingIconType==='flower'&&!c.loadingIconCustomImage.length"
10 class="zp-line-loading-image" :style="[c.iconCustomStyle]" :src="zTheme.flower[ts]" />
11 <!-- #endif -->
12 <!-- #ifdef APP-NVUE -->
13 <view>
14 <loading-indicator v-if="finalStatus===M.Loading&&finalLoadingIconType!=='circle'" class="zp-line-loading-image" :style="[{color:zTheme.indicator[ts]}]" :animating="true" />
15 </view>
16 <!-- #endif -->
17 <text v-if="finalStatus===M.Loading&&finalLoadingIconType==='circle'&&!c.loadingIconCustomImage.length"
18 class="zp-l-circle-loading-view" :style="[{borderColor:zTheme.circleBorder[ts],borderTopColor:zTheme.circleBorderTop[ts]},c.iconCustomStyle]" />
19 <text class="zp-l-text" :style="[{color:zTheme.title[ts]},c.titleCustomStyle]">{{ownLoadingMoreText}}</text>
20 <text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" class="zp-l-line" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" />
21 </template>
22 </view>
23 </template>
24 <script>
25 import zStatic from '../js/z-paging-static'
26 import Enum from '../js/z-paging-enum'
27 export default {
28 name: 'z-paging-load-more',
29 data() {
30 return {
31 M: Enum.More,
32 zTheme: {
33 title: { white: '#efefef', black: '#a4a4a4' },
34 line: { white: '#efefef', black: '#eeeeee' },
35 circleBorder: { white: '#aaaaaa', black: '#c8c8c8' },
36 circleBorderTop: { white: '#ffffff', black: '#444444' },
37 flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower },
38 indicator: { white: '#eeeeee', black: '#777777' }
39 }
40 };
41 },
42 props: ['zConfig'],
43 computed: {
44 ts() {
45 return this.c.defaultThemeStyle;
46 },
47 c() {
48 return this.zConfig || {};
49 },
50 ownLoadingMoreText() {
51 const statusTextArr = [this.c.defaultText,this.c.loadingText,this.c.noMoreText,this.c.failText];
52 return statusTextArr[this.finalStatus];
53 },
54 finalStatus() {
55 if (this.c.defaultAsLoading && this.c.status === this.M.Default) return this.M.Loading;
56 return this.c.status;
57 },
58 finalLoadingIconType() {
59 // #ifdef APP-NVUE
60 return 'flower';
61 // #endif
62 return this.c.loadingIconType;
63 }
64 },
65 methods: {
66 doClick() {
67 this.$emit('doClick');
68 }
69 }
70 }
71 </script>
72
73 <style scoped>
74 @import "../css/z-paging-static.css";
75
76 .zp-l-container {
77 height: 80rpx;
78 font-size: 27rpx;
79 /* #ifndef APP-NVUE */
80 clear: both;
81 display: flex;
82 /* #endif */
83 flex-direction: row;
84 align-items: center;
85 justify-content: center;
86 }
87
88 .zp-l-line-loading-custom-image {
89 color: #a4a4a4;
90 margin-right: 8rpx;
91 width: 28rpx;
92 height: 28rpx;
93 }
94
95 .zp-l-line-loading-custom-image-animated{
96 /* #ifndef APP-NVUE */
97 animation: loading-circle 1s linear infinite;
98 /* #endif */
99 }
100
101 .zp-l-circle-loading-view {
102 margin-right: 8rpx;
103 width: 23rpx;
104 height: 23rpx;
105 border: 3rpx solid #dddddd;
106 border-radius: 50%;
107 /* #ifndef APP-NVUE */
108 animation: loading-circle 1s linear infinite;
109 /* #endif */
110 /* #ifdef APP-NVUE */
111 width: 30rpx;
112 height: 30rpx;
113 /* #endif */
114 }
115
116 .zp-l-text {
117 /* #ifdef APP-NVUE */
118 font-size: 30rpx;
119 margin: 0rpx 10rpx;
120 /* #endif */
121 }
122
123 .zp-l-line {
124 height: 1px;
125 width: 100rpx;
126 margin: 0rpx 10rpx;
127 }
128
129 /* #ifndef APP-NVUE */
130 @keyframes loading-circle {
131 0% {
132 -webkit-transform: rotate(0deg);
133 transform: rotate(0deg);
134 }
135 100% {
136 -webkit-transform: rotate(360deg);
137 transform: rotate(360deg);
138 }
139 }
140 /* #endif */
141 </style>
1 <!-- [z-paging]下拉刷新view -->
2 <template>
3 <view style="height: 100%;">
4 <view :class="showUpdateTime?'zp-r-container zp-r-container-padding':'zp-r-container'">
5 <view class="zp-r-left">
6 <image v-if="status!==R.Loading" :class="leftImageClass" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" />
7 <!-- #ifndef APP-NVUE -->
8 <image v-else :class="{'zp-line-loading-image':refreshingAnimated,'zp-r-left-image':true}" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" />
9 <!-- #endif -->
10 <!-- #ifdef APP-NVUE -->
11 <view v-else :style="[{'margin-right':showUpdateTime?'18rpx':'12rpx'}]">
12 <loading-indicator :class="isIos?'zp-loading-image-ios':'zp-loading-image-android'"
13 :style="[{color:zTheme.indicator[ts]},imgStyle]" :animating="true" />
14 </view>
15 <!-- #endif -->
16 </view>
17 <view class="zp-r-right">
18 <text class="zp-r-right-text" :style="[rightTextStyle,titleStyle]">{{currentTitle}}</text>
19 <text v-if="showUpdateTime&&refresherTimeText.length" class="zp-r-right-text zp-r-right-time-text" :style="[rightTextStyle,updateTimeStyle]">
20 {{refresherTimeText}}
21 </text>
22 </view>
23 </view>
24 </view>
25 </template>
26 <script>
27 import zStatic from '../js/z-paging-static'
28 import u from '../js/z-paging-utils'
29 import Enum from '../js/z-paging-enum'
30
31 export default {
32 name: 'z-paging-refresh',
33 data() {
34 return {
35 R: Enum.Refresher,
36 isIos: uni.getSystemInfoSync().platform === 'ios',
37 refresherTimeText: '',
38 zTheme: {
39 title: { white: '#efefef', black: '#555555' },
40 arrow: { white: zStatic.base64ArrowWhite, black: zStatic.base64Arrow },
41 flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower },
42 success: { white: zStatic.base64SuccessWhite, black: zStatic.base64Success },
43 indicator: { white: '#eeeeee', black: '#777777' }
44 }
45 };
46 },
47 props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'defaultImg', 'pullingImg',
48 'refreshingImg', 'completeImg', 'refreshingAnimated', 'showUpdateTime', 'updateTimeKey', 'imgStyle', 'titleStyle', 'updateTimeStyle', 'updateTimeTextMap'
49 ],
50 computed: {
51 ts() {
52 return this.defaultThemeStyle;
53 },
54 statusTextArr() {
55 this.updateTime();
56 return [this.defaultText,this.pullingText,this.refreshingText,this.completeText];
57 },
58 currentTitle() {
59 return this.statusTextArr[this.status] || this.defaultText;
60 },
61 leftImageClass() {
62 if (this.status === this.R.Complete) return 'zp-r-left-image-pre-size';
63 return `zp-r-left-image zp-r-left-image-pre-size ${this.status === this.R.Default ? 'zp-r-arrow-down' : 'zp-r-arrow-top'}`;
64 },
65 leftImageStyle() {
66 const showUpdateTime = this.showUpdateTime;
67 const size = showUpdateTime ? '36rpx' : '30rpx';
68 return {width: size,height: size,'margin-right': showUpdateTime ? '20rpx' : '9rpx'};
69 },
70 leftImageSrc() {
71 const R = this.R;
72 const status = this.status;
73 if (status === R.Default) {
74 if (!!this.defaultImg) return this.defaultImg;
75 return this.zTheme.arrow[this.ts];
76 } else if (status === R.ReleaseToRefresh) {
77 if (!!this.pullingImg) return this.pullingImg;
78 if (!!this.defaultImg) return this.defaultImg;
79 return this.zTheme.arrow[this.ts];
80 } else if (status === R.Loading) {
81 if (!!this.refreshingImg) return this.refreshingImg;
82 return this.zTheme.flower[this.ts];;
83 } else if (status === R.Complete) {
84 if (!!this.completeImg) return this.completeImg;
85 return this.zTheme.success[this.ts];;
86 }
87 return '';
88 },
89 rightTextStyle() {
90 let stl = {};
91 // #ifdef APP-NVUE
92 const textHeight = this.showUpdateTime ? '40rpx' : '80rpx';
93 stl = {'height': textHeight, 'line-height': textHeight}
94 // #endif
95 stl['color'] = this.zTheme.title[this.ts];
96 return stl;
97 }
98 },
99 methods: {
100 updateTime() {
101 if (this.showUpdateTime) {
102 this.refresherTimeText = u.getRefesrherFormatTimeByKey(this.updateTimeKey, this.updateTimeTextMap);
103 }
104 }
105 }
106 }
107 </script>
108
109 <style scoped>
110 @import "../css/z-paging-static.css";
111
112 .zp-r-container {
113 /* #ifndef APP-NVUE */
114 display: flex;
115 height: 100%;
116 /* #endif */
117 flex-direction: row;
118 justify-content: center;
119 align-items: center;
120 }
121
122 .zp-r-container-padding {
123 /* #ifdef APP-NVUE */
124 padding: 15rpx 0rpx;
125 /* #endif */
126 }
127
128 .zp-r-left {
129 /* #ifndef APP-NVUE */
130 display: flex;
131 /* #endif */
132 flex-direction: row;
133 align-items: center;
134 overflow: hidden;
135 /* #ifdef MP-ALIPAY */
136 margin-top: -4rpx;
137 /* #endif */
138 }
139
140 .zp-r-left-image {
141 transition-duration: .2s;
142 transition-property: transform;
143 color: #666666;
144 }
145
146 .zp-r-left-image-pre-size{
147 /* #ifndef APP-NVUE */
148 width: 30rpx;
149 height: 30rpx;
150 overflow: hidden;
151 /* #endif */
152 }
153
154 .zp-r-arrow-top {
155 transform: rotate(0deg);
156 }
157
158 .zp-r-arrow-down {
159 transform: rotate(180deg);
160 }
161
162 .zp-r-right {
163 font-size: 27rpx;
164 /* #ifndef APP-NVUE */
165 display: flex;
166 /* #endif */
167 flex-direction: column;
168 align-items: center;
169 justify-content: center;
170 }
171
172 .zp-r-right-text {
173 /* #ifdef APP-NVUE */
174 font-size: 28rpx;
175 /* #endif */
176 }
177
178 .zp-r-right-time-text {
179 margin-top: 10rpx;
180 font-size: 24rpx;
181 }
182 </style>
1 // z-paging全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置
2
3 export default {}
...\ No newline at end of file ...\ No newline at end of file
1 /* [z-paging]公共css*/
2
3 .z-paging-content {
4 position: relative;
5 /* #ifndef APP-NVUE */
6 display: flex;
7 width: 100%;
8 height: 100%;
9 overflow: hidden;
10 /* #endif */
11 flex-direction: column;
12 }
13
14 .z-paging-content-fixed, .zp-loading-fixed {
15 position: fixed;
16 /* #ifndef APP-NVUE */
17 height: auto;
18 width: auto;
19 /* #endif */
20 top: 0;
21 left: 0;
22 bottom: 0;
23 right: 0;
24 }
25
26 .zp-page-top,.zp-page-bottom {
27 /* #ifndef APP-NVUE */
28 width: auto;
29 /* #endif */
30 position: fixed;
31 left: 0;
32 right: 0;
33 z-index: 999;
34 }
35
36 .zp-page-left,.zp-page-right{
37 /* #ifndef APP-NVUE */
38 height: 100%;
39 /* #endif */
40 }
41
42 .zp-scroll-view-super {
43 flex: 1;
44 overflow: hidden;
45 position: relative;
46 }
47
48 .zp-view-super{
49 /* #ifndef APP-NVUE */
50 display: flex;
51 /* #endif */
52 flex-direction: row;
53 }
54
55 .zp-custom-refresher-container {
56 overflow: hidden;
57 }
58
59 .zp-scroll-view-container,.zp-scroll-view {
60 position: relative;
61 /* #ifndef APP-NVUE */
62 height: 100%;
63 width: 100%;
64 /* #endif */
65 }
66
67 .zp-absoulte{
68 /* #ifndef APP-NVUE */
69 position: absolute;
70 top: 0;
71 width: auto;
72 /* #endif */
73 }
74
75 .zp-right{
76 right: 0;
77 }
78
79 .zp-scroll-view-absolute {
80 position: absolute;
81 top: 0;
82 left: 0;
83 }
84
85 /* #ifndef APP-NVUE */
86 .zp-scroll-view-hide-scrollbar ::-webkit-scrollbar {
87 display: none;
88 -webkit-appearance: none;
89 width: 0 !important;
90 height: 0 !important;
91 background: transparent;
92 }
93 /* #endif */
94
95 .zp-paging-touch-view {
96 width: 100%;
97 height: 100%;
98 position: relative;
99 }
100
101 .zp-fixed-bac-view {
102 position: absolute;
103 width: 100%;
104 top: 0;
105 left: 0;
106 height: 200px;
107 }
108
109 .zp-paging-main {
110 height: 100%;
111 /* #ifndef APP-NVUE */
112 display: flex;
113 /* #endif */
114 flex-direction: column;
115 }
116
117 .zp-paging-container {
118 flex: 1;
119 position: relative;
120 /* #ifndef APP-NVUE */
121 display: flex;
122 /* #endif */
123 flex-direction: column;
124 }
125
126 .zp-chat-record-loading-container {
127 /* #ifndef APP-NVUE */
128 display: flex;
129 width: 100%;
130 /* #endif */
131 /* #ifdef APP-NVUE */
132 width: 750rpx;
133 /* #endif */
134 align-items: center;
135 justify-content: center;
136 height: 60rpx;
137 font-size: 26rpx;
138 }
139
140 .zp-chat-record-loading-custom-image {
141 width: 35rpx;
142 height: 35rpx;
143 /* #ifndef APP-NVUE */
144 animation: loading-flower 1s linear infinite;
145 /* #endif */
146 }
147
148 .zp-custom-refresher-container {
149 /* #ifndef APP-NVUE */
150 display: flex;
151 /* #endif */
152 flex-direction: row;
153 justify-content: center;
154 align-items: center;
155 }
156
157 .zp-back-to-top {
158 width: 76rpx;
159 height: 76rpx;
160 z-index: 999;
161 position: absolute;
162 bottom: 0rpx;
163 right: 25rpx;
164 transition-duration: .3s;
165 transition-property: opacity;
166 }
167
168 .zp-back-to-top-show {
169 opacity: 1;
170 }
171
172 .zp-back-to-top-hide {
173 opacity: 0;
174 }
175
176 .zp-back-to-top-img {
177 /* #ifndef APP-NVUE */
178 width: 100%;
179 height: 100%;
180 /* #endif */
181 /* #ifdef APP-NVUE */
182 flex: 1;
183 /* #endif */
184 z-index: 999;
185 }
186
187 .zp-empty-view {
188 /* #ifdef APP-NVUE */
189 height: 100%;
190 /* #endif */
191 flex: 1;
192 }
193
194 .zp-empty-view-center {
195 /* #ifndef APP-NVUE */
196 display: flex;
197 /* #endif */
198 flex-direction: column;
199 align-items: center;
200 justify-content: center;
201 }
202
203 .zp-loading-fixed {
204 z-index: 9999;
205 }
206
207 .zp-safe-area-inset-bottom {
208 position: absolute;
209 /* #ifndef APP-PLUS */
210 height: env(safe-area-inset-bottom);
211 /* #endif */
212 }
213
214 .zp-n-refresh-container {
215 /* #ifndef APP-NVUE */
216 display: flex;
217 /* #endif */
218 justify-content: center;
219 width: 750rpx;
220 }
221
222 .zp-n-list-container{
223 /* #ifndef APP-NVUE */
224 display: flex;
225 /* #endif */
226 flex-direction: row;
227 flex: 1;
228 }
1 /* [z-paging]公用的静态css资源 */
2
3 .zp-line-loading-image {
4 margin-right: 8rpx;
5 width: 28rpx;
6 height: 28rpx;
7 /* #ifndef APP-NVUE */
8 animation: loading-flower 1s steps(12) infinite;
9 /* #endif */
10 color: #666666;
11 }
12
13 .zp-loading-image-ios{
14 width: 20px;
15 height: 20px;
16 }
17
18 .zp-loading-image-android{
19 width: 32rpx;
20 height: 32rpx;
21 }
22
23 /* #ifndef APP-NVUE */
24 @keyframes loading-flower {
25 0% {
26 -webkit-transform: rotate(0deg);
27 transform: rotate(0deg);
28 }
29 to {
30 -webkit-transform: rotate(1turn);
31 transform: rotate(1turn);
32 }
33 }
34 /* #endif */
35
1 {
2 "zp.refresher.default": "Pull down to refresh",
3 "zp.refresher.pulling": "Release to refresh",
4 "zp.refresher.refreshing": "Refreshing...",
5 "zp.refresher.complete": "Refresh succeeded",
6
7 "zp.loadingMore.default": "Click to load more",
8 "zp.loadingMore.loading": "Loading...",
9 "zp.loadingMore.noMore": "No more data",
10 "zp.loadingMore.fail": "Load failed,click to reload",
11
12 "zp.emptyView.title": "No data",
13 "zp.emptyView.reload": "Reload",
14 "zp.emptyView.error": "Sorry,load failed",
15
16 "zp.refresherUpdateTime.title": "Last update: ",
17 "zp.refresherUpdateTime.none": "None",
18 "zp.refresherUpdateTime.today": "Today",
19 "zp.refresherUpdateTime.yesterday": "Yesterday",
20
21 "zp.systemLoading.title": "Loading..."
22 }
1 import en from './en.json'
2 import zhHans from './zh-Hans.json'
3 import zhHant from './zh-Hant.json'
4 export default {
5 en,
6 'zh-Hans': zhHans,
7 'zh-Hant': zhHant
8 }
1 {
2 "zp.refresher.default": "继续下拉刷新",
3 "zp.refresher.pulling": "松开立即刷新",
4 "zp.refresher.refreshing": "正在刷新...",
5 "zp.refresher.complete": "刷新成功",
6
7 "zp.loadingMore.default": "点击加载更多",
8 "zp.loadingMore.loading": "正在加载...",
9 "zp.loadingMore.noMore": "没有更多了",
10 "zp.loadingMore.fail": "加载失败,点击重新加载",
11
12 "zp.emptyView.title": "没有数据哦~",
13 "zp.emptyView.reload": "重新加载",
14 "zp.emptyView.error": "很抱歉,加载失败",
15
16 "zp.refresherUpdateTime.title": "最后更新:",
17 "zp.refresherUpdateTime.none": "无",
18 "zp.refresherUpdateTime.today": "今天",
19 "zp.refresherUpdateTime.yesterday": "昨天",
20
21 "zp.systemLoading.title": "加载中..."
22 }
1 {
2 "zp.refresher.default": "繼續下拉重繪",
3 "zp.refresher.pulling": "鬆開立即重繪",
4 "zp.refresher.refreshing": "正在重繪...",
5 "zp.refresher.complete": "重繪成功",
6
7 "zp.loadingMore.default": "點擊加載更多",
8 "zp.loadingMore.loading": "正在加載...",
9 "zp.loadingMore.noMore": "沒有更多了",
10 "zp.loadingMore.fail": "加載失敗,點擊重新加載",
11
12 "zp.emptyView.title": "沒有數據哦~",
13 "zp.emptyView.reload": "重新加載",
14 "zp.emptyView.error": "很抱歉,加載失敗",
15
16 "zp.refresherUpdateTime.title": "最後更新:",
17 "zp.refresherUpdateTime.none": "無",
18 "zp.refresherUpdateTime.today": "今天",
19 "zp.refresherUpdateTime.yesterday": "昨天",
20
21 "zp.systemLoading.title": "加載中..."
22 }
1 // [z-paging]useZPaging hooks
2
3 import { onPageScroll, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
4
5 function useZPaging(paging) {
6 const cPaging = !!paging ? paging.value || paging : null;
7
8 onPullDownRefresh(() => {
9 if (!cPaging) return;
10 cPaging.value.reload().catch(() => {});
11 })
12
13 onPageScroll(e => {
14 if (!cPaging) return;
15 cPaging.value.updatePageScrollTop(e.scrollTop);
16 e.scrollTop < 10 && cPaging.value.doChatRecordLoadMore();
17 })
18
19 onReachBottom(() => {
20 if (!cPaging) return;
21 cPaging.value.pageReachBottom();
22 })
23 }
24
25 export default useZPaging
...\ No newline at end of file ...\ No newline at end of file
1 // [z-paging]useZPagingComp hooks
2
3 function useZPagingComp(paging) {
4
5 const cPaging = !!paging ? paging.value || paging : null;
6
7 const reload = () => {
8 if (!cPaging) return;
9 cPaging.value.reload().catch(() => {});
10 }
11 const updatePageScrollTop = scrollTop => {
12 if (!cPaging) return;
13 cPaging.value.updatePageScrollTop(scrollTop);
14 }
15 const doChatRecordLoadMore = () => {
16 if (!cPaging) return;
17 cPaging.value.doChatRecordLoadMore();
18 }
19 const pageReachBottom = () => {
20 if (!cPaging) return;
21 cPaging.value.pageReachBottom();
22 }
23 return { reload, updatePageScrollTop, doChatRecordLoadMore, pageReachBottom };
24 }
25
26 export default useZPagingComp
...\ No newline at end of file ...\ No newline at end of file
1 // [z-paging]点击返回顶部view模块
2 import u from '.././z-paging-utils'
3
4 export default {
5 props: {
6 //自动显示点击返回顶部按钮,默认为否
7 autoShowBackToTop: {
8 type: Boolean,
9 default: u.gc('autoShowBackToTop', false)
10 },
11 //点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx
12 backToTopThreshold: {
13 type: [Number, String],
14 default: u.gc('backToTopThreshold', '400rpx')
15 },
16 //点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片
17 backToTopImg: {
18 type: String,
19 default: u.gc('backToTopImg', '')
20 },
21 //点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是
22 backToTopWithAnimate: {
23 type: Boolean,
24 default: u.gc('backToTopWithAnimate', true)
25 },
26 //点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx
27 backToTopBottom: {
28 type: [Number, String],
29 default: u.gc('backToTopBottom', '160rpx')
30 },
31 //点击返回顶部按钮的自定义样式
32 backToTopStyle: {
33 type: Object,
34 default: function() {
35 return u.gc('backToTopStyle', {});
36 },
37 },
38 //iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是
39 enableBackToTop: {
40 type: Boolean,
41 default: u.gc('enableBackToTop', true)
42 },
43 },
44 data() {
45 return {
46 backToTopClass: 'zp-back-to-top zp-back-to-top-hide',
47 lastBackToTopShowTime: 0,
48 showBackToTopClass: false,
49 }
50 },
51 computed: {
52 finalEnableBackToTop() {
53 return this.usePageScroll ? false : this.enableBackToTop;
54 },
55 finalBackToTopThreshold() {
56 return u.convertToPx(this.backToTopThreshold);
57 },
58 finalBackToTopStyle() {
59 const backToTopStyle = this.backToTopStyle;
60 if (!backToTopStyle.bottom) {
61 backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottom) + 'px';
62 }
63 if(!backToTopStyle.position){
64 backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute';
65 }
66 return backToTopStyle;
67 },
68 },
69 methods: {
70 //点击返回顶部
71 _backToTopClick() {
72 !this.backToTopWithAnimate && this._checkShouldShowBackToTop(0);
73 this.scrollToTop(this.backToTopWithAnimate);
74 },
75 //判断是否要显示返回顶部按钮
76 _checkShouldShowBackToTop(scrollTop) {
77 if (!this.autoShowBackToTop) {
78 this.showBackToTopClass = false;
79 return;
80 }
81 if (scrollTop > this.finalBackToTopThreshold) {
82 if (!this.showBackToTopClass) {
83 this.showBackToTopClass = true;
84 this.lastBackToTopShowTime = new Date().getTime();
85 u.delay(() => {
86 this.backToTopClass = 'zp-back-to-top zp-back-to-top-show';
87 }, 300)
88 }
89 } else {
90 if (this.showBackToTopClass) {
91 this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide';
92 u.delay(() => {
93 this.showBackToTopClass = false;
94 }, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300)
95 }
96 }
97 },
98 }
99 }
100
1 // [z-paging]通用布局相关模块
2
3 // #ifdef APP-NVUE
4 const weexDom = weex.requireModule('dom');
5 // #endif
6
7 export default {
8 data() {
9 return {
10 systemInfo: null,
11 cssSafeAreaInsetBottom: -1,
12 }
13 },
14 computed: {
15 windowTop() {
16 if (!this.systemInfo) return 0;
17 //暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634
18 //感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25
19 // #ifdef VUE3 && H5
20 const pageHeadNode = document.getElementsByTagName("uni-page-head");
21 if (!pageHeadNode.length) return 0;
22 // #endif
23 return this.systemInfo.windowTop || 0;
24 },
25 safeAreaBottom() {
26 if (!this.systemInfo) return 0;
27 let safeAreaBottom = 0;
28 // #ifdef APP-PLUS
29 safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ;
30 // #endif
31 // #ifndef APP-PLUS
32 safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0);
33 // #endif
34 return safeAreaBottom;
35 },
36 isOldWebView() {
37 // #ifndef APP-NVUE || MP-KUAISHOU
38 try {
39 const systemInfos = systemInfo.system.split(' ');
40 const deviceType = systemInfos[0];
41 const version = parseInt(systemInfos[1]);
42 if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
43 return true;
44 }
45 } catch(e) {
46 return false;
47 }
48 // #endif
49 return false;
50 },
51 zSlots() {
52 // #ifdef VUE2
53
54 // #ifdef MP-ALIPAY
55 return this.$slots;
56 // #endif
57
58 return this.$scopedSlots || this.$slots;
59 // #endif
60
61 return this.$slots;
62 }
63 },
64 methods: {
65 //获取节点尺寸
66 _getNodeClientRect(select, inDom = true, scrollOffset = false) {
67 // #ifdef APP-NVUE
68 select = select.replace(/[.|#]/g, '');
69 const ref = this.$refs[select];
70 return new Promise((resolve, reject) => {
71 if (ref) {
72 weexDom.getComponentRect(ref, option => {
73 resolve(option && option.result ? [option.size] : false);
74 })
75 } else {
76 resolve(false);
77 }
78 });
79 return;
80 // #endif
81 //#ifdef MP-ALIPAY
82 inDom = false;
83 //#endif
84 let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery();
85 scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect();
86 return new Promise((resolve, reject) => {
87 res.exec(data => {
88 resolve((data && data != '' && data != undefined && data.length) ? data : false);
89 });
90 });
91 },
92 //获取slot="left"和slot="right"宽度并且更新布局
93 _updateLeftAndRightWidth(targetStyle, parentNodePrefix) {
94 this.$nextTick(() => {
95 let delayTime = 0;
96 // #ifdef MP-BAIDU
97 delayTime = 10;
98 // #endif
99 setTimeout(() => {
100 ['left','right'].map(position => {
101 this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => {
102 this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px');
103 });
104 })
105 }, delayTime)
106 })
107 },
108 //通过获取css设置的底部安全区域占位view高度设置bottom距离
109 _getCssSafeAreaInsetBottom(success) {
110 this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => {
111 this.cssSafeAreaInsetBottom = res ? res[0].height : -1;
112 res && success && success();
113 });
114 }
115 }
116 }
1 // [z-paging]空数据图view模块
2 import u from '.././z-paging-utils'
3
4 export default {
5 props: {
6 //是否强制隐藏空数据图,默认为否
7 hideEmptyView: {
8 type: Boolean,
9 default: u.gc('hideEmptyView', false)
10 },
11 //空数据图描述文字,默认为“没有数据哦~”
12 emptyViewText: {
13 type: [String, Object],
14 default: u.gc('emptyViewText', null)
15 },
16 //是否显示空数据图重新加载按钮(无数据时),默认为否
17 showEmptyViewReload: {
18 type: Boolean,
19 default: u.gc('showEmptyViewReload', false)
20 },
21 //加载失败时是否显示空数据图重新加载按钮,默认为是
22 showEmptyViewReloadWhenError: {
23 type: Boolean,
24 default: u.gc('showEmptyViewReloadWhenError', true)
25 },
26 //空数据图点击重新加载文字,默认为“重新加载”
27 emptyViewReloadText: {
28 type: [String, Object],
29 default: u.gc('emptyViewReloadText', null)
30 },
31 //空数据图图片,默认使用z-paging内置的图片
32 emptyViewImg: {
33 type: String,
34 default: u.gc('emptyViewImg', '')
35 },
36 //空数据图“加载失败”描述文字,默认为“很抱歉,加载失败”
37 emptyViewErrorText: {
38 type: [String, Object],
39 default: u.gc('emptyViewErrorText', null)
40 },
41 //空数据图“加载失败”图片,默认使用z-paging内置的图片
42 emptyViewErrorImg: {
43 type: String,
44 default: u.gc('emptyViewErrorImg', '')
45 },
46 //空数据图样式
47 emptyViewStyle: {
48 type: Object,
49 default: function() {
50 return u.gc('emptyViewStyle', {});
51 }
52 },
53 //空数据图容器样式
54 emptyViewSuperStyle: {
55 type: Object,
56 default: function() {
57 return u.gc('emptyViewSuperStyle', {});
58 }
59 },
60 //空数据图img样式
61 emptyViewImgStyle: {
62 type: Object,
63 default: function() {
64 return u.gc('emptyViewImgStyle', {});
65 }
66 },
67 //空数据图描述文字样式
68 emptyViewTitleStyle: {
69 type: Object,
70 default: function() {
71 return u.gc('emptyViewTitleStyle', {});
72 }
73 },
74 //空数据图重新加载按钮样式
75 emptyViewReloadStyle: {
76 type: Object,
77 default: function() {
78 return u.gc('emptyViewReloadStyle', {});
79 }
80 },
81 //空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging
82 emptyViewFixed: {
83 type: Boolean,
84 default: u.gc('emptyViewFixed', false)
85 },
86 //空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效
87 emptyViewCenter: {
88 type: Boolean,
89 default: u.gc('emptyViewCenter', true)
90 },
91 //加载中时是否自动隐藏空数据图,默认为是
92 autoHideEmptyViewWhenLoading: {
93 type: Boolean,
94 default: u.gc('autoHideEmptyViewWhenLoading', true)
95 },
96 //用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是
97 autoHideEmptyViewWhenPull: {
98 type: Boolean,
99 default: u.gc('autoHideEmptyViewWhenPull', true)
100 },
101 //空数据view的z-index,默认为9
102 emptyViewZIndex: {
103 type: Number,
104 default: u.gc('emptyViewZIndex', 9)
105 },
106 },
107 computed: {
108 finalEmptyViewImg() {
109 return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg;
110 },
111 finalShowEmptyViewReload() {
112 return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload;
113 },
114 showEmpty() {
115 if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false;
116 if (this.autoHideEmptyViewWhenLoading) {
117 if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true;
118 } else {
119 return true;
120 }
121 return !this.autoHideEmptyViewWhenPull && !this.isUserReload;
122 },
123 },
124 methods: {
125 //点击了空数据view重新加载按钮
126 _emptyViewReload() {
127 let callbacked = false;
128 this.$emit('emptyViewReload', reload => {
129 if (reload === undefined || reload === true) {
130 this.fromEmptyViewReload = true;
131 this.reload().catch(() => {});
132 }
133 callbacked = true;
134 });
135 this.$nextTick(() => {
136 if (!callbacked) {
137 this.fromEmptyViewReload = true;
138 this.reload().catch(() => {});
139 }
140 })
141 },
142 //点击了空数据view
143 _emptyViewClick() {
144 this.$emit('emptyViewClick');
145 },
146 }
147 }
...\ No newline at end of file ...\ No newline at end of file
1 // [z-paging]i18n模块
2 import { initVueI18n } from '@dcloudio/uni-i18n'
3 import messages from '../../i18n/index.js'
4 const { t } = initVueI18n(messages)
5
6 import u from '.././z-paging-utils'
7 import c from '.././z-paging-constant'
8 import interceptor from '../z-paging-interceptor'
9
10 const language = uni.getSystemInfoSync().language;
11 export default {
12 data() {
13 return {
14 language
15 }
16 },
17 computed: {
18 finalLanguage() {
19 try {
20 const local = uni.getLocale();
21 const language = this.language;
22 return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local;
23 } catch (e) {
24 return 'zh-Hans';
25 }
26 },
27 finalRefresherDefaultText() {
28 return this._getI18nText('zp.refresher.default', this.refresherDefaultText);
29 },
30 finalRefresherPullingText() {
31 return this._getI18nText('zp.refresher.pulling', this.refresherPullingText);
32 },
33 finalRefresherRefreshingText() {
34 return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText);
35 },
36 finalRefresherCompleteText() {
37 return this._getI18nText('zp.refresher.complete', this.refresherCompleteText);
38 },
39 finalRefresherUpdateTimeTextMap() {
40 return {
41 title: t('zp.refresherUpdateTime.title'),
42 none: t('zp.refresherUpdateTime.none'),
43 today: t('zp.refresherUpdateTime.today'),
44 yesterday: t('zp.refresherUpdateTime.yesterday')
45 };
46 },
47 finalLoadingMoreDefaultText() {
48 return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText);
49 },
50 finalLoadingMoreLoadingText() {
51 return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText);
52 },
53 finalLoadingMoreNoMoreText() {
54 return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText);
55 },
56 finalLoadingMoreFailText() {
57 return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText);
58 },
59 finalEmptyViewText() {
60 return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText);
61 },
62 finalEmptyViewReloadText() {
63 return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText);
64 },
65 finalEmptyViewErrorText() {
66 return this._getI18nText('zp.emptyView.error', this.emptyViewErrorText);
67 },
68 finalSystemLoadingText() {
69 return this._getI18nText('zp.systemLoading.title', this.systemLoadingText);
70 },
71 },
72 methods: {
73 //获取当前z-paging的语言
74 getLanguage() {
75 return this.finalLanguage;
76 },
77 //获取国际化转换后的文本
78 _getI18nText(key, value) {
79 const dataType = Object.prototype.toString.call(value);
80 if (dataType === '[object Object]') {
81 const nextValue = value[this.finalLanguage];
82 if (nextValue) return nextValue;
83 } else if (dataType === '[object String]') {
84 return value;
85 }
86 return t(key);
87 },
88 //系统language转i18n local
89 _language2Local(language) {
90 const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-');
91 if (formatedLanguage.indexOf('zh') !== -1) {
92 if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) {
93 return 'zh-Hans';
94 }
95 return 'zh-Hant';
96 }
97 if (formatedLanguage.indexOf('en') !== -1) return 'en';
98 return language;
99 }
100 }
101 }
1 // [z-paging]loading相关模块
2 import u from '.././z-paging-utils'
3 import Enum from '.././z-paging-enum'
4
5 export default {
6 props: {
7 //第一次加载后自动隐藏loading slot,默认为是
8 autoHideLoadingAfterFirstLoaded: {
9 type: Boolean,
10 default: u.gc('autoHideLoadingAfterFirstLoaded', true)
11 },
12 //loading slot是否铺满屏幕并固定,默认为否
13 loadingFullFixed: {
14 type: Boolean,
15 default: u.gc('loadingFullFixed', false)
16 },
17 //是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。
18 autoShowSystemLoading: {
19 type: Boolean,
20 default: u.gc('autoShowSystemLoading', false)
21 },
22 //显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效)
23 systemLoadingMask: {
24 type: Boolean,
25 default: u.gc('systemLoadingMask', true)
26 },
27 //显示系统Loading时显示的文字,默认为"加载中"
28 systemLoadingText: {
29 type: [String, Object],
30 default: u.gc('systemLoadingText', null)
31 },
32 },
33 data() {
34 return {
35 loading: false,
36 loadingForNow: false,
37 }
38 },
39 watch: {
40 loadingStatus(newVal) {
41 this.$emit('loadingStatusChange', newVal);
42 this.$nextTick(() => {
43 this.loadingStatusAfterRender = newVal;
44 })
45 // #ifdef APP-NVUE
46 if (this.useChatRecordMode) {
47 if (this.pageNo === this.defaultPageNo && newVal === Enum.More.NoMore) {
48 this.nIsFirstPageAndNoMore = true;
49 return;
50 }
51 }
52 this.nIsFirstPageAndNoMore = false;
53 // #endif
54 },
55 loading(newVal){
56 if (newVal) {
57 this.loadingForNow = newVal;
58 }
59 },
60 },
61 computed: {
62 showLoading() {
63 if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false;
64 if (this.finalShowSystemLoading){
65 uni.showLoading({
66 title: this.finalSystemLoadingText,
67 mask: this.systemLoadingMask
68 })
69 }
70 return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher;
71 },
72 finalShowSystemLoading() {
73 return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher;
74 }
75 },
76 methods: {
77 //处理开始加载更多状态
78 _startLoading(isReload = false) {
79 if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) {
80 this.loadingStatus = Enum.More.Loading;
81 }
82 this.loading = true;
83 },
84 //停止系统loading和refresh
85 _endSystemLoadingAndRefresh(){
86 this.finalShowSystemLoading && uni.hideLoading();
87 !this.useCustomRefresher && uni.stopPullDownRefresh();
88 // #ifdef APP-NVUE
89 this.usePageScroll && uni.stopPullDownRefresh();
90 // #endif
91 }
92 }
93 }
1 // [z-paging]nvue独有部分模块
2 import u from '.././z-paging-utils'
3 import c from '.././z-paging-constant'
4 import Enum from '.././z-paging-enum'
5
6 // #ifdef APP-NVUE
7 const weexAnimation = weex.requireModule('animation');
8 // #endif
9 export default {
10 props: {
11 // #ifdef APP-NVUE
12 //nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list
13 nvueListIs: {
14 type: String,
15 default: u.gc('nvueListIs', 'list')
16 },
17 //nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall
18 nvueWaterfallConfig: {
19 type: Object,
20 default: function() {
21 return u.gc('nvueWaterfallConfig', {});
22 }
23 },
24 //nvue 控制是否回弹效果,iOS不支持动态修改
25 nvueBounce: {
26 type: Boolean,
27 default: u.gc('nvueBounce', true)
28 },
29 //nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否
30 nvueFastScroll: {
31 type: Boolean,
32 default: u.gc('nvueFastScroll', false)
33 },
34 //nvue中list的id
35 nvueListId: {
36 type: String,
37 default: u.gc('nvueListId', '')
38 },
39 //nvue中refresh组件的样式
40 nvueRefresherStyle: {
41 type: Object,
42 default: function() {
43 return u.gc('nvueRefresherStyle', {});
44 }
45 },
46 //nvue中是否按分页模式(类似竖向swiper)显示List,默认为false
47 nvuePagingEnabled: {
48 type: Boolean,
49 default: u.gc('nvuePagingEnabled', false)
50 },
51 //是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否
52 hideNvueBottomTag: {
53 type: Boolean,
54 default: u.gc('hideNvueBottomTag', false)
55 },
56 //nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能
57 offsetAccuracy: {
58 type: Number,
59 default: u.gc('offsetAccuracy', 10)
60 },
61 // #endif
62 },
63 data() {
64 return {
65 nRefresherLoading: false,
66 nListIsDragging: false,
67 nShowBottom: true,
68 nFixFreezing: false,
69 nShowRefresherReveal: false,
70 nIsFirstPageAndNoMore: false,
71 nFirstPageAndNoMoreChecked: false,
72 nLoadingMoreFixedHeight: false,
73 nShowRefresherRevealHeight: 0,
74 nOldShowRefresherRevealHeight: -1,
75 nRefresherWidth: uni.upx2px(750),
76 }
77 },
78 watch: {
79 // #ifdef APP-NVUE
80 nIsFirstPageAndNoMore: {
81 handler(newVal) {
82 const cellStyle = !this.useChatRecordMode || newVal ? {} : { transform: 'rotate(180deg)' };
83 this.$emit('update:cellStyle', cellStyle);
84 this.$emit('cellStyleChange', cellStyle);
85 },
86 immediate: true
87 },
88 // #endif
89 },
90 computed: {
91 // #ifdef APP-NVUE
92 nScopedSlots() {
93 // #ifdef VUE2
94 return this.$scopedSlots;
95 // #endif
96 // #ifdef VUE3
97 return null;
98 // #endif
99 },
100 nWaterfallColumnCount() {
101 if (this.finalNvueListIs !== 'waterfall') return 0;
102 return this._nGetWaterfallConfig('column-count', 2);
103 },
104 nWaterfallColumnWidth() {
105 return this._nGetWaterfallConfig('column-width', 'auto');
106 },
107 nWaterfallColumnGap() {
108 return this._nGetWaterfallConfig('column-gap', 'normal');
109 },
110 nWaterfallLeftGap() {
111 return this._nGetWaterfallConfig('left-gap', 0);
112 },
113 nWaterfallRightGap() {
114 return this._nGetWaterfallConfig('right-gap', 0);
115 },
116 nViewIs() {
117 const is = this.finalNvueListIs;
118 return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell';
119 },
120 nSafeAreaBottomHeight() {
121 return this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
122 },
123 nChatRecordRotateStyle() {
124 return this.useChatRecordMode ? { transform: this.nIsFirstPageAndNoMore ? 'rotate(0deg)' : 'rotate(180deg)' } : {};
125 },
126 finalNvueListIs() {
127 if (this.usePageScroll) return 'view';
128 const nvueListIsLowerCase = this.nvueListIs.toLowerCase();
129 if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase;
130 return 'list';
131 },
132 finalNvueSuperListIs() {
133 return this.usePageScroll ? 'view' : 'scroller';
134 },
135 finalNvueRefresherEnabled() {
136 return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode;
137 },
138 // #endif
139 },
140 mounted(){
141 // #ifdef APP-NVUE
142 //旋转屏幕时更新宽度
143 uni.onWindowResize((res) => {
144 // this._nUpdateRefresherWidth();
145 })
146 // #endif
147 },
148 methods: {
149 // #ifdef APP-NVUE
150 //列表滚动时触发
151 _nOnScroll(e) {
152 this.$emit('scroll', e);
153 const contentOffsetY = -e.contentOffset.y;
154 this.oldScrollTop = contentOffsetY;
155 this.nListIsDragging = e.isDragging;
156 this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1);
157 },
158 //下拉刷新刷新中
159 _nOnRrefresh() {
160 if (this.nShowRefresherReveal) return;
161 this.nRefresherLoading = true;
162 this.refresherStatus = Enum.Refresher.Loading;
163 this._doRefresherLoad();
164 },
165 //下拉刷新下拉中
166 _nOnPullingdown(e) {
167 if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return;
168 this._emitTouchmove(e);
169 const { viewHeight, pullingDistance } = e;
170 this.refresherStatus = pullingDistance >= viewHeight ? Enum.Refresher.ReleaseToRefresh : Enum.Refresher.Default;
171 },
172 //下拉刷新结束
173 _nRefresherEnd(doEnd = true) {
174 if (doEnd) {
175 this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight);
176 !this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore();
177 this.nRefresherLoading = false;
178 }
179 },
180 //执行主动触发下拉刷新动画
181 _nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) {
182 this._cleanRefresherCompleteTimeout();
183 this._cleanRefresherEndTimeout();
184
185 if (!this.finalShowRefresherWhenReload) {
186 this.refresherEndTimeout = u.delay(() => {
187 this.refresherStatus = Enum.Refresher.Default;
188 }, this.refresherCompleteDuration);
189 return;
190 }
191 const stackCount = this.refresherRevealStackCount;
192 if (height === 0 && checkStack) {
193 this.refresherRevealStackCount --;
194 if (stackCount > 1) return;
195 this.refresherEndTimeout = u.delay(() => {
196 this.refresherStatus = Enum.Refresher.Default;
197 }, this.refresherCompleteDuration);
198 }
199 if (stackCount > 1) {
200 this.refresherStatus = Enum.Refresher.Loading;
201 }
202
203 const duration = animate ? 200 : 0;
204 if (this.nOldShowRefresherRevealHeight !== height) {
205 if (height > 0) {
206 this.nShowRefresherReveal = true;
207 }
208 weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], {
209 styles: {
210 height: `${height}px`,
211 transform: `translateY(${translateY}px)`,
212 },
213 duration,
214 timingFunction: 'linear',
215 needLayout: true,
216 delay: 0
217 })
218 }
219 u.delay(() => {
220 if (animate) {
221 this.nShowRefresherReveal = height > 0;
222 }
223 }, duration > 0 ? duration - 60 : 0);
224 this.nOldShowRefresherRevealHeight = height;
225 },
226 //滚动到底部加载更多
227 _nOnLoadmore() {
228 if (this.nShowRefresherReveal || !this.totalData.length) return;
229 this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom');
230 },
231 //获取nvue waterfall单项配置
232 _nGetWaterfallConfig(key, defaultValue) {
233 return this.nvueWaterfallConfig[key] || defaultValue;
234 },
235 //更新nvue 下拉刷新view容器的宽度
236 _nUpdateRefresherWidth() {
237 u.delay(() => {
238 this.$nextTick(()=>{
239 this._getNodeClientRect('.zp-n-list').then(node => {
240 if (node) {
241 this.nRefresherWidth = node[0].width || this.nRefresherWidth;
242 }
243 })
244 })
245 })
246 }
247 // #endif
248 }
249 }
1 // [z-paging]处理main.js中的配置信息工具
2
3 let config = null;
4 let getedStorage = false;
5 const storageKey = 'Z-PAGING-CONFIG-STORAGE-KEY'
6
7 function setConfig(value) {
8 uni.setStorageSync(storageKey, value);
9 }
10
11 function getConfig() {
12 if (getedStorage) return config;
13 config = uni.getStorageSync(storageKey);
14 getedStorage = true;
15 return config;
16 }
17
18 export default {
19 setConfig,
20 getConfig
21 };
1 // [z-paging]常量
2
3 export default {
4 version: '2.5.9',
5 delayTime: 100,
6 errorUpdateKey: 'z-paging-error-emit',
7 completeUpdateKey: 'z-paging-complete-emit',
8 cachePrefixKey: 'z-paging-cache',
9
10 listCellIndexKey: 'zp_index',
11 listCellIndexUniqueKey: 'zp_unique_index'
12 }
1 // [z-paging]枚举
2
3 export default {
4 //当前加载类型 0.下拉刷新 1.上拉加载更多
5 LoadingType: {
6 Refresher: 0,
7 LoadingMore: 1
8 },
9 //下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束
10 Refresher: {
11 Default: 0,
12 ReleaseToRefresh: 1,
13 Loading: 2,
14 Complete: 3
15 },
16 //底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败
17 More: {
18 Default: 0,
19 Loading: 1,
20 NoMore: 2,
21 Fail: 3
22 },
23 //@query触发来源 0.用户主动下拉刷新 1.通过reload触发 2.通过refresh触发 3.通过滚动到底部加载更多或点击底部加载更多触发
24 QueryFrom: {
25 UserPullDown: 0,
26 Reload: 1,
27 Refresh: 2,
28 LoadingMore: 3
29 },
30 //虚拟列表cell高度模式
31 CellHeightMode: {
32 //固定高度
33 Fixed: 'fixed',
34 //动态高度
35 Dynamic: 'dynamic'
36 },
37 //列表缓存模式
38 CacheMode: {
39 //默认模式,只会缓存一次
40 Default: 'default',
41 //总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存
42 Always: 'always'
43 }
44 }
...\ No newline at end of file ...\ No newline at end of file
1 // [z-paging]拦截器
2
3 //拦截&处理@query事件
4 function handleQuery(callback) {
5 try {
6 setTimeout(function() {
7 _getApp().globalData.zp_handleQueryCallback = callback;
8 }, 1);
9 } catch (e) {}
10 }
11
12 //拦截&处理@query事件(私有,请勿调用)
13 function _handleQuery(pageNo, pageSize, from, lastItem){
14 const callback = _getApp().globalData.zp_handleQueryCallback;
15 return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from];
16 }
17
18 //拦截&处理系统language转i18n local
19 function handleLanguage2Local(callback) {
20 try {
21 setTimeout(function() {
22 _getApp().globalData.zp_handleLanguage2LocalCallback = callback;
23 }, 1);
24 } catch (e) {}
25 }
26
27 //拦截&处理系统language转i18n local(私有,请勿调用)
28 function _handleLanguage2Local(language, local){
29 const callback = _getApp().globalData.zp_handleLanguage2LocalCallback;
30 return callback ? callback(language, local) : local;
31 }
32
33 //获取当前app对象
34 function _getApp(){
35 // #ifndef APP-NVUE
36 return getApp();
37 // #endif
38 // #ifdef APP-NVUE
39 return getApp({ allowDefault: true });
40 // #endif
41 }
42
43 export default {
44 handleQuery,
45 _handleQuery,
46 handleLanguage2Local,
47 _handleLanguage2Local
48 };
1 // [z-paging]使用页面滚动时引入此mixin,用于监听和处理onPullDownRefresh等页面生命周期方法
2
3 export default {
4 onPullDownRefresh() {
5 if (this.isPagingRefNotFound()) return;
6 this.$refs.paging.reload().catch(() => {});
7 },
8 onPageScroll(e) {
9 if (this.isPagingRefNotFound()) return;
10 this.$refs.paging.updatePageScrollTop(e.scrollTop);
11 e.scrollTop < 10 && this.$refs.paging.doChatRecordLoadMore();
12 },
13 onReachBottom() {
14 if (this.isPagingRefNotFound()) return;
15 this.$refs.paging.pageReachBottom();
16 },
17 methods: {
18 isPagingRefNotFound() {
19 return !this.$refs.paging;
20 }
21 }
22 }
1 // [z-paging]工具类
2
3 import zConfig from './z-paging-config'
4 import zLocalConfig from '../config/index'
5 import c from './z-paging-constant'
6
7 const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY';
8 let config = null;
9 const timeoutMap = {};
10
11 /*
12 当z-paging未使用uni_modules管理时,控制台会有警告:WARNING: Module not found: Error: Can't resolve '@/uni_modules/z-paging'...
13 此时注释下方try中的代码即可
14 */
15 // #ifdef VUE2
16 try {
17 const contextKeys = require.context('@/uni_modules/z-paging', false, /\z-paging-config$/).keys();
18 if (contextKeys.length) {
19 const suffix = '.js';
20 config = require('@/uni_modules/z-paging/z-paging-config' + suffix);
21 }
22 } catch (e) {}
23 // #endif
24
25 //获取默认配置信息
26 function gc(key, defaultValue) {
27 if (!config) {
28 if (zLocalConfig && Object.keys(zLocalConfig).length) {
29 config = zLocalConfig;
30 } else {
31 const tempConfig = zConfig.getConfig();
32 if (zConfig && tempConfig) {
33 config = tempConfig;
34 }
35 }
36 }
37 if (!config) return defaultValue;
38 const value = config[_toKebab(key)];
39 return value === undefined ? defaultValue : value;
40 }
41
42
43 //获取最终的touch位置
44 function getTouch(e) {
45 let touch = null;
46 if (e.touches && e.touches.length) {
47 touch = e.touches[0];
48 } else if (e.changedTouches && e.changedTouches.length) {
49 touch = e.changedTouches[0];
50 } else if (e.datail && e.datail != {}) {
51 touch = e.datail;
52 } else {
53 return {
54 touchX: 0,
55 touchY: 0
56 }
57 }
58 return {
59 touchX: touch.clientX,
60 touchY: touch.clientY
61 };
62 }
63
64 //判断当前手势是否在z-paging内触发
65 function getTouchFromZPaging(target) {
66 if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') {
67 const classList = target.classList;
68 if (classList && classList.contains('z-paging-content')) {
69 return {
70 isFromZp: true,
71 isPageScroll: classList.contains('z-paging-content-page'),
72 isReachedTop: classList.contains('z-paging-reached-top')
73 };
74 } else {
75 return getTouchFromZPaging(target.parentNode);
76 }
77 } else {
78 return { isFromZp: false };
79 }
80 }
81
82 //获取z-paging所在的parent
83 function getParent(parent) {
84 if (!parent) return null;
85 if (parent.$refs.paging) return parent;
86 return getParent(parent.$parent);
87 }
88
89 //打印错误信息
90 function consoleErr(err) {
91 console.error(`[z-paging]${err}`);
92 }
93
94 //延时操作,如果key存在,调用时根据key停止之前的延时操作
95 function delay(callback, ms = c.delayTime, key) {
96 const timeout = setTimeout(callback, ms);;
97 if (!!key) {
98 timeoutMap[key] && clearTimeout(timeoutMap[key]);
99 timeoutMap[key] = timeout;
100 }
101 return timeout;
102 }
103
104 //设置下拉刷新时间
105 function setRefesrherTime(time, key) {
106 const datas = getRefesrherTime() || {};
107 datas[key] = time;
108 uni.setStorageSync(storageKey, datas);
109 }
110
111 //获取下拉刷新时间
112 function getRefesrherTime() {
113 return uni.getStorageSync(storageKey);
114 }
115
116 //通过下拉刷新标识key获取下拉刷新时间
117 function getRefesrherTimeByKey(key) {
118 const datas = getRefesrherTime();
119 return datas && datas[key] ? datas[key] : null;
120 }
121
122 //通过下拉刷新标识key获取下拉刷新时间(格式化之后)
123 function getRefesrherFormatTimeByKey(key, textMap) {
124 const time = getRefesrherTimeByKey(key);
125 const timeText = time ? _timeFormat(time, textMap) : textMap.none;
126 return `${textMap.title}${timeText}`;
127 }
128
129 //将文本的px或者rpx转为px的值
130 function convertToPx(text) {
131 const dataType = Object.prototype.toString.call(text);
132 if (dataType === '[object Number]') return text;
133 let isRpx = false;
134 if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) {
135 text = text.replace('rpx', '').replace('upx', '');
136 isRpx = true;
137 } else if (text.indexOf('px') !== -1) {
138 text = text.replace('px', '');
139 }
140 if (!isNaN(text)) {
141 if (isRpx) return Number(uni.upx2px(text));
142 return Number(text);
143 }
144 return 0;
145 }
146
147 //获取当前时间
148 function getTime() {
149 return (new Date()).getTime();
150 }
151
152 //获取z-paging实例id
153 function getInstanceId() {
154 const s = [];
155 const hexDigits = "0123456789abcdef";
156 for (let i = 0; i < 10; i++) {
157 s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
158 }
159 return s.join('') + getTime();
160 }
161
162 // 等待一段时间
163 function wait(ms) {
164 return new Promise(resolve => {
165 setTimeout(resolve, ms);
166 });
167 }
168
169 //------------------ 私有方法 ------------------------
170 //时间格式化
171 function _timeFormat(time, textMap) {
172 const date = new Date(time);
173 const currentDate = new Date();
174 const dateDay = new Date(time).setHours(0, 0, 0, 0);
175 const currentDateDay = new Date().setHours(0, 0, 0, 0);
176 const disTime = dateDay - currentDateDay;
177 let dayStr = '';
178 const timeStr = _dateTimeFormat(date);
179 if (disTime === 0) {
180 dayStr = textMap.today;
181 } else if (disTime === -86400000) {
182 dayStr = textMap.yesterday;
183 } else {
184 dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear());
185 }
186 return `${dayStr} ${timeStr}`;
187 }
188
189 //date格式化为年月日
190 function _dateDayFormat(date, showYear = true) {
191 const year = date.getFullYear();
192 const month = date.getMonth() + 1;
193 const day = date.getDate();
194 return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`;
195 }
196
197 //data格式化为时分
198 function _dateTimeFormat(date) {
199 const hour = date.getHours();
200 const minute = date.getMinutes();
201 return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`;
202 }
203
204 //不满2位在前面填充0
205 function _fullZeroToTwo(str) {
206 str = str.toString();
207 return str.length === 1 ? '0' + str : str;
208 }
209
210 //驼峰转短横线
211 function _toKebab(value) {
212 return value.replace(/([A-Z])/g, "-$1").toLowerCase();
213 }
214
215 export default {
216 gc,
217 setRefesrherTime,
218 getRefesrherFormatTimeByKey,
219 getTouch,
220 getTouchFromZPaging,
221 getParent,
222 convertToPx,
223 getTime,
224 getInstanceId,
225 consoleErr,
226 delay,
227 wait
228 };
1 // [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理
2
3 import u from '../js/z-paging-utils'
4 const data = {
5 startY: 0,
6 isTouchFromZPaging: false,
7 isUsePageScroll: false,
8 isReachedTop: true,
9 isIosAndH5: false,
10 appLaunched: false
11 }
12
13 export default {
14 mounted() {
15 if (window) {
16 this._handleTouch();
17 // #ifdef APP-VUE
18 this.$ownerInstance.callMethod('_handlePageLaunch');
19 // #endif
20 }
21 },
22 methods: {
23 //接收逻辑层发送的数据
24 renderPropIsIosAndH5Change(newVal) {
25 if (newVal === -1) return;
26 data.isIosAndH5 = newVal;
27 },
28 //拦截处理touch事件
29 _handleTouch() {
30 if (!window.$zPagingRenderJsInited) {
31 window.$zPagingRenderJsInited = true;
32 window.addEventListener('touchstart', this._handleTouchstart, { passive: true })
33 window.addEventListener('touchmove', this._handleTouchmove, { passive: false })
34 }
35 },
36 _handleTouchstart(e) {
37 const touch = u.getTouch(e);
38 data.startY = touch.touchY;
39 const touchResult = u.getTouchFromZPaging(e.target);
40 data.isTouchFromZPaging = touchResult.isFromZp;
41 data.isUsePageScroll = touchResult.isPageScroll;
42 data.isReachedTop = touchResult.isReachedTop;
43 },
44 _handleTouchmove(e) {
45 const touch = u.getTouch(e);
46 const moveY = touch.touchY - data.startY;
47 if (data.isTouchFromZPaging && ((data.isReachedTop && moveY > 0) || (data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) {
48 if (e.cancelable && !e.defaultPrevented) {
49 e.preventDefault();
50 }
51 }
52 },
53 _removeAllEventListener(){
54 window.removeEventListener('touchstart');
55 window.removeEventListener('touchmove');
56 }
57 }
58 };
1 // [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新,降低逻辑层与视图层的通信折损,提升性能
2
3 var currentDis = 0;
4 var isPCFlag = -1;
5 var startY = -1;
6
7 function propObserver(newValue, oldValue, ownerIns, ins) {
8 var state = ownerIns.getState() || {};
9 state.currentIns = ins;
10 var dataset = ins.getDataset();
11 var loading = dataset.loading == true;
12 if (newValue && newValue.indexOf('end') != -1) {
13 var transition = newValue.split('end')[0];
14 _setTransform('translateY(0px)', ins, false, transition);
15 state.moveDis = 0;
16 state.oldMoveDis = 0;
17 currentDis = 0;
18 } else if (newValue && newValue.indexOf('begin') != -1) {
19 var refresherThreshold = ins.getDataset().refresherthreshold;
20 _setTransformValue(refresherThreshold, ins, state, false);
21 }
22 }
23
24 function touchstart(e, ownerIns) {
25 var ins = _getIns(ownerIns);
26 var state = {};
27 var dataset = {};
28 ownerIns.callMethod('_handleListTouchstart');
29 if (ins) {
30 state = ins.getState();
31 dataset = ins.getDataset();
32 if (_touchDisabled(e, ins, 0)) return;
33 }
34 var isTouchEnded = state.isTouchEnded;
35 state.oldMoveDis = 0;
36 var touch = _getTouch(e);
37 var loading = _isTrue(dataset.loading);
38 state.startY = touch.touchY;
39 startY = state.startY;
40 state.lastTouch = touch;
41 if (!loading && isTouchEnded) {
42 state.isTouchmoving = false;
43 }
44 state.isTouchEnded = false;
45 ownerIns.callMethod('_handleRefresherTouchstart', touch);
46 }
47
48 function touchmove(e, ownerIns) {
49 var touch = _getTouch(e);
50 var ins = _getIns(ownerIns);
51 var dataset = ins.getDataset();
52 var refresherThreshold = dataset.refresherthreshold;
53 var isIos = _isTrue(dataset.isios);
54 var state = ins.getState();
55 var watchTouchDirectionChange = _isTrue(dataset.watchtouchdirectionchange);
56 var moveDisObj = {};
57 var moveDis = 0;
58 var prevent = false;
59 if (watchTouchDirectionChange) {
60 moveDisObj = _getMoveDis(e, ins);
61 moveDis = moveDisObj.currentDis;
62 prevent = moveDisObj.isDown;
63 var direction = prevent ? 'top' : 'bottom';
64 if (prevent == state.oldTouchDirection && prevent != state.oldEmitedTouchDirection) {
65 ownerIns.callMethod('_handleTouchDirectionChange', { direction: direction });
66 state.oldEmitedTouchDirection = prevent;
67 }
68 state.oldTouchDirection = prevent;
69 }
70 if (_touchDisabled(e, ins, 1)) {
71 _handlePullingDown(state, ownerIns, false);
72 return true;
73 }
74 if (!_getAngleIsInRange(e, touch, state, dataset)) {
75 _handlePullingDown(state, ownerIns, false);
76 return true;
77 }
78 moveDisObj = _getMoveDis(e, ins);
79 moveDis = moveDisObj.currentDis;
80 prevent = moveDisObj.isDown;
81 if (moveDis < 0) {
82 _setTransformValue(0, ins, state, false);
83 _handlePullingDown(state, ownerIns, false);
84 return true;
85 }
86 if (prevent && !state.disabledBounce) {
87 ownerIns.callMethod('_handleScrollViewDisableBounce', {bounce: false});
88 state.disabledBounce = true;
89 _handlePullingDown(state, ownerIns, prevent);
90 return !prevent;
91 }
92 _setTransformValue(moveDis, ins, state, false);
93 var oldRefresherStatus = state.refresherStatus;
94 var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving);
95 var hasTouchmove = _isTrue(dataset.hastouchmove);
96 var isTouchmoving = state.isTouchmoving;
97 state.refresherStatus = moveDis >= refresherThreshold ? 1 : 0;
98 if (!isTouchmoving) {
99 state.isTouchmoving = true;
100 isTouchmoving = true;
101 }
102 if (state.isTouchEnded) {
103 state.isTouchEnded = false;
104 }
105 if (hasTouchmove) {
106 ownerIns.callMethod('_handleWxsPullingDown', { moveDis:moveDis, diffDis:moveDisObj.diffDis });
107 }
108 if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) {
109 ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch);
110 }
111 _handlePullingDown(state, ownerIns, prevent);
112 return !prevent;
113 }
114
115 function touchend(e, ownerIns) {
116 var touch = _getTouch(e);
117 var ins = _getIns(ownerIns);
118 var dataset = ins.getDataset();
119 var state = ins.getState();
120 if (_touchDisabled(e, ins, 2)) return;
121 state.reachMaxAngle = true;
122 state.hitReachMaxAngleCount = 0;
123 state.disabledBounce = false;
124 if (!state.isTouchmoving) return;
125 var oldRefresherStatus = state.refresherStatus;
126 var oldMoveDis = state.moveDis;
127 var refresherThreshold = ins.getDataset().refresherthreshold
128 var moveDis = _getMoveDis(e, ins).currentDis;
129 if (!(moveDis >= refresherThreshold && oldRefresherStatus === 1)) {
130 state.isTouchmoving = false;
131 }
132 ownerIns.callMethod('_handleRefresherTouchend', moveDis);
133 state.isTouchEnded = true;
134 if (oldMoveDis < refresherThreshold) return;
135 var animate = false;
136 if (moveDis >= refresherThreshold) {
137 moveDis = refresherThreshold;
138 animate = true;
139 }
140 _setTransformValue(moveDis, ins, state, animate);
141 }
142
143 // #ifdef H5
144 function isPC() {
145 if (!navigator) return false;
146 if (isPCFlag != -1) return isPCFlag;
147 var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
148 isPCFlag = agents.every(function(item) { return navigator.userAgent.indexOf(item) < 0 });
149 return isPCFlag;
150 }
151
152 var movable = false;
153
154 function mousedown(e, ins) {
155 if (!isPC()) return;
156 touchstart(e, ins);
157 movable = true;
158 }
159
160 function mousemove(e, ins) {
161 if (!isPC() || !movable) return;
162 touchmove(e, ins);
163 }
164
165 function mouseup(e, ins) {
166 if (!isPC()) return;
167 touchend(e, ins);
168 movable = false;
169 }
170
171 function mouseleave(e, ins) {
172 if (!isPC()) return;
173 movable = false;
174 }
175 // #endif
176
177
178 function _setTransformValue(value, ins, state, animate) {
179 value = value || 0;
180 if (state.moveDis == value) return;
181 state.moveDis = value;
182 _setTransform('translateY(' + value + 'px)', ins, animate, '');
183 }
184
185 function _setTransform(transform, ins, animate, transition) {
186 var dataset = ins.getDataset();
187 if (_isTrue(dataset.refreshernotransform)) return;
188 transform = transform == 'translateY(0px)' ? 'none' : transform;
189 ins.requestAnimationFrame(function() {
190 var stl = { 'transform': transform };
191 if (animate) {
192 stl['transition'] = 'transform .1s linear';
193 }
194 if (transition.length) {
195 stl['transition'] = transition;
196 }
197 ins.setStyle(stl);
198 })
199 }
200
201 function _getMoveDis(e, ins) {
202 var state = ins.getState();
203 var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold);
204 var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate);
205 var refresherPullRate = parseFloat(ins.getDataset().refresherpullrate);
206 var touch = _getTouch(e);
207 var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY;
208 var moveDis = touch.touchY - currentStartY;
209 var oldMoveDis = state.oldMoveDis || 0;
210 state.oldMoveDis = moveDis;
211 var diffDis = moveDis - oldMoveDis;
212 if (diffDis > 0) {
213 diffDis = diffDis * refresherPullRate;
214 if (currentDis > refresherThreshold) {
215 diffDis = diffDis * (1 - refresherOutRate);
216 }
217 }
218 diffDis = diffDis > 100 ? diffDis / 100 : (diffDis > 20 ? diffDis / 20 : diffDis);
219 currentDis += diffDis;
220 currentDis = Math.max(0, currentDis);
221 return {
222 currentDis: currentDis,
223 diffDis: diffDis,
224 isDown: diffDis > 0
225 };
226 }
227
228 function _getTouch(e) {
229 var touch = e;
230 if (e.touches && e.touches.length) {
231 touch = e.touches[0];
232 } else if (e.changedTouches && e.changedTouches.length) {
233 touch = e.changedTouches[0];
234 } else if (e.datail && e.datail != {}) {
235 touch = e.datail;
236 }
237 return {
238 touchX: touch.clientX,
239 touchY: touch.clientY
240 };
241 }
242
243 function _getIns(ownerIns) {
244 var ins = ownerIns.getState().currentIns;
245 if (!ins) {
246 ownerIns.callMethod('_handlePropUpdate');
247 }
248 return ins;
249 }
250
251 function _touchDisabled(e, ins, processTag) {
252 var dataset = ins.getDataset();
253 var state = ins.getState();
254 var loading = _isTrue(dataset.loading);
255 var useChatRecordMode = _isTrue(dataset.usechatrecordmode);
256 var refresherEnabled = _isTrue(dataset.refresherenabled);
257 var useCustomRefresher = _isTrue(dataset.usecustomrefresher);
258 var usePageScroll = _isTrue(dataset.usepagescroll);
259 var pageScrollTop = parseFloat(dataset.pagescrolltop);
260 var scrollTop = parseFloat(dataset.scrolltop);
261 return loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher ||
262 (usePageScroll && useCustomRefresher && pageScrollTop > 5) ||
263 (!usePageScroll && useCustomRefresher && scrollTop > 5);
264 }
265
266 function _getAngleIsInRange(e, touch, state, dataset) {
267 var maxAngle = dataset.refreshermaxangle;
268 var refresherAecc = _isTrue(dataset.refresheraecc);
269 var lastTouch = state.lastTouch;
270 var reachMaxAngle = state.reachMaxAngle;
271 var moveDis = state.oldMoveDis;
272 if (!lastTouch) return true;
273 if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) {
274 if ((!moveDis || moveDis < 1) && !refresherAecc && reachMaxAngle != null && !reachMaxAngle) return false;
275 var x = Math.abs(touch.touchX - lastTouch.touchX);
276 var y = Math.abs(touch.touchY - lastTouch.touchY);
277 var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
278 if ((x || y) && x > 1) {
279 var angle = Math.asin(y / z) / Math.PI * 180;
280 if (angle < maxAngle) {
281 var hitReachMaxAngleCount = state.hitReachMaxAngleCount || 0;
282 state.hitReachMaxAngleCount = ++hitReachMaxAngleCount;
283 if (state.hitReachMaxAngleCount > 2) {
284 state.lastTouch = touch;
285 state.reachMaxAngle = false;
286 }
287 return false;
288 }
289 }
290 }
291 state.lastTouch = touch;
292 return true;
293 }
294
295 function _handlePullingDown(state, ins, onPullingDown) {
296 var oldOnPullingDown = state.onPullingDown || false;
297 if (oldOnPullingDown != onPullingDown) {
298 ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown);
299 }
300 state.onPullingDown = onPullingDown;
301 }
302
303 function _isTrue(value) {
304 value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false;
305 return value == true || value == 'true';
306 }
307
308 module.exports = {
309 touchstart: touchstart,
310 touchmove: touchmove,
311 touchend: touchend,
312 mousedown: mousedown,
313 mousemove: mousemove,
314 mouseup: mouseup,
315 mouseleave: mouseleave,
316 propObserver: propObserver
317 }
1 {
2 "id": "z-paging",
3 "name": "z-paging",
4 "displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,支持nvue、vue3",
5 "version": "2.5.9",
6 "description": "超简单、低耦合!使用wxs+renderjs实现。支持长列表优化,支持自定义下拉刷新、上拉加载更多,支持自动管理空数据图、点击返回顶部,支持聊天分页、本地分页,支持国际化等100+项配置",
7 "keywords": [
8 "下拉刷新",
9 "上拉加载",
10 "分页器",
11 "nvue",
12 "虚拟列表"
13 ],
14 "repository": "https://github.com/SmileZXLee/uni-z-paging",
15 "engines": {
16 "HBuilderX": "^3.0.7"
17 },
18 "dcloudext": {
19 "sale": {
20 "regular": {
21 "price": "0.00"
22 },
23 "sourcecode": {
24 "price": "0.00"
25 }
26 },
27 "contact": {
28 "qq": "393727164"
29 },
30 "declaration": {
31 "ads": "无",
32 "data": "无",
33 "permissions": "无"
34 },
35 "npmurl": "https://www.npmjs.com/package/z-paging",
36 "type": "component-vue"
37 },
38 "uni_modules": {
39 "dependencies": [],
40 "encrypt": [],
41 "platforms": {
42 "cloud": {
43 "tcb": "y",
44 "aliyun": "y"
45 },
46 "client": {
47 "App": {
48 "app-vue": "y",
49 "app-nvue": "y"
50 },
51 "H5-mobile": {
52 "Safari": "y",
53 "Android Browser": "y",
54 "微信浏览器(Android)": "y",
55 "QQ浏览器(Android)": "y"
56 },
57 "H5-pc": {
58 "Chrome": "y",
59 "IE": "y",
60 "Edge": "y",
61 "Firefox": "y",
62 "Safari": "y"
63 },
64 "小程序": {
65 "微信": "y",
66 "阿里": "y",
67 "百度": "y",
68 "字节跳动": "y",
69 "QQ": "y",
70 "钉钉": "y",
71 "快手": "y",
72 "飞书": "y",
73 "京东": "y"
74 },
75 "快应用": {
76 "华为": "y",
77 "联盟": "y"
78 },
79 "Vue": {
80 "vue2": "y",
81 "vue3": "y"
82 }
83 }
84 }
85 }
86 }
...\ No newline at end of file ...\ No newline at end of file
1 # z-paging
2
3 <p align="center">
4 <img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;">
5 </p>
6
7 [![version](https://img.shields.io/badge/version-2.5.9-blue)](https://github.com/SmileZXLee/uni-z-paging)
8 [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
9
10 ### 文档地址:[https://z-paging.zxlee.cn](https://z-paging.zxlee.cn)
11
12 ### 更新组件前,请注意[版本差异](https://z-paging.zxlee.cn/start/upgrade-guide.html)
13
14 ***
15 ### 功能&特点
16 * 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。
17 * 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。
18 * 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。
19 * 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部等诸多功能。
20 * 【全平台兼容】支持vue、nvue,vue2、vue3,支持h5、app及各家小程序。
21 * 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs从视图层实现下拉刷新;支持虚拟列表,轻松渲染万级数据!
22
23 ***
24 ### 反馈qq群
25 * 官方1群`已满`[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH)
26
27 * 官方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)
28
29 ***
30
31 ### 预览
32
33 ***
34
35 | 自定义下拉刷新效果演示 | 滑动切换选项卡+吸顶演示 |
36 | :----------------------------------------------------------: | :----------------------------------------------------------: |
37 | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo5.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo6.gif) |
38
39 | 聊天记录模式演示 | 虚拟列表(流畅渲染1万+条)演示 |
40 | :----------------------------------------------------------: | :----------------------------------------------------------: |
41 | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo7.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo8.gif) |
42
43 ### 在线demo体验地址:
44
45 * [https://demo.z-paging.zxlee.cn](https://demo.z-paging.zxlee.cn)
46
47 | 扫码体验 |
48 | ------------------------------------------------------------ |
49 | ![](https://z-paging.zxlee.cn/public/img/code.png) |
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!