import baseComponent from '../helpers/baseComponent' import classNames from '../helpers/classNames' import arrayTreeFilter from '../helpers/arrayTreeFilter' const WUX_CASCADER = 'wux-cascader' const defaultFieldNames = { label: 'label', value: 'value', children: 'children', } baseComponent({ externalClasses: ['wux-scroll-view-class'], properties: { prefixCls: { type: String, value: 'wux-cascader', }, defaultValue: { type: Array, value: [], }, value: { type: Array, value: [], observer(newVal) { if (this.data.controlled) { this.setData({ activeValue: newVal }, () => this.getCurrentOptions(newVal)) } }, }, controlled: { type: Boolean, value: false, }, title: { type: String, value: '', }, options: { type: Array, value: [], observer: 'getCurrentOptions', }, chooseTitle: { type: String, value: '请选择', }, visible: { type: Boolean, value: false, }, defaultFieldNames: { type: Object, value: defaultFieldNames, }, }, data: { activeOptions: [], activeIndex: 0, bodyStyle: '', activeValue: [], showOptions: [], fieldNames: {}, }, computed: { classes() { const { prefixCls } = this.data const wrap = classNames(prefixCls) const hd = `${prefixCls}__hd` const title = `${prefixCls}__title` const menus = `${prefixCls}__menus` const menu = `${prefixCls}__menu` const bd = `${prefixCls}__bd` const inner = `${prefixCls}__inner` const scrollView = `${prefixCls}__scroll-view` const option = `${prefixCls}__option` const item = `${prefixCls}__item` const icon = `${prefixCls}__icon` const ft = `${prefixCls}__ft` return { wrap, hd, title, menus, menu, bd, inner, scrollView, option, item, icon, ft, } }, }, 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 { options, chooseTitle } = this.data const activeOptions = this.getActiveOptions(activeValue) if (hasChildren && activeOptions.length < options.length) { const value = this.getFieldName('value') const label = this.getFieldName('label') activeOptions.push({ [value]: WUX_CASCADER, [label]: chooseTitle }) } 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[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 params = { activeValue, activeOptions, activeIndex, showOptions, } // 判断 hasChildren 计算需要更新的数据 if (hasChildren || (activeValue.length === showOptions.length && (optionIndex = Math.max(0, optionIndex - 1)))) { params.bodyStyle = `transform: translate(${-50 * optionIndex}%)` params.showOptions = showOptions } // 判断是否需要 setData 更新数据 if (condition) { this.setData(params) } // 回调函数 if (typeof callback === 'function') { callback.call(this, currentOptions, activeOptions, !hasChildren) } }, /** * 更新级联数据 * @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, [label]: this.data.chooseTitle }) const showOptions = this.getShowOptions(activeValue) const activeIndex = activeOptions.length - 1 const params = { showOptions, activeOptions, activeIndex, bodyStyle: '', } this.setData(params) } }, /** * 点击菜单时的回调函数 */ onMenuClick(e) { const { menuIndex } = e.currentTarget.dataset const index = menuIndex > 1 ? menuIndex - 1 : 0 const bodyStyle = `transform: translate(${-50 * index}%)` this.setData({ bodyStyle, activeIndex: menuIndex, }) }, /** * 点击选项时的回调函数 */ onItemSelect(e) { const { item, optionIndex } = e.currentTarget.dataset // 判断是否禁用 if (!item || item.disabled) return // updated this.updated(item, optionIndex, !this.data.controlled, this.onChange) }, /** * 组件关闭时的回调函数 */ onPopupClose() { this.triggerEvent('close') }, /** * 选择完成时的回调函数 */ onChange(currentOptions = {}, activeOptions = [], done = false) { const options = activeOptions.filter((n) => n[this.getFieldName('value')] !== WUX_CASCADER) const value = options.map((n) => n[this.getFieldName('value')]) // 判断是否异步加载 if (currentOptions.isLeaf === false && !currentOptions.children) { this.emitEvent({ value, options, done: false }) this.triggerEvent('load', { value, options }) return } // 正常加载 this.emitEvent({ value, options, done }) }, emitEvent(params = {}) { this.triggerEvent('change', params) // 当选择完成时关闭组件 if (params.done) { this.onPopupClose() } }, getFieldName(name) { return this.data.fieldNames[name] }, }, attached() { const { defaultValue, value, controlled } = this.data const activeValue = controlled ? value : defaultValue const fieldNames = Object.assign({}, defaultFieldNames, this.data.defaultFieldNames) this.setData({ activeValue, fieldNames }, () => this.getCurrentOptions(activeValue)) }, })