mirror of
https://gitee.com/TSpecific/tuniao-ui.git
synced 2026-03-14 19:24:00 +08:00
新增10+页面模板
新增form表单部分组件的演示代码 新增图标 添加第三方开发插件演示(所属权归属第三方) 新增tn-scroll-view下拉组件开发(会员) 新增tn-stack-swiper堆叠轮播添加垂直滚动属性(会员) 新增tn-waterfall瀑布流组件(会员) 新增tn-tree-view树形菜单组件(会员) 新增tn-cascade-selection级联选择组件(会员) 新增tn-custom-swiper组件(会员【实验室】) 新增tn-lazy-load懒加载组件 新增tn-load-more加载更多组件 新增tn-sekeleton骨架屏组件 新增tn-empty空白页组件 新增tn-landscape压屏窗组件 新增tn-verification-code-input验证码输入组件 新增tn-goods-nav商品导航组件 修复tn-slider在tn-form-item下在H5端无法滑动的bug 修复tn-swipe-action-item点击回调事件中无法读取name属性的bug 修复群友已反馈的bug 优化部分页面在iphone上底部确实的问题
This commit is contained in:
190
tuniao-ui/components/tn-empty/tn-empty.vue
Normal file
190
tuniao-ui/components/tn-empty/tn-empty.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<view v-if="show" class="tn-empty-class tn-empty" :style="[emptyStyle]">
|
||||
<view
|
||||
v-if="!isImage"
|
||||
class="tn-empty__icon"
|
||||
:class="[icon ? `tn-icon-${icon}` : `tn-icon-empty-${mode}`]"
|
||||
:style="[iconStyle]"
|
||||
></view>
|
||||
<image
|
||||
v-else
|
||||
class="tn-empty__image"
|
||||
:style="[imageStyle]"
|
||||
:src="icon"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
|
||||
<view
|
||||
class="tn-empty__text"
|
||||
:style="[textStyle]"
|
||||
>{{ text ? text : icons[mode]}}</view>
|
||||
<view v-if="$slots.default || $slots.$default" class="tn-empty__slot">
|
||||
<slot/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-empty',
|
||||
props: {
|
||||
// 显示空白页
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 内置icon的名称
|
||||
// 图片路径,建议使用绝对路径
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 预置图标类型
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'data'
|
||||
},
|
||||
// 提示文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字颜色
|
||||
textColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字大小,单位rpx
|
||||
textSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标颜色
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小,单位rpx
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图片宽度(当图标为图片时生效),单位rpx
|
||||
imgWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图片高度(当图标为图片时生效),单位rpx
|
||||
imgHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 自定义组件样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
emptyStyle() {
|
||||
let style = {}
|
||||
Object.assign(style, this.customStyle)
|
||||
return style
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
if (this.iconSize) {
|
||||
style.fontSize = this.iconSize + 'rpx'
|
||||
}
|
||||
if (this.iconColor) {
|
||||
style.color = this.iconColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
imageStyle() {
|
||||
let style = {}
|
||||
if (this.imgWidth) {
|
||||
style.width = this.imgWidth + 'rpx'
|
||||
}
|
||||
if (this.imgHeight) {
|
||||
style.height = this.imgHeight + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
textStyle() {
|
||||
let style = {}
|
||||
if (this.textColor) {
|
||||
style.color = this.textColor
|
||||
}
|
||||
if (this.textSize) {
|
||||
style.fontSize = this.textSize + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
// 判断传递的icon是否为图片
|
||||
isImage() {
|
||||
return this.icon.indexOf('/') >= 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
icons: {
|
||||
cart: '购物车为空',
|
||||
page: '页面不存在',
|
||||
search: '搜索结果为空',
|
||||
address: '地址为空',
|
||||
network: '网络不通',
|
||||
order: '订单为空',
|
||||
coupon: '优惠券为空',
|
||||
favor: '暂无收藏',
|
||||
permission: '无权限',
|
||||
history: '历史记录为空',
|
||||
message: '暂无消息',
|
||||
list: '列表为空',
|
||||
data: '暂无数据',
|
||||
comment: '暂无评论'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__icon {
|
||||
margin-top: 14rpx;
|
||||
color: #AAAAAA;
|
||||
font-size: 90rpx;
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
color: #AAAAAA;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
&__slot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
359
tuniao-ui/components/tn-goods-nav/tn-goods-nav.vue
Normal file
359
tuniao-ui/components/tn-goods-nav/tn-goods-nav.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-goods-nav-class tn-goods-nav"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
{
|
||||
'tn-goods-nav--fixed': fixed,
|
||||
'tn-safe-area-inset-bottom': safeAreaInsetBottom
|
||||
}
|
||||
]"
|
||||
:style="[backgroundColorStyle, navStyle]"
|
||||
>
|
||||
<view class="options">
|
||||
<view
|
||||
v-for="(item, index) in optionsData"
|
||||
:key="index"
|
||||
class="options__item"
|
||||
:class="[{'options__item--avatar': item.showAvatar}]"
|
||||
@tap="handleOptionClick(index)"
|
||||
>
|
||||
<block v-if="item.showAvatar">
|
||||
<tn-avatar
|
||||
:src="item.avatar"
|
||||
size="sm"
|
||||
:badge="item.showBadge"
|
||||
:badgeText="item.count"
|
||||
:badgeBgColor="item.countBackgroundColor"
|
||||
:badgeColor="item.countFontColor"
|
||||
:badgeSize="26"
|
||||
></tn-avatar>
|
||||
</block>
|
||||
<block v-else>
|
||||
<view class="options__item__icon" :class="[`tn-icon-${item.icon}`]" :style="[optionStyle(index, 'icon')]">
|
||||
<tn-badge v-if="item.showBadge" :absolute="true" :backgroundColor="item.countBackgroundColor" :fontColor="item.countFontColor" :fontSize="16" padding="2rpx 5rpx">{{ item.count }}</tn-badge>
|
||||
</view>
|
||||
<view class="options__item__text" :style="[optionStyle(index, 'text')]">{{ item.text }}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<view class="buttons">
|
||||
<view
|
||||
v-for="(item, index) in buttonGroupsData"
|
||||
:key="index"
|
||||
class="buttons__item"
|
||||
:class="[buttonClass(index)]"
|
||||
:style="[buttonStyle(index)]"
|
||||
@tap="handleButtonClick(index)"
|
||||
>
|
||||
<view class="buttons__item__text">{{ item.text }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-goods-nav',
|
||||
props: {
|
||||
// 选项信息
|
||||
// 建议不超过3个
|
||||
// {
|
||||
// icon: '', // 图标名称
|
||||
// text: '', // 显示的文本
|
||||
// count: '', // 角标的值
|
||||
// countBackgroundColor: '', // 角标背景颜色
|
||||
// countFontColor: '', // 角标字体颜色
|
||||
// iconColor: '', // 图标颜色
|
||||
// textColor: '', // 文本颜色
|
||||
// avatar: '', // 显示头像(此时将不显示图标和文本)
|
||||
// }
|
||||
options: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [{
|
||||
icon: 'shop',
|
||||
text: '店铺'
|
||||
},{
|
||||
icon: 'service',
|
||||
text: '客服'
|
||||
},{
|
||||
icon: 'star',
|
||||
text: '收藏'
|
||||
}]
|
||||
}
|
||||
},
|
||||
// 按钮组
|
||||
// 建议不超过2个
|
||||
// {
|
||||
// text: '', // 显示的文本
|
||||
// backgroundColor: '', // 按钮背景颜色
|
||||
// color: '' // 文本颜色
|
||||
// }
|
||||
buttonGroups: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [{
|
||||
text: '加入购物车',
|
||||
backgroundColor: '#FFA726',
|
||||
color: '#FFFFFF'
|
||||
},{
|
||||
text: '结算',
|
||||
backgroundColor: '#FF7043',
|
||||
color: '#FFFFFF'
|
||||
}]
|
||||
}
|
||||
},
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 导航的高度,单位rpx
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 导航的层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 按钮类型
|
||||
// rect -> 方形 paddingRect -> 上下带边距方形 round -> 圆角
|
||||
buttonType: {
|
||||
type: String,
|
||||
default: 'rect'
|
||||
},
|
||||
// 是否固定在底部
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundColorStyle() {
|
||||
return this.$t.colorUtils.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.colorUtils.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
navStyle() {
|
||||
let style = {}
|
||||
if (this.height) {
|
||||
style.height = this.height + 'rpx'
|
||||
}
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.goodsNav
|
||||
return style
|
||||
},
|
||||
// 选项style
|
||||
optionStyle() {
|
||||
return (index, type) => {
|
||||
let style = {}
|
||||
const item = this.optionsData[index]
|
||||
if (type === 'icon' && item.iconColor) {
|
||||
style.color = item.iconColor
|
||||
} else if (type === 'text' && item.fontColor) {
|
||||
style.color = item.fontColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 按钮class
|
||||
buttonClass() {
|
||||
return (index) => {
|
||||
let clazz = ''
|
||||
const item = this.buttonGroupsData[index]
|
||||
if (item.backgroundColorClass) {
|
||||
clazz += ` ${item.backgroundColorClass}`
|
||||
}
|
||||
if (item.colorClass) {
|
||||
clazz += ` ${item.colorClass}`
|
||||
}
|
||||
|
||||
clazz += ` buttons__item--${this.$t.string.humpConvertChar(this.buttonType, '-')}`
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
// 按钮style
|
||||
buttonStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
const item = this.buttonGroupsData[index]
|
||||
if (item.backgroundColorStyle) {
|
||||
style.backgroundColor = item.backgroundColorStyle
|
||||
}
|
||||
if (item.colorStyle) {
|
||||
style.color = item.colorStyle
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options(val) {
|
||||
this.initData()
|
||||
},
|
||||
buttonGroups(val) {
|
||||
this.initData()
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 保存选项数据
|
||||
optionsData: [],
|
||||
// 保存按钮组数据
|
||||
buttonGroupsData: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initData()
|
||||
},
|
||||
methods: {
|
||||
// 初始化选项和按钮数据
|
||||
initData() {
|
||||
this.handleOptionsData()
|
||||
this.handleButtonGroupsData()
|
||||
},
|
||||
// 选项点击事件
|
||||
handleOptionClick(index) {
|
||||
this.$emit('optionClick', {
|
||||
index: index
|
||||
})
|
||||
},
|
||||
// 按钮点击事件
|
||||
handleButtonClick(index) {
|
||||
this.$emit('buttonClick', {
|
||||
index: index
|
||||
})
|
||||
},
|
||||
// 处理选项组数据
|
||||
handleOptionsData() {
|
||||
this.optionsData = this.options.map((item) => {
|
||||
let option = {...item}
|
||||
option.showAvatar = item.hasOwnProperty('avatar')
|
||||
if (item.hasOwnProperty('count')) {
|
||||
const count = this.$t.number.formatNumberString(item.count, 2)
|
||||
option.showBadge = true
|
||||
option.count = typeof count === 'number' ? String(count) : count
|
||||
option.countBackgroundColor = item.countBackgroundColor ? item.countBackgroundColor : '#01BEFF'
|
||||
option.countFontColor = item.countFontColor ? item.countFontColor : '#FFFFFF'
|
||||
}
|
||||
|
||||
return option
|
||||
})
|
||||
},
|
||||
// 处理按钮组数据
|
||||
handleButtonGroupsData() {
|
||||
this.buttonGroupsData = this.buttonGroups.map((item) => {
|
||||
let button = {...item}
|
||||
if (item.hasOwnProperty('backgroundColor')) {
|
||||
button.backgroundColorClass = this.$t.colorUtils.getBackgroundColorInternalClass(item.backgroundColor)
|
||||
button.backgroundColorStyle = this.$t.colorUtils.getBackgroundColorStyle(item.backgroundColor)
|
||||
}
|
||||
if (item.hasOwnProperty('color')) {
|
||||
button.colorClass = this.$t.colorUtils.getBackgroundColorInternalClass(item.color)
|
||||
button.colorStyle = this.$t.colorUtils.getBackgroundColorStyle(item.color)
|
||||
}
|
||||
return button
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-goods-nav {
|
||||
background-color: #FFFFFF;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
width: 100%;
|
||||
box-sizing: content-box;
|
||||
|
||||
&--fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
color: #AAAAAA;
|
||||
|
||||
&__item {
|
||||
padding: 0 26rpx;
|
||||
|
||||
&--avatar {
|
||||
padding: 0rpx 0rpx 0rpx 26rpx !important;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
font-size: 36rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
padding: 0 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&--rect {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--padding-rect {
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
&--round {
|
||||
height: 75%;
|
||||
&:first-child {
|
||||
border-top-left-radius: 100rpx;
|
||||
border-bottom-left-radius: 100rpx;
|
||||
}
|
||||
&:last-child {
|
||||
border-top-right-radius: 100rpx;
|
||||
border-bottom-right-radius: 100rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
225
tuniao-ui/components/tn-landscape/tn-landscape.vue
Normal file
225
tuniao-ui/components/tn-landscape/tn-landscape.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="tn-landscape-class tn-landscape">
|
||||
<view v-if="showValue" class="tn-landscape__container" :style="[containerStyle]">
|
||||
<slot></slot>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-landscape__icon tn-icon-close-fill"
|
||||
:class="[{
|
||||
'tn-landscape__icon--left-top': closePosition === 'leftTop',
|
||||
'tn-landscape__icon--right-top': closePosition === 'rightTop',
|
||||
'tn-landscape__icon--bottom': closePosition === 'bottom'
|
||||
}]"
|
||||
:style="[closeBtnStyle]"
|
||||
@tap="close"
|
||||
></view>
|
||||
</view>
|
||||
<view
|
||||
v-if="mask"
|
||||
class="tn-landscape__mask"
|
||||
:class="[{'tn-landscape__mask--show': showValue}]"
|
||||
:style="[maskStyle]"
|
||||
@tap="closeMask"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-landscape',
|
||||
props: {
|
||||
// 显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示关闭图标
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 关闭图标颜色
|
||||
closeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 关闭图标大小,单位rpx
|
||||
closeSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标位置
|
||||
// leftTop -> 左上角 rightTop -> 右上角 bottom -> 底部
|
||||
closePosition: {
|
||||
type: String,
|
||||
default: 'rightTop'
|
||||
},
|
||||
// 关闭图标top值,单位rpx
|
||||
// 当关闭图标在leftTop或者rightTop时生效
|
||||
closeTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标right值,单位rpx
|
||||
// 当关闭图标在RightTop时生效
|
||||
closeRight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标bottom值,单位rpx
|
||||
// 当关闭图标在bottom时生效
|
||||
closeBottom: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 关闭图标left值,单位rpx
|
||||
// 当关闭图标在leftTop时生效
|
||||
closeLeft: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 点击遮罩可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
containerStyle() {
|
||||
let style = {}
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.landsacpe
|
||||
return style
|
||||
},
|
||||
closeBtnStyle() {
|
||||
let style = {}
|
||||
if (this.closePosition === 'leftTop') {
|
||||
if (this.closeTop) {
|
||||
style.top = this.closeTop + 'rpx'
|
||||
}
|
||||
if (this.closeLeft) {
|
||||
style.left = this.closeLeft + 'rpx'
|
||||
}
|
||||
} else if (this.closePosition === 'rightTop') {
|
||||
if (this.closeTop) {
|
||||
style.top = this.closeTop + 'rpx'
|
||||
}
|
||||
if (this.closeRight) {
|
||||
style.right = this.closeRight + 'rpx'
|
||||
}
|
||||
} else if (this.closePosition === 'bottom') {
|
||||
if (this.closeBottom) {
|
||||
style.bottom = this.closeBottom + 'rpx'
|
||||
}
|
||||
}
|
||||
if (this.closeSize) {
|
||||
style.fontSize = this.closeSize + 'rpx'
|
||||
}
|
||||
if (this.closeColor) {
|
||||
style.color = this.closeColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
maskStyle() {
|
||||
let style = {}
|
||||
style.zIndex = this.zIndex ? this.zIndex - 1 : this.$t.zIndex.landsacpe - 1
|
||||
return style
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(val) {
|
||||
this.showValue = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showValue: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 关闭压屏窗
|
||||
close() {
|
||||
this.showValue = false
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击遮罩关闭
|
||||
closeMask() {
|
||||
if (!this.maskCloseable) return
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-landscape {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__container {
|
||||
max-width: 100%;
|
||||
position: fixed;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-size: 50rpx;
|
||||
color: #FFFFFF;
|
||||
|
||||
&--left-top {
|
||||
top: -40rpx;
|
||||
left: 20rpx;
|
||||
}
|
||||
|
||||
&--right-top {
|
||||
top: -40rpx;
|
||||
right: 40rpx;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
left: 50%;
|
||||
bottom: -40rpx;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__mask {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $tn-mask-bg-color;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transform: scale3d(1, 1, 0);
|
||||
transition: all 0.25s ease-in;
|
||||
|
||||
&--show {
|
||||
opacity: 1 !important;
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
254
tuniao-ui/components/tn-lazy-load/tn-lazy-load.vue
Normal file
254
tuniao-ui/components/tn-lazy-load/tn-lazy-load.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<view class="tn-lazy-load-class tn-lazy-load">
|
||||
<view
|
||||
class="tn-lazy-load__item"
|
||||
:class="[`tn-lazy-load__item--${elIndex}`]"
|
||||
:style="[lazyLoadItemStyle]"
|
||||
>
|
||||
<view class="tn-lazy-load__item__content">
|
||||
<image
|
||||
v-if="!error"
|
||||
class="tn-lazy-load__item__image"
|
||||
:style="[imageStyle]"
|
||||
:src="show ? image : loadingImg"
|
||||
:mode="imgMode"
|
||||
@load="handleImgLoaded"
|
||||
@error="handleImgError"
|
||||
@tap="handleImgClick"
|
||||
></image>
|
||||
<image
|
||||
v-else
|
||||
class="tn-lazy-load__item__image tn-lazy-load__item__image--error"
|
||||
:style="[imageStyle]"
|
||||
:src="errorImg"
|
||||
:mode="imgMode"
|
||||
@load="handleErrorImgLoaded"
|
||||
@tap="handleImgClick"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-lazy-load',
|
||||
props: {
|
||||
// 组件标识
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 待显示的图片地址
|
||||
image: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图片裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'scaleToFill'
|
||||
},
|
||||
// 占位图片路径
|
||||
loadingImg: {
|
||||
type: String,
|
||||
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM0QjNBQjkyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM0QjNBQkEyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzRCM0FCNzJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzRCM0FCODJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtRHfPcAAAAzUExURZWVldfX18PDw62trZubm9zc3Li4uKGhoebm5tLS0uHh4c3Nzaenp729vcjIyLKysuvr6141L40AAAcXSURBVHja7NzZlqpGAEBR5lG0//9rIw7IJKJi4or7PGTdtN10wr5SVAEGf/qqArsAiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAg+nmQFMi5Jis+sIniED23jSzIgLTtg2D//iYme/8QBM/9lQ+CAEhbNLM3N9hEHAThX7GPCiBfAxK1b51kD+R7QMLjXg7iCsgWIPUh7pfVozG791oeBPngm48G583uW5GkBvI+SBaM2xXDn1oqum423bX/mgF5FySc2cv93Voug9TdZotsggnkBZB2NzbhrSY5HnoG07jei8dvzsJB/c3W60SALILE46+WCztsbhPR7R2VJq0ukEcT49nyy8QhaKcRa3fYHZD4+ufqOJAcgDz8/59vtw1I3QP5K6JsOG0vm3hce4I8LQp/BaRZGJC3AAn7IKOKXbC+7EdA5vdmmVwOLksgRThqOqiH4XEGsht+peoPUE8U/jJIO5OLH4GEwUslV5G0PTBG5Uiw/Y2jyigO3l9HAHKv9PYb82LloH74dZBoBUgar+l48NsNvtD0fkez9iwrAvIYZDRCl+Xs149Hm/KZmQ+QjUCiO1ei4ru7EsgnQYrkznlQb7thCuRfAzlOAPN72427P4VA/i2Q/DKT/Ls/VR8fvIBsDZIuz7TPF6TCbnk4GJkB2RokejTjuE7/unlgCuSTIO0Cy+Plp6vDfnQlBchy8QtjSHVd3EgmK1bHLm+H6+nXYbz2DuQRSPnqoL7vvq0u70on4zvxgCyWD3b9UyDVdW24PaWaiGTnFZJwPIQAebDpIKheBIm7n124ZthMJipAlkqHO+IZkP1tbfzOJark/A7MgKyvvl60fRqkvXfhuow+t9+q00+0/yyBrK8ZngOtBzldhw2X9tvpNGty0gvkmbPeJ0Cy/r09s/stbmfo0yMWkEdjevgKyOn2t2pxv7UXoibTdCDLje9/Ww1ymqzn87dbp92242ZmMRjI8hASvwKSLq4udqN6ksw8nxXN3tszD9L8Gkg+2mFrQYql5az4tvFj5xOx4VwnSdeBtGdyPwUytxK77pBVlNHdO7OK3rh/eTPUvdutT3fO52tuHMqD4N7llv8pyOQQ//w19YVDfX27+Sfuby9/6nau4pdA8vEdOZuChEH/quHt0Jg+IRJ/5+PrHwKZXfjbDiS73Zo7mu5UkzX7uTsXe0e/7nC3ePf1O69+BUg2XDfZCqSqOu7rGVf8cHBe8zhC2b61dtUHXv0OkGo6ZL4JkpbRYXdUaFevivx2M/1GIOctNh949TtAoumQ+TpIHMX54CJu+8BDd8FkE5BqcZh/59XvAClmTvKfB0nDqIlHo3T70SftyW1eX9dXtgQJqs1f/Q6QaOa/7wmQKtxH8eiGoCRuovODIO3VxOMmruZbHrLyD7z6DSDtGyT7ew1kf9hNn07c986JTovzzem0Id9wUG+Vk/IDr34DSNR7huZJkMFT6vEhqrPx/j5cnlZML8N6/PAzh9Y99Flm5Yde/c9BquDOkvkKkMP58dA4qi9vivE8JOvGz/j8FokfPpr288+pH2ZPOZrLmeGD+7KOh6dqYWJ48ki7yUg0tz0go/fv/LLddfV3sgOLJyaGPY/zrSlh1a36Arkzoue9CyG35ze6E6/dzO2Ga0EGHqdRJIkfn9/8OEjTW8Vq91ZWh39FeehWA7Nu9ft8CpUEk1WWOyDF0OPyEU2Pnzf/bZC0P6IPzmAvu7KauQBVrgKpJ0tG2arHzX8e5Pb3PezNs/PrX+3JMyCLn9XXf37tPFHvt09WfCDDjx+yyn1/p1V11j7GnB/q3leLuVva79S/tzed+db08YpF4uOZtmz/9oXWMq6BCAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiAALELvqt/BBgACqVeUBXxcCkAAAAASUVORK5CYII='
|
||||
},
|
||||
// 加载失败的错误占位图
|
||||
errorImg: {
|
||||
type: String,
|
||||
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdDMjhENDYyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdDMjhENDcyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0MyOEQ0NDJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0MyOEQ0NTJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhLwhikAAAAzUExURZWVldfX162t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'
|
||||
},
|
||||
// 图片进入可见区域前多少像素前,单位rpx,开始加载图片
|
||||
// 负数为图片超出屏幕底部多少像素后触发懒加载,正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上)
|
||||
threshold: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
// 是否开启过渡效果
|
||||
isEffect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 动画过渡时间
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: 500
|
||||
},
|
||||
// 渡效果的速度曲线,各个之间差别不大,因为这是淡入淡出,且时间很短,不是那些变形或者移动的情况,会明显
|
||||
// linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
|
||||
effect: {
|
||||
type: String,
|
||||
default: 'ease-in-out'
|
||||
},
|
||||
// 图片高度,单位rpx
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 450
|
||||
},
|
||||
// 图片圆角
|
||||
borderRadius: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
thresholdValue() {
|
||||
// 先取绝对值,因为threshold可能是负数,最后根据this.threshold是正数或者负数,重新还原
|
||||
let threshold = uni.upx2px(Math.abs(this.threshold))
|
||||
return this.threshold < 0 ? -threshold : threshold
|
||||
},
|
||||
lazyLoadItemStyle() {
|
||||
let style = {}
|
||||
style.opacity = Number(this.opacity)
|
||||
if (this.borderRadius) {
|
||||
style.borderRadius = this.borderRadius
|
||||
}
|
||||
// 因为time值需要改变,所以不直接用duration值(不能改变父组件prop传过来的值)
|
||||
style.transition = `opacity ${this.time / 1000}s ${this.effect}`
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
return style
|
||||
},
|
||||
imageStyle() {
|
||||
let style = {}
|
||||
if (typeof this.height === 'string' && this.height.indexOf('%') === -1) {
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(val) {
|
||||
// 如果不开启过渡效果直接返回
|
||||
if (!this.effect) return
|
||||
this.time = 0
|
||||
// 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的白色),再改成1,是为了获得过渡效果
|
||||
this.opacity = 0
|
||||
setTimeout(() => {
|
||||
this.time = this.duration
|
||||
this.opacity = 1
|
||||
}, 30)
|
||||
},
|
||||
image(val) {
|
||||
// 修改图片后重置部分变量
|
||||
if (!val) {
|
||||
// 如果传入null或者'',或者undefined,标记为错误状态
|
||||
this.error = true
|
||||
} else {
|
||||
this.init()
|
||||
this.error = false
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elIndex: this.$t.uuid(),
|
||||
// 显示图片
|
||||
show: false,
|
||||
// 图片透明度
|
||||
opacity: 1,
|
||||
// 动画时间
|
||||
time: this.duration,
|
||||
// 懒加载状态
|
||||
// loadlazy-懒加载中状态,loading-图片正在加载,loaded-图片加加载完成
|
||||
loadStatus: '',
|
||||
// 图片加载失败
|
||||
error: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 由于一些特殊原因,不能将此变量放到data中定义
|
||||
this.observer = {}
|
||||
this.observerName = 'lazyLoadContentObserver'
|
||||
},
|
||||
mounted() {
|
||||
// 在需要用到懒加载的页面,在触发底部的时候触发tOnLazyLoadReachBottom事件,保证所有图片进行加载
|
||||
this.$nextTick(() => {
|
||||
uni.$once('tOnLazyLoadReachBottom', () => {
|
||||
if (!this.show) this.show = true
|
||||
})
|
||||
})
|
||||
// mounted的时候,不一定挂载了这个元素,延时30ms,否则会报错或者不报错,但是也没有效果
|
||||
setTimeout(() => {
|
||||
this.disconnectObserver(this.observerName)
|
||||
const contentObserver = uni.createIntersectionObserver(this)
|
||||
contentObserver.relativeToViewport({
|
||||
bottom: this.thresholdValue
|
||||
}).observe(`.tn-lazy-load__item--${this.elIndex}`, (res) => {
|
||||
if (res.intersectionRatio > 0) {
|
||||
// 懒加载状态改变
|
||||
this.show = true
|
||||
// 如果图片已经加载,去掉监听,减少性能消耗
|
||||
this.disconnectObserver(this.observerName)
|
||||
}
|
||||
})
|
||||
this[this.observerName] = contentObserver
|
||||
}, 50)
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
this.error = false
|
||||
this.loadStatus = ''
|
||||
},
|
||||
// 处理图片点击事件
|
||||
handleImgClick() {
|
||||
let whichImg = ''
|
||||
// 如果show为false,则表示图片还没有开始加载,点击的是最开始占位图
|
||||
if (this.show === false) whichImg = 'lazyImg'
|
||||
// 如果error为true,则表示图片加载失败,点击的是错误占位图
|
||||
else if (this.error === true) whichImg = 'errorImg'
|
||||
// 点击了正常的图片
|
||||
else whichImg = 'realImg'
|
||||
|
||||
this.$emit('click', {
|
||||
index: this.index,
|
||||
whichImg: whichImg
|
||||
})
|
||||
},
|
||||
// 处理图片加载完成事件,通过show来区分是占位图触发还是加载真正的图片触发
|
||||
handleImgLoaded() {
|
||||
if (this.loadStatus = '') {
|
||||
// 占位图加载完成
|
||||
this.loadStatus = 'lazyed'
|
||||
}
|
||||
else if (this.loadStatus == 'lazyed') {
|
||||
// 真正的图片加载完成
|
||||
this.loadStatus = 'loaded'
|
||||
this.$emit('loaded', this.index)
|
||||
}
|
||||
},
|
||||
// 处理错误图片加载完成
|
||||
handleErrorImgLoaded() {
|
||||
this.$emit('error', this.index)
|
||||
},
|
||||
// 处理图片加载失败
|
||||
handleImgError() {
|
||||
this.error = true
|
||||
},
|
||||
disconnectObserver(observerName) {
|
||||
const observer = this[observerName]
|
||||
observer && observer.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-lazy-load {
|
||||
&__item {
|
||||
background-color: $tn-bg-gray-color;
|
||||
overflow: hidden;
|
||||
|
||||
&__image {
|
||||
// 解决父容器会多出3px的问题
|
||||
display: block;
|
||||
width: 100%;
|
||||
// 骗系统开启硬件加速
|
||||
transform: transition3d(0, 0, 0);
|
||||
// 防止图片加载“闪一下”
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
188
tuniao-ui/components/tn-load-more/tn-load-more.vue
Normal file
188
tuniao-ui/components/tn-load-more/tn-load-more.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<view class="tn-load-more-class tn-load-more">
|
||||
<view
|
||||
class="tn-load-more__wrap"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[loadStyle]"
|
||||
>
|
||||
<view class="tn-load-more__line"></view>
|
||||
<view
|
||||
class="tn-load-more__content"
|
||||
:class="[{'tn-load-more__content--more': (status === 'loadmore' || status === 'nomore')}]"
|
||||
>
|
||||
<view class="tn-load-more__loading">
|
||||
<tn-loading
|
||||
class="tn-load-more__loading__icon"
|
||||
:mode="loadingIconType"
|
||||
:show="status === 'loading' && loadingIcon"
|
||||
:color="loadingIconColor"
|
||||
></tn-loading>
|
||||
</view>
|
||||
<view
|
||||
class="tn-load-more__text"
|
||||
:class="[fontColorClass, {'tn-load-more__text--dot': (status === 'nomore' && dot)}]"
|
||||
:style="[loadTextStyle]"
|
||||
>{{ showText }}</view>
|
||||
</view>
|
||||
<view class="tn-load-more__line"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-load-more',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 加载状态
|
||||
// loadmore -> 加载更多
|
||||
// loading -> 加载中
|
||||
// nomore -> 没有更多
|
||||
status: {
|
||||
type: String,
|
||||
default: 'loadmore'
|
||||
},
|
||||
// 显示加载图标
|
||||
loadingIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 加载图标样式,参考tn-loading组件的加载类型
|
||||
loadingIconType: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 在圆圈加载状态下,圆圈的颜色
|
||||
loadingIconColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 显示的文字
|
||||
loadText: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
loadmore: '加载更多',
|
||||
loading: '正在加载...',
|
||||
nomore: '没有更多了'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 是否显示粗点,在nomore状态下生效
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 自定义组件样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loadStyle() {
|
||||
let style = {}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
// 合并用户自定义样式
|
||||
if (Object.keys(this.customStyle).length > 0) {
|
||||
Object.assign(style, this.customStyle)
|
||||
}
|
||||
return style
|
||||
},
|
||||
loadTextStyle() {
|
||||
let style = {}
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSizeStyle) {
|
||||
style.fontSize = this.fontSizeStyle
|
||||
style.lineHeight = this.$t.string.getLengthUnitValue(this.fontSize + 2, this.fontUnit)
|
||||
}
|
||||
return style
|
||||
},
|
||||
// 显示的提示文字
|
||||
showText() {
|
||||
let text = ''
|
||||
if (this.status === 'loadmore') text = this.loadText.loadmore || '加载更多'
|
||||
else if (this.status === 'loading') text = this.loadText.loading || '正在加载...'
|
||||
else if (this.status === 'nomore' && this.dot) text = this.dotText
|
||||
else text = this.loadText.nomore || '没有更多了'
|
||||
|
||||
return text
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 粗点
|
||||
dotText: '●'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理加载更多事件
|
||||
loadMore() {
|
||||
// 只有在 loadmore 状态下点击才会发送点击事件,内容不满一屏时无法触发底部上拉事件,所以需要点击来触发
|
||||
if (this.status === 'loadmore') this.$emit('loadmore')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-load-more {
|
||||
|
||||
&__wrap {
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $tn-content-color;
|
||||
}
|
||||
|
||||
&__line {
|
||||
vertical-align: middle;
|
||||
border: 1px solid $tn-content-color;
|
||||
width: 50rpx;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 12rpx;
|
||||
|
||||
&--more {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
&__loading {
|
||||
margin-right: 8rpx;
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 30rpx;
|
||||
|
||||
&--dot {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
254
tuniao-ui/components/tn-skeleton/tn-skeleton.vue
Normal file
254
tuniao-ui/components/tn-skeleton/tn-skeleton.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="tn-skeleton-class tn-skeleton"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[skeletonStyle]"
|
||||
@touchmove.stop.prevent
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in rectNodes"
|
||||
:key="$t.uuid()"
|
||||
class="tn-skeleton__item tn-skeleton__item--rect"
|
||||
:class="[elBackgroundColorClass, {'tn-skeleton__item--fade': animation}]"
|
||||
:style="[itemStyle('rect', item)]"
|
||||
></view>
|
||||
<view
|
||||
v-for="(item, index) in circleNodes"
|
||||
:key="$t.uuid()"
|
||||
class="tn-skeleton__item tn-skeleton__item--circle"
|
||||
:class="[elBackgroundColorClass, {'tn-skeleton__item--fade': animation}]"
|
||||
:style="[itemStyle('circle', item)]"
|
||||
></view>
|
||||
<view
|
||||
v-for="(item, index) in filletNodes"
|
||||
:key="$t.uuid()"
|
||||
class="tn-skeleton__item tn-skeleton__item--fillet"
|
||||
:class="[elBackgroundColorClass, {'tn-skeleton__item--fade': animation}]"
|
||||
:style="[itemStyle('fillet', item)]"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-skeleton',
|
||||
mixins: [ componentsColorMixin ],
|
||||
props: {
|
||||
// 显示骨架屏
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 需要渲染的元素背景颜色
|
||||
elBackgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 开启加载动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 矩形元素自定义样式
|
||||
rectCustomStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 圆形元素自定义样式
|
||||
circleCustomStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 圆角元素自定义样式
|
||||
filletCustomStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elBackgroundColorStyle() {
|
||||
return this.$t.colorUtils.getBackgroundColorStyle(this.elBackgroundColor)
|
||||
},
|
||||
elBackgroundColorClass() {
|
||||
return this.$t.colorUtils.getBackgroundColorInternalClass(this.elBackgroundColor)
|
||||
},
|
||||
// 骨架屏样式
|
||||
skeletonStyle() {
|
||||
let style = {}
|
||||
style.width = this.skeletonWidth + 'px'
|
||||
style.height = this.skeletonHeight + 'px'
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
style.left = this.left + 'px'
|
||||
style.top = this.top + 'px'
|
||||
return style
|
||||
},
|
||||
// 元素样式
|
||||
itemStyle() {
|
||||
return (type, item) => {
|
||||
let style = {}
|
||||
style.width = item.width + 'px'
|
||||
style.height = item.height + 'px'
|
||||
if (this.elBackgroundColorStyle) {
|
||||
style.backgroundColor = this.elBackgroundColorStyle
|
||||
}
|
||||
style.left = (item.left - this.left) + 'px'
|
||||
style.top = (item.top - this.top) + 'px'
|
||||
if (type === 'rect') {
|
||||
Object.assign(style, this.rectCustomStyle)
|
||||
} else if (type === 'circle') {
|
||||
style.borderRadius = (item.width / 2) + 'px'
|
||||
Object.assign(style, this.circleCustomStyle)
|
||||
} else if (type === 'fillet') {
|
||||
Object.assign(style, this.filletCustomStyle)
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 骨架屏宽度
|
||||
skeletonWidth: 750,
|
||||
// 骨架屏高度
|
||||
skeletonHeight: 1500,
|
||||
// 圆角元素
|
||||
filletNodes: [],
|
||||
// 圆形元素
|
||||
circleNodes: [],
|
||||
// 矩形元素
|
||||
rectNodes: [],
|
||||
// 元素偏移位置
|
||||
top: 0,
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
// 获取系统信息
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
this.skeletonWidth = systemInfo.safeArea.width
|
||||
this.skeletonHeight = systemInfo.safeArea.height
|
||||
this.selectQueryInfo()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 查询节点信息
|
||||
selectQueryInfo() {
|
||||
// 获取整个父容器的宽高作为骨架屏的宽高
|
||||
// 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
|
||||
let query = null
|
||||
// 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
|
||||
// #ifdef MP-WEIXIN
|
||||
query = uni.createSelectorQuery().in(this.$parent)
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
query = uni.createSelectorQuery()
|
||||
// #endif
|
||||
query.selectAll('.tn-skeleton').boundingClientRect().exec((res) => {
|
||||
console.log(res);
|
||||
this.skeletonWidth = res[0][0].width
|
||||
this.skeletonHeight = res[0][0].height
|
||||
this.top = res[0][0].bottom - res[0][0].height
|
||||
this.left = res[0][0].left
|
||||
})
|
||||
|
||||
// 获取元素列表
|
||||
this.getRectElements()
|
||||
this.getCircleElements()
|
||||
this.getFillteElements()
|
||||
},
|
||||
// 矩形元素列表
|
||||
getRectElements() {
|
||||
let query = null
|
||||
// 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
|
||||
// #ifdef MP-WEIXIN
|
||||
query = uni.createSelectorQuery().in(this.$parent)
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
query = uni.createSelectorQuery()
|
||||
// #endif
|
||||
query.selectAll('.tn-skeleton-rect').boundingClientRect().exec((res) => {
|
||||
this.rectNodes = res[0]
|
||||
})
|
||||
},
|
||||
// 圆形元素列表
|
||||
getCircleElements() {
|
||||
let query = null
|
||||
// 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
|
||||
// #ifdef MP-WEIXIN
|
||||
query = uni.createSelectorQuery().in(this.$parent)
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
query = uni.createSelectorQuery()
|
||||
// #endif
|
||||
query.selectAll('.tn-skeleton-circle').boundingClientRect().exec((res) => {
|
||||
this.circleNodes = res[0]
|
||||
})
|
||||
},
|
||||
// 圆角元素列表
|
||||
getFillteElements() {
|
||||
let query = null
|
||||
// 在微信小程序中,如果把骨架屏放入组件使用,需要in(this)上下文为父组件才有效
|
||||
// #ifdef MP-WEIXIN
|
||||
query = uni.createSelectorQuery().in(this.$parent)
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
query = uni.createSelectorQuery()
|
||||
// #endif
|
||||
query.selectAll('.tn-skeleton-fillet').boundingClientRect().exec((res) => {
|
||||
this.filletNodes = res[0]
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-skeleton {
|
||||
position: absolute;
|
||||
z-index: 9998;
|
||||
overflow: hidden;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__item {
|
||||
position: absolute;
|
||||
background-color: #F0F0F0;
|
||||
|
||||
&--fillet {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--fade {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #E6E6E6;
|
||||
animation-duration: 1.5s;
|
||||
animation-name: blink;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<view class="tn-verification-code-class tn-verification-code">
|
||||
<view class="tn-code__container">
|
||||
<input class="tn-code__input" :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxLength" @input="getValue" />
|
||||
<view v-for="(item, index) in loopCharArr" :key="index">
|
||||
<view
|
||||
class="tn-code__item"
|
||||
:class="[{
|
||||
'tn-code__item--breathe': breathe && charArrLength === index,
|
||||
'tn-code__item__box': mode === 'box',
|
||||
'tn-code__item__box--active': mode === 'box' && charArrLength === index
|
||||
}]"
|
||||
:style="[itemStyle(index)]"
|
||||
>
|
||||
<view
|
||||
v-if="mode !== 'middleLine'"
|
||||
class="tn-code__item__line tn-code__item__line--placeholder"
|
||||
:style="[placeholderLineStyle(index)]"
|
||||
></view>
|
||||
<view
|
||||
v-if="mode === 'middleLine' && charArrLength <= index"
|
||||
class="tn-code__item__line tn-code__item__line--middle"
|
||||
:class="[{
|
||||
'tn-code__item__line--bold': bold,
|
||||
'tn-code__item--breathe': breathe && charArrLength === index,
|
||||
'tn-code__item__line--active': charArrLength === index
|
||||
}]"
|
||||
:style="[lineStyle(index)]"
|
||||
></view>
|
||||
<view
|
||||
v-if="mode === 'bottomLine'"
|
||||
class="tn-code__item__line tn-code__item__line--bottom"
|
||||
:class="[{
|
||||
'tn-code__item__line--bold': bold,
|
||||
'tn-code__item--breathe': breathe && charArrLength === index,
|
||||
'tn-code__item__line--active': charArrLength === index
|
||||
}]"
|
||||
:style="[lineStyle(index)]"
|
||||
></view>
|
||||
<block v-if="!dotFill">
|
||||
<text>{{ charArr[index] ? charArr[index] : '' }}</text>
|
||||
</block>
|
||||
<block v-else>
|
||||
<text class="tn-code__item__dot">{{ charArr[index] ? '●' : '' }}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-verification-code',
|
||||
props: {
|
||||
// 验证码的值
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 最大输入长度
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
// 显示模式
|
||||
// box -> 盒子 bottomLine -> 底部横线 middleLine -> 中间横线
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'box'
|
||||
},
|
||||
// 用圆点填充空白位置
|
||||
dotFill: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 字体加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 激活时颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 未激活时颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 输入框宽度,单位rpx
|
||||
inputWidth: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
// 当前激活的item带呼吸效果
|
||||
breathe: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自动获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 隐藏原生键盘,当使用自定义键盘的时候设置该参数未true即可
|
||||
disabledKeyboard: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 拆分要显示的字符
|
||||
charArr() {
|
||||
return this.valueModel.split('')
|
||||
},
|
||||
// 当前输入字符的长度
|
||||
charArrLength() {
|
||||
return this.charArr.length
|
||||
},
|
||||
// 输入框的个数
|
||||
loopCharArr() {
|
||||
return new Array(this.maxLength)
|
||||
},
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
style.fontWeight = this.bold ? 'bold' : 'normal'
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + 'rpx'
|
||||
}
|
||||
if (this.inputWidth) {
|
||||
style.width = this.inputWidth + 'rpx'
|
||||
style.height = this.inputWidth + 'rpx'
|
||||
style.lineHeight = this.inputWidth + 'rpx'
|
||||
}
|
||||
if (this.inactiveColor) {
|
||||
style.color = this.inactiveColor
|
||||
style.borderColor = this.inactiveColor
|
||||
}
|
||||
if (this.mode === 'box' && this.charArrLength === index) {
|
||||
style.borderColor = this.activeColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
placeholderLineStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
style.display = this.charArrLength === index ? 'block' : 'none'
|
||||
if (this.inputWidth) {
|
||||
style.height = (this.inputWidth * 0.5) + 'rpx'
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
lineStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this.inactiveColor) {
|
||||
style.backgroundColor = this.inactiveColor
|
||||
}
|
||||
if (this.charArrLength === index && this.activeColor) {
|
||||
style.backgroundColor = this.activeColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(val) {
|
||||
// 转换为字符串
|
||||
val = String(val)
|
||||
// 截掉超出的部分
|
||||
this.valueModel = val.substring(0, this.maxLength)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueModel: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取填写的值
|
||||
getValue(e) {
|
||||
const {
|
||||
value
|
||||
} = e.detail
|
||||
this.valueModel = value
|
||||
// 判断输入的长度是否超出了maxlength的值
|
||||
if (String(value).length > this.maxLength) return
|
||||
// 未达到maxlength之前,触发change事件,否则触发finish事件
|
||||
this.$emit('change', value)
|
||||
this.$emit('input', value)
|
||||
if (String(value).length == this.maxLength) {
|
||||
this.$emit('finish', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-verification-code {
|
||||
text-align: center;
|
||||
|
||||
.tn-code {
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 200%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
z-index: 9;
|
||||
opacity: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 10rpx 10rpx;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: #838383;
|
||||
|
||||
&--breathe {
|
||||
animation: breathe 2s infinite ease;
|
||||
}
|
||||
|
||||
&__box {
|
||||
border: 2rpx solid #AAAAAA;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&--active {
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-duration: 1500ms;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid #01BEFF;
|
||||
}
|
||||
}
|
||||
|
||||
&__line {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: #AAAAAA;
|
||||
|
||||
&--bold {
|
||||
height: 4px !important;
|
||||
}
|
||||
|
||||
&--placeholder {
|
||||
display: none;
|
||||
width: 2rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
&--middle, &--bottom {
|
||||
width: 80%;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
&--bottom {
|
||||
top: auto !important;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%) !important;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: #01BEFF !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
font-size: 34rpx;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,9 @@
|
||||
// 各组件的z-index值
|
||||
export default {
|
||||
landsacpe: 29100,
|
||||
navbar: 29090,
|
||||
toast: 20090,
|
||||
goodsNav: 20089,
|
||||
alert: 20085,
|
||||
modal: 20080,
|
||||
popup: 20075,
|
||||
|
||||
@@ -89,8 +89,24 @@ function getDigit(number) {
|
||||
return digit
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定范围的随机数
|
||||
|
||||
* @param {Object} min 最小值
|
||||
* @param {Object} max 最大值
|
||||
*/
|
||||
function random(min, max) {
|
||||
if (min >= 0 && max > 0 && max >= min) {
|
||||
let gab = max - min + 1
|
||||
return Math.floor(Math.random() * gab + min)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
formatNumberString,
|
||||
formatNumberAddZero,
|
||||
formatNumberAddUnit
|
||||
formatNumberAddUnit,
|
||||
random
|
||||
}
|
||||
@@ -32,7 +32,32 @@ function getLengthUnitValue(value, unit = 'rpx') {
|
||||
else return value + unit
|
||||
}
|
||||
|
||||
/**
|
||||
* 将驼峰命名的字符串转换为指定连接符来进行连接
|
||||
*
|
||||
* @param {Object} string 待转换的字符串
|
||||
* @param {Object} replace 进行连接的字符
|
||||
*/
|
||||
function humpConvertChar(string, replace = '_') {
|
||||
if (!string || !replace) {
|
||||
return ''
|
||||
}
|
||||
return string.replace(/([A-Z])/g, `${replace}$1`).toLowerCase()
|
||||
}
|
||||
|
||||
function charConvertHump(string, replace = '_') {
|
||||
if (!string || !replace) {
|
||||
return ''
|
||||
}
|
||||
let reg = RegExp("//" + replace + "(/w)/", "g")
|
||||
return string.replace(reg, function(all, letter) {
|
||||
return letter.toUpperCase()
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
trim,
|
||||
getLengthUnitValue
|
||||
getLengthUnitValue,
|
||||
humpConvertChar,
|
||||
charConvertHump
|
||||
}
|
||||
Reference in New Issue
Block a user