feat: 页面更新

master
xjs 2025-03-06 18:07:14 +08:00
parent 3e768c6477
commit 8f176e3201
94 changed files with 3867 additions and 9883 deletions

View File

@ -96,6 +96,9 @@ export default defineManifestConfig({
'mp-alipay': { 'mp-alipay': {
usingComponents: true, usingComponents: true,
styleIsolation: 'shared', styleIsolation: 'shared',
setting: {
minified: true,
},
}, },
'mp-baidu': { 'mp-baidu': {
usingComponents: true, usingComponents: true,

View File

@ -111,7 +111,7 @@
"qs": "6.5.3", "qs": "6.5.3",
"vue": "3.4.21", "vue": "3.4.21",
"wot-design-uni": "^1.4.0", "wot-design-uni": "^1.4.0",
"z-paging": "^2.8.4" "z-paging": "^2.8.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.6.1", "@commitlint/cli": "^18.6.1",

View File

@ -84,8 +84,8 @@ importers:
specifier: ^1.4.0 specifier: ^1.4.0
version: 1.4.0(vue@3.4.21(typescript@5.7.2)) version: 1.4.0(vue@3.4.21(typescript@5.7.2))
z-paging: z-paging:
specifier: ^2.8.4 specifier: ^2.8.5
version: 2.8.4 version: 2.8.5
devDependencies: devDependencies:
'@commitlint/cli': '@commitlint/cli':
specifier: ^18.6.1 specifier: ^18.6.1
@ -6653,8 +6653,8 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
z-paging@2.8.4: z-paging@2.8.5:
resolution: {integrity: sha512-967aWGPZsC9siwIoWtXH6L+9gCoKe29jZtW+AMD9NSfsfymPZjBBYu7owT/siUQyCaaNJ7uUbpAet+YgpMn0xA==} resolution: {integrity: sha512-tvMnwo1zudiFATbKYOEzejf9pI94UMXbK7A5tfq9khIJy8YBOLiexLNEJF/s+I7vqyLYwhALoI0cIarEe13kgw==}
engines: {HBuilderX: ^3.0.7} engines: {HBuilderX: ^3.0.7}
snapshots: snapshots:
@ -14812,4 +14812,4 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
z-paging@2.8.4: {} z-paging@2.8.5: {}

View File

@ -38,7 +38,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useToast } from 'wot-design-uni' import { useToast } from 'wot-design-uni'
import { useLogin } from '@/hooks/useUserInfo' import { useLogin } from '@/login-sub/hooks/useUserInfo'
import { import {
getSessionKey, getSessionKey,
getVolunteerInitialization, getVolunteerInitialization,

View File

@ -66,7 +66,10 @@
}, },
"mp-alipay": { "mp-alipay": {
"usingComponents": true, "usingComponents": true,
"styleIsolation": "shared" "styleIsolation": "shared",
"setting": {
"minified": true
}
}, },
"mp-baidu": { "mp-baidu": {
"usingComponents": true "usingComponents": true

View File

@ -0,0 +1,57 @@
<template>
<view class="">
<l-echart ref="echart"></l-echart>
</view>
</template>
<script setup>
import lEchart from '@/pages-evaluation-sub/uni_modules/lime-echart/components/l-echart/l-echart.vue'
const echarts = require('./uni_modules/lime-echart/static/echarts.min')
const echart = ref(null)
onMounted(() => {
console.log(echarts, echart.value)
echart.value.init(echarts, (chart) => {
let option = {
title: {
text: '某站点用户访问来源',
subtext: '纯属虚构',
left: 'center',
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '50%',
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
{ value: 300, name: '视频广告' },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
],
}
chart.setOption(option)
})
})
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,209 @@
## 1.0.02025-02-27
- fix: 修复uniappx微信小程序不显示问题
## 0.9.92025-02-24
- feat: 更新v4
## 0.9.82024-12-20
- fix: 修复 APP 无法放大问题
## 0.9.72024-12-02
- feat: uniapp 增加`landscape`,当`landscape`为`true`时旋转90deg达到横屏效果。
- feat: 支持uniapp x 微信小程序
## 0.9.62024-07-23
- fix: 修复 uni is not defined
## 0.9.52024-07-19
- chore: 鸿蒙`measureText`为异步,异步字体不正常,使用模拟方式。
## 0.9.42024-07-18
- chore: 更新文档
## 0.9.32024-07-16
- feat: 鸿蒙 canvas 事件缺失,待官方修复,如何在鸿蒙使用请看文档`常见问题 vue3`
## 0.9.22024-07-12
- chore: 删除多余文件
## 0.9.12024-07-12
- fix: 修复 安卓5不显示图表问题
## 0.9.02024-06-13
- chore: 合并nvue和uvue
## 0.8.92024-05-19
- chore: 更新文档
## 0.8.82024-05-13
- chore: 更新文档和uvue示例
## 0.8.72024-04-26
- fix: uniapp x需要HBX 4.13以上
## 0.8.62024-04-10
- feat: 支持 uniapp x ios
## 0.8.52024-04-03
- fix: 修复 nvue `reset`传值不生效问题
- feat: 支持 uniapp x web
## 0.8.42024-01-27
- chore: 更新文档
## 0.8.32024-01-21
- chore: 更新文档
## 0.8.22024-01-21
- feat: 支持 `uvue`
## 0.8.12023-08-24
- fix: app 的`touch`事件为`object` 导致无法显示 `tooltip`
## 0.8.02023-08-22
- fix: 离屏 报错问题
- fix: 微信小程序PC无法使用事件
- chore: 更新文档
## 0.7.92023-07-29
- chore: 更新文档
## 0.7.82023-07-29
- fix: 离屏 报错问题
## 0.7.72023-07-27
- chore: 更新文档
- chore: lime-echart 里的示例使用自定tooltips
- feat: 对支持离屏的使用离屏创建(微信、字节、支付宝)
## 0.7.62023-06-30
- fix: vue3 报`width`的错
## 0.7.52023-05-25
- chore: 更新文档 和 demo, 使用`lime-echart`这个标签即可查看示例
## 0.7.42023-05-22
- chore: 增加关于钉钉小程序上传时提示安全问题的说明及修改建议
## 0.7.32023-05-16
- chore: 更新 vue3 非微信小程序平台可能缺少`wx`的说明
## 0.7.22023-05-16
- chore: 更新 vue3 非微信小程序平台的可以缺少`wx`的说明
## 0.7.12023-04-26
- chore: 更新demo使用`lime-echart`这个标签即可查看示例
- chore微信小程序的`tooltip`文字有阴影,怀疑是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
## 0.7.02023-04-24
- fix: 修复`setAttribute is not a function`
## 0.6.92023-04-15
- chore: 更新文档vue3请使用echarts esm的包
## 0.6.82023-03-22
- feat: mac pc无法使用canvas 2d
## 0.6.72023-03-17
- feat: 更新文档
## 0.6.62023-03-17
- feat: 微信小程序PC已经支持canvas 2d故去掉判断PC
## 0.6.52022-11-03
- fix: 某些手机touches为对象导致无法交互。
## 0.6.42022-10-28
- fix: 优化点击事件的触发条件
## 0.6.32022-10-26
- fix: 修复 dataZoom 拖动问题
## 0.6.22022-10-23
- fix: 修复 飞书小程序 尺寸问题
## 0.6.12022-10-19
- fix: 修复 PC mousewheel 事件 鼠标位置不准确的BUG不兼容火狐
- feat: showLoading 增加传参
## 0.6.02022-09-16
- feat: 增加PC的mousewheel事件
## 0.5.42022-09-16
- fix: 修复 nvue 动态数据不显示问题
## 0.5.32022-09-16
- feat: 增加enableHover属性 在PC端时当鼠标进入显示tooltip不必按下。
- chore: 更新文档
## 0.5.22022-09-16
- feat: 增加enableHover属性 在PC端时当鼠标进入显示tooltip不必按下。
## 0.5.12022-09-16
- fix: 修复nvue报错
## 0.5.02022-09-15
- feat: init(echarts, theme?:string, opts?:{}, callback: function(chart))
## 0.4.82022-09-11
- feat: 增加 @finished
## 0.4.72022-08-24
- chore: 去掉 stylus
## 0.4.62022-08-24
- feat: 增加 beforeDelay
## 0.4.52022-08-12
- chore: 更新文档
## 0.4.42022-08-12
- fix: 修复 resize 无参数时报错
## 0.4.32022-08-07
# 评论有说本插件对新手不友好,让我做不好就不要发出来。 还有的说跟官网一样,发出来做什么,给我整无语了。
# 所以在此提醒一下准备要下载的你,如果你从未使用过 echarts 请不要下载 或 谨慎下载。
# 如果你确认要下载麻烦看完文档。还有请注意插件是让echarts在uniapp能运行API 配置请自行去官网查阅!
# 如果你不会echarts 但又需要图表,市场上有个很优秀的图表插件 uchart 你可以去使用这款插件uchart的作者人很好也热情。
# 每个人都有自己的本职工作,如果你能力强可以自行兼容,如果使用了他人的插件也麻烦尊重他人的成果和劳动时间。谢谢。
# 为了心情愉悦,本人已经使用插件屏蔽差评。
- chore: 更新文档
## 0.4.22022-07-20
- feat: 增加 resize
## 0.4.12022-06-07
- fix: 修复 canvasToTempFilePath 不生效问题
## 0.4.02022-06-04
- chore 为了词云 增加一个canvas 标签
- 词云下载地址[echart-wordcloud](https://ext.dcloud.net.cn/plugin?id=8430)
## 0.3.92022-06-02
- chore: 更新文档
- tips: lines 不支持 `trailLength`
## 0.3.82022-05-31
- fix: 修复 因mouse事件冲突tooltip跳动问题
## 0.3.72022-05-26
- chore: 更新文档
- chore: 设置默认宽高300px
- fix: 修复 vue3 微信小程序 拖影BUG
- chore: 支持PC
## 0.3.52022-04-28
- chore: 更新使用方式
- 🔔 必须使用hbuilderx 3.4.8-alpha以上
## 0.3.42021-08-03
- chore: 增加 setOption的参数值
## 0.3.32021-07-22
- fix: 修复 径向渐变报错的问题
## 0.3.22021-07-09
- chore: 统一命名规范,无须主动引入组件
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
## 0.3.12021-06-21
- fix: 修复 app-nvue ios is-enable 无效的问题
## [代码示例站点1](https://limeui.qcoon.cn/#/echart-example)
## [代码示例站点2](http://liangei.gitee.io/limeui/#/echart-example)
## 0.3.02021-06-14
- fix: 修复 头条系小程序 2d 报 JSON.stringify 的问题
- 目前 头条系小程序 2d 无法在开发工具上预览划动图表页面无法滚动axisLabel 字体颜色无法更改建议使用非2d。
## 0.2.92021-06-06
- fix: 修复 头条系小程序 2d 放大的BUG
- 头条系小程序 2d 无法在开发工具上预览,也存在划动图表页面无法滚动的问题。
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.82021-05-19
- fix: 修复 微信小程序 PC 显示过大的问题
## 0.2.72021-05-19
- fix: 修复 微信小程序 PC 不显示问题
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.62021-05-14
- feat: 支持 `image`
- feat: props 增加 `ec.clear`,更新时是否先删除图表样式
- feat: props 增加 `isDisableScroll` ,触摸图表时是否禁止页面滚动
- feat: props 增加 `webviewStyles` webview 的样式, 仅nvue有效
## 0.2.52021-05-13
- docs: 插件用到了css 预编译器 [stylus](https://ext.dcloud.net.cn/plugin?name=compile-stylus) 请安装它
## 0.2.42021-05-12
- fix: 修复 百度平台 多个图表ctx 和 渐变色 bug
- ## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.32021-05-10
- feat: 增加 `canvasToTempFilePath` 方法,用于生成图片
```js
this.$refs.chart.canvasToTempFilePath({success: (res) => {
console.log('tempFilePath:', res.tempFilePath)
}})
```
## 0.2.22021-05-10
- feat: 增加 `dispose` 方法,用于销毁实例
- feat: 增加 `isClickable` 是否派发点击
- feat: 实验性的支持 `nvue` 使用要慎重考虑
- ## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.2.12021-05-06
- fix修复 微信小程序 json 报错
- chore: `reset` 更改为 `setChart`
- feat: 增加 `isEnable` 开启初始化 启用这个后 无须再使用`init`方法
```html
<l-echart ref="chart" is-enable />
```
```js
// 显示加载
this.$refs.chart.showLoading()
// 使用实例回调
this.$refs.chart.setChart(chart => ...code)
// 直接设置图表配置
this.$refs.chart.setOption(data)
```
## 0.2.02021-05-05
- fix修复 头条 百度 偏移的问题
- docs: 更新文档
## [代码示例http://liangei.gitee.io/limeui/#/echart-example](http://liangei.gitee.io/limeui/#/echart-example)
## 0.1.02021-05-02
- chore: 第一次上传,基本全端兼容,使用方法与官网一致。
- 已知BUG非2d 无法使用背景色,已反馈官方
- 已知BUG头条 百度 有许些偏移
- 后期计划兼容nvue

View File

@ -0,0 +1,399 @@
import {getDeviceInfo} from './utils';
const cacheChart = {}
const fontSizeReg = /([\d\.]+)px/;
class EventEmit {
constructor() {
this.__events = {};
}
on(type, listener) {
if (!type || !listener) {
return;
}
const events = this.__events[type] || [];
events.push(listener);
this.__events[type] = events;
}
emit(type, e) {
if (type.constructor === Object) {
e = type;
type = e && e.type;
}
if (!type) {
return;
}
const events = this.__events[type];
if (!events || !events.length) {
return;
}
events.forEach((listener) => {
listener.call(this, e);
});
}
off(type, listener) {
const __events = this.__events;
const events = __events[type];
if (!events || !events.length) {
return;
}
if (!listener) {
delete __events[type];
return;
}
for (let i = 0, len = events.length; i < len; i++) {
if (events[i] === listener) {
events.splice(i, 1);
i--;
}
}
}
}
class Image {
constructor() {
this.currentSrc = null
this.naturalHeight = 0
this.naturalWidth = 0
this.width = 0
this.height = 0
this.tagName = 'IMG'
}
set src(src) {
this.currentSrc = src
uni.getImageInfo({
src,
success: (res) => {
this.naturalWidth = this.width = res.width
this.naturalHeight = this.height = res.height
this.onload()
},
fail: () => {
this.onerror()
}
})
}
get src() {
return this.currentSrc
}
}
class OffscreenCanvas {
constructor(ctx, com, canvasId) {
this.tagName = 'canvas'
this.com = com
this.canvasId = canvasId
this.ctx = ctx
}
set width(w) {
this.com.offscreenWidth = w
}
set height(h) {
this.com.offscreenHeight = h
}
get width() {
return this.com.offscreenWidth || 0
}
get height() {
return this.com.offscreenHeight || 0
}
getContext(type) {
return this.ctx
}
getImageData() {
return new Promise((resolve, reject) => {
this.com.$nextTick(() => {
uni.canvasGetImageData({
x:0,
y:0,
width: this.com.offscreenWidth,
height: this.com.offscreenHeight,
canvasId: this.canvasId,
success: (res) => {
resolve(res)
},
fail: (err) => {
reject(err)
},
}, this.com)
})
})
}
}
export class Canvas {
constructor(ctx, com, isNew, canvasNode={}) {
cacheChart[com.canvasId] = {ctx}
this.canvasId = com.canvasId;
this.chart = null;
this.isNew = isNew
this.tagName = 'canvas'
this.canvasNode = canvasNode;
this.com = com;
if (!isNew) {
this._initStyle(ctx)
}
this._initEvent();
this._ee = new EventEmit()
}
getContext(type) {
if (type === '2d') {
return this.ctx;
}
}
setAttribute(key, value) {
if(key === 'aria-label') {
this.com['ariaLabel'] = value
}
}
setChart(chart) {
this.chart = chart;
}
createOffscreenCanvas(param){
if(!this.children) {
this.com.isOffscreenCanvas = true
this.com.offscreenWidth = param.width||300
this.com.offscreenHeight = param.height||300
const com = this.com
const canvasId = this.com.offscreenCanvasId
const context = uni.createCanvasContext(canvasId, this.com)
this._initStyle(context)
this.children = new OffscreenCanvas(context, com, canvasId)
}
return this.children
}
appendChild(child) {
console.log('child', child)
}
dispatchEvent(type, e) {
if(typeof type == 'object') {
this._ee.emit(type.type, type);
} else {
this._ee.emit(type, e);
}
return true
}
attachEvent() {
}
detachEvent() {
}
addEventListener(type, listener) {
this._ee.on(type, listener)
}
removeEventListener(type, listener) {
this._ee.off(type, listener)
}
_initCanvas(zrender, ctx) {
// zrender.util.getContext = function() {
// return ctx;
// };
// zrender.util.$override('measureText', function(text, font) {
// ctx.font = font || '12px sans-serif';
// return ctx.measureText(text, font);
// });
}
_initStyle(ctx, child) {
const styles = [
'fillStyle',
'strokeStyle',
'fontSize',
'globalAlpha',
'opacity',
'textAlign',
'textBaseline',
'shadow',
'lineWidth',
'lineCap',
'lineJoin',
'lineDash',
'miterLimit',
// #ifdef H5
'font',
// #endif
];
const colorReg = /#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])\b/g;
styles.forEach(style => {
Object.defineProperty(ctx, style, {
set: value => {
// #ifdef H5
if (style === 'font' && fontSizeReg.test(value)) {
const match = fontSizeReg.exec(value);
ctx.setFontSize(match[1]);
return;
}
// #endif
if (style === 'opacity') {
ctx.setGlobalAlpha(value)
return;
}
if (style !== 'fillStyle' && style !== 'strokeStyle' || value !== 'none' && value !== null) {
// #ifdef H5 || APP-PLUS || MP-BAIDU
if(typeof value == 'object') {
if (value.hasOwnProperty('colorStop') || value.hasOwnProperty('colors')) {
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
}
return
}
// #endif
// #ifdef MP-TOUTIAO
if(colorReg.test(value)) {
value = value.replace(colorReg, '#$1$1$2$2$3$3')
}
// #endif
ctx['set' + style.charAt(0).toUpperCase() + style.slice(1)](value);
}
}
});
});
if(!this.isNew && !child) {
ctx.uniDrawImage = ctx.drawImage
ctx.drawImage = (...a) => {
a[0] = a[0].src
ctx.uniDrawImage(...a)
}
}
if(!ctx.createRadialGradient) {
ctx.createRadialGradient = function() {
return ctx.createCircularGradient(...[...arguments].slice(-3))
};
}
// 字节不支持
if (!ctx.strokeText) {
ctx.strokeText = (...a) => {
ctx.fillText(...a)
}
}
// 钉钉不支持 , 鸿蒙是异步
if (!ctx.measureText || getDeviceInfo().osName == 'harmonyos') {
ctx._measureText = ctx.measureText
const strLen = (str) => {
let len = 0;
for (let i = 0; i < str.length; i++) {
if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
len++;
} else {
len += 2;
}
}
return len;
}
ctx.measureText = (text, font) => {
let fontSize = ctx?.state?.fontSize || 12;
if (font) {
fontSize = parseInt(font.match(/([\d\.]+)px/)[1])
}
fontSize /= 2;
let isBold = fontSize >= 16;
const widthFactor = isBold ? 1.3 : 1;
// ctx._measureText(text, (res) => {})
return {
width: strLen(text) * fontSize * widthFactor
};
}
}
}
_initEvent(e) {
this.event = {};
const eventNames = [{
wxName: 'touchStart',
ecName: 'mousedown'
}, {
wxName: 'touchMove',
ecName: 'mousemove'
}, {
wxName: 'touchEnd',
ecName: 'mouseup'
}, {
wxName: 'touchEnd',
ecName: 'click'
}];
eventNames.forEach(name => {
this.event[name.wxName] = e => {
const touch = e.touches[0];
this.chart.getZr().handler.dispatch(name.ecName, {
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
zrY: name.wxName === 'tap' ? touch.clientY : touch.y
});
};
});
}
set width(w) {
this.canvasNode.width = w
}
set height(h) {
this.canvasNode.height = h
}
get width() {
return this.canvasNode.width || 0
}
get height() {
return this.canvasNode.height || 0
}
get ctx() {
return cacheChart[this.canvasId]['ctx'] || null
}
set chart(chart) {
cacheChart[this.canvasId]['chart'] = chart
}
get chart() {
return cacheChart[this.canvasId]['chart'] || null
}
}
export function dispatch(name, {x,y, wheelDelta}) {
this.dispatch(name, {
zrX: x,
zrY: y,
zrDelta: wheelDelta,
preventDefault: () => {},
stopPropagation: () =>{}
});
}
export function setCanvasCreator(echarts, {canvas, node}) {
if(echarts && !echarts.registerPreprocessor) {
return console.warn('echarts 版本不对或未传入echartsvue3请使用esm格式')
}
echarts.registerPreprocessor(option => {
if (option && option.series) {
if (option.series.length > 0) {
option.series.forEach(series => {
series.progressive = 0;
});
} else if (typeof option.series === 'object') {
option.series.progressive = 0;
}
}
});
function loadImage(src, onload, onerror) {
let img = null
if(node && node.createImage) {
img = node.createImage()
img.onload = onload.bind(img);
img.onerror = onerror.bind(img);
img.src = src;
return img
} else {
img = new Image()
img.onload = onload.bind(img)
img.onerror = onerror.bind(img);
img.src = src
return img
}
}
if(echarts.setPlatformAPI) {
echarts.setPlatformAPI({
loadImage: canvas.setChart ? loadImage : null,
createCanvas(){
const key = 'createOffscreenCanvas'
return uni.canIUse(key) && uni[key] ? uni[key]({type: '2d'}) : canvas
}
})
} else if(echarts.setCanvasCreator) {
echarts.setCanvasCreator(() => {
return canvas;
});
}
}

View File

@ -0,0 +1,347 @@
<template>
<!-- #ifdef APP -->
<web-view class="lime-echart" ref="chartRef" @load="loaded" :style="[customStyle]" :webview-styles="[webviewStyles]"
src="/uni_modules/lime-echart/static/uvue.html?v=10112">
</web-view>
<!-- #endif -->
<!-- #ifdef H5 -->
<div class="lime-echart" ref="chartRef"></div>
<!-- #endif -->
<!-- #ifndef H5 || APP-->
<view class="lime-echart">
<canvas style="width:100%; height:100%" v-if="canvasid" :id="canvasid" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend"></canvas>
</view>
<!-- #endif -->
</template>
<script lang="uts" setup>
// @ts-nocheck
import { getCurrentInstance, nextTick } from "vue";
import { Echarts } from './uvue';
// #ifdef WEB
import { dispatch } from './canvas';
// #endif
// #ifndef APP || WEB
import { Canvas, setCanvasCreator, dispatch } from './canvas';
import { wrapTouch, convertTouchesToArray, devicePixelRatio, sleep, canIUseCanvas2d, getRect } from './utils';
// #endif
type EchartsResolve = (value : Echarts) => void
defineOptions({
name: 'l-echart'
})
const emits = defineEmits(['finished'])
const props = defineProps({
// #ifdef APP
webviewStyles: {
type: Object
},
customStyle: {
type: Object
},
// #endif
// #ifndef APP
webviewStyles: {
type: Object
},
customStyle: {
type: [String, Object]
},
// #endif
isDisableScroll: {
type: Boolean,
default: false
},
isClickable: {
type: Boolean,
default: true
},
enableHover: {
type: Boolean,
default: false
},
beforeDelay: {
type: Number,
default: 30
}
})
const instance = getCurrentInstance()!;
const canvasid = `lime-echart-${instance.uid}`
const finished = ref(false)
const map = [] as EchartsResolve[]
const callbackMap = [] as EchartsResolve[]
// let context = null as UniWebViewElement | null
let chart = null as Echarts | null
let chartRef = ref<UniWebViewElement | null>(null)
const trigger = () => {
// #ifdef APP
if (finished.value) {
if (chart == null) {
chart = new Echarts(chartRef.value!)
}
while (map.length > 0) {
const resolve = map.pop() as EchartsResolve
resolve(chart!)
}
}
// #endif
// #ifndef APP
while (map.length > 0) {
if (chart != null) {
const resolve = map.pop() as EchartsResolve
resolve(chart!)
}
}
// #endif
if (chart != null) {
while (callbackMap.length > 0) {
const callback = callbackMap.pop() as EchartsResolve
callback(chart!)
}
}
}
// #ifdef APP
const loaded = (event : UniWebViewLoadEvent) => {
event.stopPropagation()
event.preventDefault()
finished.value = true
trigger()
emits('finished')
}
// #endif
const _next = () : boolean => {
if (chart == null) {
console.warn(`组件还未初始化,请先使用 init`)
return true
}
return false
}
const setOption = (option : UTSJSONObject) => {
if (_next()) return
chart!.setOption(option);
}
const showLoading = () => {
if (_next()) return
chart!.showLoading();
}
const hideLoading = () => {
if (_next()) return
chart!.hideLoading();
}
const clear = () => {
if (_next()) return
chart!.clear();
}
const dispose = () => {
if (_next()) return
chart!.dispose();
}
const resize = (size : UTSJSONObject) => {
if (_next()) return
chart!.resize(size);
}
const canvasToTempFilePath = (opt : UTSJSONObject) => {
if (_next()) return
chart!.canvasToTempFilePath(opt);
}
// #ifdef APP
function init(callback : ((chart : Echarts) => void) | null) : Promise<Echarts> {
if (callback != null) {
callbackMap.push(callback)
}
return new Promise<Echarts>((resolve) => {
map.push(resolve)
trigger()
})
}
// #endif
// #ifndef APP
// #ifndef WEB
let use2dCanvas = canIUseCanvas2d()
const getContext = async () => {
return new Promise((resolve, reject)=>{
uni.createCanvasContextAsync({
id: canvasid,
component: instance.proxy!,
success: (context : CanvasContext) => {
const canvasContext = context.getContext('2d')!;
const canvas = canvasContext.canvas;
let uniCanvas;
const width = canvas.offsetWidth
const height = canvas.offsetHeight
// 处理高清屏逻辑
const dpr = uni.getDeviceInfo().devicePixelRatio ?? 1;
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
canvasContext.scale(dpr, dpr); // 仅需调用一次,当调用 reset 方法后需要再次 scale
if(use2dCanvas) {
uniCanvas = new Canvas(canvasContext, instance.proxy, true, context);
} else {
uniCanvas = new Canvas(canvasContext, instance.proxy, false);
}
resolve({ canvas: uniCanvas, width, height, devicePixelRatio: 1, node: context});
},
fail(err) {
reject(err)
console.log('err', err)
}
})
})
// return getRect(`#${canvasid}`, {context: instance.proxy!, type: use2dCanvas ? 'fields': 'boundingClientRect'}).then(res => {
// if(res) {
// let dpr = uni.getWindowInfo().pixelRatio
// let {width, height, node} = res
// let canvas;
// if(node) {
// const ctx = node.getContext('2d');
// canvas = new Canvas(ctx, instance.proxy, true, node);
// } else {
// const ctx = uni.createCanvasContext(canvasid, instance.proxy);
// canvas = new Canvas(ctx, instance.proxy, false);
// }
// return { canvas, width, height, devicePixelRatio: dpr, node };
// } else {
// return {}
// }
// })
}
// #endif
const getTouch = (e) => {
const touches = e.touches[0]
// #ifdef WEB
const rect = chart!.getZr().dom.getBoundingClientRect();
const touch = {
x: touches.clientX - rect.left,
y: touches.clientY - rect.top
}
// #endif
// #ifndef WEB
const touch = {
x: touches.x,
y: touches.y
}
// #endif
return touch
}
const touchstart = (e) => {
if (chart == null) return
const handler = chart.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'click', touch)
}
const touchmove = (e) => {
if (chart == null) return
const handler = chart.getZr().handler;
const touch = getTouch(e)
dispatch.call(handler, 'mousemove', touch)
// const rect = chart.getZr().dom.getBoundingClientRect()
// handler.dispatch('mousemove', {
// zrX: e.touches[0].clientX - rect.left,
// zrY: e.touches[0].clientY - rect.top
// })
}
const touchend = (e) => {
if (chart == null) return
const handler = chart.getZr().handler;
const touch = {
x: 999999999,
y: 999999999
}
dispatch.call(handler, 'mousemove', touch)
dispatch.call(handler, 'touchend', touch)
}
async function init(echarts : any, ...args : any[]) : Promise<Echarts> {
if (echarts == null) {
console.error('请确保已经引入了 ECharts 库');
return Promise.reject('请确保已经引入了 ECharts 库');
}
let theme : string | null = null
let opts = {}
let callback : Function | null = null;
args.forEach(item => {
if (typeof item === 'function') {
callback = item
} else if (['string'].includes(typeof item)) {
theme = item
} else if (typeof item === 'object') {
opts = item
}
})
// #ifdef WEB
echarts.env.domSupported = true
echarts.env.hasGlobalWindow = true
echarts.env.node = false
echarts.env.pointerEventsSupported = false
echarts.env.svgSupported = true
echarts.env.touchEventsSupported = true
echarts.env.transform3dSupported = true
echarts.env.transformSupported = true
echarts.env.worker = false
echarts.env.wxa = false
chart = echarts.init(chartRef.value, theme, opts)
// window.addEventListener('touchstart', touchstart)
// window.addEventListener('touchmove', touchmove)
// window.addEventListener('touchend', touchend)
// #endif
// #ifndef WEB
let config = await getContext();
setCanvasCreator(echarts, config)
chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts))
// #endif
if (callback != null && typeof callback == 'function') {
callbackMap.push(callback)
}
return new Promise<Echarts>((resolve) => {
map.push(resolve)
trigger()
})
}
onMounted(() => {
nextTick(() => {
finished.value = true
trigger()
emits('finished')
})
})
onUnmounted(() => {
// #ifdef WEB
// window.removeEventListener('touchstart', touchstart)
// window.removeEventListener('touchmove', touchmove)
// window.removeEventListener('touchend', touchend)
// #endif
})
// #endif
defineExpose({
init,
setOption,
showLoading,
hideLoading,
clear,
dispose,
resize,
canvasToTempFilePath
})
</script>
<style lang="scss">
.lime-echart {
flex: 1;
width: 100%;
}
</style>

View File

@ -0,0 +1,514 @@
<template>
<view class="lime-echart" :style="[customStyle]" v-if="canvasId" ref="limeEchart" :aria-label="ariaLabel">
<!-- #ifndef APP-NVUE -->
<canvas
class="lime-echart__canvas"
v-if="use2dCanvas"
type="2d"
:id="canvasId"
:style="canvasStyle"
:disable-scroll="isDisableScroll"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
/>
<canvas
class="lime-echart__canvas"
v-else
:width="nodeWidth"
:height="nodeHeight"
:style="canvasStyle"
:canvas-id="canvasId"
:id="canvasId"
:disable-scroll="isDisableScroll"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
/>
<view class="lime-echart__mask"
v-if="isPC"
@mousedown="touchStart"
@mousemove="touchMove"
@mouseup="touchEnd"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd">
</view>
<canvas v-if="isOffscreenCanvas" :style="offscreenStyle" :canvas-id="offscreenCanvasId"></canvas>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<web-view
class="lime-echart__canvas"
:id="canvasId"
:style="canvasStyle"
:webview-styles="webviewStyles"
ref="webview"
src="/uni_modules/lime-echart/static/uvue.html?v=1"
@pagefinish="finished = true"
@onPostMessage="onMessage"
></web-view>
<!-- #endif -->
</view>
</template>
<script>
// @ts-nocheck
// #ifndef APP-NVUE
import {Canvas, setCanvasCreator, dispatch} from './canvas';
import {wrapTouch, convertTouchesToArray, devicePixelRatio ,sleep, canIUseCanvas2d, getRect, getDeviceInfo} from './utils';
// #endif
// #ifdef APP-NVUE
import { base64ToPath, sleep } from './utils';
import {Echarts} from './nvue'
// #endif
/**
* LimeChart 图表
* @description 全端兼容的eCharts
* @tutorial https://ext.dcloud.net.cn/plugin?id=4899
* @property {String} customStyle 自定义样式
* @property {String} type 指定 canvas 类型
* @value 2d 使用canvas 2d部分小程序支持
* @value '' 使用原生canvas会有层级问题
* @value bottom right 不缩放图片只显示图片的右下边区域
* @property {Boolean} isDisableScroll
* @property {number} beforeDelay = [30] 延迟初始化 (毫秒)
* @property {Boolean} enableHover PC端使用鼠标悬浮
* @event {Function} finished 加载完成触发
*/
export default {
name: 'lime-echart',
props: {
// #ifdef MP-WEIXIN || MP-TOUTIAO
type: {
type: String,
default: '2d'
},
// #endif
// #ifdef APP-NVUE
webviewStyles: Object,
// hybrid: Boolean,
// #endif
customStyle: String,
isDisableScroll: Boolean,
isClickable: {
type: Boolean,
default: true
},
enableHover: Boolean,
beforeDelay: {
type: Number,
default: 30
},
landscape: Boolean
},
data() {
return {
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
use2dCanvas: true,
// #endif
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
use2dCanvas: false,
// #endif
ariaLabel: '图表',
width: null,
height: null,
nodeWidth: null,
nodeHeight: null,
// canvasNode: null,
config: {},
inited: false,
finished: false,
file: '',
platform: '',
isPC: false,
isDown: false,
isOffscreenCanvas: false,
offscreenWidth: 0,
offscreenHeight: 0,
};
},
computed: {
rootStyle() {
if(this.landscape) {
return `transform: translate(-50%,-50%) rotate(90deg); top:50%; left:50%;`
}
},
canvasId() {
return `lime-echart${this._ && this._.uid || this._uid}`
},
offscreenCanvasId() {
return `${this.canvasId}_offscreen`
},
offscreenStyle() {
return `width:${this.offscreenWidth}px;height: ${this.offscreenHeight}px; position: fixed; left: 99999px; background: red`
},
canvasStyle() {
return this.rootStyle + (this.width && this.height ? ('width:' + this.width + 'px;height:' + this.height + 'px') : '')
}
},
// #ifndef VUE3
beforeDestroy() {
this.clear()
this.dispose()
// #ifdef H5
if(this.isPC) {
document.removeEventListener('mousewheel', this.mousewheel)
}
// #endif
},
// #endif
// #ifdef VUE3
beforeUnmount() {
this.clear()
this.dispose()
// #ifdef H5
if(this.isPC) {
document.removeEventListener('mousewheel', this.mousewheel)
}
// #endif
},
// #endif
created() {
// #ifdef H5
if(!('ontouchstart' in window)) {
this.isPC = true
document.addEventListener('mousewheel', this.mousewheel)
}
// #endif
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
const { platform } = getDeviceInfo();
this.isPC = /windows/i.test(platform)
// #endif
this.use2dCanvas = this.type === '2d' && canIUseCanvas2d()
},
mounted() {
this.$nextTick(() => {
this.$emit('finished')
})
},
methods: {
// #ifdef APP-NVUE
onMessage(e) {
const detail = e?.detail?.data[0] || null;
const data = detail?.data
const key = detail?.event
const options = data?.options
const event = data?.event
const file = detail?.file
if (key == 'log' && data) {
console.log(data)
}
if(event) {
this.chart.dispatchAction(event.replace(/"/g,''), options)
}
if(file) {
thie.file = file
}
},
// #endif
setChart(callback) {
if(!this.chart) {
console.warn(`组件还未初始化,请先使用 init`)
return
}
if(typeof callback === 'function' && this.chart) {
callback(this.chart);
}
// #ifdef APP-NVUE
if(typeof callback === 'function') {
this.$refs.webview.evalJs(`setChart(${JSON.stringify(callback.toString())}, ${JSON.stringify(this.chart.options)})`);
}
// #endif
},
setOption() {
if (!this.chart || !this.chart.setOption) {
console.warn(`组件还未初始化,请先使用 init`)
return
}
this.chart.setOption(...arguments);
},
showLoading() {
if(this.chart) {
this.chart.showLoading(...arguments)
}
},
hideLoading() {
if(this.chart) {
this.chart.hideLoading()
}
},
clear() {
if(this.chart && !this.chart.isDisposed()) {
this.chart.clear()
}
},
dispose() {
if(this.chart && !this.chart.isDisposed()) {
this.chart.dispose()
}
},
resize(size) {
if(size && size.width && size.height) {
this.height = size.height
this.width = size.width
if(this.chart) {this.chart.resize(size)}
} else {
this.$nextTick(() => {
getRect('.lime-echart', this).then(res =>{
if (res) {
let { width, height } = res;
this.width = width = width || 300;
this.height = height = height || 300;
this.chart.resize({width, height})
}
})
})
}
},
canvasToTempFilePath(args = {}) {
// #ifndef APP-NVUE
const { use2dCanvas, canvasId } = this;
return new Promise((resolve, reject) => {
const copyArgs = Object.assign({
canvasId,
success: resolve,
fail: reject
}, args);
if (use2dCanvas) {
delete copyArgs.canvasId;
copyArgs.canvas = this.canvasNode;
}
uni.canvasToTempFilePath(copyArgs, this);
});
// #endif
// #ifdef APP-NVUE
this.file = ''
this.$refs.webview.evalJs(`canvasToTempFilePath()`);
return new Promise((resolve, reject) => {
this.$watch('file', async (file) => {
if(file) {
const tempFilePath = await base64ToPath(file)
resolve(args.success({tempFilePath}))
} else {
reject(args.fail({error: ``}))
}
})
})
// #endif
},
async init(echarts, ...args) {
// #ifndef APP-NVUE
if(args && args.length == 0 && !echarts) {
console.error('缺少参数init(echarts, theme?:string, opts?: object, callback?: function)')
return
}
// #endif
let theme=null,opts={},callback;
Array.from(arguments).forEach(item => {
if(typeof item === 'function') {
callback = item
}
if(['string'].includes(typeof item)) {
theme = item
}
if(typeof item === 'object') {
opts = item
}
})
if(this.beforeDelay) {
await sleep(this.beforeDelay)
}
let config = await this.getContext();
// #ifndef APP-NVUE
setCanvasCreator(echarts, config)
try {
this.chart = echarts.init(config.canvas, theme, Object.assign({}, config, opts || {}))
callback?.(this.chart)
return this.chart
} catch(e) {
console.error("【lime-echarts】:", e)
return null
}
// #endif
// #ifdef APP-NVUE
this.chart = new Echarts(this.$refs.webview)
this.$refs.webview.evalJs(`init(null, null, ${JSON.stringify(opts)}, ${theme})`)
callback?.(this.chart)
return this.chart
// #endif
},
getContext() {
// #ifdef APP-NVUE
if(this.finished) {
return Promise.resolve(this.finished)
}
return new Promise(resolve => {
this.$watch('finished', (val) => {
if(val) {
resolve(this.finished)
}
})
})
// #endif
// #ifndef APP-NVUE
return getRect(`#${this.canvasId}`, this, this.use2dCanvas).then(res => {
if(res) {
let dpr = devicePixelRatio
let {width, height, node} = res
let canvas;
this.width = width = width || 300;
this.height = height = height || 300;
if(node) {
const ctx = node.getContext('2d');
canvas = new Canvas(ctx, this, true, node);
this.canvasNode = node
} else {
// #ifdef MP-TOUTIAO
dpr = !this.isPC ? devicePixelRatio : 1// 1.25
// #endif
// #ifndef MP-ALIPAY || MP-TOUTIAO
dpr = this.isPC ? devicePixelRatio : 1
// #endif
// #ifdef MP-ALIPAY || MP-LARK
dpr = devicePixelRatio
// #endif
// #ifdef WEB
dpr = 1
// #endif
this.rect = res
this.nodeWidth = width * dpr;
this.nodeHeight = height * dpr;
const ctx = uni.createCanvasContext(this.canvasId, this);
canvas = new Canvas(ctx, this, false);
}
return { canvas, width, height, devicePixelRatio: dpr, node };
} else {
return {}
}
})
// #endif
},
// #ifndef APP-NVUE
getRelative(e, touches) {
let { clientX, clientY } = e
if(!(clientX && clientY) && touches && touches[0]) {
clientX = touches[0].clientX
clientY = touches[0].clientY
}
return {x: clientX - this.rect.left, y: clientY - this.rect.top, wheelDelta: e.wheelDelta || 0}
},
getTouch(e, touches) {
const {x} = touches && touches[0] || {}
const touch = x ? touches[0] : this.getRelative(e, touches);
if(this.landscape) {
[touch.x, touch.y] = [touch.y, this.height - touch.x]
}
return touch;
},
touchStart(e) {
this.isDown = true
const next = () => {
const touches = convertTouchesToArray(e.touches)
if(this.chart) {
const touch = this.getTouch(e, touches)
this.startX = touch.x
this.startY = touch.y
this.startT = new Date()
const handler = this.chart.getZr().handler;
dispatch.call(handler, 'mousedown', touch)
dispatch.call(handler, 'mousemove', touch)
handler.processGesture(wrapTouch(e), 'start');
clearTimeout(this.endTimer);
}
}
if(this.isPC) {
getRect(`#${this.canvasId}`, {context: this}).then(res => {
this.rect = res
next()
})
return
}
next()
},
touchMove(e) {
if(this.isPC && this.enableHover && !this.isDown) {this.isDown = true}
const touches = convertTouchesToArray(e.touches)
if (this.chart && this.isDown) {
const handler = this.chart.getZr().handler;
dispatch.call(handler, 'mousemove', this.getTouch(e, touches))
handler.processGesture(wrapTouch(e), 'change');
}
},
touchEnd(e) {
this.isDown = false
if (this.chart) {
const touches = convertTouchesToArray(e.changedTouches)
const {x} = touches && touches[0] || {}
const touch = (x ? touches[0] : this.getRelative(e, touches)) || {};
if(this.landscape) {
[touch.x, touch.y] = [touch.y, this.height - touch.x]
}
const handler = this.chart.getZr().handler;
const isClick = Math.abs(touch.x - this.startX) < 10 && new Date() - this.startT < 200;
dispatch.call(handler, 'mouseup', touch)
handler.processGesture(wrapTouch(e), 'end');
if(isClick) {
dispatch.call(handler, 'click', touch)
} else {
this.endTimer = setTimeout(() => {
dispatch.call(handler, 'mousemove', {x: 999999999,y: 999999999});
dispatch.call(handler, 'mouseup', {x: 999999999,y: 999999999});
},50)
}
}
},
// #endif
// #ifdef H5
mousewheel(e){
if(this.chart) {
dispatch.call(this.chart.getZr().handler, 'mousewheel', this.getTouch(e))
}
}
// #endif
}
};
</script>
<style>
.lime-echart {
position: relative;
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.lime-echart__canvas {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
/* #ifndef APP-NVUE */
.lime-echart__mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 1;
}
/* #endif */
</style>

View File

@ -0,0 +1,51 @@
export class Echarts {
eventMap = new Map()
constructor(webview) {
this.webview = webview
this.options = null
}
setOption() {
this.options = arguments
this.webview.evalJs(`setOption(${JSON.stringify(arguments)})`);
}
getOption() {
return this.options
}
showLoading() {
this.webview.evalJs(`showLoading(${JSON.stringify(arguments)})`);
}
hideLoading() {
this.webview.evalJs(`hideLoading()`);
}
clear() {
this.webview.evalJs(`clear()`);
}
dispose() {
this.webview.evalJs(`dispose()`);
}
resize(size) {
if(size) {
this.webview.evalJs(`resize(${JSON.stringify(size)})`);
} else {
this.webview.evalJs(`resize()`);
}
}
on(type, ...args) {
const query = args[0]
const useQuery = query && typeof query != 'function'
const param = useQuery ? [type, query] : [type]
const key = `${type}${useQuery ? JSON.stringify(query): '' }`
const callback = useQuery ? args[1]: args[0]
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.webview.evalJs(`on(${JSON.stringify(param)})`);
console.warn('nvue 暂不支持事件')
}
dispatchAction(type, options){
const handler = this.eventMap.get(type)
if(handler){
handler(options)
}
}
}

View File

@ -0,0 +1,185 @@
// @ts-nocheck
/**
* 获取设备基础信息
*
* @see [uni.getDeviceInfo](https://uniapp.dcloud.net.cn/api/system/getDeviceInfo.html)
*/
export function getDeviceInfo() {
if (uni.getDeviceInfo || uni.canIUse('getDeviceInfo')) {
return uni.getDeviceInfo();
} else {
return uni.getSystemInfoSync();
}
}
/**
* 获取窗口信息
*
* @see [uni.getWindowInfo](https://uniapp.dcloud.net.cn/api/system/getWindowInfo.html)
*/
export function getWindowInfo() {
if (uni.getWindowInfo || uni.canIUse('getWindowInfo')) {
return uni.getWindowInfo();
} else {
return uni.getSystemInfoSync();
}
}
/**
* 获取APP基础信息
*
* @see [uni.getAppBaseInfo](https://uniapp.dcloud.net.cn/api/system/getAppBaseInfo.html)
*/
export function getAppBaseInfo() {
if (uni.getAppBaseInfo || uni.canIUse('getAppBaseInfo')) {
return uni.getAppBaseInfo();
} else {
return uni.getSystemInfoSync();
}
}
// #ifndef APP-NVUE
// 计算版本
export function compareVersion(v1, v2) {
v1 = v1.split('.')
v2 = v2.split('.')
const len = Math.max(v1.length, v2.length)
while (v1.length < len) {
v1.push('0')
}
while (v2.length < len) {
v2.push('0')
}
for (let i = 0; i < len; i++) {
const num1 = parseInt(v1[i], 10)
const num2 = parseInt(v2[i], 10)
if (num1 > num2) {
return 1
} else if (num1 < num2) {
return -1
}
}
return 0
}
// const systemInfo = uni.getSystemInfoSync();
function gte(version) {
// 截止 2023-03-22 mac pc小程序不支持 canvas 2d
// let {
// SDKVersion,
// platform
// } = systemInfo;
const { platform } = getDeviceInfo();
let { SDKVersion } = getAppBaseInfo();
// #ifdef MP-ALIPAY
SDKVersion = my.SDKVersion
// #endif
// #ifdef MP-WEIXIN
return platform !== 'mac' && compareVersion(SDKVersion, version) >= 0;
// #endif
return compareVersion(SDKVersion, version) >= 0;
}
export function canIUseCanvas2d() {
// #ifdef MP-WEIXIN
return gte('2.9.0');
// #endif
// #ifdef MP-ALIPAY
return gte('2.7.0');
// #endif
// #ifdef MP-TOUTIAO
return gte('1.78.0');
// #endif
return false
}
export function convertTouchesToArray(touches) {
// 如果 touches 是一个数组,则直接返回它
if (Array.isArray(touches)) {
return touches;
}
// 如果touches是一个对象则转换为数组
if (typeof touches === 'object' && touches !== null) {
return Object.values(touches);
}
// 对于其他类型,直接返回它
return touches;
}
export function wrapTouch(event) {
event.touches = convertTouchesToArray(event.touches)
for (let i = 0; i < event.touches.length; ++i) {
const touch = event.touches[i];
touch.offsetX = touch.x;
touch.offsetY = touch.y;
}
return event;
}
// export const devicePixelRatio = uni.getSystemInfoSync().pixelRatio
export const devicePixelRatio = getWindowInfo().pixelRatio;
// #endif
// #ifdef APP-NVUE
export function base64ToPath(base64) {
return new Promise((resolve, reject) => {
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, () => {
if (!format) {
reject(new Error('ERROR_BASE64SRC_PARSE'))
}
const time = new Date().getTime();
const filePath = `_doc/uniapp_temp/${time}.${format}`
bitmap.save(filePath, {},
() => {
bitmap.clear()
resolve(filePath)
},
(error) => {
bitmap.clear()
console.error(`${JSON.stringify(error)}`)
reject(error)
})
}, (error) => {
bitmap.clear()
console.error(`${JSON.stringify(error)}`)
reject(error)
})
})
}
// #endif
export function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, time)
})
}
export function getRect(selector, context, node) {
return new Promise((resolve, reject) => {
const dom = uni.createSelectorQuery().in(context).select(selector);
const result = (rect) => {
if (rect) {
resolve(rect)
} else {
reject()
}
}
if (!node) {
dom.boundingClientRect(result).exec()
} else {
dom.fields({
node: true,
size: true,
rect: true
}, result).exec()
}
});
};

View File

@ -0,0 +1,133 @@
// @ts-nocheck
// #ifdef APP
type EchartsEventHandler = (event: UTSJSONObject)=>void
// type EchartsTempResolve = (obj : UTSJSONObject) => void
// type EchartsTempOptions = UTSJSONObject
export class Echarts {
options: UTSJSONObject = {} as UTSJSONObject
context: UniWebViewElement
eventMap: Map<string, EchartsEventHandler> = new Map()
private temp: UTSJSONObject[] = []
constructor(context: UniWebViewElement){
this.context = context
this.init()
}
init(){
this.context.evalJS(`init(null, null, ${JSON.stringify({})})`)
this.context.addEventListener('message', (e : UniWebViewMessageEvent) => {
// event.stopPropagation()
// event.preventDefault()
const detail = e.detail.data[0]
const file = detail.getString('file')
const data = detail.get('data')
const key = detail.getString('event')
const options = typeof data == 'object' ? (data as UTSJSONObject).getJSON('options'): null
const event = typeof data == 'object' ? (data as UTSJSONObject).getString('event'): null
if (key == 'log' && data != null) {
console.log(data)
}
if (event != null && options != null) {
this.dispatchAction(event.replace(/"/g,''), options)
}
if(file != null){
while (this.temp.length > 0) {
const opt = this.temp.pop()
const success = opt?.get('success')
if(typeof success == 'function'){
success as (res: UTSJSONObject) => void
success({tempFilePath: file})
}
}
}
})
}
setOption(option: UTSJSONObject){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option])})`)
}
setOption(option: UTSJSONObject, notMerge: boolean = false, lazyUpdate: boolean = false){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge, lazyUpdate])})`)
}
setOption(option: UTSJSONObject, notMerge: UTSJSONObject){
this.options = option;
this.context.evalJS(`setOption(${JSON.stringify([option, notMerge])})`)
}
getOption(): UTSJSONObject {
return this.options
}
showLoading(){
this.context.evalJS(`showLoading(${JSON.stringify([] as any[])})`);
}
showLoading(type: string, opts: UTSJSONObject){
this.context.evalJS(`showLoading(${JSON.stringify([type, opts])})`);
}
hideLoading(){
this.context.evalJS(`hideLoading()`);
}
clear(){
this.context.evalJS(`clear()`);
}
dispose(){
this.context.evalJS(`dispose()`);
}
resize(size:UTSJSONObject){
setTimeout(()=>{
this.context.evalJS(`resize(${JSON.stringify(size)})`);
},0)
}
resize(){
setTimeout(()=>{
this.context.evalJS(`resize()`);
},10)
}
on(type:string, query: any, callback: EchartsEventHandler) {
const key = `${type}${JSON.stringify(query)}`
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.context.evalJS(`on(${JSON.stringify([type, query])})`);
console.warn('uvue 暂不支持事件')
}
on(type:string, callback: EchartsEventHandler) {
const key = `${type}`
if(typeof callback == 'function'){
this.eventMap.set(key, callback)
}
this.context.evalJS(`on(${JSON.stringify([type])})`);
console.warn('uvue 暂不支持事件')
}
dispatchAction(type:string, options: UTSJSONObject){
const handler = this.eventMap.get(type)
if(handler!=null){
handler(options)
}
}
canvasToTempFilePath(opt: UTSJSONObject){
// this.context.evalJS(`on(${JSON.stringify(opt)})`);
this.context.evalJS(`canvasToTempFilePath(${JSON.stringify(opt)})`);
this.temp.push(opt)
}
}
// #endif
// #ifndef APP
export class Echarts {
constructor() {}
setOption(option: UTSJSONObject): void
isDisposed(): boolean;
clear(): void;
resize(size:UTSJSONObject): void;
resize(): void;
canvasToTempFilePath(opt : UTSJSONObject): void;
dispose(): void;
showLoading(cfg?: UTSJSONObject): void;
showLoading(name?: string, cfg?: UTSJSONObject): void;
hideLoading(): void;
getZr(): any
}
// #endif

View File

@ -0,0 +1,159 @@
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script>
export default {
data() {
return {
showTip: false,
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
}
},
mounted() {
console.log('lime echarts nvue')
},
methods: {
init() {
const chartRef = this.$refs['chartRef']
chartRef.init(chart => {
chart.setOption(this.option);
setTimeout(()=>{
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
chart.setOption(option);
},1000)
})
},
save() {
// this.$refs.chart.canvasToTempFilePath({
// success(res) {
// console.log('res::::', res)
// }
// })
}
}
}
</script>
<style>
</style>

View File

@ -0,0 +1,159 @@
<template>
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
</template>
<script lang="uts" setup>
// @ts-nocheck
// #ifndef APP
import * as echarts from 'echarts/dist/echarts.esm.js'
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(null)
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0
},
renderMode: 'richText',
},
// formatter: async (params: any) => {
// console.log('params', params)
// return 1
// },
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
},
yAxis: {
type: 'value'
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410]
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320]
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320]
}
]
}
const init = async () =>{
if(chartRef.value== null) return
// #ifdef APP
const chart = await chartRef.value!.init(null)
// #endif
// #ifndef APP
const chart = await chartRef.value!.init(echarts, null)
// #endif
chart.setOption(option)
chart.on('mouseover', function (params) {
console.log('params', params);
});
// setTimeout(()=> {
// const option1 = {
// tooltip: {
// trigger: 'axis',
// // shadowBlur: 0,
// textStyle: {
// textShadowBlur: 0
// },
// renderMode: 'richText',
// },
// legend: {
// data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎']
// },
// grid: {
// left: '3%',
// right: '4%',
// bottom: '3%',
// containLabel: true
// },
// xAxis: {
// type: 'category',
// boundaryGap: false,
// data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
// },
// yAxis: {
// type: 'value'
// },
// series: [
// {
// name: '邮件营销',
// type: 'line',
// stack: '总量',
// data: [820, 132, 101, 134, 90, 230, 210]
// },
// {
// name: '联盟广告',
// type: 'line',
// stack: '总量',
// data: [220, 182, 191, 234, 290, 330, 310]
// },
// {
// name: '视频广告',
// type: 'line',
// stack: '总量',
// data: [950, 232, 201, 154, 190, 330, 410]
// },
// {
// name: '直接访问',
// type: 'line',
// stack: '总量',
// data: [320, 332, 301, 334, 390, 330, 320]
// },
// {
// name: '搜索引擎',
// type: 'line',
// stack: '总量',
// data: [820, 932, 901, 934, 1290, 1330, 1320]
// }
// ]
// }
// chart.setOption(option1)
// },1000)
}
</script>
<style>
</style>

View File

@ -0,0 +1,227 @@
<template>
<view>
<view style="height: 750rpx; position: relative">
<l-echart ref="chart" @finished="init"></l-echart>
<view
class="customTooltips"
:style="{ left: position[0] + 'px', top: position[1] + 'px' }"
v-if="params.length && position.length && showTip"
>
<view>这是个自定的tooltips</view>
<view>{{ params[0]['axisValue'] }}</view>
<view v-for="item in params">
<view>
<text>{{ item.seriesName }}</text>
<text>{{ item.value }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
// nvue
// #ifdef VUE2
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
// #endif
// #ifdef VUE3
// #ifdef MP
// vue3 使vite umd使使require
const echarts = require('../../static/echarts.min')
// #endif
// #ifndef MP
// vue3 使vite umdnpm
import * as echarts from 'echarts/dist/echarts.esm'
// #endif
// #endif
export default {
data() {
return {
showTip: false,
position: [],
params: [],
option: {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
position: (point, params, dom, rect, size) => {
// tooltips
const box = [170, 170]
//
const offsetX = point[0] < size.viewSize[0] / 2 ? 20 : -box[0] - 20
const offsetY = point[1] < size.viewSize[1] / 2 ? 20 : -box[1] - 20
const x = point[0] + offsetX
const y = point[1] + offsetY
this.position = [x, y]
this.params = params
},
formatter: (params, ticket, callback) => {},
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 232, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [320, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
},
}
},
methods: {
init() {
// init(echarts, theme?:string, opts?:{}, chart => {})
// echarts nvuenvue
// theme 'dark'
// opts = { //
// locale?: string // `5.0.0`
// }
// chart => {} callback
// setTimeout(()=>{
// this.$refs.chart.init(echarts, chart => {
// chart.setOption(this.option);
// });
// },300)
this.$refs.chart.init(echarts, (chart) => {
chart.setOption(this.option)
// tooltip
chart.on('showTip', (params) => {
this.showTip = true
console.log('showTip::')
})
chart.on('hideTip', (params) => {
setTimeout(() => {
this.showTip = false
}, 300)
})
setTimeout(() => {
const option = {
tooltip: {
trigger: 'axis',
// shadowBlur: 0,
textStyle: {
textShadowBlur: 0,
},
renderMode: 'richText',
},
legend: {
data: ['邮件营销', '联盟广告', '视频广告', '直接访问', '搜索引擎'],
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
yAxis: {
type: 'value',
},
series: [
{
name: '邮件营销',
type: 'line',
stack: '总量',
data: [1120, 132, 101, 134, 90, 230, 210],
},
{
name: '联盟广告',
type: 'line',
stack: '总量',
data: [220, 182, 191, 234, 290, 330, 310],
},
{
name: '视频广告',
type: 'line',
stack: '总量',
data: [150, 632, 201, 154, 190, 330, 410],
},
{
name: '直接访问',
type: 'line',
stack: '总量',
data: [820, 332, 301, 334, 390, 330, 320],
},
{
name: '搜索引擎',
type: 'line',
stack: '总量',
data: [820, 932, 901, 934, 1290, 1330, 1320],
},
],
}
chart.setOption(option)
}, 1000)
})
},
save() {
this.$refs.chart.canvasToTempFilePath({
success(res) {
console.log('res::::', res)
},
})
},
},
}
</script>
<style>
.customTooltips {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 20rpx;
}
</style>

View File

@ -1,19 +1,17 @@
{ {
"id": "z-paging", "id": "lime-echart",
"name": "z-paging", "displayName": "echarts",
"displayName": "【z-paging下拉刷新、上拉加载】高性能全平台兼容。支持虚拟列表分页全自动处理", "version": "1.0.0",
"version": "2.8.5", "description": "echarts 全端兼容一款使echarts图表能跑在uniapp各端中的插件, 支持uniapp/uniappx(web,ios,安卓)",
"description": "超简单、低耦合使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等数百项配置",
"keywords": [ "keywords": [
"下拉刷新", "echarts",
"上拉加载", "canvas",
"分页器", "图表",
"nvue", "可视化"
"虚拟列表"
], ],
"repository": "https://github.com/SmileZXLee/uni-z-paging", "repository": "https://gitee.com/liangei/lime-echart",
"engines": { "engines": {
"HBuilderX": "^3.0.7" "HBuilderX": "^3.6.4"
}, },
"dcloudext": { "dcloudext": {
"sale": { "sale": {
@ -25,14 +23,14 @@
} }
}, },
"contact": { "contact": {
"qq": "393727164" "qq": ""
}, },
"declaration": { "declaration": {
"ads": "无", "ads": "无",
"data": "无", "data": "无",
"permissions": "无" "permissions": "无"
}, },
"npmurl": "https://www.npmjs.com/package/z-paging", "npmurl": "",
"type": "component-vue" "type": "component-vue"
}, },
"uni_modules": { "uni_modules": {
@ -48,8 +46,8 @@
"App": { "App": {
"app-vue": "y", "app-vue": "y",
"app-nvue": "y", "app-nvue": "y",
"app-harmony": "u", "app-uvue": "y",
"app-uvue": "u" "app-harmony": "u"
}, },
"H5-mobile": { "H5-mobile": {
"Safari": "y", "Safari": "y",
@ -58,11 +56,11 @@
"QQ浏览器(Android)": "y" "QQ浏览器(Android)": "y"
}, },
"H5-pc": { "H5-pc": {
"Chrome": "y", "Chrome": "u",
"IE": "y", "IE": "u",
"Edge": "y", "Edge": "u",
"Firefox": "y", "Firefox": "u",
"Safari": "y" "Safari": "u"
}, },
"小程序": { "小程序": {
"微信": "y", "微信": "y",
@ -70,14 +68,14 @@
"百度": "y", "百度": "y",
"字节跳动": "y", "字节跳动": "y",
"QQ": "y", "QQ": "y",
"钉钉": "y", "钉钉": "u",
"快手": "y", "快手": "u",
"飞书": "y", "飞书": "u",
"京东": "y" "京东": "u"
}, },
"快应用": { "快应用": {
"华为": "y", "华为": "u",
"联盟": "y" "联盟": "u"
}, },
"Vue": { "Vue": {
"vue2": "y", "vue2": "y",
@ -85,5 +83,9 @@
} }
} }
} }
},
"dependencies": {
"echarts": "^5.4.1",
"zrender": "^5.4.3"
} }
} }

View File

@ -0,0 +1,408 @@
# echarts 图表 <span style="font-size:16px;">👑👑👑👑👑 <span style="background:#ff9d00;padding:2px 4px;color:#fff;font-size:10px;border-radius: 3px;">全端</span></span>
> 一个基于 JavaScript 的开源可视化图表库 [查看更多](https://limeui.qcoon.cn/#/echart) <br>
> 基于 echarts 做了兼容处理,更多示例请访问 [uni示例](https://limeui.qcoon.cn/#/echart-example) | [官方示例](https://echarts.apache.org/examples/zh/index.html) <br>
## 平台兼容
| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App |
| --- | ---------- | ------------ | ---------- | ---------- | --------- | ---- |
| √ | √ | √ | √ | √ | √ | √ |
## 安装
- 第一步:在市场导入 [百度图表](https://ext.dcloud.net.cn/plugin?id=4899)
- 第二步:选择插件依赖:<br>
1、可以选插件内的`echarts`包或自定义包,自定义包[下载地址](https://echarts.apache.org/zh/builder.html)<br>
2、或者使用`npm`安装`echarts`
**注意**
* 🔔 echarts 5.3.0及以上
* 🔔 如果是 `cli` 项目请下载插件到`src`目录下的`uni_modules`,没有这个目录就创建一个
## 代码演示
### Vue2
- 引入依赖,可以是插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html),也可以是`npm`包
```html
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef" @finished="init"></l-echart></view>
```
```js
// 插件内的 三选一
import * as echarts from '@/uni_modules/lime-echart/static/echarts.min'
// 自定义的 三选一 下载后放入项目的路径
import * as echarts from 'xxx/echarts.min'
// npm包 三选一 需要在控制台 输入命令npm install echarts
import * as echarts from 'echarts'
```
```js
export default {
data() {
return {
option: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
confine: true
},
legend: {
data: ['热度', '正面', '负面']
},
grid: {
left: 20,
right: 20,
bottom: 15,
top: 40,
containLabel: true
},
xAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
series: [
{
name: '热度',
type: 'bar',
label: {
normal: {
show: true,
position: 'inside'
}
},
data: [300, 270, 340, 344, 300, 320, 310],
},
{
name: '正面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true
}
},
data: [120, 102, 141, 174, 190, 250, 220]
},
{
name: '负面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true,
position: 'left'
}
},
data: [-20, -32, -21, -34, -90, -130, -110]
}
]
},
};
},
// 组件能被调用必须是组件的节点已经被渲染到页面上
methods: {
async init() {
// chart 图表实例不能存在data里
const chart = await this.$refs.chartRef.init(echarts);
chart.setOption(this.option)
}
}
}
```
### Vue3
- 小程序可以使用`require`引入插件内提供或自己下载的[自定义包](https://echarts.apache.org/zh/builder.html)
- `require`仅支持相对路径,不支持路径别名
- 非小程序使用 `npm`
```html
<view style="width:750rpx; height:750rpx"><l-echart ref="chartRef"></l-echart></view>
```
```js
// 小程序 二选一
// 插件内的 二选一
const echarts = require('../../uni_modules/lime-echart/static/echarts.min');
// 自定义的 二选一 下载后放入项目的路径
const echarts = require('xxx/xxx/echarts');
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 非小程序
// 需要在控制台 输入命令npm install echarts
import * as echarts from 'echarts'
```
```js
const chartRef = ref(null)
const option = {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
confine: true
},
legend: {
data: ['热度', '正面', '负面']
},
grid: {
left: 20,
right: 20,
bottom: 15,
top: 40,
containLabel: true
},
xAxis: [
{
type: 'value',
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
yAxis: [
{
type: 'category',
axisTick: { show: false },
data: ['汽车之家', '今日头条', '百度贴吧', '一点资讯', '微信', '微博', '知乎'],
axisLine: {
lineStyle: {
color: '#999999'
}
},
axisLabel: {
color: '#666666'
}
}
],
series: [
{
name: '热度',
type: 'bar',
label: {
normal: {
show: true,
position: 'inside'
}
},
data: [300, 270, 340, 344, 300, 320, 310],
},
{
name: '正面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true
}
},
data: [120, 102, 141, 174, 190, 250, 220]
},
{
name: '负面',
type: 'bar',
stack: '总量',
label: {
normal: {
show: true,
position: 'left'
}
},
data: [-20, -32, -21, -34, -90, -130, -110]
}
]
};
onMounted( ()=>{
// 组件能被调用必须是组件的节点已经被渲染到页面上
setTimeout(async()=>{
if(!chartRef.value) return
const myChart = await chartRef.value.init(echarts)
myChart.setOption(option)
},300)
})
```
### Uvue
- Uvue和Nvue不需要引入`echarts`,因为它们的实现方式是`webview`
- uniapp x需要HBX 4.13以上
```html
<view style="width: 100%; height: 408px;">
<l-echart ref="chartRef" @finished="init"></l-echart>
</view>
```
```js
// @ts-nocheck
// #ifdef H5
import * as echarts from 'echarts/dist/echarts.esm.js'
// #endif
const chartRef = ref<LEchartComponentPublicInstance|null>(null);
const init = async () => {
if(chartRef.value== null) return
// #ifdef APP
const chart = await chartRef.value!.init(null)
// #endif
// #ifdef H5
const chart = await chartRef.value!.init(echarts, null)
// #endif
chart.setOption(option)
}
```
## 数据更新
- 1、使用 `ref` 可获取`setOption`设置更新
- 2、也可以拿到图表实例`chart`设置`myChart.setOption(data)`
```js
// ref
this.$refs.chart.setOption(data)
// 图表实例
myChart.setOption(data)
```
## 图表大小
- 在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。
```js
// 默认获取容器尺寸
this.$refs.chart.resize()
// 指定尺寸
this.$refs.chart.resize({width: 375, height: 375})
```
## 自定义Tooltips
- uvue\nvue 不支持
由于除H5之外都不存在dom但又有tooltips个性化的需求代码就不贴了看示例吧
```
代码位于/uni_modules/lime-echart/component/lime-echart
```
## 插件标签
- 默认 l-echart 为 component
- 默认 lime-echart 为 demo
```html
// 在任意地方使用可查看domo, 代码位于/uni_modules/lime-echart/component/lime-echart
<lime-echart></lime-echart>
```
## 常见问题
- 钉钉小程序 由于没有`measureText`,模拟的`measureText`又无法得到当前字体的`fontWeight`,故可能存在估计不精细的问题
- 微信小程序 `2d` 只支持 真机调试2.0
- 微信开发工具会出现 `canvas` 不跟随页面的情况,真机不影响
- 微信开发工具会出现 `canvas` 层级过高的问题,真机一般不受影响,可以先测只有两个元素的页面看是否会有层级问题。
- toolbox 不支持 `saveImage`
- echarts 5.3.0 的 lines 不支持 trailLength故需设置为 `0`
- dataZoom H5不要设置 `showDetail`
- 如果微信小程序的`tooltip`文字有阴影,可能是微信的锅,临时解决方法是`tooltip.shadowBlur = 0`
- 如果钉钉小程序上传时报安全问题`Uint8Clamped`,可以向钉钉反馈是安全代码扫描把Uint8Clamped数组错误识别了也可以在 echarts 文件修改`Uint8Clamped`
```js
// 找到这段代码把代码中`Uint8Clamped`改成`Uint8_Clamped`,再把下划线去掉,不过直接去掉`Uint8Clamped`也是可行的
// ["Int8","Uint8","Uint8Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e+"Array]"]
// 改成如下
["Int8","Uint8","Uint8_Clamped","Int16","Uint16","Int32","Uint32","Float32","Float64"],(function(t,e){return t["[object "+e.replace('_','')+"Array]"]
```
### vue3
如果您是使用 **vite + vue3** 非微信小程序可能会遇到`echarts`文件缺少`wx`判断导致无法使用或缺少`tooltip`<br>
方式一:可以在`echarts.min.js`文件开头增加以下内容参考插件内的echart.min.js的做法
```js
// 某些echarts版本下 uniapp app 需要global不然会报__ECHARTS__DEFAULT__RENDERER__
let global = null
let wx = uni
```
方式二:在`vite.config.js`的`define`设置环境
```js
// 或者在`vite.config.js`的`define`设置环境
import { defineConfig } from 'vite';
import uni from '@dcloudio/vite-plugin-uni';
const define = {}
if(!["mp-weixin", "h5", "web"].includes(process.env.UNI_PLATFORM)) {
define['global'] = null
define['wx'] = 'uni'
}
export default defineConfig({
plugins: [uni()],
define
});
```
## Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --------------- | -------- | ------- | ------------ | ----- |
| custom-style | 自定义样式 | `string` | - | - |
| type | 指定 canvas 类型 | `string` | `2d` | |
| is-disable-scroll | 触摸图表时是否禁止页面滚动 | `boolean` | `false` | |
| beforeDelay | 延迟初始化 (毫秒) | `number` | `30` | |
| enableHover | PC端使用鼠标悬浮 | `boolean` | `false` | |
| landscape | 是否旋转90deg,模拟横屏效果 | `boolean` | `false` | |
## 事件
| 参数 | 说明 |
| --------------- | --------------- |
| init(echarts, chart => {}) | 初始化调用函数,第一个参数是传入`echarts`,第二个参数是回调函数,回调函数的参数是 `chart` 实例 |
| setChart(chart => {}) | 已经初始化后,请使用这个方法,是个回调函数,参数是 `chart` 实例 |
| setOption(data) | [图表配置项](https://echarts.apache.org/zh/option.html#title),用于更新 ,传递是数据 `option` |
| clear() | 清空当前实例,会移除实例中所有的组件和图表。 |
| dispose() | 销毁实例 |
| showLoading() | 显示加载 |
| hideLoading() | 隐藏加载 |
| [canvasToTempFilePath](https://uniapp.dcloud.io/api/canvas/canvasToTempFilePath.html#canvastotempfilepath)(opt) | 用于生成图片,与官方使用方法一致,但不需要传`canvasId` |
## 打赏
如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/alipay.png)
![](https://testingcf.jsdelivr.net/gh/liangei/image@1.9/wpay.png)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style type="text/css">
html,
body,
.canvas {
padding: 0;
margin: 0;
overflow-y: hidden;
background-color: transparent;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="canvas" id="limeChart"></div>
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
<script type="text/javascript" src="./echarts.min.js"></script>
<script type="text/javascript" src="./ecStat.min.js"></script>
<!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
<script>
let chart = null;
let cache = [];
console.log = function(...agrs) {
postMessage(agrs)
}
function emit(event, data) {
let dataStr = JSON.stringify(data, stringify)
postMessage({
event,
data: dataStr
})
cache = []
}
function postMessage(data) {
uni.postMessage({
data
});
}
function stringify(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
return;
}
cache.push(value);
}
return value;
}
function parse(name, callback, options) {
const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
if (optionNameReg.test(callback)) {
const optionNames = callback.match(optionNameReg)
if(optionNames[1]) {
const _this = optionNames[1].split('.')[0]
window[_this] = {}
window[_this][optionNames[2]] = options
return optionNames[2]
} else {
return null
}
}
return null
}
function init(callback, options, opts = {}, theme = null) {
if(!chart) {
chart = echarts.init(document.getElementById('limeChart'), theme, opts)
if(options) {
chart.setOption(options)
}
// const name = parse('a', callback, options)
// console.log('options::', callback)
// if(name) this[name] = options
// eval(`a = ${callback};`)
// if(a) {a(chart)}
}
}
function setChart(callback, options) {
if(!callback) return
if(chart && callback && options) {
var r = null
const name = parse('r', callback, options)
if(name) this[name] = options
eval(`r = ${callback};`)
if(r) {r(chart)}
}
}
function setOption(data) {
if (chart) chart.setOption(data[0], data[1])
}
function showLoading(data) {
if (chart) chart.showLoading(data[0], data[1])
}
function hideLoading() {
if (chart) chart.hideLoading()
}
function clear() {
if (chart) chart.clear()
}
function dispose() {
if (chart) chart.dispose()
}
function resize(size) {
if (chart) chart.resize(size)
}
function canvasToTempFilePath(opt = {}) {
if (chart) {
const src = chart.getDataURL(opt)
postMessage({
file: true,
data: src
})
}
}
</script>
</body>
</html>

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style type="text/css">
html,
body,
.canvas {
padding: 0;
margin: 0;
overflow-y: hidden;
background-color: transparent;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="canvas" id="limeChart"></div>
<script type="text/javascript" src="./uni.webview.1.5.3.js"></script>
<script type="text/javascript" src="./echarts.min.js"></script>
<script type="text/javascript" src="./ecStat.min.js"></script>
<!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
<script>
let chart = null;
let cache = [];
console.log = function(...agrs) {
postMessage(agrs)
}
function emit(event, data) {
let dataStr = JSON.stringify(data, stringify)
postMessage({
event,
data: dataStr
})
cache = []
}
function postMessage(data) {
uni.postMessage({
data
});
}
function stringify(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
return;
}
cache.push(value);
}
return value;
}
function parse(name, callback, options) {
const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
if (optionNameReg.test(callback)) {
const optionNames = callback.match(optionNameReg)
if (optionNames[1]) {
const _this = optionNames[1].split('.')[0]
window[_this] = {}
window[_this][optionNames[2]] = options
return optionNames[2]
} else {
return null
}
}
return null
}
function init(callback, options, opts = {}, theme = null) {
if (!chart) {
chart = echarts.init(document.getElementById('limeChart'), theme, opts)
if (options) {
chart.setOption(options)
}
// const name = parse('a', callback, options)
// console.log('options::', callback)
// if(name) this[name] = options
// eval(`a = ${callback};`)
// if(a) {a(chart)}
}
}
function setChart(callback, options) {
if (!callback) return
if (chart && callback && options) {
var r = null
const name = parse('r', callback, options)
if (name) this[name] = options
eval(`r = ${callback};`)
if (r) {
r(chart)
}
}
}
function setOption(data) {
if (chart) chart.setOption(data[0], data[1])
}
function showLoading(data) {
if (chart) chart.showLoading(data[0], data[1])
}
function hideLoading() {
if (chart) chart.hideLoading()
}
function clear() {
if (chart) chart.clear()
}
function dispose() {
if (chart) chart.dispose()
}
function resize(size) {
if (chart) chart.resize(size)
}
function canvasToTempFilePath(opt = {}) {
if (chart) {
const src = chart.getDataURL(opt)
postMessage({
file: true,
data: src
})
}
}
function on(data) {
if (chart && data.length > 0) {
const [type, query] = data
const useQuery = query && typeof query != 'function'
const key = `${type}${useQuery ? JSON.stringify(query): '' }`
if (query) {
chart.on(type, query, (options) => {
const obj = {}
Object.keys(options).forEach(key => {
if (key != 'event') {
obj[key] = options[key]
}
})
emit(`@${key}`, {
event: key,
options: obj
})
})
} else {
chart.on(type, (options) => {
const obj = {}
Object.keys(options).forEach(key => {
if (key != 'event') {
obj[key] = options[key]
}
})
emit(`@${key}`, {
event: key,
options: obj
})
})
}
}
}
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style type="text/css">
html,
body,
.canvas {
padding: 0;
margin: 0;
overflow-y: hidden;
background-color: transparent;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="canvas" id="limeChart"></div>
<script type="text/javascript" src="./uni.webview.1.5.5.js"></script>
<script type="text/javascript" src="./echarts.min.js"></script>
<script type="text/javascript" src="./ecStat.min.js"></script>
<!-- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/echarts-liquidfill@latest/dist/echarts-liquidfill.min.js"></script> -->
<script>
let chart = null;
let cache = [];
console.log = function() {
emit('log', {
log: arguments,
})
}
function emit(event, data) {
postMessage({
event,
data
})
cache = []
}
function postMessage(data) {
uni.webView.postMessage({
data
})
// window.__uniapp_x_.postMessage(JSON.stringify(data))
};
function stringify(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
return;
}
cache.push(value);
}
return value;
}
function parse(name, callback, options) {
const optionNameReg = /[\w]+\.setOption\(([\w]+\.)?([\w]+)\)/
if (optionNameReg.test(callback)) {
const optionNames = callback.match(optionNameReg)
if (optionNames[1]) {
const _this = optionNames[1].split('.')[0]
window[_this] = {}
window[_this][optionNames[2]] = options
return optionNames[2]
} else {
return null
}
}
return null
}
function init(callback, options, opts, theme) {
if (!chart) {
chart = echarts.init(document.getElementById('limeChart'), theme, opts)
if (options) {
chart.setOption(options)
}
}
}
function on(data) {
if (chart && data.length > 0) {
const [type, query] = data
const key = `${type}${JSON.stringify(query||'')}`
if (query) {
chart.on(type, query, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
} else {
chart.on(type, function(options) {
var obj = {};
Object.keys(options).forEach(function(key) {
if (key != 'event') {
obj[key] = options[key];
}
});
emit(key, {
event: key,
options: obj,
});
});
}
}
}
function setChart(callback, options) {
if (!callback) return
if (chart && callback && options) {
var r = null
const name = parse('r', callback, options)
if (name) this[name] = options
eval(`r = ${callback};`)
if (r) {
r(chart)
}
}
}
function setOption(data) {
if (chart) chart.setOption(data[0], data[1])
}
function showLoading(data) {
if (chart) chart.showLoading(data[0], data[1])
}
function hideLoading() {
if (chart) chart.hideLoading()
}
function clear() {
if (chart) chart.clear()
}
function dispose() {
if (chart) chart.dispose()
}
function resize(size) {
if (chart) chart.resize(size)
}
function canvasToTempFilePath(opt) {
if (chart) {
delete opt.success
const src = chart.getDataURL(opt)
postMessage({
// event: 'file',
file: src
})
}
}
document.addEventListener('touchmove', () => {
})
</script>
</body>
</html>

View File

@ -26,7 +26,7 @@
</wd-badge> </wd-badge>
<image <image
class="w-[286rpx] h-[286rpx] mt-[134rpx]" class="w-[286rpx] h-[286rpx] mt-[134rpx] mix-blend-multiply"
src="@/pages-sub/static/images/autoFill/auto-fill.jpg" src="@/pages-sub/static/images/autoFill/auto-fill.jpg"
></image> ></image>
</view> </view>

View File

@ -8,13 +8,13 @@
{{ year }} {{ year }}
<wd-icon name="arrow-down" size="16rpx" custom-class="ml-[80rpx]"></wd-icon> <wd-icon name="arrow-down" size="16rpx" custom-class="ml-[80rpx]"></wd-icon>
</view> </view>
<view <!-- <view
@click="handleShow(2)" @click="handleShow(2)"
class="px-[24rpx] py-[8rpx] bg-[#f8f8f8] rounded-[8rpx] flex justify-between items-center text-[24rpx]" class="px-[24rpx] py-[8rpx] bg-[#f8f8f8] rounded-[8rpx] flex justify-between items-center text-[24rpx]"
> >
{{ batche }} {{ batche }}
<wd-icon name="arrow-down" size="16rpx" custom-class="ml-[80rpx]"></wd-icon> <wd-icon name="arrow-down" size="16rpx" custom-class="ml-[80rpx]"></wd-icon>
</view> </view> -->
</view> </view>
<scroll-view class="mt-[38rpx] flex-1 pb-safe"> <scroll-view class="mt-[38rpx] flex-1 pb-safe">
@ -35,7 +35,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import WXXTable from '@/pages-sub/home/components/Table.vue' import WXXTable from '@/pages-sub/home/components/Table.vue'
import { getPlanProList } from '@/service/index/api' import { getAdmissionMark } from '@/service/index/api'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
const props = defineProps({ const props = defineProps({
@ -50,7 +50,6 @@ const userStore = useUserStore()
const batchList = [] const batchList = []
const batche = ref(batchList[0]) const batche = ref(batchList[0])
const batches = ref(batchList) const batches = ref(batchList)
const provinceName = userStore.userInfo.estimatedAchievement.provinceName
const year = ref('') const year = ref('')
const yearList = ref([]) const yearList = ref([])
@ -62,12 +61,12 @@ let pickType = 1
const tableData = ref([]) const tableData = ref([])
const columns = [ const columns = [
{ name: '年份', key: 'years', width: '10%' }, { name: '年份', key: 'year', width: '10%' },
{ name: '院校/批次', key: 'batchName', width: '19%' }, { name: '院校/批次', key: 'subject_name', width: '19%' },
{ name: '选考要求', key: 'selectsubject', width: '16%' }, { name: '选考要求', key: 'second_subject', width: '16%' },
{ name: '录取数', key: 'plancount', width: '13%' }, { name: '录取数', key: 'enroll_num', width: '13%' },
{ name: '分数', key: 'lowscore', width: '24%' }, { name: '分数', key: 'score', width: '24%' },
{ name: '最低位次', key: 'lowscorerank', width: '18%' }, { name: '最低位次', key: 'rank_min', width: '18%' },
] ]
let isFirst = true let isFirst = true
@ -92,7 +91,7 @@ const handleShow = (type: number) => {
} }
const getPlanProListData = async (newVal) => { const getPlanProListData = async (newVal) => {
getPlanProList({ getAdmissionMark({
years: year.value === '全部年份' ? '' : year.value, years: year.value === '全部年份' ? '' : year.value,
_uid: newVal, _uid: newVal,
batchName: batche.value === '全部分类' ? '' : batche.value, batchName: batche.value === '全部分类' ? '' : batche.value,
@ -100,12 +99,16 @@ const getPlanProListData = async (newVal) => {
}).then((resp) => { }).then((resp) => {
const _res = resp.result as { const _res = resp.result as {
batches: { batchName: string }[] batches: { batchName: string }[]
yearsDtos: string[] years: string[]
plans: any[] newZsplanfusions: any[]
} }
yearList.value = ['全部年份', ..._res.yearsDtos] yearList.value = ['全部年份', ..._res.years]
batches.value = ['全部分类', ..._res.batches.map((item) => item.batchName)] batches.value = ['全部分类']
tableData.value = _res.plans tableData.value = _res.newZsplanfusions.map((item) => {
let _item = item
_item.score = `最高分:${item.score_max !== '0' ? item.score_max : '--'} \n 最低分:${item.score_min !== '0' ? item.score_min : '--'} \n 平均分:${item.score_average !== '0' ? item.score_average : '--'}`
return _item
})
if (isFirst) { if (isFirst) {
year.value = yearList.value[0] year.value = yearList.value[0]

View File

@ -14,11 +14,11 @@
<view class="name">院校类型</view> <view class="name">院校类型</view>
</view> </view>
<view class="flex flex-col items-center"> <view class="flex flex-col items-center">
<view class="title">{{ universityResult?.resourcecount.doctorate }}</view> <view class="title">{{ universityResult?.resourcecount.doctorate || '-/-' }}</view>
<view class="name">博士点</view> <view class="name">博士点</view>
</view> </view>
<view class="flex flex-col items-center"> <view class="flex flex-col items-center">
<view class="title">{{ universityResult?.resourcecount.master }}</view> <view class="title">{{ universityResult?.resourcecount.master || '-/-' }}</view>
<view class="name">硕士点</view> <view class="name">硕士点</view>
</view> </view>
</view> </view>

View File

@ -12,7 +12,7 @@
@click="handleShow(2)" @click="handleShow(2)"
class="px-[24rpx] py-[8rpx] bg-[#f8f8f8] rounded-[8rpx] flex justify-between items-center text-[24rpx]" class="px-[24rpx] py-[8rpx] bg-[#f8f8f8] rounded-[8rpx] flex justify-between items-center text-[24rpx]"
> >
{{ batche }} {{ provinceName || '请选择省份' }}
<wd-icon name="arrow-down" size="16rpx" custom-class="ml-[80rpx]"></wd-icon> <wd-icon name="arrow-down" size="16rpx" custom-class="ml-[80rpx]"></wd-icon>
</view> </view>
</view> </view>
@ -23,7 +23,22 @@
<wd-action-sheet v-model="show" title=""> <wd-action-sheet v-model="show" title="">
<view class="px-[32rpx]"> <view class="px-[32rpx]">
<wd-picker-view :columns="pickList" v-model="pickValue" custom-class="picker-wrapper" /> <wd-picker-view
:columns="pickList"
v-model="pickValue"
custom-class="picker-wrapper"
v-if="pickType === 1"
/>
</view>
<view class="py-[32rpx] flex flex-col items-center justify-center">
<text class="text-[#303030] text-[36rpx] font-bold mb-[50rpx]">省份</text>
<Region
v-show="pickType === 2"
@change="handleRegionChange"
@change-name="handleRegionChangeName"
:defaultValue="[provinceCode]"
:max="1"
/>
</view> </view>
<view class="flex items-center justify-between px-[32rpx]"> <view class="flex items-center justify-between px-[32rpx]">
<view class="cancel-btn" @click="show = false">取消</view> <view class="cancel-btn" @click="show = false">取消</view>
@ -37,6 +52,7 @@
import WXXTable from '@/pages-sub/home/components/Table.vue' import WXXTable from '@/pages-sub/home/components/Table.vue'
import { getPlanProList } from '@/service/index/api' import { getPlanProList } from '@/service/index/api'
import { useUserStore } from '@/store' import { useUserStore } from '@/store'
import Region from '@/pages-sub/home/components/Region.vue'
const props = defineProps({ const props = defineProps({
id: { id: {
@ -47,10 +63,8 @@ const props = defineProps({
const userStore = useUserStore() const userStore = useUserStore()
const batchList = [] const provinceName = ref(userStore.userInfo.city.provincename)
const batche = ref(batchList[0]) const provinceCode = userStore.userInfo.city.code
const batches = ref(batchList)
const provinceName = userStore.userInfo.estimatedAchievement.provinceName
const year = ref('') const year = ref('')
const yearList = ref([]) const yearList = ref([])
@ -87,7 +101,7 @@ const handleShow = (type: number) => {
pickList.value = yearList.value pickList.value = yearList.value
pickValue.value = year.value pickValue.value = year.value
} else if (type === 2) { } else if (type === 2) {
pickList.value = batches.value pickList.value = []
} }
} }
@ -95,7 +109,7 @@ const getPlanProListData = async (newVal) => {
getPlanProList({ getPlanProList({
years: year.value === '全部年份' ? '' : year.value, years: year.value === '全部年份' ? '' : year.value,
_uid: newVal, _uid: newVal,
batchName: batche.value === '全部分类' ? '' : batche.value, batchName: '',
locationCode: userStore.userInfo.estimatedAchievement.provinceCode, locationCode: userStore.userInfo.estimatedAchievement.provinceCode,
}).then((resp) => { }).then((resp) => {
const _res = resp.result as { const _res = resp.result as {
@ -104,12 +118,11 @@ const getPlanProListData = async (newVal) => {
plans: any[] plans: any[]
} }
yearList.value = ['全部年份', ..._res.yearsDtos] yearList.value = ['全部年份', ..._res.yearsDtos]
batches.value = ['全部分类', ..._res.batches.map((item) => item.batchName)]
tableData.value = _res.plans tableData.value = _res.plans
if (isFirst) { if (isFirst) {
year.value = yearList.value[0] year.value = yearList.value[0]
batche.value = batches.value[0]
} }
isFirst = false isFirst = false
}) })
@ -119,11 +132,19 @@ const handleConfirm = () => {
if (pickType === 1) { if (pickType === 1) {
year.value = pickValue.value year.value = pickValue.value
} else if (pickType === 2) { } else if (pickType === 2) {
batche.value = pickValue.value
} }
show.value = false show.value = false
getPlanProListData(props.id) getPlanProListData(props.id)
} }
const handleRegionChange = (val) => {
console.log(val)
}
const handleRegionChangeName = (val) => {
console.log(val)
provinceName.value = val.join(',')
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.submit-btn { .submit-btn {

View File

@ -12,7 +12,7 @@
</route> </route>
<template> <template>
<view class="content"> <view class="">
<z-paging <z-paging
ref="paging" ref="paging"
use-virtual-list use-virtual-list
@ -164,14 +164,5 @@ const handleNatureChange = (val) => {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.wd-navbar__content) { @import '../styles/navbar_search.scss';
display: flex;
align-items: center;
.wd-navbar__title {
display: flex;
align-items: center;
margin-left: 112rpx;
}
}
</style> </style>

View File

@ -42,7 +42,14 @@
{{ universityBaseInfo?.universityResult.features.slice(0, 4).join('/&nbsp;') }} {{ universityBaseInfo?.universityResult.features.slice(0, 4).join('/&nbsp;') }}
</text> </text>
</view> </view>
<wd-button icon="star" custom-class="collect-btn" plain @click="handleStar"></wd-button> <wd-button
:icon="startFlag ? 'star-filled' : 'star'"
custom-class="collect-btn"
plain
@click="handleStar"
>
收藏
</wd-button>
</view> </view>
<view class="card-swiper mb-[32rpx]"> <view class="card-swiper mb-[32rpx]">
@ -68,7 +75,7 @@
:slidable-num="4" :slidable-num="4"
:offset-top="90" :offset-top="90"
> >
<wd-tab :title="`院校简介`" :name="0"> <wd-tab title="院校简介" :name="0">
<view class="bg-[#f8f8f8]"> <view class="bg-[#f8f8f8]">
<Profile :university-result="universityBaseInfo?.universityResult" /> <Profile :university-result="universityBaseInfo?.universityResult" />
<FirstClass :id="collegeId" /> <FirstClass :id="collegeId" />
@ -105,8 +112,15 @@ import EnrollmentPlan from './components/EnrollmentPlan.vue'
import EnrollmentIntro from './components/EnrollmentIntro.vue' import EnrollmentIntro from './components/EnrollmentIntro.vue'
import EnrollmentMark from './components/EnrollmentMark.vue' import EnrollmentMark from './components/EnrollmentMark.vue'
import Situation from './components/Situation.vue' import Situation from './components/Situation.vue'
import { getUniversityInfo } from '@/service/index/api' import {
getUniversityInfo,
getUnCollectionList,
deleteUnCollection,
addUnCollection,
} from '@/service/index/api'
import { useUserStore } from '@/store'
const userStore = useUserStore()
const navigatorBack = () => { const navigatorBack = () => {
uni.navigateBack() uni.navigateBack()
} }
@ -120,13 +134,44 @@ onLoad((options) => {
getUniversityInfo({ Id: collegeId.value }).then((res) => { getUniversityInfo({ Id: collegeId.value }).then((res) => {
if (res.code === 200) { if (res.code === 200) {
universityBaseInfo.value = res.result universityBaseInfo.value = res.result
getUnCollectionList({
SearchKey: universityBaseInfo.value.universityResult.name,
}).then((resp) => {
if (resp.code === 200 && (resp.result as any[]).length > 0) {
startFlag.value = true
} else {
startFlag.value = false
}
})
} }
}) })
}) })
const tabIndex = ref<number>(0) const tabIndex = ref<number>(0)
const handleStar = () => {} const startFlag = ref(false)
const handleStar = () => {
if (startFlag.value && universityBaseInfo.value.universityResult._id) {
//
deleteUnCollection({
wxId: userStore.userInfo.estimatedAchievement.wxId,
uId: universityBaseInfo.value._id,
}).then((resp) => {
if (resp.code === 200) {
startFlag.value = false
}
})
} else {
addUnCollection({
wxId: userStore.userInfo.estimatedAchievement.wxId,
uId: universityBaseInfo.value._id,
}).then((resp) => {
if (resp.code === 200) {
startFlag.value = true
}
})
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -17,7 +17,7 @@
{{ item.name }} {{ item.name }}
</view> </view>
</view> </view>
<view class="flex-1 px-[32rpx] py-[24rpx] h-[50vh] overflow-y-auto"> <view class="flex-1 py-[24rpx] h-[50vh] overflow-y-auto">
<Phase v-show="currentMenu === 1" /> <Phase v-show="currentMenu === 1" />
<Region v-show="currentMenu === 2" /> <Region v-show="currentMenu === 2" />
<UniType v-show="currentMenu === 3" /> <UniType v-show="currentMenu === 3" />

View File

@ -1,10 +1,5 @@
<template> <template>
<CheckGroup <CheckGroup :list="regionList" @change="handleChange" label-key="simplename" v-bind="$attrs" />
:list="regionList"
@change="handleChange"
:default-value="regionInfo"
label-key="simplename"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -30,13 +25,6 @@ getRegionInfo().then((res) => {
} }
}) })
const props = defineProps({
regionInfo: {
type: Array as PropType<string[]>,
default: () => [],
},
})
const emits = defineEmits(['change', 'changeName']) const emits = defineEmits(['change', 'changeName'])
const handleChange = (val: any) => { const handleChange = (val: any) => {

View File

@ -4,6 +4,7 @@
custom-class="flex flex-wrap gap-[16rpx] px-[32rpx] pb-[16rpx] items-center" custom-class="flex flex-wrap gap-[16rpx] px-[32rpx] pb-[16rpx] items-center"
checked-color="#1580FF" checked-color="#1580FF"
@change="handleChange" @change="handleChange"
v-bind="$attrs"
> >
<wd-checkbox <wd-checkbox
v-for="item in list" v-for="item in list"
@ -45,15 +46,7 @@ defineOptions({
}) })
const emits = defineEmits(['change']) const emits = defineEmits(['change'])
const defValue = ref<string[]>([]) const defValue = ref<string[]>(props.defaultValue)
watch(
() => props.defaultValue,
(newV) => {
defValue.value = props.defaultValue
},
{ immediate: true },
)
const handleChange = (val: unknown) => { const handleChange = (val: unknown) => {
emits('change', val) emits('change', val)

View File

@ -1,9 +1,8 @@
import { getCountryRules } from '@/service/index/api' import { getCountryRules } from '@/service/index/api'
import { useRulesStore } from '@/store/rules'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
import { RuleResult } from '@/types/app-type' import { RuleResult } from '@/types/app-type'
const rulesStore = useRulesStore()
const userStore = useUserStore() const userStore = useUserStore()
export const requireSubjectList = ref([]) export const requireSubjectList = ref([])
@ -17,7 +16,7 @@ export const useRules = () => {
getCountryRules().then((res) => { getCountryRules().then((res) => {
if (res.code === 200) { if (res.code === 200) {
const results = res.result as RuleResult[] const results = res.result as RuleResult[]
rulesStore.setRules(results)
const matchRule = results.filter((item) => item.p === userStore.userInfo.city.policy) const matchRule = results.filter((item) => item.p === userStore.userInfo.city.policy)
const matchRules = matchRule.length > 0 ? matchRule[0].rule : [] const matchRules = matchRule.length > 0 ? matchRule[0].rule : []
requireSubjectList.value = matchRules.filter((item) => item?.isfirst === 'true') requireSubjectList.value = matchRules.filter((item) => item?.isfirst === 'true')

View File

@ -0,0 +1,101 @@
<template>
<view class="bg-white flex w-full flex-1">
<view class="w-[260rpx] bg-[#F7F8FA] flex flex-col items-center">
<view
:class="`flex items-center view-block pl-[32rpx] h-[96rpx] text-[28rpx] font-semibold ${item.key === currentMenu ? 'active' : ''}`"
v-for="item in menus"
:key="item.key"
@click="changeMenu(item)"
>
{{ item.name }}
</view>
</view>
<view class="flex-1 mt-[40rpx] flex flex-col">
<MajorTreeList :sub-major-list="currentMenuObj" />
</view>
</view>
</template>
<script lang="ts" setup>
import { getMajorLevelList } from '@/service/index/api'
import MajorTreeList from './MajorTreeList.vue'
const props = defineProps({
type: {
type: Number,
default: 1050,
},
keyword: {
type: String,
default: '',
},
})
const menus = ref([])
const currentMenu = ref('')
const currentMenuObj = ref<{
name: string
key: string
count: number
itemsCount: number
items: any[]
}>({
name: '',
key: '',
count: 0,
itemsCount: 0,
items: [],
})
const subMajorList = ref([])
const changeMenu = (item: {
name: string
key: string
count: number
itemsCount: number
items: any[]
}) => {
currentMenu.value = item.key
currentMenuObj.value = item
}
const getMajorList = ({ Type, KeyWord }) => {
getMajorLevelList({ Type, KeyWord }).then((resp) => {
if (resp.code === 200) {
menus.value = resp.result[0].rootDtos as { key: string; name: string; items: any[] }[]
currentMenu.value = menus.value[0].key
subMajorList.value = menus.value[0].items
currentMenuObj.value = menus.value[0]
}
})
}
watch(
[() => props.type],
([type]) => {
getMajorList({ Type: type, KeyWord: props.keyword })
},
{ immediate: true },
)
watch(
() => props.keyword,
(newV) => {
getMajorList({ Type: props.type, KeyWord: newV })
},
)
</script>
<style lang="scss" scoped>
.active {
background-color: #fff;
color: #1580ff;
}
.view-block {
width: calc(100% - 32rpx);
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<scroll-view class="h-full">
<view class="font-semibold text-[#303030] text-[28rpx] mb-[32rpx] px-[32rpx]">
{{ subMajorList.name }}
<text class="text-[22rpx] text-[#bfbfbf] font-normal">({{ subMajorList.count }})</text>
</view>
<wd-collapse v-model="value" accordion>
<wd-collapse-item
:name="item.key"
custom-class="custom-collapse-item"
v-for="item in subMajorList.items"
>
<template #title="{ expanded, disabled, isFirst }">
<view class="header">
<text :class="`text-[24rpx] text-[#303030] ${expanded ? 'text-[#1580FF]!' : ''}`">
{{ item.name }}
</text>
<view class="flex items-center justify-between">
<text
:class="`mr-[12rpx] text-[24rpx] text-[#303030] ${expanded ? 'text-[#1580FF]!' : ''}`"
>
{{ item.childMajors.length }}
</text>
<wd-icon name="arrow-up" size="16rpx" v-if="expanded" color="#1580FF"></wd-icon>
<wd-icon name="arrow-down" size="16rpx" v-else></wd-icon>
</view>
</view>
</template>
<view :scroll-x="true" class="flex flex-col gap-[48rpx] h-full">
<view
class="text-[24rpx] text-[#303030] ml-[26rpx]"
v-for="childMajor in item.childMajors"
:key="item"
>
{{ childMajor.zymc }}
</view>
</view>
</wd-collapse-item>
</wd-collapse>
</scroll-view>
</template>
<script lang="ts" setup>
const props = defineProps({
subMajorList: {
type: Object as () => {
name: string
key: string
count: number
itemsCount: number
items: any[]
},
default: () => ({
name: '',
key: '',
count: 0,
itemsCount: 0,
items: [],
}),
},
})
const value = ref<string>('')
watch(
() => props.subMajorList,
(newV) => {
value.value = newV.items[0].key.toString()
},
)
</script>
<style lang="scss" scoped>
:deep(.custom-collapse-item) {
padding: 16rpx 0 !important;
:deep(.wd-collapse-item__header) {
}
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
</style>

View File

@ -1,5 +1,80 @@
<route lang="json5" type="page">
{
layout: 'page',
style: {
navigationStyle: 'custom',
},
}
</route>
<template> <template>
<text>查专业</text> <scroll-view :scroll-y="true" class="h-screen flex flex-col">
<wd-navbar
safeAreaInsetTop
custom-class="bg-white!"
:bordered="false"
left-arrow
placeholder
fixed
@click-left="navigatorBack"
>
<template #title>
<wd-input
type="text"
v-model="searchValue"
placeholder="搜索专业"
@confirm="handleChange"
no-border
prefixIcon="search"
custom-class="rounded-[48rpx]! bg-[#F7F7F7]! flex items-center border-none! px-[24rpx] py-[12rpx]"
custom-input-class="h-full flex items-center"
/>
</template>
</wd-navbar>
<wd-tabs
sticky
color="#303030"
inactiveColor="#BFBFBF"
custom-class="tabs-wrapper"
swipeable
slidable="auto"
:slidable-num="4"
:offset-top="98"
animated
>
<wd-tab title="普通本科" :name="1050">
<MajorList :type="1050" :keyword="searchValue"></MajorList>
</wd-tab>
<wd-tab title="职教本科" :name="1070">
<MajorList :type="1070" :keyword="searchValue"></MajorList>
</wd-tab>
<wd-tab title="高职专科" :name="1060">
<MajorList :type="1060" :keyword="searchValue"></MajorList>
</wd-tab>
</wd-tabs>
</scroll-view>
</template> </template>
<script lang="ts" setup></script> <script lang="ts" setup>
import MajorList from './components/MajorList.vue'
const searchValue = ref('')
const navigatorBack = () => {
uni.navigateBack()
}
const handleChange = () => {}
</script>
<style lang="scss" scoped>
@import '../styles/navbar_search.scss';
:deep(.tabs-wrapper) {
--wot-tabs-nav-fs: 28rpx;
--wot-tabs-nav-height: 80rpx;
flex: 1 1 auto;
}
:deep(.wd-tab__body) {
// height: 100%;
}
</style>

View File

@ -0,0 +1,13 @@
:deep(.wd-navbar__content) {
display: flex;
align-items: center;
.wd-navbar__title {
display: flex;
align-items: center;
margin-left: 112rpx;
}
}
:deep(.wd-input__prefix) {
line-height: 1 !important;
}

View File

@ -202,7 +202,11 @@
}, },
{ {
"path": "home/major/index", "path": "home/major/index",
"type": "page" "type": "page",
"layout": "page",
"style": {
"navigationStyle": "custom"
}
}, },
{ {
"path": "home/news/index", "path": "home/news/index",
@ -246,6 +250,15 @@
} }
} }
] ]
},
{
"root": "pages-evaluation-sub",
"pages": [
{
"path": "index",
"type": "page"
}
]
} }
] ]
} }

View File

@ -130,8 +130,26 @@ export const addUnCollection = (params: { wxId: number; uId: number }) => {
export const getUnCollectionList = (params: { export const getUnCollectionList = (params: {
SearchKey: string SearchKey: string
Page: number Page?: number
PageSize: number PageSize?: number
}) => { }) => {
//获取用户院校收藏表列表
return http.get('/api/unCollection/list', params) return http.get('/api/unCollection/list', params)
} }
export const getMajorLevelList = (params: { Type: number; KeyWord: string }) => {
return http.get(`/api/zhiYuan/majors`, params)
}
export const getAdmissionMark = (params: {
_uid: number
years: string
batchName: string
locationCode: string
}) => {
return http.get('/api/PlanPro/admissionMark', params)
}
export const deleteUnCollection = (params: { wxId: number; uId: number }) => {
return http.post('/api/unCollection/delete', params)
}

View File

@ -1,36 +0,0 @@
import { RulesStore } from '@/types/app-type'
import { defineStore } from 'pinia'
const initState = {
rules: [],
}
export const useRulesStore = defineStore(
'rules',
() => {
const store = ref<RulesStore>({ ...initState })
const setRules = (val: Array<any>) => {
store.value.rules = val
}
const clearRules = () => {
store.value = { ...initState }
}
// 一般没有reset需求不需要的可以删除
const reset = () => {
store.value = { ...initState }
}
return {
store,
clearRules,
setRules,
reset,
}
},
{
persist: true,
},
)

View File

@ -86,7 +86,7 @@ declare global {
const toValue: typeof import('vue')['toValue'] const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef'] const triggerRef: typeof import('vue')['triggerRef']
const unSortTypeList: typeof import('../hooks/useUnSortType')['unSortTypeList'] const unSortTypeList: typeof import('../hooks/useUnSortType')['unSortTypeList']
const universityBaseInfo: typeof import('../hooks/useUserInfo')['universityBaseInfo'] const universityBaseInfo: typeof import('../login-sub/hooks/useUserInfo')['universityBaseInfo']
const unref: typeof import('vue')['unref'] const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs'] const useAttrs: typeof import('vue')['useAttrs']
const useCityInfo: typeof import('../hooks/useCityInfoHook')['useCityInfo'] const useCityInfo: typeof import('../hooks/useCityInfoHook')['useCityInfo']
@ -95,7 +95,7 @@ declare global {
const useCssModule: typeof import('vue')['useCssModule'] const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars'] const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId'] const useId: typeof import('vue')['useId']
const useLogin: typeof import('../hooks/useUserInfo')['useLogin'] const useLogin: typeof import('../login-sub/hooks/useUserInfo')['useLogin']
const useModel: typeof import('vue')['useModel'] const useModel: typeof import('vue')['useModel']
const useNatureList: typeof import('../hooks/useFilterSchool')['useNatureList'] const useNatureList: typeof import('../hooks/useFilterSchool')['useNatureList']
const useNavbarWeixin: typeof import('../hooks/useNavbarWeixin')['default'] const useNavbarWeixin: typeof import('../hooks/useNavbarWeixin')['default']
@ -106,13 +106,13 @@ declare global {
const useSlots: typeof import('vue')['useSlots'] const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef'] const useTemplateRef: typeof import('vue')['useTemplateRef']
const useUnSortType: typeof import('../hooks/useUnSortType')['useUnSortType'] const useUnSortType: typeof import('../hooks/useUnSortType')['useUnSortType']
const useUniversityInfo: typeof import('../hooks/useUserInfo')['useUniversityInfo'] const useUniversityInfo: typeof import('../login-sub/hooks/useUserInfo')['useUniversityInfo']
const useUniversityLevel: typeof import('../hooks/useFilterSchool')['useUniversityLevel'] const useUniversityLevel: typeof import('../hooks/useFilterSchool')['useUniversityLevel']
const useUniversityRank: typeof import('../hooks/useUnSortType')['useUniversityRank'] const useUniversityRank: typeof import('../hooks/useUnSortType')['useUniversityRank']
const useUniversityType: typeof import('../hooks/useFilterSchool')['useUniversityType'] const useUniversityType: typeof import('../hooks/useFilterSchool')['useUniversityType']
const useUpload: typeof import('../hooks/useUpload')['default'] const useUpload: typeof import('../hooks/useUpload')['default']
const useUpload2: typeof import('../hooks/useUpload2')['default'] const useUpload2: typeof import('../hooks/useUpload2')['default']
const useWxInfo: typeof import('../hooks/useUserInfo')['useWxInfo'] const useWxInfo: typeof import('../login-sub/hooks/useUserInfo')['useWxInfo']
const watch: typeof import('vue')['watch'] const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect'] const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect'] const watchPostEffect: typeof import('vue')['watchPostEffect']
@ -210,7 +210,6 @@ declare module 'vue' {
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']> readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']> readonly useCssVars: UnwrapRef<typeof import('vue')['useCssVars']>
readonly useId: UnwrapRef<typeof import('vue')['useId']> readonly useId: UnwrapRef<typeof import('vue')['useId']>
readonly useLogin: UnwrapRef<typeof import('../hooks/useUserInfo')['useLogin']>
readonly useModel: UnwrapRef<typeof import('vue')['useModel']> readonly useModel: UnwrapRef<typeof import('vue')['useModel']>
readonly useNatureList: UnwrapRef<typeof import('../hooks/useFilterSchool')['useNatureList']> readonly useNatureList: UnwrapRef<typeof import('../hooks/useFilterSchool')['useNatureList']>
readonly useNavbarWeixin: UnwrapRef<typeof import('../hooks/useNavbarWeixin')['default']> readonly useNavbarWeixin: UnwrapRef<typeof import('../hooks/useNavbarWeixin')['default']>
@ -223,7 +222,6 @@ declare module 'vue' {
readonly useUniversityRank: UnwrapRef<typeof import('../hooks/useUnSortType')['useUniversityRank']> readonly useUniversityRank: UnwrapRef<typeof import('../hooks/useUnSortType')['useUniversityRank']>
readonly useUniversityType: UnwrapRef<typeof import('../hooks/useFilterSchool')['useUniversityType']> readonly useUniversityType: UnwrapRef<typeof import('../hooks/useFilterSchool')['useUniversityType']>
readonly useUpload: UnwrapRef<typeof import('../hooks/useUpload')['default']> readonly useUpload: UnwrapRef<typeof import('../hooks/useUpload')['default']>
readonly useWxInfo: UnwrapRef<typeof import('../hooks/useUserInfo')['useWxInfo']>
readonly watch: UnwrapRef<typeof import('vue')['watch']> readonly watch: UnwrapRef<typeof import('vue')['watch']>
readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']> readonly watchEffect: UnwrapRef<typeof import('vue')['watchEffect']>
readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']> readonly watchPostEffect: UnwrapRef<typeof import('vue')['watchPostEffect']>

View File

@ -27,7 +27,8 @@ interface NavigateToOptions {
"/pages-sub/home/news/newsList" | "/pages-sub/home/news/newsList" |
"/pages-sub/home/rank/index" | "/pages-sub/home/rank/index" |
"/pages-sub/home/schoolRank/index" | "/pages-sub/home/schoolRank/index" |
"/login-sub/index"; "/login-sub/index" |
"/pages-evaluation-sub/index";
} }
interface RedirectToOptions extends NavigateToOptions {} interface RedirectToOptions extends NavigateToOptions {}

View File

@ -1,30 +0,0 @@
## 2.8.52025-02-09
1.`新增` 方法`scrollToX`支持控制x轴滚动到指定位置。
2.`修复` 快手小程序中报错`await isn't allowed in non-async function`的问题。
3.`修复` 在iOS+nvue中设置了`:loading-more-enabled="false"`后,调用`scrollToBottom`无法滚动到底部的问题。
4.`修复` 在支付宝小程序+页面滚动中,数据为空时空数据图未居中的问题。
5.`优化` fetch types修改。
## 2.8.42024-12-02
1.`修复` 在虚拟列表+vue2中顶部占位采用transformY方案在虚拟列表+vue3中顶部占位采用view占位方案。以解决在vue2+微信小程序+安卓+兼容模式中,可能出现的虚拟列表闪动的问题。
2.`修复` 在列表渲染时(尤其是在虚拟列表中)偶现的【点击加载更多】闪现的问题。
3.`优化` 统一在RefresherStatus枚举中Loading取值。
4.`优化` `defaultPageNo`&`defaultPageSize`修改为只允许number类型。
5.`优化` 提升兼容性&细节优化。
## 2.8.32024-11-27
1.`修复` `doInsertVirtualListItem`插入数据无效的问题。
2.`优化` 提升兼容性&细节优化。
## 2.8.22024-11-25
1.`优化` types中`ZPagingRef`和`ZPagingInstance`支持泛型。
## 2.8.12024-11-24
1.`新增` 完整的`props`、`slots`、`methods`、`events`的typescript types声明可在ts中获得绝佳的代码提示体验。
2.`新增` `virtual-cell-id-prefix`虚拟列表cell id的前缀适用于一个页面有多个虚拟列表的情况用以区分不同虚拟列表cell的id。
3.`修复` 在vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若`z-paging`在`swiper-item`标签内的情况下存在的无法获取slot插入的cell高度的问题。
4.`修复` 在虚拟列表中分页数据小于1页时插入新数据虚拟列表未生效的问题。
5.`修复` 在虚拟列表中调用`refresh`时cell的index计算不正确的问题。
6.`修复` 在快手小程序中内容较少或空数据时`z-paging`未能铺满全屏的问题。
7.`优化` `events`中的参数涉及枚举的部分统一由之前的number类型修改为string类型展示更直观涉及的events`@query`中的`from`参数;`@refresherStatusChange`中的`status`参数;`@loadingStatusChange`中的`status`参数;`slot=refresher`中的`refresherStatus`参数;`slot=chatLoading`中的`loadingMoreStatus`参数。更新版本请特别留意!
## 2.8.02024-10-21
1.`新增` 全面支持鸿蒙Next。
2.`修复` 设置了`refresher-complete-delay`后在下拉刷新期间调用reload导致的无法再次下拉刷新的问题。
3.`优化` 废弃虚拟列表transformY顶部占位方案修改为空view占位。解决因使用旧方案导致的vue3中可能出现的虚拟列表闪动问题。提升虚拟列表的兼容性。

View File

@ -1,38 +0,0 @@
<!-- 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]" @touchstart="onTouchstart">
<slot />
</cell>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<view :style="[cellStyle]" @touchstart="onTouchstart">
<slot />
</view>
<!-- #endif -->
</template>
<script>
export default {
name: 'z-paging-cell',
props: {
//cellStyle
cellStyle: {
type: Object,
default: function () {
return {}
},
},
},
methods: {
onTouchstart(e) {
this.$emit('touchstart', e)
},
},
}
</script>

View File

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

View File

@ -1,143 +0,0 @@
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<!-- 滑动切换选项卡swiper-item此组件支持easycom规范可以在项目中直接引用 -->
<template>
<view class="zp-swiper-item-container">
<z-paging ref="paging" :fixed="false"
:auto="false" :useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle"
:preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol"
@query="_queryList" @listChange="_updateList">
<slot />
<template #header>
<slot name="header"/>
</template>
<template #cell="{item,index}">
<slot name="cell" :item="item" :index="index"/>
</template>
<template #footer>
<slot name="footer"/>
</template>
</z-paging>
</view>
</template>
<script>
import zPaging from '../z-paging/z-paging'
export default {
name: "z-paging-swiper-item",
components: { zPaging },
data() {
return {
firstLoaded: false
}
},
props: {
// indexswiper
tabIndex: {
type: Number,
default: function() {
return 0
}
},
// swiperindex
currentIndex: {
type: Number,
default: function() {
return 0
}
},
// 使
useVirtualList: {
type: Boolean,
default: false
},
// z-paging()use-virtual-listtruetrue
useInnerList: {
type: Boolean,
default: false
},
// cellkeynvuenvueuse-inner-list
cellKeyName: {
type: String,
default: ''
},
// innerList
innerListStyle: {
type: Object,
default: function() {
return {};
}
},
// ()1212celldom()
preloadPage: {
type: [Number, String],
default: 12
},
// cellfixedcellcelldynamicdynamicfixed
cellHeightMode: {
type: String,
default: 'fixed'
},
// 122
virtualListCol: {
type: [Number, String],
default: 1
},
// scroll60
virtualScrollFps: {
type: [Number, String],
default: 60
},
},
watch: {
currentIndex: {
handler(newVal, oldVal) {
if (newVal === this.tabIndex) {
// item
if (!this.firstLoaded) {
this.$nextTick(()=>{
let delay = 5;
// #ifdef MP-TOUTIAO
delay = 100;
// #endif
setTimeout(() => {
this.$refs.paging.reload().catch(() => {});
}, delay);
})
}
}
},
immediate: true
}
},
methods: {
reload(data) {
return this.$refs.paging.reload(data);
},
complete(data) {
this.firstLoaded = true;
return this.$refs.paging.complete(data);
},
_queryList(pageNo, pageSize, from) {
this.$emit('query', pageNo, pageSize, from);
},
_updateList(list) {
this.$emit('updateList', list);
}
}
}
</script>
<style scoped>
.zp-swiper-item-container {
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
</style>

View File

@ -1,167 +0,0 @@
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群790460711 -->
<!-- 滑动切换选项卡swiper容器此组件支持easycom规范可以在项目中直接引用 -->
<template>
<view :class="fixed?'zp-swiper-container zp-swiper-container-fixed':'zp-swiper-container'" :style="[finalSwiperStyle]">
<!-- #ifndef APP-PLUS -->
<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
<!-- #endif -->
<slot v-if="zSlots.top" name="top" />
<view class="zp-swiper-super">
<view v-if="zSlots.left" :class="{'zp-swiper-left':true,'zp-absoulte':isOldWebView}">
<slot name="left" />
</view>
<view :class="{'zp-swiper':true,'zp-absoulte':isOldWebView}" :style="[swiperContentStyle]">
<slot />
</view>
<view v-if="zSlots.right" :class="{'zp-swiper-right':true,'zp-absoulte zp-right':isOldWebView}">
<slot name="right" />
</view>
</view>
<slot v-if="zSlots.bottom" name="bottom" />
</view>
</template>
<script>
import commonLayoutModule from '../z-paging/js/modules/common-layout'
export default {
name: "z-paging-swiper",
mixins: [commonLayoutModule],
data() {
return {
swiperContentStyle: {}
};
},
props: {
// 使fixed
fixed: {
type: Boolean,
default: true
},
//
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// z-paging-swiper
swiperStyle: {
type: Object,
default: function() {
return {};
},
}
},
mounted() {
this.$nextTick(() => {
this.systemInfo = uni.getSystemInfoSync();
setTimeout(this.updateFixedLayout, 100)
})
// #ifndef APP-PLUS
this._getCssSafeAreaInsetBottom();
// #endif
this.updateLeftAndRightWidth();
this.swiperContentStyle = { 'flex': '1' };
// #ifndef APP-NVUE
this.swiperContentStyle = { width: '100%',height: '100%' };
// #endif
},
computed: {
finalSwiperStyle() {
const swiperStyle = { ...this.swiperStyle };
if (!this.systemInfo) return swiperStyle;
const windowTop = this.windowTop;
const windowBottom = this.systemInfo.windowBottom;
if (this.fixed) {
if (windowTop && !swiperStyle.top) {
swiperStyle.top = windowTop + 'px';
}
if (!swiperStyle.bottom) {
let bottom = windowBottom || 0;
bottom += this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
if (bottom > 0) {
swiperStyle.bottom = bottom + 'px';
}
}
}
return swiperStyle;
}
},
methods: {
// slot="left"slot="right"slot="left"slot="right"
updateLeftAndRightWidth() {
if (!this.isOldWebView) return;
this.$nextTick(() => this._updateLeftAndRightWidth(this.swiperContentStyle, 'zp-swiper'));
}
}
}
</script>
<style scoped>
.zp-swiper-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
flex: 1;
}
.zp-swiper-container-fixed {
position: fixed;
/* #ifndef APP-NVUE */
height: auto;
width: auto;
/* #endif */
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.zp-safe-area-inset-bottom {
position: absolute;
/* #ifndef APP-PLUS */
height: env(safe-area-inset-bottom);
/* #endif */
}
.zp-swiper-super {
flex: 1;
overflow: hidden;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.zp-swiper-left,.zp-swiper-right{
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
}
.zp-swiper {
flex: 1;
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
}
.zp-absoulte {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
width: auto;
/* #endif */
}
.zp-swiper-item {
height: 100%;
}
</style>

View File

@ -1,182 +0,0 @@
<!-- [z-paging]上拉加载更多view -->
<template>
<view class="zp-l-container" :class="{'zp-l-container-rpx':c.unit==='rpx','zp-l-container-px':c.unit==='px'}" :style="[c.customStyle]" @click="doClick">
<template v-if="!c.hideContent">
<!-- 底部加载更多没有更多数据分割线 -->
<text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" :class="{'zp-l-line-rpx':c.unit==='rpx','zp-l-line-px':c.unit==='px'}" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" />
<!-- 底部加载更多loading -->
<!-- #ifndef APP-NVUE -->
<image v-if="finalStatus===M.Loading&&!!c.loadingIconCustomImage"
:src="c.loadingIconCustomImage" :style="[c.iconCustomStyle]" :class="{'zp-l-line-loading-custom-image':true,'zp-l-line-loading-custom-image-animated':c.loadingAnimated,'zp-l-line-loading-custom-image-rpx':c.unit==='rpx','zp-l-line-loading-custom-image-px':c.unit==='px'}" />
<image v-if="finalStatus===M.Loading&&finalLoadingIconType==='flower'&&!c.loadingIconCustomImage.length"
:class="{'zp-line-loading-image':true,'zp-line-loading-image-rpx':c.unit==='rpx','zp-line-loading-image-px':c.unit==='px'}" :style="[c.iconCustomStyle]" :src="zTheme.flower[ts]" />
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<!-- 在nvue中底部加载更多loading使用系统自带的 -->
<view>
<loading-indicator v-if="finalStatus===M.Loading&&finalLoadingIconType!=='circle'" :class="{'zp-line-loading-image-rpx':c.unit==='rpx','zp-line-loading-image-px':c.unit==='px'}" :style="[{color:zTheme.indicator[ts]}]" :animating="true" />
</view>
<!-- #endif -->
<!-- 底部加载更多文字 -->
<text v-if="finalStatus===M.Loading&&finalLoadingIconType==='circle'&&!c.loadingIconCustomImage.length"
class="zp-l-circle-loading-view" :class="{'zp-l-circle-loading-view-rpx':c.unit==='rpx','zp-l-circle-loading-view-px':c.unit==='px'}" :style="[{borderColor:zTheme.circleBorder[ts],borderTopColor:zTheme.circleBorderTop[ts]},c.iconCustomStyle]" />
<text v-if="!c.isChat||(!c.chatDefaultAsLoading&&finalStatus===M.Default)||finalStatus===M.Fail" :class="{'zp-l-text-rpx':c.unit==='rpx','zp-l-text-px':c.unit==='px'}" :style="[{color:zTheme.title[ts]},c.titleCustomStyle]">{{ownLoadingMoreText}}</text>
<!-- 底部加载更多没有更多数据分割线 -->
<text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" :class="{'zp-l-line-rpx':c.unit==='rpx','zp-l-line-px':c.unit==='px'}" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" />
</template>
</view>
</template>
<script>
import zStatic from '../js/z-paging-static'
import Enum from '../js/z-paging-enum'
export default {
name: 'z-paging-load-more',
data() {
return {
M: Enum.More,
zTheme: {
title: { white: '#efefef', black: '#a4a4a4' },
line: { white: '#efefef', black: '#eeeeee' },
circleBorder: { white: '#aaaaaa', black: '#c8c8c8' },
circleBorderTop: { white: '#ffffff', black: '#444444' },
flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower },
indicator: { white: '#eeeeee', black: '#777777' }
}
};
},
props: ['zConfig'],
computed: {
ts() {
return this.c.defaultThemeStyle;
},
//
c() {
return this.zConfig || {};
},
//
ownLoadingMoreText() {
return {
[this.M.Default]: this.c.defaultText,
[this.M.Loading]: this.c.loadingText,
[this.M.NoMore]: this.c.noMoreText,
[this.M.Fail]: this.c.failText,
}[this.finalStatus];
},
//
finalStatus() {
if (this.c.defaultAsLoading && this.c.status === this.M.Default) return this.M.Loading;
return this.c.status;
},
// icon
finalLoadingIconType() {
// #ifdef APP-NVUE
return 'flower';
// #endif
return this.c.loadingIconType;
}
},
methods: {
//
doClick() {
this.$emit('doClick');
}
}
}
</script>
<style scoped>
@import "../css/z-paging-static.css";
.zp-l-container {
/* #ifndef APP-NVUE */
clear: both;
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
}
.zp-l-container-rpx {
height: 80rpx;
font-size: 27rpx;
}
.zp-l-container-px {
height: 40px;
font-size: 14px;
}
.zp-l-line-loading-custom-image {
color: #a4a4a4;
}
.zp-l-line-loading-custom-image-rpx {
margin-right: 8rpx;
width: 28rpx;
height: 28rpx;
}
.zp-l-line-loading-custom-image-px {
margin-right: 4px;
width: 14px;
height: 14px;
}
.zp-l-line-loading-custom-image-animated{
/* #ifndef APP-NVUE */
animation: loading-circle 1s linear infinite;
/* #endif */
}
.zp-l-circle-loading-view {
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-circle-loading-view-rpx {
margin-right: 8rpx;
width: 23rpx;
height: 23rpx;
}
.zp-l-circle-loading-view-px {
margin-right: 4px;
width: 12px;
height: 12px;
}
.zp-l-text-rpx {
font-size: 30rpx;
margin: 0rpx 6rpx;
}
.zp-l-text-px {
font-size: 15px;
margin: 0px 3px;
}
.zp-l-line-rpx {
height: 1px;
width: 100rpx;
margin: 0rpx 10rpx;
}
.zp-l-line-px {
height: 1px;
width: 50px;
margin: 0rpx 5px;
}
/* #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

@ -1,215 +0,0 @@
<!-- [z-paging]下拉刷新view -->
<template>
<view style="height: 100%;">
<view :class="showUpdateTime?'zp-r-container zp-r-container-padding':'zp-r-container'">
<view class="zp-r-left">
<!-- 非加载中(继续下拉刷新松手立即刷新状态图片) -->
<image v-if="status!==R.Loading" :class="leftImageClass" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" />
<!-- 加载状态图片 -->
<!-- #ifndef APP-NVUE -->
<image v-else :class="{'zp-line-loading-image':refreshingAnimated,'zp-r-left-image':true,'zp-r-left-image-pre-size-rpx':unit==='rpx','zp-r-left-image-pre-size-px':unit==='px'}" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" />
<!-- #endif -->
<!-- 在nvue中加载状态loading使用系统loading -->
<!-- #ifdef APP-NVUE -->
<view v-else :style="[{'margin-right':showUpdateTime?addUnit(18,unit):addUnit(12, unit)}]">
<loading-indicator :class="isIos?{'zp-loading-image-ios-rpx':unit==='rpx','zp-loading-image-ios-px':unit==='px'}:{'zp-loading-image-android-rpx':unit==='rpx','zp-loading-image-android-px':unit==='px'}"
:style="[{color:zTheme.indicator[ts]},imgStyle]" :animating="true" />
</view>
<!-- #endif -->
</view>
<!-- 右侧文字内容 -->
<view class="zp-r-right">
<!-- 右侧下拉刷新状态文字 -->
<text class="zp-r-right-text" :style="[rightTextStyle,titleStyle]">{{currentTitle}}</text>
<!-- 右侧下拉刷新时间文字 -->
<text v-if="showUpdateTime&&refresherTimeText.length" class="zp-r-right-text" :class="{'zp-r-right-time-text-rpx':unit==='rpx','zp-r-right-time-text-px':unit==='px'}" :style="[{color:zTheme.title[ts]},updateTimeStyle]">
{{refresherTimeText}}
</text>
</view>
</view>
</view>
</template>
<script>
import zStatic from '../js/z-paging-static'
import u from '../js/z-paging-utils'
import Enum from '../js/z-paging-enum'
export default {
name: 'z-paging-refresh',
data() {
return {
R: Enum.Refresher,
isIos: uni.getSystemInfoSync().platform === 'ios',
refresherTimeText: '',
zTheme: {
title: { white: '#efefef', black: '#555555' },
arrow: { white: zStatic.base64ArrowWhite, black: zStatic.base64Arrow },
flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower },
success: { white: zStatic.base64SuccessWhite, black: zStatic.base64Success },
indicator: { white: '#eeeeee', black: '#777777' }
}
};
},
props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'goF2Text', 'defaultImg', 'pullingImg',
'refreshingImg', 'completeImg', 'refreshingAnimated', 'showUpdateTime', 'updateTimeKey', 'imgStyle', 'titleStyle', 'updateTimeStyle', 'updateTimeTextMap', 'unit'
],
computed: {
ts() {
return this.defaultThemeStyle;
},
// Map
statusTextMap() {
this.updateTime();
const { R, defaultText, pullingText, refreshingText, completeText, goF2Text } = this;
return {
[R.Default]: defaultText,
[R.ReleaseToRefresh]: pullingText,
[R.Loading]: refreshingText,
[R.Complete]: completeText,
[R.GoF2]: goF2Text,
};
},
//
currentTitle() {
return this.statusTextMap[this.status] || this.defaultText;
},
// class
leftImageClass() {
const preSizeClass = `zp-r-left-image-pre-size-${this.unit}`;
if (this.status === this.R.Complete) return preSizeClass;
return `zp-r-left-image ${preSizeClass} ${this.status === this.R.Default ? 'zp-r-arrow-down' : 'zp-r-arrow-top'}`;
},
// style
leftImageStyle() {
const showUpdateTime = this.showUpdateTime;
const size = showUpdateTime ? u.addUnit(36, this.unit) : u.addUnit(34, this.unit);
return {width: size,height: size,'margin-right': showUpdateTime ? u.addUnit(20, this.unit) : u.addUnit(9, this.unit)};
},
// src
leftImageSrc() {
const R = this.R;
const status = this.status;
if (status === R.Default) {
if (!!this.defaultImg) return this.defaultImg;
return this.zTheme.arrow[this.ts];
} else if (status === R.ReleaseToRefresh) {
if (!!this.pullingImg) return this.pullingImg;
if (!!this.defaultImg) return this.defaultImg;
return this.zTheme.arrow[this.ts];
} else if (status === R.Loading) {
if (!!this.refreshingImg) return this.refreshingImg;
return this.zTheme.flower[this.ts];;
} else if (status === R.Complete) {
if (!!this.completeImg) return this.completeImg;
return this.zTheme.success[this.ts];;
} else if (status === R.GoF2) {
return this.zTheme.arrow[this.ts];
}
return '';
},
// style
rightTextStyle() {
let stl = {};
// #ifdef APP-NVUE
const textHeight = this.showUpdateTime ? u.addUnit(40, this.unit) : u.addUnit(80, this.unit);
stl = {'height': textHeight, 'line-height': textHeight}
// #endif
stl['color'] = this.zTheme.title[this.ts];
stl['font-size'] = u.addUnit(30, this.unit);
return stl;
}
},
methods: {
//
addUnit(value, unit) {
return u.addUnit(value, unit);
},
//
updateTime() {
if (this.showUpdateTime) {
this.refresherTimeText = u.getRefesrherFormatTimeByKey(this.updateTimeKey, this.updateTimeTextMap);
}
}
}
}
</script>
<style scoped>
@import "../css/z-paging-static.css";
.zp-r-container {
/* #ifndef APP-NVUE */
display: flex;
height: 100%;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
}
.zp-r-container-padding {
/* #ifdef APP-NVUE */
padding: 7px 0rpx;
/* #endif */
}
.zp-r-left {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
overflow: hidden;
/* #ifdef MP-ALIPAY */
margin-top: -4rpx;
/* #endif */
}
.zp-r-left-image {
transition-duration: .2s;
transition-property: transform;
color: #666666;
}
.zp-r-left-image-pre-size-rpx {
/* #ifndef APP-NVUE */
width: 34rpx;
height: 34rpx;
overflow: hidden;
/* #endif */
}
.zp-r-left-image-pre-size-px {
/* #ifndef APP-NVUE */
width: 17px;
height: 17px;
overflow: hidden;
/* #endif */
}
.zp-r-arrow-top {
transform: rotate(0deg);
}
.zp-r-arrow-down {
transform: rotate(180deg);
}
.zp-r-right {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
}
.zp-r-right-time-text-rpx {
margin-top: 10rpx;
font-size: 26rpx;
}
.zp-r-right-time-text-px {
margin-top: 5px;
font-size: 13px;
}
</style>

View File

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

View File

@ -1,241 +0,0 @@
/* [z-paging]公共css*/
.z-paging-content {
position: relative;
flex-direction: column;
/* #ifndef APP-NVUE */
overflow: hidden;
/* #endif */
}
.z-paging-content-full {
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
height: 100%;
/* #endif */
}
.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-f2-content {
width: 100%;
position: fixed;
top: 0;
left: 0;
background-color: white;
}
.zp-page-top, .zp-page-bottom {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
position: fixed;
left: 0;
right: 0;
z-index: 999;
}
.zp-page-left, .zp-page-right {
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
}
.zp-scroll-view-super {
flex: 1;
overflow: hidden;
position: relative;
}
.zp-view-super {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.zp-scroll-view-container, .zp-scroll-view {
position: relative;
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
}
.zp-absoulte {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
width: auto;
/* #endif */
}
.zp-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-custom-image {
width: 35rpx;
height: 35rpx;
/* #ifndef APP-NVUE */
animation: loading-flower 1s linear infinite;
/* #endif */
}
.zp-page-bottom-keyboard-placeholder-animate {
transition-property: height;
transition-duration: 0.15s;
/* #ifndef APP-NVUE */
will-change: height;
/* #endif */
}
.zp-custom-refresher-container {
overflow: hidden;
}
.zp-custom-refresher-refresh {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
}
.zp-back-to-top {
z-index: 999;
position: absolute;
bottom: 0rpx;
transition-duration: .3s;
transition-property: opacity;
}
.zp-back-to-top-rpx {
width: 76rpx;
height: 76rpx;
bottom: 0rpx;
right: 25rpx;
}
.zp-back-to-top-px {
width: 38px;
height: 38px;
bottom: 0px;
right: 13px;
}
.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-back-to-top-img-inversion {
transform: rotate(180deg);
}
.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

@ -1,50 +0,0 @@
/* [z-paging]公用的静态css资源 */
.zp-line-loading-image {
/* #ifndef APP-NVUE */
animation: loading-flower 1s steps(12) infinite;
/* #endif */
color: #666666;
}
.zp-line-loading-image-rpx {
margin-right: 8rpx;
width: 34rpx;
height: 34rpx;
}
.zp-line-loading-image-px {
margin-right: 4px;
width: 17px;
height: 17px;
}
.zp-loading-image-ios-rpx {
width: 40rpx;
height: 40rpx;
}
.zp-loading-image-ios-px {
width: 20px;
height: 20px;
}
.zp-loading-image-android-rpx {
width: 34rpx;
height: 34rpx;
}
.zp-loading-image-android-px {
width: 17px;
height: 17px;
}
/* #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

@ -1,23 +0,0 @@
{
"zp.refresher.default": "Pull down to refresh",
"zp.refresher.pulling": "Release to refresh",
"zp.refresher.refreshing": "Refreshing...",
"zp.refresher.complete": "Refresh succeeded",
"zp.refresher.f2": "Refresh to enter 2f",
"zp.loadingMore.default": "Click to load more",
"zp.loadingMore.loading": "Loading...",
"zp.loadingMore.noMore": "No more data",
"zp.loadingMore.fail": "Load failed,click to reload",
"zp.emptyView.title": "No data",
"zp.emptyView.reload": "Reload",
"zp.emptyView.error": "Sorry,load failed",
"zp.refresherUpdateTime.title": "Last update: ",
"zp.refresherUpdateTime.none": "None",
"zp.refresherUpdateTime.today": "Today",
"zp.refresherUpdateTime.yesterday": "Yesterday",
"zp.systemLoading.title": "Loading..."
}

View File

@ -1,8 +0,0 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@ -1,23 +0,0 @@
{
"zp.refresher.default": "继续下拉刷新",
"zp.refresher.pulling": "松开立即刷新",
"zp.refresher.refreshing": "正在刷新...",
"zp.refresher.complete": "刷新成功",
"zp.refresher.f2": "松手进入二楼",
"zp.loadingMore.default": "点击加载更多",
"zp.loadingMore.loading": "正在加载...",
"zp.loadingMore.noMore": "没有更多了",
"zp.loadingMore.fail": "加载失败,点击重新加载",
"zp.emptyView.title": "没有数据哦~",
"zp.emptyView.reload": "重新加载",
"zp.emptyView.error": "很抱歉,加载失败",
"zp.refresherUpdateTime.title": "最后更新:",
"zp.refresherUpdateTime.none": "无",
"zp.refresherUpdateTime.today": "今天",
"zp.refresherUpdateTime.yesterday": "昨天",
"zp.systemLoading.title": "加载中..."
}

View File

@ -1,23 +0,0 @@
{
"zp.refresher.default": "繼續下拉重繪",
"zp.refresher.pulling": "鬆開立即重繪",
"zp.refresher.refreshing": "正在重繪...",
"zp.refresher.complete": "重繪成功",
"zp.refresher.f2": "鬆手進入二樓",
"zp.loadingMore.default": "點擊加載更多",
"zp.loadingMore.loading": "正在加載...",
"zp.loadingMore.noMore": "沒有更多了",
"zp.loadingMore.fail": "加載失敗,點擊重新加載",
"zp.emptyView.title": "沒有數據哦~",
"zp.emptyView.reload": "重新加載",
"zp.emptyView.error": "很抱歉,加載失敗",
"zp.refresherUpdateTime.title": "最後更新:",
"zp.refresherUpdateTime.none": "無",
"zp.refresherUpdateTime.today": "今天",
"zp.refresherUpdateTime.yesterday": "昨天",
"zp.systemLoading.title": "加載中..."
}

View File

@ -1,25 +0,0 @@
// [z-paging]useZPaging hooks
import { onPageScroll, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
function useZPaging(paging) {
const cPaging = !!paging ? paging.value || paging : null;
onPullDownRefresh(() => {
if (!cPaging || !cPaging.value) return;
cPaging.value.reload().catch(() => {});
})
onPageScroll(e => {
if (!cPaging || !cPaging.value) return;
cPaging.value.updatePageScrollTop(e.scrollTop);
e.scrollTop < 10 && cPaging.value.doChatRecordLoadMore();
})
onReachBottom(() => {
if (!cPaging || !cPaging.value) return;
cPaging.value.pageReachBottom();
})
}
export default useZPaging

View File

@ -1,25 +0,0 @@
// [z-paging]useZPagingComp hooks
function useZPagingComp(paging) {
const cPaging = !!paging ? paging.value || paging : null;
const reload = () => {
if (!cPaging || !cPaging.value) return;
cPaging.value.reload().catch(() => {});
}
const updatePageScrollTop = scrollTop => {
if (!cPaging || !cPaging.value) return;
cPaging.value.updatePageScrollTop(scrollTop);
}
const doChatRecordLoadMore = () => {
if (!cPaging || !cPaging.value) return;
cPaging.value.doChatRecordLoadMore();
}
const pageReachBottom = () => {
if (!cPaging || !cPaging.value) return;
cPaging.value.pageReachBottom();
}
return { reload, updatePageScrollTop, doChatRecordLoadMore, pageReachBottom };
}
export default useZPagingComp

View File

@ -1,125 +0,0 @@
// [z-paging]点击返回顶部view模块
import u from '.././z-paging-utils'
export default {
props: {
// 自动显示点击返回顶部按钮,默认为否
autoShowBackToTop: {
type: Boolean,
default: u.gc('autoShowBackToTop', false)
},
// 点击返回顶部按钮显示/隐藏的阈值(滚动距离)单位为px默认为400rpx
backToTopThreshold: {
type: [Number, String],
default: u.gc('backToTopThreshold', '400rpx')
},
// 点击返回顶部按钮的自定义图片地址默认使用z-paging内置的图片
backToTopImg: {
type: String,
default: u.gc('backToTopImg', '')
},
// 点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是
backToTopWithAnimate: {
type: Boolean,
default: u.gc('backToTopWithAnimate', true)
},
// 点击返回顶部按钮与底部的距离注意添加单位px或rpx默认为160rpx
backToTopBottom: {
type: [Number, String],
default: u.gc('backToTopBottom', '160rpx')
},
// 点击返回顶部按钮的自定义样式
backToTopStyle: {
type: Object,
default: u.gc('backToTopStyle', {}),
},
// iOS点击顶部状态栏、安卓双击标题栏时滚动条返回顶部只支持竖向默认为是
enableBackToTop: {
type: Boolean,
default: u.gc('enableBackToTop', true)
},
},
data() {
return {
// 点击返回顶部的class
backToTopClass: 'zp-back-to-top zp-back-to-top-hide',
// 上次点击返回顶部的时间
lastBackToTopShowTime: 0,
// 点击返回顶部显示的class是否在展示中使得按钮展示/隐藏过度效果更自然
showBackToTopClass: false,
}
},
computed: {
backToTopThresholdUnitConverted() {
return u.addUnit(this.backToTopThreshold, this.unit);
},
backToTopBottomUnitConverted() {
return u.addUnit(this.backToTopBottom, this.unit);
},
finalEnableBackToTop() {
return this.usePageScroll ? false : this.enableBackToTop;
},
finalBackToTopThreshold() {
return u.convertToPx(this.backToTopThresholdUnitConverted);
},
finalBackToTopStyle() {
const backToTopStyle = this.backToTopStyle;
if (!backToTopStyle.bottom) {
backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottomUnitConverted) + 'px';
}
if(!backToTopStyle.position){
backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute';
}
return backToTopStyle;
},
finalBackToTopClass() {
return `${this.backToTopClass} zp-back-to-top-${this.unit}`;
}
},
methods: {
// 点击了返回顶部
_backToTopClick() {
let callbacked = false;
this.$emit('backToTopClick', toTop => {
(toTop === undefined || toTop === true) && this._handleToTop();
callbacked = true;
});
// 如果用户没有禁止默认的返回顶部事件,则触发滚动到顶部
this.$nextTick(() => {
!callbacked && this._handleToTop();
})
},
// 处理滚动到顶部(聊天记录模式中为滚动到底部)
_handleToTop() {
!this.backToTopWithAnimate && this._checkShouldShowBackToTop(0);
!this.useChatRecordMode ? this.scrollToTop(this.backToTopWithAnimate) : this.scrollToBottom(this.backToTopWithAnimate);
},
// 判断是否要显示返回顶部按钮
_checkShouldShowBackToTop(scrollTop) {
if (!this.autoShowBackToTop) {
this.showBackToTopClass = false;
return;
}
if (scrollTop > this.finalBackToTopThreshold) {
if (!this.showBackToTopClass) {
// 记录当前点击返回顶部按钮显示的class生效了
this.showBackToTopClass = true;
this.lastBackToTopShowTime = new Date().getTime();
// 当滚动到需要展示返回顶部的阈值内则延迟300毫秒展示返回到顶部按钮
u.delay(() => {
this.backToTopClass = 'zp-back-to-top zp-back-to-top-show';
}, 300)
}
} else {
// 如果当前点击返回顶部按钮显示的class是生效状态并且滚动小于触发阈值则隐藏返回顶部按钮
if (this.showBackToTopClass) {
this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide';
u.delay(() => {
this.showBackToTopClass = false;
}, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300)
}
}
},
}
}

View File

@ -1,149 +0,0 @@
// [z-paging]聊天记录模式模块
import u from '.././z-paging-utils'
export default {
props: {
// 使用聊天记录模式,默认为否
useChatRecordMode: {
type: Boolean,
default: u.gc('useChatRecordMode', false)
},
// 使用聊天记录模式时滚动到顶部后列表垂直移动偏移距离。默认0rpx。单位px暂时无效
chatRecordMoreOffset: {
type: [Number, String],
default: u.gc('chatRecordMoreOffset', '0rpx')
},
// 使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是
autoHideKeyboardWhenChat: {
type: Boolean,
default: u.gc('autoHideKeyboardWhenChat', true)
},
// 使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度,默认为是
autoAdjustPositionWhenChat: {
type: Boolean,
default: u.gc('autoAdjustPositionWhenChat', true)
},
// 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px
chatAdjustPositionOffset: {
type: [Number, String],
default: u.gc('chatAdjustPositionOffset', '0rpx')
},
// 使用聊天记录模式中键盘弹出时是否自动滚动到底部,默认为否
autoToBottomWhenChat: {
type: Boolean,
default: u.gc('autoToBottomWhenChat', false)
},
// 使用聊天记录模式中reload时是否显示chatLoading默认为否
showChatLoadingWhenReload: {
type: Boolean,
default: u.gc('showChatLoadingWhenReload', false)
},
// 在聊天记录模式中滑动到顶部状态为默认状态时以加载中的状态展示默认为是。若设置为否则默认会显示【点击加载更多】然后才会显示loading
chatLoadingMoreDefaultAsLoading: {
type: Boolean,
default: u.gc('chatLoadingMoreDefaultAsLoading', true)
},
},
data() {
return {
// 键盘高度
keyboardHeight: 0,
// 键盘高度是否未改变,此时占位高度变化不需要动画效果
isKeyboardHeightChanged: false,
}
},
computed: {
finalChatRecordMoreOffset() {
return u.convertToPx(this.chatRecordMoreOffset);
},
finalChatAdjustPositionOffset() {
return u.convertToPx(this.chatAdjustPositionOffset);
},
// 聊天记录模式旋转180度style
chatRecordRotateStyle() {
let cellStyle;
// 在vue中直接将列表倒置因此在vue的cell中也直接写style="transform: scaleY(-1)"转回来即可。
// #ifndef APP-NVUE
cellStyle = this.useChatRecordMode ? { transform: 'scaleY(-1)' } : {};
// #endif
// 在nvue中需要考虑数据量不满一页的情况因为nvue中的list无法通过flex-end修改不满一页的起始位置会导致不满一页时列表数据从底部开始因此需要特别判断
// 当数据不满一屏的时候,不进行列表倒置
// #ifdef APP-NVUE
cellStyle = this.useChatRecordMode ? { transform: this.isFirstPageAndNoMore ? 'scaleY(1)' : 'scaleY(-1)' } : {};
// #endif
this.$emit('update:cellStyle', cellStyle);
this.$emit('cellStyleChange', cellStyle);
// 在聊天记录模式中,如果列表没有倒置并且当前是第一页,则需要自动滚动到最底部
this.$nextTick(() => {
if (this.isFirstPage && this.isChatRecordModeAndNotInversion) {
this.$nextTick(() => {
// 这里多次触发滚动到底部是为了避免在某些情况下即使是在nextTick但是cell未渲染完毕导致滚动到底部位置不正确的问题
this._scrollToBottom(false);
u.delay(() => {
this._scrollToBottom(false);
u.delay(() => {
this._scrollToBottom(false);
}, 50)
}, 50)
})
}
})
return cellStyle;
},
// 是否是聊天记录列表并且有配置transform
isChatRecordModeHasTransform() {
return this.useChatRecordMode && this.chatRecordRotateStyle && this.chatRecordRotateStyle.transform;
},
// 是否是聊天记录列表并且列表未倒置
isChatRecordModeAndNotInversion() {
return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(1)';
},
// 是否是聊天记录列表并且列表倒置
isChatRecordModeAndInversion() {
return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(-1)';
},
// 最终的聊天记录模式中底部安全区域的高度,如果开启了底部安全区域并且键盘未弹出,则添加底部区域高度
chatRecordModeSafeAreaBottom() {
return this.safeAreaInsetBottom && !this.keyboardHeight ? this.safeAreaBottom : 0;
}
},
mounted() {
// 监听键盘高度变化H5、百度小程序、抖音小程序、飞书小程序不支持
// #ifndef H5 || MP-BAIDU || MP-TOUTIAO
if (this.useChatRecordMode) {
uni.onKeyboardHeightChange(this._handleKeyboardHeightChange);
}
// #endif
},
methods: {
// 添加聊天记录
addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) {
if (!this.useChatRecordMode) return;
this.isTotalChangeFromAddData = true;
this.addDataFromTop(data, toBottom, toBottomWithAnimate);
},
// 手动触发滚动到顶部加载更多,聊天记录模式时有效
doChatRecordLoadMore() {
this.useChatRecordMode && this._onLoadingMore('click');
},
// 处理键盘高度变化
_handleKeyboardHeightChange(res) {
this.$emit('keyboardHeightChange', res);
if (this.autoAdjustPositionWhenChat) {
this.isKeyboardHeightChanged = true;
this.keyboardHeight = res.height > 0 ? res.height + this.finalChatAdjustPositionOffset : res.height;
}
if (this.autoToBottomWhenChat && this.keyboardHeight > 0) {
u.delay(() => {
this.scrollToBottom(false);
u.delay(() => {
this.scrollToBottom(false);
})
})
}
}
}
}

View File

@ -1,142 +0,0 @@
// [z-paging]通用布局相关模块
// #ifdef APP-NVUE
const weexDom = weex.requireModule('dom');
// #endif
export default {
data() {
return {
systemInfo: null,
cssSafeAreaInsetBottom: -1,
isReadyDestroy: false,
}
},
computed: {
// 顶部可用距离
windowTop() {
if (!this.systemInfo) return 0;
// 暂时修复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) return 0;
// #endif
return this.systemInfo.windowTop || 0;
},
// 底部安全区域高度
safeAreaBottom() {
if (!this.systemInfo) return 0;
let safeAreaBottom = 0;
// #ifdef APP-PLUS
safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ;
// #endif
// #ifndef APP-PLUS
safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0);
// #endif
return safeAreaBottom;
},
// 是否是比较老的webview在一些老的webview中需要进行一些特殊处理
isOldWebView() {
// #ifndef APP-NVUE || MP-KUAISHOU
try {
const systemInfos = uni.getSystemInfoSync().system.split(' ');
const deviceType = systemInfos[0];
const version = parseInt(systemInfos[1]);
if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
return true;
}
} catch(e) {
return false;
}
// #endif
return false;
},
// 当前组件的$slots兼容不同平台
zSlots() {
// #ifdef VUE2
// #ifdef MP-ALIPAY
return this.$slots;
// #endif
return this.$scopedSlots || this.$slots;
// #endif
return this.$slots;
},
},
beforeDestroy() {
this.isReadyDestroy = true;
},
// #ifdef VUE3
unmounted() {
this.isReadyDestroy = true;
},
// #endif
methods: {
// 更新fixed模式下z-paging的布局
updateFixedLayout() {
this.fixed && this.$nextTick(() => {
this.systemInfo = uni.getSystemInfoSync();
})
},
// 获取节点尺寸
_getNodeClientRect(select, inDom = true, scrollOffset = false, inParent = false) {
if (this.isReadyDestroy) {
return Promise.resolve(false);
};
// nvue中获取节点信息
// #ifdef APP-NVUE
select = select.replace(/[.|#]/g, '');
const ref = this.$refs[select];
return new Promise((resolve, reject) => {
if (ref) {
weexDom.getComponentRect(ref, option => {
resolve(option && option.result ? [option.size] : false);
})
} else {
resolve(false);
}
});
return;
// #endif
// vue中获取节点信息
//#ifdef MP-ALIPAY
inDom = false;
//#endif
const inDomObj = inParent ? this.$parent : this;
let res = inDom || inParent ? uni.createSelectorQuery().in(inDomObj) : uni.createSelectorQuery();
scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect();
return new Promise((resolve, reject) => {
res.exec(data => {
resolve((data && data != '' && data != undefined && data.length) ? data : false);
});
});
},
// 获取slot="left"和slot="right"宽度并且更新布局
_updateLeftAndRightWidth(targetStyle, parentNodePrefix) {
this.$nextTick(() => {
let delayTime = 0;
// #ifdef MP-BAIDU
delayTime = 10;
// #endif
setTimeout(() => {
['left','right'].map(position => {
this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => {
this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px');
});
})
}, delayTime)
})
},
// 通过获取css设置的底部安全区域占位view高度设置bottom距离直接通过systemInfo在部分平台上无法获取到底部安全区域
_getCssSafeAreaInsetBottom(success) {
this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => {
this.cssSafeAreaInsetBottom = res ? res[0].height : -1;
res && success && success();
});
}
}
}

View File

@ -1,736 +0,0 @@
// [z-paging]数据处理模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
import interceptor from '../z-paging-interceptor'
export default {
props: {
// 自定义初始的pageNo默认为1
defaultPageNo: {
type: Number,
default: u.gc('defaultPageNo', 1),
observer: function(newVal) {
this.pageNo = newVal;
},
},
// 自定义pageSize默认为10
defaultPageSize: {
type: Number,
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: u.gc('dataKey', null),
},
// 使用缓存若开启将自动缓存第一页的数据默认为否。请注意因考虑到切换tab时不同tab数据不同的情况默认仅会缓存组件首次加载时第一次请求到的数据后续的下拉刷新操作不会更新缓存。
useCache: {
type: Boolean,
default: u.gc('useCache', false)
},
// 使用缓存时缓存的key用于区分不同列表的缓存数据useCache为true时必须设置否则缓存无效
cacheKey: {
type: String,
default: u.gc('cacheKey', null)
},
// 缓存模式默认仅会缓存组件首次加载时第一次请求到的数据可设置为always即代表总是缓存每次列表刷新(下拉刷新、调用reload等)都会更新缓存
cacheMode: {
type: String,
default: u.gc('cacheMode', Enum.CacheMode.Default)
},
// 自动注入的list名可自动修改父view(包含ref="paging")中对应name的list值
autowireListName: {
type: String,
default: u.gc('autowireListName', '')
},
// 自动注入的query名可自动调用父view(包含ref="paging")中的query方法
autowireQueryName: {
type: String,
default: u.gc('autowireQueryName', '')
},
// 获取分页数据Function功能与@query类似。若设置了fetch则@query将不再触发
fetch: {
type: Function,
default: null
},
// fetch的附加参数fetch配置后有效
fetchParams: {
type: Object,
default: u.gc('fetchParams', null)
},
// 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)
},
// 自动拼接complete中传过来的数组(使用聊天记录模式时无效)
concat: {
type: Boolean,
default: u.gc('concat', true)
},
// 请求失败是否触发reject默认为是
callNetworkReject: {
type: Boolean,
default: u.gc('callNetworkReject', true)
},
// 父组件v-model所绑定的list的值
value: {
type: Array,
default: function() {
return [];
}
},
// #ifdef VUE3
modelValue: {
type: Array,
default: function() {
return [];
}
}
// #endif
},
data (){
return {
currentData: [],
totalData: [],
realTotalData: [],
totalLocalPagingList: [],
dataPromiseResultMap: {
reload: null,
complete: null,
localPaging: null
},
isSettingCacheList: false,
pageNo: 1,
currentRefreshPageSize: 0,
isLocalPaging: false,
isAddedData: false,
isTotalChangeFromAddData: false,
privateConcat: true,
myParentQuery: -1,
firstPageLoaded: false,
pagingLoaded: false,
loaded: false,
isUserReload: true,
fromEmptyViewReload: false,
queryFrom: '',
listRendering: false,
isHandlingRefreshToPage: false,
isFirstPageAndNoMore: false,
totalDataChangeThrow: true
}
},
computed: {
pageSize() {
return this.defaultPageSize;
},
finalConcat() {
return this.concat && this.privateConcat;
},
finalUseCache() {
if (this.useCache && !this.cacheKey) {
u.consoleErr('use-cache为true时必须设置cache-key否则缓存无效');
}
return this.useCache && !!this.cacheKey;
},
finalCacheKey() {
return this.cacheKey ? `${c.cachePrefixKey}-${this.cacheKey}` : null;
},
isFirstPage() {
return this.pageNo === this.defaultPageNo;
}
},
watch: {
totalData(newVal, oldVal) {
this._totalDataChange(newVal, oldVal, this.totalDataChangeThrow);
this.totalDataChangeThrow = true;
},
currentData(newVal, oldVal) {
this._currentDataChange(newVal, oldVal);
},
useChatRecordMode(newVal, oldVal) {
if (newVal) {
this.nLoadingMoreFixedHeight = false;
}
},
value: {
handler(newVal) {
// 当v-model绑定的数据源被更改时此时数据源改变不emit input事件避免循环调用
if (newVal !== this.totalData) {
this.totalDataChangeThrow = false;
this.totalData = newVal;
}
},
immediate: true
},
// #ifdef VUE3
modelValue: {
handler(newVal) {
// 当v-model绑定的数据源被更改时此时数据源改变不emit input事件避免循环调用
if (newVal !== this.totalData) {
this.totalDataChangeThrow = false;
this.totalData = newVal;
}
},
immediate: true
}
// #endif
},
methods: {
// 请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为是否成功(默认为是)
complete(data, success = true) {
this.customNoMore = -1;
return this.addData(data, success);
},
//【保证数据一致】请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为dataKey需与:data-key绑定的一致第三个参数为是否成功(默认为是)
completeByKey(data, dataKey = null, success = true) {
if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) {
this.isFirstPage && this.endRefresh();
return new Promise(resolve => resolve());
}
this.customNoMore = -1;
return this.addData(data, success);
},
//【通过total判断是否有更多数据】请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为total(列表总数),第三个参数为是否成功(默认为是)
completeByTotal(data, total, success = true) {
if (total == 'undefined') {
this.customNoMore = -1;
} else {
const dataTypeRes = this._checkDataType(data, success, false);
data = dataTypeRes.data;
success = dataTypeRes.success;
if (total >= 0 && success) {
return new Promise((resolve, reject) => {
this.$nextTick(() => {
let nomore = false;
const realTotalDataCount = this.pageNo == this.defaultPageNo ? 0 : this.realTotalData.length;
const dataLength = this.privateConcat ? data.length : 0;
let exceedCount = realTotalDataCount + dataLength - total;
// 没有更多数据了
if (exceedCount >= 0) {
nomore = true;
// 仅截取total内部分的数据
exceedCount = this.defaultPageSize - exceedCount;
if (this.privateConcat && exceedCount > 0 && exceedCount < data.length) {
data = data.splice(0, exceedCount);
}
}
this.completeByNoMore(data, nomore, success).then(res => resolve(res)).catch(() => reject());
})
});
}
}
return this.addData(data, success);
},
//【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging处理第一个参数为请求结果数组第二个参数为是否没有更多数据第三个参数为是否成功(默认是是)
completeByNoMore(data, nomore, success = true) {
if (nomore != 'undefined') {
this.customNoMore = nomore == true ? 1 : 0;
}
return this.addData(data, success);
},
// 请求结束且请求失败时调用,支持传入请求失败原因
completeByError(errorMsg) {
this.customerEmptyViewErrorText = errorMsg;
return this.complete(false);
},
// 与上方complete方法功能一致新版本中设置服务端回调数组请使用complete方法
addData(data, success = true) {
if (!this.fromCompleteEmit) {
this.disabledCompleteEmit = true;
this.fromCompleteEmit = false;
}
const currentTimeStamp = u.getTime();
const disTime = currentTimeStamp - this.requestTimeStamp;
let minDelay = this.minDelay;
if (this.isFirstPage && this.finalShowRefresherWhenReload) {
minDelay = Math.max(400, minDelay);
}
const addDataDalay = (this.requestTimeStamp > 0 && disTime < minDelay) ? minDelay - disTime : 0;
this.$nextTick(() => {
u.delay(() => {
this._addData(data, success, false);
}, this.delay > 0 ? this.delay : addDataDalay)
})
return new Promise((resolve, reject) => {
this.dataPromiseResultMap.complete = { resolve, reject };
});
},
// 从顶部添加数据不会影响分页的pageNo和pageSize
addDataFromTop(data, toTop = true, toTopWithAnimate = true) {
// 数据是否拼接到顶部,如果是聊天记录模式并且列表没有倒置,则应该拼接在底部
let addFromTop = !this.isChatRecordModeAndNotInversion;
data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : (addFromTop ? data.reverse() : data);
// #ifndef APP-NVUE
this.finalUseVirtualList && this._setCellIndex(data, 'top')
// #endif
this.totalData = addFromTop ? [...data, ...this.totalData] : [...this.totalData, ...data];
if (toTop) {
u.delay(() => this.useChatRecordMode ? this.scrollToBottom(toTopWithAnimate) : this.scrollToTop(toTopWithAnimate));
}
},
// 重新设置列表数据调用此方法不会影响pageNo和pageSize也不会触发请求。适用场景当需要删除列表中某一项时将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时请使用此方法请勿直接修改page中:list.sync绑定的数组)
resetTotalData(data) {
this.isTotalChangeFromAddData = true;
data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data;
this.totalData = data;
},
// 设置本地分页数据,请求结束(成功或者失败)调用此方法将请求的结果传递给z-paging作分页处理若调用了此方法则上拉加载更多时内部会自动分页不会触发@query所绑定的事件
setLocalPaging(data, success = true) {
this.isLocalPaging = true;
this.$nextTick(() => {
this._addData(data, success, true);
})
return new Promise((resolve, reject) => {
this.dataPromiseResultMap.localPaging = { resolve, reject };
});
},
// 重新加载分页数据pageNo会恢复为默认值相当于下拉刷新的效果(animate为true时会展示下拉刷新动画默认为false)
reload(animate = this.showRefresherWhenReload) {
if (animate) {
this.privateShowRefresherWhenReload = animate;
this.isUserPullDown = true;
}
if (!this.showLoadingMoreWhenReload) {
this.listRendering = true;
}
this.$nextTick(() => {
this._preReload(animate, false);
})
return new Promise((resolve, reject) => {
this.dataPromiseResultMap.reload = { resolve, reject };
});
},
// 刷新列表数据pageNo和pageSize不会重置列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致
refresh() {
return this._handleRefreshWithDisPageNo(this.pageNo - this.defaultPageNo + 1);
},
// 刷新列表数据至指定页例如pageNo=5时则代表刷新列表至第5页此时pageNo会变为5列表会展示前5页的数据。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致
refreshToPage(pageNo) {
this.isHandlingRefreshToPage = true;
return this._handleRefreshWithDisPageNo(pageNo + this.defaultPageNo - 1);
},
// 手动更新列表缓存数据将自动截取v-model绑定的list中的前pageSize条覆盖缓存请确保在list数据更新到预期结果后再调用此方法
updateCache() {
if (this.finalUseCache && this.totalData.length) {
this._saveLocalCache(this.totalData.slice(0, Math.min(this.totalData.length, this.pageSize)));
}
},
// 清空分页数据
clean() {
this._reload(true);
this._addData([], true, false);
},
// 清空分页数据
clear() {
this.clean();
},
// reload之前的一些处理
_preReload(animate = this.showRefresherWhenReload, isFromMounted = true, retryCount = 0) {
const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher;
// #ifndef APP-NVUE
// 如果获取slot="refresher"高度失败则不触发reload直到获取slot="refresher"高度成功
if (this.customRefresherHeight === -1 && showRefresher) {
u.delay(() => {
retryCount ++;
// 如果重试次数是10的倍数(也就是每500毫秒)尝试重新获取一下slot="refresher"高度
// 此举是为了解决在某些特殊情况下z-paging组件mounted了但是未展示在用户面前比如在tabbar页面中未切换到对应tabbar但是通过代码让z-paging展示了此时控制台会报Error: Not FoundPage因为这时候去获取dom节点信息获取不到
// 当用户在某个时刻让此z-paging展示在面前时即可顺利获取到slot="refresher"高度,递归停止
if (retryCount % 10 === 0) {
this._updateCustomRefresherHeight();
}
this._preReload(animate, isFromMounted, retryCount);
}, c.delayTime / 2);
return;
}
// #endif
this.isUserReload = true;
this.loadingType = Enum.LoadingType.Refresher;
if (animate) {
this.privateShowRefresherWhenReload = animate;
// #ifndef APP-NVUE
if (this.useCustomRefresher) {
this._doRefresherRefreshAnimate();
} else {
this.refresherTriggered = true;
}
// #endif
// #ifdef APP-NVUE
this.refresherStatus = Enum.Refresher.Loading;
this.refresherRevealStackCount ++;
u.delay(() => {
this._getNodeClientRect('zp-n-refresh-container', false).then((node) => {
if (node) {
let nodeHeight = node[0].height;
this.nShowRefresherReveal = true;
this.nShowRefresherRevealHeight = nodeHeight;
u.delay(() => {
this._nDoRefresherEndAnimation(0, -nodeHeight, false, false);
u.delay(() => {
this._nDoRefresherEndAnimation(nodeHeight, 0);
}, 10)
}, 10)
}
this._reload(false, isFromMounted);
this._doRefresherLoad(false);
});
}, this.pagingLoaded ? 10 : 100)
return;
// #endif
} else {
this._refresherEnd(false, false, false, false);
}
this._reload(false, isFromMounted);
},
// 重新加载分页数据
_reload(isClean = false, isFromMounted = false, isUserPullDown = false) {
this.isAddedData = false;
this.insideOfPaging = -1;
this.cacheScrollNodeHeight = -1;
this.pageNo = this.defaultPageNo;
this._cleanRefresherEndTimeout();
!this.privateShowRefresherWhenReload && !isClean && this._startLoading(true);
this.firstPageLoaded = true;
this.isTotalChangeFromAddData = false;
if (!this.isSettingCacheList) {
this.totalData = [];
}
if (!isClean) {
this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload);
let delay = 0;
// #ifdef MP-TOUTIAO
delay = 5;
// #endif
u.delay(this._callMyParentQuery, delay);
if (!isFromMounted && this.autoScrollToTopWhenReload) {
let checkedNRefresherLoading = true;
// #ifdef APP-NVUE
checkedNRefresherLoading = !this.nRefresherLoading;
// #endif
checkedNRefresherLoading && this._scrollToTop(false);
}
}
// #ifdef APP-NVUE
this.$nextTick(() => {
this.nShowBottom = this.realTotalData.length > 0;
})
// #endif
},
// 处理服务端返回的数组
_addData(data, success, isLocal) {
this.isAddedData = true;
this.fromEmptyViewReload = false;
this.isTotalChangeFromAddData = true;
this.refresherTriggered = false;
this._endSystemLoadingAndRefresh();
const tempIsUserPullDown = this.isUserPullDown;
if (this.showRefresherUpdateTime && this.isFirstPage) {
u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey);
this.$refs.refresh && this.$refs.refresh.updateTime();
}
if (!isLocal && tempIsUserPullDown && this.isFirstPage) {
this.isUserPullDown = false;
}
this.listRendering = true;
this.$nextTick(() => {
u.delay(() => this.listRendering = false);
})
let dataTypeRes = this._checkDataType(data, success, isLocal);
data = dataTypeRes.data;
success = dataTypeRes.success;
let delayTime = c.delayTime;
if (this.useChatRecordMode) delayTime = 0;
this.loadingForNow = false;
u.delay(() => {
this.pagingLoaded = true;
this.$nextTick(()=>{
!isLocal && this._refresherEnd(delayTime > 0, true, tempIsUserPullDown);
})
})
if (this.isFirstPage) {
this.isLoadFailed = !success;
this.$emit('isLoadFailedChange', this.isLoadFailed);
if (this.finalUseCache && success && (this.cacheMode === Enum.CacheMode.Always ? true : this.isSettingCacheList)) {
this._saveLocalCache(data);
}
}
this.isSettingCacheList = false;
if (success) {
if (!(this.privateConcat === false && !this.isHandlingRefreshToPage && this.loadingStatus === Enum.More.NoMore)) {
this.loadingStatus = Enum.More.Default;
}
if (isLocal) {
// 如果当前是本地分页则必然是由setLocalPaging方法触发此时直接本地加载第一页数据即可。后续本地分页加载更多方法由滚动到底部加载更多事件处理
this.totalLocalPagingList = data;
const localPageNo = this.defaultPageNo;
const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize;
this._localPagingQueryList(localPageNo, localPageSize, 0, res => {
u.delay(() => {
this.completeByTotal(res, this.totalLocalPagingList.length);;
}, 0)
})
} else {
// 如果当前不是本地分页,则按照正常分页逻辑进行数据处理&emit数据
let dataChangeDelayTime = 0;
// #ifdef APP-NVUE
if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') {
dataChangeDelayTime = 150;
}
// #endif
u.delay(() => {
this._currentDataChange(data, this.currentData);
this._callDataPromise(true, this.totalData);
}, dataChangeDelayTime)
}
if (this.isHandlingRefreshToPage) {
this.isHandlingRefreshToPage = false;
this.pageNo = this.defaultPageNo + Math.ceil(data.length / this.pageSize) - 1;
if (data.length % this.pageSize !== 0) {
this.customNoMore = 1;
}
}
} else {
this._currentDataChange(data, this.currentData);
this._callDataPromise(false);
this.loadingStatus = Enum.More.Fail;
this.isHandlingRefreshToPage = false;
if (this.loadingType === Enum.LoadingType.LoadMore) {
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;
// emit列表更新事件
if (eventThrow) {
this.$emit('input', newVal);
// #ifdef VUE3
this.$emit('update:modelValue', newVal);
// #endif
this.$emit('update:list', newVal);
this.$emit('listChange', newVal);
this._callMyParentList(newVal);
}
this.firstPageLoaded = false;
this.isTotalChangeFromAddData = false;
this.$nextTick(() => {
u.delay(()=>{
// emit z-paging内容区域高度改变事件
this._getNodeClientRect('.zp-paging-container-content').then(res => {
res && this.$emit('contentHeightChanged', res[0].height);
});
}, c.delayTime * (this.isIos ? 1 : 3))
// #ifdef APP-NVUE
// 在nvue中延时600毫秒展示底部加载更多避免底部加载更多太早加载闪一下的问题
u.delay(() => {
this.nShowBottom = true;
}, c.delayTime * 6, 'nShowBottomDelay');
// #endif
})
},
// 当前数据改变时调用
_currentDataChange(newVal, oldVal) {
newVal = [...newVal];
// #ifndef APP-NVUE
this.finalUseVirtualList && this._setCellIndex(newVal, 'bottom');
// #endif
if (this.isFirstPage && this.finalConcat) {
this.totalData = [];
}
// customNoMore-1代表交由z-paging自行判断1代表没有更多了0代表还有更多数据
if (this.customNoMore !== -1) {
// 如果customNoMore等于1 或者 customNoMore不是0并且新增数组长度为0(也就是不是明确的还有更多数据并且新增的数组长度为0),则没有更多数据了
if (this.customNoMore === 1 || (this.customNoMore !== 0 && !newVal.length)) {
this.loadingStatus = Enum.More.NoMore;
}
} else {
// 如果新增的数据数组长度为0 或者 新增的数组长度小于默认的pageSize则没有更多数据了
if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) {
this.loadingStatus = Enum.More.NoMore;
}
}
if (!this.totalData.length) {
// #ifdef APP-NVUE
// 如果在聊天记录模式+nvue中并且数据不满一页时需要将列表倒序因为此时没有将列表旋转180度数组中第0条数据应当在最底下显示
if (this.useChatRecordMode && this.finalConcat && this.isFirstPage && this.loadingStatus === Enum.More.NoMore) {
newVal.reverse();
}
// #endif
this.totalData = newVal;
} else {
if (this.finalConcat) {
const currentScrollTop = this.oldScrollTop;
this.totalData = [...this.totalData, ...newVal];
// 此处是为了解决在微信小程序中,在某些情况下滚动到底部加载更多后滚动位置直接变为最底部的问题,因此需要通过代码强制滚动回加载更多前的位置
// #ifdef MP-WEIXIN
if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) {
this.loadingMoreTimeStamp = u.getTime();
this.$nextTick(() => {
this.scrollToY(currentScrollTop);
})
}
// #endif
} else {
this.totalData = newVal;
}
}
this.privateConcat = true;
},
// 根据pageNo处理refresh操作
_handleRefreshWithDisPageNo(pageNo) {
if (!this.isHandlingRefreshToPage && !this.realTotalData.length) return this.reload();
if (pageNo >= 1) {
this.loading = true;
this.privateConcat = false;
const totalPageSize = pageNo * this.pageSize;
this.currentRefreshPageSize = totalPageSize;
// 如果调用refresh时是本地分页则在组件内部自己处理分页逻辑不emit query相关事件
if (this.isLocalPaging && this.isHandlingRefreshToPage) {
this._localPagingQueryList(this.defaultPageNo, totalPageSize, 0, res => {
this.complete(res);
})
} else {
// emit query相关事件
this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh);
this._callMyParentQuery(this.defaultPageNo, totalPageSize);
}
}
return new Promise((resolve, reject) => {
this.dataPromiseResultMap.reload = { resolve, reject };
});
},
// 本地分页请求
_localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) {
pageNo = Math.max(1, pageNo);
pageSize = Math.max(1, pageSize);
const totalPagingList = [...this.totalLocalPagingList];
const pageNoIndex = (pageNo - 1) * pageSize;
const finalPageNoIndex = Math.min(totalPagingList.length, pageNoIndex + pageSize);
const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex);
u.delay(() => callback(resultPagingList), localPagingLoadingTime)
},
// 存储列表缓存数据
_saveLocalCache(data) {
uni.setStorageSync(this.finalCacheKey, data);
},
// 通过缓存数据填充列表数据
_setListByLocalCache() {
this.totalData = uni.getStorageSync(this.finalCacheKey) || [];
this.isSettingCacheList = true;
},
// 修改父view的list
_callMyParentList(newVal) {
if (this.autowireListName.length) {
const myParent = u.getParent(this.$parent);
if (myParent && myParent[this.autowireListName]) {
myParent[this.autowireListName] = newVal;
}
}
},
// 调用父view的query
_callMyParentQuery(customPageNo = 0, customPageSize = 0) {
if (this.autowireQueryName) {
if (this.myParentQuery === -1) {
const myParent = u.getParent(this.$parent);
if (myParent && myParent[this.autowireQueryName]) {
this.myParentQuery = myParent[this.autowireQueryName];
}
}
if (this.myParentQuery !== -1) {
customPageSize > 0 ? this.myParentQuery(customPageNo, customPageSize) : this.myParentQuery(this.pageNo, this.defaultPageSize);
}
}
},
// emit query事件
_emitQuery(pageNo, pageSize, from){
this.queryFrom = from;
this.requestTimeStamp = u.getTime();
const [lastItem] = this.realTotalData.slice(-1);
if (this.fetch) {
const fetchParams = interceptor._handleFetchParams({pageNo, pageSize, from, lastItem: lastItem || null}, this.fetchParams);
const fetchResult = this.fetch(fetchParams);
if (!interceptor._handleFetchResult(fetchResult, this, fetchParams)) {
u.isPromise(fetchResult) ? fetchResult.then(res => {
this.complete(res);
}).catch(err => {
this.complete(false);
}) : this.complete(fetchResult)
}
} else {
this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null));
}
},
// 触发数据改变promise
_callDataPromise(success, totalList) {
for (const key in this.dataPromiseResultMap) {
const obj = this.dataPromiseResultMap[key];
if (!obj) continue;
success ? obj.resolve({ totalList, noMore: this.loadingStatus === Enum.More.NoMore }) : this.callNetworkReject && obj.reject(`z-paging-${key}-error`);
}
},
// 检查complete data的类型
_checkDataType(data, success, isLocal) {
const dataType = Object.prototype.toString.call(data);
if (dataType === '[object Boolean]') {
success = data;
data = [];
} else if (dataType !== '[object Array]') {
data = [];
if (dataType !== '[object Undefined]' && dataType !== '[object Null]') {
u.consoleErr(`${isLocal ? 'setLocalPaging' : 'complete'}参数类型不正确第一个参数类型必须为Array!`);
}
}
return { data, success };
},
}
}

View File

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

View File

@ -1,119 +0,0 @@
// [z-paging]i18n模块
import { initVueI18n } from '@dcloudio/uni-i18n'
import messages from '../../i18n/index.js'
const { t } = initVueI18n(messages)
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import interceptor from '../z-paging-interceptor'
const language = uni.getSystemInfoSync().language;
export default {
data() {
return {
language
}
},
computed: {
finalLanguage() {
try {
const local = uni.getLocale();
const language = this.language;
return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local;
} catch (e) {
// 如果获取系统本地语言异常则默认返回中文uni.getLocale在部分低版本HX或者cli中可能报找不到的问题
return 'zh-Hans';
}
},
// 最终的下拉刷新默认状态的文字
finalRefresherDefaultText() {
return this._getI18nText('zp.refresher.default', this.refresherDefaultText);
},
// 最终的下拉刷新下拉中的文字
finalRefresherPullingText() {
return this._getI18nText('zp.refresher.pulling', this.refresherPullingText);
},
// 最终的下拉刷新中文字
finalRefresherRefreshingText() {
return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText);
},
// 最终的下拉刷新完成文字
finalRefresherCompleteText() {
return this._getI18nText('zp.refresher.complete', this.refresherCompleteText);
},
// 最终的下拉刷新上次更新时间文字
finalRefresherUpdateTimeTextMap() {
return {
title: t('zp.refresherUpdateTime.title'),
none: t('zp.refresherUpdateTime.none'),
today: t('zp.refresherUpdateTime.today'),
yesterday: t('zp.refresherUpdateTime.yesterday')
};
},
// 最终的继续下拉进入二楼文字
finalRefresherGoF2Text() {
return this._getI18nText('zp.refresher.f2', this.refresherGoF2Text);
},
// 最终的底部加载更多默认状态文字
finalLoadingMoreDefaultText() {
return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText);
},
// 最终的底部加载更多加载中文字
finalLoadingMoreLoadingText() {
return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText);
},
// 最终的底部加载更多没有更多数据文字
finalLoadingMoreNoMoreText() {
return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText);
},
// 最终的底部加载更多加载失败文字
finalLoadingMoreFailText() {
return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText);
},
// 最终的空数据图title
finalEmptyViewText() {
return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText);
},
// 最终的空数据图reload title
finalEmptyViewReloadText() {
return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText);
},
// 最终的空数据图加载失败文字
finalEmptyViewErrorText() {
return this.customerEmptyViewErrorText || this._getI18nText('zp.emptyView.error', this.emptyViewErrorText);
},
// 最终的系统loading title
finalSystemLoadingText() {
return this._getI18nText('zp.systemLoading.title', this.systemLoadingText);
},
},
methods: {
// 获取当前z-paging的语言
getLanguage() {
return this.finalLanguage;
},
// 获取国际化转换后的文本
_getI18nText(key, value) {
const dataType = Object.prototype.toString.call(value);
if (dataType === '[object Object]') {
const nextValue = value[this.finalLanguage];
if (nextValue) return nextValue;
} else if (dataType === '[object String]') {
return value;
}
return t(key);
},
// 系统language转i18n local
_language2Local(language) {
const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-');
if (formatedLanguage.indexOf('zh') !== -1) {
if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) {
return 'zh-Hans';
}
return 'zh-Hant';
}
if (formatedLanguage.indexOf('en') !== -1) return 'en';
return language;
}
}
}

View File

@ -1,374 +0,0 @@
// [z-paging]滚动到底部加载更多模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
export default {
props: {
// 自定义底部加载更多样式
loadingMoreCustomStyle: {
type: Object,
default: u.gc('loadingMoreCustomStyle', {})
},
// 自定义底部加载更多文字样式
loadingMoreTitleCustomStyle: {
type: Object,
default: u.gc('loadingMoreTitleCustomStyle', {})
},
// 自定义底部加载更多加载中动画样式
loadingMoreLoadingIconCustomStyle: {
type: Object,
default: u.gc('loadingMoreLoadingIconCustomStyle', {})
},
// 自定义底部加载更多加载中动画图标类型可选flower或circle默认为flower
loadingMoreLoadingIconType: {
type: String,
default: u.gc('loadingMoreLoadingIconType', 'flower')
},
// 自定义底部加载更多加载中动画图标图片
loadingMoreLoadingIconCustomImage: {
type: String,
default: u.gc('loadingMoreLoadingIconCustomImage', '')
},
// 底部加载更多加载中view是否展示旋转动画默认为是
loadingMoreLoadingAnimated: {
type: Boolean,
default: u.gc('loadingMoreLoadingAnimated', true)
},
// 是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是
loadingMoreEnabled: {
type: Boolean,
default: u.gc('loadingMoreEnabled', true)
},
// 是否启用滑动到底部加载更多数据,默认为是
toBottomLoadingMoreEnabled: {
type: Boolean,
default: u.gc('toBottomLoadingMoreEnabled', true)
},
// 滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】
loadingMoreDefaultAsLoading: {
type: Boolean,
default: u.gc('loadingMoreDefaultAsLoading', false)
},
// 滑动到底部"默认"文字,默认为【点击加载更多】
loadingMoreDefaultText: {
type: [String, Object],
default: u.gc('loadingMoreDefaultText', null)
},
// 滑动到底部"加载中"文字,默认为【正在加载...】
loadingMoreLoadingText: {
type: [String, Object],
default: u.gc('loadingMoreLoadingText', null)
},
// 滑动到底部"没有更多"文字,默认为【没有更多了】
loadingMoreNoMoreText: {
type: [String, Object],
default: u.gc('loadingMoreNoMoreText', null)
},
// 滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】
loadingMoreFailText: {
type: [String, Object],
default: u.gc('loadingMoreFailText', null)
},
// 当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view默认为否
hideNoMoreInside: {
type: Boolean,
default: u.gc('hideNoMoreInside', false)
},
// 当没有更多数据且分页数组长度少于这个值时隐藏没有更多数据的view默认为0代表不限制。
hideNoMoreByLimit: {
type: Number,
default: u.gc('hideNoMoreByLimit', 0)
},
// 是否显示默认的加载更多text默认为是
showDefaultLoadingMoreText: {
type: Boolean,
default: u.gc('showDefaultLoadingMoreText', true)
},
// 是否显示没有更多数据的view
showLoadingMoreNoMoreView: {
type: Boolean,
default: u.gc('showLoadingMoreNoMoreView', true)
},
// 是否显示没有更多数据的分割线,默认为是
showLoadingMoreNoMoreLine: {
type: Boolean,
default: u.gc('showLoadingMoreNoMoreLine', true)
},
// 自定义底部没有更多数据的分割线样式
loadingMoreNoMoreLineCustomStyle: {
type: Object,
default: u.gc('loadingMoreNoMoreLineCustomStyle', {})
},
// 当分页未满一屏时,是否自动加载更多,默认为否(nvue无效)
insideMore: {
type: Boolean,
default: u.gc('insideMore', false)
},
// 距底部/右边多远时单位px触发 scrolltolower 事件默认为100rpx
lowerThreshold: {
type: [Number, String],
default: u.gc('lowerThreshold', '100rpx')
},
},
data() {
return {
M: Enum.More,
// 底部加载更多状态
loadingStatus: Enum.More.Default,
// 在渲染之后的底部加载更多状态
loadingStatusAfterRender: Enum.More.Default,
// 底部加载更多时间戳
loadingMoreTimeStamp: 0,
// 底部加载更多slot
loadingMoreDefaultSlot: null,
// 是否展示底部加载更多
showLoadingMore: false,
// 是否是开发者自定义的加载更多,-1代表交由z-paging自行判断1代表没有更多了0代表还有更多数据
customNoMore: -1,
}
},
computed: {
// 底部加载更多配置
zLoadMoreConfig() {
return {
status: this.loadingStatusAfterRender,
defaultAsLoading: this.loadingMoreDefaultAsLoading || (this.useChatRecordMode && this.chatLoadingMoreDefaultAsLoading),
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,
unit: this.unit,
isChat: this.useChatRecordMode,
chatDefaultAsLoading: this.chatLoadingMoreDefaultAsLoading
};
},
// 最终的底部加载更多主题
finalLoadingMoreThemeStyle() {
return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle;
},
// 最终的底部加载更多触发阈值
finalLowerThreshold() {
return u.convertToPx(this.lowerThreshold);
},
// 是否显示默认状态下的底部加载更多
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');
},
// 底部加载更多固定高度
loadingMoreFixedHeight() {
return u.addUnit('80rpx', this.unit);
},
},
methods: {
// 页面滚动到底部时通知z-paging进行进一步处理
pageReachBottom() {
!this.useChatRecordMode && this.toBottomLoadingMoreEnabled && this._onLoadingMore('toBottom');
},
// 手动触发上拉加载更多(非必须,可依据具体需求使用)
doLoadMore(type) {
this._onLoadingMore(type);
},
// 通过@scroll事件检测是否滚动到了底部(顺带检测下是否滚动到了顶部)
_checkScrolledToBottom(scrollDiff, checked = false) {
// 如果当前scroll-view高度未获取则获取其高度
if (this.cacheScrollNodeHeight === -1) {
// 获取当前scroll-view高度
this._getNodeClientRect('.zp-scroll-view').then((res) => {
if (res) {
const scrollNodeHeight = res[0].height;
// 缓存当前scroll-view高度如果获取过了不再获取
this.cacheScrollNodeHeight = scrollNodeHeight;
// // scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离)
if (scrollDiff - scrollNodeHeight <= this.finalLowerThreshold) {
// 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件
this._onLoadingMore('toBottom');
}
}
});
} else {
// scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离)
if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) {
// 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件
this._onLoadingMore('toBottom');
} else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) {
// 如果与底部的距离小于500px则获取当前滚动的位置延迟150毫秒重复上述步骤再次检测(避免@scroll触发时获取的scrollTop不正确导致的其他问题此时获取的scrollTop不一定可信)。防止因为部分性能较差安卓设备@scroll采样率过低导致的滚动到底部但是依然没有触发的问题
u.delay(() => {
this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => {
if (res) {
this.oldScrollTop = res[0].scrollTop;
const newScrollDiff = res[0].scrollHeight - this.oldScrollTop;
this._checkScrolledToBottom(newScrollDiff, true);
}
})
}, 150, 'checkScrolledToBottomDelay')
}
// 检测一下是否已经滚动到了顶部了因为在安卓中滚动到顶部时scrollTop不一定为0(和滚动到底部一样的原因)所以需要在scrollTop小于150px时通过获取.zp-scroll-view的scrollTop再判断一下
if (this.oldScrollTop <= 150 && this.oldScrollTop !== 0) {
u.delay(() => {
// 这里再判断一下是否确实已经滚动到顶部了如果已经滚动到顶部了则不用再判断了再次判断的原因是可能150毫秒之后oldScrollTop才是0
if (this.oldScrollTop !== 0) {
this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => {
// 如果150毫秒后.zp-scroll-view的scrollTop为0则认为已经滚动到了顶部了
if (res && res[0].scrollTop === 0 && this.oldScrollTop !== 0) {
this._onScrollToUpper();
}
})
}
}, 150, 'checkScrolledToTopDelay')
}
}
},
// 触发加载更多时调用,from:toBottom-滑动到底部触发click-点击加载更多触发
_onLoadingMore(from = 'click') {
// 如果是ios并且是滚动到底部的则在滚动到底部时候尝试将列表设置为禁止滚动然后设置为允许滚动以禁止底部bounce的效果
if (this.isIos && from === 'toBottom' && !this.scrollToBottomBounceEnabled && this.scrollEnable) {
this.scrollEnable = false;
this.$nextTick(() => {
this.scrollEnable = true;
})
}
// emit scrolltolower
this.$emit('scrolltolower', from);
// 如果是只使用下拉刷新 或者 禁用底部加载更多 或者 底部加载更多不是默认状态或加载失败状态 或者 是加载中状态 或者 空数据图已经展示了则return不触发内部加载更多逻辑
if (this.refresherOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return;
// #ifdef MP-WEIXIN
if (!this.isIos && !this.refresherOnly && !this.usePageScroll) {
const currentTimestamp = u.getTime();
// 在非ios平台+scroll-view中节流处理
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) {
// 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件
this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => {
this.completeByTotal(res, this.totalLocalPagingList.length);
this.queryFrom = Enum.QueryFrom.LoadMore;
})
} else {
// emit @query相关加载更多事件
this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadMore);
this._callMyParentQuery();
}
// 设置当前加载状态为底部加载更多状态
this.loadingType = Enum.LoadingType.LoadMore;
}
},
// (预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view
_preCheckShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode) {
if (this.loadingStatus === Enum.More.NoMore && this.hideNoMoreByLimit > 0 && newVal.length) {
this.showLoadingMore = newVal.length > this.hideNoMoreByLimit;
} else if ((this.loadingStatus === Enum.More.NoMore && this.hideNoMoreInside && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) {
this.$nextTick(() => {
this._checkShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode);
})
if (this.insideMore && this.insideOfPaging !== false && newVal.length) {
this.showLoadingMore = newVal.length;
}
} else {
this.showLoadingMore = newVal.length;
}
},
// 判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view
async _checkShowNoMoreInside(totalData, oldScrollViewNode, oldPagingContainerNode) {
try {
const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view');
// 在页面滚动模式下
if (this.usePageScroll) {
if (scrollViewNode) {
// 获取滚动内容总高度
const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height;
// 如果滚动内容总高度小于窗口高度则认为内容未超出z-paging
this.insideOfPaging = scrollViewTotalH < this.windowHeight;
// 如果需要没有更多数据时隐藏底部加载更多view并且内容未超过z-paging则隐藏底部加载更多
if (this.hideNoMoreInside) {
this.showLoadingMore = !this.insideOfPaging;
}
// 如果需要内容未超过z-paging时自动加载更多则触发加载更多
this._updateInsideOfPaging();
}
} else {
// 在scroll-view滚动模式下
const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content');
// 获取滚动内容总高度
const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0;
// 获取z-paging内置scroll-view高度
const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0;
// 如果滚动内容总高度小于z-paging内置scroll-view高度则认为内容未超出z-paging
this.insideOfPaging = pagingContainerH < scrollViewH;
if (this.hideNoMoreInside) {
this.showLoadingMore = !this.insideOfPaging;
}
// 如果需要内容未超过z-paging时自动加载更多则触发加载更多
this._updateInsideOfPaging();
}
} catch (e) {
// 如果发生了异常判断totalData数组长度为0则认为内容未超出z-paging
this.insideOfPaging = !totalData.length;
if (this.hideNoMoreInside) {
this.showLoadingMore = !this.insideOfPaging;
}
// 如果需要内容未超过z-paging时自动加载更多则触发加载更多
this._updateInsideOfPaging();
}
},
// 是否要展示上拉加载更多view
_showLoadingMore(type) {
if (!this.showLoadingMoreWhenReload && (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.realTotalData.length)) return false;
if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) ||
(!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.refresherOnly) {
return false;
}
if (this.useChatRecordMode && type !== 'Loading') return false;
if (!this.zSlots) return false;
if (type === 'Custom') {
return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView);
}
const res = this.loadingStatus === Enum.More[type] && this.zSlots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true);
if (res) {
// #ifdef APP-NVUE
if (!this.isIos) {
this.nLoadingMoreFixedHeight = false;
}
// #endif
}
return res;
},
}
}

View File

@ -1,95 +0,0 @@
// [z-paging]loading相关模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
export default {
props: {
// 第一次加载后自动隐藏loading slot默认为是
autoHideLoadingAfterFirstLoaded: {
type: Boolean,
default: u.gc('autoHideLoadingAfterFirstLoaded', true)
},
// loading slot是否铺满屏幕并固定默认为否
loadingFullFixed: {
type: Boolean,
default: u.gc('loadingFullFixed', false)
},
// 是否自动显示系统Loading即uni.showLoading若开启则将在刷新列表时(调用reload、refresh时)显示下拉刷新和滚动到底部加载更多不会显示默认为false。
autoShowSystemLoading: {
type: Boolean,
default: u.gc('autoShowSystemLoading', false)
},
// 显示系统Loading时是否显示透明蒙层防止触摸穿透默认为是(H5、App、微信小程序、百度小程序有效)
systemLoadingMask: {
type: Boolean,
default: u.gc('systemLoadingMask', true)
},
// 显示系统Loading时显示的文字默认为"加载中"
systemLoadingText: {
type: [String, Object],
default: u.gc('systemLoadingText', null)
},
},
data() {
return {
loading: false,
loadingForNow: false,
}
},
watch: {
// loading状态
loadingStatus(newVal) {
this.$emit('loadingStatusChange', newVal);
this.$nextTick(() => {
this.loadingStatusAfterRender = newVal;
})
if (this.useChatRecordMode) {
if (this.isFirstPage && (newVal === Enum.More.NoMore || newVal === Enum.More.Fail)) {
this.isFirstPageAndNoMore = true;
return;
}
}
this.isFirstPageAndNoMore = false;
},
loading(newVal){
if (newVal) {
this.loadingForNow = newVal;
}
},
},
computed: {
// 是否显示loading
showLoading() {
if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false;
if (this.finalShowSystemLoading) {
// 显示系统loading
uni.showLoading({
title: this.finalSystemLoadingText,
mask: this.systemLoadingMask
})
}
return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher;
},
// 最终的是否显示系统loading
finalShowSystemLoading() {
return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher;
}
},
methods: {
// 处理开始加载更多状态
_startLoading(isReload = false) {
if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) {
this.loadingStatus = Enum.More.Loading;
}
this.loading = true;
},
// 停止系统loading和refresh
_endSystemLoadingAndRefresh(){
this.finalShowSystemLoading && uni.hideLoading();
!this.useCustomRefresher && uni.stopPullDownRefresh();
// #ifdef APP-NVUE
this.usePageScroll && uni.stopPullDownRefresh();
// #endif
}
}
}

View File

@ -1,255 +0,0 @@
// [z-paging]nvue独有部分模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
// #ifdef APP-NVUE
const weexAnimation = weex.requireModule('animation');
// #endif
export default {
props: {
// #ifdef APP-NVUE
// nvue中修改列表类型可选值有list、waterfall和scroller默认为list
nvueListIs: {
type: String,
default: u.gc('nvueListIs', 'list')
},
// nvue waterfall配置仅在nvue中且nvueListIs=waterfall时有效配置参数详情参见https://uniapp.dcloud.io/component/waterfall
nvueWaterfallConfig: {
type: Object,
default: 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: u.gc('nvueRefresherStyle', {})
},
// nvue中是否按分页模式(类似竖向swiper)显示List默认为false
nvuePagingEnabled: {
type: Boolean,
default: u.gc('nvuePagingEnabled', false)
},
// 是否隐藏nvue列表底部的tagView此view用于标识滚动到底部位置若隐藏则滚动到底部功能将失效在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否
hideNvueBottomTag: {
type: Boolean,
default: u.gc('hideNvueBottomTag', false)
},
// nvue中控制onscroll事件触发的频率表示两次onscroll事件之间列表至少滚动了10px。注意将该值设置为较小的数值会提高滚动事件采样的精度但同时也会降低页面的性能
offsetAccuracy: {
type: Number,
default: u.gc('offsetAccuracy', 10)
},
// #endif
},
data() {
return {
nRefresherLoading: false,
nListIsDragging: false,
nShowBottom: true,
nFixFreezing: false,
nShowRefresherReveal: false,
nLoadingMoreFixedHeight: false,
nShowRefresherRevealHeight: 0,
nOldShowRefresherRevealHeight: -1,
nRefresherWidth: u.rpx2px(750),
nF2Opacity: 0
}
},
computed: {
// #ifdef APP-NVUE
nScopedSlots() {
// #ifdef VUE2
return this.$scopedSlots;
// #endif
// #ifdef VUE3
return null;
// #endif
},
nWaterfallColumnCount() {
if (this.finalNvueListIs !== 'waterfall') return 0;
return this._nGetWaterfallConfig('column-count', 2);
},
nWaterfallColumnWidth() {
return this._nGetWaterfallConfig('column-width', 'auto');
},
nWaterfallColumnGap() {
return this._nGetWaterfallConfig('column-gap', 'normal');
},
nWaterfallLeftGap() {
return this._nGetWaterfallConfig('left-gap', 0);
},
nWaterfallRightGap() {
return this._nGetWaterfallConfig('right-gap', 0);
},
nViewIs() {
const is = this.finalNvueListIs;
return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell';
},
nSafeAreaBottomHeight() {
return this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
},
finalNvueListIs() {
if (this.usePageScroll) return 'view';
const nvueListIsLowerCase = this.nvueListIs.toLowerCase();
if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase;
return 'list';
},
finalNvueSuperListIs() {
return this.usePageScroll ? 'view' : 'scroller';
},
finalNvueRefresherEnabled() {
return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode;
},
// #endif
},
mounted(){
// #ifdef APP-NVUE
//旋转屏幕时更新宽度
uni.onWindowResize((res) => {
// this._nUpdateRefresherWidth();
})
// #endif
},
methods: {
// #ifdef APP-NVUE
// 列表滚动时触发
_nOnScroll(e) {
this.$emit('scroll', e);
const contentOffsetY = -e.contentOffset.y;
this.oldScrollTop = contentOffsetY;
this.nListIsDragging = e.isDragging;
this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1);
},
// 列表滚动结束
_nOnScrollend(e) {
this.$emit('scrollend', e);
},
// 下拉刷新刷新中
_nOnRrefresh() {
if (this.nShowRefresherReveal) return;
// 进入刷新状态
this.nRefresherLoading = true;
if (this.refresherStatus === Enum.Refresher.GoF2) {
this._handleGoF2();
this.$nextTick(() => {
this._nRefresherEnd();
})
} else {
this.refresherStatus = Enum.Refresher.Loading;
this._doRefresherLoad();
}
},
// 下拉刷新下拉中
_nOnPullingdown(e) {
if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return;
this._emitTouchmove(e);
let { viewHeight, pullingDistance } = e;
// 更新下拉刷新状态
// 下拉刷新距离超过阈值
if (pullingDistance >= viewHeight) {
// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
// (pullingDistance - viewHeight) + this.finalRefresherThreshold 不等同于pullingDistance此处是为了兼容不同平台下拉相同距离pullingDistance不一致的问题pullingDistance仅与viewHeight互相关联
this.refresherStatus = this.refresherF2Enabled && (pullingDistance - viewHeight) + this.finalRefresherThreshold >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
} else {
// 下拉刷新距离未超过阈值,显示默认状态
this.refresherStatus = Enum.Refresher.Default;
}
},
// 下拉刷新结束
_nRefresherEnd(doEnd = true) {
if (doEnd) {
this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight);
!this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore();
this.nRefresherLoading = false;
}
},
// 执行主动触发下拉刷新动画
_nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) {
// 清除下拉刷新相关timeout
this._cleanRefresherCompleteTimeout();
this._cleanRefresherEndTimeout();
if (!this.finalShowRefresherWhenReload) {
// 如果reload不需要自动展示下拉刷新view则在complete duration结束后再把下拉刷新状态设置回默认
this.refresherEndTimeout = u.delay(() => {
this.refresherStatus = Enum.Refresher.Default;
}, this.refresherCompleteDuration);
return;
}
// 用户处理用户在短时间内多次调用reload的情况此时下拉刷新view不需要重复显示只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可
const stackCount = this.refresherRevealStackCount;
if (height === 0 && checkStack) {
this.refresherRevealStackCount --;
if (stackCount > 1) return;
this.refresherEndTimeout = u.delay(() => {
this.refresherStatus = Enum.Refresher.Default;
}, this.refresherCompleteDuration);
}
if (stackCount > 1) {
this.refresherStatus = Enum.Refresher.Loading;
}
const duration = animate ? 200 : 0;
if (this.nOldShowRefresherRevealHeight !== height) {
if (height > 0) {
this.nShowRefresherReveal = true;
}
// 展示下拉刷新view
weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], {
styles: {
height: `${height}px`,
transform: `translateY(${translateY}px)`,
},
duration,
timingFunction: 'linear',
needLayout: true,
delay: 0
})
}
u.delay(() => {
if (animate) {
this.nShowRefresherReveal = height > 0;
}
}, duration > 0 ? duration - 60 : 0);
this.nOldShowRefresherRevealHeight = height;
},
// 滚动到底部加载更多
_nOnLoadmore() {
if (this.nShowRefresherReveal || !this.totalData.length) return;
this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom');
},
// 获取nvue waterfall单项配置
_nGetWaterfallConfig(key, defaultValue) {
return this.nvueWaterfallConfig[key] || defaultValue;
},
// 更新nvue 下拉刷新view容器的宽度
_nUpdateRefresherWidth() {
u.delay(() => {
this.$nextTick(()=>{
this._getNodeClientRect('.zp-n-list').then(node => {
if (node) {
this.nRefresherWidth = node[0].width || this.nRefresherWidth;
}
})
})
})
}
// #endif
}
}

View File

@ -1,831 +0,0 @@
// [z-paging]下拉刷新view模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
// #ifdef APP-NVUE
const weexAnimation = weex.requireModule('animation');
// #endif
export default {
props: {
// 下拉刷新的主题样式支持blackwhite默认black
refresherThemeStyle: {
type: String,
default: u.gc('refresherThemeStyle', '')
},
// 自定义下拉刷新中左侧图标的样式
refresherImgStyle: {
type: Object,
default: u.gc('refresherImgStyle', {})
},
// 自定义下拉刷新中右侧状态描述文字的样式
refresherTitleStyle: {
type: Object,
default: u.gc('refresherTitleStyle', {})
},
// 自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效)
refresherUpdateTimeStyle: {
type: Object,
default: u.gc('refresherUpdateTimeStyle', {})
},
// 在微信小程序和QQ小程序中是否实时监听下拉刷新中进度默认为否
watchRefresherTouchmove: {
type: Boolean,
default: u.gc('watchRefresherTouchmove', false)
},
// 底部加载更多的主题样式支持blackwhite默认black
loadingMoreThemeStyle: {
type: String,
default: u.gc('loadingMoreThemeStyle', '')
},
// 是否只使用下拉刷新设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多强制隐藏空数据图。默认为否
refresherOnly: {
type: Boolean,
default: u.gc('refresherOnly', false)
},
// 自定义下拉刷新默认状态下回弹动画时间单位为毫秒默认为100毫秒nvue无效
refresherDefaultDuration: {
type: [Number, String],
default: u.gc('refresherDefaultDuration', 100)
},
// 自定义下拉刷新结束以后延迟回弹的时间单位为毫秒默认为0
refresherCompleteDelay: {
type: [Number, String],
default: u.gc('refresherCompleteDelay', 0)
},
// 自定义下拉刷新结束回弹动画时间单位为毫秒默认为300毫秒(refresherEndBounceEnabled为false时refresherCompleteDuration为设定值的1/3)nvue无效
refresherCompleteDuration: {
type: [Number, String],
default: u.gc('refresherCompleteDuration', 300)
},
// 自定义下拉刷新中是否允许列表滚动,默认为是
refresherRefreshingScrollable: {
type: Boolean,
default: u.gc('refresherRefreshingScrollable', true)
},
// 自定义下拉刷新结束状态下是否允许列表滚动,默认为否
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)
},
// 自定义继续下拉进入二楼文字
refresherGoF2Text: {
type: [String, Object],
default: u.gc('refresherGoF2Text', null)
},
// 自定义下拉刷新默认状态下的图片
refresherDefaultImg: {
type: String,
default: u.gc('refresherDefaultImg', null)
},
// 自定义下拉刷新松手立即刷新状态下的图片默认与refresherDefaultImg一致
refresherPullingImg: {
type: String,
default: u.gc('refresherPullingImg', null)
},
// 自定义下拉刷新刷新中状态下的图片
refresherRefreshingImg: {
type: String,
default: u.gc('refresherRefreshingImg', null)
},
// 自定义下拉刷新刷新结束状态下的图片
refresherCompleteImg: {
type: String,
default: u.gc('refresherCompleteImg', null)
},
// 自定义下拉刷新刷新中状态下是否展示旋转动画
refresherRefreshingAnimated: {
type: Boolean,
default: u.gc('refresherRefreshingAnimated', true)
},
// 是否开启自定义下拉刷新刷新结束回弹效果,默认为是
refresherEndBounceEnabled: {
type: Boolean,
default: u.gc('refresherEndBounceEnabled', true)
},
// 是否开启自定义下拉刷新,默认为是
refresherEnabled: {
type: Boolean,
default: u.gc('refresherEnabled', true)
},
// 设置自定义下拉刷新阈值默认为80rpx
refresherThreshold: {
type: [Number, String],
default: u.gc('refresherThreshold', '80rpx')
},
// 设置系统下拉刷新默认样式,支持设置 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.65(nvue无效)
refresherOutRate: {
type: Number,
default: u.gc('refresherOutRate', 0.65)
},
// 是否开启下拉进入二楼功能,默认为否
refresherF2Enabled: {
type: Boolean,
default: u.gc('refresherF2Enabled', false)
},
// 下拉进入二楼阈值默认为200rpx
refresherF2Threshold: {
type: [Number, String],
default: u.gc('refresherF2Threshold', '200rpx')
},
// 下拉进入二楼动画时间单位为毫秒默认为200毫秒
refresherF2Duration: {
type: [Number, String],
default: u.gc('refresherF2Duration', 200)
},
// 下拉进入二楼状态松手后是否弹出二楼,默认为是
showRefresherF2: {
type: Boolean,
default: u.gc('showRefresherF2', true)
},
// 设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值默认为0.75即代表若用户下拉10px则实际位移为7.5px(nvue无效)
refresherPullRate: {
type: Number,
default: u.gc('refresherPullRate', 0.75)
},
// 是否显示最后更新时间,默认为否
showRefresherUpdateTime: {
type: Boolean,
default: u.gc('showRefresherUpdateTime', false)
},
// 如果需要区别不同页面的最后更新时间请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串
refresherUpdateTimeKey: {
type: String,
default: u.gc('refresherUpdateTimeKey', 'default')
},
// 下拉刷新时下拉到“松手立即刷新”或“松手进入二楼”状态时是否使手机短振动默认为否h5无效
refresherVibrate: {
type: Boolean,
default: u.gc('refresherVibrate', false)
},
// 下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动默认为否。注意此属性只是禁止下拉刷新view移动其他下拉刷新逻辑依然会正常触发
refresherNoTransform: {
type: Boolean,
default: u.gc('refresherNoTransform', false)
},
// 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否
useRefresherStatusBarPlaceholder: {
type: Boolean,
default: u.gc('useRefresherStatusBarPlaceholder', false)
},
},
data() {
return {
R: Enum.Refresher,
//下拉刷新状态
refresherStatus: Enum.Refresher.Default,
refresherTouchstartY: 0,
lastRefresherTouchmove: null,
refresherReachMaxAngle: true,
refresherTransform: 'translateY(0px)',
refresherTransition: '',
finalRefresherDefaultStyle: 'black',
refresherRevealStackCount: 0,
refresherCompleteTimeout: null,
refresherCompleteSubTimeout: null,
refresherEndTimeout: null,
isTouchmovingTimeout: null,
refresherTriggered: false,
isTouchmoving: false,
isTouchEnded: false,
isUserPullDown: false,
privateRefresherEnabled: -1,
privateShowRefresherWhenReload: false,
customRefresherHeight: -1,
showCustomRefresher: false,
doRefreshAnimateAfter: false,
isRefresherInComplete: false,
showF2: false,
f2Transform: '',
pullDownTimeStamp: 0,
moveDis: 0,
oldMoveDis: 0,
currentDis: 0,
oldCurrentMoveDis: 0,
oldRefresherTouchmoveY: 0,
oldTouchDirection: '',
oldEmitedTouchDirection: '',
oldPullingDistance: -1,
refresherThresholdUpdateTag: 0
}
},
watch: {
refresherDefaultStyle: {
handler(newVal) {
if (newVal.length) {
this.finalRefresherDefaultStyle = newVal;
}
},
immediate: true
},
refresherStatus(newVal) {
newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout();
this.refresherVibrate && (newVal === Enum.Refresher.ReleaseToRefresh || newVal === Enum.Refresher.GoF2) && this._doVibrateShort();
this.$emit('refresherStatusChange', newVal);
this.$emit('update:refresherStatus', newVal);
},
// 监听当前下拉刷新启用/禁用状态
refresherEnabled(newVal) {
// 当禁用下拉刷新时强制收回正在展示的下拉刷新view
!newVal && this.endRefresh();
}
},
computed: {
pullDownDisTimeStamp() {
return 1000 / this.refresherFps;
},
refresherThresholdUnitConverted() {
return u.addUnit(this.refresherThreshold, this.unit);
},
finalRefresherEnabled() {
if (this.useChatRecordMode) return false;
if (this.privateRefresherEnabled === -1) return this.refresherEnabled;
return this.privateRefresherEnabled === 1;
},
finalRefresherThreshold() {
let refresherThreshold = this.refresherThresholdUnitConverted;
let idDefault = false;
if (refresherThreshold === u.addUnit(80, this.unit)) {
idDefault = true;
if (this.showRefresherUpdateTime) {
refresherThreshold = u.addUnit(120, this.unit);
}
}
if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder;
return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder;
},
finalRefresherF2Threshold() {
return u.convertToPx(u.addUnit(this.refresherF2Threshold, this.unit));
},
finalRefresherThresholdPlaceholder() {
return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0;
},
finalRefresherFixedBacHeight() {
return u.convertToPx(this.refresherFixedBacHeight);
},
finalRefresherThemeStyle() {
return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle;
},
finalRefresherOutRate() {
let rate = this.refresherOutRate;
rate = Math.max(0,rate);
rate = Math.min(1,rate);
return rate;
},
finalRefresherPullRate() {
let rate = this.refresherPullRate;
rate = Math.max(0,rate);
return rate;
},
finalRefresherTransform() {
if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none';
return this.refresherTransform;
},
finalShowRefresherWhenReload() {
return this.showRefresherWhenReload || this.privateShowRefresherWhenReload;
},
finalRefresherTriggered() {
if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false;
return this.refresherTriggered;
},
showRefresher() {
const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode;
// #ifndef APP-NVUE
this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight();
// #endif
return showRefresher;
},
hasTouchmove() {
// #ifdef VUE2
// #ifdef APP-VUE || H5
if (this.$listeners && !this.$listeners.refresherTouchmove) return false;
// #endif
// #ifdef MP-WEIXIN || MP-QQ
return this.watchRefresherTouchmove;
// #endif
return true;
// #endif
return this.watchRefresherTouchmove;
},
},
methods: {
// 终止下拉刷新状态
endRefresh() {
this.totalData = this.realTotalData;
this._refresherEnd();
this._endSystemLoadingAndRefresh();
this._handleScrollViewBounce({ bounce: true });
this.$nextTick(() => {
this.refresherTriggered = false;
})
},
// 手动更新自定义下拉刷新view高度
updateCustomRefresherHeight() {
u.delay(() => this.$nextTick(this._updateCustomRefresherHeight));
},
// 关闭二楼
closeF2() {
this._handleCloseF2();
},
// 自定义下拉刷新被触发
_onRefresh(fromScrollView = false, isUserPullDown = true) {
if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return;
this.$emit('onRefresh');
this.$emit('Refresh');
// #ifdef APP-NVUE
if (this.loading) {
u.delay(this._nRefresherEnd, 500)
return;
}
// #endif
if (this.loading || this.isRefresherInComplete) return;
this.loadingType = Enum.LoadingType.Refresher;
if (this.nShowRefresherReveal) return;
this.isUserPullDown = isUserPullDown;
this.isUserReload = !isUserPullDown;
this._startLoading(true);
this.refresherTriggered = true;
if(this.reloadWhenRefresh && isUserPullDown){
this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown);
}
},
// 自定义下拉刷新被复位
_onRestore() {
this.refresherTriggered = 'restore';
this.$emit('onRestore');
this.$emit('Restore');
},
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// touch开始
_refresherTouchstart(e) {
this._handleListTouchstart();
if (this._touchDisabled()) return;
this._handleRefresherTouchstart(u.getTouch(e));
},
// #endif
// 进一步处理touch开始结果
_handleRefresherTouchstart(touch) {
if (!this.loading && this.isTouchEnded) {
this.isTouchmoving = false;
}
this.loadingType = Enum.LoadingType.Refresher;
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this.isTouchEnded = false;
this.refresherTransition = '';
this.refresherTouchstartY = touch.touchY;
this.$emit('refresherTouchstart', this.refresherTouchstartY);
this.lastRefresherTouchmove = touch;
this._cleanRefresherCompleteTimeout();
this._cleanRefresherEndTimeout();
},
// 非app-ios/android或微信小程序或QQ小程序或h5平台使用js控制下拉刷新
// #ifndef APP-IOS || APP-ANDROID || MP-WEIXIN || MP-QQ || H5
// touch中
_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';
// 只有在方向改变的时候才emit相关事件
if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) {
this._handleTouchDirectionChange({ direction });
this.oldEmitedTouchDirection = direction;
}
this.oldTouchDirection = direction;
this.oldRefresherTouchmoveY = refresherTouchmoveY;
}
// 节流处理在pullDownDisTimeStamp时间内的下拉刷新中事件不进行处理
if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return;
// 如果不允许下拉则return
if (this._touchDisabled()) return;
this.pullDownTimeStamp = Number(currentTimeStamp);
touch = u.getTouch(e);
refresherTouchmoveY = touch.touchY;
// 获取当前touch的y - 初始touch的y计算它们的差
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;
// 如果角度小于配置要求则return
if (angle < this.refresherMaxAngle) {
this.lastRefresherTouchmove = touch;
this.refresherReachMaxAngle = false;
return;
}
}
}
// 获取最终的moveDis
moveDis = this._getFinalRefresherMoveDis(moveDis);
// 处理下拉刷新位移
this._handleRefresherTouchmove(moveDis, touch);
// 下拉刷新时,禁止页面滚动以防止页面向下滚动和下拉刷新同时作用导致下拉刷新位置偏移超过预期
if (!this.disabledBounce) {
// #ifndef MP-LARK
this._handleScrollViewBounce({ bounce: false });
// #endif
this.disabledBounce = true;
}
this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis });
},
// #endif
// 进一步处理touch中结果
_handleRefresherTouchmove(moveDis, touch) {
this.refresherReachMaxAngle = true;
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this.isTouchmoving = true;
this.isTouchEnded = false;
// 更新下拉刷新状态
// 下拉刷新距离超过阈值
if (moveDis >= this.finalRefresherThreshold) {
// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
this.refresherStatus = this.refresherF2Enabled && moveDis >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
} else {
// 下拉刷新距离未超过阈值,显示默认状态
this.refresherStatus = Enum.Refresher.Default;
}
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// this.scrollEnable = false;
// 通过transform控制下拉刷新view垂直偏移
this.refresherTransform = `translateY(${moveDis}px)`;
this.lastRefresherTouchmove = touch;
// #endif
this.moveDis = moveDis;
},
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// touch结束
_refresherTouchend(e) {
// 下拉刷新用户手离开屏幕,允许列表滚动
this._handleScrollViewBounce({bounce: true});
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.disabledBounce = false;
},
// #endif
// 进一步处理touch结束结果
_handleRefresherTouchend(moveDis) {
// #ifndef APP-PLUS || H5 || MP-WEIXIN
if (!this.isTouchmoving) return;
// #endif
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this.refresherReachMaxAngle = true;
this.isTouchEnded = true;
const refresherThreshold = this.finalRefresherThreshold;
if (moveDis >= refresherThreshold && (this.refresherStatus === Enum.Refresher.ReleaseToRefresh || this.refresherStatus === Enum.Refresher.GoF2)) {
// 如果是松手进入二楼状态,则触发进入二楼
if (this.refresherStatus === Enum.Refresher.GoF2) {
this._handleGoF2();
this._refresherEnd();
} else {
// 如果是松手立即刷新状态,则触发下拉刷新
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = `translateY(${refresherThreshold}px)`;
this.refresherTransition = 'transform .1s linear';
// #endif
u.delay(() => {
this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold });
}, 0.1);
this.moveDis = refresherThreshold;
this.refresherStatus = Enum.Refresher.Loading;
this._doRefresherLoad();
}
} else {
this._refresherEnd();
this.isTouchmovingTimeout = u.delay(() => {
this.isTouchmoving = false;
}, this.refresherDefaultDuration);
}
this.scrollEnable = true;
this.$emit('refresherTouchend', moveDis);
},
// 处理列表触摸开始事件
_handleListTouchstart() {
if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) {
uni.hideKeyboard();
this.$emit('hidedKeyboard');
}
},
// 处理scroll-view bounce是否生效
_handleScrollViewBounce({ bounce }) {
if (!this.usePageScroll && !this.scrollToTopBounceEnabled) {
if (this.wxsScrollTop <= 5) {
// #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransition = '';
// #endif
this.scrollEnable = bounce;
} else if (bounce) {
this.scrollEnable = bounce;
}
}
},
// wxs正在下拉状态改变处理
_handleWxsPullingDownStatusChange(onPullingDown) {
this.wxsOnPullingDown = onPullingDown;
if (onPullingDown && !this.useChatRecordMode) {
this.renderPropScrollTop = 0;
}
},
// wxs正在下拉处理
_handleWxsPullingDown({ moveDis, diffDis }){
this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis });
},
// wxs触摸方向改变
_handleTouchDirectionChange({ direction }) {
this.$emit('touchDirectionChange',direction);
},
// wxs通知更新其props
_handlePropUpdate(){
this.wxsPropType = u.getTime().toString();
},
// 下拉刷新结束
_refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) {
if (this.loadingType === Enum.LoadingType.Refresher) {
// 计算当前下拉刷新结束需要延迟的时间
const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0;
// 如果延迟时间大于0则展示刷新结束状态否则直接展示默认状态
const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default;
if (this.finalShowRefresherWhenReload) {
const stackCount = this.refresherRevealStackCount;
this.refresherRevealStackCount --;
if (stackCount > 1) return;
}
this._cleanRefresherEndTimeout();
this.refresherEndTimeout = u.delay(() => {
// 更新下拉刷新状态
this.refresherStatus = refresherStatus;
// 如果当前下拉刷新状态不是刷新结束,则认为其不在刷新结束状态
if (refresherStatus !== Enum.Refresher.Complete) {
this.isRefresherInComplete = false;
}
}, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0);
// #ifndef APP-NVUE
if (refresherCompleteDelay > 0) {
this.isRefresherInComplete = true;
}
// #endif
this._cleanRefresherCompleteTimeout();
this.refresherCompleteTimeout = u.delay(() => {
let animateDuration = 1;
const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear';
if (fromAddData) {
animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000;
}
this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`;
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = 'translateY(0px)';
this.currentDis = 0;
// #endif
// #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.wxsPropType = this.refresherTransition + 'end' + u.getTime();
// #endif
// #ifdef APP-NVUE
this._nRefresherEnd();
// #endif
this.moveDis = 0;
// #ifndef APP-NVUE
if (refresherStatus === Enum.Refresher.Complete) {
if (this.refresherCompleteSubTimeout) {
clearTimeout(this.refresherCompleteSubTimeout);
this.refresherCompleteSubTimeout = null;
}
this.refresherCompleteSubTimeout = u.delay(() => {
this.$nextTick(() => {
this.refresherStatus = Enum.Refresher.Default;
this.isRefresherInComplete = false;
})
}, animateDuration * 800);
}
// #endif
this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis });
}, refresherCompleteDelay);
}
if (setLoading) {
u.delay(() => this.loading = false, shouldEndLoadingDelay ? 10 : 0);
isUserPullDown && this._onRestore();
}
},
// 处理进入二楼
_handleGoF2() {
if (this.showF2 || !this.refresherF2Enabled) return;
this.$emit('refresherF2Change', 'go');
if (!this.showRefresherF2) return;
// #ifndef APP-NVUE
this.f2Transform = `translateY(${-this.superContentHeight}px)`;
this.showF2 = true;
u.delay(() => {
this.f2Transform = 'translateY(0px)';
}, 100, 'f2ShowDelay')
// #endif
// #ifdef APP-NVUE
this.showF2 = true;
this.$nextTick(() => {
weexAnimation.transition(this.$refs['zp-n-f2'], {
styles: { transform: `translateY(${-this.superContentHeight}px)` },
duration: 0,
timingFunction: 'linear',
needLayout: true,
delay: 0
})
this.nF2Opacity = 1;
})
u.delay(() => {
weexAnimation.transition(this.$refs['zp-n-f2'], {
styles: { transform: 'translateY(0px)' },
duration: this.refresherF2Duration,
timingFunction: 'linear',
needLayout: true,
delay: 0
})
}, 10, 'f2GoDelay')
// #endif
},
// 处理退出二楼
_handleCloseF2() {
if (!this.showF2 || !this.refresherF2Enabled) return;
this.$emit('refresherF2Change', 'close');
if (!this.showRefresherF2) return;
// #ifndef APP-NVUE
this.f2Transform = `translateY(${-this.superContentHeight}px)`;
// #endif
// #ifdef APP-NVUE
weexAnimation.transition(this.$refs['zp-n-f2'], {
styles: { transform: `translateY(${-this.superContentHeight}px)` },
duration: this.refresherF2Duration,
timingFunction: 'linear',
needLayout: true,
delay: 0
})
// #endif
u.delay(() => {
this.showF2 = false;
this.nF2Opacity = 0;
}, this.refresherF2Duration, 'f2CloseDelay')
},
// 模拟用户手动触发下拉刷新
_doRefresherRefreshAnimate() {
this._cleanRefresherCompleteTimeout();
// 用户处理用户在短时间内多次调用reload的情况此时下拉刷新view不需要重复显示只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可
// #ifndef APP-NVUE
const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this
.customRefresherHeight === -1 && this.refresherThreshold === u.addUnit(80, this.unit);
if (doRefreshAnimateAfter) {
this.doRefreshAnimateAfter = true;
return;
}
// #endif
this.refresherRevealStackCount ++;
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`;
// #endif
// #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5
this.wxsPropType = 'begin' + u.getTime();
// #endif
this.moveDis = this.finalRefresherThreshold;
this.refresherStatus = Enum.Refresher.Loading;
this.isTouchmoving = true;
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
this._doRefresherLoad(false);
},
// 触发下拉刷新
_doRefresherLoad(isUserPullDown = true) {
this._onRefresh(false, isUserPullDown);
this.loading = true;
},
// #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5
// 获取处理后的moveDis
_getFinalRefresherMoveDis(moveDis) {
let diffDis = moveDis - this.oldCurrentMoveDis;
this.oldCurrentMoveDis = moveDis;
if (diffDis > 0) {
// 根据配置的下拉刷新用户手势位移与实际需要的位移比率计算最终的diffDis
diffDis = diffDis * this.finalRefresherPullRate;
if (this.currentDis > this.finalRefresherThreshold) {
diffDis = diffDis * (1 - this.finalRefresherOutRate);
}
}
// 控制diffDis过大的情况比如进入页面突然猛然下拉此时diffDis不应进行太大的偏移
diffDis = diffDis > 100 ? diffDis / 100 : diffDis;
this.currentDis += diffDis;
this.currentDis = Math.max(0, this.currentDis);
return this.currentDis;
},
// 判断touch手势是否要触发
_touchDisabled() {
const checkOldScrollTop = this.oldScrollTop > 5;
return this.loading || this.isRefresherInComplete || this.useChatRecordMode || !this.refresherEnabled || !this.useCustomRefresher ||(this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop);
},
// #endif
// 更新自定义下拉刷新view高度
_updateCustomRefresherHeight() {
this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => {
this.customRefresherHeight = res ? res[0].height : 0;
this.showCustomRefresher = this.customRefresherHeight > 0;
if (this.doRefreshAnimateAfter) {
this.doRefreshAnimateAfter = false;
this._doRefresherRefreshAnimate();
}
});
},
// emit pullingDown事件
_emitTouchmove(e) {
// #ifndef APP-NVUE
e.viewHeight = this.finalRefresherThreshold;
// #endif
e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0;
this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e);
this.oldPullingDistance = e.pullingDistance;
},
// 清除refresherCompleteTimeout
_cleanRefresherCompleteTimeout() {
this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout);
// #ifdef APP-NVUE
this._nRefresherEnd(false);
// #endif
},
// 清除refresherEndTimeout
_cleanRefresherEndTimeout() {
this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout);
},
}
}

View File

@ -1,541 +0,0 @@
// [z-paging]scroll相关模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
// #ifdef APP-NVUE
const weexDom = weex.requireModule('dom');
// #endif
export default {
props: {
// 使用页面滚动默认为否当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高但配置会略微繁琐
usePageScroll: {
type: Boolean,
default: u.gc('usePageScroll', false)
},
// 是否可以滚动使用内置scroll-view和nvue时有效默认为是
scrollable: {
type: Boolean,
default: u.gc('scrollable', true)
},
// 控制是否出现滚动条,默认为是
showScrollbar: {
type: Boolean,
default: u.gc('showScrollbar', true)
},
// 是否允许横向滚动,默认为否
scrollX: {
type: Boolean,
default: u.gc('scrollX', false)
},
// iOS设备上滚动到顶部时是否允许回弹效果默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯但是有吸顶view时滚动到顶部时可能出现抖动。
scrollToTopBounceEnabled: {
type: Boolean,
default: u.gc('scrollToTopBounceEnabled', false)
},
// iOS设备上滚动到底部时是否允许回弹效果默认为是。
scrollToBottomBounceEnabled: {
type: Boolean,
default: u.gc('scrollToBottomBounceEnabled', true)
},
// 在设置滚动条位置时使用动画过渡,默认为否
scrollWithAnimation: {
type: Boolean,
default: u.gc('scrollWithAnimation', false)
},
// 值应为某子元素idid不能以数字开头。设置哪个方向可滚动则在哪个方向滚动到该元素
scrollIntoView: {
type: String,
default: u.gc('scrollIntoView', '')
},
},
data() {
return {
scrollTop: 0,
oldScrollTop: 0,
scrollLeft: 0,
oldScrollLeft: 0,
scrollViewStyle: {},
scrollViewContainerStyle: {},
scrollViewInStyle: {},
pageScrollTop: -1,
scrollEnable: true,
privateScrollWithAnimation: -1,
cacheScrollNodeHeight: -1,
superContentHeight: 0,
}
},
watch: {
oldScrollTop(newVal) {
!this.usePageScroll && this._scrollTopChange(newVal,false);
},
pageScrollTop(newVal) {
this.usePageScroll && this._scrollTopChange(newVal,true);
},
usePageScroll: {
handler(newVal) {
this.loaded && this.autoHeight && this._setAutoHeight(!newVal);
// #ifdef H5
if (newVal) {
this.$nextTick(() => {
const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main;
if (mainScrollRef) {
mainScrollRef.style = {};
}
})
}
// #endif
},
immediate: true
},
finalScrollTop(newVal) {
this.renderPropScrollTop = newVal < 6 ? 0 : 10;
}
},
computed: {
finalScrollWithAnimation() {
if (this.privateScrollWithAnimation !== -1) {
return this.privateScrollWithAnimation === 1;
}
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;
},
// 当前是否是旧版webview
finalIsOldWebView() {
return this.isOldWebView && !this.usePageScroll;
},
// 当前scroll-view/list-view是否允许滚动
finalScrollable() {
return this.scrollable && !this.usePageScroll && this.scrollEnable
&& (this.refresherCompleteScrollable ? true : this.refresherStatus !== Enum.Refresher.Complete)
&& (this.refresherRefreshingScrollable ? true : this.refresherStatus !== Enum.Refresher.Loading);
}
},
methods: {
// 滚动到顶部animate为是否展示滚动动画默认为是
scrollToTop(animate, checkReverse = true) {
// 如果是聊天记录模式并且列表倒置了,则滚动到顶部实际上是滚动到底部
if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) {
this.scrollToBottom(animate, false);
return;
}
this.$nextTick(() => {
this._scrollToTop(animate, false);
// #ifdef APP-NVUE
if (this.nvueFastScroll && animate) {
u.delay(() => {
this._scrollToTop(false, false);
});
}
// #endif
})
},
// 滚动到底部animate为是否展示滚动动画默认为是
scrollToBottom(animate, checkReverse = true) {
// 如果是聊天记录模式并且列表倒置了,则滚动到底部实际上是滚动到顶部
if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) {
this.scrollToTop(animate, false);
return;
}
this.$nextTick(() => {
this._scrollToBottom(animate);
// #ifdef APP-NVUE
if (this.nvueFastScroll && animate) {
u.delay(() => {
this._scrollToBottom(false);
});
}
// #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);
})
},
// y轴滚动到指定位置(vue中有效)。y为与顶部的距离单位为pxoffset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollToY(y, offset, animate) {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this._scrollToY(y, offset, animate);
})
},
// x轴滚动到指定位置(非页面滚动且在vue中有效)。x为与左侧的距离单位为pxoffset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollToX(x, offset, animate) {
this.scrollLeft = this.oldScrollLeft;
this.$nextTick(() => {
this._scrollToX(x, offset, animate);
})
},
// 滚动到指定view(nvue中和虚拟列表中有效)。index为需要滚动的view的index(第几个从0开始)offset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollIntoViewByIndex(index, offset, animate) {
if (index >= this.realTotalData.length) {
u.consoleErr('当前滚动的index超出已渲染列表长度请先通过refreshToPage加载到对应index页并等待渲染成功后再调用此方法');
return;
}
this.$nextTick(() => {
// #ifdef APP-NVUE
// 在nvue中根据index获取对应节点信息并滚动到此节点位置
this._scrollIntoView(index, offset, animate);
// #endif
// #ifndef APP-NVUE
if (this.finalUseVirtualList) {
const isCellFixed = this.cellHeightMode === Enum.CellHeightMode.Fixed;
u.delay(() => {
if (this.finalUseVirtualList) {
// 虚拟列表 + 每个cell高度完全相同模式下此时滚动到对应index的cell就是滚动到scrollTop = cellHeight * index的位置
// 虚拟列表 + 高度是动态非固定的模式下此时滚动到对应index的cell就是滚动到scrollTop = 缓存的cell高度数组中第index个的lastTotalHeight的位置
const scrollTop = isCellFixed ? this.virtualCellHeight * index : this.virtualHeightCacheList[index].lastTotalHeight;
this.scrollToY(scrollTop, offset, animate);
}
}, isCellFixed ? 0 : 100)
}
// #endif
})
},
// 滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#"offset为偏移量单位为pxanimate为是否展示滚动动画默认为否
scrollIntoViewByView(view, offset, animate) {
this._scrollIntoView(view, offset, animate);
},
// 当使用页面滚动并且自定义下拉刷新时请在页面的onPageScroll中调用此方法告知z-paging当前的pageScrollTop否则会导致在任意位置都可以下拉刷新
updatePageScrollTop(value) {
this.pageScrollTop = value;
},
// 当使用页面滚动并且设置了slot="top"时默认初次加载会自动获取其高度并使内部容器下移当slot="top"的view高度动态改变时在其高度需要更新时调用此方法
updatePageScrollTopHeight() {
this._updatePageScrollTopOrBottomHeight('top');
},
// 当使用页面滚动并且设置了slot="bottom"时默认初次加载会自动获取其高度并使内部容器下移当slot="bottom"的view高度动态改变时在其高度需要更新时调用此方法
updatePageScrollBottomHeight() {
this._updatePageScrollTopOrBottomHeight('bottom');
},
// 更新slot="left"和slot="right"宽度当slot="left"或slot="right"宽度动态改变时调用
updateLeftAndRightWidth() {
if (!this.finalIsOldWebView) return;
this.$nextTick(() => this._updateLeftAndRightWidth(this.scrollViewContainerStyle, 'zp-page'));
},
// 更新z-paging内置scroll-view的scrollTop
updateScrollViewScrollTop(scrollTop, animate = true) {
this._updatePrivateScrollWithAnimation(animate);
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this.scrollTop = scrollTop;
this.oldScrollTop = this.scrollTop;
});
},
// 当滚动到顶部时
_onScrollToUpper() {
this.$emit('scrolltoupper');
this.$emit('scrollTopChange', 0);
this.$nextTick(() => {
this.oldScrollTop = 0;
})
},
// 当滚动到底部时
_onScrollToLower(e) {
(!e.detail || !e.detail.direction || e.detail.direction === 'bottom')
&& this.toBottomLoadingMoreEnabled
&& this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
},
// 滚动到顶部
_scrollToTop(animate = true, isPrivate = true) {
// #ifdef APP-NVUE
// 在nvue中需要通过weex.scrollToElement滚动到顶部此时在顶部插入了一个view使得滚动到这个view位置
const el = this.$refs['zp-n-list-top-tag'];
if (this.usePageScroll) {
this._getNodeClientRect('zp-page-scroll-top', false).then(node => {
const nodeHeight = node ? node[0].height : 0;
weexDom.scrollToElement(el, {
offset: -nodeHeight,
animated: animate
});
});
} else {
if (!this.isIos && this.nvueListIs === 'scroller') {
this._getNodeClientRect('zp-n-refresh-container', false).then(node => {
const nodeHeight = node ? node[0].height : 0;
weexDom.scrollToElement(el, {
offset: -nodeHeight,
animated: animate
});
});
} else {
weexDom.scrollToElement(el, {
offset: 0,
animated: animate
});
}
}
return;
// #endif
if (this.usePageScroll) {
this.$nextTick(() => {
uni.pageScrollTo({
scrollTop: 0,
duration: animate ? 100 : 0,
});
});
return;
}
this._updatePrivateScrollWithAnimation(animate);
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this.scrollTop = 0;
this.oldScrollTop = this.scrollTop;
});
},
// 滚动到底部
async _scrollToBottom(animate = true) {
// #ifdef APP-NVUE
// 在nvue中需要通过weex.scrollToElement滚动到顶部此时在底部插入了一个view使得滚动到这个view位置
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._updatePrivateScrollWithAnimation(animate);
const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container');
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0;
const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0;
if (pagingContainerH > scrollViewH) {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight;
this.oldScrollTop = this.scrollTop;
});
}
} catch (e) {}
},
// 滚动到指定view
_scrollIntoView(sel, offset = 0, animate = false, finishCallback) {
try {
this.scrollTop = this.oldScrollTop;
this.$nextTick(() => {
// #ifdef APP-NVUE
const refs = this.$parent.$refs;
if (!refs) return;
const dataType = Object.prototype.toString.call(sel);
let el = null;
if (dataType === '[object Number]') {
const els = refs[`z-paging-${sel}`];
el = els ? els[0] : null;
} else if (dataType === '[object Array]') {
el = sel[0];
} else {
el = sel;
}
if (el) {
weexDom.scrollToElement(el, {
offset: -offset,
animated: animate
});
} else {
u.consoleErr('在nvue中滚动到指定位置cell必须设置 :ref="`z-paging-${index}`"');
}
return;
// #endif
this._getNodeClientRect('#' + sel.replace('#', ''), this.$parent).then((node) => {
if (node) {
let nodeTop = node[0].top;
this._scrollIntoViewByNodeTop(nodeTop, offset, animate);
finishCallback && finishCallback();
}
});
});
} catch (e) {}
},
// 通过nodeTop滚动到指定view
_scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) {
// 如果是聊天记录模式并且列表倒置了此时nodeTop需要等于scroll-view高度 - nodeTop
if (this.isChatRecordModeAndInversion) {
this._getNodeClientRect('.zp-scroll-view').then(sNode => {
if (sNode) {
this._scrollToY(sNode[0].height - nodeTop, offset, animate, true);
}
})
} else {
this._scrollToY(nodeTop, offset, animate, true);
}
},
// y轴滚动到指定位置
_scrollToY(y, offset = 0, animate = false, addScrollTop = false) {
this._updatePrivateScrollWithAnimation(animate);
u.delay(() => {
if (this.usePageScroll) {
if (addScrollTop && this.pageScrollTop !== -1) {
y += this.pageScrollTop;
}
const scrollTop = y - offset;
uni.pageScrollTo({
scrollTop,
duration: animate ? 100 : 0
});
} else {
if (addScrollTop) {
y += this.oldScrollTop;
}
this.scrollTop = y - offset;
}
}, 10)
},
// x轴滚动到指定位置
_scrollToX(x, offset = 0, animate = false) {
this._updatePrivateScrollWithAnimation(animate);
u.delay(() => {
if (!this.usePageScroll) {
this.scrollLeft = x - offset;
} else {
u.consoleErr('使用页面滚动时不支持scrollToX');
}
}, 10)
},
// scroll-view滚动中
_scroll(e) {
this.$emit('scroll', e);
const { scrollTop, scrollLeft } = e.detail;
// #ifndef APP-NVUE
this.finalUseVirtualList && this._updateVirtualScroll(scrollTop, this.oldScrollTop - scrollTop);
// #endif
this.oldScrollTop = scrollTop;
this.oldScrollLeft = scrollLeft;
// 滚动区域内容的总高度 - 当前滚动的scrollTop = 当前滚动区域的顶部与内容底部的距离
const scrollDiff = e.detail.scrollHeight - this.oldScrollTop;
// 在非ios平台滚动中再次验证一下是否滚动到了底部。因为在一些安卓设备中有概率滚动到底部不触发@scrolltolower事件因此添加双重检测逻辑
!this.isIos && this._checkScrolledToBottom(scrollDiff);
},
// 更新内置的scroll-view是否启用滚动动画
_updatePrivateScrollWithAnimation(animate) {
this.privateScrollWithAnimation = animate ? 1 : 0;
u.delay(() => this.$nextTick(() => {
// 在滚动结束后将滚动动画状态设置回初始状态
this.privateScrollWithAnimation = -1;
}), 100, 'updateScrollWithAnimationDelay')
},
// 检测scrollView是否要铺满屏幕
_doCheckScrollViewShouldFullHeight(totalData) {
if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) {
// #ifndef APP-NVUE
this.$nextTick(() => {
this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => {
this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode)
});
})
// #endif
// #ifdef APP-NVUE
this._preCheckShowNoMoreInside(totalData)
// #endif
} else {
this._preCheckShowNoMoreInside(totalData)
}
},
// 检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时默认z-paging需要铺满全屏避免数据过少时内部的empty-view无法正确展示)
async _checkScrollViewShouldFullHeight(callback) {
try {
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content');
if (!scrollViewNode || !pagingContainerNode) return;
const scrollViewHeight = pagingContainerNode[0].height;
const scrollViewTop = scrollViewNode[0].top;
if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) {
this._setAutoHeight(true, scrollViewNode);
callback(scrollViewNode, pagingContainerNode);
} else {
this._setAutoHeight(false);
callback(null, null);
}
} catch (e) {
callback(null, null);
}
},
// 更新缓存中z-paging整个内容容器高度
async _updateCachedSuperContentHeight() {
const superContentNode = await this._getNodeClientRect('.z-paging-content');
if (superContentNode) {
this.superContentHeight = superContentNode[0].height;
}
},
// scrollTop改变时触发
_scrollTopChange(newVal, isPageScrollTop){
this.$emit('scrollTopChange', newVal);
this.$emit('update:scrollTop', newVal);
this._checkShouldShowBackToTop(newVal);
// 之前在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常因此判断scrollTop在105之内都允许下拉刷新但此方案会导致某些情况例如滚动到距离顶部10px处下拉抖动因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案
// const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : (newVal > 105 ? 106 : (newVal > 5 ? 6 : 0));
const scrollTop = newVal > 5 ? 6 : 0;
if (isPageScrollTop && this.wxsPageScrollTop !== scrollTop) {
this.wxsPageScrollTop = scrollTop;
} else if (!isPageScrollTop && this.wxsScrollTop !== scrollTop) {
this.wxsScrollTop = scrollTop;
if (scrollTop > 6) {
this.scrollEnable = true;
}
}
},
// 更新使用页面滚动时slot="top"或"bottom"插入view的高度
_updatePageScrollTopOrBottomHeight(type) {
// #ifndef APP-NVUE
if (!this.usePageScroll) return;
// #endif
this._doCheckScrollViewShouldFullHeight(this.realTotalData);
const node = `.zp-page-${type}`;
const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`;
let safeAreaInsetBottomAdd = this.safeAreaInsetBottom;
this.$nextTick(() => {
let delayTime = 0;
// #ifdef MP-BAIDU || APP-NVUE
delayTime = 50;
// #endif
u.delay(() => {
this._getNodeClientRect(node).then((res) => {
if (res) {
let pageScrollNodeHeight = res[0].height;
if (type === 'bottom') {
if (safeAreaInsetBottomAdd) {
pageScrollNodeHeight += this.safeAreaBottom;
}
} else {
this.cacheTopHeight = pageScrollNodeHeight;
}
this.$set(this.scrollViewStyle, marginText, `${pageScrollNodeHeight}px`);
} else if (safeAreaInsetBottomAdd) {
this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`);
}
});
}, delayTime)
})
},
}
}

View File

@ -1,549 +0,0 @@
// [z-paging]虚拟列表模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
export default {
props: {
// 是否使用虚拟列表,默认为否
useVirtualList: {
type: Boolean,
default: u.gc('useVirtualList', false)
},
// 在使用虚拟列表时,是否使用兼容模式,默认为否
useCompatibilityMode: {
type: Boolean,
default: u.gc('useCompatibilityMode', false)
},
// 使用兼容模式时传递的附加数据
extraData: {
type: Object,
default: u.gc('extraData', {})
},
// 是否在z-paging内部循环渲染列表(内置列表)默认为否。若use-virtual-list为true则此项恒为true
useInnerList: {
type: Boolean,
default: u.gc('useInnerList', false)
},
// 强制关闭inner-list默认为false如果为true将强制关闭innerList适用于开启了虚拟列表后需要强制关闭inner-list的情况
forceCloseInnerList: {
type: Boolean,
default: u.gc('forceCloseInnerList', false)
},
// 内置列表cell的key名称仅nvue有效在nvue中开启use-inner-list时必须填此项
cellKeyName: {
type: String,
default: u.gc('cellKeyName', '')
},
// innerList样式
innerListStyle: {
type: Object,
default: u.gc('innerListStyle', {})
},
// innerCell样式
innerCellStyle: {
type: Object,
default: u.gc('innerCellStyle', {})
},
// 预加载的列表可视范围(列表高度)页数默认为12即预加载当前页及上下各12页的cell。此数值越大则虚拟列表中加载的dom越多内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题
preloadPage: {
type: [Number, String],
default: u.gc('preloadPage', 12),
validator: (value) => {
if (value <= 0) u.consoleErr('preload-page必须大于0');
return value > 0;
}
},
// 虚拟列表cell高度模式默认为fixed也就是每个cell高度完全相同将以第一个cell高度为准进行计算。可选值【dynamic】即代表高度是动态非固定的【dynamic】性能低于【fixed】。
cellHeightMode: {
type: String,
default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed)
},
// 固定的cell高度cellHeightMode=fixed才有效若设置了值则不计算第一个cell高度而使用设置的cell高度
fixedCellHeight: {
type: [Number, String],
default: u.gc('fixedCellHeight', 0)
},
// 虚拟列表列数默认为1。常用于每行有多列的情况例如每行有2列数据需要将此值设置为2
virtualListCol: {
type: [Number, String],
default: u.gc('virtualListCol', 1)
},
// 虚拟列表scroll取样帧率默认为80过低容易出现白屏问题过高容易出现卡顿问题
virtualScrollFps: {
type: [Number, String],
default: u.gc('virtualScrollFps', 80)
},
// 虚拟列表cell id的前缀适用于一个页面有多个虚拟列表的情况用以区分不同虚拟列表cell的id注意请勿传数字或以数字开头的字符串。如设置为list1则cell的id应为list1-zp-id-${item.zp_index}
virtualCellIdPrefix: {
type: String,
default: u.gc('virtualCellIdPrefix', '')
},
},
data() {
return {
virtualListKey: u.getInstanceId(),
virtualPageHeight: 0,
virtualCellHeight: 0,
virtualScrollTimeStamp: 0,
virtualList: [],
virtualPlaceholderTopHeight: 0,
virtualPlaceholderBottomHeight: 0,
virtualTopRangeIndex: 0,
virtualBottomRangeIndex: 0,
lastVirtualTopRangeIndex: 0,
lastVirtualBottomRangeIndex: 0,
virtualItemInsertedCount: 0,
virtualHeightCacheList: [],
getCellHeightRetryCount: {
fixed: 0,
dynamic: 0
},
pagingOrgTop: -1,
updateVirtualListFromDataChange: false
}
},
watch: {
// 监听总数据的改变,刷新虚拟列表布局
realTotalData() {
this.updateVirtualListRender();
},
// 监听虚拟列表渲染数组的改变并emit
virtualList(newVal){
this.$emit('update:virtualList', newVal);
this.$emit('virtualListChange', newVal);
},
// 监听虚拟列表顶部占位高度改变并emit
virtualPlaceholderTopHeight(newVal) {
this.$emit('virtualTopHeightChange', newVal);
}
},
computed: {
virtualCellIndexKey() {
return c.listCellIndexKey;
},
finalUseVirtualList() {
if (this.useVirtualList && this.usePageScroll){
u.consoleErr('使用页面滚动时,开启虚拟列表无效!');
}
return this.useVirtualList && !this.usePageScroll;
},
finalUseInnerList() {
return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList);
},
finalCellKeyName() {
// #ifdef APP-NVUE
if (this.finalUseVirtualList && !this.cellKeyName.length){
u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name否则将可能导致列表渲染错误');
}
// #endif
return this.cellKeyName;
},
finalVirtualPageHeight(){
return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight;
},
finalFixedCellHeight() {
return u.convertToPx(this.fixedCellHeight);
},
fianlVirtualCellIdPrefix() {
const prefix = this.virtualCellIdPrefix ? this.virtualCellIdPrefix + '-' : '';
return prefix + 'zp-id';
},
finalPlaceholderTopHeightStyle() {
// #ifdef VUE2
return { transform: this.virtualPlaceholderTopHeight > 0 ? `translateY(${this.virtualPlaceholderTopHeight}px)` : 'none' };
// #endif
return {};
},
virtualRangePageHeight(){
return this.finalVirtualPageHeight * this.preloadPage;
},
virtualScrollDisTimeStamp() {
return 1000 / this.virtualScrollFps;
}
},
methods: {
// 在使用动态高度虚拟列表时若在列表数组中需要插入某个item需要调用此方法item:需要插入的itemindex:插入的cell位置若index为2则插入的item在原list的index=1之后index从0开始
doInsertVirtualListItem(item, index) {
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
this.realTotalData.splice(index, 0, item);
// #ifdef VUE3
this.realTotalData = [...this.realTotalData];
// #endif
this.virtualItemInsertedCount ++;
if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
item = { item };
}
const cellIndexKey = this.virtualCellIndexKey;
item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`;
item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
this.$nextTick(async () => {
let retryCount = 0;
while (retryCount <= 10) {
await u.wait(c.delayTime);
const cellNode = await this._getVirtualCellNodeByIndex(item[cellIndexKey]);
// 如果获取当前cell的节点信息失败则重试不超过10次
if (!cellNode) {
retryCount ++;
continue;
}
const currentHeight = cellNode ? cellNode[0].height : 0;
const lastHeightCache = this.virtualHeightCacheList[index - 1];
const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
// 在缓存的cell高度数组中插入此cell高度信息
this.virtualHeightCacheList.splice(index, 0, {
height: currentHeight,
lastTotalHeight,
totalHeight: lastTotalHeight + currentHeight
});
// 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell的高度
for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
const thisNode = this.virtualHeightCacheList[i];
thisNode.lastTotalHeight += currentHeight;
thisNode.totalHeight += currentHeight;
}
this._updateVirtualScroll(this.oldScrollTop);
break;
}
})
},
// 在使用动态高度虚拟列表时手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变后调用)index:需要更新的cell在列表中的位置从0开始
didUpdateVirtualListCell(index) {
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
const currentNode = this.virtualHeightCacheList[index];
this.$nextTick(() => {
this._getVirtualCellNodeByIndex(index).then(cellNode => {
// 更新当前cell的高度
const cellNodeHeight = cellNode ? cellNode[0].height : 0;
const heightDis = cellNodeHeight - currentNode.height;
currentNode.height = cellNodeHeight;
currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight;
// 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell变化的高度
for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
const thisNode = this.virtualHeightCacheList[i];
thisNode.totalHeight += heightDis;
thisNode.lastTotalHeight += heightDis;
}
});
})
},
// 在使用动态高度虚拟列表时若删除了列表数组中的某个item需要调用此方法以更新高度缓存数组index:删除的cell在列表中的位置从0开始
didDeleteVirtualListCell(index) {
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
const currentNode = this.virtualHeightCacheList[index];
// 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要减去当前cell的高度
for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
const thisNode = this.virtualHeightCacheList[i];
thisNode.totalHeight -= currentNode.height;
thisNode.lastTotalHeight -= currentNode.height;
}
// 将当前cell的高度信息从高度缓存数组中删除
this.virtualHeightCacheList.splice(index, 1);
},
// 手动触发虚拟列表渲染更新,可用于解决例如修改了虚拟列表数组中元素,但展示未更新的情况
updateVirtualListRender() {
// #ifndef APP-NVUE
if (this.finalUseVirtualList) {
this.updateVirtualListFromDataChange = true;
this.$nextTick(() => {
this.getCellHeightRetryCount.fixed = 0;
if (this.realTotalData.length) {
this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight()
} else {
this._resetDynamicListState(!this.isUserPullDown);
}
this._updateVirtualScroll(this.oldScrollTop);
})
}
// #endif
},
// 初始化虚拟列表
_virtualListInit() {
this.$nextTick(() => {
u.delay(() => {
// 获取虚拟列表滚动区域的高度
this._getNodeClientRect('.zp-scroll-view').then(node => {
if (node) {
this.pagingOrgTop = node[0].top;
this.virtualPageHeight = node[0].height;
}
});
});
})
},
// cellHeightMode为fixed时获取第一个cell高度
_updateFixedCellHeight() {
if (!this.finalFixedCellHeight) {
this.$nextTick(() => {
u.delay(() => {
this._getVirtualCellNodeByIndex(0).then(cellNode => {
if (!cellNode) {
if (this.getCellHeightRetryCount.fixed > 10) return;
this.getCellHeightRetryCount.fixed ++;
// 如果获取第一个cell的节点信息失败则重试不超过10次
this._updateFixedCellHeight();
} else {
this.virtualCellHeight = cellNode[0].height;
this._updateVirtualScroll(this.oldScrollTop);
}
});
}, c.delayTime, 'updateFixedCellHeightDelay');
})
} else {
this.virtualCellHeight = this.finalFixedCellHeight;
}
},
// cellHeightMode为dynamic时获取每个cell高度
_updateDynamicCellHeight(list, dataFrom = 'bottom') {
const dataFromTop = dataFrom === 'top';
const heightCacheList = this.virtualHeightCacheList;
const currentCacheList = dataFromTop ? [] : heightCacheList;
let listTotalHeight = 0;
this.$nextTick(() => {
u.delay(async () => {
for (let i = 0; i < list.length; i++) {
const cellNode = await this._getVirtualCellNodeByIndex(list[i][this.virtualCellIndexKey]);
const currentHeight = cellNode ? cellNode[0].height : 0;
if (!cellNode) {
if (this.getCellHeightRetryCount.dynamic <= 10) {
heightCacheList.splice(heightCacheList.length - i, i);
this.getCellHeightRetryCount.dynamic ++;
// 如果获取当前cell的节点信息失败则重试不超过10次
this._updateDynamicCellHeight(list, dataFrom);
}
return;
}
const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null;
const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
// 缓存当前cell的高度信息height-当前cell高度lastTotalHeight-前面所有cell的高度总和totalHeight-包含当前cell的所有高度总和
currentCacheList.push({
height: currentHeight,
lastTotalHeight,
totalHeight: lastTotalHeight + currentHeight
});
if (dataFromTop) {
listTotalHeight += currentHeight;
}
}
// 如果数据是从顶部拼接的
if (dataFromTop && list.length) {
for (let i = 0; i < heightCacheList.length; i++) {
// 更新之前所有项的缓存高度需要加上此次插入的所有cell高度之和因为是从顶部插入的cell
const heightCacheItem = heightCacheList[i];
heightCacheItem.lastTotalHeight += listTotalHeight;
heightCacheItem.totalHeight += listTotalHeight;
}
this.virtualHeightCacheList = currentCacheList.concat(heightCacheList);
}
this._updateVirtualScroll(this.oldScrollTop);
}, c.delayTime, 'updateDynamicCellHeightDelay')
})
},
// 设置cellItem的index
_setCellIndex(list, dataFrom = 'bottom') {
let currentItemIndex = 0;
const cellIndexKey = this.virtualCellIndexKey;
dataFrom === 'bottom' && ([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState();
if (this.totalData.length && this.queryFrom !== Enum.QueryFrom.Refresh) {
if (dataFrom === 'bottom') {
currentItemIndex = this.realTotalData.length;
const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null;
if (lastItem && lastItem[cellIndexKey] !== undefined) {
currentItemIndex = lastItem[cellIndexKey] + 1;
}
} else if (dataFrom === 'top') {
const firstItem = this.realTotalData.length ? this.realTotalData[0] : null;
if (firstItem && firstItem[cellIndexKey] !== undefined) {
currentItemIndex = firstItem[cellIndexKey] - list.length;
}
}
} else {
this._resetDynamicListState();
}
for (let i = 0; i < list.length; i++) {
let item = list[i];
if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
item = { item };
}
if (item[c.listCellIndexUniqueKey]) {
item = u.deepCopy(item);
}
item[cellIndexKey] = currentItemIndex + i;
item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
list[i] = item;
}
this.getCellHeightRetryCount.dynamic = 0;
this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom);
},
// 更新scroll滚动虚拟列表滚动时触发
_updateVirtualScroll(scrollTop, scrollDiff = 0) {
const currentTimeStamp = u.getTime();
scrollTop === 0 && this._resetTopRange();
if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) {
return;
}
this.virtualScrollTimeStamp = currentTimeStamp;
let scrollIndex = 0;
const cellHeightMode = this.cellHeightMode;
if (cellHeightMode === Enum.CellHeightMode.Fixed) {
// 如果是固定高度的虚拟列表
// 计算当前滚动到的cell的index = scrollTop / 虚拟列表cell的固定高度
scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0;
// 更新顶部和底部占位view的高度为兼容考虑顶部采用transformY的方式占位)
this._updateFixedTopRangeIndex(scrollIndex);
this._updateFixedBottomRangeIndex(scrollIndex);
} else if(cellHeightMode === Enum.CellHeightMode.Dynamic) {
// 如果是不固定高度的虚拟列表
// 当前滚动的方向
const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom';
// 视图区域的高度
const rangePageHeight = this.virtualRangePageHeight;
// 顶部视图区域外的高度(顶部不需要渲染而是需要占位部分的高度)
const topRangePageOffset = scrollTop - rangePageHeight;
// 底部视图区域外的高度(底部不需要渲染而是需要占位部分的高度)
const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight;
let virtualBottomRangeIndex = 0;
let virtualPlaceholderBottomHeight = 0;
let reachedLimitBottom = false;
const heightCacheList = this.virtualHeightCacheList;
const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null;
let startTopRangeIndex = this.virtualTopRangeIndex;
// 如果是向底部滚动顶部占位的高度不断增大顶部的实际渲染cell数量不断减少
if (scrollDirection === 'bottom') {
// 从顶部视图边缘的cell的位置开始向后查找
for (let i = startTopRangeIndex; i < heightCacheList.length; i++){
const heightCacheItem = heightCacheList[i];
// 如果查找到某个cell对应的totalHeight大于顶部视图区域外的高度则此cell为顶部视图边缘的cell
if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) {
// 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找
this.virtualTopRangeIndex = i;
this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight;
break;
}
}
} else {
// 如果是向顶部滚动顶部占位的高度不断减少顶部的实际渲染cell数量不断增加
let topRangeMatched = false;
// 从顶部视图边缘的cell的位置开始向前查找
for (let i = startTopRangeIndex; i >= 0; i--){
const heightCacheItem = heightCacheList[i];
// 如果查找到某个cell对应的totalHeight小于顶部视图区域外的高度则此cell为顶部视图边缘的cell
if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) {
// 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找
this.virtualTopRangeIndex = i;
this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight;
topRangeMatched = true;
break;
}
}
// 如果查找不到则认为顶部占位高度为0了顶部cell不需要继续复用重置topRangeIndex和placeholderTopHeight
!topRangeMatched && this._resetTopRange();
}
// 从顶部视图边缘的cell的位置开始向后查找
for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){
const heightCacheItem = heightCacheList[i];
// 如果查找到某个cell对应的totalHeight大于底部视图区域外的高度则此cell为底部视图边缘的cell
if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) {
// 记录底部视图边缘cell的index并更新底部占位区域的高度并停止继续查找
virtualBottomRangeIndex = i;
virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight;
reachedLimitBottom = true;
break;
}
}
if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) {
this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize;
this.virtualPlaceholderBottomHeight = 0;
} else {
this.virtualBottomRangeIndex = virtualBottomRangeIndex;
this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight;
}
this._updateVirtualList();
}
},
// 更新fixedCell模式下topRangeIndex&placeholderTopHeight
_updateFixedTopRangeIndex(scrollIndex) {
let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage;
virtualTopRangeIndex *= this.virtualListCol;
virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex);
this.virtualTopRangeIndex = virtualTopRangeIndex;
this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight;
},
// 更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight
_updateFixedBottomRangeIndex(scrollIndex) {
let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1);
virtualBottomRangeIndex *= this.virtualListCol;
virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex);
this.virtualBottomRangeIndex = virtualBottomRangeIndex;
this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol;
this._updateVirtualList();
},
// 更新virtualList
_updateVirtualList() {
const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex);
if (shouldUpdateList) {
this.updateVirtualListFromDataChange = false;
this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex;
this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex;
this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1);
}
},
// 重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态
_resetDynamicListState(resetVirtualList = false) {
this.virtualHeightCacheList = [];
if (resetVirtualList) {
this.virtualList = [];
}
this.virtualTopRangeIndex = 0;
this.virtualPlaceholderTopHeight = 0;
},
// 重置topRangeIndex和placeholderTopHeight
_resetTopRange() {
this.virtualTopRangeIndex = 0;
this.virtualPlaceholderTopHeight = 0;
this._updateVirtualList();
},
// 检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题)
_checkVirtualListScroll() {
if (this.finalUseVirtualList) {
this.$nextTick(() => {
this._getNodeClientRect('.zp-paging-touch-view').then(node => {
const currentTop = node ? node[0].top : 0;
if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) {
this._updateVirtualScroll(0);
}
});
})
}
},
// 获取对应index的虚拟列表cell节点信息
_getVirtualCellNodeByIndex(index) {
let inParent = false;
// 在vue3+(微信小程序或QQ小程序)中使用非内置列表写法时若z-paging在swiper-item内存在无法获取slot插入的cell高度的问题
// 通过uni.createSelectorQuery().in(this.$parent)来解决此问题
// #ifdef VUE3
// #ifdef MP-WEIXIN || MP-QQ
if (this.forceCloseInnerList) {
inParent = true;
}
// #endif
// #endif
return this._getNodeClientRect(`#${this.fianlVirtualCellIdPrefix}-${index}`, this.finalUseInnerList, false, inParent);
},
// 处理使用内置列表时点击了cell事件
_innerCellClick(item, index) {
this.$emit('innerCellClick', item, index);
}
}
}

View File

@ -1,19 +0,0 @@
// [z-paging]常量
export default {
// 当前版本号
version: '2.8.5',
// 延迟操作的通用时间
delayTime: 100,
// 请求失败时候全局emit使用的key
errorUpdateKey: 'z-paging-error-emit',
// 全局emit complete的key
completeUpdateKey: 'z-paging-complete-emit',
// z-paging缓存的前缀key
cachePrefixKey: 'z-paging-cache',
// 虚拟列表中列表index的key
listCellIndexKey: 'zp_index',
// 虚拟列表中列表的唯一key
listCellIndexUniqueKey: 'zp_unique_index'
}

View File

@ -1,45 +0,0 @@
// [z-paging]枚举
export default {
// 当前加载类型 refresher:下拉刷新 load-more:上拉加载更多
LoadingType: {
Refresher: 'refresher',
LoadMore: 'load-more'
},
// 下拉刷新状态 default:默认状态 release-to-refresh:松手立即刷新 loading:刷新中 complete:刷新结束 go-f2:松手进入二楼
Refresher: {
Default: 'default',
ReleaseToRefresh: 'release-to-refresh',
Loading: 'loading',
Complete: 'complete',
GoF2: 'go-f2'
},
// 底部加载更多状态 default:默认状态 loading:加载中 no-more:没有更多数据 fail:加载失败
More: {
Default: 'default',
Loading: 'loading',
NoMore: 'no-more',
Fail: 'fail'
},
// @query触发来源 user-pull-down:用户主动下拉刷新 reload:通过reload触发 refresh:通过refresh触发 load-more:通过滚动到底部加载更多或点击底部加载更多触发
QueryFrom: {
UserPullDown: 'user-pull-down',
Reload: 'reload',
Refresh: 'refresh',
LoadMore: 'load-more'
},
// 虚拟列表cell高度模式
CellHeightMode: {
// 固定高度
Fixed: 'fixed',
// 动态高度
Dynamic: 'dynamic'
},
// 列表缓存模式
CacheMode: {
// 默认模式,只会缓存一次
Default: 'default',
// 总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存
Always: 'always'
}
}

View File

@ -1,97 +0,0 @@
// [z-paging]拦截器
const queryKey = 'Query';
const fetchParamsKey = 'FetchParams';
const fetchResultKey = 'FetchResult';
const language2LocalKey = 'Language2Local';
// 拦截&处理@query事件
function handleQuery(callback) {
_addHandleByKey(queryKey, callback);
return this;
}
// 拦截&处理@query事件(私有,请勿调用)
function _handleQuery(pageNo, pageSize, from, lastItem) {
const callback = _getHandleByKey(queryKey);
return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from];
}
// 拦截&处理:fetch参数
function handleFetchParams(callback) {
_addHandleByKey(fetchParamsKey, callback);
return this;
}
// 拦截&处理:fetch参数(私有,请勿调用)
function _handleFetchParams(parmas, extraParams) {
const callback = _getHandleByKey(fetchParamsKey);
return callback ? callback(parmas, extraParams || {}) : { pageNo: parmas.pageNo, pageSize: parmas.pageSize, ...(extraParams || {}) };
}
// 拦截&处理:fetch结果
function handleFetchResult(callback) {
_addHandleByKey(fetchResultKey, callback);
return this;
}
// 拦截&处理:fetch结果(私有,请勿调用)
function _handleFetchResult(result, paging, params) {
const callback = _getHandleByKey(fetchResultKey);
callback && callback(result, paging, params);
return callback ? true : false;
}
// 拦截&处理系统language转i18n local
function handleLanguage2Local(callback) {
_addHandleByKey(language2LocalKey, callback);
return this;
}
// 拦截&处理系统language转i18n local(私有,请勿调用)
function _handleLanguage2Local(language, local) {
const callback = _getHandleByKey(language2LocalKey);
return callback ? callback(language, local) : local;
}
// 获取当前app对象
function _getApp(){
// #ifndef APP-NVUE
return getApp();
// #endif
// #ifdef APP-NVUE
return getApp({ allowDefault: true });
// #endif
}
// 是否可以访问globalData
function _hasGlobalData() {
return _getApp() && _getApp().globalData;
}
// 添加处理函数
function _addHandleByKey(key, callback) {
try {
setTimeout(function() {
if (_hasGlobalData()) {
_getApp().globalData[`zp_handle${key}Callback`] = callback;
}
}, 1);
} catch (_) {}
}
// 获取处理回调函数
function _getHandleByKey(key) {
return _hasGlobalData() ? _getApp().globalData[`zp_handle${key}Callback`] : null;
}
export default {
handleQuery,
_handleQuery,
handleFetchParams,
_handleFetchParams,
handleFetchResult,
_handleFetchResult,
handleLanguage2Local,
_handleLanguage2Local
};

View File

@ -1,516 +0,0 @@
// [z-paging]核心js
import zStatic from './z-paging-static'
import c from './z-paging-constant'
import u from './z-paging-utils'
import zPagingRefresh from '../components/z-paging-refresh'
import zPagingLoadMore from '../components/z-paging-load-more'
import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view'
// modules
import commonLayoutModule from './modules/common-layout'
import dataHandleModule from './modules/data-handle'
import i18nModule from './modules/i18n'
import nvueModule from './modules/nvue'
import emptyModule from './modules/empty'
import refresherModule from './modules/refresher'
import loadMoreModule from './modules/load-more'
import loadingModule from './modules/loading'
import chatRecordModerModule from './modules/chat-record-mode'
import scrollerModule from './modules/scroller'
import backToTopModule from './modules/back-to-top'
import virtualListModule from './modules/virtual-list'
import Enum from './z-paging-enum'
const systemInfo = uni.getSystemInfoSync();
export default {
name: "z-paging",
components: {
zPagingRefresh,
zPagingLoadMore,
zPagingEmptyView
},
mixins: [
commonLayoutModule,
dataHandleModule,
i18nModule,
nvueModule,
emptyModule,
refresherModule,
loadMoreModule,
loadingModule,
chatRecordModerModule,
scrollerModule,
backToTopModule,
virtualListModule
],
data() {
return {
// --------------静态资源---------------
base64BackToTop: zStatic.base64BackToTop,
// -------------全局数据相关--------------
// 当前加载类型
loadingType: Enum.LoadingType.Refresher,
requestTimeStamp: 0,
wxsPropType: '',
renderPropScrollTop: -1,
checkScrolledToBottomTimeOut: null,
cacheTopHeight: -1,
statusBarHeight: systemInfo.statusBarHeight,
// --------------状态&判断---------------
insideOfPaging: -1,
isLoadFailed: false,
isIos: systemInfo.platform === 'ios',
disabledBounce: false,
fromCompleteEmit: false,
disabledCompleteEmit: false,
pageLaunched: false,
active: 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: 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中设置的max-width传字符串如100px、100rpx、100%。默认为空也就是铺满窗口宽度若设置了特定值则会自动添加margin: 0 auto
maxWidth: {
type: String,
default: u.gc('maxWidth', '')
},
// z-paging的背景色优先级低于pagingStyle中设置的background。传字符串如"#ffffff"
bgColor: {
type: String,
default: u.gc('bgColor', '')
},
// 设置z-paging的容器(插槽的父view)的style
pagingContentStyle: {
type: Object,
default: 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: 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)
},
// z-paging bottom的背景色默认透明传字符串如"#ffffff"
bottomBgColor: {
type: String,
default: u.gc('bottomBgColor', '')
},
// 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默认为1
contentZIndex: {
type: Number,
default: u.gc('contentZIndex', 1)
},
// z-paging二楼的z-index默认为100
f2ZIndex: {
type: Number,
default: u.gc('f2ZIndex', 100)
},
// 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是
autoFullHeight: {
type: Boolean,
default: u.gc('autoFullHeight', true)
},
// 是否监听列表触摸方向改变,默认为否
watchTouchDirectionChange: {
type: Boolean,
default: u.gc('watchTouchDirectionChange', false)
},
// z-paging中布局的单位默认为rpx
unit: {
type: String,
default: u.gc('unit', 'rpx')
}
},
created() {
// 组件创建时,检测是否开始加载状态
if (this.createdReload && !this.refresherOnly && this.auto) {
this._startLoading();
this.$nextTick(this._preReload);
}
},
mounted() {
this.active = true;
this.wxsPropType = u.getTime().toString();
this.renderJsIgnore;
if (!this.createdReload && !this.refresherOnly && this.auto) {
// 开始预加载
u.delay(() => this.$nextTick(this._preReload), 0);
}
// 如果开启了列表缓存,在初始化的时候通过缓存数据填充列表数据
this.finalUseCache && this._setListByLocalCache();
let delay = 0;
// #ifdef H5 || MP
delay = c.delayTime;
// #endif
this.$nextTick(() => {
// 初始化systemInfo
this.systemInfo = uni.getSystemInfoSync();
// 初始化z-paging高度
!this.usePageScroll && this.autoHeight && this._setAutoHeight();
// #ifdef MP-KUAISHOU
this._setFullScrollViewInHeight();
// #endif
this.loaded = true;
u.delay(() => {
// 更新fixed模式下z-paging的布局主要是更新windowTop、windowBottom
this.updateFixedLayout();
// 更新缓存中z-paging整个内容容器高度
this._updateCachedSuperContentHeight();
});
})
// 初始化页面滚动模式下slot="top"、slot="bottom"高度
this.updatePageScrollTopHeight();
this.updatePageScrollBottomHeight();
// 初始化slot="left"、slot="right"宽度
this.updateLeftAndRightWidth();
if (this.finalRefresherEnabled && this.useCustomRefresher) {
this.$nextTick(() => {
this.isTouchmoving = true;
})
}
// 监听uni.$emit中全局emit的complete error等事件
this._onEmit();
// #ifdef APP-NVUE
if (!this.isIos && !this.useChatRecordMode) {
this.nLoadingMoreFixedHeight = true;
}
// 在nvue中更新nvue下拉刷新view容器的宽度而不是写死默认的750rpx需要考虑列表宽度不是铺满屏幕的情况
this._nUpdateRefresherWidth();
// #endif
// #ifndef APP-NVUE
// 虚拟列表模式时,初始化数据
this.finalUseVirtualList && this._virtualListInit();
// #endif
// #ifndef APP-PLUS
this.$nextTick(() => {
// 非app平台中在通过获取css设置的底部安全区域占位view高度设置bottom距离后更新页面滚动底部高度
setTimeout(() => {
this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight());
}, delay)
})
// #endif
},
destroyed() {
this._handleUnmounted();
},
// #ifdef VUE3
unmounted() {
this._handleUnmounted();
},
// #endif
watch: {
defaultThemeStyle: {
handler(newVal) {
if (newVal.length) {
this.finalRefresherDefaultStyle = newVal;
}
},
immediate: true
},
autoHeight(newVal) {
this.loaded && !this.usePageScroll && this._setAutoHeight(newVal);
},
autoHeightAddition(newVal) {
this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal);
},
},
computed: {
// 当前z-paging的内置样式
finalPagingStyle() {
const pagingStyle = { ...this.pagingStyle };
if (!this.systemInfo) return pagingStyle;
const { windowTop, windowBottom } = this;
if (!this.usePageScroll && this.fixed) {
if (windowTop && !pagingStyle.top) {
pagingStyle.top = windowTop + 'px';
}
if (windowBottom && !pagingStyle.bottom) {
pagingStyle.bottom = windowBottom + 'px';
}
}
if (this.bgColor.length && !pagingStyle['background']) {
pagingStyle['background'] = this.bgColor;
}
if (this.height.length && !pagingStyle['height']) {
pagingStyle['height'] = this.height;
}
if (this.width.length && !pagingStyle['width']) {
pagingStyle['width'] = this.width;
}
if (this.maxWidth.length && !pagingStyle['max-width']) {
pagingStyle['max-width'] = this.maxWidth;
pagingStyle['margin'] = '0 auto';
}
return pagingStyle;
},
// 当前z-paging内容的样式
finalPagingContentStyle() {
if (this.contentZIndex != 1) {
this.pagingContentStyle['z-index'] = this.contentZIndex;
this.pagingContentStyle['position'] = 'relative';
}
return this.pagingContentStyle;
},
renderJsIgnore() {
if ((this.usePageScroll && this.useChatRecordMode) || (!this.refresherEnabled && this.scrollable) || !this.useCustomRefresher) {
this.$nextTick(() => {
this.renderPropScrollTop = 10;
})
}
return 0;
},
windowHeight() {
if (!this.systemInfo) return 0;
return this.systemInfo.windowHeight || 0;
},
windowBottom() {
if (!this.systemInfo) return 0;
let windowBottom = this.systemInfo.windowBottom || 0;
// 如果开启底部安全区域适配并且不使用placeholder的形式体现并且不是聊天记录模式因为聊天记录模式在keyboardHeight计算初已添加了底部安全区域在windowBottom添加底部安全区域高度
if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder && !this.useChatRecordMode) {
windowBottom += this.safeAreaBottom;
}
return windowBottom;
},
isIosAndH5() {
// #ifndef H5
return false;
// #endif
return this.isIos;
}
},
methods: {
// 当前版本号
getVersion() {
return `z-paging v${c.version}`;
},
// 设置nvue List的specialEffects
setSpecialEffects(args) {
this.setListSpecialEffects(args);
},
// 与setSpecialEffects等效兼容旧版本
setListSpecialEffects(args) {
this.nFixFreezing = args && Object.keys(args).length;
if (this.isIos) {
this.privateRefresherEnabled = 0;
}
!this.usePageScroll && this.$refs['zp-n-list'].setSpecialEffects(args);
},
// #ifdef APP-VUE
// 当app长时间进入后台后进入前台因系统内存管理导致app重新加载时进行一些适配处理
_handlePageLaunch() {
// 首次触发不进行处理只有进入后台后打开app重新加载时才处理
if (this.pageLaunched) {
// 解决在vue3+ios中app ReLaunch时顶部下拉刷新展示位置向下偏移的问题
// #ifdef VUE3
this.refresherThresholdUpdateTag = 1;
this.$nextTick(() => {
this.refresherThresholdUpdateTag = 0;
})
// #endif
// 解决使用虚拟列表时app ReLaunch时白屏问题
this._checkVirtualListScroll();
}
this.pageLaunched = true;
},
// #endif
// 使手机发生较短时间的振动15ms
_doVibrateShort() {
// #ifndef H5
// #ifdef APP-PLUS
if (this.isIos) {
const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator');
const feedbackGenerator = new UISelectionFeedbackGenerator();
feedbackGenerator.init();
setTimeout(() => {
feedbackGenerator.selectionChanged();
}, 0)
} else {
plus.device.vibrate(15);
}
// #endif
// #ifndef APP-PLUS
uni.vibrateShort();
// #endif
// #endif
},
// 设置z-paging高度
async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) {
const heightKey = 'min-height';
try {
if (shouldFullHeight) {
// 如果需要铺满全屏,则计算当前全屏可是区域的高度
let finalScrollViewNode = scrollViewNode || await this._getNodeClientRect('.zp-scroll-view');
let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom');
if (finalScrollViewNode) {
const scrollViewTop = finalScrollViewNode[0].top;
let scrollViewHeight = this.windowHeight - scrollViewTop;
scrollViewHeight -= finalScrollBottomNode ? finalScrollBottomNode[0].height : 0;
const additionHeight = u.convertToPx(this.autoHeightAddition);
// 在支付宝小程序中,添加!important会导致min-height失效因此在支付宝小程序中需要去掉
let importantSuffix = ' !important';
// #ifdef MP-ALIPAY
importantSuffix = '';
// #endif
const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px' + importantSuffix;
this.$set(this.scrollViewStyle, heightKey, finalHeight);
this.$set(this.scrollViewInStyle, heightKey, finalHeight);
}
} else {
this.$delete(this.scrollViewStyle, heightKey);
this.$delete(this.scrollViewInStyle, heightKey);
}
} catch (e) {}
},
// #ifdef MP-KUAISHOU
// 设置scroll-view内容器的最小高度等于scroll-view的高度(为了解决在快手小程序中内容较少时scroll-view内容器高度无法铺满scroll-view的问题)
async _setFullScrollViewInHeight() {
try {
// 如果需要铺满全屏,则计算当前全屏可是区域的高度
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
scrollViewNode && this.$set(this.scrollViewInStyle, 'min-height', scrollViewNode[0].height + 'px');
} catch (e) {}
},
// #endif
// 组件销毁后续处理
_handleUnmounted() {
this.active = false;
this._offEmit();
// 取消监听键盘高度变化事件H5、百度小程序、抖音小程序、飞书小程序、QQ小程序、快手小程序不支持
// #ifndef H5 || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU
this.useChatRecordMode && uni.offKeyboardHeightChange(this._handleKeyboardHeightChange);
// #endif
},
// 触发更新是否超出页面状态
_updateInsideOfPaging() {
this.insideMore && this.insideOfPaging === true && setTimeout(this.doLoadMore, 200)
},
// 清除timeout
_cleanTimeout(timeout) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
return timeout;
},
// 添加全局emit监听
_onEmit() {
uni.$on(c.errorUpdateKey, (errorMsg) => {
if (this.loading) {
if (!!errorMsg) {
this.customerEmptyViewErrorText = errorMsg;
}
this.complete(false).catch(() => {});
}
})
uni.$on(c.completeUpdateKey, (data) => {
setTimeout(() => {
if (this.loading) {
if (!this.disabledCompleteEmit) {
const type = data.type || 'normal';
const list = data.list || data;
const rule = data.rule;
this.fromCompleteEmit = true;
switch (type){
case 'normal':
this.complete(list);
break;
case 'total':
this.completeByTotal(list, rule);
break;
case 'nomore':
this.completeByNoMore(list, rule);
break;
case 'key':
this.completeByKey(list, rule);
break;
default:
break;
}
} else {
this.disabledCompleteEmit = false;
}
}
}, 1);
})
},
// 销毁全局emit和listener监听
_offEmit(){
uni.$off(c.errorUpdateKey);
uni.$off(c.completeUpdateKey);
},
},
};

View File

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

File diff suppressed because one or more lines are too long

View File

@ -1,288 +0,0 @@
// [z-paging]工具类
import zLocalConfig from '../config/index'
import c from './z-paging-constant'
const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY';
let config = null;
let configLoaded = false;
const timeoutMap = {};
// #ifdef APP-HARMONY
let screenWidth = 0;
// #endif
// 获取默认配置信息
function gc(key, defaultValue) {
// 这里return一个函数以解决在vue3+appvue中props默认配置读取在main.js之前执行导致uni.$zp全局配置无效的问题。相当于props的default中传入一个带有返回值的函数
return () => {
// 处理z-paging全局配置
_handleDefaultConfig();
// 如果全局配置不存在,则返回默认值
if (!config) return defaultValue;
const value = config[key];
// 如果全局配置存在但对应的配置项不存在,则返回默认值;反之返回配置项
return value === undefined ? defaultValue : value;
};
}
// 获取最终的touch位置
function getTouch(e) {
let touch = null;
if (e.touches && e.touches.length) {
touch = e.touches[0];
} else if (e.changedTouches && e.changedTouches.length) {
touch = e.changedTouches[0];
} else if (e.datail && e.datail != {}) {
touch = e.datail;
} else {
return { touchX: 0, touchY: 0 }
}
return {
touchX: touch.clientX,
touchY: touch.clientY
};
}
// 判断当前手势是否在z-paging内触发
function getTouchFromZPaging(target) {
if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') {
const classList = target.classList;
if (classList && classList.contains('z-paging-content')) {
// 此处额外记录当前z-paging是否是页面滚动、是否滚动到了顶部、是否是聊天记录模式以传给renderjs。避免不同z-paging组件renderjs内部判断数据互相影响导致的各种问题
return {
isFromZp: true,
isPageScroll: classList.contains('z-paging-content-page'),
isReachedTop: classList.contains('z-paging-reached-top'),
isUseChatRecordMode: classList.contains('z-paging-use-chat-record-mode')
};
} else {
return getTouchFromZPaging(target.parentNode);
}
} else {
return { isFromZp: false };
}
}
// 递归获取z-paging所在的parent如果查找不到则返回null
function getParent(parent) {
if (!parent) return null;
if (parent.$refs.paging) return parent;
return getParent(parent.$parent);
}
// 打印错误信息
function consoleErr(err) {
console.error(`[z-paging]${err}`);
}
// 延时操作如果key存在调用时清除对应key之前的延时操作
function delay(callback, ms = c.delayTime, key) {
const timeout = setTimeout(callback, ms);;
if (!!key) {
timeoutMap[key] && clearTimeout(timeoutMap[key]);
timeoutMap[key] = timeout;
}
return timeout;
}
// 设置下拉刷新时间
function setRefesrherTime(time, key) {
const datas = getRefesrherTime() || {};
datas[key] = time;
uni.setStorageSync(storageKey, datas);
}
// 获取下拉刷新时间
function getRefesrherTime() {
return uni.getStorageSync(storageKey);
}
// 通过下拉刷新标识key获取下拉刷新时间
function getRefesrherTimeByKey(key) {
const datas = getRefesrherTime();
return datas && datas[key] ? datas[key] : null;
}
// 通过下拉刷新标识key获取下拉刷新时间(格式化之后)
function getRefesrherFormatTimeByKey(key, textMap) {
const time = getRefesrherTimeByKey(key);
const timeText = time ? _timeFormat(time, textMap) : textMap.none;
return `${textMap.title}${timeText}`;
}
// 将文本的px或者rpx转为px的值
function convertToPx(text) {
const dataType = Object.prototype.toString.call(text);
if (dataType === '[object Number]') return text;
let isRpx = false;
if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) {
text = text.replace('rpx', '').replace('upx', '');
isRpx = true;
} else if (text.indexOf('px') !== -1) {
text = text.replace('px', '');
}
if (!isNaN(text)) {
if (isRpx) return Number(rpx2px(text));
return Number(text);
}
return 0;
}
// rpx => px兼容鸿蒙
function rpx2px(rpx) {
// #ifdef APP-HARMONY
if (!screenWidth) {
screenWidth = uni.getSystemInfoSync().screenWidth;
}
return (screenWidth * Number.parseFloat(rpx)) / 750;
// #endif
// #ifndef APP-HARMONY
return uni.upx2px(rpx);
// #endif
}
// 获取当前时间
function getTime() {
return (new Date()).getTime();
}
// 获取z-paging实例id随机生成10位数字+字母
function getInstanceId() {
const s = [];
const hexDigits = "0123456789abcdef";
for (let i = 0; i < 10; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
return s.join('') + getTime();
}
// 等待一段时间
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// 是否是promise
function isPromise(func) {
return Object.prototype.toString.call(func) === '[object Promise]';
}
// 添加单位
function addUnit(value, unit) {
if (Object.prototype.toString.call(value) === '[object String]') {
let tempValue = value;
tempValue = tempValue.replace('rpx', '').replace('upx', '').replace('px', '');
if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1 && value.indexOf('px') !== -1) {
tempValue = parseFloat(tempValue) * 2;
}
value = tempValue;
}
return unit === 'rpx' ? value + 'rpx' : (value / 2) + 'px';
}
// 深拷贝
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
let newObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
// ------------------ 私有方法 ------------------------
// 处理全局配置
function _handleDefaultConfig() {
// 确保只加载一次全局配置
if (configLoaded) return;
// 优先从config.js中读取
if (zLocalConfig && Object.keys(zLocalConfig).length) {
config = zLocalConfig;
}
// 如果在config.js中读取不到则尝试到uni.$zp读取
if (!config && uni.$zp) {
config = uni.$zp.config;
}
// 将config中的短横线写法全部转为驼峰写法使得读取配置时可以直接通过key去匹配而非读取每个配置时候再去转减少不必要的性能开支
config = config ? Object.keys(config).reduce((result, key) => {
result[_toCamelCase(key)] = config[key];
return result;
}, {}) : null;
configLoaded = true;
}
// 时间格式化
function _timeFormat(time, textMap) {
const date = new Date(time);
const currentDate = new Date();
// 设置time对应的天去除时分秒使得可以直接比较日期
const dateDay = new Date(time).setHours(0, 0, 0, 0);
// 设置当前的天,去除时分秒,使得可以直接比较日期
const currentDateDay = new Date().setHours(0, 0, 0, 0);
const disTime = dateDay - currentDateDay;
let dayStr = '';
const timeStr = _dateTimeFormat(date);
if (disTime === 0) {
dayStr = textMap.today;
} else if (disTime === -86400000) {
dayStr = textMap.yesterday;
} else {
dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear());
}
return `${dayStr} ${timeStr}`;
}
// date格式化为年月日
function _dateDayFormat(date, showYear = true) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`;
}
// data格式化为时分
function _dateTimeFormat(date) {
const hour = date.getHours();
const minute = date.getMinutes();
return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`;
}
// 不满2位在前面填充0
function _fullZeroToTwo(str) {
str = str.toString();
return str.length === 1 ? '0' + str : str;
}
// 驼峰转短横线
function _toKebab(value) {
return value.replace(/([A-Z])/g, "-$1").toLowerCase();
}
// 短横线转驼峰
function _toCamelCase(value) {
return value.replace(/-([a-z])/g, (_, group1) => group1.toUpperCase());
}
export default {
gc,
setRefesrherTime,
getRefesrherFormatTimeByKey,
getTouch,
getTouchFromZPaging,
getParent,
convertToPx,
getTime,
getInstanceId,
consoleErr,
delay,
wait,
isPromise,
addUnit,
deepCopy,
rpx2px
};

View File

@ -1,67 +0,0 @@
// [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理
import u from '../js/z-paging-utils'
const data = {
startY: 0,
isTouchFromZPaging: false,
isUsePageScroll: false,
isReachedTop: true,
isIosAndH5: false,
useChatRecordMode: false,
appLaunched: false
}
export default {
mounted() {
if (window) {
this._handleTouch();
// #ifdef APP-VUE
this.$ownerInstance.callMethod('_handlePageLaunch');
// #endif
}
},
methods: {
// 接收逻辑层发送的数据是否是ios+h5
renderPropIsIosAndH5Change(newVal) {
if (newVal === -1) return;
data.isIosAndH5 = newVal;
},
// 拦截处理touch事件
_handleTouch() {
if (!window.$zPagingRenderJsInited) {
window.$zPagingRenderJsInited = true;
window.addEventListener('touchstart', this._handleTouchstart, { passive: true })
window.addEventListener('touchmove', this._handleTouchmove, { passive: false })
}
},
// 处理touch开始
_handleTouchstart(e) {
const touch = u.getTouch(e);
data.startY = touch.touchY;
const touchResult = u.getTouchFromZPaging(e.target);
data.isTouchFromZPaging = touchResult.isFromZp;
data.isUsePageScroll = touchResult.isPageScroll;
data.isReachedTop = touchResult.isReachedTop;
data.useChatRecordMode = touchResult.isUseChatRecordMode;
},
// 处理touch中
_handleTouchmove(e) {
const touch = u.getTouch(e);
const moveY = touch.touchY - data.startY;
// 如果是在z-paging内触摸并且是在顶部位置且是下拉的情况下或不是聊天记录滚动模式并且在iOS+h5+scroll-view并且是往上拉的情况避免在此平台中滚动到底部后上拉有个系统灰色遮罩导致列表被短暂锁定的问题
// (data.useChatRecordMode ? moveY < 0 : moveY > 0)是为了判断是否是上拉的情况聊天记录模式列表倒置因此moveY < 0为上拉
if (data.isTouchFromZPaging && ((data.isReachedTop && (data.useChatRecordMode ? moveY < 0 : moveY > 0)) || (!data.useChatRecordMode && data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) {
if (e.cancelable && !e.defaultPrevented) {
// 阻止事件冒泡以避免在一些平台中下拉刷新时整个page跟着一起下拉&在iOS+h5+scroll-view中在底部上拉有个系统灰色遮罩导致列表被短暂锁定的问题
e.preventDefault();
}
}
},
// 移除touch相关事件监听
_removeAllEventListener(){
window.removeEventListener('touchstart');
window.removeEventListener('touchmove');
}
}
};

View File

@ -1,382 +0,0 @@
// [z-paging]微信小程序、QQ小程序、app-vue、h5上使用wxs实现自定义下拉刷新降低逻辑层与视图层的通信折损提升性能
var currentDis = 0;
var isPCFlag = -1;
var startY = -1;
// 监听js层传过来的数据
function propObserver(newVal, oldVal, ownerIns, ins) {
var state = ownerIns.getState() || {};
state.currentIns = ins;
var dataset = ins.getDataset();
var loading = dataset.loading == true;
// 如果是下拉刷新结束更新transform
if (newVal && newVal.indexOf('end') != -1) {
var transition = newVal.split('end')[0];
_setTransform('translateY(0px)', ins, false, transition);
state.moveDis = 0;
state.oldMoveDis = 0;
currentDis = 0;
} else if (newVal && newVal.indexOf('begin') != -1) {
// 如果是下拉刷新开始更新transform
var refresherThreshold = ins.getDataset().refresherthreshold;
_setTransformValue(refresherThreshold, ins, state, false);
}
}
// touch开始
function touchstart(e, ownerIns) {
var ins = _getIns(ownerIns);
var state = {};
var dataset = {};
ownerIns.callMethod('_handleListTouchstart');
if (ins) {
state = ins.getState();
dataset = ins.getDataset();
if (_touchDisabled(e, ins, 0)) return;
}
var isTouchEnded = state.isTouchEnded;
state.oldMoveDis = 0;
var touch = _getTouch(e);
var loading = _isTrue(dataset.loading);
state.startY = touch.touchY;
startY = state.startY;
state.lastTouch = touch;
if (!loading && isTouchEnded) {
state.isTouchmoving = false;
}
state.isTouchEnded = false;
// 通知js层touch开始
ownerIns.callMethod('_handleRefresherTouchstart', touch);
}
// touch中
function touchmove(e, ownerIns) {
var touch = _getTouch(e);
var ins = _getIns(ownerIns);
var dataset = ins.getDataset();
var refresherThreshold = dataset.refresherthreshold;
var refresherF2Threshold = dataset.refresherf2threshold;
var refresherF2Enabled = _isTrue(dataset.refresherf2enabled);
var isIos = _isTrue(dataset.isios);
var state = ins.getState();
var watchTouchDirectionChange = _isTrue(dataset.watchtouchdirectionchange);
var moveDisObj = {};
var moveDis = 0;
var prevent = false;
// 如果需要监听touch方向的改变
if (watchTouchDirectionChange) {
moveDisObj = _getMoveDis(e, ins);
moveDis = moveDisObj.currentDis;
prevent = moveDisObj.isDown;
var direction = prevent ? 'top' : 'bottom';
// 确保只在touch方向改变时通知一次js层而不是touchmove中持续通知
if (prevent == state.oldTouchDirection && prevent != state.oldEmitedTouchDirection) {
ownerIns.callMethod('_handleTouchDirectionChange', { direction: direction });
state.oldEmitedTouchDirection = prevent;
}
state.oldTouchDirection = prevent;
}
// 判断是否允许下拉刷新
if (_touchDisabled(e, ins, 1)) {
_handlePullingDown(state, ownerIns, false);
return true;
}
// 判断下拉刷新的角度是否在要求范围内
if (!_getAngleIsInRange(e, touch, state, dataset)) {
_handlePullingDown(state, ownerIns, false);
return true;
}
moveDisObj = _getMoveDis(e, ins);
moveDis = moveDisObj.currentDis;
prevent = moveDisObj.isDown;
if (moveDis < 0) {
// moveDis小于0将transform重置为0
_setTransformValue(0, ins, state, false);
_handlePullingDown(state, ownerIns, false);
return true;
}
if (prevent && !state.disabledBounce) {
// 如果是用户下拉并且需要触发下拉刷新需要通知js层将列表禁止滚动防止在下拉刷新过程中列表也可以滚动导致的下拉刷新偏移过大的问题在下拉刷新过程中仅通知一次
ownerIns.callMethod('_handleScrollViewBounce', { bounce: false });
state.disabledBounce = true;
_handlePullingDown(state, ownerIns, prevent);
return !prevent;
}
// 更新transform
_setTransformValue(moveDis, ins, state, false);
var oldRefresherStatus = state.refresherStatus;
var oldIsTouchmoving = _isTrue(dataset.oldistouchmoving);
var hasTouchmove = _isTrue(dataset.hastouchmove);
var isTouchmoving = state.isTouchmoving;
state.refresherStatus = moveDis >= refresherThreshold ? (refresherF2Enabled && moveDis > refresherF2Threshold ? 'goF2' : 'releaseToRefresh') : 'default';
if (!isTouchmoving) {
state.isTouchmoving = true;
isTouchmoving = true;
}
if (state.isTouchEnded) {
state.isTouchEnded = false;
}
// 如果需要实时监听下拉位置偏移则需要实时通知js层此操作会使wxs层与js层频繁通信从而导致在一些性能较差设备中下拉刷新卡顿
if (hasTouchmove) {
ownerIns.callMethod('_handleWxsPullingDown', { moveDis: moveDis, diffDis: moveDisObj.diffDis });
}
// 在下拉刷新状态改变时通知js层
if (oldRefresherStatus == undefined || oldRefresherStatus != state.refresherStatus || oldIsTouchmoving != isTouchmoving) {
ownerIns.callMethod('_handleRefresherTouchmove', moveDis, touch);
}
_handlePullingDown(state, ownerIns, prevent);
return !prevent;
}
// touch结束
function touchend(e, ownerIns) {
var touch = _getTouch(e);
var ins = _getIns(ownerIns);
var dataset = ins.getDataset();
var state = ins.getState();
if (state.disabledBounce) {
// 通知js允许列表滚动
ownerIns.callMethod('_handleScrollViewBounce', { bounce: true });
state.disabledBounce = false;
}
if (_touchDisabled(e, ins, 2)) return;
state.reachMaxAngle = true;
state.hitReachMaxAngleCount = 0;
state.fixedIsTopHitCount = 0;
if (!state.isTouchmoving) return;
var oldRefresherStatus = state.refresherStatus;
var oldMoveDis = state.moveDis;
var refresherThreshold = ins.getDataset().refresherthreshold;
var moveDis = _getMoveDis(e, ins).currentDis;
if (!(moveDis >= refresherThreshold && oldRefresherStatus === 'releaseToRefresh')) {
state.isTouchmoving = false;
}
// 通知js层touch结束
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
// 判断是否是pc平台
function isPC() {
if (!navigator) return false;
if (isPCFlag != -1) return isPCFlag;
var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
isPCFlag = agents.every(function(item) { return navigator.userAgent.indexOf(item) < 0 });
return isPCFlag;
}
var movable = false;
// 在pc平台监听mousedown、mousemove、mouseup等相关事件并转为对应touch事件处理使得在pc平台也支持通过鼠标进行下拉刷新
function mousedown(e, ins) {
if (!isPC()) return;
touchstart(e, ins);
movable = true;
}
function mousemove(e, ins) {
if (!isPC() || !movable) return;
touchmove(e, ins);
}
function mouseup(e, ins) {
if (!isPC()) return;
touchend(e, ins);
movable = false;
}
function mouseleave(e, ins) {
if (!isPC()) return;
movable = false;
}
// #endif
// 修改视图层transform
function _setTransformValue(value, ins, state, animate) {
value = value || 0;
if (state.moveDis == value) return;
state.moveDis = value;
_setTransform('translateY(' + value + 'px)', ins, animate, '');
}
// 设置视图层transform直接在视图层操作下拉刷新使得js层不需要频繁和视图层通信从而大大提升下拉刷新性能
function _setTransform(transform, ins, animate, transition) {
var dataset = ins.getDataset();
if (_isTrue(dataset.refreshernotransform)) return;
transform = transform == 'translateY(0px)' ? 'none' : transform;
ins.requestAnimationFrame(function() {
var stl = { 'transform': transform };
if (animate) {
stl['transition'] = 'transform .1s linear';
}
if (transition.length) {
stl['transition'] = transition;
}
ins.setStyle(stl);
})
}
// 进一步处理下拉刷新的偏移数据
function _getMoveDis(e, ins) {
var state = ins.getState();
var refresherThreshold = parseFloat(ins.getDataset().refresherthreshold);
var refresherOutRate = parseFloat(ins.getDataset().refresheroutrate);
var refresherPullRate = parseFloat(ins.getDataset().refresherpullrate);
var touch = _getTouch(e);
var currentStartY = !state.startY || state.startY == 'NaN' ? startY : state.startY;
var moveDis = touch.touchY - currentStartY;
var oldMoveDis = state.oldMoveDis || 0;
state.oldMoveDis = moveDis;
// 获取当前下拉刷新位置与上次的偏移量
var diffDis = moveDis - oldMoveDis;
if (diffDis > 0) {
// 对偏移量进行进一步处理通过refresherPullRate等配置进行约束
diffDis = diffDis * refresherPullRate;
if (currentDis > refresherThreshold) {
diffDis = diffDis * (1 - refresherOutRate);
}
}
// 控制diffDis过大的情况比如进入页面突然猛然下拉此时diffDis不应进行太大的偏移
diffDis = diffDis > 100 ? diffDis / 100 : (diffDis > 20 ? diffDis / 2.2 : diffDis);
currentDis += diffDis;
currentDis = Math.max(0, currentDis);
return {
currentDis: currentDis,
diffDis: diffDis,
isDown: diffDis > 0
};
}
// 获取经过统一格式包装的当前touch对象
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
};
}
// 获取当前currentIns
function _getIns(ownerIns) {
var ins = ownerIns.getState().currentIns;
if (!ins) {
ownerIns.callMethod('_handlePropUpdate');
}
return ins;
}
// 判断当前状态是否允许下拉刷新
function _touchDisabled(e, ins, processTag) {
var dataset = ins.getDataset();
var state = ins.getState();
var loading = _isTrue(dataset.loading);
var useChatRecordMode = _isTrue(dataset.usechatrecordmode);
var refresherEnabled = _isTrue(dataset.refresherenabled);
var useCustomRefresher = _isTrue(dataset.usecustomrefresher);
var usePageScroll = _isTrue(dataset.usepagescroll);
var pageScrollTop = parseFloat(dataset.pagescrolltop);
var scrollTop = parseFloat(dataset.scrolltop);
var finalScrollTop = usePageScroll ? pageScrollTop : scrollTop;
var fixedIsTop = false;
// 是否要处理滚动到顶部scrollTop不为0时候的容错为解决在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常但此方案会导致某些情况例如滚动到距离顶部10px处下拉抖动因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案
var handleFaultTolerantMove = false;
if (handleFaultTolerantMove && finalScrollTop == (state.startScrollTop || 0) && finalScrollTop <= 105) {
fixedIsTop = true;
}
var fixedIsTopHitCount = state.fixedIsTopHitCount || 0;
if (fixedIsTop) {
fixedIsTopHitCount ++;
if (fixedIsTopHitCount <= 2) {
fixedIsTop = false;
}
state.fixedIsTopHitCount = fixedIsTopHitCount;
} else {
state.fixedIsTopHitCount = 0;
}
if (handleFaultTolerantMove && processTag === 0) {
state.startScrollTop = finalScrollTop || 0;
}
if (handleFaultTolerantMove && processTag === 2) {
fixedIsTop = true;
}
return loading || useChatRecordMode || !refresherEnabled || !useCustomRefresher ||
((usePageScroll && useCustomRefresher && pageScrollTop > 5) && !fixedIsTop) ||
((!usePageScroll && useCustomRefresher && scrollTop > 5) && !fixedIsTop);
}
// 判断下拉刷新的角度是否在要求范围内
function _getAngleIsInRange(e, touch, state, dataset) {
var maxAngle = dataset.refreshermaxangle;
var refresherAecc = _isTrue(dataset.refresheraecc);
var lastTouch = state.lastTouch;
var reachMaxAngle = state.reachMaxAngle;
var moveDis = state.oldMoveDis;
if (!lastTouch) return true;
if (maxAngle >= 0 && maxAngle <= 90 && lastTouch) {
// 考虑下拉刷新手势由水平移动转为垂直方向移动的情况此时不应当只判断垂直方向角度是否符合要求应当直接禁止以避免在swiper中使用下拉刷新时横向切换swiper途中手未离开屏幕还可以下拉刷新的问题
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) {
// 如果角度小于配置要求则return同时通过hitReachMaxAngleCount控制角度判断的灵敏程度以最大程度兼容各种使用场景
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;
}
// 进一步处理是否在下拉刷新并通知js层
function _handlePullingDown(state, ins, onPullingDown) {
var oldOnPullingDown = state.onPullingDown || false;
if (oldOnPullingDown != onPullingDown) {
ins.callMethod('_handleWxsPullingDownStatusChange', onPullingDown);
}
state.onPullingDown = onPullingDown;
}
// 判断js层传过来的值是否为true
function _isTrue(value) {
value = (typeof(value) === 'string' ? JSON.parse(value) : value) || false;
return value == true || value == 'true';
}
module.exports = {
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend,
mousedown: mousedown,
mousemove: mousemove,
mouseup: mouseup,
mouseleave: mouseleave,
propObserver: propObserver
}

View File

@ -1,354 +0,0 @@
<!-- _
____ _ __ __ _ __ _(_)_ __ __ _
|_ /____| '_ \ / _` |/ _` | | '_ \ / _` |
/ /_____| |_) | (_| | (_| | | | | | (_| |
/___| | .__/ \__,_|\__, |_|_| |_|\__, |
|_| |___/ |___/
v2.8.5 (2025-02-09)
@author ZXLee <admin@zxlee.cn>
-->
<!-- 文档地址https://z-paging.zxlee.cn -->
<!-- github地址https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群343409055 -->
<template name="z-paging">
<!-- #ifndef APP-NVUE -->
<view :class="{'z-paging-content':true,'z-paging-content-full':!usePageScroll,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1,'z-paging-use-chat-record-mode':useChatRecordMode}" :style="[finalPagingStyle]">
<!-- #ifndef APP-PLUS -->
<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
<!-- #endif -->
<!-- 二楼view -->
<view v-if="showF2 && showRefresherF2" @touchmove.stop.prevent class="zp-f2-content" :style="[{'transform': f2Transform, 'transition': `transform .2s linear`, 'height': superContentHeight + 'px', 'z-index': f2ZIndex}]">
<slot name="f2"/>
</view>
<!-- 顶部固定的slot -->
<slot v-if="!usePageScroll&&zSlots.top" name="top" />
<view class="zp-page-top" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]">
<slot name="top" />
</view>
<view :class="{'zp-view-super':true,'zp-scroll-view-super':!usePageScroll}" :style="[finalScrollViewStyle]">
<view v-if="zSlots.left" :class="{'zp-page-left':true,'zp-absoulte':finalIsOldWebView}">
<slot name="left" />
</view>
<view :class="{'zp-scroll-view-container':true,'zp-absoulte':finalIsOldWebView}" :style="[scrollViewContainerStyle]">
<scroll-view
ref="zp-scroll-view" :class="{'zp-scroll-view':true,'zp-scroll-view-absolute':!usePageScroll,'zp-scroll-view-hide-scrollbar':!showScrollbar}" :style="[chatRecordRotateStyle]"
:scroll-top="scrollTop" :scroll-left="scrollLeft" :scroll-x="scrollX"
:scroll-y="finalScrollable" :enable-back-to-top="finalEnableBackToTop"
:show-scrollbar="showScrollbar" :scroll-with-animation="finalScrollWithAnimation"
:scroll-into-view="scrollIntoView" :lower-threshold="finalLowerThreshold" :upper-threshold="5"
:refresher-enabled="finalRefresherEnabled&&!useCustomRefresher" :refresher-threshold="finalRefresherThreshold"
:refresher-default-style="finalRefresherDefaultStyle" :refresher-background="refresherBackground"
:refresher-triggered="finalRefresherTriggered" @scroll="_scroll" @scrolltolower="_onScrollToLower"
@scrolltoupper="_onScrollToUpper" @refresherrestore="_onRestore" @refresherrefresh="_onRefresh(true)"
>
<view class="zp-paging-touch-view"
<!-- #ifndef APP-PLUS || MP-WEIXIN || MP-QQ || H5 -->
@touchstart="_refresherTouchstart" @touchmove="_refresherTouchmove" @touchend="_refresherTouchend" @touchcancel="_refresherTouchend"
<!-- #endif -->
<!-- #ifdef APP-PLUS || 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-PLUS || MP-WEIXIN || MP-QQ || H5 -->
:change:prop="pagingWxs.propObserver" :prop="wxsPropType"
:data-refresherThreshold="finalRefresherThreshold" :data-refresherF2Enabled="refresherF2Enabled" :data-refresherF2Threshold="finalRefresherF2Threshold" :data-isIos="isIos"
:data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode"
:data-refresherEnabled="refresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop"
:data-scrollTop="wxsScrollTop" :data-refresherMaxAngle="refresherMaxAngle" :data-refresherNoTransform="refresherNoTransform"
:data-refresherAecc="refresherAngleEnableChangeContinued" :data-usePageScroll="usePageScroll" :data-watchTouchDirectionChange="watchTouchDirectionChange"
:data-oldIsTouchmoving="isTouchmoving" :data-refresherOutRate="finalRefresherOutRate" :data-refresherPullRate="finalRefresherPullRate" :data-hasTouchmove="hasTouchmove"
<!-- #endif -->
<!-- #ifdef APP-PLUS || H5 -->
:change:renderPropIsIosAndH5="pagingRenderjs.renderPropIsIosAndH5Change" :renderPropIsIosAndH5="isIosAndH5"
<!-- #endif -->
>
<view v-if="showRefresher" class="zp-custom-refresher-view" :style="[{'margin-top': `-${finalRefresherThreshold+refresherThresholdUpdateTag}px`,'background': refresherBackground,'opacity': isTouchmoving ? 1 : 0}]">
<view class="zp-custom-refresher-container" :style="[{'height': `${finalRefresherThreshold}px`,'background': refresherBackground}]">
<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
<!-- 下拉刷新view -->
<view class="zp-custom-refresher-slot-view">
<slot v-if="!(zSlots.refresherComplete&&refresherStatus===R.Complete)&&!(zSlots.refresherF2&&refresherStatus===R.GoF2)" :refresherStatus="refresherStatus" name="refresher" />
</view>
<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
<z-paging-refresh ref="refresh" v-else-if="!showCustomRefresher" class="zp-custom-refresher-refresh" :style="[{'height': `${finalRefresherThreshold - finalRefresherThresholdPlaceholder}px`}]" :status="refresherStatus"
:defaultThemeStyle="finalRefresherThemeStyle" :defaultText="finalRefresherDefaultText"
:pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
</view>
</view>
<view class="zp-paging-container" :style="[{justifyContent:useChatRecordMode?'flex-end':'flex-start'}]">
<!-- 全屏Loading -->
<slot v-if="showLoading&&zSlots.loading&&!loadingFullFixed" name="loading" />
<!-- 主体内容 -->
<view class="zp-paging-container-content" :style="[finalPlaceholderTopHeightStyle,finalPagingContentStyle]">
<!-- #ifdef VUE3 -->
<!-- 虚拟列表顶部占位view -->
<view v-if="useVirtualList" class="zp-virtual-placeholder" :style="[{height:virtualPlaceholderTopHeight+'px'}]"/>
<!-- #endif -->
<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="`${fianlVirtualCellIdPrefix}-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)">
<view v-if="useCompatibilityMode">使z-paging.vue103</view>
<!-- <zp-public-virtual-cell v-if="useCompatibilityMode" :extraData="extraData" :item="item" :index="virtualTopRangeIndex+index" /> -->
<slot v-else name="cell" :item="item" :index="virtualTopRangeIndex+index"/>
</view>
</template>
<template v-else>
<view class="zp-list-cell" v-for="(item,index) in realTotalData" :key="index" @click="_innerCellClick(item,index)">
<slot name="cell" :item="item" :index="index"/>
</view>
</template>
</view>
<slot name="footer"/>
</template>
<!-- 聊天记录模式加载更多loading -->
<template v-if="useChatRecordMode&&realTotalData.length>=defaultPageSize&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&(realTotalData.length||(showChatLoadingWhenReload&&showLoading))&&!isFirstPageAndNoMore">
<view :style="[chatRecordRotateStyle]">
<slot v-if="loadingStatus===M.NoMore&&zSlots.chatNoMore" name="chatNoMore" />
<template v-else>
<slot v-if="zSlots.chatLoading" :loadingMoreStatus="loadingStatus" name="chatLoading" />
<z-paging-load-more v-else @doClick="_onLoadingMore('click')" :zConfig="zLoadMoreConfig" />
</template>
</view>
</template>
<!-- 虚拟列表底部占位view -->
<view v-if="useVirtualList" class="zp-virtual-placeholder" :style="[{height:virtualPlaceholderBottomHeight+'px'}]"/>
<!-- 上拉加载更多view -->
<!-- #ifndef MP-ALIPAY -->
<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
<slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" />
<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" />
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<slot v-if="loadingStatus===M.Default&&zSlots.loadingMoreDefault&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreDefault" />
<slot v-else-if="loadingStatus===M.Loading&&zSlots.loadingMoreLoading&&showLoadingMore&&loadingMoreEnabled" name="loadingMoreLoading" />
<slot v-else-if="loadingStatus===M.NoMore&&zSlots.loadingMoreNoMore&&showLoadingMore&&showLoadingMoreNoMoreView&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreNoMore" />
<slot v-else-if="loadingStatus===M.Fail&&zSlots.loadingMoreFail&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreFail" />
<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMore&&showDefaultLoadingMoreText&&!(loadingStatus===M.NoMore&&!showLoadingMoreNoMoreView)&&loadingMoreEnabled&&!useChatRecordMode" :zConfig="zLoadMoreConfig" />
<!-- #endif -->
<view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
</view>
<!-- 空数据图 -->
<view v-if="showEmpty" :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}" :style="[emptyViewSuperStyle,chatRecordRotateStyle]">
<slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed"/>
<z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload"
:emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle"
:emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" :unit="unit"
@reload="_emptyViewReload" @viewClick="_emptyViewClick" />
</view>
</view>
</view>
</view>
</scroll-view>
</view>
<view v-if="zSlots.right" :class="{'zp-page-right':true,'zp-absoulte zp-right':finalIsOldWebView}">
<slot name="right" />
</view>
</view>
<!-- 底部固定的slot -->
<view class="zp-page-bottom-container" :style="{'background': bottomBgColor}">
<slot v-if="!usePageScroll&&zSlots.bottom" name="bottom" />
<view class="zp-page-bottom" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.bottom" :style="[{'bottom': `${windowBottom}px`}]">
<slot name="bottom" />
</view>
<!-- 聊天记录模式底部占位 -->
<template v-if="useChatRecordMode&&autoAdjustPositionWhenChat">
<view :style="[{height:chatRecordModeSafeAreaBottom+'px'}]" />
<view class="zp-page-bottom-keyboard-placeholder-animate" :style="[{height:keyboardHeight+'px'}]" />
</template>
</view>
<!-- 点击返回顶部view -->
<view v-if="showBackToTopClass" :class="finalBackToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
<slot v-if="zSlots.backToTop" name="backToTop" />
<image v-else class="zp-back-to-top-img" :class="{'zp-back-to-top-img-inversion': useChatRecordMode&&!backToTopImg.length}" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
</view>
<!-- 全屏Loading(铺满z-paging并固定) -->
<view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed">
<slot name="loading" />
</view>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<component ref="z-paging-content" :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false">
<!-- 二楼view -->
<view v-if="showF2 && showRefresherF2" ref="zp-n-f2" class="zp-f2-content" @touchmove.stop.prevent :style="[{'height': superContentHeight + 'px', 'width': nRefresherWidth + 'px', 'opacity': nF2Opacity}]">
<slot name="f2"/>
</view>
<!-- 顶部固定的slot -->
<view ref="zp-page-top" v-if="zSlots.top" :class="{'zp-page-top':usePageScroll}" :style="[usePageScroll?{'top':`${windowTop}px`,'z-index':topZIndex}:{}]">
<slot name="top" />
</view>
<!-- 聊天记录模式加载更多loadingloading时候显示 -->
<view v-if="useChatRecordMode&&loadingStatus!==M.NoMore&&showChatLoadingWhenReload&&showLoading">
<slot v-if="zSlots.chatLoading" :loadingMoreStatus="loadingStatus" name="chatLoading" />
<z-paging-load-more v-else @doClick="_onLoadingMore('click')" :zConfig="zLoadMoreConfig" />
</view>
<component :is="finalNvueSuperListIs" class="zp-n-list-container" :scrollable="false">
<view v-if="zSlots.left" class="zp-page-left">
<slot name="left" />
</view>
<component :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},chatRecordRotateStyle]" :alwaysScrollableVertical="true"
:fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop"
:scrollable="finalScrollable" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth"
:column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled" :offset-accuracy="offsetAccuracy"
@loadmore="_nOnLoadmore" @scroll="_nOnScroll" @scrollend="_nOnScrollend">
<refresh v-if="(zSlots.top?cacheTopHeight!==-1:true)&&finalNvueRefresherEnabled" class="zp-n-refresh" :style="[nvueRefresherStyle]" :display="nRefresherLoading?'show':'hide'" @refresh="_nOnRrefresh" @pullingdown="_nOnPullingdown">
<view ref="zp-n-refresh-container" class="zp-n-refresh-container" :style="[{background:refresherBackground,width:nRefresherWidth}]" id="zp-n-refresh-container">
<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
<!-- 下拉刷新view -->
<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
<slot v-else-if="(nScopedSlots?nScopedSlots:zSlots).refresher" :refresherStatus="refresherStatus" name="refresher" />
<z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle"
:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
</view>
</refresh>
<component :is="nViewIs" v-if="isIos&&!useChatRecordMode?oldScrollTop>10:true" ref="zp-n-list-top-tag" class="zp-n-list-top-tag" style="margin-top: -1rpx;" :style="[{height:finalNvueRefresherEnabled?'0px':'1px'}]"></component>
<component :is="nViewIs" v-if="nShowRefresherReveal" ref="zp-n-list-refresher-reveal" :style="[{transform:`translateY(-${nShowRefresherRevealHeight}px)`},{background:refresherBackground}]">
<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
<!-- 下拉刷新view -->
<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
<slot v-else-if="(nScopedSlots?nScopedSlots:$slots).refresher" :refresherStatus="R.Loading" name="refresher" />
<z-paging-refresh ref="refresh" v-else :status="R.Loading" :defaultThemeStyle="finalRefresherThemeStyle"
:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
</component>
<!-- 内置列表 -->
<template v-if="finalUseInnerList">
<component :is="nViewIs">
<slot name="header"/>
</component>
<component :is="nViewIs" class="zp-list-cell" v-for="(item,index) in realTotalData" :key="finalCellKeyName.length?item[finalCellKeyName]:index">
<slot name="cell" :item="item" :index="index"/>
</component>
<component :is="nViewIs">
<slot name="footer"/>
</component>
</template>
<template v-else>
<slot />
</template>
<!-- 全屏Loading -->
<component :is="nViewIs" v-if="showLoading&&zSlots.loading&&!loadingFullFixed" :class="{'z-paging-content-fixed':usePageScroll}" style="flex:1" :style="[chatRecordRotateStyle]">
<slot name="loading" />
</component>
<!-- 上拉加载更多view -->
<component :is="nViewIs" v-if="!refresherOnly&&loadingMoreEnabled&&!showEmpty">
<!-- 聊天记录模式加载更多loading滚动到顶部加载更多或无更多数据时显示 -->
<template v-if="useChatRecordMode&&realTotalData.length>=defaultPageSize&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&realTotalData.length&&isChatRecordModeAndInversion">
<view :style="[chatRecordRotateStyle]">
<slot v-if="loadingStatus===M.NoMore&&zSlots.chatNoMore" name="chatNoMore" />
<template v-else>
<slot v-if="zSlots.chatLoading" :loadingMoreStatus="loadingStatus" name="chatLoading" />
<z-paging-load-more v-else @doClick="_onLoadingMore('click')" :zConfig="zLoadMoreConfig" />
</template>
</view>
</template>
<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:loadingMoreFixedHeight}:{}">
<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
<slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" />
<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" />
<view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
</view>
</component>
<!-- 空数据图 -->
<component :is="nViewIs" v-if="showEmpty" :class="{'z-paging-content-fixed':usePageScroll}" :style="[{flex:emptyViewCenter?1:0},emptyViewSuperStyle,chatRecordRotateStyle]">
<view :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}">
<slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed" />
<z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload"
:emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle"
:emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" :unit="unit"
@reload="_emptyViewReload" @viewClick="_emptyViewClick" />
</view>
</component>
<component :is="nViewIs" v-if="!hideNvueBottomTag" ref="zp-n-list-bottom-tag" class="zp-n-list-bottom-tag"></component>
</component>
<view v-if="zSlots.right" class="zp-page-right">
<slot name="right" />
</view>
</component>
<!-- 底部固定的slot -->
<view class="zp-page-bottom-container" :style="{'background': bottomBgColor}">
<slot name="bottom" />
<!-- 聊天记录模式底部占位 -->
<template v-if="useChatRecordMode&&autoAdjustPositionWhenChat">
<view :style="[{height:chatRecordModeSafeAreaBottom+'px'}]" />
<view class="zp-page-bottom-keyboard-placeholder-animate" :style="[{height:keyboardHeight+'px'}]" />
</template>
</view>
<!-- 点击返回顶部view -->
<view v-if="showBackToTopClass" :class="finalBackToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick">
<slot v-if="zSlots.backToTop" name="backToTop" />
<image v-else class="zp-back-to-top-img" :class="{'zp-back-to-top-img-inversion': useChatRecordMode&&!backToTopImg.length}" :src="backToTopImg.length?backToTopImg:base64BackToTop" />
</view>
<!-- 全屏Loading(铺满z-paging并固定) -->
<view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed">
<slot name="loading" />
</view>
</component>
<!-- #endif -->
</template>
<!-- #ifndef APP-NVUE -->
<!-- #ifdef APP-PLUS || MP-WEIXIN || MP-QQ || H5 -->
<script src="./wxs/z-paging-wxs.wxs" module="pagingWxs" lang="wxs"></script>
<!-- #endif -->
<!-- #endif -->
<script module="pagingRenderjs" lang="renderjs">
import pagingRenderjs from './wxs/z-paging-renderjs.js';
/**
* z-paging 分页组件
* @description 高性能全平台兼容支持虚拟列表支持nvuevue3
* @tutorial https://z-paging.zxlee.cn
* @notice 以下仅为部分常用属性方法和事件完整文档请查阅z-paging官网
* @property {Number|String} default-page-no 自定义初始的pageNo默认为1
* @property {Number|String} default-page-size 自定义pageSize默认为10
* @property {Object} paging-style 设置z-paging的style部分平台(如微信小程序)无法直接修改组件的style可使用此属性代替
* @property {String} height z-paging的高度优先级低于pagingStyle中设置的height传字符串如100px100rpx100%
* @property {String} width z-paging的宽度优先级低于pagingStyle中设置的width传字符串如100px100rpx100%
* @property {Boolean} use-page-scroll 使用页面滚动默认为否
* @property {Boolean} use-virtual-list 是否使用虚拟列表默认为否
* @property {Boolean} fixed z-paging是否使用fixed布局若使用fixed布局则z-paging的父view无需固定高度z-paging高度默认为100%默认为是(当使用内置scroll-view滚动时有效)
* @property {Boolean} auto [z-paging]mounted后是否自动调用reload方法(mounted后自动调用接口)默认为是
* @property {Boolean} use-chat-record-mode 使用聊天记录模式默认为否
* @event {Function} query 下拉刷新或滚动到底部时会自动触发此方法z-paging加载时也会触发(若要禁止请设置:auto="false")pageNo和pageSize会自动计算好直接传给服务器即可
* @example <z-paging ref="paging" v-model="dataList" @query="queryList"></z-paging>
*/
export default {
name:"z-paging",
// #ifndef APP-NVUE
// #ifdef APP-PLUS || H5
mixins: [pagingRenderjs],
// #endif
// #endif
}
</script>
<script src="./js/z-paging-main.js" />
<style scoped>
@import "./css/z-paging-main.css";
@import "./css/z-paging-static.css";
</style>

View File

@ -1,57 +0,0 @@
# z-paging
<p align="center">
<img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;" />
</p>
[![version](https://img.shields.io/badge/version-2.8.5-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
<img height="0" width="0" src="https://api.z-notify.zxlee.cn/v1/public/statistics/8293556910106066944/addOnly?from=uni" />
`z-paging-x`现已支持uniapp x持续完善中插件地址👉🏻 [https://ext.dcloud.net.cn/plugin?name=z-paging-x](https://ext.dcloud.net.cn/plugin?name=z-paging-x)
### 文档地址:[https://z-paging.zxlee.cn](https://z-paging.zxlee.cn)
### 更新组件前,请注意[版本差异](https://z-paging.zxlee.cn/start/upgrade-guide.html)
***
### 功能&特点
* 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。
* 【低耦合低侵入】分页自动管理。在page中无需处理任何分页相关逻辑无需在data中定义任何分页相关变量全由z-paging内部处理。
* 【超灵活支持各种类型自定义】支持自定义下拉刷新自定义上拉加载更多等各种自定义效果支持使用内置自动分页同时也支持通过监听下拉刷新和滚动到底部事件自行处理支持使用自带全屏布局规范同时也支持将z-paging自由放在任意容器中。
* 【功能丰富】支持国际化支持自定义且自动管理空数据图支持主题模式切换支持本地分页支持无闪动聊天分页模式支持展示最后更新时间支持吸顶效果支持内部scroll-view滚动与页面滚动支持一键滚动到顶部支持下拉进入二楼等诸多功能。
* 【【全平台兼容】支持vue&nvuevue2&vue3js&ts支持h5、app、鸿蒙Next及各家小程序。
* 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs在视图层实现下拉刷新支持虚拟列表轻松渲染百万级列表数据
***
### 反馈qq群
* 官方1群`已满`[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH)
* 官方2群`已满`[371624008](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=avPmibADf2TNi4LxkIwjCE5vbfXpa-r1&authKey=dQ%2FVDAR87ONxI4b32Py%2BvmXbhnopjHN7%2FJPtdsqJdsCPFZB6zDQ17L06Uh0kITUZ&noverify=0&group_code=371624008)
* 官方3群[343409055](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=sIaNqiCMIjxGQVksjytCw6R8DSiibHR7&authKey=pp995q8ZzFtl5F2xUwvvceP24QTcguWW%2FRVoDnMa8JZF4L2DmS%2B%2FV%2F5sYrcgPsmW&noverify=0&group_code=343409055)
***
### 预览
***
| 自定义下拉刷新效果演示 | 滑动切换选项卡+吸顶演示 | 聊天记录模式演示 |
| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ |
| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo5.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo6.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo7.gif) |
| 虚拟列表(流畅渲染1万+条)演示 | 下拉进入二楼演示 | 在弹窗内使用演示 |
| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ |
| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo8.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo9.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo10.gif) |
### 在线demo体验地址
* [https://demo.z-paging.zxlee.cn](https://demo.z-paging.zxlee.cn)
| 扫码体验 |
| ------------------------------------------------------------ |
| ![](https://z-paging.zxlee.cn/public/img/code.png) |
### demo下载
* 支持vue2&vue3的`选项式api`写法demo下载请点击页面右上角的【使用HBuilderX导入示例项目】或【下载示例项目ZIP】。
* 支持vue3的`组合式api`写法demo下载请访问[github](https://github.com/SmileZXLee/uni-z-paging)。

View File

@ -1,11 +0,0 @@
declare module 'vue' {
export interface GlobalComponents {
['z-paging']: typeof import('./comps/z-paging')['ZPaging']
['z-paging-swiper']: typeof import('./comps/z-paging-swiper')['ZPagingSwiper']
['z-paging-swiper-item']: typeof import('./comps/z-paging-swiper-item')['ZPagingSwiperItem']
['z-paging-empty-view']: typeof import('./comps/z-paging-empty-view')['ZPagingEmptyView']
['z-paging-cell']: typeof import('./comps/z-paging-cell')['ZPagingCell']
}
}
export {}

View File

@ -1,9 +0,0 @@
export interface AllowedComponentProps {
class?: unknown;
style?: unknown;
}
export interface VNodeProps {
key?: string | number | symbol;
ref?: unknown;
}

View File

@ -1,29 +0,0 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingCellProps {
/**
* z-paging-cell
*/
cellStyle?: Record<string, any>
}
// ****************************** Slots ******************************
declare interface ZPagingCellSlots {
// ******************** 主体布局Slot ********************
/**
* view
*/
['default']?: () => any
}
declare interface _ZPagingCell {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingCellProps
$slots: ZPagingCellSlots
}
}
export declare const ZPagingCell: _ZPagingCell

View File

@ -1,95 +0,0 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingEmptyViewProps {
/**
* z-pagingz-paging
* @default false
* @since 2.0.3
*/
emptyViewFixed?: boolean;
/**
*
* @default "没有数据哦~"
*/
emptyViewText?: string;
/**
* 使z-paging
* - 使"@""/"
*/
emptyViewImg?: string;
/**
*
* @default "重新加载"
* @since 1.6.7
*/
emptyViewReloadText?: string;
/**
* viewtop
* - fixed`margin-top`
*/
emptyViewStyle?: Record<string, any>;
/**
* img
*/
emptyViewImgStyle?: Record<string, any>;
/**
*
*/
emptyViewTitleStyle?: Record<string, any>;
/**
*
* @since 1.6.7
*/
emptyViewReloadStyle?: Record<string, any>;
/**
* ()
* @default false
* @since 1.6.7
*/
showEmptyViewReload?: boolean;
/**
*
* @default false
*/
isLoadFailed?: boolean;
/**
*
* @default 'rpx'
* @since 2.6.7
*/
unit?: 'rpx' | 'px';
// ****************************** Events ******************************
/**
*
*/
onReload?: () => void
/**
* view
* @since 2.3.3
*/
onViewClick?: () => void
}
declare interface _ZPagingEmptyView {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingEmptyViewProps
}
}
export declare const ZPagingEmptyView: _ZPagingEmptyView

View File

@ -1,95 +0,0 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingSwiperItemProps {
/**
* indexswiper
* @default 0
*/
tabIndex?: number
/**
* swiperindex
* @default 0
*/
currentIndex?: number
/**
* 使使nvuenvuez-paginglist
* @default false
*/
useVirtualList?: boolean
/**
* cell`fixed`cellcell
* @default 'fixed'
*/
cellHeightMode?: 'fixed' | 'dynamic'
/**
* ()dom()
* @default 12
*/
preloadPage?: number | string
/**
* 122
* @default 1
* @since 2.2.8
*/
virtualListCol?: number | string
/**
* scroll80
* @default 80
*/
virtualScrollFps?: number | string
/**
* z-paging(使)
* @default false
*/
useInnerList?: boolean
/**
* cellkey(nvuenvueuse-inner-list)
* @since 2.2.7
*/
cellKeyName?: string
/**
* innerList
*/
innerListStyle?: Record<string, any>
}
// ****************************** Methods ******************************
declare interface _ZPagingSwiperItemRef {
/**
* pageNo
*
* @param [animate=false]
*/
reload: (animate?: boolean) => void;
/**
*
* - completepageSize
*
* @param [data]
* @param [success=true]
*/
complete: (data?: any[] | false, success?: boolean) => void;
}
declare interface _ZPagingSwiperItem {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingSwiperItemProps
}
}
export declare const ZPagingSwiperItem: _ZPagingSwiperItem
export declare const ZPagingSwiperItemRef: _ZPagingSwiperItemRef

View File

@ -1,89 +0,0 @@
import { AllowedComponentProps, VNodeProps } from './_common'
// ****************************** Props ******************************
declare interface ZPagingSwiperProps {
/**
* 使fixed使fixedz-paging-swiperviewz-pagingviewz-paging-swiperview使slot="top"view使slot="bottom"
* @default true
*/
fixed?: boolean
/**
*
* @default false
*/
safeAreaInsetBottom?: boolean
/**
* z-paging-swiper
*/
swiperStyle?: Record<string, any>
}
// ****************************** Slots ******************************
declare interface ZPagingSwiperSlots {
// ******************** 主体布局Slot ********************
/**
* view
*/
['default']?: () => any
/**
* tab-view()slot="top"view
* viewviewviewslot="top"viewposition: fixed;
* @since 1.5.5
*/
['top']?: () => any
/**
* ()slot="bottom"view
* viewviewviewslot="bottom"viewposition: fixed;
* @since 1.6.2
*/
['bottom']?: () => any
/**
* ()slot="left"view
* viewviewviewslot="left"viewposition: fixed;
* @since 2.2.3
*/
['left']?: () => any
/**
* ()slot="right"view
* viewviewviewslot="right"viewposition: fixed;
* @since 2.2.3
*/
['right']?: () => any
}
// ****************************** Methods ******************************
declare interface _ZPagingSwiperRef {
/**
* slot="left"slot="right"slot="left"slot="right"
*
* @since 2.3.5
*/
updateLeftAndRightWidth: () => void;
/**
* fixedz-paging-swiperonShowiOS+h5+tabbar+fixed+tabbartabbar
*
* @since 2.6.5
*/
updateFixedLayout: () => void;
}
declare interface _ZPagingSwiper {
new (): {
$props: AllowedComponentProps &
VNodeProps &
ZPagingSwiperProps
$slots: ZPagingSwiperSlots
}
}
export declare const ZPagingSwiper: _ZPagingSwiper
export declare const ZPagingSwiperRef: _ZPagingSwiperRef

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +0,0 @@
/// <reference path="./comps.d.ts" />
declare module 'z-paging' {
export function install() : void
/**
* z-paging
* - uni.$zp
*
* @since 2.6.5
*/
interface $zp {
/**
*
*/
config : Record<string, any>;
}
global {
interface Uni {
$zp : $zp
}
}
}
declare type ZPagingSwiperRef = typeof import('./comps/z-paging-swiper')['ZPagingSwiperRef']
declare type ZPagingSwiperItemRef = typeof import('./comps/z-paging-swiper-item')['ZPagingSwiperItemRef']

View File

@ -20,7 +20,6 @@
"@uni-helper/uni-types", "@uni-helper/uni-types",
"@types/wechat-miniprogram", "@types/wechat-miniprogram",
"wot-design-uni/global.d.ts", "wot-design-uni/global.d.ts",
"z-paging/types",
"./src/typings.d.ts" "./src/typings.d.ts"
] ]
}, },
@ -36,5 +35,6 @@
"src/**/*.jsx", "src/**/*.jsx",
"src/**/*.vue", "src/**/*.vue",
"src/**/*.json" "src/**/*.json"
] ],
"files": ["src/pages-evaluation-sub/uni_modules/lime-echart/static/echarts.min.js"]
} }

View File

@ -55,7 +55,7 @@ export default ({ command, mode }) => {
routeBlockLang: 'json5', // 虽然设了默认值但是vue文件还是要加上 lang="json5", 这样才能很好地格式化 routeBlockLang: 'json5', // 虽然设了默认值但是vue文件还是要加上 lang="json5", 这样才能很好地格式化
// homePage 通过 vue 文件的 route-block 的type="home"来设定 // homePage 通过 vue 文件的 route-block 的type="home"来设定
// pages 目录为 src/pages分包目录不能配置在pages目录下 // pages 目录为 src/pages分包目录不能配置在pages目录下
subPackages: ['src/pages-sub', 'src/login-sub'], // 是个数组可以配置多个但是不能为pages里面的目录 subPackages: ['src/pages-sub', 'src/login-sub', 'src/pages-evaluation-sub'], // 是个数组可以配置多个但是不能为pages里面的目录
dts: 'src/types/uni-pages.d.ts', dts: 'src/types/uni-pages.d.ts',
}), }),
UniLayouts(), UniLayouts(),