690 lines
22 KiB
Vue
690 lines
22 KiB
Vue
<template>
|
||
<view class="dynamic-demo">
|
||
|
||
<!-- 效果预览窗口 -->
|
||
<view v-if="!noDemo" class="demo-container" :class="{'demo-container--full': full}">
|
||
<view class="demo">
|
||
<slot></slot>
|
||
</view>
|
||
<!-- 提示信息 -->
|
||
<view v-if="haveTips">
|
||
<view class="demo__tips__icon" @click="demoTipsClick">
|
||
<view class="icon tn-icon-help"></view>
|
||
</view>
|
||
<view class="demo__tips__content"
|
||
:class="[showContentTips ? 'demo__tips__content--show' : 'demo__tips__content--hide']">
|
||
<view v-for="(item,index) in tipsData" :key="index" class="demo__tips__content--item">{{ item }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 模式切换 -->
|
||
<view v-if="multiMode" class="mode-switch">
|
||
<view class="mode-switch__container">
|
||
<view v-for="(item, index) in sectionModeListInfos" :key="index" class="mode-switch__item"
|
||
:class="[`mode-switch-item-${index}`,{'mode-switch__item--active': modeIndex === index}]"
|
||
@click="switchMode(index)">{{ item.name }}</view>
|
||
|
||
<!-- 滑块样式 -->
|
||
<view class="mode-switch__slider" :style="[modeSwitchSliderStyle]"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 组件对应可选项容器 -->
|
||
<view class="section-container">
|
||
<scroll-view
|
||
class="section__scroll-view"
|
||
:class="{'section__scroll-view--auto': sectionScrollViewStyle.height === 'auto'}"
|
||
:style="[sectionScrollViewStyle]"
|
||
:scroll-y="sectionScrollViewStyle.height !== 'auto'"
|
||
>
|
||
<block v-for="(item,index) in btnsList" :key="index">
|
||
<view class="section__content" :class="{'section__content--visible': item.show}">
|
||
<view class="section__content__title">
|
||
<view class="section__content__title__left-line" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]"></view>
|
||
<view class="section__content__title--text tn-text-ellipsis" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]">{{ item.title }}</view>
|
||
<view class="section__content__title__right-line" :class="[`tn-main-gradient-${tuniaoColorList[index]}`]"></view>
|
||
</view>
|
||
<view class="section__content__btns">
|
||
<view v-for="(section_btn,section_index) in item.optional" :key="section_index"
|
||
class="section__content__btns__item" :class="[`tn-main-gradient-${tuniaoColorList[index]}--light`]" @click="sectionBtnClick(index, section_index)">
|
||
<view class="section__content__btns__item__bg"
|
||
:class="[`tn-main-gradient-${tuniaoColorList[index]}`, {'section__content__btns__item__bg--active':sectionIndex[modeIndex][index]['value'] === section_index}]"></view>
|
||
<view class="section__content__btns__item--text tn-text-ellipsis"
|
||
:class="[sectionIndex[modeIndex][index]['value'] === section_index ? 'section__content__btns__item--text--active' : `tn-color-${tuniaoColorList[index]}`]">{{ section_btn }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
name: 'dynamic-demo-template',
|
||
props: {
|
||
// 可选项列表数据
|
||
sectionList: {
|
||
type: Array,
|
||
default() {
|
||
return []
|
||
}
|
||
},
|
||
// 提示信息
|
||
tips: {
|
||
type: [String, Array],
|
||
default: ''
|
||
},
|
||
// 演示框的内容是否为铺满
|
||
full: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 是否使用了自定义顶部导航栏
|
||
customBar: {
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
// 是否全屏滚动
|
||
fullWindowsScroll: {
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
// 没有演示内容
|
||
noDemo: {
|
||
type: Boolean,
|
||
default: false
|
||
}
|
||
},
|
||
computed: {
|
||
tipsData() {
|
||
if (typeof this.tips === 'string') {
|
||
return [this.tips]
|
||
}
|
||
return this.tips
|
||
},
|
||
haveTips() {
|
||
return this.tips && this.tips.length > 0
|
||
},
|
||
multiMode() {
|
||
return this.sectionList.length > 1
|
||
},
|
||
sectionModeList() {
|
||
return this.sectionList.map((item) => {
|
||
return item.name
|
||
})
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
// 图鸟颜色列表
|
||
tuniaoColorList: this.$tn.color.getTuniaoColorList(),
|
||
// 保存选项列表信息(由于prop中的数据时不能被修改的)
|
||
_sectionList: [],
|
||
// 模式列表信息
|
||
sectionModeListInfos: [],
|
||
// 所选模式的序号
|
||
modeIndex: 0,
|
||
// 模式选择滑块样式
|
||
modeSwitchSliderStyle: {
|
||
width: 0,
|
||
left: 0
|
||
},
|
||
// 显示组件相关提示信息
|
||
showContentTips: false,
|
||
// 可选项滚动容器样式
|
||
sectionScrollViewStyle: {
|
||
height: 0
|
||
},
|
||
// 按钮列表信息
|
||
btnsList: [],
|
||
// 标记当前所选按钮
|
||
sectionIndex: [],
|
||
// 标记选项按钮是否可以滑动(使用scroll-view进行包裹)
|
||
sectionScrollFlag: true
|
||
}
|
||
},
|
||
watch: {
|
||
sectionList: {
|
||
handler(value) {
|
||
// 如果sectionList发生改变,重新初始化选项列表信息
|
||
this.initSectionBtns()
|
||
},
|
||
deep: true
|
||
},
|
||
sectionScrollFlag(value) {
|
||
if (!value) {
|
||
this.sectionScrollViewStyle.height = 'auto'
|
||
}
|
||
},
|
||
fullWindowsScroll: {
|
||
handler(value) {
|
||
if (value) {
|
||
this.sectionScrollViewStyle.height = 'auto'
|
||
}
|
||
},
|
||
immediate: true
|
||
}
|
||
},
|
||
created() {
|
||
// 初始化可选项模式列表
|
||
this.sectionModeListInfos = this.sectionModeList.map((item) => {
|
||
return {
|
||
name: item
|
||
}
|
||
})
|
||
// 初始化选项按钮默认信息
|
||
this.initSectionBtns()
|
||
},
|
||
mounted() {
|
||
// 等待加载组件完成
|
||
// setTimeout(() => {
|
||
// // 计算出底部scroll-view的高度
|
||
// this.initSectionScrollView()
|
||
|
||
// if (this.multiMode) {
|
||
// // 获取模式切换标签的信息
|
||
// this.getModeTabsInfo()
|
||
// }
|
||
// }, 10)
|
||
this.$nextTick(() => {
|
||
// 计算出底部scroll-view的高度
|
||
this.initSectionScrollView()
|
||
|
||
if (this.multiMode) {
|
||
// 获取模式切换标签的信息
|
||
this.getModeTabsInfo()
|
||
}
|
||
})
|
||
},
|
||
methods: {
|
||
// 初始化选项滑动窗口的高度
|
||
initSectionScrollView() {
|
||
// 全屏滚动时不进行任何的操作
|
||
if (this.fullWindowsScroll) {
|
||
return
|
||
}
|
||
// 获取屏幕的高度
|
||
uni.getSystemInfo({
|
||
success: (systemInfo) => {
|
||
// 通过当前屏幕的安全高度减去上一个元素的底部和距离上一个元素的外边距,然后减获取到的值减去标题栏的高度即可
|
||
const navBarHeight = this.customBar ? 0 : this.vuex_custom_bar_height
|
||
if (this.multiMode) {
|
||
uni.createSelectorQuery().in(this).select('.mode-switch').boundingClientRect(data => {
|
||
if (data.bottom >= systemInfo.safeArea.height) {
|
||
this.sectionScrollFlag = false
|
||
} else {
|
||
this.sectionScrollFlag = true
|
||
const containerBaseHeight = systemInfo.safeArea.height - data.bottom
|
||
this.sectionScrollViewStyle.height = (containerBaseHeight - navBarHeight) + systemInfo.statusBarHeight - uni.upx2px(75) + 'px'
|
||
}
|
||
}).exec()
|
||
} else {
|
||
if (!this.noDemo) {
|
||
uni.createSelectorQuery().in(this).select('.demo-container').boundingClientRect(data => {
|
||
if (data.bottom >= systemInfo.safeArea.height) {
|
||
this.sectionScrollFlag = false
|
||
} else {
|
||
this.sectionScrollFlag = true
|
||
const containerBaseHeight = systemInfo.safeArea.height - data.bottom
|
||
this.sectionScrollViewStyle.height = (containerBaseHeight - navBarHeight) + systemInfo.statusBarHeight - uni.upx2px(75) + 'px'
|
||
}
|
||
}).exec()
|
||
} else {
|
||
this.sectionScrollFlag = false
|
||
}
|
||
}
|
||
|
||
}
|
||
})
|
||
},
|
||
// 更新选项滑动容器的高度
|
||
updateSectionScrollView() {
|
||
this.$nextTick(() => {
|
||
this.initSectionScrollView()
|
||
})
|
||
},
|
||
// 获取各个模式tab的节点信息
|
||
getModeTabsInfo() {
|
||
let view = uni.createSelectorQuery().in(this)
|
||
for (let i = 0; i < this.sectionModeListInfos.length; i++) {
|
||
view.select('.mode-switch-item-' + i).boundingClientRect()
|
||
}
|
||
view.exec(res => {
|
||
// 如果没有获取到,则重新获取
|
||
if (!res.length) {
|
||
setTimeout(() => {
|
||
this.getModeTabsInfo()
|
||
}, 10)
|
||
return
|
||
}
|
||
// 将每个模式的宽度放入list中
|
||
res.map((item, index) => {
|
||
this.sectionModeListInfos[index].width = item.width
|
||
})
|
||
// 初始化滑块的宽度
|
||
this.modeSwitchSliderStyle.width = this.sectionModeListInfos[0].width + 'px'
|
||
|
||
// 初始化滑块的位置
|
||
this.modeSliderPosition()
|
||
})
|
||
},
|
||
// 设置模式滑块的位置
|
||
modeSliderPosition() {
|
||
let left = 0
|
||
// 计算当前所选模式选项到组件左边的距离
|
||
this.sectionModeListInfos.map((item, index) => {
|
||
if (index < this.modeIndex) left += item.width
|
||
})
|
||
|
||
this.modeSwitchSliderStyle.left = left + 'px'
|
||
},
|
||
// 切换模式
|
||
switchMode(index) {
|
||
// 不允许点击当前激活的选项
|
||
if (index === this.modeIndex) return
|
||
this.modeIndex = index
|
||
this.modeSliderPosition()
|
||
this.updateSectionBtns()
|
||
this.$emit('modeClick', {
|
||
index: index
|
||
})
|
||
},
|
||
// 点击内容提示信息
|
||
demoTipsClick() {
|
||
this.showContentTips = !this.showContentTips
|
||
},
|
||
// 初始化被选中选项按钮
|
||
initSectionBtns() {
|
||
this.sectionIndex = []
|
||
this.sectionIndex = this.sectionList.map((item) => {
|
||
if (item.hasOwnProperty('section') && item.section.length > 0) {
|
||
return Array(item.section.length).fill({
|
||
value: 0,
|
||
change: false
|
||
})
|
||
} else {
|
||
return []
|
||
}
|
||
})
|
||
|
||
this._sectionList = this.$tn.deepClone(this.sectionList)
|
||
// 给本地选项按钮列表给默认show属性
|
||
this._sectionList.map((item) => {
|
||
const section = item.section.map((section_item) => {
|
||
if (!section_item.hasOwnProperty('show')) {
|
||
section_item.show = true
|
||
}
|
||
return section_item
|
||
})
|
||
item.section = section
|
||
return item
|
||
})
|
||
|
||
// 更新按钮信息
|
||
this.updateSectionBtns()
|
||
},
|
||
// 跟新选项按钮信息
|
||
updateSectionBtns(sectionIndex = -1, showState = true) {
|
||
let sectionOptional = this._sectionList[this.modeIndex]['section']
|
||
this.btnsList = sectionOptional.map((item, index) => {
|
||
// 判断是否已经修改了对应的值
|
||
let changeValue = this.sectionIndex[this.modeIndex][index]['change'] || false
|
||
let currentSectionIndexValue = this.sectionIndex[this.modeIndex][index]['value'] || 0
|
||
// 取出默认值(如果是已经修改过的选项,则使用之前的选项信息)
|
||
let indexValue = changeValue ? currentSectionIndexValue : item.hasOwnProperty('current') ? item.current : 0
|
||
// 取出是否显示当前选项
|
||
let show = (sectionIndex !== -1 && sectionIndex === index) ? showState : item.hasOwnProperty('show') ? item.show : true
|
||
// 处理最大最小值
|
||
if (indexValue < 0) {
|
||
indexValue = 0
|
||
}
|
||
if (indexValue >= item.optional.length) {
|
||
indexValue = item.optional.length
|
||
}
|
||
// this.sectionIndex[this.modeIndex][index]['value'] = indexValue
|
||
this.$set(this.sectionIndex[this.modeIndex], index, {value: indexValue, change: changeValue})
|
||
item.show = show
|
||
return item
|
||
})
|
||
},
|
||
// 更新选项按钮状态信息
|
||
updateSectionBtnsState(sectionIndex = -1, showState = true) {
|
||
// 判断sectionIndex是否为数组
|
||
if (this.$tn.array.isArray(sectionIndex)) {
|
||
if (sectionIndex.length === 0) {
|
||
return
|
||
}
|
||
sectionIndex = sectionIndex.filter((item) => item >= 0 && item < this.sectionList[this.modeIndex]['section'].length)
|
||
sectionIndex.map((item) => {
|
||
this.btnsList[item]['show'] = showState
|
||
this._sectionList[this.modeIndex]['section'][item]['show'] = showState
|
||
})
|
||
} else {
|
||
if (sectionIndex < 0 || sectionIndex >= this.sectionList[this.modeIndex]['section'].length) {
|
||
return
|
||
}
|
||
// 将按键的对应显示状态设置为对应的状态
|
||
this.btnsList[sectionIndex]['show'] = showState
|
||
this._sectionList[this.modeIndex]['section'][sectionIndex]['show'] = showState
|
||
}
|
||
|
||
},
|
||
// 更新选项按钮选中信息
|
||
updateSectionBtnsValue(modeIndex = 0, sectionIndex = -1, value = 0) {
|
||
if (sectionIndex < 0 || sectionIndex >= this.sectionList[modeIndex]['section'].length) {
|
||
return
|
||
}
|
||
// 如果showState为false则移除对应的选项按钮,否则往对应的位置添加上对应的选项按钮
|
||
this.sectionIndex[modeIndex][sectionIndex] = {
|
||
value,
|
||
change: true
|
||
}
|
||
},
|
||
// 选项按钮点击事件
|
||
sectionBtnClick(index, sectionIndex) {
|
||
// if (this.sectionIndex[this.modeIndex][index] === sectionIndex) {
|
||
// return
|
||
// }
|
||
this.$set(this.sectionIndex[this.modeIndex], index, {value: sectionIndex, change: true})
|
||
this.$emit('click', {
|
||
methods: this.btnsList[index]['methods'],
|
||
index: sectionIndex,
|
||
name: this.btnsList[index]['optional'][sectionIndex]
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.dynamic-demo {
|
||
padding-top: 78rpx;
|
||
|
||
/* 顶部模式切换start */
|
||
.mode-switch {
|
||
width: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-top: 75rpx;
|
||
padding: 0 30rpx;
|
||
|
||
&__container {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
width: 476rpx;
|
||
height: 62rpx;
|
||
background-color: #FFFFFF;
|
||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||
border-radius: 31rpx;
|
||
}
|
||
|
||
&__item {
|
||
flex: 1;
|
||
height: 62rpx;
|
||
width: 100%;
|
||
line-height: 62rpx;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: $tn-font-sub-color;
|
||
z-index: 2;
|
||
transition: all 0.3s;
|
||
|
||
&--active {
|
||
color: #FFFFFF;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
&__slider {
|
||
position: absolute;
|
||
height: 62rpx;
|
||
border-radius: 31rpx;
|
||
// background-image: linear-gradient(-86deg, #FF8359 0%, #FFDF40 100%);
|
||
background-image: linear-gradient(-86deg, #00C3FF 0%, #58FFF5 100%);
|
||
box-shadow: 1rpx 10rpx 24rpx 0rpx #00C3FF77;
|
||
z-index: 1;
|
||
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||
}
|
||
}
|
||
|
||
/* 顶部模式切换end */
|
||
|
||
/* 演示内容展示start */
|
||
.demo-container {
|
||
min-height: 327rpx;
|
||
width: calc(100% - 60rpx);
|
||
background-color: #FFFFFF;
|
||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||
margin: 0 30rpx 5rpx 30rpx;
|
||
border-radius: 20rpx;
|
||
position: relative;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
|
||
&--full {
|
||
display: inline-block;
|
||
padding-bottom: 20rpx;
|
||
min-height: 0rpx;
|
||
padding: 10rpx 20rpx 30rpx;
|
||
}
|
||
|
||
.demo {
|
||
padding-top: 70rpx;
|
||
|
||
&__tips {
|
||
&__icon {
|
||
position: absolute;
|
||
top: 20rpx;
|
||
right: 16rpx;
|
||
width: 39rpx;
|
||
height: 39rpx;
|
||
line-height: 39rpx;
|
||
font-size: 39rpx;
|
||
|
||
.icon {
|
||
background: linear-gradient(-45deg, #FF8359 0%, #FFDF40 100%);
|
||
-webkit-background-clip: text;
|
||
color: transparent;
|
||
text-shadow: 0rpx 10rpx 10rpx rgba(255, 156, 82, 0.2);
|
||
}
|
||
}
|
||
|
||
&__content {
|
||
position: absolute;
|
||
top: 65rpx;
|
||
right: 16rpx;
|
||
font-size: 20rpx;
|
||
margin-left: 20rpx;
|
||
word-wrap: normal;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: #E6E6E6;
|
||
padding: 20rpx;
|
||
border-radius: 10rpx;
|
||
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1);
|
||
transform-origin: 0 0;
|
||
z-index: 999999;
|
||
|
||
&--hide {
|
||
transform: scaleY(0);
|
||
}
|
||
|
||
&--show {
|
||
transform: scaleY(100%);
|
||
|
||
&::after {
|
||
content: "";
|
||
width: 0px;
|
||
height: 0px;
|
||
border-width: 4px;
|
||
border-style: solid;
|
||
border-color: transparent transparent rgba(149, 149, 149, 0.1) transparent;
|
||
position: absolute;
|
||
top: -8px;
|
||
right: 6px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 演示内容展示end */
|
||
|
||
/* 可选项start */
|
||
.section-container {
|
||
width: 100%;
|
||
height: auto;
|
||
margin-top: 70rpx;
|
||
|
||
.section {
|
||
&__content {
|
||
margin-top: 70rpx;
|
||
display: none;
|
||
|
||
&--visible {
|
||
display: block;
|
||
|
||
&:last-child {
|
||
padding-bottom: calc(70rpx + env(safe-area-inset-bottom));
|
||
}
|
||
}
|
||
|
||
&:nth-child(1) {
|
||
margin-top: 0rpx;
|
||
}
|
||
|
||
&__title {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin: 0 30rpx;
|
||
text-align: center;
|
||
|
||
&__left-line,
|
||
&__right-line {
|
||
|
||
width: 100rpx;
|
||
height: 2rpx;
|
||
position: relative;
|
||
}
|
||
|
||
&__left-line {
|
||
&::after {
|
||
content: '';
|
||
background: inherit;
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
position: absolute;
|
||
top: -12rpx;
|
||
right: 0rpx;
|
||
border-radius: 50%;
|
||
transform: translateY(50%);
|
||
}
|
||
}
|
||
|
||
&__right-line {
|
||
&::after {
|
||
content: '';
|
||
background: inherit;
|
||
width: 12rpx;
|
||
height: 12rpx;
|
||
position: absolute;
|
||
top: -12rpx;
|
||
left: 0rpx;
|
||
border-radius: 50%;
|
||
transform: translateY(50%);
|
||
}
|
||
}
|
||
|
||
&--text {
|
||
-webkit-background-clip: text;
|
||
color: transparent;
|
||
min-width: 124rpx;
|
||
height: 30rpx;
|
||
font-size: 32rpx;
|
||
line-height: 1;
|
||
margin: 0 35rpx;
|
||
}
|
||
}
|
||
|
||
&__btns {
|
||
width: calc(100% - 60rpx);
|
||
margin: 0 30rpx;
|
||
margin-top: 29rpx;
|
||
padding: 50rpx 30rpx 0rpx 0rpx;
|
||
background-color: #FFFFFF;
|
||
box-shadow: 0rpx 10rpx 50rpx 0rpx rgba(0, 3, 72, 0.1);
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
flex-wrap: wrap;
|
||
|
||
&__item {
|
||
max-width: 30%;
|
||
padding: 17rpx 36rpx;
|
||
border-radius: 10rpx;
|
||
margin-bottom: 40rpx;
|
||
margin-left: 40rpx;
|
||
position: relative;
|
||
z-index: 1;
|
||
|
||
// &::before {
|
||
// content: " ";
|
||
// position: absolute;
|
||
// top: 10rpx;
|
||
// left: 1rpx;
|
||
// width: 100%;
|
||
// height: 100%;
|
||
// background: inherit;
|
||
// filter: blur(24rpx);
|
||
// opacity: 1;
|
||
// z-index: -1;
|
||
// }
|
||
|
||
&__bg {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: inherit;
|
||
z-index: -1;
|
||
opacity: 0;
|
||
transform: scale(0);
|
||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
|
||
&--active {
|
||
opacity: 1;
|
||
transform: scale(1);
|
||
}
|
||
}
|
||
|
||
&--text {
|
||
font-size: 24rpx;
|
||
line-height: 1.2em;
|
||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
|
||
&--active {
|
||
color: #FFFFFF;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 可选项end */
|
||
}
|
||
</style>
|