import React from "react"
import { Utils } from "../../../utils"
import { Checkbox, Radio } from "antd"
import { StateManage } from "../../../service/state"
import { PropertyService } from "../../../service/property"
import { uniq, uniqBy, cloneDeep } from "lodash"
import { observer } from "mobx-react"
import { ACTIONS } from "./config"
import { PopModal } from "../../common/pop-modal"
import { Tree2 } from "./index"
export class Service {
controlKey: string = ""
searchSelectControlKey: string = ""
state: any
originData = []
originDataMap = {}
httpConfig
mode
checkedKeys = []
checkedKeysMap = {}
setCheckedKeysMap = {}
callbackConfig
iconCallback
allowSearch: boolean = true
setTreeData
treeRef
disabled
allowDragType: string[];
constructor(props) {
this.controlKey = props.controlKey ? (props.controlKey + "-Tree2") : Utils.uuid()
this.searchSelectControlKey = this.controlKey + "-searchSelectControlKey"
const state = PropertyService.getObservableObj({
selectedKeys: [],
selectedKeysMap: {},
expandedKeys: [],
expandedKeysMap: {},
checkedKeys: [],
checkedKeysMap: {},
loading: false
})
StateManage.addState(this.controlKey, state)
this.originData = props.originData || []
this.httpConfig = props.httpConfig
this.mode = props.mode || {}
this.callbackConfig = props.callbackConfig
this.iconCallback = props.iconCallback
this.allowSearch = props.allowSearch
this.disabled = props.disabled
this.allowDragType = props.allowDragType || []
}
/** 数据格式化 */
formatOriginData = ({ originData }) => {
const dataSource: any = []
const originDataMap: any = {}
const checkedKeys: any = []
const expandedKeys: any = []
originData.forEach(data => {
const { id } = data
originDataMap[id] = data
data["key"] = data["id"]
dataSource.push(data)
if (data["checked"]) {
checkedKeys.push(id)
}
if (data["open"]) {
expandedKeys.push(id)
}
})
originData.forEach((data: any) => {
const { uiPid } = data
const parents: any = []
let parent: any = originDataMap[uiPid]
while (parent) {
parents.push(parent)
parent = originDataMap[parent.uiPid]
}
if (parents.length) {
data["parents"] = parents
}
})
return {
originData: dataSource,
originDataMap,
checkedKeys,
expandedKeys
}
}
/** 更新checkedKeys 或者 expandedKeys */
updateCheckedKeysOrExpandedKeys = (type: "checkedKeys" | "expandedKeys", params: Array<{ id: string, bool: boolean }>) => {
const mode = this.mode
if (type === "checkedKeys" && mode?.isRadio === true) {
const data = params[0] || {}
StateManage.set(this.controlKey, {
checkedKeys: data["bool"] ? [data.id] : [],
checkedKeysMap: data["bool"] ? { [data.id]: data.id } : {}
})
return
}
const targetKeys = StateManage.get(this.controlKey, type)
const deleteIdMap = {}
const addKeys: any = []
const addIdMap = {}
params.forEach(item => {
const { id, bool } = item
if (bool) {
addKeys.push(id)
addIdMap[id] = id
} else {
deleteIdMap[id] = id
}
})
let nextTargetKeys: any = [...addKeys]
targetKeys.forEach(key => {
// if (!deleteIdMap.hasOwnProperty(key) && !addIdMap.hasOwnProperty(key) && this.originDataMap.hasOwnProperty[key]) {
if (!deleteIdMap.hasOwnProperty(key) && !addIdMap.hasOwnProperty(key)) {
nextTargetKeys.push(key)
}
})
nextTargetKeys = uniq(nextTargetKeys)
const targetKeysMap = {}
nextTargetKeys.forEach(key => {
targetKeysMap[key] = key
})
const key = type === "expandedKeys" ? "expandedKeysMap" : "checkedKeysMap"
StateManage.set(this.controlKey, { [type]: nextTargetKeys, [key]: targetKeysMap })
}
/** 更新checkedKeys */
updateCheckedKeys = (params: Array<{ id: string, checked: boolean }>) => {
this.updateCheckedKeysOrExpandedKeys("checkedKeys", params.map(item => ({ id: item.id, bool: item.checked })))
}
/** updateCheckedKeys2, 兼容旧版本 */
updateCheckedKeys2 = (checkedKeys: string[] = [], operation: "replace" | "add" | "delete") => {
if (operation === "replace") {
const checkedKeysMap = {}
checkedKeys.forEach(key => {
checkedKeysMap[key] = key
})
StateManage.set(this.controlKey, { checkedKeys, checkedKeysMap })
} else if (operation === "add") {
this.updateCheckedKeys(checkedKeys.map(key => ({ id: key, checked: true })))
} else if (operation === "delete") {
this.updateCheckedKeys(checkedKeys.map(key => ({ id: key, checked: false })))
}
}
/** 更新expandedKeys */
updateExpandedKeys = (params: Array<{ id: string, expanded: boolean }>) => {
this.updateCheckedKeysOrExpandedKeys("expandedKeys", params.map(item => ({ id: item.id, bool: item.expanded })))
}
/** 初始化树 */
initTree = async (callback?: any) => {
let originData = this.originData || []
if (originData.length) {
this?.setOriginData(originData)
} else if (this.httpConfig?.init) {
StateManage.set(this.controlKey, { loading: true })
originData = await this.httpConfig.init()
this?.setOriginData(originData)
}
StateManage.set(this.controlKey, { loading: false })
if (callback) {
const callbackData = this.getCallbackData()
callback(callbackData)
}
return originData
}
/** title文字 */
NodeTitle = ({ nodeData }) => {
const { title, id } = nodeData
const disabled = this.disabled
return {
if (disabled) return
const { checkedKeysMap } = StateManage.get(this.controlKey)
const checked = checkedKeysMap[id] ? false : true
this.updateCheckedKeys([{ id, checked }])
this.onSelect([], { node: { id }, selected: checked })
}}>
{title}
}
/** 多选节点 */
CheckboxNode = observer(({ nodeData }) => {
const { id, prefixIcon, suffixIcon } = nodeData
const { checkedKeysMap } = StateManage.get(this.controlKey)
const disabled = this.disabled
const NodeTitle = this.NodeTitle
const checked = checkedKeysMap.hasOwnProperty(id) ? true : false
return
{
const checked = checkedKeysMap.hasOwnProperty(id) ? false : true
const children = this.findChildren({ nodeData, children: [] })
/** 联动子节点 */
const checkedKeysArr: any = []
children.forEach(id => {
checkedKeysArr.push({ id, checked })
})
/** 联动父节点 */
const parents = this.findParents({ nodeData })
parents.forEach(id => {
checkedKeysArr.push({ id, checked })
})
this.updateCheckedKeys(checkedKeysArr)
StateManage.set(this.controlKey, {
selectedKeys: checked ? [id] : [],
selectedKeysMap: checked ? { id } : {}
})
this.triggerCallback(ACTIONS.onChange)
}}
/>
{prefixIcon ? prefixIcon(nodeData) : null}
{suffixIcon ? suffixIcon(nodeData) : null}
})
/** 单选节点 */
RadioNode = ({ nodeData }) => {
const { id, prefixIcon, suffixIcon } = nodeData
const { checkedKeysMap } = StateManage.get(this.controlKey)
const checked = checkedKeysMap.hasOwnProperty(id) ? true : false
const NodeTitle = this.NodeTitle
const disabled = this.disabled
return
{
const { checkedKeysMap } = StateManage.get(this.controlKey)
const checked = checkedKeysMap.hasOwnProperty(id) ? false : true
this.updateCheckedKeys([{ id, checked }])
StateManage.set(this.controlKey, {
selectedKeys: checked ? [id] : [],
selectedKeysMap: checked ? { id } : {}
})
this.triggerCallback(ACTIONS.onChange)
}}
/>
{prefixIcon ? prefixIcon(nodeData) : null}
{suffixIcon ? suffixIcon(nodeData) : null}
}
/** 渲染单个节点 */
titleRender = (nodeData) => {
const { title, nocheck, prefixIcon, suffixIcon } = nodeData
const NodeTitle = this.NodeTitle
if (nocheck === true) {
return
{prefixIcon ? prefixIcon(nodeData) : null}{suffixIcon ? suffixIcon(nodeData) : null}
}
const { isRadio } = this.mode
if (isRadio === false) {
const CheckboxNode = this.CheckboxNode
return
}
if (isRadio === true) {
const RadioNode = this.RadioNode
return
}
return
{prefixIcon ? prefixIcon(nodeData) : null}{title}{suffixIcon ? suffixIcon(nodeData) : null}
}
/** 找到所有子孙集节点 */
findChildren = ({ nodeData, children = [] as any }) => {
if (nodeData.nocheck !== true) {
children.push(nodeData.id)
}
if (nodeData.children && nodeData.children.length) {
nodeData.children.forEach((child) => {
if (child.nocheck !== true) {
children.push(child.id)
}
if (child.children) {
this.findChildren({ nodeData: child, children })
}
})
}
return children
}
/** 找到所有祖先集节点 */
findParents = ({ nodeData }) => {
const parents = nodeData["parents"] || []
return parents.filter(item => item["nocheck"] !== true).map(item => item["id"] || item["key"])
}
/** 节点展开事件 */
onExpand = (expandedKeys, { node, expanded }) => {
const { id, children = [] } = node
if (!children?.length) {
let { mode, httpConfig = {}, originData, originDataMap = {} } = this
let httpExpand = httpConfig.expand || httpConfig.onExpand
if (mode.isAsync === true && httpExpand) {
httpExpand(node).then(dataSource => {
dataSource.forEach((data) => {
if (!originDataMap[data.id]) {
(originData as any).push(data)
}
})
this.setOriginData(originData)
this.updateExpandedKeys([{ id, expanded }])
})
}
} else {
this.updateExpandedKeys([{ id, expanded }])
}
}
/** 获取组件的返回值 */
getCallbackData = () => {
const { checkedKeys, expandedKeys, selectedKeys } = StateManage.get(this.controlKey)
const originDataMap = this.originDataMap
const checkedData: any = []
const selectedData: any = []
checkedKeys.forEach(key => {
checkedData.push(originDataMap[key])
})
selectedKeys.forEach(key => {
selectedData.push(originDataMap[key])
})
return {
checkedKeys,
checkedData,
expandedKeys,
selectedKeys,
selectedData
}
}
/** onSelect事件, 点击节点文字 */
onSelect = (selectedKeys, { node, selected }) => {
const { id } = node
StateManage.set(this.controlKey, {
selectedKeys: selected ? [id] : [],
selectedKeysMap: selected ? { id } : {}
})
this.triggerCallback(ACTIONS.onSelect)
}
/** 触发事件回调函数 */
triggerCallback = (action) => {
const callbackConfig = this.callbackConfig
if (callbackConfig?.onState) {
const callbackData = this.getCallbackData()
callbackConfig?.onState(callbackData, action)
}
}
/** 关键字搜索 */
onSearch = (value) => {
if (!value) {
StateManage.set(this.searchSelectControlKey, { dataSource: [], open: false })
return
}
const { httpConfig } = this
const searHttp = httpConfig?.search || httpConfig?.onSearch
/** 接口搜索 */
if (searHttp) {
StateManage.set(this.searchSelectControlKey, { loading: true })
searHttp(value).then(dataSource => {
StateManage.set(this.searchSelectControlKey, { loading: false, dataSource, open: dataSource?.length ? true : false })
})
return
}
/** 本地搜索 */
const filterNodes: any = []
this.originData.forEach((data: any) => {
if (filterNodes.length > 100) return
if (data["title"].indexOf(value) >= 0) {
let label = data["title"]
if (data.parents && data.parents.length) {
data.parents.forEach(parent => {
label = parent["title"] + "_" + label
})
}
filterNodes.push({ ...data, key: data["id"], label })
}
})
StateManage.set(this.searchSelectControlKey, { dataSource: filterNodes, open: true })
}
/** 定位节点事件 */
onSelectOption = (data) => {
const { key, dataref } = data
const { httpConfig, originDataMap, originData } = this
const selectHttp = httpConfig?.select || httpConfig?.onSelect
if (selectHttp) {
StateManage.set(this.searchSelectControlKey, { open: false })
selectHttp(data).then(dataSource => {
dataSource.forEach((data) => {
if (!originDataMap[data.id]) {
(originData as any).push(data)
}
})
this.setOriginData(originData)
this.triggerOnSelectOption && this.triggerOnSelectOption(key)
})
} else {
const { parents } = dataref || {}
let label = data["title"]
if (parents && parents.length) {
parents.forEach(parent => {
label = parent["title"] + "_" + label
})
}
StateManage.set(this.searchSelectControlKey, { open: false })
this.triggerOnSelectOption && this.triggerOnSelectOption(key)
}
}
/** 获取搜索框的state */
getSearchSelectState = () => {
const state = PropertyService.getObservableObj({
dataSource: [],
open: false,
loading: false,
onSearch: this.onSearch.bind(this),
onSelect: this.onSelectOption.bind(this)
})
StateManage.addState(this.searchSelectControlKey, state)
return state
}
/** 以树的方式展示搜索结果 */
showDataSourceByTree = async (value) => {
const { httpConfig } = this
let dataSource: any = []
if (httpConfig?.onPressEnter || httpConfig?.pressEnter) {
const pressEnterHttp = httpConfig?.onPressEnter || httpConfig?.pressEnter
dataSource = await pressEnterHttp(value)
const originData: any = [...this.originData]
dataSource.forEach(data => {
if (!this.originDataMap[data.id]) {
originData.push(data)
}
})
this.setOriginData(originData)
} else {
this.originData.forEach((data: any) => {
if (data.title.indexOf(value) >= 0) {
dataSource.push(data)
if (data.parents && data.parents.length) {
data.parents.forEach(parent => {
dataSource.push(parent)
})
}
}
})
}
dataSource = uniqBy(dataSource, "id")
if (dataSource.length <= 0) return
dataSource = cloneDeep(dataSource)
const { checkedKeysMap } = StateManage.get(this.controlKey)
dataSource.map(data => {
const { id } = data
if (checkedKeysMap[id]) {
data["checked"] = true
} else {
data["checked"] = false
}
// 默认展开
data["open"] = true
})
const [state, TreeTpl] = Tree2({
originData: dataSource,
mode: { ...this.mode },
height: document.body.clientHeight * 0.7
}, false)
PopModal({
title: "匹配结果树",
height: document.body.clientHeight * 0.7 + "px",
width: "500px",
content: ,
onOk: ({ }, modalState) => {
const { getCallbackData } = StateManage.get(state)
const { checkedKeys = [] } = getCallbackData()
const noCheckedKeys: any = []
dataSource.forEach(data => {
const { id } = data
if (!checkedKeys.find(key => key == id)) {
noCheckedKeys.push({ id, checked: false })
}
})
this.updateCheckedKeys(checkedKeys.map(key => ({ id: key, checked: true })))
this.updateCheckedKeys(noCheckedKeys)
this.triggerOnSelectOption(checkedKeys[0])
StateManage.set(modalState, { visible: false })
}
})
}
/** 判断是否没有数据 */
isEmptyData = () => {
const { originData } = this
if (!originData) return true
if (originData.length == 0) return true
return false
}
/** 定位指定节点 */
triggerOnSelectOption = (positionKey) => {
if (!positionKey) return
if (!this.originDataMap[positionKey]) return
const nodeData = this.originDataMap[positionKey]
const parents = nodeData["parents"] || []
this.updateExpandedKeys(parents.map(parent => ({ id: parent["id"], expanded: true })))
this.updateCheckedKeys([{ id: positionKey, checked: true }])
const timer = setTimeout(() => {
clearTimeout(timer)
this.treeRef.current.scrollTo({ key: positionKey })
StateManage.set(this.controlKey, { selectedKeys: [positionKey] })
this.triggerCallback(ACTIONS.onSelectOption)
}, 100)
}
/** setOriginData,替换整棵树的数据源, originData 需要符合树的数据源格式 */
setOriginData = (originData) => {
const { originData: dataSource, checkedKeys, originDataMap, expandedKeys } = this.formatOriginData({ originData })
this.originData = dataSource
this.originDataMap = originDataMap
this.updateCheckedKeys(checkedKeys.map(key => ({ id: key, checked: true })))
this.updateExpandedKeys(expandedKeys.map(key => ({ id: key, expanded: true })))
const treeData = Utils.getTreeData(dataSource)
this.setTreeData(treeData)
}
/** deleteOriginData 根据id删除节点 */
deleteOriginData = (ids: string[]) => {
let originData = this.originData
originData = originData.filter((data: any) => ids.includes(data.id) == false)
this.setOriginData(originData)
}
/** appendOriginData, originData 需要符合树的数据源格式 */
appendOriginData = (originData: object[] = []) => {
this.originData = this.originData.concat(originData as any)
this.setOriginData(originData)
}
/** 展开/收起全部 */
expandAll = (bool: boolean) => {
if (!bool) {
StateManage.set(this.controlKey, { expandedKeys: [] })
return
}
const originData = this.originData
const expandedKeys = originData.map(data => data["id"])
StateManage.set(this.controlKey, { expandedKeys })
this.triggerCallback(ACTIONS.onExpand)
}
/** 更新节点的文字 */
updateTitle = ({ id, title }) => {
if (!this.originDataMap[id]) {
console.info("未找到指定节点")
return
}
let originData = this.originData
originData.forEach((data: any) => {
if (data["id"] === id) {
data["title"] = title
}
})
this.setOriginData(originData)
}
/**判断节点是否可拖拽**/
checkDragable = (node) => {
return this.allowDragType.includes(node.locationtype)
}
/** 内部拖拽放置事件 **/
onDrop = ({ event, node, dragNode, dragNodesKeys }) => {
console.debug('拖拽节点!', node, this.originData.findIndex((v: any) => v.id === node.id))
console.debug('放置节点!', dragNode, this.originData.findIndex((v: any) => v.id === dragNode.id))
console.debug(this.originData, this.originDataMap)
}
}