项目初始化
This commit is contained in:
201
components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
201
components/thorui/tui-actionsheet/tui-actionsheet.vue
Normal file
@@ -0,0 +1,201 @@
|
||||
<template>
|
||||
<view @touchmove.stop.prevent>
|
||||
<view class="tui-actionsheet" :class="{'tui-actionsheet-show':show,'tui-actionsheet-radius':radius}"
|
||||
:style="{zIndex:getZIndex}">
|
||||
<view class="tui-actionsheet-tips" :style="{fontSize:size+'rpx',color:color}" v-if="tips">
|
||||
{{tips}}
|
||||
</view>
|
||||
<view :class="[isCancel?'tui-operate-box':'']">
|
||||
<block v-for="(item,index) in itemList" :key="index">
|
||||
<view class="tui-actionsheet-btn tui-actionsheet-divider"
|
||||
:class="{'tui-btn-last':!isCancel && index==itemList.length-1}"
|
||||
hover-class="tui-actionsheet-hover" :hover-stay-time="150" :data-index="index"
|
||||
:style="{color:item.color || '#2B2B2B'}" @tap="handleClickItem">{{item.text}}</view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="tui-actionsheet-btn tui-actionsheet-cancel" hover-class="tui-actionsheet-hover"
|
||||
:hover-stay-time="150" v-if="isCancel" @tap="handleClickCancel">取消</view>
|
||||
</view>
|
||||
<view class="tui-actionsheet-mask" :class="{'tui-mask-show':show}" :style="{background:maskColor,zIndex:zIndex}"
|
||||
@tap="handleClickMask"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiActionsheet",
|
||||
emits: ['click', 'cancel'],
|
||||
props: {
|
||||
//显示操作菜单
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//菜单按钮数组,自定义文本颜色,红色参考色:#e53a37
|
||||
itemList: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [{
|
||||
text: "确定",
|
||||
color: "#2B2B2B"
|
||||
}]
|
||||
}
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//v2.1.0
|
||||
maskColor: {
|
||||
type: String,
|
||||
default: "rgba(0, 0, 0, 0.6)"
|
||||
},
|
||||
//提示文字
|
||||
tips: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
//提示文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: "#808080"
|
||||
},
|
||||
//提示文字大小 rpx
|
||||
size: {
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
//是否需要圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否需要取消按钮
|
||||
isCancel: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 998
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getZIndex() {
|
||||
return Number(this.zIndex) + 2
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClickMask() {
|
||||
if (!this.maskClosable) return;
|
||||
this.handleClickCancel();
|
||||
},
|
||||
handleClickItem(e) {
|
||||
if (!this.show) return;
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
...this.itemList[index]
|
||||
});
|
||||
},
|
||||
handleClickCancel() {
|
||||
this.$emit('cancel');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-actionsheet {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
visibility: hidden;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.25s ease-in-out;
|
||||
background-color: #F7F7F7;
|
||||
min-height: 100rpx;
|
||||
}
|
||||
|
||||
.tui-actionsheet-radius {
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tui-actionsheet-show {
|
||||
transform: translate3d(0, 0, 0);
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-actionsheet-tips {
|
||||
width: 100%;
|
||||
padding: 40rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-operate-box {
|
||||
padding-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.tui-actionsheet-btn {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-btn-last {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-actionsheet-divider::before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
border-top: 1rpx solid #E7E7E7;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.tui-actionsheet-cancel {
|
||||
color: #1a1a1a;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-actionsheet-hover {
|
||||
background-color: #f7f7f9;
|
||||
}
|
||||
|
||||
.tui-actionsheet-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
129
components/thorui/tui-badge/tui-badge.vue
Normal file
129
components/thorui/tui-badge/tui-badge.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view :class="[dot ? 'tui-badge-dot' : 'tui-badge', !dot ? 'tui-badge-scale' : '']"
|
||||
:style="{ top: top, right: right, position: absolute ? 'absolute' : 'static', transform: getStyle, margin: margin,background:getBackground,color:getColor }"
|
||||
@tap="handleClick">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBadge',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
//primary,warning,green,danger,white,black,gray,white_red
|
||||
type: {
|
||||
type: String,
|
||||
default: 'primary'
|
||||
},
|
||||
//是否是圆点
|
||||
dot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否绝对定位
|
||||
absolute: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: '-8rpx'
|
||||
},
|
||||
right: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//缩放比例
|
||||
scaleRatio: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//水平方向移动距离
|
||||
translateX: {
|
||||
type: String,
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getStyle() {
|
||||
return `scale(${this.scaleRatio}) translateX(${this.translateX})`;
|
||||
},
|
||||
getBackground() {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = {
|
||||
'primary': (global && global.primary) || '#5677fc',
|
||||
'green': (global && global.success) || '#07c160',
|
||||
'warning': (global && global.warning) || '#ff7900',
|
||||
'danger': (global && global.danger) || '#EB0909',
|
||||
'white': '#fff',
|
||||
'black': '#000',
|
||||
'gray': '#ededed',
|
||||
'red': (global && global.pink) || '#f74d54',
|
||||
'pink': (global && global.pink) || '#f74d54',
|
||||
'white_red': '#fff',
|
||||
'white_primary': '#fff',
|
||||
'white_green': '#fff',
|
||||
'white_warning': '#fff',
|
||||
'white_pink': '#fff'
|
||||
} [this.type]
|
||||
return color
|
||||
},
|
||||
getColor() {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = {
|
||||
'primary': '#fff',
|
||||
'green': '#fff',
|
||||
'warning': '#fff',
|
||||
'danger': '#fff',
|
||||
'white': '#333',
|
||||
'black': '#fff',
|
||||
'gray': '#999',
|
||||
'red': '#fff',
|
||||
'pink': (global && global.pink) || '#f74d54',
|
||||
'white_red': (global && global.danger) || '#EB0909',
|
||||
'white_primary': (global && global.primary) || '#5677fc',
|
||||
'white_green': (global && global.success) || '#07c160',
|
||||
'white_warning': (global && global.warning) || '#ff7900',
|
||||
'white_pink': (global && global.pink) || '#f74d54',
|
||||
} [this.type]
|
||||
return color
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('click', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-badge-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tui-badge {
|
||||
font-size: 24rpx;
|
||||
line-height: 24rpx;
|
||||
height: 36rpx;
|
||||
min-width: 36rpx;
|
||||
padding: 0 10rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tui-badge-scale {
|
||||
transform-origin: center center;
|
||||
}
|
||||
</style>
|
119
components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
119
components/thorui/tui-bottom-popup/tui-bottom-popup.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<view @touchmove.stop.prevent>
|
||||
<view class="tui-popup-class tui-bottom-popup"
|
||||
:class="{ 'tui-popup-show': show, 'tui-popup-radius': radius,'tui-bp__safearea':isSafeArea }"
|
||||
:style="{ background: backgroundColor, height: height ? height + 'rpx' : 'auto', zIndex: zIndex,transform:`translate3d(0, ${show?translateY:'100%'}, 0)`}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="tui-popup-mask" :class="[show ? 'tui-mask-show' : '']" :style="{ zIndex: maskZIndex }" v-if="mask"
|
||||
@tap="handleClose"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBottomPopup',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
//是否需要mask
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//控制显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//高度 rpx
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//设置圆角
|
||||
radius: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 997
|
||||
},
|
||||
maskZIndex: {
|
||||
type: [Number, String],
|
||||
default: 996
|
||||
},
|
||||
//弹层显示时,垂直方向移动的距离
|
||||
translateY: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//是否需要判断底部安全区域(主要针对iphonex以上机型)
|
||||
isSafeArea: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-bottom-popup {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 100%, 0);
|
||||
transform-origin: center;
|
||||
transition: all 0.3s ease-in-out;
|
||||
min-height: 20rpx;
|
||||
}
|
||||
|
||||
.tui-bp__safearea {
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tui-popup-radius {
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.tui-popup-show {
|
||||
opacity: 1;
|
||||
/* transform: translate3d(0, 0, 0); */
|
||||
}
|
||||
|
||||
.tui-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
204
components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
204
components/thorui/tui-bubble-popup/tui-bubble-popup.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<view :class="{ 'tui-flex-end': flexEnd }">
|
||||
<view class="tui-popup-list" :class="{ 'tui-popup-show': show,'tui-z_index':show && position!='relative' }" :style="{ width: width, backgroundColor: backgroundColor, borderRadius: radius, color: color, position: position, left: left, right: right, bottom: bottom, top: top,transform:`translate(${translateX},${translateY})` }">
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `transparent transparent ${backgroundColor} transparent`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'top'"></view>
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `${backgroundColor} transparent transparent transparent`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'bottom'"></view>
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `transparent ${backgroundColor} transparent transparent`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'left'"></view>
|
||||
<view class="tui-triangle" :style="{
|
||||
borderWidth: borderWidth,
|
||||
borderColor: `transparent transparent transparent ${backgroundColor}`,
|
||||
left: triangleLeft,
|
||||
right: triangleRight,
|
||||
top: triangleTop,
|
||||
bottom: triangleBottom
|
||||
}"
|
||||
v-if="direction == 'right'"></view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view @touchmove.stop.prevent="stop" class="tui-popup-mask" :class="{ 'tui-popup-show': show }" :style="{ backgroundColor: maskBgColor }"
|
||||
v-if="mask" @tap="handleClose"></view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiBubblePopup',
|
||||
emits: ['close'],
|
||||
props: {
|
||||
//宽度
|
||||
width: {
|
||||
type: String,
|
||||
default: '300rpx'
|
||||
},
|
||||
//popup圆角
|
||||
radius: {
|
||||
type: String,
|
||||
default: '8rpx'
|
||||
},
|
||||
//popup 定位 left right top bottom值
|
||||
left: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
right: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
bottom: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
translateX:{
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
translateY:{
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#4c4c4c'
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//三角border-width
|
||||
borderWidth: {
|
||||
type: String,
|
||||
default: '12rpx'
|
||||
},
|
||||
//三角形方向 top left right bottom
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
//定位 left right top bottom值
|
||||
triangleLeft: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
triangleRight: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
triangleTop: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
triangleBottom: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
//定位 relative absolute fixed
|
||||
position: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
//flex-end
|
||||
flexEnd: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否需要mask
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskBgColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.4)'
|
||||
},
|
||||
//控制显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
if (!this.show) {
|
||||
return;
|
||||
}
|
||||
this.$emit('close', {});
|
||||
},
|
||||
stop() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-popup-list {
|
||||
z-index: 1;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-flex-end {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.tui-triangle {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
z-index: 997;
|
||||
}
|
||||
|
||||
.tui-popup-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 995;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-popup-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-z_index {
|
||||
z-index: 996;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,309 @@
|
||||
<template>
|
||||
<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
|
||||
:style="{ width: diam + 'px', height: (height || diam) + 'px' }"
|
||||
v-if="defaultShow && defaultCanvasId"></canvas>
|
||||
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
|
||||
:style="{ width: diam + 'px', height: (height || diam) + 'px' }" v-if="progressCanvasId"></canvas>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId"
|
||||
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }" v-if="defaultShow"></canvas>
|
||||
<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId"
|
||||
:style="{ width: diam*4 + 'px', height: (height || diam)*4 + 'px' }"></canvas>
|
||||
<!-- #endif -->
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiCircularProgress',
|
||||
emits: ['change', 'end'],
|
||||
props: {
|
||||
/*
|
||||
传值需使用rpx进行转换保证各终端兼容
|
||||
px = rpx / 750 * wx.getSystemInfoSync().windowWidth
|
||||
圆形进度条(画布)宽度,直径 [px]
|
||||
*/
|
||||
diam: {
|
||||
type: Number,
|
||||
default: 60
|
||||
},
|
||||
//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
|
||||
height: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//进度条线条宽度[px]
|
||||
lineWidth: {
|
||||
type: Number,
|
||||
default: 4
|
||||
},
|
||||
/*
|
||||
线条的端点样式
|
||||
butt:向线条的每个末端添加平直的边缘
|
||||
round 向线条的每个末端添加圆形线帽
|
||||
square 向线条的每个末端添加正方形线帽
|
||||
*/
|
||||
lineCap: {
|
||||
type: String,
|
||||
default: 'round'
|
||||
},
|
||||
//圆环进度字体大小 [px]
|
||||
fontSize: {
|
||||
type: Number,
|
||||
default: 12
|
||||
},
|
||||
//圆环进度字体颜色
|
||||
fontColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//是否显示进度文字
|
||||
fontShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/*
|
||||
自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
|
||||
可以使用 slot自定义显示内容
|
||||
*/
|
||||
percentText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//是否显示默认(背景)进度条
|
||||
defaultShow: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//默认进度条颜色
|
||||
defaultColor: {
|
||||
type: String,
|
||||
default: '#CCCCCC'
|
||||
},
|
||||
//进度条颜色
|
||||
progressColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//进度条渐变颜色[结合progressColor使用,默认为空]
|
||||
gradualColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//起始弧度,单位弧度
|
||||
sAngle: {
|
||||
type: Number,
|
||||
default: -Math.PI / 2
|
||||
},
|
||||
//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
|
||||
counterclockwise: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//进度百分比 [10% 传值 10]
|
||||
percentage: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//进度百分比缩放倍数[使用半弧为100%时,则可传2]
|
||||
multiple: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//动画执行时间[单位毫秒,低于50无动画]
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 800
|
||||
},
|
||||
//backwards: 动画从头播;forwards:动画从上次结束点接着播
|
||||
activeMode: {
|
||||
type: String,
|
||||
default: 'backwards'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
percentage(val) {
|
||||
this.initDraw();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
// #ifndef MP-WEIXIN || MP-QQ
|
||||
let cid = `id01_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
let did = `id02_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
// #endif
|
||||
return {
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
progressCanvasId: 'progressCanvasId',
|
||||
defaultCanvasId: 'defaultCanvasId',
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN || MP-QQ
|
||||
progressCanvasId: cid,
|
||||
defaultCanvasId: did,
|
||||
// #endif
|
||||
progressContext: null,
|
||||
linearGradient: null,
|
||||
//起始百分比
|
||||
startPercentage: 0
|
||||
// dpi
|
||||
//pixelRatio: uni.getSystemInfoSync().pixelRatio
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initDraw(true);
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
//初始化绘制
|
||||
initDraw(init) {
|
||||
let start = this.activeMode === 'backwards' ? 0 : this.startPercentage;
|
||||
start = start > this.percentage ? 0 : start;
|
||||
if (this.defaultShow && init) {
|
||||
this.drawDefaultCircular();
|
||||
}
|
||||
this.drawProgressCircular(start);
|
||||
},
|
||||
//默认(背景)圆环
|
||||
drawDefaultCircular() {
|
||||
let ctx = uni.createCanvasContext(this.defaultCanvasId, this);
|
||||
let lineWidth = Number(this.lineWidth)
|
||||
// #ifdef MP-ALIPAY
|
||||
lineWidth = lineWidth * 4
|
||||
// #endif
|
||||
ctx.setLineWidth(lineWidth);
|
||||
ctx.setStrokeStyle(this.defaultColor);
|
||||
//终止弧度
|
||||
let eAngle = Math.PI * (this.height ? 1 : 2) + this.sAngle;
|
||||
this.drawArc(ctx, eAngle);
|
||||
},
|
||||
//进度圆环
|
||||
drawProgressCircular(startPercentage) {
|
||||
let ctx = this.progressContext;
|
||||
let gradient = this.linearGradient;
|
||||
if (!ctx) {
|
||||
ctx = uni.createCanvasContext(this.progressCanvasId, this);
|
||||
//创建一个线性的渐变颜色 CanvasGradient对象
|
||||
let diam = Number(this.diam)
|
||||
// #ifdef MP-ALIPAY
|
||||
diam = diam * 4
|
||||
// #endif
|
||||
const progressColor = this.progressColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
gradient = ctx.createLinearGradient(0, 0, diam, 0);
|
||||
gradient.addColorStop('0', progressColor);
|
||||
if (this.gradualColor) {
|
||||
gradient.addColorStop('1', this.gradualColor);
|
||||
}
|
||||
// #ifdef APP-PLUS || MP
|
||||
const res = uni.getSystemInfoSync();
|
||||
if (!this.gradualColor && res.platform.toLocaleLowerCase() == 'android') {
|
||||
gradient.addColorStop('1', progressColor);
|
||||
}
|
||||
// #endif
|
||||
this.progressContext = ctx;
|
||||
this.linearGradient = gradient;
|
||||
}
|
||||
let lineWidth = Number(this.lineWidth)
|
||||
// #ifdef MP-ALIPAY
|
||||
lineWidth = lineWidth * 4
|
||||
// #endif
|
||||
ctx.setLineWidth(lineWidth);
|
||||
ctx.setStrokeStyle(gradient);
|
||||
let time = this.percentage == 0 || this.duration < 50 ? 0 : this.duration / this.percentage;
|
||||
if (this.percentage > 0) {
|
||||
startPercentage = this.duration < 50 ? this.percentage - 1 : startPercentage;
|
||||
startPercentage++;
|
||||
}
|
||||
if (this.fontShow) {
|
||||
let fontSize = Number(this.fontSize)
|
||||
// #ifdef MP-ALIPAY
|
||||
fontSize = fontSize * 4
|
||||
// #endif
|
||||
ctx.setFontSize(fontSize);
|
||||
const fontColor = this.fontColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
ctx.setFillStyle(fontColor);
|
||||
ctx.setTextAlign('center');
|
||||
ctx.setTextBaseline('middle');
|
||||
let percentage = this.percentText;
|
||||
if (!percentage) {
|
||||
percentage = this.counterclockwise ? 100 - startPercentage * this.multiple : startPercentage * this
|
||||
.multiple;
|
||||
percentage = `${percentage}%`;
|
||||
}
|
||||
let radius = this.diam / 2;
|
||||
// #ifdef MP-ALIPAY
|
||||
radius = radius * 4
|
||||
// #endif
|
||||
ctx.fillText(percentage, radius, radius);
|
||||
}
|
||||
if (this.percentage == 0 || (this.counterclockwise && startPercentage == 100)) {
|
||||
ctx.draw();
|
||||
} else {
|
||||
let eAngle = ((2 * Math.PI) / 100) * startPercentage + this.sAngle;
|
||||
this.drawArc(ctx, eAngle);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.startPercentage = startPercentage;
|
||||
if (startPercentage >= this.percentage) {
|
||||
this.$emit('end', {
|
||||
canvasId: this.progressCanvasId,
|
||||
percentage: startPercentage
|
||||
});
|
||||
} else {
|
||||
this.drawProgressCircular(startPercentage);
|
||||
}
|
||||
this.$emit('change', {
|
||||
percentage: startPercentage
|
||||
});
|
||||
}, time);
|
||||
// #ifdef H5
|
||||
// requestAnimationFrame(()=>{})
|
||||
// #endif
|
||||
},
|
||||
//创建弧线
|
||||
drawArc(ctx, eAngle) {
|
||||
ctx.setLineCap(this.lineCap);
|
||||
ctx.beginPath();
|
||||
let radius = this.diam / 2; //x=y
|
||||
let lineWidth = Number(this.lineWidth)
|
||||
// #ifdef MP-ALIPAY
|
||||
radius = radius * 4
|
||||
lineWidth = lineWidth * 4
|
||||
// #endif
|
||||
ctx.arc(radius, radius, radius - lineWidth, this.sAngle, eAngle, this.counterclockwise);
|
||||
ctx.stroke();
|
||||
ctx.draw();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-circular-container,
|
||||
.tui-circular-default {
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
/* #ifdef MP-ALIPAY */
|
||||
.tui-circular-default,
|
||||
.tui-circular-progress {
|
||||
zoom: 0.25;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
|
||||
.tui-circular-progress {
|
||||
position: absolute !important;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
3
components/thorui/tui-icon/tui-icon.js
Normal file
3
components/thorui/tui-icon/tui-icon.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default {
|
||||
|
||||
}
|
82
components/thorui/tui-icon/tui-icon.vue
Normal file
82
components/thorui/tui-icon/tui-icon.vue
Normal file
File diff suppressed because one or more lines are too long
338
components/thorui/tui-modal/tui-modal.vue
Normal file
338
components/thorui/tui-modal/tui-modal.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<template>
|
||||
<view class="tui-modal__container" :class="[show ? 'tui-modal-show' : '']" :style="{zIndex:zIndex}"
|
||||
@touchmove.stop.prevent>
|
||||
<view class="tui-modal-box"
|
||||
:style="{ width: width, padding: padding, borderRadius: radius, backgroundColor: backgroundColor,zIndex:zIndex+1 }"
|
||||
:class="[fadeIn || show ? 'tui-modal-normal' : 'tui-modal-scale', show ? 'tui-modal-show' : '']">
|
||||
<view v-if="!custom">
|
||||
<view class="tui-modal-title" v-if="title">{{ title }}</view>
|
||||
<view class="tui-modal-content" :class="[title ? '' : 'tui-mtop']"
|
||||
:style="{ color: color, fontSize: size + 'rpx' }">{{ content }}</view>
|
||||
<view class="tui-modalBtn-box" :class="[button.length != 2 ? 'tui-flex-column' : '']">
|
||||
<view v-for="(item, index) in button" :key="index" class="tui-modal-btn" :class="[
|
||||
button.length != 2 ? 'tui-btn-width' : '',
|
||||
button.length > 2 ? 'tui-mbtm' : '',
|
||||
shape == 'circle' ? 'tui-circle-btn' : ''
|
||||
]" :style="{background:getColor(item.type,true,item.plain),color:getTextColor(item.type,item.plain)}"
|
||||
@tap="handleClick(index)">
|
||||
{{ item.text || '确定' }}
|
||||
<view class="tui-modal__border" :class="[shape == 'circle' ? 'tui-circle-border' : '']"
|
||||
:style="{borderColor:getColor(item.type)}" v-if="item.plain"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isMask" class="tui-modal-mask" :class="[show ? 'tui-mask-show' : '']"
|
||||
:style="{zIndex:maskZIndex,background:maskColor}" @tap="handleClickCancel"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiModal',
|
||||
emits: ['click', 'cancel'],
|
||||
props: {
|
||||
//是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '84%'
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '40rpx 64rpx'
|
||||
},
|
||||
radius: {
|
||||
type: String,
|
||||
default: '24rpx'
|
||||
},
|
||||
//标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//内容
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//内容字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
//内容字体大小 rpx
|
||||
size: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//形状 circle, square
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
button: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [{
|
||||
text: '取消',
|
||||
type: 'red',
|
||||
plain: true //是否空心
|
||||
},
|
||||
{
|
||||
text: '确定',
|
||||
type: 'red',
|
||||
plain: false
|
||||
}
|
||||
];
|
||||
}
|
||||
},
|
||||
//点击遮罩 是否可关闭
|
||||
maskClosable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否显示mask
|
||||
isMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
maskColor: {
|
||||
type: String,
|
||||
default: 'rgba(0, 0, 0, 0.6)'
|
||||
},
|
||||
//淡入效果,自定义弹框插入input输入框时传true
|
||||
fadeIn: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//自定义弹窗内容
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//容器z-index
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 9997
|
||||
},
|
||||
//mask z-index
|
||||
maskZIndex: {
|
||||
type: Number,
|
||||
default: 9990
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
getColor(type, isBg, plain) {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = {
|
||||
'primary': (global && global.primary) || '#5677fc',
|
||||
'green': (global && global.success) || '#07c160',
|
||||
'warning': (global && global.warning) || '#ff7900',
|
||||
'danger': (global && global.danger) || '#EB0909',
|
||||
'red': (global && global.danger) || '#EB0909',
|
||||
'pink': (global && global.pink) || '#f74d54',
|
||||
'white': isBg ? '#fff' : '#333',
|
||||
'gray': isBg ? '#ededed' : '#999'
|
||||
} [type || 'primary']
|
||||
if (isBg && plain) {
|
||||
color = 'transparent'
|
||||
}
|
||||
return color
|
||||
},
|
||||
getTextColor(type, plain) {
|
||||
const global = uni && uni.$tui && uni.$tui.color;
|
||||
let color = {
|
||||
'primary': (global && global.primary) || '#5677fc',
|
||||
'green': (global && global.success) || '#07c160',
|
||||
'warning': (global && global.warning) || '#ff7900',
|
||||
'danger': (global && global.danger) || '#EB0909',
|
||||
'red': (global && global.danger) || '#EB0909',
|
||||
'pink': (global && global.pink) || '#f74d54',
|
||||
'white': '#333',
|
||||
'gray': '#999'
|
||||
} [type || 'primary']
|
||||
if (!plain && type !== 'white' && type !== 'gray') {
|
||||
color = '#fff'
|
||||
}
|
||||
return color
|
||||
},
|
||||
handleClick(index) {
|
||||
if (!this.show) return;
|
||||
this.$emit('click', {
|
||||
index: Number(index)
|
||||
});
|
||||
},
|
||||
handleClickCancel() {
|
||||
if (!this.maskClosable) return;
|
||||
this.$emit('cancel');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-modal__container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-modal-box {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.tui-modal-scale {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.tui-modal-normal {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.tui-modal-show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.tui-modal-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.tui-mask-show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tui-modal-title {
|
||||
text-align: center;
|
||||
font-size: 34rpx;
|
||||
color: #333;
|
||||
padding-top: 20rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tui-modal-content {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
padding-top: 20rpx;
|
||||
padding-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.tui-mtop {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.tui-mbtm {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.tui-modalBtn-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tui-modal-btn {
|
||||
width: 46%;
|
||||
height: 68rpx;
|
||||
line-height: 68rpx;
|
||||
position: relative;
|
||||
border-radius: 10rpx;
|
||||
font-size: 26rpx;
|
||||
overflow: visible;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.tui-modal-btn:active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: rgba(0, 0, 0, 0.15);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-modal-btn::after {
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
|
||||
.tui-modal__border {
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
transform: scale(0.5, 0.5) translateZ(0);
|
||||
border: 1px solid;
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
border-radius: 20rpx;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tui-btn-width {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
.tui-circle-btn {
|
||||
border-radius: 40rpx !important;
|
||||
}
|
||||
|
||||
.tui-circle-btn::after {
|
||||
border-radius: 40rpx !important;
|
||||
}
|
||||
|
||||
.tui-circle-border {
|
||||
border-radius: 80rpx !important;
|
||||
}
|
||||
</style>
|
258
components/thorui/tui-navigation-bar/tui-navigation-bar.vue
Normal file
258
components/thorui/tui-navigation-bar/tui-navigation-bar.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<view class="tui-navigation-bar"
|
||||
:class="{ 'tui-bar-line': opacity > 0.85 && splitLine, 'tui-navbar-fixed': isFixed, 'tui-backdrop__filter': backdropFilter && dropDownOpacity > 0 }"
|
||||
:style="{ height: height + 'px', background: isOpacity? `rgba(${background},${opacity})`:background, opacity: dropDownOpacity, zIndex: isFixed ? zIndex : 'auto' }">
|
||||
<view class="tui-status-bar" :style="{ height: statusBarHeight + 'px' }" v-if="isImmersive"></view>
|
||||
<view class="tui-navigation_bar-title"
|
||||
:style="{ opacity: transparent || opacity >= maxOpacity ? 1 : opacity, color: color, paddingTop: top - statusBarHeight + 'px' }"
|
||||
v-if="title && !isCustom">
|
||||
{{ title }}
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiNavigationBar',
|
||||
emits: ['init', 'change'],
|
||||
props: {
|
||||
//NavigationBar标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//NavigationBar标题颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//NavigationBar背景颜色,不支持rgb
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//是否需要分割线
|
||||
splitLine: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否设置不透明度
|
||||
isOpacity: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//不透明度最大值 0-1
|
||||
maxOpacity: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//背景透明 【设置该属性,则背景透明,只出现内容,isOpacity和maxOpacity失效】
|
||||
transparent: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//滚动条滚动距离
|
||||
scrollTop: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
/*
|
||||
isOpacity 为true时生效
|
||||
opacity=scrollTop /windowWidth * scrollRatio
|
||||
*/
|
||||
scrollRatio: {
|
||||
type: [Number, String],
|
||||
default: 0.3
|
||||
},
|
||||
//是否自定义header内容
|
||||
isCustom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否沉浸式
|
||||
isImmersive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//是否开启高斯模糊效果[仅在支持的浏览器有效果]
|
||||
backdropFilter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//下拉隐藏NavigationBar,主要针对有回弹效果ios端
|
||||
dropDownHide: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//z-index设置
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 996
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollTop(newValue, oldValue) {
|
||||
if (this.isOpacity && !this.transparent) {
|
||||
this.opacityChange();
|
||||
}
|
||||
},
|
||||
backgroundColor(val) {
|
||||
if (val) {
|
||||
if (this.isOpacity) {
|
||||
this.background = this.hexToRgb(val);
|
||||
} else {
|
||||
this.background = this.transparent ? 'rgba(0, 0, 0, 0)' : val
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
width: 375, //header宽度
|
||||
left: 375, //小程序端 左侧距胶囊按钮距离
|
||||
height: 44, //header高度
|
||||
top: 0,
|
||||
scrollH: 1, //滚动总高度,计算opacity
|
||||
opacity: 1, //0-1
|
||||
statusBarHeight: 0, //状态栏高度
|
||||
background: '255,255,255', //header背景色
|
||||
dropDownOpacity: 1
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.dropDownOpacity = this.backdropFilter && 0;
|
||||
this.opacity = this.isOpacity || this.transparent ? 0 : this.maxOpacity;
|
||||
if (this.isOpacity) {
|
||||
this.background = this.hexToRgb(this.backgroundColor);
|
||||
} else {
|
||||
this.background = this.transparent ? 'rgba(0, 0, 0, 0)' : this.backgroundColor
|
||||
}
|
||||
let obj = {};
|
||||
// #ifdef MP-WEIXIN
|
||||
obj = wx.getMenuButtonBoundingClientRect();
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
obj = swan.getMenuButtonBoundingClientRect();
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
my.hideAddToDesktopMenu();
|
||||
// #endif
|
||||
uni.getSystemInfo({
|
||||
success: res => {
|
||||
this.statusBarHeight = res.statusBarHeight;
|
||||
this.width = res.windowWidth;
|
||||
this.left = obj.left || res.windowWidth;
|
||||
if (this.isImmersive) {
|
||||
this.height = obj.top ? obj.top + obj.height + 8 : res.statusBarHeight + 44;
|
||||
}
|
||||
this.scrollH = res.windowWidth * this.scrollRatio;
|
||||
this.top = obj.top ? obj.top + (obj.height - 32) / 2 : res.statusBarHeight + 6;
|
||||
this.$emit('init', {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
left: this.left,
|
||||
top: this.top,
|
||||
statusBarHeight: this.statusBarHeight,
|
||||
opacity: this.opacity,
|
||||
windowHeight: res.windowHeight
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
hexToRgb(hex) {
|
||||
let rgb = '255,255,255';
|
||||
if (hex && ~hex.indexOf('#')) {
|
||||
if (hex.length === 4) {
|
||||
let text = hex.substring(1, 4);
|
||||
hex = '#' + text + text;
|
||||
}
|
||||
let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (result) {
|
||||
rgb = `${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}`;
|
||||
}
|
||||
}
|
||||
return rgb;
|
||||
},
|
||||
opacityChange() {
|
||||
if (this.dropDownHide) {
|
||||
if (this.scrollTop < 0) {
|
||||
if (this.dropDownOpacity > 0) {
|
||||
this.dropDownOpacity = 1 - Math.abs(this.scrollTop) / 30;
|
||||
}
|
||||
} else {
|
||||
this.dropDownOpacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
let scroll = this.scrollTop <= 1 ? 0 : this.scrollTop;
|
||||
let opacity = scroll / this.scrollH;
|
||||
if ((this.opacity >= this.maxOpacity && opacity >= this.maxOpacity) || (this.opacity == 0 && opacity ==
|
||||
0)) {
|
||||
return;
|
||||
}
|
||||
this.opacity = opacity > this.maxOpacity ? this.maxOpacity : opacity;
|
||||
if (this.backdropFilter) {
|
||||
this.dropDownOpacity = this.opacity >= this.maxOpacity ? 1 : this.opacity;
|
||||
}
|
||||
this.$emit('change', {
|
||||
opacity: this.opacity
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-navigation-bar {
|
||||
width: 100%;
|
||||
transition: opacity 0.4s;
|
||||
}
|
||||
|
||||
.tui-backdrop__filter {
|
||||
/* Safari for macOS & iOS */
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
/* Google Chrome */
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
.tui-navbar-fixed {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.tui-status-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tui-navigation_bar-title {
|
||||
width: 100%;
|
||||
font-size: 17px;
|
||||
line-height: 17px;
|
||||
/* #ifndef APP-PLUS */
|
||||
font-weight: 500;
|
||||
/* #endif */
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-bar-line::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
248
components/thorui/tui-numberbox/tui-numberbox.vue
Normal file
248
components/thorui/tui-numberbox/tui-numberbox.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<view class="tui-numberbox">
|
||||
<view class="tui-num__icon__box" :style="{background:iconBgColor,borderRadius:radius}" @tap="reduce"
|
||||
:class="[disabled || min >= inputValue ? 'tui-disabled' : '']">
|
||||
<text class="tui-numbox-icon tui-num__icon-reduce"
|
||||
:style="{ color: iconColor, fontSize: iconSize + 'rpx',lineHeight:iconSize + 'rpx' }"></text>
|
||||
</view>
|
||||
<input type="number" v-model="inputValue" :disabled="disabled" @blur="blur" class="tui-num-input"
|
||||
:style="{ color: color, fontSize: size + 'rpx', background: backgroundColor, height: height + 'rpx', minHeight: height + 'rpx', width: width + 'rpx' }" />
|
||||
<view class="tui-num__icon__box" :style="{background:iconBgColor,borderRadius:radius}" @tap="plus"
|
||||
:class="[disabled || inputValue >= max ? 'tui-disabled' : '']">
|
||||
<text class="tui-numbox-icon tui-num__icon-plus"
|
||||
:style="{ color: iconColor, fontSize: iconSize + 'rpx',lineHeight:iconSize + 'rpx' }"></text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiNumberbox',
|
||||
emits: ['change'],
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//最小值
|
||||
min: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//最大值
|
||||
max: {
|
||||
type: Number,
|
||||
default: 99
|
||||
},
|
||||
//迈步大小 1 1.1 10...
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
//是否禁用操作
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
iconBgColor: {
|
||||
type: String,
|
||||
default: 'transparent'
|
||||
},
|
||||
radius:{
|
||||
type: String,
|
||||
default: '50%'
|
||||
},
|
||||
//加减图标大小 rpx
|
||||
iconSize: {
|
||||
type: Number,
|
||||
default: 22
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#666666'
|
||||
},
|
||||
//input 高度
|
||||
height: {
|
||||
type: Number,
|
||||
default: 42
|
||||
},
|
||||
//input 宽度
|
||||
width: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//input 背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#F5F5F5'
|
||||
},
|
||||
//input 字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
//索引值,列表中使用
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//自定义参数
|
||||
custom: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.inputValue = +this.value;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: 0
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.inputValue = +val;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getScale(val, step) {
|
||||
let scale = 1;
|
||||
let scaleVal = 1;
|
||||
//浮点型
|
||||
if (!Number.isInteger(step)) {
|
||||
scale = Math.pow(10, (step + '').split('.')[1].length);
|
||||
}
|
||||
//浮点型
|
||||
if (!Number.isInteger(val)) {
|
||||
scaleVal = Math.pow(10, (val + '').split('.')[1].length);
|
||||
}
|
||||
return Math.max(scale, scaleVal);
|
||||
},
|
||||
calcNum: function(type) {
|
||||
if (this.disabled || (this.inputValue == this.min && type === 'reduce') || (this.inputValue == this
|
||||
.max && type === 'plus')) {
|
||||
return;
|
||||
}
|
||||
const scale = this.getScale(Number(this.inputValue), Number(this.step));
|
||||
let num = Number(this.inputValue) * scale;
|
||||
let step = this.step * scale;
|
||||
if (type === 'reduce') {
|
||||
num -= step;
|
||||
} else if (type === 'plus') {
|
||||
num += step;
|
||||
}
|
||||
let value = Number((num / scale).toFixed(String(scale).length - 1));
|
||||
if (value < this.min) {
|
||||
value = this.min;
|
||||
} else if (value > this.max) {
|
||||
value = this.max;
|
||||
}
|
||||
this.handleChange(value, type);
|
||||
},
|
||||
plus: function() {
|
||||
this.calcNum('plus');
|
||||
},
|
||||
reduce: function() {
|
||||
this.calcNum('reduce');
|
||||
},
|
||||
blur: function(e) {
|
||||
let value = e.detail.value;
|
||||
if (value) {
|
||||
if (~value.indexOf('.') && Number.isInteger(this.step) && Number.isInteger(Number(value))) {
|
||||
value = value.split('.')[0];
|
||||
}
|
||||
value = Number(value);
|
||||
if (value > this.max) {
|
||||
value = this.max;
|
||||
} else if (value < this.min) {
|
||||
value = this.min;
|
||||
}
|
||||
} else {
|
||||
value = this.min;
|
||||
}
|
||||
if ((value == this.value && value != this.inputValue) || !e.detail.value) {
|
||||
this.inputValue = value;
|
||||
}
|
||||
this.handleChange(value, 'blur');
|
||||
},
|
||||
handleChange(value, type) {
|
||||
if (this.disabled) return;
|
||||
this.$emit('change', {
|
||||
value: Number(value),
|
||||
type: type,
|
||||
index: this.index,
|
||||
custom: this.custom
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@font-face {
|
||||
font-family: 'numberbox';
|
||||
src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAASQAA0AAAAABtwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEdAAAABoAAAAciBpnRUdERUYAAARUAAAAHgAAAB4AKQALT1MvMgAAAZwAAABDAAAAVjxzSINjbWFwAAAB9AAAAEYAAAFK5zLpOGdhc3AAAARMAAAACAAAAAj//wADZ2x5ZgAAAkgAAACHAAAAnIfIEjxoZWFkAAABMAAAAC8AAAA2FZWEOWhoZWEAAAFgAAAAHAAAACQH3gOFaG10eAAAAeAAAAARAAAAEgwAAAFsb2NhAAACPAAAAAwAAAAMADAATm1heHAAAAF8AAAAHwAAACABEAAobmFtZQAAAtAAAAFJAAACiCnmEVVwb3N0AAAEHAAAAC0AAABV/+8iFXjaY2BkYGAA4gVmC5Tj+W2+MnCzMIDATWsFOQT9v5GFgbkeyOVgYAKJAgDrogf+AHjaY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm142mNgZGBgYGWQYQDRDAxMQMwFhAwM/8F8BgALpAE5AHjaY2BkYWCcwMDKwMDUyXSGgYGhH0IzvmYwYuQAijKwMjNgBQFprikMDs9Yn01kbvjfwBDD3MDQABRmBMkBAOXpDHEAeNpjYYAAFghmZGAAAACdAA4AAAB42mNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZiesT6b+P8/AwOElvwnWQxVDwSMbAxwDiMTkGBiQAWMDMMeAABRZwszAAAAAAAAAAAAAAAwAE542iWKQQrCMBBF5xNpd0pQ7EIoTEnahSCTUNqdWz2A9TrieXKeXCc1qcPn/zfzh0BYv2pVH7oQgbvqdG5Yt/DTrNlPYz+wHvuuqhFSME4sFshTgKUsKfhH5lg8BSul3i5bS3mQdd0RIh2IjnvUrkXDd8zuhuFt86tY9fonIsSYgsXpB+cCGosAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMGiTIxMjMwiWZmJQJRXVQoigTgjMd9QGIsgAFDsEBsAAAAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwAEAAEABAAAAAIAAAAAeNpjYGBgZACCq0vUOUD0TWsFORgNADPBBE4AAA==) format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.tui-num__icon__box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
padding: 12rpx;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-numbox-icon {
|
||||
font-family: 'numberbox' !important;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.tui-num__icon-reduce:before {
|
||||
content: '\e691';
|
||||
}
|
||||
|
||||
.tui-num__icon-plus:before {
|
||||
content: '\e605';
|
||||
}
|
||||
|
||||
.tui-numberbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tui-num-input {
|
||||
text-align: center;
|
||||
margin: 0 6rpx;
|
||||
font-weight: 400;
|
||||
padding: 0;
|
||||
border-width: 0;
|
||||
}
|
||||
/* #ifdef H5 */
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button{
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.tui-disabled {
|
||||
opacity: .5;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
166
components/thorui/tui-sticky/tui-sticky.vue
Normal file
166
components/thorui/tui-sticky/tui-sticky.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<view class="tui-sticky-class" :id="tui_Id">
|
||||
<!--sticky 容器-->
|
||||
<view :style="{height: stickyHeight,background:backgroundColor}" v-if="isFixed"></view>
|
||||
<view :class="{'tui-sticky-fixed':isFixed}" :style="{top:isFixed?stickyTop+'px':'auto'}">
|
||||
<slot name="header"></slot>
|
||||
</view>
|
||||
<!--sticky 容器-->
|
||||
<!--内容-->
|
||||
<slot name="content"></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "tuiSticky",
|
||||
emits: ['sticky', 'change'],
|
||||
props: {
|
||||
scrollTop: {
|
||||
type: Number
|
||||
},
|
||||
//吸顶时与顶部的距离,单位px
|
||||
stickyTop: {
|
||||
type: [Number, String]
|
||||
// #ifndef H5
|
||||
,
|
||||
default: 0
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
,
|
||||
default: 44
|
||||
// #endif
|
||||
},
|
||||
//是否指定容器,即内容放置插槽content内
|
||||
container: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否是原生自带header
|
||||
isNativeHeader: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//吸顶容器 高度 rpx
|
||||
stickyHeight: {
|
||||
type: String,
|
||||
default: "auto"
|
||||
},
|
||||
//占位容器背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "transparent"
|
||||
},
|
||||
//是否重新计算[有异步加载时使用,设置大于0的数]
|
||||
recalc: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//列表中的索引值
|
||||
index: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
scrollTop(newValue, oldValue) {
|
||||
if (this.initialize != 0) {
|
||||
this.updateScrollChange(() => {
|
||||
this.updateStickyChange();
|
||||
this.initialize = 0
|
||||
});
|
||||
} else {
|
||||
this.updateStickyChange();
|
||||
}
|
||||
},
|
||||
recalc(newValue, oldValue) {
|
||||
this.updateScrollChange(() => {
|
||||
this.updateStickyChange();
|
||||
this.initialize = 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initialize = this.recalc
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.updateScrollChange();
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
data() {
|
||||
const Id = `tui_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
return {
|
||||
timer: null,
|
||||
top: 0,
|
||||
height: 0,
|
||||
isFixed: false,
|
||||
initialize: 0, //重新初始化
|
||||
tui_Id: Id
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
updateStickyChange() {
|
||||
const top = this.top;
|
||||
const height = this.height;
|
||||
const scrollTop = this.scrollTop
|
||||
let stickyTop = this.stickyTop
|
||||
// #ifdef H5
|
||||
if (this.isNativeHeader) {
|
||||
stickyTop = stickyTop - 44
|
||||
stickyTop = stickyTop < 0 ? 0 : stickyTop
|
||||
}
|
||||
// #endif
|
||||
if (this.container) {
|
||||
this.isFixed = (scrollTop + stickyTop >= top && scrollTop + stickyTop < top + height) ? true : false
|
||||
} else {
|
||||
this.isFixed = scrollTop + stickyTop >= top ? true : false
|
||||
}
|
||||
//是否吸顶
|
||||
this.$emit("sticky", {
|
||||
isFixed: this.isFixed,
|
||||
index: this.index
|
||||
})
|
||||
},
|
||||
updateScrollChange(callback) {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.timer = setTimeout(() => {
|
||||
const selectId = `#${this.tui_Id}`;
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select(selectId)
|
||||
.fields({
|
||||
size: true,
|
||||
rect: true
|
||||
}, res => {
|
||||
if (res) {
|
||||
this.top = res.top + (this.scrollTop || 0);
|
||||
this.height = res.height;
|
||||
callback && callback();
|
||||
this.$emit("change", {
|
||||
index: Number(this.index),
|
||||
top: this.top
|
||||
})
|
||||
}
|
||||
}).exec()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-sticky-fixed {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
z-index: 888;
|
||||
}
|
||||
</style>
|
287
components/thorui/tui-tabbar/tui-tabbar.vue
Normal file
287
components/thorui/tui-tabbar/tui-tabbar.vue
Normal file
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<view class="tui-tabbar"
|
||||
:class="{ 'tui-tabbar-fixed': isFixed, 'tui-unlined': unlined, 'tui-backdrop__filter': backdropFilter }"
|
||||
:style="{ background: backgroundColor, zIndex: isFixed ? zIndex : 'auto' }">
|
||||
<block v-for="(item, index) in tabBar" :key="index">
|
||||
<view class="tui-tabbar-item" :class="{ 'tui-item-hump': item.hump }"
|
||||
:style="{ backgroundColor: item.hump && !backdropFilter ? backgroundColor : 'none' }"
|
||||
@tap="tabbarSwitch(index, item.hump, item.pagePath, item.verify)">
|
||||
<view class="tui-icon-box" :class="{ 'tui-tabbar-hump': item.hump }">
|
||||
<image :src="current == index ? item.selectedIconPath : item.iconPath"
|
||||
:class="[item.hump ? '' : 'tui-tabbar-icon']" v-if="!item.name"></image>
|
||||
<tui-icon :name="current===index?item.activeName:item.name" :customPrefix="item.customPrefix || ''"
|
||||
:size="item.iconSize || iconSize" unit="rpx" :color="current == index?getActiveColor:color"
|
||||
v-else></tui-icon>
|
||||
<view :class="[item.isDot ? 'tui-badge-dot' : 'tui-badge']"
|
||||
:style="{ color: badgeColor, backgroundColor: getBadgeBgColor }" v-if="item.num">
|
||||
{{ item.isDot ? '' : item.num }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="tui-text-scale" :class="{ 'tui-text-hump': item.hump }"
|
||||
:style="{ color: current == index ? getActiveColor : color }">{{ item.text }}</view>
|
||||
</view>
|
||||
</block>
|
||||
<view :style="{ background: backgroundColor }" :class="{ 'tui-hump-box': hump }"
|
||||
v-if="hump && !unlined && !backdropFilter"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tuiIcon from '@/components/thorui/tui-icon/tui-icon.vue'
|
||||
export default {
|
||||
name: 'tuiTabbar',
|
||||
emits: ['click'],
|
||||
components:{
|
||||
tuiIcon
|
||||
},
|
||||
props: {
|
||||
//当前索引
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#666'
|
||||
},
|
||||
//字体选中颜色
|
||||
selectedColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//背景颜色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
//是否需要中间凸起按钮
|
||||
hump: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
iconSize: {
|
||||
type: [Number, String],
|
||||
default: 52
|
||||
},
|
||||
//固定在底部
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
tabBar: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//角标字体颜色
|
||||
badgeColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//角标背景颜色
|
||||
badgeBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
unlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//是否开启高斯模糊效果[仅在支持的浏览器有效果]
|
||||
backdropFilter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 9999
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed:{
|
||||
getActiveColor(){
|
||||
return this.selectedColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getBadgeBgColor(){
|
||||
return this.badgeBgColor || (uni && uni.$tui && uni.$tui.color.pink) || '#f74d54';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
tabbarSwitch(index, hump, pagePath, verify) {
|
||||
this.$emit('click', {
|
||||
index: index,
|
||||
hump: hump,
|
||||
pagePath: pagePath,
|
||||
verify: verify
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-tabbar {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-backdrop__filter {
|
||||
/* Safari for macOS & iOS */
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
/* Google Chrome */
|
||||
backdrop-filter: blur(15px);
|
||||
}
|
||||
|
||||
.tui-tabbar-fixed {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: content-box !important;
|
||||
}
|
||||
|
||||
.tui-tabbar::before {
|
||||
content: ' ';
|
||||
width: 100%;
|
||||
border-top: 1px solid #b2b2b2;
|
||||
position: absolute;
|
||||
top: -1rpx;
|
||||
left: 0;
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 0;
|
||||
display: block;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.tui-tabbar-item {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 10rpx 0;
|
||||
box-sizing: border-box;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.tui-icon-box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-item-hump {
|
||||
height: 98rpx;
|
||||
}
|
||||
|
||||
.tui-tabbar-icon {
|
||||
width: 52rpx;
|
||||
height: 52rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tui-hump-box {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: -50rpx;
|
||||
border-radius: 50%;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.tui-hump-box::after {
|
||||
content: ' ';
|
||||
height: 200%;
|
||||
width: 200%;
|
||||
border: 1px solid #b2b2b2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: scale(0.5) translateZ(0);
|
||||
transform-origin: 0 0;
|
||||
border-radius: 120rpx;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tui-unlined::after {
|
||||
height: 0 !important;
|
||||
}
|
||||
|
||||
.tui-tabbar-hump {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
-webkit-transform: translateX(-50%) rotate(0deg);
|
||||
transform: translateX(-50%) rotate(0deg);
|
||||
top: -40rpx;
|
||||
-webkit-transition: all 0.2s linear;
|
||||
transition: all 0.2s linear;
|
||||
border-radius: 50%;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.tui-tabbar-hump image {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tui-hump-active {
|
||||
-webkit-transform: translateX(-50%) rotate(135deg);
|
||||
transform: translateX(-50%) rotate(135deg);
|
||||
}
|
||||
|
||||
.tui-text-scale {
|
||||
transform: scale(0.8);
|
||||
font-size: 25rpx;
|
||||
line-height: 28rpx;
|
||||
transform-origin: center 100%;
|
||||
}
|
||||
|
||||
.tui-text-hump {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 10rpx;
|
||||
transform: scale(0.8) translateX(-50%);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
|
||||
.tui-badge {
|
||||
position: absolute;
|
||||
font-size: 24rpx;
|
||||
height: 32rpx;
|
||||
min-width: 20rpx;
|
||||
padding: 0 6rpx;
|
||||
border-radius: 40rpx;
|
||||
right: 0;
|
||||
top: -5rpx;
|
||||
transform: translateX(70%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tui-badge-dot {
|
||||
position: absolute;
|
||||
height: 16rpx;
|
||||
width: 16rpx;
|
||||
border-radius: 50%;
|
||||
right: -4rpx;
|
||||
top: -4rpx;
|
||||
}
|
||||
</style>
|
346
components/thorui/tui-tabs/tui-tabs.vue
Normal file
346
components/thorui/tui-tabs/tui-tabs.vue
Normal file
@@ -0,0 +1,346 @@
|
||||
<template>
|
||||
<view class="tui-tabs-view"
|
||||
:class="[isFixed ? 'tui-tabs-fixed' : 'tui-tabs-relative', unlined ? 'tui-unlined' : '']" :style="{
|
||||
width: tabsWidth + 'px',
|
||||
height: height + 'rpx',
|
||||
padding: `0 ${padding}rpx`,
|
||||
background: backgroundColor,
|
||||
top: isFixed ? top + 'px' : 'auto',
|
||||
zIndex: isFixed ? zIndex : 'auto'
|
||||
}" v-if="tabsWidth>0">
|
||||
<view v-for="(item, index) in tabs" :key="index" class="tui-tabs-item"
|
||||
:style="{ width: getItemWidth,height: height + 'rpx' }" @tap.stop="swichTabs(index)">
|
||||
<view class="tui-tabs-title" :class="{'tui-tabs-disabled': item.disabled }" :style="{
|
||||
color: currentTab == index ? getSelectedColor : color,
|
||||
fontSize: size + 'rpx',
|
||||
fontWeight: bold && currentTab == index ? 'bold' : 'normal',transform:`scale(${currentTab == index?scale:1})`
|
||||
}">
|
||||
{{ item[field] }} <text v-if="item[badgeField]">({{item[badgeField]}})</text>
|
||||
<view :class="[item.isDot ? 'tui-badge__dot' : 'tui-tabs__badge']"
|
||||
:style="{ color: badgeColor, backgroundColor: getBadgeBgColor }"
|
||||
v-if="item.isDot">
|
||||
{{ item.isDot ? '' : item[badgeField] }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isSlider" class="tui-tabs-slider" :style="{
|
||||
transform: 'translateX(' + scrollLeft + 'px)',
|
||||
width: sliderWidth + 'rpx',
|
||||
height: sliderHeight + 'rpx',
|
||||
borderRadius: sliderRadius,
|
||||
bottom: bottom,
|
||||
background: getSliderBgColor,
|
||||
marginBottom: bottom == '50%' ? '-' + sliderHeight / 2 + 'rpx' : 0
|
||||
}"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'tuiTabs',
|
||||
emits: ['change'],
|
||||
props: {
|
||||
//标签页
|
||||
tabs: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
//显示文本字段名称
|
||||
field: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
badgeField: {
|
||||
type: String,
|
||||
default: 'num'
|
||||
},
|
||||
//tabs宽度,不传值则默认使用windowWidth,单位px
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
//rpx
|
||||
height: {
|
||||
type: Number,
|
||||
default: 80
|
||||
},
|
||||
//rpx 只对左右padding起作用,上下为0
|
||||
padding: {
|
||||
type: Number,
|
||||
default: 30
|
||||
},
|
||||
//背景色
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
//是否固定
|
||||
isFixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//px
|
||||
top: {
|
||||
type: Number,
|
||||
// #ifndef H5
|
||||
default: 0,
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
default: 44
|
||||
// #endif
|
||||
},
|
||||
//是否去掉底部线条
|
||||
unlined: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//当前选项卡
|
||||
currentTab: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
isSlider: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
//滑块宽度
|
||||
sliderWidth: {
|
||||
type: Number,
|
||||
default: 68
|
||||
},
|
||||
//滑块高度
|
||||
sliderHeight: {
|
||||
type: Number,
|
||||
default: 6
|
||||
},
|
||||
//滑块背景颜色
|
||||
sliderBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
sliderRadius: {
|
||||
type: String,
|
||||
default: '50rpx'
|
||||
},
|
||||
//滑块bottom
|
||||
bottom: {
|
||||
type: String,
|
||||
default: '0'
|
||||
},
|
||||
//标签页宽度
|
||||
itemWidth: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#666'
|
||||
},
|
||||
//选中后字体颜色
|
||||
selectedColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
//字体大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: 28
|
||||
},
|
||||
//选中后 是否加粗 ,未选中则无效
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
//2.3.0+
|
||||
scale: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
//角标字体颜色
|
||||
badgeColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
//角标背景颜色
|
||||
badgeBgColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: 996
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentTab() {
|
||||
this.checkCor();
|
||||
},
|
||||
tabs() {
|
||||
this.checkCor();
|
||||
},
|
||||
width(val) {
|
||||
this.tabsWidth = val;
|
||||
this.checkCor();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getItemWidth() {
|
||||
let width = 100 / (this.tabs.length || 4) + '%'
|
||||
return this.itemWidth ? this.itemWidth : width
|
||||
},
|
||||
getSliderBgColor() {
|
||||
return this.sliderBgColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getSelectedColor() {
|
||||
return this.selectedColor || (uni && uni.$tui && uni.$tui.color.primary) || '#5677fc';
|
||||
},
|
||||
getBadgeBgColor() {
|
||||
return this.badgeBgColor || (uni && uni.$tui && uni.$tui.color.pink) || '#f74d54';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 高度自适应
|
||||
setTimeout(() => {
|
||||
uni.getSystemInfo({
|
||||
success: res => {
|
||||
this.winWidth = res.windowWidth;
|
||||
this.tabsWidth = this.width == 0 ? this.winWidth : Number(this.width);
|
||||
this.checkCor();
|
||||
}
|
||||
});
|
||||
}, 0);
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
winWidth: 0,
|
||||
tabsWidth: 0,
|
||||
scrollLeft: 0
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
checkCor: function() {
|
||||
let tabsNum = this.tabs.length;
|
||||
let padding = uni.upx2px(Number(this.padding));
|
||||
let width = this.tabsWidth - padding * 2;
|
||||
let left = (width / tabsNum - uni.upx2px(Number(this.sliderWidth))) / 2 + padding;
|
||||
let scrollLeft = left;
|
||||
if (this.currentTab > 0) {
|
||||
scrollLeft = scrollLeft + (width / tabsNum) * this.currentTab;
|
||||
}
|
||||
this.scrollLeft = scrollLeft;
|
||||
},
|
||||
// 点击标题切换当前页时改变样式
|
||||
swichTabs: function(index) {
|
||||
let item = this.tabs[index];
|
||||
if (item && item.disabled) return;
|
||||
if (this.currentTab == index) {
|
||||
return false;
|
||||
} else {
|
||||
this.$emit('change', {
|
||||
index: Number(index),
|
||||
item: item
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tui-tabs-view {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tui-tabs-relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tui-tabs-fixed {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tui-tabs-fixed::before,
|
||||
.tui-tabs-relative::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1rpx solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tui-unlined::before {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.tui-tabs-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.tui-tabs-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tui-tabs-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
overflow: visible;
|
||||
transition: all 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.tui-tabs-slider {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
transition: all 0.3s ease-in-out;
|
||||
z-index: 1;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.tui-tabs__badge {
|
||||
position: absolute;
|
||||
font-size: 24rpx;
|
||||
height: 32rpx;
|
||||
min-width: 20rpx;
|
||||
padding: 0 6rpx;
|
||||
border-radius: 40rpx;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translate(88%, -50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
z-index: 4;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.tui-badge__dot {
|
||||
position: absolute;
|
||||
height: 16rpx;
|
||||
width: 16rpx;
|
||||
border-radius: 50%;
|
||||
right: -10rpx;
|
||||
top: -10rpx;
|
||||
z-index: 4;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user