import baseComponent from '../helpers/baseComponent' import classNames from '../helpers/classNames' import styleToCssString from '../helpers/styleToCssString' import arrayTreeFilter from '../helpers/arrayTreeFilter' const WUX_CASCADER_VIEW = 'wux-cascader-view' const defaultFieldNames = { label: 'label', value: 'value', children: 'children', disabled: 'disabled', } baseComponent({ externalClasses: ['wux-scroll-view-class'], properties: { prefixCls: { type: String, value: 'wux-cascader-view', }, defaultValue: { type: Array, value: [], }, value: { type: Array, value: [], }, controlled: { type: Boolean, value: false, }, options: { type: Array, value: [], }, full: { type: Boolean, value: false, }, placeholder: { type: String, value: '请选择', }, height: { type: [String, Number], value: 'auto', }, defaultFieldNames: { type: Object, value: defaultFieldNames, }, skipAnimation: { type: Boolean, value: false, }, }, data: { activeOptions: [], activeIndex: 0, bodyStyle: '', activeValue: [], showOptions: [], fieldNames: undefined, scrollViewStyle: '', }, computed: { classes: ['prefixCls, full', function(prefixCls, full) { const wrap = classNames(prefixCls) const hd = `${prefixCls}__hd` const bd = `${prefixCls}__bd` const innerScroll = classNames(`${prefixCls}__inner-scroll`, { [`${prefixCls}__inner-scroll--full`]: full, }) const scrollView = `${prefixCls}__scroll-view` const ft = `${prefixCls}__ft` return { wrap, hd, bd, innerScroll, scrollView, ft, } }], }, observers: { value(newVal) { if (this.data.controlled) { this.setData({ activeValue: newVal }) this.getCurrentOptions(newVal) } }, options() { this.getCurrentOptions(this.data.activeValue) }, height(newVal) { this.updateStyle(newVal) }, }, methods: { getActiveOptions(activeValue) { const { options } = this.data const value = this.getFieldName('value') const childrenKeyName = this.getFieldName('children') return arrayTreeFilter(options, (option, level) => option[value] === activeValue[level], { childrenKeyName }) }, getShowOptions(activeValue) { const { options } = this.data const children = this.getFieldName('children') const result = this.getActiveOptions(activeValue).map((activeOption) => activeOption[children]).filter((activeOption) => !!activeOption) return [options, ...result] }, getMenus(activeValue = [], hasChildren) { const { placeholder } = this.data const activeOptions = this.getActiveOptions(activeValue) if (hasChildren) { const value = this.getFieldName('value') const label = this.getFieldName('label') activeOptions.push({ [value]: WUX_CASCADER_VIEW, [label]: placeholder, }) } return activeOptions }, getNextActiveValue(value, optionIndex) { let { activeValue } = this.data activeValue = activeValue.slice(0, optionIndex + 1) activeValue[optionIndex] = value return activeValue }, updated(currentOptions, optionIndex, condition, callback) { const value = this.getFieldName('value') const children = this.getFieldName('children') const hasChildren = currentOptions && currentOptions[children] && currentOptions[children].length > 0 const activeValue = this.getNextActiveValue(currentOptions[value], optionIndex) const activeOptions = this.getMenus(activeValue, hasChildren) const activeIndex = activeOptions.length - 1 const showOptions = this.getShowOptions(activeValue) const props = { activeValue, activeOptions, activeIndex, showOptions, } // 判断 hasChildren 计算需要更新的数据 if (hasChildren || (activeValue.length === showOptions.length && (optionIndex = Math.max(0, optionIndex - 1)))) { props.bodyStyle = this.getTransform(optionIndex + 1) props.showOptions = showOptions } // 判断是否需要 setData 更新数据 if (condition) { this.setCascaderView(props) } // 回调函数 if (typeof callback === 'function') { callback.call(this, currentOptions, activeValue) } }, /** * 更新级联数据 * @param {Array} activeValue 当前选中值 */ getCurrentOptions(activeValue = this.data.activeValue) { const optionIndex = Math.max(0, activeValue.length - 1) const activeOptions = this.getActiveOptions(activeValue) const currentOptions = activeOptions[optionIndex] if (currentOptions) { this.updated(currentOptions, optionIndex, true) } else { const value = this.getFieldName('value') const label = this.getFieldName('label') activeOptions.push({ [value]: WUX_CASCADER_VIEW, [label]: this.data.placeholder, }) const showOptions = this.getShowOptions(activeValue) const activeIndex = activeOptions.length - 1 const props = { showOptions, activeOptions, activeIndex, bodyStyle: '', } this.setCascaderView(props) } }, setCascaderView(props) { const { activeOptions, ...restProps } = props this.setData({ activeOptions }, () => { if (this.data.activeIndex !== restProps.activeIndex) { this.triggerEvent('tabsChange', { index: restProps.activeIndex }) } this.setData(restProps) }) }, getTransform(index, animating = !this.data.skipAnimation) { const pt = this.data.full ? 2 : 1 const i = this.data.full ? index : index - 1 const bodyStyle = styleToCssString({ transition: animating ? 'transform .3s' : 'none', transform: `translate(${-50 * pt * Math.max(0, i)}%)`, }) return bodyStyle }, /** * 点击菜单时的回调函数 */ onTabsChange(e) { const activeIndex = parseInt(e.detail.key) const bodyStyle = this.getTransform(activeIndex) if ( this.data.bodyStyle !== bodyStyle || this.data.activeIndex !== activeIndex ) { this.setData({ bodyStyle, activeIndex, }) this.triggerEvent('tabsChange', { index: activeIndex }) } }, /** * 点击选项时的回调函数 */ onItemSelect(e) { const { optionIndex } = e.currentTarget.dataset const { index } = e.detail const { showOptions } = this.data const item = showOptions[optionIndex][index] // updated this.updated(item, optionIndex, !this.data.controlled, this.onChange) }, /** * 选择完成时的回调函数 */ onChange(currentOptions = {}, activeValue = []) { const values = this.getValue(activeValue) // 判断是否异步加载 if (currentOptions && currentOptions.isLeaf === false && !currentOptions.children) { this.triggerEvent('change', { ...values }) this.triggerEvent('load', { value: values.value, options: values.options }) return } // 正常加载 this.triggerEvent('change', { ...values }) }, getValue(activeValue = this.data.activeValue) { const optionIndex = Math.max(0, activeValue.length - 1) const activeOptions = this.getActiveOptions(activeValue) const currentOptions = activeOptions[optionIndex] const valueKeyName = this.getFieldName('value') const childrenKeyName = this.getFieldName('children') const hasChildren = currentOptions && currentOptions[childrenKeyName] && currentOptions[childrenKeyName].length > 0 const options = activeOptions.filter((n) => n[valueKeyName] !== WUX_CASCADER_VIEW) const value = options.map((n) => n[valueKeyName]) if (currentOptions && currentOptions.isLeaf === false && !currentOptions.children) { return { value, options, done: false, } } return { value, options, done: !hasChildren, } }, getFieldName(name) { const { defaultFieldNames, fieldNames } = this.data return typeof fieldNames !== 'undefined' ? fieldNames[name] : defaultFieldNames[name] }, updateStyle(height) { const scrollViewStyle = styleToCssString({ height, minHeight: height, }) if (this.data.scrollViewStyle !== scrollViewStyle) { this.setData({ scrollViewStyle, }) } }, }, attached() { const { defaultValue, value, controlled, height } = this.data const activeValue = controlled ? value : defaultValue const fieldNames = Object.assign({}, defaultFieldNames, this.data.defaultFieldNames) this.setData({ activeValue, fieldNames }) this.getCurrentOptions(activeValue) this.updateStyle(height) }, })