25 changed files with 599 additions and 145 deletions
@ -1,12 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"wux-popup": "../dist/popup/index", |
|||
"wux-selectable": "../dist/selectable/index", |
|||
"wux-textarea": "../dist/textarea/index", |
|||
"wux-upload": "../dist/upload/index", |
|||
"wux-cell": "../dist/cell/index", |
|||
"wux-calendar": "../dist/calendar/index", |
|||
"wux-cascader": "../dist/cascader/index" |
|||
"wux-popup": "../dist/popup/index" |
|||
} |
|||
} |
@ -0,0 +1,320 @@ |
|||
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) |
|||
}, |
|||
}) |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
} |
|||
} |
Loading…
Reference in new issue