2019-08-27 19:10:29 +08:00
|
|
|
import cloneDeep from 'lodash/cloneDeep'
|
|
|
|
|
|
|
|
function isLeaf (option) {
|
|
|
|
if (option.isLeaf === true) {
|
|
|
|
return true
|
|
|
|
} else if (option.isLeaf === false) {
|
|
|
|
return false
|
|
|
|
} else if (hasChildren(option)) {
|
|
|
|
/**
|
|
|
|
* I don't take length into consideration because it may cause some problem
|
|
|
|
* when lazy load data
|
|
|
|
*/
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
function processedOption (option, activeIds, trackId) {
|
|
|
|
return {
|
|
|
|
...option,
|
|
|
|
active: activeIds.has(option.id),
|
|
|
|
hasChildren: hasChildren(option),
|
|
|
|
checkboxChecked: checkboxChecked(option),
|
|
|
|
checkboxIndeterminate: checkboxIndeterminate(option),
|
|
|
|
isLeaf: isLeaf(option),
|
|
|
|
tracked: tracked(option, trackId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function tracked (option, trackId) {
|
|
|
|
return option.id === trackId
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkboxChecked (option) {
|
|
|
|
if (option.type === 'multiple') {
|
|
|
|
if (Array.isArray(option.children) && option.children.length) {
|
|
|
|
return option.leafCount === option.checkedLeafCount
|
|
|
|
} else {
|
|
|
|
return option.checked
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return option.checked
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function checkboxIndeterminate (option) {
|
|
|
|
if (option.type === 'multiple') {
|
|
|
|
return !option.checked && option.checkedLeafCount !== 0 && option.checkedLeafCount !== option.leafCount
|
|
|
|
} return false
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasChildren (option) {
|
|
|
|
return Array.isArray(option.children)
|
|
|
|
}
|
|
|
|
|
|
|
|
function loaded (option) {
|
|
|
|
if (!isLeaf(option) && !hasChildren(option)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
function markAvailableSiblingIds (options) {
|
|
|
|
const length = options.length
|
|
|
|
let lastAvailableOption = null
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
|
|
const option = options[i]
|
|
|
|
option.nextAvailableSiblingId = null
|
|
|
|
option.prevAvailableSiblingId = null
|
|
|
|
}
|
|
|
|
for (let i = 0; i <= length * 2; ++i) {
|
|
|
|
const option = options[i % length]
|
|
|
|
if (lastAvailableOption) {
|
|
|
|
option.prevAvailableSiblingId = lastAvailableOption.id
|
|
|
|
}
|
|
|
|
if (!option.disabled) {
|
|
|
|
lastAvailableOption = option
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lastAvailableOption = null
|
|
|
|
for (let i = length * 2; i >= 0; --i) {
|
|
|
|
const option = options[i % length]
|
|
|
|
if (lastAvailableOption) {
|
|
|
|
option.nextAvailableSiblingId = lastAvailableOption.id
|
|
|
|
}
|
|
|
|
if (!option.disabled) {
|
|
|
|
lastAvailableOption = option
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function markFirstAvailableChildId (options) {
|
|
|
|
for (const option of options) {
|
|
|
|
if (option.isLeaf) {
|
|
|
|
option.firstAvailableChildId = null
|
|
|
|
} else {
|
|
|
|
if (hasChildren(option)) {
|
|
|
|
const firstChildOption = option.children[0]
|
|
|
|
if (firstChildOption) {
|
|
|
|
if (firstChildOption.disabled) {
|
|
|
|
option.firstAvailableChildId = firstChildOption.nextAvailableSiblingId
|
|
|
|
} else {
|
|
|
|
option.firstAvailableChildId = firstChildOption.id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
option.firstAvailableChildId = null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function availableParentId (parent, depth) {
|
|
|
|
if (!parent || parent.disabled || depth === 1) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
return parent.id
|
|
|
|
}
|
|
|
|
|
|
|
|
function rootedOptions (options) {
|
|
|
|
return cloneDeep([{
|
|
|
|
children: options || []
|
|
|
|
}])
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {*} options
|
|
|
|
* @param {Map} patches
|
|
|
|
*/
|
|
|
|
function patchedOptions (options, patches) {
|
|
|
|
function traverse (options) {
|
|
|
|
if (!Array.isArray(options)) return
|
|
|
|
for (const option of options) {
|
|
|
|
if (!hasChildren(option)) {
|
|
|
|
if (patches.has(option)) {
|
|
|
|
option.children = patches.get(option)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
traverse(options)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
traverse(options)
|
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkedCascaderOptions (options, type) {
|
|
|
|
const linkedCascaderOptions = options
|
|
|
|
const path = []
|
|
|
|
let id = 0
|
|
|
|
function traverse (options, parent = null, depth = 0) {
|
|
|
|
if (!Array.isArray(options)) return
|
|
|
|
const length = options.length
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
|
|
const option = options[i]
|
|
|
|
if (depth > 0) path.push(option.label)
|
|
|
|
/**
|
|
|
|
* option.type determine option ui status
|
|
|
|
*/
|
|
|
|
option.type = type
|
|
|
|
/**
|
|
|
|
* options.availableParentId to support keyup left
|
|
|
|
*/
|
|
|
|
option.availableParentId = availableParentId(parent, depth)
|
|
|
|
/**
|
|
|
|
* option.depth to support find submenu
|
|
|
|
*/
|
|
|
|
option.depth = depth
|
|
|
|
/**
|
|
|
|
* options.id to suport track option
|
|
|
|
*/
|
|
|
|
option.id = id++
|
|
|
|
/**
|
|
|
|
* options.path to support ui status
|
|
|
|
*/
|
|
|
|
option.path = cloneDeep(path)
|
|
|
|
/**
|
|
|
|
* options.isLeaf to support ui status and lazy load
|
|
|
|
*/
|
|
|
|
option.isLeaf = isLeaf(option)
|
|
|
|
/**
|
|
|
|
* option.loaded to support lazy load
|
|
|
|
*/
|
|
|
|
option.loaded = loaded(option)
|
|
|
|
/**
|
|
|
|
* option.availableLeafCount to support ui status
|
|
|
|
* option.leafCount to support ui status
|
|
|
|
*/
|
|
|
|
if (!option.isLeaf) {
|
|
|
|
if (option.loaded) {
|
|
|
|
traverse(option.children, option, depth + 1)
|
|
|
|
option.leafCount = 0
|
|
|
|
option.availableLeafCount = 0
|
|
|
|
option.children.forEach(child => {
|
|
|
|
if (!child.disabled) {
|
|
|
|
option.availableLeafCount += child.availableLeafCount
|
|
|
|
}
|
|
|
|
option.leafCount += child.leafCount
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
option.availableLeafCount = NaN
|
|
|
|
option.leafCount = NaN
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (option.disabled) {
|
|
|
|
option.availableLeafCount = 0
|
|
|
|
} else {
|
|
|
|
option.availableLeafCount = 1
|
|
|
|
}
|
|
|
|
option.leafCount = 1
|
|
|
|
}
|
|
|
|
if (depth > 0) path.pop()
|
|
|
|
}
|
|
|
|
markAvailableSiblingIds(options)
|
|
|
|
markFirstAvailableChildId(options)
|
|
|
|
}
|
|
|
|
traverse(linkedCascaderOptions)
|
|
|
|
return linkedCascaderOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
function menuOptions (linkedCascaderOptions, value, type) {
|
|
|
|
const valueSet = new Set(value)
|
|
|
|
const checkedOptions = []
|
|
|
|
function traverse (options, depth = 0) {
|
|
|
|
if (!Array.isArray(options)) return
|
|
|
|
const length = options.length
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
|
|
const option = options[i]
|
|
|
|
option.checkedLeafCount = 0
|
|
|
|
option.hasCheckedLeaf = false
|
|
|
|
if (type === 'multiple') {
|
|
|
|
if (option.loaded) {
|
|
|
|
if (!option.isLeaf) {
|
|
|
|
traverse(option.children, depth + 1)
|
|
|
|
option.children.forEach(child => {
|
|
|
|
option.checkedLeafCount += child.checkedLeafCount
|
|
|
|
option.hasCheckedLeaf = !!(option.hasCheckedLeaf || child.hasCheckedLeaf)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
option.checked = valueSet.has(option.value)
|
|
|
|
if (option.checked) {
|
|
|
|
checkedOptions.push(option)
|
|
|
|
option.checkedLeafCount = 1
|
|
|
|
option.hasCheckedLeaf = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
option.checkedLeafCount = NaN
|
|
|
|
}
|
|
|
|
} else if (type === 'multiple-all-options') {
|
|
|
|
if (option.loaded && !option.isLeaf) {
|
|
|
|
traverse(option.children, option, depth + 1)
|
|
|
|
} else {
|
|
|
|
option.checkedLeafCount = NaN
|
|
|
|
}
|
|
|
|
option.checked = valueSet.has(option.value)
|
|
|
|
checkedOptions.push(option)
|
|
|
|
} else if (type === 'single' || type === 'single-all-options') {
|
|
|
|
if (hasChildren(option)) {
|
|
|
|
traverse(option.children, option, depth + 1)
|
|
|
|
}
|
|
|
|
option.checked = (option.value === value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
traverse(linkedCascaderOptions)
|
|
|
|
return linkedCascaderOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
function optionPath (options, optionId) {
|
|
|
|
const path = []
|
|
|
|
if (optionId === null) return path
|
|
|
|
let done = false
|
|
|
|
function traverseOptions (options) {
|
|
|
|
if (!Array.isArray(options) || !options.length) return
|
|
|
|
for (const option of options) {
|
|
|
|
if (done) return
|
|
|
|
path.push(option)
|
|
|
|
if (option.id === optionId) {
|
|
|
|
done = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (option.children) {
|
|
|
|
traverseOptions(option.children)
|
|
|
|
}
|
|
|
|
if (done) return
|
|
|
|
path.pop(option)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
traverseOptions(options)
|
|
|
|
return path
|
|
|
|
}
|
|
|
|
|
|
|
|
function menuModel (options, activeId, trackId) {
|
|
|
|
const activeOptionPath = optionPath(options, activeId)
|
|
|
|
const activeIds = new Set(activeOptionPath.map(option => option.id))
|
|
|
|
const firstSubmenu = options[0].children
|
|
|
|
const model = [firstSubmenu.map(option => {
|
|
|
|
return processedOption(option, activeIds, trackId)
|
|
|
|
})]
|
|
|
|
for (const option of activeOptionPath) {
|
|
|
|
/**
|
|
|
|
* pass root option
|
|
|
|
*/
|
|
|
|
if (option.depth === 0) continue
|
|
|
|
if (hasChildren(option)) {
|
|
|
|
model.push(option.children.map(option => {
|
|
|
|
return processedOption(option, activeIds, trackId)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return model
|
|
|
|
}
|
|
|
|
|
|
|
|
function firstOptionId (options) {
|
|
|
|
console.log(options)
|
|
|
|
return options[0].firstAvailableChildId
|
|
|
|
}
|
|
|
|
|
|
|
|
export {
|
|
|
|
firstOptionId,
|
|
|
|
rootedOptions,
|
|
|
|
patchedOptions,
|
|
|
|
linkedCascaderOptions,
|
|
|
|
menuOptions,
|
|
|
|
menuModel
|
|
|
|
}
|