tt-mp
Version:
一套组件化、可复用、易扩展的头条小程序 UI 组件库
478 lines (443 loc) • 12.6 kB
JavaScript
import baseComponent from '../helpers/baseComponent'
import classNames from '../helpers/classNames'
import { $wuxBackdrop } from '../index'
function getLabels(children = []) {
return children
.filter((v) => v.checked)
.map((v) => v.label)
.join(',')
}
function getDisplayValues(options = [], extra = true) {
return options.reduce((acc, option) => {
switch (option.type) {
case 'radio':
case 'checkbox':
acc.push(
getLabels(option.children || []) || (extra ? option.label : '')
)
break
case 'filter':
acc.push(getDisplayValues(option.children || [], false))
break
default:
acc.push(option.label)
}
return acc
}, [])
}
function getSortValue(sort) {
if (typeof sort === 'number' && [1, -1].includes(sort)) {
return sort
}
return 1
}
function getValue(children = [], single = false) {
const allValues = children.filter((v) => v.checked).map((v) => v.value)
if (!single) return allValues
return allValues[0] || ''
}
function getValues(options = []) {
return options.reduce((acc, option) => {
switch (option.type) {
case 'radio':
acc.push(getValue(option.children, true))
break
case 'checkbox':
acc.push(getValue(option.children, false))
break
case 'text':
acc.push(option.checked ? option.value : '')
break
case 'sort':
acc.push(option.checked ? getSortValue(option.sort) : '')
break
case 'filter':
acc.push(getValues(option.children))
break
}
return acc
}, [])
}
function getChangedValues(options = [], values = [], prefix = 'options') {
return options.reduce((acc, option, index) => {
if (option.type === 'radio') {
return {
...acc,
[`${prefix}[${index}].children`]: option.children.map((v) => ({
...v,
checked: v.value === values[index],
})),
}
}
if (option.type === 'checkbox') {
return {
...acc,
[`${prefix}[${index}].children`]: option.children.map((v) => ({
...v,
checked: Array.isArray(values[index])
? values[index].includes(v.value)
: false,
})),
}
}
if (option.type === 'filter') {
return {
...acc,
...getChangedValues(
option.children,
values[index] || [],
`options[${index}].children`
),
}
}
return acc
}, {})
}
function getShowOptions(options = [], values = []) {
return options.reduce((acc, option, index) => {
if (['radio', 'checkbox'].includes(option.type)) {
return [...acc, { ...option, selected: getLabels(option.children || []) }]
}
if (option.type === 'filter') {
return [
...acc,
{
...option,
children: getShowOptions(option.children || [], values[index]),
},
]
}
return acc
}, [])
}
baseComponent({
properties: {
prefixCls: {
type: String,
value: 'wux-filterbar',
},
items: {
type: Array,
value: [],
},
cancelText: {
type: String,
value: '重置',
},
confirmText: {
type: String,
value: '确定',
},
},
data: {
displayValues: [],
values: [],
},
observers: {
['items.**'](newVal) {
this.setData({ options: newVal, values: getValues(newVal) })
},
['options.**'](newVal) {
this.updatedDisplayValues(newVal)
},
},
computed: {
classes: [
'prefixCls',
function (prefixCls) {
const wrap = classNames(prefixCls)
const bd = `${prefixCls}__bd`
const item = `${prefixCls}__item`
const text = `${prefixCls}__text`
const icon = `${prefixCls}__icon`
const pop = `${prefixCls}__pop`
const scrollView = `${prefixCls}__scroll-view`
const panel = `${prefixCls}__panel`
const panelHd = `${prefixCls}__panel-hd`
const panelTitle = `${prefixCls}__panel-title`
const panelSelected = `${prefixCls}__panel-selected`
const panelBd = `${prefixCls}__panel-bd`
const groups = `${prefixCls}__groups`
const group = `${prefixCls}__group`
const radio = `${prefixCls}__radio`
const btn = `${prefixCls}__btn`
const check = `${prefixCls}__check`
const btns = `${prefixCls}__btns`
const select = `${prefixCls}__select`
return {
wrap,
bd,
item,
text,
icon,
pop,
scrollView,
panel,
panelHd,
panelTitle,
panelSelected,
panelBd,
groups,
group,
radio,
btn,
check,
btns,
select,
}
},
],
},
methods: {
updatedValues(values, callback) {
if (this.data.values !== values) {
this.setData({ values }, callback)
}
},
updatedDisplayValues(options = this.data.options) {
const displayValues = getDisplayValues(options)
if (this.data.displayValues !== displayValues) {
this.setData({ displayValues })
}
},
/**
* 关闭侧边栏筛选框
* @param {Object} e 事件对象
* @param {Function} callback 回调函数
*/
onClose(e) {
const { index } = e.currentTarget.dataset
this.onSelectClose(index)
},
onPopupSelectChange(e) {
const values = [...this.data.values]
const options =
this.showOptions || JSON.parse(JSON.stringify(this.data.options))
const { value } = e.detail
const { index, parentIndex } = e.currentTarget.dataset
values[parentIndex] = values[parentIndex] || []
values[parentIndex][index] = value
if (
options[parentIndex].children[index] &&
options[parentIndex].children[index].children
) {
options[parentIndex].children[index].children = options[
parentIndex
].children[index].children.map((v) => ({
...v,
checked: value.includes(v.value),
}))
this.updatedDisplayValues(options)
this.showOptions = options
}
this.updatedValues(values)
},
/**
* 下拉框内多项选择触发 change 事件
* @param {Object} e 事件对象
*/
onSelectChange(e) {
const values = [...this.data.values]
const { index, type } = e.currentTarget.dataset
const { selectedValue: value } = e.detail
values[index] = value
this.updatedValues(values)
// trigger onChange
if (type === 'radio') {
this.onSelectConfirm(e)
}
},
onSelectClose(index, callback) {
const params = {
values: getValues(this.data.options),
[`options[${index}].visible`]: false,
}
this.setData(params, () => {
if (typeof callback === 'function') {
callback.call(this)
}
this.showOptions = null
this.$wuxBackdrop.release()
})
},
onSelectReset(e) {
const values = [...this.data.values]
const { index } = e.currentTarget.dataset
values[index] = []
this.updatedValues(values)
const showOptions =
this.showOptions || JSON.parse(JSON.stringify(this.data.options))
if (showOptions && showOptions.length > 0) {
showOptions.forEach((option, index) => {
if (option.type === 'filter') {
option.children = option.children.reduce((acc, child) => {
return [
...acc,
{
...child,
children: child.children.map((v) => ({
...v,
checked: false,
})),
},
]
}, [])
}
})
this.updatedDisplayValues(showOptions)
this.showOptions = null
}
},
onSelectConfirm(e) {
const { options, values } = this.data
const { index } = e.currentTarget.dataset
const params = getChangedValues(options, values)
this.setData(params, () => this.onSelectClose(index, this.onChange))
},
/**
* 点击事件
* @param {Object} e 事件对象
*/
onClick(e) {
const { index } = e.currentTarget.dataset
const { options } = this.data
const values = getValues(options)
// calc real values
if (!options[index].visible) {
this.setData({ values })
}
// open
this.onOpenSelect(options, index)
},
/**
* 打开下拉框
* @param {Array} data 菜单数据
* @param {Number} index 当前索引
*/
onOpenSelect(data = [], index = 0) {
const current = data[index]
const options = data.map((n, i) => {
const params = Object.assign({}, n, {
checked: index === i ? !n.checked : false,
})
// 判断已选择的元素是否同组
if (n.checked) {
const has = this.getDifference(n.groups, current.groups)
params.checked = !!has.length
// 判断非同组的元素清空选择内容
if (index !== i && !has.length) {
if (typeof params.children === 'object') {
if (['radio', 'checkbox'].includes(n.type)) {
params.children = params.children.map((n) =>
Object.assign({}, n, {
checked: false,
})
)
}
if (['filter'].includes(n.type)) {
params.children = params.children.map((n) => {
return Object.assign({}, n, {
children: n.children.map((m) =>
Object.assign({}, m, {
checked: false,
})
),
selected: '',
})
})
}
}
if (['sort'].includes(n.type)) {
params.sort = undefined
}
}
}
// 展开或隐藏下拉框
if (['radio', 'checkbox', 'filter'].includes(n.type)) {
params.visible = index === i ? !n.visible : false
if (n.type === 'filter') {
this.$wuxBackdrop[
index === i ? (!n.visible ? 'retain' : 'release') : 'release'
]()
}
}
// 当前点击排序做出处理
if (index === i && ['sort'].includes(n.type)) {
params.sort = typeof params.sort === 'number' ? -params.sort : 1
}
return params
})
this.setData({ options, index }, () => {
if (!['radio', 'checkbox', 'filter'].includes(current.type)) {
this.onChange()
}
})
},
/**
* 关闭下拉框
*/
onCloseSelect() {
const params = this.data.options.reduce((acc, option, index) => {
if (option.checked && option.visible) {
return { ...acc, [`options[${index}].visible`]: false }
}
return acc
}, {})
this.setData(params)
},
/**
* 获取两个数组相同的元素
* @param {Array} data 数组
* @param {Array} values 数组
*/
getDifference(data = [], values = []) {
return data.filter((v) => values.includes(v))
},
/**
* 元素发生变化时的事件
*/
onChange() {
const { options } = this.data
const checkedValues = getValues(options)
const items = getShowOptions(options, checkedValues)
this.updatedValues(checkedValues, () => {
this.onCloseSelect()
this.triggerEvent('change', {
checkedItems: items.filter((n) => n.checked),
items,
checkedValues,
})
})
},
/**
* scroll-view 滚动时触发的事件
* @param {Object} e 事件对象
*/
onScroll(e) {
this.triggerEvent('scroll', e)
},
/**
* 打开 select 或 filter 时触发的回调函数
* @param {Object} e 事件对象
*/
onEnter(e) {
this.triggerEvent('open', e)
},
/**
* 关闭 select 或 filter 时触发的回调函数
* @param {Object} e 事件对象
*/
onExit(e) {
this.triggerEvent('close', e)
},
},
created() {
$wuxBackdrop('#wux-backdrop', this).then((res) => {
this.$wuxBackdrop = res
})
},
attached() {
const { items: newVal } = this.data
this.setData({ options: newVal, values: getValues(newVal) })
},
})