mand-mobile/components/tab-picker/index.vue

390 lines
9.7 KiB
Vue

<template>
<div class="md-tab-picker">
<md-popup
v-model="isTabPickerShow"
position="bottom"
:mask-closable="false"
@show="$_onShow"
@hide="$_onHide"
>
<md-popup-title-bar
:title="title"
:ok-text="okText"
:cancel-text="cancelText"
@confirm="$_onConfirm"
@cancel="$_onCancel"
></md-popup-title-bar>
<div class="md-tab-picker-content">
<md-tabs
ref="tabs"
:titles="subTitles"
:default-index="defaultTabIndex"
:force-use-array="hasTitleSlotScope"
@indexChanged="$_onIndexChange"
>
<template
slot="title"
slot-scope="props">
<slot :label="props" name="titles"></slot>
</template>
<div v-for="(title, index) of subTitles" :key="index">
<md-radio
ref="radio1"
:options="renderData[index].data"
:default-index="~renderData[index].clickedKey ? renderData[index].clickedKey : -1"
:key="renderData[index].clickedKey"
:is-slot-scope="hasOptionSlotScope"
@change="$_onRadioChange"
>
<template
slot-scope="props">
<slot :option="props.option" :index="index" name="option"></slot>
</template>
</md-radio>
</div>
</md-tabs>
<div class="slot-wrapper" v-if="isLoading || isDataError">
<div class="slot-inner">
<slot name="error" v-if="isDataError">数据异常</slot>
<slot name="loading" v-if="isLoading">loading</slot>
</div>
</div>
</div>
</md-popup>
</div>
</template>
<script>
import Popup from '../popup'
import PopupTitlebar from '../popup/title-bar'
import Tabs from '../tabs'
import Icon from '../icon'
import Radio from '../radio'
import {noop, compareObjects} from '../_util'
export default {
name: 'md-tab-picker',
components: {
[Popup.name]: Popup,
[PopupTitlebar.name]: PopupTitlebar,
[Tabs.name]: Tabs,
[Icon.name]: Icon,
[Radio.name]: Radio,
},
props: {
value: {
type: Boolean,
default: false,
},
title: {
type: String,
default: '',
},
okText: {
type: String,
default: '确认',
},
cancelText: {
type: String,
default: '取消',
},
data: {
type: Array,
default() {
return []
},
},
dataStruct: {
type: String,
default: 'normal',
},
defaultIndex: {
type: Array,
default() {
return []
},
},
optionRender: {
type: Array,
default() {
return []
},
validator(value) {
if (value.length > 0) {
return value.every(item => {
return typeof item === 'function'
})
} else {
return true
}
},
},
asyncFunc: {
type: Function,
default() {
return noop
},
},
},
data() {
return {
isTabPickerShow: false,
subTitles: [],
renderData: [],
defaultTabIndex: 0,
currentIndex: 0,
isLoading: true,
isDataError: false,
currentColumnLock: false,
}
},
watch: {
value(val) {
val && (this.isTabPickerShow = val)
},
isTabPickerShow(val) {
!val && this.$emit('input', val)
},
data: {
handler(val, oldVal) {
if (!compareObjects(val, oldVal)) {
this.$_initTabPicker()
}
},
deep: true,
},
isDataError(val) {
if (val) {
setTimeout(() => {
this.isDataError = false
}, 2000)
}
},
},
computed: {
hasTitleSlotScope() {
return !!this.$scopedSlots.titles
},
hasOptionSlotScope() {
return !!this.$scopedSlots.option
},
},
created() {
this.$_initTabPicker()
},
methods: {
// MARK: private methods
$_initTabPicker() {
switch (this.dataStruct) {
case 'normal':
this.$_initNoCascadeData()
break
case 'cascade':
this.$_initCascadeData()
break
case 'async':
this.$_initAsyncCascadeData()
break
default:
break
}
},
$_initCascadeData() {
if (this.data.length === 0) {
return
}
this.isLoading = false
if (this.defaultIndex && this.defaultIndex.length > 0) {
let i = 0
const func = array => {
if (i < this.defaultIndex.length) {
const temp = this.defaultIndex[i]
array.forEach((item, eq) => {
if (eq === temp) {
this.subTitles.push(item.label)
this.renderData.push({index: i, clickedKey: temp, data: array})
if (item && item.children && Array.isArray(item.children)) {
i++
func(item.children)
}
}
})
} else {
this.defaultTabIndex = i
return false
}
}
func(this.data)
} else {
this.subTitles.push('请选择')
this.renderData.push({index: 0, clickedKey: -1, data: this.data})
}
},
$_initNoCascadeData() {
if (this.data.length === 0) {
return
}
this.isLoading = false
this.data.forEach((item, index) => {
const temp = {
index: index,
clickedKey: this.defaultIndex.length > 0 && ~this.defaultIndex[index] ? this.defaultIndex[index] : -1,
data: item.children,
}
this.renderData.push(temp)
const currentColumn = this.renderData[index]
if (this.defaultIndex && this.defaultIndex.length > 0) {
this.subTitles.push(currentColumn.data[currentColumn.clickedKey].label)
} else {
this.subTitles.push(item.label)
}
})
},
$_initAsyncCascadeData() {
this.asyncFunc(null, this.$_renderNextTabContent)
},
$_renderNextTabContent(err, data) {
this.isLoading = false
if (err) {
this.isDataError = err
return
}
try {
this.subTitles.splice(this.currentIndex + 1, this.subTitles.length - 1, '请选择')
this.renderData.splice(this.currentIndex + 1, this.renderData.length - 1, {
index: this.currentIndex,
clickedKey: -1,
data: data.options,
asyncFunc: data.asyncFunc,
})
if (this.renderData.length > 1) {
this.$nextTick(() => {
this.$refs.tabs.selectTab(++this.currentIndex)
})
}
} catch (error) {
this.isDataError = true
}
},
// MARK: events handler, 如 $_onButtonClick
$_onShow() {
this.$emit('show')
},
$_onHide() {
this.$emit('hide')
},
$_onConfirm() {
this.isTabPickerShow = false
const selectedItem = this.getSelectedItem()
this.$emit('confirm', selectedItem)
},
$_onCancel() {
this.isTabPickerShow = false
this.$emit('cancel')
},
$_onRadioChange(value, index) {
if (this.dataStruct === 'cascade' && this.currentColumnLock) {
return
}
this.currentColumnLock = true
this.subTitles[this.currentIndex] = value.label
const currentColumn = this.renderData[this.currentIndex]
currentColumn.clickedKey = index
this.$emit('change', {
selectTab: this.currentIndex,
selectIndex: index,
selectItem: value,
})
if (this.dataStruct === 'cascade') {
if (value && value.children && Array.isArray(value.children) && value.children.length > 0) {
this.subTitles.splice(this.currentIndex + 1, this.subTitles.length - 1, '请选择')
this.renderData.splice(this.currentIndex + 1, this.renderData.length - 1, {
index: ++this.currentIndex,
clickedKey: -1,
data: value.children,
})
this.$nextTick(() => {
this.$refs.tabs.selectTab(this.currentIndex)
setTimeout(() => {
this.currentColumnLock = false
}, 500)
})
return
}
this.currentColumnLock = false
}
if (this.dataStruct === 'async' && currentColumn.asyncFunc) {
value = {
index: index,
...value,
}
// Object.assign(value, {index})
this.isLoading = true
typeof currentColumn.asyncFunc === 'function' && currentColumn.asyncFunc(value, this.$_renderNextTabContent)
}
},
$_onIndexChange(index) {
this.currentIndex = index
},
// MARK: public methods
getSelectedItem() {
return this.renderData.map((item, index) => {
if (~item.clickedKey) {
const selected = item.data[item.clickedKey]
return {
index: index,
item: {
label: selected.label,
value: selected.value,
},
}
} else {
return null
}
})
},
},
}
</script>
<style lang="stylus" scoped>
.md-tab-picker
color color-gray-1
-webkit-font-smoothing antialiased
font-size tab-picker-font-size
.md-tab-picker-content
background color-bg-base
</style>
<style lang="stylus">
.md-tab-picker
.md-tab-picker-content
position relative
.md-tabs
.md-tab-content-wrapper
height 400px
.md-tab-content
height 400px
overflow scroll
.slot-wrapper
background rgba(255, 255, 255, 0.7)
position absolute
top 80px
left 0
width 100%
bottom 0
.slot-inner
position absolute
top 50%
left 50%
transform translate(-50%, -50%)
</style>