first commit

This commit is contained in:
2026-02-23 16:31:39 +08:00
commit 8dcee4fadd
269 changed files with 45231 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
unpackage/
**/node_modules/
node_modules

20
.hbuilderx/launch.json Normal file
View File

@@ -0,0 +1,20 @@
{ // launch.json 配置了启动调试时相关设置configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数remote代表前端连云端云函数
"version": "0.0",
"configurations": [{
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

18
App.vue Normal file
View File

@@ -0,0 +1,18 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style lang="scss">
/* 注意要写在第一行同时给style标签加入lang="scss"属性 */
@import "uview-ui/index.scss";
</style>

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 www.uviewui.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

106
README.md Normal file
View File

@@ -0,0 +1,106 @@
<p align="center">
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
</p>
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
<h3 align="center">多平台快速开发的UI框架</h3>
## 说明
uView UI是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架全面的组件和便捷的工具会让您信手拈来如鱼得水
## 特性
- 兼容安卓iOS微信小程序H5QQ小程序百度小程序支付宝小程序头条小程序
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
- 众多贴心的JS利器让您飞镖在手召之即来百步穿杨
- 众多的常用页面和布局,让您专注逻辑,事半功倍
- 详尽的文档支持,现代化的演示效果
- 按需引入,精简打包体积
## 安装
```bash
# npm方式安装
npm i uview-ui
```
## 快速上手
1. `main.js`引入uView库
```js
// main.js
import uView from 'uview-ui';
Vue.use(uView);
```
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
```css
/* App.vue */
<style lang="scss">
@import "uview-ui/index.scss";
</style>
```
3. `uni.scss`引入全局scss变量文件
```css
/* uni.scss */
@import "uview-ui/theme.scss";
```
4. `pages.json`配置easycom规则(按需引入)
```js
// pages.json
{
"easycom": {
// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
// npm安装方式
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
// 下载安装方式
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
},
// 此为本身已有的内容
"pages": [
// ......
]
}
```
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
## 使用方法
配置easycom规则后自动按需引入无需`import`组件,直接引用即可。
```html
<template>
<u-button>按钮</u-button>
</template>
```
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
## 链接
- [官方文档](https://uviewui.com/)
- [更新日志](https://uviewui.com/components/changelog.html)
- [升级指南](https://uviewui.com/components/changelog.html)
- [关于我们](https://uviewui.com/cooperation/about.html)
## 预览
您可以通过**微信**扫码,查看最佳的演示效果。
<br>
<br>
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
<!-- ## 捐赠uView的研发
uView文档和源码全部开源免费如果您认为uView帮到了您的开发工作您可以捐赠uView的研发工作捐赠无门槛哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
<img src="https://uviewui.com/common/wechat.png" width="220" >
<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
-->
## 版权信息
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议意味着您无需支付任何费用也无需授权即可将uView应用到您的产品中。

View File

@@ -0,0 +1,52 @@
### 使用说明
拷贝该组件到 components 目录下之后
在 script 中引用组件
```js
import hjbUpload from "@/components/hjb-upload/hjb-upload.vue";
export default {
components: { hjbUpload },
};
```
在 template 中使用组件:
```html
<hjb-upload
ref="upload"
name="file"
action="/upload"
:headers="headers"
:limit="10"
:file-list="fileList"
:on-change="uploadChange"
>
<!-- 自定义渲染可选 -->
<image slot-scope="{item,index}" :src="item.url" />
</hjb-upload>
```
### 组件参数说明
| 参数 | 说明 | 类型 | 默认值 |
| ------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------ |
| action | 必选参数,上传的地址 | string | — |
| headers | 设置上传的请求头部 | object | — |
| multiple | 是否支持多选文件 | boolean | false |
| formData | 上传时附带的额外参数 | object | — |
| name | 上传的文件字段名 | string | file |
| fileType | 接受上传的文件类型,例如: ['png'] | array | [] |
| on-preview | 点击文件列表中已上传的文件时的钩子 | function(file) | — |
| on-remove | 文件列表移除文件时的钩子 | function(file, fileList) | — |
| on-success | 文件上传成功时的钩子 | function(response, file, fileList) | — |
| on-error | 文件上传失败时的钩子 | function(err, file, fileList) | — |
| on-progress | 文件上传时的钩子 | function(event, file, fileList) | — |
| on-change | 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 | function(file, fileList) | — |
| before-upload | 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject则停止上传。 | function(file) | — |
| before-remove | 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject则停止删除。 | function(file, fileList) | — |
| auto-upload | 是否在选取文件后立即进行上传 | boolean | true |
| file-list | 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}] | array | [] |
| http-request | 覆盖默认的上传行为,可以自定义上传的实现 | function | — |
| disabled | 是否禁用 | boolean | false |
| limit | 最大允许上传个数 | number | 10 |

View File

@@ -0,0 +1,599 @@
<template>
<view class="upload">
<view
v-for="(item, index) in uploadFiles"
class="upload__file"
:class="'upload__file--' + item.status"
:style="{
width: addUnit(width),
height: addUnit(height),
}"
@tap="onPreview(index, item)"
>
<slot :item="item" :index="index">
<image
:style="{
width: addUnit(width),
height: addUnit(height),
}"
:src="item.url"
:mode="imageMode"
/>
<view
class="upload__file--mask"
v-if="['uploading', 'fail'].includes(item.status)"
>
<text v-if="item.status == 'uploading'">{{ item.percentage }}%</text>
<icon
v-if="item.status == 'fail'"
type="warn"
color="red"
size="20"
/>
</view>
</slot>
<icon
class="upload__file--remove"
type="clear"
color="#263240"
size="20"
@tap.stop="handleRemove(item)"
/>
</view>
<view
:style="{
width: addUnit(width),
height: addUnit(height),
}"
class="upload__btn"
hover-class="upload__btn--hover"
hover-stay-time="150"
v-if="showUploadBtn"
@tap="chooseImage"
>
<slot>
<view class="icon"></view>
</slot>
</view>
</view>
</template>
<script>
function noop() {}
function uuid() {
let d = new Date().getTime();
let uuid = "xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
let r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
});
return uuid;
}
export default {
name: "hjb-upload",
props: {
fileList: {
type: Array,
default: () => [],
},
// 后端地址
action: {
type: String,
default: "",
},
// 是否开启图片多选
multiple: {
type: Boolean,
default: false,
},
sourceType: {
type: Array,
default: () => ["album", "camera"],
},
// 所选的图片的尺寸, 可选值为original compressed
sizeType: {
type: Array,
default: () => ["original", "compressed"],
},
// 预览上传的图片时的裁剪模式和image组件mode属性一致
imageMode: {
type: String,
default: "aspectFill",
},
// 头部信息
headers: {
type: Object,
default: () => ({}),
},
// 额外携带的参数
formData: {
type: Object,
default: () => ({}),
},
// 上传的文件字段名
name: {
type: String,
default: "file",
},
// 文件大小限制单位为byte
maxSize: {
type: [String, Number],
default: Number.MAX_VALUE,
},
// 最大上传数量 多图的时候才有效
limit: {
type: [String, Number],
default: 10,
},
// 是否自动上传
autoUpload: {
type: Boolean,
default: true,
},
// 是否显示toast消息提示
showTips: {
type: Boolean,
default: true,
},
// 内部预览图片区域和选择图片按钮的区域宽度
width: {
type: [String, Number],
default: 120,
},
// 内部预览图片区域和选择图片按钮的区域高度
height: {
type: [String, Number],
default: 120,
},
// 允许上传的图片后缀
fileType: {
type: Array,
default() {
// 支付宝小程序真机选择图片的后缀为"image"
// https://opendocs.alipay.com/mini/api/media-image
return [];
},
},
// 是否启用
disabled: {
type: Boolean,
default: false,
},
// 是否启用预览
enablePreview: {
type: Boolean,
default: true,
},
beforeUpload: Function,
beforeRemove: Function,
onRemove: {
type: Function,
default: noop,
},
onChange: {
type: Function,
default: noop,
},
onSuccess: {
type: Function,
default: noop,
},
onProgress: {
type: Function,
default: noop,
},
onError: {
type: Function,
default: noop,
},
httpRequest: {
type: Function,
default: uni.uploadFile,
},
},
data() {
return {
uploadFiles: [],
uploading: false,
reqs: {},
};
},
computed: {
showUploadBtn() {
if (this.multiple) {
return this.limit > this.uploadFiles.length;
}
return this.uploadFiles.length < 1;
},
},
watch: {
fileList: {
immediate: true,
handler(files) {
this.uploadFiles = files.map((item) => {
item.uid = item.uid || uuid();
item.status = item.status || "success";
return item;
});
},
},
},
methods: {
// 清除列表
clear() {
this.uploadFiles = [];
},
// 该方法供用户通过ref调用手动上传
upload() {
if (this.disabled) return;
this.uploadFiles
.filter((file) => file.status === "ready")
.forEach((item) => {
this.uploadFile(item);
});
},
// 图片预览
onPreview(index, file) {
if (!this.enablePreview) return;
const images = this.uploadFiles.map((item) => item.url || item.path);
uni.previewImage({
current: index,
urls: images,
success: () => {
this.$emit("preview", file, index);
},
fail: () => {
uni.showToast({
title: "Preview image failed",
icon: "none",
});
},
});
},
// 选择图片
chooseImage() {
if (this.disabled) return;
const {
limit,
multiple,
maxSize,
uploadFiles,
sourceType,
sizeType,
autoUpload,
} = this;
const maxCount = limit - uploadFiles.length;
uni.chooseImage({
count: multiple ? maxCount : 1,
sourceType,
sizeType,
type: "image",
success: (res) => {
const files = res.tempFiles;
if (limit && uploadFiles.length + files.length > limit) {
this.showToast("The maximum number of files allowed is exceeded");
return;
}
files.forEach((val, index) => {
// 检查文件后缀是否允许如果不在this.fileType内就会返回false
if (!this.checkFileExt(val)) return;
// 如果是非多选index大于等于1或者超出最大限制数量时不处理
if (!multiple && index >= 1) return;
if (val.size > maxSize) {
this.showToast("Exceeds allowable file size");
return;
}
val.uid = uuid();
let file = {
status: "ready",
size: val.size,
percentage: 0,
uid: val.uid,
raw: val,
url: val.path,
};
this.uploadFiles.push(file);
this.onChange(file, this.uploadFiles);
if (autoUpload) this.uploadFile(file);
});
// 每次图片选择完,抛出一个事件,并将当前内部选择的图片数组抛出去
this.$emit("choose-complete", this.uploadFiles);
},
fail: (e) => {
this.$emit("choose-fail", e);
},
});
},
getFile(rawFile) {
let fileList = this.uploadFiles;
let target;
fileList.every((item) => {
target = rawFile.uid === item.uid ? item : null;
return !target;
});
return target;
},
// 对失败的图片重新上传
retry() {
uni.showLoading({
title: "重新上传",
});
this.uploadFiles
.filter((item) => item.ststus == "fail")
.forEach((item) => {
this.uploadFile(item);
});
},
// 上传图片
uploadFile(rawFile) {
// 检查上传地址
if (!this.action) {
this.showToast("Please configure the upload address", true);
return;
}
if (!this.beforeUpload) {
this.post(rawFile);
return;
}
const before = this.beforeUpload(rawFile);
if (before && before.then) {
console.log("async before");
before.then(
(file) => {
this.post(file);
},
() => {
this.handleRemove(null, rawFile);
}
);
} else if (before !== false) {
this.post(rawFile);
} else {
this.handleRemove(null, rawFile);
}
},
post(rawFile) {
const { uid } = rawFile;
const options = {
header: this.headers,
filePath: rawFile.url,
formData: this.formData,
name: this.name,
url: this.action,
success: (res) => {
// 判断是否json字符串将其转为json格式
let data = this.jsonString(res.data)
? JSON.parse(res.data)
: res.data;
if (![200, 201, 204].includes(res.statusCode)) {
this.uploadError(data, rawFile);
} else {
this.uploadSuccess(data, rawFile);
}
delete this.reqs[uid];
},
fail: (err) => {
this.uploadError(err, rawFile);
delete this.reqs[uid];
},
};
var uploadTask = this.httpRequest(options);
this.reqs[uid] = uploadTask;
uploadTask.onProgressUpdate((res) => {
this.uploadProgress(res, rawFile);
});
},
// 上传中
uploadProgress(ev, rawFile) {
const file = this.getFile(rawFile);
this.onProgress(ev, file, this.uploadFiles);
file.status = "uploading";
file.percentage = ev.progress || 0;
},
// 上传成功
uploadSuccess(res, rawFile) {
const file = this.getFile(rawFile);
const fileList = this.uploadFiles;
file.status = "success";
file.response = res;
file.name = res.name;
file.url = res.url;
this.onSuccess(res, file, this.uploadFiles);
this.onChange(file, this.uploadFiles);
},
// 上传失败
uploadError(err, rawFile) {
const file = this.getFile(rawFile);
const fileList = this.uploadFiles;
file.status = "fail";
fileList.splice(fileList.indexOf(file), 1);
this.onError(err, file, this.uploadFiles);
this.onChange(file, this.uploadFiles);
},
// 删除
handleRemove(file, raw) {
if (raw) {
file = this.getFile(raw);
}
let doRemove = () => {
this.abort(file);
let fileList = this.uploadFiles;
fileList.splice(fileList.indexOf(file), 1);
this.onRemove(file, fileList);
};
if (!this.beforeRemove) {
doRemove();
} else if (typeof this.beforeRemove === "function") {
const before = this.beforeRemove(file, this.uploadFiles);
if (before && before.then) {
before.then(() => {
doRemove();
}, noop);
} else if (before !== false) {
doRemove();
}
}
},
// 中断上传任务
abort(file) {
const { reqs } = this;
if (file) {
let uid = file;
if (file.uid) uid = file.uid;
if (reqs[uid]) {
reqs[uid].abort();
}
} else {
Object.keys(reqs).forEach((uid) => {
if (reqs[uid]) reqs[uid].abort();
delete reqs[uid];
});
}
},
// 判断文件后缀是否允许
checkFileExt(file) {
if (this.fileType.length == 0) {
return true;
}
// 检查是否在允许的后缀中
let noArrowExt = false;
// 获取后缀名
let fileExt = "";
const reg = /.+\./;
// 如果是H5需要从name中判断
// #ifdef H5
fileExt = file.name.replace(reg, "").toLowerCase();
// #endif
// 非H5需要从path中读取后缀
// #ifndef H5
fileExt = file.path.replace(reg, "").toLowerCase();
// #endif
// 使用数组的some方法只要符合limitType中的一个就返回true
noArrowExt = this.fileType.some((ext) => {
// 转为小写
return ext.toLowerCase() === fileExt;
});
if (!noArrowExt)
this.showToast(
`You are not allowed to select files in ${fileExt} format`
);
return noArrowExt;
},
// 提示用户消息
showToast(message, force = false) {
if (this.showTips || force) {
uni.showToast({
title: message,
icon: "none",
});
}
},
// 添加单位如果有rpx%px等单位结尾或者值为auto直接返回否则加上rpx单位结尾
addUnit(value = "auto", unit = "rpx") {
let reg = /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/;
value = String(value);
// 用uView内置验证规则中的number判断是否为数值
return reg.test(value) ? `${value}${unit}` : value;
},
/**
* 是否json字符串
*/
jsonString(value) {
if (typeof value == "string") {
try {
var obj = JSON.parse(value);
if (typeof obj == "object" && obj) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
return false;
},
},
};
</script>
<style lang="scss" scoped>
.upload {
display: flex;
flex-wrap: wrap;
align-items: center;
&__file,
&__btn {
border-radius: 4rpx;
margin: 0 30rpx 30rpx 0;
}
&__file {
position: relative;
image {
border-radius: 4rpx;
}
&--mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(37, 38, 45, 0.4);
border-radius: 4rpx;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
}
&--remove {
position: absolute;
right: 0;
top: 0;
transform: translate(50%, -50%);
border: 3rpx solid #fff;
border-radius: 50%;
background: #fff;
}
}
&__btn {
display: flex;
align-items: center;
justify-content: center;
border: 2rpx solid #e0e0e0;
&--hover {
background: rgba(0, 0, 0, 0.04);
}
.icon {
position: relative;
&::before,
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 32rpx;
height: 3rpx;
transform: translate(-50%, -50%);
background-color: #b0b0b0;
}
&::after {
transform: translate(-50%, -50%) rotate(90deg);
}
}
}
}
</style>

View File

@@ -0,0 +1,4 @@
{
"name": "hjb-upload",
"version": "1.0.3"
}

View File

@@ -0,0 +1,34 @@
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<!-- z-paging-cell用于在nvue中使用cell包裹vue中使用view包裹 -->
<template>
<!-- #ifdef APP-NVUE -->
<cell :style="[cellStyle]">
<slot />
</cell>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<view :style="[cellStyle]">
<slot />
</view>
<!-- #endif -->
</template>
<script>
export default {
name: "z-paging-cell",
props: {
//cellStyle
cellStyle: {
type: Object,
default: function() {
return {}
}
}
}
}
</script>

View File

@@ -0,0 +1,160 @@
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<!-- 空数据占位view此组件支持easycom规范可以在项目中直接引用 -->
<template>
<view :class="{'zp-container':true,'zp-container-fixed':emptyViewFixed}" :style="[finalEmptyViewStyle]">
<view class="zp-main">
<image v-if="!emptyViewImg.length" class="zp-main-image" :style="[emptyViewImgStyle]" :src="emptyImg" />
<image v-else class="zp-main-image" mode="aspectFit" :style="[emptyViewImgStyle]" :src="emptyViewImg" />
<text class="zp-main-title" :style="[emptyViewTitleStyle]">{{emptyViewText}}</text>
<text v-if="showEmptyViewReload" class="zp-main-error-btn" :style="[emptyViewReloadStyle]" @click="reloadClick">{{emptyViewReloadText}}</text>
</view>
</view>
</template>
<script>
import zStatic from '../z-paging/js/z-paging-static'
export default {
name: "z-paging-empty-view",
data() {
return {
base64Empty: zStatic.base64Empty,
base64Error: zStatic.base64Error
};
},
props: {
//空数据描述文字
emptyViewText: {
type: String,
default: '没有数据哦~'
},
//空数据图片
emptyViewImg: {
type: String,
default: ''
},
//是否显示空数据图重新加载按钮
showEmptyViewReload: {
type: Boolean,
default: false
},
//空数据点击重新加载文字
emptyViewReloadText: {
type: String,
default: '重新加载'
},
//是否是加载失败
isLoadFailed: {
type: Boolean,
default: false
},
//空数据图样式
emptyViewStyle: {
type: Object,
default: function() {
return {}
}
},
//空数据图img样式
emptyViewImgStyle: {
type: Object,
default: function() {
return {}
}
},
//空数据图描述文字样式
emptyViewTitleStyle: {
type: Object,
default: function() {
return {}
}
},
//空数据图重新加载按钮样式
emptyViewReloadStyle: {
type: Object,
default: function() {
return {}
}
},
//空数据图z-index
emptyViewZIndex: {
type: Number,
default: 9
},
//空数据图片是否使用fixed布局并铺满z-paging
emptyViewFixed: {
type: Boolean,
default: true
}
},
computed: {
emptyImg() {
return this.isLoadFailed ? this.base64Error : this.base64Empty;
},
finalEmptyViewStyle(){
this.emptyViewStyle['z-index'] = this.emptyViewZIndex;
return this.emptyViewStyle;
}
},
methods: {
reloadClick() {
this.$emit('reload');
}
}
}
</script>
<style scoped>
.zp-container{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.zp-container-fixed {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.zp-main{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
padding: 50rpx 0rpx;
}
.zp-main-image {
width: 200rpx;
height: 200rpx;
}
.zp-main-title {
font-size: 26rpx;
color: #aaaaaa;
text-align: center;
margin-top: 10rpx;
}
.zp-main-error-btn {
font-size: 26rpx;
padding: 8rpx 24rpx;
border: solid 1px #dddddd;
border-radius: 6rpx;
color: #aaaaaa;
margin-top: 50rpx;
}
</style>

View File

@@ -0,0 +1,145 @@
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<!-- 滑动切换选项卡swiper-item此组件支持easycom规范可以在项目中直接引用 -->
<template>
<view class="zp-swiper-item-container">
<z-paging ref="paging" :fixed="false"
:useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle"
:preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol"
@query="_queryList" @listChange="_updateList" :mounted-auto-call-reload="false" style="height: 100%;">
<slot />
<template v-slot:header>
<slot name="header"/>
</template>
<template v-slot:cell="{item,index}">
<slot name="cell" :item="item" :index="index"/>
</template>
<template v-slot:footer>
<slot name="footer"/>
</template>
</z-paging>
</view>
</template>
<script>
import zPaging from '../z-paging/z-paging'
export default {
name: "z-paging-swiper-item",
components: {
zPaging
},
data() {
return {
firstLoaded: false
}
},
props: {
//当前组件的index也就是当前组件是swiper中的第几个
tabIndex: {
type: Number,
default: function() {
return 0
}
},
//当前swiper切换到第几个index
currentIndex: {
type: Number,
default: function() {
return 0
}
},
//是否使用虚拟列表,默认为否
useVirtualList: {
type: Boolean,
default: false
},
//是否在z-paging内部循环渲染列表(内置列表)默认为否。若use-virtual-list为true则此项恒为true
useInnerList: {
type: Boolean,
default: false
},
//内置列表cell的key名称仅nvue有效在nvue中开启use-inner-list时必须填此项
cellKeyName: {
type: String,
default: ''
},
//innerList样式
innerListStyle: {
type: Object,
default: function() {
return {};
}
},
//预加载的列表可视范围(列表高度)页数默认为7即预加载当前页及上下各7页的cell。此数值越大则虚拟列表中加载的dom越多内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题
preloadPage: {
type: [Number, String],
default: 7
},
//虚拟列表cell高度模式默认为fixed也就是每个cell高度完全相同将以第一个cell高度为准进行计算。可选值【dynamic】即代表高度是动态非固定的【dynamic】性能低于【fixed】。
cellHeightMode: {
type: String,
default: 'fixed'
},
//虚拟列表列数默认为1。常用于每行有多列的情况例如每行有2列数据需要将此值设置为2
virtualListCol: {
type: [Number, String],
default: 1
},
//虚拟列表scroll取样帧率默认为60过高可能出现卡顿等问题
virtualScrollFps: {
type: [Number, String],
default: 60
},
},
watch: {
currentIndex: {
handler(newVal, oldVal) {
if (newVal === this.tabIndex) {
//懒加载当滑动到当前的item时才去加载
if (!this.firstLoaded) {
this.$nextTick(()=>{
let delay = 5;
// #ifdef MP-TOUTIAO
delay = 100;
// #endif
setTimeout(() => {
this.$refs.paging.reload();
}, delay);
})
}
}
},
immediate: true
}
},
methods: {
reload(data) {
this.$refs.paging.reload(data);
},
complete(data) {
this.firstLoaded = true;
this.$refs.paging.complete(data);
},
_queryList(pageNo, pageSize, from) {
this.$emit('query', pageNo, pageSize, from);
},
_updateList(list) {
this.$emit('updateList', list);
}
}
}
</script>
<style scoped>
.zp-swiper-item-container {
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
</style>

View File

@@ -0,0 +1,221 @@
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<!-- 滑动切换选项卡swiper此组件支持easycom规范可以在项目中直接引用 -->
<template>
<view :class="fixed?'zp-swiper-container zp-swiper-container-fixed':'zp-swiper-container'" :style="[finalSwiperStyle]">
<!-- #ifndef APP-PLUS -->
<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
<!-- #endif -->
<slot v-if="$slots.top" name="top" />
<view class="zp-swiper-super">
<view v-if="$slots.left" :class="{'zp-swiper-left':true,'zp-absoulte':isOldWebView}">
<slot name="left" />
</view>
<view :class="{'zp-swiper':true,'zp-absoulte':isOldWebView}" :style="[swiperContentStyle]">
<slot />
</view>
<view v-if="$slots.right" :class="{'zp-swiper-right':true,'zp-absoulte zp-right':isOldWebView}">
<slot name="right" />
</view>
</view>
<slot v-if="$slots.bottom" name="bottom" />
</view>
</template>
<script>
export default {
name: "z-paging-swiper",
data() {
return {
systemInfo: null,
cssSafeAreaInsetBottom: -1,
swiperContentStyle: {}
};
},
props: {
//是否使用fixed布局默认为是
fixed: {
type: Boolean,
default: true
},
//是否开启底部安全区域适配
safeAreaInsetBottom: {
type: Boolean,
default: false
},
//z-paging-swiper样式
swiperStyle: {
type: Object,
default: function() {
return {};
},
}
},
mounted() {
this.$nextTick(() => {
this.systemInfo = uni.getSystemInfoSync();
})
// #ifndef APP-PLUS
this._getCssSafeAreaInsetBottom();
// #endif
this._updateLeftAndRightWidth();
this.swiperContentStyle = {'flex': '1'};
// #ifndef APP-NVUE
this.swiperContentStyle = {width: '100%',height: '100%'};
// #endif
},
computed: {
finalSwiperStyle() {
let swiperStyle = this.swiperStyle;
if (!this.systemInfo) return swiperStyle;
const windowTop = this.systemInfo.windowTop;
const windowBottom = this.systemInfo.windowBottom;
if (this.fixed) {
if (windowTop && !swiperStyle.top) {
swiperStyle.top = windowTop + 'px';
}
if (!swiperStyle.bottom) {
let bottom = windowBottom ? windowBottom : 0;
if (this.safeAreaInsetBottom) {
bottom += this.safeAreaBottom;
}
if(bottom > 0){
swiperStyle.bottom = bottom + 'px';
}
}
}
return swiperStyle;
},
safeAreaBottom() {
if(!this.systemInfo){
return 0;
}
let safeAreaBottom = 0;
// #ifdef APP-PLUS
safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0;
// #endif
// #ifndef APP-PLUS
safeAreaBottom = this.cssSafeAreaInsetBottom === -1 ? 0 : this.cssSafeAreaInsetBottom;
// #endif
return safeAreaBottom;
},
isOldWebView() {
// #ifndef APP-NVUE
try {
const systemInfos = uni.getSystemInfoSync().system.split(' ');
const deviceType = systemInfos[0];
const version = parseInt(systemInfos[1].slice(0,1));
if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
return true;
}
} catch(e){
return false;
}
// #endif
return false;
}
},
methods: {
//通过获取css设置的底部安全区域占位view高度设置bottom距离
_getCssSafeAreaInsetBottom(){
const query = uni.createSelectorQuery().in(this);
query.select('.zp-safe-area-inset-bottom').boundingClientRect(res => {
if (res) {
this.cssSafeAreaInsetBottom = res.height;
}
}).exec();
},
//获取slot="left"和slot="right"宽度并且更新布局
_updateLeftAndRightWidth(){
if (!this.isOldWebView) return;
this.$nextTick(() => {
let delayTime = 0;
// #ifdef MP-BAIDU
delayTime = 10;
// #endif
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.zp-swiper-left').boundingClientRect(res => {
this.swiperContentStyle['left'] = res ? res.width + 'px' : 0;
}).exec();
query.select('.zp-swiper-right').boundingClientRect(res => {
this.swiperContentStyle['right'] = res ? res.width + 'px' : 0;
}).exec();
}, delayTime)
})
}
}
}
</script>
<style scoped>
.zp-swiper-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
flex: 1;
}
.zp-swiper-container-fixed {
position: fixed;
/* #ifndef APP-NVUE */
height: auto;
width: auto;
/* #endif */
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.zp-safe-area-inset-bottom {
position: absolute;
/* #ifndef APP-PLUS */
height: env(safe-area-inset-bottom);
/* #endif */
}
.zp-swiper-super {
flex: 1;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.zp-swiper-left,.zp-swiper-right{
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
}
.zp-swiper {
flex: 1;
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
}
.zp-absoulte {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
width: auto;
/* #endif */
}
.zp-right{
right: 0;
}
.zp-swiper-item {
height: 100%;
}
</style>

View File

@@ -0,0 +1,152 @@
<!-- [z-paging]上拉加载更多view -->
<template>
<view class="zp-l-container" :style="[zConfig.customStyle]" @click="doClick">
<template v-if="!zConfig.hideContent">
<text v-if="zConfig.showNoMoreLine&&finalStatus===2" :class="zConfig.defaultThemeStyle==='white'?'zp-l-line zp-l-line-white':'zp-l-line zp-l-line-black'"
:style="[zConfig.noMoreLineCustomStyle]" />
<!-- #ifndef APP-NVUE -->
<image v-if="finalStatus===1&&zConfig.loadingIconCustomImage.length"
:src="zConfig.loadingIconCustomImage" :class="{'zp-l-line-loading-custom-image':true,'zp-l-line-loading-custom-image-animated':zConfig.loadingAnimated}" />
<image v-if="finalStatus===1&&zConfig.loadingIconType==='flower'&&!zConfig.loadingIconCustomImage.length"
class="zp-line-loading-image" :style="[zConfig.iconCustomStyle]" :src="zConfig.defaultThemeStyle==='white'?base64FlowerWhite:base64Flower" />
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view>
<loading-indicator v-if="finalStatus===1&&zConfig.loadingIconType!=='circle'" class="zp-line-loading-image" :style="[{color:zConfig.defaultThemeStyle==='white'?'white':'#777777'}]" animating />
</view>
<!-- #endif -->
<text v-if="finalStatus===1&&zConfig.loadingIconType==='circle'&&!zConfig.loadingIconCustomImage.length"
:class="zConfig.defaultThemeStyle==='white'?'zp-l-line-loading-view zp-l-line-loading-view-white':'zp-l-line-loading-view zp-l-line-loading-view-black'" :style="[zConfig.iconCustomStyle]" />
<text :class="zConfig.defaultThemeStyle==='white'?'zp-l-text zp-l-text-white':'zp-l-text zp-l-text-black'" :style="[zConfig.titleCustomStyle]">{{ownLoadingMoreText}}</text>
<text v-if="zConfig.showNoMoreLine&&finalStatus===2" :class="zConfig.defaultThemeStyle==='white'?'zp-l-line zp-l-line-white':'zp-l-line zp-l-line-black'" :style="[zConfig.noMoreLineCustomStyle]" />
</template>
</view>
</template>
<script>
import zStatic from '../js/z-paging-static'
export default {
name: 'z-paging-load-more',
data() {
return {
base64Arrow: zStatic.base64Arrow,
base64Flower: zStatic.base64Flower,
base64FlowerWhite: zStatic.base64FlowerWhite,
};
},
props: ['zConfig'],
computed: {
ownLoadingMoreText() {
return this.statusTextArr[this.finalStatus];
},
statusTextArr() {
return [this.zConfig.defaultText,this.zConfig.loadingText,this.zConfig.noMoreText,this.zConfig.failText];
},
finalStatus() {
if (this.zConfig.defaultAsLoading && this.zConfig.status === 0) return 1;
return this.zConfig.status;
}
},
methods: {
doClick() {
this.$emit('doClick');
}
}
}
</script>
<style scoped>
@import "../css/z-paging-static.css";
.zp-l-container {
height: 80rpx;
font-size: 27rpx;
/* #ifndef APP-NVUE */
clear: both;
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
}
.zp-l-line-loading-custom-image {
color: #a4a4a4;
margin-right: 8rpx;
width: 28rpx;
height: 28rpx;
}
.zp-l-line-loading-custom-image-animated{
/* #ifndef APP-NVUE */
animation: loading-circle 1s linear infinite;
/* #endif */
}
.zp-l-line-loading-view {
margin-right: 8rpx;
width: 23rpx;
height: 23rpx;
border: 3rpx solid #dddddd;
border-radius: 50%;
/* #ifndef APP-NVUE */
animation: loading-circle 1s linear infinite;
/* #endif */
/* #ifdef APP-NVUE */
width: 30rpx;
height: 30rpx;
/* #endif */
}
.zp-l-line-loading-view-black {
border-color: #c8c8c8;
border-top-color: #444444;
}
.zp-l-line-loading-view-white {
border-color: #aaaaaa;
border-top-color: #ffffff;
}
.zp-l-text {
/* #ifdef APP-NVUE */
font-size: 30rpx;
margin: 0rpx 10rpx;
/* #endif */
}
.zp-l-text-black {
color: #a4a4a4;
}
.zp-l-text-white {
color: #efefef;
}
.zp-l-line {
height: 1px;
width: 100rpx;
margin: 0rpx 10rpx;
}
.zp-l-line-black {
background-color: #eeeeee;
}
.zp-l-line-white {
background-color: #efefef;
}
/* #ifndef APP-NVUE */
@keyframes loading-circle {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,273 @@
<!-- [z-paging]下拉刷新view -->
<template>
<view style="height: 100%;">
<view :class="showUpdateTime?'zp-r-container zp-r-container-padding':'zp-r-container'">
<view class="zp-r-left">
<image v-if="status!==2" :class="leftImageClass"
:style="[{width: showUpdateTime?'36rpx':'30rpx',height: showUpdateTime?'36rpx':'30rpx','margin-right': showUpdateTime?'20rpx':'9rpx'},imgStyle]"
:src="defaultThemeStyle==='white'?(status===3?base64SuccessWhite:base64ArrowWhite):(status===3?base64Success:base64Arrow)" />
<!-- #ifndef APP-NVUE -->
<image v-else class="zp-line-loading-image zp-r-left-image"
:style="[{width: showUpdateTime?'36rpx':'30rpx',height: showUpdateTime?'36rpx':'30rpx','margin-right': showUpdateTime?'20rpx':'9rpx'},imgStyle]"
:src="defaultThemeStyle==='white'?base64FlowerWhite:base64Flower" />
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view v-else :style="[{'margin-right':showUpdateTime?'18rpx':'12rpx'}]">
<loading-indicator :class="systemInfo.platform==='ios'?'zp-loading-image-ios':'zp-loading-image-android'"
:style="[{color:defaultThemeStyle==='white'?'white':'#777777'},imgStyle]" animating />
</view>
<!-- #endif -->
</view>
<view class="zp-r-right">
<text class="zp-r-right-text"
:style="[rightTextStyle,titleStyle]">{{statusTextArr[status]||defaultText}}
</text>
<text v-if="showUpdateTime&&refresherTimeText.length" class="zp-r-right-text zp-r-right-time-text" :style="[rightTextStyle,updateTimeStyle]">{{refresherTimeText}}</text>
</view>
</view>
</view>
</template>
<script>
const systemInfo = uni.getSystemInfoSync();
import zStatic from '../js/z-paging-static'
import u from '../js/z-paging-utils'
export default {
name: 'z-paging-refresh',
data() {
return {
systemInfo: systemInfo,
base64Arrow: zStatic.base64Arrow,
base64ArrowWhite: zStatic.base64ArrowWhite,
base64Flower: zStatic.base64Flower,
base64FlowerWhite: zStatic.base64FlowerWhite,
base64Success: zStatic.base64Success,
base64SuccessWhite: zStatic.base64SuccessWhite,
refresherTimeText: '',
leftImageLoaded: false
};
},
props: {
'status': {
default: 0
},
'defaultThemeStyle': {},
'defaultText': '',
'pullingText': '',
'refreshingText': '',
'completeText': '',
'showUpdateTime': {
default: false
},
'updateTimeKey': '',
'imgStyle': {
default: {}
},
'titleStyle': {
default: {}
},
'updateTimeStyle': {
default: {}
},
},
computed: {
statusTextArr() {
this.updateTime(this.updateTimeKey);
return [this.defaultText,this.pullingText,this.refreshingText,this.completeText];
},
leftImageClass() {
if(this.status === 3){
return 'zp-r-left-image-no-transform .zp-r-left-image-pre-size';
}
let cls = 'zp-r-left-image ';
if (this.status === 0) {
if (this.leftImageLoaded) {
cls += 'zp-r-arrow-down';
} else {
this.leftImageLoaded = true;
cls += 'zp-r-arrow-down-no-duration';
}
} else {
cls += 'zp-r-arrow-top';
}
return cls + ' zp-r-left-image-pre-size';
},
rightTextStyle() {
let stl = {};
let color = '#555555';
if (this.defaultThemeStyle === 'white') {
color = '#efefef';
}
// #ifdef APP-NVUE
if (this.showUpdateTime) {
stl = {
'height': '40rpx',
'line-height': '40rpx'
};
} else {
stl = {
'height': '80rpx',
'line-height': '80rpx'
};
}
// #endif
stl['color'] = color;
return stl;
}
},
methods: {
updateTime(updateTimeKey) {
if (!updateTimeKey) {
updateTimeKey = this.updateTimeKey;
}
if (this.showUpdateTime) {
this.refresherTimeText = u.getRefesrherFormatTimeByKey(updateTimeKey);
}
}
}
}
</script>
<style scoped>
@import "../css/z-paging-static.css";
.zp-r-container {
/* #ifndef APP-NVUE */
display: flex;
height: 100%;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
}
.zp-r-container-padding {
/* #ifdef APP-NVUE */
padding: 15rpx 0rpx;
/* #endif */
}
.zp-r-left {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
overflow: hidden;
/* #ifdef MP-ALIPAY */
margin-top: -4rpx;
/* #endif */
}
.zp-r-left-image {
/* #ifndef APP-NVUE */
transform: rotate(180deg);
margin-top: 2rpx;
/* #endif */
/* #ifdef APP-NVUE */
transition-duration: .2s;
transition-property: transform;
color: #666666;
/* #endif */
}
.zp-r-left-image-no-transform {
/* #ifndef APP-NVUE */
margin-top: 2rpx;
/* #endif */
/* #ifdef APP-NVUE */
transition-duration: .2s;
transition-property: transform;
color: #666666;
/* #endif */
}
.zp-r-left-image-pre-size{
/* #ifndef APP-NVUE */
width: 30rpx;
height: 30rpx;
overflow: hidden;
/* #endif */
}
.zp-r-arrow-top {
/* #ifndef APP-NVUE */
animation: refresher-arrow-top .2s linear;
-webkit-animation: refresher-arrow-top .2s linear;
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
/* #endif */
/* #ifdef APP-NVUE */
transform: rotate(0deg);
/* #endif */
}
.zp-r-arrow-down {
/* #ifndef APP-NVUE */
animation: refresher-arrow-down .2s linear;
-webkit-animation: refresher-arrow-down .2s linear;
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
/* #endif */
/* #ifdef APP-NVUE */
transform: rotate(180deg);
/* #endif */
}
.zp-r-arrow-down-no-duration {
/* #ifndef APP-NVUE */
animation: refresher-arrow-down 0s linear;
-webkit-animation: refresher-arrow-down 0s linear;
animation-fill-mode: forwards;
-webkit-animation-fill-mode: forwards;
/* #endif */
/* #ifdef APP-NVUE */
transform: rotate(180deg);
/* #endif */
}
.zp-r-right {
font-size: 27rpx;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
}
.zp-r-right-text {
/* #ifdef APP-NVUE */
font-size: 28rpx;
/* #endif */
}
.zp-r-right-time-text {
margin-top: 10rpx;
font-size: 24rpx;
}
/* #ifndef APP-NVUE */
@keyframes refresher-arrow-top {
0% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
100% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
}
@keyframes refresher-arrow-down {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(180deg);
transform: rotate(180deg);
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,3 @@
// z-paging全局配置文件注意避免更新时此文件被覆盖若被覆盖可在此文件中右键->点击本地历史记录,找回覆盖前的配置
export default {}

View File

@@ -0,0 +1,227 @@
/* [z-paging]公共css*/
.z-paging-content {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
/* #endif */
flex-direction: column;
}
.z-paging-content-fixed, .zp-loading-fixed {
position: fixed;
/* #ifndef APP-NVUE */
height: auto;
width: auto;
/* #endif */
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.zp-page-top,.zp-page-bottom {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
position: fixed;
left: 0;
right: 0;
z-index: 999;
}
.zp-page-left,.zp-page-right{
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
}
.zp-scroll-view-super {
flex: 1;
position: relative;
}
.zp-view-super{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.zp-custom-refresher-container {
overflow: hidden;
}
.zp-scroll-view-container,.zp-scroll-view {
position: relative;
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
}
.zp-absoulte{
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
width: auto;
/* #endif */
}
.zp-right{
right: 0;
}
.zp-scroll-view-absolute {
position: absolute;
top: 0;
left: 0;
}
/* #ifndef APP-NVUE */
.zp-scroll-view-hide-scrollbar ::-webkit-scrollbar {
display: none;
-webkit-appearance: none;
width: 0 !important;
height: 0 !important;
background: transparent;
}
/* #endif */
.zp-paging-touch-view {
width: 100%;
height: 100%;
position: relative;
}
.zp-fixed-bac-view {
position: absolute;
width: 100%;
top: 0;
left: 0;
height: 200px;
}
.zp-paging-main {
height: 100%;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.zp-paging-container {
flex: 1;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.zp-chat-record-loading-container {
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
/* #endif */
/* #ifdef APP-NVUE */
width: 750rpx;
/* #endif */
align-items: center;
justify-content: center;
height: 60rpx;
font-size: 26rpx;
}
.zp-chat-record-loading-custom-image {
width: 35rpx;
height: 35rpx;
/* #ifndef APP-NVUE */
animation: loading-flower 1s linear infinite;
/* #endif */
}
.zp-custom-refresher-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
}
.zp-back-to-top {
width: 76rpx;
height: 76rpx;
z-index: 999;
position: absolute;
bottom: 0rpx;
right: 25rpx;
transition-duration: .3s;
transition-property: opacity;
}
.zp-back-to-top-show {
opacity: 1;
}
.zp-back-to-top-hide {
opacity: 0;
}
.zp-back-to-top-img {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
z-index: 999;
}
.zp-empty-view {
/* #ifdef APP-NVUE */
height: 100%;
/* #endif */
flex: 1;
}
.zp-empty-view-center {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
}
.zp-loading-fixed {
z-index: 9999;
}
.zp-safe-area-inset-bottom {
position: absolute;
/* #ifndef APP-PLUS */
height: env(safe-area-inset-bottom);
/* #endif */
}
.zp-n-refresh-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
width: 750rpx;
}
.zp-n-list-container{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex: 1;
}

View File

@@ -0,0 +1,35 @@
/* [z-paging]公用的静态css资源 */
.zp-line-loading-image {
margin-right: 8rpx;
width: 28rpx;
height: 28rpx;
/* #ifndef APP-NVUE */
animation: loading-flower 1s steps(12) infinite;
/* #endif */
color: #666666;
}
.zp-loading-image-ios{
width: 20px;
height: 20px;
}
.zp-loading-image-android{
width: 32rpx;
height: 32rpx;
}
/* #ifndef APP-NVUE */
@keyframes loading-flower {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
/* #endif */

View File

@@ -0,0 +1,108 @@
// [z-paging]点击返回顶部view模块
import u from '.././z-paging-utils'
const ZPBackToTop = {
props: {
//自动显示点击返回顶部按钮,默认为否
autoShowBackToTop: {
type: Boolean,
default: u.gc('autoShowBackToTop', false)
},
//点击返回顶部按钮显示/隐藏的阈值(滚动距离)单位为px默认为400rpx
backToTopThreshold: {
type: [Number, String],
default: u.gc('backToTopThreshold', '400rpx')
},
//点击返回顶部按钮的自定义图片地址默认使用z-paging内置的图片
backToTopImg: {
type: String,
default: u.gc('backToTopImg', '')
},
//点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是
backToTopWithAnimate: {
type: Boolean,
default: u.gc('backToTopWithAnimate', true)
},
//点击返回顶部按钮与底部的距离注意添加单位px或rpx默认为160rpx
backToTopBottom: {
type: [Number, String],
default: u.gc('backToTopBottom', '160rpx')
},
//点击返回顶部按钮的自定义样式
backToTopStyle: {
type: Object,
default: function() {
return u.gc('backToTopStyle', {});
},
},
//iOS点击顶部状态栏、安卓双击标题栏时滚动条返回顶部只支持竖向默认为是
enableBackToTop: {
type: Boolean,
default: u.gc('enableBackToTop', true)
},
},
data() {
return {
backToTopClass: 'zp-back-to-top zp-back-to-top-hide',
lastBackToTopShowTime: 0,
showBackToTopClass: false,
}
},
computed: {
finalEnableBackToTop() {
return this.usePageScroll ? false : this.enableBackToTop;
},
finalBackToTopThreshold() {
return u.convertTextToPx(this.backToTopThreshold);
},
finalBackToTopStyle() {
let tempBackToTopStyle = this.backToTopStyle;
if (!tempBackToTopStyle.bottom) {
tempBackToTopStyle.bottom = this.windowBottom + u.convertTextToPx(this.backToTopBottom) + 'px';
}
if(!tempBackToTopStyle.position){
tempBackToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute';
}
return tempBackToTopStyle;
},
},
methods: {
//点击返回顶部
_backToTopClick() {
!this.backToTopWithAnimate && this._checkShouldShowBackToTop(1, 0);
this.scrollToTop(this.backToTopWithAnimate);
},
//判断是否要显示返回顶部按钮
_checkShouldShowBackToTop(newVal, oldVal) {
if (!this.autoShowBackToTop) {
this.showBackToTopClass = false;
return;
}
if (newVal !== oldVal) {
if (newVal > this.finalBackToTopThreshold) {
if (!this.showBackToTopClass) {
this.showBackToTopClass = true;
this.lastBackToTopShowTime = new Date().getTime();
setTimeout(() => {
this.backToTopClass = 'zp-back-to-top zp-back-to-top-show';
}, 300)
}
} else {
if (this.showBackToTopClass) {
const currentTime = new Date().getTime();
let dalayTime = 300;
if(currentTime - this.lastBackToTopShowTime < 500){
dalayTime = 0;
}
this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide';
setTimeout(() => {
this.showBackToTopClass = false;
}, dalayTime)
}
}
}
},
}
}
export default ZPBackToTop;

View File

@@ -0,0 +1,732 @@
// [z-paging]数据处理模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
import interceptor from '../z-paging-interceptor'
const ZPData = {
props: {
//自定义初始的pageNo默认为1
defaultPageNo: {
type: [Number, String],
default: u.gc('defaultPageNo', 1),
observer: function(newVal, oldVal) {
this.pageNo = newVal;
},
},
//自定义pageSize默认为10
defaultPageSize: {
type: [Number, String],
default: u.gc('defaultPageSize', 10),
validator: (value) => {
if(value <= 0) u.consoleErr('default-page-size必须大于0');
return value > 0;
}
},
//为保证数据一致设置当前tab切换时的标识key并在complete中传递相同key若二者不一致则complete将不会生效
dataKey: {
type: [Number, String, Object],
default: function() {
return u.gc('dataKey', null);
},
},
//自动注入的list名可自动修改父view(包含ref="paging")中对应name的list值
autowireListName: {
type: String,
default: function() {
return u.gc('autowireListName', '');
},
},
//自动注入的query名可自动调用父view(包含ref="paging")中的query方法
autowireQueryName: {
type: String,
default: function() {
return u.gc('autowireQueryName', '');
},
},
//z-paging mounted后自动调用reload方法(mounted后自动调用接口)默认为是。请使用简便写法auto
mountedAutoCallReload: {
type: Boolean,
default: u.gc('mountedAutoCallReload', true)
},
//z-paging mounted后自动调用reload方法(mounted后自动调用接口),默认为是
auto: {
type: Boolean,
default: u.gc('auto', true)
},
//用户下拉刷新时是否触发reload方法默认为是
reloadWhenRefresh: {
type: Boolean,
default: u.gc('reloadWhenRefresh', true)
},
//reload时自动滚动到顶部默认为是
autoScrollToTopWhenReload: {
type: Boolean,
default: u.gc('autoScrollToTopWhenReload', true)
},
//reload时立即自动清空原list默认为是若立即自动清空则在reload之后、请求回调之前页面是空白的
autoCleanListWhenReload: {
type: Boolean,
default: u.gc('autoCleanListWhenReload', true)
},
//列表刷新时自动显示下拉刷新view默认为否
showRefresherWhenReload: {
type: Boolean,
default: u.gc('showRefresherWhenReload', false)
},
//列表刷新时自动显示加载更多view且为加载中状态默认为否
showLoadingMoreWhenReload: {
type: Boolean,
default: u.gc('showLoadingMoreWhenReload', false)
},
//组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题)auto为true时有效。为否时将在mounted+nextTick后触发reload默认为否
createdReload: {
type: Boolean,
default: u.gc('createdReload', false)
},
//本地分页时上拉加载更多延迟时间单位为毫秒默认200毫秒
localPagingLoadingTime: {
type: [Number, String],
default: u.gc('localPagingLoadingTime', 200)
},
//当分页未满一屏时,是否自动加载更多,默认为否(nvue无效)
insideMore: {
type: Boolean,
default: u.gc('insideMore', false)
},
//使用聊天记录模式,默认为否
useChatRecordMode: {
type: Boolean,
default: u.gc('useChatRecordMode', false)
},
//自动拼接complete中传过来的数组(使用聊天记录模式时无效)
concat: {
type: Boolean,
default: u.gc('concat', true)
},
//父组件v-model所绑定的list的值
value: {
type: Array,
default: function() {
return [];
}
},
// #ifdef VUE3
modelValue: {
type: Array,
default: function() {
return [];
}
}
// #endif
},
data (){
return {
currentData: [],
totalData: [],
realTotalData: [],
totalLocalPagingList: [],
pageNo: 1,
isLocalPaging: false,
isAddedData: false,
isTotalChangeFromAddData: false,
privateConcat: true,
myParentQuery: -1,
firstPageLoaded: false,
pagingLoaded: false,
loaded: false,
isUserReload: true,
fromEmptyViewReload: false,
listRendering: false
}
},
computed: {
pageSize() {
return this.defaultPageSize;
},
finalConcat() {
return this.concat && this.privateConcat;
},
isFirstPage() {
return this.pageNo === this.defaultPageNo;
}
},
watch: {
totalData(newVal, oldVal) {
this._totalDataChange(newVal, oldVal);
},
currentData(newVal, oldVal) {
this._currentDataChange(newVal, oldVal);
},
useChatRecordMode(newVal, oldVal) {
if (newVal) {
this.nLoadingMoreFixedHeight = false;
}
},
value: {
handler(newVal) {
this.realTotalData = newVal;
},
immediate: true
},
// #ifdef VUE3
modelValue: {
handler(newVal) {
this.realTotalData = newVal;
},
immediate: true
}
// #endif
},
methods: {
//请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为是否成功(默认是是)
complete(data, success = true) {
this.customNoMore = -1;
this.addData(data, success);
},
//简写与complete完全相同
end(data, success = true) {
this.complete(data, success);
},
//【保证数据一致】请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为dataKey需与:data-key绑定的一致第三个参数为是否成功(默认为是)
completeByKey(data, dataKey = null, success = true) {
if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) {
if (this.isFirstPage) {
this.endRefresh();
}
return;
}
this.customNoMore = -1;
this.addData(data, success);
},
//简写与completeByKey完全相同
endByKey(data, dataKey = null, success = true) {
this.completeByKey(data, dataKey, success);
},
//【通过totalCount判断是否有更多数据】请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为totalCount(列表总数),第三个参数为是否成功(默认为是)
completeByTotalCount(data, totalCount, success = true) {
if (totalCount == 'undefined') {
this.customNoMore = -1;
} else {
let dataTypeRes = this._checkDataType(data, success, false);
data = dataTypeRes.data;
success = dataTypeRes.success;
if (totalCount >= 0 && success) {
this.$nextTick(() => {
let nomore = true;
let realTotalDataCount = this.realTotalData.length;
if (this.pageNo == this.defaultPageNo) {
realTotalDataCount = 0;
}
let exceedCount = realTotalDataCount + data.length - totalCount;
if (exceedCount >= 0) {
nomore = false;
exceedCount = this.defaultPageSize - exceedCount;
if (exceedCount > 0 && exceedCount < data.length) {
data = data.splice(0, exceedCount);
}
}
this.completeByNoMore(data, nomore, success);
})
return;
}
}
this.addData(data, success);
},
//简写与completeByTotalCount完全相同
completeByTotal(data, totalCount, success = true) {
this.completeByTotalCount(data, totalCount, success);
},
//简写与completeByTotalCount完全相同
endByTotalCount(data, totalCount, success = true) {
this.completeByTotalCount(data, totalCount, success);
},
//简写与completeByTotalCount完全相同
endByTotal(data, totalCount, success = true) {
this.completeByTotalCount(data, totalCount, success);
},
//【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为是否有更多数据第三个参数为是否成功(默认是是)
completeByNoMore(data, nomore, success = true) {
if (nomore != 'undefined') {
this.customNoMore = nomore == true ? 1 : 0;
}
this.addData(data, success);
},
//简写与completeByNoMore完全相同
endByNoMore(data, nomore, success = true) {
this.completeByNoMore(data, nomore, success);
},
//与上方complete方法功能一致新版本中设置服务端回调数组请使用complete方法
addData(data, success = true) {
if (!this.fromCompleteEmit) {
this.disabledCompleteEmit = true;
this.fromCompleteEmit = false;
}
const currentTimeStamp = u.getTime();
let addDataDalay = 0;
const disTime = currentTimeStamp - this.requestTimeStamp;
let minDelay = this.minDelay;
if(this.isFirstPage && this.finalShowRefresherWhenReload){
minDelay = Math.max(400,minDelay);
}
if(this.requestTimeStamp > 0 && disTime < minDelay){
addDataDalay = minDelay - disTime;
}
this.$nextTick(() => {
let delay = this.delay > 0 ? this.delay : addDataDalay;
setTimeout(() => {
this._addData(data, success, false);
}, delay)
})
},
//从顶部添加数据不会影响分页的pageNo和pageSize
addDataFromTop(data, toTop = true, toTopWithAnimate = true) {
let dataType = Object.prototype.toString.call(data);
if (dataType !== '[object Array]') {
data = [data];
}
this.totalData = [...data, ...this.totalData];
if (toTop) {
setTimeout(() => {
this._scrollToTop(toTopWithAnimate);
}, c.delayTime)
}
},
//重新设置列表数据调用此方法不会影响pageNo和pageSize也不会触发请求。适用场景当需要删除列表中某一项时将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时请使用此方法请勿直接修改page中:list.sync绑定的数组)
resetTotalData(data) {
if (data == undefined) {
if (this.showConsoleError) {
u.consoleErr('方法resetTotalData参数缺失');
}
return;
}
this.isTotalChangeFromAddData = true;
let dataType = Object.prototype.toString.call(data);
if (dataType !== '[object Array]') {
data = [data];
}
this.totalData = data;
},
//添加聊天记录
addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) {
let dataType = Object.prototype.toString.call(data);
if (dataType !== '[object Array]') {
data = [data];
}
if (!this.useChatRecordMode) return;
this.isTotalChangeFromAddData = true;
//#ifndef APP-NVUE
this.totalData = [...this.totalData, ...data];
//#endif
//#ifdef APP-NVUE
this.totalData = this.nIsFirstPageAndNoMore ? [...this.totalData, ...data] : [...data, ...this.totalData];
//#endif
if (toBottom) {
setTimeout(() => {
//#ifndef APP-NVUE
this._scrollToBottom(toBottomWithAnimate);
//#endif
//#ifdef APP-NVUE
if (this.nIsFirstPageAndNoMore) {
this._scrollToBottom(toBottomWithAnimate);
} else {
this._scrollToTop(toBottomWithAnimate);
}
//#endif
}, c.delayTime)
}
},
//设置本地分页数据,请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging作分页处理若调用了此方法则上拉加载更多时内部会自动分页不会触发@query所绑定的事件
setLocalPaging(data, success = true) {
this.isLocalPaging = true;
this.$nextTick(() => {
this._addData(data, success, true);
})
},
//重新加载分页数据pageNo会恢复为默认值相当于下拉刷新的效果(animate为true时会展示下拉刷新动画默认为false)
reload(animate = this.showRefresherWhenReload) {
if (animate) {
this.privateShowRefresherWhenReload = animate;
this.isUserPullDown = true;
}
this._preReload(animate, false);
},
//刷新列表数据pageNo和pageSize不会重置列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致
refresh() {
if(!this.realTotalData.length){
this.reload();
return;
}
const disPageNo = this.pageNo - this.defaultPageNo + 1;
if (disPageNo >= 1) {
this.loading = true;
this.privateConcat = false;
const totalPageSize = disPageNo * this.pageSize;
this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh);
this._callMyParentQuery(this.defaultPageNo, totalPageSize);
}
},
//清空分页数据
clean() {
this._reload(true);
this._addData([], true, false);
},
//清空分页数据
clear() {
this.clean();
},
//手动触发滚动到顶部加载更多,聊天记录模式时有效
doChatRecordLoadMore() {
this.useChatRecordMode && this._onLoadingMore('click');
},
//reload之前的一些处理
_preReload(animate = this.showRefresherWhenReload, isFromMounted = true) {
this.isUserReload = true;
this.loadingType = Enum.LoadingType.Refresher;
if (animate) {
this.privateShowRefresherWhenReload = animate;
// #ifndef APP-NVUE
if (this.useCustomRefresher) {
this._doRefresherRefreshAnimate();
} else {
this.refresherTriggered = true;
}
// #endif
// #ifdef APP-NVUE
this.refresherStatus = Enum.Refresher.Loading;
this.refresherRevealStackCount++;
setTimeout(() => {
this._getNodeClientRect('zp-n-refresh-container', false).then((node) => {
if (node) {
let nodeHeight = node[0].height;
this.nShowRefresherReveal = true;
this.nShowRefresherRevealHeight = nodeHeight;
setTimeout(() => {
this._nDoRefresherEndAnimation(0, -nodeHeight, false, false);
setTimeout(() => {
this._nDoRefresherEndAnimation(nodeHeight, 0);
}, 10)
}, 10)
}
this._reload(false, isFromMounted);
this._doRefresherLoad(false);
});
}, 10)
return;
// #endif
} else {
this._refresherEnd(false, false, false, false);
}
this._reload(false, isFromMounted);
},
//重新加载分页数据
_reload(isClean = false, isFromMounted = false, isUserPullDown = false) {
this.isAddedData = false;
this.cacheScrollNodeHeight = -1;
this.insideOfPaging = -1;
this.pageNo = this.defaultPageNo;
this._cleanRefresherEndTimeout();
!this.privateShowRefresherWhenReload && !isClean && this._startLoading(true);
this.firstPageLoaded = true;
this.isTotalChangeFromAddData = false;
this.totalData = [];
if (!isClean) {
this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload);
let delay = 0;
// #ifdef MP-TOUTIAO
delay = 5;
// #endif
setTimeout(() => {
this._callMyParentQuery();
}, delay)
if (!isFromMounted && this.autoScrollToTopWhenReload) {
let checkedNRefresherLoading = true;
// #ifdef APP-NVUE
checkedNRefresherLoading = !this.nRefresherLoading;
// #endif
if (checkedNRefresherLoading) {
this._scrollToTop(false);
}
}
// #ifndef APP-NVUE
if (!this.usePageScroll && this.useChatRecordMode) {
if (this.showConsoleError) {
u.consoleWarn('使用聊天记录模式时建议使用页面滚动可将usePageScroll设置为true以启用页面滚动');
}
}
// #endif
}
this.$nextTick(() => {
// #ifdef APP-NVUE
this.nShowBottom = this.realTotalData.length > 0;
// #endif
})
},
//处理服务端返回的数组
_addData(data, success, isLocal) {
this.isAddedData = true;
this.fromEmptyViewReload = false;
this.isTotalChangeFromAddData = true;
this.refresherTriggered = false;
!this.useCustomRefresher && uni.stopPullDownRefresh();
// #ifdef APP-NVUE
this.usePageScroll && uni.stopPullDownRefresh();
// #endif
const tempIsUserPullDown = this.isUserPullDown;
if (this.showRefresherUpdateTime && this.isFirstPage) {
u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey);
this.tempLanguageUpdateKey = u.getTime();
this.$refs.refresh && this.$refs.refresh.updateTime();
}
if (tempIsUserPullDown && this.isFirstPage) {
this.isUserPullDown = false;
}
let dataTypeRes = this._checkDataType(data, success, isLocal);
data = dataTypeRes.data;
success = dataTypeRes.success;
let delayTime = c.delayTime;
// #ifdef APP-NVUE
if (this.useChatRecordMode) delayTime = 0;
// #endif
this.loadingForNow = false;
setTimeout(() => {
this.pagingLoaded = true;
this.$nextTick(()=>{
this._refresherEnd(delayTime > 0, true, tempIsUserPullDown);
})
}, delayTime)
if (this.isFirstPage) {
this.isLoadFailed = !success;
}
if (success) {
if (!(this.privateConcat === false && this.loadingStatus === Enum.More.NoMore)) {
this.loadingStatus = Enum.More.Default;
}
if (isLocal) {
this.totalLocalPagingList = data;
this._localPagingQueryList(this.defaultPageNo, this.defaultPageSize, 0, (res) => {
this.complete(res);
})
} else {
let dataChangeDelayTime = 0;
// #ifdef APP-NVUE
if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') {
dataChangeDelayTime = 150;
}
// #endif
setTimeout(() => {
this._currentDataChange(data, this.currentData);
}, dataChangeDelayTime)
}
} else {
this._currentDataChange(data, this.currentData);
this.loadingStatus = Enum.More.Fail;
if (this.loadingType === Enum.LoadingType.LoadingMore) {
this.pageNo--;
}
}
},
//所有数据改变时调用
_totalDataChange(newVal, oldVal, eventThrow=true) {
if ((!this.isUserReload || !this.autoCleanListWhenReload) && this.firstPageLoaded && !newVal.length && oldVal.length) {
return;
}
this._doCheckScrollViewShouldFullHeight(newVal);
if(!this.realTotalData.length && !newVal.length){
eventThrow = false;
}
this.realTotalData = newVal;
if (eventThrow) {
this.$emit('input', newVal);
// #ifdef VUE3
this.$emit('update:modelValue', newVal);
// #endif
this.$emit('update:list', newVal);
this.$emit('listChange', newVal);
this._callMyParentList(newVal);
}
this.firstPageLoaded = false;
this.isTotalChangeFromAddData = false;
this.$nextTick(() => {
setTimeout(()=>{
this._getNodeClientRect('.zp-paging-container-content').then((res) => {
if (res) {
this.$emit('contentHeightChanged', res[0].height);
}
});
},this.isIos?100:300)
// #ifdef APP-NVUE
if (this.useChatRecordMode && this.nIsFirstPageAndNoMore && this.isFirstPage && !this.nFirstPageAndNoMoreChecked) {
this.nFirstPageAndNoMoreChecked = true;
this._scrollToBottom(false);
}
// #endif
})
},
//当前数据改变时调用
_currentDataChange(newVal, oldVal) {
newVal = [...newVal];
this.listRendering = true;
this.$nextTick(() => {
setTimeout(() => {
this.listRendering = false;
},50)
})
// #ifndef APP-NVUE
if (this.finalUseVirtualList) {
this._setCellIndex(newVal,this.totalData.length === 0);
}
this.useChatRecordMode && newVal.reverse();
// #endif
if (this.isFirstPage && this.finalConcat) {
this.totalData = [];
}
if (this.customNoMore !== -1) {
if (this.customNoMore === 0 || !newVal.length) {
this.loadingStatus = Enum.More.NoMore;
}
} else {
if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) {
this.loadingStatus = Enum.More.NoMore;
}
}
if (!this.totalData.length) {
if (this.finalConcat) {
// #ifdef APP-NVUE
if(this.useChatRecordMode && this.isFirstPage && this.loadingStatus === Enum.More.NoMore){
newVal.reverse();
}
// #endif
this.totalData = newVal;
}
if (this.useChatRecordMode) {
// #ifndef APP-NVUE
this.$nextTick(() => {
this._scrollToBottom(false);
})
// #endif
}
} else {
if (this.useChatRecordMode) {
// #ifdef APP-NVUE
this.totalData = [...this.totalData, ...newVal];
// #endif
//#ifndef APP-NVUE
const idIndex = newVal.length;
let idIndexStr = `z-paging-${idIndex}`;
this.totalData = [...newVal, ...this.totalData];
if (this.pageNo !== this.defaultPageNo) {
this.privateScrollWithAnimation = 0;
let delayTime = 200;
this.$emit('update:chatIndex', idIndex);
setTimeout(() => {
this._scrollIntoView(idIndexStr, 30 + this.cacheTopHeight, false, () => {
this.$emit('update:chatIndex', 0);
});
}, this.usePageScroll ? 0 : delayTime)
} else {
this.$nextTick(() => {
this._scrollToBottom(false);
})
}
//#endif
} else {
if (this.finalConcat) {
const currentScrollTop = this.oldScrollTop;
this.totalData = [...this.totalData, ...newVal];
// #ifdef MP-WEIXIN
if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) {
this.loadingMoreTimeStamp = u.getTime();
this.$nextTick(()=>{
this.scrollToY(currentScrollTop);
})
}
// #endif
} else {
this.totalData = newVal;
}
}
}
this.privateConcat = true;
},
//本地分页请求
_localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) {
pageNo = parseInt(pageNo);
pageSize = parseInt(pageSize);
if (pageNo < 0 || pageSize <= 0) {
this._localPagingQueryResult(callback, [], localPagingLoadingTime);
return;
}
pageNo = Math.max(1,pageNo);
let totalPagingList = [...this.totalLocalPagingList];
let pageNoIndex = (pageNo - 1) * pageSize;
if (pageNoIndex + pageSize <= totalPagingList.length) {
this._localPagingQueryResult(callback, totalPagingList.splice(pageNoIndex, pageSize), localPagingLoadingTime);
} else if (pageNoIndex < totalPagingList.length) {
this._localPagingQueryResult(callback, totalPagingList.splice(pageNoIndex, totalPagingList.length - pageNoIndex), localPagingLoadingTime);
} else {
this._localPagingQueryResult(callback, [], localPagingLoadingTime);
}
},
//本地分页请求回调
_localPagingQueryResult(callback, arg, localPagingLoadingTime) {
setTimeout(() => {
callback(arg);
}, localPagingLoadingTime)
},
//修改父view的list
_callMyParentList(newVal) {
if (this.autowireListName.length) {
const myParent = u.getParent(this.$parent);
if (myParent && myParent[this.autowireListName]) {
myParent[this.autowireListName] = newVal;
}
}
},
//调用父view的query
_callMyParentQuery(customPageNo = 0, customPageSize = 0) {
if (this.autowireQueryName) {
if (this.myParentQuery === -1) {
const myParent = u.getParent(this.$parent);
if (myParent && myParent[this.autowireQueryName]) {
this.myParentQuery = myParent[this.autowireQueryName];
}
}
if (this.myParentQuery !== -1) {
if (customPageSize > 0) {
this.myParentQuery(customPageNo, customPageSize);
} else {
this.myParentQuery(this.pageNo, this.defaultPageSize);
}
}
}
},
//发射query事件
_emitQuery(pageNo, pageSize, from){
this.requestTimeStamp = u.getTime();
this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from));
},
//检查complete data的类型
_checkDataType(data, success, isLocal) {
const dataType = Object.prototype.toString.call(data);
if (dataType === '[object Boolean]') {
success = data;
data = [];
} else if (dataType === '[object Null]') {
data = [];
} else if (dataType !== '[object Array]') {
data = [];
let methodStr = isLocal ? 'setLocalPaging' : 'complete';
if (dataType !== '[object Undefined]') {
if (this.showConsoleError) {
u.consoleErr(`${methodStr}参数类型不正确第一个参数类型必须为Array!`);
}
}
}
return {data,success};
},
}
}
export default ZPData;

View File

@@ -0,0 +1,150 @@
// [z-paging]空数据图view模块
import u from '.././z-paging-utils'
const ZPEmptyView = {
props: {
//是否强制隐藏空数据图,默认为否
hideEmptyView: {
type: Boolean,
default: u.gc('hideEmptyView', false)
},
//空数据图描述文字,默认为“没有数据哦~”
emptyViewText: {
type: [String, Object],
default: u.gc('emptyViewText', null)
},
//是否显示空数据图重新加载按钮(无数据时),默认为否
showEmptyViewReload: {
type: Boolean,
default: u.gc('showEmptyViewReload', false)
},
//加载失败时是否显示空数据图重新加载按钮,默认为是
showEmptyViewReloadWhenError: {
type: Boolean,
default: u.gc('showEmptyViewReloadWhenError', true)
},
//空数据图点击重新加载文字,默认为“重新加载”
emptyViewReloadText: {
type: [String, Object],
default: u.gc('emptyViewReloadText', null)
},
//空数据图图片默认使用z-paging内置的图片
emptyViewImg: {
type: String,
default: u.gc('emptyViewImg', '')
},
//空数据图“加载失败”描述文字,默认为“很抱歉,加载失败”
emptyViewErrorText: {
type: [String, Object],
default: u.gc('emptyViewErrorText', null)
},
//空数据图“加载失败”图片默认使用z-paging内置的图片
emptyViewErrorImg: {
type: String,
default: u.gc('emptyViewErrorImg', '')
},
//空数据图样式
emptyViewStyle: {
type: Object,
default: function() {
return u.gc('emptyViewStyle', {});
}
},
//空数据图容器样式
emptyViewSuperStyle: {
type: Object,
default: function() {
return u.gc('emptyViewSuperStyle', {});
}
},
//空数据图img样式
emptyViewImgStyle: {
type: Object,
default: function() {
return u.gc('emptyViewImgStyle', {});
}
},
//空数据图描述文字样式
emptyViewTitleStyle: {
type: Object,
default: function() {
return u.gc('emptyViewTitleStyle', {});
}
},
//空数据图重新加载按钮样式
emptyViewReloadStyle: {
type: Object,
default: function() {
return u.gc('emptyViewReloadStyle', {});
}
},
//空数据图片是否铺满z-paging默认为是。若设置为否则为填充满z-paging的剩余部分
emptyViewFixed: {
type: Boolean,
default: function() {
return u.gc('emptyViewFixed', false)
}
},
//空数据图片是否垂直居中默认为是。emptyViewFixed为false时有效
emptyViewCenter: {
type: Boolean,
default: function() {
return u.gc('emptyViewCenter', true)
}
},
//加载中时是否自动隐藏空数据图,默认为是
autoHideEmptyViewWhenLoading: {
type: Boolean,
default: u.gc('autoHideEmptyViewWhenLoading', true)
},
//用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是
autoHideEmptyViewWhenPull: {
type: Boolean,
default: u.gc('autoHideEmptyViewWhenPull', true)
},
//空数据view的z-index默认为9
emptyViewZIndex: {
type: Number,
default: u.gc('emptyViewZIndex', 9)
},
},
computed: {
finalEmptyViewImg() {
return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg;
},
finalShowEmptyViewReload() {
return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload;
},
showEmpty() {
if(this.refresherOnly || this.hideEmptyView || this.totalData.length) return false;
if(this.autoHideEmptyViewWhenLoading){
if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true;
}else{
return true;
}
if (!this.autoHideEmptyViewWhenPull && !this.isUserReload) return true;
return false;
},
},
methods: {
//点击了空数据view重新加载按钮
_emptyViewReload() {
let callbacked = false;
this.$emit('emptyViewReload', (reload) => {
if (reload === undefined || reload === true) {
this.fromEmptyViewReload = true;
this.reload();
}
callbacked = true;
});
this.$nextTick(() => {
if (!callbacked) {
this.fromEmptyViewReload = true;
this.reload();
}
})
},
}
}
export default ZPEmptyView;

View File

@@ -0,0 +1,98 @@
// [z-paging]i18n模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import zI18n from '.././z-paging-i18n'
const systemInfo = uni.getSystemInfoSync();
const ZPI18n = {
props: {
//i18n国际化设置语言支持简体中文(zh-cn)、繁体中文(zh-hant-cn)和英文(en)
language: {
type: String,
default: u.gc('language', '')
},
//i18n国际化默认是否跟随系统语言默认为是
followSystemLanguage: {
type: Boolean,
default: u.gc('followSystemLanguage', true)
},
},
data() {
return {
tempLanguageUpdateKey: 0,
}
},
computed: {
tempLanguage() {
let systemLanguage = false;
const temp = this.tempLanguageUpdateKey;
if (this.followSystemLanguage) {
systemLanguage = systemInfo.language;
}
return uni.getStorageSync(c.i18nUpdateKey) || systemLanguage || 'zh-cn';
},
finalTempLanguage() {
return this.language.length ? this.language : this.tempLanguage;
},
finalLanguage() {
let language = this.finalTempLanguage.toLowerCase();
return zI18n._getPrivateLanguage(language, this.followSystemLanguage);
},
finalRefresherDefaultText() {
return this._getI18nText('refresherDefaultText', this.refresherDefaultText);
},
finalRefresherPullingText() {
return this._getI18nText('refresherPullingText', this.refresherPullingText);
},
finalRefresherRefreshingText() {
return this._getI18nText('refresherRefreshingText', this.refresherRefreshingText);
},
finalRefresherCompleteText() {
return this._getI18nText('refresherCompleteText', this.refresherCompleteText);
},
finalLoadingMoreDefaultText() {
return this._getI18nText('loadingMoreDefaultText', this.loadingMoreDefaultText);
},
finalLoadingMoreLoadingText() {
return this._getI18nText('loadingMoreLoadingText', this.loadingMoreLoadingText);
},
finalLoadingMoreNoMoreText() {
return this._getI18nText('loadingMoreNoMoreText', this.loadingMoreNoMoreText);
},
finalLoadingMoreFailText() {
return this._getI18nText('loadingMoreFailText', this.loadingMoreFailText);
},
finalEmptyViewText() {
return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('emptyViewText', this.emptyViewText);
},
finalEmptyViewReloadText() {
return this._getI18nText('emptyViewReloadText', this.emptyViewReloadText);
},
finalEmptyViewErrorText() {
return this._getI18nText('emptyViewErrorText', this.emptyViewErrorText);
},
},
methods: {
//设置i18n国际化语言
setI18n(language) {
zI18n.setLanguage(language);
},
//获取当前z-paging的语言
getLanguage() {
return this.finalLanguage;
},
//获取国际化转换后的文本
_getI18nText(key, value) {
const dataType = Object.prototype.toString.call(value);
if (dataType === '[object Object]') {
const nextValue = value[this.finalLanguage];
if (nextValue) return nextValue;
} else if (dataType === '[object String]') {
return value;
}
return zI18n.t[key][this.finalLanguage];
},
}
}
export default ZPI18n;

View File

@@ -0,0 +1,328 @@
// [z-paging]滚动到底部加载更多模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
const ZPLoadMore = {
props: {
//自定义底部加载更多样式
loadingMoreCustomStyle: {
type: Object,
default: function() {
return u.gc('loadingMoreCustomStyle', {});
}
},
//自定义底部加载更多文字样式
loadingMoreTitleCustomStyle: {
type: Object,
default: function() {
return u.gc('loadingMoreTitleCustomStyle', {});
}
},
//自定义底部加载更多加载中动画样式
loadingMoreLoadingIconCustomStyle: {
type: Object,
default: function() {
return u.gc('loadingMoreLoadingIconCustomStyle', {});
}
},
//自定义底部加载更多加载中动画图标类型可选flower或circle默认为flower
loadingMoreLoadingIconType: {
type: String,
default: u.gc('loadingMoreLoadingIconType', 'flower')
},
//自定义底部加载更多加载中动画图标图片
loadingMoreLoadingIconCustomImage: {
type: String,
default: u.gc('loadingMoreLoadingIconCustomImage', '')
},
//底部加载更多加载中view是否展示旋转动画默认为是
loadingMoreLoadingAnimated: {
type: Boolean,
default: u.gc('loadingMoreLoadingAnimated', true)
},
//是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是
loadingMoreEnabled: {
type: Boolean,
default: u.gc('loadingMoreEnabled', true)
},
//是否启用滑动到底部加载更多数据,默认为是
toBottomLoadingMoreEnabled: {
type: Boolean,
default: u.gc('toBottomLoadingMoreEnabled', true)
},
//滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】
loadingMoreDefaultAsLoading: {
type: [Boolean],
default: u.gc('loadingMoreDefaultAsLoading', false)
},
//滑动到底部"默认"文字,默认为【点击加载更多】
loadingMoreDefaultText: {
type: [String, Object],
default: u.gc('loadingMoreDefaultText', null)
},
//滑动到底部"加载中"文字,默认为【正在加载...】
loadingMoreLoadingText: {
type: [String, Object],
default: u.gc('loadingMoreLoadingText', null)
},
//滑动到底部"没有更多"文字,默认为【没有更多了】
loadingMoreNoMoreText: {
type: [String, Object],
default: u.gc('loadingMoreNoMoreText', null)
},
//滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】
loadingMoreFailText: {
type: [String, Object],
default: u.gc('loadingMoreFailText', null)
},
//当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view默认为否
hideLoadingMoreWhenNoMoreAndInsideOfPaging: {
type: Boolean,
default: u.gc('hideLoadingMoreWhenNoMoreAndInsideOfPaging', false)
},
//当没有更多数据且分页数组长度少于这个值时隐藏没有更多数据的view默认为0代表不限制。
hideLoadingMoreWhenNoMoreByLimit: {
type: Number,
default: u.gc('hideLoadingMoreWhenNoMoreByLimit', 0)
},
//是否显示默认的加载更多text默认为是
showDefaultLoadingMoreText: {
type: Boolean,
default: u.gc('showDefaultLoadingMoreText', true)
},
//是否显示没有更多数据的view
showLoadingMoreNoMoreView: {
type: Boolean,
default: u.gc('showLoadingMoreNoMoreView', true)
},
//是否显示没有更多数据的分割线,默认为是
showLoadingMoreNoMoreLine: {
type: Boolean,
default: u.gc('showLoadingMoreNoMoreLine', true)
},
//自定义底部没有更多数据的分割线样式
loadingMoreNoMoreLineCustomStyle: {
type: Object,
default: function() {
return u.gc('loadingMoreNoMoreLineCustomStyle', {});
},
},
//距底部/右边多远时单位px触发 scrolltolower 事件默认为100rpx
lowerThreshold: {
type: [Number, String],
default: u.gc('lowerThreshold', '100rpx')
},
},
data() {
return {
//底部加载更多状态
loadingStatus: Enum.More.Default,
loadingStatusAfterRender: Enum.More.Default,
loadingMoreTimeStamp: 0,
loadingMoreDefaultSlot: null,
showLoadingMore: false,
customNoMore: -1,
}
},
computed: {
zPagingLoadMoreConfig() {
return {
status: this.loadingStatusAfterRender,
defaultAsLoading: this.loadingMoreDefaultAsLoading,
defaultThemeStyle: this.finalLoadingMoreThemeStyle,
customStyle: this.loadingMoreCustomStyle,
titleCustomStyle: this.loadingMoreTitleCustomStyle,
iconCustomStyle: this.loadingMoreLoadingIconCustomStyle,
loadingIconType: this.loadingMoreLoadingIconType,
loadingIconCustomImage: this.loadingMoreLoadingIconCustomImage,
loadingAnimated: this.loadingMoreLoadingAnimated,
showNoMoreLine: this.showLoadingMoreNoMoreLine,
noMoreLineCustomStyle: this.loadingMoreNoMoreLineCustomStyle,
defaultText: this.finalLoadingMoreDefaultText,
loadingText: this.finalLoadingMoreLoadingText,
noMoreText: this.finalLoadingMoreNoMoreText,
failText: this.finalLoadingMoreFailText,
hideContent: !this.loadingMoreDefaultAsLoading && this.listRendering
};
},
finalLoadingMoreThemeStyle() {
return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle;
},
showLoadingMoreDefault() {
return this._showLoadingMore('Default');
},
showLoadingMoreLoading() {
return this._showLoadingMore('Loading');
},
showLoadingMoreNoMore() {
return this._showLoadingMore('NoMore');
},
showLoadingMoreFail() {
return this._showLoadingMore('Fail');
},
showLoadingMoreCustom() {
return this._showLoadingMore('Custom');
}
},
methods: {
//手动触发上拉加载更多(非必须,可依据具体需求使用)
doLoadMore(type) {
this._onLoadingMore(type);
},
//通过@scroll事件检测是否滚动到了底部
_checkScrolledToBottom(scrollDiff, checked = false) {
if (this.checkScrolledToBottomTimeOut) {
clearTimeout(this.checkScrolledToBottomTimeOut);
this.checkScrolledToBottomTimeOut = null;
}
if (this.cacheScrollNodeHeight === -1) {
this._getNodeClientRect('.zp-scroll-view').then((res) => {
if (res) {
let pageScrollNodeHeight = res[0].height;
this.cacheScrollNodeHeight = pageScrollNodeHeight;
if (scrollDiff - pageScrollNodeHeight <= this.finalLowerThreshold) {
this._onLoadingMore('toBottom');
}
}
});
} else {
if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) {
this._onLoadingMore('toBottom');
} else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) {
this.checkScrolledToBottomTimeOut = setTimeout(() => {
this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => {
this.oldScrollTop = res[0].scrollTop;
const newScrollDiff = res[0].scrollHeight - this.oldScrollTop;
this._checkScrolledToBottom(newScrollDiff, true);
})
}, 150)
}
}
},
//触发加载更多时调用,from:0-滑动到底部触发1-点击加载更多触发
_onLoadingMore(from = 'click') {
if (from === 'toBottom') {
if (!this.scrollToBottomBounceEnabled) {
if (this.scrollEnable) {
this.scrollEnable = false;
this.$nextTick(() => {
this.scrollEnable = true;
})
}
}
//#ifdef APP-VUE || H5
if (this.isIos) {
this.renderPropUsePageScroll = -1;
this.$nextTick(() => {
this.renderPropUsePageScroll = this.usePageScroll;
})
}
//#endif
}
this.$emit('scrolltolower', from);
if (from === 'toBottom' && (!this.toBottomLoadingMoreEnabled || this.useChatRecordMode)) return;
if (this.refresherOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading) return;
// #ifdef MP-WEIXIN
if (!this.isIos && !this.refresherOnly && !this.usePageScroll) {
const currentTimestamp = u.getTime();
if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) {
this.loadingMoreTimeStamp = 0;
return;
}
}
// #endif
this._doLoadingMore();
},
//处理开始加载更多
_doLoadingMore() {
if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) {
this.pageNo++;
this._startLoading(false);
if (this.isLocalPaging) {
this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, (res) => {
this.addData(res);
})
} else {
this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadingMore);
this._callMyParentQuery();
}
this.loadingType = Enum.LoadingType.LoadingMore;
}
},
//(预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view
_preCheckShowLoadingMoreWhenNoMoreAndInsideOfPaging(newVal, scrollViewNode, pagingContainerNode) {
if (this.loadingStatus === Enum.More.NoMore && this.hideLoadingMoreWhenNoMoreByLimit > 0 && newVal.length) {
this.showLoadingMore = newVal.length > this.hideLoadingMoreWhenNoMoreByLimit;
} else if ((this.loadingStatus === Enum.More.NoMore && this.hideLoadingMoreWhenNoMoreAndInsideOfPaging && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) {
this.$nextTick(() => {
this._checkShowLoadingMoreWhenNoMoreAndInsideOfPaging(newVal, scrollViewNode, pagingContainerNode);
})
if (this.insideMore && this.insideOfPaging !== false && newVal.length) {
this.showLoadingMore = newVal.length;
}
} else {
this.showLoadingMore = newVal.length;
}
},
//判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view
async _checkShowLoadingMoreWhenNoMoreAndInsideOfPaging(totalData, oldScrollViewNode, oldPagingContainerNode) {
try {
const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view');
if (this.usePageScroll) {
if (scrollViewNode) {
const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height;
this.insideOfPaging = scrollViewTotalH < this.windowHeight;
if (this.hideLoadingMoreWhenNoMoreAndInsideOfPaging) {
this.showLoadingMore = !this.insideOfPaging;
}
this._updateInsideOfPaging();
}
} else {
let pagingContainerH = 0;
let scrollViewH = 0;
const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content');
if (pagingContainerNode) {
pagingContainerH = pagingContainerNode[0].height;
}
if (scrollViewNode) {
scrollViewH = scrollViewNode[0].height;
}
this.insideOfPaging = pagingContainerH < scrollViewH;
if (this.hideLoadingMoreWhenNoMoreAndInsideOfPaging) {
this.showLoadingMore = !this.insideOfPaging;
}
this._updateInsideOfPaging();
}
} catch (e) {
this.insideOfPaging = !totalData.length;
if (this.hideLoadingMoreWhenNoMoreAndInsideOfPaging) {
this.showLoadingMore = !this.insideOfPaging;
}
this._updateInsideOfPaging();
}
},
//是否要展示上拉加载更多view
_showLoadingMore(type) {
if (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.totalData.length) return false;
if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) ||
(!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) ||
this.refresherOnly) return false;
if (this.useChatRecordMode && type !== 'Loading') return false;
if (!this.$slots) return false;
if (type === 'Custom') {
return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView);
}
const res = this.loadingStatus === Enum.More[type] && this.$slots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true);
if (res) {
// #ifdef APP-NVUE
if (!this.isIos) {
this.nLoadingMoreFixedHeight = false;
}
// #endif
}
return res;
},
}
}
export default ZPLoadMore;

View File

@@ -0,0 +1,219 @@
// [z-paging]nvue独有部分模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
// #ifdef APP-NVUE
const weexAnimation = weex.requireModule('animation');
// #endif
const ZPNvue = {
props: {
//nvue中修改列表类型可选值有list、waterfall和scroller默认为list
nvueListIs: {
type: String,
default: u.gc('nvueListIs', 'list')
},
//nvue waterfall配置仅在nvue中且nvueListIs=waterfall时有效配置参数详情参见https://uniapp.dcloud.io/component/waterfall
nvueWaterfallConfig: {
type: Object,
default: function() {
return u.gc('nvueWaterfallConfig', {});
}
},
//nvue 控制是否回弹效果iOS不支持动态修改
nvueBounce: {
type: Boolean,
default: u.gc('nvueBounce', true)
},
//nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否
nvueFastScroll: {
type: Boolean,
default: u.gc('nvueFastScroll', false)
},
//nvue中list的id
nvueListId: {
type: String,
default: u.gc('nvueListId', '')
},
//nvue中refresh组件的样式
nvueRefresherStyle: {
type: Object,
default: function() {
return u.gc('nvueRefresherStyle', {});
}
},
//nvue中是否按分页模式(类似竖向swiper)显示List默认为false
nvuePagingEnabled: {
type: Boolean,
default: u.gc('nvuePagingEnabled', false)
},
//是否隐藏nvue列表底部的tagView此view用于标识滚动到底部位置若隐藏则滚动到底部功能将失效在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否
hideNvueBottomTag: {
type: Boolean,
default: u.gc('hideNvueBottomTag', false)
},
},
data() {
return {
nRefresherLoading: false,
nListIsDragging: false,
nShowBottom: true,
nFixFreezing: false,
nShowRefresherReveal: false,
nIsFirstPageAndNoMore: false,
nFirstPageAndNoMoreChecked: false,
nLoadingMoreFixedHeight: false,
nShowRefresherRevealHeight: 0,
nRefresherWidth: uni.upx2px(750),
}
},
watch: {
nIsFirstPageAndNoMore: {
handler(newVal) {
const cellStyle = !this.useChatRecordMode || newVal ? {} : {transform: 'rotate(180deg)'};
this.$emit('update:cellStyle', cellStyle);
},
immediate: true
}
},
computed: {
// #ifdef APP-NVUE
nWaterfallColumnCount() {
if (this.finalNvueListIs !== 'waterfall') return 0;
return this._nGetWaterfallConfig('column-count', 2);
},
nWaterfallColumnWidth() {
return this._nGetWaterfallConfig('column-width', 'auto');
},
nWaterfallColumnGap() {
return this._nGetWaterfallConfig('column-gap', 'normal');
},
nWaterfallLeftGap() {
return this._nGetWaterfallConfig('left-gap', 0);
},
nWaterfallRightGap() {
return this._nGetWaterfallConfig('right-gap', 0);
},
nViewIs() {
const is = this.finalNvueListIs;
return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell';
},
nSafeAreaBottomHeight() {
return this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
},
finalNvueListIs() {
if (this.usePageScroll) return 'view';
const nvueListIsLowerCase = this.nvueListIs.toLowerCase();
if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) {
return nvueListIsLowerCase;
}
return 'list';
},
finalNvueSuperListIs() {
return this.usePageScroll ? 'view' : 'scroller';
},
finalNvueRefresherEnabled() {
return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode;
},
// #endif
},
methods: {
// #ifdef APP-NVUE
//列表滚动时触发
_nOnScroll(e) {
this.$emit('scroll', e);
const contentOffsetY = e.contentOffset.y;
this.nListIsDragging = e.isDragging;
this._checkShouldShowBackToTop(-e.contentOffset.y, -e.contentOffset.y - 1);
},
//下拉刷新刷新中
_nOnRrefresh() {
if (this.nShowRefresherReveal) return;
this.nRefresherLoading = true;
this.refresherStatus = Enum.Refresher.Loading;
this._doRefresherLoad();
},
//下拉刷新下拉中
_nOnPullingdown(e) {
if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return;
this._emitTouchmove(e);
const viewHeight = e.viewHeight;
const pullingDis = e.pullingDistance;
this.refresherStatus = pullingDis >= viewHeight ? Enum.Refresher.ReleaseToRefresh : Enum.Refresher.Default;
},
//下拉刷新结束
_nRefresherEnd(doEnd=true) {
if (doEnd) {
this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight);
!this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore();
this.nRefresherLoading = false;
}
this.$nextTick(() => {
setTimeout(()=> {
this.nShowBottom = true;
}, 10);
})
},
//执行主动触发下拉刷新动画
_nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) {
this._cleanRefresherCompleteTimeout();
this._cleanRefresherEndTimeout();
if (!this.finalShowRefresherWhenReload) {
this.refresherEndTimeout = setTimeout(() => {
this.refresherStatus = Enum.Refresher.Default;
}, this.refresherCompleteDuration);
return;
}
const stackCount = this.refresherRevealStackCount;
if (height === 0 && checkStack) {
this.refresherRevealStackCount--;
if (stackCount > 1) return;
this.refresherEndTimeout = setTimeout(() => {
this.refresherStatus = Enum.Refresher.Default;
}, this.refresherCompleteDuration);
}
if (stackCount > 1) {
this.refresherStatus = Enum.Refresher.Loading;
}
const duration = animate ? 120 : 0;
weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], {
styles: {
height: `${height}px`,
transform: `translateY(${translateY}px)`,
},
duration: duration,
timingFunction: 'linear',
needLayout: true,
delay: 0
})
setTimeout(() => {
if (animate) {
this.nShowRefresherReveal = height > 0;
}
}, duration > 0 ? duration - 100 : 0);
},
//滚动到底部加载更多
_nOnLoadmore() {
if (this.nShowRefresherReveal || !this.totalData.length) return;
this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom');
},
//获取nvue waterfall单项配置
_nGetWaterfallConfig(key, defaultValue) {
return this.nvueWaterfallConfig[key] || defaultValue;
},
//更新nvue 下拉刷新view容器的宽度
_nUpdateRefresherWidth() {
this.$nextTick(()=>{
this._getNodeClientRect('.zp-n-list').then(node => {
if (node) {
const nodeWidth = node[0].width;
this.nRefresherWidth = nodeWidth;
}
})
})
}
// #endif
}
}
export default ZPNvue;

View File

@@ -0,0 +1,632 @@
// [z-paging]下拉刷新view模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
const ZPRefresher = {
props: {
//下拉刷新的主题样式支持blackwhite默认black
refresherThemeStyle: {
type: String,
default: function() {
return u.gc('refresherThemeStyle', '');
}
},
//自定义下拉刷新中左侧图标的样式
refresherImgStyle: {
type: Object,
default: function() {
return u.gc('refresherImgStyle', {});
}
},
//自定义下拉刷新中右侧状态描述文字的样式
refresherTitleStyle: {
type: Object,
default: function() {
return u.gc('refresherTitleStyle', {});
}
},
//自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效)
refresherUpdateTimeStyle: {
type: Object,
default: function() {
return u.gc('refresherUpdateTimeStyle', {});
}
},
//在微信小程序和QQ小程序中是否实时监听下拉刷新中进度默认为否
watchRefresherTouchmove: {
type: Boolean,
default: u.gc('watchRefresherTouchmove', false)
},
//底部加载更多的主题样式支持blackwhite默认black
loadingMoreThemeStyle: {
type: String,
default: function() {
return u.gc('loadingMoreThemeStyle', '');
}
},
//是否只使用下拉刷新设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多强制隐藏空数据图。默认为否
refresherOnly: {
type: Boolean,
default: u.gc('refresherOnly', false)
},
//自定义下拉刷新默认状态下回弹动画时间单位为毫秒默认为100毫秒nvue无效
refresherDefaultDuration: {
type: [Number, String],
default: u.gc('refresherDefaultDuration', 100)
},
//自定义下拉刷新结束以后延迟回弹的时间单位为毫秒默认为0
refresherCompleteDelay: {
type: [Number, String],
default: u.gc('refresherCompleteDelay', 0)
},
//自定义下拉刷新结束回弹动画时间单位为毫秒默认为300毫秒(refresherEndBounceEnabled为false时refresherCompleteDuration为设定值的1/3)nvue无效
refresherCompleteDuration: {
type: [Number, String],
default: u.gc('refresherCompleteDuration', 300)
},
//自定义下拉刷新结束状态下是否允许列表滚动,默认为否
refresherCompleteScrollable: {
type: Boolean,
default: u.gc('refresherCompleteScrollable', false)
},
//是否使用自定义的下拉刷新默认为是即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新
useCustomRefresher: {
type: Boolean,
default: u.gc('useCustomRefresher', true)
},
//自定义下拉刷新下拉帧率默认为40过高可能会出现抖动问题
refresherFps: {
type: [Number, String],
default: u.gc('refresherFps', 40)
},
//自定义下拉刷新允许触发的最大下拉角度默认为40度当下拉角度小于设定值时自定义下拉刷新动画不会被触发
refresherMaxAngle: {
type: [Number, String],
default: u.gc('refresherMaxAngle', 40)
},
//自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否
refresherAngleEnableChangeContinued: {
type: Boolean,
default: u.gc('refresherAngleEnableChangeContinued', false)
},
//自定义下拉刷新默认状态下的文字
refresherDefaultText: {
type: [String, Object],
default: u.gc('refresherDefaultText', null)
},
//自定义下拉刷新松手立即刷新状态下的文字
refresherPullingText: {
type: [String, Object],
default: u.gc('refresherPullingText', null)
},
//自定义下拉刷新刷新中状态下的文字
refresherRefreshingText: {
type: [String, Object],
default: u.gc('refresherRefreshingText', null)
},
//自定义下拉刷新刷新结束状态下的文字
refresherCompleteText: {
type: [String, Object],
default: u.gc('refresherCompleteText', null)
},
//是否开启自定义下拉刷新刷新结束回弹效果,默认为是
refresherEndBounceEnabled: {
type: Boolean,
default: u.gc('refresherEndBounceEnabled', true)
},
//是否开启自定义下拉刷新,默认为是
refresherEnabled: {
type: Boolean,
default: u.gc('refresherEnabled', true)
},
//设置自定义下拉刷新阈值默认为80rpx
refresherThreshold: {
type: [Number, String],
default: u.gc('refresherThreshold', '80rpx')
},
//设置系统下拉刷新默认样式,支持设置 blackwhitenonenone 表示不使用默认样式默认为black
refresherDefaultStyle: {
type: String,
default: u.gc('refresherDefaultStyle', 'black')
},
//设置自定义下拉刷新区域背景
refresherBackground: {
type: String,
default: u.gc('refresherBackground', 'transparent')
},
//设置固定的自定义下拉刷新区域背景
refresherFixedBackground: {
type: String,
default: u.gc('refresherFixedBackground', 'transparent')
},
//设置固定的自定义下拉刷新区域高度默认为0
refresherFixedBacHeight: {
type: [Number, String],
default: u.gc('refresherFixedBacHeight', 0)
},
//设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例范围0-1值越大代表衰减越多。默认为0.7(nvue无效)
refresherOutRate: {
type: Number,
default: u.gc('refresherOutRate', 0.7)
},
//是否显示最后更新时间,默认为否
showRefresherUpdateTime: {
type: Boolean,
default: u.gc('showRefresherUpdateTime', false)
},
//如果需要区别不同页面的最后更新时间请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串
refresherUpdateTimeKey: {
type: String,
default: u.gc('refresherUpdateTimeKey', 'default')
},
},
data() {
return {
//下拉刷新状态
refresherStatus: Enum.Refresher.Default,
refresherTouchstartY: 0,
lastRefresherTouchmove: null,
refresherReachMaxAngle: true,
refresherTransform: 'translateY(0px)',
refresherTransition: '',
finalRefresherDefaultStyle: 'black',
refresherRevealStackCount: 0,
refresherCompleteTimeout: null,
refresherCompleteSubTimeout: null,
refresherEndTimeout: null,
isTouchmovingTimeout: null,
refresherTriggered: false,
isTouchmoving: false,
isTouchEnded: false,
isUserPullDown: false,
privateRefresherEnabled: -1,
privateShowRefresherWhenReload: false,
customRefresherHeight: -1,
showCustomRefresher: false,
doRefreshAnimateAfter: false,
isRefresherInComplete: false,
pullDownTimeStamp: 0,
moveDis: 0,
oldMoveDis: 0,
oldRefresherTouchmoveY: 0,
oldTouchDirection: ''
}
},
watch: {
refresherDefaultStyle: {
handler(newVal) {
if (newVal.length) {
this.finalRefresherDefaultStyle = newVal;
}
},
immediate: true
},
refresherStatus(newVal, oldVal) {
if (newVal === Enum.Refresher.Loading){
this._cleanRefresherEndTimeout();
}
this.$emit('refresherStatusChange', newVal);
this.$emit('update:refresherStatus', newVal);
},
moveDis(newVal, oldVal){
this.oldMoveDis = oldVal;
}
},
computed: {
pullDownDisTimeStamp() {
return 1000 / this.refresherFps;
},
finalRefresherEnabled() {
if (this.useChatRecordMode) return false;
if (this.privateRefresherEnabled === -1) return this.refresherEnabled;
return this.privateRefresherEnabled === 1;
},
finalRefresherThreshold() {
let refresherThreshold = this.refresherThreshold;
let idDefault = false;
if (refresherThreshold === '80rpx') {
idDefault = true;
if (this.showRefresherUpdateTime) {
refresherThreshold = '120rpx';
}
}
if (idDefault && this.customRefresherHeight > 0) {
return this.customRefresherHeight;
}
return u.convertTextToPx(refresherThreshold);
},
finalRefresherFixedBacHeight() {
return u.convertTextToPx(this.refresherFixedBacHeight);
},
finalRefresherThemeStyle() {
return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle;
},
finalRefresherOutRate() {
let rate = this.refresherOutRate;
rate = Math.max(0,rate);
rate = Math.min(1,rate);
return rate;
},
finalRefresherTransform() {
if (this.refresherTransform === 'translateY(0px)') return 'none';
return this.refresherTransform;
},
finalShowRefresherWhenReload() {
return this.showRefresherWhenReload || this.privateShowRefresherWhenReload;
},
finalRefresherTriggered() {
if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false;
return this.refresherTriggered;
},
showRefresher() {
const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher && this.isTouchmoving;
// #ifndef APP-NVUE
if (this.customRefresherHeight === -1 && showRefresher) {
setTimeout(() => {
this.$nextTick(()=>{
this._updateCustomRefresherHeight();
})
}, 100)
}
// #endif
return showRefresher;
},
hasTouchmove(){
// #ifdef VUE2
// #ifdef APP-VUE || H5
if (this.$listeners && !this.$listeners.refresherTouchmove) return false;
// #endif
// #ifdef MP-WEIXIN || MP-QQ
return this.watchRefresherTouchmove;
// #endif
return true;
// #endif
return this.watchRefresherTouchmove;
},
},
methods: {
//终止下拉刷新状态
endRefresh(){
this._refresherEnd();
},
handleRefresherStatusChanged(func) {
this.refresherStatusChangedFunc = func;
},
//自定义下拉刷新被触发
_onRefresh(fromScrollView=false,isUserPullDown=true) {
if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return;
this.$emit('onRefresh');
this.$emit('Refresh');
// #ifdef APP-NVUE
if (this.loading) {
setTimeout(()=>{
this._nRefresherEnd();
},500)
return;
}
// #endif
if (this.loading || this.isRefresherInComplete) return;
this.loadingType = Enum.LoadingType.Refresher;
if (this.nShowRefresherReveal) return;
this.isUserPullDown = isUserPullDown;
this.isUserReload = !isUserPullDown;
this._startLoading(true);
this.refresherTriggered = true;
if(this.reloadWhenRefresh && isUserPullDown){
if (this.useChatRecordMode) {
this._onLoadingMore('click')
} else {
this._reload(false, false, isUserPullDown);
}
}
},
//自定义下拉刷新被复位
_onRestore() {
this.refresherTriggered = 'restore';
this.$emit('onRestore');
this.$emit('Restore');
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
//拖拽开始
_refresherTouchstart(e) {
if (this._touchDisabled()) return;
const touch = u.getTouch(e);
this._handleRefresherTouchstart(touch);
},
// #endif
//进一步处理拖拽开始结果
_handleRefresherTouchstart(touch) {
if (!this.loading && this.isTouchEnded) {
this.isTouchmoving = false;
}
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this.isTouchEnded = false;
this.refresherTransition = '';
this.refresherTouchstartY = touch.touchY;
this.$emit('refresherTouchstart', this.refresherTouchstartY);
this.lastRefresherTouchmove = touch;
this._cleanRefresherCompleteTimeout();
this._cleanRefresherEndTimeout();
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
//拖拽中
_refresherTouchmove(e) {
const currentTimeStamp = u.getTime();
let touch = null;
let refresherTouchmoveY = 0;
if (this.watchTouchDirectionChange) {
touch = u.getTouch(e);
refresherTouchmoveY = touch.touchY;
const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom';
direction === this.oldTouchDirection && this._handleTouchDirectionChange({direction});
this.oldTouchDirection = direction;
this.oldRefresherTouchmoveY = refresherTouchmoveY;
}
if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return;
if (this._touchDisabled()) return;
this.pullDownTimeStamp = Number(currentTimeStamp);
touch = u.getTouch(e);
refresherTouchmoveY = touch.touchY;
let moveDis = refresherTouchmoveY - this.refresherTouchstartY;
if (moveDis < 0) return;
if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) {
if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return;
const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX);
const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY);
const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
if ((x || y) && x > 1) {
const angle = Math.asin(y / z) / Math.PI * 180;
if (angle < this.refresherMaxAngle) {
this.lastRefresherTouchmove = touch;
this.refresherReachMaxAngle = false;
return;
}
}
}
moveDis = this._getFinalRefresherMoveDis(moveDis);
this._handleRefresherTouchmove(moveDis, touch);
if (!this.disabledBounce) {
if(this.isIos){
// #ifndef MP-LARK
this._handleScrollViewDisableBounce({bounce: false});
// #endif
}
this.disabledBounce = true;
}
this._emitTouchmove({pullingDistance:moveDis,dy:this.moveDis - this.oldMoveDis});
},
// #endif
//进一步处理拖拽中结果
_handleRefresherTouchmove(moveDis, touch) {
this.refresherReachMaxAngle = true;
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this.isTouchmoving = true;
//this.refresherTransition = '';
this.isTouchEnded = false;
this.refresherStatus = moveDis >= this.finalRefresherThreshold ? Enum.Refresher.ReleaseToRefresh : this.refresherStatus = Enum.Refresher.Default;
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
// this.scrollEnable = false;
this.refresherTransform = `translateY(${moveDis}px)`;
this.lastRefresherTouchmove = touch;
// #endif
this.moveDis = moveDis;
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
//拖拽结束
_refresherTouchend(e) {
if (this._touchDisabled() || !this.isTouchmoving) return;
const touch = u.getTouch(e);
let refresherTouchendY = touch.touchY;
let moveDis = refresherTouchendY - this.refresherTouchstartY;
moveDis = this._getFinalRefresherMoveDis(moveDis);
this._handleRefresherTouchend(moveDis);
this._handleScrollViewDisableBounce({bounce: true});
this.disabledBounce = false;
},
// #endif
//进一步处理拖拽结束结果
_handleRefresherTouchend(moveDis) {
// #ifndef APP-PLUS || H5 || MP-WEIXIN
if (!this.isTouchmoving) return;
// #endif
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this.refresherReachMaxAngle = true;
if (moveDis < 0 && this.usePageScroll && this.loadingMoreEnabled && this.useCustomRefresher && this.pageScrollTop === -1) {
if (this.showConsoleError) {
u.consoleErr('usePageScroll为true并且自定义下拉刷新时必须引入mixin或在page滚动时通过调用z-paging组件的updatePageScrollTop方法设置当前的scrollTop');
}
}
this.isTouchEnded = true;
if (moveDis >= this.finalRefresherThreshold && this.refresherStatus === Enum.Refresher.ReleaseToRefresh) {
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`;
// #endif
this.moveDis = this.finalRefresherThreshold;
this.refresherStatus = Enum.Refresher.Loading;
this._doRefresherLoad();
} else {
this._refresherEnd();
this.isTouchmovingTimeout = setTimeout(() => {
this.isTouchmoving = false;
}, this.refresherDefaultDuration);
}
this.scrollEnable = true;
this.$emit('refresherTouchend', moveDis);
},
//处理scroll-view bounce是否生效
_handleScrollViewDisableBounce(e) {
if (!this.usePageScroll && !this.scrollToTopBounceEnabled) {
this.refresherTransition = '';
this.scrollEnable = e.bounce;
}
},
//wxs正在下拉状态改变处理
_handleWxsPullingDownStatusChange(onPullingDown) {
this.wxsOnPullingDown = onPullingDown;
if (onPullingDown && !this.useChatRecordMode) {
this.renderPropScrollTop = 0;
}
},
//wxs正在下拉处理
_handleWxsPullingDown(e){
this._emitTouchmove({pullingDistance:e.moveDis,dy:e.diffDis});
},
//wxs触摸方向改变
_handleTouchDirectionChange(e) {
this.$emit('touchDirectionChange',e.direction);
},
//wxs通知更新其props
_handlePropUpdate(e){
this.wxsPropType = u.getTime().toString();
},
//下拉刷新结束
_refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) {
if (this.loadingType === Enum.LoadingType.Refresher) {
let refresherCompleteDelay = 0;
if(fromAddData && (isUserPullDown || this.showRefresherWhenReload)){
refresherCompleteDelay = this.refresherCompleteDuration > 700 ? 1 : this.refresherCompleteDelay;
}
const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default;
if (this.finalShowRefresherWhenReload) {
const stackCount = this.refresherRevealStackCount;
this.refresherRevealStackCount--;
if (stackCount > 1) return;
}
this._cleanRefresherEndTimeout();
this.refresherEndTimeout = setTimeout(() => {
this.refresherStatus = refresherStatus;
}, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0);
// #ifndef APP-NVUE
if (refresherCompleteDelay > 0) {
this.isRefresherInComplete = true;
}
// #endif
this._cleanRefresherCompleteTimeout();
this.refresherCompleteTimeout = setTimeout(() => {
let animateDuration = 1;
const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear';
if (fromAddData) {
animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000;
}
this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`;
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = 'translateY(0px)';
// #endif
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
this.wxsPropType = this.refresherTransition + 'end' + u.getTime();
// #endif
// #ifdef APP-NVUE
this._nRefresherEnd();
// #endif
this.moveDis = 0;
// #ifndef APP-NVUE
if (refresherStatus === Enum.Refresher.Complete) {
if (this.refresherCompleteSubTimeout) {
clearTimeout(this.refresherCompleteSubTimeout);
this.refresherCompleteSubTimeout = null;
}
this.refresherCompleteSubTimeout = setTimeout(() => {
this.$nextTick(() => {
this.refresherStatus = Enum.Refresher.Default;
this.isRefresherInComplete = false;
})
}, animateDuration * 800);
}
// #endif
}, refresherCompleteDelay);
}
if (setLoading) {
setTimeout(() => {
this.loading = false;
}, shouldEndLoadingDelay ? c.delayTime : 0);
isUserPullDown && this._onRestore();
}
},
//模拟用户手动触发下拉刷新
_doRefresherRefreshAnimate() {
this._cleanRefresherCompleteTimeout();
// #ifndef APP-NVUE
const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this
.customRefresherHeight === -1 && this.refresherThreshold === '80rpx';
if (doRefreshAnimateAfter) {
this.doRefreshAnimateAfter = true;
return;
}
// #endif
this.refresherRevealStackCount++;
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`;
// #endif
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
this.wxsPropType = 'begin' + u.getTime();
// #endif
this.moveDis = this.finalRefresherThreshold;
this.refresherStatus = Enum.Refresher.Loading;
this.isTouchmoving = true;
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this._doRefresherLoad(false);
},
//触发下拉刷新
_doRefresherLoad(isUserPullDown=true) {
this._onRefresh(false,isUserPullDown);
this.loading = true;
},
//获取处理后的moveDis
_getFinalRefresherMoveDis(moveDis) {
moveDis = moveDis * 0.85;
if (moveDis >= this.finalRefresherThreshold) {
moveDis = this.finalRefresherThreshold + (moveDis - this.finalRefresherThreshold) * (1 - this.finalRefresherOutRate);
}
return moveDis;
},
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
//判断touch手势是否要触发
_touchDisabled() {
let checkOldScrollTop = this.oldScrollTop > 5;
return this.loading || this.isRefresherInComplete || this.useChatRecordMode || !this.refresherEnabled || !this.useCustomRefresher ||(this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop);
},
// #endif
//更新自定义下拉刷新view高度
_updateCustomRefresherHeight() {
this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => {
if (res) {
this.customRefresherHeight = res[0].height;
if (this.customRefresherHeight > 0) {
this.showCustomRefresher = true;
}
} else {
this.customRefresherHeight = 0;
}
if (this.doRefreshAnimateAfter) {
this.doRefreshAnimateAfter = false;
this._doRefresherRefreshAnimate();
}
});
},
//发射pullingDown事件
_emitTouchmove(e){
// #ifndef APP-NVUE
e.viewHeight = this.finalRefresherThreshold;
// #endif
e.rate = e.pullingDistance / e.viewHeight;
if(this.hasTouchmove){
this.$emit('refresherTouchmove',e);
}
},
//清除refresherCompleteTimeout
_cleanRefresherCompleteTimeout() {
this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout);
// #ifdef APP-NVUE
this._nRefresherEnd(false);
// #endif
},
//清除refresherEndTimeout
_cleanRefresherEndTimeout() {
this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout);
},
}
}
export default ZPRefresher;

View File

@@ -0,0 +1,469 @@
// [z-paging]scroll相关模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
// #ifdef APP-NVUE
const weexDom = weex.requireModule('dom');
// #endif
const ZPScroller = {
props: {
//使用页面滚动默认为否当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高但配置会略微繁琐
usePageScroll: {
type: Boolean,
default: u.gc('usePageScroll', false)
},
//是否可以滚动使用内置scroll-view和nvue时有效默认为是
scrollable: {
type: Boolean,
default: u.gc('scrollable', true)
},
//控制是否出现滚动条,默认为是
showScrollbar: {
type: Boolean,
default: u.gc('showScrollbar', true)
},
//是否允许横向滚动,默认为否
scrollX: {
type: Boolean,
default: u.gc('scrollX', false)
},
//iOS设备上滚动到顶部时是否允许回弹效果默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯但是有吸顶view时滚动到顶部时可能出现抖动。
scrollToTopBounceEnabled: {
type: Boolean,
default: u.gc('scrollToTopBounceEnabled', false)
},
//iOS设备上滚动到底部时是否允许回弹效果默认为是。
scrollToBottomBounceEnabled: {
type: Boolean,
default: u.gc('scrollToBottomBounceEnabled', true)
},
//在设置滚动条位置时使用动画过渡,默认为否
scrollWithAnimation: {
type: Boolean,
default: u.gc('scrollWithAnimation', false)
},
//值应为某子元素idid不能以数字开头。设置哪个方向可滚动则在哪个方向滚动到该元素
scrollIntoView: {
type: String,
default: u.gc('scrollIntoView', '')
},
},
data() {
return {
scrollTop: 0,
oldScrollTop: 0,
scrollViewStyle: {},
scrollViewContainerStyle: {},
scrollViewInStyle: {},
pageScrollTop: -1,
scrollEnable: true,
privateScrollWithAnimation: -1,
cacheScrollNodeHeight: -1
}
},
watch: {
oldScrollTop(newVal, oldVal) {
!this.usePageScroll && this._scrollTopChange(newVal,oldVal,false);
},
pageScrollTop(newVal, oldVal) {
this.usePageScroll && this._scrollTopChange(newVal,oldVal,true);
},
usePageScroll: {
handler(newVal) {
this.$nextTick(() => {
this.renderPropUsePageScroll = newVal;
})
if (this.loaded && this.autoHeight) {
this._setAutoHeight(!newVal);
}
// #ifdef H5
if (newVal) {
this.$nextTick(()=>{
const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main;
if (mainScrollRef) {
mainScrollRef.style = {};
}
})
}
// #endif
},
immediate: true
},
finalScrollTop(newVal, oldVal) {
if (!this.useChatRecordMode) {
this.renderPropScrollTop = newVal < 6 ? 0 : 10;
}
},
},
computed: {
finalScrollWithAnimation() {
if (this.privateScrollWithAnimation !== -1) {
const scrollWithAnimation = this.privateScrollWithAnimation === 1;
this.privateScrollWithAnimation = -1;
return scrollWithAnimation;
}
return this.scrollWithAnimation;
},
finalScrollViewStyle() {
if (this.superContentZIndex != 1) {
this.scrollViewStyle['z-index'] = this.superContentZIndex;
this.scrollViewStyle['position'] = 'relative';
}
return this.scrollViewStyle;
},
finalScrollTop() {
return this.usePageScroll ? this.pageScrollTop : this.oldScrollTop;
},
finalIsOldWebView() {
return this.isOldWebView && !this.usePageScroll;
}
},
methods: {
//滚动到顶部animate为是否展示滚动动画默认为是
scrollToTop(animate,checkReverse = true) {
// #ifdef APP-NVUE
if (checkReverse && this.useChatRecordMode) {
if(!this.nIsFirstPageAndNoMore){
this.scrollToBottom(animate, false);
return;
}
}
// #endif
this.$nextTick(() => {
this._scrollToTop(animate, false);
// #ifdef APP-NVUE
if (this.nvueFastScroll && animate) {
setTimeout(() => {
this._scrollToTop(false, false);
}, 150);
}
// #endif
})
},
//滚动到底部animate为是否展示滚动动画默认为是
scrollToBottom(animate,checkReverse = true) {
// #ifdef APP-NVUE
if (checkReverse && this.useChatRecordMode) {
if(!this.nIsFirstPageAndNoMore){
this.scrollToTop(animate, false);
return;
}
}
// #endif
this.$nextTick(() => {
this._scrollToBottom(animate);
// #ifdef APP-NVUE
if (this.nvueFastScroll && animate) {
setTimeout(() => {
this._scrollToBottom(false);
}, 150);
}
// #endif
})
},
//滚动到指定view(vue中有效)。sel为需要滚动的view的id值不包含"#"offset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollIntoViewById(sel, offset, animate) {
this._scrollIntoView(sel, offset, animate);
},
//滚动到指定view(vue中有效)。nodeTop为需要滚动的view的top值(通过uni.createSelectorQuery()获取)offset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollIntoViewByNodeTop(nodeTop, offset, animate) {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this._scrollIntoViewByNodeTop(nodeTop, offset, animate);
})
},
//滚动到指定位置(vue中有效)。y为与顶部的距离单位为pxoffset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollToY(y, offset, animate) {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this._scrollToY(y, offset, animate);
})
},
//滚动到指定view(nvue中有效)。index为需要滚动的view的index(第几个)offset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollIntoViewByIndex(index, offset, animate) {
this._scrollIntoView(index, offset, animate);
},
//滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#"offset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollIntoViewByView(view, offset, animate) {
this._scrollIntoView(view, offset, animate);
},
//当使用页面滚动并且自定义下拉刷新时请在页面的onPageScroll中调用此方法告知z-paging当前的pageScrollTop否则会导致在任意位置都可以下拉刷新
updatePageScrollTop(value) {
if (value == undefined) {
u.consoleErr('updatePageScrollTop方法缺少参数请将页面onPageScroll事件中的scrollTop传递给此方法');
return;
}
this.pageScrollTop = value;
},
//当使用页面滚动并且设置了slot="top"时默认初次加载会自动获取其高度并使内部容器下移当slot="top"的view高度动态改变时在其高度需要更新时调用此方法
updatePageScrollTopHeight() {
this._updatePageScrollTopOrBottomHeight('top');
},
//当使用页面滚动并且设置了slot="bottom"时默认初次加载会自动获取其高度并使内部容器下移当slot="bottom"的view高度动态改变时在其高度需要更新时调用此方法
updatePageScrollBottomHeight() {
this._updatePageScrollTopOrBottomHeight('bottom');
},
//更新z-paging内置scroll-view的scrollTop
updateScrollViewScrollTop(scrollTop, animate = true) {
this.privateScrollWithAnimation = animate ? 1 : 0;
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this.scrollTop = scrollTop;
this.oldScrollTop = this.scrollTop;
});
},
//当滚动到顶部时
_scrollToUpper() {
this.$emit('scrolltoupper');
this.$emit('scrollTopChange', 0);
this.$nextTick(() => {
this.oldScrollTop = 0;
})
if (!this.useChatRecordMode) return;
if (this.loadingStatus === Enum.More.NoMore) return;
this._onLoadingMore('click');
},
//滚动到顶部
_scrollToTop(animate = true, isPrivate = true) {
// #ifdef APP-NVUE
const el = this.$refs['zp-n-list-top-tag'];
if (this.usePageScroll) {
this._getNodeClientRect('zp-page-scroll-top', false).then((node) => {
let nodeHeight = 0;
if (node) {
nodeHeight = node[0].height;
}
weexDom.scrollToElement(el, {
offset: -nodeHeight,
animated: animate
});
});
} else {
if(!this.isIos && this.nvueListIs === 'scroller'){
this._getNodeClientRect('zp-n-refresh-container', false).then((node) => {
let nodeHeight = 0;
if (node) {
nodeHeight = node[0].height;
}
weexDom.scrollToElement(el, {
offset: -nodeHeight,
animated: animate
});
});
}else{
weexDom.scrollToElement(el, {
offset: 0,
animated: animate
});
}
}
return;
// #endif
if (this.usePageScroll) {
this.$nextTick(() => {
uni.pageScrollTo({
scrollTop: 0,
duration: animate ? 100 : 0,
});
});
return;
}
this.privateScrollWithAnimation = animate ? 1 : 0;
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this.scrollTop = 0;
this.oldScrollTop = this.scrollTop;
});
},
//滚动到底部
async _scrollToBottom(animate = true) {
// #ifdef APP-NVUE
const el = this.$refs['zp-n-list-bottom-tag'];
if (el) {
weexDom.scrollToElement(el, {
offset: 0,
animated: animate
});
} else {
u.consoleErr('滚动到底部失败因为您设置了hideNvueBottomTag为true');
}
return;
// #endif
if (this.usePageScroll) {
this.$nextTick(() => {
uni.pageScrollTo({
scrollTop: Number.MAX_VALUE,
duration: animate ? 100 : 0,
});
});
return;
}
try {
this.privateScrollWithAnimation = animate ? 1 : 0;
let pagingContainerH = 0;
let scrollViewH = 0;
const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container');
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
if (pagingContainerNode) {
pagingContainerH = pagingContainerNode[0].height;
}
if (scrollViewNode) {
scrollViewH = scrollViewNode[0].height;
}
if (pagingContainerH > scrollViewH) {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight;
this.oldScrollTop = this.scrollTop;
});
}
} catch (e) {}
},
//滚动到指定view
_scrollIntoView(sel, offset = 0, animate = false, finishCallback) {
try {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
// #ifdef APP-NVUE
const refs = this.$parent.$refs;
if (!refs) return;
const dataType = Object.prototype.toString.call(sel);
let el = null;
if (dataType === '[object Number]') {
const els = refs[`z-paging-${sel}`];
el = els ? els[0] : null;
} else if (dataType === '[object Array]') {
el = sel[0];
} else {
el = sel;
}
if (el) {
weexDom.scrollToElement(el, {
offset: -offset,
animated: animate
});
} else {
u.consoleErr('在nvue中滚动到指定位置cell必须设置 :ref="`z-paging-${index}`"');
}
return;
// #endif
if (sel.indexOf('#') != -1) {
sel = sel.replace('#', '');
}
this._getNodeClientRect('#' + sel, false).then((node) => {
if (node) {
let nodeTop = node[0].top;
this._scrollIntoViewByNodeTop(nodeTop, offset, animate);
if (finishCallback) {
finishCallback();
}
}
});
});
} catch (e) {}
},
//通过nodeTop滚动到指定view
_scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) {
this._scrollToY(nodeTop,offset,animate,true);
},
//滚动到指定位置
_scrollToY(y, offset = 0, animate = false, addScrollTop = false) {
this.privateScrollWithAnimation = animate ? 1 : 0;
if (this.usePageScroll) {
uni.pageScrollTo({
scrollTop: y - offset,
duration: animate ? 100 : 0
});
} else {
if(addScrollTop){
y += this.oldScrollTop;
}
this.scrollTop = y - offset;
this.oldScrollTop = this.scrollTop;
}
},
//scroll-view滚动中
_scroll(e) {
this.$emit('scroll', e);
const scrollTop = e.detail.scrollTop;
// #ifndef APP-NVUE
this.finalUseVirtualList && this._updateVirtualScroll(scrollTop,this.oldScrollTop - scrollTop);
// #endif
this.oldScrollTop = scrollTop;
const scrollDiff = e.detail.scrollHeight - this.oldScrollTop;
!this.isIos && this._checkScrolledToBottom(scrollDiff);
},
//scrollTop改变时触发
_scrollTopChange(newVal,oldVal,isPageScrollTop){
this.$emit('scrollTopChange', newVal);
this.$emit('update:scrollTop', newVal);
this._checkShouldShowBackToTop(newVal, oldVal);
const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : newVal;
if (isPageScrollTop) {
this.wxsPageScrollTop = scrollTop;
} else {
this.wxsScrollTop = scrollTop;
}
},
//更新使用页面滚动时slot="top"或"bottom"插入view的高度
_updatePageScrollTopOrBottomHeight(type) {
// #ifndef APP-NVUE
if (!this.usePageScroll) return;
// #endif
this._doCheckScrollViewShouldFullHeight(this.realTotalData);
const node = `.zp-page-${type}`;
const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`;
let safeAreaInsetBottomAdd = this.safeAreaInsetBottom;
// #ifdef APP-NVUE
if (!this.usePageScroll) {
safeAreaInsetBottomAdd = false;
}
// #endif
this.$nextTick(() => {
let delayTime = 0;
// #ifdef MP-BAIDU || APP-NVUE
delayTime = 10;
// #endif
setTimeout(() => {
this._getNodeClientRect(node).then((res) => {
if (res) {
let pageScrollNodeHeight = res[0].height;
if (type === 'bottom') {
if (safeAreaInsetBottomAdd) {
pageScrollNodeHeight += this.safeAreaBottom;
}
} else {
this.cacheTopHeight = pageScrollNodeHeight;
}
this.$set(this.scrollViewStyle, marginText,
`${pageScrollNodeHeight}px`);
} else if (safeAreaInsetBottomAdd) {
this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`);
}
});
}, delayTime)
})
},
//获取slot="left"和slot="right"宽度并且更新布局
_updateLeftAndRightWidth(){
if (!this.finalIsOldWebView) return;
this.$nextTick(() => {
let delayTime = 0;
// #ifdef MP-BAIDU
delayTime = 10;
// #endif
setTimeout(() => {
this._getNodeClientRect('.zp-page-left').then((res) => {
this.scrollViewContainerStyle['left'] = res ? res[0].width + 'px' : 0;
});
this._getNodeClientRect('.zp-page-right').then((res) => {
this.scrollViewContainerStyle['right'] = res ? res[0].width + 'px' : 0;
});
}, delayTime)
})
}
}
}
export default ZPScroller;

View File

@@ -0,0 +1,374 @@
// [z-paging]虚拟列表模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
const ZPVirtualList = {
props: {
//是否使用虚拟列表,默认为否
useVirtualList: {
type: Boolean,
default: u.gc('useVirtualList', false)
},
//是否在z-paging内部循环渲染列表(内置列表)默认为否。若use-virtual-list为true则此项恒为true
useInnerList: {
type: Boolean,
default: u.gc('useInnerList', false)
},
//强制关闭inner-list默认为false如果为true将强制关闭innerList适用于开启了虚拟列表后需要强制关闭inner-list的情况
forceCloseInnerList: {
type: Boolean,
default: u.gc('forceCloseInnerList', false)
},
//内置列表cell的key名称仅nvue有效在nvue中开启use-inner-list时必须填此项
cellKeyName: {
type: String,
default: u.gc('cellKeyName', '')
},
//innerList样式
innerListStyle: {
type: Object,
default: function() {
return u.gc('innerListStyle', {});
}
},
//innerCell样式
innerCellStyle: {
type: Object,
default: function() {
return u.gc('innerCellStyle', {});
}
},
//预加载的列表可视范围(列表高度)页数默认为7即预加载当前页及上下各7页的cell。此数值越大则虚拟列表中加载的dom越多内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题
preloadPage: {
type: [Number, String],
default: u.gc('preloadPage', 7),
validator: (value) => {
if (value <= 0) u.consoleErr('preload-page必须大于0');
return value > 0;
}
},
//虚拟列表cell高度模式默认为fixed也就是每个cell高度完全相同将以第一个cell高度为准进行计算。可选值【dynamic】即代表高度是动态非固定的【dynamic】性能低于【fixed】。
cellHeightMode: {
type: String,
default: u.gc('cellHeightMode', 'fixed')
},
//虚拟列表列数默认为1。常用于每行有多列的情况例如每行有2列数据需要将此值设置为2
virtualListCol: {
type: [Number, String],
default: u.gc('virtualListCol', 1)
},
//虚拟列表scroll取样帧率默认为60过高可能出现卡顿等问题
virtualScrollFps: {
type: [Number, String],
default: u.gc('virtualScrollFps', 60)
},
},
data() {
return {
virtualListKey: u.getInstanceId(),
virtualPageHeight: 0,
virtualCellHeight: 0,
virtualScrollTimeStamp: 0,
virtualList: [],
virtualPlaceholderTopHeight: 0,
virtualPlaceholderBottomHeight: 0,
virtualTopRangeIndex: 0,
virtualBottomRangeIndex: 0,
lastVirtualTopRangeIndex: 0,
lastVirtualBottomRangeIndex: 0,
virtualHeightCacheList: [],
getCellHeightRetryCount: {
fixed: 0,
dynamic: 0
},
pagingOrgTop: -1,
updateVirtualListFromDataChange: false
}
},
watch: {
realTotalData(newVal) {
// #ifndef APP-NVUE
if (this.finalUseVirtualList) {
this.updateVirtualListFromDataChange = true;
this.$nextTick(() => {
if (!newVal.length) {
this._resetDynamicListState(!this.isUserPullDown);
}
this.getCellHeightRetryCount.fixed = 0;
this.finalUseVirtualList && newVal.length && this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight();
this.finalUseVirtualList && this._updateVirtualScroll(this.oldScrollTop);
})
}
// #endif
},
virtualList(newVal){
this.$emit('update:virtualList', newVal);
this.$emit('virtualListChange', newVal);
}
},
computed: {
finalUseVirtualList() {
if (this.useVirtualList && this.usePageScroll){
u.consoleErr('使用页面滚动时,开启虚拟列表无效!');
}
return this.useVirtualList && !this.usePageScroll;
},
finalUseInnerList() {
return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList)
},
finalCellKeyName() {
// #ifdef APP-NVUE
if (this.finalUseVirtualList){
if (!this.cellKeyName.length){
u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name否则将可能导致列表渲染错误');
}
}
// #endif
return this.cellKeyName;
},
finalVirtualPageHeight(){
return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight;
return virtualPageHeight * this.preloadPage;
},
virtualRangePageHeight(){
return this.finalVirtualPageHeight * this.preloadPage;
},
virtualScrollDisTimeStamp() {
return 1000 / this.virtualScrollFps;
},
},
methods: {
//初始化虚拟列表
_virtualListInit() {
this.$nextTick(() => {
setTimeout(() => {
this._getNodeClientRect('.zp-scroll-view').then(node => {
if (node && node.length) {
this.pagingOrgTop = node[0].top;
this.virtualPageHeight = node[0].height;
}
});
}, 100);
})
},
//cellHeightMode为fixed时获取第一个cell高度
_updateFixedCellHeight() {
this.$nextTick(() => {
const updateFixedCellHeightTimeout = setTimeout(() => {
this._getNodeClientRect(`#zp-${0}`,this.finalUseInnerList).then(cellNode => {
const hasCellNode = cellNode && cellNode.length;
if (!hasCellNode) {
clearTimeout(updateFixedCellHeightTimeout);
if (this.getCellHeightRetryCount.fixed > 10) {
u.consoleErr('获取虚拟列表cell高度失败可能是for循环cell处没有写:id="`zp-${item.zp_index}`",请检查您的代码!')
return;
}
this.getCellHeightRetryCount.fixed++;
this._updateFixedCellHeight();
} else {
this.virtualCellHeight = cellNode[0].height;
this._updateVirtualScroll(this.oldScrollTop);
}
});
}, 100);
})
},
//cellHeightMode为dynamic时获取每个cell高度
_updateDynamicCellHeight(list) {
this.$nextTick(() => {
const updateDynamicCellHeightTimeout = setTimeout(async () => {
for (let i = 0; i < list.length; i++) {
let item = list[i];
const cellNode = await this._getNodeClientRect(`#zp-${item[c.listCellIndexKey]}`,this.finalUseInnerList);
const hasCellNode = cellNode && cellNode.length;
const currentHeight = hasCellNode ? cellNode[0].height : 0;
if (!hasCellNode) {
clearTimeout(updateDynamicCellHeightTimeout);
this.virtualHeightCacheList = this.virtualHeightCacheList.slice(-i);
if (this.getCellHeightRetryCount.dynamic > 10) {
u.consoleErr('获取虚拟列表cell高度失败可能是for循环cell处没有写:id="`zp-${item.zp_index}`",请检查您的代码!')
return;
}
this.getCellHeightRetryCount.dynamic++;
this._updateDynamicCellHeight(list);
break;
}
let lastHeightCache = null;
if (this.virtualHeightCacheList.length) {
lastHeightCache = this.virtualHeightCacheList.slice(-1)[0];
}
const lastHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
this.virtualHeightCacheList.push({
height: currentHeight,
lastHeight: lastHeight,
totalHeight: lastHeight + currentHeight
});
}
this._updateVirtualScroll(this.oldScrollTop);
}, 100)
})
},
//设置cellItem的index
_setCellIndex(list, isFirstPage) {
let lastItem = null;
let lastItemIndex = 0;
if (!isFirstPage) {
lastItemIndex = this.realTotalData.length;
if (this.realTotalData.length) {
lastItem = this.realTotalData.slice(-1)[0];
}
if (lastItem && lastItem[c.listCellIndexKey] !== undefined) {
lastItemIndex = lastItem[c.listCellIndexKey] + 1;
}
} else {
this._resetDynamicListState();
}
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
item = {item};
}
item[c.listCellIndexKey] = lastItemIndex + i;
item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[c.listCellIndexKey]}`;
list[i] = item;
}
this.getCellHeightRetryCount.dynamic = 0;
this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list);
},
//更新scroll滚动
_updateVirtualScroll(scrollTop, scrollDiff = 0) {
const currentTimeStamp = u.getTime();
if (scrollTop === 0) {
this._resetTopRange();
}
if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) {
return;
}
this.virtualScrollTimeStamp = Number(currentTimeStamp);
let scrollIndex = 0;
const cellHeightMode = this.cellHeightMode;
if (cellHeightMode === Enum.CellHeightMode.Fixed) {
scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0;
this._updateFixedTopRangeIndex(scrollIndex);
this._updateFixedBottomRangeIndex(scrollIndex);
} else if(cellHeightMode === Enum.CellHeightMode.Dynamic) {
const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom';
const rangePageHeight = this.virtualRangePageHeight;
const topRangePageOffset = scrollTop - rangePageHeight;
const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight;
let virtualBottomRangeIndex = 0;
let virtualPlaceholderBottomHeight = 0;
let reachedLimitBottom = false;
let lastHeightCache = null;
const heightCacheList = this.virtualHeightCacheList;
if (heightCacheList.length) {
lastHeightCache = heightCacheList.slice(-1)[0];
}
let startTopRangeIndex = this.virtualTopRangeIndex;
if (scrollDirection === 'bottom') {
for (let i = startTopRangeIndex; i < heightCacheList.length;i++){
const heightCacheItem = heightCacheList[i];
if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) {
this.virtualTopRangeIndex = i;
this.virtualPlaceholderTopHeight = heightCacheItem.lastHeight;
break;
}
}
} else {
let topRangeMatched = false;
for (let i = startTopRangeIndex; i >= 0;i--){
const heightCacheItem = heightCacheList[i];
if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) {
this.virtualTopRangeIndex = i;
this.virtualPlaceholderTopHeight = heightCacheItem.lastHeight;
topRangeMatched = true;
break;
}
}
!topRangeMatched && this._resetTopRange();
}
for (let i = this.virtualTopRangeIndex; i < heightCacheList.length;i++){
const heightCacheItem = heightCacheList[i];
if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) {
virtualBottomRangeIndex = i;
virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight;
reachedLimitBottom = true;
break;
}
}
if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) {
this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize;
this.virtualPlaceholderBottomHeight = 0;
} else {
this.virtualBottomRangeIndex = virtualBottomRangeIndex;
this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight;
}
this._updateVirtualList();
}
},
//更新fixedCell模式下topRangeIndex&placeholderTopHeight
_updateFixedTopRangeIndex(scrollIndex) {
let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) * this.preloadPage;
virtualTopRangeIndex *= this.virtualListCol;
virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex);
this.virtualTopRangeIndex = virtualTopRangeIndex;
this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight;
},
//更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight
_updateFixedBottomRangeIndex(scrollIndex) {
let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) * (this.preloadPage + 1);
virtualBottomRangeIndex *= this.virtualListCol;
virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex);
this.virtualBottomRangeIndex = virtualBottomRangeIndex;
this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol;
this._updateVirtualList();
},
//更新virtualList
_updateVirtualList() {
const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex);
if (shouldUpdateList) {
this.updateVirtualListFromDataChange = false;
this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex;
this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex;
this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1);
}
},
//重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态
_resetDynamicListState(resetVirtualList = false) {
this.virtualHeightCacheList = [];
if (resetVirtualList) {
this.virtualList = [];
}
this.virtualTopRangeIndex = 0;
this.virtualPlaceholderTopHeight = 0;
},
//重置topRangeIndex和placeholderTopHeight
_resetTopRange() {
this.virtualTopRangeIndex = 0;
this.virtualPlaceholderTopHeight = 0;
this._updateVirtualList();
},
//检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题)
_checkVirtualListScroll() {
if (this.finalUseVirtualList) {
this.$nextTick(() => {
this._getNodeClientRect('.zp-paging-touch-view').then(node => {
const hasNode = node && node.length;
const currentTop = hasNode ? node[0].top : 0;
if (!hasNode || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)){
this._updateVirtualScroll(0);
}
});
})
}
}
}
}
export default ZPVirtualList;

View File

@@ -0,0 +1,26 @@
// [z-paging]处理main.js中的配置信息工具
let config = null;
let getedStorage = false;
const storageKey = 'Z-PAGING-CONFIG-STORAGE-KEY'
function setConfig(value) {
try {
uni.setStorageSync(storageKey, value);
} catch (e) {}
}
function getConfig() {
try {
if (getedStorage) return config;
config = uni.getStorageSync(storageKey);
getedStorage = true;
} catch (e) {
return null;
}
}
export default {
setConfig,
getConfig
};

View File

@@ -0,0 +1,21 @@
// [z-paging]常量
const version = '2.3.1';
const delayTime = 100;
const i18nUpdateKey = 'z-paging-i18n-update';
const errorUpdateKey = 'z-paging-error-emit';
const completeUpdateKey = 'z-paging-complete-emit';
const listCellIndexKey = 'zp_index';
const listCellIndexUniqueKey = 'zp_unique_index';
export default {
version,
delayTime,
i18nUpdateKey,
errorUpdateKey,
completeUpdateKey,
listCellIndexKey,
listCellIndexUniqueKey
}

View File

@@ -0,0 +1,37 @@
// [z-paging]枚举
const Enum = {
//当前加载类型 0.下拉刷新 1.上拉加载更多
LoadingType: {
Refresher: 0,
LoadingMore: 1
},
//下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束
Refresher: {
Default: 0,
ReleaseToRefresh: 1,
Loading: 2,
Complete: 3
},
//底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败
More: {
Default: 0,
Loading: 1,
NoMore: 2,
Fail: 3
},
//@query触发来源 0.用户主动下拉刷新 1.通过reload触发 2.通过refresh触发 3.通过滚动到底部加载更多或点击底部加载更多触发
QueryFrom: {
UserPullDown: 0,
Reload: 1,
Refresh: 2,
LoadingMore: 3
},
//虚拟列表cell高度模式
CellHeightMode: {
Fixed: 'fixed',
Dynamic: 'dynamic'
}
}
export default Enum;

View File

@@ -0,0 +1,133 @@
// z-paging国际化(支持中文、中文繁体和英文)
const i18nUpdateKey = 'z-paging-i18n-update';
const t = {
refresherDefaultText: {
'en': 'Pull down to refresh',
'zh-cn': '继续下拉刷新',
'zh-hant-cn': '繼續下拉重繪',
},
refresherPullingText: {
'en': 'Release to refresh',
'zh-cn': '松开立即刷新',
'zh-hant-cn': '鬆開立即重繪',
},
refresherRefreshingText: {
'en': 'Refreshing...',
'zh-cn': '正在刷新...',
'zh-hant-cn': '正在重繪...',
},
refresherCompleteText: {
'en': 'Refresh succeeded',
'zh-cn': '刷新成功',
'zh-hant-cn': '重繪成功',
},
loadingMoreDefaultText: {
'en': 'Click to load more',
'zh-cn': '点击加载更多',
'zh-hant-cn': '點擊加載更多',
},
loadingMoreLoadingText: {
'en': 'Loading...',
'zh-cn': '正在加载...',
'zh-hant-cn': '正在加載...',
},
loadingMoreNoMoreText: {
'en': 'No more data',
'zh-cn': '没有更多了',
'zh-hant-cn': '沒有更多了',
},
loadingMoreFailText: {
'en': 'Load failed,click to reload',
'zh-cn': '加载失败,点击重新加载',
'zh-hant-cn': '加載失敗,點擊重新加載',
},
emptyViewText: {
'en': 'No data',
'zh-cn': '没有数据哦~',
'zh-hant-cn': '沒有數據哦~',
},
emptyViewReloadText: {
'en': 'Reload',
'zh-cn': '重新加载',
'zh-hant-cn': '重新加載',
},
emptyViewErrorText: {
'en': 'Sorry,load failed',
'zh-cn': '很抱歉,加载失败',
'zh-hant-cn': '很抱歉,加載失敗',
},
refresherUpdateTimeText: {
'en': 'Last update: ',
'zh-cn': '最后更新:',
'zh-hant-cn': '最後更新:',
},
refresherUpdateTimeNoneText: {
'en': 'None',
'zh-cn': '无',
'zh-hant-cn': '無',
},
refresherUpdateTimeTodayText: {
'en': 'Today',
'zh-cn': '今天',
'zh-hant-cn': '今天',
},
refresherUpdateTimeYesterdayText: {
'en': 'Yesterday',
'zh-cn': '昨天',
'zh-hant-cn': '昨天',
}
}
// 获取当前语言,格式为:zh-cn、zh-hant-cn、en。followSystemLanguage:获取的结果是否是在不跟随系统语言下获取到的
function getLanguage(followSystemLanguage = true) {
return _getPrivateLanguage(false, followSystemLanguage);
}
// 获取当前语言,格式为:简体中文、繁體中文、English。followSystemLanguage:获取的结果是否是在不跟随系统语言下获取到的
function getLanguageName(followSystemLanguage = true) {
const language = getLanguage(followSystemLanguage);
const languageNameMap = {
'zh-cn': '简体中文',
'zh-hant-cn': '繁體中文',
'en': 'English'
};
return languageNameMap[language];
}
//设置当前语言,格式为:zh-cn、zh-hant-cn、en
function setLanguage(myLanguage) {
uni.setStorageSync(i18nUpdateKey, myLanguage);
uni.$emit(i18nUpdateKey, myLanguage);
}
// 插件内部使用,请勿直接调用
function _getPrivateLanguage(myLanguage, followSystemLanguage = true) {
let systemLanguage = '';
if (followSystemLanguage) {
systemLanguage = uni.getSystemInfoSync().language;
}
let language = myLanguage || uni.getStorageSync(i18nUpdateKey) || systemLanguage;
language = language.toLowerCase();
const reg = new RegExp('_', '');
language = language.replace(reg, '-');
if (language.indexOf('zh') !== -1) {
if (language === 'zh' || language === 'zh-cn' || language.indexOf('zh-hans') !== -1) {
return 'zh-cn';
}
return 'zh-hant-cn';
}
if (language.indexOf('en') !== -1) {
return 'en';
}
return 'zh-cn';
}
export default {
t,
getLanguage,
getLanguageName,
setLanguage,
_getPrivateLanguage,
}

View File

@@ -0,0 +1,34 @@
// [z-paging]拦截器
//拦截&处理@query事件
function handleQuery(callback) {
try {
setTimeout(function() {
_getApp().globalData.zp_handleQueryCallback = callback;
}, 1);
} catch (e) {}
}
//拦截&处理@query事件(私有,请勿调用)
function _handleQuery(pageNo, pageSize, from){
const handleQueryCallback = _getApp().globalData.zp_handleQueryCallback;
if (handleQueryCallback) {
return handleQueryCallback(pageNo, pageSize, from);
}
return [pageNo, pageSize, from];
}
//获取当前app对象
function _getApp(){
// #ifndef APP-NVUE
return getApp();
// #endif
// #ifdef APP-NVUE
return getApp({allowDefault: true});
// #endif
}
export default {
handleQuery,
_handleQuery
};

View File

@@ -0,0 +1,604 @@
// [z-paging]核心js
import zStatic from './z-paging-static'
import c from './z-paging-constant'
import u from './z-paging-utils'
import zPagingRefresh from '../components/z-paging-refresh'
import zPagingLoadMore from '../components/z-paging-load-more'
import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view'
// modules
import dataHandleModule from './modules/data-handle'
import i18nModule from './modules/i18n'
import nvueModule from './modules/nvue'
import emptyModule from './modules/empty'
import refresherModule from './modules/refresher'
import loadMoreModule from './modules/load-more'
import scrollerModule from './modules/scroller'
import backToTopModule from './modules/back-to-top'
import virtualListModule from './modules/virtual-list'
import Enum from './z-paging-enum'
const systemInfo = uni.getSystemInfoSync();
// #ifdef APP-NVUE
const weexDom = weex.requireModule('dom');
// #endif
export default {
name: "z-paging",
components: {
zPagingRefresh,
zPagingLoadMore,
zPagingEmptyView
},
mixins: [
dataHandleModule,
i18nModule,
nvueModule,
emptyModule,
refresherModule,
loadMoreModule,
scrollerModule,
backToTopModule,
virtualListModule
],
data() {
return {
//--------------静态资源---------------
base64Arrow: zStatic.base64Arrow,
base64Flower: zStatic.base64Flower,
base64BackToTop: zStatic.base64BackToTop,
//-------------全局数据相关--------------
//当前加载类型
loadingType: Enum.LoadingType.Refresher,
requestTimeStamp: 0,
chatRecordLoadingMoreText: '',
wxsPropType: '',
renderPropScrollTop: -1,
renderPropUsePageScroll: -1,
checkScrolledToBottomTimeOut: null,
systemInfo: null,
cssSafeAreaInsetBottom: -1,
cacheTopHeight: 0,
//--------------状态&判断---------------
insideOfPaging: -1,
loading: false,
loadingForNow: false,
isLoadFailed: false,
isIos: systemInfo.platform === 'ios',
disabledBounce: false,
fromCompleteEmit: false,
disabledCompleteEmit: false,
//---------------wxs相关---------------
wxsIsScrollTopInTopRange: true,
wxsScrollTop: 0,
wxsPageScrollTop: 0,
wxsOnPullingDown: false,
};
},
props: {
//调用complete后延迟处理的时间单位为毫秒默认0毫秒优先级高于minDelay
delay: {
type: [Number, String],
default: u.gc('delay', 0),
},
//触发@query后最小延迟处理的时间单位为毫秒默认0毫秒优先级低于delay假设设置为300毫秒若分页请求时间小于300毫秒则在调用complete后延迟[300毫秒-请求时长]若请求时长大于300毫秒则不延迟当show-refresher-when-reload为true或reload(true)时其最小值为400
minDelay: {
type: [Number, String],
default: u.gc('minDelay', 0),
},
//设置z-paging的style部分平台(如微信小程序)无法直接修改组件的style可使用此属性代替
pagingStyle: {
type: Object,
default: function() {
return u.gc('pagingStyle', {});
},
},
//z-paging的高度优先级低于pagingStyle中设置的height传字符串如100px、100rpx、100%
height: {
type: String,
default: u.gc('height', '')
},
//z-paging的宽度优先级低于pagingStyle中设置的width传字符串如100px、100rpx、100%
width: {
type: String,
default: u.gc('width', '')
},
//z-paging的背景色优先级低于pagingStyle中设置的background。传字符串如"#ffffff"
bgColor: {
type: String,
default: u.gc('bgColor', '')
},
//设置z-paging的容器(插槽的父view)的style
pagingContentStyle: {
type: Object,
default: function() {
return u.gc('pagingContentStyle', {});
},
},
//z-paging是否自动高度若自动高度则会自动铺满屏幕
autoHeight: {
type: Boolean,
default: u.gc('autoHeight', false)
},
//z-paging是否自动高度时附加的高度注意添加单位px或rpx若需要减少高度则传负数
autoHeightAddition: {
type: [Number, String],
default: u.gc('autoHeightAddition', '0px')
},
//loading(下拉刷新、上拉加载更多)的主题样式支持blackwhite默认black
defaultThemeStyle: {
type: String,
default: function() {
return u.gc('defaultThemeStyle', 'black');
}
},
//z-paging是否使用fixed布局若使用fixed布局则z-paging的父view无需固定高度z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效)
fixed: {
type: Boolean,
default: u.gc('fixed', true)
},
//是否开启底部安全区域适配
safeAreaInsetBottom: {
type: Boolean,
default: u.gc('safeAreaInsetBottom', false)
},
//开启底部安全区域适配后是否使用placeholder形式实现默认为否。为否时滚动区域会自动避开底部安全区域也就是所有滚动内容都不会挡住底部安全区域若设置为是则滚动时滚动内容会挡住底部安全区域但是当滚动到底部时才会避开底部安全区域
useSafeAreaPlaceholder: {
type: Boolean,
default: u.gc('useSafeAreaPlaceholder', false)
},
//第一次加载后自动隐藏loading slot默认为是
autoHideLoadingAfterFirstLoaded: {
type: Boolean,
default: u.gc('autoHideLoadingAfterFirstLoaded', true)
},
//loading slot是否铺满屏幕并固定默认为否
loadingFullFixed: {
type: Boolean,
default: u.gc('loadingFullFixed', false)
},
//slot="top"的view的z-index默认为99仅使用页面滚动时有效
topZIndex: {
type: Number,
default: u.gc('topZIndex', 99)
},
//z-paging内容容器父view的z-index默认为1
superContentZIndex: {
type: Number,
default: u.gc('superContentZIndex', 1)
},
//z-paging内容容器部分的z-index默认为10
contentZIndex: {
type: Number,
default: u.gc('contentZIndex', 10)
},
//使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是
autoFullHeight: {
type: Boolean,
default: u.gc('autoFullHeight', true)
},
//是否监听列表触摸方向改变,默认为否
watchTouchDirectionChange: {
type: Boolean,
default: u.gc('watchTouchDirectionChange', false)
},
//是否将错误信息打印至控制台,默认为是
showConsoleError: {
type: Boolean,
default: u.gc('showConsoleError', true)
},
},
created(){
if (this.createdReload && !this.refresherOnly && (this.mountedAutoCallReload && this.auto)) {
this._startLoading();
this._preReload();
}
},
mounted() {
this.wxsPropType = u.getTime().toString();
this.renderJsIgnore;
if (!this.createdReload && !this.refresherOnly && (this.mountedAutoCallReload && this.auto)) {
this.$nextTick(() => {
this._preReload();
})
}
let delay = 0;
// #ifdef H5 || MP
delay = 100;
// #endif
this.$nextTick(() => {
this.systemInfo = uni.getSystemInfoSync();
if (!this.usePageScroll && this.autoHeight) {
this._setAutoHeight();
}
this.loaded = true;
})
this.updatePageScrollTopHeight();
this.updatePageScrollBottomHeight();
this._updateLeftAndRightWidth();
if (this.finalRefresherEnabled && this.useCustomRefresher) {
this.$nextTick(() => {
this.isTouchmoving = true;
})
}
this._onEmit();
// #ifdef APP-NVUE
if (!this.isIos && !this.useChatRecordMode) {
this.nLoadingMoreFixedHeight = true;
}
this._nUpdateRefresherWidth();
// #endif
// #ifndef APP-NVUE
this.finalUseVirtualList && this._virtualListInit();
// #endif
// #ifndef APP-PLUS
this.$nextTick(() => {
setTimeout(() => {
this._getCssSafeAreaInsetBottom();
},delay)
})
// #endif
},
destroyed() {
this._offEmit();
},
// #ifdef VUE3
unmounted() {
this._offEmit();
},
// #endif
watch: {
loadingStatus(newVal, oldVal) {
this.$emit('loadingStatusChange', newVal);
this.$nextTick(()=>{
this.loadingStatusAfterRender = newVal;
})
// #ifdef APP-NVUE
if (this.useChatRecordMode) {
if (this.pageNo === this.defaultPageNo && newVal === Enum.More.NoMore) {
this.nIsFirstPageAndNoMore = true;
return;
}
}
this.nIsFirstPageAndNoMore = false;
// #endif
},
loading(newVal){
if(newVal){
this.loadingForNow = newVal;
}
},
defaultThemeStyle: {
handler(newVal) {
if (newVal.length) {
this.finalRefresherDefaultStyle = newVal;
}
},
immediate: true
},
autoHeight(newVal, oldVal) {
if (this.loaded && !this.usePageScroll) {
this._setAutoHeight(newVal);
}
},
autoHeightAddition(newVal, oldVal) {
if (this.loaded && !this.usePageScroll && this.autoHeight) {
this._setAutoHeight(newVal);
}
},
},
computed: {
zScopedSlots() {
return this.$scopedSlots;
},
finalPagingStyle() {
let pagingStyle = this.pagingStyle;
if (!this.systemInfo) return pagingStyle;
let windowTop = this.systemInfo.windowTop;
//暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题具体bug详见https://ask.dcloud.net.cn/question/141634
//感谢litangyuhttps://github.com/SmileZXLee/uni-z-paging/issues/25
// #ifdef VUE3 && H5
const pageHeadNode = document.getElementsByTagName("uni-page-head");
if (!pageHeadNode.length) windowTop = 0;
// #endif
const windowBottom = this.systemInfo.windowBottom;
if (!this.usePageScroll && this.fixed) {
if (windowTop && !pagingStyle.top) {
pagingStyle.top = windowTop + 'px';
}
if (!pagingStyle.bottom) {
let bottom = windowBottom || 0;
if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder) {
bottom += this.safeAreaBottom;
}
if(bottom > 0){
pagingStyle.bottom = bottom + 'px';
}
}
}
if (this.bgColor.length && !pagingStyle['background']) {
pagingStyle['background'] = this.bgColor;
}
if (this.height.length && !pagingStyle['height']) {
pagingStyle['height'] = this.height;
}
if (this.width.length && !pagingStyle['width']) {
pagingStyle['width'] = this.width;
}
return pagingStyle;
},
finalLowerThreshold() {
return u.convertTextToPx(this.lowerThreshold);
},
finalPagingContentStyle() {
if (this.contentZIndex != 1) {
this.pagingContentStyle['z-index'] = this.contentZIndex;
this.pagingContentStyle['position'] = 'relative';
}
return this.pagingContentStyle;
},
showLoading() {
if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false;
if (this.autoHideLoadingAfterFirstLoaded) {
return this.fromEmptyViewReload ? true : !this.pagingLoaded;
} else{
return this.loadingType === Enum.LoadingType.Refresher;
}
},
safeAreaBottom() {
if (!this.systemInfo) return 0;
let safeAreaBottom = 0;
// #ifdef APP-PLUS
safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0;
// #endif
// #ifndef APP-PLUS
safeAreaBottom = this.cssSafeAreaInsetBottom === -1 ? 0 : this.cssSafeAreaInsetBottom;
// #endif
return safeAreaBottom;
},
renderJsIgnore() {
if ((this.usePageScroll && this.useChatRecordMode) || !this.refresherEnabled || !this.useCustomRefresher) {
this.$nextTick(() => {
this.renderPropScrollTop = 10;
})
}
return 0;
},
windowHeight() {
return !this.systemInfo ? 0 : this.systemInfo.windowHeight || 0;
},
windowTop() {
return !this.systemInfo ? 0 : this.systemInfo.windowTop || 0;
},
windowBottom() {
if (!this.systemInfo) return 0;
let windowBottom = this.systemInfo.windowBottom || 0;
if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder) {
windowBottom += this.safeAreaBottom;
}
return windowBottom;
},
isOldWebView() {
// #ifndef APP-NVUE
try {
const systemInfos = systemInfo.system.split(' ');
const deviceType = systemInfos[0];
const version = parseInt(systemInfos[1].slice(0,1));
if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
return true;
}
} catch(e){
return false;
}
// #endif
return false;
}
},
methods: {
//当前版本号
getVersion() {
return `z-paging v${zConstant.version}`;
},
//设置nvue List的specialEffects
setSpecialEffects(args) {
this.setListSpecialEffects(args);
},
//与setSpecialEffects等效兼容旧版本
setListSpecialEffects(args) {
this.nFixFreezing = args && Object.keys(args).length;
if (this.isIos) {
this.privateRefresherEnabled = 0;
}
if (!this.usePageScroll) {
this.$refs['zp-n-list'].setSpecialEffects(args);
}
},
//处理开始加载更多状态
_startLoading(isReload = false) {
if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) {
this.loadingStatus = Enum.More.Loading;
}
this.loading = true;
},
//检测scrollView是否要铺满屏幕
_doCheckScrollViewShouldFullHeight(totalData){
if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) {
// #ifndef APP-NVUE
this.$nextTick(() => {
this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => {
this._preCheckShowLoadingMoreWhenNoMoreAndInsideOfPaging(totalData, scrollViewNode, pagingContainerNode)
});
})
// #endif
// #ifdef APP-NVUE
this._preCheckShowLoadingMoreWhenNoMoreAndInsideOfPaging(totalData)
// #endif
} else {
this._preCheckShowLoadingMoreWhenNoMoreAndInsideOfPaging(totalData)
}
},
//检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时默认z-paging需要铺满全屏避免数据过少时内部的empty-view无法正确展示)
async _checkScrollViewShouldFullHeight(callback) {
try {
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content');
if (!scrollViewNode || !pagingContainerNode) return;
const scrollViewHeight = pagingContainerNode[0].height;
const scrollViewTop = scrollViewNode[0].top;
if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) {
this._setAutoHeight(true, scrollViewNode);
callback(scrollViewNode, pagingContainerNode);
} else {
this._setAutoHeight(false);
callback(null, null);
}
} catch (e) {
callback(null, null);
}
},
//设置z-paging高度
async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) {
let heightKey = 'height';
// #ifndef APP-NVUE
if (this.usePageScroll) {
heightKey = 'min-height';
}
// #endif
try {
if (shouldFullHeight) {
let finalScrollViewNode = scrollViewNode ? scrollViewNode : await this._getNodeClientRect('.scroll-view');
let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom');
if (finalScrollViewNode) {
const scrollViewTop = finalScrollViewNode[0].top;
let scrollViewHeight = this.windowHeight - scrollViewTop;
if(finalScrollBottomNode){
scrollViewHeight -= finalScrollBottomNode[0].height;
}
let additionHeight = u.convertTextToPx(this.autoHeightAddition);
this.$set(this.scrollViewStyle, heightKey, scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px');
this.$set(this.scrollViewInStyle, heightKey, scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px');
}
} else {
this.$delete(this.scrollViewStyle, heightKey);
this.$delete(this.scrollViewInStyle, heightKey);
}
} catch (e) {}
},
//通过获取css设置的底部安全区域占位view高度设置bottom距离
_getCssSafeAreaInsetBottom(){
this._getNodeClientRect('.zp-safe-area-inset-bottom').then((res) => {
if (res) {
this.cssSafeAreaInsetBottom = res[0].height;
if (this.safeAreaInsetBottom) {
this.updatePageScrollBottomHeight();
}
}
});
},
//触发更新是否超出页面状态
_updateInsideOfPaging() {
if (this.insideMore && this.insideOfPaging === true) {
setTimeout(() => {
this.doLoadMore();
}, 200)
}
},
//获取节点尺寸
_getNodeClientRect(select, inThis = true, scrollOffset = false) {
// #ifdef APP-NVUE
select = select.replace('.', '').replace('#', '');
const ref = this.$refs[select];
return new Promise((resolve, reject) => {
if (ref) {
weexDom.getComponentRect(ref, option => {
if (option && option.result) {
resolve([option.size]);
} else {
resolve(false);
}
})
} else {
resolve(false);
}
});
return;
// #endif
//#ifdef MP-ALIPAY
inThis = false;
//#endif
let res = inThis ? uni.createSelectorQuery().in(this) : uni.createSelectorQuery();
if (scrollOffset) {
res.select(select).scrollOffset();
} else {
res.select(select).boundingClientRect();
}
return new Promise((resolve, reject) => {
res.exec(data => {
resolve((data && data != '' && data != undefined && data.length) ? data : false);
});
});
},
//清除timeout
_cleanTimeout(timeout) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
return timeout;
},
//添加全局emit监听
_onEmit() {
uni.$on(c.i18nUpdateKey, () => {
this.tempLanguageUpdateKey = u.getTime();
})
uni.$on(c.errorUpdateKey, () => {
if (this.loading) {
this.complete(false);
}
})
uni.$on(c.completeUpdateKey, (data) => {
setTimeout(() => {
if (this.loading) {
if (!this.disabledCompleteEmit) {
const type = data.type || 'normal';
const list = data.list || data;
const rule = data.rule;
this.fromCompleteEmit = true;
switch (type){
case 'normal':
this.complete(list);
break;
case 'total':
this.completeByTotal(list, rule);
break;
case 'nomore':
this.completeByNoMore(list, rule);
break;
case 'key':
this.completeByKey(list, rule);
break;
default:
break;
}
} else {
this.disabledCompleteEmit = false;
}
}
}, 1);
})
},
//销毁全局emit和listener监听
_offEmit(){
uni.$off(c.i18nUpdateKey);
uni.$off(c.errorUpdateKey);
uni.$off(c.completeUpdateKey);
}
},
};

View File

@@ -0,0 +1,26 @@
// [z-paging]使用页面滚动时引入此mixin用于监听和处理onPullDownRefresh等页面生命周期方法
const ZPagingMixin = {
onPullDownRefresh() {
if (this.isPagingRefNotFound()) return;
this.$refs.paging.reload();
},
onPageScroll(e) {
if (this.isPagingRefNotFound()) return;
this.$refs.paging.updatePageScrollTop(e.scrollTop);
if (e.scrollTop < 10) {
this.$refs.paging.doChatRecordLoadMore();
}
},
onReachBottom() {
if (this.isPagingRefNotFound()) return;
this.$refs.paging.doLoadMore();
},
methods: {
isPagingRefNotFound() {
return !this.$refs.paging || this.$refs.paging === undefined;
}
}
}
export default ZPagingMixin;

View File

@@ -0,0 +1,23 @@
// [z-paging]公用的静态图片资源
const base64Arrow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAD1BMVEVHcExRUVFMTExRUVFRUVE9CdWsAAAABHRSTlMAjjrY9ZnUjwAAAQFJREFUWMPt2MsNgzAMgGEEE1B1gKJmAIRYoCH7z9RCXrabh33iYktcIv35EEg5ZBh07pvxJU6MFSPOSRnjnBUjUsaciRUjMsb4xIoRCWNiYsUInzE5sWKEyxiYWDbyefqHx1zIeiYTk7mQYziTYecxHvEJjwmIT3hMQELCYSISEg4TkZj0mYTEpM8kJCU9JiMp6TEZyUmbAUhO2gxAQNJiIAKSFgMRmNQZhMCkziAEJTUGIyipMRjBSZkhCE7KDEFIUmTeGCHJxWz0zXaE0GTCG8ZFtEaS347r/1fe11YyHYVfubxayfjoHmc0YYwmmmiiiSaaaKLJ7ckyz5ve+dw3Xw2emdwm9xSbAAAAAElFTkSuQmCC';
const base64ArrowWhite = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVHcEz///////////////////+IGTx/AAAABnRSTlMA/dAkXZOhASU/AAABYElEQVRYw+2YwXLCIBCGsdAHWGbyAKZ4zxi9O017rxLf/1UaWFAgA1m8dcpedNSPf/l/Vh0Ya/Wn6hN0JcGvoCqRM4C8VBFiDwBqqNuJKV0rAnCgy3AUqZE57x0iqTL8Br4U3WBf/YWaIlTKfAcELU/h9w72CSVPa3C3OCDvhpHbRp/s2vq4fHhCeiCl2A3m4Qd71DQR257mFBlMcTlbFnFWzNtHxewYEfSiaLS4el8d8nyhmKJd1CF4eOS0keLMAuSxubLBIeIGQW8YHCFFo7EH9+YDcQt9FMZEswTheaNxTHwHT8SZorJjMrEVwo4Zo0U8HSEyZvJMOg4RjnmmRr8nDYeIz3OMkbfE/QhBo+U9RnZJxjGCRh/WKmHEMWLNkfPKsGh/CWJk1JjG0kcuJggTt34VDP8aWAFhp4nybVb5+9qQhjSkIQ1pSEMa8k+Q5U9rV3dF8MpFBK+/7miVq1/HZ2qmo9D+pAAAAABJRU5ErkJggg==';
const base64Flower = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC';
const base64FlowerWhite = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEX///9HcEz///////////////84chYNAAAABnRSTlP/AGzCOYZj5g1nAAACfklEQVRYw+2YTVPDIBCGtza9Jw25a0bvcax30o73OOr//yvma2F3YWlpPTijXNpAHrK8LLALVPFium2vNIFSbwGKTGQA2GUiHcD29yDNy3sMIdUBQl7r2H8mOEVqAHgPkYZUS6Qc2zYhQqtjyDZEximCZwWZLIBeIgYShs2NzxKpSUehYpMJhURGb+O+w5BpMCAREKPnCDHbIY20SzhM5yxziAXpOiBXydrekT9i5XDEq4NIIHHgyU5mRGqviII4mREJJA4QJzMiILwlRJzpKxJKvCBm8OsBBbLux0tsPl4RKYm5aPu6jw1U4mGxEUR9g8M1PcqBEp/WJliNgYOXueBzS4jZSIcgY5lCtevgDSgyzE+rAfuOTQMq0yzvoGH18qju27Mayzs4fPyMziCx81NJa5RNfW7vPYK9KOfDiVkBxFHG8hAj9txuoBuSWORsFfkpBf7xKFLSeaOefEojh5jz22DJEqMP8fUyaKdQx+RnG+yXMpe8Aars8ueR1pVH/bW3FyyvPRw90upLDHwpgBDtg4aUBNkxRLXMAi03IhcZtr1m+FeI/O/JNyDmmL1djLOauSlNflBpW18RQ2bPqXI22MXXEk75KRHTnkPkYbESbdKP2ZFk0r5sIwffAjy1lx+vx7NLjB6/E7Jfv5ERKhzpN0w8IDE8IGFDv5dhz10s7GFiXRZcUeLCEG5P5nDq9k4PFDcoMpE3GY4OuxuCXhmuyNB6k0RsLIAvqp9NE5r8ZCSS8gxnUp7ODdYhZTqxuiJ9uyJJtPmpqJ7wVj+XVieS903iViHziqAhchLEJAyb7jWU647EpUofQ0ziUuXXXhDddtlllSwjgSQu7r4BRWhQqfDPMVwAAAAASUVORK5CYII='
const base64Success = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVRUVFHcExTU1NRUVFRUVFRUVFOSlSUAAAABnRSTlP/AI6+VySB3ZENAAACcElEQVRYw+2YyYKCMAyGI8hdpdxdZu7gcpdZ7jL6/s8yYheSNi0aPdqbwOffpGmaFOYPD3gj4bisN7vddv17N/JVgxn5x12IWgIaWTuO/IE3PseQbwjGPo2cgRmHFLJwdm/X643zwiqOKPPJ1nj3sjEP2iiifZWj5bhopSyGaEO2HX5fbQJzwJ+W7x/jw5ZFjsEU0PMph9xE8i5EqprKALW95eJQURkgzw98uJ/JvwGecR7bIjWWsUgVrrIfFZ2HlLy3sKETD1mmRLRMRhGVssRa0xJkdn3SpJBymBkM8+pSSDXMDNyDaToVHd2fgpNt0sjwiUZO19+jGQ+gQEg9Oq+bufmAVGihomNmjQG7UG3020vrlm7lkFnKFGU3kZ0KGAdmKe821pipQ+qEKcrZeTL2g5FsUks4cStjEZWwXg0b0n4GxmEpkWwIs5VBynjgK7xZaz1/0D7OxkVuLpsY5BQNFyLS84VBjjbg0iL2r2EQHBOxBhikuUOkdxODVF1cxHoWtPPsiyXO455Iv34hssCO8EV4ZIYTjS8SR4qYSHRiTiYQ4ZFbHi0iIhhBTi6dTCgSWRcnw4h4yGTuyTAiOGBIWGoZTgSHJQl+LcOJ4OCnW6yX2bMnJ9pidCOXtkTkTrIGpYuOynAiOF14SamMiOCk5Ke+mq8BcOrrvym8d0zKIQnWT+M1WwOQNO4fFiWb18hhERxJPx2fblbPHHyC41VyiAtKBUFBIih7JMWVoIQTFIr3lKPN80WvoLSWFPC653ioTZA0I0FrQ7qU6asaK0H7JmkSJa2ooOGVtNUsc3j9FYHkIkJy3SG6VHnfXKXGP9t4N9Q4Ye98AAAAAElFTkSuQmCC';
const base64SuccessWhite = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAGFBMVEVHcEz///////////////////////////8dS1W+AAAAB3RSTlMAiVYk6KvDHLfaegAAAo1JREFUWMPtWEtzmzAQNhCTq910ytXpiyvxTNOr60zrayepx9d02gnX4sTm7xcEiJX2gdnkGJ1A4tOnfWqXyeR1vMRYzrcPD9v5h5MBl3/Ldvx4cxIg/FWC8X0xjLjalM54uhhCfCrRuJURX0pi3EmIqZV7O59vrRZmguStHL9b7S7ftfLwOtiZDw7AHMtmquAQ12b5Wwbnordm8g9zLLO49qc/m2n6aKnhwPOGZ08hAiNHhheiHae1lOUPGZpQkPKa3q0mOUjaRzSRaGUjpy/mmWSwySSpllcEteBKAT52KEnSbblA51pJEPxBQoiH1FP4E3s5+FJv07h6/ylD6ui7B+9fq/ehrFB98ghec9EoVtyjK8pqCHLmCBOwMWSCeWFNN4MbPAk55NhsvoFHSSVR0k5TCTTEzlUGcqV/nVp7n9oIVkmtaqbAEqEgfdgHJPwsEAyZ9r4VAZXFjpEwyaw3+H2v42KYxKhs1XvY/gSSGv+IHyUSuHXCeZhLAgVI3EjgSGo1Fb3xO0tGGU9S2/KAIbtjxpJASG73qox6w5LUq0cEOa+iIONIWIilQSQ0pPa2jgaRQAgQP7c0mITRWGxpMAmEQFN2NAQJNCV0mI6GIIEO47hlQ0ORQLd0nL+hoUjg1m6I1TRr8uYEAriBHLcVFQ5UEMiBe3XkTBEG04WXlGKGxPnMS305XQPA1Ocn2JiuAZwE66fxnKwBnDTuXxZTMq85lwW6kt5ndLqZPefiU1yvmktcUSooChJF2aMprhQlnKJQ5FxRKkcVRa+itNYU8Io2oVkY14w0NMWYlqft91Bj9VHq+ca3b43BxjWJmla0sfKohlfTVpPN+93L/yLQ/IjQ/O5Q/VR5HdL4D7mlxmjwVdELAAAAAElFTkSuQmCC';
const base64Empty = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAbUExURdvc3EdwTMLBwcjIyLSzs/Hx8ff39////19dXXz7IJEAAAAFdFJOU/4A6J9QDyyutAAAB5VJREFUeNrtnM1z4jYYxhUHkR4hdu9eU7Z75Ct7jgPbs9ZZmSuTrUWPmXTA186e+LMrf0uWLMtf2WkHXQgzln88et5XeiVMwPQdGrhCrpAr5Aq5Qv4TkJ07OGQFMLp1B4VYCz+kDblDQhJGeH4eEDLBYdLOHwaDWNBPIeHLYJAJ3meQ83IoCMTHDBKOBoKYGOeM8G0gyD0LObnDQB5ZSCtTNCBfsM9AboaBPLCQcDAIM1zht/dQEkMsd1DjI4hpw2YzMtBJeBbydWpCTJs3YDKGX62YgfGoVwi9KwtZJAzcYHHRm7sYCKD390nQSIoO5JGZIEOYxNoZ4+deISYLyeL5hLHbJ2QK98W0kudMgJe9Qh73odhO+KZHyNYGvgQS9gmJKhUigwSj3iBPUhXxePWmxBqHw0Mej9WQ3qILVjLC177yxNxXQ/7uK+Mn1aNVLsGsBTaWrSAPobYl0aUHt2fIs2Rgz7c9QYL0pSTkSzILLFtAJMH1cidN998T9E0/Sg73/pEEwrgkYRh86wlC949gJsR6EobBcz8hHOVgKYi2m6kZtodIkjEQvF3QjbGpmplB4/lRgJhxgRS2N15iijAvPmByDtCxfQhPJ8J4CR82rgCCBILarScw6X0OcMUyYrFVmbxErl0ZacFIoloOLdJAO42qY+NMDss2kKS8xmiZxcCpFKXWvpRGbQqJp5ixyRfJMmR6x0Fk+z29kmgWDYI5ziFbdug/84HxvduhWhLOJ2StPDQrMJPSjNANklh8QhB7dBO0yTGRwn1fkOk8rbQjiB8Ymww+JuiuN0icmSccK4naLMWYa/euL0+m23GyM8kgAc6sYeL4z04Qa4WjGepcKIliO8EUGSk7d9OGWOsoK31OSdy8TQZ59Y/hWbaV1IVs5/Ed6UzGK4nANAJiyGhRsZPUg2yzLe9hLyiJIyCaDU7udC2uy9pnkKvidlBUEltzFAqxRhBrBZm7HfZnjEQI3boqTsJq15PUDEaKZLgiJYc8OZtCtnM/4G93OFYooXpvdy0guwWWNQkEHl/j7Jw1XRmtlS9HYJkSPjk1IUnyyRqUKQn45NSDlP1mcg9i6En1ZU2IADnEtHF1Q+JwIcS/d5YakPuDUamEShGUHHikAz9oQCaE0CsrpYjDBVkEHQYdyK+EkKPhVErxqh1xbJ/oQf4gEeVsOIEc41WJNAwcd9GBfCZJezXsJhAvH+ImEEIOzlwXgpw5wQ0gH3MIOcsiQAahZuSD69/UQyxcQEggiQARQseVFO/ASAMCgM9gjkHZmhLENzi1AOhA7ullkMWUrfHKfpMiDBHtDIx6yCS6jseEnDUe7zcT6DGCtnrIY3olZw1hrPHkfucIAJa1EDu/lsVEyVmGGA67coKijeogFnMxlEaAV5ghRdDm1kDuuatZTJBGgJdOthIzsvZbDWRRuh6ScgR4EQLgagQvRQIxxQ4sxqcR4GE+c4CkjZQQW9YF89Y4OFAjOCki5KmiDxsBL3PlSJWlAFVogaoIePlYi2ClCJAHRa/cmre5eqTii4uvisqQJxqnip6pNd68DhEvyEs5xIyHBNdh4thCKhU++10kD7Gy1Up1A/o56FKuRJQWSFCuf8dpbisxhqHSKlSSgvG7VTaFKO5TzYD5VMPUxEB2YJNiqq3xYJ0KrroH8mq7xpoXqEZgfgNRUQsDtTVvUOk3sLUKbqrBr7YGvkCkQNC/9SA+vTYtvERrxiKEmcogk4ZqCLUd59MIEiFYHlIoxelCaJWDMmtOPIa80XVLbkb6hzaEwwTcPEmV4AIRlBGNIEmuJBFwLAZoHClJ36J8h+wxihpCqJosAnJrSKwEcQOFAFeWN4RQMYc0Ao4Jhg5gpASzyWcDvjpuDIlTkrGGJEro1rHIjHKR3wJCAj+z5oyi11gJBkXy9QFJIiAu78d+pgSjuWhGN0gUAZAcEncSJf4LRrZ8I94WEmcNCJJqBWYjVbE9bg2JxiyrViBWty6QvO56D8jPVWLA4ZX8dfkxvJJPl8t8aCX+pU/Iz1SCf7lc4OBK0OWfQaKLP0TKjj96VvIp+/BDZjwNKF2ItV2vN7sWStAl87oWkm3dZ+k3lEMoYXe8cT1eq2TOePJDD8KfQdxu6iEPxanUZa4HmZRq3dunGsj3BzFq6yD3wnZNX4n2emI2hXyXQpi6RRZdfSgxHNuxVZBFdyVeBPDmCsiksxKUiDAUEKuzkvRUEs0V08pjVyU2/yqFmF2VZGYop3peitdUiQd1pnrL7qTE01tPzE6eaEKm23dQwh2jNlbiay+/245zl94abw45CzNPyqYQ2++kxHGV1crWzg4A2yvR+BY7wziwnRLN7+O36aA54+ZKGjxZYK3txJpxQyUNn5GwtquII4+ACiWtnvawduu1A3SVtH5uhTvAVSpBG7fDYz6RQ+M6JWjmKm6g+RvTla9UMtspu+s+37VbVCupNqPx43CsNawSb1PbtcmDfQWmUILW7rRXSPHtSq5k5ur0a/hb7DQCUiW3G71ejX/wvV1kSoyNbp8Wvyqn1lCIKvl6gNDkNBYzt0GHdr+Pt9xGl1//ncAVcoVcIVfIFXKFXCFXyP8I8i8SyTW4yTz2lwAAAABJRU5ErkJggg=='
const base64Error = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAJFBMVEVHcEzo6Oibm5ukpKSbm5uampqbm5ubm5u5ubnn5+fm5ub6+vpGpDPdAAAAC3RSTlMA/v4hb+u20dq8aQhnHL4AAATwSURBVHja7ZvNb+JGGMbdjjdVe3NPodzeMhj1ZMUGujkh28B9wKR7iwwBqafWSbPqsbm0uTWtVlrTS9v0Et9yqLQS/1zHNiTZMMB4bO92d+dRUITt8c/PvPPx2h4URUpKSkpKSkpK6m3K6lFN73Q+S/+es3W3fzabTh2Dl0FAXEM+BgrgeXxRsdILTDU9n7J0vz/+EsCYC9KEiSFc06pf5zouqAkjkGWo0OG5Fq6j2IwZ6I4/4DhyTxc20oUjAj5PRTTrwvGAZ9p+ADzlD4RDctCoLhb7JUP87xeLxS3BZUIQ+YNCFi8wRwc4GIqGRK/GkM+5ILV8kFflOmkkkH/LddJ4c05eSSfSyTvrZPEmnCw+UCeuk84QvTSnUF0uCL68fBle/swF+RL1QZ/EpU6gHtOsAM64pnjPwxgTLsjoBM58ODNaAXYBT5QeGdr0KwcEA8He0TkPBLCjUDNHpG4qlg8eTCzXht1FVd1MxTPHj5LTtUiSrKK+7iDf8wBGxk4If3arLv/HF4Tox0A2nlFIGp+CIA+LzSamgbp4TNvZjECtDAjyMcB5HybLO6NxsRA1vmFCNjguNXDGWygbRPXh/B+zn9zPWK5RCkT18QxA57YgAqE+HGS6/tAoD4JO0ts+M2tbyQJpc95a5oI0xXNhCZGQ/x8E0VSCkUZY6Z6CIE/qdO5eL+yPlW6tMMgefPs3o7Bdt8iguJj4DThlZSY/rJ0yB+RraLDK2jAQCHyr4zIhT9mQ7vowygMZjpgQG+CYUV2E1EWqixyzICr8eFFnBB5ba1Y4IIfeKQvysW7ssZrwQLEFmvCBM2U6oRkdI5Wgm1QnO8RUTDlASoiESIiESMh7BDHKhlhu/LbSNUuEqL3lu1p945vO3BArADiaum7vBGDUKQfSIjBykooyrRPQnTIglHFfSagHeqd4iErwa9duEebpckGQjx/VT4v5fC0XhJHAd1mPRvJAVMZiAeQzTpgHYrPKqkGnUEiX+dCoNS4UgniHMTnUS4iESIiE7IS0x+mnVAidglDwVcmQJpy2WQ8VC4UgogfbA1RE4Nuw3UghEBV2rKl7V5ygAJPSY9KGQbP01mVjA5Fa2f1kQN2U3k+M9POWB8gnJUNMZJioWTMzKwOklyxgDrCXVcMMEF90tXM9C2TiCqmfCdIRi/jeewNpyerKok9WkGuzfCdYC+fXRsmBxxpVGG2zY0ZBbieJKvPrDQce3lxppBhIjGFWGkVoxUEoZt0Mukn2XBQH0bTHZpaMIp2sU/6qasU70W6/eHjM09VmYSc6C6Jpvz+orKvVxot8kL3HkMr9IZ9qeZ2o6RrO9mOI9ufdIR9peZ2gNIW31yC/MpyI9ngUDNIsezPks3vIsWDGdYA7cZa9pbqUVeCr/neiaR3U3R4BfXPg75vwb8I/b7HjxChobDZCO+Ny4wuxxaVxPPowcoNnrzPmzGFlX3RJHz2FafbhJ41n8PLx2DCM7KkwQgpqka1DVzKdJNHfJwBe9l/n0eSZFsIPjVSY8xZKZpSXnogwled98wAx3xRcdBNq1f1fhFVdIcL5tvaDolC7XaqaWStEtLOJHkbhlSauMLrma4yHEa03AVUoIUs/M2NQFkchBZiGUPeKonAnqhLOo4hrKf0WTyZ1FcU0Ki0hVrSr+Mucnvya7jYUKSkpKSkpKSmpD0f/AXq+Umj5XnXDAAAAAElFTkSuQmCC';
const base64BackToTop = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAElBMVEVRUVH+/v5HcEyZmZlRUVFRUVGm1ByOAAAABnRSTlPMzADMTZAJBBGsAAAEnElEQVR42t2cS27jMAyGf7/2U+QCQeDsbeQCgZDujaC5/1UmkzaJn+JDFGcw3LdfflKibJkkDnxrL7dbg7sNt6+L4O8OYBM+B0ys+QrGkHZG+OEEQ8g6go8Bx1GIGMdpNOQyIG6XdMgnSPtKhLQDGEZFBgYMkhKFtGBb0EIEjDgFRowoBVaMGAWpMedEfxMiZtwpUsgZCqtlkCNUdpVAWigtCCCDFtLwIWeoreZCWiRYYEKGFEjDg+yRZCUH0iLRAgNyToXUNCRZyMqWhGnUN2IPm3wSlwJ7IUspyCBkIQUZhCykIIeQuRTkEDKXAuM9srrtYbrZN7Y98giZSoFd+t1OxmMITG0dcrSFXFchZ1tIvQZpYWxhBbK3hpQrkMEa0iwh5t4a+QvZvDXyF7J5a+Qv5PPW21/I5623v5DPW29/IaO3Xv5Clrw1y1/Ikrdm+Qs5svw83yNnSJ5BQb4F/F7EIEJSnThGBAXxkFQfLOviQUE8JAUPsosHBfGQfDAtHhREQ1JxIV00KIgmrnRI84S0yAd5BAXxxJUck0f6Qnwr9qmr6xF5xLMjcwn/iudIEAdWnyjkEXlQKZiRVzoqRyLbgeUKKR8Q4alY7cSnoxzSf2ggsqehKr6YVpcXpOd7H93f60cKhOd7Re2LteUF4eLqiVS1mr0ge4io6C2+soaFkJ7MuuuQs1yITEp9hwwKISIpzR2iESKSIoT0rLNwuVHQqoSIpAQJpGce60vIUSdEIuUqgPTsJ5QFZK8UIpBS8iG94GFrDjlrhfCl8CG96Llxmle4kEr6vKWBPIVo9kqDQSRk9/3cWoikcCFPAd33v4dIChPyEvLzBA6RlEYWke4JEUnhKXkLeUEKxRHJFfKCQHGucIW8IdZSRkLeEGMpYyEjiK2UsZARxFTKRMgYYillImQMMZQyFTKB2EmZCplAuFLIHT8TMoWwpQwiIVMIUwqpZP5bp5CCvCTiQKr5f5lCQN+tPCBn2ZvVDFJwIDUP0m1BYAfZYRNSsCB7BqTbhoARePIxtZ9tgwWkoJcwCalmv3MBAemtO4R6dah2HaKQqj8Zvp9sQDjvJ21+SPCBHPJDDk6QITekEV7gqCC19CpKAym9IMfckKv4olMBCeIrWwVEfvkshzQekO9r9P1/ALk+IG1eSPCDiCJfyG+FyU+A6ZCa/piZDinpz7LpkCv5gdkAEshP5emQhv7onw6pGeULyZCSUYiRDAmMkpJkCKs4JhFSq8p8hJBSVbAkhARV6ZUQoisik0FqXTmcDHLVFfbJIEFXoiiCNMpiSxGkVJaNiiBBWQArgTTaUl4JpNQWJUsgQVteXQg+AKkLxQWFGKW+5J2+eVp4S168X3CF1CltCKdTJ8lb84YK2bUBO+wZW0Pqv9nk4tKu49N45NJC5dMM5tLW5tOg59Jq6NM06dL+abFXwr/RkuvTXJwae1abtE/Dt0/ruksTvs84AZ/BCC4jHnyGVfiM3VBQFANEXEah+Ax18RlP4zNox2dkkM/wI58xTn8yDCXGYCDV3W5RGSajtXyGhG1jbpbjzpwGt/0MJft8jqC7iUbQ/QZaxdnKqcIftwAAAABJRU5ErkJggg==';
export default {
base64Arrow,
base64ArrowWhite,
base64Flower,
base64FlowerWhite,
base64Success,
base64SuccessWhite,
base64Empty,
base64Error,
base64BackToTop
}

View File

@@ -0,0 +1,244 @@
// [z-paging]工具类
import zI18n from './z-paging-i18n'
import zConfig from './z-paging-config'
import zLocalConfig from '../config/index'
const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY'
let config = null;
/*
当z-paging未使用uni_modules管理时控制台会有警告WARNING: Module not found: Error: Can't resolve '@/uni_modules/z-paging'...
此时注释下方try中的代码即可
*/
// #ifdef VUE2
try {
const contextKeys = require.context('@/uni_modules/z-paging', false, /\z-paging-config$/).keys();
if (contextKeys.length) {
const suffix = '.js';
config = require('@/uni_modules/z-paging/z-paging-config' + suffix);
}
} catch (e) {}
// #endif
//获取默认配置信息
function gc(key, defaultValue) {
if (!config) {
if (zLocalConfig && Object.keys(zLocalConfig).length) {
config = zLocalConfig;
} else {
const temConfig = zConfig.getConfig();
if (zConfig && temConfig) {
config = temConfig;
}
}
}
if (!config) return defaultValue;
const value = config[_toKebab(key)];
return value === undefined ? defaultValue : value;
}
//判断两个数组是否相等
function arrayIsEqual(arr1, arr2) {
if (arr1 === arr2) return true;
if (arr1.length !== arr2.length) return false;
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
//获取最终的touch位置
function getTouch(e) {
let touch = null;
if (e.touches && e.touches.length) {
touch = e.touches[0];
} else if (e.changedTouches && e.changedTouches.length) {
touch = e.changedTouches[0];
} else if (e.datail && e.datail != {}) {
touch = e.datail;
} else {
return {
touchX: 0,
touchY: 0
}
}
return {
touchX: touch.clientX,
touchY: touch.clientY
};
}
//判断当前手势是否在z-paging内触发
function getTouchFromZPaging(target) {
if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') {
var classList = target.classList;
if (classList && classList.contains('zp-paging-touch-view')) {
return true;
} else {
return getTouchFromZPaging(target.parentNode);
}
} else {
return false;
}
}
//获取z-paging所在的parent
function getParent(parent) {
if (!parent) return null;
if (parent.$refs.paging) return parent;
return getParent(parent.$parent);
}
//打印错误信息
function consoleErr(err) {
console.error(`[z-paging]${err}`);
}
//打印警告信息
function consoleWarn(warn) {
console.warn(`[z-paging]${warn}`);
}
//设置下拉刷新时间
function setRefesrherTime(time, key) {
try {
let datas = getRefesrherTime();
if (!datas) {
datas = {};
}
datas[key] = time;
uni.setStorageSync(storageKey, datas);
} catch (e) {}
}
//获取下拉刷新时间
function getRefesrherTime() {
try {
const datas = uni.getStorageSync(storageKey);
return datas;
} catch (e) {
return null;
}
}
//通过下拉刷新标识key获取下拉刷新时间
function getRefesrherTimeByKey(key) {
const datas = getRefesrherTime();
if (datas) {
const data = datas[key];
if (data) return data;
}
return null;
}
//通过下拉刷新标识key获取下拉刷新时间(格式化之后)
function getRefesrherFormatTimeByKey(key) {
const time = getRefesrherTimeByKey(key);
let timeText = zI18n.t['refresherUpdateTimeNoneText'][zI18n.getLanguage()];
if (time) {
timeText = _timeFormat(time);
}
return `${zI18n.t['refresherUpdateTimeText'][zI18n.getLanguage()]}${timeText}`;
}
//将文本的px或者rpx转为px的值
function convertTextToPx(text) {
const dataType = Object.prototype.toString.call(text);
if (dataType === '[object Number]') {
return text;
}
let isRpx = false;
if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) {
text = text.replace('rpx', '').replace('upx', '');
isRpx = true;
} else if (text.indexOf('px') !== -1) {
text = text.replace('px', '');
}
if (!isNaN(text)) {
if (isRpx) return Number(uni.upx2px(text));
return Number(text);
}
return 0;
}
//获取当前时间
function getTime() {
return (new Date()).getTime();
}
//获取z-paging实例id
function getInstanceId() {
let s = [];
const hexDigits = "0123456789abcdef";
for (let i = 0; i < 10; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
return s.join('') + getTime();
}
//------------------ 私有方法 ------------------------
//时间格式化
function _timeFormat(time) {
const date = new Date(time);
const currentDate = new Date();
const dateDay = new Date(time).setHours(0, 0, 0, 0);
const currentDateDay = new Date().setHours(0, 0, 0, 0);
const disTime = dateDay - currentDateDay;
let dayStr = '';
const timeStr = _dateTimeFormat(date);
if (disTime === 0) {
dayStr = zI18n.t['refresherUpdateTimeTodayText'][zI18n.getLanguage()];
} else if (disTime === -86400000) {
dayStr = zI18n.t['refresherUpdateTimeYesterdayText'][zI18n.getLanguage()];
} else {
dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear());
}
return `${dayStr} ${timeStr}`;
}
//date格式化为年月日
function _dateDayFormat(date, showYear = true) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
if (showYear) {
return `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`;
} else {
return `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`;
}
}
//data格式化为时分
function _dateTimeFormat(date) {
const hour = date.getHours();
const minute = date.getMinutes();
return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`;
}
//不满2位在前面填充0
function _fullZeroToTwo(str) {
str = str.toString();
if (str.length === 1) return '0' + str;
return str;
}
//驼峰转短横线
function _toKebab(value) {
return value.replace(/([A-Z])/g, "-$1").toLowerCase();
}
export default {
gc,
setRefesrherTime,
getRefesrherFormatTimeByKey,
arrayIsEqual,
getTouch,
getTouchFromZPaging,
getParent,
convertTextToPx,
getTime,
getInstanceId,
consoleErr,
consoleWarn
};

View File

@@ -0,0 +1,63 @@
// [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理
import u from '../js/z-paging-utils'
var data = {
renderScrollTop: 0,
renderUsePageScroll: false,
startY: 0,
isTouchFromZPaging: false
}
var currentVm = null;
export default {
mounted() {
this._handleTouch();
// #ifdef APP-VUE
this.$ownerInstance && this.$ownerInstance.callMethod('_checkVirtualListScroll');
// #endif
},
methods: {
//接收逻辑层发送的数据
renderPropScrollTopChange(newVal, oldVal, ownerVm, vm) {
currentVm = ownerVm;
data.renderScrollTop = newVal;
},
renderPropUsePageScrollChange(newVal, oldVal, ownerVm, vm) {
if(newVal !== -1){
data.renderUsePageScroll = newVal;
}
},
//拦截处理touch事件
_handleTouch() {
if (window && !window.$zPagingRenderJsInited) {
window.$zPagingRenderJsInited = true;
window.addEventListener('touchstart', this._handleTouchstart, {
passive: true
})
window.addEventListener('touchmove', this._handleTouchmove, {
passive: false
})
}
},
_handleTouchstart(e) {
const touch = u.getTouch(e);
data.startY = touch.touchY;
data.isTouchFromZPaging = u.getTouchFromZPaging(e.target);
},
_handleTouchmove(e) {
const touch = u.getTouch(e);
var moveY = touch.touchY - data.startY;
//v2.1.4起删除条件:(data.isTouchFromZPaging && data.renderIsIos && !data.renderUsePageScroll && moveY < 0)
if (data.isTouchFromZPaging && data.renderScrollTop < 1 && moveY > 0) {
if (e.cancelable && !e.defaultPrevented) {
e.preventDefault();
}
}
},
_removeAllEventListener(){
window.removeEventListener('touchstart');
window.removeEventListener('touchmove');
}
}
};

View File

@@ -0,0 +1,353 @@
// [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新降低逻辑层与视图层的通信折损提升性能
var currentDis = 0;
var isPCFlag = -1;
var startY = -1;
function propObserver(newValue, oldValue, ownerIns, ins) {
var state = ownerIns.getState() || {};
state.currentIns = ins;
var dataset = ins.getDataset();
var loading = dataset.loading == true;
if (newValue && newValue.indexOf('end') != -1) {
var transition = newValue.split('end')[0];
_setTransform('translateY(0px)', ins, false, transition);
state.moveDis = 0;
state.oldMoveDis = 0;
currentDis = 0;
} else if (newValue && newValue.indexOf('begin') != -1) {
var refresherThreshold = ins.getDataset().refresherthreshold;
_setTransformValue(refresherThreshold, ins, state, false);
}
}
function touchstart(e, ownerIns) {
var ins = _getIns(ownerIns);
var state = {};
var dataset = {};
if (ins) {
state = ins.getState();
dataset = ins.getDataset();
if (_touchDisabled(e, ins, 0)) return;
}
var isTouchEnded = state.isTouchEnded;
state.oldMoveDis = 0;
var touch = _getTouch(e);
var loading = _getIsTrue(dataset.loading);
state.startY = touch.touchY;
startY = state.startY;
state.lastTouch = touch;
if (!loading && isTouchEnded) {
state.isTouchmoving = false;
}
state.isTouchEnded = false;
ownerIns.callMethod('_handleRefresherTouchstart', touch);
}
function touchmove(e, ownerIns) {
var touch = _getTouch(e);
var ins = _getIns(ownerIns);
var dataset = ins.getDataset();
var refresherThreshold = dataset.refresherthreshold;
var isIos = _getIsTrue(dataset.isios);
var state = ins.getState();
var dataset = ins.getDataset();
var watchTouchDirectionChange = _getIsTrue(dataset.watchtouchdirectionchange);
var moveDisObj = {};
var moveDis = 0;
var prevent = false;
if (watchTouchDirectionChange) {
moveDisObj = _getMoveDis(e, ins);
moveDis = moveDisObj.currentDis;
prevent = moveDisObj.isDown;
if(state.oldAcceptedIsDown == prevent){
ownerIns.callMethod('_handleTouchDirectionChange', {direction: prevent ? 'top' : 'bottom'});
state.oldIsDown = prevent;
}
state.oldAcceptedIsDown = prevent;
}
if (_touchDisabled(e, ins, 1)) {
_handlePullingDown(state, ownerIns, false);
return true;
}
if (!_getAngleIsInRange(e, touch, state, dataset)) {
_handlePullingDown(state, ownerIns, false);
return true;
}
moveDisObj = _getMoveDis(e, ins);
moveDis = moveDisObj.currentDis;
prevent = moveDisObj.isDown;
if (moveDis < 0) {
_setTransformValue(0, ins, state, false);
_handlePullingDown(state, ownerIns, false);
return true;
}
if (prevent && !state.disabledBounce) {
if (isIos) {
ownerIns.callMethod('_handleScrollViewDisableBounce', {bounce: false});
}
state.disabledBounce = true;
_handlePullingDown(state, ownerIns, prevent);
return !prevent;
}
_setTransformValue(moveDis, ins, state, false);
var oldRefresherStatus = state.refresherStatus;
var oldIsTouchmoving = _getIsTrue(dataset.oldistouchmoving);
var hasTouchmove = _getIsTrue(dataset.hastouchmove);
var isTouchmoving = state.isTouchmoving;
state.refresherStatus = moveDis >= refresherThreshold ? 1 : 0;
if (!isTouchmoving) {
state.isTouchmoving = true;
isTouchmoving = true;
}
if (state.isTouchEnded) {
state.isTouchEnded = false;
}
if (hasTouchmove) {
ownerIns.callMethod('_handleWxsPullingDown', {moveDis:moveDis, diffDis:moveDisObj.diffDis});
}
if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) {
ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch);
}
_handlePullingDown(state, ownerIns, prevent);
return !prevent;
}
function touchend(e, ownerIns) {
var touch = _getTouch(e);
var ins = _getIns(ownerIns);
var dataset = ins.getDataset();
var state = ins.getState();
if (_touchDisabled(e, ins, 2)) return;
state.reachMaxAngle = true;
state.hitReachMaxAngleCount = 0;
state.disabledBounce = false;
state.fixedIsTopHitCount = 0;
//ownerIns.callMethod('_handleScrollViewDisableBounce', {bounce:true});
if (!state.isTouchmoving) return;
var oldRefresherStatus = state.refresherStatus;
var oldMoveDis = state.moveDis;
var refresherThreshold = ins.getDataset().refresherthreshold
var moveDis = _getMoveDis(e, ins).currentDis;
if (!(moveDis >= refresherThreshold && oldRefresherStatus === 1)) {
state.isTouchmoving = false;
}
ownerIns.callMethod('_handleRefresherTouchend', moveDis);
state.isTouchEnded = true;
if (oldMoveDis < refresherThreshold) return;
var animate = false;
if (moveDis >= refresherThreshold) {
moveDis = refresherThreshold;
animate = true;
}
_setTransformValue(moveDis, ins, state, animate);
}
// #ifdef H5
function isPC() {
if (isPCFlag != -1) return isPCFlag;
var userAgentInfo = navigator.userAgent;
var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
var flag = true;
for (var i = 0; i < Agents.length; i++) {
if (userAgentInfo.indexOf(Agents[i]) > 0) {
flag = false;
break;
}
}
isPCFlag = flag;
return isPCFlag;
}
var movable = false;
function mousedown(e, ins) {
if (!isPC()) return;
touchstart(e, ins);
movable = true;
}
function mousemove(e, ins) {
if (!isPC()) return;
if (!movable) return;
touchmove(e, ins);
}
function mouseup(e, ins) {
if (!isPC()) return;
touchend(e, ins);
movable = false;
}
function mouseleave(e, ins) {
if (!isPC()) return;
movable = false;
}
// #endif
function _setTransformValue(value, ins, state, animate) {
value = value || 0;
if (state.moveDis == value) return;
state.moveDis = value;
_setTransform('translateY(' + value + 'px)', ins, animate, '');
}
function _setTransform(transform, ins, animate, transition) {
if (transform == 'translateY(0px)') {
transform = 'none';
}
ins.requestAnimationFrame(function() {
var stl = { 'transform': transform };
if (animate) {
stl['transition'] = 'transform .1s linear';
}
if (transition.length) {
stl['transition'] = transition;
}
ins.setStyle(stl);
})
}
function _getMoveDis(e, ins) {
var state = ins.getState();
var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold);
var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate);
var touch = _getTouch(e);
var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY;
var moveDis = touch.touchY - currentStartY;
var oldMoveDis = state.oldMoveDis || 0;
state.oldMoveDis = moveDis;
var diffDis = moveDis - oldMoveDis;
if (diffDis > 0) {
diffDis = diffDis * 0.85;
if (currentDis > refresherThreshold) {
diffDis = diffDis * (1 - refresherOutRate);
}
}
if (diffDis > 200) {
currentDis = 0;
diffDis = 0;
} else {
currentDis += diffDis;
currentDis = Math.max(0, currentDis);
}
return {
currentDis: currentDis,
diffDis: diffDis,
isDown: diffDis > 0
};
}
function _getTouch(e) {
var touch = e;
if (e.touches && e.touches.length) {
touch = e.touches[0];
} else if (e.changedTouches && e.changedTouches.length) {
touch = e.changedTouches[0];
} else if (e.datail && e.datail != {}) {
touch = e.datail;
}
return {
touchX: touch.clientX,
touchY: touch.clientY
};
}
function _getIns(ownerIns) {
var ins = ownerIns.getState().currentIns;
if (!ins) {
ownerIns.callMethod('_handlePropUpdate');
}
return ins;
}
function _touchDisabled(e, ins, processTag) {
var dataset = ins.getDataset();
var state = ins.getState();
var loading = _getIsTrue(dataset.loading);
var useChatRecordMode = _getIsTrue(dataset.usechatrecordmode);
var refresherEnabled = _getIsTrue(dataset.refresherenabled);
var useCustomRefresher = _getIsTrue(dataset.usecustomrefresher);
var usePageScroll = _getIsTrue(dataset.usepagescroll);
var pageScrollTop = parseFloat(dataset.pagescrolltop);
var scrollTop = parseFloat(dataset.scrolltop);
var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop;
var fixedIsTop = false;
var isIos = _getIsTrue(dataset.isios);
if (!isIos && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) {
fixedIsTop = true;
}
var fixedIsTopHitCount = state.fixedIsTopHitCount || 0;
if (fixedIsTop) {
fixedIsTopHitCount++;
if (fixedIsTopHitCount <= 3) {
fixedIsTop = false;
}
state.fixedIsTopHitCount = fixedIsTopHitCount;
} else {
state.fixedIsTopHitCount = 0;
}
if (!isIos && processTag === 0) {
state.startScrollTop = finalScrollTop || 0;
}
if (!isIos && processTag === 2) {
fixedIsTop = true;
}
var res = loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher || ((usePageScroll && useCustomRefresher && pageScrollTop > 5) && !fixedIsTop) ||
((!usePageScroll && useCustomRefresher && scrollTop > 5) && !fixedIsTop);
return res;
}
function _getAngleIsInRange(e, touch, state, dataset) {
var maxAngle = dataset.refreshermaxangle;
var refresherAecc = _getIsTrue(dataset.refresheraecc);
var lastTouch = state.lastTouch;
var reachMaxAngle = state.reachMaxAngle;
var moveDis = state.oldMoveDis;
if (!lastTouch) return true;
if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) {
if ((!moveDis || moveDis < 1) && !refresherAecc && reachMaxAngle != null && !reachMaxAngle) return false;
var x = Math.abs(touch.touchX - lastTouch.touchX);
var y = Math.abs(touch.touchY - lastTouch.touchY);
var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
if ((x || y) && x > 1) {
var angle = Math.asin(y / z) / Math.PI * 180;
if (angle < maxAngle) {
var hitReachMaxAngleCount = state.hitReachMaxAngleCount || 0;
state.hitReachMaxAngleCount = ++hitReachMaxAngleCount;
if (state.hitReachMaxAngleCount > 2) {
state.lastTouch = touch;
state.reachMaxAngle = false;
}
return false;
}
}
}
state.lastTouch = touch;
return true;
}
function _handlePullingDown(state, ins, onPullingDown) {
var oldOnPullingDown = state.onPullingDown || false;
if (oldOnPullingDown != onPullingDown) {
ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown);
}
state.onPullingDown = onPullingDown;
}
function _getIsTrue(value) {
value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false;
return value == true || value == 'true';
}
module.exports = {
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend,
mousedown: mousedown,
mousemove: mousemove,
mouseup: mouseup,
mouseleave: mouseleave,
propObserver: propObserver
}

View File

@@ -0,0 +1,359 @@
<!-- _
____ _ __ __ _ __ _(_)_ __ __ _
|_ /____| '_ \ / _` |/ _` | | '_ \ / _` |
/ /_____| |_) | (_| | (_| | | | | | (_| |
/___| | .__/ \__,_|\__, |_|_| |_|\__, |
|_| |___/ |___/
v2.3.1 (2022-05-29)
by ZXLee
-->
<!-- API文档地址https://z-paging.zxlee.cn -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<template name="z-paging">
<!-- #ifndef APP-NVUE -->
<view :class="!usePageScroll&&fixed?'z-paging-content z-paging-content-fixed':'z-paging-content'" :style="[finalPagingStyle]">
<!-- #ifndef APP-PLUS -->
<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
<!-- #endif -->
<!-- 顶部固定的slot -->
<slot v-if="!usePageScroll&&$slots.top" name="top" />
<view class="zp-page-top" v-else-if="usePageScroll&&$slots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]">
<slot name="top" />
</view>
<view :class="{'zp-view-super':true,'zp-scroll-view-super':!usePageScroll}" :style="[finalScrollViewStyle]">
<view v-if="$slots.left" :class="{'zp-page-left':true,'zp-absoulte':finalIsOldWebView}">
<slot name="left" />
</view>
<view :class="{'zp-scroll-view-container':true,'zp-absoulte':finalIsOldWebView}" :style="[scrollViewContainerStyle]">
<scroll-view
ref="zp-scroll-view" :class="{'zp-scroll-view':true,'zp-scroll-view-absolute':!usePageScroll,'zp-scroll-view-hide-scrollbar':!showScrollbar}"
:scroll-top="scrollTop" :scroll-x="scrollX"
:scroll-y="scrollable&&!usePageScroll&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==3)" :enable-back-to-top="finalEnableBackToTop"
:show-scrollbar="showScrollbar" :scroll-with-animation="finalScrollWithAnimation"
:scroll-into-view="scrollIntoView" :lower-threshold="finalLowerThreshold" :upper-threshold="5"
:refresher-enabled="finalRefresherEnabled&&!useCustomRefresher" :refresher-threshold="finalRefresherThreshold"
:refresher-default-style="finalRefresherDefaultStyle" :refresher-background="refresherBackground"
:refresher-triggered="finalRefresherTriggered" @scroll="_scroll" @scrolltolower="_onLoadingMore('toBottom')"
@scrolltoupper="_scrollToUpper" @refresherrestore="_onRestore" @refresherrefresh="_onRefresh(true)">
<view class="zp-paging-touch-view"
<!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
@touchstart="_refresherTouchstart" @touchmove="_refresherTouchmove" @touchend="_refresherTouchend" @touchcancel="_refresherTouchend"
<!-- #endif -->
<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
@touchstart="pagingWxs.touchstart" @touchmove="pagingWxs.touchmove" @touchend="pagingWxs.touchend" @touchcancel="pagingWxs.touchend"
@mousedown="pagingWxs.mousedown" @mousemove="pagingWxs.mousemove" @mouseup="pagingWxs.mouseup" @mouseleave="pagingWxs.mouseleave"
<!-- #endif -->
>
<view v-if="finalRefresherFixedBacHeight>0" class="zp-fixed-bac-view" :style="[{'background': refresherFixedBackground,'height': `${finalRefresherFixedBacHeight}px`}]"></view>
<view class="zp-paging-main" :style="[scrollViewInStyle,{'transform': finalRefresherTransform,'transition': refresherTransition}]"
<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
:change:prop="pagingWxs.propObserver" :prop="wxsPropType"
:data-refresherThreshold="finalRefresherThreshold" :data-isIos="isIos"
:data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode"
:data-refresherEnabled="refresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop"
:data-scrollTop="wxsScrollTop" :data-refresherMaxAngle="refresherMaxAngle"
:data-refresherAecc="refresherAngleEnableChangeContinued" :data-usePageScroll="usePageScroll" :data-watchTouchDirectionChange="watchTouchDirectionChange"
:data-oldIsTouchmoving="isTouchmoving" :data-refresherOutRate="finalRefresherOutRate" :data-hasTouchmove="hasTouchmove"
<!-- #endif -->
<!-- #ifdef APP-VUE || H5 -->
:change:renderPropScrollTop="pagingRenderjs.renderPropScrollTopChange" :renderPropScrollTop="renderPropScrollTop"
:change:renderPropUsePageScroll="pagingRenderjs.renderPropUsePageScrollChange" :renderPropUsePageScroll="renderPropUsePageScroll"
<!-- #endif -->
>
<view v-if="showRefresher" class="zp-custom-refresher-view" :style="[{'margin-top': `-${finalRefresherThreshold}px`,'background': refresherBackground}]">
<view class="zp-custom-refresher-container" :style="[{'height': `${finalRefresherThreshold}px`,'background': refresherBackground}]">
<!-- 下拉刷新view -->
<view class="zp-custom-refresher-slot-view">
<slot v-if="!($slots.refresherComplete&&refresherStatus===3)" :refresherStatus="refresherStatus" name="refresher" />
</view>
<slot v-if="$slots.refresherComplete&&refresherStatus===3" name="refresherComplete" />
<z-paging-refresh ref="refresh" v-else-if="!showCustomRefresher" :style="[{'height': `${finalRefresherThreshold}px`}]" :status="refresherStatus"
:defaultThemeStyle="finalRefresherThemeStyle" :defaultText="finalRefresherDefaultText"
:pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText"
:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey"
:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" />
</view>
</view>
<view class="zp-paging-container">
<slot v-if="useChatRecordMode&&$slots.chatLoading&&loadingStatus!==2&&realTotalData.length" name="chatLoading" />
<view v-else-if="useChatRecordMode&&loadingStatus!==2&&realTotalData.length" class="zp-chat-record-loading-container">
<text v-if="loadingStatus!==1" @click="_scrollToUpper()"
:class="defaultThemeStyle==='white'?'zp-loading-more-text zp-loading-more-text-white':'zp-loading-more-text zp-loading-more-text-black'">{{chatRecordLoadingMoreText}}</text>
<image v-else :src="base64Flower" class="zp-chat-record-loading-custom-image" />
</view>
<!-- 全屏Loading -->
<slot v-if="$slots.loading&&showLoading&&!loadingFullFixed" name="loading" />
<!-- 主体内容 -->
<view class="zp-paging-container-content" :style="[{transform:virtualPlaceholderTopHeight>0?`translateY(${virtualPlaceholderTopHeight}px)`:'none'},finalPagingContentStyle]">
<slot />
<!-- 内置列表&虚拟列表 -->
<template v-if="finalUseInnerList">
<slot name="header"/>
<view class="zp-list-container" :style="[innerListStyle]">
<template v-if="finalUseVirtualList">
<view class="zp-list-cell" :style="[innerCellStyle]" :id="`zp-${item['zp_index']}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']">
<slot name="cell" :item="item" :index="virtualTopRangeIndex+index"/>
</view>
</template>
<template v-else>
<view class="zp-list-cell" v-for="(item,index) in realTotalData" :key="index">
<slot name="cell" :item="item" :index="index"/>
</view>
</template>
</view>
<slot name="footer"/>
</template>
<view v-if="useVirtualList" class="zp-virtual-placeholder" :style="[{height:virtualPlaceholderBottomHeight+'px'}]"/>
<!-- 上拉加载更多view -->
<!-- #ifndef MP-ALIPAY -->
<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
<slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" />
<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zPagingLoadMoreConfig" />
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<slot v-if="loadingStatus===0&&$slots.loadingMoreDefault&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreDefault" />
<slot v-else-if="loadingStatus===1&&$slots.loadingMoreLoading&&showLoadingMore&&loadingMoreEnabled" name="loadingMoreLoading" />
<slot v-else-if="loadingStatus===2&&$slots.loadingMoreNoMore&&showLoadingMore&&showLoadingMoreNoMoreView&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreNoMore" />
<slot v-else-if="loadingStatus===3&&$slots.loadingMoreFail&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreFail" />
<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMore&&showDefaultLoadingMoreText&&!(loadingStatus===2&&!showLoadingMoreNoMoreView)&&loadingMoreEnabled&&!useChatRecordMode" :zConfig="zPagingLoadMoreConfig" />
<!-- #endif -->
<view v-if="safeAreaInsetBottom && useSafeAreaPlaceholder" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
</view>
<!-- 空数据图 -->
<view :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}" :style="[{emptyViewSuperStyle}]" v-if="showEmpty">
<slot v-if="$slots.empty" name="empty" />
<z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload"
:emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle"
:emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed"
@reload="_emptyViewReload" />
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view v-if="$slots.right" :class="{'zp-page-right':true,'zp-absoulte zp-right':finalIsOldWebView}">
<slot name="right" />
</view>
</view>
<!-- 底部固定的slot -->
<slot v-if="!usePageScroll&&$slots.bottom" name="bottom" />
<view class="zp-page-bottom" v-else-if="usePageScroll&&$slots.bottom" :style="[{'bottom': `${windowBottom}px`}]">
<slot name="bottom" />
</view>
<!-- 点击返回顶部view -->
<view v-if="showBackToTopClass" :class="backToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
<slot v-if="$slots.backToTop" name="backToTop" />
<image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
</view>
<!-- 全屏Loading(铺满z-paging并固定) -->
<view v-if="$slots.loading&&showLoading&&loadingFullFixed" class="zp-loading-fixed">
<slot name="loading" />
</view>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false">
<!-- 顶部固定的slot -->
<view ref="zp-page-top" :is="nViewIs" class="zp-page-top" v-if="$slots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]">
<slot name="top" />
</view>
<view :is="finalNvueSuperListIs" class="zp-n-list-container" :scrollable="false">
<view v-if="$slots.left" class="zp-page-left" :style="[{'marginTop':scrollViewStyle.marginTop,'marginBottom':scrollViewStyle.marginBottom}]">
<slot name="left" />
</view>
<view ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},scrollViewStyle,useChatRecordMode ? {transform: nIsFirstPageAndNoMore?'rotate(0deg)':'rotate(180deg)'}:{}]" :is="finalNvueListIs" alwaysScrollableVertical="true"
:fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar&&!useChatRecordMode" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop"
:scrollable="scrollable&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==3)" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth"
:column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled"
@loadmore="_nOnLoadmore" @scroll="_nOnScroll">
<refresh class="zp-n-refresh" :style="[nvueRefresherStyle]" v-if="finalNvueRefresherEnabled" :display="nRefresherLoading?'show':'hide'" @refresh="_nOnRrefresh" @pullingdown="_nOnPullingdown">
<view ref="zp-n-refresh-container" class="zp-n-refresh-container" :style="[{background:refresherBackground,width:nRefresherWidth}]" id="zp-n-refresh-container">
<!-- 下拉刷新view -->
<slot v-if="zScopedSlots.refresherComplete&&refresherStatus===3" name="refresherComplete" />
<slot v-else-if="zScopedSlots.refresher" :refresherStatus="refresherStatus" name="refresher" />
<z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle"
:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText"
:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey"
:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" />
</view>
</refresh>
<view ref="zp-n-list-top-tag" class="zp-n-list-top-tag" style="margin-top: -1rpx;" :style="[{height:finalNvueRefresherEnabled?'0px':'1px'}]" :is="nViewIs"></view>
<view v-if="nShowRefresherReveal" ref="zp-n-list-refresher-reveal" :style="[{transform:`translateY(-${nShowRefresherRevealHeight}px)`,height:'0px'},{background:refresherBackground}]" :is="nViewIs">
<!-- 下拉刷新view -->
<slot v-if="zScopedSlots.refresherComplete&&refresherStatus===3" name="refresherComplete" />
<slot v-else-if="zScopedSlots.refresher" :refresherStatus="refresherStatus" name="refresher" />
<z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle"
:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText"
:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey"
:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" />
</view>
<template v-if="finalUseInnerList">
<view :is="nViewIs">
<slot name="header"/>
</view>
<view class="zp-list-cell" :is="nViewIs" v-for="(item,index) in realTotalData" :key="finalCellKeyName.length?item[finalCellKeyName]:index">
<slot name="cell" :item="item" :index="index"/>
</view>
<view :is="nViewIs">
<slot name="footer"/>
</view>
</template>
<template v-else>
<slot />
</template>
<!-- 全屏Loading -->
<view :class="{'z-paging-content-fixed':usePageScroll}" style="flex: 1;" :style="[useChatRecordMode ? {transform: nIsFirstPageAndNoMore?'rotate(0deg)':'rotate(180deg)'}:{}]" v-if="$slots.loading&&showLoading&&!loadingFullFixed" :is="nViewIs">
<slot name="loading" />
</view>
<!-- 空数据图 -->
<view class="z-paging-empty-view" :class="{'z-paging-content-fixed':usePageScroll}" style="flex: 1;" :style="[emptyViewSuperStyle,useChatRecordMode ? {transform: nIsFirstPageAndNoMore?'rotate(0deg)':'rotate(180deg)'}:{}]" v-if="showEmpty" :is="nViewIs">
<view :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}">
<slot v-if="$slots.empty" name="empty" />
<z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload"
:emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle"
:emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed"
@reload="_emptyViewReload" />
</view>
</view>
<view v-if="!hideNvueBottomTag" ref="zp-n-list-bottom-tag" class="zp-n-list-bottom-tag" is="header"></view>
<!-- 上拉加载更多view -->
<view :is="nViewIs" v-if="!refresherOnly&&loadingMoreEnabled">
<view v-if="useChatRecordMode">
<view v-if="loadingStatus!==2&&realTotalData.length">
<slot v-if="$slots.chatLoading" name="chatLoading" />
<view v-else class="zp-chat-record-loading-container">
<text v-if="loadingStatus!==1" @click="_scrollToUpper()"
:class="defaultThemeStyle==='white'?'zp-loading-more-text zp-loading-more-text-white':'zp-loading-more-text zp-loading-more-text-black'">{{chatRecordLoadingMoreText}}</text>
<view>
<loading-indicator v-if="loadingStatus===1" class="zp-line-loading-image" animating />
</view>
</view>
</view>
</view>
<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:'80rpx'}:{}">
<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
<slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" />
<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zPagingLoadMoreConfig" />
<view v-if="safeAreaInsetBottom && useSafeAreaPlaceholder" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
</view>
</view>
</view>
<view v-if="$slots.right" class="zp-page-right">
<slot name="right" />
</view>
</view>
<!-- 底部固定的slot -->
<slot name="bottom" />
<!-- 点击返回顶部view -->
<view v-if="showBackToTopClass" :class="backToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
<slot v-if="$slots.backToTop" name="backToTop" />
<image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
</view>
<!-- 全屏Loading(铺满z-paging并固定) -->
<view v-if="$slots.loading&&showLoading&&loadingFullFixed" class="zp-loading-fixed">
<slot name="loading" />
</view>
</view>
<!-- #endif -->
</template>
<!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 -->
<script src="./wxs/z-paging-wxs.wxs" module="pagingWxs" lang="wxs"></script>
<!-- #endif -->
<script module="pagingRenderjs" lang="renderjs">
import pagingRenderjs from './wxs/z-paging-renderjs.js';
/**
* z-paging 分页组件
* @description 高性能全平台兼容。支持虚拟列表支持nvue、vue3
* @tutorial https://z-paging.zxlee.cn
* @notice 以下仅为部分常用属性、方法&事件完整文档请查阅z-paging官网
* @property {Number|String} default-page-no 自定义初始的pageNo默认为1
* @property {Number|String} default-page-size 自定义pageSize默认为10
* @property {Number|String} delay 调用complete后延迟处理的时间单位为毫秒
* @property {String} language i18n国际化设置语言支持简体中文(zh-cn)、繁体中文(zh-hant-cn)和英文(en)
* @property {Object} paging-style 设置z-paging的style部分平台(如微信小程序)无法直接修改组件的style可使用此属性代替
* @property {String} height z-paging的高度优先级低于pagingStyle中设置的height传字符串如100px、100rpx、100%
* @property {String} width z-paging的宽度优先级低于pagingStyle中设置的width传字符串如100px、100rpx、100%
* @property {String} bg-color z-paging的背景色优先级低于pagingStyle中设置的background。传字符串如"#ffffff"
* @property {String} default-theme-style loading(下拉刷新、上拉加载更多)的主题样式支持blackwhite默认black
* @property {String} refresher-theme-style 下拉刷新的主题样式支持blackwhite默认black
* @property {Boolean} refresher-only 是否只使用下拉刷新设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多强制隐藏空数据图。默认为否
* @property {Boolean} use-page-scroll 使用页面滚动默认为否当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高但配置会略微繁琐
* @property {Boolean} use-virtual-list 是否使用虚拟列表,默认为否
* @property {Boolean} fixed z-paging是否使用fixed布局若使用fixed布局则z-paging的父view无需固定高度z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效)
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区域适配,默认为否
* @property {Boolean} auto [z-paging]mounted后是否自动调用reload方法(mounted后自动调用接口),默认为是
* @property {Boolean} auto-scroll-to-top-when-reload reload时是否自动滚动到顶部默认为是
* @property {Boolean} auto-clean-list-when-reload reload时是否立即自动清空原list默认为是若立即自动清空则在reload之后、请求回调之前页面是空白的
* @property {Boolean} show-refresher-when-reload 列表刷新时是否自动显示下拉刷新view默认为否
* @property {String|Object} refresher-default-text 自定义下拉刷新默认状态下的文字
* @property {String|Object} refresher-pulling-text 自定义下拉刷新松手立即刷新状态下的文字
* @property {String|Object} refresher-refreshing-text 自定义下拉刷新刷新中状态下的文字
* @property {String|Object} refresher-complete-text 自定义下拉刷新刷新结束状态下的文字
* @property {Boolean} refresher-end-bounce-enabled 是否开启自定义下拉刷新刷新结束回弹效果,默认为是
* @property {Object} loading-more-custom-style 自定义底部加载更多样式
* @property {Boolean} loading-more-enabled 是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是
* @property {String|Object} loading-more-default-text 滑动到底部"默认"文字,默认为【点击加载更多】
* @property {String|Object} loading-more-loading-text 滑动到底部"加载中"文字,默认为【正在加载...】
* @property {String|Object} loading-more-no-more-text 滑动到底部"没有更多"文字,默认为【没有更多了】
* @property {String|Object} loading-more-fail-text 滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】
* @property {Boolean} inside-more 当分页未满一屏时,是否自动加载更多(nvue无效),默认为否
* @property {Boolean} hide-empty-view 是否强制隐藏空数据图,默认为否
* @property {String|Object} empty-view-text 空数据图描述文字,默认为“没有数据哦~”
* @property {Boolean} show-empty-view-reload 是否显示空数据图重新加载按钮(无数据时),默认为否
* @property {Boolean} show-empty-view-reload-when-error 加载失败时是否显示空数据图重新加载按钮,默认为是
* @property {String} empty-view-img 空数据图图片默认使用z-paging内置的图片
* @property {String|Object} empty-view-reload-text 空数据图点击重新加载文字
* @property {String|Object} empty-view-error-text 空数据图“加载失败”描述文字
* @property {String} empty-view-error-img 空数据图“加载失败”图片默认使用z-paging内置的图片(建议使用绝对路径)
* @property {Object} empty-view-style 空数据图样式
* @property {Boolean} empty-view-fixed 空数据图片是否铺满z-paging默认为是。若设置为否则为填充满z-paging的剩余部分
* @property {Boolean} empty-view-center 空数据图片是否垂直居中默认为是。emptyViewFixed为false时有效
* @property {Boolean} auto-show-back-to-top 自动显示点击返回顶部按钮,默认为否
* @property {Number|String} back-to-top-threshold 点击返回顶部按钮显示/隐藏的阈值(滚动距离)单位为px默认为400rpx
* @property {String} back-to-top-img 点击返回顶部按钮的自定义图片地址默认使用z-paging内置的图片
* @property {Boolean} back-to-top-with-animate 点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是
* @property {Number|String} back-to-top-bottom 点击返回顶部按钮与底部的距离注意添加单位px或rpx默认为160rpx
* @property {Object} back-to-top-style 点击返回顶部按钮的自定义样式
* @property {Boolean} show-scrollbar 控制是否出现滚动条,默认为是
* @property {Number|String} lower-threshold 距底部/右边多远时单位px触发 scrolltolower 事件默认为100rpx
* @property {Boolean} refresher-enabled 是否开启自定义下拉刷新,默认为是
* @property {String} refresher-default-style 设置自定义下拉刷新默认样式,支持设置 blackwhitenonenone 表示不使用默认样式默认为black
* @property {String} refresher-background 设置自定义下拉刷新区域背景颜色
* @property {Boolean} show-refresher-update-time 是否显示上次下拉刷新更新时间,默认为否
* @property {String} refresher-update-time-key 上次下拉刷新更新时间的key用于区别不同的上次更新时间
* @property {Boolean} use-chat-record-mode 使用聊天记录模式,默认为否
* @property {Boolean} auto-full-height 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是
* @property {String} nvue-list-is nvue中修改列表类型可选值有list、waterfall和scroller默认为list
* @property {Object} nvue-waterfall-config nvue waterfall配置仅在nvue中且nvueListIs=waterfall时有效配置参数详情参见https://uniapp.dcloud.io/component/waterfall
* @event {Function} query 下拉刷新或滚动到底部时会自动触发此方法。z-paging加载时也会触发(若要禁止,请设置:auto="false")。pageNo和pageSize会自动计算好直接传给服务器即可。
* @event {Function} refresherStatusChange 自定义下拉刷新状态改变(use-custom-refresher为true时生效)
* @event {Function} loadingStatusChange 上拉加载更多状态改变
* @event {Function} onRefresh 自定义下拉刷新被触发
* @event {Function} scroll `z-paging`内置的scroll-view滚动时触发
* @event {Function} scrolltolower `z-paging`内置的scroll-view滚动底部时触发
* @event {Function} scrolltoupper `z-paging`内置的scroll-view滚动顶部时触发
* @event {Function} listChange 分页渲染的数组改变时触发
* @event {Function} emptyViewReload 点击了空数据图中的重新加载按钮
* @example <z-paging ref="paging" v-model="dataList" @query="queryList"></z-paging>
*/
export default {
name:"z-paging",
// #ifdef APP-VUE || H5
mixins: [pagingRenderjs],
// #endif
}
</script>
<script src="./js/z-paging-main.js" />
<style scoped>
@import "./css/z-paging-main.css";
@import "./css/z-paging-static.css";
</style>

910
iconfont.css Normal file

File diff suppressed because one or more lines are too long

20
index.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

141
index.js Normal file
View File

@@ -0,0 +1,141 @@
// 引入全局mixin
import mixin from './libs/mixin/mixin.js'
// 引入关于是否mixin集成小程序分享的配置
// import wxshare from './libs/mixin/mpShare.js'
// 全局挂载引入http相关请求拦截插件
import http from './libs/request'
function wranning(str) {
// 开发环境进行信息输出,主要是一些报错信息
// 这个环境的来由是在程序编写时候,点击hx编辑器运行调试代码的时候,详见:
// https://uniapp.dcloud.io/frame?id=%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83%e5%92%8c%e7%94%9f%e4%ba%a7%e7%8e%af%e5%a2%83
if (process.env.NODE_ENV === 'development') {
console.warn(str)
}
}
// 尝试判断在根目录的/store中是否有$u.mixin.js此文件uView默认为需要挂在到全局的vuex的state变量
// HX2.6.11版本,放到try中,控制台依然会警告,暂时不用此方式,
// let vuexStore = {};
// try {
// vuexStore = require("@/store/$u.mixin.js");
// } catch (e) {
// //TODO handle the exception
// }
// post类型对象参数转为get类型url参数
import queryParams from './libs/function/queryParams.js'
// 路由封装
import route from './libs/function/route.js'
// 时间格式化
import timeFormat from './libs/function/timeFormat.js'
// 时间戳格式化,返回多久之前
import timeFrom from './libs/function/timeFrom.js'
// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制
import colorGradient from './libs/function/colorGradient.js'
// 生成全局唯一guid字符串
import guid from './libs/function/guid.js'
// 主题相关颜色,info|success|warning|primary|default|error,此颜色已在uview.scss中定义,但是为js中也能使用,故也定义一份
import color from './libs/function/color.js'
// 根据type获取图标名称
import type2icon from './libs/function/type2icon.js'
// 打乱数组的顺序
import randomArray from './libs/function/randomArray.js'
// 对象和数组的深度克隆
import deepClone from './libs/function/deepClone.js'
// 对象深度拷贝
import deepMerge from './libs/function/deepMerge.js'
// 添加单位
import addUnit from './libs/function/addUnit.js'
// 规则检验
import test from './libs/function/test.js'
// 随机数
import random from './libs/function/random.js'
// 去除空格
import trim from './libs/function/trim.js'
// toast提示对uni.showToast的封装
import toast from './libs/function/toast.js'
// 获取父组件参数
import getParent from './libs/function/getParent.js'
// 获取整个父组件
import $parent from './libs/function/$parent.js'
// 获取sys()和os()工具方法
// 获取设备信息,挂载到$u的sys()(system的缩写)属性中,
// 同时把安卓和ios平台的名称"ios"和"android"挂到$u.os()中,方便取用
import {sys, os} from './libs/function/sys.js'
// 防抖方法
import debounce from './libs/function/debounce.js'
// 节流方法
import throttle from './libs/function/throttle.js'
// 配置信息
import config from './libs/config/config.js'
// 各个需要fixed的地方的z-index配置文件
import zIndex from './libs/config/zIndex.js'
const $u = {
queryParams: queryParams,
route: route,
timeFormat: timeFormat,
date: timeFormat, // 另名date
timeFrom,
colorGradient: colorGradient.colorGradient,
colorToRgba: colorGradient.colorToRgba,
guid,
color,
sys,
os,
type2icon,
randomArray,
wranning,
get: http.get,
post: http.post,
put: http.put,
'delete': http.delete,
hexToRgb: colorGradient.hexToRgb,
rgbToHex: colorGradient.rgbToHex,
test,
random,
deepClone,
deepMerge,
getParent,
$parent,
addUnit,
trim,
type: ['primary', 'success', 'error', 'warning', 'info'],
http,
toast,
config, // uView配置信息相关比如版本号
zIndex,
debounce,
throttle,
}
// $u挂载到uni对象上
uni.$u = $u
const install = Vue => {
Vue.mixin(mixin)
if (Vue.prototype.openShare) {
Vue.mixin(mpShare);
}
// Vue.mixin(vuexStore);
// 时间格式化同时两个名称date和timeFormat
Vue.filter('timeFormat', (timestamp, format) => {
return timeFormat(timestamp, format)
})
Vue.filter('date', (timestamp, format) => {
return timeFormat(timestamp, format)
})
// 将多久以前的方法,注入到全局过滤器
Vue.filter('timeFrom', (timestamp, format) => {
return timeFrom(timestamp, format)
})
Vue.prototype.$u = $u
}
export default {
install
}

23
index.scss Normal file
View File

@@ -0,0 +1,23 @@
// 引入公共基础类
@import "./libs/css/common.scss";
@import "./libs/css/color.scss";
// 非nvue的样式
/* #ifndef APP-NVUE */
@import "./libs/css/style.vue.scss";
/* #endif */
// nvue的特有样式
/* #ifdef APP-NVUE */
@import "./libs/css/style.nvue.scss";
/* #endif */
// 小程序特有的样式
/* #ifdef MP */
@import "./libs/css/style.mp.scss";
/* #endif */
// H5特有的样式
/* #ifdef H5 */
@import "./libs/css/style.h5.scss";
/* #endif */

17
libs/config/config.js Normal file
View File

@@ -0,0 +1,17 @@
// 此版本发布于2022-04-19
let version = '1.8.6';
export default {
//api:'http://work.ii999.live:19011',
api:'http://222.132.51.82:18088',
v: version,
version: version,
// 主题名称
type: [
'primary',
'success',
'info',
'error',
'warning'
]
}

20
libs/config/zIndex.js Normal file
View File

@@ -0,0 +1,20 @@
// uniapp在H5中各API的z-index值如下
/**
* actionsheet: 999
* modal: 999
* navigate: 998
* tabbar: 998
* toast: 999
*/
export default {
toast: 10090,
noNetwork: 10080,
// popup包含popupactionsheetkeyboardpicker的值
popup: 10075,
mask: 10070,
navbar: 980,
topTips: 975,
sticky: 970,
indexListSticky: 965,
}

155
libs/css/color.scss Normal file
View File

@@ -0,0 +1,155 @@
.u-type-primary-light {
color: $u-type-primary-light;
}
.u-type-warning-light {
color: $u-type-warning-light;
}
.u-type-success-light {
color: $u-type-success-light;
}
.u-type-error-light {
color: $u-type-error-light;
}
.u-type-info-light {
color: $u-type-info-light;
}
.u-type-primary-light-bg {
background-color: $u-type-primary-light;
}
.u-type-warning-light-bg {
background-color: $u-type-warning-light;
}
.u-type-success-light-bg {
background-color: $u-type-success-light;
}
.u-type-error-light-bg {
background-color: $u-type-error-light;
}
.u-type-info-light-bg {
background-color: $u-type-info-light;
}
.u-type-primary-dark {
color: $u-type-primary-dark;
}
.u-type-warning-dark {
color: $u-type-warning-dark;
}
.u-type-success-dark {
color: $u-type-success-dark;
}
.u-type-error-dark {
color: $u-type-error-dark;
}
.u-type-info-dark {
color: $u-type-info-dark;
}
.u-type-primary-dark-bg {
background-color: $u-type-primary-dark;
}
.u-type-warning-dark-bg {
background-color: $u-type-warning-dark;
}
.u-type-success-dark-bg {
background-color: $u-type-success-dark;
}
.u-type-error-dark-bg {
background-color: $u-type-error-dark;
}
.u-type-info-dark-bg {
background-color: $u-type-info-dark;
}
.u-type-primary-disabled {
color: $u-type-primary-disabled;
}
.u-type-warning-disabled {
color: $u-type-warning-disabled;
}
.u-type-success-disabled {
color: $u-type-success-disabled;
}
.u-type-error-disabled {
color: $u-type-error-disabled;
}
.u-type-info-disabled {
color: $u-type-info-disabled;
}
.u-type-primary {
color: $u-type-primary;
}
.u-type-warning {
color: $u-type-warning;
}
.u-type-success {
color: $u-type-success;
}
.u-type-error {
color: $u-type-error;
}
.u-type-info {
color: $u-type-info;
}
.u-type-primary-bg {
background-color: $u-type-primary;
}
.u-type-warning-bg {
background-color: $u-type-warning;
}
.u-type-success-bg {
background-color: $u-type-success;
}
.u-type-error-bg {
background-color: $u-type-error;
}
.u-type-info-bg {
background-color: $u-type-info;
}
.u-main-color {
color: $u-main-color;
}
.u-content-color {
color: $u-content-color;
}
.u-tips-color {
color: $u-tips-color;
}
.u-light-color {
color: $u-light-color;
}

176
libs/css/common.scss Normal file
View File

@@ -0,0 +1,176 @@
.u-relative,
.u-rela {
position: relative;
}
.u-absolute,
.u-abso {
position: absolute;
}
// nvue不能用标签命名样式不能放在微信组件中否则微信开发工具会报警告无法使用标签名当做选择器
/* #ifndef APP-NVUE */
image {
display: inline-block;
}
// 在weex也即nvue中所有元素默认为border-box
view,
text {
box-sizing: border-box;
}
/* #endif */
.u-font-xs {
font-size: 22rpx;
}
.u-font-sm {
font-size: 26rpx;
}
.u-font-md {
font-size: 28rpx;
}
.u-font-lg {
font-size: 30rpx;
}
.u-font-xl {
font-size: 34rpx;
}
.u-flex {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
}
.u-flex-wrap {
flex-wrap: wrap;
}
.u-flex-nowrap {
flex-wrap: nowrap;
}
.u-col-center {
align-items: center;
}
.u-col-top {
align-items: flex-start;
}
.u-col-bottom {
align-items: flex-end;
}
.u-row-center {
justify-content: center;
}
.u-row-left {
justify-content: flex-start;
}
.u-row-right {
justify-content: flex-end;
}
.u-row-between {
justify-content: space-between;
}
.u-row-around {
justify-content: space-around;
}
.u-text-left {
text-align: left;
}
.u-text-center {
text-align: center;
}
.u-text-right {
text-align: right;
}
.u-flex-col {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
// 定义flex等分
@for $i from 0 through 12 {
.u-flex-#{$i} {
flex: $i;
}
}
// 定义字体(px)单位小于20都为px单位字体
@for $i from 9 to 20 {
.u-font-#{$i} {
font-size: $i + px;
}
}
// 定义字体(rpx)单位大于或等于20的都为rpx单位字体
@for $i from 20 through 40 {
.u-font-#{$i} {
font-size: $i + rpx;
}
}
// 定义内外边距历遍1-80
@for $i from 0 through 80 {
// 只要双数和能被5除尽的数
@if $i % 2 == 0 or $i % 5 == 0 {
// 得出u-margin-30或者u-m-30
.u-margin-#{$i}, .u-m-#{$i} {
margin: $i + rpx!important;
}
// 得出u-padding-30或者u-p-30
.u-padding-#{$i}, .u-p-#{$i} {
padding: $i + rpx!important;
}
@each $short, $long in l left, t top, r right, b bottom {
// 缩写版,结果如: u-m-l-30
// 定义外边距
.u-m-#{$short}-#{$i} {
margin-#{$long}: $i + rpx!important;
}
// 定义内边距
.u-p-#{$short}-#{$i} {
padding-#{$long}: $i + rpx!important;
}
// 完整版结果如u-margin-left-30
// 定义外边距
.u-margin-#{$long}-#{$i} {
margin-#{$long}: $i + rpx!important;
}
// 定义内边距
.u-padding-#{$long}-#{$i} {
padding-#{$long}: $i + rpx!important;
}
}
}
}
// 重置nvue的默认关于flex的样式
.u-reset-nvue {
flex-direction: row;
align-items: center;
}

View File

@@ -0,0 +1,7 @@
// 定义混入指令用于在非nvue环境下的flex定义因为nvue没有display属性会报错
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}

8
libs/css/style.h5.scss Normal file
View File

@@ -0,0 +1,8 @@
/* H5的时候隐藏滚动条 */
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}

72
libs/css/style.mp.scss Normal file
View File

@@ -0,0 +1,72 @@
/* start--微信小程序编译后页面有组件名的元素,特别处理--start */
/* #ifdef MP-WEIXIN || MP-QQ */
u-td, u-th {
flex: 1;
align-self: stretch;
}
.u-td {
height: 100%;
}
u-icon {
display: inline-flex;
align-items: center;
}
// 各家小程序宫格组件外层设置为100%避免受到父元素display: flex;的影响
u-grid {
width: 100%;
flex: 0 0 100%;
}
// 避免小程序线条组件因为父组件display: flex;而失效
u-line {
flex: 1;
}
u-switch {
display: inline-flex;
align-items: center;
}
u-dropdown {
flex: 1;
}
/* #endif */
/* end-微信小程序编译后页面有组件名的元素,特别处理--end */
/* #ifdef MP-QQ || MP-TOUTIAO */
// 需要做这一切额外的兼容都是因为TX的无能
u-icon {
line-height: 0;
}
/* #endif */
/* start--头条小程序编译后页面有组件名的元素,特别处理--start */
// 由于头条小程序不支持直接组件名形式写样式,目前只能在写组件的时候给组件加上对应的类名
/* #ifdef MP-TOUTIAO */
.u-td, .u-th, .u-tr {
flex: 1;
align-self: stretch;
}
.u-row, .u-col {
flex: 1;
align-self: stretch;
}
// 避免小程序线条组件因为父组件display: flex;而失效
.u-line {
flex: 1;
}
.u-dropdown {
flex: 1;
}
/* #endif */
/* end-头条小程序编译后页面有组件名的元素,特别处理--end */

3
libs/css/style.nvue.scss Normal file
View File

@@ -0,0 +1,3 @@
.nvue {
font-size: 24rpx;
}

175
libs/css/style.vue.scss Normal file
View File

@@ -0,0 +1,175 @@
page {
color: $u-main-color;
font-size: 28rpx;
}
/* start--去除webkit的默认样式--start */
.u-fix-ios-appearance {
-webkit-appearance:none;
}
/* end--去除webkit的默认样式--end */
/* start--icon图标外层套一个view让其达到更好的垂直居中的效果--start */
.u-icon-wrap {
display: flex;
align-items: center;
}
/* end-icon图标外层套一个view让其达到更好的垂直居中的效果--end */
/* start--iPhoneX底部安全区定义--start */
.safe-area-inset-bottom {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
/* end-iPhoneX底部安全区定义--end */
/* start--各种hover点击反馈相关的类名-start */
.u-hover-class {
// background-color: #f7f8f9!important;
opacity: 0.6;
}
.u-cell-hover {
background-color: #f7f8f9!important;
}
/* end--各种hover点击反馈相关的类名--end */
/* start--文本行数限制--start */
.u-line-1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.u-line-2 {
-webkit-line-clamp: 2;
}
.u-line-3 {
-webkit-line-clamp: 3;
}
.u-line-4 {
-webkit-line-clamp: 4;
}
.u-line-5 {
-webkit-line-clamp: 5;
}
.u-line-2, .u-line-3, .u-line-4, .u-line-5 {
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box; // 弹性伸缩盒
-webkit-box-orient: vertical; // 设置伸缩盒子元素排列方式
}
/* end--文本行数限制--end */
/* start--Retina 屏幕下的 1px 边框--start */
.u-border,
.u-border-bottom,
.u-border-left,
.u-border-right,
.u-border-top,
.u-border-top-bottom {
position: relative
}
.u-border-bottom:after,
.u-border-left:after,
.u-border-right:after,
.u-border-top-bottom:after,
.u-border-top:after,
.u-border:after {
/* #ifndef APP-NVUE */
content: ' ';
/* #endif */
position: absolute;
left: 0;
top: 0;
pointer-events: none;
box-sizing: border-box;
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
// 多加0.1%,能解决有时候边框缺失的问题
width: 199.8%;
height: 199.7%;
transform: scale(0.5, 0.5);
border: 0 solid $u-border-color;
z-index: 2;
}
.u-border-top:after {
border-top-width: 1px
}
.u-border-left:after {
border-left-width: 1px
}
.u-border-right:after {
border-right-width: 1px
}
.u-border-bottom:after {
border-bottom-width: 1px
}
.u-border-top-bottom:after {
border-width: 1px 0
}
.u-border:after {
border-width: 1px
}
/* end--Retina 屏幕下的 1px 边框--end */
/* start--clearfix--start */
.u-clearfix:after,
.clearfix:after {
/* #ifndef APP-NVUE */
content: '';
/* #endif */
display: table;
clear: both
}
/* end--clearfix--end */
/* start--高斯模糊tabbar底部处理--start */
.u-blur-effect-inset {
width: 750rpx;
height: var(--window-bottom);
background-color: #FFFFFF;
}
/* end--高斯模糊tabbar底部处理--end */
/* start--提升H5端uni.toast()的层级避免被uView的modal等遮盖--start */
/* #ifdef H5 */
uni-toast {
z-index: 10090;
}
uni-toast .uni-toast {
z-index: 10090;
}
/* #endif */
/* end--提升H5端uni.toast()的层级避免被uView的modal等遮盖--end */
/* start--去除button的所有默认样式--start */
.u-reset-button {
padding: 0;
font-size: inherit;
line-height: inherit;
background-color: transparent;
color: inherit;
}
.u-reset-button::after {
border: none;
}
/* end--去除button的所有默认样式--end */

18
libs/function/$parent.js Normal file
View File

@@ -0,0 +1,18 @@
// 获取父组件的参数因为支付宝小程序不支持provide/inject的写法
// this.$parent在非H5中可以准确获取到父组件但是在H5中需要多次this.$parent.$parent.xxx
// 这里默认值等于undefined有它的含义因为最顶层元素(组件)的$parent就是undefined意味着不传name
// 值(默认为undefined),就是查找最顶层的$parent
export default function $parent(name = undefined) {
let parent = this.$parent;
// 通过while历遍这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options && parent.$options.name !== name) {
// 如果组件的name不相等继续上一级寻找
parent = parent.$parent;
} else {
return parent;
}
}
return false;
}

8
libs/function/addUnit.js Normal file
View File

@@ -0,0 +1,8 @@
import validation from './test.js';
// 添加单位如果有rpx%px等单位结尾或者值为auto直接返回否则加上rpx单位结尾
export default function addUnit(value = 'auto', unit = 'rpx') {
value = String(value);
// 用uView内置验证规则中的number判断是否为数值
return validation.number(value) ? `${value}${unit}` : value;
}

5
libs/function/bem.js Normal file
View File

@@ -0,0 +1,5 @@
function bem(name, conf) {
}
module.exports.bem = bem;

37
libs/function/color.js Normal file
View File

@@ -0,0 +1,37 @@
// 为了让用户能够自定义主题会逐步弃用此文件各颜色通过css提供
// 为了给某些特殊场景使用和向后兼容,无需删除此文件(2020-06-20)
let color = {
primary: "#2979ff",
primaryDark: "#2b85e4",
primaryDisabled: "#a0cfff",
primaryLight: "#ecf5ff",
bgColor: "#f3f4f6",
info: "#909399",
infoDark: "#82848a",
infoDisabled: "#c8c9cc",
infoLight: "#f4f4f5",
warning: "#ff9900",
warningDark: "#f29100",
warningDisabled: "#fcbd71",
warningLight: "#fdf6ec",
error: "#fa3534",
errorDark: "#dd6161",
errorDisabled: "#fab6b6",
errorLight: "#fef0f0",
success: "#19be6b",
successDark: "#18b566",
successDisabled: "#71d5a1",
successLight: "#dbf1e1",
mainColor: "#303133",
contentColor: "#606266",
tipsColor: "#909399",
lightColor: "#c0c4cc",
borderColor: "#e4e7ed"
}
export default color;

View File

@@ -0,0 +1,134 @@
/**
* 求两个颜色之间的渐变值
* @param {string} startColor 开始的颜色
* @param {string} endColor 结束的颜色
* @param {number} step 颜色等分的份额
* */
function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {
let startRGB = hexToRgb(startColor, false); //转换为rgb数组模式
let startR = startRGB[0];
let startG = startRGB[1];
let startB = startRGB[2];
let endRGB = hexToRgb(endColor, false);
let endR = endRGB[0];
let endG = endRGB[1];
let endB = endRGB[2];
let sR = (endR - startR) / step; //总差值
let sG = (endG - startG) / step;
let sB = (endB - startB) / step;
let colorArr = [];
for (let i = 0; i < step; i++) {
//计算每一步的hex值
let hex = rgbToHex('rgb(' + Math.round((sR * i + startR)) + ',' + Math.round((sG * i + startG)) + ',' + Math.round((sB *
i + startB)) + ')');
colorArr.push(hex);
}
return colorArr;
}
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
function hexToRgb(sColor, str = true) {
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
sColor = sColor.toLowerCase();
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = "#";
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
//处理六位的颜色值
let sColorChange = [];
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
}
if(!str) {
return sColorChange;
} else {
return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`;
}
} else if (/^(rgb|RGB)/.test(sColor)) {
let arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",")
return arr.map(val => Number(val));
} else {
return sColor;
}
};
// 将rgb表示方式转换为hex表示方式
function rgbToHex(rgb) {
let _this = rgb;
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
if (/^(rgb|RGB)/.test(_this)) {
let aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",");
let strHex = "#";
for (let i = 0; i < aColor.length; i++) {
let hex = Number(aColor[i]).toString(16);
hex = String(hex).length == 1 ? 0 + '' + hex : hex; // 保证每个rgb的值为2位
if (hex === "0") {
hex += hex;
}
strHex += hex;
}
if (strHex.length !== 7) {
strHex = _this;
}
return strHex;
} else if (reg.test(_this)) {
let aNum = _this.replace(/#/, "").split("");
if (aNum.length === 6) {
return _this;
} else if (aNum.length === 3) {
let numHex = "#";
for (let i = 0; i < aNum.length; i += 1) {
numHex += (aNum[i] + aNum[i]);
}
return numHex;
}
} else {
return _this;
}
}
/**
* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba2552552550.5)字符串
* sHex为传入的十六进制的色值
* alpha为rgba的透明度
*/
function colorToRgba(color, alpha = 0.3) {
color = rgbToHex(color)
// 十六进制颜色值的正则表达式
var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
/* 16进制颜色转为RGB格式 */
let sColor = color.toLowerCase()
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
var sColorNew = '#'
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
// 处理六位的颜色值
var sColorChange = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
}
// return sColorChange.join(',')
return 'rgba(' + sColorChange.join(',') + ',' + alpha + ')'
}
else {
return sColor
}
}
export default {
colorGradient,
hexToRgb,
rgbToHex,
colorToRgba
}

29
libs/function/debounce.js Normal file
View File

@@ -0,0 +1,29 @@
let timeout = null;
/**
* 防抖原理一定时间内只有最后一次操作再过wait毫秒后才执行函数
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function debounce(func, wait = 500, immediate = false) {
// 清除定时器
if (timeout !== null) clearTimeout(timeout);
// 立即执行,此类情况一般用不到
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
timeout = null;
}, wait);
if (callNow) typeof func === 'function' && func();
} else {
// 设置定时器当最后一次操作后timeout不会再被清除所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(function() {
typeof func === 'function' && func();
}, wait);
}
}
export default debounce

View File

@@ -0,0 +1,23 @@
// 判断arr是否为一个数组返回一个bool值
function isArray (arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
// 深度克隆
function deepClone (obj) {
// 对常见的“非”值,直接返回原来值
if([null, undefined, NaN, false].includes(obj)) return obj;
if(typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for(let i in obj) {
if(obj.hasOwnProperty(i)){
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}
export default deepClone;

View File

@@ -0,0 +1,30 @@
import deepClone from "./deepClone";
// JS对象深度合并
function deepMerge(target = {}, source = {}) {
target = deepClone(target);
if (typeof target !== 'object' || typeof source !== 'object') return false;
for (var prop in source) {
if (!source.hasOwnProperty(prop)) continue;
if (prop in target) {
if (typeof target[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (typeof source[prop] !== 'object') {
target[prop] = source[prop];
} else {
if (target[prop].concat && source[prop].concat) {
target[prop] = target[prop].concat(source[prop]);
} else {
target[prop] = deepMerge(target[prop], source[prop]);
}
}
}
} else {
target[prop] = source[prop];
}
}
return target;
}
export default deepMerge;

View File

@@ -0,0 +1,47 @@
// 获取父组件的参数因为支付宝小程序不支持provide/inject的写法
// this.$parent在非H5中可以准确获取到父组件但是在H5中需要多次this.$parent.$parent.xxx
export default function getParent(name, keys) {
let parent = this.$parent;
// 通过while历遍这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options.name !== name) {
// 如果组件的name不相等继续上一级寻找
parent = parent.$parent;
} else {
let data = {};
// 判断keys是否数组如果传过来的是一个数组那么直接使用数组元素值当做键值去父组件寻找
if(Array.isArray(keys)) {
keys.map(val => {
data[val] = parent[val] ? parent[val] : '';
})
} else {
// 历遍传过来的对象参数
for(let i in keys) {
// 如果子组件有此值则用,无此值则用父组件的值
// 判断是否空数组,如果是,则用父组件的值,否则用子组件的值
if(Array.isArray(keys[i])) {
if(keys[i].length) {
data[i] = keys[i];
} else {
data[i] = parent[i];
}
} else if(keys[i].constructor === Object) {
// 判断是否对象,如果是对象,且有属性,那么使用子组件的值,否则使用父组件的值
if(Object.keys(keys[i]).length) {
data[i] = keys[i];
} else {
data[i] = parent[i];
}
} else {
// 只要子组件有传值即使是false值也是“传值”了也需要覆盖父组件的同名参数
data[i] = (keys[i] || keys[i] === false) ? keys[i] : parent[i];
}
}
}
return data;
}
}
return {};
}

41
libs/function/guid.js Normal file
View File

@@ -0,0 +1,41 @@
/**
* 本算法来源于简书开源代码详见https://www.jianshu.com/p/fdbf293d0a85
* 全局唯一标识符uuidGlobally Unique Identifier,也称作 uuid(Universally Unique IDentifier)
* 一般用于多个组件之间,给它一个唯一的标识符,或者v-for循环的时候,如果使用数组的index可能会导致更新列表出现问题
* 最可能的情况是左滑删除item或者对某条信息流"不喜欢"并去掉它的时候,会导致组件内的数据可能出现错乱
* v-for的时候,推荐使用后端返回的id而不是循环的index
* @param {Number} len uuid的长度
* @param {Boolean} firstU 将返回的首字母置为"u"
* @param {Number} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
*/
function guid(len = 32, firstU = true, radix = null) {
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
let uuid = [];
radix = radix || chars.length;
if (len) {
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
} else {
let r;
// rfc4122标准要求返回的uuid中,某些位为固定的字符
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | Math.random() * 16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
}
}
}
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guid不能用作id或者class
if (firstU) {
uuid.shift();
return 'u' + uuid.join('');
} else {
return uuid.join('');
}
}
export default guid;

385
libs/function/md5.js Normal file
View File

@@ -0,0 +1,385 @@
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */
/*
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*/
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
* Perform a simple self-test to see if the VM is working
*/
function md5_vm_test()
{
return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
}
/*
* Calculate the MD5 of a raw string
*/
function rstr_md5(s)
{
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
* Calculate the HMAC-MD5, of a key and some data (raw strings)
*/
function rstr_hmac_md5(key, data)
{
var bkey = rstr2binl(key);
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
var ipad = Array(16), opad = Array(16);
for(var i = 0; i < 16; i++)
{
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
* Convert a raw string to a hex string
*/
function rstr2hex(input)
{
try { hexcase } catch(e) { hexcase=0; }
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F)
+ hex_tab.charAt( x & 0x0F);
}
return output;
}
/*
* Convert a raw string to a base-64 string
*/
function rstr2b64(input)
{
try { b64pad } catch(e) { b64pad=''; }
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var output = "";
var len = input.length;
for(var i = 0; i < len; i += 3)
{
var triplet = (input.charCodeAt(i) << 16)
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
for(var j = 0; j < 4; j++)
{
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
return output;
}
/*
* Convert a raw string to an arbitrary string encoding
*/
function rstr2any(input, encoding)
{
var divisor = encoding.length;
var i, j, q, x, quotient;
/* Convert to an array of 16-bit big-endian values, forming the dividend */
var dividend = Array(Math.ceil(input.length / 2));
for(i = 0; i < dividend.length; i++)
{
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
var full_length = Math.ceil(input.length * 8 /
(Math.log(encoding.length) / Math.log(2)));
var remainders = Array(full_length);
for(j = 0; j < full_length; j++)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
output += encoding.charAt(remainders[i]);
return output;
}
/*
* Encode a string as utf-8.
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;
while(++i < input.length)
{
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
{
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
i++;
}
/* Encode output as utf-8 */
if(x <= 0x7F)
output += String.fromCharCode(x);
else if(x <= 0x7FF)
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
0x80 | ( x & 0x3F));
else if(x <= 0xFFFF)
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}
/*
* Encode a string as utf-16
*/
function str2rstr_utf16le(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
(input.charCodeAt(i) >>> 8) & 0xFF);
return output;
}
function str2rstr_utf16be(input)
{
var output = "";
for(var i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
input.charCodeAt(i) & 0xFF);
return output;
}
/*
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*/
function rstr2binl(input)
{
var output = Array(input.length >> 2);
for(var i = 0; i < output.length; i++)
output[i] = 0;
for(var i = 0; i < input.length * 8; i += 8)
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
return output;
}
/*
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}
/*
* Calculate the MD5 of an array of little-endian words, and a bit length.
*/
function binl_md5(x, len)
{
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for(var i = 0; i < x.length; i += 16)
{
var olda = a;
var oldb = b;
var oldc = c;
var oldd = d;
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return Array(a, b, c, d);
}
/*
* These functions implement the four basic operations the algorithm uses.
*/
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
module.exports = {
md5 : function(str){
return hex_md5(str);
}
}

View File

@@ -0,0 +1,58 @@
/**
* 对象转url参数
* @param {*} data,对象
* @param {*} isPrefix,是否自动加上"?"
*/
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
let prefix = isPrefix ? '?' : ''
let _result = []
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets';
for (let key in data) {
let value = data[key]
// 去掉为空的参数
if (['', undefined, null].indexOf(value) >= 0) {
continue;
}
// 如果值为数组,另行处理
if (value.constructor === Array) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case 'indices':
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(key + '[' + i + ']=' + value[i])
}
break;
case 'brackets':
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach(_value => {
_result.push(key + '[]=' + _value)
})
break;
case 'repeat':
// 结果: ids=1&ids=2&ids=3
value.forEach(_value => {
_result.push(key + '=' + _value)
})
break;
case 'comma':
// 结果: ids=1,2,3
let commaStr = "";
value.forEach(_value => {
commaStr += (commaStr ? "," : "") + _value;
})
_result.push(key + '=' + commaStr)
break;
default:
value.forEach(_value => {
_result.push(key + '[]=' + _value)
})
}
} else {
_result.push(key + '=' + value)
}
}
return _result.length ? prefix + _result.join('&') : ''
}
export default queryParams;

10
libs/function/random.js Normal file
View File

@@ -0,0 +1,10 @@
function random(min, max) {
if (min >= 0 && max > 0 && max >= min) {
let gab = max - min + 1;
return Math.floor(Math.random() * gab + min);
} else {
return 0;
}
}
export default random;

View File

@@ -0,0 +1,7 @@
// 打乱数组
function randomArray(array = []) {
// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
return array.sort(() => Math.random() - 0.5);
}
export default randomArray

122
libs/function/route.js Normal file
View File

@@ -0,0 +1,122 @@
/**
* 路由跳转方法该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
* 并且带有路由拦截功能
*/
class Router {
constructor() {
// 原始属性定义
this.config = {
type: 'navigateTo',
url: '',
delta: 1, // navigateBack页面后退时,回退的层数
params: {}, // 传递的参数
animationType: 'pop-in', // 窗口动画,只在APP有效
animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
intercept: false, // 是否需要拦截
}
// 因为route方法是需要对外赋值给另外的对象使用同时route内部有使用this会导致route失去上下文
// 这里在构造函数中进行this绑定
this.route = this.route.bind(this)
}
// 判断url前面是否有"/",如果没有则加上,否则无法跳转
addRootPath(url) {
return url[0] === '/' ? url : `/${url}`
}
// 整合路由参数
mixinParam(url, params) {
url = url && this.addRootPath(url)
// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
// 如果有url中有get参数转换后无需带上"?"
let query = ''
if (/.*\/.*\?.*=.*/.test(url)) {
// object对象转为get类型的参数
query = uni.$u.queryParams(params, false);
// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
return url += "&" + query
} else {
// 直接拼接参数因为此处url中没有后面的query参数也就没有"?/&"之类的符号
query = uni.$u.queryParams(params);
return url += query
}
}
// 对外的方法名称
async route(options = {}, params = {}) {
// 合并用户的配置和内部的默认配置
let mergeConfig = {}
if (typeof options === 'string') {
// 如果options为字符串则为route(url, params)的形式
mergeConfig.url = this.mixinParam(options, params)
mergeConfig.type = 'navigateTo'
} else {
mergeConfig = uni.$u.deepClone(options, this.config)
// 否则正常使用mergeConfig中的url和params进行拼接
mergeConfig.url = this.mixinParam(options.url, options.params)
}
if(params.intercept) {
this.config.intercept = params.intercept
}
// params参数也带给拦截器
mergeConfig.params = params
// 合并内外部参数
mergeConfig = uni.$u.deepMerge(this.config, mergeConfig)
// 判断用户是否定义了拦截器
if (typeof uni.$u.routeIntercept === 'function') {
// 定一个promise根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
const isNext = await new Promise((resolve, reject) => {
uni.$u.routeIntercept(mergeConfig, resolve)
})
// 如果isNext为true则执行路由跳转
isNext && this.openPage(mergeConfig)
} else {
this.openPage(mergeConfig)
}
}
// 执行路由跳转
openPage(config) {
// 解构参数
const {
url,
type,
delta,
animationType,
animationDuration
} = config
if (config.type == 'navigateTo' || config.type == 'to') {
uni.navigateTo({
url,
animationType,
animationDuration
});
}
if (config.type == 'redirectTo' || config.type == 'redirect') {
uni.redirectTo({
url
});
}
if (config.type == 'switchTab' || config.type == 'tab') {
uni.switchTab({
url
});
}
if (config.type == 'reLaunch' || config.type == 'launch') {
uni.reLaunch({
url
});
}
if (config.type == 'navigateBack' || config.type == 'back') {
uni.navigateBack({
delta
});
}
}
}
export default (new Router()).route

9
libs/function/sys.js Normal file
View File

@@ -0,0 +1,9 @@
export function os() {
return uni.getSystemInfoSync().platform;
};
export function sys() {
return uni.getSystemInfoSync();
}

232
libs/function/test.js Normal file
View File

@@ -0,0 +1,232 @@
/**
* 验证电子邮箱格式
*/
function email(value) {
return /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/.test(value);
}
/**
* 验证手机格式
*/
function mobile(value) {
return /^1[3-9]\d{9}$/.test(value)
}
/**
* 验证URL格式
*/
function url(value) {
return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-.\/?%&=]*)?/.test(value)
}
/**
* 验证日期格式
*/
function date(value) {
return !/Invalid|NaN/.test(new Date(value).toString())
}
/**
* 验证ISO类型的日期格式
*/
function dateISO(value) {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
}
/**
* 验证十进制数字
*/
function number(value) {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
}
/**
* 验证整数
*/
function digits(value) {
return /^\d+$/.test(value)
}
/**
* 验证身份证号码
*/
function idCard(value) {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
value)
}
/**
* 是否车牌号
*/
function carNo(value) {
// 新能源车牌
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
// 旧车牌
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
if (value.length === 7) {
return creg.test(value);
} else if (value.length === 8) {
return xreg.test(value);
} else {
return false;
}
}
/**
* 金额,只允许2位小数
*/
function amount(value) {
//金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value);
}
/**
* 中文
*/
function chinese(value) {
let reg = /^[\u4e00-\u9fa5]+$/gi;
return reg.test(value);
}
/**
* 只能输入字母
*/
function letter(value) {
return /^[a-zA-Z]*$/.test(value);
}
/**
* 只能是字母或者数字
*/
function enOrNum(value) {
//英文或者数字
let reg = /^[0-9a-zA-Z]*$/g;
return reg.test(value);
}
/**
* 验证是否包含某个值
*/
function contains(value, param) {
return value.indexOf(param) >= 0
}
/**
* 验证一个值范围[min, max]
*/
function range(value, param) {
return value >= param[0] && value <= param[1]
}
/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value, param) {
return value.length >= param[0] && value.length <= param[1]
}
/**
* 是否固定电话
*/
function landline(value) {
let reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/;
return reg.test(value);
}
/**
* 判断是否为空
*/
function empty(value) {
switch (typeof value) {
case 'undefined':
return true;
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true;
break;
case 'boolean':
if (!value) return true;
break;
case 'number':
if (0 === value || isNaN(value)) return true;
break;
case 'object':
if (null === value || value.length === 0) return true;
for (var i in value) {
return false;
}
return true;
}
return false;
}
/**
* 是否json字符串
*/
function jsonString(value) {
if (typeof value == 'string') {
try {
var obj = JSON.parse(value);
if (typeof obj == 'object' && obj) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
return false;
}
/**
* 是否数组
*/
function array(value) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === "[object Array]";
}
}
/**
* 是否对象
*/
function object(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
/**
* 是否短信验证码
*/
function code(value, len = 6) {
return new RegExp(`^\\d{${len}}$`).test(value);
}
export default {
email,
mobile,
url,
date,
dateISO,
number,
digits,
idCard,
carNo,
amount,
chinese,
letter,
enOrNum,
contains,
range,
rangeLength,
empty,
isEmpty: empty,
jsonString,
landline,
object,
array,
code
}

32
libs/function/throttle.js Normal file
View File

@@ -0,0 +1,32 @@
let timer, flag;
/**
* 节流原理:在一定时间内,只能触发一次
*
* @param {Function} func 要执行的回调函数
* @param {Number} wait 延时的时间
* @param {Boolean} immediate 是否立即执行
* @return null
*/
function throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!flag) {
flag = true;
// 如果是立即执行则在wait毫秒内开始时执行
typeof func === 'function' && func();
timer = setTimeout(() => {
flag = false;
}, wait);
}
} else {
if (!flag) {
flag = true
// 如果是非立即执行则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false
typeof func === 'function' && func();
}, wait);
}
}
};
export default throttle

View File

@@ -0,0 +1,51 @@
// padStart 的 polyfill因为某些机型或情况还无法支持es7的padStart比如电脑版的微信小程序
// 所以这里做一个兼容polyfill的兼容处理
if (!String.prototype.padStart) {
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
String.prototype.padStart = function(maxLength, fillString = ' ') {
if (Object.prototype.toString.call(fillString) !== "[object String]") throw new TypeError(
'fillString must be String')
let str = this
// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
if (str.length >= maxLength) return String(str)
let fillLength = maxLength - str.length,
times = Math.ceil(fillLength / fillString.length)
while (times >>= 1) {
fillString += fillString
if (times === 1) {
fillString += fillString
}
}
return fillString.slice(0, fillLength) + str;
}
}
// 其他更多是格式化有如下:
// yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合
function timeFormat(dateTime = null, fmt = 'yyyy-mm-dd') {
// 如果为null,则格式化当前时间
if (!dateTime) dateTime = Number(new Date());
// 如果dateTime长度为10或者13则为秒和毫秒的时间戳如果超过13位则为其他的时间格式
if (dateTime.toString().length == 10) dateTime *= 1000;
let date = new Date(dateTime);
let ret;
let opt = {
"y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"h+": date.getHours().toString(), // 时
"M+": date.getMinutes().toString(), // 分
"s+": date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
};
};
return fmt;
}
export default timeFormat

47
libs/function/timeFrom.js Normal file
View File

@@ -0,0 +1,47 @@
import timeFormat from '../../libs/function/timeFormat.js';
/**
* 时间戳转为多久之前
* @param String timestamp 时间戳
* @param String | Boolean format 如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
* 如果为布尔值false无论什么时间都返回多久以前的格式
*/
function timeFrom(dateTime = null, format = 'yyyy-mm-dd') {
// 如果为null,则格式化当前时间
if (!dateTime) dateTime = Number(new Date());
// 如果dateTime长度为10或者13则为秒和毫秒的时间戳如果超过13位则为其他的时间格式
if (dateTime.toString().length == 10) dateTime *= 1000;
let timestamp = + new Date(Number(dateTime));
let timer = (Number(new Date()) - timestamp) / 1000;
// 如果小于5分钟,则返回"刚刚",其他以此类推
let tips = '';
switch (true) {
case timer < 300:
tips = '刚刚';
break;
case timer >= 300 && timer < 3600:
tips = parseInt(timer / 60) + '分钟前';
break;
case timer >= 3600 && timer < 86400:
tips = parseInt(timer / 3600) + '小时前';
break;
case timer >= 86400 && timer < 2592000:
tips = parseInt(timer / 86400) + '天前';
break;
default:
// 如果format为false则无论什么时间戳都显示xx之前
if(format === false) {
if(timer >= 2592000 && timer < 365 * 86400) {
tips = parseInt(timer / (86400 * 30)) + '个月前';
} else {
tips = parseInt(timer / (86400 * 365)) + '年前';
}
} else {
tips = timeFormat(timestamp, format);
}
}
return tips;
}
export default timeFrom;

9
libs/function/toast.js Normal file
View File

@@ -0,0 +1,9 @@
function toast(title, duration = 1500) {
uni.showToast({
title: title,
icon: 'none',
duration: duration
})
}
export default toast

15
libs/function/trim.js Normal file
View File

@@ -0,0 +1,15 @@
function trim(str, pos = 'both') {
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, "");
} else if (pos == "left") {
return str.replace(/^\s*/, '');
} else if (pos == 'right') {
return str.replace(/(\s*$)/g, "");
} else if (pos == 'all') {
return str.replace(/\s+/g, "");
} else {
return str;
}
}
export default trim

View File

@@ -0,0 +1,35 @@
/**
* 根据主题type值,获取对应的图标
* @param String type 主题名称,primary|info|error|warning|success
* @param String fill 是否使用fill填充实体的图标
*/
function type2icon(type = 'success', fill = false) {
// 如果非预置值,默认为success
if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success';
let iconName = '';
// 目前(2019-12-12),info和primary使用同一个图标
switch (type) {
case 'primary':
iconName = 'info-circle';
break;
case 'info':
iconName = 'info-circle';
break;
case 'error':
iconName = 'close-circle';
break;
case 'warning':
iconName = 'error-circle';
break;
case 'success':
iconName = 'checkmark-circle';
break;
default:
iconName = 'checkmark-circle';
}
// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
if (fill) iconName += '-fill';
return iconName;
}
export default type2icon

64
libs/mixin/mixin.js Normal file
View File

@@ -0,0 +1,64 @@
module.exports = {
data() {
return {}
},
onLoad() {
// getRect挂载到$u上因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
this.$u.getRect = this.$uGetRect
},
methods: {
// 查询节点信息
// 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸为支付宝的bug(2020-07-21)
// 解决办法为在组件根部再套一个没有任何作用的view元素
$uGetRect(selector, all) {
return new Promise(resolve => {
uni.createSelectorQuery().
in(this)[all ? 'selectAll' : 'select'](selector)
.boundingClientRect(rect => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
})
.exec()
})
},
getParentData(parentName = '') {
// 避免在created中去定义parent变量
if(!this.parent) this.parent = false;
// 这里的本质原理是,通过获取父组件实例(也即u-radio-group的this)
// 将父组件this中对应的参数赋值给本组件(u-radio的this)的parentData对象中对应的属性
// 之所以需要这么做是因为所有端中头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
this.parent = this.$u.$parent.call(this, parentName);
if(this.parent) {
// 历遍parentData中的属性将parent中的同名属性赋值给parentData
Object.keys(this.parentData).map(key => {
this.parentData[key] = this.parent[key];
});
}
},
// 阻止事件冒泡
preventEvent(e) {
e && e.stopPropagation && e.stopPropagation()
}
},
onReachBottom() {
uni.$emit('uOnReachBottom')
},
beforeDestroy() {
// 判断当前页面是否存在parent和children一般在checkbox和checkbox-group父子联动的场景会有此情况
// 组件销毁时移除子组件在父组件children数组中的实例释放资源避免数据混乱
if(this.parent && uni.$u.test.array(this.parent.children)) {
// 组件销毁时移除父组件中的children数组中对应的实例
const childrenList = this.parent.children
childrenList.map((child, index) => {
// 如果相等,则移除
if(child === this) {
childrenList.splice(index, 1)
}
})
}
}
}

18
libs/mixin/mpShare.js Normal file
View File

@@ -0,0 +1,18 @@
module.exports = {
onLoad() {
// 设置默认的转发参数
this.$u.mpShare = {
title: '', // 默认为小程序名称
path: '', // 默认为当前页面路径
imageUrl: '' // 默认为当前页面的截图
}
},
onShareAppMessage() {
return this.$u.mpShare
},
// #ifdef MP-WEIXIN
onShareTimeline() {
return this.$u.mpShare
}
// #endif
}

169
libs/request/index.js Normal file
View File

@@ -0,0 +1,169 @@
import deepMerge from "../function/deepMerge";
import validate from "../function/test";
class Request {
// 设置全局默认配置
setConfig(customConfig) {
// 深度合并对象,否则会造成对象深层属性丢失
this.config = deepMerge(this.config, customConfig);
}
// 主要请求部分
request(options = {}) {
// 检查请求拦截
if (this.interceptor.request && typeof this.interceptor.request === 'function') {
let tmpConfig = {};
let interceptorRequest = this.interceptor.request(options);
if (interceptorRequest === false) {
// 返回一个处于pending状态中的Promise来取消原promise避免进入then()回调
return new Promise(()=>{});
}
this.options = interceptorRequest;
}
options.dataType = options.dataType || this.config.dataType;
options.responseType = options.responseType || this.config.responseType;
options.url = options.url || '';
options.params = options.params || {};
options.header = Object.assign({}, this.config.header, options.header);
options.method = options.method || this.config.method;
return new Promise((resolve, reject) => {
options.complete = (response) => {
// 请求返回后隐藏loading(如果请求返回快的话可能会没有loading)
uni.hideLoading();
// 清除定时器如果请求回来了就无需loading
clearTimeout(this.config.timer);
this.config.timer = null;
// 判断用户对拦截返回数据的要求如果originalData为true返回所有的数据(response)到拦截器否则只返回response.data
if(this.config.originalData) {
// 判断是否存在拦截器
if (this.interceptor.response && typeof this.interceptor.response === 'function') {
let resInterceptors = this.interceptor.response(response);
// 如果拦截器不返回false就将拦截器返回的内容给this.$u.post的then回调
if (resInterceptors !== false) {
resolve(resInterceptors);
} else {
// 如果拦截器返回false意味着拦截器定义者认为返回有问题直接接入catch回调
reject(response);
}
} else {
// 如果要求返回原始数据,就算没有拦截器,也返回最原始的数据
resolve(response);
}
} else {
if (response.statusCode == 200) {
if (this.interceptor.response && typeof this.interceptor.response === 'function') {
let resInterceptors = this.interceptor.response(response.data);
if (resInterceptors !== false) {
resolve(resInterceptors);
} else {
reject(response.data);
}
} else {
// 如果不是返回原始数据(originalData=false)且没有拦截器的情况下返回纯数据给then回调
resolve(response.data);
}
} else {
// 不返回原始数据的情况下服务器状态码不为200modal弹框提示
// if(response.errMsg) {
// uni.showModal({
// title: response.errMsg
// });
// }
reject(response)
}
}
}
// 判断用户传递的URL是否/开头,如果不是,加上/这里使用了uView的test.js验证库的url()方法
options.url = validate.url(options.url) ? options.url : (this.config.baseUrl + (options.url.indexOf('/') == 0 ?
options.url : '/' + options.url));
// 是否显示loading
// 加一个是否已有timer定时器的判断否则有两个同时请求的时候后者会清除前者的定时器id
// 而没有清除前者的定时器导致前者超时一直显示loading
if(this.config.showLoading && !this.config.timer) {
this.config.timer = setTimeout(() => {
uni.showLoading({
title: this.config.loadingText,
mask: this.config.loadingMask
})
this.config.timer = null;
}, this.config.loadingTime);
}
uni.request(options);
})
// .catch(res => {
// // 如果返回reject()不让其进入this.$u.post().then().catch()后面的catct()
// // 因为很多人都会忘了写后面的catch()导致报错捕获不到catch
// return new Promise(()=>{});
// })
}
constructor() {
this.config = {
baseUrl: '', // 请求的根域名
// 默认的请求头
header: {},
method: 'POST',
// 设置为json返回后uni.request会对数据进行一次JSON.parse
dataType: 'json',
// 此参数无需处理因为5+和支付宝小程序不支持默认为text即可
responseType: 'text',
showLoading: true, // 是否显示请求中的loading
loadingText: '请求中...',
loadingTime: 800, // 在此时间内请求还没回来的话就显示加载中动画单位ms
timer: null, // 定时器
originalData: false, // 是否在拦截器中返回服务端的原始数据,见文档说明
loadingMask: true, // 展示loading的时候是否给一个透明的蒙层防止触摸穿透
}
// 拦截器
this.interceptor = {
// 请求前的拦截
request: null,
// 请求后的拦截
response: null
}
// get请求
this.get = (url, data = {}, header = {}) => {
return this.request({
method: 'GET',
url,
header,
data
})
}
// post请求
this.post = (url, data = {}, header = {}) => {
return this.request({
url,
method: 'POST',
header,
data
})
}
// put请求不支持支付宝小程序(HX2.6.15)
this.put = (url, data = {}, header = {}) => {
return this.request({
url,
method: 'PUT',
header,
data
})
}
// delete请求不支持支付宝和头条小程序(HX2.6.15)
this.delete = (url, data = {}, header = {}) => {
return this.request({
url,
method: 'DELETE',
header,
data
})
}
}
}
export default new Request

19
libs/store/index.js Normal file
View File

@@ -0,0 +1,19 @@
// 暂时不用vuex模块方式实现将该方法直接放入到/store/index.js中
const module = {
actions: {
$uStore({rootState}, params) {
let nameArr = params.name.split('.');
if(nameArr.length >= 2) {
let obj = rootState[nameArr[0]];
for(let i = 1; i < nameArr.length - 1; i ++) {
obj = obj[nameArr[i]];
}
obj[nameArr[nameArr.length - 1]] = params.value;
} else {
rootState[params.name] = params.value;
}
}
}
}
export default module

1
libs/util/area.js Normal file

File diff suppressed because one or more lines are too long

1356
libs/util/async-validator.js Normal file

File diff suppressed because it is too large Load Diff

1
libs/util/city.js Normal file

File diff suppressed because one or more lines are too long

51
libs/util/emitter.js Normal file
View File

@@ -0,0 +1,51 @@
/**
* 递归使用 call 方式this指向
* @param componentName // 需要找的组件的名称
* @param eventName // 事件名称
* @param params // 需要传递的参数
*/
function broadcast(componentName, eventName, params) {
// 循环子节点找到名称一样的子节点 否则 递归 当前子节点
this.$children.map(child=>{
if (componentName===child.$options.name) {
child.$emit.apply(child,[eventName].concat(params))
}else {
broadcast.apply(child,[componentName,eventName].concat(params))
}
})
}
export default {
methods: {
/**
* 派发 (向上查找) (一个)
* @param componentName // 需要找的组件的名称
* @param eventName // 事件名称
* @param params // 需要传递的参数
*/
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;//$parent 找到最近的父节点 $root 根节点
let name = parent.$options.name; // 获取当前组件实例的name
// 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点
// 循环出当前名称的一样的组件实例
while (parent && (!name||name!==componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
// 有节点表示当前找到了name一样的实例
if (parent) {
parent.$emit.apply(parent,[eventName].concat(params))
}
},
/**
* 广播 (向下查找) (广播多个)
* @param componentName // 需要找的组件的名称
* @param eventName // 事件名称
* @param params // 需要传递的参数
*/
broadcast(componentName, eventName, params) {
broadcast.call(this,componentName, eventName, params)
}
}
}

1
libs/util/province.js Normal file
View File

@@ -0,0 +1 @@
var provinceData=[{"label":"北京市","value":"11"},{"label":"天津市","value":"12"},{"label":"河北省","value":"13"},{"label":"山西省","value":"14"},{"label":"内蒙古自治区","value":"15"},{"label":"辽宁省","value":"21"},{"label":"吉林省","value":"22"},{"label":"黑龙江省","value":"23"},{"label":"上海市","value":"31"},{"label":"江苏省","value":"32"},{"label":"浙江省","value":"33"},{"label":"安徽省","value":"34"},{"label":"福建省","value":"35"},{"label":"江西省","value":"36"},{"label":"山东省","value":"37"},{"label":"河南省","value":"41"},{"label":"湖北省","value":"42"},{"label":"湖南省","value":"43"},{"label":"广东省","value":"44"},{"label":"广西壮族自治区","value":"45"},{"label":"海南省","value":"46"},{"label":"重庆市","value":"50"},{"label":"四川省","value":"51"},{"label":"贵州省","value":"52"},{"label":"云南省","value":"53"},{"label":"西藏自治区","value":"54"},{"label":"陕西省","value":"61"},{"label":"甘肃省","value":"62"},{"label":"青海省","value":"63"},{"label":"宁夏回族自治区","value":"64"},{"label":"新疆维吾尔自治区","value":"65"},{"label":"台湾","value":"66"},{"label":"香港","value":"67"},{"label":"澳门","value":"68"}];export default provinceData;

28
main.js Normal file
View File

@@ -0,0 +1,28 @@
import App from './App'
import store from './store'
// #ifndef VUE3
import Vue from 'vue'
Vue.prototype.$store = store
import uView from "uview-ui";
Vue.use(uView);
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
store,
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

97
manifest.json Normal file
View File

@@ -0,0 +1,97 @@
{
"name" : "经纬度上传",
"appid" : "__UNI__D070EBB",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : 101,
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {
"Maps" : {},
"Geolocation" : {}
},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {
"geolocation" : {
"amap" : {
"__platform__" : [ "ios", "android" ],
"appkey_ios" : "6afc5834f157391fb71ea8c11b89308f",
"appkey_android" : "6afc5834f157391fb71ea8c11b89308f"
}
},
"ad" : {},
"maps" : {
"amap" : {
"appkey_ios" : "6afc5834f157391fb71ea8c11b89308f",
"appkey_android" : "6afc5834f157391fb71ea8c11b89308f"
}
}
}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wx10a465f1cd9ec501",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "获取用户位置"
}
}
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2"
}
/* */

34
package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "uView",
"version": "1.8.6",
"description": "uView UI是uni-app生态优秀的UI框架全面的组件和便捷的工具会让您信手拈来如鱼得水",
"main": "index.js",
"keywords": [
"uview",
"ui",
"uni-app"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": ""
},
"devDependencies": {
"node-sass": "^4.14.0",
"sass-loader": "^8.0.2"
},
"author": "uView",
"license": "MIT",
"id": "uview-v1",
"dcloudext": {
"category": [
"前端组件",
"通用组件"
]
},
"dependencies": {
"dayjs": "^1.11.3"
}
}

35
pages.json Normal file
View File

@@ -0,0 +1,35 @@
{
"easycom": {
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
},
"pages": [ //pages数组中第一项表示应用启动页参考https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "经纬度拾取",
"navigationStyle": "custom", // 隐藏系统导航栏
"navigationBarTextStyle": "white" // 状态栏字体为白色,只能为 white-白色black-黑色 二选一
}
}, {
"path": "pages/listPage/listPage",
"style": {
"navigationBarTitleText": "设备列表",
"enablePullDownRefresh": false
}
}, {
"path": "pages/addEquip/addEquip",
"style": {
"navigationBarTitleText": "新增设备",
"enablePullDownRefresh": false
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "经纬度拾取",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}

291
pages/addEquip/addEquip.vue Normal file
View File

@@ -0,0 +1,291 @@
<template>
<view>
<u-form :model="cameara" ref="uForm">
<u-form-item label-width="150" label="设备编号">
<u-input v-model="cameara.number" disabled='true' />
</u-form-item>
<u-form-item label-width="150" label="设备类型">
<u-input v-model="cameara.type" disabled='true' />
</u-form-item>
<u-field v-model="cameara.remarks" label="备注" placeholder="备注" type="textarea">
</u-field>
<view>
<view class="uploadImages">
<view v-for="(item,index) in imgList" class="imgBox">
<image mode="aspectFit" :src="item.url"></image>
<uni-icons type="trash" size="22" @click="removeImage(index)"></uni-icons>
</view>
<uni-icons type="plusempty" size="48" @click="selectBase64Img"></uni-icons>
</view>
</view>
</u-form>
<u-button v-if="cameara.id!=''" class="btn1" type="primary" @click="updateAction(1)">更新</u-button>
<u-button v-if="cameara.id==''" class="btn1" type="primary" @click="updateAction(2)">新增</u-button>
</view>
</template>
<script>
import config from "../../libs/config/config.js"
const _api = config.api + "/upload"
export default {
data() {
return {
cameara: {
id: "",
camera_name: "",
camera_code: "",
longitude: "",
latitude: "",
images: "",
remarks: "",
type: "",
},
radioList: [{
name: '摄像头',
disabled: false
},
{
name: '消防栓',
disabled: false
},
{
name: '消防炮',
disabled: false
},
{
name: '泡沫栓',
disabled: false
},
{
name: '栓炮一体',
disabled: false
},
{
name: '手动报警器',
disabled: false
},
{
name: '消防器材箱',
disabled: false
}
],
radio: '',
switchVal: false,
action: _api,
fileList: [],
uploadList: [],
camearaDetails: null,
imgList: [],
};
},
onLoad(options) {
if (options.cameara) {
this.cameara = JSON.parse(options.cameara)
uni.setNavigationBarTitle({
title: '编辑'
})
this.getById(this.cameara.id)
}
},
onShow() {
this.cameara = this.$store.state.equip.currentData
if(this.$store.state.equip.currentDataDetails){
if(this.$store.state.equip.currentDataDetails.remarks){
this.cameara.remarks=this.$store.state.equip.currentDataDetails.remarks
}
}
},
methods: {
getImgByDocId(docIdList) {
let _this =this;
docIdList.map((item) => {
_this.$u.post(config.api + '/xfhrshInternet/service/selImageBase64', JSON.stringify({
docId: item.docId,
})).then(res => {
console.log(res)
if(res.code==0){
_this.imgList.push({
url: res.data,
docId: item.docId
})
}
});
})
},
removeImage(index) {
this.imgList=this.imgList.splice(index, 1)
console.log(index)
},
selectBase64Img() {
let _this = this
uni.chooseImage({
count: 1, //默认9
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], //从相册选择
success: function(res) {
const tempFilePath = res.tempFilePaths[0]
_this.imgUrlToBase64(tempFilePath)
}
});
},
uploadImg() {
uni.chooseImage({
count: 1,
success: (res) => {
console.log(res, '返回临时路径');
const tempFilePath = res.tempFilePaths[0]
this.imgUrlToBase64(tempFilePath)
}
})
},
imgUrlToBase64(url) {
let _this = this;
// #ifdef MP-WEIXIN
uni.getFileSystemManager().readFile({
filePath: url, //选择图片返回的相对路径
encoding: 'base64', //编码格式
success: res => { //成功的回调
console.log(res, '返回结果');
let base64 = 'data:image/png;base64,' + res.data //不加上这串字符,在页面无法显示的哦
console.log(base64)
this.$u.post(config.api + '/xfhrshInternet/service/uploadImage', JSON.stringify({
fileName: uni.$u.guid(20) + '.png',
data: base64
})).then(res => {
console.log()
_this.imgList.push({
url: base64,
docId: res.docId
})
//this.camera_list = res;
});
},
fail: (e) => {
console.log("图片转换失败");
}
})
// #endif
// #ifdef H5
uni.request({
url: url,
method: 'GET',
responseType: 'arraybuffer',
success: ress => {
let base64 = uni.arrayBufferToBase64(ress.data); //把arraybuffer转成base64
base64 = 'data:image/png;base64,' + base64;
console.log(base64, '转换结果')
this.imgURL = base64
},
fail: (e) => {
console.log("图片转换失败");
}
})
// #endif
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(url, (entry) => {
// 可通过entry对象操作test.html文件
entry.file((file) => {
let fileReader = new plus.io.FileReader();
fileReader.onloadend = (evt) => {
const base64 = evt.target.result.substr(22);
this.imgURL = base64
}
fileReader.readAsDataURL(file);
});
}, (e) => {
alert("Resolve file URL failed: " + e.message);
});
// #endif
},
getById(id) {
this.$u.post(config.api + '/xfhrshInternet/service/selPicture', {
subjectId: id
}).then(res => {
console.log(res)
if (res.status = "success") {
if(res.data.length>0){
this.$store.dispatch('setCurrentEquipDtails', {
data: res.data[0]
})
this.getImgByDocId(res.data[0].docIds)
}
}
//this.camera_list = res;
});
},
// 图片自动上传成功的钩子
onSuccess(data) {
console.log("========================onSuccess")
let files = [];
// 通过filter过滤数组筛选出上传进度为100的文件
files = this.$refs.uUpload.lists.filter(val => {
return val.progress == 100;
})
// 如果不需要进行太多的处理,直接如下即可
files = this.$refs.uUpload.lists;
console.log(files);
this.fileList.push({
url: data.url
});
},
clickAction() {
uni.$emit('updateCamera', {
cameara: this.cameara
})
uni.navigateBack()
},
onRemove(index) {
console.log('图片已被移除', index);
this.fileList.splice(index, 1); // arr.splice(i,n) 删除从i(索引值)开始之后的那个元素。返回值是删除的元素
},
updateAction(index) {
console.log(this.$store.state.equip.currentData)
let _data = {
subjectId: this.$store.state.equip.currentData.id,
remarks: this.cameara.remarks,
docId: []
}
this.imgList.map((item) => {
_data.docId.push(item.docId)
})
_data.docId=JSON.stringify(_data.docId)
this.$store.dispatch('updateEquipDetails', {data: _data})
}
}
};
</script>
<style lang="scss">
.uploadImages {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
}
.imgBox {
width: 200rpx;
height: 200rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
image {
width: 180rpx;
height: 180rpx;
}
}
.u-btn {
margin-top: 40rpx;
}
</style>

639
pages/index/index.vue Normal file
View File

@@ -0,0 +1,639 @@
<template>
<view>
<u-navbar :is-back="isBack" title="经纬度拾取">
</u-navbar>
<map style="width: 100vw; height: 40vh;" id="maps" class="map" :show-location="true"
:enable-satellite="satellite" :theme="theme" :scale="scale" :markers="markers" @regionchange="regionchange"
@tap="tapAction"></map>
<view class="selectPointPosition">
<view>已选位置:</view>
<view>经度{{longitude}}纬度{{latitude}}</view>
</view>
<view class="cameraInfo">
<view class="btnListBox">
<view>操作:</view>
<u-icon name="map-fill" size="56" @click="reGetLocation()" style="margin-right: 20rpx;"></u-icon>
</view>
<u-section title="已选择设备" :sub-title="cameara.number==null?'请选择':cameara.number"
@click="goToSelectCamera()"></u-section>
<view class="cameraItem" v-if="cameara.number!=null">
<view class="editItem">
<view class="editItemBtn" @click="goToEdit()">
<u-icon name="edit-pen" size="36" style="margin-right: 20rpx;"></u-icon>
<text>编辑其它信息</text>
</view>
</view>
<view class="cameraItemLi">
<view>设备编码</view>
<view>{{cameara.number}}</view>
</view>
<view class="cameraItemLi">
<view>设备类型</view>
<view>{{cameara.type}}</view>
</view>
<view class="cameraItemLi">
<view>纬度</view>
<view v-if="cameara.latitude">{{cameara.latitude}}</view>
<view class="unKnow" v-if="!cameara.latitude"></view>
</view>
<view class="cameraItemLi">
<view>经度</view>
<view v-if="cameara.longitude">{{cameara.longitude}}</view>
<view class="unKnow" v-if="!cameara.longitude"></view>
</view>
<view class="btnList">
<u-button size="mini" class="btn" type="info" @click="moveTo(10000)">聚焦历史点位</u-button>
<u-button size="mini" class="btn" type="info" @click="moveTo(100)">聚焦手选位置</u-button>
<u-button size="mini" class="btn" type="info" @click="moveTo(1)">聚焦当前位置</u-button>
</view>
<view class="btnList1">
<u-button size="mini" type="primary" class="btn" @click="updateAction(0)">设置黑色点为当前设备位置</u-button>
<u-button size="mini" type="primary" class="btn" @click="updateAction(1)">设置红色点为当前设备位置</u-button>
</view>
</view>
</view>
<view class="tipsList">
<view></view>
<view>黑色图标为用户当前位置</view>
<view>红色图标为用户手动选中位置点击地图可以收到选择</view>
<view>黄色图标为设备旧位置</view>
</view>
<view v-for="item in logs">
{{item}}
</view>
<u-popup v-model="leftModelShow">
<u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search>
<view>出淤泥而不染濯清涟而不妖</view>
</u-popup>
<u-action-sheet :list="list" v-model="showActionList"></u-action-sheet>
</view>
</template>
<script>
import dayjs from "dayjs"
import config from "../../libs/config/config.js"
export default {
data() {
return {
theme: 'satellite',
satellite: true,
isBack: false,
leftModelShow: false,
latitude: "", // 默认定在首都
longitude: "",
scale: 16, // 默认16
markers: [],
markerHeight: 30,
timer: null,
logs: [],
keyword: '',
accuracy: '', //位置的精确度
altitude: '', //高度,单位 m
horizontalAccuracy: '', //水平精度,单位 m
cameara: {
id: "",
camera_name: "",
camera_code: "",
longitude: "",
latitude: "",
images: "",
remarks: "",
type: "",
},
showActionList: false,
list: [{
text: '点赞',
color: 'blue',
fontSize: 28,
subText: '感谢您的点赞'
}, {
text: '分享'
}, {
text: '评论'
}],
};
},
onShow() {
const returnedTarget = Object.assign(this.$store.state.equip.currentData, this.$store.state.equip.currentDataDetails);
this.cameara=returnedTarget
this.$store.dispatch('queryCurrentDataDetails', {})
},
onReady() {
this._mapContext = uni.createMapContext("maps", this);
this.timer = setInterval(() => {
//this.getLocationInfo()
}, 2000)
this.getLocationInfo()
//this.bindMapClickAction();
// this.addMarkers();
},
methods: {
tapAction(e) {
console.log(e)
let point = e.detail;
point.iconPath = '../../static/address_2.png';
point.id = 100
this.latitude = point.latitude;
this.longitude = point.longitude;
this._mapContext.moveToLocation({
latitude: point.latitude,
longitude: point.longitude,
})
let list = JSON.parse(JSON.stringify(this.markers));
let isFrist = 0;
list.forEach(function(item) {
if (item.id == 100) {
isFrist = 1;
item.latitude = point.latitude;
item.longitude = point.longitude;
} else {
}
});
if (isFrist == 0) {
this.markers = this.markers.concat(point);
} else {
this.markers = list;
}
},
bindMapClickAction() {
var that = this;
var maps = this._mapContext.$getAppMap();
maps.onclick = function(point) {
console.log(point);
point.iconPath = '../../static/address_2.png';
point.id = 100
that.latitude = point.latitude;
that.longitude = point.longitude;
that._mapContext.moveToLocation({
latitude: point.latitude,
longitude: point.longitude,
})
let list = JSON.parse(JSON.stringify(that.markers));
let isFrist = 0;
list.forEach(function(item) {
if (item.id == 100) {
isFrist = 1;
item.latitude = point.latitude;
item.longitude = point.longitude;
} else {
}
});
if (isFrist == 0) {
that.markers = that.markers.concat(point);
} else {
that.markers = list;
}
//that.markers = list;
console.log(_item)
// if (_item.length > 0) {
// console.log("=============平移:", point.latitude)
// console.log("=============平移:", point.longitude)
// that._mapContext.translateMarker({
// markerId: 100,
// destination: {
// latitude: point.latitude,
// longitude: point.longitude,
// },
// fail(e) {
// console.log(e)
// }
// })
// } else {
// console.log("=============新增")
// that.markers = that.markers.concat(point);
// }
};
},
goToEdit() {
let url = "../addEquip/addEquip?cameara=" + JSON.stringify(this.cameara)
uni.navigateTo({
url: url
});
},
add() {
uni.navigateTo({
url: '../addEquip/addEquip'
});
},
updateAction(index) {
let cameara = JSON.parse(JSON.stringify(this.cameara))
let selfClick = this.markers.filter(item => {
if (item.id == 100) {
return item
}
})
selfClick = selfClick[0];
console.log("=========================selfClick:", JSON.stringify(selfClick))
let selfAuto = this.markers.filter(item => {
if (item.id == 1) {
return item
}
})
selfAuto = selfAuto[0];
console.log("=========================selfAuto:", JSON.stringify(selfAuto))
this.cameara = cameara;
// 用户当前位置
if (index == 0) {
cameara.latitude = selfAuto.latitude;
cameara.longitude = selfAuto.longitude;
} else {
// 用户手选位置
cameara.latitude = selfClick.latitude;
cameara.longitude = selfClick.longitude;
}
this.$store.dispatch('updateEquipDetails', {data: cameara})
},
// 拖拽地图
regionchange(event) {
console.log("========中心点改变1")
if (event.type == 'regionchange') {
//this.getCenterLanLat()
}
},
// 获取当前地图中心的经纬度
getCenterLanLat() {
console.log("========中心点改变2")
let self = this
self._mapContext.getCenterLocation({
altitude:true,
type: 'gcj02',
success: (res) => {
let address = {
latitude: res.latitude,
longitude: res.longitude
}
let list = JSON.parse(JSON.stringify(self.markers));
let isFrist = 0;
list.forEach(function(item) {
if (item.id == 1000) {
isFrist = 1;
item.latitude = res.latitude;
item.longitude = res.longitude;
}
});
if (isFrist == 0) {
console.log("========中心点改变20")
self.markers = self.markers.concat({
id: 1000,
width: 50,
height: 50,
latitude: address.latitude,
longitude: address.longitude,
iconPath: "../../static/address_3.png",
});
} else {
console.log("========中心点改变21")
self.markers = list;
}
},
fail: (err) => {
// console.log('获取当前地图中心的经纬度2', err);
}
})
},
tapMap(e) {
var that = this;
var id = e.currentTarget.id;
var maps = this._mapContext.$getAppMap();
console.log("============maps:", maps)
maps.onclick = function(point) {
console.log(point);
point.iconPath = '../../static/address_2.png';
point.id = 100
that._mapContext.moveToLocation({
latitude: point.latitude,
longitude: point.longitude,
})
let list = JSON.parse(JSON.stringify(that.markers));
let isFrist = 0;
list.forEach(function(item) {
if (item.id == 100) {
isFrist = 1;
item.latitude = point.latitude;
item.longitude = point.longitude;
} else {
}
});
if (isFrist == 0) {
that.markers = that.markers.concat(point);
} else {
that.markers = list;
}
//that.markers = list;
console.log(_item)
// if (_item.length > 0) {
// console.log("=============平移:", point.latitude)
// console.log("=============平移:", point.longitude)
// that._mapContext.translateMarker({
// markerId: 100,
// destination: {
// latitude: point.latitude,
// longitude: point.longitude,
// },
// fail(e) {
// console.log(e)
// }
// })
// } else {
// console.log("=============新增")
// that.markers = that.markers.concat(point);
// }
};
},
goToSelectCamera() {
uni.navigateTo({
url: '../listPage/listPage'
});
console.log("获取周围美食");
},
reGetLocation() {
this.getLocationInfo();
},
// 确认授权后,获取用户位置
getLocationInfo() {
const that = this;
uni.getLocation({
isHighAccuracy: true,
highAccuracyExpireTime: 3500,
accuracy: 'best',
altitude:true,
type: "gcj02",
success: function(res) {
console.log("============================定位结果res:",res);
// 暂时
that.longitude = res.longitude; //118.787575;
that.latitude = res.latitude; //32.05024;
//精确度信息
that.accuracy = res.accuracy;
that.altitude = res.altitude;
that.horizontalAccuracy = res.horizontalAccuracy;
that._mapContext.moveToLocation({
latitude: res.latitude,
longitude: res.longitude,
})
let list = JSON.parse(JSON.stringify(that.markers));
let isFrist = 0;
list.forEach(function(item) {
if (item.id == 1) {
isFrist = 1;
item.latitude = res.latitude;
item.longitude = res.longitude;
} else {
}
});
if (isFrist == 0) {
that.markers.push({
id: 1,
width: 50,
height: 50,
latitude: res.latitude,
longitude: res.longitude,
iconPath: "../../static/address_1.png",
width: that.markerHeight, //宽
height: that.markerHeight, //高
color: '#FFFFFF'
})
} else {
that.markers = list;
}
},
fail(err) {
uni.showToast({
icon: "error",
title: "获取位置出错"
})
console.log(err)
}
});
},
getList() {
console.log("获取周围美食");
console.log(this.markers.length)
},
historyPosition() {
const returnedTarget = Object.assign(this.$store.state.equip.currentData, this.$store.state.equip.currentDataDetails);
let cameara=returnedTarget
if (cameara.longitude) {
console.log("==============更新旧点")
// cameara.longitude = info[0];
// cameara.latitude = info[1];
let point = {};
point.iconPath = '../../static/address_4.png';
point.id = 10000
point.latitude = cameara.latitude;
point.longitude = cameara.longitude;
let list = JSON.parse(JSON.stringify(this.markers));
let isFrist = 0;
list.forEach(function(item) {
if (item.id == 10000) {
isFrist = 1;
item.latitude = cameara.latitude;
item.longitude = cameara.longitude;
}
});
if (isFrist == 0) {
this.markers = this.markers.concat(point);
} else {
this.markers = list;
}
console.log("=====================this.markers:", JSON.stringify(this.markers))
} else {
let list = JSON.parse(JSON.stringify(this.markers));
let _index = 10000;
list.forEach(function(item, index) {
if (item.id == 10000) {
_index = index
}
});
if (_index != 10000) {
list.splice(_index, _index)
}
this.markers = list;
}
},
moveTo(index) {
let point_list = this.markers.filter(item => {
if (item.id == index) {
return item
}
})
let point = point_list[0];
if (point_list.length > 0) {
this._mapContext.moveToLocation({
latitude: point.latitude,
longitude: point.longitude,
})
}
},
getById(id) {
this.$u.post(config.api + '/xfhrshInternet/service/selPicture', {
subjectId: id
}).then(res => {
if(res.code==0){
this.$store.dispatch('setCurrentEquipDtails', {data: res.data[0]})
}
//this.camera_list = res;
});
},
},
onUnload: function() {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
},
onLoad() {
// 监听事件
uni.$on('updateCamera', (cameara) => {
setTimeout(() => {
this.historyPosition()
}, 2500)
})
},
onUnload() {
// 移除监听事件
uni.$off('updateCamera');
},
};
</script>
<style lang="scss">
.u-nav-slot {
padding: 0 20rpx 0 0;
}
.cameraInfo {
margin-top: 20rpx;
margin-bottom: 20rpx;
.btnListBox {
display: flex;
width: 300rpx;
justify-content: space-around;
align-items: center;
margin-bottom: 20rpx;
margin-left: 10rpx;
}
}
.cameraItem {
margin: 0 24rpx;
margin-top: 30rpx !important;
border: 1px solid #A3A8A8;
border-radius: 10rpx;
padding: 10rpx;
.editItem {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin: 20rpx 0;
.editItemBtn {
border: 1rpx solid #A3A8A8;
border-radius: 4rpx;
padding: 4rpx 4rpx;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
}
.cameraItemLi {
display: flex;
flex-direction: row;
justify-content: space-between;
.unKnow {
color: red;
}
}
}
.btn {
margin-top: 10rpx;
}
.btnList {
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-around;
}
.btnList1 {
display: flex;
flex-direction: row;
width: 100%;
justify-content: space-around;
}
.tipsList {
margin: 10rpx;
}
.selectPointPosition {
display: flex;
justify-content: space-between;
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="test">
<text>12313</text>
</div>
</template>
<script>
export default {
data() {
return {
}
},
methods: {
}
}
</script>
<style>
.test{
background-color: aqua;
}
</style>

242
pages/listPage/listPage.vue Normal file
View File

@@ -0,0 +1,242 @@
<template>
<view>
<u-search placeholder="请输入摄像机名称或者编码" v-model="keyword" @change="change"></u-search>
<view class="tagsList">
<view class="u-page__tag-item" v-for="(item, index) in radios" :key="index">
<u-tag :text="item.type==''?'全部':item.type" :type="item.checked?'success':'info'" :name="index" @click="radioClick(index)">
</u-tag>
</view>
</view>
<z-paging ref="paging" v-model="dataList" @query="queryList" default-page-no="0">
<view v-for="(item,index) in dataList" class="cameraItem">
<u-swipe-action @click="swiperActionClick" :options="options" :index="index"
@content-click="contentClick">
<view>
<view class="cameraItemLi">
<view>设备编号</view>
<view>{{item.number}}</view>
</view>
<view class="cameraItemLi">
<view>设备类型</view>
<view>{{item.type}}</view>
</view>
<view class="cameraItemLi">
<view>经度</view>
<view v-if="item.longitude">{{item.longitude}}</view>
<view class="unKnow" v-if="!item.longitude"></view>
</view>
</view>
</u-swipe-action>
</view>
</z-paging>
</view>
</template>
<script>
import config from "../../libs/config/config.js"
export default {
data() {
return {
radios: [{
checked: true,
type:''
},
{
checked: false,
type:'变焦枪机'
},
{
checked: false,
type:'高清固定摄像机'
},{
checked: false,
type:'标清固定摄像机'
},{
checked: false,
type:'高清云台摄像机'
},{
checked: false,
type:'标清云台摄像机'
},{
checked: false,
type:'消防栓'
},{
checked: false,
type:'消防炮'
},{
checked: false,
type:'泡沫栓'
},{
checked: false,
type:'炮栓一体'
},{
checked: false,
type:'手动报警器'
},{
checked: false,
type:'消防器材箱'
},
],
keyword: '',
dataList: [],
type:'',
options: [{
text: '编辑',
style: {
backgroundColor: '#3c9cff'
}
}, {
text: '删除',
style: {
backgroundColor: '#f56c6c'
}
}],
}
},
onShow() {
//this.$refs.paging.reload();
},
onLoad() {},
methods: {
contentClick(index) {
if (this.dataList[index].isDelete) {
uni.showToast({
icon: "none",
title: "当前设备已被删除"
})
return;
}
this.clickAction(this.dataList[index])
console.log("=====================contentClick", index)
},
swiperActionClick(index, index1) {
if (this.dataList[index].isDelete) {
uni.showToast({
icon: "none",
title: "当前设备已被删除"
})
return;
}
if (index1 == 0) {
console.log("编辑")
let url = "../addEquip/addEquip?cameara=" + JSON.stringify(this.dataList[index])
uni.navigateTo({
url: url
});
}
if (index1 == 1) {
console.log("删除")
console.log(this.dataList[index])
this.delete(index)
}
console.log("=============index:", index)
console.log("=============index1:", index1)
},
delete(index) {
this.$u.post(config.api + '/delete', {
id: this.dataList[index].id
}).then(res => {
if (res.status = "success") {
uni.showToast({
icon: "none",
title: "操作成功"
})
this.dataList[index].isDelete = true;
this.$refs.paging.reload();
}
});
},
queryList(pageNo, pageSize) {
//这里的pageNo和pageSize会自动计算好直接传给服务器即可
//这里的请求只是演示请替换成自己的项目的网络请求并在网络请求回调中通过this.$refs.paging.complete(请求回来的数组)将请求结果传给z-paging
this.$u.post(config.api + '/xfhrshInternet/service/selfireFacilities', {
type: this.type,
number: this.keyword,
page: String(parseFloat(pageNo) + 1),
row: String(pageSize),
}).then(res => {
console.log(typeof res)
console.log(res)
this.$refs.paging.complete(res.data);
});
// this.$request.queryList({
// pageNo,
// pageSize
// }).then(res => {
// //请勿在网络请求回调中给dataList赋值只需要调用complete就可以了
// this.$refs.paging.complete(res.data.list);
// }).catch(res => {
// //如果请求失败写this.$refs.paging.complete(false),会自动展示错误页面
// //注意每次都需要在catch中写这句话很麻烦z-paging提供了方案可以全局统一处理
// //在底层的网络请求抛出异常时写uni.$emit('z-paging-error-emit');即可
// this.$refs.paging.complete(false);
// })
},
clickAction(item) {
this.$store.dispatch('setCurrentEquip', {data: item})
uni.$emit('updateCamera', {
cameara: item
})
uni.navigateBack()
},
change(value) {
this.keyword = value;
this.$refs.paging.reload();
},
getData(key = "") {
this.$u.post(config.api + '/camera_list', {
keyword: this.keyword,
pageNum: 0,
pageSize: 10,
}).then(res => {
this.camera_list = res;
});
},
radioClick(name) {
console.log("==============name:",name)
this.radios.map((item, index) => {
if(index === name){
item.checked = true
this.type = item.type;
this.$refs.paging.reload();
}else{
item.checked = false
}
})
},
}
}
</script>
<style lang="scss">
.tagsList{
display: flex;
flex-wrap: wrap;
}
.z-paging-content {
margin-top: 250rpx;
}
.cameraItem {
margin: 0 24rpx;
margin-top: 30rpx !important;
border: 1px solid #A3A8A8;
border-radius: 10rpx;
padding: 10rpx;
.cameraItemLi {
display: flex;
flex-direction: row;
justify-content: space-between;
.unKnow {
color: red;
}
}
}
</style>

43
project.config.json Normal file
View File

@@ -0,0 +1,43 @@
{
"appid": "wxf073f43c226d810e",
"compileType": "miniprogram",
"libVersion": "2.24.6",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"urlCheck": true,
"coverView": true,
"es6": true,
"postcss": true,
"lazyloadPlaceholderEnable": false,
"preloadBackgroundData": false,
"minified": true,
"autoAudits": false,
"uglifyFileName": false,
"uploadWithSourceMap": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmManually": false,
"packNpmRelationList": [],
"minifyWXSS": true,
"useStaticServer": true,
"showES6CompileOption": false,
"checkInvalidKey": true,
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
},
"disableUseStrict": false,
"useCompilerPlugins": false,
"minifyWXML": true
},
"condition": {},
"editorSetting": {
"tabIndent": "insertSpaces",
"tabSize": 2
},
"description": "项目配置文件详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
}

View File

@@ -0,0 +1,7 @@
{
"projectname": "location_point",
"setting": {
"compileHotReLoad": true
},
"description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html"
}

BIN
static/address_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/address_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
static/address_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/address_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

12
store/index.js Normal file
View File

@@ -0,0 +1,12 @@
// 页面路径store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import equip from '@/store/modules/equip'
Vue.use(Vuex)
export default new Vuex.Store({
modules:{
equip
}
})

149
store/modules/equip.js Normal file
View File

@@ -0,0 +1,149 @@
import config from "@/libs/config/config.js"
export default {
state: {
timestamp: 1608820295,//初始时间戳
currentData:{
id:null,
number:null,
type:null,
},
currentDataDetails:{
docId: "",
docIds:[],
latitude: "",
longitude: "",
pictureId: null,
remarks: "",
subjectId: null,
},
oldDataDetails:{
docId: "",
docIds:[],
latitude: "",
longitude: "",
pictureId: null,
remarks: "",
subjectId: null,
},
},
getters: {
timeString(state) {//时间戳转换后的时间
var date = new Date(state.timestamp);
var year = date.getFullYear();
var mon = date.getMonth()+1;
var day = date.getDate();
var hours = date.getHours();
var minu = date.getMinutes();
var sec = date.getSeconds();
var trMon = mon<10 ? '0'+mon : mon
var trDay = day<10 ? '0'+day : day
return year+'-'+trMon+'-'+trDay+" "+hours+":"+minu+":"+sec;
}
},
mutations: {
updateTime(state){//更新当前时间戳
state.timestamp = Date.now()
},
set(state,payload){
state.currentData=payload.data;
},
setDtails(state,payload){
state.currentDataDetails=payload.data;
},
queryCurrentDataDetails(state,payload){
console.log("==========================queryCurrentDataDetails")
console.log(state.currentData)
uni.$u.post(config.api + '/xfhrshInternet/service/selPicture', {
subjectId:state.currentData.id
}).then(res => {
console.log(typeof res)
console.log(res)
if(res.code==0){
if(res.data.length>0){
state.currentDataDetails=res.data[0];
}else{
state.currentDataDetails=state.oldDataDetails;
}
}
});
},
updateEquipDetails(state,payload){
let _data=JSON.parse(JSON.stringify(payload.data))
delete _data.number
delete _data.type
console.log(_data)
_data.subjectId=_data.id
delete _data.id
if(_data.latitude){
_data.latitude=String(_data.latitude)
}else{
_data.latitude=state.currentDataDetails.latitude
}
if(_data.longitude){
_data.longitude=String(_data.longitude)
}else{
_data.longitude=state.currentDataDetails.longitude
}
if(_data.remarks){
}else{
_data.remarks=state.currentDataDetails.remarks
}
if(_data.docId){
}else{
_data.docId=state.currentDataDetails.docId
}
console.log("=================================state.currentDataDetails")
console.log(state.currentDataDetails)
if(state.currentDataDetails){
if(state.currentDataDetails.pictureId!=null){
_data.pictureId=state.currentDataDetails.pictureId;
}
}
_data.subjectId=state.currentData.id
uni.$u.post(config.api + '/xfhrshInternet/service/insertPicture', _data).then(res => {
console.log(typeof res)
console.log(res)
if(res.code==0){
uni.showToast({
icon: "success",
title: "操作成功"
})
uni.$emit('updateCamera', {
cameara: {}
})
}else{
uni.showToast({
icon: "error",
title: res.message
})
}
});
}
},
actions: {
setCurrentEquip(context , payload){
context.commit('set',payload)
},
setCurrentEquipDtails(context , payload){
context.commit('setDtails',payload)
},
updateEquipDetails(context , payload){
context.commit('updateEquipDetails',payload)
setTimeout(()=>{
context.commit('queryCurrentDataDetails',payload)
},500)
},
queryCurrentDataDetails(context , payload){
context.commit('queryCurrentDataDetails',payload)
},
updateCurrentDataDetails(context , payload){
context.commit('updateCurrentDataDetails',payload)
}
}
}

Some files were not shown because too many files have changed in this diff Show More