@vtx/ember
Advanced tools
| <template> | ||
| <card title="指标趋势" style="margin-top: 20rpx;flex:none"> | ||
| <template v-slot:rightContent> | ||
| <view class="history-query-wrap"> | ||
| <view style="width: 160rpx"> | ||
| <v-select | ||
| :data=" | ||
| indicatorCycleFilterList.map(item => ({ | ||
| key: item.value, | ||
| value: item.label, | ||
| })) | ||
| " | ||
| v-model="collectFrequency" | ||
| ></v-select> | ||
| </view> | ||
| <view class="date-wrap"> | ||
| <view | ||
| class="date-item" | ||
| v-for="item in radioList" | ||
| :key="item.key" | ||
| :class="{ selected: item.key === type }" | ||
| @click="type = item.key" | ||
| >{{ item.value }} | ||
| </view> | ||
| </view> | ||
| </view> | ||
| </template> | ||
| <view style="width: 100%"> | ||
| <view | ||
| v-show="dataList.length !== 0" | ||
| style="height: 600rpx; width: calc(100vw - 80rpx)" | ||
| ref="echartsRef" | ||
| ></view> | ||
| <!-- <canvas | ||
| v-show="dataList.length !== 0" | ||
| canvas-id="uchart-canvas" | ||
| id="uchart-canvas" | ||
| @tap="tap" | ||
| /> --> | ||
| <u-empty | ||
| v-show="dataList.length == 0" | ||
| text="暂无内容" | ||
| style="height: 600rpx; width: 100%" | ||
| /> | ||
| </view> | ||
| </card> | ||
| </template> | ||
| <script> | ||
| import card from '../card-v2.vue'; | ||
| import mixins from '../mixins'; | ||
| import { request } from '@vtx/utils'; | ||
| import dayjs from 'dayjs'; | ||
| import * as echarts from 'echarts'; | ||
| import uCharts from '@qiun/ucharts'; | ||
| let uChartsInstance = {}; | ||
| export default { | ||
| components: { card }, | ||
| mixins: [mixins], | ||
| props: { | ||
| deviceId: String, | ||
| monitorItemCodes: String, | ||
| }, | ||
| data() { | ||
| return { | ||
| radioList: [ | ||
| { | ||
| key: '1', | ||
| value: '今日', | ||
| factors: [ | ||
| 'realtime', | ||
| 'minute1', | ||
| 'minute5', | ||
| 'minute10', | ||
| 'minute15', | ||
| 'minute30', | ||
| 'hour', | ||
| 'hour2', | ||
| 'hour4', | ||
| 'hour6', | ||
| 'hour12', | ||
| ], | ||
| startTime: dayjs().format('YYYY-MM-DD 00:00:00'), | ||
| endTime: dayjs().format('YYYY-MM-DD 23:59:59'), | ||
| }, | ||
| { | ||
| key: '7', | ||
| value: '近7天', | ||
| factors: [ | ||
| 'hour', | ||
| 'hour2', | ||
| 'hour4', | ||
| 'hour6', | ||
| 'hour12', | ||
| 'day', | ||
| 'minute30', | ||
| 'minute15', | ||
| 'minute10', | ||
| 'minute5', | ||
| 'minute1', | ||
| 'realtime', | ||
| ], | ||
| startTime: dayjs() | ||
| .subtract(7, 'day') | ||
| .format('YYYY-MM-DD 00:00:00'), | ||
| endTime: dayjs().format('YYYY-MM-DD 23:59:59'), | ||
| }, | ||
| ], | ||
| type: '1', | ||
| hisData: [], | ||
| dataList: [], | ||
| myChart: null, | ||
| // 检测项 | ||
| factorList: [], | ||
| // 所有统计周期枚举 | ||
| collectFrequencyList: [], | ||
| // 所有的指标周期下拉 | ||
| indicatorCycleList: [], | ||
| // 过滤后的指标周期下拉 | ||
| indicatorCycleFilterList: [], | ||
| collectFrequency: undefined, | ||
| }; | ||
| }, | ||
| computed: { | ||
| watchParams() { | ||
| return { | ||
| type: this.type, | ||
| deviceId: this.deviceId, | ||
| collectFrequency: this.collectFrequency, | ||
| monitorItemCodes: this.monitorItemCodes, | ||
| }; | ||
| }, | ||
| }, | ||
| watch: { | ||
| deviceId(val) { | ||
| if (val) { | ||
| // 拿到设备对应的因子配置 | ||
| this.getFactorList(); | ||
| // 因子配置的枚举 | ||
| this.getEnumList(); | ||
| } | ||
| }, | ||
| // 根据时间跨度 和 所有统计周期枚举 确定所有的指标周期下拉 | ||
| type(val) { | ||
| if (val) { | ||
| this.setIndicatorCycleList(); | ||
| } | ||
| }, | ||
| collectFrequencyList(val) { | ||
| if (val?.length) { | ||
| this.setIndicatorCycleList(); | ||
| } | ||
| }, | ||
| // 根据设备对应的因子配置 和 所有的指标周期下拉 和 因子 确认过滤后的指标周期下拉 并且默认第一个指标周期 | ||
| factorList(val) { | ||
| if (val?.length) { | ||
| this.setCollectFrequency(); | ||
| } | ||
| }, | ||
| indicatorCycleList(val) { | ||
| if (val?.length) { | ||
| this.setCollectFrequency(); | ||
| } | ||
| }, | ||
| monitorItemCodes(val) { | ||
| if (val) { | ||
| this.setCollectFrequency(); | ||
| } | ||
| }, | ||
| // 根据多个参数改变调接口 | ||
| watchParams() { | ||
| if (this.type && this.deviceId && this.collectFrequency && this.monitorItemCodes) { | ||
| this.hisDataByTime(); | ||
| } | ||
| }, | ||
| hisData(val) { | ||
| let _xAxis = [], | ||
| _yAxis = [], | ||
| _series = []; | ||
| const chartColor = [ | ||
| '#73A0FA', | ||
| '#73DEB3', | ||
| '#29A634', | ||
| '#9CBF30', | ||
| '#F3B825', | ||
| '#EB532E', | ||
| '#DB2C6F', | ||
| '#9179F2', | ||
| ]; | ||
| let textColor = '#929292'; | ||
| let bgColor = '#fff'; | ||
| let borderColor = '#fff'; | ||
| val.map((v, index) => { | ||
| const { dataList, monitorItemName, unit } = v; | ||
| if (!_xAxis?.length) { | ||
| _xAxis = dataList.map(d => d.timeDesc); | ||
| } | ||
| let values = dataList.map(d => d.value); | ||
| this.dataList = dataList; | ||
| let name = `${monitorItemName || ''}${unit ? `(${unit})` : ''}`; | ||
| let color = chartColor[index % chartColor.length]; | ||
| let markLine = undefined, | ||
| max = undefined; | ||
| if (val?.length == 1) { | ||
| const { alarmConfig } = v; | ||
| const { alarmList, alarmMax } = | ||
| this.getAlarmLineAreaData(alarmConfig, values) || []; | ||
| if (alarmList?.length) { | ||
| markLine = { | ||
| data: alarmList, | ||
| lineStyle: { | ||
| color: 'red', | ||
| width: 2, | ||
| }, | ||
| label: { | ||
| position: 'end', | ||
| distance: [10], | ||
| }, | ||
| }; | ||
| max = alarmMax; | ||
| } | ||
| } | ||
| _series.push({ | ||
| name, | ||
| data: values, | ||
| color, | ||
| type: 'line', | ||
| yAxisIndex: _yAxis.length, | ||
| markLine, | ||
| }); | ||
| _yAxis.push({ | ||
| name: name, | ||
| type: 'value', | ||
| nameLocation: 'middle', | ||
| nameTextStyle: { | ||
| color, | ||
| }, | ||
| nameGap: 0, | ||
| offset: _yAxis.length ? _yAxis.length * 45 : 0, | ||
| position: 'left', | ||
| alignTicks: true, | ||
| axisLabel: { | ||
| color, | ||
| margin: 12, | ||
| }, | ||
| axisLine: { | ||
| show: true, | ||
| lineStyle: { | ||
| color, | ||
| }, | ||
| }, | ||
| axisTick: { | ||
| show: false, | ||
| }, | ||
| minInterval: 1, | ||
| splitLine: { | ||
| lineStyle: { | ||
| type: 'dashed', | ||
| color: 'rgba(0,0,0,0.2)', | ||
| }, | ||
| }, | ||
| max, | ||
| }); | ||
| }); | ||
| const option = { | ||
| color: ['#0E85FF', '#49FF81', '#26D4FF', '#F5D629', '#C97CFF'], | ||
| toolbox: { | ||
| show: true, | ||
| top: 20, | ||
| feature: { | ||
| dataZoom: { | ||
| yAxisIndex: 'none', | ||
| }, | ||
| restore: {}, | ||
| magicType: { type: ['line', 'bar'] }, | ||
| saveAsImage: { | ||
| show: true, | ||
| excludeComponents: ['toolbox'], | ||
| pixelRatio: 2, | ||
| name: '图片', | ||
| }, | ||
| }, | ||
| }, | ||
| tooltip: { | ||
| trigger: 'axis', | ||
| backgroundColor: bgColor, | ||
| borderColor: borderColor, | ||
| textStyle: { | ||
| color: textColor, | ||
| }, | ||
| confine: true, | ||
| }, | ||
| xAxis: { | ||
| data: _xAxis, | ||
| type: 'category', | ||
| boundaryGap: true, | ||
| axisTick: { | ||
| show: true, | ||
| }, | ||
| axisLine: { | ||
| show: true, | ||
| }, | ||
| z: 10, | ||
| axisLabel: { | ||
| color: textColor, | ||
| }, | ||
| }, | ||
| legend: { | ||
| type: 'scroll', | ||
| top: 0, | ||
| left: 'center', | ||
| textStyle: { | ||
| color: textColor, | ||
| }, | ||
| }, | ||
| grid: { | ||
| top: 60, | ||
| left: 30, | ||
| right: 0, | ||
| bottom: 50, | ||
| containLabel: true, | ||
| }, | ||
| yAxis: _yAxis, | ||
| dataZoom: [ | ||
| { | ||
| // 这个dataZoom组件,默认控制x轴。 | ||
| type: 'slider', // 这个 dataZoom 组件是 slider 型 dataZoom 组件 | ||
| start: 0, // 左边在 10% 的位置。 | ||
| end: 100, // 右边在 60% 的位置。 | ||
| }, | ||
| { | ||
| // 这个dataZoom组件,也控制x轴。 | ||
| type: 'inside', // 这个 dataZoom 组件是 inside 型 dataZoom 组件 | ||
| start: 0, // 左边在 10% 的位置。 | ||
| end: 100, // 右边在 60% 的位置。 | ||
| }, | ||
| ], | ||
| series: _series, | ||
| }; | ||
| this.myChart.setOption(option); | ||
| // this.drawCharts('uchart-canvas', option); | ||
| }, | ||
| }, | ||
| mounted() { | ||
| this.initEcharts(); | ||
| }, | ||
| methods: { | ||
| initEcharts() { | ||
| this.myChart = echarts.init(this.$refs.echartsRef.$el); | ||
| }, | ||
| getEnumList() { | ||
| const { token } = this; | ||
| request | ||
| .get(`/cloud/device/common/enumList`, { | ||
| body: { | ||
| token, | ||
| className: 'FactorCollectFrequencyEnum', | ||
| }, | ||
| }) | ||
| .then(res => { | ||
| if (res?.result == 0 && res?.data?.length) { | ||
| this.collectFrequencyList = res.data || []; | ||
| } | ||
| }); | ||
| }, | ||
| getFactorList() { | ||
| const { deviceId, token } = this; | ||
| request | ||
| .get(`/cloud/device/monitor/factor/list`, { | ||
| body: { | ||
| token, | ||
| deviceId, | ||
| collectFrequencies: | ||
| 'realtime,minute1,minute5,minute10,minute15,minute30,hour,hour2,hour4,hour6,hour12,day,week,month,season,year', | ||
| }, | ||
| }) | ||
| .then(res => { | ||
| if (res?.result == 0 && res?.data?.length) { | ||
| this.factorList = res.data || []; | ||
| } | ||
| }); | ||
| }, | ||
| setIndicatorCycleList() { | ||
| if (this.type && this.collectFrequencyList?.length) { | ||
| const factors = this.radioList?.find(v => v?.key === this.type)?.factors; | ||
| const indicatorCycleList = []; | ||
| factors?.map(item => { | ||
| const obj = this.collectFrequencyList?.find(v => v?.code == item); | ||
| if (obj) { | ||
| indicatorCycleList.push({ | ||
| label: obj?.name, | ||
| value: obj?.code, | ||
| }); | ||
| } | ||
| }); | ||
| this.indicatorCycleList = indicatorCycleList; | ||
| } | ||
| }, | ||
| setCollectFrequency() { | ||
| if ( | ||
| this.factorList?.length && | ||
| this.monitorItemCodes && | ||
| this.indicatorCycleList?.length | ||
| ) { | ||
| let factors = this.factorList.filter( | ||
| item => item.monitorItemCode == this.monitorItemCodes, | ||
| ); | ||
| let showList = factors.map(item => item.collectFrequency); | ||
| let arr = this.indicatorCycleList.filter(item => showList.includes(item.value)); | ||
| this.indicatorCycleFilterList = arr; | ||
| if (arr.length) { | ||
| this.collectFrequency = arr?.[0]?.value; | ||
| } else { | ||
| this.collectFrequency = undefined; | ||
| this.indicatorCycleFilterList = []; | ||
| } | ||
| } | ||
| }, | ||
| hisDataByTime() { | ||
| const { deviceId, token, type, monitorItemCodes, collectFrequency } = this; | ||
| const { startTime, endTime } = this.radioList?.find(v => v?.key === type); | ||
| if (deviceId && collectFrequency && monitorItemCodes && startTime && endTime) { | ||
| request | ||
| .get(`/cloud/zhsw-jcss/api/facility/hisDataByTime`, { | ||
| body: { | ||
| deviceId, | ||
| collectFrequency, | ||
| token, | ||
| startTime, | ||
| endTime, | ||
| monitorItemCodes, | ||
| }, | ||
| }) | ||
| .then(res => { | ||
| if (res && !res.result && res?.data) { | ||
| this.hisData = res?.data; | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| getAlarmLineAreaData(data = [], values) { | ||
| let alarmList = [], | ||
| alarmValues = [], | ||
| alarmMax = 0; | ||
| const getName = op => { | ||
| let name = '阈值线'; | ||
| if (op.indexOf('>') !== -1) { | ||
| name = '上限'; | ||
| } else if (op.indexOf('<') !== -1) { | ||
| name = '下限'; | ||
| } | ||
| return name; | ||
| }; | ||
| const getMarkLineItem = (name, value) => { | ||
| return { | ||
| name, | ||
| yAxis: value, | ||
| label: { | ||
| formatter: '{b}:{c}', | ||
| position: 'insideEndTop', | ||
| color: 'red', | ||
| }, | ||
| }; | ||
| }; | ||
| if (data?.length) { | ||
| data.map(item => { | ||
| const alarmData = item?.ruleParam?.thresholdRuleDetails || []; | ||
| alarmData.length && | ||
| alarmData.map(val => { | ||
| let { op, params } = val; | ||
| if (params?.length === 2) { | ||
| alarmList.push(getMarkLineItem('下限', params[0])); | ||
| alarmList.push(getMarkLineItem('上限', params[1])); | ||
| } else { | ||
| alarmList.push(getMarkLineItem(getName(op), params[0])); | ||
| } | ||
| if (val.params && val.params.length) { | ||
| alarmValues.push(...val.params); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| if (alarmValues.length) { | ||
| const l = [...(values || []), ...(alarmValues || [])]; | ||
| alarmMax = Number((Math.max(...l) * 1.1).toFixed(1)); | ||
| } | ||
| return { alarmList, alarmMax }; | ||
| }, | ||
| drawCharts(id, option) { | ||
| const ctx = uni.createCanvasContext(id, this); | ||
| uChartsInstance[id] = new uCharts({ | ||
| type: 'line', | ||
| enableScroll: true, | ||
| color: option.color, | ||
| context: ctx, | ||
| width: uni.upx2px(630), | ||
| height: uni.upx2px(600), | ||
| categories: option.xAxis.data, | ||
| series: option.series, | ||
| xAxis: { | ||
| disableGrid: true, //不绘制纵向网格(即默认绘制网格) | ||
| scrollShow: true, // 是否显示滚动条,配合拖拽滚动使用(即仅在启用【基本配置】的 enableScroll 时有效) | ||
| itemCount: 2, // 单屏数据密度即图表可视区域内显示的X轴数据点数量,仅在启用enableScroll时有效 | ||
| scrollBackgroundColor: '#F8F8F8', // 滚动条背景颜色 | ||
| scrollColor: '#19BE6B', //滚动条颜色 | ||
| }, | ||
| yAxis: { | ||
| showTitle: true, | ||
| disableGrid: true, | ||
| splitNumber: 1, | ||
| data: option.yAxis?.map(v => ({ | ||
| title: v.name, | ||
| max: v.max, | ||
| tofix: 0, | ||
| titleFontColor: v.nameTextStyle.color, | ||
| textAlign: v.nameLocation, | ||
| })), | ||
| }, | ||
| legend: { show: false }, | ||
| // 手势缩放/平移(等价 inside) | ||
| extra: { | ||
| line: { | ||
| moveAble: true, // 手指拖移 | ||
| zoomAble: true, // 双指缩放 | ||
| scrollShow: true, // 是否显示滚动条 | ||
| }, | ||
| }, | ||
| }); | ||
| }, | ||
| tap(e) { | ||
| uChartsInstance[e.target.id].touchLegend(e); | ||
| uChartsInstance[e.target.id].showToolTip(e); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style lang="scss"> | ||
| .history-query-wrap { | ||
| display: flex; | ||
| justify-content: space-between; | ||
| align-items: center; | ||
| .date-wrap { | ||
| display: flex; | ||
| height: 50rpx; | ||
| background: #f0f2f5; | ||
| border-radius: 8rpx; | ||
| font-size: 28rpx; | ||
| font-weight: 400; | ||
| color: rgba(0, 0, 0, 0.45); | ||
| align-items: center; | ||
| .date-item { | ||
| display: flex; | ||
| align-items: center; | ||
| height: 100%; | ||
| padding: 0 20rpx; | ||
| &.selected { | ||
| color: #ffffff; | ||
| background: #1890ff; | ||
| box-shadow: 0rpx 1rpx 4rpx 1rpx rgba(0, 0, 0, 0.16); | ||
| border-radius: 4rpx 4rpx 4rpx 4rpx; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ::v-deep { | ||
| uni-canvas { | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
| } | ||
| </style> |
+7
-3
| { | ||
| "name": "@vtx/ember", | ||
| "version": "3.0.9", | ||
| "version": "3.0.10", | ||
| "scripts": { | ||
@@ -63,2 +63,3 @@ "start": "vuepress dev docs", | ||
| "@types/node": "^20.8.10", | ||
| "@vtx/utils": "^1.0.0", | ||
| "@vue/runtime-core": "^3.2.45", | ||
@@ -97,5 +98,8 @@ "@vuepress/client": "^2.0.0-rc.0", | ||
| "vuepress": "^2.0.0-rc.0", | ||
| "@vtx/utils": "^1.0.0" | ||
| "@qiun/ucharts": "^2.5.0-20230101" | ||
| }, | ||
| "dependencies": {} | ||
| "dependencies": {}, | ||
| "files": [ | ||
| "components" | ||
| ] | ||
| } |
| # all files | ||
| [*] | ||
| charset = utf-8 | ||
| end_of_line = lf | ||
| indent_style = space | ||
| indent_size = 4 | ||
| insert_final_newline = true | ||
| trim_trailing_whitespace = true |
Sorry, the diff of this file is not supported yet
-53
| module.exports = { | ||
| root: true, | ||
| env: { | ||
| browser: true, | ||
| es6: true, | ||
| }, | ||
| extends: ['plugin:vue/essential'], | ||
| parserOptions: { | ||
| parser: 'babel-eslint', | ||
| }, | ||
| plugins: ['vue'], | ||
| rules: { | ||
| /** | ||
| * 禁用 console | ||
| */ | ||
| 'no-console': [2, { allow: ['warn', 'error'] }], | ||
| /** | ||
| * 禁止重复定义类的成员 | ||
| */ | ||
| 'no-dupe-class-members': 'error', | ||
| /** | ||
| * 禁止对象字面量中出现重复的 key | ||
| */ | ||
| 'no-dupe-keys': 'error', | ||
| /** | ||
| * 禁止重复导入模块 | ||
| */ | ||
| 'no-duplicate-imports': 'error', | ||
| /** | ||
| * 禁止出现空语句块 | ||
| */ | ||
| 'no-empty': 'error', | ||
| /** | ||
| * 禁止解构赋值中出现空 {} 或 [] | ||
| */ | ||
| 'no-empty-pattern': 'error', | ||
| /** | ||
| * 禁止不必要的布尔转换 | ||
| */ | ||
| 'no-extra-boolean-cast': 'error', | ||
| /** | ||
| * switch 的 case 内必须有 break, return 或 throw,空的 case 除外 | ||
| */ | ||
| 'no-fallthrough': 'error', | ||
| /** | ||
| * 禁止使用 var | ||
| */ | ||
| 'no-var': 'error', | ||
| 'vue/valid-template-root': 'off', | ||
| 'vue/no-v-html': 'off', | ||
| 'vue/no-v-text-v-html-on-component': 'off' | ||
| }, | ||
| }; |
| pages: | ||
| cache: | ||
| paths: | ||
| - node_modules/ | ||
| script: | ||
| - yarn install --registry=https://registry.npm.taobao.org --pure-lockfile | ||
| - yarn build | ||
| - yarn example:build | ||
| - mv doc-dist public | ||
| - mv dist/build/h5 public/example | ||
| artifacts: | ||
| paths: | ||
| - public | ||
| tags: | ||
| - node-docker | ||
| only: | ||
| - master |
| { // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ | ||
| // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 | ||
| "version": "0.0", | ||
| "configurations": [{ | ||
| "default" : | ||
| { | ||
| "launchtype" : "local" | ||
| }, | ||
| "mp-weixin" : | ||
| { | ||
| "launchtype" : "local" | ||
| }, | ||
| "type" : "uniCloud" | ||
| } | ||
| ] | ||
| } |
Sorry, the diff of this file is not supported yet
| module.exports = { | ||
| // 一行最多 100 字符 | ||
| printWidth: 100, | ||
| // 使用 4 个空格缩进 | ||
| tabWidth: 4, | ||
| // 使用单引号 | ||
| singleQuote: true, | ||
| // 末尾需要逗号 | ||
| trailingComma: 'all', | ||
| proseWrap: 'never', | ||
| // 换行符使用 lf | ||
| endOfLine: 'lf', | ||
| }; |
| { | ||
| "extends": [ | ||
| "stylelint-config-standard", | ||
| "stylelint-config-rational-order", | ||
| "stylelint-config-prettier" | ||
| ], | ||
| "plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"], | ||
| "rules": { | ||
| "declaration-empty-line-before": null, | ||
| "at-rule-no-unknown": null, | ||
| "selector-type-no-unknown": [ | ||
| true, | ||
| { | ||
| "ignoreTypes": ["/^components/"] | ||
| } | ||
| ], | ||
| "unit-no-unknown": [true, { "ignoreUnits": ["rpx"] }] | ||
| } | ||
| } |
| { | ||
| "files.eol": "\n", | ||
| "editor.tabSize": 4, | ||
| "editor.formatOnSave": true, | ||
| "editor.defaultFormatter": "esbenp.prettier-vscode", | ||
| "css.validate": false, | ||
| "less.validate": false, | ||
| "scss.validate": false, | ||
| "editor.codeActionsOnSave": { | ||
| "source.fixAll.eslint": "explicit" | ||
| }, | ||
| "git.ignoreLimitWarning": true, | ||
| "i18n-ally.localesPaths": [ | ||
| "src/locales" | ||
| ], | ||
| "i18n-ally.enabledParsers": [ | ||
| "json" | ||
| ], | ||
| // 语言 | ||
| // key 风格 | ||
| "i18n-ally.keystyle": "nested", | ||
| "i18n-ally.sortKeys": true, | ||
| "i18n-ally.displayLanguage": "zh-CN", | ||
| // 自动提取 | ||
| "i18n-ally.extract.autoDetect": true, | ||
| "i18n-ally.annotationInPlace": false, | ||
| // 1. 文件名 → locale 映射(告诉插件“zh-CN.json”就是 locale: zh-CN) | ||
| "i18n-ally.localeCountryMap": { | ||
| "zh-CN": "zh-CN", | ||
| "en": "en" | ||
| }, | ||
| /* ===== 新增 key 时同时写所有语言包 ===== */ | ||
| "i18n-ally.extract.targetPickingStrategy": "none", | ||
| /* ===== 禁止再建 _locales 目录 ===== */ | ||
| "i18n-ally.pathMatcher": "{locale}.{ext}", | ||
| /* ===== 缺失译文用中文自动填充 ===== */ | ||
| "i18n-ally.copyFrom": "zh-CN", | ||
| /* ===== JSON 格式化 ===== */ | ||
| "[jsonc]": { | ||
| "editor.defaultFormatter": "vscode.json-language-features" | ||
| }, | ||
| "[vue]": { | ||
| "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| } | ||
| } |
| module.exports = { | ||
| plugins: [ | ||
| '@babel/plugin-proposal-optional-chaining', | ||
| '@babel/plugin-proposal-nullish-coalescing-operator', | ||
| ], | ||
| }; |
| module.exports = { | ||
| extends: ['@commitlint/config-conventional'], | ||
| rules: { | ||
| 'type-enum': [ | ||
| 2, | ||
| 'always', | ||
| [ | ||
| 'chore', | ||
| 'build', | ||
| 'ci', | ||
| 'docs', | ||
| 'feat', | ||
| 'fix', | ||
| 'perf', | ||
| 'refactor', | ||
| 'style', | ||
| 'test', | ||
| 'release', | ||
| ], | ||
| ], | ||
| }, | ||
| }; |
-144
| ## 目录约定 | ||
| ``` | ||
| ├── components # 组件 | ||
| │ └── v-text | ||
| │ └── v-text.vue | ||
| ├── docs # 在线文档 | ||
| │ └── components | ||
| │ └── text.md # 组件对应的文档 v-text.vue 对应 text.md | ||
| ├── src # 组件示例 | ||
| ├── CHANGELOG.md # CHANGELOG 根据指令自动生成 | ||
| ├── README.md | ||
| └── vue.config.js # example 配置 | ||
| ``` | ||
| ## 组件开发约定 | ||
| ### 组件写在 `components` 目录下 | ||
| 所有的组件写在 `components` 目录下,并符合 `components/组件名称/组件名称.vue` 目录结构。可参考[easycom 组件规范](https://uniapp.dcloud.io/component/README?id=easycom%e7%bb%84%e4%bb%b6%e8%a7%84%e8%8c%83) | ||
| ### 需兼容小程序和 H5 | ||
| - [小程序不支持列表](https://uniapp.dcloud.io/vue-components?id=%e5%b0%8f%e7%a8%8b%e5%ba%8f%e4%b8%8d%e6%94%af%e6%8c%81%e5%88%97%e8%a1%a8) | ||
| - [命名限制](https://uniapp.dcloud.io/vue-components?id=%e5%91%bd%e5%90%8d%e9%99%90%e5%88%b6) | ||
| ### 组件命名必须以 `v-` 开头 | ||
| 所有的组件命名必须以 `v-` 开头,举例我们需开发一个树形控件,我们在 `components` 目录下新建 `v-tree/v-tree.vue` | ||
| ## Doc 在线文档约定 | ||
| 在线文档通过 [VuePress](https://vuepress.vuejs.org/zh/) 搭建。每个组件都必须提供一个使用文档(md 文件),目录为 `docs/components/*` 下 | ||
| 文档结构大致如下,标题 -> 代码示例 -> API -> example | ||
| ``` | ||
| # 组件标题 | ||
| 组件说明 | ||
| ## 代码示例 | ||
| ### 小标题 | ||
| // 示例代码,代码块 | ||
| ## API | ||
| 表格 | ||
| ``` | ||
| ## 提交流程规范 | ||
| 代码提交前必须执行`yarn lint`,校验通过过才可提交。提交信息必须遵循 [Angular 提交信息规范](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit) 。版本更新和 生成 `CHANGELOG.md` 通过`yarn release`指令执行 | ||
| ## scripts 说明 | ||
| ``` | ||
| # 启动在线文档服务 | ||
| $ yarn start | ||
| # 打包在线文档 | ||
| $ yarn build | ||
| # 组件库示例服务 | ||
| $ yarn example:dev | ||
| # 打包组件库示例 | ||
| $ yarn example:build | ||
| # 校验 | ||
| $ yarn lint | ||
| # 版本发布 | ||
| $ yarn release | ||
| ``` | ||
| ## 添加一个 `v-text` 组件 | ||
| 1. 在 `components` 目录下新建 `v-text/v-text.vue` | ||
| ```vue | ||
| <template> | ||
| <view></view> | ||
| </template> | ||
| ``` | ||
| 2. 新增示例界面,在 src/pages 目录下新建 `v-text/index.vue` | ||
| 3. 在 src/pages.json 对刚新增的示例页面进行配置 | ||
| ```json | ||
| { | ||
| "pages": [ | ||
| { | ||
| "path": "pages/v-text/index", | ||
| "style": { | ||
| "navigationStyle": "custom" | ||
| } | ||
| } | ||
| ], | ||
| ... | ||
| } | ||
| ``` | ||
| 4. 新增 `v-text` 文档,在 `docs/components` 目录下新建 `text.md` | ||
| ``` | ||
| # Text 文字 | ||
| 组件说明 | ||
| ## 代码示例 | ||
| ### 小标题 | ||
| // 示例代码,代码块 | ||
| ## API | ||
| 表格 | ||
| ``` | ||
| 5. 配置文档让其显示,在 `docs/.vuepress/config.js` 内添加配置 | ||
| ```js | ||
| module.exports = (options, context) => ({ | ||
| themeConfig: { | ||
| sidebar: { | ||
| '/component': [ | ||
| '/components/installation', | ||
| { | ||
| title: '组件', | ||
| collapsable: false, | ||
| sidebarDepth: 0, | ||
| children: ['/components/text'], | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
| }); | ||
| ``` |
Sorry, the diff of this file is not supported yet
| <template> | ||
| <div class="demo-page"> | ||
| <div class="page-content"> | ||
| <iframe :src="src" frameborder="0" /> | ||
| </div> | ||
| </div> | ||
| </template> | ||
| <style lang="less"> | ||
| .demo-page { | ||
| position: fixed; | ||
| top: 80px; | ||
| right: 20px; | ||
| width: 310px; | ||
| height: 605.909091px; | ||
| box-sizing: border-box; | ||
| background-image: url('./iphoneX.png'); | ||
| background-repeat: no-repeat; | ||
| background-size: 100%; | ||
| padding: 64px 21px 22px 23px; | ||
| } | ||
| @media (min-width: 1600px) { | ||
| .demo-page { | ||
| right: 20px; | ||
| width: 330px; | ||
| height: 645px; | ||
| } | ||
| } | ||
| .page-content { | ||
| box-sizing: border-box; | ||
| width: 100%; | ||
| height: 100%; | ||
| iframe { | ||
| height: 100%; | ||
| width: 100%; | ||
| } | ||
| } | ||
| </style> | ||
| <script> | ||
| export default { | ||
| props: { | ||
| src: String, | ||
| }, | ||
| data() { | ||
| return {}; | ||
| }, | ||
| methods: {}, | ||
| }; | ||
| </script> |
| import { defaultTheme } from 'vuepress'; | ||
| import { getDirname, path } from '@vuepress/utils'; | ||
| import { registerComponentsPlugin } from '@vuepress/plugin-register-components'; | ||
| const __dirname = getDirname(import.meta.url); | ||
| export default { | ||
| lang: 'zh-CN', | ||
| title: '@vtx/ember', //系统标题 | ||
| description: '加载失败', // 系统logo加载失败显示 | ||
| base: '/ember-plus/', | ||
| theme: defaultTheme({ | ||
| repoLabel: 'GitHub', // github | ||
| navbar: [ | ||
| { text: '3.x组件', link: '/components/installation' }, | ||
| { | ||
| text: '1.x组件', | ||
| link: 'http://front-end.pages.cloudhw.cn:8000/ember/components/installation.html', | ||
| }, | ||
| { | ||
| text: '2.x组件', | ||
| link: 'http://yanshi.huanwei.envcloud.com.cn:8003/ember-next/components/installation.html', | ||
| }, | ||
| ], // 头部菜单 | ||
| sidebar: { | ||
| '/component': [ | ||
| '/components/installation', | ||
| { | ||
| title: '3.x组件', | ||
| collapsable: false, | ||
| sidebarDepth: 0, | ||
| children: [ | ||
| '/components/date-picker', | ||
| '/components/image', | ||
| '/components/multiple-select', | ||
| '/components/select', | ||
| '/components/tree-select', | ||
| '/components/upload', | ||
| '/components/dropdown', | ||
| '/components/virtual-select', | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| editLink: false, | ||
| editLinkText: '编辑此页', | ||
| editLinkPattern: '', | ||
| sidebarDepth: 2, | ||
| lastUpdated: true, | ||
| lastUpdatedText: '更新时间', | ||
| contributors: false, | ||
| contributorsText: '贡献者', | ||
| notFound: [ | ||
| '这里什么都没有', | ||
| '我们怎么到这来了?', | ||
| '这是一个 404 页面', | ||
| '看起来我们进入了错误的链接', | ||
| ], | ||
| backToHome: '返回首页', | ||
| // a11y | ||
| openInNewWindow: '在新窗口打开', | ||
| toggleDarkMode: '切换夜间模式', | ||
| toggleSidebar: '切换侧边栏', | ||
| }), | ||
| plugins: [ | ||
| registerComponentsPlugin({ | ||
| componentsDir: path.resolve(__dirname, './components'), | ||
| }), | ||
| ], | ||
| }; |
| --- | ||
| title: DatePicker 日期选择框 | ||
| --- | ||
| # DatePicker 日期选择框 | ||
| 选择日期的控件,基于 [uView Picker 选择器](https://www.uviewui.com/components/picker.html) 二次封装。 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-date-picker/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-date-picker v-model="date" maxDate="2022"></v-date-picker> | ||
| //年 | ||
| <v-date-picker type="year" v-model="date1"></v-date-picker> | ||
| //月 | ||
| <v-date-picker type="month" v-model="date2"></v-date-picker> | ||
| //日 | ||
| <v-date-picker type="date" v-model="date3"></v-date-picker> | ||
| //年月日时分秒 | ||
| <v-date-picker type="datetime" v-model="date4"></v-date-picker> | ||
| //时分秒 | ||
| <v-date-picker type="time" v-model="date5"></v-date-picker> | ||
| //时分 | ||
| <v-date-picker type="hourMinute" v-model="date6"></v-date-picker> | ||
| //重置功能 | ||
| <u-button @click="reset">重置</u-button> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import dayjs from 'dayjs'; | ||
| export default { | ||
| watch: { | ||
| date(val) { | ||
| console.log('date', val); | ||
| }, | ||
| date1(val) { | ||
| console.log('date1', val); | ||
| }, | ||
| date2(val) { | ||
| console.log('date2', val); | ||
| }, | ||
| date3(val) { | ||
| console.log('date3', val); | ||
| }, | ||
| date4(val) { | ||
| console.log('date4', val); | ||
| }, | ||
| date5(val) { | ||
| console.log('date5', val); | ||
| }, | ||
| date6(val) { | ||
| console.log('date6', val); | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| date: dayjs().format('YYYY-MM-DD'), | ||
| date1: dayjs().format('YYYY'), | ||
| date2: dayjs().format('YYYY-MM'), | ||
| date3: dayjs().format('YYYY-MM-DD'), | ||
| date4: dayjs().format('YYYY-MM-DD HH:mm:ss'), | ||
| date5: dayjs().format('HH:mm:ss'), | ||
| date6: dayjs().format('HH:mm'), | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.date1 = '2022'; | ||
| this.date2 = '2022-01'; | ||
| this.date3 = '2022-01-01'; | ||
| this.date4 = '2022-01-01 23:59:59'; | ||
| this.date5 = '23:59:59'; | ||
| this.date6 = '23:59'; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| ### Props | ||
| | 参数 | 说明 | 类型 | 默认值 | 可选值 | | ||
| | --- | --- | --- | --- | --- | | ||
| | v-model | 指定当前选中的日期 | String | - | - | | ||
| | type | 选择器类型 | String | date | date / month / year / datetime / time | | ||
| | disabled | 是否禁用选择 | Boolean | false | - | | ||
| | placeholder | 文本 placeholder 显示值 | string | - | - | | ||
| | border | 文本是否有边框 | Boolean | false | - | | ||
| | input | 是否展示 input | Boolean | true | - | | ||
| | maxDate | 可选的最大时间 | Number | - | - | | ||
| | minDate | 可选的最小时间 | Number | - | - | | ||
| | minHour | 可选的最小小时,仅 mode=time 有效 | Number | - | - | | ||
| | maxHour | 可选的最大小时,仅 mode=time 有效 | Number | - | - | | ||
| | minMinute | 可选的最小分钟,仅 mode=time 有效 | Number | - | - | | ||
| | maxMinute | 可选的最大分钟,仅 mode=time 有效 | Number | - | - | | ||
| | minSecond | 可选的最小秒钟,仅 mode=time 有效 | Number | - | - | | ||
| | maxSecond | 可选的最大秒钟,仅 mode=time 有效 | Number | - | - | | ||
| ### Events | ||
| | 事件名 | 说明 | 回调参数 | 默认值 | | ||
| | ------ | -------------------------------- | -------------------- | ------------ | | ||
| | change | 时间改变时触发,返回当前选择的值 | Function(dateString) | 当前选中的值 | |
| --- | ||
| title: Dropdown 下拉菜单 | ||
| --- | ||
| # Dropdown 下拉菜单 | ||
| 基于 [u-dropdown 下拉菜单](https://v1.uviewui.com/components/dropdown.html)封装 。 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-dropdown/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| <view class="u-m-p-50"> | ||
| <view class="u-demo-area u-flex u-row-center"> | ||
| <v-dropdown | ||
| :close-on-click-mask="mask" | ||
| ref="uDropdown" | ||
| :activeColor="activeColor" | ||
| :borderBottom="borderBottom" | ||
| > | ||
| <v-dropdown-item | ||
| @change="change" | ||
| v-model="value1" | ||
| title="距离" | ||
| :options="options1" | ||
| :titleSize="14" | ||
| ></v-dropdown-item> | ||
| <v-dropdown-item | ||
| @change="change" | ||
| v-model="value2" | ||
| title="温度" | ||
| :options="options2" | ||
| ></v-dropdown-item> | ||
| <v-dropdown-item title="属性"> | ||
| <view class="slot-content"> | ||
| <view class="item-box"> | ||
| <view | ||
| class="item" | ||
| :class="[item.active ? 'active' : '']" | ||
| @tap="tagClick(index)" | ||
| v-for="(item, index) in list" | ||
| :key="index" | ||
| > | ||
| {{ item.label }} | ||
| </view> | ||
| </view> | ||
| <u-button type="primary" @click="closeDropdown">确定</u-button> | ||
| </view> | ||
| </v-dropdown-item> | ||
| </v-dropdown> | ||
| </view> | ||
| </view> | ||
| <view class="u-config-wrap"> | ||
| <view class="u-config-title u-border-bottom"> 参数配置 </view> | ||
| <view class="u-config-item"> | ||
| <view class="u-item-title">下边框</view> | ||
| <u-subsection | ||
| :current="curNow" | ||
| :list="['有', '无']" | ||
| @change="borderChange" | ||
| ></u-subsection> | ||
| </view> | ||
| <view class="u-config-item"> | ||
| <view class="u-item-title">激活颜色</view> | ||
| <u-subsection | ||
| :current="curNow2" | ||
| :list="['#2979ff', '#ff9900', '#19be6b']" | ||
| @change="activeColorChange" | ||
| ></u-subsection> | ||
| </view> | ||
| <view class="u-config-item"> | ||
| <view class="u-item-title">遮罩是否可点击</view> | ||
| <u-subsection | ||
| :current="curNow3" | ||
| :list="['是', '否']" | ||
| @change="maskChange" | ||
| ></u-subsection> | ||
| </view> | ||
| </view> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| return { | ||
| curNow: 1, | ||
| curNow2: 0, | ||
| curNow3: 0, | ||
| value1: '', | ||
| value2: '2', | ||
| mask: true, | ||
| options1: [ | ||
| { | ||
| label: '默认排序', | ||
| value: 1, | ||
| }, | ||
| { | ||
| label: '距离优先', | ||
| value: 2, | ||
| }, | ||
| { | ||
| label: '价格优先', | ||
| value: 3, | ||
| }, | ||
| ], | ||
| options2: [ | ||
| { | ||
| label: '去冰', | ||
| value: 1, | ||
| }, | ||
| { | ||
| label: '加冰', | ||
| value: 2, | ||
| }, | ||
| { | ||
| label: '正常温', | ||
| value: 3, | ||
| }, | ||
| { | ||
| label: '加热', | ||
| value: 4, | ||
| }, | ||
| { | ||
| label: '极寒风暴', | ||
| value: 5, | ||
| }, | ||
| ], | ||
| list: [ | ||
| { | ||
| label: '琪花瑶草', | ||
| active: true, | ||
| }, | ||
| { | ||
| label: '清词丽句', | ||
| active: false, | ||
| }, | ||
| { | ||
| label: '宛转蛾眉', | ||
| active: false, | ||
| }, | ||
| { | ||
| label: '煦色韶光', | ||
| active: false, | ||
| }, | ||
| { | ||
| label: '鱼沉雁落', | ||
| active: false, | ||
| }, | ||
| { | ||
| label: '章台杨柳', | ||
| active: false, | ||
| }, | ||
| { | ||
| label: '霞光万道', | ||
| active: false, | ||
| }, | ||
| ], | ||
| borderBottom: false, | ||
| activeColor: '#2979ff', | ||
| }; | ||
| }, | ||
| methods: { | ||
| borderChange(index) { | ||
| this.curNow = index; | ||
| this.borderBottom = !index; | ||
| }, | ||
| activeColorChange(index) { | ||
| this.curNow2 = index; | ||
| this.activeColor = ['#2979ff', '#ff9900', '#19be6b'][index]; | ||
| }, | ||
| change(index) { | ||
| this.$u.toast(`点击了第${index}项`); | ||
| }, | ||
| closeDropdown() { | ||
| this.$refs.uDropdown.close(); | ||
| }, | ||
| tagClick(index) { | ||
| this.list[index].active = !this.list[index].active; | ||
| }, | ||
| maskChange(index) { | ||
| this.curNow3 = index; | ||
| this.mask = !index; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style scoped lang="scss"> | ||
| .u-config-wrap { | ||
| padding: 40rpx; | ||
| .u-config-title { | ||
| text-align: center; | ||
| font-size: 16px; | ||
| font-weight: bold; | ||
| margin: 10px 0; | ||
| padding: 5px; | ||
| } | ||
| .u-item-title { | ||
| padding: 12rpx; | ||
| } | ||
| .u-config-item { | ||
| padding: 24rpx 0; | ||
| } | ||
| } | ||
| .slot-content { | ||
| background-color: #ffffff; | ||
| padding: 24rpx; | ||
| .item-box { | ||
| margin-bottom: 50rpx; | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| justify-content: space-between; | ||
| .item { | ||
| border: 1px solid #2979ff; | ||
| color: #2979ff; | ||
| padding: 8rpx 40rpx; | ||
| border-radius: 100rpx; | ||
| margin-top: 30rpx; | ||
| font-size: 28rpx; | ||
| } | ||
| .active { | ||
| color: #ffffff; | ||
| background-color: #2979ff; | ||
| } | ||
| } | ||
| } | ||
| </style> | ||
| ``` | ||
| ## API | ||
| 详细 API 请参考 [Dropdown 下拉菜单](https://v1.uviewui.com/components/dropdown.html),使用上无任何差异 |
| --- | ||
| title: Image 图片 | ||
| --- | ||
| # Image 图片 | ||
| 基于 [uView Image 图片](https://www.uviewui.com/components/image.html) 内置点击预览,预览方式 API 为 [uni.previewImage(OBJECT)](https://uniapp.dcloud.io/api/media/image?id=unipreviewimageobject)。 | ||
| 结合 `v-image-previewer` 使用支持多张图片预览。 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-image/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-image width="100%" height="300rpx" :src="src"></v-image> | ||
| //多张图片预览 | ||
| <v-image-previewer> | ||
| <v-image width="100%" height="300rpx" :src="src" style="margin-bottom: 8px"></v-image> | ||
| <v-image | ||
| width="100%" | ||
| height="300rpx" | ||
| src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/auto-orient,1/resize,p_10/quality,q_10" | ||
| ></v-image> | ||
| </v-image-previewer> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| data() { | ||
| return { | ||
| src: | ||
| 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=273034283,2961511002&fm=26&gp=0.jpg', | ||
| }; | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| 详细 API 请参考 [uView Image 图片](https://www.uviewui.com/components/image.html),使用上无任何差异 |
| --- | ||
| title: 快速上手 | ||
| sidebarDepth: 0 | ||
| --- | ||
| # 快速上手 | ||
| ::: warning | ||
| ## 使用注意 | ||
| 因为本组件库很多都是基于 [uView](https://www.uviewui.com/components/intro.html) 做的一个二次封装,所以在使用该组件时需先引入 uView | ||
| 1. main.js 引入 uView 库 | ||
| ```js | ||
| // main.js | ||
| import uviewPlus from 'uview-plus'; | ||
| app.use(uviewPlus); | ||
| ``` | ||
| 2. `App.vue` 引入基础样式(注意 style 标签需声明 scss 属性支持) | ||
| ```vue | ||
| /* App.vue */ | ||
| <style lang="scss"> | ||
| @import 'uview-plus/index.scss'; | ||
| </style> | ||
| ``` | ||
| 3. `uni.scss` 引入全局 scss 变量文件 | ||
| ```scss | ||
| /* uni.scss */ | ||
| @import 'uview-plus/theme.scss'; | ||
| ``` | ||
| 4. `pages.json` 配置 easycom 规则(按需引入) | ||
| ```json | ||
| // pages.json | ||
| { | ||
| "easycom": { | ||
| "^u-(.*)": "uview-plus/components/u-$1/u-$1.vue" | ||
| }, | ||
| // 此为本身已有的内容 | ||
| "pages": [ | ||
| // ...... | ||
| ] | ||
| } | ||
| ``` | ||
| ::: | ||
| ## Yarn 安装 | ||
| ```shell | ||
| yarn add @vtx/ember -S | ||
| ``` | ||
| ## 使用方法 | ||
| `pages.json` 配置 easycom 规则(按需引入) | ||
| ```json | ||
| // pages.json | ||
| // pages.json | ||
| { | ||
| "easycom": { | ||
| // 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175 | ||
| "custom": { | ||
| "^u--(.*)": "uview-plus/components/u-$1/u-$1.vue", | ||
| "^up-(.*)": "uview-plus/components/u-$1/u-$1.vue", | ||
| "^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue" | ||
| } | ||
| }, | ||
| // 此为本身已有的内容 | ||
| "pages": [ | ||
| // ...... | ||
| ] | ||
| } | ||
| ``` | ||
| 配置 easycom 规则后,自动按需引入,无需 `import` 组件,直接引用即可。 | ||
| ```vue | ||
| <template> | ||
| <v-multiple-select></v-multiple-select> | ||
| </template> | ||
| ``` | ||
| <!-- <Mobile src="http://front-end.pages.cloudhw.cn:8000/ember/example/#/"/> --> | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/"/> |
| --- | ||
| title: MultipleSelect 下拉多选 | ||
| --- | ||
| # MultipleSelect 下拉多选 | ||
| 此选择器用于单列多选场景,常用于表单中使用。 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-multiple-select/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-multiple-select :data="list" v-model="checkedKeys"></v-multiple-select> | ||
| <v-multiple-select :data="list" v-model="checkedKeys1"></v-multiple-select> | ||
| <v-multiple-select | ||
| :data="list" | ||
| v-model="checkedKeys2" | ||
| border="surround" | ||
| ></v-multiple-select> | ||
| //自定义确认颜色和字体 | ||
| <v-multiple-select :data="[]" confirmText="提交" confirmColor="#18b566"></v-multiple-select> | ||
| //重置功能 | ||
| <u-button @click="reset">重置</u-button> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| data() { | ||
| const data = []; | ||
| for (let i = 10; i < 36; i++) { | ||
| data.push({ | ||
| key: i.toString(36) + i, | ||
| value: i.toString(36) + i, | ||
| }); | ||
| } | ||
| return { | ||
| list: data, | ||
| checkedKeys: [], | ||
| checkedKeys1: ['a10'], | ||
| checkedKeys2: [], | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.checkedKeys1 = ['a10']; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| ### Props | ||
| | 参数 | 说明 | 类型 | 默认值 | | ||
| | ----------------- | -------------------------------------- | ------- | -------------- | | ||
| | v-model | 指定当前选中的条目 | Array | [] | | ||
| | data | 下拉列表数据,要求数据格式{key, value} | Array | [] | | ||
| | disabled | 是否禁用选择 | Boolean | - | | ||
| | placeholder | 文本 placeholder 显示值 | string | '请选择' | | ||
| | border | 文本是否有边框 | Boolean | false | | ||
| | searchPlaceholder | 搜索框 placeholder 显示值 | string | '请输入关键字' | | ||
| | confirmText | 弹出层确认提示文字 | string | '确认' | | ||
| | confirmColor | 弹出层确认提示文字颜色 | string | '#2979ff' | | ||
| | input | 是否显示 input 框 | Boolean | true | | ||
| ### Events | ||
| | 事件名 | 说明 | 回调参数 | 默认值 | | ||
| | ------ | ------------------------------ | --------------------- | ------------ | | ||
| | hide | 弹出层隐藏时的回调方法 | Function() | | ||
| | change | 点击确定按钮,返回当前选择的值 | Function(checkedKeys) | 当前选中的值 | |
| --- | ||
| title: Select 下拉 | ||
| --- | ||
| # Select 下拉 | ||
| 基于 [uView Select 选择器](https://www.uviewui.com/components/select.html) 二次封装,目前只支持单列,常用于 form 表单使用。 | ||
| 为什么要做封装,在 form 表单中如果要使用 `u-select`,我们需要这样去写,我们需提供一个状态 `show` 去控制 select 选择器的显隐,选择某一列后 input 的值也需要同步变更展示。当一个 form 表单内存在多个 select 组件时代码就会显得很混乱。 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-select/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-select :data="list" v-model="checkedKeys1"></v-select> | ||
| <v-select :data="list" v-model="checkedKeys"></v-select> | ||
| <v-select | ||
| :data="list" | ||
| v-model="checkedKeys1" | ||
| border="surround" | ||
| :clear="clear" | ||
| :clearFlag="true" | ||
| ></v-select> | ||
| <v-select :data="list" v-model="checkedKeys" border="surround"></v-select> | ||
| //重置功能 | ||
| <u-button @click="reset">重置</u-button> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| data() { | ||
| const data = []; | ||
| for (let i = 10; i < 36; i++) { | ||
| data.push({ | ||
| key: i.toString(36) + i + Math.random(), | ||
| value: i.toString(36) + i, | ||
| }); | ||
| } | ||
| return { | ||
| list: data, | ||
| checkedKeys: 'k20', | ||
| checkedKeys1: '', | ||
| test: '123', | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.checkedKeys = 'k20'; | ||
| }, | ||
| clear() { | ||
| console.log('111clear'); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| ### Props | ||
| | 参数 | 说明 | 类型 | 默认值 | | ||
| | ----------------- | -------------------------------------- | ------- | -------------- | | ||
| | v-model | 指定当前选中的条目 | String | '' | | ||
| | data | 下拉列表数据,要求数据格式{key, value} | Array | [] | | ||
| | disabled | 是否禁用选择 | Boolean | - | | ||
| | placeholder | 文本 placeholder 显示值 | string | '请选择' | | ||
| | border | 文本是否有边框 | Boolean | false | | ||
| | searchPlaceholder | 搜索文本 placeholder 显示值 | string | '请输入关键字' | | ||
| | input | 是否显示 input 框 | Boolean | true | | ||
| | clearFlag | 是否显示清除按钮 | Boolean | false | | ||
| ### Events | ||
| | 事件名 | 说明 | 回调参数 | 默认值 | | ||
| | ------ | ------------------------------ | -------------------- | ---------------------- | | ||
| | change | 点击确定按钮,返回当前选择的值 | Function(value,show) | 当前选中的值和显示隐藏 | | ||
| | hide | 弹出层隐藏时的回调方法 | Function() | - | | ||
| | clear | 清除按钮回调方法 | Function(value) | false | |
| --- | ||
| title: TreeSelect 下拉树 | ||
| --- | ||
| # TreeSelect 下拉树 | ||
| 树型选择控件,常用于表单中。基于 [next-tree](https://ext.dcloud.net.cn/plugin?id=12265) 封装 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-tree-select/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-tree-select | ||
| v-model="checkedKey" | ||
| :treeData="treeData" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| ></v-tree-select> | ||
| //多选模式 | ||
| <v-tree-select | ||
| v-model="checkedKey1" | ||
| :treeData="treeData" | ||
| mode="multiple" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| @change="handleChange" | ||
| :checkStrictly="true" | ||
| ></v-tree-select> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| data() { | ||
| return { | ||
| treeData: [], | ||
| checkedKey: [1], | ||
| checkedKey1: [1, 11, 111], | ||
| }; | ||
| }, | ||
| onLoad() { | ||
| // 模拟异步请求 | ||
| setTimeout(() => { | ||
| this.treeData = [ | ||
| { | ||
| id: 1, | ||
| label: '一级 1', | ||
| children: [ | ||
| { | ||
| id: 11, | ||
| label: '二级 1-1', | ||
| children: [ | ||
| { | ||
| id: 111, | ||
| label: '三级 1-1-1', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 2, | ||
| label: '一级 2', | ||
| children: [ | ||
| { | ||
| id: 21, | ||
| label: '二级 2-1', | ||
| children: [ | ||
| { | ||
| id: 211, | ||
| label: '三级 2-1-1', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 22, | ||
| label: '二级 2-2', | ||
| children: [ | ||
| { | ||
| id: 221, | ||
| label: '三级 2-2-1', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 3, | ||
| label: '一级 3', | ||
| children: [ | ||
| { | ||
| id: 31, | ||
| label: '二级 3-1', | ||
| children: [ | ||
| { | ||
| id: 311, | ||
| label: '三级 3-1-1', | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 32, | ||
| label: '二级 3-2', | ||
| children: [ | ||
| { | ||
| id: 321, | ||
| label: '三级 3-2-1', | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
| }, 200); | ||
| }, | ||
| methods: { | ||
| handleChange(checkedKeys) { | ||
| console.log(checkedKeys); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| ### Props | ||
| | 参数 | 说明 | 类型 | 默认值 | | ||
| | --- | --- | --- | --- | | ||
| | v-model | 指定当前选中的条目 | Array | [] | | ||
| | treeData | 下拉树数据 https://ext.dcloud.net.cn/plugin?id=12265 | Array | [] | | ||
| | disabled | 是否禁用选择 | Boolean | false | | ||
| | placeholder | 文本 placeholder 显示值 | string | '请选择' | | ||
| | border | 文本是否有边框 | Boolean | false | | ||
| | searchPlaceholder | 搜索框 placeholder 显示值 | string | '请输入关键字' | | ||
| | confirmText | 弹出层确认提示文字 | string | '确认' | | ||
| | confirmColor | 弹出层确认提示文字颜色 | string | '#2979ff' | | ||
| | mode | 多选('multiple')或单选('single')模式 | String | 'single' | | ||
| | nodeKey | 每个树节点的 id | String | 'id' | | ||
| | nodeLabel | 每个树节点的名称 | String | 'label' | | ||
| | input | 是否显示 input 框 | Boolean | true | | ||
| | checkStrictly | 父子不互相关联 | Boolean | false | | ||
| | disabledKey | 禁用节点绑定属性 | String | 'disabled' | | ||
| ### Events | ||
| | 事件名 | 说明 | 回调参数 | 默认值 | | ||
| | --- | --- | --- | --- | | ||
| | change | 点击确定按钮,返回当前选择的值 | Function(checkedKeys, object) | 当前选中的值和该值所在对象 | |
| --- | ||
| title: Upload 上传 | ||
| --- | ||
| # Upload 上传 | ||
| 该组件用于上传图片场景,基于[uView Upload](https://www.uviewui.com/components/upload.html)封装。 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-upload/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| 默认支持 `v-model`,内容格式为`[{id: 'f4f29d6892be4be796207a27a746e839', name: '2.jpg', type: 'image/jpeg'}, ...]` | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-upload v-model="photos" @change="handleChange"></v-upload> | ||
| //只读 | ||
| <v-upload v-model="photos" readonly></v-upload> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| return { | ||
| // [{id: 'f4f29d6892be4be796207a27a746e839', name: '2.jpg', type: 'image/jpeg'}] | ||
| photos: [], | ||
| }; | ||
| }, | ||
| methods: { | ||
| handleChange() { | ||
| // TODO | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| ### Props | ||
| | 参数 | 说明 | 类型 | 默认值 | | ||
| | --- | --- | --- | --- | | ||
| | v-model | 文件列表 | Array[file],详见下方说明 | [] | | ||
| | action | 上传后端地址 | String | '/cloudFile/common/uploadFile' | | ||
| | downloadUrl | 下载后端地址 | String | '/cloudFile/common/downloadFile?id=' | | ||
| | maxCount | 最大上传数量 | String \| Number | 6 | | ||
| | readonly | 是否只读 | Boolean | false | | ||
| | fileList | 显示已上传的文件列表 | Array[file] | [] | | ||
| 更多 `Props` 参考 [uView Upload 上传](https://www.uviewui.com/components/upload.html)。 | ||
| `file`格式: | ||
| `id` 图片服务器 id,`name`图片名称,`type`文件类型 | ||
| ``` | ||
| {id: '', name: '', type: ''} | ||
| ``` | ||
| ### Events | ||
| | 事件名 | 说明 | 回调参数 | 默认值 | | ||
| | --- | --- | --- | --- | | ||
| | change | 文件列表变化时触发 | Function(fileList) | 当前上传的文件 | | ||
| | on-remove | 移除图片时触发 | (index, lists, name),name 为通过 props 传递的 index 参数 | - | | ||
| | on-success | 图片上传成功时触发 | (data, index, lists, name),data 为服务器返回的数据,name 为通过 props 传递的 index 参数 | - | |
| --- | ||
| title: VirtualSelect 虚拟下拉列表 | ||
| --- | ||
| # VirtualSelect 虚拟下拉列表 | ||
| <Mobile src="http://yanshi.huanwei.envcloud.com.cn:8003/ember-plus/example/#/pages/v-virtual-select/index"/> | ||
| ## 代码演示 | ||
| ### 基础使用 | ||
| ```vue | ||
| <template> | ||
| <view> | ||
| //基本使用 | ||
| <v-virtual-select | ||
| :requestOption="{ | ||
| url: '/cloud/advise/web/base/shop/list', | ||
| method: 'POST', | ||
| }" | ||
| :requestParams="{ | ||
| order: 'asc,asc', | ||
| sort: 'orgName,manageUnitName', | ||
| }" | ||
| :setValueLabel="{ | ||
| value: 'id', | ||
| label: 'name', | ||
| }" | ||
| :setPageRows="{ | ||
| page: 'page', | ||
| rows: 'rows', | ||
| searchText: 'nameLike', | ||
| }" | ||
| v-model="checkedKeys" | ||
| ></v-virtual-select> | ||
| // 多选 | ||
| <v-virtual-select | ||
| :requestOption="{ | ||
| url: '/cloud/advise/web/base/shop/list', | ||
| method: 'POST', | ||
| }" | ||
| :requestParams="{ | ||
| order: 'asc,asc', | ||
| sort: 'orgName,manageUnitName', | ||
| }" | ||
| :setValueLabel="{ | ||
| value: 'id', | ||
| label: 'name', | ||
| }" | ||
| :setPageRows="{ | ||
| page: 'page', | ||
| rows: 'rows', | ||
| searchText: 'nameLike', | ||
| }" | ||
| v-model="checkedKeys1" | ||
| mode="multiple" | ||
| ></v-virtual-select> | ||
| //重置功能 | ||
| <u-button @click="reset">重置</u-button> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| const data = []; | ||
| for (let i = 10; i < 10000; i++) { | ||
| const value = i.toString(36) + i; | ||
| data.push({ | ||
| value, | ||
| label: value, | ||
| }); | ||
| } | ||
| return { | ||
| list: data, | ||
| checkedKeys: [], | ||
| checkedKeys1: [{ value: 'a10', label: 'a10' }], | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.checkedKeys = [{ value: 'a10', label: 'a10' }]; | ||
| this.checkedKeys1 = []; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| ``` | ||
| ## API | ||
| ### Props | ||
| | 参数 | 说明 | 类型 | 默认值 | | ||
| | --- | --- | --- | --- | | ||
| | v-model | 指定当前选中的值 | Array | [] | | ||
| | requestOption | 请求方法和地址 | Object({url:'',method: ''}) | - | | ||
| | requestParams | 请求参数 | Object | - | | ||
| | setValueLabel | 返回展示名称的字段 | Object({label:'',value: ''}) | Object({label:'id',value: 'name'}) | | ||
| | setPageRows | 请求的页面和行数 | Object({page:'',rows: ''}) | Object({page:'page',rows: 'rows'}) | | ||
| | mode | 多选或单选('multiple' 多选 ,'' 单选) | String | - | | ||
| | title | 顶部标题 | String | - | | ||
| | disabled | 是否禁用选择 | Boolean | false | | ||
| | border | 文本是否有边框 | String | 'border' | | ||
| | searchPlaceholder | 选择器搜索框 placeholder 显示值 | String | '请输入关键字' | | ||
| | cancelColor | "取消"按钮的颜色 | String | '#606266' | | ||
| | confirmColor | "确定"按钮的颜色 | String | '#2979ff' | | ||
| | zIndex | 弹出的 z-index 值 | String \| Number | 0 | | ||
| | maskCloseAble | 是否允许通过点击遮罩关闭 | Boolean | true | | ||
| | cancelText | 取消按钮的文字 | String | '取消' | | ||
| | confirmText | 确认按钮的文字 | String | '确认' | | ||
| | input | 是否显示 input | Boolean | true | | ||
| ### Events | ||
| | 事件名 | 说明 | 回调参数 | 默认值 | | ||
| | ------ | ---------------- | --------------------- | ------------ | | ||
| | change | 返回当前选择的值 | Function(checkedKeys) | 当前选中的值 | |
| --- | ||
| home: true | ||
| heroText: '@vtx/ember' | ||
| tagline: 伏泰 Uni-app 组件库 | ||
| actionText: 快速入门 → | ||
| actionLink: /components/installation | ||
| --- | ||
| ## 使用 Yarn 安装 | ||
| ```shell | ||
| yarn add @vtx/ember -S | ||
| ``` | ||
| ## 使用方法 | ||
| `pages.json` 配置 easycom 规则(按需引入) | ||
| ```json | ||
| // pages.json | ||
| { | ||
| "easycom": { | ||
| "^v-(.*)": "@vtx/ember/components/v-$1/v-$1.vue" | ||
| }, | ||
| // 此为本身已有的内容 | ||
| "pages": [ | ||
| // ...... | ||
| ] | ||
| } | ||
| ``` | ||
| 配置 easycom 规则后,自动按需引入,无需 `import` 组件,直接引用即可。 | ||
| ```vue | ||
| <template> | ||
| <v-multiple-select :data="[]"></v-multiple-select> | ||
| </template> | ||
| ``` |
-20
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <script> | ||
| var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || | ||
| CSS.supports('top: constant(a)')) | ||
| document.write( | ||
| '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + | ||
| (coverSupport ? ', viewport-fit=cover' : '') + '" />') | ||
| </script> | ||
| <title></title> | ||
| <!--preload-links--> | ||
| <!--app-context--> | ||
| </head> | ||
| <body> | ||
| <div id="app"><!--app-html--></div> | ||
| <script type="module" src="/src/main.js"></script> | ||
| </body> | ||
| </html> |
| const path = require('path') | ||
| module.exports = { | ||
| parser: require('postcss-comment'), | ||
| plugins: [ | ||
| require('postcss-import')({ | ||
| resolve (id, basedir, importOptions) { | ||
| if (id.startsWith('~@/')) { | ||
| return path.resolve(process.env.UNI_INPUT_DIR, id.substr(3)) | ||
| } else if (id.startsWith('@/')) { | ||
| return path.resolve(process.env.UNI_INPUT_DIR, id.substr(2)) | ||
| } else if (id.startsWith('/') && !id.startsWith('//')) { | ||
| return path.resolve(process.env.UNI_INPUT_DIR, id.substr(1)) | ||
| } | ||
| return id | ||
| } | ||
| }), | ||
| require('autoprefixer')({ | ||
| remove: process.env.UNI_PLATFORM !== 'h5' | ||
| }), | ||
| require('@dcloudio/vue-cli-plugin-uni/packages/postcss') | ||
| ] | ||
| } |
| /// <reference types='@dcloudio/types' /> | ||
| import 'vue' | ||
| declare module '@vue/runtime-core' { | ||
| type Hooks = App.AppInstance & Page.PageInstance; | ||
| interface ComponentCustomOptions extends Hooks { | ||
| } | ||
| } |
-18
| <script> | ||
| export default { | ||
| onLaunch: function () { | ||
| console.log('App Launch') | ||
| }, | ||
| onShow: function () { | ||
| console.log('App Show') | ||
| }, | ||
| onHide: function () { | ||
| console.log('App Hide') | ||
| }, | ||
| } | ||
| </script> | ||
| <style lang="scss"> | ||
| /* 注意要写在第一行,同时给style标签加入lang="scss"属性 */ | ||
| @import "uview-plus/index.scss"; | ||
| </style> |
| <template> | ||
| <div class="demo-block"> | ||
| <div class="demo-block__title">{{ title }}</div> | ||
| <slot></slot> | ||
| </div> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| props: { | ||
| title: String, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style lang="scss" scoped> | ||
| .demo-block { | ||
| padding: 0 16px; | ||
| &__title { | ||
| margin: 0; | ||
| padding: 24px 0 16px; | ||
| color: rgba(69, 90, 100, 0.6); | ||
| font-weight: normal; | ||
| font-size: 14px; | ||
| line-height: 16px; | ||
| } | ||
| } | ||
| </style> |
| { | ||
| "bu-xian-shi-input-shu-ru-kuang-chu-fa": "Hide input box trigger", | ||
| "can-shu-pei-zhi": "Parameter configuration", | ||
| "datepicker-ri-qi-xuan-ze-kuang": "DatePicker", | ||
| "dian-ji-ci-chu-chu-fa": "Click here to trigger", | ||
| "dian-ji-ci-chu-chu-fa-dan-xuan": "Click here to trigger (single)", | ||
| "dian-ji-ci-chu-chu-fa-duo-xuan": "Click here to trigger (multiple)", | ||
| "dian-ji-le-di-index-xiang": "Clicked item {0}", | ||
| "dropdown": "Dropdown", | ||
| "dropdown-xia-la-cai-dan": "Dropdown menu", | ||
| "duo-xuan": "Multiple", | ||
| "duo-xuan-mo-shi-fu-zi-bu-hu-xiang-guan-lian": "Multiple mode (parent & child no independent)", | ||
| "duo-xuan-mo-shi-fu-zi-hu-xiang-guan-lian": "Multiple mode (parent & child independent)", | ||
| "duo-zhang-tu-pian-yu-lan": "Multi-image preview", | ||
| "er-ji-11": "Level 2 1-1", | ||
| "er-ji-21": "Level 2 2-1", | ||
| "er-ji-22": "Level 2 2-2", | ||
| "er-ji-31": "Level 2 3-1", | ||
| "er-ji-32": "Level 2 3-2", | ||
| "fu-tai-qian-duan-uniapp-zu-jian-ku": "Futai Frontend uni-app component library", | ||
| "image": "Image", | ||
| "image-tu-pian": "Image", | ||
| "ji-ben-shi-yong": "Basic usage", | ||
| "ji-han-feng-bao": "Arctic Storm", | ||
| "ji-huo-yan-se": "Active color", | ||
| "jia-bing": "Extra ice", | ||
| "jia-ge-you-xian": "Price first", | ||
| "jia-re": "Heat up", | ||
| "jie-guo": "Result", | ||
| "ju-li": "Distance", | ||
| "ju-li-you-xian": "Distance first", | ||
| "mo-ren-pai-xu": "Default sort", | ||
| "multipleselect-xia-la-duo-xuan": "MultipleSelect", | ||
| "nian": "Year", | ||
| "nian-yue-ri-shi-fen-miao": "Year-Month-Day-Hour-Minute-Second", | ||
| "qi-hua-yao-cao": "Rare blossoms and jade-like grass", | ||
| "qing-ci-li-ju": "Elegant words and beautiful phrases", | ||
| "qu-bing": "No ice", | ||
| "que-ding": "OK", | ||
| "ri": "Day", | ||
| "ri-qi-xuan-ze-kuang": "DatePicker", | ||
| "san-ji-111": "Level 3 1-1-1", | ||
| "san-ji-211": "Level 3 2-1-1", | ||
| "san-ji-221": "Level 3 2-2-1", | ||
| "san-ji-311": "Level 3 3-1-1", | ||
| "san-ji-321": "Level 3 3-2-1", | ||
| "select": "Select", | ||
| "select-xia-la": "Select", | ||
| "shang-chuan": "Upload", | ||
| "shi-fen-miao": "Hour-Minute-Second", | ||
| "shu-xing": "Attribute", | ||
| "treeselect-xia-la-shu": "TreeSelect", | ||
| "tu-pian": "Image", | ||
| "upload": "Upload", | ||
| "upload-shang-chuan": "Upload", | ||
| "virtualselect-xu-ni-xia-la-lie-biao": "VirtualSelect", | ||
| "wan-zhuanemei": "Graceful eyebrows", | ||
| "wen-du": "Temperature", | ||
| "xia-bian-kuang": "Bottom border", | ||
| "xia-guang-wan-dao": "Rays of dawn", | ||
| "xia-la": "Select", | ||
| "xia-la-cai-dan": "Dropdown menu", | ||
| "xia-la-duo-xuan": "MultipleSelect", | ||
| "xia-la-shu": "TreeSelect", | ||
| "xu-ni-xia-la-lie-biao": "VirtualSelect", | ||
| "xu-se-shao-guang": "Warm spring light", | ||
| "yi-ji-1": "Level 1 1", | ||
| "yi-ji-2": "Level 1 2", | ||
| "yi-ji-3": "Level 1 3", | ||
| "yu-chen-yan-la": "Fish sink and geese alight (devastating beauty)", | ||
| "yue": "Month", | ||
| "zhang-tai-yang-liu": "Zhangtai willows", | ||
| "zhe-zhao-shi-fou-ke-dian-ji": "Mask closable", | ||
| "zheng-chang-wen": "Normal temperature", | ||
| "zhong-zhi": "Reset", | ||
| "zhong-zhi-gong-neng": "Reset function", | ||
| "zi-ding-yi-que-ren-yan-se-he-zi-ti": "Custom confirm color & font", | ||
| "zu-jian": "Component", | ||
| "zan-wu-shu-ju": "No Data" | ||
| } |
| import zhHans from './zh-CN.json' | ||
| import en from './en.json' | ||
| import { getUrlParam } from '@vtx/utils' | ||
| import { createI18n } from 'vue-i18n' | ||
| const getLocale = () => { | ||
| if (getUrlParam('locale') === 'zh-CN') { | ||
| return 'zh-Hans' | ||
| } else if (getUrlParam('locale') === 'en-US') { | ||
| return 'en' | ||
| } else if (uni.getStorageSync('locale') === 'zh-CN') { | ||
| return 'zh-Hans' | ||
| } else if (uni.getStorageSync('locale') === 'en-US') { | ||
| return 'en' | ||
| } else if (uni.getStorageSync('UNI_LOCALE') === 'zh-Hans') { | ||
| return 'zh-Hans' | ||
| } else if (uni.getStorageSync('UNI_LOCALE') === 'en') { | ||
| return 'en' | ||
| } | ||
| else { | ||
| return 'zh-Hans' | ||
| } | ||
| } | ||
| const locale = getLocale(); | ||
| if (locale === 'zh-Hans') { | ||
| uni.setStorageSync('locale', 'zh-CN') | ||
| uni.setStorageSync('UNI_LOCALE', 'zh-Hans') | ||
| } | ||
| if (locale === 'en') { | ||
| uni.setStorageSync('locale', 'en-US') | ||
| uni.setStorageSync('UNI_LOCALE', 'en') | ||
| } | ||
| const i18n = createI18n({ | ||
| legacy: false, | ||
| locale, | ||
| fallbackLocale: locale, | ||
| messages: { | ||
| 'zh-Hans': zhHans, | ||
| en | ||
| } | ||
| }) | ||
| export default i18n |
| { | ||
| "bu-he-fa-de-data-shu-ju": "不合法的data数据", | ||
| "bu-xian-shi-input-shu-ru-kuang-chu-fa": "不显示input输入框触发", | ||
| "can-shu-pei-zhi": "参数配置", | ||
| "datepicker-ri-qi-xuan-ze-kuang": "DatePicker 日期选择框", | ||
| "dian-ji-ci-chu-chu-fa": "点击此处触发", | ||
| "dian-ji-ci-chu-chu-fa-dan-xuan": "点击此处触发单选", | ||
| "dian-ji-ci-chu-chu-fa-duo-xuan": "点击此处触发多选", | ||
| "dian-ji-le-di-index-xiang": "点击了第{0}项", | ||
| "dian-ji-le-di-index-xiang-0": "点击了第{0}项", | ||
| "dropdown": "Dropdown", | ||
| "dropdown-xia-la-cai-dan": "Dropdown 下拉菜单", | ||
| "duo-xuan": "多选", | ||
| "duo-xuan-mo-shi": "多选模式", | ||
| "duo-xuan-mo-shi-fu-zi-bu-hu-xiang-guan-lian": "多选模式(父子不互相关联)", | ||
| "duo-xuan-mo-shi-fu-zi-hu-xiang-guan-lian": "多选模式(父子互相关联)", | ||
| "duo-zhang-tu-pian-yu-lan": "多张图片预览", | ||
| "er-ji-11": "二级 1-1", | ||
| "er-ji-21": "二级 2-1", | ||
| "er-ji-22": "二级 2-2", | ||
| "er-ji-31": "二级 3-1", | ||
| "er-ji-32": "二级 3-2", | ||
| "fu-tai-qian-duan-uniapp-zu-jian-ku": "伏泰前端uni-app组件库", | ||
| "image": "Image", | ||
| "image-tu-pian": "Image 图片", | ||
| "ji-ben-shi-yong": "基本使用", | ||
| "ji-han-feng-bao": "极寒风暴", | ||
| "ji-huo-yan-se": "激活颜色", | ||
| "jia-bing": "加冰", | ||
| "jia-ge-you-xian": "价格优先", | ||
| "jia-re": "加热", | ||
| "jia-zai-zhong": "加载中...", | ||
| "jie-guo": "结果", | ||
| "ju-li": "距离", | ||
| "ju-li-you-xian": "距离优先", | ||
| "mo-ren-pai-xu": "默认排序", | ||
| "multipleselect-xia-la-duo-xuan": "MultipleSelect 下拉多选", | ||
| "nian": "年", | ||
| "nian-yue-ri-shi-fen-miao": "年月日时分秒", | ||
| "qi-hua-yao-cao": "琪花瑶草", | ||
| "qing-ci-li-ju": "清词丽句", | ||
| "qu-bing": "去冰", | ||
| "que-ding": "确定", | ||
| "ri": "日", | ||
| "ri-qi-xuan-ze-kuang": "日期选择框", | ||
| "san-ji-111": "三级 1-1-1", | ||
| "san-ji-211": "三级 2-1-1", | ||
| "san-ji-221": "三级 2-2-1", | ||
| "san-ji-311": "三级 3-1-1", | ||
| "san-ji-321": "三级 3-2-1", | ||
| "select": "Select", | ||
| "select-xia-la": "Select 下拉", | ||
| "shang-chuan": "上传", | ||
| "shi-fen-miao": "时分秒", | ||
| "shu-xing": "属性", | ||
| "treeselect-xia-la-shu": "TreeSelect 下拉树", | ||
| "tu-pian": "图片", | ||
| "upload": "Upload", | ||
| "upload-shang-chuan": "Upload 上传", | ||
| "virtualselect-xu-ni-xia-la-lie-biao": "VirtualSelect 虚拟下拉列表", | ||
| "wan-zhuanemei": "宛转蛾眉", | ||
| "wen-du": "温度", | ||
| "xia-bian-kuang": "下边框", | ||
| "xia-guang-wan-dao": "霞光万道", | ||
| "xia-la": "下拉", | ||
| "xia-la-cai-dan": "下拉菜单", | ||
| "xia-la-duo-xuan": "下拉多选", | ||
| "xia-la-shu": "下拉树", | ||
| "xu-ni-xia-la-lie-biao": "虚拟下拉列表", | ||
| "xu-se-shao-guang": "煦色韶光", | ||
| "yi-ji-1": "一级 1", | ||
| "yi-ji-2": "一级 2", | ||
| "yi-ji-3": "一级 3", | ||
| "yu-chen-yan-la": "鱼沉雁落", | ||
| "yue": "月", | ||
| "zan-wu-shu-ju": "暂无数据", | ||
| "zhang-tai-yang-liu": "章台杨柳", | ||
| "zhe-zhao-shi-fou-ke-dian-ji": "遮罩是否可点击", | ||
| "zheng-chang-wen": "正常温", | ||
| "zhong-zhi": "重置", | ||
| "zhong-zhi-gong-neng": "重置功能", | ||
| "zi-ding-yi-que-ren-yan-se-he-zi-ti": "自定义确认颜色和字体", | ||
| "zu-jian": "组件" | ||
| } |
-17
| import { | ||
| createSSRApp | ||
| } from "vue"; | ||
| import App from "./App.vue"; | ||
| import uviewPlus from 'uview-plus'; | ||
| import CDemo from './components/c-demo'; | ||
| import i18n from './locales/index'; | ||
| export function createApp() { | ||
| const app = createSSRApp(App); | ||
| app.use(uviewPlus) | ||
| app.use(i18n) | ||
| app.component('c-demo', CDemo); | ||
| return { | ||
| app | ||
| }; | ||
| } |
| { | ||
| "name": "", | ||
| "appid": "", | ||
| "description": "", | ||
| "versionName": "1.0.0", | ||
| "versionCode": "100", | ||
| "transformPx": false, | ||
| "h5": { | ||
| "router": { | ||
| "base": "./" | ||
| }, | ||
| "template": "./index.html" | ||
| } | ||
| } |
| export default { | ||
| methods: { | ||
| navigateToHome() { | ||
| uni.navigateTo({ url: '/pages/index/index' }); | ||
| }, | ||
| }, | ||
| }; |
| { | ||
| "easycom": { | ||
| "custom": { | ||
| "^v-(.*)": "/components/v-$1/v-$1.vue", | ||
| "^u--(.*)": "uview-plus/components/u-$1/u-$1.vue", | ||
| "^up-(.*)": "uview-plus/components/u-$1/u-$1.vue", | ||
| "^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue" | ||
| } | ||
| }, | ||
| "pages": [ | ||
| { | ||
| "path": "pages/index/index", | ||
| "style": { | ||
| "navigationBarTitleText": "@vtx/ember" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-date-picker/index", | ||
| "style": { | ||
| "navigationBarTitleText": "DatePicker 日期选择框" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-image/index", | ||
| "style": { | ||
| "navigationBarTitleText": "Image 图片" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-multiple-select/index", | ||
| "style": { | ||
| "navigationBarTitleText": "MultipleSelect 下拉多选" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-select/index", | ||
| "style": { | ||
| "navigationBarTitleText": "Select 下拉" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-tree-select/index", | ||
| "style": { | ||
| "navigationBarTitleText": "TreeSelect 下拉树" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-virtual-select/index", | ||
| "style": { | ||
| "navigationBarTitleText": "VirtualSelect 虚拟下拉列表", | ||
| "enablePullDownRefresh": false | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-upload/index", | ||
| "style": { | ||
| "navigationBarTitleText": "Upload 上传" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-dropdown/index", | ||
| "style": { | ||
| "navigationBarTitleText": "Dropdown 下拉菜单" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-facility-detail/index", | ||
| "style": { | ||
| "navigationBarTitleText": "FacilityDetail 设备、设施详情弹出" | ||
| } | ||
| }, | ||
| { | ||
| "path": "pages/v-facility-detail-v2/index", | ||
| "style": { | ||
| "navigationBarTitleText": "FacilityDetail 设备、设施详情" | ||
| } | ||
| } | ||
| ], | ||
| "globalStyle": { | ||
| "navigationBarTextStyle": "black", | ||
| "navigationBarTitleText": "uni-app", | ||
| "navigationBarBackgroundColor": "#F8F8F8", | ||
| "backgroundColor": "#F8F8F8" | ||
| } | ||
| } |
| <template> | ||
| <div class="demo-home"> | ||
| <h1 class="demo-home__title"> | ||
| <span>@vtx/ember</span> | ||
| </h1> | ||
| <h2 class="demo-home__desc">{{ $t('fu-tai-qian-duan-uniapp-zu-jian-ku') }}</h2> | ||
| <div class="demo-home-nav"> | ||
| <div class="demo-home-nav__title">{{ $t('zu-jian') }}</div> | ||
| <div class="demo-home-nav__group"> | ||
| <view | ||
| class="demo-home-nav__block" | ||
| v-for="item in components" | ||
| :key="item.key" | ||
| @click="to(item.path)" | ||
| > | ||
| {{ item.title }} | ||
| <u-icon name="arrow-right" class="demo-home-nav__icon"></u-icon> | ||
| </view> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| data() { | ||
| return { | ||
| components: [ | ||
| { | ||
| key: 'DatePicker', | ||
| title: this.$t('datepicker-ri-qi-xuan-ze-kuang'), | ||
| path: '/pages/v-date-picker/index', | ||
| }, | ||
| { | ||
| key: this.$t('image'), | ||
| title: this.$t('image-tu-pian'), | ||
| path: '/pages/v-image/index', | ||
| }, | ||
| { | ||
| key: 'MultipleSelect', | ||
| title: this.$t('multipleselect-xia-la-duo-xuan'), | ||
| path: '/pages/v-multiple-select/index', | ||
| }, | ||
| { | ||
| key: this.$t('select'), | ||
| title: this.$t('select-xia-la'), | ||
| path: '/pages/v-select/index', | ||
| }, | ||
| { | ||
| key: 'TreeSelect', | ||
| title: this.$t('treeselect-xia-la-shu'), | ||
| path: '/pages/v-tree-select/index', | ||
| }, | ||
| { | ||
| key: 'VirtualSelect', | ||
| title: this.$t('virtualselect-xu-ni-xia-la-lie-biao'), | ||
| path: '/pages/v-virtual-select/index', | ||
| }, | ||
| { | ||
| key: this.$t('upload'), | ||
| title: this.$t('upload-shang-chuan'), | ||
| path: '/pages/v-upload/index', | ||
| }, | ||
| { | ||
| key: this.$t('dropdown'), | ||
| title: this.$t('dropdown-xia-la-cai-dan'), | ||
| path: '/pages/v-dropdown/index', | ||
| }, | ||
| { | ||
| key: 'FacilityDetail', | ||
| title: 'FacilityDetail 设备、设施详情弹出', | ||
| path: '/pages/v-facility-detail/index', | ||
| }, | ||
| { | ||
| key: 'FacilityDetailV2', | ||
| title: 'FacilityDetailV2 设备、设施详情无弹窗', | ||
| path: '/pages/v-facility-detail-v2/index', | ||
| }, | ||
| ], | ||
| }; | ||
| }, | ||
| methods: { | ||
| to(path) { | ||
| uni.navigateTo({ url: path }); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style lang="scss" scoped> | ||
| .demo-home { | ||
| box-sizing: border-box; | ||
| width: 100%; | ||
| min-height: 100vh; | ||
| padding: 24px 10px 20px; | ||
| background: #fff; | ||
| } | ||
| .demo-home__title, | ||
| .demo-home__desc { | ||
| padding-left: 16px; | ||
| font-weight: normal; | ||
| line-height: 1; | ||
| user-select: none; | ||
| } | ||
| .demo-home__title { | ||
| margin: 0 0 16px; | ||
| font-size: 32px; | ||
| span { | ||
| display: inline-block; | ||
| vertical-align: middle; | ||
| } | ||
| } | ||
| .demo-home__desc { | ||
| margin: 0 0 30px; | ||
| color: rgba(69, 90, 100, 0.6); | ||
| font-size: 14px; | ||
| } | ||
| .demo-home-nav { | ||
| &__title { | ||
| margin: 24px 0 8px 16px; | ||
| color: rgba(69, 90, 100, 0.6); | ||
| font-size: 14px; | ||
| } | ||
| } | ||
| .demo-home-nav__block { | ||
| position: relative; | ||
| display: flex; | ||
| margin: 0 0 12px; | ||
| padding-left: 20px; | ||
| color: #323233; | ||
| font-weight: 600; | ||
| font-size: 14px; | ||
| line-height: 40px; | ||
| background: #f7f8fa; | ||
| border-radius: 99px; | ||
| transition: background 0.3s; | ||
| .demo-home-nav__icon { | ||
| position: absolute; | ||
| top: 50%; | ||
| right: 16px; | ||
| width: 16px; | ||
| height: 16px; | ||
| margin-top: -8px; | ||
| } | ||
| &:hover { | ||
| background: #eef0f4; | ||
| } | ||
| } | ||
| </style> |
| <template> | ||
| <view> | ||
| <u-navbar :title="$t('ri-qi-xuan-ze-kuang')" @leftClick="navigateToHome" :border="true" > </u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-date-picker v-model="date"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('nian')"> | ||
| <v-date-picker type="year" v-model="date1"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('yue')"> | ||
| <v-date-picker type="month" v-model="date2"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('ri')"> | ||
| <v-date-picker type="date" v-model="date3"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('nian-yue-ri-shi-fen-miao')"> | ||
| <v-date-picker type="datetime" v-model="date4"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('shi-fen-miao')"> | ||
| <v-date-picker type="time" v-model="date5"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('shi-fen')"> | ||
| <v-date-picker type="hourMinute" v-model="date6"></v-date-picker> | ||
| </c-demo> | ||
| <c-demo :title="$t('zhong-zhi-gong-neng')"> | ||
| <u-button @click="reset">{{ $t('zhong-zhi') }}</u-button> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import dayjs from 'dayjs'; | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| watch: { | ||
| date(val) { | ||
| console.log('date', val); | ||
| }, | ||
| date1(val) { | ||
| console.log('date1', val); | ||
| }, | ||
| date2(val) { | ||
| console.log('date2', val); | ||
| }, | ||
| date3(val) { | ||
| console.log('date3', val); | ||
| }, | ||
| date4(val) { | ||
| console.log('date4', val); | ||
| }, | ||
| date5(val) { | ||
| console.log('date5', val); | ||
| }, | ||
| date6(val) { | ||
| console.log('date6', val); | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| date: dayjs().format('YYYY-MM-DD'), | ||
| date1: dayjs().format('YYYY'), | ||
| date2: dayjs().format('YYYY-MM'), | ||
| date3: dayjs().format('YYYY-MM-DD'), | ||
| date4: dayjs().format('YYYY-MM-DD HH:mm:ss'), | ||
| date5: dayjs().format('HH:mm:ss'), | ||
| date6: dayjs().format('HH:mm'), | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.date = dayjs().format('YYYY-MM-DD'); | ||
| this.date1 = dayjs().format('YYYY'); | ||
| this.date2 = dayjs().format('YYYY-MM'); | ||
| this.date3 = dayjs().format('YYYY-MM-DD'); | ||
| this.date4 = dayjs().format('YYYY-MM-DD HH:mm:ss'); | ||
| this.date5 = dayjs().format('HH:mm:ss'); | ||
| this.date6 = dayjs().format('HH:mm'); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> |
| <template> | ||
| <view> | ||
| <u-navbar | ||
| :title="$t('xia-la-cai-dan')" | ||
| @leftClick="navigateToHome" | ||
| :border="true" | ||
| ></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <view class="u-demo-area u-flex u-row-center"> | ||
| <v-dropdown | ||
| :close-on-click-mask="mask" | ||
| ref="uDropdown" | ||
| :activeColor="activeColor" | ||
| :borderBottom="borderBottom" | ||
| > | ||
| <v-dropdown-item | ||
| @change="change" | ||
| v-model="value1" | ||
| :title="$t('ju-li')" | ||
| :options="options1" | ||
| :titleSize="14" | ||
| ></v-dropdown-item> | ||
| <v-dropdown-item | ||
| @change="change" | ||
| v-model="value2" | ||
| :title="$t('wen-du')" | ||
| :options="options2" | ||
| ></v-dropdown-item> | ||
| <v-dropdown-item :title="$t('shu-xing')"> | ||
| <view class="slot-content"> | ||
| <view class="item-box"> | ||
| <view | ||
| class="item" | ||
| :class="[item.active ? 'active' : '']" | ||
| @tap="tagClick(index)" | ||
| v-for="(item, index) in list" | ||
| :key="index" | ||
| > | ||
| {{ item.label }} | ||
| </view> | ||
| </view> | ||
| <u-button type="primary" @click="closeDropdown">{{ | ||
| $t('que-ding') | ||
| }}</u-button> | ||
| </view> | ||
| </v-dropdown-item> | ||
| </v-dropdown> | ||
| </view> | ||
| <view class="u-config-wrap"> | ||
| <view class="u-config-title u-border-bottom"> {{ $t('can-shu-pei-zhi') }} </view> | ||
| <view class="u-config-item"> | ||
| <view class="u-item-title">{{ $t('xia-bian-kuang') }}</view> | ||
| <u-subsection | ||
| :current="curNow" | ||
| :list="['有', '无']" | ||
| @change="borderChange" | ||
| ></u-subsection> | ||
| </view> | ||
| <view class="u-config-item"> | ||
| <view class="u-item-title">{{ $t('ji-huo-yan-se') }}</view> | ||
| <u-subsection | ||
| :current="curNow2" | ||
| :list="['#2979ff', '#ff9900', '#19be6b']" | ||
| @change="activeColorChange" | ||
| ></u-subsection> | ||
| </view> | ||
| <view class="u-config-item"> | ||
| <view class="u-item-title">{{ $t('zhe-zhao-shi-fou-ke-dian-ji') }}</view> | ||
| <u-subsection | ||
| :current="curNow3" | ||
| :list="['是', '否']" | ||
| @change="maskChange" | ||
| ></u-subsection> | ||
| </view> | ||
| </view> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| return { | ||
| curNow: 1, | ||
| curNow2: 0, | ||
| curNow3: 0, | ||
| value1: '', | ||
| value2: '2', | ||
| mask: true, | ||
| options1: [ | ||
| { | ||
| label: this.$t('mo-ren-pai-xu'), | ||
| value: 1, | ||
| }, | ||
| { | ||
| label: this.$t('ju-li-you-xian'), | ||
| value: 2, | ||
| }, | ||
| { | ||
| label: this.$t('jia-ge-you-xian'), | ||
| value: 3, | ||
| }, | ||
| ], | ||
| options2: [ | ||
| { | ||
| label: this.$t('qu-bing'), | ||
| value: 1, | ||
| }, | ||
| { | ||
| label: this.$t('jia-bing'), | ||
| value: 2, | ||
| }, | ||
| { | ||
| label: this.$t('zheng-chang-wen'), | ||
| value: 3, | ||
| }, | ||
| { | ||
| label: this.$t('jia-re'), | ||
| value: 4, | ||
| }, | ||
| { | ||
| label: this.$t('ji-han-feng-bao'), | ||
| value: 5, | ||
| }, | ||
| ], | ||
| list: [ | ||
| { | ||
| label: this.$t('qi-hua-yao-cao'), | ||
| active: true, | ||
| }, | ||
| { | ||
| label: this.$t('qing-ci-li-ju'), | ||
| active: false, | ||
| }, | ||
| { | ||
| label: this.$t('wan-zhuanemei'), | ||
| active: false, | ||
| }, | ||
| { | ||
| label: this.$t('xu-se-shao-guang'), | ||
| active: false, | ||
| }, | ||
| { | ||
| label: this.$t('yu-chen-yan-la'), | ||
| active: false, | ||
| }, | ||
| { | ||
| label: this.$t('zhang-tai-yang-liu'), | ||
| active: false, | ||
| }, | ||
| { | ||
| label: this.$t('xia-guang-wan-dao'), | ||
| active: false, | ||
| }, | ||
| ], | ||
| borderBottom: false, | ||
| activeColor: '#2979ff', | ||
| }; | ||
| }, | ||
| methods: { | ||
| borderChange(index) { | ||
| this.curNow = index; | ||
| this.borderBottom = !index; | ||
| }, | ||
| activeColorChange(index) { | ||
| this.curNow2 = index; | ||
| this.activeColor = ['#2979ff', '#ff9900', '#19be6b'][index]; | ||
| }, | ||
| change(index) { | ||
| this.$u.toast(this.$t('dian-ji-le-di-index-xiang', [index])); | ||
| }, | ||
| closeDropdown() { | ||
| this.$refs.uDropdown.close(); | ||
| }, | ||
| tagClick(index) { | ||
| this.list[index].active = !this.list[index].active; | ||
| }, | ||
| maskChange(index) { | ||
| this.curNow3 = index; | ||
| this.mask = !index; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style scoped lang="scss"> | ||
| .u-config-wrap { | ||
| padding: 40rpx; | ||
| .u-config-title { | ||
| text-align: center; | ||
| font-size: 16px; | ||
| font-weight: bold; | ||
| margin: 10px 0; | ||
| padding: 5px; | ||
| } | ||
| .u-item-title { | ||
| padding: 12rpx; | ||
| } | ||
| .u-config-item { | ||
| padding: 24rpx 0; | ||
| } | ||
| } | ||
| .slot-content { | ||
| background-color: #ffffff; | ||
| padding: 24rpx; | ||
| .item-box { | ||
| margin-bottom: 50rpx; | ||
| display: flex; | ||
| flex-wrap: wrap; | ||
| justify-content: space-between; | ||
| .item { | ||
| border: 1px solid #2979ff; | ||
| color: #2979ff; | ||
| padding: 8rpx 40rpx; | ||
| border-radius: 100rpx; | ||
| margin-top: 30rpx; | ||
| font-size: 28rpx; | ||
| } | ||
| .active { | ||
| color: #ffffff; | ||
| background-color: #2979ff; | ||
| } | ||
| } | ||
| } | ||
| </style> |
| <template> | ||
| <view style="height: calc(100vh - 44px);background-color: #f5f5f5;"> | ||
| <v-facility-detail | ||
| :title="title" | ||
| :type="type" | ||
| :id="id" | ||
| :token="token" | ||
| :reqParams="reqParams" | ||
| :version="version" | ||
| ></v-facility-detail> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import { getUrlParam } from '@vtx/utils'; | ||
| export default { | ||
| data() { | ||
| return { | ||
| reqParams: { | ||
| token: getUrlParam('token') || '6640c8db-23c2-4800-b60e-968f5471607e', | ||
| tenantId: getUrlParam('tenantId') || '7cf54d8ea4864ff28cd515af6eed5e40', | ||
| userId: getUrlParam('userId') || 'e69114cc5f382745917341bd9d18b0ba', | ||
| apiPre: 'https://lhsw.cloudhw.cn:8466/', | ||
| }, | ||
| type: '1', | ||
| id: getUrlParam('id') || '2001908740425117698', | ||
| title: decodeURIComponent(getUrlParam('title') || '919雨水窨井1'), | ||
| token: getUrlParam('token') || '6640c8db-23c2-4800-b60e-968f5471607e', | ||
| version: '2', | ||
| }; | ||
| }, | ||
| }; | ||
| </script> | ||
| <style></style> |
| <template> | ||
| <view style="height: 100vh; background-color: #f5f5f5"> | ||
| <u-button @click="click">打开详情</u-button> | ||
| <v-facility-detail | ||
| :title="title" | ||
| :type="type" | ||
| :id="id" | ||
| :token="token" | ||
| :reqParams="reqParams" | ||
| :show="show" | ||
| @close="show = false" | ||
| ></v-facility-detail> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| data() { | ||
| return { | ||
| show: false, | ||
| reqParams: { | ||
| token: '0fc6621a-727b-4dc0-8d70-e6e7a4f81781', | ||
| tenantId: '7cf54d8ea4864ff28cd515af6eed5e40', | ||
| userId: 'e69114cc5f382745917341bd9d18b0ba', | ||
| apiPre: 'http://183.129.170.219:8103/', | ||
| }, | ||
| type: '1', | ||
| id: '1839180487893975040', | ||
| title: '0926雨1水窨井1', | ||
| token: '0fc6621a-727b-4dc0-8d70-e6e7a4f81781', | ||
| }; | ||
| }, | ||
| methods: { | ||
| click() { | ||
| if (this.id == '1839180487893975040') { | ||
| this.id = '1819197588860043264'; | ||
| this.title = '东方大道雨水窨井001'; | ||
| } else { | ||
| this.id = '1839180487893975040'; | ||
| this.title = '0926雨1水窨井1'; | ||
| } | ||
| this.show = true; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style></style> |
| <template> | ||
| <view> | ||
| <u-navbar :title="$t('tu-pian')" @leftClick="navigateToHome" :border="true"></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-image width="100%" height="300rpx" :src="src"></v-image> | ||
| </c-demo> | ||
| <c-demo :title="$t('duo-zhang-tu-pian-yu-lan')"> | ||
| <v-image-previewer> | ||
| <v-image width="100%" height="300rpx" :src="src" style="margin-bottom: 8px"></v-image> | ||
| <v-image width="100%" height="300rpx" | ||
| src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/auto-orient,1/resize,p_10/quality,q_10"></v-image> | ||
| </v-image-previewer> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| return { | ||
| src: | ||
| 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=273034283,2961511002&fm=26&gp=0.jpg', | ||
| }; | ||
| }, | ||
| }; | ||
| </script> |
| <template> | ||
| <view> | ||
| <u-navbar :title="$t('xia-la-duo-xuan')" @leftClick="navigateToHome" :border="true"></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-multiple-select :data="list" v-model="checkedKeys"></v-multiple-select> | ||
| <v-multiple-select :data="list" v-model="checkedKeys1"></v-multiple-select> | ||
| <v-multiple-select :data="list" v-model="checkedKeys2" border="surround"></v-multiple-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('zi-ding-yi-que-ren-yan-se-he-zi-ti')"> | ||
| <v-multiple-select :data="[]" confirmText="提交" confirmColor="#18b566"></v-multiple-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('zhong-zhi-gong-neng')"> | ||
| <u-button @click="reset">{{ $t('zhong-zhi') }}</u-button> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| const data = []; | ||
| for (let i = 10; i < 36; i++) { | ||
| data.push({ | ||
| key: i.toString(36) + i, | ||
| value: i.toString(36) + i, | ||
| }); | ||
| } | ||
| return { | ||
| list: data, | ||
| checkedKeys: [], | ||
| checkedKeys1: ['a10'], | ||
| checkedKeys2: [], | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.checkedKeys1 = ['a10']; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style></style> |
| <template> | ||
| <view> | ||
| <u-navbar :title="$t('xia-la')" @leftClick="navigateToHome" :border="true"></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-select :data="list" v-model="checkedKeys1" :customStyle="{ background: '#f3f5f9' }" | ||
| /></c-demo> | ||
| <c-demo :title="$t('')"> | ||
| <v-select :data="list" v-model="checkedKeys2" @change="change" /> | ||
| </c-demo> | ||
| <c-demo :title="$t('')"> | ||
| <v-select | ||
| :data="list" | ||
| v-model="checkedKeys3" | ||
| border="surround" | ||
| :clear="clear" | ||
| :clearFlag="true" | ||
| /></c-demo> | ||
| <c-demo :title="$t('')"> | ||
| <v-select :data="list" v-model="checkedKeys4" border="surround" /> | ||
| </c-demo> | ||
| <c-demo :title="$t('zhong-zhi-gong-neng')"> | ||
| <u-button @click="reset">{{ $t('zhong-zhi') }}</u-button> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| const data = []; | ||
| for (let i = 10; i < 36; i++) { | ||
| data.push({ | ||
| key: i.toString(36) + i + Math.random(), | ||
| value: i.toString(36) + i, | ||
| }); | ||
| } | ||
| return { | ||
| list: data, | ||
| checkedKeys1: 'k20', | ||
| checkedKeys2: '', | ||
| checkedKeys3: '', | ||
| checkedKeys4: '', | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.checkedKeys1 = 'k20'; | ||
| this.checkedKeys2 = ''; | ||
| this.checkedKeys3 = ''; | ||
| this.checkedKeys4 = ''; | ||
| }, | ||
| clear() { | ||
| this.checkedKeys1 = ''; | ||
| }, | ||
| change(val) { | ||
| console.log('111val', val); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style></style> |
| <template> | ||
| <view> | ||
| <u-navbar :title="$t('xia-la-shu')" @leftClick="navigateToHome" :border="true"></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-tree-select | ||
| v-model="checkedKey" | ||
| :treeData="treeData" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| ></v-tree-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('duo-xuan-mo-shi-fu-zi-bu-hu-xiang-guan-lian')"> | ||
| <v-tree-select | ||
| v-model="checkedKey1" | ||
| :treeData="treeData" | ||
| mode="multiple" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| @change="handleChange" | ||
| ></v-tree-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('duo-xuan-mo-shi-fu-zi-hu-xiang-guan-lian')"> | ||
| <v-tree-select | ||
| v-model="checkedKey11" | ||
| :treeData="treeData" | ||
| mode="multiple" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| @change="handleChange" | ||
| :checkStrictly="true" | ||
| ></v-tree-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('bu-xian-shi-input-shu-ru-kuang-chu-fa')"> | ||
| <u-button type="primary" @click="visible2 = true" size="medium">{{ | ||
| $t('dian-ji-ci-chu-chu-fa-duo-xuan') | ||
| }}</u-button> | ||
| <v-tree-select | ||
| v-model="checkedKey2" | ||
| :treeData="treeData" | ||
| mode="multiple" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| :input="false" | ||
| v-if="visible2" | ||
| @hide="visible2 = false" | ||
| ></v-tree-select> | ||
| <u-cell :title="$t('jie-guo')" :value="inputValue2" :arrow="false"></u-cell> | ||
| <u-button type="primary" @click="visible3 = true" size="medium">{{ | ||
| $t('dian-ji-ci-chu-chu-fa-dan-xuan') | ||
| }}</u-button> | ||
| <v-tree-select | ||
| v-model="checkedKey3" | ||
| :treeData="treeData" | ||
| :node-key="'id'" | ||
| :node-label="'label'" | ||
| :input="false" | ||
| v-if="visible3" | ||
| @hide="visible3 = false" | ||
| ></v-tree-select> | ||
| <u-cell :title="$t('jie-guo')" :value="inputValue3" :arrow="false"></u-cell> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| return { | ||
| treeData: [], | ||
| checkedKey: [], | ||
| checkedKey1: [1, 11, 111], | ||
| checkedKey11: [1, 11, 111], | ||
| checkedKey2: [], | ||
| checkedKey3: [], | ||
| visible2: false, | ||
| visible3: false, | ||
| }; | ||
| }, | ||
| onLoad() { | ||
| // 模拟异步请求 | ||
| setTimeout(() => { | ||
| this.treeData = [ | ||
| { | ||
| id: 1, | ||
| label: this.$t('yi-ji-1'), | ||
| children: [ | ||
| { | ||
| id: 11, | ||
| label: this.$t('er-ji-11'), | ||
| children: [ | ||
| { | ||
| id: 111, | ||
| label: this.$t('san-ji-111'), | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 2, | ||
| label: this.$t('yi-ji-2'), | ||
| children: [ | ||
| { | ||
| id: 21, | ||
| label: this.$t('er-ji-21'), | ||
| children: [ | ||
| { | ||
| id: 211, | ||
| label: this.$t('san-ji-211'), | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 22, | ||
| label: this.$t('er-ji-22'), | ||
| children: [ | ||
| { | ||
| id: 221, | ||
| label: this.$t('san-ji-221'), | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 3, | ||
| label: this.$t('yi-ji-3'), | ||
| children: [ | ||
| { | ||
| id: 31, | ||
| label: this.$t('er-ji-31'), | ||
| children: [ | ||
| { | ||
| id: 311, | ||
| label: this.$t('san-ji-311'), | ||
| }, | ||
| ], | ||
| }, | ||
| { | ||
| id: 32, | ||
| label: this.$t('er-ji-32'), | ||
| children: [ | ||
| { | ||
| id: 321, | ||
| label: this.$t('san-ji-321'), | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
| }, 200); | ||
| }, | ||
| computed: { | ||
| // 计算文本框显示的值 | ||
| inputValue2: function() { | ||
| return this.checkedKey2.join(','); | ||
| }, | ||
| inputValue3: function() { | ||
| return this.checkedKey3.join(','); | ||
| }, | ||
| }, | ||
| methods: { | ||
| handleChange(checkedKeys) { | ||
| console.log(checkedKeys); | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style></style> |
| <template> | ||
| <view> | ||
| <u-navbar @leftClick="navigateToHome" :border="true"></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-upload :fileList="photos" @change="handleChange"></v-upload> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| return { | ||
| // [{id: 'f4f29d6892be4be796207a27a746e839', name: '2.jpg', type: 'image/jpeg'}] | ||
| photos: [], | ||
| }; | ||
| }, | ||
| methods: { | ||
| handleChange(fileList) { | ||
| console.log('111fileList', fileList); | ||
| // TODO | ||
| }, | ||
| }, | ||
| }; | ||
| </script> |
| <template> | ||
| <view> | ||
| <u-navbar @leftClick="navigateToHome" :border="true"></u-navbar> | ||
| <c-demo :title="$t('ji-ben-shi-yong')"> | ||
| <v-virtual-select | ||
| :requestOption="{ | ||
| url: '/cloud/advise/web/base/shop/list', | ||
| method: 'POST', | ||
| }" | ||
| :requestParams="{ | ||
| order: 'asc,asc', | ||
| sort: 'orgName,manageUnitName', | ||
| }" | ||
| :setValueLabel="{ | ||
| value: 'id', | ||
| label: 'name', | ||
| }" | ||
| :setPageRows="{ | ||
| page: 'page', | ||
| rows: 'rows', | ||
| searchText: 'nameLike', | ||
| }" | ||
| v-model="checkedKeys" | ||
| border="bottom" | ||
| ></v-virtual-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('duo-xuan')"> | ||
| <v-virtual-select | ||
| :requestOption="{ | ||
| url: '/cloud/advise/web/base/shop/list', | ||
| method: 'POST', | ||
| }" | ||
| :requestParams="{ | ||
| order: 'asc,asc', | ||
| sort: 'orgName,manageUnitName', | ||
| }" | ||
| :setValueLabel="{ | ||
| value: 'id', | ||
| label: 'name', | ||
| }" | ||
| :setPageRows="{ | ||
| page: 'page', | ||
| rows: 'rows', | ||
| searchText: 'nameLike', | ||
| }" | ||
| v-model="checkedKeys1" | ||
| border="bottom" | ||
| mode="multiple" | ||
| ></v-virtual-select> | ||
| </c-demo> | ||
| <c-demo :title="$t('zhong-zhi-gong-neng')"> | ||
| <u-button @click="reset">{{ $t('zhong-zhi') }}</u-button> | ||
| </c-demo> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import mixin from '../../mixins/components'; | ||
| export default { | ||
| mixins: [mixin], | ||
| data() { | ||
| const data = []; | ||
| for (let i = 10; i < 10000; i++) { | ||
| const value = i.toString(36) + i; | ||
| data.push({ | ||
| value, | ||
| label: value, | ||
| }); | ||
| } | ||
| return { | ||
| list: data, | ||
| checkedKeys: [], | ||
| checkedKeys1: [{ value: 'a10', label: 'a10' }], | ||
| }; | ||
| }, | ||
| methods: { | ||
| reset() { | ||
| this.checkedKeys = [{ value: 'a10', label: 'a10' }]; | ||
| this.checkedKeys1 = []; | ||
| }, | ||
| }, | ||
| }; | ||
| </script> | ||
| <style></style> |
| ## 2.6.2(2023-10-31) | ||
| 1.`修复` 在源码中有异常字符导致的在vue3中编译报错的问题。 | ||
| 2.`修复` 在微信小程序中`z-paging-refresh`的height无效的问题(by xiaohe0601)。 | ||
| 近期更新: | ||
| ============================= | ||
| 1.`新增` 手动更新自定义下拉刷新view高度方法。 | ||
| 2.`新增` 点击返回顶部按钮添加事件监听&支持拦截。 | ||
| 3.`新增` 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况。 | ||
| 4.`新增` 支持配置网络请求失败触发`reject`。 | ||
| 5.`修复` 显示空数据图时,滚动到底部依然可以加载更多的问题。 | ||
| 6.`修复` 在vue2中底部加载更多相关`slot`使用`template`插入无效的问题。 | ||
| 7.`修复` `complete`的`Promise`可能无效的问题。 | ||
| 8.`优化` `hooks`判断`z-paging`为空则不调用。 | ||
| <!-- z-paging --> | ||
| <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> | ||
| <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> | ||
| <!-- 反馈QQ群:790460711 --> | ||
| <!-- z-paging-cell,用于在nvue中使用cell包裹,vue中使用view包裹 --> | ||
| <template> | ||
| <!-- #ifdef APP-NVUE --> | ||
| <cell :style="[cellStyle]"> | ||
| <slot /> | ||
| </cell> | ||
| <!-- #endif --> | ||
| <!-- #ifndef APP-NVUE --> | ||
| <view :style="[cellStyle]"> | ||
| <slot /> | ||
| </view> | ||
| <!-- #endif --> | ||
| </template> | ||
| <script> | ||
| export default { | ||
| name: "z-paging-cell", | ||
| props: { | ||
| //cellStyle | ||
| cellStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {} | ||
| } | ||
| } | ||
| } | ||
| } | ||
| </script> | ||
| <!-- z-paging --> | ||
| <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> | ||
| <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> | ||
| <!-- 反馈QQ群:790460711 --> | ||
| <!-- 空数据占位view,此组件支持easycom规范,可以在项目中直接引用 --> | ||
| <template> | ||
| <view :class="{'zp-container':true,'zp-container-fixed':emptyViewFixed}" :style="[finalEmptyViewStyle]" @click="emptyViewClick"> | ||
| <view class="zp-main"> | ||
| <image v-if="!emptyViewImg.length" class="zp-main-image" :style="[emptyViewImgStyle]" :src="emptyImg" /> | ||
| <image v-else class="zp-main-image" mode="aspectFit" :style="[emptyViewImgStyle]" :src="emptyViewImg" /> | ||
| <text class="zp-main-title" :style="[emptyViewTitleStyle]">{{emptyViewText}}</text> | ||
| <text v-if="showEmptyViewReload" class="zp-main-error-btn" :style="[emptyViewReloadStyle]" @click.stop="reloadClick">{{emptyViewReloadText}}</text> | ||
| </view> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import zStatic from '../z-paging/js/z-paging-static' | ||
| export default { | ||
| name: "z-paging-empty-view", | ||
| data() { | ||
| return { | ||
| }; | ||
| }, | ||
| props: { | ||
| //空数据描述文字 | ||
| emptyViewText: { | ||
| type: String, | ||
| default: '没有数据哦~' | ||
| }, | ||
| //空数据图片 | ||
| emptyViewImg: { | ||
| type: String, | ||
| default: '' | ||
| }, | ||
| //是否显示空数据图重新加载按钮 | ||
| showEmptyViewReload: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
| //空数据点击重新加载文字 | ||
| emptyViewReloadText: { | ||
| type: String, | ||
| default: '重新加载' | ||
| }, | ||
| //是否是加载失败 | ||
| isLoadFailed: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
| //空数据图样式 | ||
| emptyViewStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {} | ||
| } | ||
| }, | ||
| //空数据图img样式 | ||
| emptyViewImgStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {} | ||
| } | ||
| }, | ||
| //空数据图描述文字样式 | ||
| emptyViewTitleStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {} | ||
| } | ||
| }, | ||
| //空数据图重新加载按钮样式 | ||
| emptyViewReloadStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {} | ||
| } | ||
| }, | ||
| //空数据图z-index | ||
| emptyViewZIndex: { | ||
| type: Number, | ||
| default: 9 | ||
| }, | ||
| //空数据图片是否使用fixed布局并铺满z-paging | ||
| emptyViewFixed: { | ||
| type: Boolean, | ||
| default: true | ||
| } | ||
| }, | ||
| computed: { | ||
| emptyImg() { | ||
| return this.isLoadFailed ? zStatic.base64Error : zStatic.base64Empty; | ||
| }, | ||
| finalEmptyViewStyle(){ | ||
| this.emptyViewStyle['z-index'] = this.emptyViewZIndex; | ||
| return this.emptyViewStyle; | ||
| } | ||
| }, | ||
| methods: { | ||
| reloadClick() { | ||
| this.$emit('reload'); | ||
| }, | ||
| emptyViewClick() { | ||
| this.$emit('viewClick'); | ||
| } | ||
| } | ||
| } | ||
| </script> | ||
| <style scoped> | ||
| .zp-container{ | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| align-items: center; | ||
| justify-content: center; | ||
| } | ||
| .zp-container-fixed { | ||
| /* #ifndef APP-NVUE */ | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| width: 100%; | ||
| height: 100%; | ||
| /* #endif */ | ||
| /* #ifdef APP-NVUE */ | ||
| flex: 1; | ||
| /* #endif */ | ||
| } | ||
| .zp-main{ | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| align-items: center; | ||
| padding: 50rpx 0rpx; | ||
| } | ||
| .zp-main-image { | ||
| width: 200rpx; | ||
| height: 200rpx; | ||
| } | ||
| .zp-main-title { | ||
| font-size: 26rpx; | ||
| color: #aaaaaa; | ||
| text-align: center; | ||
| margin-top: 10rpx; | ||
| } | ||
| .zp-main-error-btn { | ||
| font-size: 26rpx; | ||
| padding: 8rpx 24rpx; | ||
| border: solid 1px #dddddd; | ||
| border-radius: 6rpx; | ||
| color: #aaaaaa; | ||
| margin-top: 50rpx; | ||
| } | ||
| </style> |
| <!-- z-paging --> | ||
| <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> | ||
| <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> | ||
| <!-- 反馈QQ群:790460711 --> | ||
| <!-- 滑动切换选项卡swiper-item,此组件支持easycom规范,可以在项目中直接引用 --> | ||
| <template> | ||
| <view class="zp-swiper-item-container"> | ||
| <z-paging ref="paging" :fixed="false" | ||
| :auto="false" :useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle" | ||
| :preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol" | ||
| @query="_queryList" @listChange="_updateList" style="height: 100%;"> | ||
| <slot /> | ||
| <template #header> | ||
| <slot name="header"/> | ||
| </template> | ||
| <template #cell="{item,index}"> | ||
| <slot name="cell" :item="item" :index="index"/> | ||
| </template> | ||
| <template #footer> | ||
| <slot name="footer"/> | ||
| </template> | ||
| </z-paging> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import zPaging from '../z-paging/z-paging' | ||
| export default { | ||
| name: "z-paging-swiper-item", | ||
| components: { zPaging }, | ||
| data() { | ||
| return { | ||
| firstLoaded: false | ||
| } | ||
| }, | ||
| props: { | ||
| //当前组件的index,也就是当前组件是swiper中的第几个 | ||
| tabIndex: { | ||
| type: Number, | ||
| default: function() { | ||
| return 0 | ||
| } | ||
| }, | ||
| //当前swiper切换到第几个index | ||
| currentIndex: { | ||
| type: Number, | ||
| default: function() { | ||
| return 0 | ||
| } | ||
| }, | ||
| //是否使用虚拟列表,默认为否 | ||
| useVirtualList: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
| //是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true | ||
| useInnerList: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
| //内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项 | ||
| cellKeyName: { | ||
| type: String, | ||
| default: '' | ||
| }, | ||
| //innerList样式 | ||
| innerListStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {}; | ||
| } | ||
| }, | ||
| //预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题 | ||
| preloadPage: { | ||
| type: [Number, String], | ||
| default: 12 | ||
| }, | ||
| //虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。 | ||
| cellHeightMode: { | ||
| type: String, | ||
| default: 'fixed' | ||
| }, | ||
| //虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2 | ||
| virtualListCol: { | ||
| type: [Number, String], | ||
| default: 1 | ||
| }, | ||
| //虚拟列表scroll取样帧率,默认为60,过高可能出现卡顿等问题 | ||
| virtualScrollFps: { | ||
| type: [Number, String], | ||
| default: 60 | ||
| }, | ||
| }, | ||
| watch: { | ||
| currentIndex: { | ||
| handler(newVal, oldVal) { | ||
| if (newVal === this.tabIndex) { | ||
| //懒加载,当滑动到当前的item时,才去加载 | ||
| if (!this.firstLoaded) { | ||
| this.$nextTick(()=>{ | ||
| let delay = 5; | ||
| // #ifdef MP-TOUTIAO | ||
| delay = 100; | ||
| // #endif | ||
| setTimeout(() => { | ||
| this.$refs.paging.reload().catch(() => {}); | ||
| }, delay); | ||
| }) | ||
| } | ||
| } | ||
| }, | ||
| immediate: true | ||
| } | ||
| }, | ||
| methods: { | ||
| reload(data) { | ||
| return this.$refs.paging.reload(data); | ||
| }, | ||
| complete(data) { | ||
| this.firstLoaded = true; | ||
| return this.$refs.paging.complete(data); | ||
| }, | ||
| _queryList(pageNo, pageSize, from) { | ||
| this.$emit('query', pageNo, pageSize, from); | ||
| }, | ||
| _updateList(list) { | ||
| this.$emit('updateList', list); | ||
| } | ||
| } | ||
| } | ||
| </script> | ||
| <style scoped> | ||
| .zp-swiper-item-container { | ||
| /* #ifndef APP-NVUE */ | ||
| height: 100%; | ||
| /* #endif */ | ||
| /* #ifdef APP-NVUE */ | ||
| flex: 1; | ||
| /* #endif */ | ||
| } | ||
| </style> |
| <!-- z-paging --> | ||
| <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> | ||
| <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> | ||
| <!-- 反馈QQ群:790460711 --> | ||
| <!-- 滑动切换选项卡swiper容器,此组件支持easycom规范,可以在项目中直接引用 --> | ||
| <template> | ||
| <view :class="fixed?'zp-swiper-container zp-swiper-container-fixed':'zp-swiper-container'" :style="[finalSwiperStyle]"> | ||
| <!-- #ifndef APP-PLUS --> | ||
| <view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view> | ||
| <!-- #endif --> | ||
| <slot v-if="zSlots.top" name="top" /> | ||
| <view class="zp-swiper-super"> | ||
| <view v-if="zSlots.left" :class="{'zp-swiper-left':true,'zp-absoulte':isOldWebView}"> | ||
| <slot name="left" /> | ||
| </view> | ||
| <view :class="{'zp-swiper':true,'zp-absoulte':isOldWebView}" :style="[swiperContentStyle]"> | ||
| <slot /> | ||
| </view> | ||
| <view v-if="zSlots.right" :class="{'zp-swiper-right':true,'zp-absoulte zp-right':isOldWebView}"> | ||
| <slot name="right" /> | ||
| </view> | ||
| </view> | ||
| <slot v-if="zSlots.bottom" name="bottom" /> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import commonLayoutModule from '../z-paging/js/modules/common-layout' | ||
| export default { | ||
| name: "z-paging-swiper", | ||
| mixins: [commonLayoutModule], | ||
| data() { | ||
| return { | ||
| swiperContentStyle: {} | ||
| }; | ||
| }, | ||
| props: { | ||
| //是否使用fixed布局,默认为是 | ||
| fixed: { | ||
| type: Boolean, | ||
| default: true | ||
| }, | ||
| //是否开启底部安全区域适配 | ||
| safeAreaInsetBottom: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
| //z-paging-swiper样式 | ||
| swiperStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return {}; | ||
| }, | ||
| } | ||
| }, | ||
| mounted() { | ||
| this.$nextTick(() => { | ||
| this.systemInfo = uni.getSystemInfoSync(); | ||
| }) | ||
| // #ifndef APP-PLUS | ||
| this._getCssSafeAreaInsetBottom(); | ||
| // #endif | ||
| this.updateLeftAndRightWidth(); | ||
| this.swiperContentStyle = { 'flex': '1' }; | ||
| // #ifndef APP-NVUE | ||
| this.swiperContentStyle = { width: '100%',height: '100%' }; | ||
| // #endif | ||
| }, | ||
| computed: { | ||
| finalSwiperStyle() { | ||
| const swiperStyle = this.swiperStyle; | ||
| if (!this.systemInfo) return swiperStyle; | ||
| const windowTop = this.windowTop; | ||
| const windowBottom = this.systemInfo.windowBottom; | ||
| if (this.fixed) { | ||
| if (windowTop && !swiperStyle.top) { | ||
| swiperStyle.top = windowTop + 'px'; | ||
| } | ||
| if (!swiperStyle.bottom) { | ||
| let bottom = windowBottom || 0; | ||
| bottom += this.safeAreaInsetBottom ? this.safeAreaBottom : 0; | ||
| if (bottom > 0) { | ||
| swiperStyle.bottom = bottom + 'px'; | ||
| } | ||
| } | ||
| } | ||
| return swiperStyle; | ||
| } | ||
| }, | ||
| methods: { | ||
| //更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用 | ||
| updateLeftAndRightWidth() { | ||
| if (!this.isOldWebView) return; | ||
| this.$nextTick(() => this._updateLeftAndRightWidth(this.swiperContentStyle, 'zp-swiper')); | ||
| } | ||
| } | ||
| } | ||
| </script> | ||
| <style scoped> | ||
| .zp-swiper-container { | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| flex: 1; | ||
| } | ||
| .zp-swiper-container-fixed { | ||
| position: fixed; | ||
| /* #ifndef APP-NVUE */ | ||
| height: auto; | ||
| width: auto; | ||
| /* #endif */ | ||
| top: 0; | ||
| left: 0; | ||
| bottom: 0; | ||
| right: 0; | ||
| } | ||
| .zp-safe-area-inset-bottom { | ||
| position: absolute; | ||
| /* #ifndef APP-PLUS */ | ||
| height: env(safe-area-inset-bottom); | ||
| /* #endif */ | ||
| } | ||
| .zp-swiper-super { | ||
| flex: 1; | ||
| overflow: hidden; | ||
| position: relative; | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: row; | ||
| } | ||
| .zp-swiper-left,.zp-swiper-right{ | ||
| /* #ifndef APP-NVUE */ | ||
| height: 100%; | ||
| /* #endif */ | ||
| } | ||
| .zp-swiper { | ||
| flex: 1; | ||
| /* #ifndef APP-NVUE */ | ||
| height: 100%; | ||
| width: 100%; | ||
| /* #endif */ | ||
| } | ||
| .zp-absoulte { | ||
| /* #ifndef APP-NVUE */ | ||
| position: absolute; | ||
| top: 0; | ||
| width: auto; | ||
| /* #endif */ | ||
| } | ||
| .zp-right{ | ||
| right: 0; | ||
| } | ||
| .zp-swiper-item { | ||
| height: 100%; | ||
| } | ||
| </style> |
| <!-- [z-paging]上拉加载更多view --> | ||
| <template> | ||
| <view class="zp-l-container" :style="[c.customStyle]" @click="doClick"> | ||
| <template v-if="!c.hideContent"> | ||
| <text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" class="zp-l-line" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" /> | ||
| <!-- #ifndef APP-NVUE --> | ||
| <image v-if="finalStatus===M.Loading&&!!c.loadingIconCustomImage" | ||
| :src="c.loadingIconCustomImage" :style="[c.iconCustomStyle]" :class="{'zp-l-line-loading-custom-image':true,'zp-l-line-loading-custom-image-animated':c.loadingAnimated}" /> | ||
| <image v-if="finalStatus===M.Loading&&finalLoadingIconType==='flower'&&!c.loadingIconCustomImage.length" | ||
| class="zp-line-loading-image" :style="[c.iconCustomStyle]" :src="zTheme.flower[ts]" /> | ||
| <!-- #endif --> | ||
| <!-- #ifdef APP-NVUE --> | ||
| <view> | ||
| <loading-indicator v-if="finalStatus===M.Loading&&finalLoadingIconType!=='circle'" class="zp-line-loading-image" :style="[{color:zTheme.indicator[ts]}]" :animating="true" /> | ||
| </view> | ||
| <!-- #endif --> | ||
| <text v-if="finalStatus===M.Loading&&finalLoadingIconType==='circle'&&!c.loadingIconCustomImage.length" | ||
| class="zp-l-circle-loading-view" :style="[{borderColor:zTheme.circleBorder[ts],borderTopColor:zTheme.circleBorderTop[ts]},c.iconCustomStyle]" /> | ||
| <text class="zp-l-text" :style="[{color:zTheme.title[ts]},c.titleCustomStyle]">{{ownLoadingMoreText}}</text> | ||
| <text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" class="zp-l-line" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" /> | ||
| </template> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import zStatic from '../js/z-paging-static' | ||
| import Enum from '../js/z-paging-enum' | ||
| export default { | ||
| name: 'z-paging-load-more', | ||
| data() { | ||
| return { | ||
| M: Enum.More, | ||
| zTheme: { | ||
| title: { white: '#efefef', black: '#a4a4a4' }, | ||
| line: { white: '#efefef', black: '#eeeeee' }, | ||
| circleBorder: { white: '#aaaaaa', black: '#c8c8c8' }, | ||
| circleBorderTop: { white: '#ffffff', black: '#444444' }, | ||
| flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower }, | ||
| indicator: { white: '#eeeeee', black: '#777777' } | ||
| } | ||
| }; | ||
| }, | ||
| props: ['zConfig'], | ||
| computed: { | ||
| ts() { | ||
| return this.c.defaultThemeStyle; | ||
| }, | ||
| c() { | ||
| return this.zConfig || {}; | ||
| }, | ||
| ownLoadingMoreText() { | ||
| const statusTextArr = [this.c.defaultText,this.c.loadingText,this.c.noMoreText,this.c.failText]; | ||
| return statusTextArr[this.finalStatus]; | ||
| }, | ||
| finalStatus() { | ||
| if (this.c.defaultAsLoading && this.c.status === this.M.Default) return this.M.Loading; | ||
| return this.c.status; | ||
| }, | ||
| finalLoadingIconType() { | ||
| // #ifdef APP-NVUE | ||
| return 'flower'; | ||
| // #endif | ||
| return this.c.loadingIconType; | ||
| } | ||
| }, | ||
| methods: { | ||
| doClick() { | ||
| this.$emit('doClick'); | ||
| } | ||
| } | ||
| } | ||
| </script> | ||
| <style scoped> | ||
| @import "../css/z-paging-static.css"; | ||
| .zp-l-container { | ||
| height: 80rpx; | ||
| font-size: 27rpx; | ||
| /* #ifndef APP-NVUE */ | ||
| clear: both; | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: row; | ||
| align-items: center; | ||
| justify-content: center; | ||
| } | ||
| .zp-l-line-loading-custom-image { | ||
| color: #a4a4a4; | ||
| margin-right: 8rpx; | ||
| width: 28rpx; | ||
| height: 28rpx; | ||
| } | ||
| .zp-l-line-loading-custom-image-animated{ | ||
| /* #ifndef APP-NVUE */ | ||
| animation: loading-circle 1s linear infinite; | ||
| /* #endif */ | ||
| } | ||
| .zp-l-circle-loading-view { | ||
| margin-right: 8rpx; | ||
| width: 23rpx; | ||
| height: 23rpx; | ||
| border: 3rpx solid #dddddd; | ||
| border-radius: 50%; | ||
| /* #ifndef APP-NVUE */ | ||
| animation: loading-circle 1s linear infinite; | ||
| /* #endif */ | ||
| /* #ifdef APP-NVUE */ | ||
| width: 30rpx; | ||
| height: 30rpx; | ||
| /* #endif */ | ||
| } | ||
| .zp-l-text { | ||
| /* #ifdef APP-NVUE */ | ||
| font-size: 30rpx; | ||
| margin: 0rpx 10rpx; | ||
| /* #endif */ | ||
| } | ||
| .zp-l-line { | ||
| height: 1px; | ||
| width: 100rpx; | ||
| margin: 0rpx 10rpx; | ||
| } | ||
| /* #ifndef APP-NVUE */ | ||
| @keyframes loading-circle { | ||
| 0% { | ||
| -webkit-transform: rotate(0deg); | ||
| transform: rotate(0deg); | ||
| } | ||
| 100% { | ||
| -webkit-transform: rotate(360deg); | ||
| transform: rotate(360deg); | ||
| } | ||
| } | ||
| /* #endif */ | ||
| </style> |
| <!-- [z-paging]下拉刷新view --> | ||
| <template> | ||
| <view style="height: 100%;"> | ||
| <view :class="showUpdateTime?'zp-r-container zp-r-container-padding':'zp-r-container'"> | ||
| <view class="zp-r-left"> | ||
| <image v-if="status!==R.Loading" :class="leftImageClass" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" /> | ||
| <!-- #ifndef APP-NVUE --> | ||
| <image v-else :class="{'zp-line-loading-image':refreshingAnimated,'zp-r-left-image':true}" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" /> | ||
| <!-- #endif --> | ||
| <!-- #ifdef APP-NVUE --> | ||
| <view v-else :style="[{'margin-right':showUpdateTime?'18rpx':'12rpx'}]"> | ||
| <loading-indicator :class="isIos?'zp-loading-image-ios':'zp-loading-image-android'" | ||
| :style="[{color:zTheme.indicator[ts]},imgStyle]" :animating="true" /> | ||
| </view> | ||
| <!-- #endif --> | ||
| </view> | ||
| <view class="zp-r-right"> | ||
| <text class="zp-r-right-text" :style="[rightTextStyle,titleStyle]">{{currentTitle}}</text> | ||
| <text v-if="showUpdateTime&&refresherTimeText.length" class="zp-r-right-text zp-r-right-time-text" :style="[rightTextStyle,updateTimeStyle]"> | ||
| {{refresherTimeText}} | ||
| </text> | ||
| </view> | ||
| </view> | ||
| </view> | ||
| </template> | ||
| <script> | ||
| import zStatic from '../js/z-paging-static' | ||
| import u from '../js/z-paging-utils' | ||
| import Enum from '../js/z-paging-enum' | ||
| export default { | ||
| name: 'z-paging-refresh', | ||
| data() { | ||
| return { | ||
| R: Enum.Refresher, | ||
| isIos: uni.getSystemInfoSync().platform === 'ios', | ||
| refresherTimeText: '', | ||
| zTheme: { | ||
| title: { white: '#efefef', black: '#555555' }, | ||
| arrow: { white: zStatic.base64ArrowWhite, black: zStatic.base64Arrow }, | ||
| flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower }, | ||
| success: { white: zStatic.base64SuccessWhite, black: zStatic.base64Success }, | ||
| indicator: { white: '#eeeeee', black: '#777777' } | ||
| } | ||
| }; | ||
| }, | ||
| props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'defaultImg', 'pullingImg', | ||
| 'refreshingImg', 'completeImg', 'refreshingAnimated', 'showUpdateTime', 'updateTimeKey', 'imgStyle', 'titleStyle', 'updateTimeStyle', 'updateTimeTextMap' | ||
| ], | ||
| computed: { | ||
| ts() { | ||
| return this.defaultThemeStyle; | ||
| }, | ||
| statusTextArr() { | ||
| this.updateTime(); | ||
| return [this.defaultText,this.pullingText,this.refreshingText,this.completeText]; | ||
| }, | ||
| currentTitle() { | ||
| return this.statusTextArr[this.status] || this.defaultText; | ||
| }, | ||
| leftImageClass() { | ||
| if (this.status === this.R.Complete) return 'zp-r-left-image-pre-size'; | ||
| return `zp-r-left-image zp-r-left-image-pre-size ${this.status === this.R.Default ? 'zp-r-arrow-down' : 'zp-r-arrow-top'}`; | ||
| }, | ||
| leftImageStyle() { | ||
| const showUpdateTime = this.showUpdateTime; | ||
| const size = showUpdateTime ? '36rpx' : '30rpx'; | ||
| return {width: size,height: size,'margin-right': showUpdateTime ? '20rpx' : '9rpx'}; | ||
| }, | ||
| leftImageSrc() { | ||
| const R = this.R; | ||
| const status = this.status; | ||
| if (status === R.Default) { | ||
| if (!!this.defaultImg) return this.defaultImg; | ||
| return this.zTheme.arrow[this.ts]; | ||
| } else if (status === R.ReleaseToRefresh) { | ||
| if (!!this.pullingImg) return this.pullingImg; | ||
| if (!!this.defaultImg) return this.defaultImg; | ||
| return this.zTheme.arrow[this.ts]; | ||
| } else if (status === R.Loading) { | ||
| if (!!this.refreshingImg) return this.refreshingImg; | ||
| return this.zTheme.flower[this.ts];; | ||
| } else if (status === R.Complete) { | ||
| if (!!this.completeImg) return this.completeImg; | ||
| return this.zTheme.success[this.ts];; | ||
| } | ||
| return ''; | ||
| }, | ||
| rightTextStyle() { | ||
| let stl = {}; | ||
| // #ifdef APP-NVUE | ||
| const textHeight = this.showUpdateTime ? '40rpx' : '80rpx'; | ||
| stl = {'height': textHeight, 'line-height': textHeight} | ||
| // #endif | ||
| stl['color'] = this.zTheme.title[this.ts]; | ||
| return stl; | ||
| } | ||
| }, | ||
| methods: { | ||
| updateTime() { | ||
| if (this.showUpdateTime) { | ||
| this.refresherTimeText = u.getRefesrherFormatTimeByKey(this.updateTimeKey, this.updateTimeTextMap); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| </script> | ||
| <style scoped> | ||
| @import "../css/z-paging-static.css"; | ||
| .zp-r-container { | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| height: 100%; | ||
| /* #endif */ | ||
| flex-direction: row; | ||
| justify-content: center; | ||
| align-items: center; | ||
| } | ||
| .zp-r-container-padding { | ||
| /* #ifdef APP-NVUE */ | ||
| padding: 15rpx 0rpx; | ||
| /* #endif */ | ||
| } | ||
| .zp-r-left { | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: row; | ||
| align-items: center; | ||
| overflow: hidden; | ||
| /* #ifdef MP-ALIPAY */ | ||
| margin-top: -4rpx; | ||
| /* #endif */ | ||
| } | ||
| .zp-r-left-image { | ||
| transition-duration: .2s; | ||
| transition-property: transform; | ||
| color: #666666; | ||
| } | ||
| .zp-r-left-image-pre-size{ | ||
| /* #ifndef APP-NVUE */ | ||
| width: 30rpx; | ||
| height: 30rpx; | ||
| overflow: hidden; | ||
| /* #endif */ | ||
| } | ||
| .zp-r-arrow-top { | ||
| transform: rotate(0deg); | ||
| } | ||
| .zp-r-arrow-down { | ||
| transform: rotate(180deg); | ||
| } | ||
| .zp-r-right { | ||
| font-size: 27rpx; | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| } | ||
| .zp-r-right-text { | ||
| /* #ifdef APP-NVUE */ | ||
| font-size: 28rpx; | ||
| /* #endif */ | ||
| } | ||
| .zp-r-right-time-text { | ||
| margin-top: 10rpx; | ||
| font-size: 24rpx; | ||
| } | ||
| </style> |
| // z-paging全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置 | ||
| export default {} |
| /* [z-paging]公共css*/ | ||
| .z-paging-content { | ||
| position: relative; | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| width: 100%; | ||
| height: 100%; | ||
| overflow: hidden; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| } | ||
| .z-paging-content-fixed, .zp-loading-fixed { | ||
| position: fixed; | ||
| /* #ifndef APP-NVUE */ | ||
| height: auto; | ||
| width: auto; | ||
| /* #endif */ | ||
| top: 0; | ||
| left: 0; | ||
| bottom: 0; | ||
| right: 0; | ||
| } | ||
| .zp-page-top,.zp-page-bottom { | ||
| /* #ifndef APP-NVUE */ | ||
| width: auto; | ||
| /* #endif */ | ||
| position: fixed; | ||
| left: 0; | ||
| right: 0; | ||
| z-index: 999; | ||
| } | ||
| .zp-page-left,.zp-page-right{ | ||
| /* #ifndef APP-NVUE */ | ||
| height: 100%; | ||
| /* #endif */ | ||
| } | ||
| .zp-scroll-view-super { | ||
| flex: 1; | ||
| overflow: hidden; | ||
| position: relative; | ||
| } | ||
| .zp-view-super{ | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: row; | ||
| } | ||
| .zp-scroll-view-container,.zp-scroll-view { | ||
| position: relative; | ||
| /* #ifndef APP-NVUE */ | ||
| height: 100%; | ||
| width: 100%; | ||
| /* #endif */ | ||
| } | ||
| .zp-absoulte{ | ||
| /* #ifndef APP-NVUE */ | ||
| position: absolute; | ||
| top: 0; | ||
| width: auto; | ||
| /* #endif */ | ||
| } | ||
| .zp-right{ | ||
| right: 0; | ||
| } | ||
| .zp-scroll-view-absolute { | ||
| position: absolute; | ||
| top: 0; | ||
| left: 0; | ||
| } | ||
| /* #ifndef APP-NVUE */ | ||
| .zp-scroll-view-hide-scrollbar ::-webkit-scrollbar { | ||
| display: none; | ||
| -webkit-appearance: none; | ||
| width: 0 !important; | ||
| height: 0 !important; | ||
| background: transparent; | ||
| } | ||
| /* #endif */ | ||
| .zp-paging-touch-view { | ||
| width: 100%; | ||
| height: 100%; | ||
| position: relative; | ||
| } | ||
| .zp-fixed-bac-view { | ||
| position: absolute; | ||
| width: 100%; | ||
| top: 0; | ||
| left: 0; | ||
| height: 200px; | ||
| } | ||
| .zp-paging-main { | ||
| height: 100%; | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| } | ||
| .zp-paging-container { | ||
| flex: 1; | ||
| position: relative; | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| } | ||
| .zp-chat-record-loading-container { | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| width: 100%; | ||
| /* #endif */ | ||
| /* #ifdef APP-NVUE */ | ||
| width: 750rpx; | ||
| /* #endif */ | ||
| align-items: center; | ||
| justify-content: center; | ||
| height: 60rpx; | ||
| font-size: 26rpx; | ||
| } | ||
| .zp-chat-record-loading-custom-image { | ||
| width: 35rpx; | ||
| height: 35rpx; | ||
| /* #ifndef APP-NVUE */ | ||
| animation: loading-flower 1s linear infinite; | ||
| /* #endif */ | ||
| } | ||
| .zp-custom-refresher-container { | ||
| overflow: hidden; | ||
| } | ||
| .zp-custom-refresher-refresh { | ||
| /* #ifndef APP-NVUE */ | ||
| display: block; | ||
| /* #endif */ | ||
| } | ||
| .zp-back-to-top { | ||
| width: 76rpx; | ||
| height: 76rpx; | ||
| z-index: 999; | ||
| position: absolute; | ||
| bottom: 0rpx; | ||
| right: 25rpx; | ||
| transition-duration: .3s; | ||
| transition-property: opacity; | ||
| } | ||
| .zp-back-to-top-show { | ||
| opacity: 1; | ||
| } | ||
| .zp-back-to-top-hide { | ||
| opacity: 0; | ||
| } | ||
| .zp-back-to-top-img { | ||
| /* #ifndef APP-NVUE */ | ||
| width: 100%; | ||
| height: 100%; | ||
| /* #endif */ | ||
| /* #ifdef APP-NVUE */ | ||
| flex: 1; | ||
| /* #endif */ | ||
| z-index: 999; | ||
| } | ||
| .zp-empty-view { | ||
| /* #ifdef APP-NVUE */ | ||
| height: 100%; | ||
| /* #endif */ | ||
| flex: 1; | ||
| } | ||
| .zp-empty-view-center { | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: column; | ||
| align-items: center; | ||
| justify-content: center; | ||
| } | ||
| .zp-loading-fixed { | ||
| z-index: 9999; | ||
| } | ||
| .zp-safe-area-inset-bottom { | ||
| position: absolute; | ||
| /* #ifndef APP-PLUS */ | ||
| height: env(safe-area-inset-bottom); | ||
| /* #endif */ | ||
| } | ||
| .zp-n-refresh-container { | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| justify-content: center; | ||
| width: 750rpx; | ||
| } | ||
| .zp-n-list-container{ | ||
| /* #ifndef APP-NVUE */ | ||
| display: flex; | ||
| /* #endif */ | ||
| flex-direction: row; | ||
| flex: 1; | ||
| } |
| /* [z-paging]公用的静态css资源 */ | ||
| .zp-line-loading-image { | ||
| margin-right: 8rpx; | ||
| width: 28rpx; | ||
| height: 28rpx; | ||
| /* #ifndef APP-NVUE */ | ||
| animation: loading-flower 1s steps(12) infinite; | ||
| /* #endif */ | ||
| color: #666666; | ||
| } | ||
| .zp-loading-image-ios{ | ||
| width: 20px; | ||
| height: 20px; | ||
| } | ||
| .zp-loading-image-android{ | ||
| width: 32rpx; | ||
| height: 32rpx; | ||
| } | ||
| /* #ifndef APP-NVUE */ | ||
| @keyframes loading-flower { | ||
| 0% { | ||
| -webkit-transform: rotate(0deg); | ||
| transform: rotate(0deg); | ||
| } | ||
| to { | ||
| -webkit-transform: rotate(1turn); | ||
| transform: rotate(1turn); | ||
| } | ||
| } | ||
| /* #endif */ | ||
| { | ||
| "zp.refresher.default": "Pull down to refresh", | ||
| "zp.refresher.pulling": "Release to refresh", | ||
| "zp.refresher.refreshing": "Refreshing...", | ||
| "zp.refresher.complete": "Refresh succeeded", | ||
| "zp.loadingMore.default": "Click to load more", | ||
| "zp.loadingMore.loading": "Loading...", | ||
| "zp.loadingMore.noMore": "No more data", | ||
| "zp.loadingMore.fail": "Load failed,click to reload", | ||
| "zp.emptyView.title": "No data", | ||
| "zp.emptyView.reload": "Reload", | ||
| "zp.emptyView.error": "Sorry,load failed", | ||
| "zp.refresherUpdateTime.title": "Last update: ", | ||
| "zp.refresherUpdateTime.none": "None", | ||
| "zp.refresherUpdateTime.today": "Today", | ||
| "zp.refresherUpdateTime.yesterday": "Yesterday", | ||
| "zp.systemLoading.title": "Loading...", | ||
| "2022-01-01-23-59-59": "2022-01-01 23:59:59", | ||
| "yyyy-mm-dd-hh-mm-ss": "YYYY-MM-DD HH:mm:ss", | ||
| "zhong-zhi": "重置", | ||
| "zhong-zhi-gong-neng": "重置功能", | ||
| "shi-fen": "时分", | ||
| "shi-fen-miao": "时分秒", | ||
| "nian-yue-ri-shi-fen-miao": "年月日时分秒", | ||
| "ri": "日", | ||
| "yue": "月", | ||
| "nian": "年", | ||
| "ji-ben-shi-yong": "基本使用" | ||
| } |
| 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 | ||
| } |
| { | ||
| "zp.refresher.default": "继续下拉刷新", | ||
| "zp.refresher.pulling": "松开立即刷新", | ||
| "zp.refresher.refreshing": "正在刷新...", | ||
| "zp.refresher.complete": "刷新成功", | ||
| "zp.loadingMore.default": "点击加载更多", | ||
| "zp.loadingMore.loading": "正在加载...", | ||
| "zp.loadingMore.noMore": "没有更多了", | ||
| "zp.loadingMore.fail": "加载失败,点击重新加载", | ||
| "zp.emptyView.title": "没有数据哦~", | ||
| "zp.emptyView.reload": "重新加载", | ||
| "zp.emptyView.error": "很抱歉,加载失败", | ||
| "zp.refresherUpdateTime.title": "最后更新:", | ||
| "zp.refresherUpdateTime.none": "无", | ||
| "zp.refresherUpdateTime.today": "今天", | ||
| "zp.refresherUpdateTime.yesterday": "昨天", | ||
| "zp.systemLoading.title": "加载中..." | ||
| } |
| { | ||
| "zp.refresher.default": "繼續下拉重繪", | ||
| "zp.refresher.pulling": "鬆開立即重繪", | ||
| "zp.refresher.refreshing": "正在重繪...", | ||
| "zp.refresher.complete": "重繪成功", | ||
| "zp.loadingMore.default": "點擊加載更多", | ||
| "zp.loadingMore.loading": "正在加載...", | ||
| "zp.loadingMore.noMore": "沒有更多了", | ||
| "zp.loadingMore.fail": "加載失敗,點擊重新加載", | ||
| "zp.emptyView.title": "沒有數據哦~", | ||
| "zp.emptyView.reload": "重新加載", | ||
| "zp.emptyView.error": "很抱歉,加載失敗", | ||
| "zp.refresherUpdateTime.title": "最後更新:", | ||
| "zp.refresherUpdateTime.none": "無", | ||
| "zp.refresherUpdateTime.today": "今天", | ||
| "zp.refresherUpdateTime.yesterday": "昨天", | ||
| "zp.systemLoading.title": "加載中..." | ||
| } |
| // [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 |
| // [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 |
| // [z-paging]点击返回顶部view模块 | ||
| import u from '.././z-paging-utils' | ||
| export default { | ||
| props: { | ||
| //自动显示点击返回顶部按钮,默认为否 | ||
| autoShowBackToTop: { | ||
| type: Boolean, | ||
| default: u.gc('autoShowBackToTop', false) | ||
| }, | ||
| //点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx | ||
| backToTopThreshold: { | ||
| type: [Number, String], | ||
| default: u.gc('backToTopThreshold', '400rpx') | ||
| }, | ||
| //点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片 | ||
| backToTopImg: { | ||
| type: String, | ||
| default: u.gc('backToTopImg', '') | ||
| }, | ||
| //点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是 | ||
| backToTopWithAnimate: { | ||
| type: Boolean, | ||
| default: u.gc('backToTopWithAnimate', true) | ||
| }, | ||
| //点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx | ||
| backToTopBottom: { | ||
| type: [Number, String], | ||
| default: u.gc('backToTopBottom', '160rpx') | ||
| }, | ||
| //点击返回顶部按钮的自定义样式 | ||
| backToTopStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('backToTopStyle', {}); | ||
| }, | ||
| }, | ||
| //iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是 | ||
| enableBackToTop: { | ||
| type: Boolean, | ||
| default: u.gc('enableBackToTop', true) | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| backToTopClass: 'zp-back-to-top zp-back-to-top-hide', | ||
| lastBackToTopShowTime: 0, | ||
| showBackToTopClass: false, | ||
| } | ||
| }, | ||
| computed: { | ||
| finalEnableBackToTop() { | ||
| return this.usePageScroll ? false : this.enableBackToTop; | ||
| }, | ||
| finalBackToTopThreshold() { | ||
| return u.convertToPx(this.backToTopThreshold); | ||
| }, | ||
| finalBackToTopStyle() { | ||
| const backToTopStyle = this.backToTopStyle; | ||
| if (!backToTopStyle.bottom) { | ||
| backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottom) + 'px'; | ||
| } | ||
| if(!backToTopStyle.position){ | ||
| backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute'; | ||
| } | ||
| return backToTopStyle; | ||
| }, | ||
| }, | ||
| methods: { | ||
| //点击返回顶部 | ||
| _backToTopClick() { | ||
| let callbacked = false; | ||
| this.$emit('backToTopClick', toTop => { | ||
| (toTop === undefined || toTop === true) && this._handleToTop(); | ||
| callbacked = true; | ||
| }); | ||
| this.$nextTick(() => { | ||
| !callbacked && this._handleToTop(); | ||
| }) | ||
| }, | ||
| //处理滚动到顶部 | ||
| _handleToTop() { | ||
| !this.backToTopWithAnimate && this._checkShouldShowBackToTop(0); | ||
| this.scrollToTop(this.backToTopWithAnimate); | ||
| }, | ||
| //判断是否要显示返回顶部按钮 | ||
| _checkShouldShowBackToTop(scrollTop) { | ||
| if (!this.autoShowBackToTop) { | ||
| this.showBackToTopClass = false; | ||
| return; | ||
| } | ||
| if (scrollTop > this.finalBackToTopThreshold) { | ||
| if (!this.showBackToTopClass) { | ||
| this.showBackToTopClass = true; | ||
| this.lastBackToTopShowTime = new Date().getTime(); | ||
| u.delay(() => { | ||
| this.backToTopClass = 'zp-back-to-top zp-back-to-top-show'; | ||
| }, 300) | ||
| } | ||
| } else { | ||
| if (this.showBackToTopClass) { | ||
| this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide'; | ||
| u.delay(() => { | ||
| this.showBackToTopClass = false; | ||
| }, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300) | ||
| } | ||
| } | ||
| }, | ||
| } | ||
| } | ||
| // [z-paging]通用布局相关模块 | ||
| // #ifdef APP-NVUE | ||
| const weexDom = weex.requireModule('dom'); | ||
| // #endif | ||
| export default { | ||
| data() { | ||
| return { | ||
| systemInfo: null, | ||
| cssSafeAreaInsetBottom: -1, | ||
| } | ||
| }, | ||
| computed: { | ||
| windowTop() { | ||
| if (!this.systemInfo) return 0; | ||
| //暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634 | ||
| //感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25 | ||
| // #ifdef VUE3 && H5 | ||
| const pageHeadNode = document.getElementsByTagName("uni-page-head"); | ||
| if (!pageHeadNode.length) return 0; | ||
| // #endif | ||
| return this.systemInfo.windowTop || 0; | ||
| }, | ||
| safeAreaBottom() { | ||
| if (!this.systemInfo) return 0; | ||
| let safeAreaBottom = 0; | ||
| // #ifdef APP-PLUS | ||
| safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ; | ||
| // #endif | ||
| // #ifndef APP-PLUS | ||
| safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0); | ||
| // #endif | ||
| return safeAreaBottom; | ||
| }, | ||
| isOldWebView() { | ||
| // #ifndef APP-NVUE || MP-KUAISHOU | ||
| try { | ||
| const systemInfos = uni.getSystemInfoSync().system.split(' '); | ||
| const deviceType = systemInfos[0]; | ||
| const version = parseInt(systemInfos[1]); | ||
| if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) { | ||
| return true; | ||
| } | ||
| } catch(e) { | ||
| return false; | ||
| } | ||
| // #endif | ||
| return false; | ||
| }, | ||
| zSlots() { | ||
| // #ifdef VUE2 | ||
| // #ifdef MP-ALIPAY | ||
| return this.$slots; | ||
| // #endif | ||
| return this.$scopedSlots || this.$slots; | ||
| // #endif | ||
| return this.$slots; | ||
| } | ||
| }, | ||
| methods: { | ||
| //获取节点尺寸 | ||
| _getNodeClientRect(select, inDom = true, scrollOffset = false) { | ||
| // #ifdef APP-NVUE | ||
| select = select.replace(/[.|#]/g, ''); | ||
| const ref = this.$refs[select]; | ||
| return new Promise((resolve, reject) => { | ||
| if (ref) { | ||
| weexDom.getComponentRect(ref, option => { | ||
| resolve(option && option.result ? [option.size] : false); | ||
| }) | ||
| } else { | ||
| resolve(false); | ||
| } | ||
| }); | ||
| return; | ||
| // #endif | ||
| //#ifdef MP-ALIPAY | ||
| inDom = false; | ||
| //#endif | ||
| let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery(); | ||
| scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect(); | ||
| return new Promise((resolve, reject) => { | ||
| res.exec(data => { | ||
| resolve((data && data != '' && data != undefined && data.length) ? data : false); | ||
| }); | ||
| }); | ||
| }, | ||
| //获取slot="left"和slot="right"宽度并且更新布局 | ||
| _updateLeftAndRightWidth(targetStyle, parentNodePrefix) { | ||
| this.$nextTick(() => { | ||
| let delayTime = 0; | ||
| // #ifdef MP-BAIDU | ||
| delayTime = 10; | ||
| // #endif | ||
| setTimeout(() => { | ||
| ['left','right'].map(position => { | ||
| this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => { | ||
| this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px'); | ||
| }); | ||
| }) | ||
| }, delayTime) | ||
| }) | ||
| }, | ||
| //通过获取css设置的底部安全区域占位view高度设置bottom距离 | ||
| _getCssSafeAreaInsetBottom(success) { | ||
| this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => { | ||
| this.cssSafeAreaInsetBottom = res ? res[0].height : -1; | ||
| res && success && success(); | ||
| }); | ||
| } | ||
| } | ||
| } |
| // [z-paging]数据处理模块 | ||
| import u from '.././z-paging-utils' | ||
| import c from '.././z-paging-constant' | ||
| import Enum from '.././z-paging-enum' | ||
| import interceptor from '../z-paging-interceptor' | ||
| export default { | ||
| props: { | ||
| //自定义初始的pageNo,默认为1 | ||
| defaultPageNo: { | ||
| type: [Number, String], | ||
| default: u.gc('defaultPageNo', 1), | ||
| observer: function(newVal) { | ||
| this.pageNo = newVal; | ||
| }, | ||
| }, | ||
| //自定义pageSize,默认为10 | ||
| defaultPageSize: { | ||
| type: [Number, String], | ||
| default: u.gc('defaultPageSize', 10), | ||
| validator: (value) => { | ||
| if (value <= 0) u.consoleErr('default-page-size必须大于0!'); | ||
| return value > 0; | ||
| } | ||
| }, | ||
| //为保证数据一致,设置当前tab切换时的标识key,并在complete中传递相同key,若二者不一致,则complete将不会生效 | ||
| dataKey: { | ||
| type: [Number, String, Object], | ||
| default: function() { | ||
| return u.gc('dataKey', null); | ||
| }, | ||
| }, | ||
| //使用缓存,若开启将自动缓存第一页的数据,默认为否。请注意,因考虑到切换tab时不同tab数据不同的情况,默认仅会缓存组件首次加载时第一次请求到的数据,后续的下拉刷新操作不会更新缓存。 | ||
| useCache: { | ||
| type: Boolean, | ||
| default: u.gc('useCache', false) | ||
| }, | ||
| //使用缓存时缓存的key,用于区分不同列表的缓存数据,useCache为true时必须设置,否则缓存无效 | ||
| cacheKey: { | ||
| type: String, | ||
| default: u.gc('cacheKey', null) | ||
| }, | ||
| //缓存模式,默认仅会缓存组件首次加载时第一次请求到的数据,可设置为always,即代表总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 | ||
| cacheMode: { | ||
| type: String, | ||
| default: u.gc('cacheMode', Enum.CacheMode.Default) | ||
| }, | ||
| //自动注入的list名,可自动修改父view(包含ref="paging")中对应name的list值 | ||
| autowireListName: { | ||
| type: String, | ||
| default: u.gc('autowireListName', '') | ||
| }, | ||
| //自动注入的query名,可自动调用父view(包含ref="paging")中的query方法 | ||
| autowireQueryName: { | ||
| type: String, | ||
| default: u.gc('autowireQueryName', '') | ||
| }, | ||
| //z-paging mounted后自动调用reload方法(mounted后自动调用接口),默认为是 | ||
| auto: { | ||
| type: Boolean, | ||
| default: u.gc('auto', true) | ||
| }, | ||
| //用户下拉刷新时是否触发reload方法,默认为是 | ||
| reloadWhenRefresh: { | ||
| type: Boolean, | ||
| default: u.gc('reloadWhenRefresh', true) | ||
| }, | ||
| //reload时自动滚动到顶部,默认为是 | ||
| autoScrollToTopWhenReload: { | ||
| type: Boolean, | ||
| default: u.gc('autoScrollToTopWhenReload', true) | ||
| }, | ||
| //reload时立即自动清空原list,默认为是,若立即自动清空,则在reload之后、请求回调之前页面是空白的 | ||
| autoCleanListWhenReload: { | ||
| type: Boolean, | ||
| default: u.gc('autoCleanListWhenReload', true) | ||
| }, | ||
| //列表刷新时自动显示下拉刷新view,默认为否 | ||
| showRefresherWhenReload: { | ||
| type: Boolean, | ||
| default: u.gc('showRefresherWhenReload', false) | ||
| }, | ||
| //列表刷新时自动显示加载更多view,且为加载中状态,默认为否 | ||
| showLoadingMoreWhenReload: { | ||
| type: Boolean, | ||
| default: u.gc('showLoadingMoreWhenReload', false) | ||
| }, | ||
| //组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题),auto为true时有效。为否时将在mounted+nextTick后触发reload,默认为否 | ||
| createdReload: { | ||
| type: Boolean, | ||
| default: u.gc('createdReload', false) | ||
| }, | ||
| //本地分页时上拉加载更多延迟时间,单位为毫秒,默认200毫秒 | ||
| localPagingLoadingTime: { | ||
| type: [Number, String], | ||
| default: u.gc('localPagingLoadingTime', 200) | ||
| }, | ||
| //使用聊天记录模式,默认为否 | ||
| useChatRecordMode: { | ||
| type: Boolean, | ||
| default: u.gc('useChatRecordMode', false) | ||
| }, | ||
| //使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是 | ||
| autoHideKeyboardWhenChat: { | ||
| type: Boolean, | ||
| default: u.gc('autoHideKeyboardWhenChat', true) | ||
| }, | ||
| //自动拼接complete中传过来的数组(使用聊天记录模式时无效) | ||
| concat: { | ||
| type: Boolean, | ||
| default: u.gc('concat', true) | ||
| }, | ||
| //请求失败是否触发reject,默认为是 | ||
| callNetworkReject: { | ||
| type: Boolean, | ||
| default: u.gc('callNetworkReject', true) | ||
| }, | ||
| //父组件v-model所绑定的list的值 | ||
| value: { | ||
| type: Array, | ||
| default: function() { | ||
| return []; | ||
| } | ||
| }, | ||
| // #ifdef VUE3 | ||
| modelValue: { | ||
| type: Array, | ||
| default: function() { | ||
| return []; | ||
| } | ||
| } | ||
| // #endif | ||
| }, | ||
| data (){ | ||
| return { | ||
| currentData: [], | ||
| totalData: [], | ||
| realTotalData: [], | ||
| totalLocalPagingList: [], | ||
| dataPromiseResultMap: { | ||
| reload: null, | ||
| complete: null, | ||
| localPaging: null | ||
| }, | ||
| isSettingCacheList: false, | ||
| pageNo: 1, | ||
| currentRefreshPageSize: 0, | ||
| isLocalPaging: false, | ||
| isAddedData: false, | ||
| isTotalChangeFromAddData: false, | ||
| privateConcat: true, | ||
| myParentQuery: -1, | ||
| firstPageLoaded: false, | ||
| pagingLoaded: false, | ||
| loaded: false, | ||
| isUserReload: true, | ||
| fromEmptyViewReload: false, | ||
| queryFrom: '', | ||
| listRendering: false, | ||
| isHandlingRefreshToPage: false | ||
| } | ||
| }, | ||
| computed: { | ||
| pageSize() { | ||
| return this.defaultPageSize; | ||
| }, | ||
| finalConcat() { | ||
| return this.concat && this.privateConcat; | ||
| }, | ||
| finalUseCache() { | ||
| if (this.useCache && !this.cacheKey) { | ||
| u.consoleErr('use-cache为true时,必须设置cache-key,否则缓存无效!'); | ||
| } | ||
| return this.useCache && !!this.cacheKey; | ||
| }, | ||
| finalCacheKey() { | ||
| return this.cacheKey ? `${c.cachePrefixKey}-${this.cacheKey}` : null; | ||
| }, | ||
| isFirstPage() { | ||
| return this.pageNo === this.defaultPageNo; | ||
| } | ||
| }, | ||
| watch: { | ||
| totalData(newVal, oldVal) { | ||
| this._totalDataChange(newVal, oldVal); | ||
| }, | ||
| currentData(newVal, oldVal) { | ||
| this._currentDataChange(newVal, oldVal); | ||
| }, | ||
| useChatRecordMode(newVal, oldVal) { | ||
| if (newVal) { | ||
| this.nLoadingMoreFixedHeight = false; | ||
| } | ||
| }, | ||
| value: { | ||
| handler(newVal) { | ||
| this.realTotalData = newVal; | ||
| }, | ||
| immediate: true | ||
| }, | ||
| // #ifdef VUE3 | ||
| modelValue: { | ||
| handler(newVal) { | ||
| this.realTotalData = newVal; | ||
| }, | ||
| immediate: true | ||
| } | ||
| // #endif | ||
| }, | ||
| methods: { | ||
| //请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否成功(默认是是) | ||
| complete(data, success = true) { | ||
| this.customNoMore = -1; | ||
| return this.addData(data, success); | ||
| }, | ||
| //【保证数据一致】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为dataKey,需与:data-key绑定的一致,第三个参数为是否成功(默认为是) | ||
| completeByKey(data, dataKey = null, success = true) { | ||
| if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) { | ||
| this.isFirstPage && this.endRefresh(); | ||
| return new Promise(resolve => resolve()); | ||
| } | ||
| this.customNoMore = -1; | ||
| return this.addData(data, success); | ||
| }, | ||
| //【通过total判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为total(列表总数),第三个参数为是否成功(默认为是) | ||
| completeByTotal(data, total, success = true) { | ||
| if (total == 'undefined') { | ||
| this.customNoMore = -1; | ||
| } else { | ||
| const dataTypeRes = this._checkDataType(data, success, false); | ||
| data = dataTypeRes.data; | ||
| success = dataTypeRes.success; | ||
| if (total >= 0 && success) { | ||
| return new Promise((resolve, reject) => { | ||
| this.$nextTick(() => { | ||
| let nomore = false; | ||
| const realTotalDataCount = this.pageNo == this.defaultPageNo ? 0 : this.realTotalData.length; | ||
| const dataLength = this.privateConcat ? data.length : 0; | ||
| let exceedCount = realTotalDataCount + dataLength - total; | ||
| if (exceedCount >= 0) { | ||
| nomore = true; | ||
| exceedCount = this.defaultPageSize - exceedCount; | ||
| if (this.privateConcat && exceedCount > 0 && exceedCount < data.length) { | ||
| data = data.splice(0, exceedCount); | ||
| } | ||
| } | ||
| this.completeByNoMore(data, nomore, success).then(res => resolve(res)).catch(() => reject()); | ||
| }) | ||
| }); | ||
| } | ||
| } | ||
| return this.addData(data, success); | ||
| }, | ||
| //【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否有更多数据,第三个参数为是否成功(默认是是) | ||
| completeByNoMore(data, nomore, success = true) { | ||
| if (nomore != 'undefined') { | ||
| this.customNoMore = nomore == true ? 1 : 0; | ||
| } | ||
| return this.addData(data, success); | ||
| }, | ||
| //与上方complete方法功能一致,新版本中设置服务端回调数组请使用complete方法 | ||
| addData(data, success = true) { | ||
| if (!this.fromCompleteEmit) { | ||
| this.disabledCompleteEmit = true; | ||
| this.fromCompleteEmit = false; | ||
| } | ||
| const currentTimeStamp = u.getTime(); | ||
| const disTime = currentTimeStamp - this.requestTimeStamp; | ||
| let minDelay = this.minDelay; | ||
| if (this.isFirstPage && this.finalShowRefresherWhenReload) { | ||
| minDelay = Math.max(400, minDelay); | ||
| } | ||
| const addDataDalay = (this.requestTimeStamp > 0 && disTime < minDelay) ? minDelay - disTime : 0; | ||
| this.$nextTick(() => { | ||
| u.delay(() => { | ||
| this._addData(data, success, false); | ||
| }, this.delay > 0 ? this.delay : addDataDalay) | ||
| }) | ||
| return new Promise((resolve, reject) => { | ||
| this.dataPromiseResultMap.complete = { resolve, reject }; | ||
| }); | ||
| }, | ||
| //从顶部添加数据,不会影响分页的pageNo和pageSize | ||
| addDataFromTop(data, toTop = true, toTopWithAnimate = true) { | ||
| data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data.reverse(); | ||
| // #ifndef APP-NVUE | ||
| this.finalUseVirtualList && this._setCellIndex(data, 'top') | ||
| // #endif | ||
| this.totalData = [...data, ...this.totalData]; | ||
| if (toTop) { | ||
| u.delay(() => this._scrollToTop(toTopWithAnimate)); | ||
| } | ||
| }, | ||
| //重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求。适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时,请使用此方法,请勿直接修改page中:list.sync绑定的数组) | ||
| resetTotalData(data) { | ||
| this.isTotalChangeFromAddData = true; | ||
| data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; | ||
| this.totalData = data; | ||
| }, | ||
| //添加聊天记录 | ||
| addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) { | ||
| data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data; | ||
| if (!this.useChatRecordMode) return; | ||
| this.isTotalChangeFromAddData = true; | ||
| //#ifndef APP-NVUE | ||
| this.totalData = [...this.totalData, ...data]; | ||
| //#endif | ||
| //#ifdef APP-NVUE | ||
| this.totalData = this.nIsFirstPageAndNoMore ? [...this.totalData, ...data] : [...data, ...this.totalData]; | ||
| //#endif | ||
| if (toBottom) { | ||
| u.delay(() => { | ||
| //#ifndef APP-NVUE | ||
| this._scrollToBottom(toBottomWithAnimate); | ||
| //#endif | ||
| //#ifdef APP-NVUE | ||
| this.nIsFirstPageAndNoMore ? this._scrollToBottom(toBottomWithAnimate) : this._scrollToTop(toBottomWithAnimate); | ||
| //#endif | ||
| }) | ||
| } | ||
| }, | ||
| //设置本地分页数据,请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging作分页处理(若调用了此方法,则上拉加载更多时内部会自动分页,不会触发@query所绑定的事件) | ||
| setLocalPaging(data, success = true) { | ||
| this.isLocalPaging = true; | ||
| this.$nextTick(() => { | ||
| this._addData(data, success, true); | ||
| }) | ||
| return new Promise((resolve, reject) => { | ||
| this.dataPromiseResultMap.localPaging = { resolve, reject }; | ||
| }); | ||
| }, | ||
| //重新加载分页数据,pageNo会恢复为默认值,相当于下拉刷新的效果(animate为true时会展示下拉刷新动画,默认为false) | ||
| reload(animate = this.showRefresherWhenReload) { | ||
| if (animate) { | ||
| this.privateShowRefresherWhenReload = animate; | ||
| this.isUserPullDown = true; | ||
| } | ||
| if (!this.showLoadingMoreWhenReload) { | ||
| this.listRendering = true; | ||
| } | ||
| this.$nextTick(() => { | ||
| this._preReload(animate, false); | ||
| }) | ||
| return new Promise((resolve, reject) => { | ||
| this.dataPromiseResultMap.reload = { resolve, reject }; | ||
| }); | ||
| }, | ||
| //刷新列表数据,pageNo和pageSize不会重置,列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 | ||
| refresh() { | ||
| return this._handleRefreshWithDisPageNo(this.pageNo - this.defaultPageNo + 1); | ||
| }, | ||
| //刷新列表数据至指定页,例如pageNo=5时则代表刷新列表至第5页,此时pageNo会变为5,列表会展示前5页的数据。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致 | ||
| refreshToPage(pageNo) { | ||
| this.isHandlingRefreshToPage = true; | ||
| return this._handleRefreshWithDisPageNo(pageNo + this.defaultPageNo - 1); | ||
| }, | ||
| //手动更新列表缓存数据,将自动截取v-model绑定的list中的前pageSize条覆盖缓存,请确保在list数据更新到预期结果后再调用此方法 | ||
| updateCache() { | ||
| if (this.finalUseCache && this.totalData.length) { | ||
| this._saveLocalCache(this.totalData.slice(0, Math.min(this.totalData.length, this.pageSize))); | ||
| } | ||
| }, | ||
| //清空分页数据 | ||
| clean() { | ||
| this._reload(true); | ||
| this._addData([], true, false); | ||
| }, | ||
| //清空分页数据 | ||
| clear() { | ||
| this.clean(); | ||
| }, | ||
| //手动触发滚动到顶部加载更多,聊天记录模式时有效 | ||
| doChatRecordLoadMore() { | ||
| this.useChatRecordMode && this._onLoadingMore('click'); | ||
| }, | ||
| //reload之前的一些处理 | ||
| _preReload(animate = this.showRefresherWhenReload, isFromMounted = true) { | ||
| const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher; | ||
| // #ifndef APP-NVUE | ||
| if (this.customRefresherHeight === -1 && showRefresher) { | ||
| u.delay(() => this._preReload(animate, isFromMounted), c.delayTime / 2); | ||
| return; | ||
| } | ||
| // #endif | ||
| this.isUserReload = true; | ||
| this.loadingType = Enum.LoadingType.Refresher; | ||
| if (animate) { | ||
| this.privateShowRefresherWhenReload = animate; | ||
| // #ifndef APP-NVUE | ||
| if (this.useCustomRefresher) { | ||
| this._doRefresherRefreshAnimate(); | ||
| } else { | ||
| this.refresherTriggered = true; | ||
| } | ||
| // #endif | ||
| // #ifdef APP-NVUE | ||
| this.refresherStatus = Enum.Refresher.Loading; | ||
| this.refresherRevealStackCount ++; | ||
| u.delay(() => { | ||
| this._getNodeClientRect('zp-n-refresh-container', false).then((node) => { | ||
| if (node) { | ||
| let nodeHeight = node[0].height; | ||
| this.nShowRefresherReveal = true; | ||
| this.nShowRefresherRevealHeight = nodeHeight; | ||
| u.delay(() => { | ||
| this._nDoRefresherEndAnimation(0, -nodeHeight, false, false); | ||
| u.delay(() => { | ||
| this._nDoRefresherEndAnimation(nodeHeight, 0); | ||
| }, 10) | ||
| }, 10) | ||
| } | ||
| this._reload(false, isFromMounted); | ||
| this._doRefresherLoad(false); | ||
| }); | ||
| }, this.pagingLoaded ? 10 : 100) | ||
| return; | ||
| // #endif | ||
| } else { | ||
| this._refresherEnd(false, false, false, false); | ||
| } | ||
| this._reload(false, isFromMounted); | ||
| }, | ||
| //重新加载分页数据 | ||
| _reload(isClean = false, isFromMounted = false, isUserPullDown = false) { | ||
| this.isAddedData = false; | ||
| this.insideOfPaging = -1; | ||
| this.cacheScrollNodeHeight = -1; | ||
| this.pageNo = this.defaultPageNo; | ||
| this._cleanRefresherEndTimeout(); | ||
| !this.privateShowRefresherWhenReload && !isClean && this._startLoading(true); | ||
| this.firstPageLoaded = true; | ||
| this.isTotalChangeFromAddData = false; | ||
| if (!this.isSettingCacheList) { | ||
| this.totalData = []; | ||
| } | ||
| if (!isClean) { | ||
| this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload); | ||
| let delay = 0; | ||
| // #ifdef MP-TOUTIAO | ||
| delay = 5; | ||
| // #endif | ||
| u.delay(this._callMyParentQuery, delay); | ||
| if (!isFromMounted && this.autoScrollToTopWhenReload) { | ||
| let checkedNRefresherLoading = true; | ||
| // #ifdef APP-NVUE | ||
| checkedNRefresherLoading = !this.nRefresherLoading; | ||
| // #endif | ||
| checkedNRefresherLoading && this._scrollToTop(false); | ||
| } | ||
| } | ||
| // #ifdef APP-NVUE | ||
| this.$nextTick(() => { | ||
| this.nShowBottom = this.realTotalData.length > 0; | ||
| }) | ||
| // #endif | ||
| }, | ||
| //处理服务端返回的数组 | ||
| _addData(data, success, isLocal) { | ||
| this.isAddedData = true; | ||
| this.fromEmptyViewReload = false; | ||
| this.isTotalChangeFromAddData = true; | ||
| this.refresherTriggered = false; | ||
| this._endSystemLoadingAndRefresh(); | ||
| const tempIsUserPullDown = this.isUserPullDown; | ||
| if (this.showRefresherUpdateTime && this.isFirstPage) { | ||
| u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey); | ||
| this.$refs.refresh && this.$refs.refresh.updateTime(); | ||
| } | ||
| if (!isLocal && tempIsUserPullDown && this.isFirstPage) { | ||
| this.isUserPullDown = false; | ||
| } | ||
| if (!this.isFirstPage) { | ||
| this.listRendering = true; | ||
| this.$nextTick(() => { | ||
| u.delay(() => this.listRendering = false); | ||
| }) | ||
| } else { | ||
| this.listRendering = false; | ||
| } | ||
| let dataTypeRes = this._checkDataType(data, success, isLocal); | ||
| data = dataTypeRes.data; | ||
| success = dataTypeRes.success; | ||
| let delayTime = c.delayTime; | ||
| // #ifdef APP-NVUE | ||
| if (this.useChatRecordMode) delayTime = 0; | ||
| // #endif | ||
| this.loadingForNow = false; | ||
| u.delay(() => { | ||
| this.pagingLoaded = true; | ||
| this.$nextTick(()=>{ | ||
| !isLocal && this._refresherEnd(delayTime > 0, true, tempIsUserPullDown); | ||
| }) | ||
| }) | ||
| if (this.isFirstPage) { | ||
| this.isLoadFailed = !success; | ||
| this.$emit('isLoadFailedChange', this.isLoadFailed); | ||
| if (this.finalUseCache && success && (this.cacheMode === Enum.CacheMode.Always ? true : this.isSettingCacheList)) { | ||
| this._saveLocalCache(data); | ||
| } | ||
| } | ||
| this.isSettingCacheList = false; | ||
| if (success) { | ||
| if (!(this.privateConcat === false && this.loadingStatus === Enum.More.NoMore)) { | ||
| this.loadingStatus = Enum.More.Default; | ||
| } | ||
| if (isLocal) { | ||
| this.totalLocalPagingList = data; | ||
| const localPageNo = this.defaultPageNo; | ||
| const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize; | ||
| this._localPagingQueryList(localPageNo, localPageSize, 0, res => { | ||
| this.completeByTotal(res, this.totalLocalPagingList.length); | ||
| }) | ||
| } else { | ||
| let dataChangeDelayTime = 0; | ||
| // #ifdef APP-NVUE | ||
| if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') { | ||
| dataChangeDelayTime = 150; | ||
| } | ||
| // #endif | ||
| u.delay(() => { | ||
| this._currentDataChange(data, this.currentData); | ||
| this._callDataPromise(true, this.totalData); | ||
| }, dataChangeDelayTime) | ||
| } | ||
| if (this.isHandlingRefreshToPage) { | ||
| this.isHandlingRefreshToPage = false; | ||
| this.pageNo = this.defaultPageNo + Math.ceil(data.length / this.pageSize) - 1; | ||
| if (data.length % this.pageSize !== 0) { | ||
| this.customNoMore = 1; | ||
| } | ||
| } | ||
| } else { | ||
| this._currentDataChange(data, this.currentData); | ||
| this._callDataPromise(false); | ||
| this.loadingStatus = Enum.More.Fail; | ||
| this.isHandlingRefreshToPage = false; | ||
| if (this.loadingType === Enum.LoadingType.LoadingMore) { | ||
| this.pageNo --; | ||
| } | ||
| } | ||
| }, | ||
| //所有数据改变时调用 | ||
| _totalDataChange(newVal, oldVal, eventThrow=true) { | ||
| if ((!this.isUserReload || !this.autoCleanListWhenReload) && this.firstPageLoaded && !newVal.length && oldVal.length) { | ||
| return; | ||
| } | ||
| this._doCheckScrollViewShouldFullHeight(newVal); | ||
| if(!this.realTotalData.length && !newVal.length){ | ||
| eventThrow = false; | ||
| } | ||
| this.realTotalData = newVal; | ||
| if (eventThrow) { | ||
| this.$emit('input', newVal); | ||
| // #ifdef VUE3 | ||
| this.$emit('update:modelValue', newVal); | ||
| // #endif | ||
| this.$emit('update:list', newVal); | ||
| this.$emit('listChange', newVal); | ||
| this._callMyParentList(newVal); | ||
| } | ||
| this.firstPageLoaded = false; | ||
| this.isTotalChangeFromAddData = false; | ||
| this.$nextTick(() => { | ||
| u.delay(()=>{ | ||
| this._getNodeClientRect('.zp-paging-container-content').then(res => { | ||
| res && this.$emit('contentHeightChanged', res[0].height); | ||
| }); | ||
| }, c.delayTime * (this.isIos ? 1 : 3)) | ||
| // #ifdef APP-NVUE | ||
| if (this.useChatRecordMode && this.nIsFirstPageAndNoMore && this.isFirstPage && !this.nFirstPageAndNoMoreChecked) { | ||
| this.nFirstPageAndNoMoreChecked = true; | ||
| this._scrollToBottom(false); | ||
| } | ||
| u.delay(() => { | ||
| this.nShowBottom = true; | ||
| }, c.delayTime * 6, 'nShowBottomDelay'); | ||
| // #endif | ||
| }) | ||
| }, | ||
| //当前数据改变时调用 | ||
| _currentDataChange(newVal, oldVal) { | ||
| newVal = [...newVal]; | ||
| // #ifndef APP-NVUE | ||
| this.finalUseVirtualList && this._setCellIndex(newVal, 'bottom'); | ||
| this.useChatRecordMode && newVal.reverse(); | ||
| // #endif | ||
| if (this.isFirstPage && this.finalConcat) { | ||
| this.totalData = []; | ||
| } | ||
| if (this.customNoMore !== -1) { | ||
| if (this.customNoMore === 1 || !newVal.length) { | ||
| this.loadingStatus = Enum.More.NoMore; | ||
| } | ||
| } else { | ||
| if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) { | ||
| this.loadingStatus = Enum.More.NoMore; | ||
| } | ||
| } | ||
| if (!this.totalData.length) { | ||
| if (this.finalConcat) { | ||
| // #ifdef APP-NVUE | ||
| if (this.useChatRecordMode && this.isFirstPage && this.loadingStatus === Enum.More.NoMore) { | ||
| newVal.reverse(); | ||
| } | ||
| // #endif | ||
| this.totalData = newVal; | ||
| } | ||
| if (this.useChatRecordMode) { | ||
| // #ifndef APP-NVUE | ||
| this.$nextTick(() => { | ||
| this._scrollToBottom(false); | ||
| }) | ||
| // #endif | ||
| } | ||
| } else { | ||
| if (this.useChatRecordMode) { | ||
| // #ifdef APP-NVUE | ||
| this.totalData = [...this.totalData, ...newVal]; | ||
| // #endif | ||
| //#ifndef APP-NVUE | ||
| const idIndex = newVal.length; | ||
| let idIndexStr = `z-paging-${idIndex}`; | ||
| this.totalData = [...newVal, ...this.totalData]; | ||
| if (this.pageNo !== this.defaultPageNo) { | ||
| this.privateScrollWithAnimation = 0; | ||
| this.$emit('update:chatIndex', idIndex); | ||
| this.$nextTick(() => { | ||
| this._scrollIntoView(idIndexStr, 30 + Math.max(0, this.cacheTopHeight), false, () => { | ||
| this.$emit('update:chatIndex', 0); | ||
| }); | ||
| }) | ||
| } else { | ||
| this.$nextTick(() => { | ||
| this._scrollToBottom(false); | ||
| }) | ||
| } | ||
| //#endif | ||
| } else { | ||
| if (this.finalConcat) { | ||
| const currentScrollTop = this.oldScrollTop; | ||
| this.totalData = [...this.totalData, ...newVal]; | ||
| // #ifdef MP-WEIXIN | ||
| if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) { | ||
| this.loadingMoreTimeStamp = u.getTime(); | ||
| this.$nextTick(() => { | ||
| this.scrollToY(currentScrollTop); | ||
| }) | ||
| } | ||
| // #endif | ||
| } else { | ||
| this.totalData = newVal; | ||
| } | ||
| } | ||
| } | ||
| this.privateConcat = true; | ||
| }, | ||
| //根据pageNo处理refresh操作 | ||
| _handleRefreshWithDisPageNo(pageNo) { | ||
| if (!this.realTotalData.length) return this.reload(); | ||
| if (pageNo >= 1) { | ||
| this.loading = true; | ||
| this.privateConcat = false; | ||
| const totalPageSize = pageNo * this.pageSize; | ||
| this.currentRefreshPageSize = totalPageSize; | ||
| this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh); | ||
| this._callMyParentQuery(this.defaultPageNo, totalPageSize); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| this.dataPromiseResultMap.reload = { resolve, reject }; | ||
| }); | ||
| }, | ||
| //本地分页请求 | ||
| _localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) { | ||
| pageNo = Math.max(1, pageNo); | ||
| pageSize = Math.max(1, pageSize); | ||
| const totalPagingList = [...this.totalLocalPagingList]; | ||
| const pageNoIndex = (pageNo - 1) * pageSize; | ||
| const finalPageNoIndex = Math.min(totalPagingList.length, pageNoIndex + pageSize); | ||
| const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex); | ||
| u.delay(() => callback(resultPagingList), localPagingLoadingTime) | ||
| }, | ||
| //存储列表缓存数据 | ||
| _saveLocalCache(data) { | ||
| uni.setStorageSync(this.finalCacheKey, data); | ||
| }, | ||
| //通过缓存数据填充列表数据 | ||
| _setListByLocalCache() { | ||
| this.totalData = uni.getStorageSync(this.finalCacheKey) || []; | ||
| this.isSettingCacheList = true; | ||
| }, | ||
| //修改父view的list | ||
| _callMyParentList(newVal) { | ||
| if (this.autowireListName.length) { | ||
| const myParent = u.getParent(this.$parent); | ||
| if (myParent && myParent[this.autowireListName]) { | ||
| myParent[this.autowireListName] = newVal; | ||
| } | ||
| } | ||
| }, | ||
| //调用父view的query | ||
| _callMyParentQuery(customPageNo = 0, customPageSize = 0) { | ||
| if (this.autowireQueryName) { | ||
| if (this.myParentQuery === -1) { | ||
| const myParent = u.getParent(this.$parent); | ||
| if (myParent && myParent[this.autowireQueryName]) { | ||
| this.myParentQuery = myParent[this.autowireQueryName]; | ||
| } | ||
| } | ||
| if (this.myParentQuery !== -1) { | ||
| customPageSize > 0 ? this.myParentQuery(customPageNo, customPageSize) : this.myParentQuery(this.pageNo, this.defaultPageSize); | ||
| } | ||
| } | ||
| }, | ||
| //emit query事件 | ||
| _emitQuery(pageNo, pageSize, from){ | ||
| this.queryFrom = from; | ||
| this.requestTimeStamp = u.getTime(); | ||
| const [lastItem] = this.realTotalData.slice(-1); | ||
| this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null)); | ||
| }, | ||
| //触发数据改变promise | ||
| _callDataPromise(success, totalList) { | ||
| for (const key in this.dataPromiseResultMap) { | ||
| const obj = this.dataPromiseResultMap[key]; | ||
| if (!obj) continue; | ||
| success ? obj.resolve({ totalList, noMore: this.loadingStatus === Enum.More.NoMore }) : this.callNetworkReject && obj.reject(`z-paging-${key}-error`); | ||
| } | ||
| }, | ||
| //检查complete data的类型 | ||
| _checkDataType(data, success, isLocal) { | ||
| const dataType = Object.prototype.toString.call(data); | ||
| if (dataType === '[object Boolean]') { | ||
| success = data; | ||
| data = []; | ||
| } else if (dataType !== '[object Array]') { | ||
| data = []; | ||
| if (dataType !== '[object Undefined]' && dataType !== '[object Null]') { | ||
| u.consoleErr(`${isLocal ? 'setLocalPaging' : 'complete'}参数类型不正确,第一个参数类型必须为Array!`); | ||
| } | ||
| } | ||
| return { data, success }; | ||
| }, | ||
| } | ||
| } |
| // [z-paging]空数据图view模块 | ||
| import u from '.././z-paging-utils' | ||
| export default { | ||
| props: { | ||
| //是否强制隐藏空数据图,默认为否 | ||
| hideEmptyView: { | ||
| type: Boolean, | ||
| default: u.gc('hideEmptyView', false) | ||
| }, | ||
| //空数据图描述文字,默认为“没有数据哦~” | ||
| emptyViewText: { | ||
| type: [String, Object], | ||
| default: u.gc('emptyViewText', null) | ||
| }, | ||
| //是否显示空数据图重新加载按钮(无数据时),默认为否 | ||
| showEmptyViewReload: { | ||
| type: Boolean, | ||
| default: u.gc('showEmptyViewReload', false) | ||
| }, | ||
| //加载失败时是否显示空数据图重新加载按钮,默认为是 | ||
| showEmptyViewReloadWhenError: { | ||
| type: Boolean, | ||
| default: u.gc('showEmptyViewReloadWhenError', true) | ||
| }, | ||
| //空数据图点击重新加载文字,默认为“重新加载” | ||
| emptyViewReloadText: { | ||
| type: [String, Object], | ||
| default: u.gc('emptyViewReloadText', null) | ||
| }, | ||
| //空数据图图片,默认使用z-paging内置的图片 | ||
| emptyViewImg: { | ||
| type: String, | ||
| default: u.gc('emptyViewImg', '') | ||
| }, | ||
| //空数据图“加载失败”描述文字,默认为“很抱歉,加载失败” | ||
| emptyViewErrorText: { | ||
| type: [String, Object], | ||
| default: u.gc('emptyViewErrorText', null) | ||
| }, | ||
| //空数据图“加载失败”图片,默认使用z-paging内置的图片 | ||
| emptyViewErrorImg: { | ||
| type: String, | ||
| default: u.gc('emptyViewErrorImg', '') | ||
| }, | ||
| //空数据图样式 | ||
| emptyViewStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('emptyViewStyle', {}); | ||
| } | ||
| }, | ||
| //空数据图容器样式 | ||
| emptyViewSuperStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('emptyViewSuperStyle', {}); | ||
| } | ||
| }, | ||
| //空数据图img样式 | ||
| emptyViewImgStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('emptyViewImgStyle', {}); | ||
| } | ||
| }, | ||
| //空数据图描述文字样式 | ||
| emptyViewTitleStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('emptyViewTitleStyle', {}); | ||
| } | ||
| }, | ||
| //空数据图重新加载按钮样式 | ||
| emptyViewReloadStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('emptyViewReloadStyle', {}); | ||
| } | ||
| }, | ||
| //空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging | ||
| emptyViewFixed: { | ||
| type: Boolean, | ||
| default: u.gc('emptyViewFixed', false) | ||
| }, | ||
| //空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效 | ||
| emptyViewCenter: { | ||
| type: Boolean, | ||
| default: u.gc('emptyViewCenter', true) | ||
| }, | ||
| //加载中时是否自动隐藏空数据图,默认为是 | ||
| autoHideEmptyViewWhenLoading: { | ||
| type: Boolean, | ||
| default: u.gc('autoHideEmptyViewWhenLoading', true) | ||
| }, | ||
| //用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是 | ||
| autoHideEmptyViewWhenPull: { | ||
| type: Boolean, | ||
| default: u.gc('autoHideEmptyViewWhenPull', true) | ||
| }, | ||
| //空数据view的z-index,默认为9 | ||
| emptyViewZIndex: { | ||
| type: Number, | ||
| default: u.gc('emptyViewZIndex', 9) | ||
| }, | ||
| }, | ||
| computed: { | ||
| finalEmptyViewImg() { | ||
| return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg; | ||
| }, | ||
| finalShowEmptyViewReload() { | ||
| return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload; | ||
| }, | ||
| showEmpty() { | ||
| if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false; | ||
| if (this.autoHideEmptyViewWhenLoading) { | ||
| if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true; | ||
| } else { | ||
| return true; | ||
| } | ||
| return !this.autoHideEmptyViewWhenPull && !this.isUserReload; | ||
| }, | ||
| }, | ||
| methods: { | ||
| //点击了空数据view重新加载按钮 | ||
| _emptyViewReload() { | ||
| let callbacked = false; | ||
| this.$emit('emptyViewReload', reload => { | ||
| if (reload === undefined || reload === true) { | ||
| this.fromEmptyViewReload = true; | ||
| this.reload().catch(() => {}); | ||
| } | ||
| callbacked = true; | ||
| }); | ||
| this.$nextTick(() => { | ||
| if (!callbacked) { | ||
| this.fromEmptyViewReload = true; | ||
| this.reload().catch(() => {}); | ||
| } | ||
| }) | ||
| }, | ||
| //点击了空数据view | ||
| _emptyViewClick() { | ||
| this.$emit('emptyViewClick'); | ||
| }, | ||
| } | ||
| } |
| // [z-paging]i18n模块 | ||
| import { initVueI18n } from '@dcloudio/uni-i18n' | ||
| import messages from '../../i18n/index.js' | ||
| const { t } = initVueI18n(messages) | ||
| import u from '.././z-paging-utils' | ||
| import c from '.././z-paging-constant' | ||
| import interceptor from '../z-paging-interceptor' | ||
| const language = uni.getSystemInfoSync().language; | ||
| export default { | ||
| data() { | ||
| return { | ||
| language | ||
| } | ||
| }, | ||
| computed: { | ||
| finalLanguage() { | ||
| try { | ||
| const local = uni.getLocale(); | ||
| const language = this.language; | ||
| return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local; | ||
| } catch (e) { | ||
| return 'zh-Hans'; | ||
| } | ||
| }, | ||
| finalRefresherDefaultText() { | ||
| return this._getI18nText('zp.refresher.default', this.refresherDefaultText); | ||
| }, | ||
| finalRefresherPullingText() { | ||
| return this._getI18nText('zp.refresher.pulling', this.refresherPullingText); | ||
| }, | ||
| finalRefresherRefreshingText() { | ||
| return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText); | ||
| }, | ||
| finalRefresherCompleteText() { | ||
| return this._getI18nText('zp.refresher.complete', this.refresherCompleteText); | ||
| }, | ||
| finalRefresherUpdateTimeTextMap() { | ||
| return { | ||
| title: t('zp.refresherUpdateTime.title'), | ||
| none: t('zp.refresherUpdateTime.none'), | ||
| today: t('zp.refresherUpdateTime.today'), | ||
| yesterday: t('zp.refresherUpdateTime.yesterday') | ||
| }; | ||
| }, | ||
| finalLoadingMoreDefaultText() { | ||
| return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText); | ||
| }, | ||
| finalLoadingMoreLoadingText() { | ||
| return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText); | ||
| }, | ||
| finalLoadingMoreNoMoreText() { | ||
| return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText); | ||
| }, | ||
| finalLoadingMoreFailText() { | ||
| return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText); | ||
| }, | ||
| finalEmptyViewText() { | ||
| return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText); | ||
| }, | ||
| finalEmptyViewReloadText() { | ||
| return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText); | ||
| }, | ||
| finalEmptyViewErrorText() { | ||
| return this._getI18nText('zp.emptyView.error', this.emptyViewErrorText); | ||
| }, | ||
| finalSystemLoadingText() { | ||
| return this._getI18nText('zp.systemLoading.title', this.systemLoadingText); | ||
| }, | ||
| }, | ||
| methods: { | ||
| //获取当前z-paging的语言 | ||
| getLanguage() { | ||
| return this.finalLanguage; | ||
| }, | ||
| //获取国际化转换后的文本 | ||
| _getI18nText(key, value) { | ||
| const dataType = Object.prototype.toString.call(value); | ||
| if (dataType === '[object Object]') { | ||
| const nextValue = value[this.finalLanguage]; | ||
| if (nextValue) return nextValue; | ||
| } else if (dataType === '[object String]') { | ||
| return value; | ||
| } | ||
| return t(key); | ||
| }, | ||
| //系统language转i18n local | ||
| _language2Local(language) { | ||
| const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-'); | ||
| if (formatedLanguage.indexOf('zh') !== -1) { | ||
| if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) { | ||
| return 'zh-Hans'; | ||
| } | ||
| return 'zh-Hant'; | ||
| } | ||
| if (formatedLanguage.indexOf('en') !== -1) return 'en'; | ||
| return language; | ||
| } | ||
| } | ||
| } |
| // [z-paging]滚动到底部加载更多模块 | ||
| import u from '.././z-paging-utils' | ||
| import Enum from '.././z-paging-enum' | ||
| export default { | ||
| props: { | ||
| //自定义底部加载更多样式 | ||
| loadingMoreCustomStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('loadingMoreCustomStyle', {}); | ||
| } | ||
| }, | ||
| //自定义底部加载更多文字样式 | ||
| loadingMoreTitleCustomStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('loadingMoreTitleCustomStyle', {}); | ||
| } | ||
| }, | ||
| //自定义底部加载更多加载中动画样式 | ||
| loadingMoreLoadingIconCustomStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('loadingMoreLoadingIconCustomStyle', {}); | ||
| } | ||
| }, | ||
| //自定义底部加载更多加载中动画图标类型,可选flower或circle,默认为flower | ||
| loadingMoreLoadingIconType: { | ||
| type: String, | ||
| default: u.gc('loadingMoreLoadingIconType', 'flower') | ||
| }, | ||
| //自定义底部加载更多加载中动画图标图片 | ||
| loadingMoreLoadingIconCustomImage: { | ||
| type: String, | ||
| default: u.gc('loadingMoreLoadingIconCustomImage', '') | ||
| }, | ||
| //底部加载更多加载中view是否展示旋转动画,默认为是 | ||
| loadingMoreLoadingAnimated: { | ||
| type: Boolean, | ||
| default: u.gc('loadingMoreLoadingAnimated', true) | ||
| }, | ||
| //是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是 | ||
| loadingMoreEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('loadingMoreEnabled', true) | ||
| }, | ||
| //是否启用滑动到底部加载更多数据,默认为是 | ||
| toBottomLoadingMoreEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('toBottomLoadingMoreEnabled', true) | ||
| }, | ||
| //滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】 | ||
| loadingMoreDefaultAsLoading: { | ||
| type: [Boolean], | ||
| default: u.gc('loadingMoreDefaultAsLoading', false) | ||
| }, | ||
| //滑动到底部"默认"文字,默认为【点击加载更多】 | ||
| loadingMoreDefaultText: { | ||
| type: [String, Object], | ||
| default: u.gc('loadingMoreDefaultText', null) | ||
| }, | ||
| //滑动到底部"加载中"文字,默认为【正在加载...】 | ||
| loadingMoreLoadingText: { | ||
| type: [String, Object], | ||
| default: u.gc('loadingMoreLoadingText', null) | ||
| }, | ||
| //滑动到底部"没有更多"文字,默认为【没有更多了】 | ||
| loadingMoreNoMoreText: { | ||
| type: [String, Object], | ||
| default: u.gc('loadingMoreNoMoreText', null) | ||
| }, | ||
| //滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】 | ||
| loadingMoreFailText: { | ||
| type: [String, Object], | ||
| default: u.gc('loadingMoreFailText', null) | ||
| }, | ||
| //当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view,默认为否 | ||
| hideNoMoreInside: { | ||
| type: Boolean, | ||
| default: u.gc('hideNoMoreInside', false) | ||
| }, | ||
| //当没有更多数据且分页数组长度少于这个值时,隐藏没有更多数据的view,默认为0,代表不限制。 | ||
| hideNoMoreByLimit: { | ||
| type: Number, | ||
| default: u.gc('hideNoMoreByLimit', 0) | ||
| }, | ||
| //是否显示默认的加载更多text,默认为是 | ||
| showDefaultLoadingMoreText: { | ||
| type: Boolean, | ||
| default: u.gc('showDefaultLoadingMoreText', true) | ||
| }, | ||
| //是否显示没有更多数据的view | ||
| showLoadingMoreNoMoreView: { | ||
| type: Boolean, | ||
| default: u.gc('showLoadingMoreNoMoreView', true) | ||
| }, | ||
| //是否显示没有更多数据的分割线,默认为是 | ||
| showLoadingMoreNoMoreLine: { | ||
| type: Boolean, | ||
| default: u.gc('showLoadingMoreNoMoreLine', true) | ||
| }, | ||
| //自定义底部没有更多数据的分割线样式 | ||
| loadingMoreNoMoreLineCustomStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('loadingMoreNoMoreLineCustomStyle', {}); | ||
| }, | ||
| }, | ||
| //当分页未满一屏时,是否自动加载更多,默认为否(nvue无效) | ||
| insideMore: { | ||
| type: Boolean, | ||
| default: u.gc('insideMore', false) | ||
| }, | ||
| //距底部/右边多远时(单位px),触发 scrolltolower 事件,默认为100rpx | ||
| lowerThreshold: { | ||
| type: [Number, String], | ||
| default: u.gc('lowerThreshold', '100rpx') | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| M: Enum.More, | ||
| //底部加载更多状态 | ||
| loadingStatus: Enum.More.Default, | ||
| loadingStatusAfterRender: Enum.More.Default, | ||
| loadingMoreTimeStamp: 0, | ||
| loadingMoreDefaultSlot: null, | ||
| showLoadingMore: false, | ||
| customNoMore: -1, | ||
| } | ||
| }, | ||
| computed: { | ||
| zLoadMoreConfig() { | ||
| return { | ||
| status: this.loadingStatusAfterRender, | ||
| defaultAsLoading: this.loadingMoreDefaultAsLoading, | ||
| defaultThemeStyle: this.finalLoadingMoreThemeStyle, | ||
| customStyle: this.loadingMoreCustomStyle, | ||
| titleCustomStyle: this.loadingMoreTitleCustomStyle, | ||
| iconCustomStyle: this.loadingMoreLoadingIconCustomStyle, | ||
| loadingIconType: this.loadingMoreLoadingIconType, | ||
| loadingIconCustomImage: this.loadingMoreLoadingIconCustomImage, | ||
| loadingAnimated: this.loadingMoreLoadingAnimated, | ||
| showNoMoreLine: this.showLoadingMoreNoMoreLine, | ||
| noMoreLineCustomStyle: this.loadingMoreNoMoreLineCustomStyle, | ||
| defaultText: this.finalLoadingMoreDefaultText, | ||
| loadingText: this.finalLoadingMoreLoadingText, | ||
| noMoreText: this.finalLoadingMoreNoMoreText, | ||
| failText: this.finalLoadingMoreFailText, | ||
| hideContent: !this.loadingMoreDefaultAsLoading && this.listRendering, | ||
| }; | ||
| }, | ||
| finalLoadingMoreThemeStyle() { | ||
| return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle; | ||
| }, | ||
| showLoadingMoreDefault() { | ||
| return this._showLoadingMore('Default'); | ||
| }, | ||
| showLoadingMoreLoading() { | ||
| return this._showLoadingMore('Loading'); | ||
| }, | ||
| showLoadingMoreNoMore() { | ||
| return this._showLoadingMore('NoMore'); | ||
| }, | ||
| showLoadingMoreFail() { | ||
| return this._showLoadingMore('Fail'); | ||
| }, | ||
| showLoadingMoreCustom() { | ||
| return this._showLoadingMore('Custom'); | ||
| } | ||
| }, | ||
| methods: { | ||
| //页面滚动到底部时通知z-paging进行进一步处理 | ||
| pageReachBottom() { | ||
| !this.useChatRecordMode && this._onLoadingMore('toBottom'); | ||
| }, | ||
| //手动触发上拉加载更多(非必须,可依据具体需求使用) | ||
| doLoadMore(type) { | ||
| this._onLoadingMore(type); | ||
| }, | ||
| //通过@scroll事件检测是否滚动到了底部 | ||
| _checkScrolledToBottom(scrollDiff, checked = false) { | ||
| if (this.cacheScrollNodeHeight === -1) { | ||
| this._getNodeClientRect('.zp-scroll-view').then((res) => { | ||
| if (res) { | ||
| const pageScrollNodeHeight = res[0].height; | ||
| this.cacheScrollNodeHeight = pageScrollNodeHeight; | ||
| if (scrollDiff - pageScrollNodeHeight <= this.finalLowerThreshold) { | ||
| this._onLoadingMore('toBottom'); | ||
| } | ||
| } | ||
| }); | ||
| } else { | ||
| if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) { | ||
| this._onLoadingMore('toBottom'); | ||
| } else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) { | ||
| u.delay(() => { | ||
| this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => { | ||
| this.oldScrollTop = res[0].scrollTop; | ||
| const newScrollDiff = res[0].scrollHeight - this.oldScrollTop; | ||
| this._checkScrolledToBottom(newScrollDiff, true); | ||
| }) | ||
| }, 150, 'checkScrolledToBottomDelay') | ||
| } | ||
| } | ||
| }, | ||
| //触发加载更多时调用,from:toBottom-滑动到底部触发;1、click-点击加载更多触发 | ||
| _onLoadingMore(from = 'click') { | ||
| if (this.isIos && from === 'toBottom' && !this.scrollToBottomBounceEnabled && this.scrollEnable) { | ||
| this.scrollEnable = false; | ||
| this.$nextTick(() => { | ||
| this.scrollEnable = true; | ||
| }) | ||
| } | ||
| this.$emit('scrolltolower', from); | ||
| if (from === 'toBottom' && (!this.toBottomLoadingMoreEnabled || this.useChatRecordMode)) return; | ||
| if (this.refresherOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return; | ||
| // #ifdef MP-WEIXIN | ||
| if (!this.isIos && !this.refresherOnly && !this.usePageScroll) { | ||
| const currentTimestamp = u.getTime(); | ||
| if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) { | ||
| this.loadingMoreTimeStamp = 0; | ||
| return; | ||
| } | ||
| } | ||
| // #endif | ||
| this._doLoadingMore(); | ||
| }, | ||
| //处理开始加载更多 | ||
| _doLoadingMore() { | ||
| if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) { | ||
| this.pageNo ++; | ||
| this._startLoading(false); | ||
| if (this.isLocalPaging) { | ||
| this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => { | ||
| this.completeByTotal(res, this.totalLocalPagingList.length); | ||
| }) | ||
| } else { | ||
| this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadingMore); | ||
| this._callMyParentQuery(); | ||
| } | ||
| this.loadingType = Enum.LoadingType.LoadingMore; | ||
| } | ||
| }, | ||
| //(预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view | ||
| _preCheckShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode) { | ||
| if (this.loadingStatus === Enum.More.NoMore && this.hideNoMoreByLimit > 0 && newVal.length) { | ||
| this.showLoadingMore = newVal.length > this.hideNoMoreByLimit; | ||
| } else if ((this.loadingStatus === Enum.More.NoMore && this.hideNoMoreInside && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) { | ||
| this.$nextTick(() => { | ||
| this._checkShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode); | ||
| }) | ||
| if (this.insideMore && this.insideOfPaging !== false && newVal.length) { | ||
| this.showLoadingMore = newVal.length; | ||
| } | ||
| } else { | ||
| this.showLoadingMore = newVal.length; | ||
| } | ||
| }, | ||
| //判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view | ||
| async _checkShowNoMoreInside(totalData, oldScrollViewNode, oldPagingContainerNode) { | ||
| try { | ||
| const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); | ||
| if (this.usePageScroll) { | ||
| if (scrollViewNode) { | ||
| const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height; | ||
| this.insideOfPaging = scrollViewTotalH < this.windowHeight; | ||
| if (this.hideNoMoreInside) { | ||
| this.showLoadingMore = !this.insideOfPaging; | ||
| } | ||
| this._updateInsideOfPaging(); | ||
| } | ||
| } else { | ||
| const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content'); | ||
| const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; | ||
| const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; | ||
| this.insideOfPaging = pagingContainerH < scrollViewH; | ||
| if (this.hideNoMoreInside) { | ||
| this.showLoadingMore = !this.insideOfPaging; | ||
| } | ||
| this._updateInsideOfPaging(); | ||
| } | ||
| } catch (e) { | ||
| this.insideOfPaging = !totalData.length; | ||
| if (this.hideNoMoreInside) { | ||
| this.showLoadingMore = !this.insideOfPaging; | ||
| } | ||
| this._updateInsideOfPaging(); | ||
| } | ||
| }, | ||
| //是否要展示上拉加载更多view | ||
| _showLoadingMore(type) { | ||
| if (!this.showLoadingMoreWhenReload && (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.realTotalData.length)) return false; | ||
| if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) || | ||
| (!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.refresherOnly) { | ||
| return false; | ||
| } | ||
| if (this.useChatRecordMode && type !== 'Loading') return false; | ||
| if (!this.zSlots) return false; | ||
| if (type === 'Custom') { | ||
| return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView); | ||
| } | ||
| const res = this.loadingStatus === Enum.More[type] && this.zSlots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true); | ||
| if (res) { | ||
| // #ifdef APP-NVUE | ||
| if (!this.isIos) { | ||
| this.nLoadingMoreFixedHeight = false; | ||
| } | ||
| // #endif | ||
| } | ||
| return res; | ||
| }, | ||
| } | ||
| } |
| // [z-paging]loading相关模块 | ||
| import u from '.././z-paging-utils' | ||
| import Enum from '.././z-paging-enum' | ||
| export default { | ||
| props: { | ||
| //第一次加载后自动隐藏loading slot,默认为是 | ||
| autoHideLoadingAfterFirstLoaded: { | ||
| type: Boolean, | ||
| default: u.gc('autoHideLoadingAfterFirstLoaded', true) | ||
| }, | ||
| //loading slot是否铺满屏幕并固定,默认为否 | ||
| loadingFullFixed: { | ||
| type: Boolean, | ||
| default: u.gc('loadingFullFixed', false) | ||
| }, | ||
| //是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。 | ||
| autoShowSystemLoading: { | ||
| type: Boolean, | ||
| default: u.gc('autoShowSystemLoading', false) | ||
| }, | ||
| //显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效) | ||
| systemLoadingMask: { | ||
| type: Boolean, | ||
| default: u.gc('systemLoadingMask', true) | ||
| }, | ||
| //显示系统Loading时显示的文字,默认为"加载中" | ||
| systemLoadingText: { | ||
| type: [String, Object], | ||
| default: u.gc('systemLoadingText', null) | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| loading: false, | ||
| loadingForNow: false, | ||
| } | ||
| }, | ||
| watch: { | ||
| loadingStatus(newVal) { | ||
| this.$emit('loadingStatusChange', newVal); | ||
| this.$nextTick(() => { | ||
| this.loadingStatusAfterRender = newVal; | ||
| }) | ||
| // #ifdef APP-NVUE | ||
| if (this.useChatRecordMode) { | ||
| if (this.pageNo === this.defaultPageNo && newVal === Enum.More.NoMore) { | ||
| this.nIsFirstPageAndNoMore = true; | ||
| return; | ||
| } | ||
| } | ||
| this.nIsFirstPageAndNoMore = false; | ||
| // #endif | ||
| }, | ||
| loading(newVal){ | ||
| if (newVal) { | ||
| this.loadingForNow = newVal; | ||
| } | ||
| }, | ||
| }, | ||
| computed: { | ||
| showLoading() { | ||
| if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false; | ||
| if (this.finalShowSystemLoading){ | ||
| uni.showLoading({ | ||
| title: this.finalSystemLoadingText, | ||
| mask: this.systemLoadingMask | ||
| }) | ||
| } | ||
| return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher; | ||
| }, | ||
| finalShowSystemLoading() { | ||
| return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher; | ||
| } | ||
| }, | ||
| methods: { | ||
| //处理开始加载更多状态 | ||
| _startLoading(isReload = false) { | ||
| if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) { | ||
| this.loadingStatus = Enum.More.Loading; | ||
| } | ||
| this.loading = true; | ||
| }, | ||
| //停止系统loading和refresh | ||
| _endSystemLoadingAndRefresh(){ | ||
| this.finalShowSystemLoading && uni.hideLoading(); | ||
| !this.useCustomRefresher && uni.stopPullDownRefresh(); | ||
| // #ifdef APP-NVUE | ||
| this.usePageScroll && uni.stopPullDownRefresh(); | ||
| // #endif | ||
| } | ||
| } | ||
| } |
| // [z-paging]nvue独有部分模块 | ||
| import u from '.././z-paging-utils' | ||
| import c from '.././z-paging-constant' | ||
| import Enum from '.././z-paging-enum' | ||
| // #ifdef APP-NVUE | ||
| const weexAnimation = weex.requireModule('animation'); | ||
| // #endif | ||
| export default { | ||
| props: { | ||
| // #ifdef APP-NVUE | ||
| //nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list | ||
| nvueListIs: { | ||
| type: String, | ||
| default: u.gc('nvueListIs', 'list') | ||
| }, | ||
| //nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall | ||
| nvueWaterfallConfig: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('nvueWaterfallConfig', {}); | ||
| } | ||
| }, | ||
| //nvue 控制是否回弹效果,iOS不支持动态修改 | ||
| nvueBounce: { | ||
| type: Boolean, | ||
| default: u.gc('nvueBounce', true) | ||
| }, | ||
| //nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否 | ||
| nvueFastScroll: { | ||
| type: Boolean, | ||
| default: u.gc('nvueFastScroll', false) | ||
| }, | ||
| //nvue中list的id | ||
| nvueListId: { | ||
| type: String, | ||
| default: u.gc('nvueListId', '') | ||
| }, | ||
| //nvue中refresh组件的样式 | ||
| nvueRefresherStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('nvueRefresherStyle', {}); | ||
| } | ||
| }, | ||
| //nvue中是否按分页模式(类似竖向swiper)显示List,默认为false | ||
| nvuePagingEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('nvuePagingEnabled', false) | ||
| }, | ||
| //是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否 | ||
| hideNvueBottomTag: { | ||
| type: Boolean, | ||
| default: u.gc('hideNvueBottomTag', false) | ||
| }, | ||
| //nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能 | ||
| offsetAccuracy: { | ||
| type: Number, | ||
| default: u.gc('offsetAccuracy', 10) | ||
| }, | ||
| // #endif | ||
| }, | ||
| data() { | ||
| return { | ||
| nRefresherLoading: false, | ||
| nListIsDragging: false, | ||
| nShowBottom: true, | ||
| nFixFreezing: false, | ||
| nShowRefresherReveal: false, | ||
| nIsFirstPageAndNoMore: false, | ||
| nFirstPageAndNoMoreChecked: false, | ||
| nLoadingMoreFixedHeight: false, | ||
| nShowRefresherRevealHeight: 0, | ||
| nOldShowRefresherRevealHeight: -1, | ||
| nRefresherWidth: uni.upx2px(750), | ||
| } | ||
| }, | ||
| watch: { | ||
| // #ifdef APP-NVUE | ||
| nIsFirstPageAndNoMore: { | ||
| handler(newVal) { | ||
| const cellStyle = !this.useChatRecordMode || newVal ? {} : { transform: 'rotate(180deg)' }; | ||
| this.$emit('update:cellStyle', cellStyle); | ||
| this.$emit('cellStyleChange', cellStyle); | ||
| }, | ||
| immediate: true | ||
| }, | ||
| // #endif | ||
| }, | ||
| computed: { | ||
| // #ifdef APP-NVUE | ||
| nScopedSlots() { | ||
| // #ifdef VUE2 | ||
| return this.$scopedSlots; | ||
| // #endif | ||
| // #ifdef VUE3 | ||
| return null; | ||
| // #endif | ||
| }, | ||
| nWaterfallColumnCount() { | ||
| if (this.finalNvueListIs !== 'waterfall') return 0; | ||
| return this._nGetWaterfallConfig('column-count', 2); | ||
| }, | ||
| nWaterfallColumnWidth() { | ||
| return this._nGetWaterfallConfig('column-width', 'auto'); | ||
| }, | ||
| nWaterfallColumnGap() { | ||
| return this._nGetWaterfallConfig('column-gap', 'normal'); | ||
| }, | ||
| nWaterfallLeftGap() { | ||
| return this._nGetWaterfallConfig('left-gap', 0); | ||
| }, | ||
| nWaterfallRightGap() { | ||
| return this._nGetWaterfallConfig('right-gap', 0); | ||
| }, | ||
| nViewIs() { | ||
| const is = this.finalNvueListIs; | ||
| return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell'; | ||
| }, | ||
| nSafeAreaBottomHeight() { | ||
| return this.safeAreaInsetBottom ? this.safeAreaBottom : 0; | ||
| }, | ||
| nChatRecordRotateStyle() { | ||
| return this.useChatRecordMode ? { transform: this.nIsFirstPageAndNoMore ? 'rotate(0deg)' : 'rotate(180deg)' } : {}; | ||
| }, | ||
| finalNvueListIs() { | ||
| if (this.usePageScroll) return 'view'; | ||
| const nvueListIsLowerCase = this.nvueListIs.toLowerCase(); | ||
| if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase; | ||
| return 'list'; | ||
| }, | ||
| finalNvueSuperListIs() { | ||
| return this.usePageScroll ? 'view' : 'scroller'; | ||
| }, | ||
| finalNvueRefresherEnabled() { | ||
| return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode; | ||
| }, | ||
| // #endif | ||
| }, | ||
| mounted(){ | ||
| // #ifdef APP-NVUE | ||
| //旋转屏幕时更新宽度 | ||
| uni.onWindowResize((res) => { | ||
| // this._nUpdateRefresherWidth(); | ||
| }) | ||
| // #endif | ||
| }, | ||
| methods: { | ||
| // #ifdef APP-NVUE | ||
| //列表滚动时触发 | ||
| _nOnScroll(e) { | ||
| this.$emit('scroll', e); | ||
| const contentOffsetY = -e.contentOffset.y; | ||
| this.oldScrollTop = contentOffsetY; | ||
| this.nListIsDragging = e.isDragging; | ||
| this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1); | ||
| }, | ||
| //下拉刷新刷新中 | ||
| _nOnRrefresh() { | ||
| if (this.nShowRefresherReveal) return; | ||
| this.nRefresherLoading = true; | ||
| this.refresherStatus = Enum.Refresher.Loading; | ||
| this._doRefresherLoad(); | ||
| }, | ||
| //下拉刷新下拉中 | ||
| _nOnPullingdown(e) { | ||
| if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return; | ||
| this._emitTouchmove(e); | ||
| const { viewHeight, pullingDistance } = e; | ||
| this.refresherStatus = pullingDistance >= viewHeight ? Enum.Refresher.ReleaseToRefresh : Enum.Refresher.Default; | ||
| }, | ||
| //下拉刷新结束 | ||
| _nRefresherEnd(doEnd = true) { | ||
| if (doEnd) { | ||
| this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight); | ||
| !this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore(); | ||
| this.nRefresherLoading = false; | ||
| } | ||
| }, | ||
| //执行主动触发下拉刷新动画 | ||
| _nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) { | ||
| this._cleanRefresherCompleteTimeout(); | ||
| this._cleanRefresherEndTimeout(); | ||
| if (!this.finalShowRefresherWhenReload) { | ||
| this.refresherEndTimeout = u.delay(() => { | ||
| this.refresherStatus = Enum.Refresher.Default; | ||
| }, this.refresherCompleteDuration); | ||
| return; | ||
| } | ||
| const stackCount = this.refresherRevealStackCount; | ||
| if (height === 0 && checkStack) { | ||
| this.refresherRevealStackCount --; | ||
| if (stackCount > 1) return; | ||
| this.refresherEndTimeout = u.delay(() => { | ||
| this.refresherStatus = Enum.Refresher.Default; | ||
| }, this.refresherCompleteDuration); | ||
| } | ||
| if (stackCount > 1) { | ||
| this.refresherStatus = Enum.Refresher.Loading; | ||
| } | ||
| const duration = animate ? 200 : 0; | ||
| if (this.nOldShowRefresherRevealHeight !== height) { | ||
| if (height > 0) { | ||
| this.nShowRefresherReveal = true; | ||
| } | ||
| weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], { | ||
| styles: { | ||
| height: `${height}px`, | ||
| transform: `translateY(${translateY}px)`, | ||
| }, | ||
| duration, | ||
| timingFunction: 'linear', | ||
| needLayout: true, | ||
| delay: 0 | ||
| }) | ||
| } | ||
| u.delay(() => { | ||
| if (animate) { | ||
| this.nShowRefresherReveal = height > 0; | ||
| } | ||
| }, duration > 0 ? duration - 60 : 0); | ||
| this.nOldShowRefresherRevealHeight = height; | ||
| }, | ||
| //滚动到底部加载更多 | ||
| _nOnLoadmore() { | ||
| if (this.nShowRefresherReveal || !this.totalData.length) return; | ||
| this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom'); | ||
| }, | ||
| //获取nvue waterfall单项配置 | ||
| _nGetWaterfallConfig(key, defaultValue) { | ||
| return this.nvueWaterfallConfig[key] || defaultValue; | ||
| }, | ||
| //更新nvue 下拉刷新view容器的宽度 | ||
| _nUpdateRefresherWidth() { | ||
| u.delay(() => { | ||
| this.$nextTick(()=>{ | ||
| this._getNodeClientRect('.zp-n-list').then(node => { | ||
| if (node) { | ||
| this.nRefresherWidth = node[0].width || this.nRefresherWidth; | ||
| } | ||
| }) | ||
| }) | ||
| }) | ||
| } | ||
| // #endif | ||
| } | ||
| } |
| // [z-paging]下拉刷新view模块 | ||
| import u from '.././z-paging-utils' | ||
| import c from '.././z-paging-constant' | ||
| import Enum from '.././z-paging-enum' | ||
| export default { | ||
| props: { | ||
| //下拉刷新的主题样式,支持black,white,默认black | ||
| refresherThemeStyle: { | ||
| type: String, | ||
| default: u.gc('refresherThemeStyle', '') | ||
| }, | ||
| //自定义下拉刷新中左侧图标的样式 | ||
| refresherImgStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('refresherImgStyle', {}); | ||
| } | ||
| }, | ||
| //自定义下拉刷新中右侧状态描述文字的样式 | ||
| refresherTitleStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('refresherTitleStyle', {}); | ||
| } | ||
| }, | ||
| //自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效) | ||
| refresherUpdateTimeStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('refresherUpdateTimeStyle', {}); | ||
| } | ||
| }, | ||
| //在微信小程序和QQ小程序中,是否实时监听下拉刷新中进度,默认为否 | ||
| watchRefresherTouchmove: { | ||
| type: Boolean, | ||
| default: u.gc('watchRefresherTouchmove', false) | ||
| }, | ||
| //底部加载更多的主题样式,支持black,white,默认black | ||
| loadingMoreThemeStyle: { | ||
| type: String, | ||
| default: u.gc('loadingMoreThemeStyle', '') | ||
| }, | ||
| //是否只使用下拉刷新,设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图。默认为否 | ||
| refresherOnly: { | ||
| type: Boolean, | ||
| default: u.gc('refresherOnly', false) | ||
| }, | ||
| //自定义下拉刷新默认状态下回弹动画时间,单位为毫秒,默认为100毫秒,nvue无效 | ||
| refresherDefaultDuration: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherDefaultDuration', 100) | ||
| }, | ||
| //自定义下拉刷新结束以后延迟回弹的时间,单位为毫秒,默认为0 | ||
| refresherCompleteDelay: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherCompleteDelay', 0) | ||
| }, | ||
| //自定义下拉刷新结束回弹动画时间,单位为毫秒,默认为300毫秒(refresherEndBounceEnabled为false时,refresherCompleteDuration为设定值的1/3),nvue无效 | ||
| refresherCompleteDuration: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherCompleteDuration', 300) | ||
| }, | ||
| //自定义下拉刷新结束状态下是否允许列表滚动,默认为否 | ||
| refresherCompleteScrollable: { | ||
| type: Boolean, | ||
| default: u.gc('refresherCompleteScrollable', false) | ||
| }, | ||
| //是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新 | ||
| useCustomRefresher: { | ||
| type: Boolean, | ||
| default: u.gc('useCustomRefresher', true) | ||
| }, | ||
| //自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题 | ||
| refresherFps: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherFps', 40) | ||
| }, | ||
| //自定义下拉刷新允许触发的最大下拉角度,默认为40度,当下拉角度小于设定值时,自定义下拉刷新动画不会被触发 | ||
| refresherMaxAngle: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherMaxAngle', 40) | ||
| }, | ||
| //自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否 | ||
| refresherAngleEnableChangeContinued: { | ||
| type: Boolean, | ||
| default: u.gc('refresherAngleEnableChangeContinued', false) | ||
| }, | ||
| //自定义下拉刷新默认状态下的文字 | ||
| refresherDefaultText: { | ||
| type: [String, Object], | ||
| default: u.gc('refresherDefaultText', null) | ||
| }, | ||
| //自定义下拉刷新松手立即刷新状态下的文字 | ||
| refresherPullingText: { | ||
| type: [String, Object], | ||
| default: u.gc('refresherPullingText', null) | ||
| }, | ||
| //自定义下拉刷新刷新中状态下的文字 | ||
| refresherRefreshingText: { | ||
| type: [String, Object], | ||
| default: u.gc('refresherRefreshingText', null) | ||
| }, | ||
| //自定义下拉刷新刷新结束状态下的文字 | ||
| refresherCompleteText: { | ||
| type: [String, Object], | ||
| default: u.gc('refresherCompleteText', null) | ||
| }, | ||
| //自定义下拉刷新默认状态下的图片 | ||
| refresherDefaultImg: { | ||
| type: String, | ||
| default: u.gc('refresherDefaultImg', null) | ||
| }, | ||
| //自定义下拉刷新松手立即刷新状态下的图片,默认与refresherDefaultImg一致 | ||
| refresherPullingImg: { | ||
| type: String, | ||
| default: u.gc('refresherPullingImg', null) | ||
| }, | ||
| //自定义下拉刷新刷新中状态下的图片 | ||
| refresherRefreshingImg: { | ||
| type: String, | ||
| default: u.gc('refresherRefreshingImg', null) | ||
| }, | ||
| //自定义下拉刷新刷新结束状态下的图片 | ||
| refresherCompleteImg: { | ||
| type: String, | ||
| default: u.gc('refresherCompleteImg', null) | ||
| }, | ||
| //自定义下拉刷新刷新中状态下是否展示旋转动画 | ||
| refresherRefreshingAnimated: { | ||
| type: Boolean, | ||
| default: u.gc('refresherRefreshingAnimated', true) | ||
| }, | ||
| //是否开启自定义下拉刷新刷新结束回弹效果,默认为是 | ||
| refresherEndBounceEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('refresherEndBounceEnabled', true) | ||
| }, | ||
| //是否开启自定义下拉刷新,默认为是 | ||
| refresherEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('refresherEnabled', true) | ||
| }, | ||
| //设置自定义下拉刷新阈值,默认为80rpx | ||
| refresherThreshold: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherThreshold', '80rpx') | ||
| }, | ||
| //设置系统下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式,默认为black | ||
| refresherDefaultStyle: { | ||
| type: String, | ||
| default: u.gc('refresherDefaultStyle', 'black') | ||
| }, | ||
| //设置自定义下拉刷新区域背景 | ||
| refresherBackground: { | ||
| type: String, | ||
| default: u.gc('refresherBackground', 'transparent') | ||
| }, | ||
| //设置固定的自定义下拉刷新区域背景 | ||
| refresherFixedBackground: { | ||
| type: String, | ||
| default: u.gc('refresherFixedBackground', 'transparent') | ||
| }, | ||
| //设置固定的自定义下拉刷新区域高度,默认为0 | ||
| refresherFixedBacHeight: { | ||
| type: [Number, String], | ||
| default: u.gc('refresherFixedBacHeight', 0) | ||
| }, | ||
| //设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例,范围0-1,值越大代表衰减越多。默认为0.65(nvue无效) | ||
| refresherOutRate: { | ||
| type: Number, | ||
| default: u.gc('refresherOutRate', 0.65) | ||
| }, | ||
| //设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值,默认为0.75,即代表若用户下拉10px,则实际位移为7.5px(nvue无效) | ||
| refresherPullRate: { | ||
| type: Number, | ||
| default: u.gc('refresherPullRate', 0.75) | ||
| }, | ||
| //是否显示最后更新时间,默认为否 | ||
| showRefresherUpdateTime: { | ||
| type: Boolean, | ||
| default: u.gc('showRefresherUpdateTime', false) | ||
| }, | ||
| //如果需要区别不同页面的最后更新时间,请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串 | ||
| refresherUpdateTimeKey: { | ||
| type: String, | ||
| default: u.gc('refresherUpdateTimeKey', 'default') | ||
| }, | ||
| //下拉刷新时下拉到“松手立即刷新”状态时是否使手机短振动,默认为否(h5无效) | ||
| refresherVibrate: { | ||
| type: Boolean, | ||
| default: u.gc('refresherVibrate', false) | ||
| }, | ||
| //下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动,默认为否。注意此属性只是禁止下拉刷新view移动,其他下拉刷新逻辑依然会正常触发 | ||
| refresherNoTransform: { | ||
| type: Boolean, | ||
| default: u.gc('refresherNoTransform', false) | ||
| }, | ||
| //是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否 | ||
| useRefresherStatusBarPlaceholder: { | ||
| type: Boolean, | ||
| default: u.gc('useRefresherStatusBarPlaceholder', false) | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| R: Enum.Refresher, | ||
| //下拉刷新状态 | ||
| refresherStatus: Enum.Refresher.Default, | ||
| refresherTouchstartY: 0, | ||
| lastRefresherTouchmove: null, | ||
| refresherReachMaxAngle: true, | ||
| refresherTransform: 'translateY(0px)', | ||
| refresherTransition: '', | ||
| finalRefresherDefaultStyle: 'black', | ||
| refresherRevealStackCount: 0, | ||
| refresherCompleteTimeout: null, | ||
| refresherCompleteSubTimeout: null, | ||
| refresherEndTimeout: null, | ||
| isTouchmovingTimeout: null, | ||
| refresherTriggered: false, | ||
| isTouchmoving: false, | ||
| isTouchEnded: false, | ||
| isUserPullDown: false, | ||
| privateRefresherEnabled: -1, | ||
| privateShowRefresherWhenReload: false, | ||
| customRefresherHeight: -1, | ||
| showCustomRefresher: false, | ||
| doRefreshAnimateAfter: false, | ||
| isRefresherInComplete: false, | ||
| pullDownTimeStamp: 0, | ||
| moveDis: 0, | ||
| oldMoveDis: 0, | ||
| currentDis: 0, | ||
| oldCurrentMoveDis: 0, | ||
| oldRefresherTouchmoveY: 0, | ||
| oldTouchDirection: '', | ||
| oldEmitedTouchDirection: '', | ||
| oldPullingDistance: -1, | ||
| refresherThresholdUpdateTag: 0 | ||
| } | ||
| }, | ||
| watch: { | ||
| refresherDefaultStyle: { | ||
| handler(newVal) { | ||
| if (newVal.length) { | ||
| this.finalRefresherDefaultStyle = newVal; | ||
| } | ||
| }, | ||
| immediate: true | ||
| }, | ||
| refresherStatus(newVal) { | ||
| newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout(); | ||
| this.refresherVibrate && newVal === Enum.Refresher.ReleaseToRefresh && this._doVibrateShort(); | ||
| this.$emit('refresherStatusChange', newVal); | ||
| this.$emit('update:refresherStatus', newVal); | ||
| }, | ||
| refresherEnabled(newVal) { | ||
| !newVal && this.endRefresh(); | ||
| } | ||
| }, | ||
| computed: { | ||
| pullDownDisTimeStamp() { | ||
| return 1000 / this.refresherFps; | ||
| }, | ||
| finalRefresherEnabled() { | ||
| if (this.useChatRecordMode) return false; | ||
| if (this.privateRefresherEnabled === -1) return this.refresherEnabled; | ||
| return this.privateRefresherEnabled === 1; | ||
| }, | ||
| finalRefresherThreshold() { | ||
| let refresherThreshold = this.refresherThreshold; | ||
| let idDefault = false; | ||
| if (refresherThreshold === '80rpx') { | ||
| idDefault = true; | ||
| if (this.showRefresherUpdateTime) { | ||
| refresherThreshold = '120rpx'; | ||
| } | ||
| } | ||
| if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder; | ||
| return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder; | ||
| }, | ||
| finalRefresherThresholdPlaceholder() { | ||
| return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0; | ||
| }, | ||
| finalRefresherFixedBacHeight() { | ||
| return u.convertToPx(this.refresherFixedBacHeight); | ||
| }, | ||
| finalRefresherThemeStyle() { | ||
| return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle; | ||
| }, | ||
| finalRefresherOutRate() { | ||
| let rate = this.refresherOutRate; | ||
| rate = Math.max(0,rate); | ||
| rate = Math.min(1,rate); | ||
| return rate; | ||
| }, | ||
| finalRefresherPullRate() { | ||
| let rate = this.refresherPullRate; | ||
| rate = Math.max(0,rate); | ||
| return rate; | ||
| }, | ||
| finalRefresherTransform() { | ||
| if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none'; | ||
| return this.refresherTransform; | ||
| }, | ||
| finalShowRefresherWhenReload() { | ||
| return this.showRefresherWhenReload || this.privateShowRefresherWhenReload; | ||
| }, | ||
| finalRefresherTriggered() { | ||
| if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false; | ||
| return this.refresherTriggered; | ||
| }, | ||
| showRefresher() { | ||
| const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher; | ||
| // #ifndef APP-NVUE | ||
| this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight(); | ||
| // #endif | ||
| return showRefresher; | ||
| }, | ||
| hasTouchmove(){ | ||
| // #ifdef VUE2 | ||
| // #ifdef APP-VUE || H5 | ||
| if (this.$listeners && !this.$listeners.refresherTouchmove) return false; | ||
| // #endif | ||
| // #ifdef MP-WEIXIN || MP-QQ | ||
| return this.watchRefresherTouchmove; | ||
| // #endif | ||
| return true; | ||
| // #endif | ||
| return this.watchRefresherTouchmove; | ||
| }, | ||
| }, | ||
| methods: { | ||
| //终止下拉刷新状态 | ||
| endRefresh() { | ||
| this.totalData = this.realTotalData; | ||
| this._refresherEnd(); | ||
| this._endSystemLoadingAndRefresh(); | ||
| this._handleScrollViewDisableBounce({ bounce: true }); | ||
| this.$nextTick(() => { | ||
| this.refresherTriggered = false; | ||
| }) | ||
| }, | ||
| handleRefresherStatusChanged(func) { | ||
| this.refresherStatusChangedFunc = func; | ||
| }, | ||
| //手动更新自定义下拉刷新view高度 | ||
| updateCustomRefresherHeight() { | ||
| u.delay(() => this.$nextTick(this._updateCustomRefresherHeight)); | ||
| }, | ||
| //自定义下拉刷新被触发 | ||
| _onRefresh(fromScrollView = false,isUserPullDown = true) { | ||
| if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return; | ||
| this.$emit('onRefresh'); | ||
| this.$emit('Refresh'); | ||
| // #ifdef APP-NVUE | ||
| if (this.loading) { | ||
| u.delay(this._nRefresherEnd, 500) | ||
| return; | ||
| } | ||
| // #endif | ||
| if (this.loading || this.isRefresherInComplete) return; | ||
| this.loadingType = Enum.LoadingType.Refresher; | ||
| if (this.nShowRefresherReveal) return; | ||
| this.isUserPullDown = isUserPullDown; | ||
| this.isUserReload = !isUserPullDown; | ||
| this._startLoading(true); | ||
| this.refresherTriggered = true; | ||
| if(this.reloadWhenRefresh && isUserPullDown){ | ||
| this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown); | ||
| } | ||
| }, | ||
| //自定义下拉刷新被复位 | ||
| _onRestore() { | ||
| this.refresherTriggered = 'restore'; | ||
| this.$emit('onRestore'); | ||
| this.$emit('Restore'); | ||
| }, | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| //拖拽开始 | ||
| _refresherTouchstart(e) { | ||
| this._handleListTouchstart(); | ||
| if (this._touchDisabled()) return; | ||
| this._handleRefresherTouchstart(u.getTouch(e)); | ||
| }, | ||
| // #endif | ||
| //进一步处理拖拽开始结果 | ||
| _handleRefresherTouchstart(touch) { | ||
| if (!this.loading && this.isTouchEnded) { | ||
| this.isTouchmoving = false; | ||
| } | ||
| this.loadingType = Enum.LoadingType.Refresher; | ||
| this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); | ||
| this.isTouchEnded = false; | ||
| this.refresherTransition = ''; | ||
| this.refresherTouchstartY = touch.touchY; | ||
| this.$emit('refresherTouchstart', this.refresherTouchstartY); | ||
| this.lastRefresherTouchmove = touch; | ||
| this._cleanRefresherCompleteTimeout(); | ||
| this._cleanRefresherEndTimeout(); | ||
| }, | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| //拖拽中 | ||
| _refresherTouchmove(e) { | ||
| const currentTimeStamp = u.getTime(); | ||
| let touch = null; | ||
| let refresherTouchmoveY = 0; | ||
| if (this.watchTouchDirectionChange) { | ||
| touch = u.getTouch(e); | ||
| refresherTouchmoveY = touch.touchY; | ||
| const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom'; | ||
| if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) { | ||
| this._handleTouchDirectionChange({ direction }); | ||
| this.oldEmitedTouchDirection = direction; | ||
| } | ||
| this.oldTouchDirection = direction; | ||
| this.oldRefresherTouchmoveY = refresherTouchmoveY; | ||
| } | ||
| if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return; | ||
| if (this._touchDisabled()) return; | ||
| this.pullDownTimeStamp = Number(currentTimeStamp); | ||
| touch = u.getTouch(e); | ||
| refresherTouchmoveY = touch.touchY; | ||
| let moveDis = refresherTouchmoveY - this.refresherTouchstartY; | ||
| if (moveDis < 0) return; | ||
| if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) { | ||
| if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return; | ||
| const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX); | ||
| const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY); | ||
| const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); | ||
| if ((x || y) && x > 1) { | ||
| const angle = Math.asin(y / z) / Math.PI * 180; | ||
| if (angle < this.refresherMaxAngle) { | ||
| this.lastRefresherTouchmove = touch; | ||
| this.refresherReachMaxAngle = false; | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| moveDis = this._getFinalRefresherMoveDis(moveDis); | ||
| this._handleRefresherTouchmove(moveDis, touch); | ||
| if (!this.disabledBounce) { | ||
| if(this.isIos){ | ||
| // #ifndef MP-LARK | ||
| this._handleScrollViewDisableBounce({ bounce: false }); | ||
| // #endif | ||
| } | ||
| this.disabledBounce = true; | ||
| } | ||
| this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis }); | ||
| }, | ||
| // #endif | ||
| //进一步处理拖拽中结果 | ||
| _handleRefresherTouchmove(moveDis, touch) { | ||
| this.refresherReachMaxAngle = true; | ||
| this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); | ||
| this.isTouchmoving = true; | ||
| this.isTouchEnded = false; | ||
| this.refresherStatus = moveDis >= this.finalRefresherThreshold ? Enum.Refresher.ReleaseToRefresh : this.refresherStatus = Enum.Refresher.Default; | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| // this.scrollEnable = false; | ||
| this.refresherTransform = `translateY(${moveDis}px)`; | ||
| this.lastRefresherTouchmove = touch; | ||
| // #endif | ||
| this.moveDis = moveDis; | ||
| }, | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| //拖拽结束 | ||
| _refresherTouchend(e) { | ||
| if (this._touchDisabled() || !this.isTouchmoving) return; | ||
| const touch = u.getTouch(e); | ||
| let refresherTouchendY = touch.touchY; | ||
| let moveDis = refresherTouchendY - this.refresherTouchstartY; | ||
| moveDis = this._getFinalRefresherMoveDis(moveDis); | ||
| this._handleRefresherTouchend(moveDis); | ||
| this._handleScrollViewDisableBounce({bounce: true}); | ||
| this.disabledBounce = false; | ||
| }, | ||
| // #endif | ||
| //进一步处理拖拽结束结果 | ||
| _handleRefresherTouchend(moveDis) { | ||
| // #ifndef APP-PLUS || H5 || MP-WEIXIN | ||
| if (!this.isTouchmoving) return; | ||
| // #endif | ||
| this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); | ||
| this.refresherReachMaxAngle = true; | ||
| this.isTouchEnded = true; | ||
| const refresherThreshold = this.finalRefresherThreshold; | ||
| if (moveDis >= refresherThreshold && this.refresherStatus === Enum.Refresher.ReleaseToRefresh) { | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| this.refresherTransform = `translateY(${refresherThreshold}px)`; | ||
| this.refresherTransition = 'transform .1s linear'; | ||
| // #endif | ||
| u.delay(() => { | ||
| this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold }); | ||
| }, 0.1); | ||
| this.moveDis = refresherThreshold; | ||
| this.refresherStatus = Enum.Refresher.Loading; | ||
| this._doRefresherLoad(); | ||
| } else { | ||
| this._refresherEnd(); | ||
| this.isTouchmovingTimeout = u.delay(() => { | ||
| this.isTouchmoving = false; | ||
| }, this.refresherDefaultDuration); | ||
| } | ||
| this.scrollEnable = true; | ||
| this.$emit('refresherTouchend', moveDis); | ||
| }, | ||
| //处理列表触摸开始事件 | ||
| _handleListTouchstart() { | ||
| if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) { | ||
| uni.hideKeyboard(); | ||
| this.$emit('hidedKeyboard'); | ||
| } | ||
| }, | ||
| //处理scroll-view bounce是否生效 | ||
| _handleScrollViewDisableBounce({ bounce }) { | ||
| if (!this.usePageScroll && !this.scrollToTopBounceEnabled && this.wxsScrollTop <= 5) { | ||
| // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| this.refresherTransition = ''; | ||
| // #endif | ||
| this.scrollEnable = bounce; | ||
| } | ||
| }, | ||
| //wxs正在下拉状态改变处理 | ||
| _handleWxsPullingDownStatusChange(onPullingDown) { | ||
| this.wxsOnPullingDown = onPullingDown; | ||
| if (onPullingDown && !this.useChatRecordMode) { | ||
| this.renderPropScrollTop = 0; | ||
| } | ||
| }, | ||
| //wxs正在下拉处理 | ||
| _handleWxsPullingDown({ moveDis, diffDis }){ | ||
| this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis }); | ||
| }, | ||
| //wxs触摸方向改变 | ||
| _handleTouchDirectionChange({ direction }) { | ||
| this.$emit('touchDirectionChange',direction); | ||
| }, | ||
| //wxs通知更新其props | ||
| _handlePropUpdate(){ | ||
| this.wxsPropType = u.getTime().toString(); | ||
| }, | ||
| //下拉刷新结束 | ||
| _refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) { | ||
| if (this.loadingType === Enum.LoadingType.Refresher) { | ||
| const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0; | ||
| const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default; | ||
| if (this.finalShowRefresherWhenReload) { | ||
| const stackCount = this.refresherRevealStackCount; | ||
| this.refresherRevealStackCount --; | ||
| if (stackCount > 1) return; | ||
| } | ||
| this._cleanRefresherEndTimeout(); | ||
| this.refresherEndTimeout = u.delay(() => { | ||
| this.refresherStatus = refresherStatus; | ||
| }, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0); | ||
| // #ifndef APP-NVUE | ||
| if (refresherCompleteDelay > 0) { | ||
| this.isRefresherInComplete = true; | ||
| } | ||
| // #endif | ||
| this._cleanRefresherCompleteTimeout(); | ||
| this.refresherCompleteTimeout = u.delay(() => { | ||
| let animateDuration = 1; | ||
| const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear'; | ||
| if (fromAddData) { | ||
| animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000; | ||
| } | ||
| this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`; | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| this.refresherTransform = 'translateY(0px)'; | ||
| this.currentDis = 0; | ||
| // #endif | ||
| // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| this.wxsPropType = this.refresherTransition + 'end' + u.getTime(); | ||
| // #endif | ||
| // #ifdef APP-NVUE | ||
| this._nRefresherEnd(); | ||
| // #endif | ||
| this.moveDis = 0; | ||
| // #ifndef APP-NVUE | ||
| if (refresherStatus === Enum.Refresher.Complete) { | ||
| if (this.refresherCompleteSubTimeout) { | ||
| clearTimeout(this.refresherCompleteSubTimeout); | ||
| this.refresherCompleteSubTimeout = null; | ||
| } | ||
| this.refresherCompleteSubTimeout = u.delay(() => { | ||
| this.$nextTick(() => { | ||
| this.refresherStatus = Enum.Refresher.Default; | ||
| this.isRefresherInComplete = false; | ||
| }) | ||
| }, animateDuration * 800); | ||
| } | ||
| // #endif | ||
| this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis }); | ||
| }, refresherCompleteDelay); | ||
| } | ||
| if (setLoading) { | ||
| u.delay(() => this.loading = false, shouldEndLoadingDelay ? c.delayTime : 0); | ||
| isUserPullDown && this._onRestore(); | ||
| } | ||
| }, | ||
| //模拟用户手动触发下拉刷新 | ||
| _doRefresherRefreshAnimate() { | ||
| this._cleanRefresherCompleteTimeout(); | ||
| // #ifndef APP-NVUE | ||
| const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this | ||
| .customRefresherHeight === -1 && this.refresherThreshold === '80rpx'; | ||
| if (doRefreshAnimateAfter) { | ||
| this.doRefreshAnimateAfter = true; | ||
| return; | ||
| } | ||
| // #endif | ||
| this.refresherRevealStackCount ++; | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`; | ||
| // #endif | ||
| // #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| this.wxsPropType = 'begin' + u.getTime(); | ||
| // #endif | ||
| this.moveDis = this.finalRefresherThreshold; | ||
| this.refresherStatus = Enum.Refresher.Loading; | ||
| this.isTouchmoving = true; | ||
| this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout); | ||
| this._doRefresherLoad(false); | ||
| }, | ||
| //触发下拉刷新 | ||
| _doRefresherLoad(isUserPullDown = true) { | ||
| this._onRefresh(false,isUserPullDown); | ||
| this.loading = true; | ||
| }, | ||
| // #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 | ||
| //获取处理后的moveDis | ||
| _getFinalRefresherMoveDis(moveDis) { | ||
| let diffDis = moveDis - this.oldCurrentMoveDis; | ||
| this.oldCurrentMoveDis = moveDis; | ||
| if (diffDis > 0) { | ||
| diffDis = diffDis * this.finalRefresherPullRate; | ||
| if (this.currentDis > this.finalRefresherThreshold) { | ||
| diffDis = diffDis * (1 - this.finalRefresherOutRate); | ||
| } | ||
| } | ||
| diffDis = diffDis > 100 ? diffDis / 100 : diffDis; | ||
| this.currentDis += diffDis; | ||
| this.currentDis = Math.max(0, this.currentDis); | ||
| return this.currentDis; | ||
| }, | ||
| //判断touch手势是否要触发 | ||
| _touchDisabled() { | ||
| const checkOldScrollTop = this.oldScrollTop > 5; | ||
| return this.loading || this.isRefresherInComplete || this.useChatRecordMode || !this.refresherEnabled || !this.useCustomRefresher ||(this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop); | ||
| }, | ||
| // #endif | ||
| //更新自定义下拉刷新view高度 | ||
| _updateCustomRefresherHeight() { | ||
| this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => { | ||
| this.customRefresherHeight = res ? res[0].height : 0; | ||
| this.showCustomRefresher = this.customRefresherHeight > 0; | ||
| if (this.doRefreshAnimateAfter) { | ||
| this.doRefreshAnimateAfter = false; | ||
| this._doRefresherRefreshAnimate(); | ||
| } | ||
| }); | ||
| }, | ||
| //发射pullingDown事件 | ||
| _emitTouchmove(e) { | ||
| // #ifndef APP-NVUE | ||
| e.viewHeight = this.finalRefresherThreshold; | ||
| // #endif | ||
| e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0; | ||
| this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e); | ||
| this.oldPullingDistance = e.pullingDistance; | ||
| }, | ||
| //清除refresherCompleteTimeout | ||
| _cleanRefresherCompleteTimeout() { | ||
| this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout); | ||
| // #ifdef APP-NVUE | ||
| this._nRefresherEnd(false); | ||
| // #endif | ||
| }, | ||
| //清除refresherEndTimeout | ||
| _cleanRefresherEndTimeout() { | ||
| this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout); | ||
| }, | ||
| } | ||
| } |
| // [z-paging]scroll相关模块 | ||
| import u from '.././z-paging-utils' | ||
| import Enum from '.././z-paging-enum' | ||
| // #ifdef APP-NVUE | ||
| const weexDom = weex.requireModule('dom'); | ||
| // #endif | ||
| export default { | ||
| props: { | ||
| //使用页面滚动,默认为否,当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动,使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高,但配置会略微繁琐 | ||
| usePageScroll: { | ||
| type: Boolean, | ||
| default: u.gc('usePageScroll', false) | ||
| }, | ||
| //是否可以滚动,使用内置scroll-view和nvue时有效,默认为是 | ||
| scrollable: { | ||
| type: Boolean, | ||
| default: u.gc('scrollable', true) | ||
| }, | ||
| //控制是否出现滚动条,默认为是 | ||
| showScrollbar: { | ||
| type: Boolean, | ||
| default: u.gc('showScrollbar', true) | ||
| }, | ||
| //是否允许横向滚动,默认为否 | ||
| scrollX: { | ||
| type: Boolean, | ||
| default: u.gc('scrollX', false) | ||
| }, | ||
| //iOS设备上滚动到顶部时是否允许回弹效果,默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯,但是有吸顶view时滚动到顶部时可能出现抖动。 | ||
| scrollToTopBounceEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('scrollToTopBounceEnabled', false) | ||
| }, | ||
| //iOS设备上滚动到底部时是否允许回弹效果,默认为是。 | ||
| scrollToBottomBounceEnabled: { | ||
| type: Boolean, | ||
| default: u.gc('scrollToBottomBounceEnabled', true) | ||
| }, | ||
| //在设置滚动条位置时使用动画过渡,默认为否 | ||
| scrollWithAnimation: { | ||
| type: Boolean, | ||
| default: u.gc('scrollWithAnimation', false) | ||
| }, | ||
| //值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素 | ||
| scrollIntoView: { | ||
| type: String, | ||
| default: u.gc('scrollIntoView', '') | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| scrollTop: 0, | ||
| oldScrollTop: 0, | ||
| scrollViewStyle: {}, | ||
| scrollViewContainerStyle: {}, | ||
| scrollViewInStyle: {}, | ||
| pageScrollTop: -1, | ||
| scrollEnable: true, | ||
| privateScrollWithAnimation: -1, | ||
| cacheScrollNodeHeight: -1 | ||
| } | ||
| }, | ||
| watch: { | ||
| oldScrollTop(newVal) { | ||
| !this.usePageScroll && this._scrollTopChange(newVal,false); | ||
| }, | ||
| pageScrollTop(newVal) { | ||
| this.usePageScroll && this._scrollTopChange(newVal,true); | ||
| }, | ||
| usePageScroll: { | ||
| handler(newVal) { | ||
| this.loaded && this.autoHeight && this._setAutoHeight(!newVal); | ||
| // #ifdef H5 | ||
| if (newVal) { | ||
| this.$nextTick(() => { | ||
| const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main; | ||
| if (mainScrollRef) { | ||
| mainScrollRef.style = {}; | ||
| } | ||
| }) | ||
| } | ||
| // #endif | ||
| }, | ||
| immediate: true | ||
| }, | ||
| finalScrollTop(newVal) { | ||
| if (!this.useChatRecordMode) { | ||
| this.renderPropScrollTop = newVal < 6 ? 0 : 10; | ||
| } | ||
| }, | ||
| }, | ||
| computed: { | ||
| finalScrollWithAnimation() { | ||
| if (this.privateScrollWithAnimation !== -1) { | ||
| const scrollWithAnimation = this.privateScrollWithAnimation === 1; | ||
| this.privateScrollWithAnimation = -1; | ||
| return scrollWithAnimation; | ||
| } | ||
| return this.scrollWithAnimation; | ||
| }, | ||
| finalScrollViewStyle() { | ||
| if (this.superContentZIndex != 1) { | ||
| this.scrollViewStyle['z-index'] = this.superContentZIndex; | ||
| this.scrollViewStyle['position'] = 'relative'; | ||
| } | ||
| return this.scrollViewStyle; | ||
| }, | ||
| finalScrollTop() { | ||
| return this.usePageScroll ? this.pageScrollTop : this.oldScrollTop; | ||
| }, | ||
| finalIsOldWebView() { | ||
| return this.isOldWebView && !this.usePageScroll; | ||
| } | ||
| }, | ||
| methods: { | ||
| //滚动到顶部,animate为是否展示滚动动画,默认为是 | ||
| scrollToTop(animate, checkReverse = true) { | ||
| // #ifdef APP-NVUE | ||
| if (checkReverse && this.useChatRecordMode) { | ||
| if (!this.nIsFirstPageAndNoMore) { | ||
| this.scrollToBottom(animate, false); | ||
| return; | ||
| } | ||
| } | ||
| // #endif | ||
| this.$nextTick(() => { | ||
| this._scrollToTop(animate, false); | ||
| // #ifdef APP-NVUE | ||
| if (this.nvueFastScroll && animate) { | ||
| u.delay(() => { | ||
| this._scrollToTop(false, false); | ||
| }); | ||
| } | ||
| // #endif | ||
| }) | ||
| }, | ||
| //滚动到底部,animate为是否展示滚动动画,默认为是 | ||
| scrollToBottom(animate, checkReverse = true) { | ||
| // #ifdef APP-NVUE | ||
| if (checkReverse && this.useChatRecordMode) { | ||
| if (!this.nIsFirstPageAndNoMore) { | ||
| this.scrollToTop(animate, false); | ||
| return; | ||
| } | ||
| } | ||
| // #endif | ||
| this.$nextTick(() => { | ||
| this._scrollToBottom(animate); | ||
| // #ifdef APP-NVUE | ||
| if (this.nvueFastScroll && animate) { | ||
| u.delay(() => { | ||
| this._scrollToBottom(false); | ||
| }); | ||
| } | ||
| // #endif | ||
| }) | ||
| }, | ||
| //滚动到指定view(vue中有效)。sel为需要滚动的view的id值,不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 | ||
| scrollIntoViewById(sel, offset, animate) { | ||
| this._scrollIntoView(sel, offset, animate); | ||
| }, | ||
| //滚动到指定view(vue中有效)。nodeTop为需要滚动的view的top值(通过uni.createSelectorQuery()获取);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 | ||
| scrollIntoViewByNodeTop(nodeTop, offset, animate) { | ||
| this.scrollTop = this.oldScrollTop; | ||
| this.$nextTick(() => { | ||
| this._scrollIntoViewByNodeTop(nodeTop, offset, animate); | ||
| }) | ||
| }, | ||
| //滚动到指定位置(vue中有效)。y为与顶部的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 | ||
| scrollToY(y, offset, animate) { | ||
| this.scrollTop = this.oldScrollTop; | ||
| this.$nextTick(() => { | ||
| this._scrollToY(y, offset, animate); | ||
| }) | ||
| }, | ||
| //滚动到指定view(nvue中有效)。index为需要滚动的view的index(第几个);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 | ||
| scrollIntoViewByIndex(index, offset, animate) { | ||
| this._scrollIntoView(index, offset, animate); | ||
| }, | ||
| //滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否 | ||
| scrollIntoViewByView(view, offset, animate) { | ||
| this._scrollIntoView(view, offset, animate); | ||
| }, | ||
| //当使用页面滚动并且自定义下拉刷新时,请在页面的onPageScroll中调用此方法,告知z-paging当前的pageScrollTop,否则会导致在任意位置都可以下拉刷新 | ||
| updatePageScrollTop(value) { | ||
| this.pageScrollTop = value; | ||
| }, | ||
| //当使用页面滚动并且设置了slot="top"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="top"的view高度动态改变时,在其高度需要更新时调用此方法 | ||
| updatePageScrollTopHeight() { | ||
| this._updatePageScrollTopOrBottomHeight('top'); | ||
| }, | ||
| //当使用页面滚动并且设置了slot="bottom"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="bottom"的view高度动态改变时,在其高度需要更新时调用此方法 | ||
| updatePageScrollBottomHeight() { | ||
| this._updatePageScrollTopOrBottomHeight('bottom'); | ||
| }, | ||
| //更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用 | ||
| updateLeftAndRightWidth() { | ||
| if (!this.finalIsOldWebView) return; | ||
| this.$nextTick(() => this._updateLeftAndRightWidth(this.scrollViewContainerStyle, 'zp-page')); | ||
| }, | ||
| //更新z-paging内置scroll-view的scrollTop | ||
| updateScrollViewScrollTop(scrollTop, animate = true) { | ||
| this.privateScrollWithAnimation = animate ? 1 : 0; | ||
| this.scrollTop = this.oldScrollTop; | ||
| this.$nextTick(() => { | ||
| this.scrollTop = scrollTop; | ||
| this.oldScrollTop = this.scrollTop; | ||
| }); | ||
| }, | ||
| //当滚动到顶部时 | ||
| _onScrollToUpper() { | ||
| this.$emit('scrolltoupper'); | ||
| this.$emit('scrollTopChange', 0); | ||
| this.$nextTick(() => { | ||
| this.oldScrollTop = 0; | ||
| }) | ||
| this.useChatRecordMode && this.loadingStatus !== Enum.More.NoMore && this._onLoadingMore('click'); | ||
| }, | ||
| //当滚动到底部时 | ||
| _onScrollToLower(e) { | ||
| (!e.detail || !e.detail.direction || e.detail.direction === 'bottom') && this._onLoadingMore('toBottom') | ||
| }, | ||
| //滚动到顶部 | ||
| _scrollToTop(animate = true, isPrivate = true) { | ||
| // #ifdef APP-NVUE | ||
| const el = this.$refs['zp-n-list-top-tag']; | ||
| if (this.usePageScroll) { | ||
| this._getNodeClientRect('zp-page-scroll-top', false).then(node => { | ||
| const nodeHeight = node ? node[0].height : 0; | ||
| weexDom.scrollToElement(el, { | ||
| offset: -nodeHeight, | ||
| animated: animate | ||
| }); | ||
| }); | ||
| } else { | ||
| if (!this.isIos && this.nvueListIs === 'scroller') { | ||
| this._getNodeClientRect('zp-n-refresh-container', false).then(node => { | ||
| const nodeHeight = node ? node[0].height : 0; | ||
| weexDom.scrollToElement(el, { | ||
| offset: -nodeHeight, | ||
| animated: animate | ||
| }); | ||
| }); | ||
| } else { | ||
| weexDom.scrollToElement(el, { | ||
| offset: 0, | ||
| animated: animate | ||
| }); | ||
| } | ||
| } | ||
| return; | ||
| // #endif | ||
| if (this.usePageScroll) { | ||
| this.$nextTick(() => { | ||
| uni.pageScrollTo({ | ||
| scrollTop: 0, | ||
| duration: animate ? 100 : 0, | ||
| }); | ||
| }); | ||
| return; | ||
| } | ||
| this.privateScrollWithAnimation = animate ? 1 : 0; | ||
| this.scrollTop = this.oldScrollTop; | ||
| this.$nextTick(() => { | ||
| this.scrollTop = 0; | ||
| this.oldScrollTop = this.scrollTop; | ||
| }); | ||
| }, | ||
| //滚动到底部 | ||
| async _scrollToBottom(animate = true) { | ||
| // #ifdef APP-NVUE | ||
| const el = this.$refs['zp-n-list-bottom-tag']; | ||
| if (el) { | ||
| weexDom.scrollToElement(el, { | ||
| offset: 0, | ||
| animated: animate | ||
| }); | ||
| } else { | ||
| u.consoleErr('滚动到底部失败,因为您设置了hideNvueBottomTag为true'); | ||
| } | ||
| return; | ||
| // #endif | ||
| if (this.usePageScroll) { | ||
| this.$nextTick(() => { | ||
| uni.pageScrollTo({ | ||
| scrollTop: Number.MAX_VALUE, | ||
| duration: animate ? 100 : 0, | ||
| }); | ||
| }); | ||
| return; | ||
| } | ||
| try { | ||
| this.privateScrollWithAnimation = animate ? 1 : 0; | ||
| const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container'); | ||
| const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); | ||
| const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0; | ||
| const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0; | ||
| if (pagingContainerH > scrollViewH) { | ||
| this.scrollTop = this.oldScrollTop; | ||
| this.$nextTick(() => { | ||
| this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight; | ||
| this.oldScrollTop = this.scrollTop; | ||
| }); | ||
| } | ||
| } catch (e) {} | ||
| }, | ||
| //滚动到指定view | ||
| _scrollIntoView(sel, offset = 0, animate = false, finishCallback) { | ||
| try { | ||
| this.scrollTop = this.oldScrollTop; | ||
| this.$nextTick(() => { | ||
| // #ifdef APP-NVUE | ||
| const refs = this.$parent.$refs; | ||
| if (!refs) return; | ||
| const dataType = Object.prototype.toString.call(sel); | ||
| let el = null; | ||
| if (dataType === '[object Number]') { | ||
| const els = refs[`z-paging-${sel}`]; | ||
| el = els ? els[0] : null; | ||
| } else if (dataType === '[object Array]') { | ||
| el = sel[0]; | ||
| } else { | ||
| el = sel; | ||
| } | ||
| if (el) { | ||
| weexDom.scrollToElement(el, { | ||
| offset: -offset, | ||
| animated: animate | ||
| }); | ||
| } else { | ||
| u.consoleErr('在nvue中滚动到指定位置,cell必须设置 :ref="`z-paging-${index}`"'); | ||
| } | ||
| return; | ||
| // #endif | ||
| this._getNodeClientRect('#' + sel.replace('#', ''), this.$parent).then((node) => { | ||
| if (node) { | ||
| let nodeTop = node[0].top; | ||
| this._scrollIntoViewByNodeTop(nodeTop, offset, animate); | ||
| finishCallback && finishCallback(); | ||
| } | ||
| }); | ||
| }); | ||
| } catch (e) {} | ||
| }, | ||
| //通过nodeTop滚动到指定view | ||
| _scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) { | ||
| this._scrollToY(nodeTop, offset, animate, true); | ||
| }, | ||
| //滚动到指定位置 | ||
| _scrollToY(y, offset = 0, animate = false, addScrollTop = false) { | ||
| this.privateScrollWithAnimation = animate ? 1 : 0; | ||
| if (this.usePageScroll) { | ||
| uni.pageScrollTo({ | ||
| scrollTop: y - offset, | ||
| duration: animate ? 100 : 0 | ||
| }); | ||
| } else { | ||
| if(addScrollTop){ | ||
| y += this.oldScrollTop; | ||
| } | ||
| this.scrollTop = y - offset; | ||
| this.oldScrollTop = this.scrollTop; | ||
| } | ||
| }, | ||
| //scroll-view滚动中 | ||
| _scroll(e) { | ||
| this.$emit('scroll', e); | ||
| const scrollTop = e.detail.scrollTop; | ||
| // #ifndef APP-NVUE | ||
| this.finalUseVirtualList && this._updateVirtualScroll(scrollTop, this.oldScrollTop - scrollTop); | ||
| // #endif | ||
| this.oldScrollTop = scrollTop; | ||
| const scrollDiff = e.detail.scrollHeight - this.oldScrollTop; | ||
| !this.isIos && this._checkScrolledToBottom(scrollDiff); | ||
| }, | ||
| //检测scrollView是否要铺满屏幕 | ||
| _doCheckScrollViewShouldFullHeight(totalData) { | ||
| if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) { | ||
| // #ifndef APP-NVUE | ||
| this.$nextTick(() => { | ||
| this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => { | ||
| this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode) | ||
| }); | ||
| }) | ||
| // #endif | ||
| // #ifdef APP-NVUE | ||
| this._preCheckShowNoMoreInside(totalData) | ||
| // #endif | ||
| } else { | ||
| this._preCheckShowNoMoreInside(totalData) | ||
| } | ||
| }, | ||
| //检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时,默认z-paging需要铺满全屏,避免数据过少时内部的empty-view无法正确展示) | ||
| async _checkScrollViewShouldFullHeight(callback) { | ||
| try { | ||
| const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view'); | ||
| const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content'); | ||
| if (!scrollViewNode || !pagingContainerNode) return; | ||
| const scrollViewHeight = pagingContainerNode[0].height; | ||
| const scrollViewTop = scrollViewNode[0].top; | ||
| if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) { | ||
| this._setAutoHeight(true, scrollViewNode); | ||
| callback(scrollViewNode, pagingContainerNode); | ||
| } else { | ||
| this._setAutoHeight(false); | ||
| callback(null, null); | ||
| } | ||
| } catch (e) { | ||
| callback(null, null); | ||
| } | ||
| }, | ||
| //scrollTop改变时触发 | ||
| _scrollTopChange(newVal, isPageScrollTop){ | ||
| this.$emit('scrollTopChange', newVal); | ||
| this.$emit('update:scrollTop', newVal); | ||
| this._checkShouldShowBackToTop(newVal); | ||
| const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : (newVal > 105 ? 106 : (newVal > 5 ? 6 : 0)); | ||
| if (isPageScrollTop && this.wxsPageScrollTop !== scrollTop) { | ||
| this.wxsPageScrollTop = scrollTop; | ||
| } else if (!isPageScrollTop && this.wxsScrollTop !== scrollTop) { | ||
| this.wxsScrollTop = scrollTop; | ||
| if (scrollTop > 6) { | ||
| this.scrollEnable = true; | ||
| } | ||
| } | ||
| }, | ||
| //更新使用页面滚动时slot="top"或"bottom"插入view的高度 | ||
| _updatePageScrollTopOrBottomHeight(type) { | ||
| // #ifndef APP-NVUE | ||
| if (!this.usePageScroll) return; | ||
| // #endif | ||
| this._doCheckScrollViewShouldFullHeight(this.realTotalData); | ||
| const node = `.zp-page-${type}`; | ||
| const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`; | ||
| let safeAreaInsetBottomAdd = this.safeAreaInsetBottom; | ||
| this.$nextTick(() => { | ||
| let delayTime = 0; | ||
| // #ifdef MP-BAIDU || APP-NVUE | ||
| delayTime = 50; | ||
| // #endif | ||
| u.delay(() => { | ||
| this._getNodeClientRect(node).then((res) => { | ||
| if (res) { | ||
| let pageScrollNodeHeight = res[0].height; | ||
| if (type === 'bottom') { | ||
| if (safeAreaInsetBottomAdd) { | ||
| pageScrollNodeHeight += this.safeAreaBottom; | ||
| } | ||
| } else { | ||
| this.cacheTopHeight = pageScrollNodeHeight; | ||
| } | ||
| this.$set(this.scrollViewStyle, marginText, `${pageScrollNodeHeight}px`); | ||
| } else if (safeAreaInsetBottomAdd) { | ||
| this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`); | ||
| } | ||
| }); | ||
| }, delayTime) | ||
| }) | ||
| }, | ||
| } | ||
| } |
| // [z-paging]虚拟列表模块 | ||
| import u from '.././z-paging-utils' | ||
| import c from '.././z-paging-constant' | ||
| import Enum from '.././z-paging-enum' | ||
| export default { | ||
| props: { | ||
| //是否使用虚拟列表,默认为否 | ||
| useVirtualList: { | ||
| type: Boolean, | ||
| default: u.gc('useVirtualList', false) | ||
| }, | ||
| //在使用虚拟列表时,是否使用兼容模式,默认为否 | ||
| useCompatibilityMode: { | ||
| type: Boolean, | ||
| default: u.gc('useCompatibilityMode', false) | ||
| }, | ||
| //使用兼容模式时传递的附加数据 | ||
| extraData: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('extraData', {}); | ||
| } | ||
| }, | ||
| //是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true | ||
| useInnerList: { | ||
| type: Boolean, | ||
| default: u.gc('useInnerList', false) | ||
| }, | ||
| //强制关闭inner-list,默认为false,如果为true将强制关闭innerList,适用于开启了虚拟列表后需要强制关闭inner-list的情况 | ||
| forceCloseInnerList: { | ||
| type: Boolean, | ||
| default: u.gc('forceCloseInnerList', false) | ||
| }, | ||
| //内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项 | ||
| cellKeyName: { | ||
| type: String, | ||
| default: u.gc('cellKeyName', '') | ||
| }, | ||
| //innerList样式 | ||
| innerListStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('innerListStyle', {}); | ||
| } | ||
| }, | ||
| //innerCell样式 | ||
| innerCellStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('innerCellStyle', {}); | ||
| } | ||
| }, | ||
| //预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题 | ||
| preloadPage: { | ||
| type: [Number, String], | ||
| default: u.gc('preloadPage', 12), | ||
| validator: (value) => { | ||
| if (value <= 0) u.consoleErr('preload-page必须大于0!'); | ||
| return value > 0; | ||
| } | ||
| }, | ||
| //虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。 | ||
| cellHeightMode: { | ||
| type: String, | ||
| default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed) | ||
| }, | ||
| //虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2 | ||
| virtualListCol: { | ||
| type: [Number, String], | ||
| default: u.gc('virtualListCol', 1) | ||
| }, | ||
| //虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题 | ||
| virtualScrollFps: { | ||
| type: [Number, String], | ||
| default: u.gc('virtualScrollFps', 80) | ||
| }, | ||
| }, | ||
| data() { | ||
| return { | ||
| virtualListKey: u.getInstanceId(), | ||
| virtualPageHeight: 0, | ||
| virtualCellHeight: 0, | ||
| virtualScrollTimeStamp: 0, | ||
| virtualList: [], | ||
| virtualPlaceholderTopHeight: 0, | ||
| virtualPlaceholderBottomHeight: 0, | ||
| virtualTopRangeIndex: 0, | ||
| virtualBottomRangeIndex: 0, | ||
| lastVirtualTopRangeIndex: 0, | ||
| lastVirtualBottomRangeIndex: 0, | ||
| virtualItemInsertedCount: 0, | ||
| virtualHeightCacheList: [], | ||
| getCellHeightRetryCount: { | ||
| fixed: 0, | ||
| dynamic: 0 | ||
| }, | ||
| pagingOrgTop: -1, | ||
| updateVirtualListFromDataChange: false | ||
| } | ||
| }, | ||
| watch: { | ||
| realTotalData(newVal) { | ||
| // #ifndef APP-NVUE | ||
| if (this.finalUseVirtualList) { | ||
| this.updateVirtualListFromDataChange = true; | ||
| this.$nextTick(() => { | ||
| this.getCellHeightRetryCount.fixed = 0; | ||
| !newVal.length && this._resetDynamicListState(!this.isUserPullDown); | ||
| newVal.length && this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight(); | ||
| this._updateVirtualScroll(this.oldScrollTop); | ||
| }) | ||
| } | ||
| // #endif | ||
| }, | ||
| virtualList(newVal){ | ||
| this.$emit('update:virtualList', newVal); | ||
| this.$emit('virtualListChange', newVal); | ||
| } | ||
| }, | ||
| computed: { | ||
| virtualCellIndexKey() { | ||
| return c.listCellIndexKey; | ||
| }, | ||
| finalUseVirtualList() { | ||
| if (this.useVirtualList && this.usePageScroll){ | ||
| u.consoleErr('使用页面滚动时,开启虚拟列表无效!'); | ||
| } | ||
| return this.useVirtualList && !this.usePageScroll; | ||
| }, | ||
| finalUseInnerList() { | ||
| return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList); | ||
| }, | ||
| finalCellKeyName() { | ||
| // #ifdef APP-NVUE | ||
| if (this.finalUseVirtualList && !this.cellKeyName.length){ | ||
| u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name,否则将可能导致列表渲染错误!'); | ||
| } | ||
| // #endif | ||
| return this.cellKeyName; | ||
| }, | ||
| finalVirtualPageHeight(){ | ||
| return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight; | ||
| }, | ||
| virtualRangePageHeight(){ | ||
| return this.finalVirtualPageHeight * this.preloadPage; | ||
| }, | ||
| virtualScrollDisTimeStamp() { | ||
| return 1000 / this.virtualScrollFps; | ||
| }, | ||
| }, | ||
| methods: { | ||
| //在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法;item:需要插入的item,index:插入的cell位置,若index为2,则插入的item在原list的index=1之后,index从0开始 | ||
| doInsertVirtualListItem(item, index) { | ||
| if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; | ||
| this.virtualItemInsertedCount ++; | ||
| if (!item || Object.prototype.toString.call(item) !== '[object Object]') { | ||
| item = { item }; | ||
| } | ||
| const cellIndexKey = this.virtualCellIndexKey; | ||
| item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`; | ||
| item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; | ||
| this.totalData.splice(index, 0, item); | ||
| this.$nextTick(async () => { | ||
| let retryCount = 0; | ||
| while (retryCount <= 10) { | ||
| await u.wait(c.delayTime); | ||
| const cellNode = await this._getNodeClientRect(`#zp-id-${item[cellIndexKey]}`, this.finalUseInnerList); | ||
| if (!cellNode) { | ||
| retryCount ++; | ||
| continue; | ||
| } | ||
| const currentHeight = cellNode ? cellNode[0].height : 0; | ||
| const lastHeightCache = this.virtualHeightCacheList[index - 1]; | ||
| const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; | ||
| this.virtualHeightCacheList.splice(index, 0, { | ||
| height: currentHeight, | ||
| lastTotalHeight, | ||
| totalHeight: lastTotalHeight + currentHeight | ||
| }); | ||
| for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { | ||
| const thisNode = this.virtualHeightCacheList[i]; | ||
| thisNode.lastTotalHeight += currentHeight; | ||
| thisNode.totalHeight += currentHeight; | ||
| } | ||
| this._updateVirtualScroll(this.oldScrollTop); | ||
| break; | ||
| } | ||
| }) | ||
| }, | ||
| //在使用动态高度虚拟列表时,手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变时调用);index:需要更新的cell在列表中的位置,从0开始 | ||
| didUpdateVirtualListCell(index) { | ||
| if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; | ||
| const currentNode = this.virtualHeightCacheList[index]; | ||
| this.$nextTick(() => { | ||
| this._getNodeClientRect(`#zp-id-${index}`, this.finalUseInnerList).then(cellNode => { | ||
| const cellNodeHeight = cellNode ? cellNode[0].height : 0; | ||
| const heightDis = cellNodeHeight - currentNode.height; | ||
| currentNode.height = cellNodeHeight; | ||
| currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight; | ||
| for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { | ||
| const thisNode = this.virtualHeightCacheList[i]; | ||
| thisNode.totalHeight += heightDis; | ||
| thisNode.lastTotalHeight += heightDis; | ||
| } | ||
| }); | ||
| }) | ||
| }, | ||
| //在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组;index:删除的cell在列表中的位置,从0开始 | ||
| didDeleteVirtualListCell(index) { | ||
| if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return; | ||
| const currentNode = this.virtualHeightCacheList[index]; | ||
| for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) { | ||
| const thisNode = this.virtualHeightCacheList[i]; | ||
| thisNode.totalHeight -= currentNode.height; | ||
| thisNode.lastTotalHeight -= currentNode.height; | ||
| } | ||
| this.virtualHeightCacheList.splice(index, 1); | ||
| }, | ||
| //初始化虚拟列表 | ||
| _virtualListInit() { | ||
| this.$nextTick(() => { | ||
| u.delay(() => { | ||
| this._getNodeClientRect('.zp-scroll-view').then(node => { | ||
| if (node) { | ||
| this.pagingOrgTop = node[0].top; | ||
| this.virtualPageHeight = node[0].height; | ||
| } | ||
| }); | ||
| }); | ||
| }) | ||
| }, | ||
| //cellHeightMode为fixed时获取第一个cell高度 | ||
| _updateFixedCellHeight() { | ||
| this.$nextTick(() => { | ||
| u.delay(() => { | ||
| this._getNodeClientRect(`#zp-id-${0}`,this.finalUseInnerList).then(cellNode => { | ||
| if (!cellNode) { | ||
| if (this.getCellHeightRetryCount.fixed > 10) return; | ||
| this.getCellHeightRetryCount.fixed ++; | ||
| this._updateFixedCellHeight(); | ||
| } else { | ||
| this.virtualCellHeight = cellNode[0].height; | ||
| this._updateVirtualScroll(this.oldScrollTop); | ||
| } | ||
| }); | ||
| }, c.delayTime, 'updateFixedCellHeightDelay'); | ||
| }) | ||
| }, | ||
| //cellHeightMode为dynamic时获取每个cell高度 | ||
| _updateDynamicCellHeight(list, dataFrom = 'bottom') { | ||
| const dataFromTop = dataFrom === 'top'; | ||
| const heightCacheList = this.virtualHeightCacheList; | ||
| const currentCacheList = dataFromTop ? [] : heightCacheList; | ||
| let listTotalHeight = 0; | ||
| this.$nextTick(() => { | ||
| u.delay(async () => { | ||
| for (let i = 0; i < list.length; i++) { | ||
| const cellNode = await this._getNodeClientRect(`#zp-id-${list[i][this.virtualCellIndexKey]}`, this.finalUseInnerList); | ||
| const currentHeight = cellNode ? cellNode[0].height : 0; | ||
| if (!cellNode) { | ||
| if (this.getCellHeightRetryCount.dynamic <= 10) { | ||
| heightCacheList.splice(heightCacheList.length - i, i); | ||
| this.getCellHeightRetryCount.dynamic ++; | ||
| this._updateDynamicCellHeight(list, dataFrom); | ||
| } | ||
| return; | ||
| } | ||
| const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null; | ||
| const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0; | ||
| currentCacheList.push({ | ||
| height: currentHeight, | ||
| lastTotalHeight, | ||
| totalHeight: lastTotalHeight + currentHeight | ||
| }); | ||
| if (dataFromTop) { | ||
| listTotalHeight += currentHeight; | ||
| } | ||
| } | ||
| if (dataFromTop && list.length) { | ||
| for (let i = 0; i < heightCacheList.length; i++) { | ||
| const heightCacheItem = heightCacheList[i]; | ||
| heightCacheItem.lastTotalHeight += listTotalHeight; | ||
| heightCacheItem.totalHeight += listTotalHeight; | ||
| } | ||
| this.virtualHeightCacheList = currentCacheList.concat(heightCacheList); | ||
| } | ||
| this._updateVirtualScroll(this.oldScrollTop); | ||
| }, c.delayTime, 'updateDynamicCellHeightDelay') | ||
| }) | ||
| }, | ||
| //设置cellItem的index | ||
| _setCellIndex(list, dataFrom = 'bottom') { | ||
| let currentItemIndex = 0; | ||
| const cellIndexKey = this.virtualCellIndexKey; | ||
| if (this.totalData.length) { | ||
| if (dataFrom === 'bottom') { | ||
| currentItemIndex = this.realTotalData.length; | ||
| const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null; | ||
| if (lastItem && lastItem[cellIndexKey] !== undefined) { | ||
| currentItemIndex = lastItem[cellIndexKey] + 1; | ||
| } | ||
| } else if (dataFrom === 'top') { | ||
| const firstItem = this.realTotalData.length ? this.realTotalData[0] : null; | ||
| if (firstItem && firstItem[cellIndexKey] !== undefined) { | ||
| currentItemIndex = firstItem[cellIndexKey] - list.length; | ||
| } | ||
| } | ||
| } else { | ||
| this._resetDynamicListState(); | ||
| } | ||
| for (let i = 0; i < list.length; i++) { | ||
| let item = list[i]; | ||
| if (!item || Object.prototype.toString.call(item) !== '[object Object]') { | ||
| item = { item }; | ||
| } | ||
| item[cellIndexKey] = currentItemIndex + i; | ||
| item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`; | ||
| list[i] = item; | ||
| } | ||
| this.getCellHeightRetryCount.dynamic = 0; | ||
| this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom); | ||
| }, | ||
| //更新scroll滚动 | ||
| _updateVirtualScroll(scrollTop, scrollDiff = 0) { | ||
| const currentTimeStamp = u.getTime(); | ||
| scrollTop === 0 && this._resetTopRange(); | ||
| if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) { | ||
| return; | ||
| } | ||
| this.virtualScrollTimeStamp = currentTimeStamp; | ||
| let scrollIndex = 0; | ||
| const cellHeightMode = this.cellHeightMode; | ||
| if (cellHeightMode === Enum.CellHeightMode.Fixed) { | ||
| scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0; | ||
| this._updateFixedTopRangeIndex(scrollIndex); | ||
| this._updateFixedBottomRangeIndex(scrollIndex); | ||
| } else if(cellHeightMode === Enum.CellHeightMode.Dynamic) { | ||
| const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom'; | ||
| const rangePageHeight = this.virtualRangePageHeight; | ||
| const topRangePageOffset = scrollTop - rangePageHeight; | ||
| const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight; | ||
| let virtualBottomRangeIndex = 0; | ||
| let virtualPlaceholderBottomHeight = 0; | ||
| let reachedLimitBottom = false; | ||
| const heightCacheList = this.virtualHeightCacheList; | ||
| const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null; | ||
| let startTopRangeIndex = this.virtualTopRangeIndex; | ||
| if (scrollDirection === 'bottom') { | ||
| for (let i = startTopRangeIndex; i < heightCacheList.length; i++){ | ||
| const heightCacheItem = heightCacheList[i]; | ||
| if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) { | ||
| this.virtualTopRangeIndex = i; | ||
| this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; | ||
| break; | ||
| } | ||
| } | ||
| } else { | ||
| let topRangeMatched = false; | ||
| for (let i = startTopRangeIndex; i >= 0; i--){ | ||
| const heightCacheItem = heightCacheList[i]; | ||
| if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) { | ||
| this.virtualTopRangeIndex = i; | ||
| this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight; | ||
| topRangeMatched = true; | ||
| break; | ||
| } | ||
| } | ||
| !topRangeMatched && this._resetTopRange(); | ||
| } | ||
| for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){ | ||
| const heightCacheItem = heightCacheList[i]; | ||
| if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) { | ||
| virtualBottomRangeIndex = i; | ||
| virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight; | ||
| reachedLimitBottom = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) { | ||
| this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize; | ||
| this.virtualPlaceholderBottomHeight = 0; | ||
| } else { | ||
| this.virtualBottomRangeIndex = virtualBottomRangeIndex; | ||
| this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight; | ||
| } | ||
| this._updateVirtualList(); | ||
| } | ||
| }, | ||
| //更新fixedCell模式下topRangeIndex&placeholderTopHeight | ||
| _updateFixedTopRangeIndex(scrollIndex) { | ||
| let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage; | ||
| virtualTopRangeIndex *= this.virtualListCol; | ||
| virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex); | ||
| this.virtualTopRangeIndex = virtualTopRangeIndex; | ||
| this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight; | ||
| }, | ||
| //更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight | ||
| _updateFixedBottomRangeIndex(scrollIndex) { | ||
| let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1); | ||
| virtualBottomRangeIndex *= this.virtualListCol; | ||
| virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex); | ||
| this.virtualBottomRangeIndex = virtualBottomRangeIndex; | ||
| this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol; | ||
| this._updateVirtualList(); | ||
| }, | ||
| //更新virtualList | ||
| _updateVirtualList() { | ||
| const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex); | ||
| if (shouldUpdateList) { | ||
| this.updateVirtualListFromDataChange = false; | ||
| this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex; | ||
| this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex; | ||
| this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1); | ||
| } | ||
| }, | ||
| //重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态 | ||
| _resetDynamicListState(resetVirtualList = false) { | ||
| this.virtualHeightCacheList = []; | ||
| if (resetVirtualList) { | ||
| this.virtualList = []; | ||
| } | ||
| this.virtualTopRangeIndex = 0; | ||
| this.virtualPlaceholderTopHeight = 0; | ||
| }, | ||
| //重置topRangeIndex和placeholderTopHeight | ||
| _resetTopRange() { | ||
| this.virtualTopRangeIndex = 0; | ||
| this.virtualPlaceholderTopHeight = 0; | ||
| this._updateVirtualList(); | ||
| }, | ||
| //检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题) | ||
| _checkVirtualListScroll() { | ||
| if (this.finalUseVirtualList) { | ||
| this.$nextTick(() => { | ||
| this._getNodeClientRect('.zp-paging-touch-view').then(node => { | ||
| const currentTop = node ? node[0].top : 0; | ||
| if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) { | ||
| this._updateVirtualScroll(0); | ||
| } | ||
| }); | ||
| }) | ||
| } | ||
| }, | ||
| //处理使用内置列表时点击了cell事件 | ||
| _innerCellClick(item, index) { | ||
| this.$emit('innerCellClick', item, index); | ||
| } | ||
| } | ||
| } |
| // [z-paging]处理main.js中的配置信息工具 | ||
| let config = null; | ||
| let getedStorage = false; | ||
| const storageKey = 'Z-PAGING-CONFIG-STORAGE-KEY' | ||
| function setConfig(value) { | ||
| uni.setStorageSync(storageKey, value); | ||
| } | ||
| function getConfig() { | ||
| if (getedStorage) return config; | ||
| config = uni.getStorageSync(storageKey); | ||
| getedStorage = true; | ||
| return config; | ||
| } | ||
| export default { | ||
| setConfig, | ||
| getConfig | ||
| }; |
| // [z-paging]常量 | ||
| export default { | ||
| version: '2.6.2', | ||
| delayTime: 100, | ||
| errorUpdateKey: 'z-paging-error-emit', | ||
| completeUpdateKey: 'z-paging-complete-emit', | ||
| cachePrefixKey: 'z-paging-cache', | ||
| listCellIndexKey: 'zp_index', | ||
| listCellIndexUniqueKey: 'zp_unique_index' | ||
| } |
| // [z-paging]枚举 | ||
| export default { | ||
| //当前加载类型 0.下拉刷新 1.上拉加载更多 | ||
| LoadingType: { | ||
| Refresher: 0, | ||
| LoadingMore: 1 | ||
| }, | ||
| //下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束 | ||
| Refresher: { | ||
| Default: 0, | ||
| ReleaseToRefresh: 1, | ||
| Loading: 2, | ||
| Complete: 3 | ||
| }, | ||
| //底部加载更多状态 0.默认状态 1.加载中 2.没有更多数据 3.加载失败 | ||
| More: { | ||
| Default: 0, | ||
| Loading: 1, | ||
| NoMore: 2, | ||
| Fail: 3 | ||
| }, | ||
| //@query触发来源 0.用户主动下拉刷新 1.通过reload触发 2.通过refresh触发 3.通过滚动到底部加载更多或点击底部加载更多触发 | ||
| QueryFrom: { | ||
| UserPullDown: 0, | ||
| Reload: 1, | ||
| Refresh: 2, | ||
| LoadingMore: 3 | ||
| }, | ||
| //虚拟列表cell高度模式 | ||
| CellHeightMode: { | ||
| //固定高度 | ||
| Fixed: 'fixed', | ||
| //动态高度 | ||
| Dynamic: 'dynamic' | ||
| }, | ||
| //列表缓存模式 | ||
| CacheMode: { | ||
| //默认模式,只会缓存一次 | ||
| Default: 'default', | ||
| //总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存 | ||
| Always: 'always' | ||
| } | ||
| } |
| // [z-paging]拦截器 | ||
| //拦截&处理@query事件 | ||
| function handleQuery(callback) { | ||
| try { | ||
| setTimeout(function() { | ||
| _getApp().globalData.zp_handleQueryCallback = callback; | ||
| }, 1); | ||
| } catch (e) {} | ||
| } | ||
| //拦截&处理@query事件(私有,请勿调用) | ||
| function _handleQuery(pageNo, pageSize, from, lastItem){ | ||
| const callback = _getApp().globalData.zp_handleQueryCallback; | ||
| return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from]; | ||
| } | ||
| //拦截&处理系统language转i18n local | ||
| function handleLanguage2Local(callback) { | ||
| try { | ||
| setTimeout(function() { | ||
| _getApp().globalData.zp_handleLanguage2LocalCallback = callback; | ||
| }, 1); | ||
| } catch (e) {} | ||
| } | ||
| //拦截&处理系统language转i18n local(私有,请勿调用) | ||
| function _handleLanguage2Local(language, local){ | ||
| const callback = _getApp().globalData.zp_handleLanguage2LocalCallback; | ||
| return callback ? callback(language, local) : local; | ||
| } | ||
| //获取当前app对象 | ||
| function _getApp(){ | ||
| // #ifndef APP-NVUE | ||
| return getApp(); | ||
| // #endif | ||
| // #ifdef APP-NVUE | ||
| return getApp({ allowDefault: true }); | ||
| // #endif | ||
| } | ||
| export default { | ||
| handleQuery, | ||
| _handleQuery, | ||
| handleLanguage2Local, | ||
| _handleLanguage2Local | ||
| }; |
| // [z-paging]核心js | ||
| import zStatic from './z-paging-static' | ||
| import c from './z-paging-constant' | ||
| import u from './z-paging-utils' | ||
| import zPagingRefresh from '../components/z-paging-refresh' | ||
| import zPagingLoadMore from '../components/z-paging-load-more' | ||
| import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view' | ||
| // modules | ||
| import commonLayoutModule from './modules/common-layout' | ||
| import dataHandleModule from './modules/data-handle' | ||
| import i18nModule from './modules/i18n' | ||
| import nvueModule from './modules/nvue' | ||
| import emptyModule from './modules/empty' | ||
| import refresherModule from './modules/refresher' | ||
| import loadMoreModule from './modules/load-more' | ||
| import loadingModule from './modules/loading' | ||
| import scrollerModule from './modules/scroller' | ||
| import backToTopModule from './modules/back-to-top' | ||
| import virtualListModule from './modules/virtual-list' | ||
| import Enum from './z-paging-enum' | ||
| const systemInfo = uni.getSystemInfoSync(); | ||
| export default { | ||
| name: "z-paging", | ||
| components: { | ||
| zPagingRefresh, | ||
| zPagingLoadMore, | ||
| zPagingEmptyView | ||
| }, | ||
| mixins: [ | ||
| commonLayoutModule, | ||
| dataHandleModule, | ||
| i18nModule, | ||
| nvueModule, | ||
| emptyModule, | ||
| refresherModule, | ||
| loadMoreModule, | ||
| loadingModule, | ||
| scrollerModule, | ||
| backToTopModule, | ||
| virtualListModule | ||
| ], | ||
| data() { | ||
| return { | ||
| //--------------静态资源--------------- | ||
| base64Arrow: zStatic.base64Arrow, | ||
| base64Flower: zStatic.base64Flower, | ||
| base64BackToTop: zStatic.base64BackToTop, | ||
| //-------------全局数据相关-------------- | ||
| //当前加载类型 | ||
| loadingType: Enum.LoadingType.Refresher, | ||
| requestTimeStamp: 0, | ||
| chatRecordLoadingMoreText: '', | ||
| wxsPropType: '', | ||
| renderPropScrollTop: -1, | ||
| checkScrolledToBottomTimeOut: null, | ||
| cacheTopHeight: -1, | ||
| statusBarHeight: systemInfo.statusBarHeight, | ||
| //--------------状态&判断--------------- | ||
| insideOfPaging: -1, | ||
| isLoadFailed: false, | ||
| isIos: systemInfo.platform === 'ios', | ||
| disabledBounce: false, | ||
| fromCompleteEmit: false, | ||
| disabledCompleteEmit: false, | ||
| pageLaunched: false, | ||
| //---------------wxs相关--------------- | ||
| wxsIsScrollTopInTopRange: true, | ||
| wxsScrollTop: 0, | ||
| wxsPageScrollTop: 0, | ||
| wxsOnPullingDown: false, | ||
| }; | ||
| }, | ||
| props: { | ||
| //调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay | ||
| delay: { | ||
| type: [Number, String], | ||
| default: u.gc('delay', 0), | ||
| }, | ||
| //触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400 | ||
| minDelay: { | ||
| type: [Number, String], | ||
| default: u.gc('minDelay', 0), | ||
| }, | ||
| //设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 | ||
| pagingStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('pagingStyle', {}); | ||
| }, | ||
| }, | ||
| //z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100% | ||
| height: { | ||
| type: String, | ||
| default: u.gc('height', '') | ||
| }, | ||
| //z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100% | ||
| width: { | ||
| type: String, | ||
| default: u.gc('width', '') | ||
| }, | ||
| //z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff" | ||
| bgColor: { | ||
| type: String, | ||
| default: u.gc('bgColor', '') | ||
| }, | ||
| //设置z-paging的容器(插槽的父view)的style | ||
| pagingContentStyle: { | ||
| type: Object, | ||
| default: function() { | ||
| return u.gc('pagingContentStyle', {}); | ||
| }, | ||
| }, | ||
| //z-paging是否自动高度,若自动高度则会自动铺满屏幕 | ||
| autoHeight: { | ||
| type: Boolean, | ||
| default: u.gc('autoHeight', false) | ||
| }, | ||
| //z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数 | ||
| autoHeightAddition: { | ||
| type: [Number, String], | ||
| default: u.gc('autoHeightAddition', '0px') | ||
| }, | ||
| //loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black | ||
| defaultThemeStyle: { | ||
| type: String, | ||
| default: u.gc('defaultThemeStyle', 'black') | ||
| }, | ||
| //z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) | ||
| fixed: { | ||
| type: Boolean, | ||
| default: u.gc('fixed', true) | ||
| }, | ||
| //是否开启底部安全区域适配 | ||
| safeAreaInsetBottom: { | ||
| type: Boolean, | ||
| default: u.gc('safeAreaInsetBottom', false) | ||
| }, | ||
| //开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域 | ||
| useSafeAreaPlaceholder: { | ||
| type: Boolean, | ||
| default: u.gc('useSafeAreaPlaceholder', false) | ||
| }, | ||
| //slot="top"的view的z-index,默认为99,仅使用页面滚动时有效 | ||
| topZIndex: { | ||
| type: Number, | ||
| default: u.gc('topZIndex', 99) | ||
| }, | ||
| //z-paging内容容器父view的z-index,默认为1 | ||
| superContentZIndex: { | ||
| type: Number, | ||
| default: u.gc('superContentZIndex', 1) | ||
| }, | ||
| //z-paging内容容器部分的z-index,默认为10 | ||
| contentZIndex: { | ||
| type: Number, | ||
| default: u.gc('contentZIndex', 10) | ||
| }, | ||
| //使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是 | ||
| autoFullHeight: { | ||
| type: Boolean, | ||
| default: u.gc('autoFullHeight', true) | ||
| }, | ||
| //是否监听列表触摸方向改变,默认为否 | ||
| watchTouchDirectionChange: { | ||
| type: Boolean, | ||
| default: u.gc('watchTouchDirectionChange', false) | ||
| }, | ||
| }, | ||
| created(){ | ||
| if (this.createdReload && !this.refresherOnly && this.auto) { | ||
| this._startLoading(); | ||
| this.$nextTick(this._preReload); | ||
| } | ||
| }, | ||
| mounted() { | ||
| this.wxsPropType = u.getTime().toString(); | ||
| this.renderJsIgnore; | ||
| if (!this.createdReload && !this.refresherOnly && this.auto) { | ||
| this.$nextTick(this._preReload); | ||
| } | ||
| this.finalUseCache && this._setListByLocalCache(); | ||
| let delay = 0; | ||
| // #ifdef H5 || MP | ||
| delay = c.delayTime; | ||
| // #endif | ||
| this.$nextTick(() => { | ||
| this.systemInfo = uni.getSystemInfoSync(); | ||
| !this.usePageScroll && this.autoHeight && this._setAutoHeight(); | ||
| this.loaded = true; | ||
| }) | ||
| this.updatePageScrollTopHeight(); | ||
| this.updatePageScrollBottomHeight(); | ||
| this.updateLeftAndRightWidth(); | ||
| if (this.finalRefresherEnabled && this.useCustomRefresher) { | ||
| this.$nextTick(() => { | ||
| this.isTouchmoving = true; | ||
| }) | ||
| } | ||
| this._onEmit(); | ||
| // #ifdef APP-NVUE | ||
| if (!this.isIos && !this.useChatRecordMode) { | ||
| this.nLoadingMoreFixedHeight = true; | ||
| } | ||
| this._nUpdateRefresherWidth(); | ||
| // #endif | ||
| // #ifndef APP-NVUE | ||
| this.finalUseVirtualList && this._virtualListInit(); | ||
| // #endif | ||
| // #ifndef APP-PLUS | ||
| this.$nextTick(() => { | ||
| setTimeout(() => { | ||
| this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight()); | ||
| }, delay) | ||
| }) | ||
| // #endif | ||
| }, | ||
| destroyed() { | ||
| this._offEmit(); | ||
| }, | ||
| // #ifdef VUE3 | ||
| unmounted() { | ||
| this._offEmit(); | ||
| }, | ||
| // #endif | ||
| watch: { | ||
| defaultThemeStyle: { | ||
| handler(newVal) { | ||
| if (newVal.length) { | ||
| this.finalRefresherDefaultStyle = newVal; | ||
| } | ||
| }, | ||
| immediate: true | ||
| }, | ||
| autoHeight(newVal) { | ||
| this.loaded && !this.usePageScroll && this._setAutoHeight(newVal); | ||
| }, | ||
| autoHeightAddition(newVal) { | ||
| this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal); | ||
| }, | ||
| }, | ||
| computed: { | ||
| finalPagingStyle() { | ||
| const pagingStyle = this.pagingStyle; | ||
| if (!this.systemInfo) return pagingStyle; | ||
| const { windowTop, windowBottom } = this; | ||
| if (!this.usePageScroll && this.fixed) { | ||
| if (windowTop && !pagingStyle.top) { | ||
| pagingStyle.top = windowTop + 'px'; | ||
| } | ||
| if (windowBottom && !pagingStyle.bottom) { | ||
| pagingStyle.bottom = windowBottom + 'px'; | ||
| } | ||
| } | ||
| if (this.bgColor.length && !pagingStyle['background']) { | ||
| pagingStyle['background'] = this.bgColor; | ||
| } | ||
| if (this.height.length && !pagingStyle['height']) { | ||
| pagingStyle['height'] = this.height; | ||
| } | ||
| if (this.width.length && !pagingStyle['width']) { | ||
| pagingStyle['width'] = this.width; | ||
| } | ||
| return pagingStyle; | ||
| }, | ||
| finalLowerThreshold() { | ||
| return u.convertToPx(this.lowerThreshold); | ||
| }, | ||
| finalPagingContentStyle() { | ||
| if (this.contentZIndex != 1) { | ||
| this.pagingContentStyle['z-index'] = this.contentZIndex; | ||
| this.pagingContentStyle['position'] = 'relative'; | ||
| } | ||
| return this.pagingContentStyle; | ||
| }, | ||
| renderJsIgnore() { | ||
| if ((this.usePageScroll && this.useChatRecordMode) || !this.refresherEnabled || !this.useCustomRefresher) { | ||
| this.$nextTick(() => { | ||
| this.renderPropScrollTop = 10; | ||
| }) | ||
| } | ||
| return 0; | ||
| }, | ||
| windowHeight() { | ||
| if (!this.systemInfo) return 0; | ||
| return this.systemInfo.windowHeight || 0; | ||
| }, | ||
| windowBottom() { | ||
| if (!this.systemInfo) return 0; | ||
| let windowBottom = this.systemInfo.windowBottom || 0; | ||
| if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder) { | ||
| windowBottom += this.safeAreaBottom; | ||
| } | ||
| return windowBottom; | ||
| }, | ||
| isIosAndH5() { | ||
| // #ifndef H5 | ||
| return false; | ||
| // #endif | ||
| return this.isIos; | ||
| } | ||
| }, | ||
| methods: { | ||
| //当前版本号 | ||
| getVersion() { | ||
| return `z-paging v${c.version}`; | ||
| }, | ||
| //设置nvue List的specialEffects | ||
| setSpecialEffects(args) { | ||
| this.setListSpecialEffects(args); | ||
| }, | ||
| //与setSpecialEffects等效,兼容旧版本 | ||
| setListSpecialEffects(args) { | ||
| this.nFixFreezing = args && Object.keys(args).length; | ||
| if (this.isIos) { | ||
| this.privateRefresherEnabled = 0; | ||
| } | ||
| !this.usePageScroll && this.$refs['zp-n-list'].setSpecialEffects(args); | ||
| }, | ||
| // #ifdef APP-VUE | ||
| //当app长时间进入后台后进入前台,因系统内存管理导致app重新加载时,进行一些适配处理 | ||
| _handlePageLaunch() { | ||
| // 首次触发不进行处理,只有进入后台后打开app重新加载时才处理 | ||
| if (this.pageLaunched) { | ||
| // 解决在vue3+ios中,app ReLaunch时顶部下拉刷新展示位置向下偏移的问题 | ||
| // #ifdef VUE3 | ||
| this.refresherThresholdUpdateTag = 1; | ||
| this.$nextTick(() => { | ||
| this.refresherThresholdUpdateTag = 0; | ||
| }) | ||
| // #endif | ||
| // 解决使用虚拟列表时,app ReLaunch时白屏问题 | ||
| this._checkVirtualListScroll(); | ||
| } | ||
| this.pageLaunched = true; | ||
| }, | ||
| // #endif | ||
| //使手机发生较短时间的振动(15ms) | ||
| _doVibrateShort() { | ||
| // #ifndef H5 | ||
| // #ifdef APP-PLUS | ||
| if (this.isIos) { | ||
| const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator'); | ||
| const feedbackGenerator = new UISelectionFeedbackGenerator(); | ||
| feedbackGenerator.init(); | ||
| setTimeout(() => { | ||
| feedbackGenerator.selectionChanged(); | ||
| }, 0) | ||
| } else { | ||
| plus.device.vibrate(15); | ||
| } | ||
| // #endif | ||
| // #ifndef APP-PLUS | ||
| uni.vibrateShort(); | ||
| // #endif | ||
| // #endif | ||
| }, | ||
| //设置z-paging高度 | ||
| async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) { | ||
| let heightKey = 'min-height'; | ||
| // #ifndef APP-NVUE | ||
| heightKey = 'min-height'; | ||
| // #endif | ||
| try { | ||
| if (shouldFullHeight) { | ||
| let finalScrollViewNode = scrollViewNode || await this._getNodeClientRect('.zp-scroll-view'); | ||
| let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom'); | ||
| if (finalScrollViewNode) { | ||
| const scrollViewTop = finalScrollViewNode[0].top; | ||
| let scrollViewHeight = this.windowHeight - scrollViewTop; | ||
| scrollViewHeight -= finalScrollBottomNode ? finalScrollBottomNode[0].height : 0; | ||
| const additionHeight = u.convertToPx(this.autoHeightAddition); | ||
| const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px !important'; | ||
| this.$set(this.scrollViewStyle, heightKey, finalHeight); | ||
| this.$set(this.scrollViewInStyle, heightKey, finalHeight); | ||
| } | ||
| } else { | ||
| this.$delete(this.scrollViewStyle, heightKey); | ||
| this.$delete(this.scrollViewInStyle, heightKey); | ||
| } | ||
| } catch (e) {} | ||
| }, | ||
| //触发更新是否超出页面状态 | ||
| _updateInsideOfPaging() { | ||
| this.insideMore && this.insideOfPaging === true && setTimeout(this.doLoadMore, 200) | ||
| }, | ||
| //清除timeout | ||
| _cleanTimeout(timeout) { | ||
| if (timeout) { | ||
| clearTimeout(timeout); | ||
| timeout = null; | ||
| } | ||
| return timeout; | ||
| }, | ||
| //添加全局emit监听 | ||
| _onEmit() { | ||
| uni.$on(c.errorUpdateKey, () => { | ||
| this.loading && this.complete(false).catch(() => {}); | ||
| }) | ||
| uni.$on(c.completeUpdateKey, (data) => { | ||
| setTimeout(() => { | ||
| if (this.loading) { | ||
| if (!this.disabledCompleteEmit) { | ||
| const type = data.type || 'normal'; | ||
| const list = data.list || data; | ||
| const rule = data.rule; | ||
| this.fromCompleteEmit = true; | ||
| switch (type){ | ||
| case 'normal': | ||
| this.complete(list); | ||
| break; | ||
| case 'total': | ||
| this.completeByTotal(list, rule); | ||
| break; | ||
| case 'nomore': | ||
| this.completeByNoMore(list, rule); | ||
| break; | ||
| case 'key': | ||
| this.completeByKey(list, rule); | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| } else { | ||
| this.disabledCompleteEmit = false; | ||
| } | ||
| } | ||
| }, 1); | ||
| }) | ||
| }, | ||
| //销毁全局emit和listener监听 | ||
| _offEmit(){ | ||
| uni.$off(c.errorUpdateKey); | ||
| uni.$off(c.completeUpdateKey); | ||
| } | ||
| }, | ||
| }; |
| // [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; | ||
| } | ||
| } | ||
| } |
| // [z-paging]公用的静态图片资源 | ||
| export default { | ||
| base64Arrow: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAD1BMVEVHcExRUVFMTExRUVFRUVE9CdWsAAAABHRSTlMAjjrY9ZnUjwAAAQFJREFUWMPt2MsNgzAMgGEEE1B1gKJmAIRYoCH7z9RCXrabh33iYktcIv35EEg5ZBh07pvxJU6MFSPOSRnjnBUjUsaciRUjMsb4xIoRCWNiYsUInzE5sWKEyxiYWDbyefqHx1zIeiYTk7mQYziTYecxHvEJjwmIT3hMQELCYSISEg4TkZj0mYTEpM8kJCU9JiMp6TEZyUmbAUhO2gxAQNJiIAKSFgMRmNQZhMCkziAEJTUGIyipMRjBSZkhCE7KDEFIUmTeGCHJxWz0zXaE0GTCG8ZFtEaS347r/1fe11YyHYVfubxayfjoHmc0YYwmmmiiiSaaaKLJ7ckyz5ve+dw3Xw2emdwm9xSbAAAAAElFTkSuQmCC', | ||
| base64ArrowWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVHcEz///////////////////+IGTx/AAAABnRSTlMA/dAkXZOhASU/AAABYElEQVRYw+2YwXLCIBCGsdAHWGbyAKZ4zxi9O017rxLf/1UaWFAgA1m8dcpedNSPf/l/Vh0Ya/Wn6hN0JcGvoCqRM4C8VBFiDwBqqNuJKV0rAnCgy3AUqZE57x0iqTL8Br4U3WBf/YWaIlTKfAcELU/h9w72CSVPa3C3OCDvhpHbRp/s2vq4fHhCeiCl2A3m4Qd71DQR257mFBlMcTlbFnFWzNtHxewYEfSiaLS4el8d8nyhmKJd1CF4eOS0keLMAuSxubLBIeIGQW8YHCFFo7EH9+YDcQt9FMZEswTheaNxTHwHT8SZorJjMrEVwo4Zo0U8HSEyZvJMOg4RjnmmRr8nDYeIz3OMkbfE/QhBo+U9RnZJxjGCRh/WKmHEMWLNkfPKsGh/CWJk1JjG0kcuJggTt34VDP8aWAFhp4nybVb5+9qQhjSkIQ1pSEMa8k+Q5U9rV3dF8MpFBK+/7miVq1/HZ2qmo9D+pAAAAABJRU5ErkJggg==', | ||
| base64Flower: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAKlBMVEVHcEzDw8Ovr6+pqamUlJTCwsKenp61tbWxsbGysrLNzc2bm5u5ubmjo6MpovhuAAAACnRSTlMA/P79/sHDhiZS0DxZowAABBBJREFUWMPtl89rE0EUx7ctTXatB3MI1SWnDbUKPUgXqh4ED8Uf7KUVSm3ooVSpSii0Fn/gD4j4o+APiEoVmos9FO2celiqZVgwgaKHPQiCCkv+F99kM7Ozm5kxq1dfD91k9pPve9/3ZjbRNHHok/mKli4eIPNgSuRObuN9SqSEzM20iGnm0yIbqCuV7NSSSIV7uyPM6JMBYdeTOanh/QihJYZsUCSby+VkMj2AvOt0rAeQAwqE3lfKMZVlQCZk1QOCKkkVPadITCfIRNKxfoJI5+0OIFtJx14CMSg1mRSDko7VAfksRQzEbGYqxOJcVTWMCH2I1/IACNW0PWU2M8cmAVHtnH5mM1VRWtwKZjOd5JbF6s1IbaYqaotjNlPHgDAnlAizubTR6ovMYn052g/U5qcmOpi0WL8xTS/3IfSet5m8MEr5ajjF5le6dq/OJpobrdY0t3i9QgefWrxW9/1BLhk0E9m8FeUMhhXal499iD0eQRfDF+ts/tttORRerfp+oV7f4xJj82iUYm1Yzod+ZQEAlS/8mMBwKebVmCVp1f0JLS6zKd17+iwRKTARVg2SHtz3iEbBH+Q+U28zW2Jiza8Tjb1YFoYZMsJyjDqp3M9XBQdSdPLFdxEpvOB37JrHcmR/y9+LgoTlCFGZEa2sc6d4PGlweEa2JSVPoVm+IfGG3ZL037iV9oH+P+Jxc4HGVflNq1M0pivao/EopO4b/ojVCP9GjmiXOeS0DOn1o/iiccT4ORnyvBGF3yUywkQajW4Ti0SGuiy/wVSg/L8w+X/8Q+hvUx8Xd90z4oV5a1i88MbFWHz0WZZ1UrTwBGPX3Rat9AFiXRMRjoMdIdJLEOt2h7jrYOzgOamKZSWSNspOS0X8SAqRYmxRL7sg4eLzYmNehcxh3uoyud/BH2Udux4ywxFTc1xC7Mgf4vMhc5S+kSH3Y7yj+qpwIWSoPTVCOOPVthGx9FbGqrwFw6wSFxJr+17zeKcztt3u+2roAEVgUjDd+AHGuxHy2rZHaa8JMkTHEeyi85ANPO9j9BVuBRD2FY5LDMo/Sz/2hReqGIs/KiFin+CsPsYO/yvM3jL2vE8EbX7/Bf8ejtr2GLN65bioAdgLd8Bis/mD5GmP2qeqyo2ZwQEOtAjRIDH7mBKpUcMoApbZJ5UIxkEwxyMZyMxW/uKFvHCFR3SSmerHyDNQ2dF4JG6zIMpBgLfjSF9x1D6smFcYnGApjmSLICO3ecCDWrQ48geba9DI3STy2i7ax6WIB62fSyIZIiO3GFQqSURp8wCo7GhJBGwuSovJBNjb7kT6FPVnIa9qJ2Ko+l9mefGIdinaMp0yC1URYiwsdfNE45EuA5Cx9EhalfvN5s+UyItm81vaB3p4joniN+SCP7Qc1hblAAAAAElFTkSuQmCC', | ||
| base64FlowerWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEX///9HcEz///////////////84chYNAAAABnRSTlP/AGzCOYZj5g1nAAACfklEQVRYw+2YTVPDIBCGtza9Jw25a0bvcax30o73OOr//yvma2F3YWlpPTijXNpAHrK8LLALVPFium2vNIFSbwGKTGQA2GUiHcD29yDNy3sMIdUBQl7r2H8mOEVqAHgPkYZUS6Qc2zYhQqtjyDZEximCZwWZLIBeIgYShs2NzxKpSUehYpMJhURGb+O+w5BpMCAREKPnCDHbIY20SzhM5yxziAXpOiBXydrekT9i5XDEq4NIIHHgyU5mRGqviII4mREJJA4QJzMiILwlRJzpKxJKvCBm8OsBBbLux0tsPl4RKYm5aPu6jw1U4mGxEUR9g8M1PcqBEp/WJliNgYOXueBzS4jZSIcgY5lCtevgDSgyzE+rAfuOTQMq0yzvoGH18qju27Mayzs4fPyMziCx81NJa5RNfW7vPYK9KOfDiVkBxFHG8hAj9txuoBuSWORsFfkpBf7xKFLSeaOefEojh5jz22DJEqMP8fUyaKdQx+RnG+yXMpe8Aars8ueR1pVH/bW3FyyvPRw90upLDHwpgBDtg4aUBNkxRLXMAi03IhcZtr1m+FeI/O/JNyDmmL1djLOauSlNflBpW18RQ2bPqXI22MXXEk75KRHTnkPkYbESbdKP2ZFk0r5sIwffAjy1lx+vx7NLjB6/E7Jfv5ERKhzpN0w8IDE8IGFDv5dhz10s7GFiXRZcUeLCEG5P5nDq9k4PFDcoMpE3GY4OuxuCXhmuyNB6k0RsLIAvqp9NE5r8ZCSS8gxnUp7ODdYhZTqxuiJ9uyJJtPmpqJ7wVj+XVieS903iViHziqAhchLEJAyb7jWU647EpUofQ0ziUuXXXhDddtlllSwjgSQu7r4BRWhQqfDPMVwAAAAASUVORK5CYII=', | ||
| base64Success: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAElBMVEVRUVFHcExTU1NRUVFRUVFRUVFOSlSUAAAABnRSTlP/AI6+VySB3ZENAAACcElEQVRYw+2YyYKCMAyGI8hdpdxdZu7gcpdZ7jL6/s8yYheSNi0aPdqbwOffpGmaFOYPD3gj4bisN7vddv17N/JVgxn5x12IWgIaWTuO/IE3PseQbwjGPo2cgRmHFLJwdm/X643zwiqOKPPJ1nj3sjEP2iiifZWj5bhopSyGaEO2HX5fbQJzwJ+W7x/jw5ZFjsEU0PMph9xE8i5EqprKALW95eJQURkgzw98uJ/JvwGecR7bIjWWsUgVrrIfFZ2HlLy3sKETD1mmRLRMRhGVssRa0xJkdn3SpJBymBkM8+pSSDXMDNyDaToVHd2fgpNt0sjwiUZO19+jGQ+gQEg9Oq+bufmAVGihomNmjQG7UG3020vrlm7lkFnKFGU3kZ0KGAdmKe821pipQ+qEKcrZeTL2g5FsUks4cStjEZWwXg0b0n4GxmEpkWwIs5VBynjgK7xZaz1/0D7OxkVuLpsY5BQNFyLS84VBjjbg0iL2r2EQHBOxBhikuUOkdxODVF1cxHoWtPPsiyXO455Iv34hssCO8EV4ZIYTjS8SR4qYSHRiTiYQ4ZFbHi0iIhhBTi6dTCgSWRcnw4h4yGTuyTAiOGBIWGoZTgSHJQl+LcOJ4OCnW6yX2bMnJ9pidCOXtkTkTrIGpYuOynAiOF14SamMiOCk5Ke+mq8BcOrrvym8d0zKIQnWT+M1WwOQNO4fFiWb18hhERxJPx2fblbPHHyC41VyiAtKBUFBIih7JMWVoIQTFIr3lKPN80WvoLSWFPC653ioTZA0I0FrQ7qU6asaK0H7JmkSJa2ooOGVtNUsc3j9FYHkIkJy3SG6VHnfXKXGP9t4N9Q4Ye98AAAAAElFTkSuQmCC', | ||
| base64SuccessWhite: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAGFBMVEVHcEz///////////////////////////8dS1W+AAAAB3RSTlMAiVYk6KvDHLfaegAAAo1JREFUWMPtWEtzmzAQNhCTq910ytXpiyvxTNOr60zrayepx9d02gnX4sTm7xcEiJX2gdnkGJ1A4tOnfWqXyeR1vMRYzrcPD9v5h5MBl3/Ldvx4cxIg/FWC8X0xjLjalM54uhhCfCrRuJURX0pi3EmIqZV7O59vrRZmguStHL9b7S7ftfLwOtiZDw7AHMtmquAQ12b5Wwbnordm8g9zLLO49qc/m2n6aKnhwPOGZ08hAiNHhheiHae1lOUPGZpQkPKa3q0mOUjaRzSRaGUjpy/mmWSwySSpllcEteBKAT52KEnSbblA51pJEPxBQoiH1FP4E3s5+FJv07h6/ylD6ui7B+9fq/ehrFB98ghec9EoVtyjK8pqCHLmCBOwMWSCeWFNN4MbPAk55NhsvoFHSSVR0k5TCTTEzlUGcqV/nVp7n9oIVkmtaqbAEqEgfdgHJPwsEAyZ9r4VAZXFjpEwyaw3+H2v42KYxKhs1XvY/gSSGv+IHyUSuHXCeZhLAgVI3EjgSGo1Fb3xO0tGGU9S2/KAIbtjxpJASG73qox6w5LUq0cEOa+iIONIWIilQSQ0pPa2jgaRQAgQP7c0mITRWGxpMAmEQFN2NAQJNCV0mI6GIIEO47hlQ0ORQLd0nL+hoUjg1m6I1TRr8uYEAriBHLcVFQ5UEMiBe3XkTBEG04WXlGKGxPnMS305XQPA1Ocn2JiuAZwE66fxnKwBnDTuXxZTMq85lwW6kt5ndLqZPefiU1yvmktcUSooChJF2aMprhQlnKJQ5FxRKkcVRa+itNYU8Io2oVkY14w0NMWYlqft91Bj9VHq+ca3b43BxjWJmla0sfKohlfTVpPN+93L/yLQ/IjQ/O5Q/VR5HdL4D7mlxmjwVdELAAAAAElFTkSuQmCC', | ||
| base64Empty: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAbUExURdvc3EdwTMLBwcjIyLSzs/Hx8ff39////19dXXz7IJEAAAAFdFJOU/4A6J9QDyyutAAAB5VJREFUeNrtnM1z4jYYxhUHkR4hdu9eU7Z75Ct7jgPbs9ZZmSuTrUWPmXTA186e+LMrf0uWLMtf2WkHXQgzln88et5XeiVMwPQdGrhCrpAr5Aq5Qv4TkJ07OGQFMLp1B4VYCz+kDblDQhJGeH4eEDLBYdLOHwaDWNBPIeHLYJAJ3meQ83IoCMTHDBKOBoKYGOeM8G0gyD0LObnDQB5ZSCtTNCBfsM9AboaBPLCQcDAIM1zht/dQEkMsd1DjI4hpw2YzMtBJeBbydWpCTJs3YDKGX62YgfGoVwi9KwtZJAzcYHHRm7sYCKD390nQSIoO5JGZIEOYxNoZ4+deISYLyeL5hLHbJ2QK98W0kudMgJe9Qh73odhO+KZHyNYGvgQS9gmJKhUigwSj3iBPUhXxePWmxBqHw0Mej9WQ3qILVjLC177yxNxXQ/7uK+Mn1aNVLsGsBTaWrSAPobYl0aUHt2fIs2Rgz7c9QYL0pSTkSzILLFtAJMH1cidN998T9E0/Sg73/pEEwrgkYRh86wlC949gJsR6EobBcz8hHOVgKYi2m6kZtodIkjEQvF3QjbGpmplB4/lRgJhxgRS2N15iijAvPmByDtCxfQhPJ8J4CR82rgCCBILarScw6X0OcMUyYrFVmbxErl0ZacFIoloOLdJAO42qY+NMDss2kKS8xmiZxcCpFKXWvpRGbQqJp5ixyRfJMmR6x0Fk+z29kmgWDYI5ziFbdug/84HxvduhWhLOJ2StPDQrMJPSjNANklh8QhB7dBO0yTGRwn1fkOk8rbQjiB8Ymww+JuiuN0icmSccK4naLMWYa/euL0+m23GyM8kgAc6sYeL4z04Qa4WjGepcKIliO8EUGSk7d9OGWOsoK31OSdy8TQZ59Y/hWbaV1IVs5/Ed6UzGK4nANAJiyGhRsZPUg2yzLe9hLyiJIyCaDU7udC2uy9pnkKvidlBUEltzFAqxRhBrBZm7HfZnjEQI3boqTsJq15PUDEaKZLgiJYc8OZtCtnM/4G93OFYooXpvdy0guwWWNQkEHl/j7Jw1XRmtlS9HYJkSPjk1IUnyyRqUKQn45NSDlP1mcg9i6En1ZU2IADnEtHF1Q+JwIcS/d5YakPuDUamEShGUHHikAz9oQCaE0CsrpYjDBVkEHQYdyK+EkKPhVErxqh1xbJ/oQf4gEeVsOIEc41WJNAwcd9GBfCZJezXsJhAvH+ImEEIOzlwXgpw5wQ0gH3MIOcsiQAahZuSD69/UQyxcQEggiQARQseVFO/ASAMCgM9gjkHZmhLENzi1AOhA7ullkMWUrfHKfpMiDBHtDIx6yCS6jseEnDUe7zcT6DGCtnrIY3olZw1hrPHkfucIAJa1EDu/lsVEyVmGGA67coKijeogFnMxlEaAV5ghRdDm1kDuuatZTJBGgJdOthIzsvZbDWRRuh6ScgR4EQLgagQvRQIxxQ4sxqcR4GE+c4CkjZQQW9YF89Y4OFAjOCki5KmiDxsBL3PlSJWlAFVogaoIePlYi2ClCJAHRa/cmre5eqTii4uvisqQJxqnip6pNd68DhEvyEs5xIyHBNdh4thCKhU++10kD7Gy1Up1A/o56FKuRJQWSFCuf8dpbisxhqHSKlSSgvG7VTaFKO5TzYD5VMPUxEB2YJNiqq3xYJ0KrroH8mq7xpoXqEZgfgNRUQsDtTVvUOk3sLUKbqrBr7YGvkCkQNC/9SA+vTYtvERrxiKEmcogk4ZqCLUd59MIEiFYHlIoxelCaJWDMmtOPIa80XVLbkb6hzaEwwTcPEmV4AIRlBGNIEmuJBFwLAZoHClJ36J8h+wxihpCqJosAnJrSKwEcQOFAFeWN4RQMYc0Ao4Jhg5gpASzyWcDvjpuDIlTkrGGJEro1rHIjHKR3wJCAj+z5oyi11gJBkXy9QFJIiAu78d+pgSjuWhGN0gUAZAcEncSJf4LRrZ8I94WEmcNCJJqBWYjVbE9bg2JxiyrViBWty6QvO56D8jPVWLA4ZX8dfkxvJJPl8t8aCX+pU/Iz1SCf7lc4OBK0OWfQaKLP0TKjj96VvIp+/BDZjwNKF2ItV2vN7sWStAl87oWkm3dZ+k3lEMoYXe8cT1eq2TOePJDD8KfQdxu6iEPxanUZa4HmZRq3dunGsj3BzFq6yD3wnZNX4n2emI2hXyXQpi6RRZdfSgxHNuxVZBFdyVeBPDmCsiksxKUiDAUEKuzkvRUEs0V08pjVyU2/yqFmF2VZGYop3peitdUiQd1pnrL7qTE01tPzE6eaEKm23dQwh2jNlbiay+/245zl94abw45CzNPyqYQ2++kxHGV1crWzg4A2yvR+BY7wziwnRLN7+O36aA54+ZKGjxZYK3txJpxQyUNn5GwtquII4+ACiWtnvawduu1A3SVtH5uhTvAVSpBG7fDYz6RQ+M6JWjmKm6g+RvTla9UMtspu+s+37VbVCupNqPx43CsNawSb1PbtcmDfQWmUILW7rRXSPHtSq5k5ur0a/hb7DQCUiW3G71ejX/wvV1kSoyNbp8Wvyqn1lCIKvl6gNDkNBYzt0GHdr+Pt9xGl1//ncAVcoVcIVfIFXKFXCFXyP8I8i8SyTW4yTz2lwAAAABJRU5ErkJggg==', | ||
| base64Error: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAJFBMVEVHcEzo6Oibm5ukpKSbm5uampqbm5ubm5u5ubnn5+fm5ub6+vpGpDPdAAAAC3RSTlMA/v4hb+u20dq8aQhnHL4AAATwSURBVHja7ZvNb+JGGMbdjjdVe3NPodzeMhj1ZMUGujkh28B9wKR7iwwBqafWSbPqsbm0uTWtVlrTS9v0Et9yqLQS/1zHNiTZMMB4bO92d+dRUITt8c/PvPPx2h4URUpKSkpKSkpK6m3K6lFN73Q+S/+es3W3fzabTh2Dl0FAXEM+BgrgeXxRsdILTDU9n7J0vz/+EsCYC9KEiSFc06pf5zouqAkjkGWo0OG5Fq6j2IwZ6I4/4DhyTxc20oUjAj5PRTTrwvGAZ9p+ADzlD4RDctCoLhb7JUP87xeLxS3BZUIQ+YNCFi8wRwc4GIqGRK/GkM+5ILV8kFflOmkkkH/LddJ4c05eSSfSyTvrZPEmnCw+UCeuk84QvTSnUF0uCL68fBle/swF+RL1QZ/EpU6gHtOsAM64pnjPwxgTLsjoBM58ODNaAXYBT5QeGdr0KwcEA8He0TkPBLCjUDNHpG4qlg8eTCzXht1FVd1MxTPHj5LTtUiSrKK+7iDf8wBGxk4If3arLv/HF4Tox0A2nlFIGp+CIA+LzSamgbp4TNvZjECtDAjyMcB5HybLO6NxsRA1vmFCNjguNXDGWygbRPXh/B+zn9zPWK5RCkT18QxA57YgAqE+HGS6/tAoD4JO0ts+M2tbyQJpc95a5oI0xXNhCZGQ/x8E0VSCkUZY6Z6CIE/qdO5eL+yPlW6tMMgefPs3o7Bdt8iguJj4DThlZSY/rJ0yB+RraLDK2jAQCHyr4zIhT9mQ7vowygMZjpgQG+CYUV2E1EWqixyzICr8eFFnBB5ba1Y4IIfeKQvysW7ssZrwQLEFmvCBM2U6oRkdI5Wgm1QnO8RUTDlASoiESIiESMh7BDHKhlhu/LbSNUuEqL3lu1p945vO3BArADiaum7vBGDUKQfSIjBykooyrRPQnTIglHFfSagHeqd4iErwa9duEebpckGQjx/VT4v5fC0XhJHAd1mPRvJAVMZiAeQzTpgHYrPKqkGnUEiX+dCoNS4UgniHMTnUS4iESIiE7IS0x+mnVAidglDwVcmQJpy2WQ8VC4UgogfbA1RE4Nuw3UghEBV2rKl7V5ygAJPSY9KGQbP01mVjA5Fa2f1kQN2U3k+M9POWB8gnJUNMZJioWTMzKwOklyxgDrCXVcMMEF90tXM9C2TiCqmfCdIRi/jeewNpyerKok9WkGuzfCdYC+fXRsmBxxpVGG2zY0ZBbieJKvPrDQce3lxppBhIjGFWGkVoxUEoZt0Mukn2XBQH0bTHZpaMIp2sU/6qasU70W6/eHjM09VmYSc6C6Jpvz+orKvVxot8kL3HkMr9IZ9qeZ2o6RrO9mOI9ufdIR9peZ2gNIW31yC/MpyI9ngUDNIsezPks3vIsWDGdYA7cZa9pbqUVeCr/neiaR3U3R4BfXPg75vwb8I/b7HjxChobDZCO+Ny4wuxxaVxPPowcoNnrzPmzGFlX3RJHz2FafbhJ41n8PLx2DCM7KkwQgpqka1DVzKdJNHfJwBe9l/n0eSZFsIPjVSY8xZKZpSXnogwled98wAx3xRcdBNq1f1fhFVdIcL5tvaDolC7XaqaWStEtLOJHkbhlSauMLrma4yHEa03AVUoIUs/M2NQFkchBZiGUPeKonAnqhLOo4hrKf0WTyZ1FcU0Ki0hVrSr+Mucnvya7jYUKSkpKSkpKSmpD0f/AXq+Umj5XnXDAAAAAElFTkSuQmCC', | ||
| base64BackToTop: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAElBMVEVRUVH+/v5HcEyZmZlRUVFRUVGm1ByOAAAABnRSTlPMzADMTZAJBBGsAAAEnElEQVR42t2cS27jMAyGf7/2U+QCQeDsbeQCgZDujaC5/1UmkzaJn+JDFGcw3LdfflKibJkkDnxrL7dbg7sNt6+L4O8OYBM+B0ys+QrGkHZG+OEEQ8g6go8Bx1GIGMdpNOQyIG6XdMgnSPtKhLQDGEZFBgYMkhKFtGBb0EIEjDgFRowoBVaMGAWpMedEfxMiZtwpUsgZCqtlkCNUdpVAWigtCCCDFtLwIWeoreZCWiRYYEKGFEjDg+yRZCUH0iLRAgNyToXUNCRZyMqWhGnUN2IPm3wSlwJ7IUspyCBkIQUZhCykIIeQuRTkEDKXAuM9srrtYbrZN7Y98giZSoFd+t1OxmMITG0dcrSFXFchZ1tIvQZpYWxhBbK3hpQrkMEa0iwh5t4a+QvZvDXyF7J5a+Qv5PPW21/I5623v5DPW29/IaO3Xv5Clrw1y1/Ikrdm+Qs5svw83yNnSJ5BQb4F/F7EIEJSnThGBAXxkFQfLOviQUE8JAUPsosHBfGQfDAtHhREQ1JxIV00KIgmrnRI84S0yAd5BAXxxJUck0f6Qnwr9qmr6xF5xLMjcwn/iudIEAdWnyjkEXlQKZiRVzoqRyLbgeUKKR8Q4alY7cSnoxzSf2ggsqehKr6YVpcXpOd7H93f60cKhOd7Re2LteUF4eLqiVS1mr0ge4io6C2+soaFkJ7MuuuQs1yITEp9hwwKISIpzR2iESKSIoT0rLNwuVHQqoSIpAQJpGce60vIUSdEIuUqgPTsJ5QFZK8UIpBS8iG94GFrDjlrhfCl8CG96Llxmle4kEr6vKWBPIVo9kqDQSRk9/3cWoikcCFPAd33v4dIChPyEvLzBA6RlEYWke4JEUnhKXkLeUEKxRHJFfKCQHGucIW8IdZSRkLeEGMpYyEjiK2UsZARxFTKRMgYYillImQMMZQyFTKB2EmZCplAuFLIHT8TMoWwpQwiIVMIUwqpZP5bp5CCvCTiQKr5f5lCQN+tPCBn2ZvVDFJwIDUP0m1BYAfZYRNSsCB7BqTbhoARePIxtZ9tgwWkoJcwCalmv3MBAemtO4R6dah2HaKQqj8Zvp9sQDjvJ21+SPCBHPJDDk6QITekEV7gqCC19CpKAym9IMfckKv4olMBCeIrWwVEfvkshzQekO9r9P1/ALk+IG1eSPCDiCJfyG+FyU+A6ZCa/piZDinpz7LpkCv5gdkAEshP5emQhv7onw6pGeULyZCSUYiRDAmMkpJkCKs4JhFSq8p8hJBSVbAkhARV6ZUQoisik0FqXTmcDHLVFfbJIEFXoiiCNMpiSxGkVJaNiiBBWQArgTTaUl4JpNQWJUsgQVteXQg+AKkLxQWFGKW+5J2+eVp4S168X3CF1CltCKdTJ8lb84YK2bUBO+wZW0Pqv9nk4tKu49N45NJC5dMM5tLW5tOg59Jq6NM06dL+abFXwr/RkuvTXJwae1abtE/Dt0/ruksTvs84AZ/BCC4jHnyGVfiM3VBQFANEXEah+Ax18RlP4zNox2dkkM/wI58xTn8yDCXGYCDV3W5RGSajtXyGhG1jbpbjzpwGt/0MJft8jqC7iUbQ/QZaxdnKqcIftwAAAABJRU5ErkJggg==', | ||
| } |
| // [z-paging]工具类 | ||
| import zConfig from './z-paging-config' | ||
| import zLocalConfig from '../config/index' | ||
| import c from './z-paging-constant' | ||
| const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY'; | ||
| let config = null; | ||
| const timeoutMap = {}; | ||
| /* | ||
| 当z-paging未使用uni_modules管理时,控制台会有警告:WARNING: Module not found: Error: Can't resolve '@/uni_modules/z-paging'... | ||
| 此时注释下方try中的代码即可 | ||
| */ | ||
| // #ifdef VUE2 | ||
| try { | ||
| const contextKeys = require.context('@/uni_modules/z-paging', false, /\z-paging-config$/).keys(); | ||
| if (contextKeys.length) { | ||
| const suffix = '.js'; | ||
| config = require('@/uni_modules/z-paging/z-paging-config' + suffix); | ||
| } | ||
| } catch (e) {} | ||
| // #endif | ||
| //获取默认配置信息 | ||
| function gc(key, defaultValue) { | ||
| if (!config) { | ||
| if (zLocalConfig && Object.keys(zLocalConfig).length) { | ||
| config = zLocalConfig; | ||
| } else { | ||
| const tempConfig = zConfig.getConfig(); | ||
| if (zConfig && tempConfig) { | ||
| config = tempConfig; | ||
| } | ||
| } | ||
| } | ||
| if (!config) return defaultValue; | ||
| const value = config[_toKebab(key)]; | ||
| return value === undefined ? defaultValue : value; | ||
| } | ||
| //获取最终的touch位置 | ||
| function getTouch(e) { | ||
| let touch = null; | ||
| if (e.touches && e.touches.length) { | ||
| touch = e.touches[0]; | ||
| } else if (e.changedTouches && e.changedTouches.length) { | ||
| touch = e.changedTouches[0]; | ||
| } else if (e.datail && e.datail != {}) { | ||
| touch = e.datail; | ||
| } else { | ||
| return { | ||
| touchX: 0, | ||
| touchY: 0 | ||
| } | ||
| } | ||
| return { | ||
| touchX: touch.clientX, | ||
| touchY: touch.clientY | ||
| }; | ||
| } | ||
| //判断当前手势是否在z-paging内触发 | ||
| function getTouchFromZPaging(target) { | ||
| if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') { | ||
| const classList = target.classList; | ||
| if (classList && classList.contains('z-paging-content')) { | ||
| return { | ||
| isFromZp: true, | ||
| isPageScroll: classList.contains('z-paging-content-page'), | ||
| isReachedTop: classList.contains('z-paging-reached-top') | ||
| }; | ||
| } else { | ||
| return getTouchFromZPaging(target.parentNode); | ||
| } | ||
| } else { | ||
| return { isFromZp: false }; | ||
| } | ||
| } | ||
| //获取z-paging所在的parent | ||
| function getParent(parent) { | ||
| if (!parent) return null; | ||
| if (parent.$refs.paging) return parent; | ||
| return getParent(parent.$parent); | ||
| } | ||
| //打印错误信息 | ||
| function consoleErr(err) { | ||
| console.error(`[z-paging]${err}`); | ||
| } | ||
| //延时操作,如果key存在,调用时根据key停止之前的延时操作 | ||
| function delay(callback, ms = c.delayTime, key) { | ||
| const timeout = setTimeout(callback, ms);; | ||
| if (!!key) { | ||
| timeoutMap[key] && clearTimeout(timeoutMap[key]); | ||
| timeoutMap[key] = timeout; | ||
| } | ||
| return timeout; | ||
| } | ||
| //设置下拉刷新时间 | ||
| function setRefesrherTime(time, key) { | ||
| const datas = getRefesrherTime() || {}; | ||
| datas[key] = time; | ||
| uni.setStorageSync(storageKey, datas); | ||
| } | ||
| //获取下拉刷新时间 | ||
| function getRefesrherTime() { | ||
| return uni.getStorageSync(storageKey); | ||
| } | ||
| //通过下拉刷新标识key获取下拉刷新时间 | ||
| function getRefesrherTimeByKey(key) { | ||
| const datas = getRefesrherTime(); | ||
| return datas && datas[key] ? datas[key] : null; | ||
| } | ||
| //通过下拉刷新标识key获取下拉刷新时间(格式化之后) | ||
| function getRefesrherFormatTimeByKey(key, textMap) { | ||
| const time = getRefesrherTimeByKey(key); | ||
| const timeText = time ? _timeFormat(time, textMap) : textMap.none; | ||
| return `${textMap.title}${timeText}`; | ||
| } | ||
| //将文本的px或者rpx转为px的值 | ||
| function convertToPx(text) { | ||
| const dataType = Object.prototype.toString.call(text); | ||
| if (dataType === '[object Number]') return text; | ||
| let isRpx = false; | ||
| if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) { | ||
| text = text.replace('rpx', '').replace('upx', ''); | ||
| isRpx = true; | ||
| } else if (text.indexOf('px') !== -1) { | ||
| text = text.replace('px', ''); | ||
| } | ||
| if (!isNaN(text)) { | ||
| if (isRpx) return Number(uni.upx2px(text)); | ||
| return Number(text); | ||
| } | ||
| return 0; | ||
| } | ||
| //获取当前时间 | ||
| function getTime() { | ||
| return (new Date()).getTime(); | ||
| } | ||
| //获取z-paging实例id | ||
| function getInstanceId() { | ||
| const s = []; | ||
| const hexDigits = "0123456789abcdef"; | ||
| for (let i = 0; i < 10; i++) { | ||
| s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); | ||
| } | ||
| return s.join('') + getTime(); | ||
| } | ||
| // 等待一段时间 | ||
| function wait(ms) { | ||
| return new Promise(resolve => { | ||
| setTimeout(resolve, ms); | ||
| }); | ||
| } | ||
| //------------------ 私有方法 ------------------------ | ||
| //时间格式化 | ||
| function _timeFormat(time, textMap) { | ||
| const date = new Date(time); | ||
| const currentDate = new Date(); | ||
| const dateDay = new Date(time).setHours(0, 0, 0, 0); | ||
| const currentDateDay = new Date().setHours(0, 0, 0, 0); | ||
| const disTime = dateDay - currentDateDay; | ||
| let dayStr = ''; | ||
| const timeStr = _dateTimeFormat(date); | ||
| if (disTime === 0) { | ||
| dayStr = textMap.today; | ||
| } else if (disTime === -86400000) { | ||
| dayStr = textMap.yesterday; | ||
| } else { | ||
| dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear()); | ||
| } | ||
| return `${dayStr} ${timeStr}`; | ||
| } | ||
| //date格式化为年月日 | ||
| function _dateDayFormat(date, showYear = true) { | ||
| const year = date.getFullYear(); | ||
| const month = date.getMonth() + 1; | ||
| const day = date.getDate(); | ||
| return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`; | ||
| } | ||
| //data格式化为时分 | ||
| function _dateTimeFormat(date) { | ||
| const hour = date.getHours(); | ||
| const minute = date.getMinutes(); | ||
| return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`; | ||
| } | ||
| //不满2位在前面填充0 | ||
| function _fullZeroToTwo(str) { | ||
| str = str.toString(); | ||
| return str.length === 1 ? '0' + str : str; | ||
| } | ||
| //驼峰转短横线 | ||
| function _toKebab(value) { | ||
| return value.replace(/([A-Z])/g, "-$1").toLowerCase(); | ||
| } | ||
| export default { | ||
| gc, | ||
| setRefesrherTime, | ||
| getRefesrherFormatTimeByKey, | ||
| getTouch, | ||
| getTouchFromZPaging, | ||
| getParent, | ||
| convertToPx, | ||
| getTime, | ||
| getInstanceId, | ||
| consoleErr, | ||
| delay, | ||
| wait | ||
| }; |
| // [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理 | ||
| import u from '../js/z-paging-utils' | ||
| const data = { | ||
| startY: 0, | ||
| isTouchFromZPaging: false, | ||
| isUsePageScroll: false, | ||
| isReachedTop: true, | ||
| isIosAndH5: false, | ||
| appLaunched: false | ||
| } | ||
| export default { | ||
| mounted() { | ||
| if (window) { | ||
| this._handleTouch(); | ||
| // #ifdef APP-VUE | ||
| this.$ownerInstance.callMethod('_handlePageLaunch'); | ||
| // #endif | ||
| } | ||
| }, | ||
| methods: { | ||
| //接收逻辑层发送的数据 | ||
| renderPropIsIosAndH5Change(newVal) { | ||
| if (newVal === -1) return; | ||
| data.isIosAndH5 = newVal; | ||
| }, | ||
| //拦截处理touch事件 | ||
| _handleTouch() { | ||
| if (!window.$zPagingRenderJsInited) { | ||
| window.$zPagingRenderJsInited = true; | ||
| window.addEventListener('touchstart', this._handleTouchstart, { passive: true }) | ||
| window.addEventListener('touchmove', this._handleTouchmove, { passive: false }) | ||
| } | ||
| }, | ||
| _handleTouchstart(e) { | ||
| const touch = u.getTouch(e); | ||
| data.startY = touch.touchY; | ||
| const touchResult = u.getTouchFromZPaging(e.target); | ||
| data.isTouchFromZPaging = touchResult.isFromZp; | ||
| data.isUsePageScroll = touchResult.isPageScroll; | ||
| data.isReachedTop = touchResult.isReachedTop; | ||
| }, | ||
| _handleTouchmove(e) { | ||
| const touch = u.getTouch(e); | ||
| const moveY = touch.touchY - data.startY; | ||
| if (data.isTouchFromZPaging && ((data.isReachedTop && moveY > 0) || (data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) { | ||
| if (e.cancelable && !e.defaultPrevented) { | ||
| e.preventDefault(); | ||
| } | ||
| } | ||
| }, | ||
| _removeAllEventListener(){ | ||
| window.removeEventListener('touchstart'); | ||
| window.removeEventListener('touchmove'); | ||
| } | ||
| } | ||
| }; |
Sorry, the diff of this file is not supported yet
| <!-- _ | ||
| ____ _ __ __ _ __ _(_)_ __ __ _ | ||
| |_ /____| '_ \ / _` |/ _` | | '_ \ / _` | | ||
| / /_____| |_) | (_| | (_| | | | | | (_| | | ||
| /___| | .__/ \__,_|\__, |_|_| |_|\__, | | ||
| |_| |___/ |___/ | ||
| v2.6.2 (2023-10-31) | ||
| by ZXLee | ||
| --> | ||
| <!-- 文档地址:https://z-paging.zxlee.cn --> | ||
| <!-- github地址:https://github.com/SmileZXLee/uni-z-paging --> | ||
| <!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 --> | ||
| <!-- 反馈QQ群:790460711(已满)、371624008 --> | ||
| <template name="z-paging"> | ||
| <!-- #ifndef APP-NVUE --> | ||
| <view :class="{'z-paging-content':true,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1}" :style="[finalPagingStyle]"> | ||
| <!-- #ifndef APP-PLUS --> | ||
| <view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view> | ||
| <!-- #endif --> | ||
| <!-- 顶部固定的slot --> | ||
| <slot v-if="!usePageScroll&&zSlots.top" name="top" /> | ||
| <view class="zp-page-top" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]"> | ||
| <slot name="top" /> | ||
| </view> | ||
| <view :class="{'zp-view-super':true,'zp-scroll-view-super':!usePageScroll}" :style="[finalScrollViewStyle]"> | ||
| <view v-if="zSlots.left" :class="{'zp-page-left':true,'zp-absoulte':finalIsOldWebView}"> | ||
| <slot name="left" /> | ||
| </view> | ||
| <view :class="{'zp-scroll-view-container':true,'zp-absoulte':finalIsOldWebView}" :style="[scrollViewContainerStyle]"> | ||
| <scroll-view | ||
| ref="zp-scroll-view" :class="{'zp-scroll-view':true,'zp-scroll-view-absolute':!usePageScroll,'zp-scroll-view-hide-scrollbar':!showScrollbar}" | ||
| :scroll-top="scrollTop" :scroll-x="scrollX" | ||
| :scroll-y="scrollable&&!usePageScroll&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==R.Complete)" :enable-back-to-top="finalEnableBackToTop" | ||
| :show-scrollbar="showScrollbar" :scroll-with-animation="finalScrollWithAnimation" | ||
| :scroll-into-view="scrollIntoView" :lower-threshold="finalLowerThreshold" :upper-threshold="5" | ||
| :refresher-enabled="finalRefresherEnabled&&!useCustomRefresher" :refresher-threshold="finalRefresherThreshold" | ||
| :refresher-default-style="finalRefresherDefaultStyle" :refresher-background="refresherBackground" | ||
| :refresher-triggered="finalRefresherTriggered" @scroll="_scroll" @scrolltolower="_onScrollToLower" | ||
| @scrolltoupper="_onScrollToUpper" @refresherrestore="_onRestore" @refresherrefresh="_onRefresh(true)" | ||
| > | ||
| <view class="zp-paging-touch-view" | ||
| <!-- #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | ||
| @touchstart="_refresherTouchstart" @touchmove="_refresherTouchmove" @touchend="_refresherTouchend" @touchcancel="_refresherTouchend" | ||
| <!-- #endif --> | ||
| <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | ||
| @touchstart="pagingWxs.touchstart" @touchmove="pagingWxs.touchmove" @touchend="pagingWxs.touchend" @touchcancel="pagingWxs.touchend" | ||
| @mousedown="pagingWxs.mousedown" @mousemove="pagingWxs.mousemove" @mouseup="pagingWxs.mouseup" @mouseleave="pagingWxs.mouseleave" | ||
| <!-- #endif --> | ||
| > | ||
| <view v-if="finalRefresherFixedBacHeight>0" class="zp-fixed-bac-view" :style="[{'background': refresherFixedBackground,'height': `${finalRefresherFixedBacHeight}px`}]"></view> | ||
| <view class="zp-paging-main" :style="[scrollViewInStyle,{'transform': finalRefresherTransform,'transition': refresherTransition}]" | ||
| <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | ||
| :change:prop="pagingWxs.propObserver" :prop="wxsPropType" | ||
| :data-refresherThreshold="finalRefresherThreshold" :data-isIos="isIos" | ||
| :data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode" | ||
| :data-refresherEnabled="refresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop" | ||
| :data-scrollTop="wxsScrollTop" :data-refresherMaxAngle="refresherMaxAngle" :data-refresherNoTransform="refresherNoTransform" | ||
| :data-refresherAecc="refresherAngleEnableChangeContinued" :data-usePageScroll="usePageScroll" :data-watchTouchDirectionChange="watchTouchDirectionChange" | ||
| :data-oldIsTouchmoving="isTouchmoving" :data-refresherOutRate="finalRefresherOutRate" :data-refresherPullRate="finalRefresherPullRate" :data-hasTouchmove="hasTouchmove" | ||
| <!-- #endif --> | ||
| <!-- #ifdef APP-VUE || H5 --> | ||
| :change:renderPropIsIosAndH5="pagingRenderjs.renderPropIsIosAndH5Change" :renderPropIsIosAndH5="isIosAndH5" | ||
| <!-- #endif --> | ||
| > | ||
| <view v-if="showRefresher" class="zp-custom-refresher-view" :style="[{'margin-top': `-${finalRefresherThreshold+refresherThresholdUpdateTag}px`,'background': refresherBackground,'opacity': isTouchmoving ? 1 : 0}]"> | ||
| <view class="zp-custom-refresher-container" :style="[{'height': `${finalRefresherThreshold}px`,'background': refresherBackground}]"> | ||
| <view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" /> | ||
| <!-- 下拉刷新view --> | ||
| <view class="zp-custom-refresher-slot-view"> | ||
| <slot v-if="!(zSlots.refresherComplete&&refresherStatus===R.Complete)" :refresherStatus="refresherStatus" name="refresher" /> | ||
| </view> | ||
| <slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" /> | ||
| <z-paging-refresh ref="refresh" v-else-if="!showCustomRefresher" class="zp-custom-refresher-refresh" :style="[{'height': `${finalRefresherThreshold - finalRefresherThresholdPlaceholder}px`}]" :status="refresherStatus" | ||
| :defaultThemeStyle="finalRefresherThemeStyle" :defaultText="finalRefresherDefaultText" | ||
| :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" | ||
| :defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated" | ||
| :showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap" | ||
| :imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" /> | ||
| </view> | ||
| </view> | ||
| <view class="zp-paging-container"> | ||
| <slot v-if="useChatRecordMode&&zSlots.chatLoading&&loadingStatus!==M.NoMore&&realTotalData.length" name="chatLoading" /> | ||
| <view v-else-if="useChatRecordMode&&loadingStatus!==M.NoMore&&realTotalData.length" class="zp-chat-record-loading-container"> | ||
| <text v-if="loadingStatus!==M.Loading" @click="_onScrollToUpper" | ||
| :class="defaultThemeStyle==='white'?'zp-loading-more-text zp-loading-more-text-white':'zp-loading-more-text zp-loading-more-text-black'">{{chatRecordLoadingMoreText}}</text> | ||
| <image v-else :src="base64Flower" class="zp-chat-record-loading-custom-image" /> | ||
| </view> | ||
| <!-- 全屏Loading --> | ||
| <slot v-if="showLoading&&zSlots.loading&&!loadingFullFixed" name="loading" /> | ||
| <!-- 主体内容 --> | ||
| <view class="zp-paging-container-content" :style="[{transform:virtualPlaceholderTopHeight>0?`translateY(${virtualPlaceholderTopHeight}px)`:'none'},finalPagingContentStyle]"> | ||
| <slot /> | ||
| <!-- 内置列表&虚拟列表 --> | ||
| <template v-if="finalUseInnerList"> | ||
| <slot name="header"/> | ||
| <view class="zp-list-container" :style="[innerListStyle]"> | ||
| <template v-if="finalUseVirtualList"> | ||
| <view class="zp-list-cell" :style="[innerCellStyle]" :id="`zp-id-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)"> | ||
| <view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第100行中注释这一行,并打开下面一行注释</view> | ||
| <!-- <zp-public-virtual-cell v-if="useCompatibilityMode" :extraData="extraData" :item="item" :index="virtualTopRangeIndex+index" /> --> | ||
| <slot v-else name="cell" :item="item" :index="virtualTopRangeIndex+index"/> | ||
| </view> | ||
| </template> | ||
| <template v-else> | ||
| <view class="zp-list-cell" v-for="(item,index) in realTotalData" :key="index" @click="_innerCellClick(item,index)"> | ||
| <slot name="cell" :item="item" :index="index"/> | ||
| </view> | ||
| </template> | ||
| </view> | ||
| <slot name="footer"/> | ||
| </template> | ||
| <view v-if="useVirtualList" class="zp-virtual-placeholder" :style="[{height:virtualPlaceholderBottomHeight+'px'}]"/> | ||
| <!-- 上拉加载更多view --> | ||
| <!-- #ifndef MP-ALIPAY --> | ||
| <slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" /> | ||
| <slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" /> | ||
| <slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" /> | ||
| <slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" /> | ||
| <z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" /> | ||
| <!-- #endif --> | ||
| <!-- #ifdef MP-ALIPAY --> | ||
| <slot v-if="loadingStatus===M.Default&&zSlots.loadingMoreDefault&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreDefault" /> | ||
| <slot v-else-if="loadingStatus===M.Loading&&zSlots.loadingMoreLoading&&showLoadingMore&&loadingMoreEnabled" name="loadingMoreLoading" /> | ||
| <slot v-else-if="loadingStatus===M.NoMore&&zSlots.loadingMoreNoMore&&showLoadingMore&&showLoadingMoreNoMoreView&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreNoMore" /> | ||
| <slot v-else-if="loadingStatus===M.Fail&&zSlots.loadingMoreFail&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreFail" /> | ||
| <z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMore&&showDefaultLoadingMoreText&&!(loadingStatus===M.NoMore&&!showLoadingMoreNoMoreView)&&loadingMoreEnabled&&!useChatRecordMode" :zConfig="zLoadMoreConfig" /> | ||
| <!-- #endif --> | ||
| <view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" /> | ||
| </view> | ||
| <!-- 空数据图 --> | ||
| <view v-if="showEmpty" :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}" :style="[emptyViewSuperStyle]"> | ||
| <slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed"/> | ||
| <z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload" | ||
| :emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle" | ||
| :emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" | ||
| @reload="_emptyViewReload" @viewClick="_emptyViewClick" /> | ||
| </view> | ||
| </view> | ||
| </view> | ||
| </view> | ||
| </scroll-view> | ||
| </view> | ||
| <view v-if="zSlots.right" :class="{'zp-page-right':true,'zp-absoulte zp-right':finalIsOldWebView}"> | ||
| <slot name="right" /> | ||
| </view> | ||
| </view> | ||
| <!-- 底部固定的slot --> | ||
| <slot v-if="!usePageScroll&&zSlots.bottom" name="bottom" /> | ||
| <view class="zp-page-bottom" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.bottom" :style="[{'bottom': `${windowBottom}px`}]"> | ||
| <slot name="bottom" /> | ||
| </view> | ||
| <!-- 点击返回顶部view --> | ||
| <view v-if="showBackToTopClass" :class="backToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick"> | ||
| <slot v-if="zSlots.backToTop" name="backToTop" /> | ||
| <image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" /> | ||
| </view> | ||
| <!-- 全屏Loading(铺满z-paging并固定) --> | ||
| <view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed"> | ||
| <slot name="loading" /> | ||
| </view> | ||
| </view> | ||
| <!-- #endif --> | ||
| <!-- #ifdef APP-NVUE --> | ||
| <component :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false"> | ||
| <!-- 顶部固定的slot --> | ||
| <view ref="zp-page-top" v-if="zSlots.top" :class="{'zp-page-top':usePageScroll}" :style="[usePageScroll?{'top':`${windowTop}px`,'z-index':topZIndex}:{}]"> | ||
| <slot name="top" /> | ||
| </view> | ||
| <component :is="finalNvueSuperListIs" class="zp-n-list-container" :scrollable="false"> | ||
| <view v-if="zSlots.left" class="zp-page-left"> | ||
| <slot name="left" /> | ||
| </view> | ||
| <component :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},nChatRecordRotateStyle]" :alwaysScrollableVertical="true" | ||
| :fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar&&!useChatRecordMode" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop" | ||
| :scrollable="scrollable&&scrollEnable&&(refresherCompleteScrollable?true:refresherStatus!==R.Complete)" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth" | ||
| :column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled" :offset-accuracy="offsetAccuracy" | ||
| @loadmore="_nOnLoadmore" @scroll="_nOnScroll"> | ||
| <refresh v-if="(zSlots.top?cacheTopHeight!==-1:true)&&finalNvueRefresherEnabled" class="zp-n-refresh" :style="[nvueRefresherStyle]" :display="nRefresherLoading?'show':'hide'" @refresh="_nOnRrefresh" @pullingdown="_nOnPullingdown"> | ||
| <view ref="zp-n-refresh-container" class="zp-n-refresh-container" :style="[{background:refresherBackground,width:nRefresherWidth}]" id="zp-n-refresh-container"> | ||
| <view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" /> | ||
| <!-- 下拉刷新view --> | ||
| <slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" /> | ||
| <slot v-else-if="(nScopedSlots?nScopedSlots:zSlots).refresher" :refresherStatus="refresherStatus" name="refresher" /> | ||
| <z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle" | ||
| :defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" | ||
| :defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated" | ||
| :showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap" | ||
| :imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" /> | ||
| </view> | ||
| </refresh> | ||
| <component :is="nViewIs" v-if="isIos&&!useChatRecordMode?oldScrollTop>10:true" ref="zp-n-list-top-tag" class="zp-n-list-top-tag" style="margin-top: -1rpx;" :style="[{height:finalNvueRefresherEnabled?'0px':'1px'}]"></component> | ||
| <component :is="nViewIs" v-if="nShowRefresherReveal" ref="zp-n-list-refresher-reveal" :style="[{transform:`translateY(-${nShowRefresherRevealHeight}px)`},{background:refresherBackground}]"> | ||
| <view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" /> | ||
| <!-- 下拉刷新view --> | ||
| <slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" /> | ||
| <slot v-else-if="(nScopedSlots?nScopedSlots:$slots).refresher" :refresherStatus="R.Loading" name="refresher" /> | ||
| <z-paging-refresh ref="refresh" v-else :status="R.Loading" :defaultThemeStyle="finalRefresherThemeStyle" | ||
| :defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" | ||
| :defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated" | ||
| :showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap" | ||
| :imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" /> | ||
| </component> | ||
| <template v-if="finalUseInnerList"> | ||
| <component :is="nViewIs"> | ||
| <slot name="header"/> | ||
| </component> | ||
| <component :is="nViewIs" class="zp-list-cell" v-for="(item,index) in realTotalData" :key="finalCellKeyName.length?item[finalCellKeyName]:index"> | ||
| <slot name="cell" :item="item" :index="index"/> | ||
| </component> | ||
| <component :is="nViewIs"> | ||
| <slot name="footer"/> | ||
| </component> | ||
| </template> | ||
| <template v-else> | ||
| <slot /> | ||
| </template> | ||
| <!-- 全屏Loading --> | ||
| <component :is="nViewIs" v-if="showLoading&&zSlots.loading&&!loadingFullFixed" :class="{'z-paging-content-fixed':usePageScroll}" style="flex:1" :style="[nChatRecordRotateStyle]"> | ||
| <slot name="loading" /> | ||
| </component> | ||
| <!-- 上拉加载更多view --> | ||
| <component :is="nViewIs" v-if="!refresherOnly&&loadingMoreEnabled&&!showEmpty"> | ||
| <view v-if="useChatRecordMode"> | ||
| <view v-if="loadingStatus!==M.NoMore&&realTotalData.length"> | ||
| <slot v-if="zSlots.chatLoading" name="chatLoading" /> | ||
| <view v-else class="zp-chat-record-loading-container"> | ||
| <text v-if="loadingStatus!==M.Loading" @click="_onScrollToUpper" | ||
| :class="defaultThemeStyle==='white'?'zp-loading-more-text zp-loading-more-text-white':'zp-loading-more-text zp-loading-more-text-black'">{{chatRecordLoadingMoreText}}</text> | ||
| <view> | ||
| <loading-indicator v-if="loadingStatus===M.Loading" class="zp-line-loading-image" animating /> | ||
| </view> | ||
| </view> | ||
| </view> | ||
| </view> | ||
| <view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:'80rpx'}:{}"> | ||
| <slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" /> | ||
| <slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" /> | ||
| <slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" /> | ||
| <slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" /> | ||
| <z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" /> | ||
| <view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" /> | ||
| </view> | ||
| </component> | ||
| <!-- 空数据图 --> | ||
| <component :is="nViewIs" v-if="showEmpty" :class="{'z-paging-content-fixed':usePageScroll}" :style="[{flex:emptyViewCenter?1:0},emptyViewSuperStyle,nChatRecordRotateStyle]"> | ||
| <view :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}"> | ||
| <slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed" /> | ||
| <z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload" | ||
| :emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle" | ||
| :emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" | ||
| @reload="_emptyViewReload" @viewClick="_emptyViewClick" /> | ||
| </view> | ||
| </component> | ||
| <component is="header" v-if="!hideNvueBottomTag" ref="zp-n-list-bottom-tag" class="zp-n-list-bottom-tag"></component> | ||
| </component> | ||
| <view v-if="zSlots.right" class="zp-page-right"> | ||
| <slot name="right" /> | ||
| </view> | ||
| </component> | ||
| <!-- 底部固定的slot --> | ||
| <slot name="bottom" /> | ||
| <!-- 点击返回顶部view --> | ||
| <view v-if="showBackToTopClass" :class="backToTopClass" :style="[finalBackToTopStyle]" @click.stop="_backToTopClick"> | ||
| <slot v-if="zSlots.backToTop" name="backToTop" /> | ||
| <image v-else class="zp-back-to-top-img" :src="backToTopImg.length?backToTopImg:base64BackToTop" /> | ||
| </view> | ||
| <!-- 全屏Loading(铺满z-paging并固定) --> | ||
| <view v-if="showLoading&&zSlots.loading&&loadingFullFixed" class="zp-loading-fixed"> | ||
| <slot name="loading" /> | ||
| </view> | ||
| </component> | ||
| <!-- #endif --> | ||
| </template> | ||
| <!-- #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5 --> | ||
| <script src="./wxs/z-paging-wxs.wxs" module="pagingWxs" lang="wxs"></script> | ||
| <!-- #endif --> | ||
| <script module="pagingRenderjs" lang="renderjs"> | ||
| import pagingRenderjs from './wxs/z-paging-renderjs.js'; | ||
| /** | ||
| * z-paging 分页组件 | ||
| * @description 高性能,全平台兼容。支持虚拟列表,支持nvue、vue3 | ||
| * @tutorial https://z-paging.zxlee.cn | ||
| * @notice 以下仅为部分常用属性、方法和事件,完整文档请查阅z-paging官网 | ||
| * @property {Number|String} default-page-no 自定义初始的pageNo,默认为1 | ||
| * @property {Number|String} default-page-size 自定义pageSize,默认为10 | ||
| * @property {Object} paging-style 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 | ||
| * @property {String} height z-paging的高度,优先级低于pagingStyle中设置的height,传字符串,如100px、100rpx、100% | ||
| * @property {String} width z-paging的宽度,优先级低于pagingStyle中设置的width,传字符串,如100px、100rpx、100% | ||
| * @property {Boolean} use-page-scroll 使用页面滚动,默认为否 | ||
| * @property {Boolean} use-virtual-list 是否使用虚拟列表,默认为否 | ||
| * @property {Boolean} fixed z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) | ||
| * @property {Boolean} auto [z-paging]mounted后是否自动调用reload方法(mounted后自动调用接口),默认为是 | ||
| * @property {Boolean} use-chat-record-mode 使用聊天记录模式,默认为否 | ||
| * @event {Function} query 下拉刷新或滚动到底部时会自动触发此方法。z-paging加载时也会触发(若要禁止,请设置:auto="false")。pageNo和pageSize会自动计算好,直接传给服务器即可。 | ||
| * @example <z-paging ref="paging" v-model="dataList" @query="queryList"></z-paging> | ||
| */ | ||
| export default { | ||
| name:"z-paging", | ||
| // #ifdef APP-VUE || H5 | ||
| mixins: [pagingRenderjs], | ||
| // #endif | ||
| } | ||
| </script> | ||
| <script src="./js/z-paging-main.js" /> | ||
| <style scoped> | ||
| @import "./css/z-paging-main.css"; | ||
| @import "./css/z-paging-static.css"; | ||
| </style> |
| { | ||
| "id": "z-paging", | ||
| "name": "z-paging", | ||
| "displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,支持nvue、vue3", | ||
| "version": "2.6.2", | ||
| "description": "超简单、低耦合!使用wxs+renderjs实现。支持长列表优化,支持自定义下拉刷新、上拉加载更多,支持自动管理空数据图、点击返回顶部,支持聊天分页、本地分页,支持国际化等100+项配置", | ||
| "keywords": [ | ||
| "下拉刷新", | ||
| "上拉加载", | ||
| "分页器", | ||
| "nvue", | ||
| "虚拟列表" | ||
| ], | ||
| "repository": "https://github.com/SmileZXLee/uni-z-paging", | ||
| "engines": { | ||
| "HBuilderX": "^3.0.7" | ||
| }, | ||
| "dcloudext": { | ||
| "sale": { | ||
| "regular": { | ||
| "price": "0.00" | ||
| }, | ||
| "sourcecode": { | ||
| "price": "0.00" | ||
| } | ||
| }, | ||
| "contact": { | ||
| "qq": "393727164" | ||
| }, | ||
| "declaration": { | ||
| "ads": "无", | ||
| "data": "无", | ||
| "permissions": "无" | ||
| }, | ||
| "npmurl": "https://www.npmjs.com/package/z-paging", | ||
| "type": "component-vue" | ||
| }, | ||
| "uni_modules": { | ||
| "dependencies": [], | ||
| "encrypt": [], | ||
| "platforms": { | ||
| "cloud": { | ||
| "tcb": "y", | ||
| "aliyun": "y" | ||
| }, | ||
| "client": { | ||
| "App": { | ||
| "app-vue": "y", | ||
| "app-nvue": "y" | ||
| }, | ||
| "H5-mobile": { | ||
| "Safari": "y", | ||
| "Android Browser": "y", | ||
| "微信浏览器(Android)": "y", | ||
| "QQ浏览器(Android)": "y" | ||
| }, | ||
| "H5-pc": { | ||
| "Chrome": "y", | ||
| "IE": "y", | ||
| "Edge": "y", | ||
| "Firefox": "y", | ||
| "Safari": "y" | ||
| }, | ||
| "小程序": { | ||
| "微信": "y", | ||
| "阿里": "y", | ||
| "百度": "y", | ||
| "字节跳动": "y", | ||
| "QQ": "y", | ||
| "钉钉": "y", | ||
| "快手": "y", | ||
| "飞书": "y", | ||
| "京东": "y" | ||
| }, | ||
| "快应用": { | ||
| "华为": "y", | ||
| "联盟": "y" | ||
| }, | ||
| "Vue": { | ||
| "vue2": "y", | ||
| "vue3": "y" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| # z-paging | ||
| <p align="center"> | ||
| <img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;"> | ||
| </p> | ||
| [](https://github.com/SmileZXLee/uni-z-paging) | ||
| [](https://en.wikipedia.org/wiki/MIT_License) | ||
| ### 文档地址:[https://z-paging.zxlee.cn](https://z-paging.zxlee.cn) | ||
| ### 更新组件前,请注意[版本差异](https://z-paging.zxlee.cn/start/upgrade-guide.html) | ||
| *** | ||
| ### 功能&特点 | ||
| * 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。 | ||
| * 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。 | ||
| * 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。 | ||
| * 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部等诸多功能。 | ||
| * 【全平台兼容】支持vue、nvue,vue2、vue3,支持h5、app及各家小程序。 | ||
| * 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs从视图层实现下拉刷新;支持虚拟列表,轻松渲染万级数据! | ||
| *** | ||
| ### 反馈qq群 | ||
| * 官方1群`已满`:[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH) | ||
| * 官方2群:[371624008](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=avPmibADf2TNi4LxkIwjCE5vbfXpa-r1&authKey=dQ%2FVDAR87ONxI4b32Py%2BvmXbhnopjHN7%2FJPtdsqJdsCPFZB6zDQ17L06Uh0kITUZ&noverify=0&group_code=371624008) | ||
| *** | ||
| ### 预览 | ||
| *** | ||
| | 自定义下拉刷新效果演示 | 滑动切换选项卡+吸顶演示 | | ||
| | :----------------------------------------------------------: | :----------------------------------------------------------: | | ||
| |  |  | | ||
| | 聊天记录模式演示 | 虚拟列表(流畅渲染1万+条)演示 | | ||
| | :----------------------------------------------------------: | :----------------------------------------------------------: | | ||
| |  |  | | ||
| ### 在线demo体验地址: | ||
| * [https://demo.z-paging.zxlee.cn](https://demo.z-paging.zxlee.cn) | ||
| | 扫码体验 | | ||
| | ------------------------------------------------------------ | | ||
| |  | | ||
| ### demo下载 | ||
| * 支持vue2&vue3的`选项式api`写法demo下载,请点击页面右上角的【使用HBuilderX导入示例项目】或【下载示例项目ZIP】。 | ||
| * 支持vue3的`组合式api`写法demo下载,请访问[github](https://github.com/SmileZXLee/uni-z-paging)。 |
-79
| /** | ||
| * 这里是uni-app内置的常用样式变量 | ||
| * | ||
| * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 | ||
| * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App | ||
| * | ||
| */ | ||
| /** | ||
| * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 | ||
| * | ||
| * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 | ||
| */ | ||
| /* 颜色变量 */ | ||
| /* 行为相关颜色 */ | ||
| $uni-color-primary: #007aff; | ||
| $uni-color-success: #4cd964; | ||
| $uni-color-warning: #f0ad4e; | ||
| $uni-color-error: #dd524d; | ||
| /* 文字基本颜色 */ | ||
| $uni-text-color: #333; //基本色 | ||
| $uni-text-color-inverse: #fff; //反色 | ||
| $uni-text-color-grey: #999; //辅助灰色,如加载更多的提示信息 | ||
| $uni-text-color-placeholder: #808080; | ||
| $uni-text-color-disable: #c0c0c0; | ||
| /* 背景颜色 */ | ||
| $uni-bg-color: #ffffff; | ||
| $uni-bg-color-grey: #f8f8f8; | ||
| $uni-bg-color-hover: #f1f1f1; //点击状态颜色 | ||
| $uni-bg-color-mask: rgba(0, 0, 0, 0.4); //遮罩颜色 | ||
| /* 边框颜色 */ | ||
| $uni-border-color: #c8c7cc; | ||
| /* 尺寸变量 */ | ||
| /* 文字尺寸 */ | ||
| $uni-font-size-sm: 24rpx; | ||
| $uni-font-size-base: 28rpx; | ||
| $uni-font-size-lg: 32rpx; | ||
| /* 图片尺寸 */ | ||
| $uni-img-size-sm: 40rpx; | ||
| $uni-img-size-base: 52rpx; | ||
| $uni-img-size-lg: 80rpx; | ||
| /* Border Radius */ | ||
| $uni-border-radius-sm: 4rpx; | ||
| $uni-border-radius-base: 6rpx; | ||
| $uni-border-radius-lg: 12rpx; | ||
| $uni-border-radius-circle: 50%; | ||
| /* 水平间距 */ | ||
| $uni-spacing-row-sm: 10px; | ||
| $uni-spacing-row-base: 20rpx; | ||
| $uni-spacing-row-lg: 30rpx; | ||
| /* 垂直间距 */ | ||
| $uni-spacing-col-sm: 8rpx; | ||
| $uni-spacing-col-base: 16rpx; | ||
| $uni-spacing-col-lg: 24rpx; | ||
| /* 透明度 */ | ||
| $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 | ||
| /* 文章场景相关 */ | ||
| $uni-color-title: #2c405a; // 文章标题颜色 | ||
| $uni-font-size-title: 40rpx; | ||
| $uni-color-subtitle: #555555; // 二级标题颜色 | ||
| $uni-font-size-subtitle: 36rpx; | ||
| $uni-color-paragraph: #3f536e; // 文章段落颜色 | ||
| $uni-font-size-paragraph: 30rpx; | ||
| /* uni.scss */ | ||
| @import 'uview-plus/theme.scss'; |
| import { defineConfig } from 'vite' | ||
| import uni from '@dcloudio/vite-plugin-uni' | ||
| import path from 'path' | ||
| const target = 'http://ums.sxb805.cn:9001/'; | ||
| export default defineConfig({ | ||
| plugins: [ | ||
| uni(), | ||
| ], | ||
| resolve: { | ||
| alias: { | ||
| '@': path.resolve(__dirname, 'src') | ||
| } | ||
| }, | ||
| server: { | ||
| host: '0.0.0.0', //服务器ip地址 | ||
| port: 5555, //本地端口 | ||
| fs: { | ||
| strict: false, // 支持引用除入口目录的文件 | ||
| }, | ||
| // open: true, // 是否自动在浏览器打开 | ||
| proxy: { | ||
| '/cloud': { | ||
| target, | ||
| changeOrigin: true, | ||
| }, | ||
| }, | ||
| } | ||
| }) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
0
-100%394568
-44.26%57
1.79%65
-57.79%899
-85.52%