mirror of
https://gitee.com/TSpecific/tuniao-ui.git
synced 2026-09-05 03:32:46 +08:00
更新图标库
修复已知bug
This commit is contained in:
+3
-3
@@ -1,4 +1,4 @@
|
||||
TuniaoUi for uniApp v1.0.0 | by 图鸟 2021-09-01
|
||||
仅供开发,如作它用所承受的法律责任一概与作者无关
|
||||
|
||||
TuniaoUi for uniApp v1.0.0 | by 图鸟 2021-09-01
|
||||
仅供开发,如作它用所承受的法律责任一概与作者无关
|
||||
|
||||
*使用TuniaoUi开发扩展与插件时,请注明基于tuniao字眼
|
||||
@@ -1,202 +1,202 @@
|
||||
<template>
|
||||
<view v-if="value" class="tn-action-sheet-class tn-action-sheet">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
length="auto"
|
||||
:popup="false"
|
||||
:borderRadius="borderRadius"
|
||||
:maskCloseable="maskCloseable"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:zIndex="elZIndex"
|
||||
@close="close"
|
||||
>
|
||||
<!-- 提示信息 -->
|
||||
<view
|
||||
v-if="tips.text"
|
||||
class="tn-action-sheet__tips tn-border-solid-bottom"
|
||||
:style="[tipsStyle]"
|
||||
>
|
||||
{{tips.text}}
|
||||
</view>
|
||||
<!-- 按钮列表 -->
|
||||
<block v-for="(item, index) in list" :key="index">
|
||||
<view
|
||||
class="tn-action-sheet__item tn-text-ellipsis"
|
||||
:class="[ index < list.length - 1 ? 'tn-border-solid-bottom' : '']"
|
||||
:style="[itemStyle(index)]"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="itemClick(index)"
|
||||
@touchmove.stop.prevent
|
||||
>
|
||||
<text>{{item.text}}</text>
|
||||
<text v-if="item.subText" class="tn-action-sheet__item__subtext tn-text-ellipsis">{{item.subText}}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 取消按钮 -->
|
||||
<block v-if="cancelBtn">
|
||||
<view class="tn-action-sheet__cancel--gab"></view>
|
||||
<view
|
||||
class="tn-action-sheet__cancel tn-action-sheet__item"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="close"
|
||||
>{{cancelText}}</view>
|
||||
</block>
|
||||
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-action-sheet',
|
||||
props: {
|
||||
// 通过v-model控制弹出和收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按钮文字数组,可以自定义颜色和字体大小
|
||||
// return [{
|
||||
// text: '确定',
|
||||
// subText: '这是一个确定按钮',
|
||||
// color: '',
|
||||
// fontSize: '',
|
||||
// disabled: true
|
||||
// }]
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 顶部提示文字
|
||||
tips: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
text: '',
|
||||
color: '',
|
||||
fontSize: 26
|
||||
}
|
||||
}
|
||||
},
|
||||
// 弹出的顶部圆角值
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 点击遮罩可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 开启底部安全区域
|
||||
// 在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// z-index值
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部提示样式
|
||||
tipsStyle() {
|
||||
let style = {}
|
||||
if (this.tips.color) style.color = this.tips.color
|
||||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this.list[index].color) style.color = this.list[index].color
|
||||
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'
|
||||
|
||||
// 选项被禁用的样式
|
||||
if (this.list[index].disabled) style.color = '#AAAAAA'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击取消按钮
|
||||
close() {
|
||||
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
|
||||
this.popupClose();
|
||||
this.$emit('close');
|
||||
},
|
||||
// 关闭弹窗
|
||||
popupClose() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
// 点击对应的item
|
||||
itemClick(index) {
|
||||
// 如果是禁用项则不进行操作
|
||||
if (this.list[index].disabled) return
|
||||
this.$emit('click', index)
|
||||
this.popupClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-action-sheet {
|
||||
&__tips {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
padding: 34rpx 0;
|
||||
line-height: 1;
|
||||
color: $tn-content-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
padding: 34rpx 0;
|
||||
|
||||
&__subtext {
|
||||
font-size: 24rpx;
|
||||
color: $tn-content-color;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
color: $tn-font-color;
|
||||
|
||||
&--gab {
|
||||
height: 12rpx;
|
||||
background-color: #eaeaec;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view v-if="value" class="tn-action-sheet-class tn-action-sheet">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
length="auto"
|
||||
:popup="false"
|
||||
:borderRadius="borderRadius"
|
||||
:maskCloseable="maskCloseable"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:zIndex="elZIndex"
|
||||
@close="close"
|
||||
>
|
||||
<!-- 提示信息 -->
|
||||
<view
|
||||
v-if="tips.text"
|
||||
class="tn-action-sheet__tips tn-border-solid-bottom"
|
||||
:style="[tipsStyle]"
|
||||
>
|
||||
{{tips.text}}
|
||||
</view>
|
||||
<!-- 按钮列表 -->
|
||||
<block v-for="(item, index) in list" :key="index">
|
||||
<view
|
||||
class="tn-action-sheet__item tn-text-ellipsis"
|
||||
:class="[ index < list.length - 1 ? 'tn-border-solid-bottom' : '']"
|
||||
:style="[itemStyle(index)]"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="itemClick(index)"
|
||||
@touchmove.stop.prevent
|
||||
>
|
||||
<text>{{item.text}}</text>
|
||||
<text v-if="item.subText" class="tn-action-sheet__item__subtext tn-text-ellipsis">{{item.subText}}</text>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 取消按钮 -->
|
||||
<block v-if="cancelBtn">
|
||||
<view class="tn-action-sheet__cancel--gab"></view>
|
||||
<view
|
||||
class="tn-action-sheet__cancel tn-action-sheet__item"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@tap="close"
|
||||
>{{cancelText}}</view>
|
||||
</block>
|
||||
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-action-sheet',
|
||||
props: {
|
||||
// 通过v-model控制弹出和收起
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按钮文字数组,可以自定义颜色和字体大小
|
||||
// return [{
|
||||
// text: '确定',
|
||||
// subText: '这是一个确定按钮',
|
||||
// color: '',
|
||||
// fontSize: '',
|
||||
// disabled: true
|
||||
// }]
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 顶部提示文字
|
||||
tips: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
text: '',
|
||||
color: '',
|
||||
fontSize: 26
|
||||
}
|
||||
}
|
||||
},
|
||||
// 弹出的顶部圆角值
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 点击遮罩可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 开启底部安全区域
|
||||
// 在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// z-index值
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部提示样式
|
||||
tipsStyle() {
|
||||
let style = {}
|
||||
if (this.tips.color) style.color = this.tips.color
|
||||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
// 操作项目的样式
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this.list[index].color) style.color = this.list[index].color
|
||||
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'
|
||||
|
||||
// 选项被禁用的样式
|
||||
if (this.list[index].disabled) style.color = '#AAAAAA'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击取消按钮
|
||||
close() {
|
||||
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
|
||||
this.popupClose();
|
||||
this.$emit('close');
|
||||
},
|
||||
// 关闭弹窗
|
||||
popupClose() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
// 点击对应的item
|
||||
itemClick(index) {
|
||||
// 如果是禁用项则不进行操作
|
||||
if (this.list[index].disabled) return
|
||||
this.$emit('click', index)
|
||||
this.popupClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-action-sheet {
|
||||
&__tips {
|
||||
font-size: 26rpx;
|
||||
text-align: center;
|
||||
padding: 34rpx 0;
|
||||
line-height: 1;
|
||||
color: $tn-content-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
padding: 34rpx 0;
|
||||
|
||||
&__subtext {
|
||||
font-size: 24rpx;
|
||||
color: $tn-content-color;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
color: $tn-font-color;
|
||||
|
||||
&--gab {
|
||||
height: 12rpx;
|
||||
background-color: #eaeaec;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
<template>
|
||||
<view class="tn-avatar-group-class tn-avatar-group">
|
||||
<view v-for="(item, index) in lists" :key="index" class="tn-avatar-group__item" :style="[itemStyle(index)]">
|
||||
<tn-avatar
|
||||
:src="item.src || ''"
|
||||
:text="item.text || ''"
|
||||
:icon="item.icon || ''"
|
||||
:size="size"
|
||||
:shape="shape"
|
||||
:imgMode="imgMode"
|
||||
:border="true"
|
||||
:borderSize="4"
|
||||
></tn-avatar>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-avatar-group-class tn-avatar-group">
|
||||
<view v-for="(item, index) in lists" :key="index" class="tn-avatar-group__item" :style="[itemStyle(index)]">
|
||||
<tn-avatar
|
||||
:src="item.src || ''"
|
||||
:text="item.text || ''"
|
||||
:icon="item.icon || ''"
|
||||
:size="size"
|
||||
:shape="shape"
|
||||
:imgMode="imgMode"
|
||||
:border="true"
|
||||
:borderSize="4"
|
||||
></tn-avatar>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-avatar-group',
|
||||
props: {
|
||||
// 头像列表
|
||||
lists: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 头像类型
|
||||
// square 带圆角正方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 大小
|
||||
// sm 小头像 lg 大头像 xl 加大头像
|
||||
// 如果为其他则认为是直接设置大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 当设置为显示头像信息时,
|
||||
// 图片的裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 头像之间的遮挡比例
|
||||
// 0.4 代表 40%
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 0.4
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this._checkSizeIsInline()) {
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.marginLeft = index != 0 ? `${-48 * this.gap}rpx` : ''
|
||||
break
|
||||
case 'lg':
|
||||
style.marginLeft = index != 0 ? `${-96 * this.gap}rpx` : ''
|
||||
break
|
||||
case 'xl':
|
||||
style.marginLeft = index != 0 ? `${-128 * this.gap}rpx` : ''
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const size = Number(this.size.replace(/(px|rpx)/g, '')) || 64
|
||||
style.marginLeft = index != 0 ? `-${size * this.gap}rpx` : ''
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 检查是否使用内置的大小进行设置
|
||||
_checkSizeIsInline() {
|
||||
if (/(xs|sm|md|lg|xl|xxl)/.test(this.size)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-avatar-group',
|
||||
props: {
|
||||
// 头像列表
|
||||
lists: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 头像类型
|
||||
// square 带圆角正方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 大小
|
||||
// sm 小头像 lg 大头像 xl 加大头像
|
||||
// 如果为其他则认为是直接设置大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 当设置为显示头像信息时,
|
||||
// 图片的裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 头像之间的遮挡比例
|
||||
// 0.4 代表 40%
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 0.4
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
itemStyle() {
|
||||
return (index) => {
|
||||
let style = {}
|
||||
if (this._checkSizeIsInline()) {
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.marginLeft = index != 0 ? `${-48 * this.gap}rpx` : ''
|
||||
break
|
||||
case 'lg':
|
||||
style.marginLeft = index != 0 ? `${-96 * this.gap}rpx` : ''
|
||||
break
|
||||
case 'xl':
|
||||
style.marginLeft = index != 0 ? `${-128 * this.gap}rpx` : ''
|
||||
break
|
||||
}
|
||||
} else {
|
||||
const size = Number(this.size.replace(/(px|rpx)/g, '')) || 64
|
||||
style.marginLeft = index != 0 ? `-${size * this.gap}rpx` : ''
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 检查是否使用内置的大小进行设置
|
||||
_checkSizeIsInline() {
|
||||
if (/(xs|sm|md|lg|xl|xxl)/.test(this.size)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-avatar-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.tn-avatar-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,298 +1,298 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-avatar-class tn-avatar"
|
||||
:class="[backgroundColorClass,avatarClass]"
|
||||
:style="[avatarStyle]"
|
||||
@tap="click"
|
||||
>
|
||||
<image
|
||||
v-if="showImg"
|
||||
class="tn-avatar__img"
|
||||
:class="[imgClass]"
|
||||
:src="src"
|
||||
:mode="imgMode || 'aspectFill'"
|
||||
@error="loadImageError"
|
||||
></image>
|
||||
<view v-else class="tn-avatar__text" >
|
||||
<view v-if="text">{{ text }}</view>
|
||||
<view v-else :class="[`tn-icon-${icon}`]"></view>
|
||||
</view>
|
||||
|
||||
<!-- 角标 -->
|
||||
<tn-badge
|
||||
v-if="badge && (badgeIcon || badgeText)"
|
||||
:radius="badgeSize"
|
||||
:backgroundColor="badgeBgColor"
|
||||
:fontColor="badgeColor"
|
||||
:fontSize="badgeSize - 8"
|
||||
:absolute="true"
|
||||
:top="badgePosition[0]"
|
||||
:right="badgePosition[1]"
|
||||
>
|
||||
<view v-if="badgeIcon && badgeText === ''">
|
||||
<view :class="[`tn-icon-${badgeIcon}`]"></view>
|
||||
</view>
|
||||
<view v-else>
|
||||
{{ badgeText }}
|
||||
</view>
|
||||
</tn-badge>
|
||||
<template>
|
||||
<view
|
||||
class="tn-avatar-class tn-avatar"
|
||||
:class="[backgroundColorClass,avatarClass]"
|
||||
:style="[avatarStyle]"
|
||||
@tap="click"
|
||||
>
|
||||
<image
|
||||
v-if="showImg"
|
||||
class="tn-avatar__img"
|
||||
:class="[imgClass]"
|
||||
:src="src"
|
||||
:mode="imgMode || 'aspectFill'"
|
||||
@error="loadImageError"
|
||||
></image>
|
||||
<view v-else class="tn-avatar__text" >
|
||||
<view v-if="text">{{ text }}</view>
|
||||
<view v-else :class="[`tn-icon-${icon}`]"></view>
|
||||
</view>
|
||||
|
||||
<!-- 角标 -->
|
||||
<tn-badge
|
||||
v-if="badge && (badgeIcon || badgeText)"
|
||||
:radius="badgeSize"
|
||||
:backgroundColor="badgeBgColor"
|
||||
:fontColor="badgeColor"
|
||||
:fontSize="badgeSize - 8"
|
||||
:absolute="true"
|
||||
:top="badgePosition[0]"
|
||||
:right="badgePosition[1]"
|
||||
>
|
||||
<view v-if="badgeIcon && badgeText === ''">
|
||||
<view :class="[`tn-icon-${badgeIcon}`]"></view>
|
||||
</view>
|
||||
<view v-else>
|
||||
{{ badgeText }}
|
||||
</view>
|
||||
</tn-badge>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-avatar',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 头像类型
|
||||
// square 带圆角正方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 大小
|
||||
// sm 小头像 lg 大头像 xl 加大头像
|
||||
// 如果为其他则认为是直接设置大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否显示阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.1)'
|
||||
},
|
||||
// 边框大小, rpx
|
||||
borderSize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 头像路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 当设置为显示头像信息时,
|
||||
// 图片的裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 是否显示角标
|
||||
badge: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 设置显示角标后,角标大小
|
||||
badgeSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 角标背景颜色
|
||||
badgeBgColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 角标字体颜色
|
||||
badgeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 角标图标
|
||||
badgeIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 角标文字,优先级比icon高
|
||||
badgeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 角标坐标
|
||||
// [top, right]
|
||||
badgePosition: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 图片显示是否发生错误
|
||||
imgLoadError: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showImg() {
|
||||
// 如果设置了图片地址,则为显示图片,否则为显示文本
|
||||
return this.text === '' && this.icon === ''
|
||||
},
|
||||
avatarClass() {
|
||||
let clazz = ''
|
||||
clazz += ` tn-avatar--${this.shape}`
|
||||
|
||||
if (this._checkSizeIsInline()) {
|
||||
clazz += ` tn-avatar--${this.size}`
|
||||
}
|
||||
|
||||
if (this.shadow) {
|
||||
clazz += ' tn-avatar--shadow'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
avatarStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.background = this.backgroundColorStyle
|
||||
} else if (this.shadow && this.showImg) {
|
||||
style.backgroundImage = `url(${this.src})`
|
||||
}
|
||||
|
||||
if (this.border) {
|
||||
style.border = `${this.borderSize}rpx solid ${this.borderColor}`
|
||||
}
|
||||
|
||||
if (!this._checkSizeIsInline()) {
|
||||
style.width = this.size
|
||||
style.height = this.size
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
imgClass() {
|
||||
let clazz = ''
|
||||
clazz += ` tn-avatar__img--${this.shape}`
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载图片失败
|
||||
loadImageError() {
|
||||
this.imgLoadError = true
|
||||
},
|
||||
// 点击事件
|
||||
click() {
|
||||
this.$emit("click", this.index)
|
||||
},
|
||||
|
||||
// 检查是否使用内置的大小进行设置
|
||||
_checkSizeIsInline() {
|
||||
if (/^(xs|sm|md|lg|xl|xxl)$/.test(this.size)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-avatar',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 头像类型
|
||||
// square 带圆角正方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 大小
|
||||
// sm 小头像 lg 大头像 xl 加大头像
|
||||
// 如果为其他则认为是直接设置大小
|
||||
size: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否显示阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.1)'
|
||||
},
|
||||
// 边框大小, rpx
|
||||
borderSize: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
// 头像路径
|
||||
src: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 文字
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 当设置为显示头像信息时,
|
||||
// 图片的裁剪模式
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
},
|
||||
// 是否显示角标
|
||||
badge: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 设置显示角标后,角标大小
|
||||
badgeSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 角标背景颜色
|
||||
badgeBgColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 角标字体颜色
|
||||
badgeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 角标图标
|
||||
badgeIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 角标文字,优先级比icon高
|
||||
badgeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 角标坐标
|
||||
// [top, right]
|
||||
badgePosition: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0, 0]
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 图片显示是否发生错误
|
||||
imgLoadError: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showImg() {
|
||||
// 如果设置了图片地址,则为显示图片,否则为显示文本
|
||||
return this.text === '' && this.icon === ''
|
||||
},
|
||||
avatarClass() {
|
||||
let clazz = ''
|
||||
clazz += ` tn-avatar--${this.shape}`
|
||||
|
||||
if (this._checkSizeIsInline()) {
|
||||
clazz += ` tn-avatar--${this.size}`
|
||||
}
|
||||
|
||||
if (this.shadow) {
|
||||
clazz += ' tn-avatar--shadow'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
avatarStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.background = this.backgroundColorStyle
|
||||
} else if (this.shadow && this.showImg) {
|
||||
style.backgroundImage = `url(${this.src})`
|
||||
}
|
||||
|
||||
if (this.border) {
|
||||
style.border = `${this.borderSize}rpx solid ${this.borderColor}`
|
||||
}
|
||||
|
||||
if (!this._checkSizeIsInline()) {
|
||||
style.width = this.size
|
||||
style.height = this.size
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
imgClass() {
|
||||
let clazz = ''
|
||||
clazz += ` tn-avatar__img--${this.shape}`
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 加载图片失败
|
||||
loadImageError() {
|
||||
this.imgLoadError = true
|
||||
},
|
||||
// 点击事件
|
||||
click() {
|
||||
this.$emit("click", this.index)
|
||||
},
|
||||
|
||||
// 检查是否使用内置的大小进行设置
|
||||
_checkSizeIsInline() {
|
||||
if (/^(xs|sm|md|lg|xl|xxl)$/.test(this.size)) return true
|
||||
else return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-avatar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $tn-font-holder-color;
|
||||
// color: #FFFFFF;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
z-index: 1;
|
||||
|
||||
&--sm {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
&--lg {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
&--xl {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 5000rpx;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: inherit;
|
||||
filter: blur(10rpx);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
z-index: -1;
|
||||
opacity: 0.4;
|
||||
transform-origin: 0 0;
|
||||
border-radius: inherit;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&--square {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 5000rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-avatar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $tn-font-holder-color;
|
||||
// color: #FFFFFF;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
z-index: 1;
|
||||
|
||||
&--sm {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
&--lg {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
&--xl {
|
||||
width: 128rpx;
|
||||
height: 128rpx;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 5000rpx;
|
||||
}
|
||||
|
||||
&--shadow {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: inherit;
|
||||
filter: blur(10rpx);
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
z-index: -1;
|
||||
opacity: 0.4;
|
||||
transform-origin: 0 0;
|
||||
border-radius: inherit;
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&--square {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 5000rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,173 +1,173 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-badge-class tn-badge"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
badgeClass
|
||||
]"
|
||||
:style="[badgeStyle]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot v-if="!dot"></slot>
|
||||
<template>
|
||||
<view
|
||||
class="tn-badge-class tn-badge"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
badgeClass
|
||||
]"
|
||||
:style="[badgeStyle]"
|
||||
@click="handleClick"
|
||||
>
|
||||
<slot v-if="!dot"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-badge',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 徽章的大小 rpx
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为一个点
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否使用绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// top
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// right
|
||||
right: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 居中 对齐右上角
|
||||
translateCenter: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
badgeClass() {
|
||||
let clazz = ''
|
||||
if (this.dot) {
|
||||
clazz += ' tn-badge--dot'
|
||||
}
|
||||
if (this.absolute) {
|
||||
clazz += ' tn-badge--absolute'
|
||||
|
||||
if (this.translateCenter) {
|
||||
clazz += ' tn-badge--center-position'
|
||||
}
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
badgeStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.radius !== 0) {
|
||||
style.width = this.radius + 'rpx'
|
||||
style.height = this.radius + 'rpx'
|
||||
style.lineHeight = this.radius + 'rpx'
|
||||
|
||||
// style.borderRadius = (this.radius * 8) + 'rpx'
|
||||
}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.top) {
|
||||
style.top = this.$t.string.getLengthUnitValue(this.top)
|
||||
}
|
||||
if (this.right) {
|
||||
style.right = this.$t.string.getLengthUnitValue(this.right)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-badge',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 徽章的大小 rpx
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为一个点
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否使用绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// top
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// right
|
||||
right: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 居中 对齐右上角
|
||||
translateCenter: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
badgeClass() {
|
||||
let clazz = ''
|
||||
if (this.dot) {
|
||||
clazz += ' tn-badge--dot'
|
||||
}
|
||||
if (this.absolute) {
|
||||
clazz += ' tn-badge--absolute'
|
||||
|
||||
if (this.translateCenter) {
|
||||
clazz += ' tn-badge--center-position'
|
||||
}
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
badgeStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.radius !== 0) {
|
||||
style.width = this.radius + 'rpx'
|
||||
style.height = this.radius + 'rpx'
|
||||
style.lineHeight = this.radius + 'rpx'
|
||||
|
||||
// style.borderRadius = (this.radius * 8) + 'rpx'
|
||||
}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.top) {
|
||||
style.top = this.$t.string.getLengthUnitValue(this.top)
|
||||
}
|
||||
if (this.right) {
|
||||
style.right = this.$t.string.getLengthUnitValue(this.right)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-badge {
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
font-size: 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
// color: #FFFFFF;
|
||||
border-radius: 100rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
line-height: initial;
|
||||
|
||||
&--dot {
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
}
|
||||
&--absolute {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
&--center-position {
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.tn-badge {
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
font-size: 20rpx;
|
||||
background-color: #FFFFFF;
|
||||
// color: #FFFFFF;
|
||||
border-radius: 100rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
line-height: initial;
|
||||
|
||||
&--dot {
|
||||
width: 8rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
}
|
||||
&--absolute {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
&--center-position {
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,302 +1,302 @@
|
||||
<template>
|
||||
<button
|
||||
class="tn-btn-class tn-btn"
|
||||
:class="[
|
||||
buttonClass,
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[buttonStyle]"
|
||||
hover-class="tn-hover"
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
@getuserinfo="handleGetUserInfo"
|
||||
@getphonenumber="handleGetPhoneNumber"
|
||||
@contact="handleContact"
|
||||
@error="handleError"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: "tn-button",
|
||||
// 解决再微信小程序种,自定义按钮无法触发bindsubmit
|
||||
behaviors: ['wx://form-field-button'],
|
||||
props: {
|
||||
// 按钮索引,用于区分多个按钮
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 按钮形状 default 默认 round 圆角 icon 图标按钮
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 是否加阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 宽度 rpx或%
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 高度 rpx或%
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮的尺寸 sm lg
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体是否加粗
|
||||
fontBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0 30rpx'
|
||||
},
|
||||
// 外边距 与css的margin参数用法相同
|
||||
margin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当plain=true时,是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当plain=true时,是否加粗显示边框
|
||||
borderBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示加载图标
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 触发form表单的事件类型
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 开放能力
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否阻止重复点击(默认间隔是200ms)
|
||||
blockRepeatClick: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 根据不同的参数动态生成class
|
||||
buttonClass() {
|
||||
let clazz = ''
|
||||
// 按钮形状
|
||||
switch (this.shape) {
|
||||
case 'icon':
|
||||
case 'round':
|
||||
clazz += ' tn-round'
|
||||
break
|
||||
}
|
||||
|
||||
// 阴影
|
||||
if (this.shadow) {
|
||||
if (this.backgroundColorClass !== '' && this.backgroundColorClass.indexOf('tn-bg') != -1) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-shadow-${color}`
|
||||
} else {
|
||||
clazz += ' tn-shadow-blur'
|
||||
}
|
||||
}
|
||||
|
||||
// 字体加粗
|
||||
if (this.fontBold) {
|
||||
clazz += ' tn-text-bold'
|
||||
}
|
||||
|
||||
// 设置为镂空并且设置镂空便可才进行设置
|
||||
if (this.plain) {
|
||||
clazz += ' tn-btn--plain'
|
||||
if (this.border) {
|
||||
clazz += ' tn-border-solid'
|
||||
if (this.borderBold) {
|
||||
clazz += ' tn-bold-border'
|
||||
}
|
||||
if (this.backgroundColor !== '' && this.backgroundColor.includes('tn-bg')) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-border-${color}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
// 按钮的样式
|
||||
buttonStyle() {
|
||||
let style = {}
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.padding = '0 20rpx'
|
||||
style.fontSize = '22rpx'
|
||||
style.height = this.height || '48rpx'
|
||||
break
|
||||
case 'lg':
|
||||
style.padding = '0 40rpx'
|
||||
style.fontSize = '32rpx'
|
||||
style.height = this.height || '80rpx'
|
||||
break
|
||||
default :
|
||||
style.padding = '0 30rpx'
|
||||
style.fontSize = '28rpx'
|
||||
style.height = this.height || '64rpx'
|
||||
}
|
||||
|
||||
// 是否手动设置了内边距
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
|
||||
// 是否手动设置外边距
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
|
||||
// 是否手动设置了字体大小
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
style.width = this.shape === 'icon' ? style.height : this.width
|
||||
style.padding = this.shape === 'icon' ? '0' : style.padding
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
if (!this.backgroundColorClass) {
|
||||
if (this.plain) {
|
||||
style.borderColor = this.backgroundColorStyle || '#080808'
|
||||
} else {
|
||||
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
|
||||
}
|
||||
}
|
||||
|
||||
// 设置阴影
|
||||
if (this.shadow && !this.backgroundColorClass) {
|
||||
if (this.backgroundColorStyle.indexOf('#') != -1) {
|
||||
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || '#000000')}10`
|
||||
} else if (this.backgroundColorStyle.indexOf('rgb') != -1 || this.backgroundColorStyle.indexOf('rgba') != -1 || !this.backgroundColorStyle) {
|
||||
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || 'rgba(0, 0, 0, 0.1)')}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 上次点击的时间
|
||||
clickTime: 0,
|
||||
// 两次点击防抖的间隔时间
|
||||
clickIntervalTime: 200
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击事件
|
||||
handleClick() {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
if (this.blockRepeatClick) {
|
||||
const nowTime = new Date().getTime()
|
||||
if (nowTime - this.clickTime <= this.clickIntervalTime) {
|
||||
return
|
||||
}
|
||||
this.clickTime = nowTime
|
||||
setTimeout(() => {
|
||||
this.clickTime = 0
|
||||
}, this.clickIntervalTime)
|
||||
}
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
// 兼容tap事件
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
handleGetUserInfo({ detail = {} } = {}) {
|
||||
this.$emit('getuserinfo', detail);
|
||||
},
|
||||
handleContact({ detail = {} } = {}) {
|
||||
this.$emit('contact', detail);
|
||||
},
|
||||
handleGetPhoneNumber({ detail = {} } = {}) {
|
||||
this.$emit('getphonenumber', detail);
|
||||
},
|
||||
handleError({ detail = {} } = {}) {
|
||||
this.$emit('error', detail);
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-btn {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
overflow: visible;
|
||||
transform: translate(0rpx, 0rpx);
|
||||
// background-color: $tn-mai
|
||||
border-radius: 12rpx;
|
||||
// color: $tn-font-color;
|
||||
margin: 0;
|
||||
|
||||
&--plain {
|
||||
background-color: transparent !important;
|
||||
background-image: none;
|
||||
|
||||
&.tn-round {
|
||||
border-radius: 1000rpx !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<button
|
||||
class="tn-btn-class tn-btn"
|
||||
:class="[
|
||||
buttonClass,
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[buttonStyle]"
|
||||
hover-class="tn-hover"
|
||||
:loading="loading"
|
||||
:disabled="disabled"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
@getuserinfo="handleGetUserInfo"
|
||||
@getphonenumber="handleGetPhoneNumber"
|
||||
@contact="handleContact"
|
||||
@error="handleError"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: "tn-button",
|
||||
// 解决再微信小程序种,自定义按钮无法触发bindsubmit
|
||||
behaviors: ['wx://form-field-button'],
|
||||
props: {
|
||||
// 按钮索引,用于区分多个按钮
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 按钮形状 default 默认 round 圆角 icon 图标按钮
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
// 是否加阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 宽度 rpx或%
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 高度 rpx或%
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮的尺寸 sm lg
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体是否加粗
|
||||
fontBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '0 30rpx'
|
||||
},
|
||||
// 外边距 与css的margin参数用法相同
|
||||
margin: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当plain=true时,是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当plain=true时,是否加粗显示边框
|
||||
borderBold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示加载图标
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 触发form表单的事件类型
|
||||
formType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 开放能力
|
||||
openType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否阻止重复点击(默认间隔是200ms)
|
||||
blockRepeatClick: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 根据不同的参数动态生成class
|
||||
buttonClass() {
|
||||
let clazz = ''
|
||||
// 按钮形状
|
||||
switch (this.shape) {
|
||||
case 'icon':
|
||||
case 'round':
|
||||
clazz += ' tn-round'
|
||||
break
|
||||
}
|
||||
|
||||
// 阴影
|
||||
if (this.shadow) {
|
||||
if (this.backgroundColorClass !== '' && this.backgroundColorClass.indexOf('tn-bg') != -1) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-shadow-${color}`
|
||||
} else {
|
||||
clazz += ' tn-shadow-blur'
|
||||
}
|
||||
}
|
||||
|
||||
// 字体加粗
|
||||
if (this.fontBold) {
|
||||
clazz += ' tn-text-bold'
|
||||
}
|
||||
|
||||
// 设置为镂空并且设置镂空便可才进行设置
|
||||
if (this.plain) {
|
||||
clazz += ' tn-btn--plain'
|
||||
if (this.border) {
|
||||
clazz += ' tn-border-solid'
|
||||
if (this.borderBold) {
|
||||
clazz += ' tn-bold-border'
|
||||
}
|
||||
if (this.backgroundColor !== '' && this.backgroundColor.includes('tn-bg')) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-border-${color}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
// 按钮的样式
|
||||
buttonStyle() {
|
||||
let style = {}
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.padding = '0 20rpx'
|
||||
style.fontSize = '22rpx'
|
||||
style.height = this.height || '48rpx'
|
||||
break
|
||||
case 'lg':
|
||||
style.padding = '0 40rpx'
|
||||
style.fontSize = '32rpx'
|
||||
style.height = this.height || '80rpx'
|
||||
break
|
||||
default :
|
||||
style.padding = '0 30rpx'
|
||||
style.fontSize = '28rpx'
|
||||
style.height = this.height || '64rpx'
|
||||
}
|
||||
|
||||
// 是否手动设置了内边距
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
|
||||
// 是否手动设置外边距
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
|
||||
// 是否手动设置了字体大小
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
style.width = this.shape === 'icon' ? style.height : this.width
|
||||
style.padding = this.shape === 'icon' ? '0' : style.padding
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
if (!this.backgroundColorClass) {
|
||||
if (this.plain) {
|
||||
style.borderColor = this.backgroundColorStyle || '#080808'
|
||||
} else {
|
||||
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
|
||||
}
|
||||
}
|
||||
|
||||
// 设置阴影
|
||||
if (this.shadow && !this.backgroundColorClass) {
|
||||
if (this.backgroundColorStyle.indexOf('#') != -1) {
|
||||
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || '#000000')}10`
|
||||
} else if (this.backgroundColorStyle.indexOf('rgb') != -1 || this.backgroundColorStyle.indexOf('rgba') != -1 || !this.backgroundColorStyle) {
|
||||
style.boxShadow = `6rpx 6rpx 8rpx ${(this.backgroundColorStyle || 'rgba(0, 0, 0, 0.1)')}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 上次点击的时间
|
||||
clickTime: 0,
|
||||
// 两次点击防抖的间隔时间
|
||||
clickIntervalTime: 200
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 按钮点击事件
|
||||
handleClick() {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
if (this.blockRepeatClick) {
|
||||
const nowTime = new Date().getTime()
|
||||
if (nowTime - this.clickTime <= this.clickIntervalTime) {
|
||||
return
|
||||
}
|
||||
this.clickTime = nowTime
|
||||
setTimeout(() => {
|
||||
this.clickTime = 0
|
||||
}, this.clickIntervalTime)
|
||||
}
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
// 兼容tap事件
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
handleGetUserInfo({ detail = {} } = {}) {
|
||||
this.$emit('getuserinfo', detail);
|
||||
},
|
||||
handleContact({ detail = {} } = {}) {
|
||||
this.$emit('contact', detail);
|
||||
},
|
||||
handleGetPhoneNumber({ detail = {} } = {}) {
|
||||
this.$emit('getphonenumber', detail);
|
||||
},
|
||||
handleError({ detail = {} } = {}) {
|
||||
this.$emit('error', detail);
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-btn {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
overflow: visible;
|
||||
transform: translate(0rpx, 0rpx);
|
||||
// background-color: $tn-mai
|
||||
border-radius: 12rpx;
|
||||
// color: $tn-font-color;
|
||||
margin: 0;
|
||||
|
||||
&--plain {
|
||||
background-color: transparent !important;
|
||||
background-image: none;
|
||||
|
||||
&.tn-round {
|
||||
border-radius: 1000rpx !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,320 +1,320 @@
|
||||
<template>
|
||||
<view class="tn-car-keyboard-class tn-car-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="tn-car-keyboard__grids">
|
||||
|
||||
<view
|
||||
v-for="(data, index) in inputCarNumber ? endKeyBoardList : areaList"
|
||||
:key="index"
|
||||
class="tn-car-keyboard__grids__item"
|
||||
>
|
||||
<view
|
||||
v-for="(sub_data, sub_index) in data"
|
||||
:key="sub_index"
|
||||
class="tn-car-keyboard__grids__btn"
|
||||
:class="{'tn-car-keyboard__grids__btn--disabled': sub_data === 'I'}"
|
||||
:hover-class="sub_data !== 'I' ? 'tn-car-keyboard--hover' : ''"
|
||||
:hover-stay-time="100"
|
||||
@tap="click(index, sub_index)"
|
||||
>
|
||||
{{ sub_data }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-car-keyboard__back"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@touchstart.stop="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-left-arrow tn-car-keyboard__back__icon"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-car-keyboard__change"
|
||||
hover-class="tn-car-keyboard--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="changeMode"
|
||||
>
|
||||
<text class="tn-car-keyboard__mode--zh" :class="[`tn-car-keyboard__mode--${!inputCarNumber ? 'active' : 'inactive'}`]">中</text>
|
||||
/
|
||||
<text class="tn-car-keyboard__mode--en" :class="[`tn-car-keyboard__mode--${inputCarNumber ? 'active' : 'inactive'}`]">英</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-car-keyboard-class tn-car-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="tn-car-keyboard__grids">
|
||||
|
||||
<view
|
||||
v-for="(data, index) in inputCarNumber ? endKeyBoardList : areaList"
|
||||
:key="index"
|
||||
class="tn-car-keyboard__grids__item"
|
||||
>
|
||||
<view
|
||||
v-for="(sub_data, sub_index) in data"
|
||||
:key="sub_index"
|
||||
class="tn-car-keyboard__grids__btn"
|
||||
:class="{'tn-car-keyboard__grids__btn--disabled': sub_data === 'I'}"
|
||||
:hover-class="sub_data !== 'I' ? 'tn-car-keyboard--hover' : ''"
|
||||
:hover-stay-time="100"
|
||||
@tap="click(index, sub_index)"
|
||||
>
|
||||
{{ sub_data }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-car-keyboard__back"
|
||||
hover-class="tn-hover-class"
|
||||
:hover-stay-time="150"
|
||||
@touchstart.stop="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-left-arrow tn-car-keyboard__back__icon"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-car-keyboard__change"
|
||||
hover-class="tn-car-keyboard--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="changeMode"
|
||||
>
|
||||
<text class="tn-car-keyboard__mode--zh" :class="[`tn-car-keyboard__mode--${!inputCarNumber ? 'active' : 'inactive'}`]">中</text>
|
||||
/
|
||||
<text class="tn-car-keyboard__mode--en" :class="[`tn-car-keyboard__mode--${inputCarNumber ? 'active' : 'inactive'}`]">英</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-car-keyboard',
|
||||
props: {
|
||||
// 是否打乱键盘顺序
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 切换中英文输入
|
||||
switchEnMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学',
|
||||
'临',
|
||||
'警'
|
||||
]
|
||||
// 打乱顺序
|
||||
if (this.randomEnabled) data = this.$t.array.random(data)
|
||||
// 切割二维数组
|
||||
let showData = []
|
||||
showData[0] = data.slice(0, 10)
|
||||
showData[1] = data.slice(10, 20)
|
||||
showData[2] = data.slice(20, 30)
|
||||
showData[3] = data.slice(30, 37)
|
||||
return showData
|
||||
},
|
||||
endKeyBoardList() {
|
||||
let data = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
]
|
||||
// 打乱顺序
|
||||
if (this.randomEnabled) data = this.$t.array.random(data)
|
||||
// 切割二维数组
|
||||
let showData = []
|
||||
showData[0] = data.slice(0, 10)
|
||||
showData[1] = data.slice(10, 20)
|
||||
showData[2] = data.slice(20, 29)
|
||||
showData[3] = data.slice(29, 36)
|
||||
return showData
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 标记是否输入车牌号码
|
||||
inputCarNumber: false,
|
||||
// 长按多次删除事件监听
|
||||
longPressDeleteTimer: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
switchEnMode: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.inputCarNumber = true
|
||||
} else {
|
||||
this.inputCarNumber = false
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击键盘按钮
|
||||
click(i, j) {
|
||||
let value = ''
|
||||
// 根据不同模式获取不同数组的值
|
||||
if (this.inputCarNumber) value = this.endKeyBoardList[i][j]
|
||||
else value = this.areaList[i][j]
|
||||
|
||||
// 车牌里不包含I
|
||||
if (value === 'I') return
|
||||
|
||||
this.$emit('change', value)
|
||||
},
|
||||
// 修改输入模式
|
||||
// 中文/英文
|
||||
changeMode() {
|
||||
this.inputCarNumber = !this.inputCarNumber
|
||||
},
|
||||
// 点击退格
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
this.clearTimer()
|
||||
this.longPressDeleteTimer = setInterval(() => {
|
||||
this.$emit('backspace')
|
||||
}, 250)
|
||||
},
|
||||
// 清空定时器
|
||||
clearTimer() {
|
||||
if (this.longPressDeleteTimer) {
|
||||
clearInterval(this.longPressDeleteTimer)
|
||||
this.longPressDeleteTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-car-keyboard',
|
||||
props: {
|
||||
// 是否打乱键盘顺序
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 切换中英文输入
|
||||
switchEnMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
areaList() {
|
||||
let data = [
|
||||
'京',
|
||||
'沪',
|
||||
'粤',
|
||||
'津',
|
||||
'冀',
|
||||
'豫',
|
||||
'云',
|
||||
'辽',
|
||||
'黑',
|
||||
'湘',
|
||||
'皖',
|
||||
'鲁',
|
||||
'苏',
|
||||
'浙',
|
||||
'赣',
|
||||
'鄂',
|
||||
'桂',
|
||||
'甘',
|
||||
'晋',
|
||||
'陕',
|
||||
'蒙',
|
||||
'吉',
|
||||
'闽',
|
||||
'贵',
|
||||
'渝',
|
||||
'川',
|
||||
'青',
|
||||
'琼',
|
||||
'宁',
|
||||
'藏',
|
||||
'港',
|
||||
'澳',
|
||||
'新',
|
||||
'使',
|
||||
'学',
|
||||
'临',
|
||||
'警'
|
||||
]
|
||||
// 打乱顺序
|
||||
if (this.randomEnabled) data = this.$t.array.random(data)
|
||||
// 切割二维数组
|
||||
let showData = []
|
||||
showData[0] = data.slice(0, 10)
|
||||
showData[1] = data.slice(10, 20)
|
||||
showData[2] = data.slice(20, 30)
|
||||
showData[3] = data.slice(30, 37)
|
||||
return showData
|
||||
},
|
||||
endKeyBoardList() {
|
||||
let data = [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
0,
|
||||
'Q',
|
||||
'W',
|
||||
'E',
|
||||
'R',
|
||||
'T',
|
||||
'Y',
|
||||
'U',
|
||||
'I',
|
||||
'O',
|
||||
'P',
|
||||
'A',
|
||||
'S',
|
||||
'D',
|
||||
'F',
|
||||
'G',
|
||||
'H',
|
||||
'J',
|
||||
'K',
|
||||
'L',
|
||||
'Z',
|
||||
'X',
|
||||
'C',
|
||||
'V',
|
||||
'B',
|
||||
'N',
|
||||
'M'
|
||||
]
|
||||
// 打乱顺序
|
||||
if (this.randomEnabled) data = this.$t.array.random(data)
|
||||
// 切割二维数组
|
||||
let showData = []
|
||||
showData[0] = data.slice(0, 10)
|
||||
showData[1] = data.slice(10, 20)
|
||||
showData[2] = data.slice(20, 29)
|
||||
showData[3] = data.slice(29, 36)
|
||||
return showData
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 标记是否输入车牌号码
|
||||
inputCarNumber: false,
|
||||
// 长按多次删除事件监听
|
||||
longPressDeleteTimer: null
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
switchEnMode: {
|
||||
handler(value) {
|
||||
if (value) {
|
||||
this.inputCarNumber = true
|
||||
} else {
|
||||
this.inputCarNumber = false
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击键盘按钮
|
||||
click(i, j) {
|
||||
let value = ''
|
||||
// 根据不同模式获取不同数组的值
|
||||
if (this.inputCarNumber) value = this.endKeyBoardList[i][j]
|
||||
else value = this.areaList[i][j]
|
||||
|
||||
// 车牌里不包含I
|
||||
if (value === 'I') return
|
||||
|
||||
this.$emit('change', value)
|
||||
},
|
||||
// 修改输入模式
|
||||
// 中文/英文
|
||||
changeMode() {
|
||||
this.inputCarNumber = !this.inputCarNumber
|
||||
},
|
||||
// 点击退格
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
this.clearTimer()
|
||||
this.longPressDeleteTimer = setInterval(() => {
|
||||
this.$emit('backspace')
|
||||
}, 250)
|
||||
},
|
||||
// 清空定时器
|
||||
clearTimer() {
|
||||
if (this.longPressDeleteTimer) {
|
||||
clearInterval(this.longPressDeleteTimer)
|
||||
this.longPressDeleteTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-car-keyboard {
|
||||
position: relative;
|
||||
padding: 24rpx 0;
|
||||
background-color: #E6E6E6;
|
||||
|
||||
&__grids {
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
flex: 0 0 64rpx;
|
||||
width: 62rpx;
|
||||
height: 80rpx;
|
||||
font-size: 38rpx;
|
||||
line-height: 80rpx;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
background-color: #FFFFFF;
|
||||
margin: 8rpx 5rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__back {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
height: 80rpx;
|
||||
right: 22rpx;
|
||||
bottom: 32rpx;
|
||||
background-color: #E6E6E6;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
}
|
||||
|
||||
&__change {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
height: 80rpx;
|
||||
left: 22rpx;
|
||||
bottom: 32rpx;
|
||||
line-height: 1;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
}
|
||||
|
||||
&__mode {
|
||||
&--zh {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
&--en {
|
||||
transform: translateY(10rpx);
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: $tn-main-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
&.tn-car-keyboard__mode--zh {
|
||||
transform: scale(0.85) translateY(-10rpx);
|
||||
}
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
&.tn-car-keyboard__mode--en {
|
||||
transform: scale(0.85) translateY(10rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: #E6E6E6 !important;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-car-keyboard {
|
||||
position: relative;
|
||||
padding: 24rpx 0;
|
||||
background-color: #E6E6E6;
|
||||
|
||||
&__grids {
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
flex: 0 0 64rpx;
|
||||
width: 62rpx;
|
||||
height: 80rpx;
|
||||
font-size: 38rpx;
|
||||
line-height: 80rpx;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
background-color: #FFFFFF;
|
||||
margin: 8rpx 5rpx;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__back {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
height: 80rpx;
|
||||
right: 22rpx;
|
||||
bottom: 32rpx;
|
||||
background-color: #E6E6E6;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
}
|
||||
|
||||
&__change {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 96rpx;
|
||||
height: 80rpx;
|
||||
left: 22rpx;
|
||||
bottom: 32rpx;
|
||||
line-height: 1;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 0rpx $tn-box-shadow-color;
|
||||
}
|
||||
|
||||
&__mode {
|
||||
&--zh {
|
||||
transform: translateY(-10rpx);
|
||||
}
|
||||
&--en {
|
||||
transform: translateY(10rpx);
|
||||
}
|
||||
|
||||
&--active {
|
||||
color: $tn-main-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
&.tn-car-keyboard__mode--zh {
|
||||
transform: scale(0.85) translateY(-10rpx);
|
||||
}
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
&.tn-car-keyboard__mode--en {
|
||||
transform: scale(0.85) translateY(10rpx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: #E6E6E6 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,134 +1,134 @@
|
||||
<template>
|
||||
<view class="tn-checkbox-group-class tn-checkbox-group">
|
||||
<slot></slot>
|
||||
<template>
|
||||
<view class="tn-checkbox-group-class tn-checkbox-group">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [ Emitter ],
|
||||
name: 'tn-checkbox-group',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 可以选中多少个checkbox
|
||||
max: {
|
||||
type: Number,
|
||||
default: 999
|
||||
},
|
||||
// 表单提交时的标识符
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 禁用选择
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 每个checkbox占的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件tn-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(tn-checkbox-group)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [this.value, this.disabled, this.disabledLabel, this.shape, this.activeColor, this.size, this.width, this.wrap, this.iconSize]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件中的子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-checkbox)如果有updateParentData方法的话,子组件重新从父组件拉取了最新的值
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
initValue(values) {
|
||||
this.$emit('input', values)
|
||||
},
|
||||
// 触发事件
|
||||
emitEvent() {
|
||||
let values = []
|
||||
this.children.map(child => {
|
||||
if (child.checkValue) values.push(child.name)
|
||||
})
|
||||
this.$emit('change', values)
|
||||
this.$emit('input', values)
|
||||
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 tn-form-item 进行校验
|
||||
this.dispatch('tn-form-item', 'on-form-change', values)
|
||||
}, 60)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [ Emitter ],
|
||||
name: 'tn-checkbox-group',
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 可以选中多少个checkbox
|
||||
max: {
|
||||
type: Number,
|
||||
default: 999
|
||||
},
|
||||
// 表单提交时的标识符
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 禁用选择
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 每个checkbox占的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件tn-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(tn-checkbox-group)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [this.value, this.disabled, this.disabledLabel, this.shape, this.activeColor, this.size, this.width, this.wrap, this.iconSize]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件中的子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-checkbox)如果有updateParentData方法的话,子组件重新从父组件拉取了最新的值
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
initValue(values) {
|
||||
this.$emit('input', values)
|
||||
},
|
||||
// 触发事件
|
||||
emitEvent() {
|
||||
let values = []
|
||||
this.children.map(child => {
|
||||
if (child.checkValue) values.push(child.name)
|
||||
})
|
||||
this.$emit('change', values)
|
||||
this.$emit('input', values)
|
||||
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 tn-form-item 进行校验
|
||||
this.dispatch('tn-form-item', 'on-form-change', values)
|
||||
}, 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-checkbox-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
&::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-checkbox-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
&::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,328 +1,328 @@
|
||||
<template>
|
||||
<view class="tn-checkbox-class tn-checkbox" :style="[checkboxStyle]">
|
||||
<view
|
||||
class="tn-checkbox__icon-wrap"
|
||||
:class="[iconClass]"
|
||||
:style="[iconStyle]"
|
||||
@tap="toggle"
|
||||
>
|
||||
<view class="tn-checkbox__icon-wrap__icon" :class="[`tn-icon-${iconName}`]"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-checkbox__label"
|
||||
:class="[labelClass]"
|
||||
:style="{
|
||||
fontSize: labelSize ? labelSize + 'rpx' : ''
|
||||
}"
|
||||
@tap="onClickLabel"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-checkbox-class tn-checkbox" :style="[checkboxStyle]">
|
||||
<view
|
||||
class="tn-checkbox__icon-wrap"
|
||||
:class="[iconClass]"
|
||||
:style="[iconStyle]"
|
||||
@tap="toggle"
|
||||
>
|
||||
<view class="tn-checkbox__icon-wrap__icon" :class="[`tn-icon-${iconName}`]"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-checkbox__label"
|
||||
:class="[labelClass]"
|
||||
:style="{
|
||||
fontSize: labelSize ? labelSize + 'rpx' : ''
|
||||
}"
|
||||
@tap="onClickLabel"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-checkbox',
|
||||
props: {
|
||||
// checkbox名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否为选中状态
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用选择
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标名称
|
||||
iconName: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label的字体大小
|
||||
labelSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用选中,父组件的禁用会覆盖当前的禁用状态
|
||||
isDisabled() {
|
||||
return this.disabled ? this.disabled : (this.parent ? this.parentData.disabled : false)
|
||||
},
|
||||
// 是否禁用点击label选中,父组件的禁用会覆盖当前的禁用状态
|
||||
isDisabledLabel() {
|
||||
return this.disabledLabel ? this.disabledLabel : (this.parent ? this.parentData.disabledLabel : false)
|
||||
},
|
||||
// 尺寸
|
||||
checkboxSize() {
|
||||
return this.size ? this.size : (this.parent ? this.parentData.size : 34)
|
||||
},
|
||||
// 激活时的颜色
|
||||
elAvtiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parent ? this.parentData.activeColor : '#01BEFF')
|
||||
},
|
||||
// 形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parent ? this.parentData.shape : 'square')
|
||||
},
|
||||
iconClass() {
|
||||
let clazz = ''
|
||||
clazz += (' tn-checkbox__icon-wrap--' + this.elShape)
|
||||
|
||||
if (this.checkValue) clazz += ' tn-checkbox__icon-wrap--checked'
|
||||
if (this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled'
|
||||
if (this.value && this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled--checked'
|
||||
|
||||
return clazz
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
// 判断是否用户手动禁用和传递的值
|
||||
if (this.elAvtiveColor && this.checkValue && !this.isDisabled) {
|
||||
style.borderColor = this.elAvtiveColor
|
||||
style.backgroundColor = this.elAvtiveColor
|
||||
}
|
||||
|
||||
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
|
||||
style.color = this.checkValue ? '#FFFFFF' : 'transparent'
|
||||
|
||||
style.width = this.checkboxSize + 'rpx'
|
||||
style.height = style.width
|
||||
|
||||
style.fontSize = (this.iconSize ? this.iconSize : (this.parent ? this.parentData.iconSize : 20)) + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
checkboxStyle() {
|
||||
let style = {}
|
||||
if (this.parent && this.parentData.width) {
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parentData.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parentData.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
labelClass() {
|
||||
let clazz = ''
|
||||
if (this.isDisabled) {
|
||||
clazz += ' tn-checkbox__label--disabled'
|
||||
}
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前checkbox的value值
|
||||
checkValue: false,
|
||||
parentData: {
|
||||
value: null,
|
||||
max: null,
|
||||
disabled: null,
|
||||
disabledLabel: null,
|
||||
shape: null,
|
||||
activeColor: null,
|
||||
size: null,
|
||||
width: null,
|
||||
wrap: null,
|
||||
iconSize: null
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.checkValue = val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
// this.parent = this.$t.$parent.call(this, 'tn-checkbox-group')
|
||||
// // 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
|
||||
// this.parent && this.parent.children.push(this)
|
||||
// // 初始化父组件的value值
|
||||
// this.parent && this.parent.emitEvent()
|
||||
this.updateParentData()
|
||||
this.parent && this.parent.children.push(this)
|
||||
},
|
||||
methods: {
|
||||
updateCheckValue() {
|
||||
// 更新当前checkbox的选中状态
|
||||
this.checkValue = (this.parent && this.parentData.value.includes(this.name)) || this.value === true
|
||||
if (this.parent) {
|
||||
if (this.value && !this.parentData.value.includes(this.name)) {
|
||||
this.parentData.value.push(this.name)
|
||||
this.parent.initValue(this.parentData.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
updateParentData() {
|
||||
this.getParentData('tn-checkbox-group')
|
||||
this.updateCheckValue()
|
||||
},
|
||||
onClickLabel() {
|
||||
if (!this.isDisabled && !this.isDisabledLabel) {
|
||||
this.setValue()
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setValue()
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', {
|
||||
name: this.name,
|
||||
value: !this.checkValue
|
||||
})
|
||||
if (this.parent) {
|
||||
this.checkValue = !this.checkValue
|
||||
// 执行父组件tn-checkbox-group的事件方法
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
setTimeout(() => {
|
||||
if(this.parent.emitEvent) this.parent.emitEvent();
|
||||
}, 80)
|
||||
}
|
||||
},
|
||||
// 设置input的值,通过v-modal绑定组件的值
|
||||
setValue() {
|
||||
// 判断是否为可选项组
|
||||
if (this.parent) {
|
||||
// 反转状态
|
||||
if (this.checkValue === true) {
|
||||
this.emitEvent()
|
||||
// this.$emit('input', !this.checkValue)
|
||||
} else {
|
||||
// 超出最大可选项,弹出提示
|
||||
if (this.parentData.value.length >= this.parentData.max) {
|
||||
return this.$t.messageUtils.toast(`最多可选${this.parent.max}项`)
|
||||
}
|
||||
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
|
||||
this.emitEvent();
|
||||
// this.$emit('input', !this.checkValue);
|
||||
}
|
||||
} else {
|
||||
// 只有一个可选项
|
||||
this.emitEvent()
|
||||
this.$emit('input', !this.checkValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-checkbox',
|
||||
props: {
|
||||
// checkbox名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否为选中状态
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用选择
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标名称
|
||||
iconName: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label的字体大小
|
||||
labelSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用选中,父组件的禁用会覆盖当前的禁用状态
|
||||
isDisabled() {
|
||||
return this.disabled ? this.disabled : (this.parent ? this.parentData.disabled : false)
|
||||
},
|
||||
// 是否禁用点击label选中,父组件的禁用会覆盖当前的禁用状态
|
||||
isDisabledLabel() {
|
||||
return this.disabledLabel ? this.disabledLabel : (this.parent ? this.parentData.disabledLabel : false)
|
||||
},
|
||||
// 尺寸
|
||||
checkboxSize() {
|
||||
return this.size ? this.size : (this.parent ? this.parentData.size : 34)
|
||||
},
|
||||
// 激活时的颜色
|
||||
elAvtiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parent ? this.parentData.activeColor : '#01BEFF')
|
||||
},
|
||||
// 形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parent ? this.parentData.shape : 'square')
|
||||
},
|
||||
iconClass() {
|
||||
let clazz = ''
|
||||
clazz += (' tn-checkbox__icon-wrap--' + this.elShape)
|
||||
|
||||
if (this.checkValue) clazz += ' tn-checkbox__icon-wrap--checked'
|
||||
if (this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled'
|
||||
if (this.value && this.isDisabled) clazz += ' tn-checkbox__icon-wrap--disabled--checked'
|
||||
|
||||
return clazz
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
// 判断是否用户手动禁用和传递的值
|
||||
if (this.elAvtiveColor && this.checkValue && !this.isDisabled) {
|
||||
style.borderColor = this.elAvtiveColor
|
||||
style.backgroundColor = this.elAvtiveColor
|
||||
}
|
||||
|
||||
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
|
||||
style.color = this.checkValue ? '#FFFFFF' : 'transparent'
|
||||
|
||||
style.width = this.checkboxSize + 'rpx'
|
||||
style.height = style.width
|
||||
|
||||
style.fontSize = (this.iconSize ? this.iconSize : (this.parent ? this.parentData.iconSize : 20)) + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
checkboxStyle() {
|
||||
let style = {}
|
||||
if (this.parent && this.parentData.width) {
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parentData.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parentData.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
labelClass() {
|
||||
let clazz = ''
|
||||
if (this.isDisabled) {
|
||||
clazz += ' tn-checkbox__label--disabled'
|
||||
}
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前checkbox的value值
|
||||
checkValue: false,
|
||||
parentData: {
|
||||
value: null,
|
||||
max: null,
|
||||
disabled: null,
|
||||
disabledLabel: null,
|
||||
shape: null,
|
||||
activeColor: null,
|
||||
size: null,
|
||||
width: null,
|
||||
wrap: null,
|
||||
iconSize: null
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.checkValue = val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
|
||||
// this.parent = this.$t.$parent.call(this, 'tn-checkbox-group')
|
||||
// // 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
|
||||
// this.parent && this.parent.children.push(this)
|
||||
// // 初始化父组件的value值
|
||||
// this.parent && this.parent.emitEvent()
|
||||
this.updateParentData()
|
||||
this.parent && this.parent.children.push(this)
|
||||
},
|
||||
methods: {
|
||||
updateCheckValue() {
|
||||
// 更新当前checkbox的选中状态
|
||||
this.checkValue = (this.parent && this.parentData.value.includes(this.name)) || this.value === true
|
||||
if (this.parent) {
|
||||
if (this.value && !this.parentData.value.includes(this.name)) {
|
||||
this.parentData.value.push(this.name)
|
||||
this.parent.initValue(this.parentData.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
updateParentData() {
|
||||
this.getParentData('tn-checkbox-group')
|
||||
this.updateCheckValue()
|
||||
},
|
||||
onClickLabel() {
|
||||
if (!this.isDisabled && !this.isDisabledLabel) {
|
||||
this.setValue()
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setValue()
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
this.$emit('change', {
|
||||
name: this.name,
|
||||
value: !this.checkValue
|
||||
})
|
||||
if (this.parent) {
|
||||
this.checkValue = !this.checkValue
|
||||
// 执行父组件tn-checkbox-group的事件方法
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
setTimeout(() => {
|
||||
if(this.parent.emitEvent) this.parent.emitEvent();
|
||||
}, 80)
|
||||
}
|
||||
},
|
||||
// 设置input的值,通过v-modal绑定组件的值
|
||||
setValue() {
|
||||
// 判断是否为可选项组
|
||||
if (this.parent) {
|
||||
// 反转状态
|
||||
if (this.checkValue === true) {
|
||||
this.emitEvent()
|
||||
// this.$emit('input', !this.checkValue)
|
||||
} else {
|
||||
// 超出最大可选项,弹出提示
|
||||
if (this.parentData.value.length >= this.parentData.max) {
|
||||
return this.$t.message.toast(`最多可选${this.parent.max}项`)
|
||||
}
|
||||
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
|
||||
this.emitEvent();
|
||||
// this.$emit('input', !this.checkValue);
|
||||
}
|
||||
} else {
|
||||
// 只有一个可选项
|
||||
this.emitEvent()
|
||||
this.$emit('input', !this.checkValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-checkbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $tn-font-color;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
border: 1px solid $tn-font-sub-color;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
border-color: $tn-main-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: $tn-font-holder-color;
|
||||
border-color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: $tn-font-sub-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-checkbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $tn-font-color;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
border: 1px solid $tn-font-sub-color;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
border-color: $tn-main-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: $tn-font-holder-color;
|
||||
border-color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: $tn-font-sub-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,223 +1,223 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-circle-progress-class tn-circle-progress"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
>
|
||||
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
|
||||
<!-- 默认圆环 -->
|
||||
<canvas
|
||||
class="tn-circle-progress__canvas-bg"
|
||||
:canvas-id="elBgId"
|
||||
:id="elBgId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<!-- 进度圆环 -->
|
||||
<canvas
|
||||
class="tn-circle-progress__canvas"
|
||||
:canvas-id="elId"
|
||||
:id="elId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<view class="tn-circle-progress__content">
|
||||
<slot v-if="$slots.default || $slots.$default"></slot>
|
||||
<view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
class="tn-circle-progress-class tn-circle-progress"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
>
|
||||
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
|
||||
<!-- 默认圆环 -->
|
||||
<canvas
|
||||
class="tn-circle-progress__canvas-bg"
|
||||
:canvas-id="elBgId"
|
||||
:id="elBgId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<!-- 进度圆环 -->
|
||||
<canvas
|
||||
class="tn-circle-progress__canvas"
|
||||
:canvas-id="elId"
|
||||
:id="elId"
|
||||
:style="{
|
||||
width: widthPx + 'px',
|
||||
height: widthPx + 'px'
|
||||
}"
|
||||
></canvas>
|
||||
<view class="tn-circle-progress__content">
|
||||
<slot v-if="$slots.default || $slots.$default"></slot>
|
||||
<view v-else-if="showPercent" class="tn-circle-progress__content__percent">{{ percent + '%' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-circle-progress',
|
||||
props: {
|
||||
// 进度(百分比)
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100
|
||||
}
|
||||
},
|
||||
// 圆环线宽
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
default: 14
|
||||
},
|
||||
// 整体圆的宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否运动
|
||||
stripedActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 激活部分颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 非激活部分颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#f0f0f0'
|
||||
},
|
||||
// 是否显示进度条内部百分比值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆环执行动画的时间,ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1500
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 微信小程序中不能使用this.$t.uuid()形式动态生成id值,否则会报错
|
||||
// #ifdef MP-WEIXIN
|
||||
elBgId: 'tCircleProgressBgId',
|
||||
elId: 'tCircleProgressElId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
elBgId: this.$t.uuid(),
|
||||
elId: this.$t.uuid(),
|
||||
// #endif
|
||||
// 活动圆上下文
|
||||
progressContext: null,
|
||||
// 转换成px为单位的背景宽度
|
||||
widthPx: uni.upx2px(this.width || 200),
|
||||
// 转换成px为单位的圆环宽度
|
||||
borderWidthPx: uni.upx2px(this.borderWidth || 14),
|
||||
// canvas画圆的起始角度,默认为-90度,顺时针
|
||||
startAngle: -90 * Math.PI / 180,
|
||||
// 动态修改进度值的时候,保存进度值的变化前后值
|
||||
newPercent: 0,
|
||||
oldPercent: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
percent(newVal, oldVal = 0) {
|
||||
if (newVal > 100) newVal = 100
|
||||
if (oldVal < 0) oldVal = 0
|
||||
|
||||
this.newPercent = newVal
|
||||
this.oldPercent = oldVal
|
||||
setTimeout(() => {
|
||||
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
|
||||
// 将此值减少或者新增到新的百分比值
|
||||
this.drawCircleByProgress(oldVal)
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 赋值,用于加载后第一个画圆使用
|
||||
this.newPercent = this.percent;
|
||||
this.oldPercent = 0;
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.drawProgressBg()
|
||||
this.drawCircleByProgress(this.oldPercent)
|
||||
}, 50)
|
||||
},
|
||||
methods: {
|
||||
// 绘制进度条背景
|
||||
drawProgressBg() {
|
||||
let ctx = uni.createCanvasContext(this.elBgId, this)
|
||||
// 设置线宽
|
||||
ctx.setLineWidth(this.borderWidthPx)
|
||||
// 设置颜色
|
||||
ctx.setStrokeStyle(this.inactiveColor)
|
||||
ctx.beginPath()
|
||||
let radius = this.widthPx / 2
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false)
|
||||
ctx.stroke()
|
||||
ctx.draw()
|
||||
},
|
||||
// 绘制圆弧的进度
|
||||
drawCircleByProgress(progress) {
|
||||
// 如果已经存在则拿来使用
|
||||
let ctx = this.progressContext
|
||||
if (!ctx) {
|
||||
ctx =uni.createCanvasContext(this.elId, this)
|
||||
this.progressContext = ctx
|
||||
}
|
||||
ctx.setLineCap('round')
|
||||
// 设置线条宽度和颜色
|
||||
ctx.setLineWidth(this.borderWidthPx)
|
||||
ctx.setStrokeStyle(this.activeColor)
|
||||
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
|
||||
let preSecondTime = Math.floor(this.duration / 100)
|
||||
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
|
||||
let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle
|
||||
let radius = this.widthPx / 2
|
||||
ctx.beginPath()
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false)
|
||||
ctx.stroke()
|
||||
ctx.draw()
|
||||
|
||||
// 如果变更后新值大于旧值,意味着增大了百分比
|
||||
if (this.newPercent > this.oldPercent) {
|
||||
// 每次递增百分之一
|
||||
progress++
|
||||
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
|
||||
if (progress > this.newPercent) return
|
||||
} else {
|
||||
progress--
|
||||
if (progress < this.newPercent) return
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 定时器,每次操作间隔为time值,为了让进度条有动画效果
|
||||
this.drawCircleByProgress(progress)
|
||||
}, preSecondTime)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-circle-progress',
|
||||
props: {
|
||||
// 进度(百分比)
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100
|
||||
}
|
||||
},
|
||||
// 圆环线宽
|
||||
borderWidth: {
|
||||
type: Number,
|
||||
default: 14
|
||||
},
|
||||
// 整体圆的宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 200
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否运动
|
||||
stripedActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 激活部分颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 非激活部分颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#f0f0f0'
|
||||
},
|
||||
// 是否显示进度条内部百分比值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆环执行动画的时间,ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1500
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 微信小程序中不能使用this.$t.uuid()形式动态生成id值,否则会报错
|
||||
// #ifdef MP-WEIXIN
|
||||
elBgId: 'tCircleProgressBgId',
|
||||
elId: 'tCircleProgressElId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
elBgId: this.$t.uuid(),
|
||||
elId: this.$t.uuid(),
|
||||
// #endif
|
||||
// 活动圆上下文
|
||||
progressContext: null,
|
||||
// 转换成px为单位的背景宽度
|
||||
widthPx: uni.upx2px(this.width || 200),
|
||||
// 转换成px为单位的圆环宽度
|
||||
borderWidthPx: uni.upx2px(this.borderWidth || 14),
|
||||
// canvas画圆的起始角度,默认为-90度,顺时针
|
||||
startAngle: -90 * Math.PI / 180,
|
||||
// 动态修改进度值的时候,保存进度值的变化前后值
|
||||
newPercent: 0,
|
||||
oldPercent: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
percent(newVal, oldVal = 0) {
|
||||
if (newVal > 100) newVal = 100
|
||||
if (oldVal < 0) oldVal = 0
|
||||
|
||||
this.newPercent = newVal
|
||||
this.oldPercent = oldVal
|
||||
setTimeout(() => {
|
||||
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
|
||||
// 将此值减少或者新增到新的百分比值
|
||||
this.drawCircleByProgress(oldVal)
|
||||
}, 50)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 赋值,用于加载后第一个画圆使用
|
||||
this.newPercent = this.percent;
|
||||
this.oldPercent = 0;
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.drawProgressBg()
|
||||
this.drawCircleByProgress(this.oldPercent)
|
||||
}, 50)
|
||||
},
|
||||
methods: {
|
||||
// 绘制进度条背景
|
||||
drawProgressBg() {
|
||||
let ctx = uni.createCanvasContext(this.elBgId, this)
|
||||
// 设置线宽
|
||||
ctx.setLineWidth(this.borderWidthPx)
|
||||
// 设置颜色
|
||||
ctx.setStrokeStyle(this.inactiveColor)
|
||||
ctx.beginPath()
|
||||
let radius = this.widthPx / 2
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 360 * Math.PI / 180, false)
|
||||
ctx.stroke()
|
||||
ctx.draw()
|
||||
},
|
||||
// 绘制圆弧的进度
|
||||
drawCircleByProgress(progress) {
|
||||
// 如果已经存在则拿来使用
|
||||
let ctx = this.progressContext
|
||||
if (!ctx) {
|
||||
ctx =uni.createCanvasContext(this.elId, this)
|
||||
this.progressContext = ctx
|
||||
}
|
||||
ctx.setLineCap('round')
|
||||
// 设置线条宽度和颜色
|
||||
ctx.setLineWidth(this.borderWidthPx)
|
||||
ctx.setStrokeStyle(this.activeColor)
|
||||
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
|
||||
let preSecondTime = Math.floor(this.duration / 100)
|
||||
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
|
||||
let endAngle = ((360 * Math.PI / 180) / 100) * progress + this.startAngle
|
||||
let radius = this.widthPx / 2
|
||||
ctx.beginPath()
|
||||
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false)
|
||||
ctx.stroke()
|
||||
ctx.draw()
|
||||
|
||||
// 如果变更后新值大于旧值,意味着增大了百分比
|
||||
if (this.newPercent > this.oldPercent) {
|
||||
// 每次递增百分之一
|
||||
progress++
|
||||
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
|
||||
if (progress > this.newPercent) return
|
||||
} else {
|
||||
progress--
|
||||
if (progress < this.newPercent) return
|
||||
}
|
||||
setTimeout(() => {
|
||||
// 定时器,每次操作间隔为time值,为了让进度条有动画效果
|
||||
this.drawCircleByProgress(progress)
|
||||
}, preSecondTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-circle-progress {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
|
||||
&__canvas {
|
||||
position: absolute;
|
||||
|
||||
&-bg {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__percent {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-circle-progress {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
|
||||
&__canvas {
|
||||
position: absolute;
|
||||
|
||||
&-bg {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__percent {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,236 +1,236 @@
|
||||
<template>
|
||||
<view class="tn-collapse-item-class tn-collapse-item" :style="[itemStyle]">
|
||||
<!-- 头部 -->
|
||||
<view
|
||||
class="tn-collapse-item__head"
|
||||
:style="[headStyle]"
|
||||
:hover-stay-time="200"
|
||||
:hover-class="hoverClass"
|
||||
@tap.stop="headClick"
|
||||
>
|
||||
<block v-if="!$slots['title-all'] || !$slots['$title-all']">
|
||||
<view
|
||||
v-if="!$slots.title || !$slots.$title"
|
||||
class="tn-collapse-item__head__title tn-text-ellipsis"
|
||||
:style="[
|
||||
{ textAlign: align ? align : 'left'},
|
||||
isShow && activeStyle && !arrow ? activeStyle : ''
|
||||
]"
|
||||
>{{ title }}</view>
|
||||
<view v-else>
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
<view class="tn-collapse-item__head__icon__wrap">
|
||||
<view
|
||||
v-if="arrow"
|
||||
class="tn-icon-down tn-collapse-item__head__icon__arrow"
|
||||
:class="{'tn-collapse-item__head__icon__arrow--active': isShow}"
|
||||
:style="[arrowIconStyle]"
|
||||
></view>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else>
|
||||
<slot name="title-all"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
class="tn-collapse-item__body"
|
||||
:style="[{
|
||||
height: isShow ? height + 'px' : '0'
|
||||
}]"
|
||||
>
|
||||
<view class="tn-collapse-item__body__content" :id="elId" :style="[bodyStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-collapse-item-class tn-collapse-item" :style="[itemStyle]">
|
||||
<!-- 头部 -->
|
||||
<view
|
||||
class="tn-collapse-item__head"
|
||||
:style="[headStyle]"
|
||||
:hover-stay-time="200"
|
||||
:hover-class="hoverClass"
|
||||
@tap.stop="headClick"
|
||||
>
|
||||
<block v-if="!$slots['title-all'] || !$slots['$title-all']">
|
||||
<view
|
||||
v-if="!$slots.title || !$slots.$title"
|
||||
class="tn-collapse-item__head__title tn-text-ellipsis"
|
||||
:style="[
|
||||
{ textAlign: align ? align : 'left'},
|
||||
isShow && activeStyle && !arrow ? activeStyle : ''
|
||||
]"
|
||||
>{{ title }}</view>
|
||||
<view v-else>
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
<view class="tn-collapse-item__head__icon__wrap">
|
||||
<view
|
||||
v-if="arrow"
|
||||
class="tn-icon-down tn-collapse-item__head__icon__arrow"
|
||||
:class="{'tn-collapse-item__head__icon__arrow--active': isShow}"
|
||||
:style="[arrowIconStyle]"
|
||||
></view>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else>
|
||||
<slot name="title-all"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
class="tn-collapse-item__body"
|
||||
:style="[{
|
||||
height: isShow ? height + 'px' : '0'
|
||||
}]"
|
||||
>
|
||||
<view class="tn-collapse-item__body__content" :id="elId" :style="[bodyStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-collapse-item',
|
||||
props: {
|
||||
// 展开
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 唯一标识
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 点击不收起
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 活动时样式
|
||||
activeStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标识
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
arrowIconStyle() {
|
||||
let style = {}
|
||||
if (this.arrowColor) {
|
||||
style.color = this.arrowColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
elId: this.$t.uuid(),
|
||||
// body高度
|
||||
height: 0,
|
||||
// 头部样式
|
||||
headStyle: {},
|
||||
// 主体样式
|
||||
bodyStyle: {},
|
||||
// item样式
|
||||
itemStyle: {},
|
||||
// 显示右边箭头
|
||||
arrow: true,
|
||||
// 箭头颜色
|
||||
arrowColor: '',
|
||||
// 点击头部时的效果样式
|
||||
hoverClass: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
open(value) {
|
||||
this.isShow = value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.isShow = this.open
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容或者修改了内容时重新获取内容的信息
|
||||
init() {
|
||||
this.parent = this.$t.$parent.call(this, 'tn-collapse')
|
||||
if (this.parent) {
|
||||
this.nameSync = this.name ? this.name : this.parent.childrens.length
|
||||
// 不存在才添加对应实例
|
||||
!this.parent.childrens.includes(this) && this.parent.childrens.push(this)
|
||||
this.headStyle = this.parent.headStyle
|
||||
this.bodyStyle = this.parent.bodyStyle
|
||||
this.itemStyle = this.parent.itemStyle
|
||||
this.arrow = this.parent.arrow
|
||||
this.arrowColor = this.parent.arrowColor
|
||||
this.hoverClass = this.parent.hoverClass
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.queryRect()
|
||||
})
|
||||
},
|
||||
// 点击头部
|
||||
headClick() {
|
||||
if (this.disabled) return
|
||||
if (this.parent && this.parent.accordion) {
|
||||
this.parent.childrens.map(child => {
|
||||
// 如果是手风琴模式,将其他的item关闭
|
||||
if (this !== child) {
|
||||
child.isShow = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.isShow = !this.isShow
|
||||
// 触发修改事件
|
||||
this.$emit('change', {
|
||||
index: this.index,
|
||||
show: this.isShow
|
||||
})
|
||||
// 只有在打开时才触发父元素的change
|
||||
if (this.isShow) this.parent && this.parent.onChange()
|
||||
this.$forceUpdate()
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
this._tGetRect('#'+this.elId).then(res => {
|
||||
this.height = res.height
|
||||
})
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-collapse-item',
|
||||
props: {
|
||||
// 展开
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 唯一标识
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题对齐方式
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 点击不收起
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 活动时样式
|
||||
activeStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 标识
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
arrowIconStyle() {
|
||||
let style = {}
|
||||
if (this.arrowColor) {
|
||||
style.color = this.arrowColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
elId: this.$t.uuid(),
|
||||
// body高度
|
||||
height: 0,
|
||||
// 头部样式
|
||||
headStyle: {},
|
||||
// 主体样式
|
||||
bodyStyle: {},
|
||||
// item样式
|
||||
itemStyle: {},
|
||||
// 显示右边箭头
|
||||
arrow: true,
|
||||
// 箭头颜色
|
||||
arrowColor: '',
|
||||
// 点击头部时的效果样式
|
||||
hoverClass: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
open(value) {
|
||||
this.isShow = value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.isShow = this.open
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 异步获取内容或者修改了内容时重新获取内容的信息
|
||||
init() {
|
||||
this.parent = this.$t.$parent.call(this, 'tn-collapse')
|
||||
if (this.parent) {
|
||||
this.nameSync = this.name ? this.name : this.parent.childrens.length
|
||||
// 不存在才添加对应实例
|
||||
!this.parent.childrens.includes(this) && this.parent.childrens.push(this)
|
||||
this.headStyle = this.parent.headStyle
|
||||
this.bodyStyle = this.parent.bodyStyle
|
||||
this.itemStyle = this.parent.itemStyle
|
||||
this.arrow = this.parent.arrow
|
||||
this.arrowColor = this.parent.arrowColor
|
||||
this.hoverClass = this.parent.hoverClass
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.queryRect()
|
||||
})
|
||||
},
|
||||
// 点击头部
|
||||
headClick() {
|
||||
if (this.disabled) return
|
||||
if (this.parent && this.parent.accordion) {
|
||||
this.parent.childrens.map(child => {
|
||||
// 如果是手风琴模式,将其他的item关闭
|
||||
if (this !== child) {
|
||||
child.isShow = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.isShow = !this.isShow
|
||||
// 触发修改事件
|
||||
this.$emit('change', {
|
||||
index: this.index,
|
||||
show: this.isShow
|
||||
})
|
||||
// 只有在打开时才触发父元素的change
|
||||
if (this.isShow) this.parent && this.parent.onChange()
|
||||
this.$forceUpdate()
|
||||
},
|
||||
// 查询内容高度
|
||||
queryRect() {
|
||||
this._tGetRect('#'+this.elId).then(res => {
|
||||
this.height = res.height
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-collapse-item {
|
||||
|
||||
&__head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
line-height: 1;
|
||||
padding: 24rpx 0;
|
||||
padding-left: 24rpx;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&__arrow {
|
||||
transition: all 0.3s;
|
||||
margin-right: 20rpx;
|
||||
margin-left: 14rpx;
|
||||
font-size: inherit;
|
||||
|
||||
&--active {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-collapse-item {
|
||||
|
||||
&__head {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
line-height: 1;
|
||||
padding: 24rpx 0;
|
||||
padding-left: 24rpx;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__title {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&__arrow {
|
||||
transition: all 0.3s;
|
||||
margin-right: 20rpx;
|
||||
margin-left: 14rpx;
|
||||
font-size: inherit;
|
||||
|
||||
&--active {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
text-align: left;
|
||||
background-color: #FFFFFF;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,96 +1,96 @@
|
||||
<template>
|
||||
<view class="tn-collapse-class tn-collapse">
|
||||
<slot></slot>
|
||||
<template>
|
||||
<view class="tn-collapse-class tn-collapse">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-collapse',
|
||||
props: {
|
||||
// 是否为手风琴
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 头部样式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 主题样式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 每一个item的样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 显示箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 箭头颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 点击标题栏时的按压样式
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'tn-hover'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
parentData() {
|
||||
return [this.headStyle, this.bodyStyle, this.itemStyle, this.arrow, this.arrowColor, this.hoverClass]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
parentData() {
|
||||
// 如果父组件的参数发生变化重新初始化子组件的信息
|
||||
if (this.childrens.length > 0) {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化内部所有子元素计算高度,异步获取数据时重新渲染
|
||||
init() {
|
||||
this.childrens.forEach((child, index) => {
|
||||
child.init()
|
||||
})
|
||||
},
|
||||
// collapseItem被点击时由collapseItem调用父组件
|
||||
onChange() {
|
||||
let activeItem = []
|
||||
this.childrens.forEach((child, index) => {
|
||||
if (child.isShow) {
|
||||
activeItem.push(child.nameSync)
|
||||
}
|
||||
})
|
||||
// 如果时手风琴模式,只有一个匹配结果,即activeItem长度为1
|
||||
if (this.accordion) activeItem = activeItem.join(',')
|
||||
this.$emit('change', activeItem)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-collapse',
|
||||
props: {
|
||||
// 是否为手风琴
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 头部样式
|
||||
headStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 主题样式
|
||||
bodyStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 每一个item的样式
|
||||
itemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 显示箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 箭头颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 点击标题栏时的按压样式
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'tn-hover'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
parentData() {
|
||||
return [this.headStyle, this.bodyStyle, this.itemStyle, this.arrow, this.arrowColor, this.hoverClass]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
parentData() {
|
||||
// 如果父组件的参数发生变化重新初始化子组件的信息
|
||||
if (this.childrens.length > 0) {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
// 重新初始化内部所有子元素计算高度,异步获取数据时重新渲染
|
||||
init() {
|
||||
this.childrens.forEach((child, index) => {
|
||||
child.init()
|
||||
})
|
||||
},
|
||||
// collapseItem被点击时由collapseItem调用父组件
|
||||
onChange() {
|
||||
let activeItem = []
|
||||
this.childrens.forEach((child, index) => {
|
||||
if (child.isShow) {
|
||||
activeItem.push(child.nameSync)
|
||||
}
|
||||
})
|
||||
// 如果时手风琴模式,只有一个匹配结果,即activeItem长度为1
|
||||
if (this.accordion) activeItem = activeItem.join(',')
|
||||
this.$emit('change', activeItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,318 +1,318 @@
|
||||
<template>
|
||||
<text
|
||||
class="tn-color-icon-class tn-color-icon"
|
||||
:class="[
|
||||
'tn-color-icon-' + name
|
||||
]"
|
||||
:style="{
|
||||
fontSize: size + unit,
|
||||
margin: margin
|
||||
}"
|
||||
@tap="handleClick"
|
||||
<template>
|
||||
<text
|
||||
class="tn-color-icon-class tn-color-icon"
|
||||
:class="[
|
||||
'tn-color-icon-' + name
|
||||
]"
|
||||
:style="{
|
||||
fontSize: size + unit,
|
||||
margin: margin
|
||||
}"
|
||||
@tap="handleClick"
|
||||
></text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-color-icon',
|
||||
props: {
|
||||
// 索引
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 图标名称
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小
|
||||
size: {
|
||||
type: Number,
|
||||
default:32
|
||||
},
|
||||
// 大小单位
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit("click", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit("tap", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-color-icon',
|
||||
props: {
|
||||
// 索引
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 图标名称
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小
|
||||
size: {
|
||||
type: Number,
|
||||
default:32
|
||||
},
|
||||
// 大小单位
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit("click", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit("tap", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: "tuniaoColorFont"; /* Project id 2445412 */
|
||||
/* Color fonts */
|
||||
src: url('iconfont.woff2?t=1632654518618') format('woff2');
|
||||
}
|
||||
|
||||
.tn-color-icon {
|
||||
font-family: "tuniaoColorFont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-github:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-qq:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-weixin:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-alipay:before {
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-weibo:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-dingtalk:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.tn-color-icon-safe:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.tn-color-icon-wifi:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.tn-color-icon-help:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.tn-color-icon-tag:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.tn-color-icon-play:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.tn-color-icon-stopwatch:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.tn-color-icon-home:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.tn-color-icon-map:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.tn-color-icon-book:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.tn-color-icon-qrcode:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.tn-color-icon-discover:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.tn-color-icon-visitor:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.tn-color-icon-menu:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.tn-color-icon-renew:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.tn-color-icon-business:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.tn-color-icon-telephone:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.tn-color-icon-medicine:before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
.tn-color-icon-chicken:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.tn-color-icon-clock:before {
|
||||
content: "\e619";
|
||||
}
|
||||
|
||||
.tn-color-icon-download:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
.tn-color-icon-lamp:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
||||
.tn-color-icon-hourglass:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.tn-color-icon-calendar:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.tn-color-icon-bluetooth:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.tn-color-icon-fish:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.tn-color-icon-seal:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.tn-color-icon-remind:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.tn-color-icon-music:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.tn-color-icon-email:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.tn-color-icon-medal:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.tn-color-icon-image:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.tn-color-icon-network:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.tn-color-icon-wallet:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.tn-color-icon-program:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.tn-color-icon-shrimp:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.tn-color-icon-collect:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.tn-color-icon-screw:before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
.tn-color-icon-set:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.tn-color-icon-userfavorite:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.tn-color-icon-useradd:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.tn-color-icon-honor:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.tn-color-icon-shop:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.tn-color-icon-usercard:before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.tn-color-icon-school:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.tn-color-icon-user:before {
|
||||
content: "\e633";
|
||||
}
|
||||
|
||||
.tn-color-icon-internet:before {
|
||||
content: "\e634";
|
||||
}
|
||||
|
||||
.tn-color-icon-time:before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
||||
.tn-color-icon-topic:before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
.tn-color-icon-phone:before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
.tn-color-icon-usertable:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.tn-color-icon-userset:before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.tn-color-icon-game:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
<style scoped>
|
||||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: "tuniaoColorFont"; /* Project id 2445412 */
|
||||
/* Color fonts */
|
||||
src: url('iconfont.woff2?t=1632654518618') format('woff2');
|
||||
}
|
||||
|
||||
.tn-color-icon {
|
||||
font-family: "tuniaoColorFont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-github:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-qq:before {
|
||||
content: "\e602";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-weixin:before {
|
||||
content: "\e603";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-alipay:before {
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-weibo:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.tn-color-icon-logo-dingtalk:before {
|
||||
content: "\e606";
|
||||
}
|
||||
|
||||
.tn-color-icon-safe:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.tn-color-icon-wifi:before {
|
||||
content: "\e608";
|
||||
}
|
||||
|
||||
.tn-color-icon-help:before {
|
||||
content: "\e609";
|
||||
}
|
||||
|
||||
.tn-color-icon-tag:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.tn-color-icon-play:before {
|
||||
content: "\e60b";
|
||||
}
|
||||
|
||||
.tn-color-icon-stopwatch:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.tn-color-icon-home:before {
|
||||
content: "\e60d";
|
||||
}
|
||||
|
||||
.tn-color-icon-map:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.tn-color-icon-book:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.tn-color-icon-qrcode:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.tn-color-icon-discover:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.tn-color-icon-visitor:before {
|
||||
content: "\e612";
|
||||
}
|
||||
|
||||
.tn-color-icon-menu:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.tn-color-icon-renew:before {
|
||||
content: "\e614";
|
||||
}
|
||||
|
||||
.tn-color-icon-business:before {
|
||||
content: "\e615";
|
||||
}
|
||||
|
||||
.tn-color-icon-telephone:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.tn-color-icon-medicine:before {
|
||||
content: "\e617";
|
||||
}
|
||||
|
||||
.tn-color-icon-chicken:before {
|
||||
content: "\e618";
|
||||
}
|
||||
|
||||
.tn-color-icon-clock:before {
|
||||
content: "\e619";
|
||||
}
|
||||
|
||||
.tn-color-icon-download:before {
|
||||
content: "\e61a";
|
||||
}
|
||||
|
||||
.tn-color-icon-lamp:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
|
||||
.tn-color-icon-hourglass:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.tn-color-icon-calendar:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.tn-color-icon-bluetooth:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.tn-color-icon-fish:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.tn-color-icon-seal:before {
|
||||
content: "\e620";
|
||||
}
|
||||
|
||||
.tn-color-icon-remind:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.tn-color-icon-music:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.tn-color-icon-email:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.tn-color-icon-medal:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.tn-color-icon-image:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.tn-color-icon-network:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.tn-color-icon-wallet:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.tn-color-icon-program:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.tn-color-icon-shrimp:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.tn-color-icon-collect:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
||||
.tn-color-icon-screw:before {
|
||||
content: "\e62b";
|
||||
}
|
||||
|
||||
.tn-color-icon-set:before {
|
||||
content: "\e62c";
|
||||
}
|
||||
|
||||
.tn-color-icon-userfavorite:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.tn-color-icon-useradd:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.tn-color-icon-honor:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.tn-color-icon-shop:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.tn-color-icon-usercard:before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.tn-color-icon-school:before {
|
||||
content: "\e632";
|
||||
}
|
||||
|
||||
.tn-color-icon-user:before {
|
||||
content: "\e633";
|
||||
}
|
||||
|
||||
.tn-color-icon-internet:before {
|
||||
content: "\e634";
|
||||
}
|
||||
|
||||
.tn-color-icon-time:before {
|
||||
content: "\e635";
|
||||
}
|
||||
|
||||
.tn-color-icon-topic:before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
.tn-color-icon-phone:before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
.tn-color-icon-usertable:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.tn-color-icon-userset:before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.tn-color-icon-game:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,251 +1,251 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-column-notice-class tn-column-notice"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[noticeStyle]"
|
||||
>
|
||||
<!-- 左图标 -->
|
||||
<view class="tn-column-notice__icon">
|
||||
<view
|
||||
v-if="leftIcon"
|
||||
class="tn-column-notice__icon--left"
|
||||
:class="[`tn-icon-${leftIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('leftIcon')]"
|
||||
@tap="clickLeftIcon"></view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动显示内容 -->
|
||||
<swiper class="tn-column-notice__swiper" :style="[swiperStyle]" :vertical="vertical" circular :autoplay="autoplay && playStatus === 'play'" :interval="duration" @change="change">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" class="tn-column-notice__swiper--item">
|
||||
<view
|
||||
class="tn-column-notice__swiper--content tn-text-ellipsis"
|
||||
:class="[fontColorClass]"
|
||||
:style="[fontStyle()]"
|
||||
@tap="click(index)"
|
||||
>{{ item }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 右图标 -->
|
||||
<view class="tn-column-notice__icon">
|
||||
<view
|
||||
v-if="rightIcon"
|
||||
class="tn-column-notice__icon--right"
|
||||
:class="[`tn-icon-${rightIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('rightIcon')]"
|
||||
@tap="clickRightIcon"></view>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-column-notice__icon--right"
|
||||
:class="[`tn-icon-close`,fontColorClass]"
|
||||
:style="[fontStyle('close')]"
|
||||
@tap="close"></view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
class="tn-column-notice-class tn-column-notice"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[noticeStyle]"
|
||||
>
|
||||
<!-- 左图标 -->
|
||||
<view class="tn-column-notice__icon">
|
||||
<view
|
||||
v-if="leftIcon"
|
||||
class="tn-column-notice__icon--left"
|
||||
:class="[`tn-icon-${leftIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('leftIcon')]"
|
||||
@tap="clickLeftIcon"></view>
|
||||
</view>
|
||||
|
||||
<!-- 滚动显示内容 -->
|
||||
<swiper class="tn-column-notice__swiper" :style="[swiperStyle]" :vertical="vertical" circular :autoplay="autoplay && playStatus === 'play'" :interval="duration" @change="change">
|
||||
<swiper-item v-for="(item, index) in list" :key="index" class="tn-column-notice__swiper--item">
|
||||
<view
|
||||
class="tn-column-notice__swiper--content tn-text-ellipsis"
|
||||
:class="[fontColorClass]"
|
||||
:style="[fontStyle()]"
|
||||
@tap="click(index)"
|
||||
>{{ item }}</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 右图标 -->
|
||||
<view class="tn-column-notice__icon">
|
||||
<view
|
||||
v-if="rightIcon"
|
||||
class="tn-column-notice__icon--right"
|
||||
:class="[`tn-icon-${rightIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('rightIcon')]"
|
||||
@tap="clickRightIcon"></view>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-column-notice__icon--right"
|
||||
:class="[`tn-icon-close`,fontColorClass]"
|
||||
:style="[fontStyle('close')]"
|
||||
@tap="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-column-notice',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 滚动方向
|
||||
// horizontal -> 水平滚动 vertical -> 垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动周期
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontStyle() {
|
||||
return (type) => {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : ''
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : ''
|
||||
if (type === 'leftIcon' && this.leftIconSize) {
|
||||
style.fontSize = this.leftIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'rightIcon' && this.rightIconSize) {
|
||||
style.fontSize = this.rightIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'close') {
|
||||
style.fontSize = '24rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
noticeStyle() {
|
||||
let style = {}
|
||||
style.backgroundColor = this.backgroundColorStyle ? this.backgroundColorStyle : 'transparent'
|
||||
if (this.padding) style.padding = this.padding
|
||||
return style
|
||||
},
|
||||
swiperStyle() {
|
||||
let style = {}
|
||||
style.height = this.fontSize ? this.fontSize + 6 + this.fontUnit : '32rpx'
|
||||
style.lineHeight = style.height
|
||||
|
||||
return style
|
||||
},
|
||||
// 标记是否为垂直
|
||||
vertical() {
|
||||
if (this.mode === 'horizontal') return false
|
||||
else return true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
// 点击了通知栏
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
},
|
||||
// 切换消息时间
|
||||
change(event) {
|
||||
let index = event.detail.current
|
||||
if (index === this.list.length - 1) {
|
||||
this.$emit('end')
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-column-notice',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 滚动方向
|
||||
// horizontal -> 水平滚动 vertical -> 垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动周期
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontStyle() {
|
||||
return (type) => {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : ''
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : ''
|
||||
if (type === 'leftIcon' && this.leftIconSize) {
|
||||
style.fontSize = this.leftIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'rightIcon' && this.rightIconSize) {
|
||||
style.fontSize = this.rightIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'close') {
|
||||
style.fontSize = '24rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
noticeStyle() {
|
||||
let style = {}
|
||||
style.backgroundColor = this.backgroundColorStyle ? this.backgroundColorStyle : 'transparent'
|
||||
if (this.padding) style.padding = this.padding
|
||||
return style
|
||||
},
|
||||
swiperStyle() {
|
||||
let style = {}
|
||||
style.height = this.fontSize ? this.fontSize + 6 + this.fontUnit : '32rpx'
|
||||
style.lineHeight = style.height
|
||||
|
||||
return style
|
||||
},
|
||||
// 标记是否为垂直
|
||||
vertical() {
|
||||
if (this.mode === 'horizontal') return false
|
||||
else return true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
},
|
||||
methods: {
|
||||
// 点击了通知栏
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
},
|
||||
// 切换消息时间
|
||||
change(event) {
|
||||
let index = event.detail.current
|
||||
if (index === this.list.length - 1) {
|
||||
this.$emit('end')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-column-notice {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&__swiper {
|
||||
height: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 12rpx;
|
||||
|
||||
&--item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--content {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&--left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
margin-left: 12rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-column-notice {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&__swiper {
|
||||
height: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 12rpx;
|
||||
|
||||
&--item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--content {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&--left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
margin-left: 12rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,314 +1,314 @@
|
||||
<template>
|
||||
<view class="tn-countdown-class tn-countdown">
|
||||
<view
|
||||
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ d }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showHours && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '天'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showHours"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ h }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showMinutes"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '时'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showMinutes"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ m }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showSeconds"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '分'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showSeconds"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ s }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showSeconds && separator === 'cn'"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
秒
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-countdown-class tn-countdown">
|
||||
<view
|
||||
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ d }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showHours && (hideZeroDay || (!hideZeroDay && d != '00'))"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '天'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showHours"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ h }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showMinutes"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '时'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showMinutes"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ m }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showSeconds"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
{{ separator === 'en' ? ':' : '分'}}
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="showSeconds"
|
||||
class="tn-countdown__item"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[itemStyle]"
|
||||
>
|
||||
<view class="tn-countdown__item__time" :class="[fontColorClass]" :style="[letterStyle]">
|
||||
{{ s }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="showSeconds && separator === 'cn'"
|
||||
class="tn-countdown__separator"
|
||||
:style="{
|
||||
fontSize: separatorSize + 'rpx',
|
||||
color: separatorColor,
|
||||
paddingBottom: separator === 'en' ? '4rpx' : 0
|
||||
}"
|
||||
>
|
||||
秒
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-down',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 倒计时时间,秒作为单位
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否自动开始
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 数字框高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 分隔符类型
|
||||
// en -> 使用英文的冒号 cn -> 使用中文进行分割
|
||||
separator: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
// 分割符大小
|
||||
separatorSize: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 分隔符颜色
|
||||
separatorColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 是否显示边框
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 是否显示秒
|
||||
showSeconds: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示分
|
||||
showMinutes: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示时
|
||||
showHours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示天
|
||||
showDays: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 如果当天的部分为0时,是否隐藏不显示
|
||||
hideZeroDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 倒计时item的样式
|
||||
itemStyle() {
|
||||
let style = {}
|
||||
if (this.height) {
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
style.width = style.height
|
||||
}
|
||||
if (this.showBorder) {
|
||||
style.borderStyle = 'solid'
|
||||
style.borderColor = this.borderColor
|
||||
style.borderWidth = '1rpx'
|
||||
}
|
||||
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
|
||||
return style
|
||||
},
|
||||
// 倒计时数字样式
|
||||
letterStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.fontSizeStyle || '30rpx'
|
||||
style.color = this.fontColorStyle || '#080808'
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
d: '00',
|
||||
h: '00',
|
||||
m: '00',
|
||||
s: '00',
|
||||
// 定时器
|
||||
timer: null,
|
||||
// 记录倒计过程中变化的秒数
|
||||
seconds: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听时间戳变化
|
||||
timestamp(value) {
|
||||
this.clearTimer()
|
||||
this.start()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果时自动倒计时,加载完成开始计时
|
||||
this.autoplay && this.timestamp && this.start()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
methods: {
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 避免可能出现的倒计时重叠情况
|
||||
this.clearTimer()
|
||||
if (this.timestamp <= 0) return
|
||||
this.seconds = Number(this.timestamp)
|
||||
this.formatTime(this.seconds)
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds--
|
||||
// 发出change事件
|
||||
this.$emit('change', this.seconds)
|
||||
if (this.seconds < 0) {
|
||||
return this.end()
|
||||
}
|
||||
this.formatTime(this.seconds)
|
||||
}, 1000)
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(seconds) {
|
||||
// 小于等于0的话,结束倒计时
|
||||
seconds <= 0 && this.end()
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0]
|
||||
day = Math.floor(seconds / (60 * 60 * 24))
|
||||
// 如果不显示天,则将天对应的小时计入到小时中
|
||||
// 先把当前的hour计算出来供分和秒使用
|
||||
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
|
||||
let showHour = null
|
||||
if (this.showDays) {
|
||||
showHour = hour
|
||||
} else {
|
||||
// 将天数对应的小时加入到时中进行显示
|
||||
showHour = Math.floor(seconds / (60 * 60))
|
||||
}
|
||||
minute = Math.floor(seconds / 60) - (hour * 60) - (day * 24 * 60)
|
||||
second = Math.floor(seconds) - (minute * 60) - (hour * 60 * 60) - (day * 24 * 60 * 60)
|
||||
// 如果小于0在前面进行补0操作
|
||||
showHour = this.$t.number.formatNumberAddZero(showHour)
|
||||
minute = this.$t.number.formatNumberAddZero(minute)
|
||||
second = this.$t.number.formatNumberAddZero(second)
|
||||
day = this.$t.number.formatNumberAddZero(day)
|
||||
|
||||
this.d = day
|
||||
this.h = showHour
|
||||
this.m = minute
|
||||
this.s = second
|
||||
},
|
||||
// 倒计时结束
|
||||
end() {
|
||||
this.clearTimer()
|
||||
this.$emit('end')
|
||||
},
|
||||
// 清除倒计时
|
||||
clearTimer() {
|
||||
if (this.timer !== null) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-down',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 倒计时时间,秒作为单位
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否自动开始
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 数字框高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 分隔符类型
|
||||
// en -> 使用英文的冒号 cn -> 使用中文进行分割
|
||||
separator: {
|
||||
type: String,
|
||||
default: 'en'
|
||||
},
|
||||
// 分割符大小
|
||||
separatorSize: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 分隔符颜色
|
||||
separatorColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 是否显示边框
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 是否显示秒
|
||||
showSeconds: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示分
|
||||
showMinutes: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示时
|
||||
showHours: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示天
|
||||
showDays: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 如果当天的部分为0时,是否隐藏不显示
|
||||
hideZeroDay: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 倒计时item的样式
|
||||
itemStyle() {
|
||||
let style = {}
|
||||
if (this.height) {
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
style.width = style.height
|
||||
}
|
||||
if (this.showBorder) {
|
||||
style.borderStyle = 'solid'
|
||||
style.borderColor = this.borderColor
|
||||
style.borderWidth = '1rpx'
|
||||
}
|
||||
style.backgroundColor = this.backgroundColorStyle || '#FFFFFF'
|
||||
return style
|
||||
},
|
||||
// 倒计时数字样式
|
||||
letterStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.fontSizeStyle || '30rpx'
|
||||
style.color = this.fontColorStyle || '#080808'
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
d: '00',
|
||||
h: '00',
|
||||
m: '00',
|
||||
s: '00',
|
||||
// 定时器
|
||||
timer: null,
|
||||
// 记录倒计过程中变化的秒数
|
||||
seconds: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听时间戳变化
|
||||
timestamp(value) {
|
||||
this.clearTimer()
|
||||
this.start()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 如果时自动倒计时,加载完成开始计时
|
||||
this.autoplay && this.timestamp && this.start()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
},
|
||||
methods: {
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 避免可能出现的倒计时重叠情况
|
||||
this.clearTimer()
|
||||
if (this.timestamp <= 0) return
|
||||
this.seconds = Number(this.timestamp)
|
||||
this.formatTime(this.seconds)
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds--
|
||||
// 发出change事件
|
||||
this.$emit('change', this.seconds)
|
||||
if (this.seconds < 0) {
|
||||
return this.end()
|
||||
}
|
||||
this.formatTime(this.seconds)
|
||||
}, 1000)
|
||||
},
|
||||
// 格式化时间
|
||||
formatTime(seconds) {
|
||||
// 小于等于0的话,结束倒计时
|
||||
seconds <= 0 && this.end()
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0]
|
||||
day = Math.floor(seconds / (60 * 60 * 24))
|
||||
// 如果不显示天,则将天对应的小时计入到小时中
|
||||
// 先把当前的hour计算出来供分和秒使用
|
||||
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
|
||||
let showHour = null
|
||||
if (this.showDays) {
|
||||
showHour = hour
|
||||
} else {
|
||||
// 将天数对应的小时加入到时中进行显示
|
||||
showHour = Math.floor(seconds / (60 * 60))
|
||||
}
|
||||
minute = Math.floor(seconds / 60) - (hour * 60) - (day * 24 * 60)
|
||||
second = Math.floor(seconds) - (minute * 60) - (hour * 60 * 60) - (day * 24 * 60 * 60)
|
||||
// 如果小于0在前面进行补0操作
|
||||
showHour = this.$t.number.formatNumberAddZero(showHour)
|
||||
minute = this.$t.number.formatNumberAddZero(minute)
|
||||
second = this.$t.number.formatNumberAddZero(second)
|
||||
day = this.$t.number.formatNumberAddZero(day)
|
||||
|
||||
this.d = day
|
||||
this.h = showHour
|
||||
this.m = minute
|
||||
this.s = second
|
||||
},
|
||||
// 倒计时结束
|
||||
end() {
|
||||
this.clearTimer()
|
||||
this.$emit('end')
|
||||
},
|
||||
// 清除倒计时
|
||||
clearTimer() {
|
||||
if (this.timer !== null) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-countdown {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
|
||||
&__item {
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rpx;
|
||||
border-radius: 6rpx;
|
||||
white-space: nowrap;
|
||||
transform: translateZ(0);
|
||||
|
||||
&__time {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__separator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-countdown {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
|
||||
&__item {
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rpx;
|
||||
border-radius: 6rpx;
|
||||
white-space: nowrap;
|
||||
transform: translateZ(0);
|
||||
|
||||
&__time {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__separator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 5rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,171 +1,171 @@
|
||||
<template>
|
||||
<view class="tn-count-scroll-class tn-count-scroll">
|
||||
<view
|
||||
v-for="(item, index) in columns"
|
||||
:key="index"
|
||||
class="tn-count-scroll__box"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: heightPxValue + 'px'
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-count-scroll__column"
|
||||
:style="{
|
||||
transform: `translate3d(0, -${keys[index] * heightPxValue}px, 0)`,
|
||||
transitionDuration: `${duration}s`
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-for="(value, value_index) in item"
|
||||
:key="value_index"
|
||||
class="tn-count-scroll__column__item"
|
||||
:class="[fontColorClass]"
|
||||
:style="{
|
||||
height: heightPxValue + 'px',
|
||||
lineHeight: heightPxValue + 'px',
|
||||
fontSize: fontSizeStyle || '32rpx',
|
||||
fontWeight: bold ? 'bold': 'normal',
|
||||
color: fontColorStyle || '#080808'
|
||||
}"
|
||||
>
|
||||
{{ value }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-count-scroll-class tn-count-scroll">
|
||||
<view
|
||||
v-for="(item, index) in columns"
|
||||
:key="index"
|
||||
class="tn-count-scroll__box"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(width),
|
||||
height: heightPxValue + 'px'
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-count-scroll__column"
|
||||
:style="{
|
||||
transform: `translate3d(0, -${keys[index] * heightPxValue}px, 0)`,
|
||||
transitionDuration: `${duration}s`
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-for="(value, value_index) in item"
|
||||
:key="value_index"
|
||||
class="tn-count-scroll__column__item"
|
||||
:class="[fontColorClass]"
|
||||
:style="{
|
||||
height: heightPxValue + 'px',
|
||||
lineHeight: heightPxValue + 'px',
|
||||
fontSize: fontSizeStyle || '32rpx',
|
||||
fontWeight: bold ? 'bold': 'normal',
|
||||
color: fontColorStyle || '#080808'
|
||||
}"
|
||||
>
|
||||
{{ value }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-scroll',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 行高
|
||||
height: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
// 单个字的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 持续时间
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1.2
|
||||
},
|
||||
// 十分位分割符
|
||||
decimalSeparator: {
|
||||
type: String,
|
||||
default: '.'
|
||||
},
|
||||
// 千分位分割符
|
||||
thousandthsSeparator: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
heightPxValue() {
|
||||
return uni.upx2px(this.height || 0)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 每列的数据
|
||||
columns: [],
|
||||
// 每列对应值所在的滚动位置
|
||||
keys: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.initColumn(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 为了达到一进入就有滚动效果,延迟执行初始化
|
||||
this.initColumn()
|
||||
setTimeout(() => {
|
||||
this.initColumn(this.value)
|
||||
}, 20)
|
||||
},
|
||||
methods: {
|
||||
// 初始化每一列的数据
|
||||
initColumn(val) {
|
||||
val = val + ''
|
||||
let digit = val.length,
|
||||
columnArray = [],
|
||||
rows = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
for (let i = 0; i < digit; i++) {
|
||||
if (val[i] === this.decimalSeparator || val[i] === this.thousandthsSeparator) {
|
||||
columnArray.push(val[i])
|
||||
} else {
|
||||
columnArray.push(rows)
|
||||
}
|
||||
}
|
||||
this.columns = columnArray
|
||||
this.roll(val)
|
||||
},
|
||||
// 滚动处理
|
||||
roll(value) {
|
||||
let valueArray = value.toString().split(''),
|
||||
lengths = this.columns.length,
|
||||
indexs = [];
|
||||
|
||||
while (valueArray.length) {
|
||||
let figure = valueArray.pop()
|
||||
if (figure === this.decimalSeparator || figure === this.thousandthsSeparator) {
|
||||
indexs.unshift(0)
|
||||
} else {
|
||||
indexs.unshift(Number(figure))
|
||||
}
|
||||
}
|
||||
while(indexs.length < lengths) {
|
||||
indexs.unshift(0)
|
||||
}
|
||||
this.keys = indexs
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-scroll',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 行高
|
||||
height: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
// 单个字的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 持续时间
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1.2
|
||||
},
|
||||
// 十分位分割符
|
||||
decimalSeparator: {
|
||||
type: String,
|
||||
default: '.'
|
||||
},
|
||||
// 千分位分割符
|
||||
thousandthsSeparator: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
heightPxValue() {
|
||||
return uni.upx2px(this.height || 0)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 每列的数据
|
||||
columns: [],
|
||||
// 每列对应值所在的滚动位置
|
||||
keys: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.initColumn(val)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 为了达到一进入就有滚动效果,延迟执行初始化
|
||||
this.initColumn()
|
||||
setTimeout(() => {
|
||||
this.initColumn(this.value)
|
||||
}, 20)
|
||||
},
|
||||
methods: {
|
||||
// 初始化每一列的数据
|
||||
initColumn(val) {
|
||||
val = val + ''
|
||||
let digit = val.length,
|
||||
columnArray = [],
|
||||
rows = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||||
for (let i = 0; i < digit; i++) {
|
||||
if (val[i] === this.decimalSeparator || val[i] === this.thousandthsSeparator) {
|
||||
columnArray.push(val[i])
|
||||
} else {
|
||||
columnArray.push(rows)
|
||||
}
|
||||
}
|
||||
this.columns = columnArray
|
||||
this.roll(val)
|
||||
},
|
||||
// 滚动处理
|
||||
roll(value) {
|
||||
let valueArray = value.toString().split(''),
|
||||
lengths = this.columns.length,
|
||||
indexs = [];
|
||||
|
||||
while (valueArray.length) {
|
||||
let figure = valueArray.pop()
|
||||
if (figure === this.decimalSeparator || figure === this.thousandthsSeparator) {
|
||||
indexs.unshift(0)
|
||||
} else {
|
||||
indexs.unshift(Number(figure))
|
||||
}
|
||||
}
|
||||
while(indexs.length < lengths) {
|
||||
indexs.unshift(0)
|
||||
}
|
||||
this.keys = indexs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-count-scroll {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__box {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__column {
|
||||
transform: translate3d(0, 0, 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0, 1);
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-count-scroll {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__box {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__column {
|
||||
transform: translate3d(0, 0, 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0, 1);
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,231 +1,231 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-count-num-class tn-count-num"
|
||||
:class="[fontColorClass]"
|
||||
:style="{
|
||||
fontSize: fontSizeStyle || '50rpx',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: fontColorStyle || '#080808'
|
||||
}"
|
||||
>
|
||||
{{ displayValue }}
|
||||
<template>
|
||||
<view
|
||||
class="tn-count-num-class tn-count-num"
|
||||
:class="[fontColorClass]"
|
||||
:style="{
|
||||
fontSize: fontSizeStyle || '50rpx',
|
||||
fontWeight: bold ? 'bold' : 'normal',
|
||||
color: fontColorStyle || '#080808'
|
||||
}"
|
||||
>
|
||||
{{ displayValue }}
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-to',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 开始的数值,默认为0
|
||||
startVal: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 结束目标数值
|
||||
endVal: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
// 是否自动开始
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动到目标值的持续时间,单位为毫秒
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// 是否在即将结束的时候使用缓慢滚动的效果
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 显示的小数位数
|
||||
decimals: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 十进制的分割符
|
||||
decimalSeparator: {
|
||||
type: String,
|
||||
default: '.'
|
||||
},
|
||||
// 千分位的分隔符
|
||||
// 类似金额的分割(¥23,321.05中的",")
|
||||
thousandthsSeparator: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
localDuration: this.duration,
|
||||
// 显示的数值
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
// 打印的数值
|
||||
printValue: null,
|
||||
// 是否暂停
|
||||
paused: false,
|
||||
// 开始时间戳
|
||||
startTime: null,
|
||||
// 停留时间戳
|
||||
remainingTime: null,
|
||||
// 当前时间戳
|
||||
timestamp: null,
|
||||
// 上一次的时间戳
|
||||
lastTime: 0,
|
||||
rAF: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
this.autoplay && this.start()
|
||||
},
|
||||
endVal() {
|
||||
this.autoplay && this.start()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.autoplay && this.start()
|
||||
},
|
||||
methods: {
|
||||
// 开始滚动
|
||||
start() {
|
||||
this.localStartVal = this.startVal
|
||||
this.startTime = null
|
||||
this.localDuration = this.duration
|
||||
this.paused = false
|
||||
this.rAF = this.requestAnimationFrame(this.count)
|
||||
},
|
||||
// 重新开始
|
||||
reStart() {
|
||||
if (this.paused) {
|
||||
this.resume()
|
||||
this.paused = false
|
||||
} else {
|
||||
this.stop()
|
||||
this.paused = true
|
||||
}
|
||||
},
|
||||
// 停止
|
||||
stop() {
|
||||
this.cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
// 恢复
|
||||
resume() {
|
||||
this.startTime = null
|
||||
this.localDuration = this.remainingTime
|
||||
this.localStartVal = this.printValue
|
||||
this.requestAnimationFrame(this.count)
|
||||
},
|
||||
// 重置
|
||||
reset() {
|
||||
this.startTime = null
|
||||
this.cnacelAnimationFrame(this.rAF)
|
||||
this.displayValue = this.formatNumber(this.startVal)
|
||||
},
|
||||
// 销毁组件
|
||||
destroyed() {
|
||||
this.cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
// 累加时间
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp
|
||||
this.timestamp = timestamp
|
||||
const progress = timestamp - this.startTime
|
||||
this.remainingTime = this.localDuration - progress
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printValue = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
|
||||
} {
|
||||
this.printValue = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printValue = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
|
||||
} else {
|
||||
this.printValue = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printValue = this.printValue < this.endVal ? this.endVal : this.printValue
|
||||
} else {
|
||||
this.printValue = this.printValue > this.endVal ? this.endVal : this.printValue
|
||||
}
|
||||
|
||||
this.displayValue = this.formatNumber(this.printValue)
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = this.requestAnimationFrame(this.count)
|
||||
} else {
|
||||
this.$emit('end')
|
||||
}
|
||||
},
|
||||
// 缓动时间计算
|
||||
easingFn(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||
},
|
||||
// 请求帧动画
|
||||
requestAnimationFrame(cb) {
|
||||
const currentTime = new Date().getTime()
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currentTime - this.lastTime))
|
||||
const timerId = setTimeout(() => {
|
||||
cb && cb(currentTime + timeToCall)
|
||||
}, timeToCall)
|
||||
this.lastTime = currentTime + timeToCall
|
||||
return timerId
|
||||
},
|
||||
// 清除帧动画
|
||||
clearAnimationFrame(timerId) {
|
||||
clearTimeout(timerId)
|
||||
},
|
||||
// 格式化数值
|
||||
formatNumber(number) {
|
||||
const reg = /(\d+)(\d{3})/
|
||||
number = Number(number)
|
||||
number = number.toFixed(Number(this.decimals))
|
||||
number += ''
|
||||
const numberArray = number.split('.')
|
||||
let num1 = numberArray[0]
|
||||
const num2 = numberArray.length > 1 ? this.decimalSeparator + numberArray[1] : ''
|
||||
|
||||
if (this.thousandthsSeparator && !this.isNumber(this.thousandthsSeparator)) {
|
||||
while(reg.test(num1)) {
|
||||
num1 = num1.replace(reg, '$1' + this.thousandthsSeparator + '$2')
|
||||
}
|
||||
}
|
||||
return num1 + num2
|
||||
},
|
||||
// 判断是否为数字
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val))
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-count-to',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 开始的数值,默认为0
|
||||
startVal: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 结束目标数值
|
||||
endVal: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: true
|
||||
},
|
||||
// 是否自动开始
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动到目标值的持续时间,单位为毫秒
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// 是否在即将结束的时候使用缓慢滚动的效果
|
||||
useEasing: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 显示的小数位数
|
||||
decimals: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 十进制的分割符
|
||||
decimalSeparator: {
|
||||
type: String,
|
||||
default: '.'
|
||||
},
|
||||
// 千分位的分隔符
|
||||
// 类似金额的分割(¥23,321.05中的",")
|
||||
thousandthsSeparator: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
countDown() {
|
||||
return this.startVal > this.endVal
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localStartVal: this.startVal,
|
||||
localDuration: this.duration,
|
||||
// 显示的数值
|
||||
displayValue: this.formatNumber(this.startVal),
|
||||
// 打印的数值
|
||||
printValue: null,
|
||||
// 是否暂停
|
||||
paused: false,
|
||||
// 开始时间戳
|
||||
startTime: null,
|
||||
// 停留时间戳
|
||||
remainingTime: null,
|
||||
// 当前时间戳
|
||||
timestamp: null,
|
||||
// 上一次的时间戳
|
||||
lastTime: 0,
|
||||
rAF: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startVal() {
|
||||
this.autoplay && this.start()
|
||||
},
|
||||
endVal() {
|
||||
this.autoplay && this.start()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.autoplay && this.start()
|
||||
},
|
||||
methods: {
|
||||
// 开始滚动
|
||||
start() {
|
||||
this.localStartVal = this.startVal
|
||||
this.startTime = null
|
||||
this.localDuration = this.duration
|
||||
this.paused = false
|
||||
this.rAF = this.requestAnimationFrame(this.count)
|
||||
},
|
||||
// 重新开始
|
||||
reStart() {
|
||||
if (this.paused) {
|
||||
this.resume()
|
||||
this.paused = false
|
||||
} else {
|
||||
this.stop()
|
||||
this.paused = true
|
||||
}
|
||||
},
|
||||
// 停止
|
||||
stop() {
|
||||
this.cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
// 恢复
|
||||
resume() {
|
||||
this.startTime = null
|
||||
this.localDuration = this.remainingTime
|
||||
this.localStartVal = this.printValue
|
||||
this.requestAnimationFrame(this.count)
|
||||
},
|
||||
// 重置
|
||||
reset() {
|
||||
this.startTime = null
|
||||
this.cnacelAnimationFrame(this.rAF)
|
||||
this.displayValue = this.formatNumber(this.startVal)
|
||||
},
|
||||
// 销毁组件
|
||||
destroyed() {
|
||||
this.cancelAnimationFrame(this.rAF)
|
||||
},
|
||||
// 累加时间
|
||||
count(timestamp) {
|
||||
if (!this.startTime) this.startTime = timestamp
|
||||
this.timestamp = timestamp
|
||||
const progress = timestamp - this.startTime
|
||||
this.remainingTime = this.localDuration - progress
|
||||
if (this.useEasing) {
|
||||
if (this.countDown) {
|
||||
this.printValue = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration)
|
||||
} {
|
||||
this.printValue = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration)
|
||||
}
|
||||
} else {
|
||||
if (this.countDown) {
|
||||
this.printValue = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration)
|
||||
} else {
|
||||
this.printValue = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration)
|
||||
}
|
||||
}
|
||||
if (this.countDown) {
|
||||
this.printValue = this.printValue < this.endVal ? this.endVal : this.printValue
|
||||
} else {
|
||||
this.printValue = this.printValue > this.endVal ? this.endVal : this.printValue
|
||||
}
|
||||
|
||||
this.displayValue = this.formatNumber(this.printValue)
|
||||
if (progress < this.localDuration) {
|
||||
this.rAF = this.requestAnimationFrame(this.count)
|
||||
} else {
|
||||
this.$emit('end')
|
||||
}
|
||||
},
|
||||
// 缓动时间计算
|
||||
easingFn(t, b, c, d) {
|
||||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
|
||||
},
|
||||
// 请求帧动画
|
||||
requestAnimationFrame(cb) {
|
||||
const currentTime = new Date().getTime()
|
||||
// 为了使setTimteout的尽可能的接近每秒60帧的效果
|
||||
const timeToCall = Math.max(0, 16 - (currentTime - this.lastTime))
|
||||
const timerId = setTimeout(() => {
|
||||
cb && cb(currentTime + timeToCall)
|
||||
}, timeToCall)
|
||||
this.lastTime = currentTime + timeToCall
|
||||
return timerId
|
||||
},
|
||||
// 清除帧动画
|
||||
clearAnimationFrame(timerId) {
|
||||
clearTimeout(timerId)
|
||||
},
|
||||
// 格式化数值
|
||||
formatNumber(number) {
|
||||
const reg = /(\d+)(\d{3})/
|
||||
number = Number(number)
|
||||
number = number.toFixed(Number(this.decimals))
|
||||
number += ''
|
||||
const numberArray = number.split('.')
|
||||
let num1 = numberArray[0]
|
||||
const num2 = numberArray.length > 1 ? this.decimalSeparator + numberArray[1] : ''
|
||||
|
||||
if (this.thousandthsSeparator && !this.isNumber(this.thousandthsSeparator)) {
|
||||
while(reg.test(num1)) {
|
||||
num1 = num1.replace(reg, '$1' + this.thousandthsSeparator + '$2')
|
||||
}
|
||||
}
|
||||
return num1 + num2
|
||||
},
|
||||
// 判断是否为数字
|
||||
isNumber(val) {
|
||||
return !isNaN(parseFloat(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-count-num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-count-num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,190 +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>
|
||||
<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>
|
||||
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 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>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,457 +1,457 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-form-item-class tn-form-item"
|
||||
:class="{
|
||||
'tn-border-solid-bottom': elBorderBottom,
|
||||
'tn-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-form-item__body"
|
||||
:style="{
|
||||
flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
|
||||
}"
|
||||
>
|
||||
<!-- 处理微信小程序中设置属性的问题,不设置值的时候会变成true -->
|
||||
<view
|
||||
class="tn-form-item--left"
|
||||
:style="{
|
||||
width: wLabelWidth,
|
||||
flex: `0 0 ${wLabelWidth}`,
|
||||
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx'
|
||||
}"
|
||||
>
|
||||
<!-- 块对齐 -->
|
||||
<view v-if="required || leftIcon || label" class="tn-form-item--left__content"
|
||||
:style="[leftContentStyle]"
|
||||
>
|
||||
<!-- nvue不支持伪元素before -->
|
||||
<view v-if="leftIcon" class="tn-form-item--left__content__icon">
|
||||
<view :class="[`tn-icon-${leftIcon}`]" :style="leftIconStyle"></view>
|
||||
</view>
|
||||
<!-- <view
|
||||
class="tn-form-item--left__content__label"
|
||||
:style="[elLabelStyle, {
|
||||
'justify-content': elLabelAlign === 'left' ? 'flex-satrt' : elLabelAlign === 'center' ? 'center' : 'flex-end'
|
||||
}]"
|
||||
>
|
||||
{{label}}
|
||||
</view> -->
|
||||
<view
|
||||
class="tn-form-item--left__content__label"
|
||||
:style="[elLabelStyle]"
|
||||
>
|
||||
{{label}}
|
||||
</view>
|
||||
<text v-if="required" class="tn-form-item--left__content--required">*</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tn-form-item--right tn-flex">
|
||||
<view class="tn-form-item--right__content">
|
||||
<view class="tn-form-item--right__content__slot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view v-if="$slots.right || rightIcon" class="tn-form-item--right__content__icon tn-flex">
|
||||
<view v-if="rightIcon" :class="[`tn-icon-${rightIcon}`]" :style="rightIconStyle"></view>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="validateState === 'error' && showError('message')"
|
||||
class="tn-form-item__message"
|
||||
:style="{
|
||||
paddingLeft: elLabelPosition === 'left' ? elLabelWidth + 'rpx' : '0'
|
||||
}"
|
||||
>
|
||||
{{validateMessage}}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
import schema from '../../libs/utils/async-validator.js'
|
||||
// 去除警告信息
|
||||
schema.warning = function() {}
|
||||
|
||||
export default {
|
||||
mixins: [Emitter],
|
||||
name: 'tn-form-item',
|
||||
inject: {
|
||||
tnForm: {
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
// label提示语
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 绑定的值
|
||||
prop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type:Boolean,
|
||||
default: true
|
||||
},
|
||||
// label(标签名称)的位置
|
||||
// left - 左边
|
||||
// top - 上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label的对齐方式
|
||||
// left - 左对齐
|
||||
// top - 上对齐
|
||||
// right - 右对齐
|
||||
// bottom - 下对齐
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label 的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 左侧图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右侧图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标样式
|
||||
leftIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 右侧图标样式
|
||||
rightIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示必填项的*,不做校验用途
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 处理微信小程序label的宽度
|
||||
wLabelWidth() {
|
||||
// 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
|
||||
return this.elLabelPosition === 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.elLabelWidth + 'rpx') : '100%'
|
||||
},
|
||||
// 是否显示错误提示
|
||||
showError() {
|
||||
return type => {
|
||||
if (this.errorType.indexOf('none') >= 0) return false
|
||||
else if (this.errorType.indexOf(type) >= 0) return true
|
||||
else return false
|
||||
}
|
||||
},
|
||||
// label的宽度(默认值为90)
|
||||
elLabelWidth() {
|
||||
return this.labelWidth != 0 ? this.labelWidth : (this.parentData.labelWidth != 0 ? this.parentData.labelWidth : 90)
|
||||
},
|
||||
// label的样式
|
||||
elLabelStyle() {
|
||||
return Object.keys(this.labelStyle).length ? this.labelStyle : (Object.keys(this.parentData.labelStyle).length ? this.parentData.labelStyle : {})
|
||||
},
|
||||
// label显示位置
|
||||
elLabelPosition() {
|
||||
return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition : 'left')
|
||||
},
|
||||
// label对齐方式
|
||||
elLabelAlign() {
|
||||
return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left')
|
||||
},
|
||||
// label下划线
|
||||
elBorderBottom() {
|
||||
return this.borderBottom !== '' ? this.borderBottom : (this.parentData.borderBottom !== '' ? this.parentData.borderBottom : true)
|
||||
},
|
||||
leftContentStyle() {
|
||||
let style = {}
|
||||
if (this.elLabelPosition === 'left') {
|
||||
switch(this.elLabelAlign) {
|
||||
case 'left':
|
||||
style.justifyContent = 'flex-start'
|
||||
break
|
||||
case 'center':
|
||||
style.justifyContent = 'center'
|
||||
break
|
||||
default:
|
||||
style.justifyContent = 'flex-end'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 默认值
|
||||
initialValue: '',
|
||||
// 是否校验成功
|
||||
validateState: '',
|
||||
// 校验失败提示信息
|
||||
validateMessage: '',
|
||||
// 错误的提示方式(参考form组件)
|
||||
errorType: ['message'],
|
||||
// 当前子组件输入的值
|
||||
fieldValue: '',
|
||||
// 父组件的参数
|
||||
// 由于再computed中无法得知this.parent的变化,所以放在data中
|
||||
parentData: {
|
||||
borderBottom: true,
|
||||
labelWidth: 90,
|
||||
labelPosition: 'left',
|
||||
labelAlign: 'left',
|
||||
labelStyle: {},
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
validateState(val) {
|
||||
this.broadcastInputError()
|
||||
},
|
||||
"tnForm.errorType"(val) {
|
||||
this.errorType = val
|
||||
this.broadcastInputError()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 组件创建完成后,保存当前实例到form组件中
|
||||
// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用\
|
||||
this.parent = this.$t.$parent.call(this, 'tn-form')
|
||||
if (this.parent) {
|
||||
// 遍历parentData属性,将parent中同名的属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key]
|
||||
})
|
||||
// 如果没有传入prop或者tnForm为空(单独使用form-item组件的时候),就不进行校验
|
||||
if (this.prop) {
|
||||
// 将本实例添加到父组件中
|
||||
this.parent.fields.push(this)
|
||||
this.errorType = this.parent.errorType
|
||||
// 设置初始值
|
||||
this.initialValue = this.fieldValue
|
||||
// 添加表单校验,这里必须要写在$nextTick中,因为tn-form的rules是通过ref手动传入的
|
||||
// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给tn-form,导致规则为空
|
||||
this.$nextTick(() => {
|
||||
this.setRules()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前,将实例从tn-form的缓存中移除
|
||||
// 如果当前没有prop的话表示当前不进行删除
|
||||
if (this.parent && this.prop) {
|
||||
this.parent.fields.map((item, index) => {
|
||||
if (item === this) this.parent.fields.splice(index, 1)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 向input组件发出错误事件
|
||||
broadcastInputError() {
|
||||
this.broadcast('tn-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'))
|
||||
},
|
||||
// 设置校验规则
|
||||
setRules() {
|
||||
let that = this
|
||||
// 从父组件tn-form拿到当前tn-form-item需要验证 的规则
|
||||
// let rules = this.getRules()
|
||||
// if (rules.length) {
|
||||
// this.isRequired = rules.some(rule => {
|
||||
// // 如果有必填项,就返回,没有的话,就是undefined
|
||||
// return rule.required
|
||||
// })
|
||||
// }
|
||||
|
||||
// blur事件
|
||||
this.$on('on-form-blur', that.onFieldBlur)
|
||||
// change事件
|
||||
this.$on('on-form-change', that.onFieldChange)
|
||||
},
|
||||
// 从form的rules属性中取出当前form-item的校验规则
|
||||
getRules() {
|
||||
let rules = this.parent.rules
|
||||
rules = rules ? rules[this.prop] : []
|
||||
|
||||
// 返回数值形式的值
|
||||
return [].concat(rules || [])
|
||||
},
|
||||
// blur事件时进行表单认证
|
||||
onFieldBlur() {
|
||||
this.validation('blur')
|
||||
},
|
||||
// change事件时进行表单认证
|
||||
onFieldChange() {
|
||||
this.validation('change')
|
||||
},
|
||||
// 过滤出符合要求的rule规则
|
||||
getFilterRule(triggerType = '') {
|
||||
let rules = this.getRules()
|
||||
// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
|
||||
if (!triggerType) return rules
|
||||
// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
|
||||
// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
|
||||
// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
|
||||
return rules.filter(rule => rule.trigger && rule.trigger.indexOf(triggerType) !== -1)
|
||||
},
|
||||
// 校验数据
|
||||
validation(trigger, callback = ()=>{}) {
|
||||
// 校验之前先获取需要校验的值
|
||||
this.fieldValue = this.parent.model[this.prop]
|
||||
// blur和change是否有当前方式的校验规则
|
||||
let rules = this.getFilterRule(trigger)
|
||||
// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件tn-form会因为
|
||||
// 对count变量的统计错误而无法进入上一层的回调
|
||||
if (!rules || rules.length === 0) {
|
||||
return callback('')
|
||||
}
|
||||
// 设置当前为校验中
|
||||
this.validateState = 'validating'
|
||||
// 调用async-validator的方法
|
||||
let validator = new schema({
|
||||
[this.prop]: rules
|
||||
})
|
||||
validator.validate({
|
||||
[this.prop]: this.fieldValue
|
||||
}, {
|
||||
firstFields: true
|
||||
}, (errors, fields) => {
|
||||
// 记录状态和报错信息
|
||||
this.validateState = !errors ? 'success' : 'error'
|
||||
this.validateMessage = errors ? errors[0].message : ''
|
||||
|
||||
callback(this.validateMessage)
|
||||
})
|
||||
},
|
||||
|
||||
// 清空当前item信息
|
||||
resetField() {
|
||||
this.parent.model[this.prop] = this.initialValue
|
||||
// 清空错误标记
|
||||
this.validateState = 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
box-sizing: border-box;
|
||||
line-height: $tn-form-item-height;
|
||||
|
||||
&__border-bottom--error:after {
|
||||
border-color: $tn-color-red;
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&--left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding-right: 18rpx;
|
||||
flex: 1;
|
||||
|
||||
&--required {
|
||||
position: relative;
|
||||
right: 0;
|
||||
vertical-align: middle;
|
||||
color: $tn-color-red;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $tn-font-sub-color;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&__label {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
// flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex: 1;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&__slot {
|
||||
flex: 1;
|
||||
/* #ifndef MP */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-left: 10rpx;
|
||||
color: $tn-font-sub-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: $tn-color-red;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-form-item-class tn-form-item"
|
||||
:class="{
|
||||
'tn-border-solid-bottom': elBorderBottom,
|
||||
'tn-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')
|
||||
}"
|
||||
>
|
||||
<view
|
||||
class="tn-form-item__body"
|
||||
:style="{
|
||||
flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
|
||||
}"
|
||||
>
|
||||
<!-- 处理微信小程序中设置属性的问题,不设置值的时候会变成true -->
|
||||
<view
|
||||
class="tn-form-item--left"
|
||||
:style="{
|
||||
width: wLabelWidth,
|
||||
flex: `0 0 ${wLabelWidth}`,
|
||||
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx'
|
||||
}"
|
||||
>
|
||||
<!-- 块对齐 -->
|
||||
<view v-if="required || leftIcon || label" class="tn-form-item--left__content"
|
||||
:style="[leftContentStyle]"
|
||||
>
|
||||
<!-- nvue不支持伪元素before -->
|
||||
<view v-if="leftIcon" class="tn-form-item--left__content__icon">
|
||||
<view :class="[`tn-icon-${leftIcon}`]" :style="leftIconStyle"></view>
|
||||
</view>
|
||||
<!-- <view
|
||||
class="tn-form-item--left__content__label"
|
||||
:style="[elLabelStyle, {
|
||||
'justify-content': elLabelAlign === 'left' ? 'flex-satrt' : elLabelAlign === 'center' ? 'center' : 'flex-end'
|
||||
}]"
|
||||
>
|
||||
{{label}}
|
||||
</view> -->
|
||||
<view
|
||||
class="tn-form-item--left__content__label"
|
||||
:style="[elLabelStyle]"
|
||||
>
|
||||
{{label}}
|
||||
</view>
|
||||
<text v-if="required" class="tn-form-item--left__content--required">*</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tn-form-item--right tn-flex">
|
||||
<view class="tn-form-item--right__content">
|
||||
<view class="tn-form-item--right__content__slot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view v-if="$slots.right || rightIcon" class="tn-form-item--right__content__icon tn-flex">
|
||||
<view v-if="rightIcon" :class="[`tn-icon-${rightIcon}`]" :style="rightIconStyle"></view>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="validateState === 'error' && showError('message')"
|
||||
class="tn-form-item__message"
|
||||
:style="{
|
||||
paddingLeft: elLabelPosition === 'left' ? elLabelWidth + 'rpx' : '0'
|
||||
}"
|
||||
>
|
||||
{{validateMessage}}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
import schema from '../../libs/utils/async-validator.js'
|
||||
// 去除警告信息
|
||||
schema.warning = function() {}
|
||||
|
||||
export default {
|
||||
mixins: [Emitter],
|
||||
name: 'tn-form-item',
|
||||
inject: {
|
||||
tnForm: {
|
||||
default() {
|
||||
return null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
// label提示语
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 绑定的值
|
||||
prop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type:Boolean,
|
||||
default: true
|
||||
},
|
||||
// label(标签名称)的位置
|
||||
// left - 左边
|
||||
// top - 上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label的对齐方式
|
||||
// left - 左对齐
|
||||
// top - 上对齐
|
||||
// right - 右对齐
|
||||
// bottom - 下对齐
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label 的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 左侧图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右侧图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 左侧图标样式
|
||||
leftIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 右侧图标样式
|
||||
rightIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示必填项的*,不做校验用途
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 处理微信小程序label的宽度
|
||||
wLabelWidth() {
|
||||
// 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
|
||||
return this.elLabelPosition === 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.elLabelWidth + 'rpx') : '100%'
|
||||
},
|
||||
// 是否显示错误提示
|
||||
showError() {
|
||||
return type => {
|
||||
if (this.errorType.indexOf('none') >= 0) return false
|
||||
else if (this.errorType.indexOf(type) >= 0) return true
|
||||
else return false
|
||||
}
|
||||
},
|
||||
// label的宽度(默认值为90)
|
||||
elLabelWidth() {
|
||||
return this.labelWidth != 0 ? this.labelWidth : (this.parentData.labelWidth != 0 ? this.parentData.labelWidth : 90)
|
||||
},
|
||||
// label的样式
|
||||
elLabelStyle() {
|
||||
return Object.keys(this.labelStyle).length ? this.labelStyle : (Object.keys(this.parentData.labelStyle).length ? this.parentData.labelStyle : {})
|
||||
},
|
||||
// label显示位置
|
||||
elLabelPosition() {
|
||||
return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition : 'left')
|
||||
},
|
||||
// label对齐方式
|
||||
elLabelAlign() {
|
||||
return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left')
|
||||
},
|
||||
// label下划线
|
||||
elBorderBottom() {
|
||||
return this.borderBottom !== '' ? this.borderBottom : (this.parentData.borderBottom !== '' ? this.parentData.borderBottom : true)
|
||||
},
|
||||
leftContentStyle() {
|
||||
let style = {}
|
||||
if (this.elLabelPosition === 'left') {
|
||||
switch(this.elLabelAlign) {
|
||||
case 'left':
|
||||
style.justifyContent = 'flex-start'
|
||||
break
|
||||
case 'center':
|
||||
style.justifyContent = 'center'
|
||||
break
|
||||
default:
|
||||
style.justifyContent = 'flex-end'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 默认值
|
||||
initialValue: '',
|
||||
// 是否校验成功
|
||||
validateState: '',
|
||||
// 校验失败提示信息
|
||||
validateMessage: '',
|
||||
// 错误的提示方式(参考form组件)
|
||||
errorType: ['message'],
|
||||
// 当前子组件输入的值
|
||||
fieldValue: '',
|
||||
// 父组件的参数
|
||||
// 由于再computed中无法得知this.parent的变化,所以放在data中
|
||||
parentData: {
|
||||
borderBottom: true,
|
||||
labelWidth: 90,
|
||||
labelPosition: 'left',
|
||||
labelAlign: 'left',
|
||||
labelStyle: {},
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
validateState(val) {
|
||||
this.broadcastInputError()
|
||||
},
|
||||
"tnForm.errorType"(val) {
|
||||
this.errorType = val
|
||||
this.broadcastInputError()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 组件创建完成后,保存当前实例到form组件中
|
||||
// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用\
|
||||
this.parent = this.$t.$parent.call(this, 'tn-form')
|
||||
if (this.parent) {
|
||||
// 遍历parentData属性,将parent中同名的属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key]
|
||||
})
|
||||
// 如果没有传入prop或者tnForm为空(单独使用form-item组件的时候),就不进行校验
|
||||
if (this.prop) {
|
||||
// 将本实例添加到父组件中
|
||||
this.parent.fields.push(this)
|
||||
this.errorType = this.parent.errorType
|
||||
// 设置初始值
|
||||
this.initialValue = this.fieldValue
|
||||
// 添加表单校验,这里必须要写在$nextTick中,因为tn-form的rules是通过ref手动传入的
|
||||
// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给tn-form,导致规则为空
|
||||
this.$nextTick(() => {
|
||||
this.setRules()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
// 组件销毁前,将实例从tn-form的缓存中移除
|
||||
// 如果当前没有prop的话表示当前不进行删除
|
||||
if (this.parent && this.prop) {
|
||||
this.parent.fields.map((item, index) => {
|
||||
if (item === this) this.parent.fields.splice(index, 1)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 向input组件发出错误事件
|
||||
broadcastInputError() {
|
||||
this.broadcast('tn-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'))
|
||||
},
|
||||
// 设置校验规则
|
||||
setRules() {
|
||||
let that = this
|
||||
// 从父组件tn-form拿到当前tn-form-item需要验证 的规则
|
||||
// let rules = this.getRules()
|
||||
// if (rules.length) {
|
||||
// this.isRequired = rules.some(rule => {
|
||||
// // 如果有必填项,就返回,没有的话,就是undefined
|
||||
// return rule.required
|
||||
// })
|
||||
// }
|
||||
|
||||
// blur事件
|
||||
this.$on('on-form-blur', that.onFieldBlur)
|
||||
// change事件
|
||||
this.$on('on-form-change', that.onFieldChange)
|
||||
},
|
||||
// 从form的rules属性中取出当前form-item的校验规则
|
||||
getRules() {
|
||||
let rules = this.parent.rules
|
||||
rules = rules ? rules[this.prop] : []
|
||||
|
||||
// 返回数值形式的值
|
||||
return [].concat(rules || [])
|
||||
},
|
||||
// blur事件时进行表单认证
|
||||
onFieldBlur() {
|
||||
this.validation('blur')
|
||||
},
|
||||
// change事件时进行表单认证
|
||||
onFieldChange() {
|
||||
this.validation('change')
|
||||
},
|
||||
// 过滤出符合要求的rule规则
|
||||
getFilterRule(triggerType = '') {
|
||||
let rules = this.getRules()
|
||||
// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
|
||||
if (!triggerType) return rules
|
||||
// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
|
||||
// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
|
||||
// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
|
||||
return rules.filter(rule => rule.trigger && rule.trigger.indexOf(triggerType) !== -1)
|
||||
},
|
||||
// 校验数据
|
||||
validation(trigger, callback = ()=>{}) {
|
||||
// 校验之前先获取需要校验的值
|
||||
this.fieldValue = this.parent.model[this.prop]
|
||||
// blur和change是否有当前方式的校验规则
|
||||
let rules = this.getFilterRule(trigger)
|
||||
// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件tn-form会因为
|
||||
// 对count变量的统计错误而无法进入上一层的回调
|
||||
if (!rules || rules.length === 0) {
|
||||
return callback('')
|
||||
}
|
||||
// 设置当前为校验中
|
||||
this.validateState = 'validating'
|
||||
// 调用async-validator的方法
|
||||
let validator = new schema({
|
||||
[this.prop]: rules
|
||||
})
|
||||
validator.validate({
|
||||
[this.prop]: this.fieldValue
|
||||
}, {
|
||||
firstFields: true
|
||||
}, (errors, fields) => {
|
||||
// 记录状态和报错信息
|
||||
this.validateState = !errors ? 'success' : 'error'
|
||||
this.validateMessage = errors ? errors[0].message : ''
|
||||
|
||||
callback(this.validateMessage)
|
||||
})
|
||||
},
|
||||
|
||||
// 清空当前item信息
|
||||
resetField() {
|
||||
this.parent.model[this.prop] = this.initialValue
|
||||
// 清空错误标记
|
||||
this.validateState = 'success'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
box-sizing: border-box;
|
||||
line-height: $tn-form-item-height;
|
||||
|
||||
&__border-bottom--error:after {
|
||||
border-color: $tn-color-red;
|
||||
}
|
||||
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&--left {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
padding-right: 18rpx;
|
||||
flex: 1;
|
||||
|
||||
&--required {
|
||||
position: relative;
|
||||
right: 0;
|
||||
vertical-align: middle;
|
||||
color: $tn-color-red;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: $tn-font-sub-color;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
&__label {
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// align-items: center;
|
||||
// flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex: 1;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&__slot {
|
||||
flex: 1;
|
||||
/* #ifndef MP */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-left: 10rpx;
|
||||
color: $tn-font-sub-color;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
color: $tn-color-red;
|
||||
margin-top: 12rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,137 +1,137 @@
|
||||
<template>
|
||||
<view class="tn-form-class tn-form">
|
||||
<slot></slot>
|
||||
<template>
|
||||
<view class="tn-form-class tn-form">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-form',
|
||||
props: {
|
||||
// 表单数据对象(需要验证的表单数据)
|
||||
model: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 发生错误时的提示方式
|
||||
// toast - 弹出toast框
|
||||
// message - 提示信息
|
||||
// border - 如果设置了边框,边框会变成红色
|
||||
// border-bottom - 下边框会呈现红色
|
||||
// none - 无提示
|
||||
errorType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['message', 'toast']
|
||||
}
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type:Boolean,
|
||||
default: true
|
||||
},
|
||||
// label(标签名称)的位置
|
||||
// left - 左边
|
||||
// top - 上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 90
|
||||
},
|
||||
// label的对齐方式
|
||||
// left - 左对齐
|
||||
// center - 居中对齐
|
||||
// right - 右对齐
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label 的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 向子孙传递数据
|
||||
provide() {
|
||||
return {
|
||||
tnForm: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 存储当前form下的所有form-item的实例
|
||||
// 不能定义再data中,否则小程序会循环引用而报错
|
||||
this.fields = []
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 设置规则
|
||||
*
|
||||
* @param {Object} rules
|
||||
*/
|
||||
setRules(rules) {
|
||||
this.rules = rules
|
||||
},
|
||||
/**
|
||||
* 清空form-item组件
|
||||
*/
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.resetField()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 校验数据
|
||||
* @param {Object} callback 校验回调方法
|
||||
*/
|
||||
validate(callback) {
|
||||
return new Promise(resolve => {
|
||||
// 标记校验是否通过
|
||||
let valid = true
|
||||
// 标记是否检查完毕
|
||||
let count = 0
|
||||
// 存放错误信息
|
||||
let errors = []
|
||||
|
||||
// 对所有form-item进行校验
|
||||
this.fields.map(field => {
|
||||
// 调用对应form-item实例的validation校验方法
|
||||
field.validation('', error => {
|
||||
// 如果有一个form-item校验不通过,则整个表单校验不通过
|
||||
if (error) {
|
||||
valid = false
|
||||
errors.push(error)
|
||||
}
|
||||
// 当遍历完所有的form-item的校验规则,返回信息
|
||||
if (++count === this.fields.length) {
|
||||
resolve(valid)
|
||||
// 判断是否设置了toast的提示方式,只提示表单域中最前面的一条错误信息
|
||||
if (this.errorType.indexOf('none') === -1 &&
|
||||
this.errorType.indexOf('toast') >= 0 &&
|
||||
errors.length > 0) {
|
||||
this.$t.messageUtils.toast(errors[0])
|
||||
}
|
||||
// 调用回调方法
|
||||
if (typeof callback == 'function') callback(valid)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-form',
|
||||
props: {
|
||||
// 表单数据对象(需要验证的表单数据)
|
||||
model: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 发生错误时的提示方式
|
||||
// toast - 弹出toast框
|
||||
// message - 提示信息
|
||||
// border - 如果设置了边框,边框会变成红色
|
||||
// border-bottom - 下边框会呈现红色
|
||||
// none - 无提示
|
||||
errorType: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['message', 'toast']
|
||||
}
|
||||
},
|
||||
// 是否显示表单域的下划线边框
|
||||
borderBottom: {
|
||||
type:Boolean,
|
||||
default: true
|
||||
},
|
||||
// label(标签名称)的位置
|
||||
// left - 左边
|
||||
// top - 上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: Number,
|
||||
default: 90
|
||||
},
|
||||
// label的对齐方式
|
||||
// left - 左对齐
|
||||
// center - 居中对齐
|
||||
// right - 右对齐
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label 的样式
|
||||
labelStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 向子孙传递数据
|
||||
provide() {
|
||||
return {
|
||||
tnForm: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 存储当前form下的所有form-item的实例
|
||||
// 不能定义再data中,否则小程序会循环引用而报错
|
||||
this.fields = []
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 设置规则
|
||||
*
|
||||
* @param {Object} rules
|
||||
*/
|
||||
setRules(rules) {
|
||||
this.rules = rules
|
||||
},
|
||||
/**
|
||||
* 清空form-item组件
|
||||
*/
|
||||
resetFields() {
|
||||
this.fields.map(field => {
|
||||
field.resetField()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 校验数据
|
||||
* @param {Object} callback 校验回调方法
|
||||
*/
|
||||
validate(callback) {
|
||||
return new Promise(resolve => {
|
||||
// 标记校验是否通过
|
||||
let valid = true
|
||||
// 标记是否检查完毕
|
||||
let count = 0
|
||||
// 存放错误信息
|
||||
let errors = []
|
||||
|
||||
// 对所有form-item进行校验
|
||||
this.fields.map(field => {
|
||||
// 调用对应form-item实例的validation校验方法
|
||||
field.validation('', error => {
|
||||
// 如果有一个form-item校验不通过,则整个表单校验不通过
|
||||
if (error) {
|
||||
valid = false
|
||||
errors.push(error)
|
||||
}
|
||||
// 当遍历完所有的form-item的校验规则,返回信息
|
||||
if (++count === this.fields.length) {
|
||||
resolve(valid)
|
||||
// 判断是否设置了toast的提示方式,只提示表单域中最前面的一条错误信息
|
||||
if (this.errorType.indexOf('none') === -1 &&
|
||||
this.errorType.indexOf('toast') >= 0 &&
|
||||
errors.length > 0) {
|
||||
this.$t.message.toast(errors[0])
|
||||
}
|
||||
// 调用回调方法
|
||||
if (typeof callback == 'function') callback(valid)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,359 +1,382 @@
|
||||
<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>
|
||||
<template>
|
||||
<view
|
||||
class="tn-goods-nav-class tn-goods-nav"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
{
|
||||
'tn-goods-nav--fixed': fixed,
|
||||
'tn-safe-area-inset-bottom': safeAreaInsetBottom,
|
||||
'tn-goods-nav--shadow': shadow
|
||||
}
|
||||
]"
|
||||
: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
|
||||
},
|
||||
// 显示阴影
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 导航的层级
|
||||
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.color.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.color.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.color.getBackgroundColorInternalClass(item.backgroundColor)
|
||||
button.backgroundColorStyle = this.$t.color.getBackgroundColorStyle(item.backgroundColor)
|
||||
}
|
||||
if (item.hasOwnProperty('color')) {
|
||||
button.colorClass = this.$t.color.getBackgroundColorInternalClass(item.color)
|
||||
button.colorStyle = this.$t.color.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;
|
||||
|
||||
&--shadow {
|
||||
box-shadow: 0rpx -10rpx 30rpx 0rpx rgba(0, 0, 0, 0.05);
|
||||
|
||||
&::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
background-color: transparent;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&--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>
|
||||
|
||||
@@ -1,114 +1,114 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-grid-item-class tn-grid-item"
|
||||
:class="[
|
||||
backgroundColorClass
|
||||
]"
|
||||
:hover-class="hoverClass"
|
||||
:hover-stay-time="150"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
width: gridWidth
|
||||
}"
|
||||
@tap="click"
|
||||
>
|
||||
<view
|
||||
class="tn-grid-item__box"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-grid-item',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件数据
|
||||
parentData: {
|
||||
// 按下去的样式
|
||||
hoverClass: '',
|
||||
col: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 父组件实例
|
||||
this.updateParentData()
|
||||
this.parent.children.push(this)
|
||||
},
|
||||
computed: {
|
||||
// 计算每个宫格的宽度
|
||||
gridWidth() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return '100%'
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
return 100 / Number(this.parentData.col) + '%'
|
||||
// #endif
|
||||
},
|
||||
// 点击效果
|
||||
hoverClass() {
|
||||
return this.parentData.hoverClass !== 'none'
|
||||
? this.parentData.hoverClass + ' tn-grid-item--hover'
|
||||
: this.parentData.hoverClass
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取父组件参数
|
||||
updateParentData() {
|
||||
this.getParentData('tn-grid')
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index)
|
||||
this.parent && this.parent.click(this.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-grid-item {
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
|
||||
/* #ifdef MP */
|
||||
// float: left;
|
||||
/* #endif */
|
||||
|
||||
&__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background: $tn-space-color !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-grid-item-class tn-grid-item"
|
||||
:class="[
|
||||
backgroundColorClass
|
||||
]"
|
||||
:hover-class="hoverClass"
|
||||
:hover-stay-time="150"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
width: gridWidth
|
||||
}"
|
||||
@tap="click"
|
||||
>
|
||||
<view
|
||||
class="tn-grid-item__box"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-grid-item',
|
||||
props: {
|
||||
// 序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件数据
|
||||
parentData: {
|
||||
// 按下去的样式
|
||||
hoverClass: '',
|
||||
col: 3
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 父组件实例
|
||||
this.updateParentData()
|
||||
this.parent.children.push(this)
|
||||
},
|
||||
computed: {
|
||||
// 计算每个宫格的宽度
|
||||
gridWidth() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return '100%'
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
return 100 / Number(this.parentData.col) + '%'
|
||||
// #endif
|
||||
},
|
||||
// 点击效果
|
||||
hoverClass() {
|
||||
return this.parentData.hoverClass !== 'none'
|
||||
? this.parentData.hoverClass + ' tn-grid-item--hover'
|
||||
: this.parentData.hoverClass
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 获取父组件参数
|
||||
updateParentData() {
|
||||
this.getParentData('tn-grid')
|
||||
},
|
||||
click() {
|
||||
this.$emit('click', this.index)
|
||||
this.parent && this.parent.click(this.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-grid-item {
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
|
||||
/* #ifdef MP */
|
||||
// float: left;
|
||||
/* #endif */
|
||||
|
||||
&__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background: $tn-space-color !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,111 +1,111 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-grid-class tn-grid"
|
||||
:style="{
|
||||
justifyContent: gridAlignStyle
|
||||
}"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-grid',
|
||||
props: {
|
||||
// 分成几列
|
||||
col: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 宫格对齐方式
|
||||
// left 左对齐 center 居中对齐 right 右对齐
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 点击时的效果,none没有效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'tn-hover'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件和子组件需要共享参数变化,通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件是否有updateParentData方式,有才执行
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = []
|
||||
},
|
||||
computed: {
|
||||
// 计算父组件的值是否发生变化
|
||||
parentData() {
|
||||
return [this.hoverClass, this.col, this.border]
|
||||
},
|
||||
// 宫格对齐方式
|
||||
gridAlignStyle() {
|
||||
switch(this.align) {
|
||||
case 'left':
|
||||
return 'flex-start'
|
||||
case 'center':
|
||||
return 'center'
|
||||
case 'right':
|
||||
return 'flex-end'
|
||||
default:
|
||||
return 'flex-start'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
// 组件中兼容小程序的方式,不过不能使用对齐方式
|
||||
// .tn-grid {
|
||||
// width: 100%;
|
||||
// /* #ifdef MP */
|
||||
// position: relative;
|
||||
// box-sizing: border-box;
|
||||
// overflow: hidden;
|
||||
// /* #endif */
|
||||
|
||||
// /* #ifndef MP */
|
||||
// /* #ifndef APP-NVUE */
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// /* #endif */
|
||||
// flex-wrap: wrap;
|
||||
// align-items: center;
|
||||
// /* #endif */
|
||||
// }
|
||||
|
||||
// 在使用组件时兼容小程序
|
||||
.tn-grid {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-grid-class tn-grid"
|
||||
:style="{
|
||||
justifyContent: gridAlignStyle
|
||||
}"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-grid',
|
||||
props: {
|
||||
// 分成几列
|
||||
col: {
|
||||
type: [Number, String],
|
||||
default: 3
|
||||
},
|
||||
// 宫格对齐方式
|
||||
// left 左对齐 center 居中对齐 right 右对齐
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 点击时的效果,none没有效果
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: 'tn-hover'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件和子组件需要共享参数变化,通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件是否有updateParentData方式,有才执行
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = []
|
||||
},
|
||||
computed: {
|
||||
// 计算父组件的值是否发生变化
|
||||
parentData() {
|
||||
return [this.hoverClass, this.col, this.border]
|
||||
},
|
||||
// 宫格对齐方式
|
||||
gridAlignStyle() {
|
||||
switch(this.align) {
|
||||
case 'left':
|
||||
return 'flex-start'
|
||||
case 'center':
|
||||
return 'center'
|
||||
case 'right':
|
||||
return 'flex-end'
|
||||
default:
|
||||
return 'flex-start'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
// 组件中兼容小程序的方式,不过不能使用对齐方式
|
||||
// .tn-grid {
|
||||
// width: 100%;
|
||||
// /* #ifdef MP */
|
||||
// position: relative;
|
||||
// box-sizing: border-box;
|
||||
// overflow: hidden;
|
||||
// /* #endif */
|
||||
|
||||
// /* #ifndef MP */
|
||||
// /* #ifndef APP-NVUE */
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// /* #endif */
|
||||
// flex-wrap: wrap;
|
||||
// align-items: center;
|
||||
// /* #endif */
|
||||
// }
|
||||
|
||||
// 在使用组件时兼容小程序
|
||||
.tn-grid {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,90 +1,90 @@
|
||||
<template>
|
||||
<!-- 支付宝小程序使用_tGetRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view :id="elId" class="tn-index-anchor__wrap" :style="[wrapperStyle]">
|
||||
<view class="tn-index-anchor" :class="[active ? 'tn-index-anchor--active' : '']" :style="[customAnchorStyle]">
|
||||
<view v-if="useSlot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<block v-else>
|
||||
<text>{{ index }}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<!-- 支付宝小程序使用_tGetRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view :id="elId" class="tn-index-anchor__wrap" :style="[wrapperStyle]">
|
||||
<view class="tn-index-anchor" :class="[active ? 'tn-index-anchor--active' : '']" :style="[customAnchorStyle]">
|
||||
<view v-if="useSlot">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<block v-else>
|
||||
<text>{{ index }}</text>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-index-anchor',
|
||||
props: {
|
||||
// 使用自定义内容
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 索引字符
|
||||
index: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customAnchorStyle() {
|
||||
return Object.assign(this.anchorStyle, this.customStyle)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elId: this.$t.uuid(),
|
||||
// 内容的高度
|
||||
height: 0,
|
||||
// 内容的top
|
||||
top: 0,
|
||||
// 是否被激活
|
||||
active: false,
|
||||
// 样式(父组件外部提供)
|
||||
wrapperStyle: {},
|
||||
anchorStyle: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
},
|
||||
mounted() {
|
||||
this.parent = this.$t.$parent.call(this, 'tn-index-list')
|
||||
if (this.parent) {
|
||||
this.parent.childrens.push(this)
|
||||
this.parent.updateData()
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-index-anchor',
|
||||
props: {
|
||||
// 使用自定义内容
|
||||
useSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 索引字符
|
||||
index: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
customAnchorStyle() {
|
||||
return Object.assign(this.anchorStyle, this.customStyle)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elId: this.$t.uuid(),
|
||||
// 内容的高度
|
||||
height: 0,
|
||||
// 内容的top
|
||||
top: 0,
|
||||
// 是否被激活
|
||||
active: false,
|
||||
// 样式(父组件外部提供)
|
||||
wrapperStyle: {},
|
||||
anchorStyle: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
},
|
||||
mounted() {
|
||||
this.parent = this.$t.$parent.call(this, 'tn-index-list')
|
||||
if (this.parent) {
|
||||
this.parent.childrens.push(this)
|
||||
this.parent.updateData()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-index-anchor {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8rpx 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
background-color: rgb(245, 245, 245);
|
||||
|
||||
&--active {
|
||||
right: 0;
|
||||
left: 0;
|
||||
color: $tn-main-color;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-index-anchor {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 8rpx 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
background-color: rgb(245, 245, 245);
|
||||
|
||||
&--active {
|
||||
right: 0;
|
||||
left: 0;
|
||||
color: $tn-main-color;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,361 +1,361 @@
|
||||
<template>
|
||||
<!-- 支付宝小程序使用_tGetRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view class="tn-index-list-class tn-index-list">
|
||||
<slot></slot>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<view
|
||||
v-if="showSidebar"
|
||||
class="tn-index-list__sidebar"
|
||||
@touchstart.stop.prevent="onTouchMove"
|
||||
@touchmove.stop.prevent="onTouchMove"
|
||||
@touchend.stop.prevent="onTouchStop"
|
||||
@touchcancel.stop.prevent="onTouchStop"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in indexList"
|
||||
:key="index"
|
||||
class="tn-index-list__sidebar__item"
|
||||
:style="{
|
||||
zIndex: zIndex + 1,
|
||||
color: activeAnchorIndex === index ? activeColor : ''
|
||||
}"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选中弹出框 -->
|
||||
<view
|
||||
v-if="touchMove && indexList[touchMoveIndex]"
|
||||
class="tn-index-list__alert"
|
||||
:style="{
|
||||
zIndex: selectAlertZIndex
|
||||
}"
|
||||
>
|
||||
<text>{{ indexList[touchMoveIndex] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<!-- 支付宝小程序使用_tGetRect()获取组件的根元素尺寸,所以在外面套一个"壳" -->
|
||||
<view>
|
||||
<view class="tn-index-list-class tn-index-list">
|
||||
<slot></slot>
|
||||
|
||||
<!-- 侧边栏 -->
|
||||
<view
|
||||
v-if="showSidebar"
|
||||
class="tn-index-list__sidebar"
|
||||
@touchstart.stop.prevent="onTouchMove"
|
||||
@touchmove.stop.prevent="onTouchMove"
|
||||
@touchend.stop.prevent="onTouchStop"
|
||||
@touchcancel.stop.prevent="onTouchStop"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in indexList"
|
||||
:key="index"
|
||||
class="tn-index-list__sidebar__item"
|
||||
:style="{
|
||||
zIndex: zIndex + 1,
|
||||
color: activeAnchorIndex === index ? activeColor : ''
|
||||
}"
|
||||
>
|
||||
{{ item }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选中弹出框 -->
|
||||
<view
|
||||
v-if="touchMove && indexList[touchMoveIndex]"
|
||||
class="tn-index-list__alert"
|
||||
:style="{
|
||||
zIndex: selectAlertZIndex
|
||||
}"
|
||||
>
|
||||
<text>{{ indexList[touchMoveIndex] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 生成 A-Z的字母列表
|
||||
let indexList = function() {
|
||||
let indexList = []
|
||||
let charCodeOfA = 'A'.charCodeAt(0)
|
||||
for (var i = 0; i < 26; i++) {
|
||||
indexList.push(String.fromCharCode(charCodeOfA + i))
|
||||
}
|
||||
return indexList
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'tn-index-list',
|
||||
props: {
|
||||
// 索引列表
|
||||
indexList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return indexList()
|
||||
}
|
||||
},
|
||||
// 是否自动吸顶
|
||||
sticky: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自动吸顶时距离顶部的距离,单位px
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 自定义顶栏的高度,单位px
|
||||
customBarHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 当前滚动的高度
|
||||
// 由于自定义组件无法获取滚动高度,所以依赖传入
|
||||
scrollTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 选中索引时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 吸顶时的z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 选中索引列表弹出提示框的z-index
|
||||
selectAlertZIndex() {
|
||||
return this.$t.zIndex.toast
|
||||
},
|
||||
// 吸顶的偏移高度
|
||||
stickyOffsetTop() {
|
||||
// #ifdef H5
|
||||
return this.stickyTop !== '' ? this.stickyTop : 44
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return this.stickyTop !== '' ? this.stickyTop : 0
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前激活的列表锚点的序号
|
||||
activeAnchorIndex: 0,
|
||||
// 显示侧边索引栏
|
||||
showSidebar: true,
|
||||
// 标记是否开始触摸移动
|
||||
touchMove: false,
|
||||
// 当前触摸移动到对应索引的序号
|
||||
touchMoveIndex: 0,
|
||||
// 滚动到对应锚点的序号
|
||||
scrollToAnchorIndex: 0,
|
||||
// 侧边栏的信息
|
||||
sidebar: {
|
||||
height: 0,
|
||||
top: 0
|
||||
},
|
||||
// 内容区域高度
|
||||
height: 0,
|
||||
// 内容区域top
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollTop() {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 只能在created生命周期定义childrens,如果在data定义,会因为循环引用而报错
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
// 更新数据
|
||||
updateData() {
|
||||
this.timer && clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => {
|
||||
this.showSidebar = !!this.childrens.length
|
||||
this.getRect().then(() => {
|
||||
this.onScroll()
|
||||
})
|
||||
}, 0)
|
||||
},
|
||||
// 获取对应的信息
|
||||
getRect() {
|
||||
return Promise.all([
|
||||
this.getAnchorRect(),
|
||||
this.getListRect(),
|
||||
this.getSidebarRect()
|
||||
])
|
||||
},
|
||||
// 获取列表内容子元素信息
|
||||
getAnchorRect() {
|
||||
return Promise.all(this.childrens.map((child, index) => {
|
||||
child._tGetRect('.tn-index-anchor__wrap').then((rect) => {
|
||||
Object.assign(child, {
|
||||
height: rect.height,
|
||||
top: rect.top - this.customBarHeight
|
||||
})
|
||||
})
|
||||
}))
|
||||
},
|
||||
// 获取列表信息
|
||||
getListRect() {
|
||||
return this._tGetRect('.tn-index-list').then(rect => {
|
||||
Object.assign(this, {
|
||||
height: rect.height,
|
||||
top: rect.top + this.scrollTop
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取侧边滚动栏信息
|
||||
getSidebarRect() {
|
||||
return this._tGetRect('.tn-index-list__sidebar').then(rect => {
|
||||
this.sidebar = {
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
}
|
||||
})
|
||||
},
|
||||
// 滚动事件
|
||||
onScroll() {
|
||||
const {
|
||||
childrens = []
|
||||
} = this
|
||||
if (!childrens.length) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
sticky,
|
||||
stickyOffsetTop,
|
||||
zIndex,
|
||||
scrollTop,
|
||||
activeColor
|
||||
} = this
|
||||
const active = this.getActiveAnchorIndex()
|
||||
this.activeAnchorIndex = active
|
||||
if (sticky) {
|
||||
let isActiveAnchorSticky = false
|
||||
if (active !== -1) {
|
||||
isActiveAnchorSticky = childrens[active].top <= 0
|
||||
}
|
||||
childrens.forEach((item, index) => {
|
||||
if (index === active) {
|
||||
let wrapperStyle = ''
|
||||
let anchorStyle = {
|
||||
color: `${activeColor}`
|
||||
}
|
||||
if (isActiveAnchorSticky) {
|
||||
wrapperStyle = {
|
||||
height: `${childrens[index].height}px`
|
||||
}
|
||||
anchorStyle = {
|
||||
position: 'fixed',
|
||||
top: `${stickyOffsetTop}px`,
|
||||
zIndex: `${zIndex ? zIndex : this.$t.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
}
|
||||
}
|
||||
item.active = true
|
||||
item.wrapperStyle = wrapperStyle
|
||||
item.anchorStyle = anchorStyle
|
||||
} else if (index === active - 1) {
|
||||
const currentAnchor = childrens[index]
|
||||
const currentOffsetTop = currentAnchor.top
|
||||
const targetOffsetTop = index === childrens.length - 1 ? this.top : childrens[index + 1].top
|
||||
const parentOffsetHeight = targetOffsetTop - currentOffsetTop
|
||||
const translateY = parentOffsetHeight - currentAnchor.height
|
||||
const anchorStyle = {
|
||||
position: 'relative',
|
||||
transform: `translate3d(0, ${translateY}px, 0)`,
|
||||
zIndex: `${zIndex ? zIndex : this.$t.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
}
|
||||
item.active = false
|
||||
item.anchorStyle = anchorStyle
|
||||
} else {
|
||||
item.active = false
|
||||
item.wrapperStyle = ''
|
||||
item.anchorStyle = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 触摸移动
|
||||
onTouchMove(event) {
|
||||
this.touchMove = true
|
||||
const sidebarLength = this.childrens.length
|
||||
const touch = event.touches[0]
|
||||
const itemHeight = this.sidebar.height / sidebarLength
|
||||
let clientY = touch.clientY
|
||||
let index = Math.floor((clientY - this.sidebar.top) / itemHeight)
|
||||
if (index < 0) {
|
||||
index = 0
|
||||
} else if (index > sidebarLength - 1) {
|
||||
index = sidebarLength - 1
|
||||
}
|
||||
this.touchMoveIndex = index
|
||||
this.scrollToAnchor(index)
|
||||
},
|
||||
// 触摸停止
|
||||
onTouchStop() {
|
||||
this.touchMove = false
|
||||
this.scrollToAnchorIndex = null
|
||||
},
|
||||
// 获取当前的锚点序号
|
||||
getActiveAnchorIndex() {
|
||||
const {
|
||||
childrens,
|
||||
sticky
|
||||
} = this
|
||||
for (let i = this.childrens.length - 1; i >= 0; i--) {
|
||||
const preAnchorHeight = i > 0 ? childrens[i - 1].height : 0
|
||||
const reachTop = sticky ? preAnchorHeight : 0
|
||||
if (reachTop >= childrens[i].top) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
},
|
||||
// 滚动到对应的锚点
|
||||
scrollToAnchor(index) {
|
||||
if (this.scrollToAnchorIndex === index) {
|
||||
return
|
||||
}
|
||||
this.scrollToAnchorIndex = index
|
||||
const anchor = this.childrens.find(item => item.index === this.indexList[index])
|
||||
if (anchor) {
|
||||
const scrollTop = anchor.top + this.scrollTop
|
||||
this.$emit('select', {
|
||||
index: anchor.index,
|
||||
scrollTop: scrollTop
|
||||
})
|
||||
uni.pageScrollTo({
|
||||
duration:0,
|
||||
scrollTop: scrollTop
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
// 生成 A-Z的字母列表
|
||||
let indexList = function() {
|
||||
let indexList = []
|
||||
let charCodeOfA = 'A'.charCodeAt(0)
|
||||
for (var i = 0; i < 26; i++) {
|
||||
indexList.push(String.fromCharCode(charCodeOfA + i))
|
||||
}
|
||||
return indexList
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'tn-index-list',
|
||||
props: {
|
||||
// 索引列表
|
||||
indexList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return indexList()
|
||||
}
|
||||
},
|
||||
// 是否自动吸顶
|
||||
sticky: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自动吸顶时距离顶部的距离,单位px
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 自定义顶栏的高度,单位px
|
||||
customBarHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 当前滚动的高度
|
||||
// 由于自定义组件无法获取滚动高度,所以依赖传入
|
||||
scrollTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 选中索引时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 吸顶时的z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 选中索引列表弹出提示框的z-index
|
||||
selectAlertZIndex() {
|
||||
return this.$t.zIndex.toast
|
||||
},
|
||||
// 吸顶的偏移高度
|
||||
stickyOffsetTop() {
|
||||
// #ifdef H5
|
||||
return this.stickyTop !== '' ? this.stickyTop : 44
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
return this.stickyTop !== '' ? this.stickyTop : 0
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前激活的列表锚点的序号
|
||||
activeAnchorIndex: 0,
|
||||
// 显示侧边索引栏
|
||||
showSidebar: true,
|
||||
// 标记是否开始触摸移动
|
||||
touchMove: false,
|
||||
// 当前触摸移动到对应索引的序号
|
||||
touchMoveIndex: 0,
|
||||
// 滚动到对应锚点的序号
|
||||
scrollToAnchorIndex: 0,
|
||||
// 侧边栏的信息
|
||||
sidebar: {
|
||||
height: 0,
|
||||
top: 0
|
||||
},
|
||||
// 内容区域高度
|
||||
height: 0,
|
||||
// 内容区域top
|
||||
top: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollTop() {
|
||||
this.updateData()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 只能在created生命周期定义childrens,如果在data定义,会因为循环引用而报错
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
// 更新数据
|
||||
updateData() {
|
||||
this.timer && clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => {
|
||||
this.showSidebar = !!this.childrens.length
|
||||
this.getRect().then(() => {
|
||||
this.onScroll()
|
||||
})
|
||||
}, 0)
|
||||
},
|
||||
// 获取对应的信息
|
||||
getRect() {
|
||||
return Promise.all([
|
||||
this.getAnchorRect(),
|
||||
this.getListRect(),
|
||||
this.getSidebarRect()
|
||||
])
|
||||
},
|
||||
// 获取列表内容子元素信息
|
||||
getAnchorRect() {
|
||||
return Promise.all(this.childrens.map((child, index) => {
|
||||
child._tGetRect('.tn-index-anchor__wrap').then((rect) => {
|
||||
Object.assign(child, {
|
||||
height: rect.height,
|
||||
top: rect.top - this.customBarHeight
|
||||
})
|
||||
})
|
||||
}))
|
||||
},
|
||||
// 获取列表信息
|
||||
getListRect() {
|
||||
return this._tGetRect('.tn-index-list').then(rect => {
|
||||
Object.assign(this, {
|
||||
height: rect.height,
|
||||
top: rect.top + this.scrollTop
|
||||
})
|
||||
})
|
||||
},
|
||||
// 获取侧边滚动栏信息
|
||||
getSidebarRect() {
|
||||
return this._tGetRect('.tn-index-list__sidebar').then(rect => {
|
||||
this.sidebar = {
|
||||
height: rect.height,
|
||||
top: rect.top
|
||||
}
|
||||
})
|
||||
},
|
||||
// 滚动事件
|
||||
onScroll() {
|
||||
const {
|
||||
childrens = []
|
||||
} = this
|
||||
if (!childrens.length) {
|
||||
return
|
||||
}
|
||||
const {
|
||||
sticky,
|
||||
stickyOffsetTop,
|
||||
zIndex,
|
||||
scrollTop,
|
||||
activeColor
|
||||
} = this
|
||||
const active = this.getActiveAnchorIndex()
|
||||
this.activeAnchorIndex = active
|
||||
if (sticky) {
|
||||
let isActiveAnchorSticky = false
|
||||
if (active !== -1) {
|
||||
isActiveAnchorSticky = childrens[active].top <= 0
|
||||
}
|
||||
childrens.forEach((item, index) => {
|
||||
if (index === active) {
|
||||
let wrapperStyle = ''
|
||||
let anchorStyle = {
|
||||
color: `${activeColor}`
|
||||
}
|
||||
if (isActiveAnchorSticky) {
|
||||
wrapperStyle = {
|
||||
height: `${childrens[index].height}px`
|
||||
}
|
||||
anchorStyle = {
|
||||
position: 'fixed',
|
||||
top: `${stickyOffsetTop}px`,
|
||||
zIndex: `${zIndex ? zIndex : this.$t.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
}
|
||||
}
|
||||
item.active = true
|
||||
item.wrapperStyle = wrapperStyle
|
||||
item.anchorStyle = anchorStyle
|
||||
} else if (index === active - 1) {
|
||||
const currentAnchor = childrens[index]
|
||||
const currentOffsetTop = currentAnchor.top
|
||||
const targetOffsetTop = index === childrens.length - 1 ? this.top : childrens[index + 1].top
|
||||
const parentOffsetHeight = targetOffsetTop - currentOffsetTop
|
||||
const translateY = parentOffsetHeight - currentAnchor.height
|
||||
const anchorStyle = {
|
||||
position: 'relative',
|
||||
transform: `translate3d(0, ${translateY}px, 0)`,
|
||||
zIndex: `${zIndex ? zIndex : this.$t.zIndex.indexListSticky}`,
|
||||
color: `${activeColor}`
|
||||
}
|
||||
item.active = false
|
||||
item.anchorStyle = anchorStyle
|
||||
} else {
|
||||
item.active = false
|
||||
item.wrapperStyle = ''
|
||||
item.anchorStyle = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 触摸移动
|
||||
onTouchMove(event) {
|
||||
this.touchMove = true
|
||||
const sidebarLength = this.childrens.length
|
||||
const touch = event.touches[0]
|
||||
const itemHeight = this.sidebar.height / sidebarLength
|
||||
let clientY = touch.clientY
|
||||
let index = Math.floor((clientY - this.sidebar.top) / itemHeight)
|
||||
if (index < 0) {
|
||||
index = 0
|
||||
} else if (index > sidebarLength - 1) {
|
||||
index = sidebarLength - 1
|
||||
}
|
||||
this.touchMoveIndex = index
|
||||
this.scrollToAnchor(index)
|
||||
},
|
||||
// 触摸停止
|
||||
onTouchStop() {
|
||||
this.touchMove = false
|
||||
this.scrollToAnchorIndex = null
|
||||
},
|
||||
// 获取当前的锚点序号
|
||||
getActiveAnchorIndex() {
|
||||
const {
|
||||
childrens,
|
||||
sticky
|
||||
} = this
|
||||
for (let i = this.childrens.length - 1; i >= 0; i--) {
|
||||
const preAnchorHeight = i > 0 ? childrens[i - 1].height : 0
|
||||
const reachTop = sticky ? preAnchorHeight : 0
|
||||
if (reachTop >= childrens[i].top) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
},
|
||||
// 滚动到对应的锚点
|
||||
scrollToAnchor(index) {
|
||||
if (this.scrollToAnchorIndex === index) {
|
||||
return
|
||||
}
|
||||
this.scrollToAnchorIndex = index
|
||||
const anchor = this.childrens.find(item => item.index === this.indexList[index])
|
||||
if (anchor) {
|
||||
const scrollTop = anchor.top + this.scrollTop
|
||||
this.$emit('select', {
|
||||
index: anchor.index,
|
||||
scrollTop: scrollTop
|
||||
})
|
||||
uni.pageScrollTo({
|
||||
duration:0,
|
||||
scrollTop: scrollTop
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-index-list {
|
||||
position: relative;
|
||||
|
||||
&__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
transform: translateY(-50%);
|
||||
user-select: none;
|
||||
z-index: 99;
|
||||
|
||||
&__item {
|
||||
font-weight: 500;
|
||||
padding: 8rpx 18rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__alert {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: fixed;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
top: 50%;
|
||||
right: 90rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -60rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 50rpx;
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-font-sub-color;
|
||||
padding: 0;
|
||||
z-index: 9999999;
|
||||
|
||||
text {
|
||||
line-height: 50rpx;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-index-list {
|
||||
position: relative;
|
||||
|
||||
&__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
transform: translateY(-50%);
|
||||
user-select: none;
|
||||
z-index: 99;
|
||||
|
||||
&__item {
|
||||
font-weight: 500;
|
||||
padding: 8rpx 18rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__alert {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: fixed;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
top: 50%;
|
||||
right: 90rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: -60rpx;
|
||||
border-radius: 24rpx;
|
||||
font-size: 50rpx;
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-font-sub-color;
|
||||
padding: 0;
|
||||
z-index: 9999999;
|
||||
|
||||
text {
|
||||
line-height: 50rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,427 +1,427 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-input-class tn-input"
|
||||
:class="{
|
||||
'tn-input--border': border,
|
||||
'tn-input--error': validateState
|
||||
}"
|
||||
:style="{
|
||||
padding: `0 ${border ? 20 : 0}rpx`,
|
||||
borderColor: borderColor,
|
||||
textAlign: inputAlign
|
||||
}"
|
||||
@tap.stop="inputClick"
|
||||
>
|
||||
<textarea
|
||||
v-if="type === 'textarea'"
|
||||
class="tn-input__input tn-input__textarea"
|
||||
:style="[inputStyle]"
|
||||
:value="defaultValue"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="maxLength"
|
||||
:fixed="fixed"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:selectionStart="elSelectionStart"
|
||||
:selectionEnd="elSelectionEnd"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
class="tn-input__input"
|
||||
:type="type === 'password' ? 'text' : type"
|
||||
:style="[inputStyle]"
|
||||
:value="defaultValue"
|
||||
:password="type === 'password' && !showPassword"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="maxLength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
:selectionStart="elSelectionStart"
|
||||
:selectionEnd="elSelectionEnd"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
|
||||
<!-- 右边的icon -->
|
||||
<view class="tn-input__right-icon tn-flex tn-flex-col-center">
|
||||
<!-- 清除按钮 -->
|
||||
<view
|
||||
v-if="clearable && value !== '' && focused"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
@tap="onClear"
|
||||
>
|
||||
<view class="icon tn-icon-close"></view>
|
||||
</view>
|
||||
<view
|
||||
v-else-if="type === 'text' && !focused && showLeftIcon && leftIcon !== ''"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
>
|
||||
<view class="icon" :class="[`tn-icon-${leftIcon}`]"></view>
|
||||
</view>
|
||||
<!-- 显示密码按钮 -->
|
||||
<view
|
||||
v-if="passwordIcon && type === 'password'"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
@tap="showPassword = !showPassword"
|
||||
>
|
||||
<view v-if="!showPassword" class="tn-icon-eye-hide"></view>
|
||||
<view v-else class="icon tn-icon-eye"></view>
|
||||
</view>
|
||||
<!-- 可选项箭头 -->
|
||||
<view
|
||||
v-if="type === 'select'"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__select"
|
||||
:class="{
|
||||
'tn-input__right-icon__select--reverse': selectOpen
|
||||
}"
|
||||
>
|
||||
<view class="icon tn-icon-up-triangle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [Emitter],
|
||||
name: 'tn-input',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 输入框的类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
// 输入框文字对齐方式
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 文本框为空时显示的信息
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholderStyle: {
|
||||
type: String,
|
||||
default: 'color: #AAAAAA'
|
||||
},
|
||||
// 是否禁用输入框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 可输入文字的最大长度
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 255
|
||||
},
|
||||
// 输入框高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 根据内容自动调整高度
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 键盘右下角显示的文字,仅在text时生效
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// 输入框自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否固定输入框
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当type为password时,是否显示右侧密码图标
|
||||
passwordIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当type为 input或者textarea时是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框的颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
// 当type为select时,旋转右侧图标,标记当时select是打开还是关闭
|
||||
selectOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可清空
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 光标与键盘的距离
|
||||
cursorSpacing: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// selectionStart和selectionEnd需要搭配使用,自动聚焦时生效
|
||||
// 光标起始位置
|
||||
selectionStart: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 光标结束位置
|
||||
selectionEnd: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 自动去除两端空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示键盘上方的完成按钮
|
||||
showConfirmBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否在输入框内最右边显示图标
|
||||
showLeftIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 最右边图标的名称
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 输入框样式
|
||||
inputStyle() {
|
||||
let style = {}
|
||||
// 如果没有设置高度,根据不同的类型设置一个默认值
|
||||
style.minHeight = this.height ? this.height + 'rpx' :
|
||||
this.type === 'textarea' ? this.textareaHeight + 'rpx' : this.inputHeight + 'rpx'
|
||||
|
||||
style = Object.assign(style, this.customStyle)
|
||||
|
||||
return style
|
||||
},
|
||||
// 光标起始位置
|
||||
elSelectionStart() {
|
||||
return String(this.selectionStart)
|
||||
},
|
||||
// 光标结束位置
|
||||
elSelectionEnd() {
|
||||
return String(this.selectionEnd)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 默认值
|
||||
defaultValue: this.value,
|
||||
// 输入框高度
|
||||
inputHeight: 70,
|
||||
// textarea的高度
|
||||
textareaHeight: 100,
|
||||
// 标记验证的状态
|
||||
validateState: false,
|
||||
// 标记是否获取到焦点
|
||||
focused: false,
|
||||
// 是否预览密码
|
||||
showPassword: false,
|
||||
// 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input事件
|
||||
lastValue: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal, oldVal) {
|
||||
this.defaultValue = newVal
|
||||
// 当值发生变化时,并且type为select时,不会触发input事件
|
||||
// 模拟input事件
|
||||
if (newVal !== oldVal && this.type === 'select') {
|
||||
this.handleInput({
|
||||
detail: {
|
||||
value: newVal
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听form-item发出的错误事件,将输入框变成红色
|
||||
this.$on("on-form-item-error", this.onFormItemError)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* input事件
|
||||
*/
|
||||
handleInput(event) {
|
||||
let value = event.detail.value
|
||||
// 是否需要去掉空格
|
||||
if (this.trim) value = this.$t.string.trim(value)
|
||||
// 原生事件
|
||||
this.$emit('input', value)
|
||||
// model赋值
|
||||
this.defaultValue = value
|
||||
// 过一个生命周期再发送事件给tn-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到tn-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if (this.$t.string.trim(value) === this.lastValue) return
|
||||
this.lastValue = value
|
||||
// #endif
|
||||
|
||||
// 发送当前的值到form-item进行校验
|
||||
this.dispatch('tn-form-item','on-form-change', value)
|
||||
}, 40)
|
||||
},
|
||||
/**
|
||||
* blur事件
|
||||
*/
|
||||
handleBlur(event) {
|
||||
let value = event.detail.value
|
||||
|
||||
// 由于点击清除图标也会触发blur事件,导致图标消失从而无法点击
|
||||
setTimeout(() => {
|
||||
this.focused = false
|
||||
}, 100)
|
||||
|
||||
// 原生事件
|
||||
this.$emit('blur', value)
|
||||
// 过一个生命周期再发送事件给tn-form-item,否则this.$emit('blur')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到tn-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if (this.$t.string.trim(value) === this.lastValue) return
|
||||
this.lastValue = value
|
||||
// #endif
|
||||
|
||||
// 发送当前的值到form-item进行校验
|
||||
this.dispatch('tn-form-item','on-form-blur', value)
|
||||
}, 40)
|
||||
},
|
||||
// 处理校验错误
|
||||
onFormItemError(status) {
|
||||
this.validateState = status
|
||||
},
|
||||
// 聚焦事件
|
||||
onFocus(event) {
|
||||
this.focused = true
|
||||
this.$emit('focus')
|
||||
},
|
||||
// 点击确认按钮事件
|
||||
onConfirm(event) {
|
||||
this.$emit('confirm', event.detail.value)
|
||||
},
|
||||
// 清除事件
|
||||
onClear(event) {
|
||||
this.$emit('input', '')
|
||||
},
|
||||
// 点击事件
|
||||
inputClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
&__input {
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
padding: 10rpx 0;
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-radius: 6rpx;
|
||||
border: 2rpx solid $tn-border-solid-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
border-color: $tn-color-red !important;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
line-height: 1;
|
||||
.icon {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
&__clear {
|
||||
.icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__select {
|
||||
transition: transform .4s;
|
||||
|
||||
.icon {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
&--reverse {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-input-class tn-input"
|
||||
:class="{
|
||||
'tn-input--border': border,
|
||||
'tn-input--error': validateState
|
||||
}"
|
||||
:style="{
|
||||
padding: `0 ${border ? 20 : 0}rpx`,
|
||||
borderColor: borderColor,
|
||||
textAlign: inputAlign
|
||||
}"
|
||||
@tap.stop="inputClick"
|
||||
>
|
||||
<textarea
|
||||
v-if="type === 'textarea'"
|
||||
class="tn-input__input tn-input__textarea"
|
||||
:style="[inputStyle]"
|
||||
:value="defaultValue"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="maxLength"
|
||||
:fixed="fixed"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:selectionStart="elSelectionStart"
|
||||
:selectionEnd="elSelectionEnd"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
class="tn-input__input"
|
||||
:type="type === 'password' ? 'text' : type"
|
||||
:style="[inputStyle]"
|
||||
:value="defaultValue"
|
||||
:password="type === 'password' && !showPassword"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="maxLength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
:selectionStart="elSelectionStart"
|
||||
:selectionEnd="elSelectionEnd"
|
||||
:cursorSpacing="cursorSpacing"
|
||||
:showConfirmBar="showConfirmBar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
|
||||
<!-- 右边的icon -->
|
||||
<view class="tn-input__right-icon tn-flex tn-flex-col-center">
|
||||
<!-- 清除按钮 -->
|
||||
<view
|
||||
v-if="clearable && value !== '' && focused"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
@tap="onClear"
|
||||
>
|
||||
<view class="icon tn-icon-close"></view>
|
||||
</view>
|
||||
<view
|
||||
v-else-if="type === 'text' && !focused && showRightIcon && rightIcon !== ''"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
>
|
||||
<view class="icon" :class="[`tn-icon-${rightIcon}`]"></view>
|
||||
</view>
|
||||
<!-- 显示密码按钮 -->
|
||||
<view
|
||||
v-if="passwordIcon && type === 'password'"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__clear"
|
||||
@tap="showPassword = !showPassword"
|
||||
>
|
||||
<view v-if="!showPassword" class="tn-icon-eye-hide"></view>
|
||||
<view v-else class="icon tn-icon-eye"></view>
|
||||
</view>
|
||||
<!-- 可选项箭头 -->
|
||||
<view
|
||||
v-if="type === 'select'"
|
||||
class="tn-input__right-icon__item tn-input__right-icon__select"
|
||||
:class="{
|
||||
'tn-input__right-icon__select--reverse': selectOpen
|
||||
}"
|
||||
>
|
||||
<view class="icon tn-icon-up-triangle"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [Emitter],
|
||||
name: 'tn-input',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 输入框的类型
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
// 输入框文字对齐方式
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 文本框为空时显示的信息
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
placeholderStyle: {
|
||||
type: String,
|
||||
default: 'color: #AAAAAA'
|
||||
},
|
||||
// 是否禁用输入框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 可输入文字的最大长度
|
||||
maxLength: {
|
||||
type: Number,
|
||||
default: 255
|
||||
},
|
||||
// 输入框高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 根据内容自动调整高度
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 键盘右下角显示的文字,仅在text时生效
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// 输入框自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否固定输入框
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动获取焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当type为password时,是否显示右侧密码图标
|
||||
passwordIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 当type为 input或者textarea时是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 边框的颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
// 当type为select时,旋转右侧图标,标记当时select是打开还是关闭
|
||||
selectOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可清空
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 光标与键盘的距离
|
||||
cursorSpacing: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// selectionStart和selectionEnd需要搭配使用,自动聚焦时生效
|
||||
// 光标起始位置
|
||||
selectionStart: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 光标结束位置
|
||||
selectionEnd: {
|
||||
type: Number,
|
||||
default: -1
|
||||
},
|
||||
// 自动去除两端空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示键盘上方的完成按钮
|
||||
showConfirmBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否在输入框内最右边显示图标
|
||||
showRightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 最右边图标的名称
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 输入框样式
|
||||
inputStyle() {
|
||||
let style = {}
|
||||
// 如果没有设置高度,根据不同的类型设置一个默认值
|
||||
style.minHeight = this.height ? this.height + 'rpx' :
|
||||
this.type === 'textarea' ? this.textareaHeight + 'rpx' : this.inputHeight + 'rpx'
|
||||
|
||||
style = Object.assign(style, this.customStyle)
|
||||
|
||||
return style
|
||||
},
|
||||
// 光标起始位置
|
||||
elSelectionStart() {
|
||||
return String(this.selectionStart)
|
||||
},
|
||||
// 光标结束位置
|
||||
elSelectionEnd() {
|
||||
return String(this.selectionEnd)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 默认值
|
||||
defaultValue: this.value,
|
||||
// 输入框高度
|
||||
inputHeight: 70,
|
||||
// textarea的高度
|
||||
textareaHeight: 100,
|
||||
// 标记验证的状态
|
||||
validateState: false,
|
||||
// 标记是否获取到焦点
|
||||
focused: false,
|
||||
// 是否预览密码
|
||||
showPassword: false,
|
||||
// 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input事件
|
||||
lastValue: '',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newVal, oldVal) {
|
||||
this.defaultValue = newVal
|
||||
// 当值发生变化时,并且type为select时,不会触发input事件
|
||||
// 模拟input事件
|
||||
if (newVal !== oldVal && this.type === 'select') {
|
||||
this.handleInput({
|
||||
detail: {
|
||||
value: newVal
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听form-item发出的错误事件,将输入框变成红色
|
||||
this.$on("on-form-item-error", this.onFormItemError)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* input事件
|
||||
*/
|
||||
handleInput(event) {
|
||||
let value = event.detail.value
|
||||
// 是否需要去掉空格
|
||||
if (this.trim) value = this.$t.string.trim(value)
|
||||
// 原生事件
|
||||
this.$emit('input', value)
|
||||
// model赋值
|
||||
this.defaultValue = value
|
||||
// 过一个生命周期再发送事件给tn-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到tn-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if (this.$t.string.trim(value) === this.lastValue) return
|
||||
this.lastValue = value
|
||||
// #endif
|
||||
|
||||
// 发送当前的值到form-item进行校验
|
||||
this.dispatch('tn-form-item','on-form-change', value)
|
||||
}, 40)
|
||||
},
|
||||
/**
|
||||
* blur事件
|
||||
*/
|
||||
handleBlur(event) {
|
||||
let value = event.detail.value
|
||||
|
||||
// 由于点击清除图标也会触发blur事件,导致图标消失从而无法点击
|
||||
setTimeout(() => {
|
||||
this.focused = false
|
||||
}, 100)
|
||||
|
||||
// 原生事件
|
||||
this.$emit('blur', value)
|
||||
// 过一个生命周期再发送事件给tn-form-item,否则this.$emit('blur')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到tn-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if (this.$t.string.trim(value) === this.lastValue) return
|
||||
this.lastValue = value
|
||||
// #endif
|
||||
|
||||
// 发送当前的值到form-item进行校验
|
||||
this.dispatch('tn-form-item','on-form-blur', value)
|
||||
}, 40)
|
||||
},
|
||||
// 处理校验错误
|
||||
onFormItemError(status) {
|
||||
this.validateState = status
|
||||
},
|
||||
// 聚焦事件
|
||||
onFocus(event) {
|
||||
this.focused = true
|
||||
this.$emit('focus')
|
||||
},
|
||||
// 点击确认按钮事件
|
||||
onConfirm(event) {
|
||||
this.$emit('confirm', event.detail.value)
|
||||
},
|
||||
// 清除事件
|
||||
onClear(event) {
|
||||
this.$emit('input', '')
|
||||
},
|
||||
// 点击事件
|
||||
inputClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
|
||||
&__input {
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
padding: 10rpx 0;
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-radius: 6rpx;
|
||||
border: 2rpx solid $tn-border-solid-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
border-color: $tn-color-red !important;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
line-height: 1;
|
||||
.icon {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&__item {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
&__clear {
|
||||
.icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__select {
|
||||
transition: transform .4s;
|
||||
|
||||
.icon {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
&--reverse {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,220 +1,220 @@
|
||||
<template>
|
||||
<view v-if="value" class="tn-keyboard-class tn-keyboard">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
:popup="false"
|
||||
length="auto"
|
||||
:mask="mask"
|
||||
:maskCloseable="maskCloseable"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:zIndex="elZIndex"
|
||||
@close="popupClose"
|
||||
>
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view v-if="tooltip" class="tn-keyboard__tooltip">
|
||||
<view
|
||||
v-if="cancelBtn"
|
||||
class="tn-keyboard__tooltip__item tn-keyboard__tooltip__cancel"
|
||||
hover-class="tn-keyboard__tooltip__cancel--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="onCancel"
|
||||
>
|
||||
{{ cancelBtn ? cancelText : ''}}
|
||||
</view>
|
||||
<view v-if="showTips" class="tn-keyboard__tooltip__item tn-keyboard__tooltip__tips">
|
||||
{{ tips ? tips : mode === 'number' ? '数字键盘' : mode === 'card' ? '身份证键盘' : '车牌号码键盘'}}
|
||||
</view>
|
||||
<view
|
||||
v-if="confirmBtn"
|
||||
class="tn-keyboard__tooltip__item tn-keyboard__tooltip__confirm"
|
||||
hover-class="tn-keybord__tooltip__confirm--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="onConfirm"
|
||||
>
|
||||
{{ confirmBtn ? confirmText : ''}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 键盘内容 -->
|
||||
<block v-if="mode === 'number' || mode === 'card'">
|
||||
<tn-number-keyboard :mode="mode" :dotEnabled="dotEnabled" :randomEnabled="randomEnabled" @change="change" @backspace="backspaceClick"></tn-number-keyboard>
|
||||
</block>
|
||||
<block v-if="mode === 'car'">
|
||||
<tn-car-keyboard :randomEnabled="randomEnabled" :switchEnMode="switchEnMode" @change="change" @backspace="backspaceClick"></tn-car-keyboard>
|
||||
</block>
|
||||
</tn-popup>
|
||||
<template>
|
||||
<view v-if="value" class="tn-keyboard-class tn-keyboard">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
:popup="false"
|
||||
length="auto"
|
||||
:mask="mask"
|
||||
:maskCloseable="maskCloseable"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:zIndex="elZIndex"
|
||||
@close="popupClose"
|
||||
>
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<view v-if="tooltip" class="tn-keyboard__tooltip">
|
||||
<view
|
||||
v-if="cancelBtn"
|
||||
class="tn-keyboard__tooltip__item tn-keyboard__tooltip__cancel"
|
||||
hover-class="tn-keyboard__tooltip__cancel--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="onCancel"
|
||||
>
|
||||
{{ cancelBtn ? cancelText : ''}}
|
||||
</view>
|
||||
<view v-if="showTips" class="tn-keyboard__tooltip__item tn-keyboard__tooltip__tips">
|
||||
{{ tips ? tips : mode === 'number' ? '数字键盘' : mode === 'card' ? '身份证键盘' : '车牌号码键盘'}}
|
||||
</view>
|
||||
<view
|
||||
v-if="confirmBtn"
|
||||
class="tn-keyboard__tooltip__item tn-keyboard__tooltip__confirm"
|
||||
hover-class="tn-keybord__tooltip__confirm--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="onConfirm"
|
||||
>
|
||||
{{ confirmBtn ? confirmText : ''}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 键盘内容 -->
|
||||
<block v-if="mode === 'number' || mode === 'card'">
|
||||
<tn-number-keyboard :mode="mode" :dotEnabled="dotEnabled" :randomEnabled="randomEnabled" @change="change" @backspace="backspaceClick"></tn-number-keyboard>
|
||||
</block>
|
||||
<block v-if="mode === 'car'">
|
||||
<tn-car-keyboard :randomEnabled="randomEnabled" :switchEnMode="switchEnMode" @change="change" @backspace="backspaceClick"></tn-car-keyboard>
|
||||
</block>
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-keyboard',
|
||||
props: {
|
||||
// 控制键盘弹出收回
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 键盘类型
|
||||
// number - 数字键盘 card - 身份证键盘 car - 车牌号码
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 当mode = number时,是否显示'.'符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否打乱顺序
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当mode = car,设置键盘中英文状态
|
||||
switchEnMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示顶部工具条
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示提示信息
|
||||
showTips: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示确认按钮
|
||||
confirmBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 取消按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(e) {
|
||||
this.$emit('change', e)
|
||||
},
|
||||
// 关闭键盘
|
||||
popupClose() {
|
||||
// 修改value的值
|
||||
this.$emit('input', false)
|
||||
},
|
||||
// 输入完成
|
||||
onConfirm() {
|
||||
this.popupClose()
|
||||
this.$emit('confirm')
|
||||
},
|
||||
// 输入取消
|
||||
onCancel() {
|
||||
this.popupClose()
|
||||
this.$emit('cancel')
|
||||
},
|
||||
// 点击退格
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-keyboard',
|
||||
props: {
|
||||
// 控制键盘弹出收回
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 键盘类型
|
||||
// number - 数字键盘 card - 身份证键盘 car - 车牌号码
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 当mode = number时,是否显示'.'符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否打乱顺序
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 当mode = car,设置键盘中英文状态
|
||||
switchEnMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示顶部工具条
|
||||
tooltip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示提示信息
|
||||
showTips: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
cancelBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示确认按钮
|
||||
confirmBtn: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 取消按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change(e) {
|
||||
this.$emit('change', e)
|
||||
},
|
||||
// 关闭键盘
|
||||
popupClose() {
|
||||
// 修改value的值
|
||||
this.$emit('input', false)
|
||||
},
|
||||
// 输入完成
|
||||
onConfirm() {
|
||||
this.popupClose()
|
||||
this.$emit('confirm')
|
||||
},
|
||||
// 输入取消
|
||||
onCancel() {
|
||||
this.popupClose()
|
||||
this.$emit('cancel')
|
||||
},
|
||||
// 点击退格
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-keyboard {
|
||||
position: relative;
|
||||
|
||||
&__tooltip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
&__item {
|
||||
color: $tn-font-color;
|
||||
flex: 0 0 33.3333333333%;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 10rpx;
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-left: 40rpx;
|
||||
color: $tn-content-color;
|
||||
|
||||
&--hover {
|
||||
color: $tn-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__confirm {
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-right: 40rpx;
|
||||
color: $tn-main-color;
|
||||
|
||||
&--hover {
|
||||
color: $tn-color-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-keyboard {
|
||||
position: relative;
|
||||
|
||||
&__tooltip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
&__item {
|
||||
color: $tn-font-color;
|
||||
flex: 0 0 33.3333333333%;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 10rpx;
|
||||
}
|
||||
|
||||
&__cancel {
|
||||
text-align: left;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-left: 40rpx;
|
||||
color: $tn-content-color;
|
||||
|
||||
&--hover {
|
||||
color: $tn-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__confirm {
|
||||
text-align: right;
|
||||
flex-grow: 1;
|
||||
flex-wrap: 0;
|
||||
padding-right: 40rpx;
|
||||
color: $tn-main-color;
|
||||
|
||||
&--hover {
|
||||
color: $tn-color-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,225 +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>
|
||||
<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>
|
||||
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 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>
|
||||
|
||||
@@ -1,254 +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>
|
||||
<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/PhLwhikAAAAzUExURZWVldfX162trcPDw5ubm7i4uNzc3Obm5s3NzaGhoeHh4cjIyKenp9LS0r29vbKysuvr67sDMEkAAAlpSURBVHja7NzpYqMgAIVRUVHc8/5PO66R1WAbOzX97q+ZtDEpR0AWTR7kVyWhCAAhgABCAAGEAAIIAQQQAggBBBACCCAEEEAIIIAQQAgggBBAACGAAEIAAYQAQgABhAACCAEEEAIIIAQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAsqeX5QWHKIcs/Ptl03lfL4zDFPWfBGmSpPn+IZzSH5KkCL5B+n+oklwz6Iz//R2QzFOabzhEmiRirAmZt/bl0w/dpMbLqeeo4wEdpC7zR5WAPKziHKtO7ql+ReKvIa9BxgNaL5ZtEkpeAGIVp5jKJa09xVo9vgSSzQcszdYvmOqjQNSQ6pHK6rO1n1Xj32788miwHLaZz1Tl9i/yayDlYJ/60/+lp8GSY7OY1B8E4p55bWmfquFk22GLuUUxi78cX+m+BjL2GLkhMrV+/muS6Sfic0CEp5T1Yu2OQdTzsKV0MJV73KVjroyTffxfuv5Tf3fd6iLT9wz8YdVHgUzF2Is9/Xhi5sYJqP1w/GUpjOiHVbaI0w2L+pg3GZzvtokcgHxWDXHaiy78l3sPke01qphamT5c+dqyeAGSumdL/mkggauTam0e3L/mPEiqtzKDbl0Z1Wn8xOa4ySo8X/7TQIJnY/seEKWf12UmC72CKP9xYjr19RPT7NNA+oMO+R0gwmlotAry+C6I0f59ch8yXVQOr0BKYcXt1IUYRyCt+Ur9HGsrQKI79WY9sY9ARPKlzFOFdb41ioD8b5Bp+mqeeRKAxINkESBFGpOpKhgv9OuYpH8A8l4Qa3qp60Kl2/k+rG2sWafuuyCBafb2j4JkgZUob3nWcmicpkxEgmTLLGejTxnWSWCi8lPmsk6DlIHFJv24ojiYyYoGacwL8zXTLEAVaDI/Ybb3NIgKDSv2oXpmHkvNs+PTpMASEdlk7fOZeRk37fwJ6yGnQarQsGIfqqcvx43rTOXY6jf7uKXdRzdLDRPbjIrx1cIj3Kr4KyBFezzgUGuR5893qkOQ19fR2uVBaU+r16LphJNOiatK7PeBZK/Kb+tUn71rcQjSvARpghfH/yG/D2RetTuI3N5QrMWdP46brP7FmKZ//CGQ9At9SL01DLkzY/Vs8Z97fQZ7gelw7jHqCz+/Wile5J4g3Vc79eb5a6oLSue+Ve83gaSv2jp5PxCzjzwFUm9zw9MllSMil1kS4d2E9SaQ1xNo9wMxx0+nQNLnew/WDHvveMAHYm08mofl3TFI/8pD3Q6kMAv6DIi2jTCwRJUvNdDYrrJum9oHhusCbWALonwxBRk1vXMnEGWuT5wAmfYuVGUYpJ7fUZujCN92hvzwWlrFgxSfANKb10DxIMbShnfrynyZZV30imA7P43ArXXHbvBVkTCIuGy25AdBrHmNeBCpL214QdLp9LZarG3IMWrmW0ehtuO7F2PS09UcgqS3B7FKPhpknrStD0HGF/vQRne37LwLG8EbHT4WxN7/Fg0yD9Yr/3br4nnstA+0Il6QxzdBmg8A6a2/IRbkcK9h/uzV8zywF/oSkOyageCPglRWgcWClHnEzs9q/t/SENVXgFijlsq3VtXdCsRp4qObrLLLgjuzSq3fX89ZZW6AfxNIzF6X9FYgThN/fk093KkvHX/hbWd+DqS/FUhlf+G3gohEXzVs3g9iDluWoaW8fL73QhB34u+tIHIf19nLuF4Q98a09Eynnl56q+ePgEhnX+dbQOp6H5XnJ0ACd8dFgkwf12nTOTcEqd2pom+CFF02TIPw6dKmrLS5qOtBpo8b5quUtrwrSGbuqPkeSJqllTFHO02NPxdMrm+y5LKdWyWXjw4vA5nGEtnjuyCFyHqNYvEolzmASm3zK1Eg5zr13lhqV1tlksnVw8Pkwgri7O07AVKLJkutRYw87bPlRpBpNXE8xGb+fhBlvEGrGPLqViu5sILIx9dAmqF1705sxF4M8+R8P5dOdQwi12fMnATpjJ2JSt/POIvU9wPJEs/jduJAjLvU0yFT0i64Yb1bsVi79dA4pEy3TzoHMq2O7Re4vXm5O9+l290NpE4CU+YRIMNye2iaqbVS2AUnn2fsekthYKReVNutVedA5juttyIXrT38mOds+ps9DWhwL7GWc61/DVKPzVN9UHDarf1icU98IOU8tm6L031Iq63t1tKzj3fe/FCpO4F0/i0Z2+yvA1KeGBjqj1qYx8/zoxpKZ1Yl367I1k+sfcft/QPy9csXy/32qX1qLZsrryG5BGQaRj0vc/b7N54XXq293TCLB5HO42Fy517obW19b+qjl3CHp0fdLJcWvmdy1etESi/uAdJrs1hTaUklHuW8qSDdC3UfXVR5cnD3rAFSSqtFb7z7eapErx7rC739jCXfbK3aWiipjXo8UbmxXPa7QQq9R289j2Gr88N7Ag5AlHPRKc37pNZv0CZtX1tVMG6rm8qW1/KlCgQvcMss933ybwXZz3dReW5yce4ByZtHFIhwT9kmjxg8BzbKDUe1PB9edBJqSN7/KM1LmqyuMZ5BpeTUw1aD/uDI0relPfSHa/Wn8Pxq1BNfxy/h3IdwOJqIKumb9CHvTqMefyY82RoQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAGEAAIIAQQQAgggBBBACCAEEEAIIIAQQAAhgABCACGAAEIAAYQAAggBBBACCAEEEAIIIAQQQAggfyL/BBgA8PgLdH0TBtkAAAAASUVORK5CYII='
|
||||
},
|
||||
// 图片进入可见区域前多少像素前,单位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>
|
||||
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/PhLwhikAAAAzUExURZWVldfX162trcPDw5ubm7i4uNzc3Obm5s3NzaGhoeHh4cjIyKenp9LS0r29vbKysuvr67sDMEkAAAlpSURBVHja7NzpYqMgAIVRUVHc8/5PO66R1WAbOzX97q+ZtDEpR0AWTR7kVyWhCAAhgABCAAGEAAIIAQQQAggBBBACCCAEEEAIIIAQQAgggBBAACGAAEIAAYQAQgABhAACCAEEEAIIIAQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAsqeX5QWHKIcs/Ptl03lfL4zDFPWfBGmSpPn+IZzSH5KkCL5B+n+oklwz6Iz//R2QzFOabzhEmiRirAmZt/bl0w/dpMbLqeeo4wEdpC7zR5WAPKziHKtO7ql+ReKvIa9BxgNaL5ZtEkpeAGIVp5jKJa09xVo9vgSSzQcszdYvmOqjQNSQ6pHK6rO1n1Xj32788miwHLaZz1Tl9i/yayDlYJ/60/+lp8GSY7OY1B8E4p55bWmfquFk22GLuUUxi78cX+m+BjL2GLkhMrV+/muS6Sfic0CEp5T1Yu2OQdTzsKV0MJV73KVjroyTffxfuv5Tf3fd6iLT9wz8YdVHgUzF2Is9/Xhi5sYJqP1w/GUpjOiHVbaI0w2L+pg3GZzvtokcgHxWDXHaiy78l3sPke01qphamT5c+dqyeAGSumdL/mkggauTam0e3L/mPEiqtzKDbl0Z1Wn8xOa4ySo8X/7TQIJnY/seEKWf12UmC72CKP9xYjr19RPT7NNA+oMO+R0gwmlotAry+C6I0f59ch8yXVQOr0BKYcXt1IUYRyCt+Ur9HGsrQKI79WY9sY9ARPKlzFOFdb41ioD8b5Bp+mqeeRKAxINkESBFGpOpKhgv9OuYpH8A8l4Qa3qp60Kl2/k+rG2sWafuuyCBafb2j4JkgZUob3nWcmicpkxEgmTLLGejTxnWSWCi8lPmsk6DlIHFJv24ojiYyYoGacwL8zXTLEAVaDI/Ybb3NIgKDSv2oXpmHkvNs+PTpMASEdlk7fOZeRk37fwJ6yGnQarQsGIfqqcvx43rTOXY6jf7uKXdRzdLDRPbjIrx1cIj3Kr4KyBFezzgUGuR5893qkOQ19fR2uVBaU+r16LphJNOiatK7PeBZK/Kb+tUn71rcQjSvARpghfH/yG/D2RetTuI3N5QrMWdP46brP7FmKZ//CGQ9At9SL01DLkzY/Vs8Z97fQZ7gelw7jHqCz+/Wile5J4g3Vc79eb5a6oLSue+Ve83gaSv2jp5PxCzjzwFUm9zw9MllSMil1kS4d2E9SaQ1xNo9wMxx0+nQNLnew/WDHvveMAHYm08mofl3TFI/8pD3Q6kMAv6DIi2jTCwRJUvNdDYrrJum9oHhusCbWALonwxBRk1vXMnEGWuT5wAmfYuVGUYpJ7fUZujCN92hvzwWlrFgxSfANKb10DxIMbShnfrynyZZV30imA7P43ArXXHbvBVkTCIuGy25AdBrHmNeBCpL214QdLp9LZarG3IMWrmW0ehtuO7F2PS09UcgqS3B7FKPhpknrStD0HGF/vQRne37LwLG8EbHT4WxN7/Fg0yD9Yr/3br4nnstA+0Il6QxzdBmg8A6a2/IRbkcK9h/uzV8zywF/oSkOyageCPglRWgcWClHnEzs9q/t/SENVXgFijlsq3VtXdCsRp4qObrLLLgjuzSq3fX89ZZW6AfxNIzF6X9FYgThN/fk093KkvHX/hbWd+DqS/FUhlf+G3gohEXzVs3g9iDluWoaW8fL73QhB34u+tIHIf19nLuF4Q98a09Eynnl56q+ePgEhnX+dbQOp6H5XnJ0ACd8dFgkwf12nTOTcEqd2pom+CFF02TIPw6dKmrLS5qOtBpo8b5quUtrwrSGbuqPkeSJqllTFHO02NPxdMrm+y5LKdWyWXjw4vA5nGEtnjuyCFyHqNYvEolzmASm3zK1Eg5zr13lhqV1tlksnVw8Pkwgri7O07AVKLJkutRYw87bPlRpBpNXE8xGb+fhBlvEGrGPLqViu5sILIx9dAmqF1705sxF4M8+R8P5dOdQwi12fMnATpjJ2JSt/POIvU9wPJEs/jduJAjLvU0yFT0i64Yb1bsVi79dA4pEy3TzoHMq2O7Re4vXm5O9+l290NpE4CU+YRIMNye2iaqbVS2AUnn2fsekthYKReVNutVedA5juttyIXrT38mOds+ps9DWhwL7GWc61/DVKPzVN9UHDarf1icU98IOU8tm6L031Iq63t1tKzj3fe/FCpO4F0/i0Z2+yvA1KeGBjqj1qYx8/zoxpKZ1Yl367I1k+sfcft/QPy9csXy/32qX1qLZsrryG5BGQaRj0vc/b7N54XXq293TCLB5HO42Fy517obW19b+qjl3CHp0fdLJcWvmdy1etESi/uAdJrs1hTaUklHuW8qSDdC3UfXVR5cnD3rAFSSqtFb7z7eapErx7rC739jCXfbK3aWiipjXo8UbmxXPa7QQq9R289j2Gr88N7Ag5AlHPRKc37pNZv0CZtX1tVMG6rm8qW1/KlCgQvcMss933ybwXZz3dReW5yce4ByZtHFIhwT9kmjxg8BzbKDUe1PB9edBJqSN7/KM1LmqyuMZ5BpeTUw1aD/uDI0relPfSHa/Wn8Pxq1BNfxy/h3IdwOJqIKumb9CHvTqMefyY82RoQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAGEAAIIAQQQAgggBBBACCAEEEAIIIAQQAAhgABCACGAAEIAAYQAAggBBBACCAEEEAIIIAQQQAggfyL/BBgA8PgLdH0TBtkAAAAASUVORK5CYII='
|
||||
},
|
||||
// 图片进入可见区域前多少像素前,单位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 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>
|
||||
|
||||
@@ -1,143 +1,143 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-line-progress-class tn-line-progress"
|
||||
:style="[progressStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-line-progress--active"
|
||||
:class="[
|
||||
$t.colorUtils.getBackgroundColorInternalClass(activeColor),
|
||||
striped ? stripedAnimation ? 'tn-line-progress__striped tn-line-progress__striped--active' : 'tn-line-progress__striped' : '',
|
||||
]"
|
||||
:style="[progressActiveStyle]"
|
||||
>
|
||||
<slot v-if="$slots.default || $slots.$default"></slot>
|
||||
<block v-else-if="showPercent">{{ percent + '%' }}</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-line-progress',
|
||||
props: {
|
||||
// 进度(百分比)
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100
|
||||
}
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示为圆角
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否运动
|
||||
stripedAnimation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 激活部分颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 非激活部分颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示进度条内部百分比值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progressStyle() {
|
||||
let style = {}
|
||||
style.borderRadius = this.round ? '100rpx' : 0
|
||||
if (this.height) {
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
}
|
||||
if (this.inactiveColor) {
|
||||
style.backgroundColor = this.inactiveColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
progressActiveStyle() {
|
||||
let style = {}
|
||||
style.width = this.percent + '%'
|
||||
if (this.$t.colorUtils.getBackgroundColorStyle(this.activeColor)) {
|
||||
style.backgroundColor = this.$t.colorUtils.getBackgroundColorStyle(this.activeColor)
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-line-progress {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 28rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 100rpx;
|
||||
background-color: $tn-progress-bg-color;
|
||||
|
||||
&--active {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-items: flex-end;
|
||||
justify-content: space-around;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
font-size: 20rpx;
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&__striped {
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
background-size: 80rpx 80rpx;
|
||||
|
||||
&--active {
|
||||
animation: progress-striped 2s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progress-striped {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 80rpx 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-line-progress-class tn-line-progress"
|
||||
:style="[progressStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-line-progress--active"
|
||||
:class="[
|
||||
$t.color.getBackgroundColorInternalClass(activeColor),
|
||||
striped ? stripedAnimation ? 'tn-line-progress__striped tn-line-progress__striped--active' : 'tn-line-progress__striped' : '',
|
||||
]"
|
||||
:style="[progressActiveStyle]"
|
||||
>
|
||||
<slot v-if="$slots.default || $slots.$default"></slot>
|
||||
<block v-else-if="showPercent">{{ percent + '%' }}</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-line-progress',
|
||||
props: {
|
||||
// 进度(百分比)
|
||||
percent: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
validator: val => {
|
||||
return val >= 0 && val <= 100
|
||||
}
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示为圆角
|
||||
round: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示条纹
|
||||
striped: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 条纹是否运动
|
||||
stripedAnimation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 激活部分颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 非激活部分颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示进度条内部百分比值
|
||||
showPercent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
progressStyle() {
|
||||
let style = {}
|
||||
style.borderRadius = this.round ? '100rpx' : 0
|
||||
if (this.height) {
|
||||
style.height = this.$t.string.getLengthUnitValue(this.height)
|
||||
}
|
||||
if (this.inactiveColor) {
|
||||
style.backgroundColor = this.inactiveColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
progressActiveStyle() {
|
||||
let style = {}
|
||||
style.width = this.percent + '%'
|
||||
if (this.$t.color.getBackgroundColorStyle(this.activeColor)) {
|
||||
style.backgroundColor = this.$t.color.getBackgroundColorStyle(this.activeColor)
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-line-progress {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 28rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 100rpx;
|
||||
background-color: $tn-progress-bg-color;
|
||||
|
||||
&--active {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-items: flex-end;
|
||||
justify-content: space-around;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
font-size: 20rpx;
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&__striped {
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
|
||||
background-size: 80rpx 80rpx;
|
||||
|
||||
&--active {
|
||||
animation: progress-striped 2s linear infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes progress-striped {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 80rpx 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,209 +1,209 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-list-cell-class tn-list-cell"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
cellClass
|
||||
]"
|
||||
:hover-class="hover ? 'tn-hover' : ''"
|
||||
:hover-stay-time="150"
|
||||
:style="[cellStyle]"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-list-cell',
|
||||
props: {
|
||||
// 列表序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否有箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//箭头是否有偏移距离
|
||||
arrowRight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否有点击效果
|
||||
hover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 隐藏线条
|
||||
unlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//线条是否有左偏移距离
|
||||
lineLeft: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//线条是否有右偏移距离
|
||||
lineRight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否加圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cellClass() {
|
||||
let clazz = ''
|
||||
|
||||
if (this.arrow) {
|
||||
clazz += ' tn-list-cell--arrow'
|
||||
if (!this.arrowRight) {
|
||||
clazz += ' tn-list-cell--arrow--none-right'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.unlined) {
|
||||
clazz += ' tn-list-cell--unlined'
|
||||
} else {
|
||||
if (this.lineLeft) {
|
||||
clazz += ' tn-list-cell--line-left'
|
||||
}
|
||||
if (this.lineRight) {
|
||||
clazz += ' tn-list-cell--line-right'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.radius) {
|
||||
clazz += ' tn-list-cell--radius'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
cellStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit("click", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit("tap", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-list-cell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
padding: 26rpx 30rpx;
|
||||
|
||||
&--radius {
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--arrow {
|
||||
&::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 30rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
margin-top: -12rpx;
|
||||
border-width: 4rpx 4rpx 0 0;
|
||||
border-color: $tn-font-holder-color;
|
||||
border-style: solid;
|
||||
transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
|
||||
}
|
||||
|
||||
&--none-right {
|
||||
&::before {
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
border-bottom: 1px solid $tn-border-solid-color;
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
|
||||
&--line-left {
|
||||
&::after {
|
||||
left: 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--line-right {
|
||||
&::after {
|
||||
right: 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--unlined {
|
||||
&::after {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-list-cell-class tn-list-cell"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
cellClass
|
||||
]"
|
||||
:hover-class="hover ? 'tn-hover' : ''"
|
||||
:hover-stay-time="150"
|
||||
:style="[cellStyle]"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-list-cell',
|
||||
props: {
|
||||
// 列表序号
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否有箭头
|
||||
arrow: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//箭头是否有偏移距离
|
||||
arrowRight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否有点击效果
|
||||
hover: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 隐藏线条
|
||||
unlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//线条是否有左偏移距离
|
||||
lineLeft: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//线条是否有右偏移距离
|
||||
lineRight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否加圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cellClass() {
|
||||
let clazz = ''
|
||||
|
||||
if (this.arrow) {
|
||||
clazz += ' tn-list-cell--arrow'
|
||||
if (!this.arrowRight) {
|
||||
clazz += ' tn-list-cell--arrow--none-right'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.unlined) {
|
||||
clazz += ' tn-list-cell--unlined'
|
||||
} else {
|
||||
if (this.lineLeft) {
|
||||
clazz += ' tn-list-cell--line-left'
|
||||
}
|
||||
if (this.lineRight) {
|
||||
clazz += ' tn-list-cell--line-right'
|
||||
}
|
||||
}
|
||||
|
||||
if (this.radius) {
|
||||
clazz += ' tn-list-cell--radius'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
cellStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit("click", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit("tap", {
|
||||
index: Number(this.index)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-list-cell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
padding: 26rpx 30rpx;
|
||||
|
||||
&--radius {
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--arrow {
|
||||
&::before {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 30rpx;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
margin-top: -12rpx;
|
||||
border-width: 4rpx 4rpx 0 0;
|
||||
border-color: $tn-font-holder-color;
|
||||
border-style: solid;
|
||||
transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
|
||||
}
|
||||
|
||||
&--none-right {
|
||||
&::before {
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: " ";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
border-bottom: 1px solid $tn-border-solid-color;
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
|
||||
&--line-left {
|
||||
&::after {
|
||||
left: 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--line-right {
|
||||
&::after {
|
||||
right: 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--unlined {
|
||||
&::after {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,184 +1,184 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-list-view-class tn-list-view"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
viewClass
|
||||
]"
|
||||
:style="[viewStyle]"
|
||||
>
|
||||
<view
|
||||
v-if="showTitle"
|
||||
class="tn-list-view__title"
|
||||
:class="[
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[titleStyle]"
|
||||
@tap="handleClickTitle"
|
||||
>{{ title }}</view>
|
||||
|
||||
<view
|
||||
v-else
|
||||
:class="[{'tn-list-view__title--card': card}]"
|
||||
@tap="handleClickTitle"
|
||||
>
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-list-view__content tn-border-solid-top tn-border-solid-bottom"
|
||||
:class="[contentClass]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-list-view',
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 去掉边框 上边框 top, 下边框 bottom, 所有边框 all
|
||||
unlined: {
|
||||
type: String,
|
||||
default: 'all'
|
||||
},
|
||||
// 上外边距
|
||||
marginTop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 内容是否显示为卡片模式
|
||||
card: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自定义标题
|
||||
customTitle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showTitle() {
|
||||
return !this.customTitle && this.title
|
||||
},
|
||||
viewClass() {
|
||||
let clazz = ''
|
||||
|
||||
if (this.card) {
|
||||
clazz += ' tn-list-view--card'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
viewStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.marginTop) {
|
||||
style.marginTop = this.marginTop
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
titleStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
contentClass() {
|
||||
let clazz = ''
|
||||
|
||||
if (this.card) {
|
||||
clazz += ' tn-list-view__content--card'
|
||||
}
|
||||
|
||||
switch(this.unlined) {
|
||||
case 'top':
|
||||
clazz += ' tn-none-border-top'
|
||||
break
|
||||
case 'bottom':
|
||||
clazz += ' tn-none-border-bottom'
|
||||
break
|
||||
case 'all':
|
||||
clazz += ' tn-none-border'
|
||||
break
|
||||
}
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
kindShowFlag: this.showKind
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理标题点击事件
|
||||
handleClickTitle() {
|
||||
if (!this.kindList) return
|
||||
this.kindShowFlag = !this.kindShowFlag
|
||||
this.$emit("clickTitle", {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-list-view {
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 30rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--card {
|
||||
// margin: 0rpx 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
|
||||
&--card {
|
||||
// width: auto;
|
||||
// overflow: hidden;
|
||||
// margin-right: 30rpx;
|
||||
// margin-left: 30rpx;
|
||||
// border-radius: 20rpx
|
||||
}
|
||||
}
|
||||
|
||||
&--card {
|
||||
// padding-bottom: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-list-view-class tn-list-view"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
viewClass
|
||||
]"
|
||||
:style="[viewStyle]"
|
||||
>
|
||||
<view
|
||||
v-if="showTitle"
|
||||
class="tn-list-view__title"
|
||||
:class="[
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[titleStyle]"
|
||||
@tap="handleClickTitle"
|
||||
>{{ title }}</view>
|
||||
|
||||
<view
|
||||
v-else
|
||||
:class="[{'tn-list-view__title--card': card}]"
|
||||
@tap="handleClickTitle"
|
||||
>
|
||||
<slot name="title"></slot>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-list-view__content tn-border-solid-top tn-border-solid-bottom"
|
||||
:class="[contentClass]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-list-view',
|
||||
props: {
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 去掉边框 上边框 top, 下边框 bottom, 所有边框 all
|
||||
unlined: {
|
||||
type: String,
|
||||
default: 'all'
|
||||
},
|
||||
// 上外边距
|
||||
marginTop: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 内容是否显示为卡片模式
|
||||
card: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自定义标题
|
||||
customTitle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showTitle() {
|
||||
return !this.customTitle && this.title
|
||||
},
|
||||
viewClass() {
|
||||
let clazz = ''
|
||||
|
||||
if (this.card) {
|
||||
clazz += ' tn-list-view--card'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
viewStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
if (this.marginTop) {
|
||||
style.marginTop = this.marginTop
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
titleStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
contentClass() {
|
||||
let clazz = ''
|
||||
|
||||
if (this.card) {
|
||||
clazz += ' tn-list-view__content--card'
|
||||
}
|
||||
|
||||
switch(this.unlined) {
|
||||
case 'top':
|
||||
clazz += ' tn-none-border-top'
|
||||
break
|
||||
case 'bottom':
|
||||
clazz += ' tn-none-border-bottom'
|
||||
break
|
||||
case 'all':
|
||||
clazz += ' tn-none-border'
|
||||
break
|
||||
}
|
||||
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
kindShowFlag: this.showKind
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理标题点击事件
|
||||
handleClickTitle() {
|
||||
if (!this.kindList) return
|
||||
this.kindShowFlag = !this.kindShowFlag
|
||||
this.$emit("clickTitle", {})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-list-view {
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 30rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--card {
|
||||
// margin: 0rpx 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
|
||||
&--card {
|
||||
// width: auto;
|
||||
// overflow: hidden;
|
||||
// margin-right: 30rpx;
|
||||
// margin-left: 30rpx;
|
||||
// border-radius: 20rpx
|
||||
}
|
||||
}
|
||||
|
||||
&--card {
|
||||
// padding-bottom: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,188 +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>
|
||||
<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>
|
||||
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 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>
|
||||
|
||||
@@ -1,100 +1,114 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="tn-loading-class tn-loading"
|
||||
:class="mode === 'circle' ? 'tn-loading-circle' : 'tn-loading-flower'"
|
||||
:style="[loadStyle]"
|
||||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="tn-loading-class tn-loading"
|
||||
:class="[
|
||||
`tn-loading-${mode}`,
|
||||
animation ? `tn-loading-${mode}--animation` : ''
|
||||
]"
|
||||
:style="[loadStyle]"
|
||||
></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-loading',
|
||||
props: {
|
||||
// 动画类型
|
||||
// circle 圆圈 flower 花朵形状
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 是否显示动画
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 圆圈颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 加载动画圆圈的样式
|
||||
loadStyle() {
|
||||
let style = {}
|
||||
style.width = this.size + 'rpx'
|
||||
style.height = style.width
|
||||
if (this.mode === 'circle') style.borderColor = `#E6E6E6 #E6E6E6 #E6E6E6 ${this.color ? this.color : '#AAAAAA'}`
|
||||
|
||||
return style
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-loading',
|
||||
props: {
|
||||
// 动画类型
|
||||
// circle 圆圈 flower 花朵形状
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示加载动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 圆圈颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 加载动画圆圈的样式
|
||||
loadStyle() {
|
||||
let style = {}
|
||||
style.width = this.size + 'rpx'
|
||||
style.height = style.width
|
||||
if (this.mode === 'circle') style.borderColor = `#E6E6E6 #E6E6E6 #E6E6E6 ${this.color ? this.color : '#AAAAAA'}`
|
||||
|
||||
return style
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-loading-circle {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
vertical-align: middle;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: 0 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid;
|
||||
border-color: #E6E6E6 #E6E6E6 #E6E6E6 #AAAAAA;
|
||||
animation: tn-circle 1s linear infinite;
|
||||
-webkit-animation: tn-circle 1s linear infinite;
|
||||
}
|
||||
|
||||
.tn-loading-flower {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;;
|
||||
background-size: 100%;
|
||||
animation: tn-flower 1s steps(12) infinite;
|
||||
-webkit-animation: tn-flower 1s steps(12) infinite;
|
||||
}
|
||||
|
||||
@keyframes tn-flower {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tn-circle {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
-webkit-transform: rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.tn-loading-circle {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
vertical-align: middle;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: 0 0;
|
||||
border-radius: 50%;
|
||||
border: 2px solid;
|
||||
border-color: #E6E6E6 #E6E6E6 #E6E6E6 #AAAAAA;
|
||||
|
||||
&--animation {
|
||||
animation: tn-circle 1s linear infinite;
|
||||
-webkit-animation: tn-circle 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.tn-loading-flower {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;;
|
||||
background-size: 100%;
|
||||
|
||||
&--animation {
|
||||
animation: tn-flower 1s steps(12) infinite;
|
||||
-webkit-animation: tn-flower 1s steps(12) infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tn-flower {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
-webkit-transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tn-circle {
|
||||
0% {
|
||||
transform: rotate(0);
|
||||
-webkit-transform: rotate(0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
-webkit-transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,281 +1,246 @@
|
||||
<template>
|
||||
<view v-if="value" class="tn-modal-class tn-modal">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="center"
|
||||
:popup="false"
|
||||
:borderRadius="radius"
|
||||
:width="width"
|
||||
:zoom="zoom"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:maskCloseable="maskCloseable"
|
||||
:zIndex="zIndex"
|
||||
:closeBtn="showCloseBtn"
|
||||
@close="close"
|
||||
>
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
class="tn-modal__box"
|
||||
:class="[
|
||||
backgroundColorClass
|
||||
]"
|
||||
:style="[boxStyle]"
|
||||
>
|
||||
<!-- 不是自定义弹框内容 -->
|
||||
<!-- <view v-if="!custom">
|
||||
<view class="tn-modal__box__title" v-if="title && title !== ''">{{ title }}</view>
|
||||
<view
|
||||
class="tn-modal__box__content"
|
||||
:class="[
|
||||
fontColorClass,
|
||||
contentClass
|
||||
]"
|
||||
:style="contentStyle"
|
||||
>{{ content }}</view>
|
||||
<view v-if="button && button.length" class="tn-modal__box__btn-box" :class="[button.length != 2 ? 'tn-flex-direction-column' : '']">
|
||||
<block v-for="(item, index) in button" :key="index">
|
||||
<tn-button
|
||||
width="100%"
|
||||
height="68rpx"
|
||||
:fontSize="26"
|
||||
:backgroundColor="item.backgroundColor || ''"
|
||||
:fontColor="item.fontColor || ''"
|
||||
:plain="item.plain || false"
|
||||
:shape="item.shape || 'round'"
|
||||
:class="[
|
||||
button.length > 2 ? 'tn-margin-bottom' : ''
|
||||
]"
|
||||
@click="handleClick(index)"
|
||||
:style="{
|
||||
width: button.length != 2 ? '80%' : '46%'
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</tn-button>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<slot></slot>
|
||||
</view> -->
|
||||
<slot>
|
||||
<view v-if="!custom">
|
||||
<view class="tn-modal__box__title" v-if="title && title !== ''">{{ title }}</view>
|
||||
<view
|
||||
class="tn-modal__box__content"
|
||||
:class="[
|
||||
fontColorClass,
|
||||
contentClass
|
||||
]"
|
||||
:style="contentStyle"
|
||||
>{{ content }}</view>
|
||||
<view v-if="button && button.length" class="tn-modal__box__btn-box" :class="[button.length != 2 ? 'tn-flex-direction-column' : '']">
|
||||
<block v-for="(item, index) in button" :key="index">
|
||||
<tn-button
|
||||
width="100%"
|
||||
height="68rpx"
|
||||
:fontSize="26"
|
||||
:backgroundColor="item.backgroundColor || ''"
|
||||
:fontColor="item.fontColor || ''"
|
||||
:plain="item.plain || false"
|
||||
:shape="item.shape || 'round'"
|
||||
:class="[
|
||||
button.length > 2 ? 'tn-margin-bottom' : ''
|
||||
]"
|
||||
@click="handleClick(index)"
|
||||
:style="{
|
||||
width: button.length != 2 ? '80%' : '46%'
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</tn-button>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</tn-popup>
|
||||
<template>
|
||||
<view v-if="value" class="tn-modal-class tn-modal">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="center"
|
||||
:popup="false"
|
||||
:borderRadius="radius"
|
||||
:width="width"
|
||||
:zoom="zoom"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:maskCloseable="maskCloseable"
|
||||
:zIndex="zIndex"
|
||||
:closeBtn="showCloseBtn"
|
||||
@close="close"
|
||||
>
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
class="tn-modal__box"
|
||||
:class="[
|
||||
backgroundColorClass
|
||||
]"
|
||||
:style="[boxStyle]"
|
||||
>
|
||||
<!-- 不是自定义弹框内容 -->
|
||||
<view v-if="!custom">
|
||||
<view class="tn-modal__box__title" v-if="title && title !== ''">{{ title }}</view>
|
||||
<view
|
||||
class="tn-modal__box__content"
|
||||
:class="[
|
||||
fontColorClass,
|
||||
contentClass
|
||||
]"
|
||||
:style="contentStyle"
|
||||
>{{ content }}</view>
|
||||
<view v-if="button && button.length" class="tn-modal__box__btn-box" :class="[button.length != 2 ? 'tn-flex-direction-column' : '']">
|
||||
<block v-for="(item, index) in button" :key="index">
|
||||
<tn-button
|
||||
width="100%"
|
||||
height="68rpx"
|
||||
:fontSize="26"
|
||||
:backgroundColor="item.backgroundColor || ''"
|
||||
:fontColor="item.fontColor || ''"
|
||||
:plain="item.plain || false"
|
||||
:shape="item.shape || 'round'"
|
||||
:class="[
|
||||
button.length > 2 ? 'tn-margin-bottom' : ''
|
||||
]"
|
||||
@click="handleClick(index)"
|
||||
:style="{
|
||||
width: button.length != 2 ? '80%' : '46%'
|
||||
}"
|
||||
>
|
||||
{{ item.text }}
|
||||
</tn-button>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-modal',
|
||||
props: {
|
||||
// 显示控制
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹框宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '84%'
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 内容
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮内容 设置参数与button组件的参数一致
|
||||
// {
|
||||
// text: '确定',
|
||||
// backgroundColor: 'red',
|
||||
// fontColor: 'white',
|
||||
// plain: true,
|
||||
// shape: ''
|
||||
// }
|
||||
button: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击遮罩是否可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示右上角关闭按钮
|
||||
showCloseBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 放大动画
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义弹框内容
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹框的z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
boxStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
contentClass() {
|
||||
let clazz = ''
|
||||
if (this.title) {
|
||||
clazz += ' tn-margin-top'
|
||||
} else {
|
||||
clazz += ' tn-modal__box__content--no-title'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理按钮点击事件
|
||||
handleClick(index) {
|
||||
if (!this.value) return
|
||||
this.$emit("click", {
|
||||
index: Number(index)
|
||||
})
|
||||
},
|
||||
// 处理关闭事件
|
||||
close() {
|
||||
this.$emit("cancel")
|
||||
this.$emit('input', false)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-modal',
|
||||
props: {
|
||||
// 显示控制
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹框宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '84%'
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 内容
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮内容 设置参数与button组件的参数一致
|
||||
// {
|
||||
// text: '确定',
|
||||
// backgroundColor: 'red',
|
||||
// fontColor: 'white',
|
||||
// plain: true,
|
||||
// shape: ''
|
||||
// }
|
||||
button: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击遮罩是否可以关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示右上角关闭按钮
|
||||
showCloseBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 放大动画
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 自定义弹框内容
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹框的z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
boxStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.padding) {
|
||||
style.padding = this.padding
|
||||
}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
contentClass() {
|
||||
let clazz = ''
|
||||
if (this.title) {
|
||||
clazz += ' tn-margin-top'
|
||||
} else {
|
||||
clazz += ' tn-modal__box__content--no-title'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
|
||||
if (this.fontSize) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理按钮点击事件
|
||||
handleClick(index) {
|
||||
if (!this.value) return
|
||||
this.$emit("click", {
|
||||
index: Number(index)
|
||||
})
|
||||
},
|
||||
// 处理关闭事件
|
||||
close() {
|
||||
this.$emit("cancel")
|
||||
this.$emit('input', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-modal {
|
||||
|
||||
&__box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
padding: 40rpx 64rpx;
|
||||
|
||||
&__title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
padding-top: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__content {
|
||||
text-align: center;
|
||||
padding-bottom: 30rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
|
||||
&--no-title {
|
||||
padding-bottom: 0rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__btn-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__content ~ &__btn-box {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-modal {
|
||||
|
||||
&__box {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
background-color: #FFFFFF;
|
||||
padding: 40rpx 64rpx;
|
||||
|
||||
&__title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
padding-top: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__content {
|
||||
text-align: center;
|
||||
padding-bottom: 30rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 28rpx;
|
||||
|
||||
&--no-title {
|
||||
padding-bottom: 0rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__btn-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__content ~ &__btn-box {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,355 +1,355 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-custom-nav-bar-class tn-custom-nav-bar"
|
||||
:style="[navBarStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-custom-nav-bar__bar"
|
||||
:class="[barClass]"
|
||||
:style="[barStyle]"
|
||||
>
|
||||
<view v-if="isBack">
|
||||
<view v-if="customBack">
|
||||
<view
|
||||
:style="{
|
||||
width: customBackStyleInfo.width + 'px',
|
||||
height: customBackStyleInfo.height + 'px',
|
||||
marginLeft: customBackStyleInfo.left + 'px'
|
||||
}"
|
||||
>
|
||||
<slot name="back"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="tn-custom-nav-bar__bar__action" @tap="handlerBack">
|
||||
<text class="tn-custom-nav-bar__bar__action--nav-back" :class="[`tn-icon-${backIcon}`]"></text>
|
||||
<text class="tn-custom-nav-bar__bar__action--nav-back-text" v-if="backTitle">{{ backTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-custom-nav-bar__bar__content" :style="[contentStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-nav-bar',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 导航栏的高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 高度单位
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
// 是否显示返回按钮
|
||||
isBack: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 返回按钮的图标
|
||||
backIcon: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 返回按钮旁显示的文字
|
||||
backTitle: {
|
||||
type: String,
|
||||
default: '返回'
|
||||
},
|
||||
// 透明状态栏
|
||||
alpha: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否固定在顶部
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示底部阴影
|
||||
bottomShadow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自定义返回按钮
|
||||
customBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 返回前回调
|
||||
beforeBack: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
navBarStyle() {
|
||||
let style = {}
|
||||
style.height = this.height === 0 ? this.customBarHeight + this.unit : this.height + this.unit
|
||||
if (this.fixed) {
|
||||
style.position = 'fixed'
|
||||
}
|
||||
style.zIndex = this.elZIndex
|
||||
|
||||
return style
|
||||
},
|
||||
barClass() {
|
||||
let clazz = ''
|
||||
if (this.backgroundColorClass) {
|
||||
clazz += ` ${this.backgroundColorClass}`
|
||||
}
|
||||
if (this.fontColorClass) {
|
||||
clazz += `${this.fontColorClass}`
|
||||
}
|
||||
if (this.fixed) {
|
||||
clazz += ' tn-custom-nav-bar__bar--fixed'
|
||||
}
|
||||
if (this.alpha) {
|
||||
clazz += ' tn-custom-nav-bar__bar--alpha'
|
||||
}
|
||||
if (this.bottomShadow) {
|
||||
clazz += ' tn-custom-nav-bar__bar--bottom-shadow'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
barStyle() {
|
||||
let style = {}
|
||||
style.height = this.height === 0 ? this.customBarHeight + this.unit : this.height + this.unit
|
||||
|
||||
if (this.fixed) {
|
||||
style.paddingTop = this.statusBarHeight + 'px'
|
||||
}
|
||||
|
||||
if(!this.backgroundColorClass) {
|
||||
style.backgroundColor = this.backgroundColor !== '' ? this.backgroundColor : '#FFFFFF'
|
||||
}
|
||||
if (!this.fontColorClass && this.fontColor) {
|
||||
style.color= this.fontColor
|
||||
}
|
||||
|
||||
style.zIndex = this.elZIndex
|
||||
|
||||
return style
|
||||
},
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
style.top = this.fixed ? this.statusBarHeight + 'px' : '0px'
|
||||
style.height = this.height === 0 ? (this.customBarHeight - this.statusBarHeight) + this.unit : this.height + this.unit
|
||||
style.lineHeight = style.height
|
||||
|
||||
if (this.isBack) {
|
||||
if (this.customBack) {
|
||||
const width = (this.customBackStyleInfo.width + this.customBackStyleInfo.left) * 2
|
||||
style.width = `calc(100% - ${width}px)`
|
||||
} else {
|
||||
style.width = 'calc(100% - 340rpx)'
|
||||
}
|
||||
} else {
|
||||
style.width = '100%'
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.navbar
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 状态栏的高度
|
||||
statusBarHeight: 0,
|
||||
// 自定义导航栏的高度
|
||||
customBarHeight: 0,
|
||||
// 自定义返回按钮时,返回容器的宽高边距信息
|
||||
customBackStyleInfo: {
|
||||
width: 86,
|
||||
height: 32,
|
||||
left: 15
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 获取vuex中的自定义顶栏的高度
|
||||
this.updateNavBarInfo()
|
||||
},
|
||||
created() {
|
||||
// 获取胶囊信息
|
||||
// #ifdef MP-WEIXIN
|
||||
let custom = wx.getMenuButtonBoundingClientRect()
|
||||
this.customBackStyleInfo.width = custom.width
|
||||
this.customBackStyleInfo.height = custom.height
|
||||
this.customBackStyleInfo.left = uni.upx2px(750) - custom.right
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// 更新导航栏的高度
|
||||
async updateNavBarInfo() {
|
||||
// 获取vuex中的自定义顶栏的高度
|
||||
let customBarHeight = this.vuex_custom_bar_height
|
||||
let statusBarHeight = this.vuex_status_bar_height
|
||||
// 如果获取失败则重新获取
|
||||
if (!customBarHeight) {
|
||||
try {
|
||||
const navBarInfo = await this.$t.updateCustomBar()
|
||||
customBarHeight = navBarInfo.customBarHeight
|
||||
statusBarHeight = navBarInfo.statusBarHeight
|
||||
} catch(e) {
|
||||
setTimeout(() => {
|
||||
this.updateNavBarInfo()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 更新vuex中的导航栏信息
|
||||
this && this.$t.vuex('vuex_status_bar_height', statusBarHeight)
|
||||
this && this.$t.vuex('vuex_custom_bar_height', customBarHeight)
|
||||
|
||||
this.customBarHeight = customBarHeight
|
||||
this.statusBarHeight = statusBarHeight
|
||||
},
|
||||
// 处理返回事件
|
||||
async handlerBack() {
|
||||
if (this.beforeBack && typeof(this.beforeBack) === 'function') {
|
||||
// 执行回调,同时传入索引当作参数
|
||||
// 在微信,支付宝等环境(H5正常),会导致父组件定义的函数体中的this变成子组件的this
|
||||
// 通过bind()方法,绑定父组件的this,让this的this为父组件的上下文
|
||||
let beforeBack = this.beforeBack.bind(this.$t.$parent.call(this))()
|
||||
// 判断是否返回了Promise
|
||||
if (!!beforeBack && typeof beforeBack.then === 'function') {
|
||||
await beforeBack.then(res => {
|
||||
// Promise返回成功
|
||||
this.navBack()
|
||||
}).catch(err => {})
|
||||
} else if (beforeBack === true) {
|
||||
this.navBack()
|
||||
}
|
||||
} else {
|
||||
this.navBack()
|
||||
}
|
||||
},
|
||||
// 返回上一页
|
||||
navBack() {
|
||||
|
||||
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
|
||||
const pages = getCurrentPages()
|
||||
if (pages && pages.length > 0) {
|
||||
const firstPage = pages[0]
|
||||
if (pages.length == 1 && (!firstPage.route || firstPage.route != 'pages/index/index')) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
} else {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-custom-nav-bar {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
&__bar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
min-height: 100rpx;
|
||||
justify-content: space-between;
|
||||
min-height: 0px;
|
||||
/* #ifdef MP-WEIXIN */
|
||||
padding-right: 220rpx;
|
||||
/* #endif */
|
||||
/* #ifdef MP-ALIPAY */
|
||||
padding-right: 150rpx;
|
||||
/* #endif */
|
||||
box-shadow: 0rpx 0rpx 0rpx;
|
||||
z-index: 9999;
|
||||
|
||||
&--fixed {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&--alpha {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&--bottom-shadow {
|
||||
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
|
||||
&--nav-back {
|
||||
/* position: absolute; */
|
||||
/* top: 50%; */
|
||||
/* left: 20rpx; */
|
||||
/* margin-top: -15rpx; */
|
||||
// width: 25rpx;
|
||||
// height: 25rpx;
|
||||
margin-left: 20rpx;
|
||||
font-size: 38rpx;
|
||||
line-height: 100%;
|
||||
// border-width: 0 0 4rpx 4rpx;
|
||||
// border-color: #000000;
|
||||
// border-style: solid;
|
||||
// transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
|
||||
}
|
||||
|
||||
&--nav-back-text {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
font-size: 32rpx;
|
||||
cursor: none;
|
||||
// pointer-events: none;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-custom-nav-bar-class tn-custom-nav-bar"
|
||||
:style="[navBarStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-custom-nav-bar__bar"
|
||||
:class="[barClass]"
|
||||
:style="[barStyle]"
|
||||
>
|
||||
<view v-if="isBack">
|
||||
<view v-if="customBack">
|
||||
<view
|
||||
:style="{
|
||||
width: customBackStyleInfo.width + 'px',
|
||||
height: customBackStyleInfo.height + 'px',
|
||||
marginLeft: customBackStyleInfo.left + 'px'
|
||||
}"
|
||||
>
|
||||
<slot name="back"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="tn-custom-nav-bar__bar__action" @tap="handlerBack">
|
||||
<text class="tn-custom-nav-bar__bar__action--nav-back" :class="[`tn-icon-${backIcon}`]"></text>
|
||||
<text class="tn-custom-nav-bar__bar__action--nav-back-text" v-if="backTitle">{{ backTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tn-custom-nav-bar__bar__content" :style="[contentStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view>
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-nav-bar',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 导航栏的高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 高度单位
|
||||
unit: {
|
||||
type: String,
|
||||
default: 'px'
|
||||
},
|
||||
// 是否显示返回按钮
|
||||
isBack: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 返回按钮的图标
|
||||
backIcon: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 返回按钮旁显示的文字
|
||||
backTitle: {
|
||||
type: String,
|
||||
default: '返回'
|
||||
},
|
||||
// 透明状态栏
|
||||
alpha: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否固定在顶部
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示底部阴影
|
||||
bottomShadow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自定义返回按钮
|
||||
customBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 返回前回调
|
||||
beforeBack: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
navBarStyle() {
|
||||
let style = {}
|
||||
style.height = this.height === 0 ? this.customBarHeight + this.unit : this.height + this.unit
|
||||
if (this.fixed) {
|
||||
style.position = 'fixed'
|
||||
}
|
||||
style.zIndex = this.elZIndex
|
||||
|
||||
return style
|
||||
},
|
||||
barClass() {
|
||||
let clazz = ''
|
||||
if (this.backgroundColorClass) {
|
||||
clazz += ` ${this.backgroundColorClass}`
|
||||
}
|
||||
if (this.fontColorClass) {
|
||||
clazz += `${this.fontColorClass}`
|
||||
}
|
||||
if (this.fixed) {
|
||||
clazz += ' tn-custom-nav-bar__bar--fixed'
|
||||
}
|
||||
if (this.alpha) {
|
||||
clazz += ' tn-custom-nav-bar__bar--alpha'
|
||||
}
|
||||
if (this.bottomShadow) {
|
||||
clazz += ' tn-custom-nav-bar__bar--bottom-shadow'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
barStyle() {
|
||||
let style = {}
|
||||
style.height = this.height === 0 ? this.customBarHeight + this.unit : this.height + this.unit
|
||||
|
||||
if (this.fixed) {
|
||||
style.paddingTop = this.statusBarHeight + 'px'
|
||||
}
|
||||
|
||||
if(!this.backgroundColorClass) {
|
||||
style.backgroundColor = this.backgroundColor !== '' ? this.backgroundColor : '#FFFFFF'
|
||||
}
|
||||
if (!this.fontColorClass && this.fontColor) {
|
||||
style.color= this.fontColor
|
||||
}
|
||||
|
||||
style.zIndex = this.elZIndex
|
||||
|
||||
return style
|
||||
},
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
style.top = this.fixed ? this.statusBarHeight + 'px' : '0px'
|
||||
style.height = this.height === 0 ? (this.customBarHeight - this.statusBarHeight) + this.unit : this.height + this.unit
|
||||
style.lineHeight = style.height
|
||||
|
||||
if (this.isBack) {
|
||||
if (this.customBack) {
|
||||
const width = (this.customBackStyleInfo.width + this.customBackStyleInfo.left) * 2
|
||||
style.width = `calc(100% - ${width}px)`
|
||||
} else {
|
||||
style.width = 'calc(100% - 340rpx)'
|
||||
}
|
||||
} else {
|
||||
style.width = '100%'
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.navbar
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 状态栏的高度
|
||||
statusBarHeight: 0,
|
||||
// 自定义导航栏的高度
|
||||
customBarHeight: 0,
|
||||
// 自定义返回按钮时,返回容器的宽高边距信息
|
||||
customBackStyleInfo: {
|
||||
width: 86,
|
||||
height: 32,
|
||||
left: 15
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 获取vuex中的自定义顶栏的高度
|
||||
this.updateNavBarInfo()
|
||||
},
|
||||
created() {
|
||||
// 获取胶囊信息
|
||||
// #ifdef MP-WEIXIN
|
||||
let custom = wx.getMenuButtonBoundingClientRect()
|
||||
this.customBackStyleInfo.width = custom.width
|
||||
this.customBackStyleInfo.height = custom.height
|
||||
this.customBackStyleInfo.left = uni.upx2px(750) - custom.right
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// 更新导航栏的高度
|
||||
async updateNavBarInfo() {
|
||||
// 获取vuex中的自定义顶栏的高度
|
||||
let customBarHeight = this.vuex_custom_bar_height
|
||||
let statusBarHeight = this.vuex_status_bar_height
|
||||
// 如果获取失败则重新获取
|
||||
if (!customBarHeight) {
|
||||
try {
|
||||
const navBarInfo = await this.$t.updateCustomBar()
|
||||
customBarHeight = navBarInfo.customBarHeight
|
||||
statusBarHeight = navBarInfo.statusBarHeight
|
||||
} catch(e) {
|
||||
setTimeout(() => {
|
||||
this.updateNavBarInfo()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 更新vuex中的导航栏信息
|
||||
this && this.$t.vuex('vuex_status_bar_height', statusBarHeight)
|
||||
this && this.$t.vuex('vuex_custom_bar_height', customBarHeight)
|
||||
|
||||
this.customBarHeight = customBarHeight
|
||||
this.statusBarHeight = statusBarHeight
|
||||
},
|
||||
// 处理返回事件
|
||||
async handlerBack() {
|
||||
if (this.beforeBack && typeof(this.beforeBack) === 'function') {
|
||||
// 执行回调,同时传入索引当作参数
|
||||
// 在微信,支付宝等环境(H5正常),会导致父组件定义的函数体中的this变成子组件的this
|
||||
// 通过bind()方法,绑定父组件的this,让this的this为父组件的上下文
|
||||
let beforeBack = this.beforeBack.bind(this.$t.$parent.call(this))()
|
||||
// 判断是否返回了Promise
|
||||
if (!!beforeBack && typeof beforeBack.then === 'function') {
|
||||
await beforeBack.then(res => {
|
||||
// Promise返回成功
|
||||
this.navBack()
|
||||
}).catch(err => {})
|
||||
} else if (beforeBack === true) {
|
||||
this.navBack()
|
||||
}
|
||||
} else {
|
||||
this.navBack()
|
||||
}
|
||||
},
|
||||
// 返回上一页
|
||||
navBack() {
|
||||
|
||||
// 通过判断当前页面的页面栈信息,是否有上一页进行返回,如果没有则跳转到首页
|
||||
const pages = getCurrentPages()
|
||||
if (pages && pages.length > 0) {
|
||||
const firstPage = pages[0]
|
||||
if (pages.length == 1 && (!firstPage.route || firstPage.route != 'pages/index/index')) {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
} else {
|
||||
uni.navigateBack({
|
||||
delta: 1
|
||||
})
|
||||
}
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-custom-nav-bar {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
&__bar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
min-height: 100rpx;
|
||||
justify-content: space-between;
|
||||
min-height: 0px;
|
||||
/* #ifdef MP-WEIXIN */
|
||||
padding-right: 220rpx;
|
||||
/* #endif */
|
||||
/* #ifdef MP-ALIPAY */
|
||||
padding-right: 150rpx;
|
||||
/* #endif */
|
||||
box-shadow: 0rpx 0rpx 0rpx;
|
||||
z-index: 9999;
|
||||
|
||||
&--fixed {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&--alpha {
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&--bottom-shadow {
|
||||
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&__action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
|
||||
&--nav-back {
|
||||
/* position: absolute; */
|
||||
/* top: 50%; */
|
||||
/* left: 20rpx; */
|
||||
/* margin-top: -15rpx; */
|
||||
// width: 25rpx;
|
||||
// height: 25rpx;
|
||||
margin-left: 20rpx;
|
||||
font-size: 38rpx;
|
||||
line-height: 100%;
|
||||
// border-width: 0 0 4rpx 4rpx;
|
||||
// border-color: #000000;
|
||||
// border-style: solid;
|
||||
// transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
|
||||
}
|
||||
|
||||
&--nav-back-text {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
font-size: 32rpx;
|
||||
cursor: none;
|
||||
// pointer-events: none;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,209 +1,209 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="showNotice"
|
||||
class="tn-notice-bar-class tn-notice-bar"
|
||||
:style="{
|
||||
borderRadius: radius + 'rpx'
|
||||
}"
|
||||
>
|
||||
<block v-if="mode === 'horizontal' && circular">
|
||||
<tn-row-notice
|
||||
:backgroundColor="backgroundColor"
|
||||
:fontColor="fontColor"
|
||||
:fontSize="fontSize"
|
||||
:fontUnit="fontUnit"
|
||||
:list="list"
|
||||
:show="show"
|
||||
:playStatus="playStatus"
|
||||
:leftIcon="leftIcon"
|
||||
:leftIconName="leftIconName"
|
||||
:leftIconSize="leftIconSize"
|
||||
:rightIcon="rightIcon"
|
||||
:rightIconName="rightIconName"
|
||||
:rightIconSize="rightIconSize"
|
||||
:closeBtn="closeBtn"
|
||||
:autoplay="autoplay"
|
||||
:radius="radius"
|
||||
:padding="padding"
|
||||
:speed="speed"
|
||||
@click="click"
|
||||
@close="close"
|
||||
@clickLeft="clickLeftIcon"
|
||||
@clickRight="clickRightIcon"
|
||||
></tn-row-notice>
|
||||
</block>
|
||||
<block v-if="mode === 'vertical' || (mode === 'horizontal' && !circular)">
|
||||
<tn-column-notice
|
||||
:backgroundColor="backgroundColor"
|
||||
:fontColor="fontColor"
|
||||
:fontSize="fontSize"
|
||||
:fontUnit="fontUnit"
|
||||
:list="list"
|
||||
:show="show"
|
||||
:mode="mode"
|
||||
:playStatus="playStatus"
|
||||
:leftIcon="leftIcon"
|
||||
:leftIconName="leftIconName"
|
||||
:leftIconSize="leftIconSize"
|
||||
:rightIcon="rightIcon"
|
||||
:rightIconName="rightIconName"
|
||||
:rightIconSize="rightIconSize"
|
||||
:closeBtn="closeBtn"
|
||||
:autoplay="autoplay"
|
||||
:radius="radius"
|
||||
:padding="padding"
|
||||
:duration="duration"
|
||||
@click="click"
|
||||
@close="close"
|
||||
@clickLeft="clickLeftIcon"
|
||||
@clickRight="clickRightIcon"
|
||||
@end="end"
|
||||
></tn-column-notice>
|
||||
</block>
|
||||
<template>
|
||||
<view
|
||||
v-if="showNotice"
|
||||
class="tn-notice-bar-class tn-notice-bar"
|
||||
:style="{
|
||||
borderRadius: radius + 'rpx'
|
||||
}"
|
||||
>
|
||||
<block v-if="mode === 'horizontal' && circular">
|
||||
<tn-row-notice
|
||||
:backgroundColor="backgroundColor"
|
||||
:fontColor="fontColor"
|
||||
:fontSize="fontSize"
|
||||
:fontUnit="fontUnit"
|
||||
:list="list"
|
||||
:show="show"
|
||||
:playStatus="playStatus"
|
||||
:leftIcon="leftIcon"
|
||||
:leftIconName="leftIconName"
|
||||
:leftIconSize="leftIconSize"
|
||||
:rightIcon="rightIcon"
|
||||
:rightIconName="rightIconName"
|
||||
:rightIconSize="rightIconSize"
|
||||
:closeBtn="closeBtn"
|
||||
:autoplay="autoplay"
|
||||
:radius="radius"
|
||||
:padding="padding"
|
||||
:speed="speed"
|
||||
@click="click"
|
||||
@close="close"
|
||||
@clickLeft="clickLeftIcon"
|
||||
@clickRight="clickRightIcon"
|
||||
></tn-row-notice>
|
||||
</block>
|
||||
<block v-if="mode === 'vertical' || (mode === 'horizontal' && !circular)">
|
||||
<tn-column-notice
|
||||
:backgroundColor="backgroundColor"
|
||||
:fontColor="fontColor"
|
||||
:fontSize="fontSize"
|
||||
:fontUnit="fontUnit"
|
||||
:list="list"
|
||||
:show="show"
|
||||
:mode="mode"
|
||||
:playStatus="playStatus"
|
||||
:leftIcon="leftIcon"
|
||||
:leftIconName="leftIconName"
|
||||
:leftIconSize="leftIconSize"
|
||||
:rightIcon="rightIcon"
|
||||
:rightIconName="rightIconName"
|
||||
:rightIconSize="rightIconSize"
|
||||
:closeBtn="closeBtn"
|
||||
:autoplay="autoplay"
|
||||
:radius="radius"
|
||||
:padding="padding"
|
||||
:duration="duration"
|
||||
@click="click"
|
||||
@close="close"
|
||||
@clickLeft="clickLeftIcon"
|
||||
@clickRight="clickRightIcon"
|
||||
@end="end"
|
||||
></tn-column-notice>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-notice-bar',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 滚动方向
|
||||
// horizontal -> 水平滚动 vertical -> 垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动周期
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// 水平滚动时的速度,即每秒滚动多少rpx
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
// 水平滚动的时候是否采用衔接的模式
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 没有数据时是否显示通知
|
||||
autoHidden: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 当设置了show为false,或者autoHidden为true且list为空时,不显示通知
|
||||
showNotice() {
|
||||
if (this.show === false || (this.autoHidden && this.list.length === 0)) return false
|
||||
else return true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击了通知栏
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
},
|
||||
// 一个周期滚动结束
|
||||
end() {
|
||||
this.$emit('end')
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-notice-bar',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 滚动方向
|
||||
// horizontal -> 水平滚动 vertical -> 垂直滚动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动周期
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 2000
|
||||
},
|
||||
// 水平滚动时的速度,即每秒滚动多少rpx
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
},
|
||||
// 水平滚动的时候是否采用衔接的模式
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 没有数据时是否显示通知
|
||||
autoHidden: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 当设置了show为false,或者autoHidden为true且list为空时,不显示通知
|
||||
showNotice() {
|
||||
if (this.show === false || (this.autoHidden && this.list.length === 0)) return false
|
||||
else return true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击了通知栏
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
},
|
||||
// 一个周期滚动结束
|
||||
end() {
|
||||
this.$emit('end')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-notice-bar {
|
||||
overflow: hidden;
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-notice-bar {
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,401 +1,401 @@
|
||||
<template>
|
||||
<view class="tn-number-box-class tn-number-box">
|
||||
<!-- 减 -->
|
||||
<view
|
||||
class="tn-number-box__btn__minus"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
{'tn-number-box__btn--disabled': disabled || inputValue <= min}
|
||||
]"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
height: $t.string.getLengthUnitValue(inputHeight),
|
||||
color: fontColorStyle,
|
||||
fontSize: fontSizeStyle
|
||||
}"
|
||||
@touchstart.stop.prevent="touchStart('minus')"
|
||||
@touchend.stop.prevent="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-reduce"></view>
|
||||
</view>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<input
|
||||
v-model="inputValue"
|
||||
:disabled="disabledInput || disabled"
|
||||
:cursor-spacing="getCursorSpacing"
|
||||
class="tn-number-box__input"
|
||||
:class="[
|
||||
fontColorClass,
|
||||
{'tn-number-box__input--disabled': disabledInput || disabled}
|
||||
]"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(inputWidth),
|
||||
height: $t.string.getLengthUnitValue(inputHeight),
|
||||
color: fontColorStyle,
|
||||
fontSize: fontSizeStyle,
|
||||
backgroundColor: backgroundColorStyle
|
||||
}"
|
||||
@blur="blurInput"
|
||||
@focus="focusInput"
|
||||
/>
|
||||
|
||||
<!-- 加 -->
|
||||
<view
|
||||
class="tn-number-box__btn__plus"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
{'tn-number-box__btn--disabled': disabled || inputValue >= max}
|
||||
]"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
height: $t.string.getLengthUnitValue(inputHeight),
|
||||
color: fontColorStyle,
|
||||
fontSize: fontSizeStyle
|
||||
}"
|
||||
@touchstart.stop.prevent="touchStart('plus')"
|
||||
@touchend.stop.prevent="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-add"></view>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-number-box-class tn-number-box">
|
||||
<!-- 减 -->
|
||||
<view
|
||||
class="tn-number-box__btn__minus"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
{'tn-number-box__btn--disabled': disabled || inputValue <= min}
|
||||
]"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
height: $t.string.getLengthUnitValue(inputHeight),
|
||||
color: fontColorStyle,
|
||||
fontSize: fontSizeStyle
|
||||
}"
|
||||
@touchstart.stop.prevent="touchStart('minus')"
|
||||
@touchend.stop.prevent="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-reduce"></view>
|
||||
</view>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<input
|
||||
v-model="inputValue"
|
||||
:disabled="disabledInput || disabled"
|
||||
:cursor-spacing="getCursorSpacing"
|
||||
class="tn-number-box__input"
|
||||
:class="[
|
||||
fontColorClass,
|
||||
{'tn-number-box__input--disabled': disabledInput || disabled}
|
||||
]"
|
||||
:style="{
|
||||
width: $t.string.getLengthUnitValue(inputWidth),
|
||||
height: $t.string.getLengthUnitValue(inputHeight),
|
||||
color: fontColorStyle,
|
||||
fontSize: fontSizeStyle,
|
||||
backgroundColor: backgroundColorStyle
|
||||
}"
|
||||
@blur="blurInput"
|
||||
@focus="focusInput"
|
||||
/>
|
||||
|
||||
<!-- 加 -->
|
||||
<view
|
||||
class="tn-number-box__btn__plus"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass,
|
||||
{'tn-number-box__btn--disabled': disabled || inputValue >= max}
|
||||
]"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
height: $t.string.getLengthUnitValue(inputHeight),
|
||||
color: fontColorStyle,
|
||||
fontSize: fontSizeStyle
|
||||
}"
|
||||
@touchstart.stop.prevent="touchStart('plus')"
|
||||
@touchend.stop.prevent="clearTimer"
|
||||
>
|
||||
<view class="tn-icon-add"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColor from '../../libs/mixin/components_color.js'
|
||||
|
||||
export default {
|
||||
mixins: [componentsColor],
|
||||
name: 'tn-number-box',
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 索引
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 99999
|
||||
},
|
||||
// 步进值
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用输入
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框的宽度
|
||||
inputWidth: {
|
||||
type: Number,
|
||||
default: 88
|
||||
},
|
||||
// 输入框的高度
|
||||
inputHeight: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 输入框和键盘之间的距离
|
||||
cursorSpacing: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// 是否开启长按进行连续递增减
|
||||
longPress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 长按触发间隔
|
||||
longPressTime: {
|
||||
type: Number,
|
||||
default: 250
|
||||
},
|
||||
// 是否只能输入正整数
|
||||
positiveInteger: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getCursorSpacing() {
|
||||
return Number(uni.upx2px(this.cursorSpacing))
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 输入框的值
|
||||
inputValue: 1,
|
||||
// 长按定时器
|
||||
longPressTimer: null,
|
||||
// 标记值的改变是来自外部还是内部
|
||||
changeFromInner: false,
|
||||
// 内部定时器
|
||||
innerChangeTimer: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
// 只有value的改变是来自外部的时候,才去同步inputValue的值,否则会造成循环错误
|
||||
if (!this.changeFromInner) {
|
||||
this.updateInputValue()
|
||||
// 因为inputValue变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true,
|
||||
// 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
|
||||
// 将changeFromInner设置为false
|
||||
this.$nextTick(() => {
|
||||
this.changeFromInner = false
|
||||
})
|
||||
}
|
||||
},
|
||||
inputValue(newVal, oldVal) {
|
||||
// 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
|
||||
if (newVal === '') return
|
||||
let value = 0
|
||||
// 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
|
||||
let isNumber = this.$t.test.number(newVal)
|
||||
if (isNumber && newVal >= this.min && newVal <= this.max) value = newVal
|
||||
else value = oldVal
|
||||
|
||||
// 判断是否只能输入大于等于0的整数
|
||||
if (this.positiveInteger) {
|
||||
// 小于0或者带有小数点
|
||||
if (newVal < 0 || String(newVal).indexOf('.') !== -1) {
|
||||
value = Math.floor(newVal)
|
||||
// 双向绑定input的值,必须要使用$nextTick修改显示的值
|
||||
this.$nextTick(() => {
|
||||
this.inputValue = value
|
||||
})
|
||||
}
|
||||
}
|
||||
this.handleChange(value, 'change')
|
||||
},
|
||||
min() {
|
||||
this.updateInputValue()
|
||||
},
|
||||
max() {
|
||||
this.updateInputValue()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateInputValue()
|
||||
},
|
||||
methods: {
|
||||
// 开始点击按钮
|
||||
touchStart(func) {
|
||||
// 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
|
||||
this[func]()
|
||||
// 如果没有开启长按功能,直接返回
|
||||
if (!this.longPress) return
|
||||
// 清空长按定时器,防止重复注册
|
||||
if (this.longPressTimer) {
|
||||
clearInterval(this.longPressTimer)
|
||||
this.longPressTimer = null
|
||||
}
|
||||
this.longPressTimer = setInterval(() => {
|
||||
// 执行加减操作
|
||||
this[func]()
|
||||
}, this.longPressTime)
|
||||
},
|
||||
// 清除定时器
|
||||
clearTimer() {
|
||||
this.$nextTick(() => {
|
||||
if (this.longPressTimer) {
|
||||
clearInterval(this.longPressTimer)
|
||||
this.longPressTimer = null
|
||||
}
|
||||
})
|
||||
},
|
||||
// 减
|
||||
minus() {
|
||||
this.computeValue('minus')
|
||||
},
|
||||
// 加
|
||||
plus() {
|
||||
this.computeValue('plus')
|
||||
},
|
||||
// 处理小数相加减出现溢出问题
|
||||
calcPlus(num1, num2) {
|
||||
let baseNum = 0, baseNum1 = 0, baseNum2 = 0
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum1 = 0
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum2 = 0
|
||||
}
|
||||
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
|
||||
// 精度
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2
|
||||
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision)
|
||||
},
|
||||
calcMinus(num1, num2) {
|
||||
let baseNum = 0, baseNum1 = 0, baseNum2 = 0
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum1 = 0
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum2 = 0
|
||||
}
|
||||
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
|
||||
// 精度
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2
|
||||
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision)
|
||||
},
|
||||
// 处理操作后的值
|
||||
computeValue(type) {
|
||||
uni.hideKeyboard()
|
||||
if (this.disabled) return
|
||||
let value = 0
|
||||
|
||||
if (type === 'minus') {
|
||||
// 减
|
||||
value = this.calcMinus(this.inputValue, this.step)
|
||||
} else if (type === 'plus') {
|
||||
// 加
|
||||
value = this.calcPlus(this.inputValue, this.step)
|
||||
}
|
||||
// 判断是否比最小值小和操作最大值
|
||||
if (value < this.min || value > this.max) return
|
||||
|
||||
this.inputValue = value
|
||||
this.handleChange(value, type)
|
||||
},
|
||||
// 处理用户手动输入
|
||||
blurInput(event) {
|
||||
let val = 0,
|
||||
value = event.detail.value
|
||||
// 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
|
||||
// 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
|
||||
if (!/(^\d+$)/.test(value) || value[0] == 0) {
|
||||
val = this.min
|
||||
} else {
|
||||
val = +value
|
||||
}
|
||||
|
||||
if (val > this.max) {
|
||||
val = this.max
|
||||
} else if (val < this.min) {
|
||||
val = this.min
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.inputValue = val
|
||||
})
|
||||
this.handleChange(val, 'blur')
|
||||
},
|
||||
// 获取焦点
|
||||
focusInput() {
|
||||
this.$emit('focus')
|
||||
},
|
||||
// 初始化inputValue
|
||||
updateInputValue() {
|
||||
let value = this.value
|
||||
if (value <= this.min) {
|
||||
value = this.min
|
||||
} else if (value >= this.max) {
|
||||
value = this.max
|
||||
}
|
||||
|
||||
this.inputValue = Number(value)
|
||||
},
|
||||
// 处理值改变状态
|
||||
handleChange(value, type) {
|
||||
if (this.disabled) return
|
||||
// 清除定时器,防止混乱
|
||||
if (this.innerChangeTimer) {
|
||||
clearTimeout(this.innerChangeTimer)
|
||||
this.innerChangeTimer = null
|
||||
}
|
||||
|
||||
// 内部修改值
|
||||
this.changeFromInner = true
|
||||
// 一定时间内,清除changeFromInner标记,否则内部值改变后
|
||||
// 外部通过程序修改value值,将会无效
|
||||
this.innerChangeTimer = setTimeout(() => {
|
||||
this.changeFromInner = false
|
||||
}, 150)
|
||||
this.$emit('input', Number(value))
|
||||
this.$emit(type, {
|
||||
value: Number(value),
|
||||
index: this.index
|
||||
})
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColor from '../../libs/mixin/components_color.js'
|
||||
|
||||
export default {
|
||||
mixins: [componentsColor],
|
||||
name: 'tn-number-box',
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 索引
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 99999
|
||||
},
|
||||
// 步进值
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否禁用输入
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框的宽度
|
||||
inputWidth: {
|
||||
type: Number,
|
||||
default: 88
|
||||
},
|
||||
// 输入框的高度
|
||||
inputHeight: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 输入框和键盘之间的距离
|
||||
cursorSpacing: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// 是否开启长按进行连续递增减
|
||||
longPress: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 长按触发间隔
|
||||
longPressTime: {
|
||||
type: Number,
|
||||
default: 250
|
||||
},
|
||||
// 是否只能输入正整数
|
||||
positiveInteger: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getCursorSpacing() {
|
||||
return Number(uni.upx2px(this.cursorSpacing))
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 输入框的值
|
||||
inputValue: 1,
|
||||
// 长按定时器
|
||||
longPressTimer: null,
|
||||
// 标记值的改变是来自外部还是内部
|
||||
changeFromInner: false,
|
||||
// 内部定时器
|
||||
innerChangeTimer: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
// 只有value的改变是来自外部的时候,才去同步inputValue的值,否则会造成循环错误
|
||||
if (!this.changeFromInner) {
|
||||
this.updateInputValue()
|
||||
// 因为inputValue变化后,会触发this.handleChange(),在其中changeFromInner会再次被设置为true,
|
||||
// 造成外面修改值,也导致被认为是内部修改的混乱,这里进行this.$nextTick延时,保证在运行周期的最后处
|
||||
// 将changeFromInner设置为false
|
||||
this.$nextTick(() => {
|
||||
this.changeFromInner = false
|
||||
})
|
||||
}
|
||||
},
|
||||
inputValue(newVal, oldVal) {
|
||||
// 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
|
||||
if (newVal === '') return
|
||||
let value = 0
|
||||
// 首先判断是否数值,并且在min和max之间,如果不是,使用原来值
|
||||
let isNumber = this.$t.test.number(newVal)
|
||||
if (isNumber && newVal >= this.min && newVal <= this.max) value = newVal
|
||||
else value = oldVal
|
||||
|
||||
// 判断是否只能输入大于等于0的整数
|
||||
if (this.positiveInteger) {
|
||||
// 小于0或者带有小数点
|
||||
if (newVal < 0 || String(newVal).indexOf('.') !== -1) {
|
||||
value = Math.floor(newVal)
|
||||
// 双向绑定input的值,必须要使用$nextTick修改显示的值
|
||||
this.$nextTick(() => {
|
||||
this.inputValue = value
|
||||
})
|
||||
}
|
||||
}
|
||||
this.handleChange(value, 'change')
|
||||
},
|
||||
min() {
|
||||
this.updateInputValue()
|
||||
},
|
||||
max() {
|
||||
this.updateInputValue()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateInputValue()
|
||||
},
|
||||
methods: {
|
||||
// 开始点击按钮
|
||||
touchStart(func) {
|
||||
// 先执行一遍方法,否则会造成松开手时,就执行了clearTimer,导致无法实现功能
|
||||
this[func]()
|
||||
// 如果没有开启长按功能,直接返回
|
||||
if (!this.longPress) return
|
||||
// 清空长按定时器,防止重复注册
|
||||
if (this.longPressTimer) {
|
||||
clearInterval(this.longPressTimer)
|
||||
this.longPressTimer = null
|
||||
}
|
||||
this.longPressTimer = setInterval(() => {
|
||||
// 执行加减操作
|
||||
this[func]()
|
||||
}, this.longPressTime)
|
||||
},
|
||||
// 清除定时器
|
||||
clearTimer() {
|
||||
this.$nextTick(() => {
|
||||
if (this.longPressTimer) {
|
||||
clearInterval(this.longPressTimer)
|
||||
this.longPressTimer = null
|
||||
}
|
||||
})
|
||||
},
|
||||
// 减
|
||||
minus() {
|
||||
this.computeValue('minus')
|
||||
},
|
||||
// 加
|
||||
plus() {
|
||||
this.computeValue('plus')
|
||||
},
|
||||
// 处理小数相加减出现溢出问题
|
||||
calcPlus(num1, num2) {
|
||||
let baseNum = 0, baseNum1 = 0, baseNum2 = 0
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum1 = 0
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum2 = 0
|
||||
}
|
||||
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
|
||||
// 精度
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2
|
||||
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision)
|
||||
},
|
||||
calcMinus(num1, num2) {
|
||||
let baseNum = 0, baseNum1 = 0, baseNum2 = 0
|
||||
try {
|
||||
baseNum1 = num1.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum1 = 0
|
||||
}
|
||||
try {
|
||||
baseNum2 = num2.toString().split('.')[1].length
|
||||
} catch(e) {
|
||||
baseNum2 = 0
|
||||
}
|
||||
|
||||
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2))
|
||||
// 精度
|
||||
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2
|
||||
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision)
|
||||
},
|
||||
// 处理操作后的值
|
||||
computeValue(type) {
|
||||
uni.hideKeyboard()
|
||||
if (this.disabled) return
|
||||
let value = 0
|
||||
|
||||
if (type === 'minus') {
|
||||
// 减
|
||||
value = this.calcMinus(this.inputValue, this.step)
|
||||
} else if (type === 'plus') {
|
||||
// 加
|
||||
value = this.calcPlus(this.inputValue, this.step)
|
||||
}
|
||||
// 判断是否比最小值小和操作最大值
|
||||
if (value < this.min || value > this.max) return
|
||||
|
||||
this.inputValue = value
|
||||
this.handleChange(value, type)
|
||||
},
|
||||
// 处理用户手动输入
|
||||
blurInput(event) {
|
||||
let val = 0,
|
||||
value = event.detail.value
|
||||
// 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
|
||||
// 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
|
||||
if (!/(^\d+$)/.test(value) || value[0] == 0) {
|
||||
val = this.min
|
||||
} else {
|
||||
val = +value
|
||||
}
|
||||
|
||||
if (val > this.max) {
|
||||
val = this.max
|
||||
} else if (val < this.min) {
|
||||
val = this.min
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.inputValue = val
|
||||
})
|
||||
this.handleChange(val, 'blur')
|
||||
},
|
||||
// 获取焦点
|
||||
focusInput() {
|
||||
this.$emit('focus')
|
||||
},
|
||||
// 初始化inputValue
|
||||
updateInputValue() {
|
||||
let value = this.value
|
||||
if (value <= this.min) {
|
||||
value = this.min
|
||||
} else if (value >= this.max) {
|
||||
value = this.max
|
||||
}
|
||||
|
||||
this.inputValue = Number(value)
|
||||
},
|
||||
// 处理值改变状态
|
||||
handleChange(value, type) {
|
||||
if (this.disabled) return
|
||||
// 清除定时器,防止混乱
|
||||
if (this.innerChangeTimer) {
|
||||
clearTimeout(this.innerChangeTimer)
|
||||
this.innerChangeTimer = null
|
||||
}
|
||||
|
||||
// 内部修改值
|
||||
this.changeFromInner = true
|
||||
// 一定时间内,清除changeFromInner标记,否则内部值改变后
|
||||
// 外部通过程序修改value值,将会无效
|
||||
this.innerChangeTimer = setTimeout(() => {
|
||||
this.changeFromInner = false
|
||||
}, 150)
|
||||
this.$emit('input', Number(value))
|
||||
this.$emit(type, {
|
||||
value: Number(value),
|
||||
index: this.index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-number-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&__btn {
|
||||
&__plus,&__minus {
|
||||
width: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $tn-font-holder-color;
|
||||
}
|
||||
|
||||
&__plus {
|
||||
border-radius: 0 8rpx 8rpx 0;
|
||||
}
|
||||
|
||||
&__minus {
|
||||
border-radius: 8rpx 0 0 8rpx;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color !important;
|
||||
background: $tn-font-holder-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0 4rpx;
|
||||
margin: 0 6rpx;
|
||||
background-color: $tn-font-holder-color;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color !important;
|
||||
background: $tn-font-holder-color !important;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-number-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&__btn {
|
||||
&__plus,&__minus {
|
||||
width: 60rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: $tn-font-holder-color;
|
||||
}
|
||||
|
||||
&__plus {
|
||||
border-radius: 0 8rpx 8rpx 0;
|
||||
}
|
||||
|
||||
&__minus {
|
||||
border-radius: 8rpx 0 0 8rpx;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color !important;
|
||||
background: $tn-font-holder-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0 4rpx;
|
||||
margin: 0 6rpx;
|
||||
background-color: $tn-font-holder-color;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color !important;
|
||||
background: $tn-font-holder-color !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,182 +1,182 @@
|
||||
<template>
|
||||
<view class="tn-number-keyboard-class tn-number-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="tn-number-keyboard__grids">
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
class="tn-number-keyboard__grids__item"
|
||||
:class="{
|
||||
'tn-bg-gray--light': showGaryBg(index),
|
||||
'tn-border-solid-top': index <= 2,
|
||||
'tn-border-solid-bottom': index < 9,
|
||||
'tn-border-solid-right': (index + 1) % 3 != 0
|
||||
}"
|
||||
:hover-class="hoverClass(index)"
|
||||
:hover-stay-time="150"
|
||||
@tap="keyboardClick(item)"
|
||||
>
|
||||
<view class="tn-number-keyboard__grids__btn">{{ item }}</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-number-keyboard__grids__item tn-bg-gray--light"
|
||||
hover-class="tn-hover"
|
||||
:hover-stay-time="150"
|
||||
@touchstart.stop="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
>
|
||||
<view class="tn-number-keyboard__grids__btn tn-number-keyboard__back">
|
||||
<view class="tn-icon-left-arrow tn-number-keyboard__back__icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-number-keyboard',
|
||||
props: {
|
||||
// 键盘类型
|
||||
// number -> 数字键盘 card -> 身份证键盘
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 是否显示键盘的'.'符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否为乱序键盘
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 键盘显示的内容
|
||||
dataList() {
|
||||
let tmp = []
|
||||
if (!this.dotEnabled && this.mode === 'number') {
|
||||
if (!this.randomEnabled) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, '', 0]
|
||||
} else {
|
||||
let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
data.splice(-1, 0, '')
|
||||
return data
|
||||
}
|
||||
} else if (this.dotEnabled && this.mode === 'number') {
|
||||
if (!this.randomEnabled) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]
|
||||
} else {
|
||||
let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
data.splice(-1, 0, this.dot)
|
||||
return data
|
||||
}
|
||||
} else if (this.mode === 'card') {
|
||||
if (!this.randomEnabled) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]
|
||||
} else {
|
||||
let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
data.splice(-1, 0, this.cardX)
|
||||
return data
|
||||
}
|
||||
}
|
||||
},
|
||||
// 按键的样式
|
||||
keyStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
if (this.mode === 'number' && !this.dotEnabled && index === 9) style.flex = '0 0 66.6666666666%'
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 是否让按键显示灰色,只在数字键盘和非乱序且在点击时
|
||||
showGaryBg() {
|
||||
return index => {
|
||||
if (!this.randomEnabled && index === 9 && (this.mode !== 'number' || (this.mode === 'number' && this.dotEnabled))) return true
|
||||
else return false
|
||||
}
|
||||
},
|
||||
// 手指停留的class
|
||||
hoverClass() {
|
||||
return index => {
|
||||
if (this.mode === 'number' && !this.dotEnabled && index === 9) return ''
|
||||
if (!this.randomEnabled && index === 9 && (this.mode === 'number' && this.dotEnabled || this.mode === 'card')) return 'tn-hover'
|
||||
else return 'tn-number-keyboard--hover'
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 退格键内容
|
||||
backspace: 'backspace',
|
||||
// 点内容
|
||||
dot: '.',
|
||||
// 长按多次删除事件监听
|
||||
longPressDeleteTimer: null,
|
||||
// 身份证的X符号
|
||||
cardX: 'X'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击退格键
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
this.clearTimer()
|
||||
this.longPressDeleteTimer = setInterval(() => {
|
||||
this.$emit('backspace')
|
||||
}, 250)
|
||||
},
|
||||
// 获取键盘显示的内容
|
||||
keyboardClick(value) {
|
||||
if (this.mode === 'number' && !this.dotEnabled && value === '') return
|
||||
// 允许键盘显示点模式和触发非点按键时,将内容转换为数字类型
|
||||
if (this.dotEnabled && value != this.dot && value != this.cardX) value = Number(value)
|
||||
this.$emit('change', value)
|
||||
},
|
||||
// 清除定时器
|
||||
clearTimer() {
|
||||
if (this.longPressDeleteTimer) {
|
||||
clearInterval(this.longPressDeleteTimer)
|
||||
this.longPressDeleteTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-number-keyboard {
|
||||
position: relative;
|
||||
|
||||
&__grids {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 0 0 33.3333333333%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 50rpx;
|
||||
color: $tn-font-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__back {
|
||||
font-size: 38rpx;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: $tn-font-holder-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-number-keyboard-class tn-number-keyboard" @touchmove.stop.prevent="() => {}">
|
||||
<view class="tn-number-keyboard__grids">
|
||||
<view
|
||||
v-for="(item, index) in dataList"
|
||||
:key="index"
|
||||
class="tn-number-keyboard__grids__item"
|
||||
:class="{
|
||||
'tn-bg-gray--light': showGaryBg(index),
|
||||
'tn-border-solid-top': index <= 2,
|
||||
'tn-border-solid-bottom': index < 9,
|
||||
'tn-border-solid-right': (index + 1) % 3 != 0
|
||||
}"
|
||||
:hover-class="hoverClass(index)"
|
||||
:hover-stay-time="150"
|
||||
@tap="keyboardClick(item)"
|
||||
>
|
||||
<view class="tn-number-keyboard__grids__btn">{{ item }}</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-number-keyboard__grids__item tn-bg-gray--light"
|
||||
hover-class="tn-hover"
|
||||
:hover-stay-time="150"
|
||||
@touchstart.stop="backspaceClick"
|
||||
@touchend="clearTimer"
|
||||
>
|
||||
<view class="tn-number-keyboard__grids__btn tn-number-keyboard__back">
|
||||
<view class="tn-icon-left-arrow tn-number-keyboard__back__icon"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-number-keyboard',
|
||||
props: {
|
||||
// 键盘类型
|
||||
// number -> 数字键盘 card -> 身份证键盘
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'number'
|
||||
},
|
||||
// 是否显示键盘的'.'符号
|
||||
dotEnabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否为乱序键盘
|
||||
randomEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 键盘显示的内容
|
||||
dataList() {
|
||||
let tmp = []
|
||||
if (!this.dotEnabled && this.mode === 'number') {
|
||||
if (!this.randomEnabled) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, '', 0]
|
||||
} else {
|
||||
let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
data.splice(-1, 0, '')
|
||||
return data
|
||||
}
|
||||
} else if (this.dotEnabled && this.mode === 'number') {
|
||||
if (!this.randomEnabled) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]
|
||||
} else {
|
||||
let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
data.splice(-1, 0, this.dot)
|
||||
return data
|
||||
}
|
||||
} else if (this.mode === 'card') {
|
||||
if (!this.randomEnabled) {
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]
|
||||
} else {
|
||||
let data = this.$t.array.random([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
|
||||
data.splice(-1, 0, this.cardX)
|
||||
return data
|
||||
}
|
||||
}
|
||||
},
|
||||
// 按键的样式
|
||||
keyStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
if (this.mode === 'number' && !this.dotEnabled && index === 9) style.flex = '0 0 66.6666666666%'
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 是否让按键显示灰色,只在数字键盘和非乱序且在点击时
|
||||
showGaryBg() {
|
||||
return index => {
|
||||
if (!this.randomEnabled && index === 9 && (this.mode !== 'number' || (this.mode === 'number' && this.dotEnabled))) return true
|
||||
else return false
|
||||
}
|
||||
},
|
||||
// 手指停留的class
|
||||
hoverClass() {
|
||||
return index => {
|
||||
if (this.mode === 'number' && !this.dotEnabled && index === 9) return ''
|
||||
if (!this.randomEnabled && index === 9 && (this.mode === 'number' && this.dotEnabled || this.mode === 'card')) return 'tn-hover'
|
||||
else return 'tn-number-keyboard--hover'
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 退格键内容
|
||||
backspace: 'backspace',
|
||||
// 点内容
|
||||
dot: '.',
|
||||
// 长按多次删除事件监听
|
||||
longPressDeleteTimer: null,
|
||||
// 身份证的X符号
|
||||
cardX: 'X'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击退格键
|
||||
backspaceClick() {
|
||||
this.$emit('backspace')
|
||||
this.clearTimer()
|
||||
this.longPressDeleteTimer = setInterval(() => {
|
||||
this.$emit('backspace')
|
||||
}, 250)
|
||||
},
|
||||
// 获取键盘显示的内容
|
||||
keyboardClick(value) {
|
||||
if (this.mode === 'number' && !this.dotEnabled && value === '') return
|
||||
// 允许键盘显示点模式和触发非点按键时,将内容转换为数字类型
|
||||
if (this.dotEnabled && value != this.dot && value != this.cardX) value = Number(value)
|
||||
this.$emit('change', value)
|
||||
},
|
||||
// 清除定时器
|
||||
clearTimer() {
|
||||
if (this.longPressDeleteTimer) {
|
||||
clearInterval(this.longPressDeleteTimer)
|
||||
this.longPressDeleteTimer = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-number-keyboard {
|
||||
position: relative;
|
||||
|
||||
&__grids {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: 0 0 33.3333333333%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 50rpx;
|
||||
color: $tn-font-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__back {
|
||||
font-size: 38rpx;
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background-color: $tn-font-holder-color;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,491 +1,491 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="visibleSync"
|
||||
class="tn-popup-class tn-popup"
|
||||
:style="[customStyle, popupStyle, { zIndex: elZIndex - 1}]"
|
||||
hover-stop-propagation
|
||||
>
|
||||
<!-- mask -->
|
||||
<view
|
||||
class="tn-popup__mask"
|
||||
:class="[{'tn-popup__mask--show': showPopup && mask}]"
|
||||
:style="{zIndex: elZIndex - 2}"
|
||||
@tap="maskClick"
|
||||
@touchmove.stop.prevent = "() => {}"
|
||||
hover-stop-propagation
|
||||
></view>
|
||||
<!-- 弹框内容 -->
|
||||
<view
|
||||
class="tn-popup__content"
|
||||
:class="[
|
||||
mode !== 'center' ? backgroundColorClass : '',
|
||||
safeAreaInsetBottom ? 'tn-safe-area-inset-bottom' : '',
|
||||
'tn-popup--' + mode,
|
||||
showPopup ? 'tn-popup__content--visible' : '',
|
||||
zoom && mode === 'center' ? 'tn-popup__content__center--animation-zoom' : ''
|
||||
]"
|
||||
:style="[contentStyle]"
|
||||
@tap="modeCenterClose"
|
||||
@touchmove.stop.prevent
|
||||
@tap.stop.prevent
|
||||
>
|
||||
<!-- 居中时候的内容 -->
|
||||
<view
|
||||
v-if="mode === 'center'"
|
||||
class="tn-popup__content__center_box"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[centerStyle]"
|
||||
@touchmove.stop.prevent
|
||||
@tap.stop.prevent
|
||||
>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-popup__close"
|
||||
:class="[`tn-icon-${closeBtnIcon}`, `tn-popup__close--${closeBtnPosition}`]"
|
||||
:style="[closeBtnStyle, {zIndex: elZIndex}]"
|
||||
@tap="close"
|
||||
></view>
|
||||
<scroll-view class="tn-popup__content__scroll-view">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 除居中外的其他情况 -->
|
||||
<scroll-view v-else class="tn-popup__content__scroll-view">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
v-if="mode !== 'center' && closeBtn"
|
||||
class="tn-popup__close"
|
||||
:class="[`tn-popup__close--${closeBtnPosition}`]"
|
||||
:style="{zIndex: elZIndex}"
|
||||
@tap="close"
|
||||
>
|
||||
<view :class="[`tn-icon-${closeBtnIcon}`]" :style="[closeBtnStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
v-if="visibleSync"
|
||||
class="tn-popup-class tn-popup"
|
||||
:style="[customStyle, popupStyle, { zIndex: elZIndex - 1}]"
|
||||
hover-stop-propagation
|
||||
>
|
||||
<!-- mask -->
|
||||
<view
|
||||
class="tn-popup__mask"
|
||||
:class="[{'tn-popup__mask--show': showPopup && mask}]"
|
||||
:style="{zIndex: elZIndex - 2}"
|
||||
@tap="maskClick"
|
||||
@touchmove.stop.prevent = "() => {}"
|
||||
hover-stop-propagation
|
||||
></view>
|
||||
<!-- 弹框内容 -->
|
||||
<view
|
||||
class="tn-popup__content"
|
||||
:class="[
|
||||
mode !== 'center' ? backgroundColorClass : '',
|
||||
safeAreaInsetBottom ? 'tn-safe-area-inset-bottom' : '',
|
||||
'tn-popup--' + mode,
|
||||
showPopup ? 'tn-popup__content--visible' : '',
|
||||
zoom && mode === 'center' ? 'tn-popup__content__center--animation-zoom' : ''
|
||||
]"
|
||||
:style="[contentStyle]"
|
||||
@tap="modeCenterClose"
|
||||
@touchmove.stop.prevent
|
||||
@tap.stop.prevent
|
||||
>
|
||||
<!-- 居中时候的内容 -->
|
||||
<view
|
||||
v-if="mode === 'center'"
|
||||
class="tn-popup__content__center_box"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[centerStyle]"
|
||||
@touchmove.stop.prevent
|
||||
@tap.stop.prevent
|
||||
>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-popup__close"
|
||||
:class="[`tn-icon-${closeBtnIcon}`, `tn-popup__close--${closeBtnPosition}`]"
|
||||
:style="[closeBtnStyle, {zIndex: elZIndex}]"
|
||||
@tap="close"
|
||||
></view>
|
||||
<scroll-view class="tn-popup__content__scroll-view">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 除居中外的其他情况 -->
|
||||
<scroll-view v-else class="tn-popup__content__scroll-view">
|
||||
<slot></slot>
|
||||
</scroll-view>
|
||||
<!-- 关闭按钮 -->
|
||||
<view
|
||||
v-if="mode !== 'center' && closeBtn"
|
||||
class="tn-popup__close"
|
||||
:class="[`tn-popup__close--${closeBtnPosition}`]"
|
||||
:style="{zIndex: elZIndex}"
|
||||
@tap="close"
|
||||
>
|
||||
<view :class="[`tn-icon-${closeBtnIcon}`]" :style="[closeBtnStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-popup',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出方向
|
||||
// left/right/top/bottom/center
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 是否显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 抽屉的宽度(mode=left/right),高度(mode=top/bottom)
|
||||
length: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 宽度,只对左,右,中部弹出时起作用,单位rpx,或者"auto"
|
||||
// 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
|
||||
width: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 高度,只对上,下,中部弹出时起作用,单位rpx,或者"auto"
|
||||
// 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否开启动画,只在mode=center有效
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 用户自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 显示圆角的大小
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 关闭按钮的图标
|
||||
closeBtnIcon: {
|
||||
type: String,
|
||||
default: 'close'
|
||||
},
|
||||
// 关闭按钮显示的位置
|
||||
// top-left/top-right/bottom-left/bottom-right
|
||||
closeBtnPosition: {
|
||||
type: String,
|
||||
default: 'top-right'
|
||||
},
|
||||
// 关闭按钮图标颜色
|
||||
closeIconColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 关闭按钮图标的大小
|
||||
closeIconSize: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 给一个负的margin-top,往上偏移,避免和键盘重合的情况,仅在mode=center时有效
|
||||
negativeTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// marginTop,在mode = top,left,right时生效,避免用户使用了自定义导航栏,组件把导航栏遮挡了
|
||||
marginTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
|
||||
// 对v-model双向绑定多层调用造成报错不能修改props值的问题
|
||||
popup: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 处理使用了自定义导航栏时被遮挡的问题
|
||||
popupStyle() {
|
||||
let style = {}
|
||||
if ((this.mode === 'top' || this.mode === 'left' || this.mode === 'right') && this.marginTop) {
|
||||
style.marginTop = this.$t.string.getLengthUnitValue(this.marginTop, 'px')
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
// 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
|
||||
if (this.mode === 'left' || this.mode === 'right') {
|
||||
style = {
|
||||
width: this.width ? this.$t.string.getLengthUnitValue(this.width) : this.$t.string.getLengthUnitValue(this.length),
|
||||
height: '100%',
|
||||
transform: `translate3D(${this.mode === 'left' ? '-100%' : '100%'}, 0px, 0px)`
|
||||
}
|
||||
} else if (this.mode === 'top' || this.mode === 'bottom') {
|
||||
style = {
|
||||
width: '100%',
|
||||
height: this.height ? this.$t.string.getLengthUnitValue(this.height) : this.$t.string.getLengthUnitValue(this.length),
|
||||
transform: `translate3D(0px, ${this.mode === 'top' ? '-100%': '100%'}, 0px)`
|
||||
}
|
||||
}
|
||||
style.zIndex = this.elZIndex
|
||||
// 如果设置了圆角的值,添加弹窗的圆角
|
||||
if (this.borderRadius) {
|
||||
switch(this.mode) {
|
||||
case 'left':
|
||||
style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`
|
||||
break
|
||||
case 'top':
|
||||
style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`
|
||||
break
|
||||
case 'right':
|
||||
style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`
|
||||
break
|
||||
case 'bottom':
|
||||
style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`
|
||||
break
|
||||
}
|
||||
style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
if (this.backgroundColorStyle && this.mode !== 'center') {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 中部弹窗的样式
|
||||
centerStyle() {
|
||||
let style = {}
|
||||
style.width = this.width ? this.$t.string.getLengthUnitValue(this.width) : this.$t.string.getLengthUnitValue(this.length)
|
||||
// 中部弹出的模式,如果没有设置高度,就用auto值,由内容撑开
|
||||
style.height = this.height ? this.$t.string.getLengthUnitValue(this.height) : 'auto'
|
||||
style.zIndex = this.elZIndex
|
||||
if (this.negativeTop) {
|
||||
style.marginTop = `-${this.$t.string.getLengthUnitValue(this.negativeTop)}`
|
||||
}
|
||||
if (this.borderRadius) {
|
||||
style.borderRadius = `${this.borderRadius}rpx`
|
||||
style.overflow='hidden'
|
||||
}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
return style
|
||||
},
|
||||
// 关闭按钮样式
|
||||
closeBtnStyle() {
|
||||
let style = {}
|
||||
if (this.closeIconColor) {
|
||||
style.color = this.closeIconColor
|
||||
}
|
||||
if (this.closeIconSize) {
|
||||
style.fontSize = this.closeIconSize + 'rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
visibleSync: false,
|
||||
showPopup: false,
|
||||
closeFromInner: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val) {
|
||||
// console.log(this.visibleSync);
|
||||
if (this.visibleSync) {
|
||||
this.visibleSync = false
|
||||
return
|
||||
}
|
||||
this.open()
|
||||
} else if (!this.closeFromInner) {
|
||||
this.close()
|
||||
}
|
||||
this.closeFromInner = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 组件渲染完成时,检查value是否为true,如果是,弹出popup
|
||||
this.value && this.open()
|
||||
},
|
||||
methods: {
|
||||
// 点击遮罩
|
||||
maskClick() {
|
||||
if (!this.maskCloseable) return
|
||||
this.close()
|
||||
},
|
||||
open() {
|
||||
this.change('visibleSync', 'showPopup', true)
|
||||
},
|
||||
// 关闭弹框
|
||||
close() {
|
||||
// 标记关闭是内部发生的,否则修改了value值,导致watch中对value检测,导致再执行一遍close
|
||||
// 造成@close事件触发两次
|
||||
this.closeFromInner = true
|
||||
this.change('showPopup', 'visibleSync', false)
|
||||
},
|
||||
// 中部弹出时,需要.tn-drawer-content将内容居中,此元素会铺满屏幕,点击需要关闭弹窗
|
||||
// 让其只在mode=center时起作用
|
||||
modeCenterClose() {
|
||||
if (this.mode != 'center' || !this.maskCloseable) return
|
||||
this.close()
|
||||
},
|
||||
// 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
|
||||
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
|
||||
change(param1, param2, status) {
|
||||
// 如果this.popup为false,意味着为picker,actionsheet等组件调用了popup组件
|
||||
if (this.popup === true) {
|
||||
this.$emit('input', status)
|
||||
}
|
||||
this[param1] = status
|
||||
if (status) {
|
||||
// #ifdef H5 || MP
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
clearTimeout(this.timer)
|
||||
}, 10)
|
||||
// #endif
|
||||
// #ifndef H5 || MP
|
||||
this.$nextTick(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
clearTimeout(this.timer)
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-popup',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹出方向
|
||||
// left/right/top/bottom/center
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// 是否显示遮罩
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 抽屉的宽度(mode=left/right),高度(mode=top/bottom)
|
||||
length: {
|
||||
type: [Number, String],
|
||||
default: 'auto'
|
||||
},
|
||||
// 宽度,只对左,右,中部弹出时起作用,单位rpx,或者"auto"
|
||||
// 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
|
||||
width: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 高度,只对上,下,中部弹出时起作用,单位rpx,或者"auto"
|
||||
// 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否开启动画,只在mode=center有效
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以通过点击遮罩进行关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 用户自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 显示圆角的大小
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 关闭按钮的图标
|
||||
closeBtnIcon: {
|
||||
type: String,
|
||||
default: 'close'
|
||||
},
|
||||
// 关闭按钮显示的位置
|
||||
// top-left/top-right/bottom-left/bottom-right
|
||||
closeBtnPosition: {
|
||||
type: String,
|
||||
default: 'top-right'
|
||||
},
|
||||
// 关闭按钮图标颜色
|
||||
closeIconColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 关闭按钮图标的大小
|
||||
closeIconSize: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 给一个负的margin-top,往上偏移,避免和键盘重合的情况,仅在mode=center时有效
|
||||
negativeTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// marginTop,在mode = top,left,right时生效,避免用户使用了自定义导航栏,组件把导航栏遮挡了
|
||||
marginTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
|
||||
// 对v-model双向绑定多层调用造成报错不能修改props值的问题
|
||||
popup: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 处理使用了自定义导航栏时被遮挡的问题
|
||||
popupStyle() {
|
||||
let style = {}
|
||||
if ((this.mode === 'top' || this.mode === 'left' || this.mode === 'right') && this.marginTop) {
|
||||
style.marginTop = this.$t.string.getLengthUnitValue(this.marginTop, 'px')
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
// 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
|
||||
if (this.mode === 'left' || this.mode === 'right') {
|
||||
style = {
|
||||
width: this.width ? this.$t.string.getLengthUnitValue(this.width) : this.$t.string.getLengthUnitValue(this.length),
|
||||
height: '100%',
|
||||
transform: `translate3D(${this.mode === 'left' ? '-100%' : '100%'}, 0px, 0px)`
|
||||
}
|
||||
} else if (this.mode === 'top' || this.mode === 'bottom') {
|
||||
style = {
|
||||
width: '100%',
|
||||
height: this.height ? this.$t.string.getLengthUnitValue(this.height) : this.$t.string.getLengthUnitValue(this.length),
|
||||
transform: `translate3D(0px, ${this.mode === 'top' ? '-100%': '100%'}, 0px)`
|
||||
}
|
||||
}
|
||||
style.zIndex = this.elZIndex
|
||||
// 如果设置了圆角的值,添加弹窗的圆角
|
||||
if (this.borderRadius) {
|
||||
switch(this.mode) {
|
||||
case 'left':
|
||||
style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`
|
||||
break
|
||||
case 'top':
|
||||
style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`
|
||||
break
|
||||
case 'right':
|
||||
style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`
|
||||
break
|
||||
case 'bottom':
|
||||
style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`
|
||||
break
|
||||
}
|
||||
style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
if (this.backgroundColorStyle && this.mode !== 'center') {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
// 中部弹窗的样式
|
||||
centerStyle() {
|
||||
let style = {}
|
||||
style.width = this.width ? this.$t.string.getLengthUnitValue(this.width) : this.$t.string.getLengthUnitValue(this.length)
|
||||
// 中部弹出的模式,如果没有设置高度,就用auto值,由内容撑开
|
||||
style.height = this.height ? this.$t.string.getLengthUnitValue(this.height) : 'auto'
|
||||
style.zIndex = this.elZIndex
|
||||
if (this.negativeTop) {
|
||||
style.marginTop = `-${this.$t.string.getLengthUnitValue(this.negativeTop)}`
|
||||
}
|
||||
if (this.borderRadius) {
|
||||
style.borderRadius = `${this.borderRadius}rpx`
|
||||
style.overflow='hidden'
|
||||
}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
return style
|
||||
},
|
||||
// 关闭按钮样式
|
||||
closeBtnStyle() {
|
||||
let style = {}
|
||||
if (this.closeIconColor) {
|
||||
style.color = this.closeIconColor
|
||||
}
|
||||
if (this.closeIconSize) {
|
||||
style.fontSize = this.closeIconSize + 'rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
visibleSync: false,
|
||||
showPopup: false,
|
||||
closeFromInner: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (val) {
|
||||
// console.log(this.visibleSync);
|
||||
if (this.visibleSync) {
|
||||
this.visibleSync = false
|
||||
return
|
||||
}
|
||||
this.open()
|
||||
} else if (!this.closeFromInner) {
|
||||
this.close()
|
||||
}
|
||||
this.closeFromInner = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 组件渲染完成时,检查value是否为true,如果是,弹出popup
|
||||
this.value && this.open()
|
||||
},
|
||||
methods: {
|
||||
// 点击遮罩
|
||||
maskClick() {
|
||||
if (!this.maskCloseable) return
|
||||
this.close()
|
||||
},
|
||||
open() {
|
||||
this.change('visibleSync', 'showPopup', true)
|
||||
},
|
||||
// 关闭弹框
|
||||
close() {
|
||||
// 标记关闭是内部发生的,否则修改了value值,导致watch中对value检测,导致再执行一遍close
|
||||
// 造成@close事件触发两次
|
||||
this.closeFromInner = true
|
||||
this.change('showPopup', 'visibleSync', false)
|
||||
},
|
||||
// 中部弹出时,需要.tn-drawer-content将内容居中,此元素会铺满屏幕,点击需要关闭弹窗
|
||||
// 让其只在mode=center时起作用
|
||||
modeCenterClose() {
|
||||
if (this.mode != 'center' || !this.maskCloseable) return
|
||||
this.close()
|
||||
},
|
||||
// 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
|
||||
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
|
||||
change(param1, param2, status) {
|
||||
// 如果this.popup为false,意味着为picker,actionsheet等组件调用了popup组件
|
||||
if (this.popup === true) {
|
||||
this.$emit('input', status)
|
||||
}
|
||||
this[param1] = status
|
||||
if (status) {
|
||||
// #ifdef H5 || MP
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
clearTimeout(this.timer)
|
||||
}, 10)
|
||||
// #endif
|
||||
// #ifndef H5 || MP
|
||||
this.$nextTick(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
clearTimeout(this.timer)
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-popup {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
transition: all 0.25s linear;
|
||||
|
||||
&--visible {
|
||||
transform: translate3D(0px, 0px, 0px) !important;
|
||||
&.tn-popup--center {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__center_box {
|
||||
min-width: 100rpx;
|
||||
min-height: 100rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__scroll-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__center--animation-zoom {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
&__scroll_view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--left {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--right {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--top {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
|
||||
&--top-left {
|
||||
top: 30rpx;
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
&--top-right {
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
&--bottom-left {
|
||||
bottom: 30rpx;
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
&--bottom-right {
|
||||
bottom: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background-color: $tn-mask-bg-color;
|
||||
transition: 0.25s linear;
|
||||
transition-property: opacity;
|
||||
opacity: 0;
|
||||
|
||||
&--show {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-popup {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
transition: all 0.25s linear;
|
||||
|
||||
&--visible {
|
||||
transform: translate3D(0px, 0px, 0px) !important;
|
||||
&.tn-popup--center {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__center_box {
|
||||
min-width: 100rpx;
|
||||
min-height: 100rpx;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__scroll-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__center--animation-zoom {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
&__scroll_view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&--left {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--right {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--top {
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
|
||||
&--top-left {
|
||||
top: 30rpx;
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
&--top-right {
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
&--bottom-left {
|
||||
bottom: 30rpx;
|
||||
left: 30rpx;
|
||||
}
|
||||
|
||||
&--bottom-right {
|
||||
bottom: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background-color: $tn-mask-bg-color;
|
||||
transition: 0.25s linear;
|
||||
transition-property: opacity;
|
||||
opacity: 0;
|
||||
|
||||
&--show {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
<template>
|
||||
<view class="tn-radio-group-class tn-radio-group">
|
||||
<slot></slot>
|
||||
<template>
|
||||
<view class="tn-radio-group-class tn-radio-group">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [ Emitter ],
|
||||
name: 'tn-radio-group',
|
||||
props: {
|
||||
// 单选组的值,会选中值相同的选项
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 每个radio占的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件tn-radio需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(tn-radio-group)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [this.value, this.disabled, this.activeColor, this.size, this.disabledLabel, this.shape, this.iconSize, this.width, this.wrap]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件中的子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-radio)如果有updateParentData方法的话,子组件重新从父组件拉取了最新的值
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
// 改方法由子组件tn-radio调用,当一个tn-radio被选中的时候,给父组件设置value值(通知其他tn-radio组件)
|
||||
setValue(value) {
|
||||
// 通过子组件传递过来的value值(此被选中的子组件内部已将parentValue设置等于value的值),将其他tn-radio设置未选中的状态
|
||||
this.children.map(child => {
|
||||
if (child.parentData.value !== value) child.parentData.value = ''
|
||||
})
|
||||
// 通过emit事件,设置父组件通过v-model双向绑定的值
|
||||
this.$emit('input', value)
|
||||
this.$emit('change', value)
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 tn-form-item 进行校验
|
||||
this.dispatch('tn-form-item', 'on-form-change', value);
|
||||
}, 60)
|
||||
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import Emitter from '../../libs/utils/emitter.js'
|
||||
|
||||
export default {
|
||||
mixins: [ Emitter ],
|
||||
name: 'tn-radio-group',
|
||||
props: {
|
||||
// 单选组的值,会选中值相同的选项
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 组件大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 每个radio占的宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
// 是否换行
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件tn-radio需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(tn-radio-group)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [this.value, this.disabled, this.activeColor, this.size, this.disabledLabel, this.shape, this.iconSize, this.width, this.wrap]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件中的子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-radio)如果有updateParentData方法的话,子组件重新从父组件拉取了最新的值
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
// 改方法由子组件tn-radio调用,当一个tn-radio被选中的时候,给父组件设置value值(通知其他tn-radio组件)
|
||||
setValue(value) {
|
||||
// 通过子组件传递过来的value值(此被选中的子组件内部已将parentValue设置等于value的值),将其他tn-radio设置未选中的状态
|
||||
this.children.map(child => {
|
||||
if (child.parentData.value !== value) child.parentData.value = ''
|
||||
})
|
||||
// 通过emit事件,设置父组件通过v-model双向绑定的值
|
||||
this.$emit('input', value)
|
||||
this.$emit('change', value)
|
||||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
|
||||
// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
|
||||
setTimeout(() => {
|
||||
// 将当前的值发送到 tn-form-item 进行校验
|
||||
this.dispatch('tn-form-item', 'on-form-change', value);
|
||||
}, 60)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-radio-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
&::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-radio-group {
|
||||
/* #ifndef MP || APP-NVUE */
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
/* #endif */
|
||||
&::after {
|
||||
content: " ";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,265 +1,265 @@
|
||||
<template>
|
||||
<view class="tn-radio-class tn-radio" :style="[radioStyle]">
|
||||
<view
|
||||
class="tn-radio__icon-wrap"
|
||||
:class="[iconClass]"
|
||||
:style=[iconStyle]
|
||||
@tap="toggle"
|
||||
>
|
||||
<view class="tn-icon-success tn-radio__icon-wrap__icon"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-radio__label"
|
||||
:style="{
|
||||
fontSize: labelSize ? labelSize + 'rpx' : ''
|
||||
}"
|
||||
@tap="onClickLabel"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-radio-class tn-radio" :style="[radioStyle]">
|
||||
<view
|
||||
class="tn-radio__icon-wrap"
|
||||
:class="[iconClass]"
|
||||
:style=[iconStyle]
|
||||
@tap="toggle"
|
||||
>
|
||||
<view class="tn-icon-success tn-radio__icon-wrap__icon"></view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-radio__label"
|
||||
:style="{
|
||||
fontSize: labelSize ? labelSize + 'rpx' : ''
|
||||
}"
|
||||
@tap="onClickLabel"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-radio',
|
||||
props: {
|
||||
// radio名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 组件尺寸
|
||||
size: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label字体大小
|
||||
labelSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 禁用,父组件会覆盖子组件的状态
|
||||
isDisabled() {
|
||||
return this.disabled ? this.disabled : (this.parentData.disabled ? this.parentData.disabled : false)
|
||||
},
|
||||
// 禁用label点击,父组件会覆盖子组件的状态
|
||||
isDisabledLabel() {
|
||||
return this.disabledLabel ? this.disabledLabel : (this.parentData.disabledLabel ? this.parentData.disabledLabel : false)
|
||||
},
|
||||
// 组件尺寸
|
||||
elSize() {
|
||||
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 34)
|
||||
},
|
||||
// 组件选中时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#01BEFF')
|
||||
},
|
||||
// 组件形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle')
|
||||
},
|
||||
iconClass() {
|
||||
let clazz = ''
|
||||
clazz += (' tn-radio__icon-wrap--' + this.elShape)
|
||||
|
||||
if (this.parentData.value === this.name) clazz += ' tn-radio__icon-wrap--checked'
|
||||
if (this.isDisabled) clazz += ' tn-radio__icon-wrap--disabled'
|
||||
if (this.parentData.value === this.name && this.isDisabled) clazz += ' tn-radio__icon-wrap--disabled--checked'
|
||||
|
||||
return clazz
|
||||
},
|
||||
iconStyle() {
|
||||
// 当前radio的name等于parent的value才认为时选中
|
||||
let style = {}
|
||||
if (this.elActiveColor && this.parentData.value === this.name && !this.isDisabled) {
|
||||
style.borderColor = this.elActiveColor
|
||||
style.backgroundColor = this.elActiveColor
|
||||
}
|
||||
|
||||
style.color = this.parentData.value === this.name ? '#FFFFFF' : 'transparent'
|
||||
|
||||
style.width = this.elSize + 'rpx'
|
||||
style.height = style.width
|
||||
|
||||
style.fontSize = (this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 20)) + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
radioStyle() {
|
||||
let style = {}
|
||||
if (this.parent && this.parentData.width) {
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parentData.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parentData.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.xxx的形式
|
||||
// 故只能使用如此方法
|
||||
parentData: {
|
||||
value: null,
|
||||
disabled: null,
|
||||
disabledLabel: null,
|
||||
shape: null,
|
||||
activeColor: null,
|
||||
size: null,
|
||||
width: null,
|
||||
wrap: null,
|
||||
iconSize: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
|
||||
this.updateParentData()
|
||||
this.parent.children.push(this)
|
||||
},
|
||||
methods: {
|
||||
updateParentData() {
|
||||
this.getParentData('tn-radio-group')
|
||||
},
|
||||
onClickLabel() {
|
||||
if (!this.isDisabledLabel && !this.isDisabled) {
|
||||
this.setRadioCheckedStatus()
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setRadioCheckedStatus()
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
// tn-radio的name不等于父组件的v-model的值时(意味着未选中),才发出事件,避免多次点击触发事件
|
||||
if (this.parentData.value !== this.name) this.$emit('change', this.name)
|
||||
},
|
||||
// 改变选中的状态
|
||||
// 更改本组件的parentData.value的值为本组件的name值,同时通过父组件遍历所有的radio实例
|
||||
// 将本组件外的其他radio的parentData.value都设置为空
|
||||
setRadioCheckedStatus() {
|
||||
this.emitEvent()
|
||||
if (this.parent) {
|
||||
this.parent.setValue(this.name)
|
||||
this.parentData.value = this.name
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-radio',
|
||||
props: {
|
||||
// radio名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 禁用点击标签进行选择
|
||||
disabledLabel: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 选择框的形状 square 方形 circle 圆形
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 组件尺寸
|
||||
size: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 图标大小
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// label字体大小
|
||||
labelSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 禁用,父组件会覆盖子组件的状态
|
||||
isDisabled() {
|
||||
return this.disabled ? this.disabled : (this.parentData.disabled ? this.parentData.disabled : false)
|
||||
},
|
||||
// 禁用label点击,父组件会覆盖子组件的状态
|
||||
isDisabledLabel() {
|
||||
return this.disabledLabel ? this.disabledLabel : (this.parentData.disabledLabel ? this.parentData.disabledLabel : false)
|
||||
},
|
||||
// 组件尺寸
|
||||
elSize() {
|
||||
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 34)
|
||||
},
|
||||
// 组件选中时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#01BEFF')
|
||||
},
|
||||
// 组件形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle')
|
||||
},
|
||||
iconClass() {
|
||||
let clazz = ''
|
||||
clazz += (' tn-radio__icon-wrap--' + this.elShape)
|
||||
|
||||
if (this.parentData.value === this.name) clazz += ' tn-radio__icon-wrap--checked'
|
||||
if (this.isDisabled) clazz += ' tn-radio__icon-wrap--disabled'
|
||||
if (this.parentData.value === this.name && this.isDisabled) clazz += ' tn-radio__icon-wrap--disabled--checked'
|
||||
|
||||
return clazz
|
||||
},
|
||||
iconStyle() {
|
||||
// 当前radio的name等于parent的value才认为时选中
|
||||
let style = {}
|
||||
if (this.elActiveColor && this.parentData.value === this.name && !this.isDisabled) {
|
||||
style.borderColor = this.elActiveColor
|
||||
style.backgroundColor = this.elActiveColor
|
||||
}
|
||||
|
||||
style.color = this.parentData.value === this.name ? '#FFFFFF' : 'transparent'
|
||||
|
||||
style.width = this.elSize + 'rpx'
|
||||
style.height = style.width
|
||||
|
||||
style.fontSize = (this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 20)) + 'rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
radioStyle() {
|
||||
let style = {}
|
||||
if (this.parent && this.parentData.width) {
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.parentData.width}`;
|
||||
// #endif
|
||||
}
|
||||
if(this.parent && this.parentData.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.xxx的形式
|
||||
// 故只能使用如此方法
|
||||
parentData: {
|
||||
value: null,
|
||||
disabled: null,
|
||||
disabledLabel: null,
|
||||
shape: null,
|
||||
activeColor: null,
|
||||
size: null,
|
||||
width: null,
|
||||
wrap: null,
|
||||
iconSize: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
|
||||
this.updateParentData()
|
||||
this.parent.children.push(this)
|
||||
},
|
||||
methods: {
|
||||
updateParentData() {
|
||||
this.getParentData('tn-radio-group')
|
||||
},
|
||||
onClickLabel() {
|
||||
if (!this.isDisabledLabel && !this.isDisabled) {
|
||||
this.setRadioCheckedStatus()
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.isDisabled) {
|
||||
this.setRadioCheckedStatus()
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
// tn-radio的name不等于父组件的v-model的值时(意味着未选中),才发出事件,避免多次点击触发事件
|
||||
if (this.parentData.value !== this.name) this.$emit('change', this.name)
|
||||
},
|
||||
// 改变选中的状态
|
||||
// 更改本组件的parentData.value的值为本组件的name值,同时通过父组件遍历所有的radio实例
|
||||
// 将本组件外的其他radio的parentData.value都设置为空
|
||||
setRadioCheckedStatus() {
|
||||
this.emitEvent()
|
||||
if (this.parent) {
|
||||
this.parent.setValue(this.name)
|
||||
this.parentData.value = this.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-radio {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $tn-font-color;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
font-size: 20rpx;
|
||||
border: 1rpx solid $tn-font-sub-color;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
border-color: $tn-main-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: $tn-font-holder-color;
|
||||
border-color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: $tn-font-sub-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-radio {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $tn-font-color;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
font-size: 20rpx;
|
||||
border: 1rpx solid $tn-font-sub-color;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #FFFFFF;
|
||||
background-color: $tn-main-color;
|
||||
border-color: $tn-main-color;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: $tn-font-holder-color;
|
||||
border-color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: $tn-font-sub-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $tn-font-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,334 +1,334 @@
|
||||
<template>
|
||||
<view
|
||||
:id="elId"
|
||||
class="tn-rate-class tn-rate"
|
||||
@touchmove.stop.prevent="touchMove"
|
||||
>
|
||||
<view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
|
||||
<view
|
||||
class="tn-rate__wrap__icon"
|
||||
:class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
|
||||
:style="[iconStyle(index)]"
|
||||
@tap="click(index + 1, $event)"
|
||||
>
|
||||
<!-- 半图标 -->
|
||||
<view
|
||||
v-if="showHalfIcon(index)"
|
||||
class="tn-rate__wrap__icon--half"
|
||||
:class="[`tn-icon-${elActionIcon}`]"
|
||||
:style="[halfIconStyle]"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
:id="elId"
|
||||
class="tn-rate-class tn-rate"
|
||||
@touchmove.stop.prevent="touchMove"
|
||||
>
|
||||
<view class="tn-rate__wrap" :class="[elClass]" v-for="(item, index) in count" :key="index">
|
||||
<view
|
||||
class="tn-rate__wrap__icon"
|
||||
:class="[`tn-icon-${(allowHalf && halfIcon ? activeIndex > index + 1 : activeIndex > index) ? elActionIcon : elInactionIcon}`]"
|
||||
:style="[iconStyle(index)]"
|
||||
@tap="click(index + 1, $event)"
|
||||
>
|
||||
<!-- 半图标 -->
|
||||
<view
|
||||
v-if="showHalfIcon(index)"
|
||||
class="tn-rate__wrap__icon--half"
|
||||
:class="[`tn-icon-${elActionIcon}`]"
|
||||
:style="[halfIconStyle]"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-rate',
|
||||
props: {
|
||||
// 选中星星的数量
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 显示的星星数
|
||||
count: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
// 最小能选择的星星数
|
||||
minCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 禁用状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以选择半星
|
||||
allowHalf: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 星星大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
// 被选中的图标
|
||||
activeIcon: {
|
||||
type: String,
|
||||
default: 'star-fill'
|
||||
},
|
||||
// 未被选中的图标
|
||||
inactiveIcon: {
|
||||
type: String,
|
||||
default: 'star'
|
||||
},
|
||||
// 被选中的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 默认颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 星星之间的距离
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
// 自定义颜色
|
||||
colors: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 自定义图标
|
||||
icons: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 图标显示的比例
|
||||
showHalfIcon(index) {
|
||||
return index => {
|
||||
return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
|
||||
}
|
||||
},
|
||||
// 被激活的图标
|
||||
elActionIcon() {
|
||||
const len = this.icons.length
|
||||
// icons参数传递了3个图标,当选中2个时,用第一个图标,4个时,用第二个图标
|
||||
// 5个时,用第三个图标作为激活的图标
|
||||
if (len && len <= this.count) {
|
||||
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
|
||||
if (step < 1) return this.icons[0]
|
||||
if (step > len) return this.icons[len - 1]
|
||||
return this.icons[step - 1]
|
||||
}
|
||||
return this.activeIcon
|
||||
},
|
||||
// 未被激活的图标
|
||||
elInactionIcon() {
|
||||
const len = this.icons.length
|
||||
// icons参数传递了3个图标,当选中2个时,用第一个图标,4个时,用第二个图标
|
||||
// 5个时,用第三个图标作为激活的图标
|
||||
if (len && len <= this.count) {
|
||||
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
|
||||
if (step < 1) return this.icons[0]
|
||||
if (step > len) return this.icons[len - 1]
|
||||
return this.icons[step - 1]
|
||||
}
|
||||
return this.inactiveIcon
|
||||
},
|
||||
// 被激活的颜色
|
||||
elActionColor() {
|
||||
const len = this.colors.length
|
||||
// 如果有设置colors参数(此参数用于将图标分段,比如一共5颗星,colors传3个颜色值,那么根据一定的规则,2颗星可能为第一个颜色
|
||||
// 4颗星为第二个颜色值,5颗星为第三个颜色值)
|
||||
if (len && len <= this.count) {
|
||||
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
|
||||
if (step < 1) return this.colors[0]
|
||||
if (step > len) return this.colors[len - 1]
|
||||
return this.colors[step - 1]
|
||||
}
|
||||
return this.activeColor
|
||||
},
|
||||
// 图标的样式
|
||||
iconStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
|
||||
// 当前图标的颜色
|
||||
if (this.allowHalf && this.halfIcon) {
|
||||
style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
|
||||
} else {
|
||||
style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 半图标样式
|
||||
halfIconStyle() {
|
||||
let style = {}
|
||||
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
|
||||
style.color = this.elActionColor
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 保证控件的唯一性
|
||||
elId: this.$t.uuid(),
|
||||
elClass: this.$t.uuid(),
|
||||
// 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
|
||||
starBoxLeft: 0,
|
||||
// 当前激活的星星的序号
|
||||
activeIndex: this.value,
|
||||
// 每个星星的宽度
|
||||
starWidth: 0,
|
||||
// 每个星星最右边到盒子组件最左边的距离
|
||||
starWidthArr: [],
|
||||
// 标记是否为半图标
|
||||
halfIcon: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.activeIndex = val
|
||||
if (this.allowHalf && (val % 1 === 0.5)) {
|
||||
this.halfIcon = true
|
||||
} else {
|
||||
this.halfIcon = false
|
||||
}
|
||||
},
|
||||
size() {
|
||||
// 当尺寸修改的时候重新获取布局尺寸信息
|
||||
this.$nextTick(() => {
|
||||
this.getElRectById()
|
||||
this.getElRectByClass()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getElRectById()
|
||||
this.getElRectByClass()
|
||||
},
|
||||
methods: {
|
||||
// 获取评分组件盒子的布局信息
|
||||
getElRectById() {
|
||||
this._tGetRect('#'+this.elId).then(res => {
|
||||
this.starBoxLeft = res.left
|
||||
})
|
||||
},
|
||||
// 获取单个星星的尺寸
|
||||
getElRectByClass() {
|
||||
this._tGetRect('.'+this.elClass).then(res => {
|
||||
this.starWidth = res.width
|
||||
// 把每个星星最右边到盒子最左边的距离
|
||||
for (let i = 0; i < this.count; i++) {
|
||||
this.starWidthArr[i] = (i + 1) * this.starWidth
|
||||
}
|
||||
})
|
||||
},
|
||||
// 手指滑动
|
||||
touchMove(e) {
|
||||
if (this.disabled) return
|
||||
if (!e.changedTouches[0]) return
|
||||
|
||||
const movePageX = e.changedTouches[0].pageX
|
||||
// 滑动点相对于评分盒子左边的距离
|
||||
const distance = movePageX - this.starBoxLeft
|
||||
|
||||
// 如果滑动到了评分盒子的左边界,设置为0星
|
||||
if (distance <= 0) {
|
||||
this.activeIndex = 0
|
||||
}
|
||||
|
||||
// 计算滑动的距离相当于点击多少颗星星
|
||||
let index = Math.ceil(distance / this.starWidth)
|
||||
if (this.allowHalf) {
|
||||
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
|
||||
if (distance < iconHalfWidth) {
|
||||
this.halfIcon = true
|
||||
index -= 0.5
|
||||
} else {
|
||||
this.halfIcon = false
|
||||
}
|
||||
}
|
||||
this.activeIndex = index > this.count ? this.count : index
|
||||
|
||||
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
|
||||
|
||||
this.emitEvent()
|
||||
},
|
||||
// 通过点击直接选中
|
||||
click(index, e) {
|
||||
if (this.disabled) return
|
||||
// 半星选择
|
||||
if (this.allowHalf) {
|
||||
if (!e.changedTouches[0]) return
|
||||
const movePageX = e.changedTouches[0].pageX
|
||||
// 点击点相对于当前图标左边的距离
|
||||
const distance = movePageX - this.starBoxLeft
|
||||
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
|
||||
if (distance < iconHalfWidth) {
|
||||
this.halfIcon = true
|
||||
} else {
|
||||
this.halfIcon = false
|
||||
}
|
||||
}
|
||||
|
||||
// 对第一个星星特殊处理,只有一个的时候,点击可以取消,否则无法作0星评价
|
||||
if (index == 1) {
|
||||
if (this.allowHalf && this.allowHalf) {
|
||||
if ((this.activeIndex === 0.5 && this.halfIcon) ||
|
||||
(this.activeIndex === 1 && !this.halfIcon)) {
|
||||
this.activeIndex = 0
|
||||
} else {
|
||||
this.activeIndex = this.halfIcon ? 0.5 : 1
|
||||
}
|
||||
} else {
|
||||
if (this.activeIndex == 1) {
|
||||
this.activeIndex = 0
|
||||
} else {
|
||||
this.activeIndex = 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
|
||||
}
|
||||
|
||||
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
|
||||
|
||||
this.emitEvent()
|
||||
},
|
||||
// 发送事件
|
||||
emitEvent() {
|
||||
this.$emit('change', this.activeIndex)
|
||||
// 修改v-model的值
|
||||
this.$emit('input', this.activeIndex)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-rate',
|
||||
props: {
|
||||
// 选中星星的数量
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 显示的星星数
|
||||
count: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
// 最小能选择的星星数
|
||||
minCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 禁用状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否可以选择半星
|
||||
allowHalf: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 星星大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
// 被选中的图标
|
||||
activeIcon: {
|
||||
type: String,
|
||||
default: 'star-fill'
|
||||
},
|
||||
// 未被选中的图标
|
||||
inactiveIcon: {
|
||||
type: String,
|
||||
default: 'star'
|
||||
},
|
||||
// 被选中的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 默认颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 星星之间的距离
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
// 自定义颜色
|
||||
colors: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 自定义图标
|
||||
icons: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 图标显示的比例
|
||||
showHalfIcon(index) {
|
||||
return index => {
|
||||
return this.allowHalf && Math.ceil(this.activeIndex) === index + 1 && this.halfIcon
|
||||
}
|
||||
},
|
||||
// 被激活的图标
|
||||
elActionIcon() {
|
||||
const len = this.icons.length
|
||||
// icons参数传递了3个图标,当选中2个时,用第一个图标,4个时,用第二个图标
|
||||
// 5个时,用第三个图标作为激活的图标
|
||||
if (len && len <= this.count) {
|
||||
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
|
||||
if (step < 1) return this.icons[0]
|
||||
if (step > len) return this.icons[len - 1]
|
||||
return this.icons[step - 1]
|
||||
}
|
||||
return this.activeIcon
|
||||
},
|
||||
// 未被激活的图标
|
||||
elInactionIcon() {
|
||||
const len = this.icons.length
|
||||
// icons参数传递了3个图标,当选中2个时,用第一个图标,4个时,用第二个图标
|
||||
// 5个时,用第三个图标作为激活的图标
|
||||
if (len && len <= this.count) {
|
||||
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
|
||||
if (step < 1) return this.icons[0]
|
||||
if (step > len) return this.icons[len - 1]
|
||||
return this.icons[step - 1]
|
||||
}
|
||||
return this.inactiveIcon
|
||||
},
|
||||
// 被激活的颜色
|
||||
elActionColor() {
|
||||
const len = this.colors.length
|
||||
// 如果有设置colors参数(此参数用于将图标分段,比如一共5颗星,colors传3个颜色值,那么根据一定的规则,2颗星可能为第一个颜色
|
||||
// 4颗星为第二个颜色值,5颗星为第三个颜色值)
|
||||
if (len && len <= this.count) {
|
||||
const step = Math.round(Math.ceil(this.activeIndex) / Math.round(this.count / len))
|
||||
if (step < 1) return this.colors[0]
|
||||
if (step > len) return this.colors[len - 1]
|
||||
return this.colors[step - 1]
|
||||
}
|
||||
return this.activeColor
|
||||
},
|
||||
// 图标的样式
|
||||
iconStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
|
||||
// 当前图标的颜色
|
||||
if (this.allowHalf && this.halfIcon) {
|
||||
style.color = this.activeIndex > index + 1 ? this.elActionColor : this.inactiveColor
|
||||
} else {
|
||||
style.color = this.activeIndex > index ? this.elActionColor : this.inactiveColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 半图标样式
|
||||
halfIconStyle() {
|
||||
let style = {}
|
||||
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.padding = `0 ${this.$t.string.getLengthUnitValue(this.gutter / 2)}`
|
||||
style.color = this.elActionColor
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 保证控件的唯一性
|
||||
elId: this.$t.uuid(),
|
||||
elClass: this.$t.uuid(),
|
||||
// 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
|
||||
starBoxLeft: 0,
|
||||
// 当前激活的星星的序号
|
||||
activeIndex: this.value,
|
||||
// 每个星星的宽度
|
||||
starWidth: 0,
|
||||
// 每个星星最右边到盒子组件最左边的距离
|
||||
starWidthArr: [],
|
||||
// 标记是否为半图标
|
||||
halfIcon: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.activeIndex = val
|
||||
if (this.allowHalf && (val % 1 === 0.5)) {
|
||||
this.halfIcon = true
|
||||
} else {
|
||||
this.halfIcon = false
|
||||
}
|
||||
},
|
||||
size() {
|
||||
// 当尺寸修改的时候重新获取布局尺寸信息
|
||||
this.$nextTick(() => {
|
||||
this.getElRectById()
|
||||
this.getElRectByClass()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getElRectById()
|
||||
this.getElRectByClass()
|
||||
},
|
||||
methods: {
|
||||
// 获取评分组件盒子的布局信息
|
||||
getElRectById() {
|
||||
this._tGetRect('#'+this.elId).then(res => {
|
||||
this.starBoxLeft = res.left
|
||||
})
|
||||
},
|
||||
// 获取单个星星的尺寸
|
||||
getElRectByClass() {
|
||||
this._tGetRect('.'+this.elClass).then(res => {
|
||||
this.starWidth = res.width
|
||||
// 把每个星星最右边到盒子最左边的距离
|
||||
for (let i = 0; i < this.count; i++) {
|
||||
this.starWidthArr[i] = (i + 1) * this.starWidth
|
||||
}
|
||||
})
|
||||
},
|
||||
// 手指滑动
|
||||
touchMove(e) {
|
||||
if (this.disabled) return
|
||||
if (!e.changedTouches[0]) return
|
||||
|
||||
const movePageX = e.changedTouches[0].pageX
|
||||
// 滑动点相对于评分盒子左边的距离
|
||||
const distance = movePageX - this.starBoxLeft
|
||||
|
||||
// 如果滑动到了评分盒子的左边界,设置为0星
|
||||
if (distance <= 0) {
|
||||
this.activeIndex = 0
|
||||
}
|
||||
|
||||
// 计算滑动的距离相当于点击多少颗星星
|
||||
let index = Math.ceil(distance / this.starWidth)
|
||||
if (this.allowHalf) {
|
||||
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
|
||||
if (distance < iconHalfWidth) {
|
||||
this.halfIcon = true
|
||||
index -= 0.5
|
||||
} else {
|
||||
this.halfIcon = false
|
||||
}
|
||||
}
|
||||
this.activeIndex = index > this.count ? this.count : index
|
||||
|
||||
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
|
||||
|
||||
this.emitEvent()
|
||||
},
|
||||
// 通过点击直接选中
|
||||
click(index, e) {
|
||||
if (this.disabled) return
|
||||
// 半星选择
|
||||
if (this.allowHalf) {
|
||||
if (!e.changedTouches[0]) return
|
||||
const movePageX = e.changedTouches[0].pageX
|
||||
// 点击点相对于当前图标左边的距离
|
||||
const distance = movePageX - this.starBoxLeft
|
||||
const iconHalfWidth = this.starWidthArr[index - 1] - (this.starWidth / 2)
|
||||
if (distance < iconHalfWidth) {
|
||||
this.halfIcon = true
|
||||
} else {
|
||||
this.halfIcon = false
|
||||
}
|
||||
}
|
||||
|
||||
// 对第一个星星特殊处理,只有一个的时候,点击可以取消,否则无法作0星评价
|
||||
if (index == 1) {
|
||||
if (this.allowHalf && this.allowHalf) {
|
||||
if ((this.activeIndex === 0.5 && this.halfIcon) ||
|
||||
(this.activeIndex === 1 && !this.halfIcon)) {
|
||||
this.activeIndex = 0
|
||||
} else {
|
||||
this.activeIndex = this.halfIcon ? 0.5 : 1
|
||||
}
|
||||
} else {
|
||||
if (this.activeIndex == 1) {
|
||||
this.activeIndex = 0
|
||||
} else {
|
||||
this.activeIndex = 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.activeIndex = (this.allowHalf && this.halfIcon) ? index - 0.5 : index
|
||||
}
|
||||
|
||||
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
|
||||
|
||||
this.emitEvent()
|
||||
},
|
||||
// 发送事件
|
||||
emitEvent() {
|
||||
this.$emit('change', this.activeIndex)
|
||||
// 修改v-model的值
|
||||
this.$emit('input', this.activeIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-rate {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&__wrap {
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--half {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-rate {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
&__wrap {
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--half {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,222 +1,222 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="tn-read-more-class tn-read-more">
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
:id="elId"
|
||||
class="tn-read-more__content"
|
||||
:style="[contentStyle]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 展开收起按钮 -->
|
||||
<view
|
||||
v-if="isLongContent"
|
||||
class="tn-read-more__show-more__wrap"
|
||||
:class="{'tn-read-more__show-more': showMore}"
|
||||
:style="[innerShadowStyle]"
|
||||
@tap="toggleReadMore">
|
||||
<text class="tn-read-more__show-more--text" :style="[fontStyle]">{{ showMore ? closeText : openText }}</text>
|
||||
<view class="tn-read-more__show-more--icon">
|
||||
<view :class="[tipIconClass]" :style="[fontStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view>
|
||||
<view class="tn-read-more-class tn-read-more">
|
||||
<!-- 内容 -->
|
||||
<view
|
||||
:id="elId"
|
||||
class="tn-read-more__content"
|
||||
:style="[contentStyle]"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
<!-- 展开收起按钮 -->
|
||||
<view
|
||||
v-if="isLongContent"
|
||||
class="tn-read-more__show-more__wrap"
|
||||
:class="{'tn-read-more__show-more': showMore}"
|
||||
:style="[innerShadowStyle]"
|
||||
@tap="toggleReadMore">
|
||||
<text class="tn-read-more__show-more--text" :style="[fontStyle]">{{ showMore ? closeText : openText }}</text>
|
||||
<view class="tn-read-more__show-more--icon">
|
||||
<view :class="[tipIconClass]" :style="[fontStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-read-more',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 默认占位高度
|
||||
showHeight: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
// 显示收起按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 展开提示文字
|
||||
openText: {
|
||||
type: String,
|
||||
default: '展开阅读全文'
|
||||
},
|
||||
// 收起提示文字
|
||||
closeText: {
|
||||
type: String,
|
||||
default: '收起'
|
||||
},
|
||||
// 展开提示图标
|
||||
openIcon: {
|
||||
type: String,
|
||||
default: 'down'
|
||||
},
|
||||
// 收起提示图标
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'up'
|
||||
},
|
||||
// 阴影样式
|
||||
shadowStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
backgroundImage: "linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%)",
|
||||
paddingTop: "300rpx",
|
||||
marginTop: "-300rpx"
|
||||
}
|
||||
}
|
||||
},
|
||||
// 组件标识
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
paramsChange() {
|
||||
return `${this.open}-${this.showHeight}`
|
||||
},
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
if (this.isLongContent && !this.showMore) {
|
||||
style.height = `${this.showHeight}rpx`
|
||||
} else {
|
||||
if (!this.initHeight) {
|
||||
style.height = 'auto'
|
||||
} else {
|
||||
style.height = `${this.contentHeight}px`
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
innerShadowStyle() {
|
||||
let style = {}
|
||||
// 折叠时才需要阴影样式
|
||||
if (!this.showMore) {
|
||||
style = Object.assign(style, this.shadowStyle)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
fontStyle() {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : '#01BEFF'
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : '28rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
tipIconClass() {
|
||||
if (this.showMore) {
|
||||
if (this.closeIcon) {
|
||||
return `tn-icon-${this.closeIcon}`
|
||||
}
|
||||
} else {
|
||||
if (this.openIcon) {
|
||||
return `tn-icon-${this.openIcon}`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elId: this.$t.uuid(),
|
||||
// 标记是否已经初始化高度完成
|
||||
initHeight: false,
|
||||
// 是否需要隐藏一部分内容
|
||||
isLongContent: false,
|
||||
// 当前展开的打开、关闭状态
|
||||
showMore: false,
|
||||
// 内容的高度
|
||||
contentHeight: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
paramsChange(value) {
|
||||
this.initHeight = false
|
||||
this.isLongContent = false
|
||||
this.showMore = true
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化组件
|
||||
init() {
|
||||
// 判断高度,如果真实内容的高度大于占位高度,则显示展开与收起的控制按钮
|
||||
this._tGetRect('#' + this.elId).then(res => {
|
||||
this.contentHeight = res.height
|
||||
this.initHeight = true
|
||||
if (res.height > uni.upx2px(this.showHeight)) {
|
||||
this.isLongContent = true
|
||||
this.showMore = false
|
||||
}
|
||||
})
|
||||
},
|
||||
// 展开或者收起内容
|
||||
toggleReadMore() {
|
||||
this.showMore = !this.showMore
|
||||
// 是否显示收起按钮
|
||||
if (!this.closeBtn) this.isLongContent = false
|
||||
|
||||
this.$emit(this.showMore ? 'open' : 'closed', this.index)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-read-more',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 默认占位高度
|
||||
showHeight: {
|
||||
type: Number,
|
||||
default: 400
|
||||
},
|
||||
// 显示收起按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 展开提示文字
|
||||
openText: {
|
||||
type: String,
|
||||
default: '展开阅读全文'
|
||||
},
|
||||
// 收起提示文字
|
||||
closeText: {
|
||||
type: String,
|
||||
default: '收起'
|
||||
},
|
||||
// 展开提示图标
|
||||
openIcon: {
|
||||
type: String,
|
||||
default: 'down'
|
||||
},
|
||||
// 收起提示图标
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'up'
|
||||
},
|
||||
// 阴影样式
|
||||
shadowStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
backgroundImage: "linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%)",
|
||||
paddingTop: "300rpx",
|
||||
marginTop: "-300rpx"
|
||||
}
|
||||
}
|
||||
},
|
||||
// 组件标识
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
paramsChange() {
|
||||
return `${this.open}-${this.showHeight}`
|
||||
},
|
||||
contentStyle() {
|
||||
let style = {}
|
||||
if (this.isLongContent && !this.showMore) {
|
||||
style.height = `${this.showHeight}rpx`
|
||||
} else {
|
||||
if (!this.initHeight) {
|
||||
style.height = 'auto'
|
||||
} else {
|
||||
style.height = `${this.contentHeight}px`
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
innerShadowStyle() {
|
||||
let style = {}
|
||||
// 折叠时才需要阴影样式
|
||||
if (!this.showMore) {
|
||||
style = Object.assign(style, this.shadowStyle)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
fontStyle() {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : '#01BEFF'
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : '28rpx'
|
||||
|
||||
return style
|
||||
},
|
||||
tipIconClass() {
|
||||
if (this.showMore) {
|
||||
if (this.closeIcon) {
|
||||
return `tn-icon-${this.closeIcon}`
|
||||
}
|
||||
} else {
|
||||
if (this.openIcon) {
|
||||
return `tn-icon-${this.openIcon}`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elId: this.$t.uuid(),
|
||||
// 标记是否已经初始化高度完成
|
||||
initHeight: false,
|
||||
// 是否需要隐藏一部分内容
|
||||
isLongContent: false,
|
||||
// 当前展开的打开、关闭状态
|
||||
showMore: false,
|
||||
// 内容的高度
|
||||
contentHeight: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
paramsChange(value) {
|
||||
this.initHeight = false
|
||||
this.isLongContent = false
|
||||
this.showMore = true
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化组件
|
||||
init() {
|
||||
// 判断高度,如果真实内容的高度大于占位高度,则显示展开与收起的控制按钮
|
||||
this._tGetRect('#' + this.elId).then(res => {
|
||||
this.contentHeight = res.height
|
||||
this.initHeight = true
|
||||
if (res.height > uni.upx2px(this.showHeight)) {
|
||||
this.isLongContent = true
|
||||
this.showMore = false
|
||||
}
|
||||
})
|
||||
},
|
||||
// 展开或者收起内容
|
||||
toggleReadMore() {
|
||||
this.showMore = !this.showMore
|
||||
// 是否显示收起按钮
|
||||
if (!this.closeBtn) this.isLongContent = false
|
||||
|
||||
this.$emit(this.showMore ? 'open' : 'closed', this.index)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-read-more {
|
||||
|
||||
&__content {
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
line-height: 1.8;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
&__show-more {
|
||||
padding-top: 0;
|
||||
background: none;
|
||||
margin-top: 20rpx;
|
||||
|
||||
&__wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 26rpx;
|
||||
}
|
||||
|
||||
&--text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-read-more {
|
||||
|
||||
&__content {
|
||||
font-size: 28rpx;
|
||||
color: $tn-font-color;
|
||||
line-height: 1.8;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
&__show-more {
|
||||
padding-top: 0;
|
||||
background: none;
|
||||
margin-top: 20rpx;
|
||||
|
||||
&__wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 26rpx;
|
||||
}
|
||||
|
||||
&--text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,301 +1,301 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="tn-row-notice-class tn-row-notice"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[noticeStyle]"
|
||||
>
|
||||
<view class="tn-row-notice__wrap">
|
||||
<!-- 左图标 -->
|
||||
<view class="tn-row-notice__icon">
|
||||
<view
|
||||
v-if="leftIcon"
|
||||
class="tn-row-notice__icon--left"
|
||||
:class="[`tn-icon-${leftIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('leftIcon')]"
|
||||
@tap="clickLeftIcon"></view>
|
||||
</view>
|
||||
|
||||
<!-- 消息体 -->
|
||||
<view class="tn-row-notice__content-box" id="tn-row-notice__content-box">
|
||||
<view
|
||||
class="tn-row-notice__content"
|
||||
id="tn-row-notice__content"
|
||||
:style="{
|
||||
animationDuration: animationDuration,
|
||||
animationPlayState: animationPlayState
|
||||
}"
|
||||
>
|
||||
<text
|
||||
class="tn-row-notice__content--text"
|
||||
:class="[fontColorClass]"
|
||||
:style="[fontStyle()]"
|
||||
@tap="click"
|
||||
>{{ showText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右图标 -->
|
||||
<view class="tn-row-notice__icon">
|
||||
<view
|
||||
v-if="rightIcon"
|
||||
class="tn-row-notice__icon--right"
|
||||
:class="[`tn-icon-${rightIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('rightIcon')]"
|
||||
@tap="clickRightIcon"></view>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-row-notice__icon--right"
|
||||
:class="[`tn-icon-close`,fontColorClass]"
|
||||
:style="[fontStyle('close')]"
|
||||
@tap="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
v-if="show"
|
||||
class="tn-row-notice-class tn-row-notice"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[noticeStyle]"
|
||||
>
|
||||
<view class="tn-row-notice__wrap">
|
||||
<!-- 左图标 -->
|
||||
<view class="tn-row-notice__icon">
|
||||
<view
|
||||
v-if="leftIcon"
|
||||
class="tn-row-notice__icon--left"
|
||||
:class="[`tn-icon-${leftIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('leftIcon')]"
|
||||
@tap="clickLeftIcon"></view>
|
||||
</view>
|
||||
|
||||
<!-- 消息体 -->
|
||||
<view class="tn-row-notice__content-box" id="tn-row-notice__content-box">
|
||||
<view
|
||||
class="tn-row-notice__content"
|
||||
id="tn-row-notice__content"
|
||||
:style="{
|
||||
animationDuration: animationDuration,
|
||||
animationPlayState: animationPlayState
|
||||
}"
|
||||
>
|
||||
<text
|
||||
class="tn-row-notice__content--text"
|
||||
:class="[fontColorClass]"
|
||||
:style="[fontStyle()]"
|
||||
@tap="click"
|
||||
>{{ showText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右图标 -->
|
||||
<view class="tn-row-notice__icon">
|
||||
<view
|
||||
v-if="rightIcon"
|
||||
class="tn-row-notice__icon--right"
|
||||
:class="[`tn-icon-${rightIconName}`,fontColorClass]"
|
||||
:style="[fontStyle('rightIcon')]"
|
||||
@tap="clickRightIcon"></view>
|
||||
<view
|
||||
v-if="closeBtn"
|
||||
class="tn-row-notice__icon--right"
|
||||
:class="[`tn-icon-close`,fontColorClass]"
|
||||
:style="[fontStyle('close')]"
|
||||
@tap="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-row-notice',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 水平滚动时的速度,即每秒滚动多少rpx
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontStyle() {
|
||||
return (type) => {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : ''
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : ''
|
||||
if (type === 'leftIcon' && this.leftIconSize) {
|
||||
style.fontSize = this.leftIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'rightIcon' && this.rightIconSize) {
|
||||
style.fontSize = this.rightIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'close') {
|
||||
style.fontSize = '24rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
noticeStyle() {
|
||||
let style = {}
|
||||
style.backgroundColor = this.backgroundColorStyle ? this.backgroundColorStyle : 'transparent'
|
||||
if (this.padding) style.padding = this.padding
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 滚动文字的宽度
|
||||
textWidth: 0,
|
||||
// 存放滚动文字的盒子的宽度
|
||||
textBoxWidth: 0,
|
||||
// 动画执行的时间
|
||||
animationDuration: '10s',
|
||||
// 动画执行状态
|
||||
animationPlayState: 'paused',
|
||||
// 当前显示的文本
|
||||
showText: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler(value) {
|
||||
this.showText = value.join(',')
|
||||
this.$nextTick(() => {
|
||||
this.initNotice()
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
playStatus(value) {
|
||||
if (value === 'play') this.animationPlayState = 'running'
|
||||
else this.animationPlayState = 'paused'
|
||||
},
|
||||
speed(value) {
|
||||
this.initNotice()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化通知栏
|
||||
initNotice() {
|
||||
let query = [],
|
||||
textBoxWidth = 0,
|
||||
textWidth = 0;
|
||||
let textQuery = new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#tn-row-notice__content`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.textWidth = ret[0].width
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
query.push(textQuery)
|
||||
|
||||
Promise.all(query).then(() => {
|
||||
// 根据t=s/v(时间=路程/速度),这里为何不需要加上#tn-row-notice__content-box的宽度,因为设置了.tn-row-notice__content样式中设置了padding-left: 100%
|
||||
this.animationDuration = `${this.textWidth / uni.upx2px(this.speed)}s`
|
||||
// 这里必须这样开始动画,否则在APP上动画速度不会改变(HX版本2.4.6,IOS13)
|
||||
this.animationPlayState = 'paused'
|
||||
setTimeout(() => {
|
||||
if (this.autoplay && this.playStatus === 'play') this.animationPlayState = 'running'
|
||||
}, 10)
|
||||
})
|
||||
},
|
||||
// 点击了通知栏
|
||||
click() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
}
|
||||
}
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
name: 'tn-row-notice',
|
||||
mixins: [componentsColorMixin],
|
||||
props: {
|
||||
// 显示的内容
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 播放状态
|
||||
// play -> 播放 paused -> 暂停
|
||||
playStatus: {
|
||||
type: String,
|
||||
default: 'play'
|
||||
},
|
||||
// 是否显示左边图标
|
||||
leftIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边图标的名称
|
||||
leftIconName: {
|
||||
type: String,
|
||||
default: 'sound'
|
||||
},
|
||||
// 左边图标的大小
|
||||
leftIconSize: {
|
||||
type: Number,
|
||||
default: 34
|
||||
},
|
||||
// 是否显示右边的图标
|
||||
rightIcon: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 右边图标的名称
|
||||
rightIconName: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// 右边图标的大小
|
||||
rightIconSize: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
// 是否显示关闭按钮
|
||||
closeBtn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 圆角
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: '18rpx 24rpx'
|
||||
},
|
||||
// 自动播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 水平滚动时的速度,即每秒滚动多少rpx
|
||||
speed: {
|
||||
type: Number,
|
||||
default: 160
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fontStyle() {
|
||||
return (type) => {
|
||||
let style = {}
|
||||
style.color = this.fontColorStyle ? this.fontColorStyle : ''
|
||||
style.fontSize = this.fontSizeStyle ? this.fontSizeStyle : ''
|
||||
if (type === 'leftIcon' && this.leftIconSize) {
|
||||
style.fontSize = this.leftIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'rightIcon' && this.rightIconSize) {
|
||||
style.fontSize = this.rightIconSize + 'rpx'
|
||||
}
|
||||
if (type === 'close') {
|
||||
style.fontSize = '24rpx'
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
noticeStyle() {
|
||||
let style = {}
|
||||
style.backgroundColor = this.backgroundColorStyle ? this.backgroundColorStyle : 'transparent'
|
||||
if (this.padding) style.padding = this.padding
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 滚动文字的宽度
|
||||
textWidth: 0,
|
||||
// 存放滚动文字的盒子的宽度
|
||||
textBoxWidth: 0,
|
||||
// 动画执行的时间
|
||||
animationDuration: '10s',
|
||||
// 动画执行状态
|
||||
animationPlayState: 'paused',
|
||||
// 当前显示的文本
|
||||
showText: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler(value) {
|
||||
this.showText = value.join(',')
|
||||
this.$nextTick(() => {
|
||||
this.initNotice()
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
playStatus(value) {
|
||||
if (value === 'play') this.animationPlayState = 'running'
|
||||
else this.animationPlayState = 'paused'
|
||||
},
|
||||
speed(value) {
|
||||
this.initNotice()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化通知栏
|
||||
initNotice() {
|
||||
let query = [],
|
||||
textBoxWidth = 0,
|
||||
textWidth = 0;
|
||||
let textQuery = new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#tn-row-notice__content`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.textWidth = ret[0].width
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
query.push(textQuery)
|
||||
|
||||
Promise.all(query).then(() => {
|
||||
// 根据t=s/v(时间=路程/速度),这里为何不需要加上#tn-row-notice__content-box的宽度,因为设置了.tn-row-notice__content样式中设置了padding-left: 100%
|
||||
this.animationDuration = `${this.textWidth / uni.upx2px(this.speed)}s`
|
||||
// 这里必须这样开始动画,否则在APP上动画速度不会改变(HX版本2.4.6,IOS13)
|
||||
this.animationPlayState = 'paused'
|
||||
setTimeout(() => {
|
||||
if (this.autoplay && this.playStatus === 'play') this.animationPlayState = 'running'
|
||||
}, 10)
|
||||
})
|
||||
},
|
||||
// 点击了通知栏
|
||||
click() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// 点击了关闭按钮
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
// 点击了左边图标
|
||||
clickLeftIcon() {
|
||||
this.$emit('clickLeft')
|
||||
},
|
||||
// 点击了右边图标
|
||||
clickRightIcon() {
|
||||
this.$emit('clickRight')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-row-notice {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__content {
|
||||
&-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
text-align: right;
|
||||
// 为了能滚动起来
|
||||
padding-left: 100%;
|
||||
animation: tn-notice-loop-animation 10s linear infinite both;
|
||||
|
||||
&--text {
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&--left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
margin-left: 12rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tn-notice-loop-animation {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-row-notice {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&__content {
|
||||
&-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
text-align: right;
|
||||
// 为了能滚动起来
|
||||
padding-left: 100%;
|
||||
animation: tn-notice-loop-animation 10s linear infinite both;
|
||||
|
||||
&--text {
|
||||
word-break: keep-all;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
&--left {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
margin-left: 12rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes tn-notice-loop-animation {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,177 +1,177 @@
|
||||
<template>
|
||||
<view class="tn-scroll-list-class tn-scroll-list">
|
||||
<scroll-view
|
||||
class="tn-scroll-list__scroll-view"
|
||||
scroll-x
|
||||
:upper-threshold="0"
|
||||
:lower-threshold="0"
|
||||
@scroll="scrollHandler"
|
||||
@scrolltoupper="scrollToUpperHandler"
|
||||
@scrolltolower="scrollToLowerHandler"
|
||||
>
|
||||
<view class="tn-scroll-list__scroll-view__content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 指示器-->
|
||||
<view
|
||||
v-if="indicator"
|
||||
class="tn-scroll-list__indicator"
|
||||
:style="[indicatorStyle]"
|
||||
>
|
||||
<view class="tn-scroll-list__indicator__line" :style="[lineStyle]">
|
||||
<view class="tn-scroll-list__indicator__line__bar" :style="[barStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-scroll-list-class tn-scroll-list">
|
||||
<scroll-view
|
||||
class="tn-scroll-list__scroll-view"
|
||||
scroll-x
|
||||
:upper-threshold="0"
|
||||
:lower-threshold="0"
|
||||
@scroll="scrollHandler"
|
||||
@scrolltoupper="scrollToUpperHandler"
|
||||
@scrolltolower="scrollToLowerHandler"
|
||||
>
|
||||
<view class="tn-scroll-list__scroll-view__content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 指示器-->
|
||||
<view
|
||||
v-if="indicator"
|
||||
class="tn-scroll-list__indicator"
|
||||
:style="[indicatorStyle]"
|
||||
>
|
||||
<view class="tn-scroll-list__indicator__line" :style="[lineStyle]">
|
||||
<view class="tn-scroll-list__indicator__line__bar" :style="[barStyle]"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-scroll-list',
|
||||
props: {
|
||||
// 是否显示指示器
|
||||
indicator: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 指示器整体宽度
|
||||
indicatorWidth: {
|
||||
type: [String, Number],
|
||||
default: 50
|
||||
},
|
||||
// 指示器滑块的宽度
|
||||
indicatorBarWidth: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 指示器颜色
|
||||
indicatorColor: {
|
||||
type: String,
|
||||
default: '#E6E6E6'
|
||||
},
|
||||
// 指示器激活时颜色
|
||||
indicatorActiveColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 自定义指示器样式
|
||||
indicatorStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 指示器滑块样式
|
||||
barStyle() {
|
||||
let style = {}
|
||||
// 获取滑动距离的比值
|
||||
// 滑块当前移动距离与总需滑动距离(指示器的总宽度减去滑块宽度)的比值 = scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比
|
||||
const scrollLeft = this.scrollInfo.scrollLeft,
|
||||
scrollWidth = this.scrollInfo.scrollWidth,
|
||||
barAllMoveWidth = uni.upx2px(this.indicatorWidth) - uni.upx2px(this.indicatorBarWidth);
|
||||
const x = scrollLeft / (scrollWidth - this.scrollWidth) * barAllMoveWidth
|
||||
style.transform = `translateX(${x}px)`
|
||||
// 设置滑块的宽度和背景颜色
|
||||
style.width = `${this.indicatorBarWidth}rpx`
|
||||
style.backgroundColor = this.indicatorActiveColor
|
||||
return style
|
||||
},
|
||||
// 指示器整体样式
|
||||
lineStyle() {
|
||||
let style = {}
|
||||
style.width = `${this.indicatorWidth}rpx`
|
||||
style.backgroundColor = this.indicatorColor
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 滑动时滑块信息
|
||||
scrollInfo: {
|
||||
scrollLeft: 0,
|
||||
scrollWidth: 0
|
||||
},
|
||||
// 滑动区域的宽度
|
||||
scrollWidth: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
this.getComponentWidth()
|
||||
},
|
||||
// 处理滚动事件
|
||||
scrollHandler(event) {
|
||||
this.scrollInfo = event.detail
|
||||
},
|
||||
// 滚动到最左边触发事件
|
||||
scrollToUpperHandler() {
|
||||
this.$emit('left')
|
||||
this.scrollInfo.scrollLeft = 0
|
||||
},
|
||||
// 滚动到最右边触发事件
|
||||
scrollToLowerHandler() {
|
||||
this.$emit('right')
|
||||
// this.scrollInfo.scrollLeft = uni.upx2px(this.indicatorWidth) - uni.upx2px(this.indicatorBarWidth)
|
||||
},
|
||||
// 获取组件的宽度
|
||||
getComponentWidth() {
|
||||
this._tGetRect('.tn-scroll-list').then(res => {
|
||||
if (!res) {
|
||||
setTimeout(() => {
|
||||
this.getComponentWidth()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
this.scrollWidth = res.width
|
||||
})
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-scroll-list',
|
||||
props: {
|
||||
// 是否显示指示器
|
||||
indicator: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 指示器整体宽度
|
||||
indicatorWidth: {
|
||||
type: [String, Number],
|
||||
default: 50
|
||||
},
|
||||
// 指示器滑块的宽度
|
||||
indicatorBarWidth: {
|
||||
type: [String, Number],
|
||||
default: 20
|
||||
},
|
||||
// 指示器颜色
|
||||
indicatorColor: {
|
||||
type: String,
|
||||
default: '#E6E6E6'
|
||||
},
|
||||
// 指示器激活时颜色
|
||||
indicatorActiveColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 自定义指示器样式
|
||||
indicatorStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 指示器滑块样式
|
||||
barStyle() {
|
||||
let style = {}
|
||||
// 获取滑动距离的比值
|
||||
// 滑块当前移动距离与总需滑动距离(指示器的总宽度减去滑块宽度)的比值 = scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比
|
||||
const scrollLeft = this.scrollInfo.scrollLeft,
|
||||
scrollWidth = this.scrollInfo.scrollWidth,
|
||||
barAllMoveWidth = uni.upx2px(this.indicatorWidth) - uni.upx2px(this.indicatorBarWidth);
|
||||
const x = scrollLeft / (scrollWidth - this.scrollWidth) * barAllMoveWidth
|
||||
style.transform = `translateX(${x}px)`
|
||||
// 设置滑块的宽度和背景颜色
|
||||
style.width = `${this.indicatorBarWidth}rpx`
|
||||
style.backgroundColor = this.indicatorActiveColor
|
||||
return style
|
||||
},
|
||||
// 指示器整体样式
|
||||
lineStyle() {
|
||||
let style = {}
|
||||
style.width = `${this.indicatorWidth}rpx`
|
||||
style.backgroundColor = this.indicatorColor
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 滑动时滑块信息
|
||||
scrollInfo: {
|
||||
scrollLeft: 0,
|
||||
scrollWidth: 0
|
||||
},
|
||||
// 滑动区域的宽度
|
||||
scrollWidth: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
this.getComponentWidth()
|
||||
},
|
||||
// 处理滚动事件
|
||||
scrollHandler(event) {
|
||||
this.scrollInfo = event.detail
|
||||
},
|
||||
// 滚动到最左边触发事件
|
||||
scrollToUpperHandler() {
|
||||
this.$emit('left')
|
||||
this.scrollInfo.scrollLeft = 0
|
||||
},
|
||||
// 滚动到最右边触发事件
|
||||
scrollToLowerHandler() {
|
||||
this.$emit('right')
|
||||
// this.scrollInfo.scrollLeft = uni.upx2px(this.indicatorWidth) - uni.upx2px(this.indicatorBarWidth)
|
||||
},
|
||||
// 获取组件的宽度
|
||||
getComponentWidth() {
|
||||
this._tGetRect('.tn-scroll-list').then(res => {
|
||||
if (!res) {
|
||||
setTimeout(() => {
|
||||
this.getComponentWidth()
|
||||
}, 10)
|
||||
return
|
||||
}
|
||||
this.scrollWidth = res.width
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-scroll-list {
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
&__scroll-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-top: 30rpx;
|
||||
|
||||
&__line {
|
||||
width: 120rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 200rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&__bar {
|
||||
width: 40rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-scroll-list {
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
&__scroll-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-top: 30rpx;
|
||||
|
||||
&__line {
|
||||
width: 120rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 200rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&__bar {
|
||||
width: 40rpx;
|
||||
height: 8rpx;
|
||||
border-radius: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,380 +1,380 @@
|
||||
<template>
|
||||
<view v-if="value" class="tn-select-class tn-select">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
:popup="false"
|
||||
length="auto"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:maskCloseable="maskCloseable"
|
||||
:zIndex="elZIndex"
|
||||
@close="close"
|
||||
>
|
||||
<view class="tn-select__content">
|
||||
<!-- 头部 -->
|
||||
<view class="tn-select__content__header" @touchmove.stop.prevent>
|
||||
<view
|
||||
class="tn-select__content__header__btn tn-select__content__header--cancel"
|
||||
:style="{ color: cancelColor }"
|
||||
hover-class="tn-hover-class"
|
||||
hover-stay-time="150"
|
||||
@tap="getResult('cancel')"
|
||||
>{{ cancelText }}</view>
|
||||
<view class="tn-select__content__header__title">{{ title }}</view>
|
||||
<view
|
||||
class="tn-select__content__header__btn tn-select__content__header--confirm"
|
||||
:style="{ color: confirmColor }"
|
||||
hover-class="tn-hover-class"
|
||||
hover-stay-time="150"
|
||||
@tap="getResult('confirm')"
|
||||
>{{ confirmText }}</view>
|
||||
</view>
|
||||
<!-- 列表内容 -->
|
||||
<view class="tn-select__content__body">
|
||||
<picker-view
|
||||
class="tn-select__content__body__view"
|
||||
:value="defaultSelector"
|
||||
@pickstart="pickStart"
|
||||
@pickend="pickEnd"
|
||||
@change="columnChange"
|
||||
>
|
||||
<picker-view-column v-for="(item, index) in columnData" :key="index">
|
||||
<view class="tn-select__content__body__item" v-for="(sub_item, sub_index) in item" :key="sub_index">
|
||||
<view class="tn-text-ellipsis">
|
||||
{{ sub_item[labelName] }}
|
||||
</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
<template>
|
||||
<view v-if="value" class="tn-select-class tn-select">
|
||||
<tn-popup
|
||||
v-model="value"
|
||||
mode="bottom"
|
||||
:popup="false"
|
||||
length="auto"
|
||||
:safeAreaInsetBottom="safeAreaInsetBottom"
|
||||
:maskCloseable="maskCloseable"
|
||||
:zIndex="elZIndex"
|
||||
@close="close"
|
||||
>
|
||||
<view class="tn-select__content">
|
||||
<!-- 头部 -->
|
||||
<view class="tn-select__content__header" @touchmove.stop.prevent>
|
||||
<view
|
||||
class="tn-select__content__header__btn tn-select__content__header--cancel"
|
||||
:style="{ color: cancelColor }"
|
||||
hover-class="tn-hover-class"
|
||||
hover-stay-time="150"
|
||||
@tap="getResult('cancel')"
|
||||
>{{ cancelText }}</view>
|
||||
<view class="tn-select__content__header__title">{{ title }}</view>
|
||||
<view
|
||||
class="tn-select__content__header__btn tn-select__content__header--confirm"
|
||||
:style="{ color: confirmColor }"
|
||||
hover-class="tn-hover-class"
|
||||
hover-stay-time="150"
|
||||
@tap="getResult('confirm')"
|
||||
>{{ confirmText }}</view>
|
||||
</view>
|
||||
<!-- 列表内容 -->
|
||||
<view class="tn-select__content__body">
|
||||
<picker-view
|
||||
class="tn-select__content__body__view"
|
||||
:value="defaultSelector"
|
||||
@pickstart="pickStart"
|
||||
@pickend="pickEnd"
|
||||
@change="columnChange"
|
||||
>
|
||||
<picker-view-column v-for="(item, index) in columnData" :key="index">
|
||||
<view class="tn-select__content__body__item" v-for="(sub_item, sub_index) in item" :key="sub_index">
|
||||
<view class="tn-text-ellipsis">
|
||||
{{ sub_item[labelName] }}
|
||||
</view>
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</tn-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-select',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 列表模式
|
||||
// single 单列 multi 多列 multi-auto 多列联动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
// 列数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// value属性名
|
||||
valueName: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// label属性名
|
||||
labelName: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
// 当mode=multi-auto时,children的属性名
|
||||
childName: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
// 默认值
|
||||
defaultValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0]
|
||||
}
|
||||
},
|
||||
// 顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 取消按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 取消按钮文字颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 确认按钮文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 确认按钮文字颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 点击遮罩关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 预留安全区域
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
|
||||
moving: false,
|
||||
// 用户保存当前列的索引,用于判断下一次变化时改变的列
|
||||
defaultSelector: [0],
|
||||
// picker-view数据
|
||||
columnData: [],
|
||||
// 保存用户选择的结果
|
||||
selectValue: [],
|
||||
// 上一次改变时的index
|
||||
lastSelectIndex: [],
|
||||
// 列数
|
||||
columnNum: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 在select弹起的时候,重新初始化所有数据
|
||||
value: {
|
||||
handler(val) {
|
||||
if (val) setTimeout(() => this.init(), 10)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 标识滑动开始,只有微信小程序才有这样的事件
|
||||
pickStart() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = true;
|
||||
// #endif
|
||||
},
|
||||
// 标识滑动结束
|
||||
pickEnd() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = false;
|
||||
// #endif
|
||||
},
|
||||
init() {
|
||||
this.setColumnNum()
|
||||
this.setDefaultSelector()
|
||||
this.setColumnData()
|
||||
this.setSelectValue()
|
||||
},
|
||||
// 获取默认选中列下标
|
||||
setDefaultSelector() {
|
||||
// 如果没有传入默认值,生成用0填充长度为columnNum的数组
|
||||
this.defaultSelector = this.defaultValue.length === this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0)
|
||||
this.lastSelectIndex = this.$t.deepClone(this.defaultSelector)
|
||||
},
|
||||
// 计算列数
|
||||
setColumnNum() {
|
||||
// 单列的数量为1
|
||||
if (this.mode === 'single') this.columnNum = 1
|
||||
// 多列时取list的长度
|
||||
else if (this.mode === 'multi') this.columnNum = this.list.length
|
||||
// 多列联动时,通过遍历list的第一个元素,得出有多少列
|
||||
else if (this.mode === 'multi-auto') {
|
||||
let num = 1
|
||||
let column = this.list
|
||||
// 如果存在children属性,再次遍历
|
||||
while (column[0][this.childName]) {
|
||||
column = column[0] ? column[0][this.childName] : {},
|
||||
num++
|
||||
}
|
||||
this.columnNum = num
|
||||
}
|
||||
},
|
||||
// 获取需要展示在picker中的列数据
|
||||
setColumnData() {
|
||||
let data = []
|
||||
this.selectValue = []
|
||||
if (this.mode === 'multi-auto') {
|
||||
// 获取所有数据中的第一个元素
|
||||
let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0]
|
||||
// 通过循环所有列数,再根据设定列的数组,得出当前需要渲染的整个列数组
|
||||
for (let i = 0; i < this.columnNum; i++) {
|
||||
// 第一列默认为整个list数组
|
||||
if (i === 0) {
|
||||
data[i] = this.list
|
||||
column = column[this.childName]
|
||||
} else {
|
||||
// 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项
|
||||
data[i] = column
|
||||
column = column[this.defaultSelector[i]][this.childName]
|
||||
}
|
||||
}
|
||||
} else if (this.mode === 'single') {
|
||||
data[0] = this.list
|
||||
} else {
|
||||
data = this.list
|
||||
}
|
||||
this.columnData = data
|
||||
},
|
||||
// 获取默认选中的值,如果没有设置,则默认选中第一项
|
||||
setSelectValue() {
|
||||
let tmp = null
|
||||
for (let i = 0; i < this.columnNum; i++) {
|
||||
tmp = this.columnData[i][this.defaultSelector[i]]
|
||||
let data = {
|
||||
value: tmp ? tmp[this.valueName] : null,
|
||||
label: tmp ? tmp[this.labelName] : null
|
||||
}
|
||||
// 判断是否存在额外的参数
|
||||
if (tmp && tmp.extra) data.extra = tmp.extra
|
||||
this.selectValue.push(data)
|
||||
}
|
||||
},
|
||||
// 列选项
|
||||
columnChange(event) {
|
||||
let index = null
|
||||
let columnIndex = event.detail.value
|
||||
|
||||
this.selectValue = []
|
||||
if (this.mode === 'multi-auto') {
|
||||
// 对比前后两个数组,判断变更的是那一列
|
||||
this.lastSelectIndex.map((v, idx) => {
|
||||
if (v != columnIndex[idx]) index = idx
|
||||
})
|
||||
this.defaultSelector = columnIndex
|
||||
// 当前变化列的下一列的数据,需要获取上一列的数据,同时需要指定是上一列的第几个的children,再往后的
|
||||
// 默认是队列的第一个为默认选项
|
||||
for (let i = index + 1; i < this.columnNum; i++) {
|
||||
this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName]
|
||||
this.defaultSelector[i] = 0
|
||||
}
|
||||
// 在历遍的过程中,可能由于上一步修改this.columnData,导致产生连锁反应,程序触发columnChange,会有多次调用
|
||||
// 只有在最后一次数据稳定后的结果是正确的,此前的历遍中,可能会产生undefined,故需要判断
|
||||
columnIndex.map((item, index) => {
|
||||
let data = this.columnData[index][columnIndex[index]]
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null
|
||||
}
|
||||
if (data && data.extra !== undefined) tmp.extra = data.extra
|
||||
this.selectValue.push(tmp)
|
||||
})
|
||||
this.lastSelectIndex = columnIndex
|
||||
} else if (this.mode === 'single') {
|
||||
let data = this.columnData[0][columnIndex[0]]
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null
|
||||
}
|
||||
if (data && data.extra !== undefined) tmp.extra = data.extra
|
||||
this.selectValue.push(tmp)
|
||||
} else if (this.mode === 'multi') {
|
||||
columnIndex.map((item, index) => {
|
||||
let data = this.columnData[index][columnIndex[index]]
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null
|
||||
}
|
||||
if (data && data.extra !== undefined) tmp.extra = data.extra
|
||||
this.selectValue.push(tmp)
|
||||
})
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
getResult(event = null) {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.moving) return;
|
||||
// #endif
|
||||
if (event) this.$emit(event, this.selectValue)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-select',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 列表模式
|
||||
// single 单列 multi 多列 multi-auto 多列联动
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
// 列数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// value属性名
|
||||
valueName: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// label属性名
|
||||
labelName: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
// 当mode=multi-auto时,children的属性名
|
||||
childName: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
},
|
||||
// 默认值
|
||||
defaultValue: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [0]
|
||||
}
|
||||
},
|
||||
// 顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 取消按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 取消按钮文字颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 确认按钮文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 确认按钮文字颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 点击遮罩关闭
|
||||
maskCloseable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 预留安全区域
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// zIndex
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.popup
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
|
||||
moving: false,
|
||||
// 用户保存当前列的索引,用于判断下一次变化时改变的列
|
||||
defaultSelector: [0],
|
||||
// picker-view数据
|
||||
columnData: [],
|
||||
// 保存用户选择的结果
|
||||
selectValue: [],
|
||||
// 上一次改变时的index
|
||||
lastSelectIndex: [],
|
||||
// 列数
|
||||
columnNum: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 在select弹起的时候,重新初始化所有数据
|
||||
value: {
|
||||
handler(val) {
|
||||
if (val) setTimeout(() => this.init(), 10)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 标识滑动开始,只有微信小程序才有这样的事件
|
||||
pickStart() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = true;
|
||||
// #endif
|
||||
},
|
||||
// 标识滑动结束
|
||||
pickEnd() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.moving = false;
|
||||
// #endif
|
||||
},
|
||||
init() {
|
||||
this.setColumnNum()
|
||||
this.setDefaultSelector()
|
||||
this.setColumnData()
|
||||
this.setSelectValue()
|
||||
},
|
||||
// 获取默认选中列下标
|
||||
setDefaultSelector() {
|
||||
// 如果没有传入默认值,生成用0填充长度为columnNum的数组
|
||||
this.defaultSelector = this.defaultValue.length === this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0)
|
||||
this.lastSelectIndex = this.$t.deepClone(this.defaultSelector)
|
||||
},
|
||||
// 计算列数
|
||||
setColumnNum() {
|
||||
// 单列的数量为1
|
||||
if (this.mode === 'single') this.columnNum = 1
|
||||
// 多列时取list的长度
|
||||
else if (this.mode === 'multi') this.columnNum = this.list.length
|
||||
// 多列联动时,通过遍历list的第一个元素,得出有多少列
|
||||
else if (this.mode === 'multi-auto') {
|
||||
let num = 1
|
||||
let column = this.list
|
||||
// 如果存在children属性,再次遍历
|
||||
while (column[0][this.childName]) {
|
||||
column = column[0] ? column[0][this.childName] : {},
|
||||
num++
|
||||
}
|
||||
this.columnNum = num
|
||||
}
|
||||
},
|
||||
// 获取需要展示在picker中的列数据
|
||||
setColumnData() {
|
||||
let data = []
|
||||
this.selectValue = []
|
||||
if (this.mode === 'multi-auto') {
|
||||
// 获取所有数据中的第一个元素
|
||||
let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0]
|
||||
// 通过循环所有列数,再根据设定列的数组,得出当前需要渲染的整个列数组
|
||||
for (let i = 0; i < this.columnNum; i++) {
|
||||
// 第一列默认为整个list数组
|
||||
if (i === 0) {
|
||||
data[i] = this.list
|
||||
column = column[this.childName]
|
||||
} else {
|
||||
// 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项
|
||||
data[i] = column
|
||||
column = column[this.defaultSelector[i]][this.childName]
|
||||
}
|
||||
}
|
||||
} else if (this.mode === 'single') {
|
||||
data[0] = this.list
|
||||
} else {
|
||||
data = this.list
|
||||
}
|
||||
this.columnData = data
|
||||
},
|
||||
// 获取默认选中的值,如果没有设置,则默认选中第一项
|
||||
setSelectValue() {
|
||||
let tmp = null
|
||||
for (let i = 0; i < this.columnNum; i++) {
|
||||
tmp = this.columnData[i][this.defaultSelector[i]]
|
||||
let data = {
|
||||
value: tmp ? tmp[this.valueName] : null,
|
||||
label: tmp ? tmp[this.labelName] : null
|
||||
}
|
||||
// 判断是否存在额外的参数
|
||||
if (tmp && tmp.extra) data.extra = tmp.extra
|
||||
this.selectValue.push(data)
|
||||
}
|
||||
},
|
||||
// 列选项
|
||||
columnChange(event) {
|
||||
let index = null
|
||||
let columnIndex = event.detail.value
|
||||
|
||||
this.selectValue = []
|
||||
if (this.mode === 'multi-auto') {
|
||||
// 对比前后两个数组,判断变更的是那一列
|
||||
this.lastSelectIndex.map((v, idx) => {
|
||||
if (v != columnIndex[idx]) index = idx
|
||||
})
|
||||
this.defaultSelector = columnIndex
|
||||
// 当前变化列的下一列的数据,需要获取上一列的数据,同时需要指定是上一列的第几个的children,再往后的
|
||||
// 默认是队列的第一个为默认选项
|
||||
for (let i = index + 1; i < this.columnNum; i++) {
|
||||
this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName]
|
||||
this.defaultSelector[i] = 0
|
||||
}
|
||||
// 在历遍的过程中,可能由于上一步修改this.columnData,导致产生连锁反应,程序触发columnChange,会有多次调用
|
||||
// 只有在最后一次数据稳定后的结果是正确的,此前的历遍中,可能会产生undefined,故需要判断
|
||||
columnIndex.map((item, index) => {
|
||||
let data = this.columnData[index][columnIndex[index]]
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null
|
||||
}
|
||||
if (data && data.extra !== undefined) tmp.extra = data.extra
|
||||
this.selectValue.push(tmp)
|
||||
})
|
||||
this.lastSelectIndex = columnIndex
|
||||
} else if (this.mode === 'single') {
|
||||
let data = this.columnData[0][columnIndex[0]]
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null
|
||||
}
|
||||
if (data && data.extra !== undefined) tmp.extra = data.extra
|
||||
this.selectValue.push(tmp)
|
||||
} else if (this.mode === 'multi') {
|
||||
columnIndex.map((item, index) => {
|
||||
let data = this.columnData[index][columnIndex[index]]
|
||||
let tmp = {
|
||||
value: data ? data[this.valueName] : null,
|
||||
label: data ? data[this.labelName] : null
|
||||
}
|
||||
if (data && data.extra !== undefined) tmp.extra = data.extra
|
||||
this.selectValue.push(tmp)
|
||||
})
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.$emit('input', false)
|
||||
},
|
||||
getResult(event = null) {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.moving) return;
|
||||
// #endif
|
||||
if (event) this.$emit(event, this.selectValue)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-select {
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
padding: 0 40rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__btn {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: $tn-font-color;
|
||||
}
|
||||
|
||||
&--cancel {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--confirm {
|
||||
color: $tn-main-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
overflow: hidden;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__view {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: $tn-font-color;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-select {
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
padding: 0 40rpx;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__btn {
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: $tn-font-color;
|
||||
}
|
||||
|
||||
&--cancel {
|
||||
color: $tn-font-sub-color;
|
||||
}
|
||||
|
||||
&--confirm {
|
||||
color: $tn-main-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 500rpx;
|
||||
overflow: hidden;
|
||||
background-color: #FFFFFF;
|
||||
|
||||
&__view {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32rpx;
|
||||
color: $tn-font-color;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,254 +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>
|
||||
<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.color.getBackgroundColorStyle(this.elBackgroundColor)
|
||||
},
|
||||
elBackgroundColorClass() {
|
||||
return this.$t.color.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>
|
||||
|
||||
@@ -1,254 +1,255 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-slider-class tn-slider"
|
||||
:class="{'tn-slider--disabled': disabled}"
|
||||
:style="{
|
||||
backgroundColor: inactiveColor
|
||||
}"
|
||||
@tap="click"
|
||||
>
|
||||
<!-- slider滑动线 -->
|
||||
<view
|
||||
class="tn-slider__gap"
|
||||
:style="[
|
||||
barStyle,
|
||||
{
|
||||
height: this.$t.string.getLengthUnitValue(lineHeight),
|
||||
backgroundColor: activeColor
|
||||
}
|
||||
]"
|
||||
>
|
||||
<!-- slider滑块 -->
|
||||
<view
|
||||
class="tn-slider__button-wrap"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
@touchcancel="touchEnd"
|
||||
>
|
||||
<view v-if="$slots.default || $slots.$default">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="tn-slider__button"
|
||||
:style="[blockStyle, {
|
||||
height: this.$t.string.getLengthUnitValue(blockWidth),
|
||||
width: this.$t.string.getLengthUnitValue(blockWidth),
|
||||
backgroundColor: blockColor
|
||||
}]"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
class="tn-slider-class tn-slider"
|
||||
:class="{'tn-slider--disabled': disabled}"
|
||||
:style="{
|
||||
backgroundColor: inactiveColor
|
||||
}"
|
||||
@tap="click"
|
||||
>
|
||||
<!-- slider滑动线 -->
|
||||
<view
|
||||
class="tn-slider__gap"
|
||||
:style="[
|
||||
barStyle,
|
||||
{
|
||||
height: this.$t.string.getLengthUnitValue(lineHeight),
|
||||
backgroundColor: activeColor
|
||||
}
|
||||
]"
|
||||
>
|
||||
<!-- slider滑块 -->
|
||||
<view
|
||||
class="tn-slider__button-wrap"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
@touchcancel="touchEnd"
|
||||
>
|
||||
<view v-if="$slots.default || $slots.$default">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="tn-slider__button"
|
||||
:style="[blockStyle, {
|
||||
height: this.$t.string.getLengthUnitValue(blockWidth),
|
||||
width: this.$t.string.getLengthUnitValue(blockWidth),
|
||||
backgroundColor: blockColor
|
||||
}]"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-slider',
|
||||
props: {
|
||||
// 进度值
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// 步进值
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 滑块宽度
|
||||
blockWidth: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 滑动条高度
|
||||
lineHeight: {
|
||||
type: Number,
|
||||
default: 8
|
||||
},
|
||||
// 滑动条激活的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 滑动条未被激活的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#E6E6E6'
|
||||
},
|
||||
// 滑块的颜色
|
||||
blockColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 自定义滑块的样式
|
||||
blockStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
startX: 0,
|
||||
status: 'end',
|
||||
newValue: 0,
|
||||
distanceX: 0,
|
||||
startValue: 0,
|
||||
barStyle: {},
|
||||
sliderRect: {
|
||||
left: 0,
|
||||
width: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
// 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发
|
||||
if (this.status === 'end') this.updateValue(val, false)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateValue(this.value, false)
|
||||
},
|
||||
mounted() {
|
||||
this._tGetRect('.tn-slider').then(res => {
|
||||
this.sliderRect = res
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 开始滑动
|
||||
touchStart(event) {
|
||||
if (this.disabled) return
|
||||
if (!event.changedTouches[0]) return
|
||||
|
||||
this.startX = 0
|
||||
// 触摸点
|
||||
this.startX = event.changedTouches[0].pageX
|
||||
this.startValue = this.format(this.value)
|
||||
|
||||
// 标识当前开始触摸
|
||||
this.status = 'start'
|
||||
},
|
||||
// 滑动移动中
|
||||
touchMove(event) {
|
||||
if (this.disabled) return
|
||||
if (!event.changedTouches[0]) return
|
||||
|
||||
// 连续触摸的过程会一直触发本方法,但只有手指触发且移动了才被认为是拖动了,才发出事件
|
||||
// 触摸后第一次移动已经将status设置为moving状态,故触摸第二次移动不会触发本事件
|
||||
if (this.status === 'start') this.$emit('start')
|
||||
let movePageX = event.changedTouches[0].pageX
|
||||
// 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
|
||||
this.distanceX = movePageX - this.sliderRect.left
|
||||
// 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,不能用此更新视图
|
||||
// 否则造成通信阻塞,需要每改变一个step值时修改一次视图
|
||||
this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + this.min
|
||||
this.status = 'moving'
|
||||
this.$emit('moving')
|
||||
|
||||
this.updateValue(this.newValue, true)
|
||||
},
|
||||
// 滑动结束
|
||||
touchEnd() {
|
||||
if(this.disabled) return
|
||||
if (this.status === 'moving') {
|
||||
this.updateValue(this.newValue, false)
|
||||
this.$emit('end')
|
||||
}
|
||||
this.status = 'end'
|
||||
},
|
||||
// 更新数值
|
||||
updateValue(value, drag) {
|
||||
// 去掉小数部分,对step进行步进处理
|
||||
value = this.format(value)
|
||||
const width = Math.round((value - this.min) / (this.max - this.min) * 100)
|
||||
// 不允许滑动的距离小于0和超过100
|
||||
if (width < 0 || width > 100) return
|
||||
// 设置移动的百分比
|
||||
let barStyle = {
|
||||
width: width + '%'
|
||||
}
|
||||
// 移动期间取消动画
|
||||
if (drag === true) {
|
||||
barStyle.transition = 'none'
|
||||
} else {
|
||||
// 非移动期间,删掉对过渡为空的声明,让css中的声明起效
|
||||
delete barStyle.transition
|
||||
}
|
||||
|
||||
// 修改value值
|
||||
this.$emit('input', value)
|
||||
this.barStyle = barStyle
|
||||
},
|
||||
// 点击事件
|
||||
click(event) {
|
||||
if (this.disabled) return
|
||||
// 直接点击的情况,计算方式和touchMove方法一致
|
||||
const value = (((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * (this.max - this.min)) + this.min
|
||||
this.updateValue(value, false)
|
||||
},
|
||||
// 格式化滑动的值
|
||||
format(value) {
|
||||
return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-slider',
|
||||
props: {
|
||||
// 进度值
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// 步进值
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
// 禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 滑块宽度
|
||||
blockWidth: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 滑动条高度
|
||||
lineHeight: {
|
||||
type: Number,
|
||||
default: 8
|
||||
},
|
||||
// 滑动条激活的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 滑动条未被激活的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#E6E6E6'
|
||||
},
|
||||
// 滑块的颜色
|
||||
blockColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 自定义滑块的样式
|
||||
blockStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
startX: 0,
|
||||
status: 'end',
|
||||
newValue: 0,
|
||||
distanceX: 0,
|
||||
startValue: 0,
|
||||
barStyle: {},
|
||||
sliderRect: {
|
||||
left: 0,
|
||||
width: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
// 只有在非滑动状态时,才可以通过value更新滑块值,这里监听,是为了让用户触发
|
||||
if (this.status === 'end') this.updateValue(val, false)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.updateValue(this.value, false)
|
||||
},
|
||||
mounted() {
|
||||
this._tGetRect('.tn-slider').then(res => {
|
||||
this.sliderRect = res
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 开始滑动
|
||||
touchStart(event) {
|
||||
if (this.disabled) return
|
||||
if (!event.changedTouches[0]) return
|
||||
|
||||
this.startX = 0
|
||||
// 触摸点
|
||||
this.startX = event.changedTouches[0].pageX
|
||||
this.startValue = this.format(this.value)
|
||||
|
||||
// 标识当前开始触摸
|
||||
this.status = 'start'
|
||||
},
|
||||
// 滑动移动中
|
||||
touchMove(event) {
|
||||
if (this.disabled) return
|
||||
if (!event.changedTouches[0]) return
|
||||
|
||||
// 连续触摸的过程会一直触发本方法,但只有手指触发且移动了才被认为是拖动了,才发出事件
|
||||
// 触摸后第一次移动已经将status设置为moving状态,故触摸第二次移动不会触发本事件
|
||||
if (this.status === 'start') this.$emit('start')
|
||||
let movePageX = event.changedTouches[0].pageX
|
||||
// 滑块的左边不一定跟屏幕左边接壤,所以需要减去最外层父元素的左边值
|
||||
this.distanceX = movePageX - this.sliderRect.left
|
||||
// 获得移动距离对整个滑块的百分比值,此为带有多位小数的值,不能用此更新视图
|
||||
// 否则造成通信阻塞,需要每改变一个step值时修改一次视图
|
||||
this.newValue = ((this.distanceX / this.sliderRect.width) * (this.max - this.min)) + this.min
|
||||
this.status = 'moving'
|
||||
this.$emit('moving')
|
||||
|
||||
this.updateValue(this.newValue, true)
|
||||
},
|
||||
// 滑动结束
|
||||
touchEnd() {
|
||||
if(this.disabled) return
|
||||
if (this.status === 'moving') {
|
||||
this.updateValue(this.newValue, false)
|
||||
this.$emit('end')
|
||||
}
|
||||
this.status = 'end'
|
||||
},
|
||||
// 更新数值
|
||||
updateValue(value, drag) {
|
||||
// 去掉小数部分,对step进行步进处理
|
||||
value = this.format(value)
|
||||
const width = Math.round((value - this.min) / (this.max - this.min) * 100)
|
||||
// 不允许滑动的距离小于0和超过100
|
||||
if (width < 0 || width > 100) return
|
||||
// 设置移动的百分比
|
||||
let barStyle = {
|
||||
width: width + '%'
|
||||
}
|
||||
// 移动期间取消动画
|
||||
if (drag === true) {
|
||||
barStyle.transition = 'none'
|
||||
} else {
|
||||
// 非移动期间,删掉对过渡为空的声明,让css中的声明起效
|
||||
delete barStyle.transition
|
||||
}
|
||||
|
||||
// 修改value值
|
||||
this.$emit('input', value)
|
||||
this.barStyle = barStyle
|
||||
},
|
||||
// 点击事件
|
||||
click(event) {
|
||||
if (this.disabled) return
|
||||
// 直接点击的情况,计算方式和touchMove方法一致
|
||||
const value = (((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * (this.max - this.min)) + this.min
|
||||
this.updateValue(value, false)
|
||||
},
|
||||
// 格式化滑动的值
|
||||
format(value) {
|
||||
return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-slider {
|
||||
position: relative;
|
||||
border-radius: 1000rpx;
|
||||
// 增加点击的范围
|
||||
border-width: 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
background-color: $tn-font-holder-color;
|
||||
background-clip: content-box;
|
||||
|
||||
&__gap {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
transition: width 0.2s;
|
||||
background-color: #01BEFF;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
|
||||
background-color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
|
||||
&-wrap {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate3d(50%, -50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.tn-slider {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-radius: 1000rpx;
|
||||
// 增加点击的范围
|
||||
border-width: 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
background-color: $tn-font-holder-color;
|
||||
background-clip: content-box;
|
||||
|
||||
&__gap {
|
||||
position: relative;
|
||||
border-radius: inherit;
|
||||
transition: width 0.2s;
|
||||
background-color: #01BEFF;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
|
||||
background-color: #FFFFFF;
|
||||
cursor: pointer;
|
||||
|
||||
&-wrap {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translate3d(50%, -50%, 0);
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,346 +1,346 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-steps-class tn-steps"
|
||||
:style="{
|
||||
flexDirection: direction
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-steps__item"
|
||||
:class="[`tn-steps__item--${direction}`]"
|
||||
@tap="clickStepItem(index)"
|
||||
>
|
||||
<!-- 数值模式 -->
|
||||
<view
|
||||
v-if="mode === 'number'"
|
||||
class="tn-steps__item__number"
|
||||
:style="{
|
||||
backgroundColor: currentIndex <= index ? 'transparent' : activeColor,
|
||||
borderColor: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
>
|
||||
<text
|
||||
class="tn-steps__item__number__text"
|
||||
:class="[{'tn-steps__item__number__text--visible': currentIndex <= index}]"
|
||||
:style="{
|
||||
color: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
>
|
||||
{{ index + 1}}
|
||||
</text>
|
||||
<view class="tn-steps__item__number__icon" :class="[`tn-icon-${item.icon || icon}`,{'tn-steps__item__number__icon--visible': currentIndex > index}]"></view>
|
||||
</view>
|
||||
|
||||
<!-- 点模式 -->
|
||||
<view
|
||||
v-if="mode === 'dot'"
|
||||
class="tn-steps__item__dot"
|
||||
:style="{
|
||||
backgroundColor: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
></view>
|
||||
|
||||
<!-- 图标模式 -->
|
||||
<view
|
||||
v-if="mode === 'icon'"
|
||||
class="tn-steps__item__icon"
|
||||
:class="[iconModeClass(index)]"
|
||||
:style="{
|
||||
color: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
></view>
|
||||
|
||||
<!-- 点图标模式 -->
|
||||
<view v-if="mode === 'dotIcon'" class="tn-steps__item__dot-icon">
|
||||
<view v-if="currentIndex <= index" class="tn-steps__item__dot-icon--dot" :style="{backgroundColor: inActiveColor}"></view>
|
||||
<view v-else class="tn-steps__item__dot-icon--icon" :class="[iconModeClass(index)]" :style="{color: activeColor}"></view>
|
||||
</view>
|
||||
|
||||
<!-- 步骤下的文字 -->
|
||||
<text
|
||||
v-if="showTitle"
|
||||
class="tn-steps__item__text tn-text-ellipsis"
|
||||
:class="[`tn-steps__item__text--${direction}`]"
|
||||
:style="{
|
||||
color: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
>
|
||||
{{ item.name }}
|
||||
</text>
|
||||
|
||||
<!-- 连接的横线 -->
|
||||
<view
|
||||
v-if="index < list.length - 1"
|
||||
class="tn-steps__item__line"
|
||||
:class="[`tn-steps__item__line--${mode}`]"
|
||||
:style="{
|
||||
borderColor: currentIndex <= index + 1 ? inActiveColor : activeColor
|
||||
}"
|
||||
></view>
|
||||
</view>
|
||||
<template>
|
||||
<view
|
||||
class="tn-steps-class tn-steps"
|
||||
:style="{
|
||||
flexDirection: direction
|
||||
}"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-steps__item"
|
||||
:class="[`tn-steps__item--${direction}`]"
|
||||
@tap="clickStepItem(index)"
|
||||
>
|
||||
<!-- 数值模式 -->
|
||||
<view
|
||||
v-if="mode === 'number'"
|
||||
class="tn-steps__item__number"
|
||||
:style="{
|
||||
backgroundColor: currentIndex <= index ? 'transparent' : activeColor,
|
||||
borderColor: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
>
|
||||
<text
|
||||
class="tn-steps__item__number__text"
|
||||
:class="[{'tn-steps__item__number__text--visible': currentIndex <= index}]"
|
||||
:style="{
|
||||
color: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
>
|
||||
{{ index + 1}}
|
||||
</text>
|
||||
<view class="tn-steps__item__number__icon" :class="[`tn-icon-${item.icon || icon}`,{'tn-steps__item__number__icon--visible': currentIndex > index}]"></view>
|
||||
</view>
|
||||
|
||||
<!-- 点模式 -->
|
||||
<view
|
||||
v-if="mode === 'dot'"
|
||||
class="tn-steps__item__dot"
|
||||
:style="{
|
||||
backgroundColor: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
></view>
|
||||
|
||||
<!-- 图标模式 -->
|
||||
<view
|
||||
v-if="mode === 'icon'"
|
||||
class="tn-steps__item__icon"
|
||||
:class="[iconModeClass(index)]"
|
||||
:style="{
|
||||
color: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
></view>
|
||||
|
||||
<!-- 点图标模式 -->
|
||||
<view v-if="mode === 'dotIcon'" class="tn-steps__item__dot-icon">
|
||||
<view v-if="currentIndex <= index" class="tn-steps__item__dot-icon--dot" :style="{backgroundColor: inActiveColor}"></view>
|
||||
<view v-else class="tn-steps__item__dot-icon--icon" :class="[iconModeClass(index)]" :style="{color: activeColor}"></view>
|
||||
</view>
|
||||
|
||||
<!-- 步骤下的文字 -->
|
||||
<text
|
||||
v-if="showTitle"
|
||||
class="tn-steps__item__text tn-text-ellipsis"
|
||||
:class="[`tn-steps__item__text--${direction}`]"
|
||||
:style="{
|
||||
color: currentIndex <= index ? inActiveColor : activeColor
|
||||
}"
|
||||
>
|
||||
{{ item.name }}
|
||||
</text>
|
||||
|
||||
<!-- 连接的横线 -->
|
||||
<view
|
||||
v-if="index < list.length - 1"
|
||||
class="tn-steps__item__line"
|
||||
:class="[`tn-steps__item__line--${mode}`]"
|
||||
:style="{
|
||||
borderColor: currentIndex <= index + 1 ? inActiveColor : activeColor
|
||||
}"
|
||||
></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-steps',
|
||||
props: {
|
||||
// 模式类型
|
||||
// dot -> 点 number -> 数字 icon -> 图标 dotIcon -> 点图标
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'dot'
|
||||
},
|
||||
// 步骤条的方向
|
||||
// row -> 横向 column -> 竖向
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 步骤条数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 当前激活的步数
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 激活步骤的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 未激活步骤的颜色
|
||||
inActiveColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 激活后显示的图标,在数字模式下有效
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// icon模式下图标的值
|
||||
iconModeClass() {
|
||||
return (index) => {
|
||||
const item = this.list[index]
|
||||
// 状态被选中并且对应数据下存在selectIcon属性
|
||||
if (this.currentIndex > index && item.hasOwnProperty('selectIcon')) {
|
||||
return `tn-icon-${item.selectIcon}`
|
||||
} else {
|
||||
// 未选中
|
||||
return `tn-icon-${item.icon || this.icon}`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current: {
|
||||
handler(val) {
|
||||
this.currentIndex = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击了某一个选项
|
||||
clickStepItem(index) {
|
||||
const chooseIndex = index + 1
|
||||
this.currentIndex = chooseIndex
|
||||
this.$emit('click', { index: chooseIndex })
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-steps',
|
||||
props: {
|
||||
// 模式类型
|
||||
// dot -> 点 number -> 数字 icon -> 图标 dotIcon -> 点图标
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'dot'
|
||||
},
|
||||
// 步骤条的方向
|
||||
// row -> 横向 column -> 竖向
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
// 步骤条数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 当前激活的步数
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 激活步骤的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 未激活步骤的颜色
|
||||
inActiveColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 激活后显示的图标,在数字模式下有效
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// icon模式下图标的值
|
||||
iconModeClass() {
|
||||
return (index) => {
|
||||
const item = this.list[index]
|
||||
// 状态被选中并且对应数据下存在selectIcon属性
|
||||
if (this.currentIndex > index && item.hasOwnProperty('selectIcon')) {
|
||||
return `tn-icon-${item.selectIcon}`
|
||||
} else {
|
||||
// 未选中
|
||||
return `tn-icon-${item.icon || this.icon}`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current: {
|
||||
handler(val) {
|
||||
this.currentIndex = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击了某一个选项
|
||||
clickStepItem(index) {
|
||||
const chooseIndex = index + 1
|
||||
this.currentIndex = chooseIndex
|
||||
this.$emit('click', { index: chooseIndex })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$tn-steps-item-number-width: 44rpx;
|
||||
$tn-steps-item-dot-width: 20rpx;
|
||||
|
||||
.tn-steps {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
min-width: 100rpx;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
|
||||
&__number {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
position: relative;
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
line-height: calc(#{$tn-steps-item-number-width} - 2rpx);
|
||||
border: 1px solid #AAAAAA;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
transition: inherit;
|
||||
transform: translateY(-#{$tn-steps-item-number-width});
|
||||
|
||||
&--visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
color: #FFFFFF;
|
||||
transition: all 0.3s linear;
|
||||
transform: translateY(#{$tn-steps-item-number-width});
|
||||
|
||||
&--visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: $tn-steps-item-dot-width;
|
||||
height: $tn-steps-item-dot-width;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&--icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
font-size: $tn-steps-item-number-width;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
&__dot-icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&--dot {
|
||||
width: $tn-steps-item-dot-width;
|
||||
height: $tn-steps-item-dot-width;
|
||||
border-radius: 50%;
|
||||
transition: inherit;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
font-size: $tn-steps-item-number-width;
|
||||
transition: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
transition: all 0.3s linear;
|
||||
&--row {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
&--column {
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__line {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
vertical-align: middle;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
&--row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tn-steps__item__line {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
width: 50%;
|
||||
left: 75%;
|
||||
|
||||
&--dot {
|
||||
top: calc(#{$tn-steps-item-dot-width} / 2);
|
||||
}
|
||||
|
||||
&--number, &--icon, &--dotIcon {
|
||||
top: calc(#{$tn-steps-item-number-width} / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
min-height: 120rpx;
|
||||
|
||||
.tn-steps__item__line {
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
height: 50%;
|
||||
top: 75%;
|
||||
|
||||
&--dot {
|
||||
left: calc(#{$tn-steps-item-dot-width} / 2);
|
||||
}
|
||||
|
||||
&--number, &--icon, &--dotIcon {
|
||||
left: calc(#{$tn-steps-item-number-width} / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$tn-steps-item-number-width: 44rpx;
|
||||
$tn-steps-item-dot-width: 20rpx;
|
||||
|
||||
.tn-steps {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
min-width: 100rpx;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
|
||||
&__number {
|
||||
// display: flex;
|
||||
// flex-wrap: wrap;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
position: relative;
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
line-height: calc(#{$tn-steps-item-number-width} - 2rpx);
|
||||
border: 1px solid #AAAAAA;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
transition: inherit;
|
||||
transform: translateY(-#{$tn-steps-item-number-width});
|
||||
|
||||
&--visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
color: #FFFFFF;
|
||||
transition: all 0.3s linear;
|
||||
transform: translateY(#{$tn-steps-item-number-width});
|
||||
|
||||
&--visible {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: $tn-steps-item-dot-width;
|
||||
height: $tn-steps-item-dot-width;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&--icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
font-size: $tn-steps-item-number-width;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
&__dot-icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s linear;
|
||||
|
||||
&--dot {
|
||||
width: $tn-steps-item-dot-width;
|
||||
height: $tn-steps-item-dot-width;
|
||||
border-radius: 50%;
|
||||
transition: inherit;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
width: $tn-steps-item-number-width;
|
||||
height: $tn-steps-item-number-width;
|
||||
font-size: $tn-steps-item-number-width;
|
||||
transition: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
transition: all 0.3s linear;
|
||||
&--row {
|
||||
margin-top: 14rpx;
|
||||
}
|
||||
|
||||
&--column {
|
||||
margin-left: 14rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&__line {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
vertical-align: middle;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
&--row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tn-steps__item__line {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
width: 50%;
|
||||
left: 75%;
|
||||
|
||||
&--dot {
|
||||
top: calc(#{$tn-steps-item-dot-width} / 2);
|
||||
}
|
||||
|
||||
&--number, &--icon, &--dotIcon {
|
||||
top: calc(#{$tn-steps-item-number-width} / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--column {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
min-height: 120rpx;
|
||||
|
||||
.tn-steps__item__line {
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
height: 50%;
|
||||
top: 75%;
|
||||
|
||||
&--dot {
|
||||
left: calc(#{$tn-steps-item-dot-width} / 2);
|
||||
}
|
||||
|
||||
&--number, &--icon, &--dotIcon {
|
||||
left: calc(#{$tn-steps-item-number-width} / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,186 +1,186 @@
|
||||
<template>
|
||||
<view class="tn-sticky-class">
|
||||
<view
|
||||
class="tn-sticky__wrap"
|
||||
:class="[stickyClass]"
|
||||
:style="[stickyStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-sticky__item"
|
||||
:style="{
|
||||
position: fixed ? 'fixed' : 'static',
|
||||
top: stickyTop + 'px',
|
||||
left: left + 'px',
|
||||
width: width === 'auto' ? 'auto' : width + 'px',
|
||||
zIndex: elZIndex
|
||||
}"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-sticky',
|
||||
props: {
|
||||
// 吸顶容器到顶部某个距离的时候进行吸顶
|
||||
// 在H5中,customNavBar的高度为45px
|
||||
offsetTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// H5顶部导航栏的高度
|
||||
h5NavHeight: {
|
||||
type: Number,
|
||||
default: 45
|
||||
},
|
||||
// 自定义顶部导航栏高度
|
||||
customNavHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否开启吸顶
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 吸顶容器的背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 索引值,区分不同的吸顶组件
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.sticky
|
||||
},
|
||||
backgroundColorStyle() {
|
||||
return this.$t.colorUtils.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.colorUtils.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
stickyClass() {
|
||||
let clazz = ''
|
||||
clazz += this.elClass
|
||||
if (this.backgroundColorClass) {
|
||||
clazz += ` ${this.backgroundColorClass}`
|
||||
}
|
||||
return clazz
|
||||
},
|
||||
stickyStyle() {
|
||||
let style = {}
|
||||
style.height = this.fixed ? this.height + 'px' : 'auto'
|
||||
if (this.backgroundColorStyle) {
|
||||
style.color = this.backgroundColorStyle
|
||||
}
|
||||
if (this.elZIndex) {
|
||||
style.zIndex = this.elZIndex
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 监听组件别名
|
||||
stickyObserverName: 'tnStickyObserver',
|
||||
// 组件的唯一编号
|
||||
elClass: this.$t.uuid(),
|
||||
// 是否固定
|
||||
fixed: false,
|
||||
// 高度
|
||||
height: 'auto',
|
||||
// 宽度
|
||||
width: 'auto',
|
||||
// 距离顶部的距离
|
||||
stickyTop: 0,
|
||||
// 左边距离
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
offsetTop(val) {
|
||||
this.initObserver()
|
||||
},
|
||||
enabled(val) {
|
||||
if (val === false) {
|
||||
this.fixed = false
|
||||
this.disconnectObserver(this.stickyObserverName)
|
||||
} else {
|
||||
this.initObserver()
|
||||
}
|
||||
},
|
||||
customNavHeight(val) {
|
||||
this.initObserver()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initObserver()
|
||||
},
|
||||
methods: {
|
||||
// 初始化监听组件的布局状态
|
||||
initObserver() {
|
||||
if (!this.enabled) return
|
||||
// #ifdef H5
|
||||
this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.h5NavHeight : this.h5NavHeight
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.customNavHeight : this.customNavHeight
|
||||
// #endif
|
||||
|
||||
this.disconnectObserver(this.stickyObserverName)
|
||||
this._tGetRect('.' + this.elClass).then((res) => {
|
||||
this.height = res.height
|
||||
this.left = res.left
|
||||
this.width = res.width
|
||||
this.$nextTick(() => {
|
||||
this.connectObserver()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 监听组件的布局状态
|
||||
connectObserver() {
|
||||
this.disconnectObserver(this.stickyObserverName)
|
||||
// 组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver
|
||||
const contentObserver = this.createIntersectionObserver({
|
||||
thresholds: [0.95, 0.98, 1]
|
||||
})
|
||||
contentObserver.relativeToViewport({
|
||||
top: -this.stickyTop
|
||||
})
|
||||
contentObserver.observe('.' + this.elClass, res => {
|
||||
if (!this.enabled) return
|
||||
this.setFixed(res.boundingClientRect.top)
|
||||
})
|
||||
this[this.stickyObserverName] = contentObserver
|
||||
},
|
||||
// 设置是否固定
|
||||
setFixed(top) {
|
||||
const fixed = top < this.stickyTop
|
||||
if (fixed) this.$emit('fixed', this.index)
|
||||
else if (this.fixed) this.$emit('unfixed', this.index)
|
||||
this.fixed = fixed
|
||||
},
|
||||
// 停止监听组件的布局状态
|
||||
disconnectObserver(observerName) {
|
||||
const observer = this[observerName]
|
||||
observer && observer.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-sticky-class">
|
||||
<view
|
||||
class="tn-sticky__wrap"
|
||||
:class="[stickyClass]"
|
||||
:style="[stickyStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-sticky__item"
|
||||
:style="{
|
||||
position: fixed ? 'fixed' : 'static',
|
||||
top: stickyTop + 'px',
|
||||
left: left + 'px',
|
||||
width: width === 'auto' ? 'auto' : width + 'px',
|
||||
zIndex: elZIndex
|
||||
}"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-sticky',
|
||||
props: {
|
||||
// 吸顶容器到顶部某个距离的时候进行吸顶
|
||||
// 在H5中,customNavBar的高度为45px
|
||||
offsetTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// H5顶部导航栏的高度
|
||||
h5NavHeight: {
|
||||
type: Number,
|
||||
default: 45
|
||||
},
|
||||
// 自定义顶部导航栏高度
|
||||
customNavHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 是否开启吸顶
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 吸顶容器的背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 索引值,区分不同的吸顶组件
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
elZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$t.zIndex.sticky
|
||||
},
|
||||
backgroundColorStyle() {
|
||||
return this.$t.color.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.color.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
stickyClass() {
|
||||
let clazz = ''
|
||||
clazz += this.elClass
|
||||
if (this.backgroundColorClass) {
|
||||
clazz += ` ${this.backgroundColorClass}`
|
||||
}
|
||||
return clazz
|
||||
},
|
||||
stickyStyle() {
|
||||
let style = {}
|
||||
style.height = this.fixed ? this.height + 'px' : 'auto'
|
||||
if (this.backgroundColorStyle) {
|
||||
style.color = this.backgroundColorStyle
|
||||
}
|
||||
if (this.elZIndex) {
|
||||
style.zIndex = this.elZIndex
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 监听组件别名
|
||||
stickyObserverName: 'tnStickyObserver',
|
||||
// 组件的唯一编号
|
||||
elClass: this.$t.uuid(),
|
||||
// 是否固定
|
||||
fixed: false,
|
||||
// 高度
|
||||
height: 'auto',
|
||||
// 宽度
|
||||
width: 'auto',
|
||||
// 距离顶部的距离
|
||||
stickyTop: 0,
|
||||
// 左边距离
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
offsetTop(val) {
|
||||
this.initObserver()
|
||||
},
|
||||
enabled(val) {
|
||||
if (val === false) {
|
||||
this.fixed = false
|
||||
this.disconnectObserver(this.stickyObserverName)
|
||||
} else {
|
||||
this.initObserver()
|
||||
}
|
||||
},
|
||||
customNavHeight(val) {
|
||||
this.initObserver()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initObserver()
|
||||
},
|
||||
methods: {
|
||||
// 初始化监听组件的布局状态
|
||||
initObserver() {
|
||||
if (!this.enabled) return
|
||||
// #ifdef H5
|
||||
this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.h5NavHeight : this.h5NavHeight
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.customNavHeight : this.customNavHeight
|
||||
// #endif
|
||||
|
||||
this.disconnectObserver(this.stickyObserverName)
|
||||
this._tGetRect('.' + this.elClass).then((res) => {
|
||||
this.height = res.height
|
||||
this.left = res.left
|
||||
this.width = res.width
|
||||
this.$nextTick(() => {
|
||||
this.connectObserver()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 监听组件的布局状态
|
||||
connectObserver() {
|
||||
this.disconnectObserver(this.stickyObserverName)
|
||||
// 组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver
|
||||
const contentObserver = this.createIntersectionObserver({
|
||||
thresholds: [0.95, 0.98, 1]
|
||||
})
|
||||
contentObserver.relativeToViewport({
|
||||
top: -this.stickyTop
|
||||
})
|
||||
contentObserver.observe('.' + this.elClass, res => {
|
||||
if (!this.enabled) return
|
||||
this.setFixed(res.boundingClientRect.top)
|
||||
})
|
||||
this[this.stickyObserverName] = contentObserver
|
||||
},
|
||||
// 设置是否固定
|
||||
setFixed(top) {
|
||||
const fixed = top < this.stickyTop
|
||||
if (fixed) this.$emit('fixed', this.index)
|
||||
else if (this.fixed) this.$emit('unfixed', this.index)
|
||||
this.fixed = fixed
|
||||
},
|
||||
// 停止监听组件的布局状态
|
||||
disconnectObserver(observerName) {
|
||||
const observer = this[observerName]
|
||||
observer && observer.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,381 +1,410 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-subsection-class tn-subsection"
|
||||
:class="[subsectionBackgroundColorClass]"
|
||||
:style="[subsectionStyle]"
|
||||
>
|
||||
<!-- 滑块 -->
|
||||
<block v-for="(item, index) in listInfo" :key="index">
|
||||
<view
|
||||
class="tn-subsection__item tn-text-ellipsis"
|
||||
:class="[
|
||||
'section-item-' + index,
|
||||
noBorderRight(index)
|
||||
]"
|
||||
:style="[itemStyle(index)]"
|
||||
@tap="click(index)"
|
||||
>
|
||||
<view class="tn-subsection__item--text tn-text-ellipsis" :style="[textStyle(index)]">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 背景 -->
|
||||
<view class="tn-subsection__bg" :style="[itemBarStyle]"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-subsection',
|
||||
props: {
|
||||
// 模式选择
|
||||
// button 按钮模式 subsection 分段模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'button'
|
||||
},
|
||||
// 组件高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
// tab的数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 当前活动tab的index
|
||||
current: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 激活时的字体颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 未激活时的字体颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 激活tab的字体是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#F4F4F4'
|
||||
},
|
||||
// 滑块的颜色
|
||||
buttonColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 是否开启动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滑动滑块的是否,是否触发震动
|
||||
vibrateShort: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 列表数据
|
||||
listInfo: [],
|
||||
// 子元素的背景样式
|
||||
itemBgStyle: {
|
||||
width: 0,
|
||||
left: 0,
|
||||
backgroundColor: '#ffffff',
|
||||
height: '100%',
|
||||
transition: ''
|
||||
},
|
||||
// 当前选中的滑块
|
||||
currentIndex: this.current,
|
||||
buttonPadding: 3,
|
||||
borderRadius: 5,
|
||||
// 组件初始化的是否current变换不应该震动
|
||||
firstVibrateShort: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current: {
|
||||
handler(val) {
|
||||
this.currentIndex = val
|
||||
this.changeSectionStatus(val)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 将list的数据,传入listInfo数组
|
||||
// 接受直接数组形式,或者数组元素为对象的形式,如:['开启', '关闭'],或者[{name: '开启'}, {name: '关闭'}]
|
||||
this.listInfo = this.list.map((item, index) => {
|
||||
if (typeof item !== 'object') {
|
||||
let obj = {
|
||||
width: 0,
|
||||
name: item
|
||||
}
|
||||
return obj
|
||||
} else {
|
||||
item.width = 0
|
||||
return obj
|
||||
}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
// 设置mode=subsection时,滑块没有样式
|
||||
noBorderRight() {
|
||||
return index => {
|
||||
if (this.mode !== 'subsection') return
|
||||
let clazz = ''
|
||||
// 不显示右边的边距
|
||||
if (index < this.list.length - 1) clazz += ' tn-subsection__item--none-border-right'
|
||||
// 显示整个组件的左右边圆角
|
||||
if (index === 0) clazz += ' tn-subsection__item--first'
|
||||
if (index === this.list.length - 1) clazz += ' tn-subsection__item--last'
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
// 文字的样式
|
||||
textStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
// 设置字体颜色
|
||||
if (this.mode === 'subsection') {
|
||||
if (index === this.currentIndex) {
|
||||
style.color = '#FFFFFF'
|
||||
} else {
|
||||
style.color = this.inactiveColor
|
||||
}
|
||||
} else {
|
||||
if (index === this.currentIndex) {
|
||||
style.color = this.activeColor
|
||||
} else {
|
||||
style.color = this.inactiveColor
|
||||
}
|
||||
}
|
||||
// 字体加粗
|
||||
if (index === this.currentIndex && this.bold) style.fontWeight = 'bold'
|
||||
// 文字大小
|
||||
style.fontSize = (this.fontSize || 26) + this.fontUnit
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 每个分段器item的样式
|
||||
itemStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
if (this.mode === 'subsection') {
|
||||
// 设置border的样式
|
||||
style.borderColor = this.inactiveColor
|
||||
style.borderWidth = '1rpx'
|
||||
style.borderStyle = 'solid'
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// mode = button时,设置外层view的样式
|
||||
subsectionStyle() {
|
||||
let style = {}
|
||||
style.height = this.height + 'rpx'
|
||||
if (this.mode === 'button') {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
style.padding = `${this.buttonPadding}px`
|
||||
style.borderRadius = `${this.borderRadius}px`
|
||||
}
|
||||
return style
|
||||
},
|
||||
// mode = button时,设置外层view的背景class
|
||||
subsectionBackgroundColorClass() {
|
||||
let clazz = ''
|
||||
if (this.mode === 'button' && this.backgroundColorClass) {
|
||||
clazz = this.backgroundColorClass
|
||||
}
|
||||
return clazz
|
||||
},
|
||||
// 滑块样式
|
||||
itemBarStyle() {
|
||||
let style = {}
|
||||
style.backgroundColor = this.buttonColor
|
||||
style.zIndex = 1
|
||||
if (this.mode === 'button') {
|
||||
style.backgroundColor = this.buttonColor
|
||||
style.borderRadius = `${this.borderRadius}px`
|
||||
style.bottom = `${this.buttonPadding}px`
|
||||
style.height = (this.height - (this.buttonPadding * 4)) + 'rpx'
|
||||
style.zIndex = 0
|
||||
}
|
||||
return Object.assign(this.itemBgStyle, style)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 等待加载组件完成
|
||||
setTimeout(() => {
|
||||
this.getTabsInfo()
|
||||
}, 10)
|
||||
},
|
||||
methods: {
|
||||
// 改变滑块样式
|
||||
changeSectionStatus(val) {
|
||||
if (this.mode === 'subsection') {
|
||||
// 根据滑块在最左和最右时,显示对应的圆角
|
||||
if (val === this.list.length - 1) {
|
||||
this.itemBgStyle.borderRadius = `0 ${this.buttonPadding}px ${this.buttonPadding}px 0`
|
||||
}
|
||||
if (val === 0) {
|
||||
this.itemBgStyle.borderRadius = `${this.buttonPadding}px 0 0 ${this.buttonPadding}px`
|
||||
}
|
||||
if (val > 0 && val < this.list.length - 1) {
|
||||
this.itemBgStyle.borderRadius = '0'
|
||||
}
|
||||
}
|
||||
// 更新滑块的位置
|
||||
setTimeout(() => {
|
||||
this.itemBgLeft()
|
||||
}, 10)
|
||||
if (this.vibrateShort && !this.firstVibrateShort) {
|
||||
// 使手机产生短促震动,微信小程序有效,APP(HX 2.6.8)和H5无效
|
||||
// #ifndef H5
|
||||
uni.vibrateShort();
|
||||
// #endif
|
||||
}
|
||||
this.firstVibrateShort = false
|
||||
},
|
||||
// 获取各个tab的节点信息
|
||||
getTabsInfo() {
|
||||
let view = uni.createSelectorQuery().in(this)
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
view.select('.section-item-' + i).boundingClientRect()
|
||||
}
|
||||
view.exec(res => {
|
||||
// 如果没有获取到,则重新获取
|
||||
if (!res.length) {
|
||||
setTimeout(() => {
|
||||
this.getTabsInfo()
|
||||
return
|
||||
}, 10)
|
||||
}
|
||||
// 将每个分段器的宽度放入listInfo中
|
||||
res.map((item, index) => {
|
||||
this.listInfo[index].width = item.width
|
||||
})
|
||||
// 初始化滑块的宽度
|
||||
if (this.mode === 'subsection') {
|
||||
this.itemBgStyle.width = this.listInfo[0].width + 'px'
|
||||
} else if (this.mode === 'button') {
|
||||
this.itemBgStyle.width = this.listInfo[0].width + 'px'
|
||||
}
|
||||
|
||||
// 初始化滑块的位置
|
||||
this.itemBgLeft()
|
||||
})
|
||||
},
|
||||
// 设置滑块的位置
|
||||
itemBgLeft() {
|
||||
// 是否开启动画
|
||||
if(this.animation) {
|
||||
this.itemBgStyle.transition = 'all 0.3s'
|
||||
} else {
|
||||
this.itemBgStyle.transition = 'all 0s'
|
||||
}
|
||||
|
||||
let left = 0
|
||||
// 计算当前活跃item到组件左边的距离
|
||||
this.listInfo.map((item, index) => {
|
||||
if (index < this.currentIndex) left += item.width
|
||||
})
|
||||
// 根据不同的模式,计算滑块的位置
|
||||
if (this.mode === 'subsection') {
|
||||
this.itemBgStyle.left = left + 'px'
|
||||
} else if (this.mode === 'button') {
|
||||
this.itemBgStyle.left = left + this.buttonPadding + 'px'
|
||||
}
|
||||
},
|
||||
|
||||
// 点击事件
|
||||
click(index) {
|
||||
// 不允许点击当前激活的选项
|
||||
if (index === this.currentIndex) return
|
||||
this.currentIndex = index
|
||||
this.changeSectionStatus(index)
|
||||
this.$emit('change', {
|
||||
index: Number(index),
|
||||
name: this.listInfo[index]['name']
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-subsection {
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&__item {
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
padding: 0 6rpx;
|
||||
|
||||
&--text {
|
||||
transform: all 0.3s;
|
||||
color: #FFFFFF;
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&--first {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--last {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--none-border-right {
|
||||
border-right: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__bg {
|
||||
background-color: $tn-main-color;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-subsection-class tn-subsection"
|
||||
:class="[subsectionBackgroundColorClass]"
|
||||
:style="[subsectionStyle]"
|
||||
>
|
||||
<!-- 滑块 -->
|
||||
<block v-for="(item, index) in listInfo" :key="index">
|
||||
<view
|
||||
class="tn-subsection__item tn-text-ellipsis"
|
||||
:class="[
|
||||
'section-item-' + index,
|
||||
noBorderRight(index)
|
||||
]"
|
||||
:style="[itemStyle(index)]"
|
||||
@tap="click(index)"
|
||||
>
|
||||
<view class="tn-subsection__item--text tn-text-ellipsis" :style="[textStyle(index)]">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 背景 -->
|
||||
<view
|
||||
class="tn-subsection__bg"
|
||||
:class="[itemBarClass]"
|
||||
:style="[itemBarStyle]"
|
||||
></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColorMixin],
|
||||
name: 'tn-subsection',
|
||||
props: {
|
||||
// 模式选择
|
||||
// button 按钮模式 subsection 分段模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'subsection'
|
||||
},
|
||||
// 组件高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
// tab的数据
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 当前活动tab的index
|
||||
current: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 激活时的字体颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
// 未激活时的字体颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#AAAAAA'
|
||||
},
|
||||
// 激活tab的字体是否加粗
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#F4F4F4'
|
||||
},
|
||||
// 滑块的颜色
|
||||
buttonColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 当mode为button时生效,圆角的值,单位rpx
|
||||
borderRadius: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
// 是否开启动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 动画类型
|
||||
// cubic-bezier -> 贝塞尔曲线
|
||||
animationType: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 滑动滑块的是否,是否触发震动
|
||||
vibrateShort: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 列表数据
|
||||
listInfo: [],
|
||||
// 子元素的背景样式
|
||||
itemBgStyle: {
|
||||
width: 0,
|
||||
left: 0,
|
||||
backgroundColor: '#ffffff',
|
||||
height: '100%'
|
||||
},
|
||||
// 当前选中的滑块
|
||||
currentIndex: this.current,
|
||||
buttonPadding: 3,
|
||||
// 组件初始化的是否current变换不应该震动
|
||||
firstVibrateShort: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current: {
|
||||
handler(val) {
|
||||
this.currentIndex = val
|
||||
this.changeSectionStatus(val)
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 将list的数据,传入listInfo数组
|
||||
// 接受直接数组形式,或者数组元素为对象的形式,如:['开启', '关闭'],或者[{name: '开启'}, {name: '关闭'}]
|
||||
this.listInfo = this.list.map((item, index) => {
|
||||
if (typeof item !== 'object') {
|
||||
let obj = {
|
||||
width: 0,
|
||||
name: item
|
||||
}
|
||||
return obj
|
||||
} else {
|
||||
item.width = 0
|
||||
return obj
|
||||
}
|
||||
})
|
||||
},
|
||||
computed: {
|
||||
// 设置mode=subsection时,滑块没有样式
|
||||
noBorderRight() {
|
||||
return index => {
|
||||
if (this.mode !== 'subsection') return
|
||||
let clazz = ''
|
||||
// 不显示右边的边距
|
||||
if (index < this.list.length - 1) clazz += ' tn-subsection__item--none-border-right'
|
||||
// 显示整个组件的左右边圆角
|
||||
if (index === 0) clazz += ' tn-subsection__item--first'
|
||||
if (index === this.list.length - 1) clazz += ' tn-subsection__item--last'
|
||||
return clazz
|
||||
}
|
||||
},
|
||||
// 文字的样式
|
||||
textStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
// 设置字体颜色
|
||||
if (index === this.currentIndex) {
|
||||
style.color = this.activeColor
|
||||
} else {
|
||||
style.color = this.inactiveColor
|
||||
}
|
||||
// 字体加粗
|
||||
if (index === this.currentIndex && this.bold) style.fontWeight = 'bold'
|
||||
// 文字大小
|
||||
style.fontSize = (this.fontSize || 26) + this.fontUnit
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 每个分段器item的样式
|
||||
itemStyle() {
|
||||
return index => {
|
||||
let style = {}
|
||||
if (this.fontSizeStyle) {
|
||||
style.fontSize = this.fontSizeStyle
|
||||
}
|
||||
if (this.mode === 'subsection') {
|
||||
// 设置border的样式
|
||||
style.borderColor = this.buttonColor
|
||||
style.borderWidth = '1rpx'
|
||||
style.borderStyle = 'solid'
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// mode = button时,设置外层view的样式
|
||||
subsectionStyle() {
|
||||
let style = {}
|
||||
style.height = this.height + 'rpx'
|
||||
if (this.mode === 'button') {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
style.padding = `${this.buttonPadding}px`
|
||||
style.borderRadius = `${this.borderRadius}rpx`
|
||||
}
|
||||
return style
|
||||
},
|
||||
// mode = button时,设置外层view的背景class
|
||||
subsectionBackgroundColorClass() {
|
||||
let clazz = ''
|
||||
if (this.mode === 'button' && this.backgroundColorClass) {
|
||||
clazz = this.backgroundColorClass
|
||||
}
|
||||
return clazz
|
||||
},
|
||||
itemBarClass() {
|
||||
let clazz = ''
|
||||
const buttonBgClass = this.$t.color.getBackgroundColorInternalClass(this.buttonColor)
|
||||
if (this.animation) {
|
||||
clazz += ' tn-subsection__bg__animation'
|
||||
if (this.animationType) {
|
||||
clazz += ` tn-subsection__bg__animation--${this.animationType}`
|
||||
}
|
||||
}
|
||||
if (buttonBgClass) {
|
||||
clazz += ` ${buttonBgClass}`
|
||||
}
|
||||
return clazz
|
||||
},
|
||||
// 滑块样式
|
||||
itemBarStyle() {
|
||||
let style = {}
|
||||
const buttonBgStyle = this.$t.color.getBackgroundColorStyle(this.buttonColor)
|
||||
if (buttonBgStyle) {
|
||||
style.backgroundColor = this.buttonColor
|
||||
}
|
||||
style.zIndex = 1
|
||||
if (this.mode === 'button') {
|
||||
style.borderRadius = `${this.borderRadius}rpx`
|
||||
style.bottom = `${this.buttonPadding}px`
|
||||
style.height = (this.height - (this.buttonPadding * 4)) + 'rpx'
|
||||
style.zIndex = 0
|
||||
}
|
||||
return Object.assign(this.itemBgStyle, style)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 等待加载组件完成
|
||||
setTimeout(() => {
|
||||
this.getTabsInfo()
|
||||
}, 10)
|
||||
},
|
||||
methods: {
|
||||
// 改变滑块样式
|
||||
changeSectionStatus(val) {
|
||||
if (this.mode === 'subsection') {
|
||||
// 根据滑块在最左和最右时,显示对应的圆角
|
||||
if (val === this.list.length - 1) {
|
||||
this.itemBgStyle.borderRadius = `0 ${this.buttonPadding}px ${this.buttonPadding}px 0`
|
||||
}
|
||||
if (val === 0) {
|
||||
this.itemBgStyle.borderRadius = `${this.buttonPadding}px 0 0 ${this.buttonPadding}px`
|
||||
}
|
||||
if (val > 0 && val < this.list.length - 1) {
|
||||
this.itemBgStyle.borderRadius = '0'
|
||||
}
|
||||
}
|
||||
// 更新滑块的位置
|
||||
setTimeout(() => {
|
||||
this.itemBgLeft()
|
||||
}, 10)
|
||||
if (this.vibrateShort && !this.firstVibrateShort) {
|
||||
// 使手机产生短促震动,微信小程序有效,APP(HX 2.6.8)和H5无效
|
||||
// #ifndef H5
|
||||
uni.vibrateShort();
|
||||
// #endif
|
||||
}
|
||||
this.firstVibrateShort = false
|
||||
},
|
||||
// 获取各个tab的节点信息
|
||||
getTabsInfo() {
|
||||
let view = uni.createSelectorQuery().in(this)
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
view.select('.section-item-' + i).boundingClientRect()
|
||||
}
|
||||
view.exec(res => {
|
||||
// 如果没有获取到,则重新获取
|
||||
if (!res.length) {
|
||||
setTimeout(() => {
|
||||
this.getTabsInfo()
|
||||
return
|
||||
}, 10)
|
||||
}
|
||||
// 将每个分段器的宽度放入listInfo中
|
||||
res.map((item, index) => {
|
||||
this.listInfo[index].width = item.width
|
||||
})
|
||||
// 初始化滑块的宽度
|
||||
if (this.mode === 'subsection') {
|
||||
this.itemBgStyle.width = this.listInfo[0].width + 'px'
|
||||
} else if (this.mode === 'button') {
|
||||
this.itemBgStyle.width = this.listInfo[0].width + 'px'
|
||||
}
|
||||
|
||||
// 初始化滑块的位置
|
||||
this.itemBgLeft()
|
||||
})
|
||||
},
|
||||
// 设置滑块的位置
|
||||
itemBgLeft() {
|
||||
let left = 0
|
||||
// 计算当前活跃item到组件左边的距离
|
||||
this.listInfo.map((item, index) => {
|
||||
if (index < this.currentIndex) left += item.width
|
||||
})
|
||||
// 根据不同的模式,计算滑块的位置
|
||||
if (this.mode === 'subsection') {
|
||||
this.itemBgStyle.left = left + 'px'
|
||||
} else if (this.mode === 'button') {
|
||||
this.itemBgStyle.left = left + this.buttonPadding + 'px'
|
||||
}
|
||||
},
|
||||
|
||||
// 点击事件
|
||||
click(index) {
|
||||
// 不允许点击当前激活的选项
|
||||
if (index === this.currentIndex) return
|
||||
this.currentIndex = index
|
||||
this.changeSectionStatus(index)
|
||||
this.$emit('change', {
|
||||
index: Number(index),
|
||||
name: this.listInfo[index]['name']
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-subsection {
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
&__item {
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #FFFFFF;
|
||||
padding: 0 6rpx;
|
||||
|
||||
|
||||
&--text {
|
||||
transition: all 0.3s;
|
||||
color: #FFFFFF;
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
&--first {
|
||||
border-top-left-radius: 8rpx;
|
||||
border-bottom-left-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--last {
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 8rpx;
|
||||
}
|
||||
|
||||
&--none-border-right {
|
||||
border-right: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__bg {
|
||||
background-color: $tn-main-color;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
transition-property: all;
|
||||
transition-duration: 0s;
|
||||
transition-timing-function: linear;
|
||||
|
||||
&__animation {
|
||||
transition-duration: 0.25s !important;
|
||||
|
||||
&--cubic-bezier {
|
||||
transition-timing-function: cubic-bezier(0.68, -0.55, 0.265, 1.55) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,220 +1,230 @@
|
||||
/**
|
||||
* 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
|
||||
* wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
|
||||
*/
|
||||
|
||||
// 开始触摸
|
||||
function touchStart(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
var touches = event.touches
|
||||
// 如果进行的是多指触控,不允许操作
|
||||
if (touches && touches.length > 1) return
|
||||
// 标识当前为滑动中状态
|
||||
state.moving = true
|
||||
// 记录触摸开始点的坐标点
|
||||
state.startX = touches[0].pageX
|
||||
state.startY = touches[0].pageY
|
||||
|
||||
ownerInstance.callMethod('closeOther')
|
||||
}
|
||||
|
||||
// 触摸滑动
|
||||
function touchMove(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (state.disabled || !state.moving) return
|
||||
var touches = event.touches
|
||||
var pageX = touches[0].pageX
|
||||
var pageY = touches[0].pageY
|
||||
var moveX = pageX - state.startX
|
||||
var moveY = pageY - state.startY
|
||||
var buttonsWidth = state.buttonsWidth
|
||||
|
||||
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
|
||||
event.preventDefault && event.preventDefault()
|
||||
event.stopPropagation && event.stopPropagation()
|
||||
}
|
||||
// 移动的Y轴距离大于X轴距离,也即终点与起点位置连线,与Y轴夹角小于45度时,认为页面时上下滑动而不是左右滑动单元格
|
||||
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||
|
||||
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,所以处理的方法是在超出后设置为0
|
||||
if (state.status === 'open') {
|
||||
// 在开启状态下,忽略左滑动
|
||||
if (moveX < 0) moveX = 0
|
||||
// 要收起菜单,最大能移动的距离为按钮的总宽度
|
||||
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||
// 如果是已经打开的状态,向左滑动时,移动收起菜单
|
||||
moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
|
||||
} else {
|
||||
// 关闭状态下,忽略右滑
|
||||
if (moveX > 0) return
|
||||
// 滑动的距离不允许超过所有按钮的总宽度,此时只能左滑
|
||||
// 滑动距离设置为按钮的宽度(负数)
|
||||
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||
// 在滑动过程中不断移动单元格内容,使其不断显示出来
|
||||
moveSwipeAction(moveX, instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸结束
|
||||
function touchEnd(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (!state.moving || state.disabled) return
|
||||
var touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||
var pageX = touches.pageX
|
||||
var pageY = touches.pageY
|
||||
var moveX = pageX - state.startX
|
||||
if (state.status === 'open') {
|
||||
// 在开启状态下,忽略左滑动
|
||||
if (moveX < 0) moveX = 0
|
||||
// 在开启状态下,点击一下内容区域,moveX为0,也即没有移动,这是执行收起操作
|
||||
if (moveX === 0) {
|
||||
return closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 在开启状态下,滑动距离小于阈值,则默认不关闭同时恢复原来的打开状态
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
// 如果滑动距离大于阈值则执行收起逻辑
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
} else {
|
||||
// 在关闭状态下,忽略右滑动
|
||||
if (moveX > 0) return
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取过渡时间
|
||||
function getDuration(value) {
|
||||
if (value.toString().indexOf('s') >= 0) return value
|
||||
return value > 30 ? value + 'ms' : value + 's'
|
||||
}
|
||||
|
||||
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||
function moveSwipeAction(moveX, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.tn-swipe-action-item__right__button')
|
||||
|
||||
// 设置菜单内容部分的偏移
|
||||
instance.requestAnimationFrame(function () {
|
||||
instance.setStyle({
|
||||
// 设置translateX的值
|
||||
'transition': 'none',
|
||||
transform: 'translateX('+ moveX +'px)',
|
||||
'-webkit-transform': 'translateX('+ moveX +'px)'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 一次性展开滑动菜单
|
||||
function openSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.tn-swipe-action-item__right__button')
|
||||
// 处理duration单位问题
|
||||
var duration = getDuration(state.duration)
|
||||
// 展开过程中,是向左移动,所以x的偏移应该是负值
|
||||
var buttonsWidth = -state.buttonsWidth
|
||||
instance.requestAnimationFrame(function () {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX('+ buttonsWidth +'px)',
|
||||
'-webkit-transform': 'translateX('+ buttonsWidth +'px)'
|
||||
})
|
||||
})
|
||||
setStatus('open', instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 一次性收起滑动菜单
|
||||
function closeSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.tn-swipe-action-item__right__button')
|
||||
var len = buttons.length
|
||||
// 处理duration单位问题
|
||||
var duration = getDuration(state.duration)
|
||||
instance.requestAnimationFrame(function () {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
// 设置各个隐藏按钮的收起状态
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
buttons[i].setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
}
|
||||
})
|
||||
setStatus('close', instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 标记菜单的当前状态,open - 打开 close - 关闭
|
||||
function setStatus(status, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
state.status = status
|
||||
ownerInstance.callMethod('setStatus', status)
|
||||
}
|
||||
|
||||
// status的状态发生变化
|
||||
function statusChange(newValue, oldValue, ownerInstance, instance) {
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
// 打开或关闭单元格
|
||||
if (newValue === 'close' && state.status === 'open') {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else if (newValue === 'open' && state.status === 'close') {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单尺寸发生变化
|
||||
function sizeChange(newValue, oldValue, ownerInstance, instance) {
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
state.disabled = newValue.disabled
|
||||
state.duration = newValue.duration
|
||||
state.show = newValue.show
|
||||
state.threshold = newValue.threshold
|
||||
state.buttons = newValue.buttons
|
||||
|
||||
if (state.buttons) {
|
||||
var len = state.buttons.length
|
||||
var buttonsWidth = 0
|
||||
var buttons = newValue.buttons
|
||||
for (var i = 0; i < len; i++) {
|
||||
buttonsWidth += buttons[i].width
|
||||
}
|
||||
}
|
||||
state.buttonsWidth = buttonsWidth
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchStart: touchStart,
|
||||
touchMove: touchMove,
|
||||
touchEnd: touchEnd,
|
||||
sizeChange: sizeChange,
|
||||
statusChange: statusChange
|
||||
/**
|
||||
* 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
|
||||
* wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
|
||||
*/
|
||||
|
||||
// 开始触摸
|
||||
function touchStart(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
var touches = event.touches
|
||||
// 如果进行的是多指触控,不允许操作
|
||||
if (touches && touches.length > 1) return
|
||||
// 标识当前为滑动中状态
|
||||
state.moving = true
|
||||
// 记录触摸开始点的坐标点
|
||||
state.startX = touches[0].pageX
|
||||
state.startY = touches[0].pageY
|
||||
// 记录开始触摸的时间
|
||||
state.touchStartTime = getDate().getTime()
|
||||
|
||||
ownerInstance.callMethod('closeOther')
|
||||
}
|
||||
|
||||
// 触摸滑动
|
||||
function touchMove(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (state.disabled || !state.moving) return
|
||||
var touches = event.touches
|
||||
var pageX = touches[0].pageX
|
||||
var pageY = touches[0].pageY
|
||||
var moveX = pageX - state.startX
|
||||
var moveY = pageY - state.startY
|
||||
var buttonsWidth = state.buttonsWidth
|
||||
|
||||
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
|
||||
// event.preventDefault && event.preventDefault()
|
||||
// event.stopPropagation && event.stopPropagation()
|
||||
}
|
||||
// 移动的Y轴距离大于X轴距离,也即终点与起点位置连线,与Y轴夹角小于45度时,认为页面时上下滑动而不是左右滑动单元格
|
||||
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||
|
||||
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,所以处理的方法是在超出后设置为0
|
||||
if (state.status === 'open') {
|
||||
// 在开启状态下,忽略左滑动
|
||||
if (moveX < 0) moveX = 0
|
||||
// 要收起菜单,最大能移动的距离为按钮的总宽度
|
||||
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||
// 如果是已经打开的状态,向左滑动时,移动收起菜单
|
||||
moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
|
||||
} else {
|
||||
// 关闭状态下,忽略右滑
|
||||
if (moveX > 0) return
|
||||
// 滑动的距离不允许超过所有按钮的总宽度,此时只能左滑
|
||||
// 滑动距离设置为按钮的宽度(负数)
|
||||
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||
// 在滑动过程中不断移动单元格内容,使其不断显示出来
|
||||
moveSwipeAction(moveX, instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸结束
|
||||
function touchEnd(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (!state.moving || state.disabled) return
|
||||
var touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||
var pageX = touches.pageX
|
||||
var pageY = touches.pageY
|
||||
var moveX = pageX - state.startX
|
||||
if (state.status === 'open') {
|
||||
// 在开启状态下,忽略左滑动
|
||||
if (moveX < 0) moveX = 0
|
||||
// 在开启状态下,点击一下内容区域,moveX为0,也即没有移动,这是执行收起操作
|
||||
if (moveX === 0) {
|
||||
return closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 在开启状态下,滑动距离小于阈值,则默认不关闭同时恢复原来的打开状态
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
// 如果滑动距离大于阈值则执行收起逻辑
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
} else {
|
||||
|
||||
// 获取手指离开的时间
|
||||
var touchEndTime = getDate().getTime()
|
||||
// 判断是否点击了
|
||||
if (Math.abs(pageX - state.startX) < 5 && Math.abs(pageY - state.startY) < 5 && touchEndTime - state.touchStartTime < 100) {
|
||||
ownerInstance.callMethod('handlerItemClick')
|
||||
}
|
||||
|
||||
// 在关闭状态下,忽略右滑动
|
||||
if (moveX > 0) return
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取过渡时间
|
||||
function getDuration(value) {
|
||||
if (value.toString().indexOf('s') >= 0) return value
|
||||
return value > 30 ? value + 'ms' : value + 's'
|
||||
}
|
||||
|
||||
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||
function moveSwipeAction(moveX, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.tn-swipe-action-item__right__button')
|
||||
|
||||
// 设置菜单内容部分的偏移
|
||||
instance.requestAnimationFrame(function () {
|
||||
instance.setStyle({
|
||||
// 设置translateX的值
|
||||
'transition': 'none',
|
||||
transform: 'translateX('+ moveX +'px)',
|
||||
'-webkit-transform': 'translateX('+ moveX +'px)'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 一次性展开滑动菜单
|
||||
function openSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.tn-swipe-action-item__right__button')
|
||||
// 处理duration单位问题
|
||||
var duration = getDuration(state.duration)
|
||||
// 展开过程中,是向左移动,所以x的偏移应该是负值
|
||||
var buttonsWidth = -state.buttonsWidth
|
||||
instance.requestAnimationFrame(function () {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX('+ buttonsWidth +'px)',
|
||||
'-webkit-transform': 'translateX('+ buttonsWidth +'px)'
|
||||
})
|
||||
})
|
||||
setStatus('open', instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 一次性收起滑动菜单
|
||||
function closeSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.tn-swipe-action-item__right__button')
|
||||
var len = buttons.length
|
||||
// 处理duration单位问题
|
||||
var duration = getDuration(state.duration)
|
||||
instance.requestAnimationFrame(function () {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
// 设置各个隐藏按钮的收起状态
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
buttons[i].setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
}
|
||||
})
|
||||
setStatus('close', instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 标记菜单的当前状态,open - 打开 close - 关闭
|
||||
function setStatus(status, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
state.status = status
|
||||
ownerInstance.callMethod('setStatus', status)
|
||||
}
|
||||
|
||||
// status的状态发生变化
|
||||
function statusChange(newValue, oldValue, ownerInstance, instance) {
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
// 打开或关闭单元格
|
||||
if (newValue === 'close' && state.status === 'open') {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else if (newValue === 'open' && state.status === 'close') {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单尺寸发生变化
|
||||
function sizeChange(newValue, oldValue, ownerInstance, instance) {
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
state.disabled = newValue.disabled
|
||||
state.duration = newValue.duration
|
||||
state.show = newValue.show
|
||||
state.threshold = newValue.threshold
|
||||
state.buttons = newValue.buttons
|
||||
|
||||
if (state.buttons) {
|
||||
var len = state.buttons.length
|
||||
var buttonsWidth = 0
|
||||
var buttons = newValue.buttons
|
||||
for (var i = 0; i < len; i++) {
|
||||
buttonsWidth += buttons[i].width
|
||||
}
|
||||
}
|
||||
state.buttonsWidth = buttonsWidth
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchStart: touchStart,
|
||||
touchMove: touchMove,
|
||||
touchEnd: touchEnd,
|
||||
sizeChange: sizeChange,
|
||||
statusChange: statusChange
|
||||
}
|
||||
@@ -1,229 +1,236 @@
|
||||
<template>
|
||||
<view class="tn-swipe-action-item-class tn-swipe-action-item">
|
||||
<view class="tn-swipe-action-item__right">
|
||||
<slot name="button">
|
||||
<view
|
||||
v-for="(item, index) in options"
|
||||
:key="index"
|
||||
class="tn-swipe-action-item__right__button"
|
||||
:style="[{
|
||||
alignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'
|
||||
}]"
|
||||
@tap="buttonClickHandler(item, index)"
|
||||
>
|
||||
<view
|
||||
class="tn-swipe-action-item__right__button__wrapper"
|
||||
:style="[{
|
||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#AAAAAA',
|
||||
borderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',
|
||||
padding: item.style && item.style.borderRadius ? '0' : '0 30rpx'
|
||||
}, item.style]"
|
||||
>
|
||||
<view
|
||||
v-if="item.icon"
|
||||
:class="[`tn-icon-${item.icon}`]"
|
||||
:style="[{
|
||||
color: item.style && item.style.color ? item.style.color : '#FFFFFF',
|
||||
fontSize: item.iconSize ? item.iconSize + 'rpx' : item.style && item.style.fontSize ? (item.style.fontsize * 1.2) + 'rpx' : '34rpx',
|
||||
marginRight: item.text ? '4rpx' : 0
|
||||
}]"
|
||||
></view>
|
||||
<text
|
||||
v-if="item.text"
|
||||
class="tn-swipe-action-item__right__button__text tn-text-ellipsis"
|
||||
:style="[{
|
||||
color: item.style && item.style.color ? item.style.color : '#FFFFFF',
|
||||
fontSize: item.style && item.style.fontSize ? item.style.fontSize + 'rpx' : '32rpx',
|
||||
lineHeight: item.style && item.style.fontSize ? item.style.fontSize + 'rpx' : '32rpx'
|
||||
}]"
|
||||
>{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-swipe-action-item__content"
|
||||
:status="status"
|
||||
:size="size"
|
||||
:change:status="wxs.statusChange"
|
||||
:change:size="wxs.sizeChange"
|
||||
@touchstart="wxs.touchStart"
|
||||
@touchmove="wxs.touchMove"
|
||||
@touchend="wxs.touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-swipe-action-item-class tn-swipe-action-item">
|
||||
<view class="tn-swipe-action-item__right">
|
||||
<slot name="button">
|
||||
<view
|
||||
v-for="(item, index) in options"
|
||||
:key="index"
|
||||
class="tn-swipe-action-item__right__button"
|
||||
:style="[{
|
||||
alignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'
|
||||
}]"
|
||||
@tap="buttonClickHandler(item, index)"
|
||||
>
|
||||
<view
|
||||
class="tn-swipe-action-item__right__button__wrapper"
|
||||
:style="[{
|
||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#AAAAAA',
|
||||
borderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',
|
||||
padding: item.style && item.style.borderRadius ? '0' : '0 30rpx'
|
||||
}, item.style]"
|
||||
>
|
||||
<view
|
||||
v-if="item.icon"
|
||||
:class="[`tn-icon-${item.icon}`]"
|
||||
:style="[{
|
||||
color: item.style && item.style.color ? item.style.color : '#FFFFFF',
|
||||
fontSize: item.iconSize ? item.iconSize + 'rpx' : item.style && item.style.fontSize ? (item.style.fontsize * 1.2) + 'rpx' : '34rpx',
|
||||
marginRight: item.text ? '4rpx' : 0
|
||||
}]"
|
||||
></view>
|
||||
<text
|
||||
v-if="item.text"
|
||||
class="tn-swipe-action-item__right__button__text tn-text-ellipsis"
|
||||
:style="[{
|
||||
color: item.style && item.style.color ? item.style.color : '#FFFFFF',
|
||||
fontSize: item.style && item.style.fontSize ? item.style.fontSize + 'rpx' : '32rpx',
|
||||
lineHeight: item.style && item.style.fontSize ? item.style.fontSize + 'rpx' : '32rpx'
|
||||
}]"
|
||||
>{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="tn-swipe-action-item__content"
|
||||
:status="status"
|
||||
:size="size"
|
||||
:change:status="wxs.statusChange"
|
||||
:change:size="wxs.sizeChange"
|
||||
@touchstart="wxs.touchStart"
|
||||
@touchmove="wxs.touchMove"
|
||||
@touchend="wxs.touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->
|
||||
<script src="./index.wxs" module="wxs" lang="wxs"></script>
|
||||
|
||||
<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->
|
||||
<script src="./index.wxs" module="wxs" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'tn-swipe-action-item',
|
||||
props: {
|
||||
// 是否显示滑动菜单
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标识符,如果是v-for可用index的索引值
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 右侧按钮内容
|
||||
options: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动关闭其他swipe按钮组
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滑动距离阈值,大于此值才会打开菜单
|
||||
threshold: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
// 动画过渡时间,单位ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 由于wxs无法直接读取外部的值,需要在外部值变化时,重新执行赋值逻辑
|
||||
itemData() {
|
||||
return [this.disabled, this.autoClose, this.threshold, this.options, this.duration]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 按钮尺寸信息
|
||||
size: {},
|
||||
// 父组件参数
|
||||
parentData: {
|
||||
autoClose: true
|
||||
},
|
||||
// 当前状态
|
||||
status: 'close'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData() {
|
||||
this.queryRect()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.updateParentData()
|
||||
this.parent && this.parent.children.push(this)
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.queryRect()
|
||||
},
|
||||
// 更新父组件信息
|
||||
updateParentData() {
|
||||
this.getParentData('tn-swipe-action')
|
||||
},
|
||||
// 查询节点
|
||||
queryRect() {
|
||||
this._tGetRect('.tn-swipe-action-item__right__button', true).then(res => {
|
||||
this.size = {
|
||||
buttons: res,
|
||||
show: this.show,
|
||||
disabled: this.disabled,
|
||||
threshold: this.threshold,
|
||||
duration: this.duration
|
||||
}
|
||||
})
|
||||
},
|
||||
// 按钮点击
|
||||
buttonClickHandler(item, index) {
|
||||
this.$emit('click', {
|
||||
index,
|
||||
name: item.name
|
||||
})
|
||||
},
|
||||
// 关闭时执行
|
||||
closeHandler() {
|
||||
this.status = 'close'
|
||||
},
|
||||
// 设置状态
|
||||
setStatus(status) {
|
||||
this.status = status
|
||||
},
|
||||
// 关闭其他单元格
|
||||
closeOther() {
|
||||
this.parent && this.parent.closeOther(this)
|
||||
}
|
||||
}
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'tn-swipe-action-item',
|
||||
props: {
|
||||
// 是否显示滑动菜单
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标识符,如果是v-for可用index的索引值
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 右侧按钮内容
|
||||
options: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动关闭其他swipe按钮组
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滑动距离阈值,大于此值才会打开菜单
|
||||
threshold: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
// 动画过渡时间,单位ms
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 由于wxs无法直接读取外部的值,需要在外部值变化时,重新执行赋值逻辑
|
||||
itemData() {
|
||||
return [this.disabled, this.autoClose, this.threshold, this.options, this.duration]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 按钮尺寸信息
|
||||
size: {},
|
||||
// 父组件参数
|
||||
parentData: {
|
||||
autoClose: true
|
||||
},
|
||||
// 当前状态
|
||||
status: 'close'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
itemData() {
|
||||
this.queryRect()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.parent = false
|
||||
this.updateParentData()
|
||||
this.parent && this.parent.children.push(this)
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.queryRect()
|
||||
},
|
||||
// 更新父组件信息
|
||||
updateParentData() {
|
||||
this.getParentData('tn-swipe-action')
|
||||
},
|
||||
// 查询节点
|
||||
queryRect() {
|
||||
this._tGetRect('.tn-swipe-action-item__right__button', true).then(res => {
|
||||
this.size = {
|
||||
buttons: res,
|
||||
show: this.show,
|
||||
disabled: this.disabled,
|
||||
threshold: this.threshold,
|
||||
duration: this.duration
|
||||
}
|
||||
})
|
||||
},
|
||||
// 按钮点击
|
||||
buttonClickHandler(item, index) {
|
||||
this.$emit('click', {
|
||||
type: 'button',
|
||||
index
|
||||
})
|
||||
},
|
||||
// item点击
|
||||
handlerItemClick() {
|
||||
this.$emit('click', {
|
||||
type: 'item',
|
||||
name: this.name
|
||||
})
|
||||
},
|
||||
// 关闭时执行
|
||||
closeHandler() {
|
||||
this.status = 'close'
|
||||
},
|
||||
// 设置状态
|
||||
setStatus(status) {
|
||||
this.status = status
|
||||
},
|
||||
// 关闭其他单元格
|
||||
closeOther() {
|
||||
this.parent && this.parent.closeOther(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-swipe-action-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
&__button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
background-color: #FFFFFF;
|
||||
transform: translateX(0px);
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-swipe-action-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
// touch-action: none;
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
&__button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
color: #FFFFFF;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
background-color: #FFFFFF;
|
||||
transform: translateX(0px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,59 +1,59 @@
|
||||
<template>
|
||||
<view class="tn-swipe-action-class tn-swipe-action">
|
||||
<slot></slot>
|
||||
<template>
|
||||
<view class="tn-swipe-action-class tn-swipe-action">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-swipe-action',
|
||||
props: {
|
||||
// 是否自动关闭其他swipe按钮组
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
swipeAction: this
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 用于监听父组件参数变化
|
||||
parentData() {
|
||||
return [this.autoClose]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-swipe-action-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
// 关闭其他单元格
|
||||
closeOther(child) {
|
||||
if (this.autoClose) {
|
||||
// 历遍所有的单元格,找出非当前操作中的单元格,进行关闭
|
||||
this.children.map((item, index) => {
|
||||
if (child !== item) {
|
||||
item.closeHandler()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-swipe-action',
|
||||
props: {
|
||||
// 是否自动关闭其他swipe按钮组
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
swipeAction: this
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 用于监听父组件参数变化
|
||||
parentData() {
|
||||
return [this.autoClose]
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(tn-swipe-action-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
// 关闭其他单元格
|
||||
closeOther(child) {
|
||||
if (this.autoClose) {
|
||||
// 历遍所有的单元格,找出非当前操作中的单元格,进行关闭
|
||||
this.children.map((item, index) => {
|
||||
if (child !== item) {
|
||||
item.closeHandler()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,363 +1,364 @@
|
||||
<template>
|
||||
<view class="tn-swiper__wrap-class tn-swiper__wrap" :style="{borderRadius: `${radius}rpx`}">
|
||||
<!-- 轮播图 -->
|
||||
<swiper
|
||||
class="tn-swiper"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[swiperStyle]"
|
||||
:current="current"
|
||||
:interval="interval"
|
||||
:circular="circular"
|
||||
:autoplay="autoplay"
|
||||
:duration="duration"
|
||||
:previous-margin="effect3d ? effect3dPreviousSpacing + 'rpx' : '0'"
|
||||
:next-margin="effect3d ? effect3dPreviousSpacing + 'rpx' : '0'"
|
||||
@change="change"
|
||||
>
|
||||
<swiper-item
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__item"
|
||||
>
|
||||
<view
|
||||
class="tn-swiper__item__image__wrap"
|
||||
:class="[swiperIndex !== index ? 'tn-swiper__item__image--scale' : '']"
|
||||
:style="{
|
||||
borderRadius: `${radius}rpx`,
|
||||
transform: effect3d && swiperIndex !== index ? 'scaleY(0.9)' : 'scaleY(1)',
|
||||
margin: effect3d && swiperIndex !== index ? '0 20rpx' : 0
|
||||
}"
|
||||
>
|
||||
<image class="tn-swiper__item__image" :src="item[name] || item" :mode="imageMode"></image>
|
||||
<view
|
||||
v-if="title && item[titleName]"
|
||||
class="tn-swiper__item__title tn-text-ellipsis"
|
||||
:style="[titleStyle]">
|
||||
{{ item[titleName] }}
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 指示点 -->
|
||||
<view class="tn-swiper__indicator" :style="[indicatorStyle]">
|
||||
<block v-if="mode === 'rect'">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__rect"
|
||||
:class="{'tn-swiper__indicator__rect--active': swiperIndex === index}"
|
||||
></view>
|
||||
</block>
|
||||
<block v-if="mode === 'dot'">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__dot"
|
||||
:class="{'tn-swiper__indicator__dot--active': swiperIndex === index}"
|
||||
></view>
|
||||
</block>
|
||||
<block v-if="mode === 'round'">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__round"
|
||||
:class="{'tn-swiper__indicator__round--active': swiperIndex === index}"
|
||||
></view>
|
||||
</block>
|
||||
<block v-if="mode === 'number'">
|
||||
<view class="tn-swiper__indicator__number">{{ swiperIndex + 1 }}/{{ list.length }}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-swiper',
|
||||
props: {
|
||||
// 轮播图列表数据
|
||||
// [{image: xxx.jpg, title: 'xxxx'}]
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 初始化时,默认显示第几项
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 250
|
||||
},
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 图片的属性名
|
||||
name: {
|
||||
type: String,
|
||||
default: 'image'
|
||||
},
|
||||
// 是否显示标题
|
||||
title: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的属性名
|
||||
titleName: {
|
||||
type: String,
|
||||
default: 'title'
|
||||
},
|
||||
// 用户自定义标题样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 圆角的值
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 8
|
||||
},
|
||||
// 指示器模式
|
||||
// rect -> 方形 round -> 圆角方形 dot -> 点 number -> 轮播图下标
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'round'
|
||||
},
|
||||
// 指示器位置
|
||||
// topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
|
||||
indicatorPosition: {
|
||||
type: String,
|
||||
default: 'bottomCenter'
|
||||
},
|
||||
// 开启3D缩放效果
|
||||
effect3d: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 在3D缩放模式下,item之间的间隔
|
||||
effect3dPreviousSpacing: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 自定播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 图片之间播放间隔多久
|
||||
interval: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
},
|
||||
// 轮播间隔时间
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
// 是否衔接滑动
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 图片裁剪模式
|
||||
imageMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundColorStyle() {
|
||||
return this.$t.colorUtils.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.colorUtils.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
swiperStyle() {
|
||||
let style = {}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
if (this.height) {
|
||||
style.height = this.height + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
indicatorStyle() {
|
||||
let style = {}
|
||||
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
|
||||
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
|
||||
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
|
||||
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
|
||||
style.top = '12rpx'
|
||||
style.bottom = 'auto'
|
||||
} else {
|
||||
style.top = 'auto'
|
||||
style.bottom = '12rpx'
|
||||
}
|
||||
style.padding = `0 ${this.effect3d ? '74rpx' : '24rpx'}`
|
||||
|
||||
return style
|
||||
},
|
||||
swiperTitleStyle() {
|
||||
let style = {}
|
||||
if (this.mode === 'none' || this.mode === '') style.paddingBottom = '12rpx'
|
||||
if (['bottomLeft','bottomCenter','bottomRight'].indexOf(this.indicatorPosition) >= 0 && this.mode === 'number') {
|
||||
style.paddingBottom = '60rpx'
|
||||
} else if (['bottomLeft','bottomCenter','bottomRight'].indexOf(this.indicatorPosition) >= 0 && this.mode !== 'number') {
|
||||
style.paddingBottom = '40rpx'
|
||||
} else {
|
||||
style.paddingBottom = '12rpx'
|
||||
}
|
||||
|
||||
style = Object.assign(style, this.titleStyle)
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前显示的item的index
|
||||
swiperIndex: this.current
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list(newVal, oldVal) {
|
||||
// 如果修改了list的数据,重置current的值
|
||||
if (newVal.length !== oldVal.length) this.swiperIndex = 0
|
||||
},
|
||||
current(value) {
|
||||
// 监听外部current的变化,实时修改内部依赖于此测swiperIndex值,如果更新了current,而不是更新swiperIndex,就会错乱,因为指示器是依赖于swiperIndex的
|
||||
this.swiperIndex = value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 图片自动切换时触发
|
||||
change(event) {
|
||||
const current = event.detail.current
|
||||
this.swiperIndex = current
|
||||
this.$emit('change', current)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-swiper {
|
||||
|
||||
&__wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
will-change: transform;
|
||||
display: block;
|
||||
/* #ifdef H5 */
|
||||
pointer-events: none;
|
||||
/* #endif */
|
||||
|
||||
&__wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
transition: all 0.5s;
|
||||
overflow: hidden;
|
||||
box-sizing: content-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&--scale {
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 28rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
padding: 0 24rpx;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
&__rect {
|
||||
width: 26rpx;
|
||||
height: 8rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
margin: 0 6rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__round {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
margin: 0 6rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--active {
|
||||
width: 34rpx;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__number {
|
||||
padding: 6rpx 16rpx;
|
||||
line-height: 1;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 100rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-swiper__wrap-class tn-swiper__wrap" :style="{borderRadius: `${radius}rpx`}">
|
||||
<!-- 轮播图 -->
|
||||
<swiper
|
||||
class="tn-swiper"
|
||||
:class="[backgroundColorClass]"
|
||||
:style="[swiperStyle]"
|
||||
:current="current"
|
||||
:interval="interval"
|
||||
:circular="circular"
|
||||
:autoplay="autoplay"
|
||||
:duration="duration"
|
||||
:previous-margin="effect3d ? effect3dPreviousSpacing + 'rpx' : '0'"
|
||||
:next-margin="effect3d ? effect3dPreviousSpacing + 'rpx' : '0'"
|
||||
@change="change"
|
||||
>
|
||||
<swiper-item
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__item"
|
||||
>
|
||||
<view
|
||||
class="tn-swiper__item__image__wrap"
|
||||
:class="[swiperIndex !== index ? 'tn-swiper__item__image--scale' : '']"
|
||||
:style="{
|
||||
borderRadius: `${radius}rpx`,
|
||||
transform: effect3d && swiperIndex !== index ? 'scaleY(0.9)' : 'scaleY(1)',
|
||||
margin: effect3d && swiperIndex !== index ? '0 20rpx' : 0
|
||||
}"
|
||||
@click="click(index)"
|
||||
>
|
||||
<image class="tn-swiper__item__image" :src="item[name] || item" :mode="imageMode"></image>
|
||||
<view
|
||||
v-if="title && item[titleName]"
|
||||
class="tn-swiper__item__title tn-text-ellipsis"
|
||||
:style="[titleStyle]">
|
||||
{{ item[titleName] }}
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<!-- 指示点 -->
|
||||
<view class="tn-swiper__indicator" :style="[indicatorStyle]">
|
||||
<block v-if="mode === 'rect'">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__rect"
|
||||
:class="{'tn-swiper__indicator__rect--active': swiperIndex === index}"
|
||||
></view>
|
||||
</block>
|
||||
<block v-if="mode === 'dot'">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__dot"
|
||||
:class="{'tn-swiper__indicator__dot--active': swiperIndex === index}"
|
||||
></view>
|
||||
</block>
|
||||
<block v-if="mode === 'round'">
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="tn-swiper__indicator__round"
|
||||
:class="{'tn-swiper__indicator__round--active': swiperIndex === index}"
|
||||
></view>
|
||||
</block>
|
||||
<block v-if="mode === 'number'">
|
||||
<view class="tn-swiper__indicator__number">{{ swiperIndex + 1 }}/{{ list.length }}</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-swiper',
|
||||
props: {
|
||||
// 轮播图列表数据
|
||||
// [{image: xxx.jpg, title: 'xxxx'}]
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 初始化时,默认显示第几项
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 250
|
||||
},
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
// 图片的属性名
|
||||
name: {
|
||||
type: String,
|
||||
default: 'image'
|
||||
},
|
||||
// 是否显示标题
|
||||
title: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的属性名
|
||||
titleName: {
|
||||
type: String,
|
||||
default: 'title'
|
||||
},
|
||||
// 用户自定义标题样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 圆角的值
|
||||
radius: {
|
||||
type: Number,
|
||||
default: 8
|
||||
},
|
||||
// 指示器模式
|
||||
// rect -> 方形 round -> 圆角方形 dot -> 点 number -> 轮播图下标
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'round'
|
||||
},
|
||||
// 指示器位置
|
||||
// topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
|
||||
indicatorPosition: {
|
||||
type: String,
|
||||
default: 'bottomCenter'
|
||||
},
|
||||
// 开启3D缩放效果
|
||||
effect3d: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 在3D缩放模式下,item之间的间隔
|
||||
effect3dPreviousSpacing: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 自定播放
|
||||
autoplay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 图片之间播放间隔多久
|
||||
interval: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
},
|
||||
// 轮播间隔时间
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
// 是否衔接滑动
|
||||
circular: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 图片裁剪模式
|
||||
imageMode: {
|
||||
type: String,
|
||||
default: 'aspectFill'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundColorStyle() {
|
||||
return this.$t.color.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.color.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
swiperStyle() {
|
||||
let style = {}
|
||||
if (this.backgroundColorStyle) {
|
||||
style.backgroundColor = this.backgroundColorStyle
|
||||
}
|
||||
if (this.height) {
|
||||
style.height = this.height + 'rpx'
|
||||
}
|
||||
return style
|
||||
},
|
||||
indicatorStyle() {
|
||||
let style = {}
|
||||
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
|
||||
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
|
||||
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
|
||||
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
|
||||
style.top = '12rpx'
|
||||
style.bottom = 'auto'
|
||||
} else {
|
||||
style.top = 'auto'
|
||||
style.bottom = '12rpx'
|
||||
}
|
||||
style.padding = `0 ${this.effect3d ? '74rpx' : '24rpx'}`
|
||||
|
||||
return style
|
||||
},
|
||||
swiperTitleStyle() {
|
||||
let style = {}
|
||||
if (this.mode === 'none' || this.mode === '') style.paddingBottom = '12rpx'
|
||||
if (['bottomLeft','bottomCenter','bottomRight'].indexOf(this.indicatorPosition) >= 0 && this.mode === 'number') {
|
||||
style.paddingBottom = '60rpx'
|
||||
} else if (['bottomLeft','bottomCenter','bottomRight'].indexOf(this.indicatorPosition) >= 0 && this.mode !== 'number') {
|
||||
style.paddingBottom = '40rpx'
|
||||
} else {
|
||||
style.paddingBottom = '12rpx'
|
||||
}
|
||||
|
||||
style = Object.assign(style, this.titleStyle)
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 当前显示的item的index
|
||||
swiperIndex: this.current
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list(newVal, oldVal) {
|
||||
// 如果修改了list的数据,重置current的值
|
||||
if (newVal.length !== oldVal.length) this.swiperIndex = 0
|
||||
},
|
||||
current(value) {
|
||||
// 监听外部current的变化,实时修改内部依赖于此测swiperIndex值,如果更新了current,而不是更新swiperIndex,就会错乱,因为指示器是依赖于swiperIndex的
|
||||
this.swiperIndex = value
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(index) {
|
||||
this.$emit('click', index)
|
||||
},
|
||||
// 图片自动切换时触发
|
||||
change(event) {
|
||||
const current = event.detail.current
|
||||
this.swiperIndex = current
|
||||
this.$emit('change', current)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-swiper {
|
||||
|
||||
&__wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
will-change: transform;
|
||||
display: block;
|
||||
/* #ifdef H5 */
|
||||
pointer-events: none;
|
||||
/* #endif */
|
||||
|
||||
&__wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
transition: all 0.5s;
|
||||
overflow: hidden;
|
||||
box-sizing: content-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&--scale {
|
||||
transform-origin: center center;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-size: 28rpx;
|
||||
padding: 12rpx 24rpx;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__indicator {
|
||||
padding: 0 24rpx;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
&__rect {
|
||||
width: 26rpx;
|
||||
height: 8rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
margin: 0 6rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--active {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__round {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
margin: 0 6rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
transition: all 0.5s;
|
||||
|
||||
&--active {
|
||||
width: 34rpx;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
&__number {
|
||||
padding: 6rpx 16rpx;
|
||||
line-height: 1;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 100rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,241 +1,241 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-switch-class tn-switch"
|
||||
:class="[
|
||||
value ? 'tn-switch--on' : '',
|
||||
disabled ? 'tn-switch--disabled' : '',
|
||||
`tn-switch--${shape}`
|
||||
]"
|
||||
:style="[switchStyle]"
|
||||
@tap="click"
|
||||
>
|
||||
<view
|
||||
class="tn-switch__node"
|
||||
:class="[`tn-switch__node--${shape}`]"
|
||||
:style="[switchNodeStyle]"
|
||||
>
|
||||
<tn-loading class="tn-switch__node__loading" :show="loading" mode="flower" :size="size * 0.6" :color="loadingColor"></tn-loading>
|
||||
</view>
|
||||
<!-- 左图标 -->
|
||||
<view
|
||||
v-if="leftIcon !== ''"
|
||||
class="tn-switch__icon tn-switch__icon--left"
|
||||
:class="[
|
||||
`tn-icon-${leftIcon}`,
|
||||
value ? 'tn-switch__icon--show' : ''
|
||||
]"
|
||||
:style="[iconStyle]"></view>
|
||||
<!-- 右图标 -->
|
||||
<view
|
||||
v-if="rightIcon !== ''"
|
||||
class="tn-switch__icon tn-switch__icon--right"
|
||||
:class="[
|
||||
`tn-icon-${rightIcon}`,
|
||||
!value ? 'tn-switch__icon--show' : ''
|
||||
]"
|
||||
:style="[iconStyle]"></view>
|
||||
<template>
|
||||
<view
|
||||
class="tn-switch-class tn-switch"
|
||||
:class="[
|
||||
value ? 'tn-switch--on' : '',
|
||||
disabled ? 'tn-switch--disabled' : '',
|
||||
`tn-switch--${shape}`
|
||||
]"
|
||||
:style="[switchStyle]"
|
||||
@tap="click"
|
||||
>
|
||||
<view
|
||||
class="tn-switch__node"
|
||||
:class="[`tn-switch__node--${shape}`]"
|
||||
:style="[switchNodeStyle]"
|
||||
>
|
||||
<tn-loading class="tn-switch__node__loading" :show="loading" mode="flower" :size="size * 0.6" :color="loadingColor"></tn-loading>
|
||||
</view>
|
||||
<!-- 左图标 -->
|
||||
<view
|
||||
v-if="leftIcon !== ''"
|
||||
class="tn-switch__icon tn-switch__icon--left"
|
||||
:class="[
|
||||
`tn-icon-${leftIcon}`,
|
||||
value ? 'tn-switch__icon--show' : ''
|
||||
]"
|
||||
:style="[iconStyle]"></view>
|
||||
<!-- 右图标 -->
|
||||
<view
|
||||
v-if="rightIcon !== ''"
|
||||
class="tn-switch__icon tn-switch__icon--right"
|
||||
:class="[
|
||||
`tn-icon-${rightIcon}`,
|
||||
!value ? 'tn-switch__icon--show' : ''
|
||||
]"
|
||||
:style="[iconStyle]"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-switch',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按钮的样式
|
||||
// circle 圆角 square 方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 尺寸
|
||||
size: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 打开时的背景颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 关闭时的背景颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 激活时的值
|
||||
activeValue: {
|
||||
type: [Number, String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 关闭时的值
|
||||
inactiveValue: {
|
||||
type: [Number, String, Boolean],
|
||||
default: false
|
||||
},
|
||||
// 左图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为加载状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击手机是否震动
|
||||
vibrateShort: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
switchStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.backgroundColor = this.value ?
|
||||
this.activeColor ? this.activeColor : '#01BEFF' :
|
||||
this.inactiveColor ? this.inactiveColor : '#AAAAAA'
|
||||
return style
|
||||
},
|
||||
switchNodeStyle() {
|
||||
let style = {}
|
||||
style.width = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.height = style.width
|
||||
return style
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size - 20)
|
||||
style.lineHeight = this.$t.string.getLengthUnitValue(this.size)
|
||||
return style
|
||||
},
|
||||
loadingColor() {
|
||||
return this.value ? this.activeColor : ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!this.disabled && !this.loading) {
|
||||
if (this.vibrateShort) uni.vibrateShort()
|
||||
this.$emit('input', !this.value)
|
||||
// 放到下一个生命周期,因为双向绑定的value修改父组件状态需要时间,且是异步的
|
||||
this.$nextTick(() => {
|
||||
this.$emit('change', this.value ? this.activeValue : this.inactiveValue);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-switch',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 按钮的样式
|
||||
// circle 圆角 square 方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'circle'
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 尺寸
|
||||
size: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 打开时的背景颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 关闭时的背景颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 激活时的值
|
||||
activeValue: {
|
||||
type: [Number, String, Boolean],
|
||||
default: true
|
||||
},
|
||||
// 关闭时的值
|
||||
inactiveValue: {
|
||||
type: [Number, String, Boolean],
|
||||
default: false
|
||||
},
|
||||
// 左图标
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 右图标
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否为加载状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击手机是否震动
|
||||
vibrateShort: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
switchStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.backgroundColor = this.value ?
|
||||
this.activeColor ? this.activeColor : '#01BEFF' :
|
||||
this.inactiveColor ? this.inactiveColor : '#AAAAAA'
|
||||
return style
|
||||
},
|
||||
switchNodeStyle() {
|
||||
let style = {}
|
||||
style.width = this.$t.string.getLengthUnitValue(this.size)
|
||||
style.height = style.width
|
||||
return style
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
style.fontSize = this.$t.string.getLengthUnitValue(this.size - 20)
|
||||
style.lineHeight = this.$t.string.getLengthUnitValue(this.size)
|
||||
return style
|
||||
},
|
||||
loadingColor() {
|
||||
return this.value ? this.activeColor : ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!this.disabled && !this.loading) {
|
||||
if (this.vibrateShort) uni.vibrateShort()
|
||||
this.$emit('input', !this.value)
|
||||
// 放到下一个生命周期,因为双向绑定的value修改父组件状态需要时间,且是异步的
|
||||
this.$nextTick(() => {
|
||||
this.$emit('change', this.value ? this.activeValue : this.inactiveValue);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-switch {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
box-sizing: initial;
|
||||
width: 2em;
|
||||
height: 1em;
|
||||
font-size: 50rpx;
|
||||
background-color: #AAAAAA;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: #FFFFFF;
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 6rpx 2rpx 0 rgba(0, 0, 0, 0.05), 0 4rpx 4rpx 0 rgba(0, 0, 0, 0.1), 0 6rpx 6rpx 0 rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
|
||||
-webkit-transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
|
||||
|
||||
&__loading {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #FFFFFF;
|
||||
font-size: 30rpx;
|
||||
line-height: 50rpx;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
transform: scale(0);
|
||||
transform-origin: 50% 50%;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
|
||||
&--left {
|
||||
top: 0;
|
||||
left: 10rpx;
|
||||
}
|
||||
|
||||
&--right {
|
||||
top: 0;
|
||||
right: 10rpx;
|
||||
}
|
||||
|
||||
&--show {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 0.1em;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background-color: $tn-main-color;
|
||||
|
||||
.tn-switch__node {
|
||||
transform: translateX(100%) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-switch {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
box-sizing: initial;
|
||||
width: 2em;
|
||||
height: 1em;
|
||||
font-size: 50rpx;
|
||||
background-color: #AAAAAA;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background-color: #FFFFFF;
|
||||
transform: scale(0.9);
|
||||
box-shadow: 0 6rpx 2rpx 0 rgba(0, 0, 0, 0.05), 0 4rpx 4rpx 0 rgba(0, 0, 0, 0.1), 0 6rpx 6rpx 0 rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
|
||||
-webkit-transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
|
||||
|
||||
&__loading {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #FFFFFF;
|
||||
font-size: 30rpx;
|
||||
line-height: 50rpx;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
position: absolute;
|
||||
transform: scale(0);
|
||||
transform-origin: 50% 50%;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
|
||||
&--left {
|
||||
top: 0;
|
||||
left: 10rpx;
|
||||
}
|
||||
|
||||
&--right {
|
||||
top: 0;
|
||||
right: 10rpx;
|
||||
}
|
||||
|
||||
&--show {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 1em;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 0.1em;
|
||||
}
|
||||
|
||||
&--on {
|
||||
background-color: $tn-main-color;
|
||||
|
||||
.tn-switch__node {
|
||||
transform: translateX(100%) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,444 +1,444 @@
|
||||
<template>
|
||||
<view class="tn-tabs-swiper-class tn-tabs-swiper" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px'), zIndex: zIndex}">
|
||||
|
||||
<scroll-view scroll-x class="tn-tabs-swiper__scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{zIndex: zIndex + 1}">
|
||||
<view class="tn-tabs-swiper__scroll-view__box" :class="{'tn-tabs-swiper__scroll-view--flex': !isScroll}">
|
||||
|
||||
<!-- item -->
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:id="'tn-tabs-swiper__scroll-view__item-' + index"
|
||||
class="tn-tabs-swiper__scroll-view__item tn-text-ellipsis"
|
||||
:style="[tabItemStyle(index)]"
|
||||
@tap="emit(index)"
|
||||
>
|
||||
<tn-badge v-if="item[count] || item['count']" backgroundColor="tn-bg-red" fontColor="#FFFFFF" :absolute="true" :top="badgeOffset[0] || 0" :right="badgeOffset[1] || 0">{{ item[count] || item['count']}}</tn-badge>
|
||||
{{ item[name] || item['name'] }}
|
||||
</view>
|
||||
|
||||
<!-- 底部滑块 -->
|
||||
<view v-if="showBar" class="tn-tabs-swiper__bar" :style="[tabBarStyle]"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColor from '../../libs/mixin/components_color.js'
|
||||
const { windowWidth } = uni.getSystemInfoSync()
|
||||
|
||||
export default {
|
||||
mixins: [componentsColor],
|
||||
name: 'tn-tabs-swiper',
|
||||
props: {
|
||||
// 标签列表
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 列表数据tab名称的属性
|
||||
name: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
// 列表数据微标数量的属性
|
||||
count: {
|
||||
type: String,
|
||||
default: 'count'
|
||||
},
|
||||
// 当前活动的tab索引
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 菜单是否可以滑动
|
||||
isScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
// 距离顶部的距离(px)
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item的高度
|
||||
itemWidth: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// swiper的宽度
|
||||
swiperWidth: {
|
||||
type: Number,
|
||||
default: 750
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 未被选中时的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 选中的item样式
|
||||
activeItemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示底部滑块
|
||||
showBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部滑块的宽度
|
||||
barWidth: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
// 底部滑块的高度
|
||||
barHeight: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
// 自定义底部滑块的样式
|
||||
barStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 单个tab的左右内边距
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 微标的偏移数[top, right]
|
||||
badgeOffset: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [20, 22]
|
||||
}
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 滚动至中心目标类型
|
||||
autoCenterMode: {
|
||||
type: String,
|
||||
default: 'window'
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentIndex() {
|
||||
const current = Number(this.current)
|
||||
// 判断是否超出
|
||||
if (current > this.list.length - 1) {
|
||||
return this.list.length - 1
|
||||
}
|
||||
if (current < 0) return 0
|
||||
return current
|
||||
},
|
||||
// 滑块需要移动的距离
|
||||
scrollBarLeft() {
|
||||
return Number(this.tabLineDx) + Number(this.tabLineAddDx)
|
||||
},
|
||||
// 滑块宽度转换为px
|
||||
barWidthPx() {
|
||||
return uni.upx2px(this.barWidth)
|
||||
},
|
||||
// 将swiper宽度转换为px
|
||||
swiperWidthPx() {
|
||||
return uni.upx2px(this.swiperWidth)
|
||||
},
|
||||
// tab样式
|
||||
tabItemStyle() {
|
||||
return index => {
|
||||
let style = {
|
||||
height: this.$t.string.getLengthUnitValue(this.height),
|
||||
lineHeight: this.$t.string.getLengthUnitValue(this.height),
|
||||
fontSize: this.fontSizeStyle || '28rpx',
|
||||
color: this.tabsInfo.length > 0 ? (this.tabsInfo[index] ? this.tabsInfo[index].color : this.activeColor) : this.inactiveColor,
|
||||
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
|
||||
flex: this.isScroll ? 'auto' : '1',
|
||||
zIndex: this.zIndex + 2
|
||||
}
|
||||
if (index === this.currentIndex) {
|
||||
if (this.bold) {
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
Object.assign(style, this.activeItemStyle)
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 底部滑块样式
|
||||
tabBarStyle() {
|
||||
let style = {
|
||||
width: this.$t.string.getLengthUnitValue(this.barWidth),
|
||||
height: this.$t.string.getLengthUnitValue(this.barHeight),
|
||||
borderRadius: `${this.barHeight / 2}rpx`,
|
||||
backgroundColor: this.activeColor,
|
||||
left: this.scrollBarLeft + 'px'
|
||||
}
|
||||
Object.assign(style, this.barStyle)
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 滚动scroll-view的左边滚动距离
|
||||
scrollLeft: 0,
|
||||
// 存放tab菜单节点信息
|
||||
tabsInfo: [],
|
||||
// 屏幕宽度
|
||||
windowWidth: 0,
|
||||
// 滑动动画结束后对应的标签Index
|
||||
animationFinishCurrent: this.current,
|
||||
// 组件的宽度
|
||||
componentsWidth: 0,
|
||||
// 移动距离
|
||||
tabLineAddDx: 0,
|
||||
tabLineDx: 0,
|
||||
// 颜色渐变数组
|
||||
colorGradientArr: [],
|
||||
// 两个颜色之间的渐变等分
|
||||
colorStep: 100,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current(value) {
|
||||
this.change(value)
|
||||
this.setFinishCurrent(value)
|
||||
},
|
||||
list() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
async init() {
|
||||
await this.getTabsInfo()
|
||||
this.countLine3Dx()
|
||||
this.getQuery(() => {
|
||||
this.setScrollViewToCenter()
|
||||
})
|
||||
// 获取渐变颜色数组
|
||||
this.colorGradientArr = this.$t.colorUtils.colorGradient(this.inactiveColor, this.activeColor, this.colorStep)
|
||||
},
|
||||
// 发送事件
|
||||
emit(index) {
|
||||
this.$emit('change', index)
|
||||
},
|
||||
// tabs发生变化
|
||||
change() {
|
||||
this.setScrollViewToCenter()
|
||||
},
|
||||
// 获取各个tab的节点信息
|
||||
getTabsInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let view = uni.createSelectorQuery().in(this)
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
view.select('#tn-tabs-swiper__scroll-view__item-'+i).boundingClientRect()
|
||||
}
|
||||
view.exec(res => {
|
||||
const arr = []
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
// 添加颜色属性
|
||||
res[i].color = this.inactiveColor
|
||||
if (i === this.currentIndex) res[i].color = this.activeColor
|
||||
arr.push(res[i])
|
||||
}
|
||||
this.tabsInfo = arr
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 查询components信息
|
||||
getQuery(cb) {
|
||||
try {
|
||||
let view = uni.createSelectorQuery().in(this).select('.tn-tabs-swiper')
|
||||
view.fields({
|
||||
size: true
|
||||
},
|
||||
data => {
|
||||
if (data) {
|
||||
this.componentsWidth = data.width
|
||||
if (cb && typeof cb === 'function') cb(data)
|
||||
} else {
|
||||
this.getQuery(cb)
|
||||
}
|
||||
}
|
||||
).exec()
|
||||
} catch (e) {
|
||||
this.componentsWidth = windowWidth
|
||||
}
|
||||
},
|
||||
// 当swiper滑动结束的时候,计算滑块最终停留的位置
|
||||
countLine3Dx() {
|
||||
const tab = this.tabsInfo[this.animationFinishCurrent]
|
||||
// 让滑块中心点和当前tab中心重合
|
||||
if (tab) this.tabLineDx = tab.left + tab.width / 2 - this.barWidthPx / 2 - this.tabsInfo[0].left
|
||||
},
|
||||
// 把活动的tab移动到屏幕中心
|
||||
setScrollViewToCenter() {
|
||||
let tab = this.tabsInfo[this.animationFinishCurrent]
|
||||
if (tab) {
|
||||
let tabCenter = tab.left + tab.width / 2
|
||||
let parentWidth
|
||||
// 活动tab移动到中心时,以屏幕还是tab组件宽度为基准
|
||||
if (this.autoCenterMode === 'window') {
|
||||
parentWidth = windowWidth
|
||||
} else {
|
||||
parentWidth = this.componentsWidth
|
||||
}
|
||||
this.scrollLeft = tabCenter - parentWidth / 2
|
||||
}
|
||||
},
|
||||
// 设置偏移位置
|
||||
setDx(dx) {
|
||||
|
||||
// 计算下一个标签的步进值
|
||||
let nextIndexStep = Math.ceil(Math.abs(dx / this.swiperWidthPx))
|
||||
let nextTabIndex = dx > 0 ? this.animationFinishCurrent + 1 : this.animationFinishCurrent - 1
|
||||
// 处理索引超出边界问题
|
||||
nextTabIndex = nextTabIndex <= 0 ? 0 : nextTabIndex
|
||||
nextTabIndex = nextTabIndex >= this.list.length ? this.list.length - 1 : nextTabIndex
|
||||
|
||||
// 当前tab中心点x轴坐标
|
||||
let currentTab = this.tabsInfo[this.animationFinishCurrent]
|
||||
let currentTabX = currentTab.left + currentTab.width / 2
|
||||
|
||||
// 下一个tab中心点x轴坐标
|
||||
let nextTab = this.tabsInfo[nextTabIndex]
|
||||
let nextTabX = nextTab.left + nextTab.width / 2
|
||||
|
||||
// 两个tab之间的距离
|
||||
let distanceX = Math.abs(nextTabX - currentTabX)
|
||||
this.tabLineAddDx = (dx / this.swiperWidthPx) * distanceX
|
||||
this.setTabColor(this.animationFinishCurrent, nextTabIndex, dx)
|
||||
},
|
||||
// 设置tab的颜色
|
||||
setTabColor(currentTabIndex, nextTabIndex, dx) {
|
||||
let nextIndexStep = Math.ceil(Math.abs(dx / this.swiperWidthPx))
|
||||
if (Math.abs(dx) > this.swiperWidthPx) {
|
||||
dx = dx > 0 ? dx - (this.swiperWidthPx * (nextIndexStep - 1)) : dx + (this.swiperWidthPx * (nextIndexStep - 1))
|
||||
}
|
||||
let colorIndex = Math.abs(Math.ceil((dx / this.swiperWidthPx) * 100))
|
||||
let colorLength = this.colorGradientArr.length
|
||||
// 处理超出索引边界
|
||||
colorIndex = colorIndex >= colorLength ? colorLength - 1 : colorIndex <= 0 ? 0 : colorIndex
|
||||
if (nextIndexStep > 1) {
|
||||
// 设置下一个tab的颜色
|
||||
// 设置之前tab的颜色为默认颜色
|
||||
if (dx > 0) {
|
||||
this.tabsInfo[nextTabIndex + (nextIndexStep - 1) > this.tabsInfo.length - 1 ? this.tabsInfo.length - 1 : nextTabIndex + (nextIndexStep - 1)].color = this.colorGradientArr[colorIndex]
|
||||
this.tabsInfo[nextTabIndex + (nextIndexStep - 2) > this.tabsInfo.length - 1 ? this.tabsInfo.length - 1 : nextTabIndex + (nextIndexStep - 2)].color = this.colorGradientArr[colorLength - 1 - colorIndex]
|
||||
} else {
|
||||
this.tabsInfo[nextTabIndex - (nextIndexStep - 1) < 0 ? 0 : nextTabIndex - (nextIndexStep - 1)].color = this.colorGradientArr[colorIndex]
|
||||
this.tabsInfo[nextTabIndex - (nextIndexStep - 2) < 0 ? 0 : nextTabIndex - (nextIndexStep - 2)].color = this.colorGradientArr[colorLength - 1 - colorIndex]
|
||||
}
|
||||
} else {
|
||||
// 设置下一个tab的颜色
|
||||
this.tabsInfo[nextTabIndex].color = this.colorGradientArr[colorIndex]
|
||||
// 设置当前tab的颜色
|
||||
this.tabsInfo[currentTabIndex].color = this.colorGradientArr[colorLength - 1 - colorIndex]
|
||||
}
|
||||
|
||||
},
|
||||
// swiper滑动结束
|
||||
setFinishCurrent(current) {
|
||||
// 如果滑动的索引不一致,修改tab颜色变化,因为可能会有直接点击tab的情况
|
||||
this.tabsInfo.map((item, index) => {
|
||||
if (current == index) item.color = this.activeColor
|
||||
else item.color = this.inactiveColor
|
||||
return item
|
||||
})
|
||||
this.tabLineAddDx = 0
|
||||
this.animationFinishCurrent = current
|
||||
this.countLine3Dx()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef H5 */
|
||||
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
|
||||
scroll-view ::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.tn-tabs-swiper {
|
||||
&__scroll-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
&__box {
|
||||
position: relative;
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
&--flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-tabs-swiper-class tn-tabs-swiper" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px'), zIndex: zIndex}">
|
||||
|
||||
<scroll-view scroll-x class="tn-tabs-swiper__scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{zIndex: zIndex + 1}">
|
||||
<view class="tn-tabs-swiper__scroll-view__box" :class="{'tn-tabs-swiper__scroll-view--flex': !isScroll}">
|
||||
|
||||
<!-- item -->
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:id="'tn-tabs-swiper__scroll-view__item-' + index"
|
||||
class="tn-tabs-swiper__scroll-view__item tn-text-ellipsis"
|
||||
:style="[tabItemStyle(index)]"
|
||||
@tap="emit(index)"
|
||||
>
|
||||
<tn-badge v-if="item[count] || item['count']" backgroundColor="tn-bg-red" fontColor="#FFFFFF" :absolute="true" :top="badgeOffset[0] || 0" :right="badgeOffset[1] || 0">{{ item[count] || item['count']}}</tn-badge>
|
||||
{{ item[name] || item['name'] }}
|
||||
</view>
|
||||
|
||||
<!-- 底部滑块 -->
|
||||
<view v-if="showBar" class="tn-tabs-swiper__bar" :style="[tabBarStyle]"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColor from '../../libs/mixin/components_color.js'
|
||||
const { windowWidth } = uni.getSystemInfoSync()
|
||||
|
||||
export default {
|
||||
mixins: [componentsColor],
|
||||
name: 'tn-tabs-swiper',
|
||||
props: {
|
||||
// 标签列表
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 列表数据tab名称的属性
|
||||
name: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
// 列表数据微标数量的属性
|
||||
count: {
|
||||
type: String,
|
||||
default: 'count'
|
||||
},
|
||||
// 当前活动的tab索引
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 菜单是否可以滑动
|
||||
isScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
// 距离顶部的距离(px)
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item的高度
|
||||
itemWidth: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// swiper的宽度
|
||||
swiperWidth: {
|
||||
type: Number,
|
||||
default: 750
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 未被选中时的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 选中的item样式
|
||||
activeItemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示底部滑块
|
||||
showBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部滑块的宽度
|
||||
barWidth: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
// 底部滑块的高度
|
||||
barHeight: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
// 自定义底部滑块的样式
|
||||
barStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 单个tab的左右内边距
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 微标的偏移数[top, right]
|
||||
badgeOffset: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [20, 22]
|
||||
}
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 滚动至中心目标类型
|
||||
autoCenterMode: {
|
||||
type: String,
|
||||
default: 'window'
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentIndex() {
|
||||
const current = Number(this.current)
|
||||
// 判断是否超出
|
||||
if (current > this.list.length - 1) {
|
||||
return this.list.length - 1
|
||||
}
|
||||
if (current < 0) return 0
|
||||
return current
|
||||
},
|
||||
// 滑块需要移动的距离
|
||||
scrollBarLeft() {
|
||||
return Number(this.tabLineDx) + Number(this.tabLineAddDx)
|
||||
},
|
||||
// 滑块宽度转换为px
|
||||
barWidthPx() {
|
||||
return uni.upx2px(this.barWidth)
|
||||
},
|
||||
// 将swiper宽度转换为px
|
||||
swiperWidthPx() {
|
||||
return uni.upx2px(this.swiperWidth)
|
||||
},
|
||||
// tab样式
|
||||
tabItemStyle() {
|
||||
return index => {
|
||||
let style = {
|
||||
height: this.$t.string.getLengthUnitValue(this.height),
|
||||
lineHeight: this.$t.string.getLengthUnitValue(this.height),
|
||||
fontSize: this.fontSizeStyle || '28rpx',
|
||||
color: this.tabsInfo.length > 0 ? (this.tabsInfo[index] ? this.tabsInfo[index].color : this.activeColor) : this.inactiveColor,
|
||||
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
|
||||
flex: this.isScroll ? 'auto' : '1',
|
||||
zIndex: this.zIndex + 2
|
||||
}
|
||||
if (index === this.currentIndex) {
|
||||
if (this.bold) {
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
Object.assign(style, this.activeItemStyle)
|
||||
}
|
||||
return style
|
||||
}
|
||||
},
|
||||
// 底部滑块样式
|
||||
tabBarStyle() {
|
||||
let style = {
|
||||
width: this.$t.string.getLengthUnitValue(this.barWidth),
|
||||
height: this.$t.string.getLengthUnitValue(this.barHeight),
|
||||
borderRadius: `${this.barHeight / 2}rpx`,
|
||||
backgroundColor: this.activeColor,
|
||||
left: this.scrollBarLeft + 'px'
|
||||
}
|
||||
Object.assign(style, this.barStyle)
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 滚动scroll-view的左边滚动距离
|
||||
scrollLeft: 0,
|
||||
// 存放tab菜单节点信息
|
||||
tabsInfo: [],
|
||||
// 屏幕宽度
|
||||
windowWidth: 0,
|
||||
// 滑动动画结束后对应的标签Index
|
||||
animationFinishCurrent: this.current,
|
||||
// 组件的宽度
|
||||
componentsWidth: 0,
|
||||
// 移动距离
|
||||
tabLineAddDx: 0,
|
||||
tabLineDx: 0,
|
||||
// 颜色渐变数组
|
||||
colorGradientArr: [],
|
||||
// 两个颜色之间的渐变等分
|
||||
colorStep: 100,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current(value) {
|
||||
this.change(value)
|
||||
this.setFinishCurrent(value)
|
||||
},
|
||||
list() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
async init() {
|
||||
await this.getTabsInfo()
|
||||
this.countLine3Dx()
|
||||
this.getQuery(() => {
|
||||
this.setScrollViewToCenter()
|
||||
})
|
||||
// 获取渐变颜色数组
|
||||
this.colorGradientArr = this.$t.color.colorGradient(this.inactiveColor, this.activeColor, this.colorStep)
|
||||
},
|
||||
// 发送事件
|
||||
emit(index) {
|
||||
this.$emit('change', index)
|
||||
},
|
||||
// tabs发生变化
|
||||
change() {
|
||||
this.setScrollViewToCenter()
|
||||
},
|
||||
// 获取各个tab的节点信息
|
||||
getTabsInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let view = uni.createSelectorQuery().in(this)
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
view.select('#tn-tabs-swiper__scroll-view__item-'+i).boundingClientRect()
|
||||
}
|
||||
view.exec(res => {
|
||||
const arr = []
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
// 添加颜色属性
|
||||
res[i].color = this.inactiveColor
|
||||
if (i === this.currentIndex) res[i].color = this.activeColor
|
||||
arr.push(res[i])
|
||||
}
|
||||
this.tabsInfo = arr
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
},
|
||||
// 查询components信息
|
||||
getQuery(cb) {
|
||||
try {
|
||||
let view = uni.createSelectorQuery().in(this).select('.tn-tabs-swiper')
|
||||
view.fields({
|
||||
size: true
|
||||
},
|
||||
data => {
|
||||
if (data) {
|
||||
this.componentsWidth = data.width
|
||||
if (cb && typeof cb === 'function') cb(data)
|
||||
} else {
|
||||
this.getQuery(cb)
|
||||
}
|
||||
}
|
||||
).exec()
|
||||
} catch (e) {
|
||||
this.componentsWidth = windowWidth
|
||||
}
|
||||
},
|
||||
// 当swiper滑动结束的时候,计算滑块最终停留的位置
|
||||
countLine3Dx() {
|
||||
const tab = this.tabsInfo[this.animationFinishCurrent]
|
||||
// 让滑块中心点和当前tab中心重合
|
||||
if (tab) this.tabLineDx = tab.left + tab.width / 2 - this.barWidthPx / 2 - this.tabsInfo[0].left
|
||||
},
|
||||
// 把活动的tab移动到屏幕中心
|
||||
setScrollViewToCenter() {
|
||||
let tab = this.tabsInfo[this.animationFinishCurrent]
|
||||
if (tab) {
|
||||
let tabCenter = tab.left + tab.width / 2
|
||||
let parentWidth
|
||||
// 活动tab移动到中心时,以屏幕还是tab组件宽度为基准
|
||||
if (this.autoCenterMode === 'window') {
|
||||
parentWidth = windowWidth
|
||||
} else {
|
||||
parentWidth = this.componentsWidth
|
||||
}
|
||||
this.scrollLeft = tabCenter - parentWidth / 2
|
||||
}
|
||||
},
|
||||
// 设置偏移位置
|
||||
setDx(dx) {
|
||||
|
||||
// 计算下一个标签的步进值
|
||||
let nextIndexStep = Math.ceil(Math.abs(dx / this.swiperWidthPx))
|
||||
let nextTabIndex = dx > 0 ? this.animationFinishCurrent + 1 : this.animationFinishCurrent - 1
|
||||
// 处理索引超出边界问题
|
||||
nextTabIndex = nextTabIndex <= 0 ? 0 : nextTabIndex
|
||||
nextTabIndex = nextTabIndex >= this.list.length ? this.list.length - 1 : nextTabIndex
|
||||
|
||||
// 当前tab中心点x轴坐标
|
||||
let currentTab = this.tabsInfo[this.animationFinishCurrent]
|
||||
let currentTabX = currentTab.left + currentTab.width / 2
|
||||
|
||||
// 下一个tab中心点x轴坐标
|
||||
let nextTab = this.tabsInfo[nextTabIndex]
|
||||
let nextTabX = nextTab.left + nextTab.width / 2
|
||||
|
||||
// 两个tab之间的距离
|
||||
let distanceX = Math.abs(nextTabX - currentTabX)
|
||||
this.tabLineAddDx = (dx / this.swiperWidthPx) * distanceX
|
||||
this.setTabColor(this.animationFinishCurrent, nextTabIndex, dx)
|
||||
},
|
||||
// 设置tab的颜色
|
||||
setTabColor(currentTabIndex, nextTabIndex, dx) {
|
||||
let nextIndexStep = Math.ceil(Math.abs(dx / this.swiperWidthPx))
|
||||
if (Math.abs(dx) > this.swiperWidthPx) {
|
||||
dx = dx > 0 ? dx - (this.swiperWidthPx * (nextIndexStep - 1)) : dx + (this.swiperWidthPx * (nextIndexStep - 1))
|
||||
}
|
||||
let colorIndex = Math.abs(Math.ceil((dx / this.swiperWidthPx) * 100))
|
||||
let colorLength = this.colorGradientArr.length
|
||||
// 处理超出索引边界
|
||||
colorIndex = colorIndex >= colorLength ? colorLength - 1 : colorIndex <= 0 ? 0 : colorIndex
|
||||
if (nextIndexStep > 1) {
|
||||
// 设置下一个tab的颜色
|
||||
// 设置之前tab的颜色为默认颜色
|
||||
if (dx > 0) {
|
||||
this.tabsInfo[nextTabIndex + (nextIndexStep - 1) > this.tabsInfo.length - 1 ? this.tabsInfo.length - 1 : nextTabIndex + (nextIndexStep - 1)].color = this.colorGradientArr[colorIndex]
|
||||
this.tabsInfo[nextTabIndex + (nextIndexStep - 2) > this.tabsInfo.length - 1 ? this.tabsInfo.length - 1 : nextTabIndex + (nextIndexStep - 2)].color = this.colorGradientArr[colorLength - 1 - colorIndex]
|
||||
} else {
|
||||
this.tabsInfo[nextTabIndex - (nextIndexStep - 1) < 0 ? 0 : nextTabIndex - (nextIndexStep - 1)].color = this.colorGradientArr[colorIndex]
|
||||
this.tabsInfo[nextTabIndex - (nextIndexStep - 2) < 0 ? 0 : nextTabIndex - (nextIndexStep - 2)].color = this.colorGradientArr[colorLength - 1 - colorIndex]
|
||||
}
|
||||
} else {
|
||||
// 设置下一个tab的颜色
|
||||
this.tabsInfo[nextTabIndex].color = this.colorGradientArr[colorIndex]
|
||||
// 设置当前tab的颜色
|
||||
this.tabsInfo[currentTabIndex].color = this.colorGradientArr[colorLength - 1 - colorIndex]
|
||||
}
|
||||
|
||||
},
|
||||
// swiper滑动结束
|
||||
setFinishCurrent(current) {
|
||||
// 如果滑动的索引不一致,修改tab颜色变化,因为可能会有直接点击tab的情况
|
||||
this.tabsInfo.map((item, index) => {
|
||||
if (current == index) item.color = this.activeColor
|
||||
else item.color = this.inactiveColor
|
||||
return item
|
||||
})
|
||||
this.tabLineAddDx = 0
|
||||
this.animationFinishCurrent = current
|
||||
this.countLine3Dx()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef H5 */
|
||||
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
|
||||
scroll-view ::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.tn-tabs-swiper {
|
||||
&__scroll-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
&__box {
|
||||
position: relative;
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
&--flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,340 +1,340 @@
|
||||
<template>
|
||||
<view class="tn-tabs-class tn-tabs" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px')}">
|
||||
|
||||
<!-- _tgetRect()对组件根节点无效,因为写了.in(this),故这里获取内层接点尺寸 -->
|
||||
<view :id="id">
|
||||
<scroll-view scroll-x class="tn-tabs__scroll-view" :scroll-left="scrollLeft" scroll-with-animation>
|
||||
<view class="tn-tabs__scroll-view__box" :class="{'tn-tabs__scroll-view--flex': !isScroll}">
|
||||
<!-- item -->
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:id="'tn-tabs__scroll-view__item-' + index"
|
||||
class="tn-tabs__scroll-view__item tn-text-ellipsis"
|
||||
:style="[tabItemStyle(index)]"
|
||||
@tap="clickTab(index)"
|
||||
>
|
||||
<tn-badge v-if="item[count] || item['count']" backgroundColor="tn-bg-red" fontColor="#FFFFFF" :absolute="true" :top="badgeOffset[0] || 0" :right="badgeOffset[1] || 0">{{ item[count] || item['count']}}</tn-badge>
|
||||
{{ item[name] || item['name'] }}
|
||||
</view>
|
||||
|
||||
<!-- 底部滑块 -->
|
||||
<view v-if="showBar" class="tn-tabs__bar" :style="[tabBarStyle]"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColor from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColor],
|
||||
name: 'tn-tabs',
|
||||
props: {
|
||||
// 标签列表
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 列表数据tab名称的属性
|
||||
name: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
// 列表数据微标数量的属性
|
||||
count: {
|
||||
type: String,
|
||||
default: 'count'
|
||||
},
|
||||
// 当前活动的tab索引
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 菜单是否可以滑动
|
||||
isScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
// 距离顶部的距离(px)
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item的宽度
|
||||
itemWidth: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 过渡动画时长
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 0.3
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 未被选中时的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 选中的item样式
|
||||
activeItemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示底部滑块
|
||||
showBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部滑块的宽度
|
||||
barWidth: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
// 底部滑块的高度
|
||||
barHeight: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
// 自定义底部滑块的样式
|
||||
barStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 单个tab的左右内边距
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 微标的偏移数[top, right]
|
||||
badgeOffset: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [20, 22]
|
||||
}
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 底部滑块样式
|
||||
tabBarStyle() {
|
||||
let style = {
|
||||
width: this.$t.string.getLengthUnitValue(this.barWidth),
|
||||
height: this.$t.string.getLengthUnitValue(this.barHeight),
|
||||
borderRadius: `${this.barHeight / 2}rpx`,
|
||||
backgroundColor: this.activeColor,
|
||||
opacity: this.barMoveFirst ? 0 : 1,
|
||||
transform: `translate(${this.scrollBarLeft}px, -100%)`,
|
||||
transitionDuration: this.barMoveFirst ? '0s' : `${this.duration}s`
|
||||
}
|
||||
Object.assign(style, this.barStyle)
|
||||
return style
|
||||
},
|
||||
// tabItem样式
|
||||
tabItemStyle() {
|
||||
return index => {
|
||||
let style = {
|
||||
width: this.$t.string.getLengthUnitValue(this.itemWidth),
|
||||
height: this.$t.string.getLengthUnitValue(this.height),
|
||||
lineHeight: this.$t.string.getLengthUnitValue(this.height),
|
||||
fontSize: this.fontSizeStyle || '28rpx',
|
||||
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
|
||||
flex: this.isScroll ? 'auto' : '1',
|
||||
transitionDuration: `${this.duration}s`
|
||||
}
|
||||
if (index === this.currentIndex) {
|
||||
if (this.bold) {
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
style.color = this.activeColor
|
||||
Object.assign(style, this.activeItemStyle)
|
||||
} else {
|
||||
style.color = this.inactiveColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// id值
|
||||
id: this.$t.uuid(),
|
||||
// 滚动scroll-view的左边距离
|
||||
scrollLeft: 0,
|
||||
// 存放查询后tab菜单的节点信息
|
||||
tabQueryInfo: [],
|
||||
// 组件宽度
|
||||
componentWidth: 0,
|
||||
// 底部滑块的移动距离
|
||||
scrollBarLeft: 0,
|
||||
// 组件到屏幕左边的巨鹿
|
||||
componentLeft: 0,
|
||||
// 当前选中的itemIndex
|
||||
currentIndex: this.current,
|
||||
// 标记底部滑块是否第一次移动,第一次移动的时候不触发动画
|
||||
barMoveFirst: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听tab的变化,重新计算tab菜单信息
|
||||
list(newValue, oldValue) {
|
||||
// list变化时,重置内部索引,防止出现超过数据边界的问题
|
||||
if (newValue.length !== oldValue.length) this.currentIndex = 0
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
current: {
|
||||
handler(val) {
|
||||
this.$nextTick(() => {
|
||||
this.currentIndex = val
|
||||
this.scrollByIndex()
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 初始化变量
|
||||
async init() {
|
||||
// 获取tabs组件的信息
|
||||
let tabRect = await this._tGetRect('#' + this.id)
|
||||
// 计算组件的宽度
|
||||
this.componentLeft = tabRect.left
|
||||
this.componentWidth = tabRect.width
|
||||
this.getTabRect()
|
||||
},
|
||||
// 点击tab菜单
|
||||
clickTab(index) {
|
||||
if (index === this.currentIndex) return
|
||||
this.$emit('change', index)
|
||||
},
|
||||
// 查询tab的布局信息
|
||||
getTabRect() {
|
||||
let query = uni.createSelectorQuery().in(this)
|
||||
// 遍历所有的tab
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
query.select(`#tn-tabs__scroll-view__item-${i}`).fields({
|
||||
size: true,
|
||||
rect: true
|
||||
})
|
||||
}
|
||||
query.exec((res) => {
|
||||
this.tabQueryInfo = res
|
||||
// 初始滚动条和底部滑块的位置
|
||||
this.scrollByIndex()
|
||||
})
|
||||
},
|
||||
// 滚动scrollView,让活动的tab处于屏幕中间
|
||||
scrollByIndex() {
|
||||
// 当前获取tab的布局信息
|
||||
let tabInfo = this.tabQueryInfo[this.currentIndex]
|
||||
if (!tabInfo) return
|
||||
|
||||
// 活动tab的宽度
|
||||
let tabWidth = tabInfo.width
|
||||
// 活动item的左边到组件左边的距离
|
||||
let offsetLeft = tabInfo.left - this.componentLeft
|
||||
// 计算scroll-view移动的距离
|
||||
let scrollLeft = offsetLeft - (this.componentWidth - tabWidth) / 2
|
||||
this.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft
|
||||
|
||||
// 计算当前滑块需要移动的距离,当前活动item的中点到左边的距离减去滑块宽度的一半
|
||||
let left = tabInfo.left + tabInfo.width / 2 - this.componentLeft
|
||||
|
||||
// 计算当前活跃item到组件左边的距离
|
||||
this.scrollBarLeft = left - uni.upx2px(this.barWidth) / 2
|
||||
|
||||
// 防止在计算时出错,所以延迟执行标记不是第一次移动
|
||||
if (this.barMoveFirst) {
|
||||
setTimeout(() => {
|
||||
this.barMoveFirst = false
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef H5 */
|
||||
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
|
||||
scroll-view ::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.tn-tabs {
|
||||
&__scroll-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
&__box {
|
||||
position: relative;
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
&--flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-tabs-class tn-tabs" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px')}">
|
||||
|
||||
<!-- _tgetRect()对组件根节点无效,因为写了.in(this),故这里获取内层接点尺寸 -->
|
||||
<view :id="id">
|
||||
<scroll-view scroll-x class="tn-tabs__scroll-view" :scroll-left="scrollLeft" scroll-with-animation>
|
||||
<view class="tn-tabs__scroll-view__box" :class="{'tn-tabs__scroll-view--flex': !isScroll}">
|
||||
<!-- item -->
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:id="'tn-tabs__scroll-view__item-' + index"
|
||||
class="tn-tabs__scroll-view__item tn-text-ellipsis"
|
||||
:style="[tabItemStyle(index)]"
|
||||
@tap="clickTab(index)"
|
||||
>
|
||||
<tn-badge v-if="item[count] || item['count']" backgroundColor="tn-bg-red" fontColor="#FFFFFF" :absolute="true" :top="badgeOffset[0] || 0" :right="badgeOffset[1] || 0">{{ item[count] || item['count']}}</tn-badge>
|
||||
{{ item[name] || item['name'] }}
|
||||
</view>
|
||||
|
||||
<!-- 底部滑块 -->
|
||||
<view v-if="showBar" class="tn-tabs__bar" :style="[tabBarStyle]"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColor from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [componentsColor],
|
||||
name: 'tn-tabs',
|
||||
props: {
|
||||
// 标签列表
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
// 列表数据tab名称的属性
|
||||
name: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
// 列表数据微标数量的属性
|
||||
count: {
|
||||
type: String,
|
||||
default: 'count'
|
||||
},
|
||||
// 当前活动的tab索引
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 菜单是否可以滑动
|
||||
isScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
// 距离顶部的距离(px)
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// item的宽度
|
||||
itemWidth: {
|
||||
type: [String, Number],
|
||||
default: 'auto'
|
||||
},
|
||||
// 过渡动画时长
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 0.3
|
||||
},
|
||||
// 选中时的颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#01BEFF'
|
||||
},
|
||||
// 未被选中时的颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#080808'
|
||||
},
|
||||
// 选中的item样式
|
||||
activeItemStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示底部滑块
|
||||
showBar: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 底部滑块的宽度
|
||||
barWidth: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
// 底部滑块的高度
|
||||
barHeight: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
// 自定义底部滑块的样式
|
||||
barStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 单个tab的左右内边距
|
||||
gutter: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
// 微标的偏移数[top, right]
|
||||
badgeOffset: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [20, 22]
|
||||
}
|
||||
},
|
||||
// 是否加粗字体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 底部滑块样式
|
||||
tabBarStyle() {
|
||||
let style = {
|
||||
width: this.$t.string.getLengthUnitValue(this.barWidth),
|
||||
height: this.$t.string.getLengthUnitValue(this.barHeight),
|
||||
borderRadius: `${this.barHeight / 2}rpx`,
|
||||
backgroundColor: this.activeColor,
|
||||
opacity: this.barMoveFirst ? 0 : 1,
|
||||
transform: `translate(${this.scrollBarLeft}px, -100%)`,
|
||||
transitionDuration: this.barMoveFirst ? '0s' : `${this.duration}s`
|
||||
}
|
||||
Object.assign(style, this.barStyle)
|
||||
return style
|
||||
},
|
||||
// tabItem样式
|
||||
tabItemStyle() {
|
||||
return index => {
|
||||
let style = {
|
||||
width: this.$t.string.getLengthUnitValue(this.itemWidth),
|
||||
height: this.$t.string.getLengthUnitValue(this.height),
|
||||
lineHeight: this.$t.string.getLengthUnitValue(this.height),
|
||||
fontSize: this.fontSizeStyle || '28rpx',
|
||||
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
|
||||
flex: this.isScroll ? 'auto' : '1',
|
||||
transitionDuration: `${this.duration}s`
|
||||
}
|
||||
if (index === this.currentIndex) {
|
||||
if (this.bold) {
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
style.color = this.activeColor
|
||||
Object.assign(style, this.activeItemStyle)
|
||||
} else {
|
||||
style.color = this.inactiveColor
|
||||
}
|
||||
return style
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// id值
|
||||
id: this.$t.uuid(),
|
||||
// 滚动scroll-view的左边距离
|
||||
scrollLeft: 0,
|
||||
// 存放查询后tab菜单的节点信息
|
||||
tabQueryInfo: [],
|
||||
// 组件宽度
|
||||
componentWidth: 0,
|
||||
// 底部滑块的移动距离
|
||||
scrollBarLeft: 0,
|
||||
// 组件到屏幕左边的巨鹿
|
||||
componentLeft: 0,
|
||||
// 当前选中的itemIndex
|
||||
currentIndex: this.current,
|
||||
// 标记底部滑块是否第一次移动,第一次移动的时候不触发动画
|
||||
barMoveFirst: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听tab的变化,重新计算tab菜单信息
|
||||
list(newValue, oldValue) {
|
||||
// list变化时,重置内部索引,防止出现超过数据边界的问题
|
||||
if (newValue.length !== oldValue.length) this.currentIndex = 0
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
current: {
|
||||
handler(val) {
|
||||
this.$nextTick(() => {
|
||||
this.currentIndex = val
|
||||
this.scrollByIndex()
|
||||
})
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
// 初始化变量
|
||||
async init() {
|
||||
// 获取tabs组件的信息
|
||||
let tabRect = await this._tGetRect('#' + this.id)
|
||||
// 计算组件的宽度
|
||||
this.componentLeft = tabRect.left
|
||||
this.componentWidth = tabRect.width
|
||||
this.getTabRect()
|
||||
},
|
||||
// 点击tab菜单
|
||||
clickTab(index) {
|
||||
if (index === this.currentIndex) return
|
||||
this.$emit('change', index)
|
||||
},
|
||||
// 查询tab的布局信息
|
||||
getTabRect() {
|
||||
let query = uni.createSelectorQuery().in(this)
|
||||
// 遍历所有的tab
|
||||
for (let i = 0; i < this.list.length; i++) {
|
||||
query.select(`#tn-tabs__scroll-view__item-${i}`).fields({
|
||||
size: true,
|
||||
rect: true
|
||||
})
|
||||
}
|
||||
query.exec((res) => {
|
||||
this.tabQueryInfo = res
|
||||
// 初始滚动条和底部滑块的位置
|
||||
this.scrollByIndex()
|
||||
})
|
||||
},
|
||||
// 滚动scrollView,让活动的tab处于屏幕中间
|
||||
scrollByIndex() {
|
||||
// 当前获取tab的布局信息
|
||||
let tabInfo = this.tabQueryInfo[this.currentIndex]
|
||||
if (!tabInfo) return
|
||||
|
||||
// 活动tab的宽度
|
||||
let tabWidth = tabInfo.width
|
||||
// 活动item的左边到组件左边的距离
|
||||
let offsetLeft = tabInfo.left - this.componentLeft
|
||||
// 计算scroll-view移动的距离
|
||||
let scrollLeft = offsetLeft - (this.componentWidth - tabWidth) / 2
|
||||
this.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft
|
||||
|
||||
// 计算当前滑块需要移动的距离,当前活动item的中点到左边的距离减去滑块宽度的一半
|
||||
let left = tabInfo.left + tabInfo.width / 2 - this.componentLeft
|
||||
|
||||
// 计算当前活跃item到组件左边的距离
|
||||
this.scrollBarLeft = left - uni.upx2px(this.barWidth) / 2
|
||||
|
||||
// 防止在计算时出错,所以延迟执行标记不是第一次移动
|
||||
if (this.barMoveFirst) {
|
||||
setTimeout(() => {
|
||||
this.barMoveFirst = false
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef H5 */
|
||||
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
|
||||
scroll-view ::v-deep ::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.tn-tabs {
|
||||
&__scroll-view {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
|
||||
&__box {
|
||||
position: relative;
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
text-align: center;
|
||||
transition-property: background-color, color;
|
||||
}
|
||||
|
||||
&--flex {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,219 +1,223 @@
|
||||
<template>
|
||||
<view
|
||||
class="tn-tag-class tn-tag"
|
||||
:class="[
|
||||
tagClass,
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[tagStyle]"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
name: 'tn-tag',
|
||||
props: {
|
||||
// 序号,用于区分多个标签
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 形状 圆角 radius 椭圆 circle 左半圆 circleLeft 右半圆 circleRight
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标签大小 sm lg
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
// 是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否将元素基点设置在左边
|
||||
originLeft: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否将元素基点设置在右边
|
||||
originRight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagClass() {
|
||||
let clazz = ''
|
||||
// 设置标签的形状
|
||||
switch(this.shape) {
|
||||
case 'radius':
|
||||
clazz += ' tn-radius'
|
||||
break
|
||||
case 'circle':
|
||||
clazz += ' tn-round'
|
||||
break
|
||||
case 'circleLeft':
|
||||
clazz += ' tn-tag--fillet-left'
|
||||
break
|
||||
case 'circleRight':
|
||||
clazz += ' tn-tag--fillet-right'
|
||||
break
|
||||
}
|
||||
|
||||
// 设置为镂空并且设置镂空便可才进行设置
|
||||
if (this.plain) {
|
||||
clazz += ' tn-tag--plain tn-border-solid'
|
||||
if (this.backgroundColor !== '' && this.backgroundColor.includes('tn-bg')) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-border-${color}`
|
||||
}
|
||||
}
|
||||
|
||||
// 设置基准点
|
||||
if (this.originLeft) {
|
||||
clazz += ' tn-tag--origin-left'
|
||||
}
|
||||
if (this.originRight) {
|
||||
clazz += ' tn-tag--origin-right'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
tagStyle() {
|
||||
let style = {}
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.padding = '0 12rpx'
|
||||
style.fontSize = '20rpx'
|
||||
style.height = '32rpx'
|
||||
break
|
||||
case 'lg':
|
||||
style.padding = '0 20rpx'
|
||||
style.fontSize = '28rpx'
|
||||
style.height = '62rpx'
|
||||
break
|
||||
default:
|
||||
style.padding = '0 16rpx'
|
||||
style.fontSize = '24rpx'
|
||||
style.height = '48rpx'
|
||||
break
|
||||
}
|
||||
|
||||
style.width = this.width || '120rpx'
|
||||
style.height = this.height || style.height
|
||||
|
||||
style.padding = this.padding || style.padding
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize !== 0) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (!this.backgroundColorClass) {
|
||||
style.backgroundColor = !this.plain ? (this.backgroundColorStyle || '#FFFFFF') : ''
|
||||
if (this.plain) {
|
||||
style.borderColor = (this.backgroundColorStyle || '#080808')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-tag {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
font-family: Helvetica Neue, Helvetica, sans-serif;
|
||||
white-space: nowrap;
|
||||
// color: #FFFFFF;
|
||||
|
||||
&--fillet-left {
|
||||
border-radius: 50rpx 0 0 50rpx;
|
||||
}
|
||||
|
||||
&--fillet-right {
|
||||
border-radius: 0 50rpx 50rpx 0;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background-color: transparent !important;
|
||||
background-image: none;
|
||||
|
||||
&.tn-round {
|
||||
border-radius: 1000rpx !important;
|
||||
}
|
||||
|
||||
&.tn-radius {
|
||||
border-radius: 12rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--origin-left {
|
||||
transform-origin: 0 center;
|
||||
}
|
||||
|
||||
&--origin-right {
|
||||
transform-origin: 100% center;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
class="tn-tag-class tn-tag"
|
||||
:class="[
|
||||
tagClass,
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="[tagStyle]"
|
||||
@tap="handleClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import componentsColorMixin from '../../libs/mixin/components_color.js'
|
||||
export default {
|
||||
mixins: [ componentsColorMixin ],
|
||||
options: {
|
||||
// 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现(不会出现shadow节点下再去创建元素)
|
||||
virtualHost: true
|
||||
},
|
||||
name: 'tn-tag',
|
||||
props: {
|
||||
// 序号,用于区分多个标签
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: '0'
|
||||
},
|
||||
// 形状 圆角 radius 椭圆 circle 左半圆 circleLeft 右半圆 circleRight
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标签大小 sm lg
|
||||
size: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 外边距
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
// 是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否将元素基点设置在左边
|
||||
originLeft: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否将元素基点设置在右边
|
||||
originRight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tagClass() {
|
||||
let clazz = ''
|
||||
// 设置标签的形状
|
||||
switch(this.shape) {
|
||||
case 'radius':
|
||||
clazz += ' tn-radius'
|
||||
break
|
||||
case 'circle':
|
||||
clazz += ' tn-round'
|
||||
break
|
||||
case 'circleLeft':
|
||||
clazz += ' tn-tag--fillet-left'
|
||||
break
|
||||
case 'circleRight':
|
||||
clazz += ' tn-tag--fillet-right'
|
||||
break
|
||||
}
|
||||
|
||||
// 设置为镂空并且设置镂空便可才进行设置
|
||||
if (this.plain) {
|
||||
clazz += ' tn-tag--plain tn-border-solid'
|
||||
if (this.backgroundColor !== '' && this.backgroundColor.includes('tn-bg')) {
|
||||
const color = this.backgroundColor.slice(this.backgroundColor.lastIndexOf('-') + 1)
|
||||
clazz += ` tn-border-${color}`
|
||||
}
|
||||
}
|
||||
|
||||
// 设置基准点
|
||||
if (this.originLeft) {
|
||||
clazz += ' tn-tag--origin-left'
|
||||
}
|
||||
if (this.originRight) {
|
||||
clazz += ' tn-tag--origin-right'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
tagStyle() {
|
||||
let style = {}
|
||||
switch(this.size) {
|
||||
case 'sm':
|
||||
style.padding = '0 12rpx'
|
||||
style.fontSize = '20rpx'
|
||||
style.height = '32rpx'
|
||||
break
|
||||
case 'lg':
|
||||
style.padding = '0 20rpx'
|
||||
style.fontSize = '28rpx'
|
||||
style.height = '62rpx'
|
||||
break
|
||||
default:
|
||||
style.padding = '0 16rpx'
|
||||
style.fontSize = '24rpx'
|
||||
style.height = '48rpx'
|
||||
break
|
||||
}
|
||||
|
||||
style.width = this.width || '120rpx'
|
||||
style.height = this.height || style.height
|
||||
|
||||
style.padding = this.padding || style.padding
|
||||
if (this.margin) {
|
||||
style.margin = this.margin
|
||||
}
|
||||
|
||||
if (this.fontColorStyle) {
|
||||
style.color = this.fontColorStyle
|
||||
}
|
||||
if (this.fontSize !== 0) {
|
||||
style.fontSize = this.fontSize + this.fontUnit
|
||||
}
|
||||
|
||||
if (!this.backgroundColorClass) {
|
||||
style.backgroundColor = !this.plain ? (this.backgroundColorStyle || '#FFFFFF') : ''
|
||||
if (this.plain) {
|
||||
style.borderColor = (this.backgroundColorStyle || '#080808')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理点击事件
|
||||
handleClick() {
|
||||
this.$emit('click', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
this.$emit('tap', {
|
||||
index: Number(this.index)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-tag {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
font-family: Helvetica Neue, Helvetica, sans-serif;
|
||||
white-space: nowrap;
|
||||
// color: #FFFFFF;
|
||||
|
||||
&--fillet-left {
|
||||
border-radius: 50rpx 0 0 50rpx;
|
||||
}
|
||||
|
||||
&--fillet-right {
|
||||
border-radius: 0 50rpx 50rpx 0;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background-color: transparent !important;
|
||||
background-image: none;
|
||||
|
||||
&.tn-round {
|
||||
border-radius: 1000rpx !important;
|
||||
}
|
||||
|
||||
&.tn-radius {
|
||||
border-radius: 12rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--origin-left {
|
||||
transform-origin: 0 center;
|
||||
}
|
||||
|
||||
&--origin-right {
|
||||
transform-origin: 100% center;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
<template>
|
||||
<view class="tn-time-line-item-class tn-time-line-item">
|
||||
<view>
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tn-time-line-item__node" :style="[nodeStyle]">
|
||||
<slot name="node">
|
||||
<view class="tn-time-line-item__node--dot"></view>
|
||||
</slot>
|
||||
</view>
|
||||
<template>
|
||||
<view class="tn-time-line-item-class tn-time-line-item">
|
||||
<view>
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tn-time-line-item__node" :style="[nodeStyle]">
|
||||
<slot name="node">
|
||||
<view class="tn-time-line-item__node--dot"></view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line-item',
|
||||
props: {
|
||||
// 节点左边图标的绝对定位top值
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nodeStyle() {
|
||||
let style = {}
|
||||
if (this.top !== '') style.top = this.top + 'rpx'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line-item',
|
||||
props: {
|
||||
// 节点左边图标的绝对定位top值
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nodeStyle() {
|
||||
let style = {}
|
||||
if (this.top !== '') style.top = this.top + 'rpx'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
left: -40rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
transform-origin: 0;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
background-color: transparent;
|
||||
|
||||
&--dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 100rpx;
|
||||
background-color: #AAAAAA;
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
left: -40rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
transform-origin: 0;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
background-color: transparent;
|
||||
|
||||
&--dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 100rpx;
|
||||
background-color: #AAAAAA;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
<template>
|
||||
<view class="tn-time-line-item-class tn-time-line-item">
|
||||
<view>
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tn-time-line-item__node" :style="[nodeStyle]">
|
||||
<slot name="node">
|
||||
<view class="tn-time-line-item__node--dot"></view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line-item',
|
||||
props: {
|
||||
// 节点左边图标的绝对定位top值
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nodeStyle() {
|
||||
let style = {}
|
||||
if (this.top !== '') style.top = this.top + 'rpx'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
left: -40rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
transform-origin: 0;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
background-color: transparent;
|
||||
|
||||
&--dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 100rpx;
|
||||
background-color: #AAAAAA;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-time-line-item-class tn-time-line-item">
|
||||
<view>
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
<view class="tn-time-line-item__node" :style="[nodeStyle]">
|
||||
<slot name="node">
|
||||
<view class="tn-time-line-item__node--dot"></view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line-item',
|
||||
props: {
|
||||
// 节点左边图标的绝对定位top值
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
nodeStyle() {
|
||||
let style = {}
|
||||
if (this.top !== '') style.top = this.top + 'rpx'
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&__node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
left: -40rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
transform-origin: 0;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
background-color: transparent;
|
||||
|
||||
&--dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 100rpx;
|
||||
background-color: #AAAAAA;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<template>
|
||||
<view class="tn-time-line-class tn-time-line">
|
||||
<slot></slot>
|
||||
<template>
|
||||
<view class="tn-time-line-class tn-time-line">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line',
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line',
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line {
|
||||
padding-left: 40rpx;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
left: 0;
|
||||
top: 12rpx;
|
||||
bottom: 0;
|
||||
border-left: 1px solid #AAAAAA;
|
||||
transform-origin: 0 0;
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line {
|
||||
padding-left: 40rpx;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
left: 0;
|
||||
top: 12rpx;
|
||||
bottom: 0;
|
||||
border-left: 1px solid #AAAAAA;
|
||||
transform-origin: 0 0;
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
<template>
|
||||
<view class="tn-time-line-class tn-time-line">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line',
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line {
|
||||
padding-left: 40rpx;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
left: 0;
|
||||
top: 12rpx;
|
||||
bottom: 0;
|
||||
border-left: 1px solid #AAAAAA;
|
||||
transform-origin: 0 0;
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="tn-time-line-class tn-time-line">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-time-line',
|
||||
props: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-time-line {
|
||||
padding-left: 40rpx;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
left: 0;
|
||||
top: 12rpx;
|
||||
bottom: 0;
|
||||
border-left: 1px solid #AAAAAA;
|
||||
transform-origin: 0 0;
|
||||
transform: scaleX(0.5);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,240 +1,240 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="visibleSync"
|
||||
class="tn-tips-class tn-tips"
|
||||
:class="[tipsClass]"
|
||||
:style="[tipsStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-tips__content"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
color: fontColorStyle
|
||||
}"
|
||||
>{{ msg }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'tn-tips',
|
||||
props: {
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 提示框显示位置 top center bottom
|
||||
position: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
// 当位置设置为top的时候,设置距离顶部的距离
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tipsClass() {
|
||||
let clazz = ''
|
||||
switch (this.position) {
|
||||
case 'top':
|
||||
clazz += ' tn-tips--top'
|
||||
break
|
||||
case 'center':
|
||||
clazz += ' tn-tips--center'
|
||||
break
|
||||
case 'bottom':
|
||||
clazz += ' tn-tips--bottom'
|
||||
break
|
||||
default:
|
||||
clazz += ' tn-tips--top'
|
||||
}
|
||||
if (this.showTips) {
|
||||
clazz += ' tn-tips--show'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
tipsStyle() {
|
||||
let style = {}
|
||||
if ((this.position === 'top' || this.position === '') && this.top) {
|
||||
style.top = this.top + 'px'
|
||||
}
|
||||
style.zIndex = (this.zIndex ? this.zIndex : this.$t.zIndex.tips) + 1
|
||||
|
||||
return style
|
||||
},
|
||||
backgroundColorStyle() {
|
||||
return this.$t.colorUtils.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.colorUtils.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
fontColorStyle() {
|
||||
return this.$t.colorUtils.getFontColorStyle(this.fontColor)
|
||||
},
|
||||
fontColorClass() {
|
||||
return this.$t.colorUtils.getFontColorInternalClass(this.fontColor)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//关闭提示框定时器
|
||||
timer: null,
|
||||
// 是否渲染组件
|
||||
visibleSync: false,
|
||||
// 是否显示内容
|
||||
showTips: false,
|
||||
// 提示信息
|
||||
msg: '',
|
||||
// 背景颜色
|
||||
backgroundColor: '',
|
||||
// 字体颜色
|
||||
fontColor: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show(options = {}) {
|
||||
const {
|
||||
duration = 2000,
|
||||
msg = '',
|
||||
backgroundColor = '',
|
||||
fontColor = ''
|
||||
} = options
|
||||
|
||||
if (this.timer !== null) clearTimeout(this.timer)
|
||||
|
||||
// 如果没有设置内容则不弹出
|
||||
if (!msg) {
|
||||
this._clearOptions()
|
||||
this.$emit('close')
|
||||
return
|
||||
}
|
||||
|
||||
this.msg = msg
|
||||
this.backgroundColor = backgroundColor || '#01BEFF'
|
||||
this.fontColor = fontColor || '#FFFFFF'
|
||||
|
||||
this.change('visibleSync', 'showTips', true)
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
this.change('showTips', 'visibleSync', false)
|
||||
}, duration)
|
||||
},
|
||||
|
||||
// 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
|
||||
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
|
||||
change(param1, param2, status) {
|
||||
this[param1] = status
|
||||
if (status) {
|
||||
// #ifdef H5 || MP
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
}, 50)
|
||||
// #endif
|
||||
// #ifndef H5 || MP
|
||||
this.$nextTick(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
this._clearOptions()
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
|
||||
// 清除传递的参数
|
||||
_clearOptions() {
|
||||
this.msg = ''
|
||||
this.backgroundColor = ''
|
||||
this.fontColor = ''
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
/*注意问题:
|
||||
1、fixed 元素宽度无法自适应,所以增加了子元素
|
||||
2、fixed 和 display冲突导致动画效果消失,暂时使用visibility替代
|
||||
*/
|
||||
.tn-tips {
|
||||
height: auto;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
&__content {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--top {
|
||||
width: 100% !important;
|
||||
/* padding: 18rpx 30rpx; */
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translateY(-100%) translateZ(0);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&--center {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
bottom: 120rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&--center, &--bottom {
|
||||
.content {
|
||||
border-radius: 8rpx;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--center, &--bottom {
|
||||
.tn-tips__content {
|
||||
padding: 18rpx 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--show {
|
||||
opacity: 1;
|
||||
|
||||
&.tn-tips--top {
|
||||
transform: translateY(0) translateZ(0) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<view
|
||||
v-if="visibleSync"
|
||||
class="tn-tips-class tn-tips"
|
||||
:class="[tipsClass]"
|
||||
:style="[tipsStyle]"
|
||||
>
|
||||
<view
|
||||
class="tn-tips__content"
|
||||
:class="[
|
||||
backgroundColorClass,
|
||||
fontColorClass
|
||||
]"
|
||||
:style="{
|
||||
backgroundColor: backgroundColorStyle,
|
||||
color: fontColorStyle
|
||||
}"
|
||||
>{{ msg }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'tn-tips',
|
||||
props: {
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 提示框显示位置 top center bottom
|
||||
position: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
// 当位置设置为top的时候,设置距离顶部的距离
|
||||
top: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tipsClass() {
|
||||
let clazz = ''
|
||||
switch (this.position) {
|
||||
case 'top':
|
||||
clazz += ' tn-tips--top'
|
||||
break
|
||||
case 'center':
|
||||
clazz += ' tn-tips--center'
|
||||
break
|
||||
case 'bottom':
|
||||
clazz += ' tn-tips--bottom'
|
||||
break
|
||||
default:
|
||||
clazz += ' tn-tips--top'
|
||||
}
|
||||
if (this.showTips) {
|
||||
clazz += ' tn-tips--show'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
tipsStyle() {
|
||||
let style = {}
|
||||
if ((this.position === 'top' || this.position === '') && this.top) {
|
||||
style.top = this.top + 'px'
|
||||
}
|
||||
style.zIndex = (this.zIndex ? this.zIndex : this.$t.zIndex.tips) + 1
|
||||
|
||||
return style
|
||||
},
|
||||
backgroundColorStyle() {
|
||||
return this.$t.color.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.color.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
fontColorStyle() {
|
||||
return this.$t.color.getFontColorStyle(this.fontColor)
|
||||
},
|
||||
fontColorClass() {
|
||||
return this.$t.color.getFontColorInternalClass(this.fontColor)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
//关闭提示框定时器
|
||||
timer: null,
|
||||
// 是否渲染组件
|
||||
visibleSync: false,
|
||||
// 是否显示内容
|
||||
showTips: false,
|
||||
// 提示信息
|
||||
msg: '',
|
||||
// 背景颜色
|
||||
backgroundColor: '',
|
||||
// 字体颜色
|
||||
fontColor: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
show(options = {}) {
|
||||
const {
|
||||
duration = 2000,
|
||||
msg = '',
|
||||
backgroundColor = '',
|
||||
fontColor = ''
|
||||
} = options
|
||||
|
||||
if (this.timer !== null) clearTimeout(this.timer)
|
||||
|
||||
// 如果没有设置内容则不弹出
|
||||
if (!msg) {
|
||||
this._clearOptions()
|
||||
this.$emit('close')
|
||||
return
|
||||
}
|
||||
|
||||
this.msg = msg
|
||||
this.backgroundColor = backgroundColor || '#01BEFF'
|
||||
this.fontColor = fontColor || '#FFFFFF'
|
||||
|
||||
this.change('visibleSync', 'showTips', true)
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
this.change('showTips', 'visibleSync', false)
|
||||
}, duration)
|
||||
},
|
||||
|
||||
// 关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
|
||||
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
|
||||
change(param1, param2, status) {
|
||||
this[param1] = status
|
||||
if (status) {
|
||||
// #ifdef H5 || MP
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
}, 50)
|
||||
// #endif
|
||||
// #ifndef H5 || MP
|
||||
this.$nextTick(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
})
|
||||
// #endif
|
||||
} else {
|
||||
this.timer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit(status ? 'open' : 'close')
|
||||
this._clearOptions()
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
|
||||
// 清除传递的参数
|
||||
_clearOptions() {
|
||||
this.msg = ''
|
||||
this.backgroundColor = ''
|
||||
this.fontColor = ''
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
/*注意问题:
|
||||
1、fixed 元素宽度无法自适应,所以增加了子元素
|
||||
2、fixed 和 display冲突导致动画效果消失,暂时使用visibility替代
|
||||
*/
|
||||
.tn-tips {
|
||||
height: auto;
|
||||
position: fixed;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
&__content {
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&--top {
|
||||
width: 100% !important;
|
||||
/* padding: 18rpx 30rpx; */
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translateY(-100%) translateZ(0);
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&--center {
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
bottom: 120rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&--center, &--bottom {
|
||||
.content {
|
||||
border-radius: 8rpx;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--center, &--bottom {
|
||||
.tn-tips__content {
|
||||
padding: 18rpx 30rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&--show {
|
||||
opacity: 1;
|
||||
|
||||
&.tn-tips--top {
|
||||
transform: translateY(0) translateZ(0) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,227 +1,227 @@
|
||||
<template>
|
||||
<view v-if="visible">
|
||||
<view
|
||||
class="tn-toast-class tn-toast"
|
||||
:class="[toastClass]"
|
||||
:style="[toastStyle]"
|
||||
>
|
||||
<image v-if="image" :src="image" class="tn-toast__img" :class="{'tn-margin-bottom-sm': title || content}"></image>
|
||||
<view v-if="icon" class="tn-toast__icon">
|
||||
<view :class="['tn-icon-' + icon]"></view>
|
||||
</view>
|
||||
<view
|
||||
v-if="title"
|
||||
class="tn-toast__text"
|
||||
:class="[haveIcon || haveContent ? '' : 'tn-toast--unicon']"
|
||||
>{{ title }}</view>
|
||||
<view
|
||||
v-if="haveContent"
|
||||
class="tn-toast__text tn-toast__content"
|
||||
>{{ content }}</view>
|
||||
</view>
|
||||
|
||||
<view class="tn-toast__mask" :class="[visible ? 'tn-toast__mask--show' : '']" :style="[maskStyle]"></view>
|
||||
<template>
|
||||
<view v-if="visible">
|
||||
<view
|
||||
class="tn-toast-class tn-toast"
|
||||
:class="[toastClass]"
|
||||
:style="[toastStyle]"
|
||||
>
|
||||
<image v-if="image" :src="image" class="tn-toast__img" :class="{'tn-margin-bottom-sm': title || content}"></image>
|
||||
<view v-if="icon" class="tn-toast__icon">
|
||||
<view :class="['tn-icon-' + icon]"></view>
|
||||
</view>
|
||||
<view
|
||||
v-if="title"
|
||||
class="tn-toast__text"
|
||||
:class="[haveIcon || haveContent ? '' : 'tn-toast--unicon']"
|
||||
>{{ title }}</view>
|
||||
<view
|
||||
v-if="haveContent"
|
||||
class="tn-toast__text tn-toast__content"
|
||||
>{{ content }}</view>
|
||||
</view>
|
||||
|
||||
<view class="tn-toast__mask" :class="[visible ? 'tn-toast__mask--show' : '']" :style="[maskStyle]"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'tn-toast',
|
||||
props: {
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
toastClass() {
|
||||
let clazz = ''
|
||||
if (this.visible) {
|
||||
clazz += ' tn-toast--show'
|
||||
}
|
||||
|
||||
if (this.content) {
|
||||
clazz += ' tn-toast--padding'
|
||||
}
|
||||
if (this.icon || this.image) {
|
||||
clazz += ' tn-toast--unicon'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
toastStyle() {
|
||||
let style = {}
|
||||
style.width = 'auto'
|
||||
if (this.icon || this.image) {
|
||||
// style.width = this.content ? '420rpx' : '360rpx'
|
||||
}
|
||||
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.toast
|
||||
|
||||
return style
|
||||
},
|
||||
maskStyle() {
|
||||
let style = {}
|
||||
const zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.toast
|
||||
style.zIndex = zIndex - 1
|
||||
return style
|
||||
},
|
||||
haveIcon() {
|
||||
return this.icon || this.image
|
||||
},
|
||||
haveContent() {
|
||||
return this.content
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 自动关闭定时器
|
||||
timer: null,
|
||||
// 是否显示
|
||||
visible: false,
|
||||
// 显示的标题
|
||||
title: '操作成功',
|
||||
// 显示的内容
|
||||
content: "",
|
||||
// 是否显示icon (icon库的图标)
|
||||
icon: '',
|
||||
// 是否显示图片 (图片地址)
|
||||
image: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 显示弹框
|
||||
show(options = {}) {
|
||||
const {
|
||||
duration = 2000,
|
||||
title = '',
|
||||
content = '',
|
||||
icon = '',
|
||||
image = ''
|
||||
} = options
|
||||
if (this.timer !== null ){
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
// 如果没有设置任何内容就不弹出
|
||||
if (!icon && !image && !title && !content) {
|
||||
this._clearOptions()
|
||||
this.$emit('closed')
|
||||
return
|
||||
}
|
||||
|
||||
this.visible = true
|
||||
this.title = title
|
||||
this.content = content
|
||||
this.icon = icon
|
||||
if (!icon) {
|
||||
this.image = image
|
||||
}
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
this.visible = false
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
this._clearOptions()
|
||||
this.$emit('closed')
|
||||
}, duration)
|
||||
},
|
||||
|
||||
|
||||
// 清除传递的参数
|
||||
_clearOptions() {
|
||||
this.title = ''
|
||||
this.content = ''
|
||||
this.icon = ''
|
||||
this.image = ''
|
||||
}
|
||||
}
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'tn-toast',
|
||||
props: {
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
toastClass() {
|
||||
let clazz = ''
|
||||
if (this.visible) {
|
||||
clazz += ' tn-toast--show'
|
||||
}
|
||||
|
||||
if (this.content) {
|
||||
clazz += ' tn-toast--padding'
|
||||
}
|
||||
if (this.icon || this.image) {
|
||||
clazz += ' tn-toast--unicon'
|
||||
}
|
||||
|
||||
return clazz
|
||||
},
|
||||
toastStyle() {
|
||||
let style = {}
|
||||
style.width = 'auto'
|
||||
if (this.icon || this.image) {
|
||||
// style.width = this.content ? '420rpx' : '360rpx'
|
||||
}
|
||||
|
||||
style.zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.toast
|
||||
|
||||
return style
|
||||
},
|
||||
maskStyle() {
|
||||
let style = {}
|
||||
const zIndex = this.zIndex ? this.zIndex : this.$t.zIndex.toast
|
||||
style.zIndex = zIndex - 1
|
||||
return style
|
||||
},
|
||||
haveIcon() {
|
||||
return this.icon || this.image
|
||||
},
|
||||
haveContent() {
|
||||
return this.content
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 自动关闭定时器
|
||||
timer: null,
|
||||
// 是否显示
|
||||
visible: false,
|
||||
// 显示的标题
|
||||
title: '操作成功',
|
||||
// 显示的内容
|
||||
content: "",
|
||||
// 是否显示icon (icon库的图标)
|
||||
icon: '',
|
||||
// 是否显示图片 (图片地址)
|
||||
image: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 显示弹框
|
||||
show(options = {}) {
|
||||
const {
|
||||
duration = 2000,
|
||||
title = '',
|
||||
content = '',
|
||||
icon = '',
|
||||
image = ''
|
||||
} = options
|
||||
if (this.timer !== null ){
|
||||
clearTimeout(this.timer)
|
||||
}
|
||||
// 如果没有设置任何内容就不弹出
|
||||
if (!icon && !image && !title && !content) {
|
||||
this._clearOptions()
|
||||
this.$emit('closed')
|
||||
return
|
||||
}
|
||||
|
||||
this.visible = true
|
||||
this.title = title
|
||||
this.content = content
|
||||
this.icon = icon
|
||||
if (!icon) {
|
||||
this.image = image
|
||||
}
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
this.visible = false
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
this._clearOptions()
|
||||
this.$emit('closed')
|
||||
}, duration)
|
||||
},
|
||||
|
||||
|
||||
// 清除传递的参数
|
||||
_clearOptions() {
|
||||
this.title = ''
|
||||
this.content = ''
|
||||
this.icon = ''
|
||||
this.image = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-toast {
|
||||
height: auto;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 10rpx;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 48%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: 0.3 ease-in-out;
|
||||
transition-property: opacity, visibility;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 20rpx 20rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--show {
|
||||
opacity: 1;
|
||||
|
||||
&.tn-toast--padding {
|
||||
padding-top: 50rpx !important;
|
||||
padding-bottom: 50rpx !important;
|
||||
}
|
||||
|
||||
&.tn-toast--unicon {
|
||||
padding: 20rpx 20rpx 20rpx 20rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 28rpx;
|
||||
line-height: 28rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #FFFFFF;
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding-top: 10rpx;
|
||||
font-size: 24rpx !important;
|
||||
}
|
||||
|
||||
&--unicon {
|
||||
padding: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&--padding {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
&__mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
transition: 0.3s ease-in-out;
|
||||
transition-property: opacity;
|
||||
opacity: 0;
|
||||
|
||||
&--show {
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tn-toast {
|
||||
height: auto;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
border-radius: 10rpx;
|
||||
opacity: 0;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 48%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: 0.3 ease-in-out;
|
||||
transition-property: opacity, visibility;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 20rpx 20rpx 20rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--show {
|
||||
opacity: 1;
|
||||
|
||||
&.tn-toast--padding {
|
||||
padding-top: 50rpx !important;
|
||||
padding-bottom: 50rpx !important;
|
||||
}
|
||||
|
||||
&.tn-toast--unicon {
|
||||
padding: 20rpx 20rpx 20rpx 20rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 28rpx;
|
||||
line-height: 28rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
color: #FFFFFF;
|
||||
font-size: 64rpx;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding-top: 10rpx;
|
||||
font-size: 24rpx !important;
|
||||
}
|
||||
|
||||
&--unicon {
|
||||
padding: 0;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&--padding {
|
||||
padding: 10rpx;
|
||||
}
|
||||
|
||||
&__mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
transition: 0.3s ease-in-out;
|
||||
transition-property: opacity;
|
||||
opacity: 0;
|
||||
|
||||
&--show {
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,324 +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>
|
||||
<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>
|
||||
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 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>
|
||||
|
||||
@@ -1,149 +1,149 @@
|
||||
<template>
|
||||
<view class="tn-code-class tn-code">
|
||||
|
||||
<template>
|
||||
<view class="tn-code-class tn-code">
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-verification-code',
|
||||
props: {
|
||||
// 倒计时总秒数
|
||||
seconds: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
// 开始时提示文字
|
||||
startText: {
|
||||
type: String,
|
||||
default: '获取验证码'
|
||||
},
|
||||
// 倒计时提示文字
|
||||
countDownText: {
|
||||
type: String,
|
||||
default: 's秒后重新获取'
|
||||
},
|
||||
// 结束时提示文字
|
||||
endText: {
|
||||
type: String,
|
||||
default: '重新获取'
|
||||
},
|
||||
// 是否在H5刷新或各端返回再进入时继续倒计时
|
||||
keepRunning: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
secNum: this.seconds,
|
||||
// 是否可以执行验证码操作
|
||||
canGetCode: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
seconds: {
|
||||
handler(n) {
|
||||
this.secNum = n
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkKeepRunning()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.setTimeToStorage()
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 检查是否继续运行
|
||||
checkKeepRunning() {
|
||||
// 获取上一次退出页面时的时间戳,如果没有上次保存,该值为空
|
||||
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$tCountDownTimestamp'))
|
||||
if (!lastTimestamp) return this.changeEvent(this.startText)
|
||||
// 当前秒的时间戳
|
||||
// + new Date() 相当于 new Date().getTime()
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 判断当前的时间戳,是否小于上一次的设定结束的时间,提前于结束的时间戳
|
||||
if (this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
|
||||
// 剩余尚未执行完倒计时秒数
|
||||
this.secNum = lastTimestamp - nowTimestamp
|
||||
// 清除本地保存的变量
|
||||
uni.removeStorageSync(this.uniqueKey + '_$tCountDownTimestamp')
|
||||
// 开始倒计时
|
||||
this.start()
|
||||
} else {
|
||||
// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
|
||||
this.changeEvent(this.startText);
|
||||
}
|
||||
},
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 防止快速点击获取验证码按钮导致产生多个定时器导致混乱
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.$emit('start')
|
||||
this.canGetCode = false
|
||||
|
||||
this.changeEvent(this.countDownText.replace(/s|S/, this.secNum))
|
||||
this.setTimeToStorage()
|
||||
this.timer = setInterval(() => {
|
||||
if (--this.secNum) {
|
||||
this.changeEvent(this.countDownText.replace(/s|S/, this.secNum))
|
||||
} else {
|
||||
// 倒计时结束,清空定时器、重置提示信息
|
||||
this.reset()
|
||||
this.$emit('end')
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
// 重置倒计时
|
||||
reset() {
|
||||
this.canGetCode = true
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.secNum = this.seconds
|
||||
this.changeEvent(this.endText)
|
||||
},
|
||||
// 倒计时改变事件
|
||||
changeEvent(text) {
|
||||
this.$emit('change', text)
|
||||
},
|
||||
// 保存当前时间戳
|
||||
// 防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
|
||||
setTimeToStorage() {
|
||||
if (!this.keepRunning ||!this.timer) return
|
||||
// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
|
||||
// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
|
||||
if (this.secNum > 0 && this.secNum <= this.seconds) {
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 保存本次倒计时结束时候的时间戳
|
||||
uni.setStorageSync(this.uniqueKey + '_$tCountDownTimestamp', nowTimestamp + this.secNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
export default {
|
||||
name: 'tn-verification-code',
|
||||
props: {
|
||||
// 倒计时总秒数
|
||||
seconds: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
// 开始时提示文字
|
||||
startText: {
|
||||
type: String,
|
||||
default: '获取验证码'
|
||||
},
|
||||
// 倒计时提示文字
|
||||
countDownText: {
|
||||
type: String,
|
||||
default: 's秒后重新获取'
|
||||
},
|
||||
// 结束时提示文字
|
||||
endText: {
|
||||
type: String,
|
||||
default: '重新获取'
|
||||
},
|
||||
// 是否在H5刷新或各端返回再进入时继续倒计时
|
||||
keepRunning: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
secNum: this.seconds,
|
||||
// 是否可以执行验证码操作
|
||||
canGetCode: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
seconds: {
|
||||
handler(n) {
|
||||
this.secNum = n
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkKeepRunning()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.setTimeToStorage()
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 检查是否继续运行
|
||||
checkKeepRunning() {
|
||||
// 获取上一次退出页面时的时间戳,如果没有上次保存,该值为空
|
||||
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$tCountDownTimestamp'))
|
||||
if (!lastTimestamp) return this.changeEvent(this.startText)
|
||||
// 当前秒的时间戳
|
||||
// + new Date() 相当于 new Date().getTime()
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 判断当前的时间戳,是否小于上一次的设定结束的时间,提前于结束的时间戳
|
||||
if (this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
|
||||
// 剩余尚未执行完倒计时秒数
|
||||
this.secNum = lastTimestamp - nowTimestamp
|
||||
// 清除本地保存的变量
|
||||
uni.removeStorageSync(this.uniqueKey + '_$tCountDownTimestamp')
|
||||
// 开始倒计时
|
||||
this.start()
|
||||
} else {
|
||||
// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
|
||||
this.changeEvent(this.startText);
|
||||
}
|
||||
},
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 防止快速点击获取验证码按钮导致产生多个定时器导致混乱
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.$emit('start')
|
||||
this.canGetCode = false
|
||||
|
||||
this.changeEvent(this.countDownText.replace(/s|S/, this.secNum))
|
||||
this.setTimeToStorage()
|
||||
this.timer = setInterval(() => {
|
||||
if (--this.secNum) {
|
||||
this.changeEvent(this.countDownText.replace(/s|S/, this.secNum))
|
||||
} else {
|
||||
// 倒计时结束,清空定时器、重置提示信息
|
||||
this.reset()
|
||||
this.$emit('end')
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
// 重置倒计时
|
||||
reset() {
|
||||
this.canGetCode = true
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.secNum = this.seconds
|
||||
this.changeEvent(this.endText)
|
||||
},
|
||||
// 倒计时改变事件
|
||||
changeEvent(text) {
|
||||
this.$emit('change', text)
|
||||
},
|
||||
// 保存当前时间戳
|
||||
// 防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
|
||||
setTimeToStorage() {
|
||||
if (!this.keepRunning ||!this.timer) return
|
||||
// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
|
||||
// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
|
||||
if (this.secNum > 0 && this.secNum <= this.seconds) {
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 保存本次倒计时结束时候的时间戳
|
||||
uni.setStorageSync(this.uniqueKey + '_$tCountDownTimestamp', nowTimestamp + this.secNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tn-code {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
<style lang="scss" scoped>
|
||||
.tn-code {
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
||||
+133
-14
File diff suppressed because one or more lines are too long
+73
-73
@@ -1,73 +1,73 @@
|
||||
// 引入全局mixin
|
||||
import mixin from './libs/mixin/mixin.js'
|
||||
// 全局挂载引入http相关请求拦截插件
|
||||
import Request from './libs/luch-request'
|
||||
|
||||
// 调试输出信息
|
||||
function wranning(str) {
|
||||
// 开发环境进行信息输出,主要是一些报错信息
|
||||
// 这个环境的来由是在程序编写时候,点击hx编辑器运行调试代码的时候,详见:
|
||||
// https://uniapp.dcloud.io/frame?id=%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83%e5%92%8c%e7%94%9f%e4%ba%a7%e7%8e%af%e5%a2%83
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(str)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新自定义状态栏的信息
|
||||
import updateCustomBarInfo from './libs/function/updateCustomBarInfo.js'
|
||||
// 获取颜色工具
|
||||
import colorUtils from './libs/function/colorUtils.js'
|
||||
// 消息工具
|
||||
import messageUtils from './libs/function/messageUtils.js'
|
||||
// 获取唯一id
|
||||
import uuid from './libs/function/uuid.js'
|
||||
// 数组工具
|
||||
import array from './libs/function/array.js'
|
||||
|
||||
// 规则检验
|
||||
import test from './libs/function/test.js'
|
||||
// 获取整个父组件
|
||||
import $parent from './libs/function/$parent.js'
|
||||
// 格式化字符串工具
|
||||
import string from './libs/function/string.js'
|
||||
// 格式化数值工具
|
||||
import number from './libs/function/number.js'
|
||||
// 深度复制
|
||||
import deepClone from './libs/function/deepClone.js'
|
||||
|
||||
// z-index配置信息
|
||||
import zIndex from './libs/config/zIndex.js'
|
||||
// 主题颜色信息
|
||||
import color from './libs/config/color.js'
|
||||
|
||||
const $t = {
|
||||
http: new Request(),
|
||||
updateCustomBar: updateCustomBarInfo,
|
||||
colorUtils,
|
||||
messageUtils,
|
||||
uuid,
|
||||
array,
|
||||
test,
|
||||
$parent,
|
||||
string,
|
||||
number,
|
||||
deepClone,
|
||||
zIndex,
|
||||
color,
|
||||
}
|
||||
|
||||
// 挂载到uni对象上
|
||||
uni.$t = $t
|
||||
|
||||
const install = Vue => {
|
||||
// 全局混入
|
||||
Vue.mixin(mixin)
|
||||
|
||||
// Filter格式化
|
||||
|
||||
Vue.prototype.$t = $t
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
}
|
||||
// 引入全局mixin
|
||||
import mixin from './libs/mixin/mixin.js'
|
||||
// 全局挂载引入http相关请求拦截插件
|
||||
import Request from './libs/luch-request'
|
||||
|
||||
// 调试输出信息
|
||||
function wranning(str) {
|
||||
// 开发环境进行信息输出,主要是一些报错信息
|
||||
// 这个环境的来由是在程序编写时候,点击hx编辑器运行调试代码的时候,详见:
|
||||
// https://uniapp.dcloud.io/frame?id=%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83%e5%92%8c%e7%94%9f%e4%ba%a7%e7%8e%af%e5%a2%83
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn(str)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新自定义状态栏的信息
|
||||
import updateCustomBarInfo from './libs/function/updateCustomBarInfo.js'
|
||||
// 获取颜色工具
|
||||
import color from './libs/function/color.js'
|
||||
// 消息工具
|
||||
import message from './libs/function/message.js'
|
||||
// 获取唯一id
|
||||
import uuid from './libs/function/uuid.js'
|
||||
// 数组工具
|
||||
import array from './libs/function/array.js'
|
||||
|
||||
// 规则检验
|
||||
import test from './libs/function/test.js'
|
||||
// 获取整个父组件
|
||||
import $parent from './libs/function/$parent.js'
|
||||
// 格式化字符串工具
|
||||
import string from './libs/function/string.js'
|
||||
// 格式化数值工具
|
||||
import number from './libs/function/number.js'
|
||||
// 深度复制
|
||||
import deepClone from './libs/function/deepClone.js'
|
||||
|
||||
// z-index配置信息
|
||||
import zIndex from './libs/config/zIndex.js'
|
||||
// 主题颜色信息
|
||||
import colorInfo from './libs/config/color.js'
|
||||
|
||||
const $t = {
|
||||
http: new Request(),
|
||||
updateCustomBar: updateCustomBarInfo,
|
||||
color,
|
||||
message,
|
||||
uuid,
|
||||
array,
|
||||
test,
|
||||
$parent,
|
||||
string,
|
||||
number,
|
||||
deepClone,
|
||||
zIndex,
|
||||
colorInfo,
|
||||
}
|
||||
|
||||
// 挂载到uni对象上
|
||||
uni.$t = $t
|
||||
|
||||
const install = Vue => {
|
||||
// 全局混入
|
||||
Vue.mixin(mixin)
|
||||
|
||||
// Filter格式化
|
||||
|
||||
Vue.prototype.$t = $t
|
||||
}
|
||||
|
||||
export default {
|
||||
install
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 引入公共基础类
|
||||
@import './libs/css/main.scss';
|
||||
@import './libs/css/color.scss';
|
||||
|
||||
// 引入公共基础类
|
||||
@import './libs/css/main.scss';
|
||||
@import './libs/css/color.scss';
|
||||
|
||||
// 小程序特有的样式
|
||||
/* #ifdef MP */
|
||||
@import "./libs/css/style.mp.scss";
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// TuniaoUI颜色值
|
||||
export default {
|
||||
mainColor: '#01BEFF',
|
||||
reverseMainColor: '#FFF00D',
|
||||
femaleColor: '#FF71D2',
|
||||
maleColor: '#82B2FF',
|
||||
mainOrange: '#FBBD12',
|
||||
bgColor: '#FFFFFF',
|
||||
spaceColor: '#F8F7F8',
|
||||
fontColor: '#080808',
|
||||
fontSubColor: '#AAAAAA',
|
||||
contentColor: '#838383',
|
||||
fontHolderColor: '#E6E6E6',
|
||||
maskBgColor: 'rgba(0, 0, 0, 0.4)',
|
||||
// TuniaoUI颜色值
|
||||
export default {
|
||||
mainColor: '#01BEFF',
|
||||
reverseMainColor: '#FFF00D',
|
||||
femaleColor: '#FF71D2',
|
||||
maleColor: '#82B2FF',
|
||||
mainOrange: '#FBBD12',
|
||||
bgColor: '#FFFFFF',
|
||||
spaceColor: '#F8F7F8',
|
||||
fontColor: '#080808',
|
||||
fontSubColor: '#AAAAAA',
|
||||
contentColor: '#838383',
|
||||
fontHolderColor: '#E6E6E6',
|
||||
maskBgColor: 'rgba(0, 0, 0, 0.4)',
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
// 各组件的z-index值
|
||||
export default {
|
||||
landsacpe: 29100,
|
||||
navbar: 29090,
|
||||
toast: 20090,
|
||||
goodsNav: 20089,
|
||||
alert: 20085,
|
||||
modal: 20080,
|
||||
popup: 20075,
|
||||
tips: 19080,
|
||||
sticky: 19075,
|
||||
indexListSticky: 19070,
|
||||
fab: 19060,
|
||||
mask: 9999,
|
||||
// 各组件的z-index值
|
||||
export default {
|
||||
landsacpe: 29100,
|
||||
navbar: 29090,
|
||||
toast: 20090,
|
||||
goodsNav: 20089,
|
||||
alert: 20085,
|
||||
modal: 20080,
|
||||
popup: 20075,
|
||||
tips: 19080,
|
||||
sticky: 19075,
|
||||
indexListSticky: 19070,
|
||||
fab: 19060,
|
||||
mask: 9999,
|
||||
tableTr: 1001,
|
||||
tableTd: 1000
|
||||
}
|
||||
+563
-563
File diff suppressed because it is too large
Load Diff
+721
-721
File diff suppressed because it is too large
Load Diff
@@ -5,31 +5,31 @@
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 双标签 start*/
|
||||
.capsule {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
width: 20%;
|
||||
min-width: 136rpx;
|
||||
height: 45rpx;
|
||||
|
||||
.capsule-tag {
|
||||
margin: 0;
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: 0rpx;
|
||||
border-bottom-right-radius: 0rpx;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
||||
&::after {
|
||||
border-top-left-radius: 0rpx;
|
||||
border-bottom-left-radius: 0rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 双标签 start*/
|
||||
.capsule {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
width: 20%;
|
||||
min-width: 136rpx;
|
||||
height: 45rpx;
|
||||
|
||||
.capsule-tag {
|
||||
margin: 0;
|
||||
|
||||
&:first-child {
|
||||
border-top-right-radius: 0rpx;
|
||||
border-bottom-right-radius: 0rpx;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
|
||||
&::after {
|
||||
border-top-left-radius: 0rpx;
|
||||
border-bottom-left-radius: 0rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 双标签 end*/
|
||||
@@ -4,49 +4,53 @@
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
/* 微信小程序编译后页面有组件名的元素,特别处理 start */
|
||||
}
|
||||
/* 微信小程序编译后页面有组件名的元素,特别处理 start */
|
||||
/* #ifdef MP-WEIXIN || MP-QQ */
|
||||
// 各家小程序宫格组件外层设置为100%,避免受到父元素display: flex;的影响
|
||||
|
||||
/* 双标签 start*/
|
||||
.capsule {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
width: 20%;
|
||||
min-width: 136rpx;
|
||||
height: 45rpx;
|
||||
|
||||
tn-tag {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
&:first-child {
|
||||
.tn-tag {
|
||||
border-top-right-radius: 0rpx;
|
||||
border-bottom-right-radius: 0rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.tn-tag {
|
||||
&::after {
|
||||
border-top-left-radius: 0rpx;
|
||||
border-bottom-left-radius: 0rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// 各家小程序宫格组件外层设置为100%,避免受到父元素display: flex;的影响
|
||||
|
||||
/* 双标签 start*/
|
||||
.capsule {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
width: 20%;
|
||||
min-width: 136rpx;
|
||||
height: 45rpx;
|
||||
|
||||
tn-tag {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
&:first-child {
|
||||
.tn-tag {
|
||||
border-top-right-radius: 0rpx;
|
||||
border-bottom-right-radius: 0rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
.tn-tag {
|
||||
&::after {
|
||||
border-top-left-radius: 0rpx;
|
||||
border-bottom-left-radius: 0rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 双标签 end*/
|
||||
|
||||
/* #endif */
|
||||
/* 微信小程序编译后页面有组件名的元素,特别处理 end */
|
||||
page {
|
||||
// overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 头条小程序编译后页面有组件名的元素,特别处理 start */
|
||||
/* #endif */
|
||||
/* 微信小程序编译后页面有组件名的元素,特别处理 end */
|
||||
|
||||
/* 头条小程序编译后页面有组件名的元素,特别处理 start */
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 各家小程序宫格组件外层设置为100%,避免受到父元素display: flex;的影响
|
||||
/* #endif */
|
||||
/* #endif */
|
||||
/* 头条小程序编译后页面有组件名的元素,特别处理 end */
|
||||
@@ -1,18 +1,18 @@
|
||||
// 获取父组件的参数,在支付宝小程序中不支持provide/inject的写法
|
||||
// 在非H5中this.$parent可以获取到父组件,但是在H5中需要多次调用this.$parent.$parent.xxx
|
||||
// 传递默认值undefined表示查找最顶层的$parent
|
||||
export default function $parent(name = undefined) {
|
||||
let parent = this.$parent
|
||||
// 通过whle遍历,这里主要是为了H5需要多层解析
|
||||
while(parent) {
|
||||
// 父组件
|
||||
if (parent.$options && parent.$options.name !== name) {
|
||||
// 如果组件的name不相等,则继续查找
|
||||
parent = parent.$parent
|
||||
} else {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
// 获取父组件的参数,在支付宝小程序中不支持provide/inject的写法
|
||||
// 在非H5中this.$parent可以获取到父组件,但是在H5中需要多次调用this.$parent.$parent.xxx
|
||||
// 传递默认值undefined表示查找最顶层的$parent
|
||||
export default function $parent(name = undefined) {
|
||||
let parent = this.$parent
|
||||
// 通过whle遍历,这里主要是为了H5需要多层解析
|
||||
while(parent) {
|
||||
// 父组件
|
||||
if (parent.$options && parent.$options.name !== name) {
|
||||
// 如果组件的name不相等,则继续查找
|
||||
parent = parent.$parent
|
||||
} else {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
/**
|
||||
* 打乱传入的数组
|
||||
*
|
||||
* @param {Array} array 待打乱的数组
|
||||
*/
|
||||
function random(array = []) {
|
||||
return array.sort(() => Math.random() - 0.5)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数组
|
||||
*
|
||||
* @param {Object} arr
|
||||
*/
|
||||
function isArray(arr) {
|
||||
return Object.prototype.toString.call(arr) === '[object Array]'
|
||||
}
|
||||
|
||||
export default {
|
||||
random,
|
||||
isArray
|
||||
/**
|
||||
* 打乱传入的数组
|
||||
*
|
||||
* @param {Array} array 待打乱的数组
|
||||
*/
|
||||
function random(array = []) {
|
||||
return array.sort(() => Math.random() - 0.5)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数组
|
||||
*
|
||||
* @param {Object} arr
|
||||
*/
|
||||
function isArray(arr) {
|
||||
return Object.prototype.toString.call(arr) === '[object Array]'
|
||||
}
|
||||
|
||||
export default {
|
||||
random,
|
||||
isArray
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
let color = [
|
||||
'red',
|
||||
'purplered',
|
||||
'purple',
|
||||
'bluepurple',
|
||||
'aquablue',
|
||||
'blue',
|
||||
'indigo',
|
||||
'cyan',
|
||||
'teal',
|
||||
'green',
|
||||
'yellowgreen',
|
||||
'lime',
|
||||
'yellow',
|
||||
'orangeyellow',
|
||||
'orange',
|
||||
'orangered',
|
||||
'brown',
|
||||
'grey',
|
||||
'gray'
|
||||
]
|
||||
|
||||
// 酷炫颜色的数量
|
||||
const COOL_BG_COLOR_COUNT = 16
|
||||
|
||||
/**
|
||||
* 获取图鸟配色颜色列表
|
||||
*/
|
||||
function getTuniaoColorList() {
|
||||
return color
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的随机颜色对应的类
|
||||
* @param {String} type 颜色类型
|
||||
*/
|
||||
function getRandomColorClass(type = 'bg') {
|
||||
const index = Math.floor(Math.random() * color.length)
|
||||
const colorValue = color[index]
|
||||
|
||||
return 'tn-' + type + '-' + colorValue
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机获取酷炫背景对应的类
|
||||
*/
|
||||
function getRandomCoolBgClass() {
|
||||
const index = (Math.random() * COOL_BG_COLOR_COUNT) + 1
|
||||
return 'tn-cool-bg-color-' + Math.floor(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取内部背景颜色类
|
||||
*
|
||||
* @param {String} backgroundColor 背景颜色信息
|
||||
*/
|
||||
function getBackgroundColorInternalClass(backgroundColor = '') {
|
||||
if (!backgroundColor) return ''
|
||||
|
||||
if (['tn-bg', 'tn-dynamic-bg', 'tn-main-gradient', 'tn-cool-bg'].some(item => {
|
||||
return backgroundColor.includes(item)
|
||||
})) {
|
||||
return backgroundColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取背景颜色样式
|
||||
*
|
||||
* @param {String} backgroundColor 背景颜色信息
|
||||
*/
|
||||
function getBackgroundColorStyle(backgroundColor = '') {
|
||||
if (!backgroundColor) return ''
|
||||
|
||||
if (!backgroundColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
|
||||
return backgroundColor.includes(item)
|
||||
})) {
|
||||
return backgroundColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取内部字体颜色类
|
||||
*
|
||||
* @param {String} fontColor 背景颜色信息
|
||||
*/
|
||||
function getFontColorInternalClass(fontColor = '') {
|
||||
if (!fontColor) return ''
|
||||
|
||||
if (['tn-color'].some(item => {
|
||||
return fontColor.includes(item)
|
||||
})) {
|
||||
return fontColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取字体颜色样式
|
||||
*
|
||||
* @param {String} fontColor 背景颜色信息
|
||||
*/
|
||||
function getFontColorStyle(fontColor = '') {
|
||||
if (!fontColor) return ''
|
||||
|
||||
if (!fontColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
|
||||
return fontColor.includes(item)
|
||||
})) {
|
||||
return fontColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 求两个颜色之间的渐变值
|
||||
*
|
||||
* @param {String} startColor 开始颜色
|
||||
* @param {String} endColor 结束颜色
|
||||
* @param {Number} step 颜色等分的份额
|
||||
*/
|
||||
function colorGradient(startColor = 'rgb(0, 0, 0)', endColor='rgb(255, 255, 255)', step = 10) {
|
||||
let startRGB = hexToRGB(startColor, false)
|
||||
let startR = startRGB[0]
|
||||
let startG = startRGB[1]
|
||||
let startB = startRGB[2]
|
||||
|
||||
let endRGB = hexToRGB(endColor, false)
|
||||
let endR = endRGB[0]
|
||||
let endG = endRGB[1]
|
||||
let endB = endRGB[2]
|
||||
|
||||
// 求差值
|
||||
let R = (endR - startR) / step
|
||||
let G = (endG - startG) / step
|
||||
let B = (endB - startB) / step
|
||||
|
||||
let colorArr = []
|
||||
for (let i = 0; i < step; i++) {
|
||||
// 计算每一步的hex值
|
||||
let hex = rgbToHex(`rgb(${Math.round(R * i + startR)}, ${Math.round(G * i + startG)}, ${Math.round(B * i + startB)})`)
|
||||
colorArr.push(hex)
|
||||
}
|
||||
return colorArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 将hex的颜色表示方式转换为rgb表示方式
|
||||
*
|
||||
* @param {String} color 颜色
|
||||
* @param {Boolean} str 是否返回字符串
|
||||
* @return {Array|String} rgb的值
|
||||
*/
|
||||
function hexToRGB(color, str = true) {
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
|
||||
color = color.toLowerCase()
|
||||
if (color && reg.test(color)) {
|
||||
// #000 => #000000
|
||||
if (color.length === 4) {
|
||||
let colorNew = '#'
|
||||
for (let i = 1; i < 4; i++) {
|
||||
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
|
||||
}
|
||||
color = colorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
let colorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
|
||||
}
|
||||
if (!str) {
|
||||
return colorChange
|
||||
} else {
|
||||
return `rgb(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]})`
|
||||
}
|
||||
} else if (/^(rgb|RGB)/.test(color)) {
|
||||
let arr = color.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(',')
|
||||
return arr.map(item => Number(item))
|
||||
} else {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将rgb的颜色表示方式转换成hex表示方式
|
||||
*
|
||||
* @param {Object} rgb rgb颜色值
|
||||
*/
|
||||
function rgbToHex(rgb) {
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
if (/^(rgb|RGB)/.test(rgb)) {
|
||||
let color = rgb.replace(/(?:\(|\)|rgb|GRB)*/g, "").split(',')
|
||||
let strHex = '#'
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
let hex = Number(color[i]).toString(16)
|
||||
// 保证每个值否是两位数
|
||||
hex = String(hex).length === 1 ? 0 + '' + hex: hex
|
||||
if (hex === '0') {
|
||||
hex += hex
|
||||
}
|
||||
strHex += hex
|
||||
}
|
||||
if (strHex.length !== 7) {
|
||||
strHex = rgb
|
||||
}
|
||||
return strHex
|
||||
} else if (reg.test(rgb)) {
|
||||
let num = rgb.replace(/#/, '').split('')
|
||||
if (num.length === 6) {
|
||||
return rgb
|
||||
} else if (num.length === 3) {
|
||||
let numHex = '#'
|
||||
for (let i = 0; i < num.length; i++) {
|
||||
numHex += (num[i] + num[i])
|
||||
}
|
||||
return numHex
|
||||
}
|
||||
} else {
|
||||
return rgb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传入的颜色值转换为rgba字符串
|
||||
*
|
||||
* @param {String} color 颜色
|
||||
* @param {Number} alpha 透明度
|
||||
*/
|
||||
function colorToRGBA(color, alpha = 0.3) {
|
||||
color = rgbToHex(color)
|
||||
// 十六进制颜色值的正则表达式
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
|
||||
color = color.toLowerCase()
|
||||
if (color && reg.test(color)) {
|
||||
// #000 => #000000
|
||||
if (color.length === 4) {
|
||||
let colorNew = '#'
|
||||
for (let i = 1; i < 4; i++) {
|
||||
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
|
||||
}
|
||||
color = colorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
let colorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
|
||||
}
|
||||
return `rgba(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]}, ${alpha})`
|
||||
} else {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
COOL_BG_COLOR_COUNT: COOL_BG_COLOR_COUNT,
|
||||
getTuniaoColorList,
|
||||
getRandomColorClass,
|
||||
getRandomCoolBgClass,
|
||||
getBackgroundColorInternalClass,
|
||||
getBackgroundColorStyle,
|
||||
getFontColorInternalClass,
|
||||
getFontColorStyle,
|
||||
colorGradient,
|
||||
hexToRGB,
|
||||
rgbToHex,
|
||||
colorToRGBA
|
||||
}
|
||||
@@ -1,270 +1,270 @@
|
||||
let color = [
|
||||
'red',
|
||||
'purplered',
|
||||
'purple',
|
||||
'bluepurple',
|
||||
'aquablue',
|
||||
'blue',
|
||||
'indigo',
|
||||
'cyan',
|
||||
'teal',
|
||||
'green',
|
||||
'yellowgreen',
|
||||
'lime',
|
||||
'yellow',
|
||||
'orangeyellow',
|
||||
'orange',
|
||||
'orangered',
|
||||
'brown',
|
||||
'grey',
|
||||
'gray'
|
||||
]
|
||||
|
||||
// 酷炫颜色的数量
|
||||
const COOL_BG_COLOR_COUNT = 16
|
||||
|
||||
/**
|
||||
* 获取图鸟配色颜色列表
|
||||
*/
|
||||
function getTuniaoColorList() {
|
||||
return color
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的随机颜色对应的类
|
||||
* @param {String} type 颜色类型
|
||||
*/
|
||||
function getRandomColorClass(type = 'bg') {
|
||||
const index = Math.floor(Math.random() * color.length)
|
||||
const colorValue = color[index]
|
||||
|
||||
return 'tn-' + type + '-' + colorValue
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机获取酷炫背景对应的类
|
||||
*/
|
||||
function getRandomCoolBgClass() {
|
||||
const index = (Math.random() * COOL_BG_COLOR_COUNT) + 1
|
||||
return 'tn-cool-bg-color-' + Math.floor(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取内部背景颜色类
|
||||
*
|
||||
* @param {String} backgroundColor 背景颜色信息
|
||||
*/
|
||||
function getBackgroundColorInternalClass(backgroundColor = '') {
|
||||
if (!backgroundColor) return ''
|
||||
|
||||
if (['tn-bg', 'tn-dynamic-bg', 'tn-main-gradient', 'tn-cool-bg'].some(item => {
|
||||
return backgroundColor.includes(item)
|
||||
})) {
|
||||
return backgroundColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取背景颜色样式
|
||||
*
|
||||
* @param {String} backgroundColor 背景颜色信息
|
||||
*/
|
||||
function getBackgroundColorStyle(backgroundColor = '') {
|
||||
if (!backgroundColor) return ''
|
||||
|
||||
if (!backgroundColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
|
||||
return backgroundColor.includes(item)
|
||||
})) {
|
||||
return backgroundColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取内部字体颜色类
|
||||
*
|
||||
* @param {String} fontColor 背景颜色信息
|
||||
*/
|
||||
function getFontColorInternalClass(fontColor = '') {
|
||||
if (!fontColor) return ''
|
||||
|
||||
if (['tn-color'].some(item => {
|
||||
return fontColor.includes(item)
|
||||
})) {
|
||||
return fontColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取字体颜色样式
|
||||
*
|
||||
* @param {String} fontColor 背景颜色信息
|
||||
*/
|
||||
function getFontColorStyle(fontColor = '') {
|
||||
if (!fontColor) return ''
|
||||
|
||||
if (!fontColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
|
||||
return fontColor.includes(item)
|
||||
})) {
|
||||
return fontColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 求两个颜色之间的渐变值
|
||||
*
|
||||
* @param {String} startColor 开始颜色
|
||||
* @param {String} endColor 结束颜色
|
||||
* @param {Number} step 颜色等分的份额
|
||||
*/
|
||||
function colorGradient(startColor = 'rgb(0, 0, 0)', endColor='rgb(255, 255, 255)', step = 10) {
|
||||
let startRGB = hexToRGB(startColor, false)
|
||||
let startR = startRGB[0]
|
||||
let startG = startRGB[1]
|
||||
let startB = startRGB[2]
|
||||
|
||||
let endRGB = hexToRGB(endColor, false)
|
||||
let endR = endRGB[0]
|
||||
let endG = endRGB[1]
|
||||
let endB = endRGB[2]
|
||||
|
||||
// 求差值
|
||||
let R = (endR - startR) / step
|
||||
let G = (endG - startG) / step
|
||||
let B = (endB - startB) / step
|
||||
|
||||
let colorArr = []
|
||||
for (let i = 0; i < step; i++) {
|
||||
// 计算每一步的hex值
|
||||
let hex = rgbToHex(`rgb(${Math.round(R * i + startR)}, ${Math.round(G * i + startG)}, ${Math.round(B * i + startB)})`)
|
||||
colorArr.push(hex)
|
||||
}
|
||||
return colorArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 将hex的颜色表示方式转换为rgb表示方式
|
||||
*
|
||||
* @param {String} color 颜色
|
||||
* @param {Boolean} str 是否返回字符串
|
||||
* @return {Array|String} rgb的值
|
||||
*/
|
||||
function hexToRGB(color, str = true) {
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
|
||||
color = color.toLowerCase()
|
||||
if (color && reg.test(color)) {
|
||||
// #000 => #000000
|
||||
if (color.length === 4) {
|
||||
let colorNew = '#'
|
||||
for (let i = 1; i < 4; i++) {
|
||||
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
|
||||
}
|
||||
color = colorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
let colorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
|
||||
}
|
||||
if (!str) {
|
||||
return colorChange
|
||||
} else {
|
||||
return `rgb(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]})`
|
||||
}
|
||||
} else if (/^(rgb|RGB)/.test(color)) {
|
||||
let arr = color.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(',')
|
||||
return arr.map(item => Number(item))
|
||||
} else {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将rgb的颜色表示方式转换成hex表示方式
|
||||
*
|
||||
* @param {Object} rgb rgb颜色值
|
||||
*/
|
||||
function rgbToHex(rgb) {
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
if (/^(rgb|RGB)/.test(rgb)) {
|
||||
let color = rgb.replace(/(?:\(|\)|rgb|GRB)*/g, "").split(',')
|
||||
let strHex = '#'
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
let hex = Number(color[i]).toString(16)
|
||||
// 保证每个值否是两位数
|
||||
hex = String(hex).length === 1 ? 0 + '' + hex: hex
|
||||
if (hex === '0') {
|
||||
hex += hex
|
||||
}
|
||||
strHex += hex
|
||||
}
|
||||
if (strHex.length !== 7) {
|
||||
strHex = rgb
|
||||
}
|
||||
return strHex
|
||||
} else if (reg.test(rgb)) {
|
||||
let num = rgb.replace(/#/, '').split('')
|
||||
if (num.length === 6) {
|
||||
return rgb
|
||||
} else if (num.length === 3) {
|
||||
let numHex = '#'
|
||||
for (let i = 0; i < num.length; i++) {
|
||||
numHex += (num[i] + num[i])
|
||||
}
|
||||
return numHex
|
||||
}
|
||||
} else {
|
||||
return rgb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传入的颜色值转换为rgba字符串
|
||||
*
|
||||
* @param {String} color 颜色
|
||||
* @param {Number} alpha 透明度
|
||||
*/
|
||||
function colorToRGBA(color, alpha = 0.3) {
|
||||
color = rgbToHex(color)
|
||||
// 十六进制颜色值的正则表达式
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
|
||||
color = color.toLowerCase()
|
||||
if (color && reg.test(color)) {
|
||||
// #000 => #000000
|
||||
if (color.length === 4) {
|
||||
let colorNew = '#'
|
||||
for (let i = 1; i < 4; i++) {
|
||||
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
|
||||
}
|
||||
color = colorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
let colorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
|
||||
}
|
||||
return `rgba(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]}, ${alpha})`
|
||||
} else {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
COOL_BG_COLOR_COUNT: COOL_BG_COLOR_COUNT,
|
||||
getTuniaoColorList,
|
||||
getRandomColorClass,
|
||||
getRandomCoolBgClass,
|
||||
getBackgroundColorInternalClass,
|
||||
getBackgroundColorStyle,
|
||||
getFontColorInternalClass,
|
||||
getFontColorStyle,
|
||||
colorGradient,
|
||||
hexToRGB,
|
||||
rgbToHex,
|
||||
colorToRGBA
|
||||
let color = [
|
||||
'red',
|
||||
'purplered',
|
||||
'purple',
|
||||
'bluepurple',
|
||||
'aquablue',
|
||||
'blue',
|
||||
'indigo',
|
||||
'cyan',
|
||||
'teal',
|
||||
'green',
|
||||
'yellowgreen',
|
||||
'lime',
|
||||
'yellow',
|
||||
'orangeyellow',
|
||||
'orange',
|
||||
'orangered',
|
||||
'brown',
|
||||
'grey',
|
||||
'gray'
|
||||
]
|
||||
|
||||
// 酷炫颜色的数量
|
||||
const COOL_BG_COLOR_COUNT = 16
|
||||
|
||||
/**
|
||||
* 获取图鸟配色颜色列表
|
||||
*/
|
||||
function getTuniaoColorList() {
|
||||
return color
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的随机颜色对应的类
|
||||
* @param {String} type 颜色类型
|
||||
*/
|
||||
function getRandomColorClass(type = 'bg') {
|
||||
const index = Math.floor(Math.random() * color.length)
|
||||
const colorValue = color[index]
|
||||
|
||||
return 'tn-' + type + '-' + colorValue
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机获取酷炫背景对应的类
|
||||
*/
|
||||
function getRandomCoolBgClass() {
|
||||
const index = (Math.random() * COOL_BG_COLOR_COUNT) + 1
|
||||
return 'tn-cool-bg-color-' + Math.floor(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取内部背景颜色类
|
||||
*
|
||||
* @param {String} backgroundColor 背景颜色信息
|
||||
*/
|
||||
function getBackgroundColorInternalClass(backgroundColor = '') {
|
||||
if (!backgroundColor) return ''
|
||||
|
||||
if (['tn-bg', 'tn-dynamic-bg', 'tn-main-gradient', 'tn-cool-bg'].some(item => {
|
||||
return backgroundColor.includes(item)
|
||||
})) {
|
||||
return backgroundColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取背景颜色样式
|
||||
*
|
||||
* @param {String} backgroundColor 背景颜色信息
|
||||
*/
|
||||
function getBackgroundColorStyle(backgroundColor = '') {
|
||||
if (!backgroundColor) return ''
|
||||
|
||||
if (!backgroundColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
|
||||
return backgroundColor.includes(item)
|
||||
})) {
|
||||
return backgroundColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取内部字体颜色类
|
||||
*
|
||||
* @param {String} fontColor 背景颜色信息
|
||||
*/
|
||||
function getFontColorInternalClass(fontColor = '') {
|
||||
if (!fontColor) return ''
|
||||
|
||||
if (['tn-color'].some(item => {
|
||||
return fontColor.includes(item)
|
||||
})) {
|
||||
return fontColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据传入的值获取字体颜色样式
|
||||
*
|
||||
* @param {String} fontColor 背景颜色信息
|
||||
*/
|
||||
function getFontColorStyle(fontColor = '') {
|
||||
if (!fontColor) return ''
|
||||
|
||||
if (!fontColor.startsWith('tn-') || ['#', 'rgb', 'rgba'].some(item => {
|
||||
return fontColor.includes(item)
|
||||
})) {
|
||||
return fontColor
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 求两个颜色之间的渐变值
|
||||
*
|
||||
* @param {String} startColor 开始颜色
|
||||
* @param {String} endColor 结束颜色
|
||||
* @param {Number} step 颜色等分的份额
|
||||
*/
|
||||
function colorGradient(startColor = 'rgb(0, 0, 0)', endColor='rgb(255, 255, 255)', step = 10) {
|
||||
let startRGB = hexToRGB(startColor, false)
|
||||
let startR = startRGB[0]
|
||||
let startG = startRGB[1]
|
||||
let startB = startRGB[2]
|
||||
|
||||
let endRGB = hexToRGB(endColor, false)
|
||||
let endR = endRGB[0]
|
||||
let endG = endRGB[1]
|
||||
let endB = endRGB[2]
|
||||
|
||||
// 求差值
|
||||
let R = (endR - startR) / step
|
||||
let G = (endG - startG) / step
|
||||
let B = (endB - startB) / step
|
||||
|
||||
let colorArr = []
|
||||
for (let i = 0; i < step; i++) {
|
||||
// 计算每一步的hex值
|
||||
let hex = rgbToHex(`rgb(${Math.round(R * i + startR)}, ${Math.round(G * i + startG)}, ${Math.round(B * i + startB)})`)
|
||||
colorArr.push(hex)
|
||||
}
|
||||
return colorArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 将hex的颜色表示方式转换为rgb表示方式
|
||||
*
|
||||
* @param {String} color 颜色
|
||||
* @param {Boolean} str 是否返回字符串
|
||||
* @return {Array|String} rgb的值
|
||||
*/
|
||||
function hexToRGB(color, str = true) {
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
|
||||
color = color.toLowerCase()
|
||||
if (color && reg.test(color)) {
|
||||
// #000 => #000000
|
||||
if (color.length === 4) {
|
||||
let colorNew = '#'
|
||||
for (let i = 1; i < 4; i++) {
|
||||
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
|
||||
}
|
||||
color = colorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
let colorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
|
||||
}
|
||||
if (!str) {
|
||||
return colorChange
|
||||
} else {
|
||||
return `rgb(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]})`
|
||||
}
|
||||
} else if (/^(rgb|RGB)/.test(color)) {
|
||||
let arr = color.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(',')
|
||||
return arr.map(item => Number(item))
|
||||
} else {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将rgb的颜色表示方式转换成hex表示方式
|
||||
*
|
||||
* @param {Object} rgb rgb颜色值
|
||||
*/
|
||||
function rgbToHex(rgb) {
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
if (/^(rgb|RGB)/.test(rgb)) {
|
||||
let color = rgb.replace(/(?:\(|\)|rgb|GRB)*/g, "").split(',')
|
||||
let strHex = '#'
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
let hex = Number(color[i]).toString(16)
|
||||
// 保证每个值否是两位数
|
||||
hex = String(hex).length === 1 ? 0 + '' + hex: hex
|
||||
if (hex === '0') {
|
||||
hex += hex
|
||||
}
|
||||
strHex += hex
|
||||
}
|
||||
if (strHex.length !== 7) {
|
||||
strHex = rgb
|
||||
}
|
||||
return strHex
|
||||
} else if (reg.test(rgb)) {
|
||||
let num = rgb.replace(/#/, '').split('')
|
||||
if (num.length === 6) {
|
||||
return rgb
|
||||
} else if (num.length === 3) {
|
||||
let numHex = '#'
|
||||
for (let i = 0; i < num.length; i++) {
|
||||
numHex += (num[i] + num[i])
|
||||
}
|
||||
return numHex
|
||||
}
|
||||
} else {
|
||||
return rgb
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将传入的颜色值转换为rgba字符串
|
||||
*
|
||||
* @param {String} color 颜色
|
||||
* @param {Number} alpha 透明度
|
||||
*/
|
||||
function colorToRGBA(color, alpha = 0.3) {
|
||||
color = rgbToHex(color)
|
||||
// 十六进制颜色值的正则表达式
|
||||
let reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/
|
||||
|
||||
color = color.toLowerCase()
|
||||
if (color && reg.test(color)) {
|
||||
// #000 => #000000
|
||||
if (color.length === 4) {
|
||||
let colorNew = '#'
|
||||
for (let i = 1; i < 4; i++) {
|
||||
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
|
||||
}
|
||||
color = colorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
let colorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
|
||||
}
|
||||
return `rgba(${colorChange[0]}, ${colorChange[1]}, ${colorChange[2]}, ${alpha})`
|
||||
} else {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
COOL_BG_COLOR_COUNT: COOL_BG_COLOR_COUNT,
|
||||
getTuniaoColorList,
|
||||
getRandomColorClass,
|
||||
getRandomCoolBgClass,
|
||||
getBackgroundColorInternalClass,
|
||||
getBackgroundColorStyle,
|
||||
getFontColorInternalClass,
|
||||
getFontColorStyle,
|
||||
colorGradient,
|
||||
hexToRGB,
|
||||
rgbToHex,
|
||||
colorToRGBA
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
/**
|
||||
* 判断是否为数组
|
||||
*
|
||||
* @param {Object} arr
|
||||
*/
|
||||
function isArray(arr) {
|
||||
return Object.prototype.toString.call(arr) === '[object Array]'
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度复制数据
|
||||
*
|
||||
* @param {Object} obj
|
||||
*/
|
||||
function deepClone(obj) {
|
||||
if ([null, undefined, NaN, false].includes(obj)) return obj
|
||||
if (typeof obj !== 'object' && typeof obj !== 'function') {
|
||||
return obj
|
||||
}
|
||||
var o = isArray(obj) ? [] : {}
|
||||
for (let i in obj) {
|
||||
if (obj.hasOwnProperty(i)) {
|
||||
o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数组
|
||||
*
|
||||
* @param {Object} arr
|
||||
*/
|
||||
function isArray(arr) {
|
||||
return Object.prototype.toString.call(arr) === '[object Array]'
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度复制数据
|
||||
*
|
||||
* @param {Object} obj
|
||||
*/
|
||||
function deepClone(obj) {
|
||||
if ([null, undefined, NaN, false].includes(obj)) return obj
|
||||
if (typeof obj !== 'object' && typeof obj !== 'function') {
|
||||
return obj
|
||||
}
|
||||
var o = isArray(obj) ? [] : {}
|
||||
for (let i in obj) {
|
||||
if (obj.hasOwnProperty(i)) {
|
||||
o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
|
||||
}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
export default deepClone
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 弹出系统内置的toast
|
||||
*/
|
||||
function toast(title, mask = false, cb = null, icon = 'none', duration = 1500) {
|
||||
uni.showToast({
|
||||
title: title,
|
||||
icon: icon,
|
||||
mask: mask,
|
||||
duration: duration,
|
||||
success: () => {
|
||||
setTimeout(() => {
|
||||
cb && cb()
|
||||
}, duration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出内置的加载框
|
||||
*/
|
||||
function loading(title) {
|
||||
uni.showLoading({
|
||||
title: title,
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出系统内置的modal
|
||||
*/
|
||||
function modal(title,
|
||||
content,
|
||||
confirmCb,
|
||||
showCancel = false,
|
||||
cancelCb = null,
|
||||
confirmText = "确定",
|
||||
cancelText = "取消") {
|
||||
uni.showModal({
|
||||
title: title,
|
||||
content: content,
|
||||
showCancel: showCancel,
|
||||
cancelText: cancelText,
|
||||
confirmText: confirmText,
|
||||
success: (res) => {
|
||||
if (res.cancel) {
|
||||
cancelCb && cancelCb()
|
||||
} else if (res.confirm) {
|
||||
confirmCb && confirmCb()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭系统内置toast
|
||||
*/
|
||||
function closeToast() {
|
||||
uni.hideToast()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭系统内置的加载框
|
||||
*/
|
||||
function closeLoading() {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
export default {
|
||||
toast,
|
||||
loading,
|
||||
modal,
|
||||
closeToast,
|
||||
closeLoading
|
||||
}
|
||||
@@ -1,74 +1,74 @@
|
||||
/**
|
||||
* 弹出系统内置的toast
|
||||
*/
|
||||
function toast(title, mask = false, cb = null, icon = 'none', duration = 1500) {
|
||||
uni.showToast({
|
||||
title: title,
|
||||
icon: icon,
|
||||
mask: mask,
|
||||
duration: duration,
|
||||
success: () => {
|
||||
setTimeout(() => {
|
||||
cb && cb()
|
||||
}, duration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出内置的加载框
|
||||
*/
|
||||
function loading(title) {
|
||||
uni.showLoading({
|
||||
title: title,
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出系统内置的modal
|
||||
*/
|
||||
function modal(title,
|
||||
content,
|
||||
confirmCb,
|
||||
showCancel = false,
|
||||
cancelCb = null,
|
||||
confirmText = "确定",
|
||||
cancelText = "取消") {
|
||||
uni.showModal({
|
||||
title: title,
|
||||
content: content,
|
||||
showCancel: showCancel,
|
||||
cancelText: cancelText,
|
||||
confirmText: confirmText,
|
||||
success: (res) => {
|
||||
if (res.cancel) {
|
||||
cancelCb && cancelCb()
|
||||
} else if (res.confirm) {
|
||||
confirmCb && confirmCb()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭系统内置toast
|
||||
*/
|
||||
function closeToast() {
|
||||
uni.hideToast()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭系统内置的加载框
|
||||
*/
|
||||
function closeLoading() {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
export default {
|
||||
toast,
|
||||
loading,
|
||||
modal,
|
||||
closeToast,
|
||||
closeLoading
|
||||
/**
|
||||
* 弹出系统内置的toast
|
||||
*/
|
||||
function toast(title, mask = false, cb = null, icon = 'none', duration = 1500) {
|
||||
uni.showToast({
|
||||
title: title,
|
||||
icon: icon,
|
||||
mask: mask,
|
||||
duration: duration,
|
||||
success: () => {
|
||||
setTimeout(() => {
|
||||
cb && cb()
|
||||
}, duration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出内置的加载框
|
||||
*/
|
||||
function loading(title) {
|
||||
uni.showLoading({
|
||||
title: title,
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出系统内置的modal
|
||||
*/
|
||||
function modal(title,
|
||||
content,
|
||||
confirmCb,
|
||||
showCancel = false,
|
||||
cancelCb = null,
|
||||
confirmText = "确定",
|
||||
cancelText = "取消") {
|
||||
uni.showModal({
|
||||
title: title,
|
||||
content: content,
|
||||
showCancel: showCancel,
|
||||
cancelText: cancelText,
|
||||
confirmText: confirmText,
|
||||
success: (res) => {
|
||||
if (res.cancel) {
|
||||
cancelCb && cancelCb()
|
||||
} else if (res.confirm) {
|
||||
confirmCb && confirmCb()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭系统内置toast
|
||||
*/
|
||||
function closeToast() {
|
||||
uni.hideToast()
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭系统内置的加载框
|
||||
*/
|
||||
function closeLoading() {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
export default {
|
||||
toast,
|
||||
loading,
|
||||
modal,
|
||||
closeToast,
|
||||
closeLoading
|
||||
}
|
||||
|
||||
+127
-111
@@ -1,112 +1,128 @@
|
||||
/**
|
||||
* 格式化数字字符串
|
||||
* @param {String, Number} value 待格式化的字符串
|
||||
* @param {Number} digits 保留位数
|
||||
*/
|
||||
function formatNumberString(value, digits = 2) {
|
||||
let number = 0
|
||||
// 判断是什么类型
|
||||
if (typeof value === 'string') {
|
||||
number = Number(value)
|
||||
} else if (typeof value === 'number') {
|
||||
number = value
|
||||
}
|
||||
if (isNaN(number) || number === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
let maxNumber = Math.pow(10, digits) - 1
|
||||
if (number > maxNumber) {
|
||||
return `${maxNumber}+`
|
||||
}
|
||||
|
||||
return number
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字字符串,往数字前添加0
|
||||
*
|
||||
* @param {Object} num 待格式化的数值
|
||||
*/
|
||||
function formatNumberAddZero(value) {
|
||||
let number = 0
|
||||
// 判断是什么类型
|
||||
if (typeof value === 'string') {
|
||||
number = Number(value)
|
||||
} else if (typeof value === 'number') {
|
||||
number = value
|
||||
}
|
||||
if (isNaN(number) || +number < 10) {
|
||||
return '0' + number
|
||||
} else {
|
||||
return String(number)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字,往数值后添加单位
|
||||
*
|
||||
* @param {Object} value 待格式化的数值
|
||||
* @param {Object} digits 保留位数
|
||||
*/
|
||||
function formatNumberAddUnit(value, digits = 2) {
|
||||
// 数值分割点
|
||||
const unitSplit = [
|
||||
{ value: 1, symbol: ''},
|
||||
{ value: 1E3, symbol: 'K'},
|
||||
{ value: 1E4, symbol: 'W'},
|
||||
]
|
||||
|
||||
const reg = /\.0+$|(\.[0=9]*[1-9])0+$/
|
||||
|
||||
let number = 0
|
||||
// 判断是什么类型
|
||||
if (typeof value === 'string') {
|
||||
number = Number(value)
|
||||
} else if (typeof value === 'number') {
|
||||
number = value
|
||||
}
|
||||
|
||||
let i
|
||||
for (i = unitSplit.length - 1; i > 0; i--) {
|
||||
if (number >= unitSplit[i].value) break
|
||||
}
|
||||
return (number / unitSplit[i].value).toFixed(digits).replace(reg, "$1") + unitSplit[i].symbol
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数值的整数位数
|
||||
*
|
||||
* @param {Object} number 数值
|
||||
*/
|
||||
function getDigit(number) {
|
||||
let digit = -1
|
||||
while (number >= 1) {
|
||||
digit++
|
||||
number = number / 10
|
||||
}
|
||||
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,
|
||||
random
|
||||
/**
|
||||
* 格式化数字字符串
|
||||
* @param {String, Number} value 待格式化的字符串
|
||||
* @param {Number} digits 保留位数
|
||||
*/
|
||||
function formatNumberString(value, digits = 2) {
|
||||
let number = 0
|
||||
// 判断是什么类型
|
||||
if (typeof value === 'string') {
|
||||
number = Number(value)
|
||||
} else if (typeof value === 'number') {
|
||||
number = value
|
||||
}
|
||||
if (isNaN(number) || number === 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
let maxNumber = Math.pow(10, digits) - 1
|
||||
if (number > maxNumber) {
|
||||
return `${maxNumber}+`
|
||||
}
|
||||
|
||||
return number
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字字符串,往数字前添加0
|
||||
*
|
||||
* @param {Object} num 待格式化的数值
|
||||
*/
|
||||
function formatNumberAddZero(value) {
|
||||
let number = 0
|
||||
// 判断是什么类型
|
||||
if (typeof value === 'string') {
|
||||
number = Number(value)
|
||||
} else if (typeof value === 'number') {
|
||||
number = value
|
||||
}
|
||||
if (isNaN(number) || +number < 10) {
|
||||
return '0' + number
|
||||
} else {
|
||||
return String(number)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化数字,往数值后添加单位
|
||||
*
|
||||
* @param {Object} value 待格式化的数值
|
||||
* @param {Object} digits 保留位数
|
||||
*/
|
||||
function formatNumberAddPriceUnit(value, digits = 2) {
|
||||
// 数值分割点
|
||||
const unitSplit = [
|
||||
{ value: 1, symbol: ''},
|
||||
{ value: 1E3, symbol: 'K'},
|
||||
{ value: 1E4, symbol: 'W'},
|
||||
]
|
||||
|
||||
const reg = /\.0+$|(\.[0=9]*[1-9])0+$/
|
||||
|
||||
let number = 0
|
||||
// 判断是什么类型
|
||||
if (typeof value === 'string') {
|
||||
number = Number(value)
|
||||
} else if (typeof value === 'number') {
|
||||
number = value
|
||||
}
|
||||
|
||||
let i
|
||||
for (i = unitSplit.length - 1; i > 0; i--) {
|
||||
if (number >= unitSplit[i].value) break
|
||||
}
|
||||
return (number / unitSplit[i].value).toFixed(digits).replace(reg, "$1") + unitSplit[i].symbol
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数值的整数位数
|
||||
*
|
||||
* @param {Object} number 数值
|
||||
*/
|
||||
function getDigit(number) {
|
||||
let digit = -1
|
||||
while (number >= 1) {
|
||||
digit++
|
||||
number = number / 10
|
||||
}
|
||||
return digit
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定范围的随机数(返回整数)
|
||||
|
||||
* @param {Object} min 最小值
|
||||
* @param {Object} max 最大值
|
||||
*/
|
||||
function random(min, max) {
|
||||
if (min >= 0 && max > 0 && max >= min) {
|
||||
let gab = max - min
|
||||
return Math.random() * gab + min
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定范围的随机数(返回整数)
|
||||
|
||||
* @param {Object} min 最小值
|
||||
* @param {Object} max 最大值
|
||||
*/
|
||||
function randomInt(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,
|
||||
formatNumberAddPriceUnit,
|
||||
random,
|
||||
randomInt
|
||||
}
|
||||
@@ -1,63 +1,69 @@
|
||||
/**
|
||||
* 去掉字符串中空格
|
||||
*
|
||||
* @param {String} str 待处理的字符串
|
||||
* @param {String} type 处理类型
|
||||
*/
|
||||
function trim(str, type = 'both') {
|
||||
if (type === 'both') {
|
||||
return str.replace(/^\s+|\s+$/g, "")
|
||||
} else if (type === 'left') {
|
||||
return str.replace(/^\s*/g, "")
|
||||
} else if (type === 'right') {
|
||||
return str.replace(/(\s*$)/g, "")
|
||||
} else if (type === 'all') {
|
||||
return str.replace(/\s+/g, "")
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带单位的长度值
|
||||
*
|
||||
* @param {String} value 待处理的值
|
||||
* @param {String} unit 单位
|
||||
*/
|
||||
function getLengthUnitValue(value, unit = 'rpx') {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
if (/(%|px|rpx|auto)$/.test(value)) return value
|
||||
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,
|
||||
humpConvertChar,
|
||||
charConvertHump
|
||||
/**
|
||||
* 去掉字符串中空格
|
||||
*
|
||||
* @param {String} str 待处理的字符串
|
||||
* @param {String} type 处理类型
|
||||
*/
|
||||
function trim(str, type = 'both') {
|
||||
if (type === 'both') {
|
||||
return str.replace(/^\s+|\s+$/g, "")
|
||||
} else if (type === 'left') {
|
||||
return str.replace(/^\s*/g, "")
|
||||
} else if (type === 'right') {
|
||||
return str.replace(/(\s*$)/g, "")
|
||||
} else if (type === 'all') {
|
||||
return str.replace(/\s+/g, "")
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取带单位的长度值
|
||||
*
|
||||
* @param {String} value 待处理的值
|
||||
* @param {String} unit 单位
|
||||
*/
|
||||
function getLengthUnitValue(value, unit = 'rpx') {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
if (/(%|px|rpx|auto)$/.test(value)) return value
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* 将用指定连接符来进行连接的字符串转为驼峰命名的字符串
|
||||
*
|
||||
* @param {Object} string 待转换的字符串
|
||||
* @param {Object} replace 进行连接的字符
|
||||
*/
|
||||
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,
|
||||
humpConvertChar,
|
||||
charConvertHump
|
||||
}
|
||||
@@ -1,44 +1,44 @@
|
||||
/**
|
||||
* 更新自定义顶部导航栏的高度
|
||||
*/
|
||||
function updateCustomBarInfo () {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getSystemInfo({
|
||||
success: (e) => {
|
||||
let statusBarHeight = 0
|
||||
let customBarHeight = 0
|
||||
// #ifndef MP
|
||||
statusBarHeight = e.statusBarHeight
|
||||
if (e.platform == 'android') {
|
||||
customBarHeight = e.statusBarHeight + 50
|
||||
} else {
|
||||
customBarHeight = e.statusBarHeight + 45
|
||||
};
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
statusBarHeight = e.statusBarHeight
|
||||
let custom = wx.getMenuButtonBoundingClientRect()
|
||||
customBarHeight = custom.bottom + ((custom.top - e.statusBarHeight) <= 4 ? (custom.top - e
|
||||
.statusBarHeight) + 4 : (custom.top - e.statusBarHeight))
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
statusBarHeight = e.statusBarHeight
|
||||
customBarHeight = e.statusBarHeight + e.titleBarHeight
|
||||
// #endif
|
||||
resolve({
|
||||
statusBarHeight,
|
||||
customBarHeight
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log("获取设备信息失败", err);
|
||||
reject()
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新自定义顶部导航栏的高度
|
||||
*/
|
||||
function updateCustomBarInfo () {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getSystemInfo({
|
||||
success: (e) => {
|
||||
let statusBarHeight = 0
|
||||
let customBarHeight = 0
|
||||
// #ifndef MP
|
||||
statusBarHeight = e.statusBarHeight
|
||||
if (e.platform == 'android') {
|
||||
customBarHeight = e.statusBarHeight + 50
|
||||
} else {
|
||||
customBarHeight = e.statusBarHeight + 45
|
||||
};
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
statusBarHeight = e.statusBarHeight
|
||||
let custom = wx.getMenuButtonBoundingClientRect()
|
||||
customBarHeight = custom.bottom + ((custom.top - e.statusBarHeight) <= 4 ? (custom.top - e
|
||||
.statusBarHeight) + 4 : (custom.top - e.statusBarHeight))
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
statusBarHeight = e.statusBarHeight
|
||||
customBarHeight = e.statusBarHeight + e.titleBarHeight
|
||||
// #endif
|
||||
resolve({
|
||||
statusBarHeight,
|
||||
customBarHeight
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log("获取设备信息失败", err);
|
||||
reject()
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
export default updateCustomBarInfo
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
module.exports = {
|
||||
data() {
|
||||
|
||||
},
|
||||
props: {
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体颜色
|
||||
fontColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 字体大小单位
|
||||
fontUnit: {
|
||||
type: String,
|
||||
default: 'rpx'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundColorStyle() {
|
||||
return this.$t.colorUtils.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.colorUtils.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
fontColorStyle() {
|
||||
return this.$t.colorUtils.getFontColorStyle(this.fontColor)
|
||||
},
|
||||
fontColorClass() {
|
||||
return this.$t.colorUtils.getFontColorInternalClass(this.fontColor)
|
||||
},
|
||||
fontSizeStyle() {
|
||||
return this.$t.string.getLengthUnitValue(this.fontSize, this.fontUnit)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
module.exports = {
|
||||
data() {
|
||||
|
||||
},
|
||||
props: {
|
||||
// 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体颜色
|
||||
fontColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 字体大小单位
|
||||
fontUnit: {
|
||||
type: String,
|
||||
default: 'rpx'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backgroundColorStyle() {
|
||||
return this.$t.color.getBackgroundColorStyle(this.backgroundColor)
|
||||
},
|
||||
backgroundColorClass() {
|
||||
return this.$t.color.getBackgroundColorInternalClass(this.backgroundColor)
|
||||
},
|
||||
fontColorStyle() {
|
||||
return this.$t.color.getFontColorStyle(this.fontColor)
|
||||
},
|
||||
fontColorClass() {
|
||||
return this.$t.color.getFontColorInternalClass(this.fontColor)
|
||||
},
|
||||
fontSizeStyle() {
|
||||
return this.$t.string.getLengthUnitValue(this.fontSize, this.fontUnit)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,68 +1,68 @@
|
||||
module.exports = {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
onLoad() {
|
||||
// getRect挂载再$t上,用为这个方法需要使用in(this),所以无法把它独立层一个单独的文件导出
|
||||
this.$t.getRect = this._tGetRect
|
||||
},
|
||||
beforeDestory() {
|
||||
// 判断当前页面是否存在parent和children
|
||||
// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
|
||||
if (this.parent && uni.$t.test.array(this.parent.children)) {
|
||||
// 组件销毁时,移除子组件在父组件children数组中的实例
|
||||
const childrenList = this.parent.children
|
||||
childrenList.map((child, index) => {
|
||||
// 如果相对,则移除
|
||||
if (child === this) {
|
||||
childrenList.splice(index, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 查询节点信息
|
||||
* 当前方法在支付宝小程序中无法获取组件跟接点的尺寸
|
||||
* 解决办法:为组件根部再套一个没有任何作用的view元素
|
||||
*/
|
||||
_tGetRect(selector, all) {
|
||||
return new Promise((resolve) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)[all ? 'selectAll' : 'select'](selector)
|
||||
.boundingClientRect(rect => {
|
||||
if (all && Array.isArray(rect) && rect.length) {
|
||||
resolve(rect)
|
||||
}
|
||||
if (!all && rect) {
|
||||
resolve(rect)
|
||||
}
|
||||
})
|
||||
.exec()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取父组件的数据
|
||||
*/
|
||||
getParentData(parentName = '') {
|
||||
// 避免再created中定义parent变量
|
||||
if (!this.parent) this.parent = false
|
||||
// 通过获取父组件实例
|
||||
// 将父组件this中对应的参数,赋值给本组件的parentData对象中对应的属性
|
||||
// 头条小程序不支持通过this.parent.xxx去监听父组件参数的变化,所以需要本方法进行实现
|
||||
this.parent = this.$t.$parent.call(this, parentName)
|
||||
if (this.parent) {
|
||||
// 遍历parentData中的属性,将parent中同名的属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key]
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 阻止事件冒泡
|
||||
*/
|
||||
preventEvent(e) {
|
||||
e && e.stopPropagation && e.stopPropagation()
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
onLoad() {
|
||||
// getRect挂载再$t上,用为这个方法需要使用in(this),所以无法把它独立层一个单独的文件导出
|
||||
this.$t.getRect = this._tGetRect
|
||||
},
|
||||
beforeDestory() {
|
||||
// 判断当前页面是否存在parent和children
|
||||
// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
|
||||
if (this.parent && uni.$t.test.array(this.parent.children)) {
|
||||
// 组件销毁时,移除子组件在父组件children数组中的实例
|
||||
const childrenList = this.parent.children
|
||||
childrenList.map((child, index) => {
|
||||
// 如果相对,则移除
|
||||
if (child === this) {
|
||||
childrenList.splice(index, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 查询节点信息
|
||||
* 当前方法在支付宝小程序中无法获取组件跟接点的尺寸
|
||||
* 解决办法:为组件根部再套一个没有任何作用的view元素
|
||||
*/
|
||||
_tGetRect(selector, all) {
|
||||
return new Promise((resolve) => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)[all ? 'selectAll' : 'select'](selector)
|
||||
.boundingClientRect(rect => {
|
||||
if (all && Array.isArray(rect) && rect.length) {
|
||||
resolve(rect)
|
||||
}
|
||||
if (!all && rect) {
|
||||
resolve(rect)
|
||||
}
|
||||
})
|
||||
.exec()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取父组件的数据
|
||||
*/
|
||||
getParentData(parentName = '') {
|
||||
// 避免再created中定义parent变量
|
||||
if (!this.parent) this.parent = false
|
||||
// 通过获取父组件实例
|
||||
// 将父组件this中对应的参数,赋值给本组件的parentData对象中对应的属性
|
||||
// 头条小程序不支持通过this.parent.xxx去监听父组件参数的变化,所以需要本方法进行实现
|
||||
this.parent = this.$t.$parent.call(this, parentName)
|
||||
if (this.parent) {
|
||||
// 遍历parentData中的属性,将parent中同名的属性赋值给parentData
|
||||
Object.keys(this.parentData).map(key => {
|
||||
this.parentData[key] = this.parent[key]
|
||||
})
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 阻止事件冒泡
|
||||
*/
|
||||
preventEvent(e) {
|
||||
e && e.stopPropagation && e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,30 @@
|
||||
module.exports = {
|
||||
onLoad() {
|
||||
// 设置默认的转发参数
|
||||
this.$t.mpShare = {
|
||||
// 分享的标题,默认为小程序名称
|
||||
title: '',
|
||||
// 分享的路径,默认为当前页面
|
||||
path: '',
|
||||
// 分享时显示的图片,默认为当前页面截图
|
||||
imageUrl: '',
|
||||
// 当前页面是否可以分享
|
||||
share: true
|
||||
}
|
||||
if (!this.$t.mpShare.share) {
|
||||
uni.hideShareMenu()
|
||||
}
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return this.$t.mpShare
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: this.$t.mpShare.title,
|
||||
query: this.$t.mpShare.path.substring(this.$t.mpShare.path.indexOf('?') + 1, this.$t.mpShare.path.length),
|
||||
imageUrl: this.$t.mpShare.imageUrl
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
module.exports = {
|
||||
onLoad() {
|
||||
// 设置默认的转发参数
|
||||
this.$t.mpShare = {
|
||||
// 分享的标题,默认为小程序名称
|
||||
title: '',
|
||||
// 分享的路径,默认为当前页面
|
||||
path: '',
|
||||
// 分享时显示的图片,默认为当前页面截图
|
||||
imageUrl: '',
|
||||
// 当前页面是否可以分享
|
||||
share: true
|
||||
}
|
||||
if (!this.$t.mpShare.share) {
|
||||
uni.hideShareMenu()
|
||||
}
|
||||
},
|
||||
onShareAppMessage() {
|
||||
return this.$t.mpShare
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: this.$t.mpShare.title,
|
||||
query: this.$t.mpShare.path.substring(this.$t.mpShare.path.indexOf('?') + 1, this.$t.mpShare.path.length),
|
||||
imageUrl: this.$t.mpShare.imageUrl
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
@@ -1,61 +1,61 @@
|
||||
const MIN_DISTANCE = 10
|
||||
|
||||
function getDirection(x, y) {
|
||||
if (x > y && x > MIN_DISTANCE) {
|
||||
return 'horizontal'
|
||||
}
|
||||
if (y > x && y > MIN_DISTANCE) {
|
||||
return 'vertical'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
touchStart(e) {
|
||||
this.resetTouchStatus()
|
||||
const touch = this.getTouchPoint(e)
|
||||
this.startX = touch.x
|
||||
this.startY = touch.y
|
||||
},
|
||||
touchMove(e) {
|
||||
const touch = this.getTouchPoint(e)
|
||||
this.deltaX = touch.x - this.startX
|
||||
this.deltaY = touch.y - this.startY
|
||||
this.offsetX = Math.abs(this.deltaX)
|
||||
this.offsetY = Math.abs(this.deltaY)
|
||||
this.direction = this.direction || getDirection(this.offsetX, this.offsetY)
|
||||
},
|
||||
getTouchPoint(e) {
|
||||
if (!e) {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
if (e.touches && e.touches[0]) {
|
||||
return {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
}
|
||||
}
|
||||
if (e.changedTouches && e.changedTouches[0]) {
|
||||
return {
|
||||
x: e.changedTouches[0].pageX,
|
||||
y: e.changedTouches[0].pageY
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: e.clientX || 0,
|
||||
y: e.clientY || 0
|
||||
}
|
||||
},
|
||||
resetTouchStatus() {
|
||||
this.direction = ''
|
||||
this.deltaX = 0
|
||||
this.deltaY = 0
|
||||
this.offsetX = 0
|
||||
this.offsetY = 0
|
||||
}
|
||||
}
|
||||
const MIN_DISTANCE = 10
|
||||
|
||||
function getDirection(x, y) {
|
||||
if (x > y && x > MIN_DISTANCE) {
|
||||
return 'horizontal'
|
||||
}
|
||||
if (y > x && y > MIN_DISTANCE) {
|
||||
return 'vertical'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
touchStart(e) {
|
||||
this.resetTouchStatus()
|
||||
const touch = this.getTouchPoint(e)
|
||||
this.startX = touch.x
|
||||
this.startY = touch.y
|
||||
},
|
||||
touchMove(e) {
|
||||
const touch = this.getTouchPoint(e)
|
||||
this.deltaX = touch.x - this.startX
|
||||
this.deltaY = touch.y - this.startY
|
||||
this.offsetX = Math.abs(this.deltaX)
|
||||
this.offsetY = Math.abs(this.deltaY)
|
||||
this.direction = this.direction || getDirection(this.offsetX, this.offsetY)
|
||||
},
|
||||
getTouchPoint(e) {
|
||||
if (!e) {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
if (e.touches && e.touches[0]) {
|
||||
return {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
}
|
||||
}
|
||||
if (e.changedTouches && e.changedTouches[0]) {
|
||||
return {
|
||||
x: e.changedTouches[0].pageX,
|
||||
y: e.changedTouches[0].pageY
|
||||
}
|
||||
}
|
||||
return {
|
||||
x: e.clientX || 0,
|
||||
y: e.clientY || 0
|
||||
}
|
||||
},
|
||||
resetTouchStatus() {
|
||||
this.direction = ''
|
||||
this.deltaX = 0
|
||||
this.deltaY = 0
|
||||
this.offsetX = 0
|
||||
this.offsetY = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,55 @@
|
||||
/**
|
||||
* 递归使用call方式 this指向
|
||||
*
|
||||
* @param {String} componentName 需要查找的组件的名称
|
||||
* @param {String} eventName 事件名称
|
||||
* @param {Object} params 需要传递的参数
|
||||
*/
|
||||
function broadcast(componentName, eventName, params) {
|
||||
// 循环子节点找到需要的节点,没有查找到就递归进行查找
|
||||
this.$children.map(child => {
|
||||
if (componentName === child.$options.name) {
|
||||
child.$emit.apply(child, [eventName].concat(params))
|
||||
} else {
|
||||
broadcast.apply(child, [componentName, eventName].concat(params))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
/**
|
||||
* 派发 向上查找一个
|
||||
* @param {Object} componentName 需要查找的组件的名称
|
||||
* @param {Object} eventName 事件名称
|
||||
* @param {Object} params 需要传递的参数
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
// 找到最近父节点 $root 根节点
|
||||
let parent = this.$parent || this.$root
|
||||
// 获取当前实例的名称
|
||||
let name = parent.$options.name
|
||||
|
||||
// 当前存在节点并且当前节点没有名称或者名称不等于我们要查找的节点名称,则继续遍历
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent
|
||||
if (parent) {
|
||||
name = parent.$options.name
|
||||
}
|
||||
}
|
||||
// 如果有节点则表示找到
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 广播 向下查找多个
|
||||
* @param {Object} componentName 需要查找的组件的名称
|
||||
* @param {Object} eventName 事件名称
|
||||
* @param {Object} params 需要传递的参数
|
||||
*/
|
||||
broadcast(componentName, eventName, params) {
|
||||
broadcast.call(this, componentName, eventName, params)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 递归使用call方式 this指向
|
||||
*
|
||||
* @param {String} componentName 需要查找的组件的名称
|
||||
* @param {String} eventName 事件名称
|
||||
* @param {Object} params 需要传递的参数
|
||||
*/
|
||||
function broadcast(componentName, eventName, params) {
|
||||
// 循环子节点找到需要的节点,没有查找到就递归进行查找
|
||||
this.$children.map(child => {
|
||||
if (componentName === child.$options.name) {
|
||||
child.$emit.apply(child, [eventName].concat(params))
|
||||
} else {
|
||||
broadcast.apply(child, [componentName, eventName].concat(params))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
/**
|
||||
* 派发 向上查找一个
|
||||
* @param {Object} componentName 需要查找的组件的名称
|
||||
* @param {Object} eventName 事件名称
|
||||
* @param {Object} params 需要传递的参数
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
// 找到最近父节点 $root 根节点
|
||||
let parent = this.$parent || this.$root
|
||||
// 获取当前实例的名称
|
||||
let name = parent.$options.name
|
||||
|
||||
// 当前存在节点并且当前节点没有名称或者名称不等于我们要查找的节点名称,则继续遍历
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent
|
||||
if (parent) {
|
||||
name = parent.$options.name
|
||||
}
|
||||
}
|
||||
// 如果有节点则表示找到
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 广播 向下查找多个
|
||||
* @param {Object} componentName 需要查找的组件的名称
|
||||
* @param {Object} eventName 事件名称
|
||||
* @param {Object} params 需要传递的参数
|
||||
*/
|
||||
broadcast(componentName, eventName, params) {
|
||||
broadcast.call(this, componentName, eventName, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
-183
@@ -1,183 +1,183 @@
|
||||
// 此文件为TuniaoUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
|
||||
// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
|
||||
// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
|
||||
|
||||
// 组件配置
|
||||
$tn-form-item-height: 70rpx;
|
||||
|
||||
|
||||
// 主颜色
|
||||
$tn-main-color: #01BEFF;
|
||||
$tn-main-orange: #FBBD12;
|
||||
$tn-embellished-green: #00FFC6;
|
||||
$tn-embellished-yellow: #FFF00D;
|
||||
$tn-auxiliary-powder: #FF71D2;
|
||||
$tn-auxiliary-blue: #82B2FF;
|
||||
$tn-bg-color: #FFFFFF;
|
||||
$tn-bg-gray-color: #F4F4F4;
|
||||
$tn-space-color: #F8F7F8;
|
||||
|
||||
// 边框颜色
|
||||
$tn-border-solid-color: rgba(0, 0, 0, 0.1);
|
||||
$tn-border-solids-color: #eee;
|
||||
$tn-border-dashed-color: #ddd;
|
||||
|
||||
// 阴影颜色
|
||||
$tn-shadow-color: rgba(0, 0, 0, 0.1);
|
||||
$tn-box-shadow-color: rgba(0, 0, 0, 0.2);
|
||||
|
||||
// 字体颜色
|
||||
$tn-font-color: #080808;
|
||||
$tn-font-sub-color: #AAAAAA;
|
||||
$tn-content-color: #838383;
|
||||
$tn-font-holder-color: #E6E6E6;
|
||||
|
||||
$tn-mask-bg-color: rgba(0, 0, 0, 0.4);
|
||||
|
||||
$tn-progress-bg-color: #f0f0f0;
|
||||
|
||||
|
||||
$tn-color-red: #E83A30;
|
||||
$tn-color-red-dark: #BA2E26;
|
||||
$tn-color-red-disabled: #F39C97;
|
||||
$tn-color-red-light: #FAD8D6;
|
||||
|
||||
$tn-color-purplered: #E72F8C;
|
||||
$tn-color-purplered-dark: #B9266F;
|
||||
$tn-color-purplered-disabled: #F397C5;
|
||||
$tn-color-purplered-light: #FAD5E8;
|
||||
|
||||
$tn-color-purple: #892FE8;
|
||||
$tn-color-purple-dark: #6E26BA;
|
||||
$tn-color-purple-disabled: #C497F3;
|
||||
$tn-color-purple-light: #E7D5FA;
|
||||
|
||||
$tn-color-bluepurple: #5F4FD9;
|
||||
$tn-color-bluepurple-dark: #4C3FAE;
|
||||
$tn-color-bluepurple-disabled: #AFA7EC;
|
||||
$tn-color-bluepurple-light: #DFDCF7;
|
||||
|
||||
$tn-color-aquablue: #3646FF;
|
||||
$tn-color-aquablue-dark: #2B38CC;
|
||||
$tn-color-aquablue-disabled: #9AA2FF;
|
||||
$tn-color-aquablue-light: #D7DAFF;
|
||||
|
||||
$tn-color-blue: #3D7EFF;
|
||||
$tn-color-blue-dark: #3165CC;
|
||||
$tn-color-blue-disabled: #9EBEFF;
|
||||
$tn-color-blue-light: #D8E5FF;
|
||||
|
||||
$tn-color-indigo: #31C9E8;
|
||||
$tn-color-indigo-dark: #27A1BA;
|
||||
$tn-color-indigo-disabled: #98E4F3;
|
||||
$tn-color-indigo-light: #D6F4FA;
|
||||
|
||||
$tn-color-cyan: #2DE8BD;
|
||||
$tn-color-cyan-dark: #24BA97;
|
||||
$tn-color-cyan-disabled: #96F3DE;
|
||||
$tn-color-cyan-light: #D5FAF2;
|
||||
|
||||
$tn-color-teal: #24F083;
|
||||
$tn-color-teal-dark: #1DC069;
|
||||
$tn-color-teal-disabled: #91F7C1;
|
||||
$tn-color-teal-light: #D3FCE6;
|
||||
|
||||
$tn-color-green: #31E749;
|
||||
$tn-color-green-dark: #27B93A;
|
||||
$tn-color-green-disabled: #98F3A4;
|
||||
$tn-color-green-light: #D6FADB;
|
||||
|
||||
$tn-color-yellowgreen: #A4E82F;
|
||||
$tn-color-yellowgreen-dark: #82BA26;
|
||||
$tn-color-yellowgreen-disabled: #D1F397;
|
||||
$tn-color-yellowgreen-light: #EDFAD5;
|
||||
|
||||
$tn-color-lime: #D5EB00;
|
||||
$tn-color-lime-dark: #AABC00;
|
||||
$tn-color-lime-disabled: #E9F57F;
|
||||
$tn-color-lime-light: #F7FBCC;
|
||||
|
||||
$tn-color-yellow: #FFF420;
|
||||
$tn-color-yellow-dark: #CCC21A;
|
||||
$tn-color-yellow-disabled: #FFF88F;
|
||||
$tn-color-yellow-light: #FFFDD2;
|
||||
|
||||
$tn-color-orangeyellow: #FFCA28;
|
||||
$tn-color-orangeyellow-dark: #CCA220;
|
||||
$tn-color-orangeyellow-disabled: #FFE493;
|
||||
$tn-color-orangeyellow-light: #FFF4D4;
|
||||
|
||||
$tn-color-orange: #FFA726;
|
||||
$tn-color-orange-dark: #CC851E;
|
||||
$tn-color-orange-disabled: #FFD392;
|
||||
$tn-color-orange-light: #FFEDD4 ;
|
||||
|
||||
$tn-color-orangered: #FF7043;
|
||||
$tn-color-orangered-dark: #CC5A36;
|
||||
$tn-color-orangered-disabled: #FFB7A1;
|
||||
$tn-color-orangered-light: #FFE2D9;
|
||||
|
||||
$tn-color-brown: #914F2C;
|
||||
$tn-color-brown-dark: #743F23;
|
||||
$tn-color-brown-disabled: #C8A795;
|
||||
$tn-color-brown-light: #E9DCD5;
|
||||
|
||||
$tn-color-grey: #78909C;
|
||||
$tn-color-grey-dark: #5F7E8B;
|
||||
$tn-color-grey-disabled: #C6D1D8;
|
||||
$tn-color-grey-light: #E4E9EC;
|
||||
|
||||
$tn-color-gray: #AAAAAA;
|
||||
$tn-color-gray-dark: #838383;
|
||||
$tn-color-gray-disabled: #E6E6E6;
|
||||
$tn-color-gray-light: #F8F7F8;
|
||||
|
||||
$tn-cool-bg-color-1-start: #F5317F;
|
||||
$tn-cool-bg-color-1-end: #FF7C6E;
|
||||
|
||||
$tn-cool-bg-color-2-start: #CA26FF;
|
||||
$tn-cool-bg-color-2-end: #F360A7;
|
||||
|
||||
$tn-cool-bg-color-3-start: #A26FFC;
|
||||
$tn-cool-bg-color-3-end: #9D12FF;
|
||||
|
||||
$tn-cool-bg-color-4-start: #AA77F0;
|
||||
$tn-cool-bg-color-4-end: #E871E5;
|
||||
|
||||
$tn-cool-bg-color-5-start: #40A0F7;
|
||||
$tn-cool-bg-color-5-end: #4866E6;
|
||||
|
||||
$tn-cool-bg-color-6-start: #209CFF;
|
||||
$tn-cool-bg-color-6-end: #68E0CF;
|
||||
|
||||
$tn-cool-bg-color-7-start: #00C3FF;
|
||||
$tn-cool-bg-color-7-end: #58FFF5;
|
||||
|
||||
$tn-cool-bg-color-8-start: #00d1FF;
|
||||
$tn-cool-bg-color-8-end: #69FF97;
|
||||
|
||||
$tn-cool-bg-color-9-start: #0FD893;
|
||||
$tn-cool-bg-color-9-end: #29ECBF;
|
||||
|
||||
$tn-cool-bg-color-10-start: #0FD850;
|
||||
$tn-cool-bg-color-10-end: #F9F047;
|
||||
|
||||
$tn-cool-bg-color-11-start: #24FE41;
|
||||
$tn-cool-bg-color-11-end: #F7FD47;
|
||||
|
||||
$tn-cool-bg-color-12-start: #D6FF7F;
|
||||
$tn-cool-bg-color-12-end: #00F657;
|
||||
|
||||
$tn-cool-bg-color-13-start: #FA709A;
|
||||
$tn-cool-bg-color-13-end: #FEE140;
|
||||
|
||||
$tn-cool-bg-color-14-start: #FE5E9C;
|
||||
$tn-cool-bg-color-14-end: #F1AA76;
|
||||
|
||||
$tn-cool-bg-color-15-start: #FF3181;
|
||||
$tn-cool-bg-color-15-end: #FF8331;
|
||||
|
||||
$tn-cool-bg-color-16-start: #ED1C24;
|
||||
$tn-cool-bg-color-16-end: #FECE12;
|
||||
|
||||
|
||||
// 此文件为TuniaoUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
|
||||
// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
|
||||
// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
|
||||
|
||||
// 组件配置
|
||||
$tn-form-item-height: 70rpx;
|
||||
|
||||
|
||||
// 主颜色
|
||||
$tn-main-color: #01BEFF;
|
||||
$tn-main-orange: #FBBD12;
|
||||
$tn-embellished-green: #00FFC6;
|
||||
$tn-embellished-yellow: #FFF00D;
|
||||
$tn-auxiliary-powder: #FF71D2;
|
||||
$tn-auxiliary-blue: #82B2FF;
|
||||
$tn-bg-color: #FFFFFF;
|
||||
$tn-bg-gray-color: #F4F4F4;
|
||||
$tn-space-color: #F8F7F8;
|
||||
|
||||
// 边框颜色
|
||||
$tn-border-solid-color: rgba(0, 0, 0, 0.1);
|
||||
$tn-border-solids-color: #eee;
|
||||
$tn-border-dashed-color: #ddd;
|
||||
|
||||
// 阴影颜色
|
||||
$tn-shadow-color: rgba(0, 0, 0, 0.1);
|
||||
$tn-box-shadow-color: rgba(0, 0, 0, 0.2);
|
||||
|
||||
// 字体颜色
|
||||
$tn-font-color: #080808;
|
||||
$tn-font-sub-color: #AAAAAA;
|
||||
$tn-content-color: #838383;
|
||||
$tn-font-holder-color: #E6E6E6;
|
||||
|
||||
$tn-mask-bg-color: rgba(0, 0, 0, 0.4);
|
||||
|
||||
$tn-progress-bg-color: #f0f0f0;
|
||||
|
||||
|
||||
$tn-color-red: #E83A30;
|
||||
$tn-color-red-dark: #BA2E26;
|
||||
$tn-color-red-disabled: #F39C97;
|
||||
$tn-color-red-light: #FAD8D6;
|
||||
|
||||
$tn-color-purplered: #E72F8C;
|
||||
$tn-color-purplered-dark: #B9266F;
|
||||
$tn-color-purplered-disabled: #F397C5;
|
||||
$tn-color-purplered-light: #FAD5E8;
|
||||
|
||||
$tn-color-purple: #892FE8;
|
||||
$tn-color-purple-dark: #6E26BA;
|
||||
$tn-color-purple-disabled: #C497F3;
|
||||
$tn-color-purple-light: #E7D5FA;
|
||||
|
||||
$tn-color-bluepurple: #5F4FD9;
|
||||
$tn-color-bluepurple-dark: #4C3FAE;
|
||||
$tn-color-bluepurple-disabled: #AFA7EC;
|
||||
$tn-color-bluepurple-light: #DFDCF7;
|
||||
|
||||
$tn-color-aquablue: #3646FF;
|
||||
$tn-color-aquablue-dark: #2B38CC;
|
||||
$tn-color-aquablue-disabled: #9AA2FF;
|
||||
$tn-color-aquablue-light: #D7DAFF;
|
||||
|
||||
$tn-color-blue: #3D7EFF;
|
||||
$tn-color-blue-dark: #3165CC;
|
||||
$tn-color-blue-disabled: #9EBEFF;
|
||||
$tn-color-blue-light: #D8E5FF;
|
||||
|
||||
$tn-color-indigo: #31C9E8;
|
||||
$tn-color-indigo-dark: #27A1BA;
|
||||
$tn-color-indigo-disabled: #98E4F3;
|
||||
$tn-color-indigo-light: #D6F4FA;
|
||||
|
||||
$tn-color-cyan: #2DE8BD;
|
||||
$tn-color-cyan-dark: #24BA97;
|
||||
$tn-color-cyan-disabled: #96F3DE;
|
||||
$tn-color-cyan-light: #D5FAF2;
|
||||
|
||||
$tn-color-teal: #24F083;
|
||||
$tn-color-teal-dark: #1DC069;
|
||||
$tn-color-teal-disabled: #91F7C1;
|
||||
$tn-color-teal-light: #D3FCE6;
|
||||
|
||||
$tn-color-green: #31E749;
|
||||
$tn-color-green-dark: #27B93A;
|
||||
$tn-color-green-disabled: #98F3A4;
|
||||
$tn-color-green-light: #D6FADB;
|
||||
|
||||
$tn-color-yellowgreen: #A4E82F;
|
||||
$tn-color-yellowgreen-dark: #82BA26;
|
||||
$tn-color-yellowgreen-disabled: #D1F397;
|
||||
$tn-color-yellowgreen-light: #EDFAD5;
|
||||
|
||||
$tn-color-lime: #D5EB00;
|
||||
$tn-color-lime-dark: #AABC00;
|
||||
$tn-color-lime-disabled: #E9F57F;
|
||||
$tn-color-lime-light: #F7FBCC;
|
||||
|
||||
$tn-color-yellow: #FFF420;
|
||||
$tn-color-yellow-dark: #CCC21A;
|
||||
$tn-color-yellow-disabled: #FFF88F;
|
||||
$tn-color-yellow-light: #FFFDD2;
|
||||
|
||||
$tn-color-orangeyellow: #FFCA28;
|
||||
$tn-color-orangeyellow-dark: #CCA220;
|
||||
$tn-color-orangeyellow-disabled: #FFE493;
|
||||
$tn-color-orangeyellow-light: #FFF4D4;
|
||||
|
||||
$tn-color-orange: #FFA726;
|
||||
$tn-color-orange-dark: #CC851E;
|
||||
$tn-color-orange-disabled: #FFD392;
|
||||
$tn-color-orange-light: #FFEDD4 ;
|
||||
|
||||
$tn-color-orangered: #FF7043;
|
||||
$tn-color-orangered-dark: #CC5A36;
|
||||
$tn-color-orangered-disabled: #FFB7A1;
|
||||
$tn-color-orangered-light: #FFE2D9;
|
||||
|
||||
$tn-color-brown: #914F2C;
|
||||
$tn-color-brown-dark: #743F23;
|
||||
$tn-color-brown-disabled: #C8A795;
|
||||
$tn-color-brown-light: #E9DCD5;
|
||||
|
||||
$tn-color-grey: #78909C;
|
||||
$tn-color-grey-dark: #5F7E8B;
|
||||
$tn-color-grey-disabled: #C6D1D8;
|
||||
$tn-color-grey-light: #E4E9EC;
|
||||
|
||||
$tn-color-gray: #AAAAAA;
|
||||
$tn-color-gray-dark: #838383;
|
||||
$tn-color-gray-disabled: #E6E6E6;
|
||||
$tn-color-gray-light: #F8F7F8;
|
||||
|
||||
$tn-cool-bg-color-1-start: #F5317F;
|
||||
$tn-cool-bg-color-1-end: #FF7C6E;
|
||||
|
||||
$tn-cool-bg-color-2-start: #CA26FF;
|
||||
$tn-cool-bg-color-2-end: #F360A7;
|
||||
|
||||
$tn-cool-bg-color-3-start: #A26FFC;
|
||||
$tn-cool-bg-color-3-end: #9D12FF;
|
||||
|
||||
$tn-cool-bg-color-4-start: #AA77F0;
|
||||
$tn-cool-bg-color-4-end: #E871E5;
|
||||
|
||||
$tn-cool-bg-color-5-start: #40A0F7;
|
||||
$tn-cool-bg-color-5-end: #4866E6;
|
||||
|
||||
$tn-cool-bg-color-6-start: #209CFF;
|
||||
$tn-cool-bg-color-6-end: #68E0CF;
|
||||
|
||||
$tn-cool-bg-color-7-start: #00C3FF;
|
||||
$tn-cool-bg-color-7-end: #58FFF5;
|
||||
|
||||
$tn-cool-bg-color-8-start: #00d1FF;
|
||||
$tn-cool-bg-color-8-end: #69FF97;
|
||||
|
||||
$tn-cool-bg-color-9-start: #0FD893;
|
||||
$tn-cool-bg-color-9-end: #29ECBF;
|
||||
|
||||
$tn-cool-bg-color-10-start: #0FD850;
|
||||
$tn-cool-bg-color-10-end: #F9F047;
|
||||
|
||||
$tn-cool-bg-color-11-start: #24FE41;
|
||||
$tn-cool-bg-color-11-end: #F7FD47;
|
||||
|
||||
$tn-cool-bg-color-12-start: #D6FF7F;
|
||||
$tn-cool-bg-color-12-end: #00F657;
|
||||
|
||||
$tn-cool-bg-color-13-start: #FA709A;
|
||||
$tn-cool-bg-color-13-end: #FEE140;
|
||||
|
||||
$tn-cool-bg-color-14-start: #FE5E9C;
|
||||
$tn-cool-bg-color-14-end: #F1AA76;
|
||||
|
||||
$tn-cool-bg-color-15-start: #FF3181;
|
||||
$tn-cool-bg-color-15-end: #FF8331;
|
||||
|
||||
$tn-cool-bg-color-16-start: #ED1C24;
|
||||
$tn-cool-bg-color-16-end: #FECE12;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user