Commit 75485e57 by pangchong

feat: 5.16提交

parent 56994ef9
......@@ -10,6 +10,10 @@
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
......
......@@ -7,5 +7,5 @@
"trailingComma": "none",
"arrowParens": "always",
"tabWidth": 4,
"printWidth": 200
"printWidth": 180
}
<script>
import useUserStore from '@/store/modules/user'
export default {
onLaunch: function () {
console.log('App Launch')
},
onShow: function () {
//退出登录
// const userStore = useUserStore()
// if (!userStore.token) {
// userStore.handleLogOut()
// }
console.log('App Show')
},
onHide: function () {
......
......@@ -59,7 +59,7 @@ const getSize = computed(() => {
</script>
<style lang="scss" scoped>
.global-button {
padding: 0 16rpx;
padding: 0 20rpx;
margin: 0;
height: v-bind(getSize);
line-height: v-bind(getSize);
......
<template>
<view class="page-wrap">
<view :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<view class="page-top" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<template v-if="showNavbar">
<uni-nav-bar :title="title" :border="false" :backgroundColor="backgroundColor" :color="color" left-icon="left" :leftWidth="leftWidth" :rightWidth="rightWidth">
<template #default>
<slot name="default"></slot>
......@@ -12,15 +13,25 @@
<slot name="right"></slot>
</template>
</uni-nav-bar>
</template>
<template v-else>
<slot></slot>
</template>
</view>
<template v-if="showNavbar">
<view class="page-content" :style="{ height: `calc(100% - 44px - ${safeAreaInsets?.top + 'px'})` }">
<slot name="content"></slot>
</view>
</template>
</view>
</template>
<script setup>
const ps = defineProps({
showNavbar: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
......@@ -29,7 +40,7 @@ const ps = defineProps({
type: String,
default: '#1D2129'
},
backgroundColor: {
navbarBackground: {
type: String,
default: '#f7f8fa'
},
......@@ -50,6 +61,12 @@ const { safeAreaInsets } = uni.getSystemInfoSync()
display: flex;
flex-direction: column;
height: 100%;
.page-top {
flex: auto;
display: flex;
flex-direction: column;
overflow: hidden;
}
:deep(.uni-navbar__header-btns) {
width: auto;
}
......
......@@ -10,32 +10,33 @@
"pages": [
//pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/panel/index",
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "工作台",
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/panel/app-center/index",
"path": "pages/panel/index",
"style": {
"navigationBarTitleText": "应用中心",
"navigationBarTitleText": "工作台",
"navigationStyle": "custom"
}
},
{
"path": "pages/login/index",
"path": "pages/panel/app-center/index",
"style": {
"navigationBarTitleText": "登录"
"navigationBarTitleText": "应用中心",
"navigationStyle": "custom"
}
},
{
"path": "pages/chat/index",
"style": {
"navigationBarTitleText": "沟通"
}
},
{
"path": "pages/mine/index",
"style": {
......@@ -47,8 +48,13 @@
"style": {
"navigationBarTitleText": "经验库"
}
},
{
"path": "pages/panel/assessment-records/index",
"style": {
"navigationBarTitleText": "考核记录"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
......@@ -76,17 +82,25 @@
"selectedIconPath": "static/menu/panel-select.png"
},
{
"pagePath": "pages/experience/index",
"text": "经验库",
"iconPath": "static/menu/experience.png",
"selectedIconPath": "static/menu/experience-select.png"
},
{
"pagePath": "pages/mine/index",
"text": "个人中心",
"iconPath": "static/menu/mine.png",
"selectedIconPath": "static/menu/mine-select.png"
}
]
},
"condition": {
//模式配置,仅开发期间生效
"current": 0, //当前激活的模式(list 的索引项)
"list": [
{
"name": "test", //模式名称
"path": "pages/panel/index" //启动页面,必选
},
{
"name": "login", //模式名称
"path": "pages/login/index" //启动页面,必选
}
]
}
}
\ No newline at end of file
......@@ -2,24 +2,45 @@ page {
background: #fff;
}
.login-wrap {
padding: 50rpx;
width: 100%;
padding: 48rpx;
.login-header{
display: flex;
align-items: center;
image{
width: 38rpx;
height: 36rpx;
}
.title{
margin-left: 16rpx;
font-size: 32rpx;
color: #0A0D14;
}
}
.login-title {
font-weight: bold;
font-size: 40rpx;
margin-bottom: 100rpx;
margin: 64rpx 0;
.p{
font-size: 48rpx;
color: #1A1C1E;
margin-bottom: 24rpx;
}
.txt{
font-size: 28rpx;
color: #6C7278;
}
}
.verify-code {
display: flex;
justify-content: space-between;
align-items: center;
image {
margin-left: 10rpx;
&-image{
margin-left: 20rpx;
width: 200rpx;
height: 70rpx;
background: #D9D9D9;
}
}
.login-btn {
margin-top: 50rpx;
width: 100%;
}
}
\ No newline at end of file
<template>
<custom-page>
<view class="login-wrap">
<view class="login-title">Mocp</view>
<view class="login-header">
<image src="/static/login/Vector.png" />
<view class="title">海航技术</view>
</view>
<view class="login-title">
<view class="p">登录你的账户</view>
<view class="txt">请输入账号密码登录</view>
</view>
<uni-forms ref="loginFormRef" :modelValue="loginForm" :rules="rules" label-position="top">
<uni-forms-item label="账号" name="username">
<uni-easyinput v-model="loginForm.username" placeholder="请输入账号" type="text" />
......@@ -10,13 +18,15 @@
</uni-forms-item>
<uni-forms-item label="验证码" name="verifyCode">
<view class="verify-code">
<uni-easyinput v-model="loginForm.verifyCode" placeholder="请输入验证码" type="text" />
<image v-if="loginCode" :src="loginCode" mode="scaleToFill" />
<uni-easyinput v-model="loginForm.verifyCode" placeholder="请输入验证码" type="text" style="flex: auto" />
<image class="verify-code-image" v-if="loginCode && loginForm.username" :src="loginCode" mode="scaleToFill" />
<view class="verify-code-image" v-else></view>
</view>
</uni-forms-item>
</uni-forms>
<button class="login-btn" size="mini" type="primary" @tap="handleLogin">登录</button>
<custom-button class="login-btn" size="large" type="primary" @tap="handleLogin">登录</custom-button>
</view>
</custom-page>
</template>
<script setup>
......@@ -24,9 +34,8 @@ import { ref, toRaw, watch } from 'vue'
import { loginApi } from '@/api/user'
import { debounce } from 'lodash'
import message from '@/utils/message'
import useUserStore from '@/store/modules/user'
import { loginCode, loginForm, rules } from './constants/index.compositions'
//获取验证码
import { getGifCaptcha } from './constants/index.functionals'
watch(
......@@ -38,15 +47,15 @@ watch(
}, 500)
)
//登录
const userStore = useUserStore()
const loginFormRef = ref()
const handleLogin = async () => {
await loginFormRef.value?.validate()
const res = await loginApi(toRaw(loginForm), { loading: '登陆中...' })
if (res.code == 200) {
//跳转工作台
uni.switchTab({ url: '/pages/panel/index' })
userStore.setUserInfo(res.data)
} else {
message.error(res.message)
message.error({ title: res.message })
}
}
</script>
......
.cancel {
font-size: 32rpx;
color: #1d2129;
}
.content {
padding: 16rpx;
.card {
border-radius: 12rpx;
background: #fff;
padding: 32rpx 0 0 0;
margin-bottom: 16rpx;
.card-title {
display: flex;
justify-content: space-between;
padding: 0 16rpx;
margin-bottom: 14rpx;
.card-title-txt {
font-size: 34rpx;
color: #1d2129;
}
}
.menu-list {
display: flex;
flex-wrap: wrap;
.menu-item {
width: 25%;
margin: 14rpx 0;
box-sizing: border-box;
}
}
}
}
\ No newline at end of file
<template>
<custom-page title="应用中心">
<custom-page title="应用中心" showNavbar>
<template #left>
<view v-if="isEdit" @tap="isEdit = false">取消</view>
<view v-if="isEdit" @tap="isEdit = false" class="cancel">取消</view>
<uni-icons type="left" size="16" @tap="goBack" v-else></uni-icons>
</template>
<template #right>
<custom-button type="primary" size="small" v-if="isEdit">保存</custom-button>
<custom-button type="primary" size="small" v-if="isEdit" style="padding: 0 32rpx">保存</custom-button>
</template>
<template #content>
<view class="content">
......@@ -15,7 +15,15 @@
<custom-button type="primary" size="small" @tap="isEdit = true">编辑</custom-button>
</view>
<view class="menu-list">
<menu-item class="menu-item" v-for="item in menuList" :data="item" :key="item.id" :type="isEdit ? 'minus' : 'default'"></menu-item>
<menu-item
class="menu-item"
v-for="item in menuListHome"
:data="item"
:key="item.id"
:showCount="false"
:type="isEdit ? 'minus' : 'default'"
@tap="goTo"
></menu-item>
</view>
</view>
<view class="card">
......@@ -23,7 +31,15 @@
<view class="card-title-txt">全部应用</view>
</view>
<view class="menu-list">
<menu-item class="menu-item" v-for="item in menuList" :data="item" :key="item.id" :type="isEdit ? 'plus' : 'default'"></menu-item>
<menu-item
class="menu-item"
v-for="item in menuListNoHome"
:data="item"
:key="item.id"
:showCount="false"
:type="isEdit ? 'plus' : 'default'"
@tap="goTo"
></menu-item>
</view>
</view>
</view>
......@@ -34,7 +50,7 @@
<script setup>
import { ref } from 'vue'
import MenuItem from '../components/menu-item.vue'
import { menuList } from '../constants/data.config'
import { menuListHome, menuListNoHome } from '../constants/index.compositions'
const isEdit = ref(false)
......@@ -42,34 +58,11 @@ const isEdit = ref(false)
const goBack = () => {
uni.navigateBack()
}
//跳转
const goTo = () => {
uni.navigateTo({ url: '/pages/panel/assessment-records/index' })
}
</script>
<style lang="scss" scoped>
.content {
padding: 16rpx;
.card {
border-radius: 12rpx;
background: #fff;
padding: 32rpx 0 0 0;
margin-bottom: 16rpx;
.card-title {
display: flex;
justify-content: space-between;
padding: 0 16rpx;
margin-bottom: 14rpx;
.card-title-txt {
font-size: 34rpx;
color: #1d2129;
}
}
.menu-list {
display: flex;
flex-wrap: wrap;
.menu-item {
width: 25%;
margin: 14rpx 0;
box-sizing: border-box;
}
}
}
}
@import './constants/index.scss';
</style>
<template>
<z-paging ref="paging" v-model="dataList">
<template #top>
<z-tabs :list="tabList" @change="tabsChange" bar-width="80rpx" bg-color="#f7f8fa" />
</template>
<view class="list">
<view class="item" v-for="item in 20" :key="item">
<view class="item-title">
<view class="desc">
<view class="type">OPEN</view>
<view class="txt">190机型管控中心</view>
<view class="place">华北基地</view>
</view>
<view class="score">
<text class="tag">扣分</text>
<text class="count">8</text>
</view>
</view>
<view class="item-content">3月31日,天津航空6837/A320飞机执行天津=长春、天津=昆明航班,23:00落地,航后执行52A检工作,利用夜间8小时左右执行飞机检查和维修工</view>
</view>
</view>
</z-paging>
</template>
<script setup>
import { ref } from 'vue'
const paging = ref()
const tabIndex = ref(0)
const tabList = ref(['OPEN', 'CLOSE', '全部'])
const dataList = ref([])
//导航切换
const tabsChange = (index) => {
tabIndex.value = index
paging.value.reload()
}
</script>
<style lang="scss" scoped>
.list {
padding: 24rpx;
.item {
padding: 24rpx;
margin-bottom: 16rpx;
border-radius: 12rpx;
background: linear-gradient(#e6eeff, #ffffff, #ffffff);
&-title {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 16rpx;
border-bottom: 2rpx solid #f4f4f4;
.desc {
display: flex;
align-items: center;
.type {
color: #f53f3f;
font-size: 34rpx;
}
.txt {
color: #1d2129;
font-size: 34rpx;
margin: 0 16rpx;
}
.place {
font-size: 28rpx;
color: #86909c;
}
}
.score {
border-radius: 6rpx;
width: 108rpx;
background: #ffece8;
color: #fff;
font-size: 22rpx;
display: flex;
align-items: center;
.tag {
width: 58rpx;
border-radius: 6rpx;
background: #f53f3f;
text-align: center;
padding: 2px 0;
margin-right: 16rpx;
}
.count {
color: #f53f3f;
}
}
}
&-content {
padding-top: 16rpx;
font-size: 30rpx;
color: #4e5969;
line-height: 40rpx;
}
}
}
</style>
<template>
<view class="menu-item" v-if="type == 'default'">
<uni-badge :text="getText" absolute="rightTop" size="small" :offset="[3, 3]" :customStyle="customStyle">
<view class="menu-item" v-if="type == 'minus'">
<image :src="getUrl" />
</uni-badge>
<text>{{ data.name }}</text>
<view class="circle minus">-</view>
</view>
<template v-else>
<uni-badge :text="getText" absolute="rightTop" size="small" :offset="[15, 15]" :customStyle="customStyle">
<view class="menu-item">
<view class="menu-item" v-else-if="type == 'plus'">
<image :src="getUrl" />
<text>{{ data.name }}</text>
<view class="circle plus">+</view>
</view>
<view class="menu-item" v-else>
<image :src="getUrl" />
<text>{{ data.name }}</text>
<view class="count" v-if="getCount > 0">{{ getCount }}</view>
</view>
</uni-badge>
</template>
</template>
<script setup>
......@@ -28,31 +29,21 @@ const ps = defineProps({
type: {
type: String,
default: 'default'
},
showCount: {
type: Boolean,
default: true
}
})
const getUrl = computed(() => {
return '/static/panel/' + ps.data.icon + '.png'
})
const getText = computed(() => {
if (ps.type == 'default') {
return ps.data.count
const getCount = computed(() => {
if (ps.showCount) {
return ps.data.count || 0
} else {
return 0
}
if (ps.type == 'minus') {
return '-'
}
if (ps.type == 'plus') {
return '+'
}
return ps.data.count
})
const customStyle = computed(() => {
if (ps.type == 'minus') {
return { background: '#C8C8C8' }
}
if (ps.type == 'plus') {
return { background: '#165DFF' }
}
return {}
})
</script>
<style lang="scss" scoped>
......@@ -61,6 +52,8 @@ const customStyle = computed(() => {
flex-direction: column;
align-items: center;
padding-top: 10rpx;
position: relative;
font-size: 30rpx;
image {
width: 64rpx;
height: 64rpx;
......@@ -70,5 +63,34 @@ const customStyle = computed(() => {
color: #1d2129;
font-size: 30rpx;
}
.circle {
width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
border-radius: 50%;
color: #fff;
position: absolute;
top: 0;
right: 0;
&.minus {
background: #c8c8c8;
}
&.plus {
background: #165dff;
}
}
.count {
color: #fff;
height: 32rpx;
line-height: 32rpx;
padding: 0 8rpx;
background: #ee0a24;
position: absolute;
top: 0;
right: 40rpx;
border-radius: 40rpx;
font-size: 22rpx;
}
}
</style>
......@@ -7,18 +7,26 @@ export const menuList = [
{ id: 3, name: '支援申请', icon: 'zysq', group: '技术支援', count: 0, home: true },
{ id: 4, name: '工作指令', icon: 'gzzl', group: '技术支援', count: 0, home: true },
{ id: 5, name: '技术评估', icon: 'jspg', group: '技术支援', count: 0, home: true },
{ id: 6, name: '运行调查', icon: 'yxdc', group: '运行管理', count: 0, home: true },
{ id: 7, name: '运行决策', icon: 'yxjc', group: '运行管理', count: 0, home: true },
{ id: 8, name: '布置工作', icon: 'bzgz', group: '运行管理', count: 0, home: true },
{ id: 6, name: '运行调查', icon: 'yxdc', group: '维修控制', count: 0, home: true },
{ id: 7, name: '运行决策', icon: 'yxjc', group: '维修控制', count: 0, home: true },
{ id: 8, name: '布置工作', icon: 'bzgz', group: '维修控制', count: 0, home: true },
{ id: 9, name: '航站管理', icon: 'hzgl', group: '航站管理', count: 0, home: true },
{ id: 10, name: '协议单位', icon: 'xydw', group: '航站管理', count: 0, home: true },
{ id: 11, name: '不正常事件', icon: 'bzcsj', group: '运行品质', count: 0, home: true },
{ id: 12, name: '考核记录', icon: 'khjl', group: '运行品质', count: 0, home: true }
{ id: 11, name: '不正常事件', icon: 'bzcsj', group: '运行品质', count: 0, home: false },
{ id: 12, name: '考核记录', icon: 'khjl', group: '运行品质', count: 0, home: false },
{ id: 13, name: 'AOG任务', icon: 'AOG', group: '运行品质', count: 0, home: false },
{ id: 14, name: 'MCO', icon: 'MCO', group: '运行品质', count: 0, home: false },
{ id: 15, name: '航班保障', icon: 'hbbz', group: '运行品质', count: 0, home: false }
]
export const allMenuItem = { id: 999, name: '全部菜单', icon: 'all', group: '', count: 0, home: true }
//获取首页菜单
export const menuListHome = computed(() => {
return menuList.filter((item) => item.home)
})
//获取除首页菜单
export const menuListNoHome = computed(() => {
return menuList.filter((item) => !item.home)
})
//获取分组菜单
export const menuListGroup = computed(() => {
return menuList.reduce((groups, item) => {
......
.content {
height: calc(100% - 386rpx);
border-radius: 16rpx 16rpx 0 0;
background: #fff;
padding-top: 32rpx;
display: flex;
flex-direction: column;
margin: 0 32rpx;
flex: auto;
.tab-nav {
display: flex;
padding: 0 32rpx;
&-item {
color: rgba(0, 0, 0, 0.6);
position: relative;
margin-right: 56rpx;
padding-bottom: 12rpx;
border-bottom: 4rpx solid #fff;
&.active {
color: #165dff;
border-bottom: 4rpx solid #165dff;
}
}
}
.tab-content {
margin-top: 36rpx;
flex: auto;
overflow-y: auto;
&-item {
.menu-content-item {
margin-bottom: 32rpx;
.menu-line {
margin: 0 32rpx;
height: 1rpx;
background: rgba(0, 0, 0, 0.05);
}
.menu-item {
margin-bottom: 32rpx !important;
}
.menu-title {
color: #1d2129;
margin-bottom: 32rpx;
padding: 0 32rpx;
font-size: 34rpx;
}
}
.menu-list {
display: flex;
flex-wrap: wrap;
.menu-item {
width: 25%;
margin-bottom: 48rpx;
box-sizing: border-box;
}
}
}
}
}
\ No newline at end of file
.navbar {
padding-bottom: 24rpx;
color: #fff;
&-box {
display: flex;
align-items: center;
padding: 20rpx 32rpx 32rpx 32rpx;
&-info {
display: flex;
flex-direction: column;
image {
width: 150rpx;
height: 70rpx;
}
}
}
&-input {
flex: auto;
margin-left: 16rpx;
:deep(.is-input-border) {
border-radius: 12rpx !important;
}
&-right {
color: #165dff;
margin-right: 32rpx;
display: flex;
align-items: center;
font-weight: 900;
.line {
background: rgba(104, 161, 254, 0.4);
margin-right: 32rpx;
width: 1rpx;
height: 32rpx;
}
}
}
&-bottom-list {
background: #fff;
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 32rpx;
border-radius: 16rpx;
padding: 0 72rpx 12rpx 72rpx;
.list-item {
text-align: center;
&-image {
width: 84rpx;
height: 84rpx;
}
&-title {
font-size: 24rpx;
color: #4e5969;
}
&-txt {
font-weight: bold;
font-size: 40rpx;
color: #1d2129;
line-height: 56rpx;
}
&-line {
width: 2rpx;
height: 58rpx;
background: #e4f1ff;
}
}
}
}
<template>
<custom-page>
<image class="page-bg" src="/static/panel/page-bg.png" />
<!-- 导航栏 -->
<view class="navbar" :style="{ paddingTop: safeAreaInsets?.top + 'px' }">
<image class="navbar-bg" src="/static/panel/navbar-bg.png" />
<view class="navbar-box">
<view class="navbar-weather">
<view class="weather-place">武汉</view>
<text class="weather-state">晴 22°</text>
</view>
<view class="navbar-input">
<uni-easyinput type="text" disabled>
<template #right>
<view class="navbar-input-right">
<text class="line"></text>
<text>搜索</text>
</view>
</template>
</uni-easyinput>
</view>
<view class="navbar-icon">
<image src="/static/panel/vector.png" />
</view>
</view>
<view class="navbar-bottom-list">
<view class="list-item">
<view class="list-item-title" style="text-align: left">我的任务</view>
<view class="list-item-descs">
<view class="list-item-desc">
<view class="txt">24</view>
<view class="p">待办任务</view>
</view>
<view class="list-item-line"></view>
<view class="list-item-desc">
<view class="txt">1</view>
<view class="p">已接受任务</view>
</view>
</view>
</view>
<view class="list-item">
<view class="list-item-title">跟机任务</view>
<view class="list-item-desc">
<view class="txt">24</view>
<view class="p">待办任务</view>
</view>
</view>
<view class="list-item">
<view class="list-item-title">AOG任务</view>
<view class="list-item-desc">
<view class="txt">24</view>
<view class="p">待办任务</view>
</view>
</view>
</view>
</view>
<!-- 图标list -->
<view class="content" :style="{ height: `calc(100% - 345rpx - ${safeAreaInsets?.top + 'px'})` }">
<view class="tab-nav">
<template v-for="(item, index) in tabNav" :key="index">
<view class="tab-nav-item" :class="{ active: activeIndex == index ? true : false }" @tap="activeIndex = index">{{ item }}</view>
</template>
</view>
<scroll-view class="tab-content" scroll-y="true">
<view class="tab-content-item" v-if="activeIndex == 0">
<view class="menu-list">
<menu-item class="menu-item" v-for="item in menuListHome" :data="item" :key="item.id" @tap="goAppCenter"></menu-item>
</view>
</view>
<view class="tab-content-item" v-if="activeIndex == 1">
<view class="menu-content">
<view class="menu-content-item" v-for="(value, key) in menuListGroup" :key="key">
<view class="menu-title">{{ key }}</view>
<view class="menu-list">
<menu-item class="menu-item" v-for="item in value" :data="item" :key="item.id"></menu-item>
</view>
<view class="menu-line"></view>
</view>
</view>
</view>
</scroll-view>
</view>
<panel-navbar></panel-navbar>
<!-- 菜单图标 -->
<panel-menu style="flex: auto"></panel-menu>
</custom-page>
</template>
<script setup>
import { ref } from 'vue'
import MenuItem from './components/menu-item.vue'
import { menuListHome, menuListGroup } from './constants/data.config'
// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()
//分类
const tabNav = ref(['常用', '分组'])
const activeIndex = ref(0)
//跳转应用中心
const goAppCenter = () => {
uni.navigateTo({ url: '/pages/panel/app-center/index' })
}
import PanelNavbar from './panel-navbar.vue'
import PanelMenu from './panel-menu.vue'
</script>
<style>
page {
overflow: hidden;
}
</style>
<style lang="scss" scoped>
.navbar {
padding-bottom: 32rpx;
position: relative;
color: #fff;
&-bg {
.page-bg {
position: absolute;
width: 100%;
z-index: -1;
}
&-box {
display: flex;
align-items: center;
padding: 20rpx 32rpx 32rpx 32rpx;
}
&-weather {
margin-right: 16rpx;
.weather-state {
font-size: 24rpx;
}
}
&-input {
width: 500rpx;
:deep(.is-input-border) {
border-radius: 12rpx !important;
}
&-right {
color: #165dff;
margin-right: 32rpx;
display: flex;
align-items: center;
font-weight: 900;
.line {
background: rgba(104, 161, 254, 0.4);
margin-right: 32rpx;
width: 1rpx;
height: 32rpx;
}
}
}
&-icon {
width: 60rpx;
height: 60rpx;
margin-left: 26rpx;
image {
width: 100%;
height: 100%;
}
}
&-bottom-list {
display: flex;
justify-content: space-between;
padding: 0 32rpx;
.list-item {
&-title {
font-size: 32rpx;
text-align: center;
margin-bottom: 26rpx;
}
&-descs {
display: flex;
align-items: center;
}
&-desc {
display: flex;
flex-direction: column;
align-items: center;
.txt {
font-weight: 700;
}
}
&-line {
margin: 0 24rpx;
height: 60rpx;
width: 1rpx;
background: rgba(255, 255, 255, 0.1);
}
}
}
}
.content {
border-radius: 38rpx 38rpx 0 0;
background: #fff;
padding-top: 32rpx;
display: flex;
flex-direction: column;
.tab-nav {
display: flex;
padding: 0 32rpx;
&-item {
color: rgba(0, 0, 0, 0.6);
position: relative;
margin-right: 56rpx;
padding-bottom: 12rpx;
border-bottom: 4rpx solid #fff;
&.active {
color: #165dff;
border-bottom: 4rpx solid #165dff;
}
}
}
.tab-content {
margin-top: 36rpx;
flex: auto;
overflow-y: auto;
&-item {
.menu-content-item {
margin-bottom: 32rpx;
.menu-line {
margin: 0 32rpx;
height: 1rpx;
background: rgba(0, 0, 0, 0.05);
}
.menu-item {
margin-bottom: 32rpx !important;
}
.menu-title {
color: #1d2129;
margin-bottom: 32rpx;
padding: 0 32rpx;
font-size: 34rpx;
}
}
.menu-list {
display: flex;
flex-wrap: wrap;
.menu-item {
width: 25%;
margin-bottom: 48rpx;
box-sizing: border-box;
}
}
}
}
z-index: -1;
}
</style>
<template>
<view class="content">
<view class="tab-nav">
<template v-for="(item, index) in tabNav" :key="index">
<view class="tab-nav-item" :class="{ active: activeIndex == index ? true : false }" @tap="activeIndex = index">{{ item }}</view>
</template>
</view>
<scroll-view class="tab-content" scroll-y="true">
<view class="tab-content-item" v-if="activeIndex == 0">
<view class="menu-list">
<menu-item class="menu-item" v-for="item in menuListHome" :data="item" :key="item.id"></menu-item>
<menu-item class="menu-item" :data="allMenuItem" @tap="goAppCenter"></menu-item>
</view>
</view>
<view class="tab-content-item" v-if="activeIndex == 1">
<view class="menu-content">
<view class="menu-content-item" v-for="(value, key) in menuListGroup" :key="key">
<view class="menu-title">{{ key }}</view>
<view class="menu-list">
<menu-item class="menu-item" v-for="item in value" :data="item" :key="item.id"></menu-item>
</view>
<view class="menu-line"></view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import MenuItem from './components/menu-item.vue'
import { menuListHome, menuListGroup, allMenuItem } from './constants/index.compositions'
//分类
const tabNav = ref(['常用', '分组'])
const activeIndex = ref(0)
//跳转应用中心
const goAppCenter = () => {
uni.navigateTo({ url: '/pages/panel/app-center/index' })
}
</script>
<style lang="scss" scoped>
@import './constants/panel-menu.scss';
</style>
<template>
<view class="navbar">
<view class="navbar-box">
<view class="navbar-box-info">
<image src="/static/panel/logo.png" />
</view>
<view class="navbar-input">
<uni-easyinput type="text" disabled placeholder="请输入功能名称">
<template #right>
<view class="navbar-input-right">
<text class="line"></text>
<text>搜索</text>
</view>
</template>
</uni-easyinput>
</view>
</view>
<view class="navbar-bottom-list">
<view class="list-item">
<image class="list-item-image" src="/static/panel/wdrw.png" />
<view class="list-item-title">我的任务</view>
<view class="list-item-txt">24</view>
</view>
<view class="list-item-line"></view>
<view class="list-item">
<image class="list-item-image" src="/static/panel/gjrw.png" />
<view class="list-item-title">跟机任务</view>
<view class="list-item-txt">24</view>
</view>
<view class="list-item-line"></view>
<view class="list-item">
<image class="list-item-image" src="/static/panel/AOGrw.png" />
<view class="list-item-title">AOG任务</view>
<view class="list-item-txt">24</view>
</view>
</view>
</view>
</template>
<script setup></script>
<style lang="scss" scoped>
@import './constants/panel-navbar.scss';
</style>
......@@ -4,16 +4,7 @@ const useUserStore = defineStore('user', {
state: () => {
return {
token: '',
user_info: {
id: '',
username: '',
nickname: '',
status: 0,
token: '',
tokenHead: ' ',
videoGroup: 0,
autoRecord: false
}
user_info: undefined
}
},
......@@ -21,26 +12,18 @@ const useUserStore = defineStore('user', {
actions: {
//用户登录
setUserInfo() {
this.token = res.token
this.user_info = res
this.router.push('/').then(function () {
wsShouldOpen.value = true
})
setUserInfo(data) {
this.token = data.token
this.user_info = data
//跳转工作台
uni.switchTab({ url: '/pages/panel/index' })
},
//退出登录
handleLogOut() {
this.token = ''
this.user_info = {
id: '',
username: '',
nickname: '',
status: 0,
token: '',
tokenHead: ' ',
videoGroup: 0,
autoRecord: false
}
this.user_info = undefined
//返回登录页面
uni.redirectTo({ url: '/pages/login/index' })
}
},
// 配置持久化
......
## 2.7.10(2024-05-10)
1.`修复` v2.7.8引出的vue3+npm+微信小程序中,uni.$zp配置失效的问题。
## 2.7.9(2024-05-10)
1.`修复` 在新版HbuilderX+vue3+微信小程序中可能出现的加载第二页数据后返回顶部无法下拉的问题。
2.`修复` 在vue3+抖音小程序中可能出现的首次加载reload没有触发的问题。
3.`优化` ts类型中,`ZPagingInstance`泛型不必填,默认为`any`
## 2.7.8(2024-05-09)
1.`新增` typescript类型声明文件,感谢小何同学提供。
2.`新增` 添加极简写法fetch相关配置及示例。
3.`新增` `fixed-cellHeight`配置,支持在虚拟列表中自定义固定的cell高度。
4.`新增` `refresher-refreshing-scrollable`配置,支持自定义下拉刷新中是否允许列表滚动。
5.`修复` 在新版HubilderX+vue3+h5中,`swiper-demo`模式`slot=top`被导航栏遮挡的问题。
6.`修复` 下拉进入二楼偶现的二楼高度未铺满全屏的问题。
7.`修复` 虚拟列表中complete若传相同对象会导致key重复的问题。
8.`修复` 在虚拟列表中调用refresh后缓存高度原始数据未清空的问题。
9.`修复` 聊天记录模式删除记录时顶部显示loading的问题。
9.`优化` `scrollIntoViewByIndex`支持在虚拟列表中滚动到指定cell。
10.`优化` `content-z-index`默认值修改为1。
## 2.7.7(2024-04-01)
1.`新增` 下拉进入二楼功能及相关配置&demo。
2.`新增` 虚拟列表写法添加【非内置列表】写法,可良好兼容vue3中的各平台并有较优的性能表现。
3.`新增` `z-paging-cell`补充`@touchstart`事件。
4.`修复` 页面滚动模式设置了`auto-full-height`后可能出现的依然有异常空白占位的问题和下拉刷新时列表数据被切割的问题。
## 2.7.6(2024-02-29)
1.`新增` `max-width`,支持设置`z-paging`的最大宽度,默认`z-paging`宽度铺满窗口。
2.`新增` `chat-adjust-position-offset`,支持设置使用聊天记录模式中键盘弹出时占位高度偏移距离。
3.`修复` 由于renderjs中聊天记录模式判断不准确导致的可能出现的从聊天记录页面跳转到其他页面后返回页面无法滚动的问题。
4.`修复` 聊天记录模式首次加载失败后,发送消息顶部会显示加载失败文字的问题。
5.`修复` 聊天记录模式nvue可能出现的键盘弹出无法滚动到底部的问题。
6.`修复` 聊天记录模式+nvue滚动条无法展示的问题&底部会显示加载中的问题。
7.`修复` 聊天记录模式监听键盘弹出可能出现的无法正常销毁的问题。
8.`修复` 直接修改dataList的值组件内部的值未更新的问题。
## 2.7.5(2024-01-23)
1.`新增` props:`chat-loading-more-default-as-loading`,支持设置在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示。
2.`新增` slots:`chatNoMore`,支持自定义聊天记录模式没有更多数据view。
3.`修复` 固定在底部view可能出现默认黄色的问题。
4.`优化` 聊天记录加载更多样式,与普通模式对齐,支持点击加载更多&点击重试,并支持加载更多相关配置。
5.`优化` 微调下拉刷新和底部加载更多样式。
6.`优化` 聊天记录模式自动滚动到底部添加延时以避免可能出现的滚动到底部位置不正确的问题。
7.`优化` 使用新的判断滚动到顶部算法以解决在安卓设备中可能出现的因滚动到顶部时scrollTop不为0导致的下拉刷新被禁用的问题。
## 2.7.4(2024-01-14)
1.`新增` props:`auto-adjust-position-when-chat`,支持设置使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度。
2.`新增` props:`auto-to-bottom-when-chat`,支持设置使用聊天记录模式中键盘弹出时是否自动滚动到底部。
3.`新增` props:`show-chat-loading-when-reload`,支持设置使用聊天记录模式中reload时是否显示chatLoading。
4.`修复` 在聊天记录模式中`scrollIntoViewById``scrollIntoViewByNodeTop`无效的问题。
5.`优化` 聊天记录模式底部安全区域针对键盘开启/关闭兼容处理。
6.`优化` 更新内置的空数据图&加载失败图,感谢图鸟UI提供的免费可商用的空数据图和加载失败图!
## 2.7.3(2024-01-10)
1.`新增` 聊天记录模式支持虚拟列表&添加相关demo。
2.`新增` nvue中list添加`@scrollend`监听。
3.`优化` 聊天记录模式+vue第一页并且没有更多时不倒置列表。
4.`优化` 聊天记录模式+nvue中数据不满屏时默认从顶部开始,不进行列表倒置。
## 2.7.2(2024-01-09)
1.`修复` `vue3+h5`中报错`uni.onKeyboardHeightChange is not a function`的问题。
2.`优化` 聊天记录模式细节:表情面板在触摸列表时隐藏&添加隐藏动画。
## 2.7.1(2024-01-08)
1.`新增` `keyboardHeightChange` event,支持监听键盘高度改变。
2.`新增` 聊天记录模式新增切换表情面板/键盘demo。
3.`优化` 键盘弹出占位添加动画效果。
## 2.7.0(2024-01-07)
2024新年快乐!!祝大家在新的一年里工作顺利,事事顺心!
1.`新增` 全新的聊天记录模式设计!将vue中的聊天记录模式与nvue中对齐,完全解决了聊天记录模式滚动到顶部加载更多在vue中抖动的问题,同时将聊天记录模式键盘自动弹出自动上推页面交由`z-paging`处理,解决了由此引发的各种问题,尤其是在微信小程序中导航栏被键盘顶出屏幕外的问题。如果您使用了`z-paging`的聊天记录模式,强烈建议更新,写法有一定变更,具体请参见demo。
2.`新增` `swiper-demo`新增`onShow`时候调用reload演示。
3.`修复` 修复滚动相关方法在微信小程序中首次滚动动画无效的问题。
4.`修复` props设置单位,单位为px时报错的问题。
5.`修复` 在某些情况下`z-paging`加载了但是未渲染时,reload无效的问题。
6.`修复` 底部loading动画未生效的问题。
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群:790460711 -->
<!-- z-paging-cell,用于在nvue中使用cell包裹,vue中使用view包裹 -->
<template>
<!-- #ifdef APP-NVUE -->
<cell :style="[cellStyle]" @touchstart="onTouchstart">
<slot />
</cell>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<view :style="[cellStyle]" @touchstart="onTouchstart">
<slot />
</view>
<!-- #endif -->
</template>
<script>
export default {
name: "z-paging-cell",
props: {
//cellStyle
cellStyle: {
type: Object,
default: function() {
return {}
}
}
},
methods: {
onTouchstart(e) {
this.$emit('touchstart', e);
}
}
}
</script>
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群:790460711 -->
<!-- 空数据占位view,此组件支持easycom规范,可以在项目中直接引用 -->
<template>
<view :class="{'zp-container':true,'zp-container-fixed':emptyViewFixed}" :style="[finalEmptyViewStyle]" @click="emptyViewClick">
<view class="zp-main">
<image v-if="!emptyViewImg.length" :class="{'zp-main-image-rpx':unit==='rpx','zp-main-image-px':unit==='px'}" :style="[emptyViewImgStyle]" :src="emptyImg" />
<image v-else :class="{'zp-main-image-rpx':unit==='rpx','zp-main-image-px':unit==='px'}" mode="aspectFit" :style="[emptyViewImgStyle]" :src="emptyViewImg" />
<text class="zp-main-title" :class="{'zp-main-title-rpx':unit==='rpx','zp-main-title-px':unit==='px'}" :style="[emptyViewTitleStyle]">{{emptyViewText}}</text>
<text v-if="showEmptyViewReload" :class="{'zp-main-error-btn':true,'zp-main-error-btn-rpx':unit==='rpx','zp-main-error-btn-px':unit==='px'}" :style="[emptyViewReloadStyle]" @click.stop="reloadClick">{{emptyViewReloadText}}</text>
</view>
</view>
</template>
<script>
import zStatic from '../z-paging/js/z-paging-static'
export default {
name: "z-paging-empty-view",
data() {
return {
};
},
props: {
// 空数据描述文字
emptyViewText: {
type: String,
default: '没有数据哦~'
},
// 空数据图片
emptyViewImg: {
type: String,
default: ''
},
// 是否显示空数据图重新加载按钮
showEmptyViewReload: {
type: Boolean,
default: false
},
// 空数据点击重新加载文字
emptyViewReloadText: {
type: String,
default: '重新加载'
},
// 是否是加载失败
isLoadFailed: {
type: Boolean,
default: false
},
// 空数据图样式
emptyViewStyle: {
type: Object,
default: function() {
return {}
}
},
// 空数据图img样式
emptyViewImgStyle: {
type: Object,
default: function() {
return {}
}
},
// 空数据图描述文字样式
emptyViewTitleStyle: {
type: Object,
default: function() {
return {}
}
},
// 空数据图重新加载按钮样式
emptyViewReloadStyle: {
type: Object,
default: function() {
return {}
}
},
// 空数据图z-index
emptyViewZIndex: {
type: Number,
default: 9
},
// 空数据图片是否使用fixed布局并铺满z-paging
emptyViewFixed: {
type: Boolean,
default: true
},
// 空数据图中布局的单位,默认为rpx
unit: {
type: String,
default: 'rpx'
}
},
computed: {
emptyImg() {
return this.isLoadFailed ? zStatic.base64Error : zStatic.base64Empty;
},
finalEmptyViewStyle(){
this.emptyViewStyle['z-index'] = this.emptyViewZIndex;
return this.emptyViewStyle;
}
},
methods: {
// 点击了reload按钮
reloadClick() {
this.$emit('reload');
},
// 点击了空数据view
emptyViewClick() {
this.$emit('viewClick');
}
}
}
</script>
<style scoped>
.zp-container{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
}
.zp-container-fixed {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
}
.zp-main{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
padding: 50rpx 0rpx;
}
.zp-main-image-rpx {
width: 240rpx;
height: 240rpx;
}
.zp-main-image-px {
width: 120px;
height: 120px;
}
.zp-main-title {
color: #aaaaaa;
text-align: center;
}
.zp-main-title-rpx {
font-size: 28rpx;
margin-top: 10rpx;
padding: 0rpx 20rpx;
}
.zp-main-title-px {
font-size: 14px;
margin-top: 5px;
padding: 0px 10px;
}
.zp-main-error-btn {
border: solid 1px #dddddd;
color: #aaaaaa;
}
.zp-main-error-btn-rpx {
font-size: 28rpx;
padding: 8rpx 24rpx;
border-radius: 6rpx;
margin-top: 50rpx;
}
.zp-main-error-btn-px {
font-size: 14px;
padding: 4px 12px;
border-radius: 3px;
margin-top: 25px;
}
</style>
<!-- z-paging -->
<!-- github地址:https://github.com/SmileZXLee/uni-z-paging -->
<!-- dcloud地址:https://ext.dcloud.net.cn/plugin?id=3935 -->
<!-- 反馈QQ群:790460711 -->
<!-- 滑动切换选项卡swiper-item,此组件支持easycom规范,可以在项目中直接引用 -->
<template>
<view class="zp-swiper-item-container">
<z-paging ref="paging" :fixed="false"
:auto="false" :useVirtualList="useVirtualList" :useInnerList="useInnerList" :cellKeyName="cellKeyName" :innerListStyle="innerListStyle"
:preloadPage="preloadPage" :cellHeightMode="cellHeightMode" :virtualScrollFps="virtualScrollFps" :virtualListCol="virtualListCol"
@query="_queryList" @listChange="_updateList">
<slot />
<template #header>
<slot name="header"/>
</template>
<template #cell="{item,index}">
<slot name="cell" :item="item" :index="index"/>
</template>
<template #footer>
<slot name="footer"/>
</template>
</z-paging>
</view>
</template>
<script>
import zPaging from '../z-paging/z-paging'
export default {
name: "z-paging-swiper-item",
components: { zPaging },
data() {
return {
firstLoaded: false
}
},
props: {
// 当前组件的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();
setTimeout(this.updateFixedLayout, 100)
})
// #ifndef APP-PLUS
this._getCssSafeAreaInsetBottom();
// #endif
this.updateLeftAndRightWidth();
this.swiperContentStyle = { 'flex': '1' };
// #ifndef APP-NVUE
this.swiperContentStyle = { width: '100%',height: '100%' };
// #endif
},
computed: {
finalSwiperStyle() {
const swiperStyle = { ...this.swiperStyle };
if (!this.systemInfo) return swiperStyle;
const windowTop = this.windowTop;
const windowBottom = this.systemInfo.windowBottom;
if (this.fixed) {
if (windowTop && !swiperStyle.top) {
swiperStyle.top = windowTop + 'px';
}
if (!swiperStyle.bottom) {
let bottom = windowBottom || 0;
bottom += this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
if (bottom > 0) {
swiperStyle.bottom = bottom + 'px';
}
}
}
return swiperStyle;
}
},
methods: {
// 更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用
updateLeftAndRightWidth() {
if (!this.isOldWebView) return;
this.$nextTick(() => this._updateLeftAndRightWidth(this.swiperContentStyle, 'zp-swiper'));
}
}
}
</script>
<style scoped>
.zp-swiper-container {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
flex: 1;
}
.zp-swiper-container-fixed {
position: fixed;
/* #ifndef APP-NVUE */
height: auto;
width: auto;
/* #endif */
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.zp-safe-area-inset-bottom {
position: absolute;
/* #ifndef APP-PLUS */
height: env(safe-area-inset-bottom);
/* #endif */
}
.zp-swiper-super {
flex: 1;
overflow: hidden;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.zp-swiper-left,.zp-swiper-right{
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
}
.zp-swiper {
flex: 1;
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
}
.zp-absoulte {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
width: auto;
/* #endif */
}
.zp-swiper-item {
height: 100%;
}
</style>
<!-- [z-paging]上拉加载更多view -->
<template>
<view class="zp-l-container" :class="{'zp-l-container-rpx':c.unit==='rpx','zp-l-container-px':c.unit==='px'}" :style="[c.customStyle]" @click="doClick">
<template v-if="!c.hideContent">
<!-- 底部加载更多没有更多数据分割线 -->
<text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" :class="{'zp-l-line-rpx':c.unit==='rpx','zp-l-line-px':c.unit==='px'}" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" />
<!-- 底部加载更多loading -->
<!-- #ifndef APP-NVUE -->
<image v-if="finalStatus===M.Loading&&!!c.loadingIconCustomImage"
:src="c.loadingIconCustomImage" :style="[c.iconCustomStyle]" :class="{'zp-l-line-loading-custom-image':true,'zp-l-line-loading-custom-image-animated':c.loadingAnimated,'zp-l-line-loading-custom-image-rpx':c.unit==='rpx','zp-l-line-loading-custom-image-px':c.unit==='px'}" />
<image v-if="finalStatus===M.Loading&&finalLoadingIconType==='flower'&&!c.loadingIconCustomImage.length"
:class="{'zp-line-loading-image':true,'zp-line-loading-image-rpx':c.unit==='rpx','zp-line-loading-image-px':c.unit==='px'}" :style="[c.iconCustomStyle]" :src="zTheme.flower[ts]" />
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<!-- 在nvue中底部加载更多loading使用系统自带的 -->
<view>
<loading-indicator v-if="finalStatus===M.Loading&&finalLoadingIconType!=='circle'" :class="{'zp-line-loading-image-rpx':c.unit==='rpx','zp-line-loading-image-px':c.unit==='px'}" :style="[{color:zTheme.indicator[ts]}]" :animating="true" />
</view>
<!-- #endif -->
<!-- 底部加载更多文字 -->
<text v-if="finalStatus===M.Loading&&finalLoadingIconType==='circle'&&!c.loadingIconCustomImage.length"
class="zp-l-circle-loading-view" :class="{'zp-l-circle-loading-view-rpx':c.unit==='rpx','zp-l-circle-loading-view-px':c.unit==='px'}" :style="[{borderColor:zTheme.circleBorder[ts],borderTopColor:zTheme.circleBorderTop[ts]},c.iconCustomStyle]" />
<text v-if="!c.isChat||(!c.chatDefaultAsLoading&&finalStatus===M.Default)||finalStatus===M.Fail" :class="{'zp-l-text-rpx':c.unit==='rpx','zp-l-text-px':c.unit==='px'}" :style="[{color:zTheme.title[ts]},c.titleCustomStyle]">{{ownLoadingMoreText}}</text>
<!-- 底部加载更多没有更多数据分割线 -->
<text v-if="c.showNoMoreLine&&finalStatus===M.NoMore" :class="{'zp-l-line-rpx':c.unit==='rpx','zp-l-line-px':c.unit==='px'}" :style="[{backgroundColor:zTheme.line[ts]},c.noMoreLineCustomStyle]" />
</template>
</view>
</template>
<script>
import zStatic from '../js/z-paging-static'
import Enum from '../js/z-paging-enum'
export default {
name: 'z-paging-load-more',
data() {
return {
M: Enum.More,
zTheme: {
title: { white: '#efefef', black: '#a4a4a4' },
line: { white: '#efefef', black: '#eeeeee' },
circleBorder: { white: '#aaaaaa', black: '#c8c8c8' },
circleBorderTop: { white: '#ffffff', black: '#444444' },
flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower },
indicator: { white: '#eeeeee', black: '#777777' }
}
};
},
props: ['zConfig'],
computed: {
ts() {
return this.c.defaultThemeStyle;
},
// 底部加载更多配置
c() {
return this.zConfig || {};
},
// 底部加载更多文字
ownLoadingMoreText() {
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;
},
// 加载更多icon类型
finalLoadingIconType() {
// #ifdef APP-NVUE
return 'flower';
// #endif
return this.c.loadingIconType;
}
},
methods: {
// 点击了加载更多
doClick() {
this.$emit('doClick');
}
}
}
</script>
<style scoped>
@import "../css/z-paging-static.css";
.zp-l-container {
/* #ifndef APP-NVUE */
clear: both;
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
}
.zp-l-container-rpx {
height: 80rpx;
font-size: 27rpx;
}
.zp-l-container-px {
height: 40px;
font-size: 14px;
}
.zp-l-line-loading-custom-image {
color: #a4a4a4;
}
.zp-l-line-loading-custom-image-rpx {
margin-right: 8rpx;
width: 28rpx;
height: 28rpx;
}
.zp-l-line-loading-custom-image-px {
margin-right: 4px;
width: 14px;
height: 14px;
}
.zp-l-line-loading-custom-image-animated{
/* #ifndef APP-NVUE */
animation: loading-circle 1s linear infinite;
/* #endif */
}
.zp-l-circle-loading-view {
border: 3rpx solid #dddddd;
border-radius: 50%;
/* #ifndef APP-NVUE */
animation: loading-circle 1s linear infinite;
/* #endif */
/* #ifdef APP-NVUE */
width: 30rpx;
height: 30rpx;
/* #endif */
}
.zp-l-circle-loading-view-rpx {
margin-right: 8rpx;
width: 23rpx;
height: 23rpx;
}
.zp-l-circle-loading-view-px {
margin-right: 4px;
width: 12px;
height: 12px;
}
.zp-l-text-rpx {
font-size: 30rpx;
margin: 0rpx 6rpx;
}
.zp-l-text-px {
font-size: 15px;
margin: 0px 3px;
}
.zp-l-line-rpx {
height: 1px;
width: 100rpx;
margin: 0rpx 10rpx;
}
.zp-l-line-px {
height: 1px;
width: 50px;
margin: 0rpx 5px;
}
/* #ifndef APP-NVUE */
@keyframes loading-circle {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
/* #endif */
</style>
<!-- [z-paging]下拉刷新view -->
<template>
<view style="height: 100%;">
<view :class="showUpdateTime?'zp-r-container zp-r-container-padding':'zp-r-container'">
<view class="zp-r-left">
<!-- 非加载中(继续下拉刷新、松手立即刷新状态图片) -->
<image v-if="status!==R.Loading" :class="leftImageClass" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" />
<!-- 加载状态图片 -->
<!-- #ifndef APP-NVUE -->
<image v-else :class="{'zp-line-loading-image':refreshingAnimated,'zp-r-left-image':true,'zp-r-left-image-pre-size-rpx':unit==='rpx','zp-r-left-image-pre-size-px':unit==='px'}" :style="[leftImageStyle,imgStyle]" :src="leftImageSrc" />
<!-- #endif -->
<!-- 在nvue中,加载状态loading使用系统loading -->
<!-- #ifdef APP-NVUE -->
<view v-else :style="[{'margin-right':showUpdateTime?addUnit(18,unit):addUnit(12, unit)}]">
<loading-indicator :class="isIos?{'zp-loading-image-ios-rpx':unit==='rpx','zp-loading-image-ios-px':unit==='px'}:{'zp-loading-image-android-rpx':unit==='rpx','zp-loading-image-android-px':unit==='px'}"
:style="[{color:zTheme.indicator[ts]},imgStyle]" :animating="true" />
</view>
<!-- #endif -->
</view>
<!-- 右侧文字内容 -->
<view class="zp-r-right">
<!-- 右侧下拉刷新状态文字 -->
<text class="zp-r-right-text" :style="[rightTextStyle,titleStyle]">{{currentTitle}}</text>
<!-- 右侧下拉刷新时间文字 -->
<text v-if="showUpdateTime&&refresherTimeText.length" class="zp-r-right-text" :class="{'zp-r-right-time-text-rpx':unit==='rpx','zp-r-right-time-text-px':unit==='px'}" :style="[{color:zTheme.title[ts]},updateTimeStyle]">
{{refresherTimeText}}
</text>
</view>
</view>
</view>
</template>
<script>
import zStatic from '../js/z-paging-static'
import u from '../js/z-paging-utils'
import Enum from '../js/z-paging-enum'
export default {
name: 'z-paging-refresh',
data() {
return {
R: Enum.Refresher,
isIos: uni.getSystemInfoSync().platform === 'ios',
refresherTimeText: '',
zTheme: {
title: { white: '#efefef', black: '#555555' },
arrow: { white: zStatic.base64ArrowWhite, black: zStatic.base64Arrow },
flower: { white: zStatic.base64FlowerWhite, black: zStatic.base64Flower },
success: { white: zStatic.base64SuccessWhite, black: zStatic.base64Success },
indicator: { white: '#eeeeee', black: '#777777' }
}
};
},
props: ['status', 'defaultThemeStyle', 'defaultText', 'pullingText', 'refreshingText', 'completeText', 'goF2Text', 'defaultImg', 'pullingImg',
'refreshingImg', 'completeImg', 'refreshingAnimated', 'showUpdateTime', 'updateTimeKey', 'imgStyle', 'titleStyle', 'updateTimeStyle', 'updateTimeTextMap', 'unit'
],
computed: {
ts() {
return this.defaultThemeStyle;
},
// 当前状态数组
statusTextArr() {
this.updateTime();
return [this.defaultText, this.pullingText, this.refreshingText, this.completeText, this.goF2Text];
},
// 当前状态文字
currentTitle() {
return this.statusTextArr[this.status] || this.defaultText;
},
// 左侧图片class
leftImageClass() {
const preSizeClass = `zp-r-left-image-pre-size-${this.unit}`;
if (this.status === this.R.Complete) return preSizeClass;
return `zp-r-left-image ${preSizeClass} ${this.status === this.R.Default ? 'zp-r-arrow-down' : 'zp-r-arrow-top'}`;
},
// 左侧图片style
leftImageStyle() {
const showUpdateTime = this.showUpdateTime;
const size = showUpdateTime ? u.addUnit(36, this.unit) : u.addUnit(34, this.unit);
return {width: size,height: size,'margin-right': showUpdateTime ? u.addUnit(20, this.unit) : u.addUnit(9, this.unit)};
},
// 左侧图片src
leftImageSrc() {
const R = this.R;
const status = this.status;
if (status === R.Default) {
if (!!this.defaultImg) return this.defaultImg;
return this.zTheme.arrow[this.ts];
} else if (status === R.ReleaseToRefresh) {
if (!!this.pullingImg) return this.pullingImg;
if (!!this.defaultImg) return this.defaultImg;
return this.zTheme.arrow[this.ts];
} else if (status === R.Loading) {
if (!!this.refreshingImg) return this.refreshingImg;
return this.zTheme.flower[this.ts];;
} else if (status === R.Complete) {
if (!!this.completeImg) return this.completeImg;
return this.zTheme.success[this.ts];;
} else if (status === R.GoF2) {
return this.zTheme.arrow[this.ts];
}
return '';
},
// 右侧文字style
rightTextStyle() {
let stl = {};
// #ifdef APP-NVUE
const textHeight = this.showUpdateTime ? u.addUnit(40, this.unit) : u.addUnit(80, this.unit);
stl = {'height': textHeight, 'line-height': textHeight}
// #endif
stl['color'] = this.zTheme.title[this.ts];
stl['font-size'] = u.addUnit(30, this.unit);
return stl;
}
},
methods: {
// 添加单位
addUnit(value, unit) {
return u.addUnit(value, unit);
},
// 更新下拉刷新时间
updateTime() {
if (this.showUpdateTime) {
this.refresherTimeText = u.getRefesrherFormatTimeByKey(this.updateTimeKey, this.updateTimeTextMap);
}
}
}
}
</script>
<style scoped>
@import "../css/z-paging-static.css";
.zp-r-container {
/* #ifndef APP-NVUE */
display: flex;
height: 100%;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
}
.zp-r-container-padding {
/* #ifdef APP-NVUE */
padding: 7px 0rpx;
/* #endif */
}
.zp-r-left {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
overflow: hidden;
/* #ifdef MP-ALIPAY */
margin-top: -4rpx;
/* #endif */
}
.zp-r-left-image {
transition-duration: .2s;
transition-property: transform;
color: #666666;
}
.zp-r-left-image-pre-size-rpx {
/* #ifndef APP-NVUE */
width: 34rpx;
height: 34rpx;
overflow: hidden;
/* #endif */
}
.zp-r-left-image-pre-size-px {
/* #ifndef APP-NVUE */
width: 17px;
height: 17px;
overflow: hidden;
/* #endif */
}
.zp-r-arrow-top {
transform: rotate(0deg);
}
.zp-r-arrow-down {
transform: rotate(180deg);
}
.zp-r-right {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
align-items: center;
justify-content: center;
}
.zp-r-right-time-text-rpx {
margin-top: 10rpx;
font-size: 26rpx;
}
.zp-r-right-time-text-px {
margin-top: 5px;
font-size: 13px;
}
</style>
// z-paging全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置
export default {}
\ No newline at end of file
/* [z-paging]公共css*/
.z-paging-content {
position: relative;
flex-direction: column;
/* #ifndef APP-NVUE */
overflow: hidden;
/* #endif */
}
.z-paging-content-full {
/* #ifndef APP-NVUE */
display: flex;
width: 100%;
height: 100%;
/* #endif */
}
.z-paging-content-fixed, .zp-loading-fixed {
position: fixed;
/* #ifndef APP-NVUE */
height: auto;
width: auto;
/* #endif */
top: 0;
left: 0;
bottom: 0;
right: 0;
}
.zp-f2-content {
width: 100%;
position: fixed;
top: 0;
left: 0;
background-color: white;
}
.zp-page-top, .zp-page-bottom {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
position: fixed;
left: 0;
right: 0;
z-index: 999;
}
.zp-page-left, .zp-page-right {
/* #ifndef APP-NVUE */
height: 100%;
/* #endif */
}
.zp-scroll-view-super {
flex: 1;
overflow: hidden;
position: relative;
}
.zp-view-super {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.zp-scroll-view-container, .zp-scroll-view {
position: relative;
/* #ifndef APP-NVUE */
height: 100%;
width: 100%;
/* #endif */
}
.zp-absoulte {
/* #ifndef APP-NVUE */
position: absolute;
top: 0;
width: auto;
/* #endif */
}
.zp-scroll-view-absolute {
position: absolute;
top: 0;
left: 0;
}
/* #ifndef APP-NVUE */
.zp-scroll-view-hide-scrollbar ::-webkit-scrollbar {
display: none;
-webkit-appearance: none;
width: 0 !important;
height: 0 !important;
background: transparent;
}
/* #endif */
.zp-paging-touch-view {
width: 100%;
height: 100%;
position: relative;
}
.zp-fixed-bac-view {
position: absolute;
width: 100%;
top: 0;
left: 0;
height: 200px;
}
.zp-paging-main {
height: 100%;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.zp-paging-container {
flex: 1;
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
}
.zp-chat-record-loading-custom-image {
width: 35rpx;
height: 35rpx;
/* #ifndef APP-NVUE */
animation: loading-flower 1s linear infinite;
/* #endif */
}
.zp-page-bottom-keyboard-placeholder-animate {
transition-property: height;
transition-duration: 0.15s;
/* #ifndef APP-NVUE */
will-change: height;
/* #endif */
}
.zp-custom-refresher-container {
overflow: hidden;
}
.zp-custom-refresher-refresh {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
}
.zp-back-to-top {
z-index: 999;
position: absolute;
bottom: 0rpx;
transition-duration: .3s;
transition-property: opacity;
}
.zp-back-to-top-rpx {
width: 76rpx;
height: 76rpx;
bottom: 0rpx;
right: 25rpx;
}
.zp-back-to-top-px {
width: 38px;
height: 38px;
bottom: 0px;
right: 13px;
}
.zp-back-to-top-show {
opacity: 1;
}
.zp-back-to-top-hide {
opacity: 0;
}
.zp-back-to-top-img {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
/* #ifdef APP-NVUE */
flex: 1;
/* #endif */
z-index: 999;
}
.zp-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 {
/* #ifndef APP-NVUE */
animation: loading-flower 1s steps(12) infinite;
/* #endif */
color: #666666;
}
.zp-line-loading-image-rpx {
margin-right: 8rpx;
width: 34rpx;
height: 34rpx;
}
.zp-line-loading-image-px {
margin-right: 4px;
width: 17px;
height: 17px;
}
.zp-loading-image-ios-rpx {
width: 40rpx;
height: 40rpx;
}
.zp-loading-image-ios-px {
width: 20px;
height: 20px;
}
.zp-loading-image-android-rpx {
width: 34rpx;
height: 34rpx;
}
.zp-loading-image-android-px {
width: 17px;
height: 17px;
}
/* #ifndef APP-NVUE */
@keyframes loading-flower {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
/* #endif */
{
"zp.refresher.default": "Pull down to refresh",
"zp.refresher.pulling": "Release to refresh",
"zp.refresher.refreshing": "Refreshing...",
"zp.refresher.complete": "Refresh succeeded",
"zp.refresher.f2": "Refresh to enter 2f",
"zp.loadingMore.default": "Click to load more",
"zp.loadingMore.loading": "Loading...",
"zp.loadingMore.noMore": "No more data",
"zp.loadingMore.fail": "Load failed,click to reload",
"zp.emptyView.title": "No data",
"zp.emptyView.reload": "Reload",
"zp.emptyView.error": "Sorry,load failed",
"zp.refresherUpdateTime.title": "Last update: ",
"zp.refresherUpdateTime.none": "None",
"zp.refresherUpdateTime.today": "Today",
"zp.refresherUpdateTime.yesterday": "Yesterday",
"zp.systemLoading.title": "Loading..."
}
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.refresher.f2": "松手进入二楼",
"zp.loadingMore.default": "点击加载更多",
"zp.loadingMore.loading": "正在加载...",
"zp.loadingMore.noMore": "没有更多了",
"zp.loadingMore.fail": "加载失败,点击重新加载",
"zp.emptyView.title": "没有数据哦~",
"zp.emptyView.reload": "重新加载",
"zp.emptyView.error": "很抱歉,加载失败",
"zp.refresherUpdateTime.title": "最后更新:",
"zp.refresherUpdateTime.none": "无",
"zp.refresherUpdateTime.today": "今天",
"zp.refresherUpdateTime.yesterday": "昨天",
"zp.systemLoading.title": "加载中..."
}
{
"zp.refresher.default": "繼續下拉重繪",
"zp.refresher.pulling": "鬆開立即重繪",
"zp.refresher.refreshing": "正在重繪...",
"zp.refresher.complete": "重繪成功",
"zp.refresher.f2": "鬆手進入二樓",
"zp.loadingMore.default": "點擊加載更多",
"zp.loadingMore.loading": "正在加載...",
"zp.loadingMore.noMore": "沒有更多了",
"zp.loadingMore.fail": "加載失敗,點擊重新加載",
"zp.emptyView.title": "沒有數據哦~",
"zp.emptyView.reload": "重新加載",
"zp.emptyView.error": "很抱歉,加載失敗",
"zp.refresherUpdateTime.title": "最後更新:",
"zp.refresherUpdateTime.none": "無",
"zp.refresherUpdateTime.today": "今天",
"zp.refresherUpdateTime.yesterday": "昨天",
"zp.systemLoading.title": "加載中..."
}
// [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
\ No newline at end of file
// [z-paging]useZPagingComp hooks
function useZPagingComp(paging) {
const cPaging = !!paging ? paging.value || paging : null;
const reload = () => {
if (!cPaging || !cPaging.value) return;
cPaging.value.reload().catch(() => {});
}
const updatePageScrollTop = scrollTop => {
if (!cPaging || !cPaging.value) return;
cPaging.value.updatePageScrollTop(scrollTop);
}
const doChatRecordLoadMore = () => {
if (!cPaging || !cPaging.value) return;
cPaging.value.doChatRecordLoadMore();
}
const pageReachBottom = () => {
if (!cPaging || !cPaging.value) return;
cPaging.value.pageReachBottom();
}
return { reload, updatePageScrollTop, doChatRecordLoadMore, pageReachBottom };
}
export default useZPagingComp
\ No newline at end of file
// [z-paging]点击返回顶部view模块
import u from '.././z-paging-utils'
export default {
props: {
// 自动显示点击返回顶部按钮,默认为否
autoShowBackToTop: {
type: Boolean,
default: u.gc('autoShowBackToTop', false)
},
// 点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx
backToTopThreshold: {
type: [Number, String],
default: u.gc('backToTopThreshold', '400rpx')
},
// 点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片
backToTopImg: {
type: String,
default: u.gc('backToTopImg', '')
},
// 点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是
backToTopWithAnimate: {
type: Boolean,
default: u.gc('backToTopWithAnimate', true)
},
// 点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx
backToTopBottom: {
type: [Number, String],
default: u.gc('backToTopBottom', '160rpx')
},
// 点击返回顶部按钮的自定义样式
backToTopStyle: {
type: Object,
default: u.gc('backToTopStyle', {}),
},
// iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是
enableBackToTop: {
type: Boolean,
default: u.gc('enableBackToTop', true)
},
},
data() {
return {
// 点击返回顶部的class
backToTopClass: 'zp-back-to-top zp-back-to-top-hide',
// 上次点击返回顶部的时间
lastBackToTopShowTime: 0,
// 点击返回顶部显示的class是否在展示中,使得按钮展示/隐藏过度效果更自然
showBackToTopClass: false,
}
},
computed: {
backToTopThresholdUnitConverted() {
return u.addUnit(this.backToTopThreshold, this.unit);
},
backToTopBottomUnitConverted() {
return u.addUnit(this.backToTopBottom, this.unit);
},
finalEnableBackToTop() {
return this.usePageScroll ? false : this.enableBackToTop;
},
finalBackToTopThreshold() {
return u.convertToPx(this.backToTopThresholdUnitConverted);
},
finalBackToTopStyle() {
const backToTopStyle = this.backToTopStyle;
if (!backToTopStyle.bottom) {
backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottomUnitConverted) + 'px';
}
if(!backToTopStyle.position){
backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute';
}
return backToTopStyle;
},
finalBackToTopClass() {
return `${this.backToTopClass} zp-back-to-top-${this.unit}`;
}
},
methods: {
// 点击了返回顶部
_backToTopClick() {
let callbacked = false;
this.$emit('backToTopClick', toTop => {
(toTop === undefined || toTop === true) && this._handleToTop();
callbacked = true;
});
// 如果用户没有禁止默认的返回顶部事件,则触发滚动到顶部
this.$nextTick(() => {
!callbacked && this._handleToTop();
})
},
// 处理滚动到顶部
_handleToTop() {
!this.backToTopWithAnimate && this._checkShouldShowBackToTop(0);
this.scrollToTop(this.backToTopWithAnimate);
},
// 判断是否要显示返回顶部按钮
_checkShouldShowBackToTop(scrollTop) {
if (!this.autoShowBackToTop) {
this.showBackToTopClass = false;
return;
}
if (scrollTop > this.finalBackToTopThreshold) {
if (!this.showBackToTopClass) {
// 记录当前点击返回顶部按钮显示的class生效了
this.showBackToTopClass = true;
this.lastBackToTopShowTime = new Date().getTime();
// 当滚动到需要展示返回顶部的阈值内,则延迟300毫秒展示返回到顶部按钮
u.delay(() => {
this.backToTopClass = 'zp-back-to-top zp-back-to-top-show';
}, 300)
}
} else {
// 如果当前点击返回顶部按钮显示的class是生效状态并且滚动小于触发阈值,则隐藏返回顶部按钮
if (this.showBackToTopClass) {
this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide';
u.delay(() => {
this.showBackToTopClass = false;
}, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300)
}
}
},
}
}
// [z-paging]聊天记录模式模块
import u from '.././z-paging-utils'
export default {
props: {
// 使用聊天记录模式,默认为否
useChatRecordMode: {
type: Boolean,
default: u.gc('useChatRecordMode', false)
},
// 使用聊天记录模式时滚动到顶部后,列表垂直移动偏移距离。默认0rpx。单位px(暂时无效)
chatRecordMoreOffset: {
type: [Number, String],
default: u.gc('chatRecordMoreOffset', '0rpx')
},
// 使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是
autoHideKeyboardWhenChat: {
type: Boolean,
default: u.gc('autoHideKeyboardWhenChat', true)
},
// 使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度,默认为是
autoAdjustPositionWhenChat: {
type: Boolean,
default: u.gc('autoAdjustPositionWhenChat', true)
},
// 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px
chatAdjustPositionOffset: {
type: [Number, String],
default: u.gc('chatAdjustPositionOffset', '0rpx')
},
// 使用聊天记录模式中键盘弹出时是否自动滚动到底部,默认为否
autoToBottomWhenChat: {
type: Boolean,
default: u.gc('autoToBottomWhenChat', false)
},
// 使用聊天记录模式中reload时是否显示chatLoading,默认为否
showChatLoadingWhenReload: {
type: Boolean,
default: u.gc('showChatLoadingWhenReload', false)
},
// 在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示,默认为是。若设置为否,则默认会显示【点击加载更多】,然后才会显示loading
chatLoadingMoreDefaultAsLoading: {
type: Boolean,
default: u.gc('chatLoadingMoreDefaultAsLoading', true)
},
},
data() {
return {
// 键盘高度
keyboardHeight: 0,
// 键盘高度是否未改变,此时占位高度变化不需要动画效果
isKeyboardHeightChanged: false,
}
},
computed: {
finalChatRecordMoreOffset() {
return u.convertToPx(this.chatRecordMoreOffset);
},
finalChatAdjustPositionOffset() {
return u.convertToPx(this.chatAdjustPositionOffset);
},
// 聊天记录模式旋转180度style
chatRecordRotateStyle() {
let cellStyle;
// 在vue中,直接将列表倒置,因此在vue的cell中,也直接写style="transform: scaleY(-1)"转回来即可。
// #ifndef APP-NVUE
cellStyle = this.useChatRecordMode ? { transform: 'scaleY(-1)' } : {};
// #endif
// 在nvue中,需要考虑数据量不满一页的情况,因为nvue中的list无法通过flex-end修改不满一页的起始位置,会导致不满一页时列表数据从底部开始,因此需要特别判断
// 当数据不满一屏的时候,不进行列表倒置
// #ifdef APP-NVUE
cellStyle = this.useChatRecordMode ? { transform: this.isFirstPageAndNoMore ? 'scaleY(1)' : 'scaleY(-1)' } : {};
// #endif
this.$emit('update:cellStyle', cellStyle);
this.$emit('cellStyleChange', cellStyle);
// 在聊天记录模式中,如果列表没有倒置并且当前是第一页,则需要自动滚动到最底部
this.$nextTick(() => {
if (this.isFirstPage && this.isChatRecordModeAndNotInversion) {
this.$nextTick(() => {
// 这里多次触发滚动到底部是为了避免在某些情况下,即使是在nextTick但是cell未渲染完毕导致滚动到底部位置不正确的问题
this._scrollToBottom(false);
u.delay(() => {
this._scrollToBottom(false);
u.delay(() => {
this._scrollToBottom(false);
}, 50)
}, 50)
})
}
})
return cellStyle;
},
// 是否是聊天记录列表并且有配置transform
isChatRecordModeHasTransform() {
return this.useChatRecordMode && this.chatRecordRotateStyle && this.chatRecordRotateStyle.transform;
},
// 是否是聊天记录列表并且列表未倒置
isChatRecordModeAndNotInversion() {
return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(1)';
},
// 是否是聊天记录列表并且列表倒置
isChatRecordModeAndInversion() {
return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(-1)';
},
// 最终的聊天记录模式中底部安全区域的高度,如果开启了底部安全区域并且键盘未弹出,则添加底部区域高度
chatRecordModeSafeAreaBottom() {
return this.safeAreaInsetBottom && !this.keyboardHeight ? this.safeAreaBottom : 0;
}
},
mounted() {
// 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持)
// #ifndef H5 || MP-BAIDU || MP-TOUTIAO
if (this.useChatRecordMode) {
uni.onKeyboardHeightChange(this._handleKeyboardHeightChange);
}
// #endif
},
methods: {
// 添加聊天记录
addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) {
if (!this.useChatRecordMode) return;
this.isTotalChangeFromAddData = true;
this.addDataFromTop(data, toBottom, toBottomWithAnimate);
},
// 手动触发滚动到顶部加载更多,聊天记录模式时有效
doChatRecordLoadMore() {
this.useChatRecordMode && this._onLoadingMore('click');
},
// 处理键盘高度变化
_handleKeyboardHeightChange(res) {
this.$emit('keyboardHeightChange', res);
if (this.autoAdjustPositionWhenChat) {
this.isKeyboardHeightChanged = true;
this.keyboardHeight = res.height > 0 ? res.height + this.finalChatAdjustPositionOffset : res.height;
}
if (this.autoToBottomWhenChat && this.keyboardHeight > 0) {
u.delay(() => {
this.scrollToBottom(false);
u.delay(() => {
this.scrollToBottom(false);
})
})
}
}
}
}
// [z-paging]通用布局相关模块
// #ifdef APP-NVUE
const weexDom = weex.requireModule('dom');
// #endif
export default {
data() {
return {
systemInfo: null,
cssSafeAreaInsetBottom: -1,
isReadyDestroy: false,
}
},
computed: {
// 顶部可用距离
windowTop() {
if (!this.systemInfo) return 0;
// 暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634
// 感谢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;
},
// 是否是比较老的webview,在一些老的webview中,需要进行一些特殊处理
isOldWebView() {
// #ifndef APP-NVUE || MP-KUAISHOU
try {
const systemInfos = uni.getSystemInfoSync().system.split(' ');
const deviceType = systemInfos[0];
const version = parseInt(systemInfos[1]);
if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
return true;
}
} catch(e) {
return false;
}
// #endif
return false;
},
// 当前组件的$slots,兼容不同平台
zSlots() {
// #ifdef VUE2
// #ifdef MP-ALIPAY
return this.$slots;
// #endif
return this.$scopedSlots || this.$slots;
// #endif
return this.$slots;
},
},
beforeDestroy() {
this.isReadyDestroy = true;
},
// #ifdef VUE3
unmounted() {
this.isReadyDestroy = true;
},
// #endif
methods: {
// 更新fixed模式下z-paging的布局
updateFixedLayout() {
this.fixed && this.$nextTick(() => {
this.systemInfo = uni.getSystemInfoSync();
})
},
// 获取节点尺寸
_getNodeClientRect(select, inDom = true, scrollOffset = false) {
if (this.isReadyDestroy) {
return Promise.resolve(false);
};
// nvue中获取节点信息
// #ifdef APP-NVUE
select = select.replace(/[.|#]/g, '');
const ref = this.$refs[select];
return new Promise((resolve, reject) => {
if (ref) {
weexDom.getComponentRect(ref, option => {
resolve(option && option.result ? [option.size] : false);
})
} else {
resolve(false);
}
});
return;
// #endif
// vue中获取节点信息
//#ifdef MP-ALIPAY
inDom = false;
//#endif
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距离(直接通过systemInfo在部分平台上无法获取到底部安全区域)
_getCssSafeAreaInsetBottom(success) {
this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => {
this.cssSafeAreaInsetBottom = res ? res[0].height : -1;
res && success && 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: u.gc('emptyViewStyle', {})
},
// 空数据图容器样式
emptyViewSuperStyle: {
type: Object,
default: u.gc('emptyViewSuperStyle', {})
},
// 空数据图img样式
emptyViewImgStyle: {
type: Object,
default: u.gc('emptyViewImgStyle', {})
},
// 空数据图描述文字样式
emptyViewTitleStyle: {
type: Object,
default: u.gc('emptyViewTitleStyle', {})
},
// 空数据图重新加载按钮样式
emptyViewReloadStyle: {
type: Object,
default: u.gc('emptyViewReloadStyle', {})
},
// 空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging
emptyViewFixed: {
type: Boolean,
default: u.gc('emptyViewFixed', false)
},
// 空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效
emptyViewCenter: {
type: Boolean,
default: u.gc('emptyViewCenter', true)
},
// 加载中时是否自动隐藏空数据图,默认为是
autoHideEmptyViewWhenLoading: {
type: Boolean,
default: u.gc('autoHideEmptyViewWhenLoading', true)
},
// 用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是
autoHideEmptyViewWhenPull: {
type: Boolean,
default: u.gc('autoHideEmptyViewWhenPull', true)
},
// 空数据view的z-index,默认为9
emptyViewZIndex: {
type: Number,
default: u.gc('emptyViewZIndex', 9)
},
},
data() {
return {
customerEmptyViewErrorText: ''
}
},
computed: {
finalEmptyViewImg() {
return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg;
},
finalShowEmptyViewReload() {
return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload;
},
// 是否展示空数据图
showEmpty() {
if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false;
if (this.autoHideEmptyViewWhenLoading) {
if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true;
} else {
return true;
}
return !this.autoHideEmptyViewWhenPull && !this.isUserReload;
},
},
methods: {
// 点击了空数据view重新加载按钮
_emptyViewReload() {
let callbacked = false;
this.$emit('emptyViewReload', reload => {
if (reload === undefined || reload === true) {
this.fromEmptyViewReload = true;
this.reload().catch(() => {});
}
callbacked = true;
});
// 如果用户没有禁止默认的点击重新加载刷新列表事件,则触发列表重新刷新
this.$nextTick(() => {
if (!callbacked) {
this.fromEmptyViewReload = true;
this.reload().catch(() => {});
}
})
},
// 点击了空数据view
_emptyViewClick() {
this.$emit('emptyViewClick');
},
}
}
\ No newline at end of file
// [z-paging]i18n模块
import { initVueI18n } from '@dcloudio/uni-i18n'
import messages from '../../i18n/index.js'
const { t } = initVueI18n(messages)
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import interceptor from '../z-paging-interceptor'
const language = uni.getSystemInfoSync().language;
export default {
data() {
return {
language
}
},
computed: {
finalLanguage() {
try {
const local = uni.getLocale();
const language = this.language;
return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local;
} catch (e) {
// 如果获取系统本地语言异常,则默认返回中文,uni.getLocale在部分低版本HX或者cli中可能报找不到的问题
return 'zh-Hans';
}
},
// 最终的下拉刷新默认状态的文字
finalRefresherDefaultText() {
return this._getI18nText('zp.refresher.default', this.refresherDefaultText);
},
// 最终的下拉刷新下拉中的文字
finalRefresherPullingText() {
return this._getI18nText('zp.refresher.pulling', this.refresherPullingText);
},
// 最终的下拉刷新中文字
finalRefresherRefreshingText() {
return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText);
},
// 最终的下拉刷新完成文字
finalRefresherCompleteText() {
return this._getI18nText('zp.refresher.complete', this.refresherCompleteText);
},
// 最终的下拉刷新上次更新时间文字
finalRefresherUpdateTimeTextMap() {
return {
title: t('zp.refresherUpdateTime.title'),
none: t('zp.refresherUpdateTime.none'),
today: t('zp.refresherUpdateTime.today'),
yesterday: t('zp.refresherUpdateTime.yesterday')
};
},
// 最终的继续下拉进入二楼文字
finalRefresherGoF2Text() {
return this._getI18nText('zp.refresher.f2', this.refresherGoF2Text);
},
// 最终的底部加载更多默认状态文字
finalLoadingMoreDefaultText() {
return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText);
},
// 最终的底部加载更多加载中文字
finalLoadingMoreLoadingText() {
return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText);
},
// 最终的底部加载更多没有更多数据文字
finalLoadingMoreNoMoreText() {
return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText);
},
// 最终的底部加载更多加载失败文字
finalLoadingMoreFailText() {
return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText);
},
// 最终的空数据图title
finalEmptyViewText() {
return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText);
},
// 最终的空数据图reload title
finalEmptyViewReloadText() {
return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText);
},
// 最终的空数据图加载失败文字
finalEmptyViewErrorText() {
return this.customerEmptyViewErrorText || this._getI18nText('zp.emptyView.error', this.emptyViewErrorText);
},
// 最终的系统loading title
finalSystemLoadingText() {
return this._getI18nText('zp.systemLoading.title', this.systemLoadingText);
},
},
methods: {
// 获取当前z-paging的语言
getLanguage() {
return this.finalLanguage;
},
// 获取国际化转换后的文本
_getI18nText(key, value) {
const dataType = Object.prototype.toString.call(value);
if (dataType === '[object Object]') {
const nextValue = value[this.finalLanguage];
if (nextValue) return nextValue;
} else if (dataType === '[object String]') {
return value;
}
return t(key);
},
// 系统language转i18n local
_language2Local(language) {
const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-');
if (formatedLanguage.indexOf('zh') !== -1) {
if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) {
return 'zh-Hans';
}
return 'zh-Hant';
}
if (formatedLanguage.indexOf('en') !== -1) return 'en';
return language;
}
}
}
// [z-paging]loading相关模块
import u from '.././z-paging-utils'
import Enum from '.././z-paging-enum'
export default {
props: {
// 第一次加载后自动隐藏loading slot,默认为是
autoHideLoadingAfterFirstLoaded: {
type: Boolean,
default: u.gc('autoHideLoadingAfterFirstLoaded', true)
},
// loading slot是否铺满屏幕并固定,默认为否
loadingFullFixed: {
type: Boolean,
default: u.gc('loadingFullFixed', false)
},
// 是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。
autoShowSystemLoading: {
type: Boolean,
default: u.gc('autoShowSystemLoading', false)
},
// 显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效)
systemLoadingMask: {
type: Boolean,
default: u.gc('systemLoadingMask', true)
},
// 显示系统Loading时显示的文字,默认为"加载中"
systemLoadingText: {
type: [String, Object],
default: u.gc('systemLoadingText', null)
},
},
data() {
return {
loading: false,
loadingForNow: false,
}
},
watch: {
// loading状态
loadingStatus(newVal) {
this.$emit('loadingStatusChange', newVal);
this.$nextTick(() => {
this.loadingStatusAfterRender = newVal;
})
if (this.useChatRecordMode) {
if (this.isFirstPage && (newVal === Enum.More.NoMore || newVal === Enum.More.Fail)) {
this.isFirstPageAndNoMore = true;
return;
}
}
this.isFirstPageAndNoMore = false;
},
loading(newVal){
if (newVal) {
this.loadingForNow = newVal;
}
},
},
computed: {
// 是否显示loading
showLoading() {
if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false;
if (this.finalShowSystemLoading) {
// 显示系统loading
uni.showLoading({
title: this.finalSystemLoadingText,
mask: this.systemLoadingMask
})
}
return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher;
},
// 最终的是否显示系统loading
finalShowSystemLoading() {
return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher;
}
},
methods: {
// 处理开始加载更多状态
_startLoading(isReload = false) {
if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) {
this.loadingStatus = Enum.More.Loading;
}
this.loading = true;
},
// 停止系统loading和refresh
_endSystemLoadingAndRefresh(){
this.finalShowSystemLoading && uni.hideLoading();
!this.useCustomRefresher && uni.stopPullDownRefresh();
// #ifdef APP-NVUE
this.usePageScroll && uni.stopPullDownRefresh();
// #endif
}
}
}
// [z-paging]nvue独有部分模块
import u from '.././z-paging-utils'
import c from '.././z-paging-constant'
import Enum from '.././z-paging-enum'
// #ifdef APP-NVUE
const weexAnimation = weex.requireModule('animation');
// #endif
export default {
props: {
// #ifdef APP-NVUE
// nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list
nvueListIs: {
type: String,
default: u.gc('nvueListIs', 'list')
},
// nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall
nvueWaterfallConfig: {
type: Object,
default: u.gc('nvueWaterfallConfig', {})
},
// nvue 控制是否回弹效果,iOS不支持动态修改
nvueBounce: {
type: Boolean,
default: u.gc('nvueBounce', true)
},
// nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否
nvueFastScroll: {
type: Boolean,
default: u.gc('nvueFastScroll', false)
},
// nvue中list的id
nvueListId: {
type: String,
default: u.gc('nvueListId', '')
},
// nvue中refresh组件的样式
nvueRefresherStyle: {
type: Object,
default: u.gc('nvueRefresherStyle', {})
},
// nvue中是否按分页模式(类似竖向swiper)显示List,默认为false
nvuePagingEnabled: {
type: Boolean,
default: u.gc('nvuePagingEnabled', false)
},
// 是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否
hideNvueBottomTag: {
type: Boolean,
default: u.gc('hideNvueBottomTag', false)
},
// nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能
offsetAccuracy: {
type: Number,
default: u.gc('offsetAccuracy', 10)
},
// #endif
},
data() {
return {
nRefresherLoading: false,
nListIsDragging: false,
nShowBottom: true,
nFixFreezing: false,
nShowRefresherReveal: false,
nLoadingMoreFixedHeight: false,
nShowRefresherRevealHeight: 0,
nOldShowRefresherRevealHeight: -1,
nRefresherWidth: uni.upx2px(750),
nF2Opacity: 0
}
},
computed: {
// #ifdef APP-NVUE
nScopedSlots() {
// #ifdef VUE2
return this.$scopedSlots;
// #endif
// #ifdef VUE3
return null;
// #endif
},
nWaterfallColumnCount() {
if (this.finalNvueListIs !== 'waterfall') return 0;
return this._nGetWaterfallConfig('column-count', 2);
},
nWaterfallColumnWidth() {
return this._nGetWaterfallConfig('column-width', 'auto');
},
nWaterfallColumnGap() {
return this._nGetWaterfallConfig('column-gap', 'normal');
},
nWaterfallLeftGap() {
return this._nGetWaterfallConfig('left-gap', 0);
},
nWaterfallRightGap() {
return this._nGetWaterfallConfig('right-gap', 0);
},
nViewIs() {
const is = this.finalNvueListIs;
return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell';
},
nSafeAreaBottomHeight() {
return this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
},
finalNvueListIs() {
if (this.usePageScroll) return 'view';
const nvueListIsLowerCase = this.nvueListIs.toLowerCase();
if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase;
return 'list';
},
finalNvueSuperListIs() {
return this.usePageScroll ? 'view' : 'scroller';
},
finalNvueRefresherEnabled() {
return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode;
},
// #endif
},
mounted(){
// #ifdef APP-NVUE
//旋转屏幕时更新宽度
uni.onWindowResize((res) => {
// this._nUpdateRefresherWidth();
})
// #endif
},
methods: {
// #ifdef APP-NVUE
// 列表滚动时触发
_nOnScroll(e) {
this.$emit('scroll', e);
const contentOffsetY = -e.contentOffset.y;
this.oldScrollTop = contentOffsetY;
this.nListIsDragging = e.isDragging;
this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1);
},
// 列表滚动结束
_nOnScrollend(e) {
this.$emit('scrollend', e);
},
// 下拉刷新刷新中
_nOnRrefresh() {
if (this.nShowRefresherReveal) return;
// 进入刷新状态
this.nRefresherLoading = true;
if (this.refresherStatus === Enum.Refresher.GoF2) {
this._handleGoF2();
this.$nextTick(() => {
this._nRefresherEnd();
})
} else {
this.refresherStatus = Enum.Refresher.Loading;
this._doRefresherLoad();
}
},
// 下拉刷新下拉中
_nOnPullingdown(e) {
if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return;
this._emitTouchmove(e);
let { viewHeight, pullingDistance } = e;
// 更新下拉刷新状态
// 下拉刷新距离超过阈值
if (pullingDistance >= viewHeight) {
// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
// (pullingDistance - viewHeight) + this.finalRefresherThreshold 不等同于pullingDistance,此处是为了兼容不同平台下拉相同距离pullingDistance不一致的问题,pullingDistance仅与viewHeight互相关联
this.refresherStatus = this.refresherF2Enabled && (pullingDistance - viewHeight) + this.finalRefresherThreshold >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
} else {
// 下拉刷新距离未超过阈值,显示默认状态
this.refresherStatus = Enum.Refresher.Default;
}
},
// 下拉刷新结束
_nRefresherEnd(doEnd = true) {
if (doEnd) {
this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight);
!this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore();
this.nRefresherLoading = false;
}
},
// 执行主动触发下拉刷新动画
_nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) {
// 清除下拉刷新相关timeout
this._cleanRefresherCompleteTimeout();
this._cleanRefresherEndTimeout();
if (!this.finalShowRefresherWhenReload) {
// 如果reload不需要自动展示下拉刷新view,则在complete duration结束后再把下拉刷新状态设置回默认
this.refresherEndTimeout = u.delay(() => {
this.refresherStatus = Enum.Refresher.Default;
}, this.refresherCompleteDuration);
return;
}
// 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可
const stackCount = this.refresherRevealStackCount;
if (height === 0 && checkStack) {
this.refresherRevealStackCount --;
if (stackCount > 1) return;
this.refresherEndTimeout = u.delay(() => {
this.refresherStatus = Enum.Refresher.Default;
}, this.refresherCompleteDuration);
}
if (stackCount > 1) {
this.refresherStatus = Enum.Refresher.Loading;
}
const duration = animate ? 200 : 0;
if (this.nOldShowRefresherRevealHeight !== height) {
if (height > 0) {
this.nShowRefresherReveal = true;
}
// 展示下拉刷新view
weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], {
styles: {
height: `${height}px`,
transform: `translateY(${translateY}px)`,
},
duration,
timingFunction: 'linear',
needLayout: true,
delay: 0
})
}
u.delay(() => {
if (animate) {
this.nShowRefresherReveal = height > 0;
}
}, duration > 0 ? duration - 60 : 0);
this.nOldShowRefresherRevealHeight = height;
},
// 滚动到底部加载更多
_nOnLoadmore() {
if (this.nShowRefresherReveal || !this.totalData.length) return;
this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom');
},
// 获取nvue waterfall单项配置
_nGetWaterfallConfig(key, defaultValue) {
return this.nvueWaterfallConfig[key] || defaultValue;
},
// 更新nvue 下拉刷新view容器的宽度
_nUpdateRefresherWidth() {
u.delay(() => {
this.$nextTick(()=>{
this._getNodeClientRect('.zp-n-list').then(node => {
if (node) {
this.nRefresherWidth = node[0].width || this.nRefresherWidth;
}
})
})
})
}
// #endif
}
}
// [z-paging]常量
export default {
// 当前版本号
version: '2.7.9',
// 延迟操作的通用时间
delayTime: 100,
// 请求失败时候全局emit使用的key
errorUpdateKey: 'z-paging-error-emit',
// 全局emit complete的key
completeUpdateKey: 'z-paging-complete-emit',
// z-paging缓存的前缀key
cachePrefixKey: 'z-paging-cache',
// 虚拟列表中列表index的key
listCellIndexKey: 'zp_index',
// 虚拟列表中列表的唯一key
listCellIndexUniqueKey: 'zp_unique_index'
}
// [z-paging]枚举
export default {
// 当前加载类型 0.下拉刷新 1.上拉加载更多
LoadingType: {
Refresher: 0,
LoadingMore: 1
},
// 下拉刷新状态 0.默认状态 1.松手立即刷新 2.刷新中 3.刷新结束 4.松手进入二楼
Refresher: {
Default: 0,
ReleaseToRefresh: 1,
Loading: 2,
Complete: 3,
GoF2: 4
},
// 底部加载更多状态 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'
}
}
\ No newline at end of file
// [z-paging]拦截器
const queryKey = 'Query';
const fetchParamsKey = 'FetchParams';
const fetchResultKey = 'FetchResult';
const language2LocalKey = 'Language2Local';
// 拦截&处理@query事件
function handleQuery(callback) {
_addHandleByKey(queryKey, callback);
return this;
}
// 拦截&处理@query事件(私有,请勿调用)
function _handleQuery(pageNo, pageSize, from, lastItem) {
const callback = _getHandleByKey(queryKey);
return callback ? callback(pageNo, pageSize, from, lastItem) : [pageNo, pageSize, from];
}
// 拦截&处理:fetch参数
function handleFetchParams(callback) {
_addHandleByKey(fetchParamsKey, callback);
return this;
}
// 拦截&处理:fetch参数(私有,请勿调用)
function _handleFetchParams(parmas, extraParams) {
const callback = _getHandleByKey(fetchParamsKey);
return callback ? callback(parmas, extraParams || {}) : { pageNo: parmas.pageNo, pageSize: parmas.pageSize, ...(extraParams || {}) };
}
// 拦截&处理:fetch结果
function handleFetchResult(callback) {
_addHandleByKey(fetchResultKey, callback);
return this;
}
// 拦截&处理:fetch结果(私有,请勿调用)
function _handleFetchResult(result, paging, params) {
const callback = _getHandleByKey(fetchResultKey);
callback && callback(result, paging, params);
return callback ? true : false;
}
// 拦截&处理系统language转i18n local
function handleLanguage2Local(callback) {
_addHandleByKey(language2LocalKey, callback);
return this;
}
// 拦截&处理系统language转i18n local(私有,请勿调用)
function _handleLanguage2Local(language, local) {
const callback = _getHandleByKey(language2LocalKey);
return callback ? callback(language, local) : local;
}
// 获取当前app对象
function _getApp(){
// #ifndef APP-NVUE
return getApp();
// #endif
// #ifdef APP-NVUE
return getApp({ allowDefault: true });
// #endif
}
// 添加处理函数
function _addHandleByKey(key, callback) {
try {
setTimeout(function() {
_getApp().globalData[`zp_handle${key}Callback`] = callback;
}, 1);
} catch (_) {}
}
// 获取处理回调函数
function _getHandleByKey(key) {
return _getApp().globalData[`zp_handle${key}Callback`];
}
export default {
handleQuery,
_handleQuery,
handleFetchParams,
_handleFetchParams,
handleFetchResult,
_handleFetchResult,
handleLanguage2Local,
_handleLanguage2Local
};
// [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]工具类
import zLocalConfig from '../config/index'
import c from './z-paging-constant'
const storageKey = 'Z-PAGING-REFRESHER-TIME-STORAGE-KEY';
let config = null;
let configLoaded = false;
const timeoutMap = {};
// 获取默认配置信息
function gc(key, defaultValue) {
// 这里return一个函数以解决在vue3+appvue中,props默认配置读取在main.js之前执行导致uni.$zp全局配置无效的问题。相当于props的default中传入一个带有返回值的函数
return () => {
// 处理z-paging全局配置
_handleDefaultConfig();
// 如果全局配置不存在,则返回默认值
if (!config) return defaultValue;
const value = config[key];
// 如果全局配置存在但对应的配置项不存在,则返回默认值;反之返回配置项
return value === undefined ? defaultValue : value;
};
}
// 获取最终的touch位置
function getTouch(e) {
let touch = null;
if (e.touches && e.touches.length) {
touch = e.touches[0];
} else if (e.changedTouches && e.changedTouches.length) {
touch = e.changedTouches[0];
} else if (e.datail && e.datail != {}) {
touch = e.datail;
} else {
return { touchX: 0, touchY: 0 }
}
return {
touchX: touch.clientX,
touchY: touch.clientY
};
}
// 判断当前手势是否在z-paging内触发
function getTouchFromZPaging(target) {
if (target && target.tagName && target.tagName !== 'BODY' && target.tagName !== 'UNI-PAGE-BODY') {
const classList = target.classList;
if (classList && classList.contains('z-paging-content')) {
// 此处额外记录当前z-paging是否是页面滚动、是否滚动到了顶部、是否是聊天记录模式以传给renderjs。避免不同z-paging组件renderjs内部判断数据互相影响导致的各种问题
return {
isFromZp: true,
isPageScroll: classList.contains('z-paging-content-page'),
isReachedTop: classList.contains('z-paging-reached-top'),
isUseChatRecordMode: classList.contains('z-paging-use-chat-record-mode')
};
} else {
return getTouchFromZPaging(target.parentNode);
}
} else {
return { isFromZp: false };
}
}
// 递归获取z-paging所在的parent,如果查找不到则返回null
function getParent(parent) {
if (!parent) return null;
if (parent.$refs.paging) return parent;
return getParent(parent.$parent);
}
// 打印错误信息
function consoleErr(err) {
console.error(`[z-paging]${err}`);
}
// 延时操作,如果key存在,调用时清除对应key之前的延时操作
function delay(callback, ms = c.delayTime, key) {
const timeout = setTimeout(callback, ms);;
if (!!key) {
timeoutMap[key] && clearTimeout(timeoutMap[key]);
timeoutMap[key] = timeout;
}
return timeout;
}
// 设置下拉刷新时间
function setRefesrherTime(time, key) {
const datas = getRefesrherTime() || {};
datas[key] = time;
uni.setStorageSync(storageKey, datas);
}
// 获取下拉刷新时间
function getRefesrherTime() {
return uni.getStorageSync(storageKey);
}
// 通过下拉刷新标识key获取下拉刷新时间
function getRefesrherTimeByKey(key) {
const datas = getRefesrherTime();
return datas && datas[key] ? datas[key] : null;
}
// 通过下拉刷新标识key获取下拉刷新时间(格式化之后)
function getRefesrherFormatTimeByKey(key, textMap) {
const time = getRefesrherTimeByKey(key);
const timeText = time ? _timeFormat(time, textMap) : textMap.none;
return `${textMap.title}${timeText}`;
}
// 将文本的px或者rpx转为px的值
function convertToPx(text) {
const dataType = Object.prototype.toString.call(text);
if (dataType === '[object Number]') return text;
let isRpx = false;
if (text.indexOf('rpx') !== -1 || text.indexOf('upx') !== -1) {
text = text.replace('rpx', '').replace('upx', '');
isRpx = true;
} else if (text.indexOf('px') !== -1) {
text = text.replace('px', '');
}
if (!isNaN(text)) {
if (isRpx) return Number(uni.upx2px(text));
return Number(text);
}
return 0;
}
// 获取当前时间
function getTime() {
return (new Date()).getTime();
}
// 获取z-paging实例id,随机生成10位数字+字母
function getInstanceId() {
const s = [];
const hexDigits = "0123456789abcdef";
for (let i = 0; i < 10; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
return s.join('') + getTime();
}
// 等待一段时间
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// 是否是promise
function isPromise(func) {
return Object.prototype.toString.call(func) === '[object Promise]';
}
// 添加单位
function addUnit(value, unit) {
if (Object.prototype.toString.call(value) === '[object String]') {
let tempValue = value;
tempValue = tempValue.replace('rpx', '').replace('upx', '').replace('px', '');
if (value.indexOf('rpx') === -1 && value.indexOf('upx') === -1 && value.indexOf('px') !== -1) {
tempValue = parseFloat(tempValue) * 2;
}
value = tempValue;
}
return unit === 'rpx' ? value + 'rpx' : (value / 2) + 'px';
}
// 深拷贝
function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
let newObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
// ------------------ 私有方法 ------------------------
// 处理全局配置
function _handleDefaultConfig() {
// 确保只加载一次全局配置
if (configLoaded) return;
// 优先从config.js中读取
if (zLocalConfig && Object.keys(zLocalConfig).length) {
config = zLocalConfig;
}
// 如果在config.js中读取不到,则尝试到uni.$zp读取
if (!config && uni.$zp) {
config = uni.$zp.config;
}
// 将config中的短横线写法全部转为驼峰写法,使得读取配置时可以直接通过key去匹配,而非读取每个配置时候再去转,减少不必要的性能开支
config = config ? Object.keys(config).reduce((result, key) => {
result[_toCamelCase(key)] = config[key];
return result;
}, {}) : null;
configLoaded = true;
}
// 时间格式化
function _timeFormat(time, textMap) {
const date = new Date(time);
const currentDate = new Date();
// 设置time对应的天,去除时分秒,使得可以直接比较日期
const dateDay = new Date(time).setHours(0, 0, 0, 0);
// 设置当前的天,去除时分秒,使得可以直接比较日期
const currentDateDay = new Date().setHours(0, 0, 0, 0);
const disTime = dateDay - currentDateDay;
let dayStr = '';
const timeStr = _dateTimeFormat(date);
if (disTime === 0) {
dayStr = textMap.today;
} else if (disTime === -86400000) {
dayStr = textMap.yesterday;
} else {
dayStr = _dateDayFormat(date, date.getFullYear() !== currentDate.getFullYear());
}
return `${dayStr} ${timeStr}`;
}
// date格式化为年月日
function _dateDayFormat(date, showYear = true) {
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return showYear ? `${year}-${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}` : `${_fullZeroToTwo(month)}-${_fullZeroToTwo(day)}`;
}
// data格式化为时分
function _dateTimeFormat(date) {
const hour = date.getHours();
const minute = date.getMinutes();
return `${_fullZeroToTwo(hour)}:${_fullZeroToTwo(minute)}`;
}
// 不满2位在前面填充0
function _fullZeroToTwo(str) {
str = str.toString();
return str.length === 1 ? '0' + str : str;
}
// 驼峰转短横线
function _toKebab(value) {
return value.replace(/([A-Z])/g, "-$1").toLowerCase();
}
// 短横线转驼峰
function _toCamelCase(value) {
return value.replace(/-([a-z])/g, (_, group1) => group1.toUpperCase());
}
export default {
gc,
setRefesrherTime,
getRefesrherFormatTimeByKey,
getTouch,
getTouchFromZPaging,
getParent,
convertToPx,
getTime,
getInstanceId,
consoleErr,
delay,
wait,
isPromise,
addUnit,
deepCopy
};
// [z-paging]使用renderjs在app-vue和h5中对touchmove事件冒泡进行处理
import u from '../js/z-paging-utils'
const data = {
startY: 0,
isTouchFromZPaging: false,
isUsePageScroll: false,
isReachedTop: true,
isIosAndH5: false,
useChatRecordMode: false,
appLaunched: false
}
export default {
mounted() {
if (window) {
this._handleTouch();
// #ifdef APP-VUE
this.$ownerInstance.callMethod('_handlePageLaunch');
// #endif
}
},
methods: {
// 接收逻辑层发送的数据(是否是ios+h5)
renderPropIsIosAndH5Change(newVal) {
if (newVal === -1) return;
data.isIosAndH5 = newVal;
},
// 拦截处理touch事件
_handleTouch() {
if (!window.$zPagingRenderJsInited) {
window.$zPagingRenderJsInited = true;
window.addEventListener('touchstart', this._handleTouchstart, { passive: true })
window.addEventListener('touchmove', this._handleTouchmove, { passive: false })
}
},
// 处理touch开始
_handleTouchstart(e) {
const touch = u.getTouch(e);
data.startY = touch.touchY;
const touchResult = u.getTouchFromZPaging(e.target);
data.isTouchFromZPaging = touchResult.isFromZp;
data.isUsePageScroll = touchResult.isPageScroll;
data.isReachedTop = touchResult.isReachedTop;
data.useChatRecordMode = touchResult.isUseChatRecordMode;
},
// 处理touch中
_handleTouchmove(e) {
const touch = u.getTouch(e);
const moveY = touch.touchY - data.startY;
// 如果是在z-paging内触摸并且(是在顶部位置且是下拉的情况下(或不是聊天记录滚动模式并且在iOS+h5+scroll-view并且是往上拉的情况:避免在此平台中滚动到底部后上拉有个系统灰色遮罩导致列表被短暂锁定的问题))
// (data.useChatRecordMode ? moveY < 0 : moveY > 0)是为了判断是否是上拉的情况,聊天记录模式列表倒置,因此moveY < 0为上拉
if (data.isTouchFromZPaging && ((data.isReachedTop && (data.useChatRecordMode ? moveY < 0 : moveY > 0)) || (!data.useChatRecordMode && data.isIosAndH5 && !data.isUsePageScroll && moveY < 0))) {
if (e.cancelable && !e.defaultPrevented) {
// 阻止事件冒泡,以避免在一些平台中下拉刷新时整个page跟着一起下拉&在iOS+h5+scroll-view中在底部上拉有个系统灰色遮罩导致列表被短暂锁定的问题
e.preventDefault();
}
}
},
// 移除touch相关事件监听
_removeAllEventListener(){
window.removeEventListener('touchstart');
window.removeEventListener('touchmove');
}
}
};
{
"id": "z-paging",
"name": "z-paging",
"displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,分页全自动处理",
"version": "2.7.10",
"description": "超简单、低耦合!使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等100+项配置",
"keywords": [
"下拉刷新",
"上拉加载",
"分页器",
"nvue",
"虚拟列表"
],
"repository": "https://github.com/SmileZXLee/uni-z-paging",
"types": "global.d.ts",
"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",
"alipay": "n"
},
"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"
}
}
}
}
}
\ No newline at end of file
# z-paging
<p align="center">
<img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;" />
</p>
[![version](https://img.shields.io/badge/version-2.7.9-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
<img height="0" width="0" src="https://api.z-notify.zxlee.cn/v1/public/statistics/8293556910106066944/addOnly?from=uni" />
`z-paging-x`现已支持uniapp x,持续完善中,插件地址👉🏻 [https://ext.dcloud.net.cn/plugin?name=z-paging-x](https://ext.dcloud.net.cn/plugin?name=z-paging-x)
### 文档地址:[https://z-paging.zxlee.cn](https://z-paging.zxlee.cn)
### 更新组件前,请注意[版本差异](https://z-paging.zxlee.cn/start/upgrade-guide.html)
***
### 功能&特点
* 【配置简单】仅需两步(绑定网络请求方法、绑定分页结果数组)轻松完成完整下拉刷新,上拉加载更多功能。
* 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。
* 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。
* 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持无闪动聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部,支持下拉进入二楼等诸多功能。
* 【全平台兼容】支持vue、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)
***
### 预览
***
| 自定义下拉刷新效果演示 | 滑动切换选项卡+吸顶演示 | 聊天记录模式演示 |
| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ |
| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo5.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo6.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo7.gif) |
| 虚拟列表(流畅渲染1万+条)演示 | 下拉进入二楼演示 | 在弹窗内使用演示 |
| :----------------------------------------------------------: | :----------------------------------------------------------: | ------------------------------------------------------------ |
| ![](https://z-paging.zxlee.cn/public/img/z-paging-demo8.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo9.gif) | ![](https://z-paging.zxlee.cn/public/img/z-paging-demo10.gif) |
### 在线demo体验地址:
* [https://demo.z-paging.zxlee.cn](https://demo.z-paging.zxlee.cn)
| 扫码体验 |
| ------------------------------------------------------------ |
| ![](https://z-paging.zxlee.cn/public/img/code.png) |
### demo下载
* 支持vue2&vue3的`选项式api`写法demo下载,请点击页面右上角的【使用HBuilderX导入示例项目】或【下载示例项目ZIP】。
* 支持vue3的`组合式api`写法demo下载,请访问[github](https://github.com/SmileZXLee/uni-z-paging)
\ No newline at end of file
## 0.2.5(2023-01-09)
修复可能出现的可能出现的与swiper联动时报错node不存在的bug
// z-tabs全局配置文件,注意避免更新时此文件被覆盖,若被覆盖,可在此文件中右键->点击本地历史记录,找回覆盖前的配置
export default {
'active-color': '#165DFF',//激活状态tab的颜色
'inactive-color': '#86909C',//未激活状态tab的颜色
'active-style': { color: '#1D2129' },//激活状态tab的样式
}
\ No newline at end of file
{
"id": "z-tabs",
"name": "z-tabs",
"displayName": "【z-tabs】一个简单轻量的tabs组件",
"version": "0.2.5",
"description": "全平台兼容,支持nvue、vue3",
"keywords": [
"tabs"
],
"repository": "https://github.com/SmileZXLee/uni-z-tabs",
"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/@zxlee/z-tabs",
"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"
}
}
}
}
}
\ No newline at end of file
# z-tabs
[![version](https://img.shields.io/badge/version-0.2.5-blue)](https://github.com/SmileZXLee/uni-z-tabs)
[![license](https://img.shields.io/github/license/SmileZXLee/uni-z-tabs)](https://en.wikipedia.org/wiki/MIT_License)
***
### 反馈qq群(点击加群):[790460711](https://jq.qq.com/?_wv=1027&k=vU2fKZZH)
***
## z-tabs文档
### 安装
#### 方式1(推荐):通过uni_modules安装,在插件市场中点击右上角【使用HbuilderX导入插件】即可。
***
#### 方式2:通过npm安装
```bash
//若项目之前未使用npm管理依赖(项目根目录下无package.json文件),先在项目根目录执行命令初始化npm工程
npm init -y
//安装
npm install @zxlee/z-tabs --save
//更新
npm update @zxlee/z-tabs
```
然后在`pages.json`中配置`easycom`(注意:下方配置只有在使用npm安装时才需要配置!!!!!)
```json
"easycom": {
"^z-tabs": "@zxlee/z-tabs/components/z-tabs/z-tabs.vue"
}
```
### 基本使用
```html
<template>
<z-tabs :list="list"></z-tabs>
</template>
<script>
export default {
data() {
return {
list: []
}
},
onLoad() {
const list = [];
for(let i = 0;i < 10;i++) {
// list内item支持字符串或对象,下方这个是字符串
list.push('tab标题');
// 如果要展示徽标数,则list中item的数据结构应为:
list.push({
name: 'tab标题',
badge: {
// 设置徽标数为6
count: 6
},
// 可以禁用某个item
disabled: true
});
}
this.list = list;
}
}
</script>
```
### props
| 参数 | 说明 | 类型 | 默认值 | 可选值 |
| :------------------ | :----------------------------------------------------------- | :------------- | :------ | :----- |
| list | 数据源数组,支持形如`['tab1','tab2']`的格式或`[{name:'tab1',value:1}]`的格式 | Array | [] | - |
| current | 当前选中的index | Number\|String | 0 | - |
| scroll-count | list数组长度超过scrollCount时滚动显示(不自动铺满全屏) | Number\|String | 5 | - |
| tab-width | 自定义每个tab的宽度,默认为0,即代表根据内容自动撑开,单位rpx,支持传100、"100px"或"100rpx" | Number\|String | 0 | 0 |
| bar-width | 滑块宽度,单位rpx,支持传100、"100px"或"100rpx" | Number\|String | 45rpx | - |
| bar-height | 滑块高度,单位rpx,支持传100、"100px"或"100rpx" | Number\|String | 8rpx | - |
| bar-style | 滑块样式,其中的`width``height`将被`bar-width``bar-height`覆盖 | Object | {} | - |
| bottom-space | tabs与底部的间距,单位rpx,支持传100、"100px"或"100rpx" | Number\|String | 8rpx | - |
| bar-animate-mode | 【v0.1.5起支持】切换tab时滑块动画模式,与`swiper`联动时有效,点击切换tab时无效,必须调用`setDx`。默认为`line`,即切换tab时滑块宽度保持不变,线性运动。可选值为`worm`,即为类似毛毛虫蠕动效果 | String | line | worm |
| name-key | list中item的name(标题)的key | String | name | - |
| value-key | list中item的value的key | String | value | - |
| active-color | 激活状态tab的颜色 | String | #007AFF | - |
| inactive-color | 未激活状态tab的颜色 | String | #666666 | - |
| disabled-color | 禁用状态tab的颜色 | String | #bbbbbb | - |
| active-style | 激活状态tab的样式 | Object | {} | - |
| inactive-style | 未激活状态tab的样式 | Object | {} | - |
| disabled-style | 禁用状态tab的样式 | Object | {} | - |
| badge-max-count | 徽标数最大数字限制,超过这个数字将变成`badge-max-count`+ | Number\|String | 99 | - |
| badge-style | 徽标样式,例如可自定义背景色,字体等等 | Object | {} | - |
| bg-color | z-tabs背景色 | String | white | - |
| tabs-style | z-tabs样式 | Object | {} | - |
| init-trigger-change | 初始化时是否自动触发change事件 | Boolean | true | false |
### events
| 事件名 | 说明 | 回调参数 |
| ------------ | -------------------- | ------------------------------------------------------------ |
| @change | tabs改变(点击)时触发 | `参数1`:index(当前切换到的index);<br/>`参数2`:value(当前切换到的value) |
| @secondClick | tabs二次点击时触发 | `参数1`:index(当前切换到的index);<br/>`参数2`:value(当前切换到的value) |
### methods
| 方法名 | 说明 | 参数 |
| ------------------- | ------------------------------------------------------------ | -------------------------------------- |
| setDx | 根据swiper的`@transition`实时更新底部dot位置 | swiper的`@transition`中的`e.detail.dx` |
| unlockDx | 在swiper的`@animationfinish`中通知`z-tabs`结束多`setDx`的锁定,若在父组件中调用了`setDx`,则必须调用`unlockDx` | - |
| updateSubviewLayout | 在nvue+安卓中,若在cell中使用`z-tabs`,且页面加载时cell在屏幕之外,因cell的复用机制,可能导致`z-tabs`内部的布局失效:例如底部bar无法显示,此时可在list滚动到一定区域内(例如快显示`z-tabs`)的时候调用此方法以更新其内部布局。其他情况无需调用! | - |
### slots
| 名称 | 说明 |
| :---- | ------------ |
| left | tabs左侧插槽 |
| right | tabs右侧插槽 |
### 支持全局配置
*`/z-tabs/components/z-tabs/config/index.js`文件中进行配置
```js
export default {
'active-color': 'red'
}
```
### 【v0.1.4起支持】底部dot与swiper联动演示
```html
<template>
<z-tabs ref="tabs" :list="tabList" :current="current" @change="tabsChange" />
<swiper :current="current" @transition="swiperTransition" @animationfinish="swiperAnimationfinish">
<swiper-item class="swiper-item" v-for="(item, index) in tabList" :key="index">
xxx
</swiper-item>
</swiper>
<template>
<script>
export default {
data() {
return {
tabList: ['测试1','测试2','测试3','测试4'],
current: 0, // tabs组件的current值,表示当前活动的tab选项
};
},
methods: {
//tabs通知swiper切换
tabsChange(index) {
this.current = index;
},
//swiper滑动中
swiperTransition(e) {
this.$refs.tabs.setDx(e.detail.dx);
},
//swiper滑动结束
swiperAnimationfinish(e) {
this.current = e.detail.current;
this.$refs.tabs.unlockDx();
}
}
}
</script>
```
\ No newline at end of file
......@@ -112,7 +112,6 @@ export const http = (options) => {
// 401错误 -> 清理用户信息,跳转到登录页
const userStore = useUserStore()
userStore.handleLogOut()
uni.navigateTo({ url: '/pages/login/index' })
reject(res)
} else {
// 其他错误 -> 根据后端错误信息轻提示
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment