mh_jy_safe_web/src/baseComponents/TreeBaseComponent/VPage.js

577 lines
21 KiB
JavaScript
Raw Normal View History

2025-08-25 10:08:30 +08:00
// 核心库
import React, { Component } from 'react'
import { connect } from 'dva'
// 组件库
import { Tree, Icon, Tabs, Input, message } from 'antd'
import { Scrollbars } from 'react-custom-scrollbars'
import { PictureThumb } from '@woowalker/feui'
import ListPage from '../../components/Table/ListPage'
import Edit from '../../components/Edit/Edit'
import CombinationPage from '../../components/Combination/CombinationPage'
import FormPage from '../../components/FormPage'
// 工具库
import { cloneDeep, isEqual } from 'lodash'
import { getDataFieldValue, permissionUtils, getCustomParams, flatTreeData } from '../../utils/common'
// 样式
import classNames from 'classnames'
import styles from '../Component.css'
import customTabStyles from '../../components/Combination/combinationPage.css'
class VPage extends Component {
constructor (props) {
super(props)
this.state = {
activeKey: '',
// 是否多选
multiCheck: props.treeConfig.multiCheck,
multiCheckCount: props.treeConfig.multiCheckCount,
// 多选选中的节点
selectedKeys: this.getDefaultSelectedKeys(props),
// 拍平后的树数据
flatedTreeData: [],
searchTreeData: [],
// 是否收起左侧树
collapse: false
}
}
componentDidMount () {
const { treeData } = this.props
this.checkFlatTreeData(treeData)
}
UNSAFE_componentWillReceiveProps (nextProps) {
if (!isEqual(nextProps.treeData, this.props.treeData)) {
this.checkFlatTreeData(nextProps.treeData)
}
}
getThumbData = () => {
const { treePicFilter } = this.props
const thumbCodes = []
const thumbConfigs = []
if (Array.isArray(treePicFilter) && treePicFilter.length) {
treePicFilter.forEach(({ Nav_PicFilterDetail = [], Nav_Picture }) => {
thumbCodes.push(Nav_Picture.CODE)
thumbConfigs.push({
target: Nav_Picture.CODE,
rules: Nav_PicFilterDetail.map(npd => ({
field: npd.FIELD_TYPE === 1 ? `Node.${npd.NAME}` : npd.NAME,
operate: Number(npd.OPERATE),
value: typeof npd.VALUE === 'string' ? npd.VALUE.toLowerCase() : npd.VALUE
}))
})
})
}
return { thumbCodes, thumbConfigs }
}
getDefaultSelectedKeys = (props) => {
const { multiCheck, multiCheckCount } = props.treeConfig
const defaultSelectedKeys = []
if (props.treeData && Array.isArray(props.treeData)) {
props.treeData.forEach(({ node = { id: '' } }) => {
node.id && defaultSelectedKeys.push(node.id)
})
}
return multiCheck ? (multiCheckCount ? defaultSelectedKeys.slice(0, multiCheckCount) : defaultSelectedKeys) : [defaultSelectedKeys[0]]
}
getDefaultExpandedKeys = () => {
const { treeData, treeConfig } = this.props
const { expandLevel } = treeConfig
if (expandLevel) {
const keys = []
function checkTreeDataLevel (data) {
data.forEach(item => {
if (item.level < expandLevel) {
keys.push(item.node.id)
if (Array.isArray(item.children) && item.children.length) {
checkTreeDataLevel(item.children)
}
}
})
}
checkTreeDataLevel(treeData)
return keys
}
return []
}
checkShowBtn = (btn, nodeData) => {
let isShow = !btn.btn_condition
if (btn.btn_condition) {
// 菜单参数
const { currActivatedMenu } = this.props.app || {}
const menuFormParameter = currActivatedMenu?.MENU_FORM_PARAMS
// 按钮配置条件
const conditionsOr = btn.btn_condition.split('|')
const conditionsAnd = btn.btn_condition.split('&')
if (conditionsOr.length === 1 && conditionsAnd.length === 1) {
// 只配置了一个条件
const fields = btn.btn_condition.split(',')
const val = getDataFieldValue(nodeData, fields[0].toLowerCase())
// 条件直接命中菜单参数,则直接显示
if (btn.btn_condition === menuFormParameter) {
isShow = true
}
// 1 等于
else if (parseInt(fields[1], 10) === 1) {
if (String(val) === String(fields[2].toLowerCase())) {
isShow = true
}
}
// 2 不等于
else if (parseInt(fields[1], 10) === 2) {
if (String(val) !== String(fields[2].toLowerCase())) {
isShow = true
}
}
} else if (conditionsOr.length > 1) {
// 或条件
for (let conditionOr of conditionsOr) {
const fields = conditionOr.split(',')
const val = getDataFieldValue(nodeData, fields[0].toLowerCase())
// 条件直接命中菜单参数,则直接显示
if (conditionOr === menuFormParameter) {
isShow = true
break
}
// 1 等于
else if (parseInt(fields[1], 10) === 1) {
if (String(val) === String(fields[2].toLowerCase())) {
isShow = true
break
}
}
// 2 不等于
else if (parseInt(fields[1], 10) === 2) {
if (String(val) !== String(fields[2].toLowerCase())) {
isShow = true
break
}
}
}
} else if (conditionsAnd.length > 1) {
// 与条件
const showResults = []
for (let conditionAnd of conditionsAnd) {
const fields = conditionAnd.split(',')
const val = getDataFieldValue(nodeData, fields[0].toLowerCase())
// 条件直接命中菜单参数,则直接显示
if (conditionAnd === menuFormParameter) {
showResults.push(true)
}
// 1 等于
else if (parseInt(fields[1], 10) === 1) {
if (String(val) === String(fields[2].toLowerCase())) {
showResults.push(true)
}
}
// 2 不等于
else if (parseInt(fields[1], 10) === 2) {
if (String(val) !== String(fields[2].toLowerCase())) {
showResults.push(true)
}
}
}
isShow = showResults.length === conditionsAnd.length
}
}
return isShow
}
checkMultiShowBtn = (btn) => {
const { currActivatedMenu } = this.props.app || {}
const menuFormParameter = currActivatedMenu?.MENU_FORM_PARAMS
let isShow = !btn.btn_condition || !menuFormParameter
if (btn.btn_condition && menuFormParameter) {
const conditionsOr = btn.btn_condition.split('|')
const conditionsAnd = btn.btn_condition.split('&')
if (conditionsOr.length === 1 && conditionsAnd.length === 1) {
// 只配置了一个条件
if (btn.btn_condition === menuFormParameter) {
isShow = true
}
} else if (conditionsOr.length > 1) {
// 或条件
for (let conditionOr of conditionsOr) {
if (conditionOr === menuFormParameter) {
isShow = true
break
}
}
} else if (conditionsAnd.length > 1) {
// 与条件
const showResults = []
for (let conditionAnd of conditionsAnd) {
if (conditionAnd === menuFormParameter) {
showResults.push(true)
}
}
isShow = showResults.length === conditionsAnd.length
}
}
return isShow
}
checkRenderBtn = (nodeData, treeConfig) => {
const { btns = [] } = treeConfig
const btnsTmp = []
btns.forEach(btn => {
if (!btn.isRule || permissionUtils(this.props.login).checkBtn(treeConfig.formId, btn.pId || btn.id)) {
// 如果配置了树多选,那么直接显示所配置的所有页面
if (treeConfig.multiCheck ? this.checkMultiShowBtn(btn) : this.checkShowBtn(btn, nodeData)) {
btn.btnType === 3 && btn.isSameLevel && (btn.btnType = -1) // 新增同级
btnsTmp.push(btn)
}
}
})
return btnsTmp
}
checkFlatTreeData = (treeData) => {
// 拍平树数据
const flatedTreeData = []
flatTreeData(treeData, flatedTreeData)
this.setState({
flatedTreeData: cloneDeep(flatedTreeData)
}, () => {
const { flatedTreeData, selectedKeys } = this.state
const allKeys = flatedTreeData.map(({ node }) => node.id)
const validKeys = selectedKeys.filter(item => allKeys.indexOf(item) !== -1)
this.handleTreeNodesSelect(validKeys.length ? validKeys : this.getDefaultSelectedKeys(this.props))
})
}
checkFlatTreeNodes = (treeData, treeConfig, selectedKeys) => {
const selectedNodes = this.state.flatedTreeData.filter(item => selectedKeys.indexOf(item.node.id) !== -1)
treeData.forEach(item => {
const showBtns = this.checkRenderBtn(item, treeConfig)
item.btns = showBtns.map(btnConfig => {
// false 以按钮形式展示 true 以页面形式展示
let typePage = btnConfig.btnType === 5 || btnConfig.btnType === 12 || btnConfig.btnType === 14 || btnConfig.btnType === 0
// component 的赋值逻辑请参考 TreeBaseComponent -> Hindex -> getRenderBtn (取的是 modal 里面的表单)
const { getRenderBtn, onSave } = this.props
let component = getRenderBtn({ record: item.node, btnConfig })
// 表单编辑
if (btnConfig.btnType === 5) {
const params = {
id: item.node.id,
parentId: item.node.id,
formCode: treeConfig.formCode,
data: {
record: item.node
},
onSave: (params) => {
onSave instanceof Function && onSave(params)
}
}
component = <Edit {...params} />
}
// 列表查看
else if (btnConfig.btnType === 12) {
const params = {
formCode: btnConfig.formCode,
formParam: this.props.formParam,
data: {
// 当前选中的节点
selectedNodes,
// 传递给页面选中的节点参数
TreeSelected: selectedKeys,
TreeSelectId: btnConfig.customParams,
// 页面操作取消树节点选中
setTreeNodeUnCheck: this.handleTreeNodesUnCheck,
rules: [
{
field: 'TreeSelected',
operator: 1,
value: selectedKeys,
isCustom: true
}
]
},
isQuery: true
}
if (btnConfig.customParams) {
params.data.rules.push({
field: btnConfig.customParams,
operator: 1,
value: item.node.id
})
}
component = <ListPage {...params} />
}
// 组合表单
else if (btnConfig.btnType === 14) {
const params = {
formCode: btnConfig.formCode,
formParam: this.props.formParam,
data: {
// 当前选中的节点
selectedNodes,
// 传递给页面选中的节点参数
TreeSelected: selectedKeys,
// 页面操作取消树节点选中
setTreeNodeUnCheck: this.handleTreeNodesUnCheck,
rules: [
{
field: btnConfig.customParams,
operator: 1,
value: item.node.id,
isCustom: true
}
]
}
}
component = <CombinationPage {...params} />
}
// 自定义弹窗
else if (btnConfig.btnType === 0) {
const record = item.node
const custParams = getCustomParams(btnConfig.customParams)
const params = {
id: record.id,
parentId: record.id,
formCode: btnConfig.formCode,
formParam: this.props.formParam,
data: {
...record,
...custParams,
customParams: btnConfig.customParams,
// 当前选中的节点
selectedNodes,
// 传递给页面选中的节点参数
TreeSelected: selectedKeys,
// 页面操作取消树节点选中
setTreeNodeUnCheck: this.handleTreeNodesUnCheck
}
}
component = <FormPage {...params} />
}
return {
typePage,
component,
btnConfig
}
})
})
}
renderTreeNodes = (treeData, thumbData) => {
return treeData.map(item => {
const { children, node } = item
const { name, is_exist_alarm, child_exist_alarm, appliance_id, nav_appliance } = node
const alert = `${name}${is_exist_alarm ? '(节点异常)' : child_exist_alarm ? '(存在子节点异常)' : (appliance_id != null && nav_appliance?.is_report == false) ? '(器具未采集数据)' : ''}`
const color = is_exist_alarm ? 'red' : child_exist_alarm ? 'orange' : (appliance_id != null && nav_appliance?.is_report == false) ? 'rgba(170, 170, 170)' : 'inhert'
const title = (
<span title={alert}>
<span style={{ color }}>{name}</span>
{
is_exist_alarm || child_exist_alarm || (appliance_id != null && nav_appliance?.is_report == false)
? (
<Icon
type={is_exist_alarm || child_exist_alarm ? 'alert' : 'disconnect'}
style={{ color: is_exist_alarm ? 'red' : child_exist_alarm ? 'orange' : 'rgba(170, 170, 170)', marginLeft: 2 }}
/>
)
: ''
}
</span>
)
const style = { width: 24, height: 24, position: 'relative', top: '-1px' }
if (children) {
return (
<Tree.TreeNode
title={title}
key={node.id}
icon={
<PictureThumb
thumbCodes={thumbData.thumbCodes}
thumbConfigs={thumbData.thumbConfigs}
nodeData={item}
defaultThumb={require('../../assets/file.png')}
style={style}
/>
}
>
{this.renderTreeNodes(children, thumbData)}
</Tree.TreeNode>
)
}
return (
<Tree.TreeNode
title={title}
key={node.id}
icon={
<PictureThumb
thumbCodes={thumbData.thumbCodes}
thumbConfigs={thumbData.thumbConfigs}
defaultThumb={require('../../assets/file.png')}
nodeData={item}
style={style}
/>
}
/>
)
})
}
handleTreeNodesSelect = (selectedKeys) => {
const { treeConfig } = this.props
const { flatedTreeData, activeKey } = this.state
// 获取选中节点所配置的按钮页面,并直接附在 flatedTreeData 数据之上
this.checkFlatTreeNodes(flatedTreeData, treeConfig, selectedKeys)
// 查找被激活 tab 的 key 值
const findNode = flatedTreeData.find(item => item.node.id === selectedKeys[0])
const pageBtns = findNode && findNode.btns ? findNode.btns.filter(btn => btn.typePage) : []
const currActived = pageBtns.find(item => item.btnConfig.id === activeKey)
this.setState({
selectedKeys,
activeKey: !currActived ? (pageBtns.length > 1 ? pageBtns[0].btnConfig.id : '') : activeKey
})
}
handleTreeNodesCheck = ({ checked: selectedKeys }) => {
const { multiCheckCount } = this.state
if (multiCheckCount && selectedKeys.length > multiCheckCount) {
message.error(`最多可选${multiCheckCount}个节点`)
return
}
this.handleTreeNodesSelect(selectedKeys)
}
handleTreeNodesUnCheck = (applianceId) => {
const { flatedTreeData, selectedKeys } = this.state
const find = flatedTreeData.find(item => item.node?.appliance_id === applianceId)
if (find) {
const copyKeys = cloneDeep(selectedKeys)
const findIndex = copyKeys.findIndex(item => item === find.node?.id)
findIndex !== -1 && copyKeys.splice(findIndex, 1)
this.handleTreeNodesCheck({ checked: copyKeys })
}
}
renderTabBar = (pageBtns) => {
const { activeKey } = this.state
return (
<div className={customTabStyles.tabBar__wrap}>
{
pageBtns.map(item => {
return (
<div
key={`${item.btnConfig.id}--customTabBar`}
onClick={() => this.setState({ activeKey: item.btnConfig.id })}
className={classNames(customTabStyles.tabBar__tab, { [customTabStyles.activated]: item.btnConfig.id === activeKey })}>
{item.btnConfig.label}
</div>
)
})
}
</div>
)
}
handleSearch = (evt) => {
const { value } = evt.target
const searchTreeData = this.state.flatedTreeData.filter(({ node = {} }) => (node.name || '').indexOf(value) !== -1)
this.setState({ searchTreeData: value ? searchTreeData : [] })
}
render () {
const { treeData, treeConfig } = this.props
const thumbData = this.getThumbData()
const defaultExpandedKeys = this.getDefaultExpandedKeys()
const expandProps = defaultExpandedKeys.length ? { defaultExpandedKeys } : { defaultExpandAll: true }
const { activeKey, multiCheck, selectedKeys, flatedTreeData, searchTreeData, collapse } = this.state
// 多选时,所有节点都会展示全部配置的页面,单选时,就一个 selectedKeys[0],所以直接取 selectedKeys[0] 来获取配置的页面
const targetNodes = flatedTreeData.find(item => item.node.id === selectedKeys[0]) || { node: {} }
const toolBtns = targetNodes.btns ? targetNodes.btns.filter(btn => !btn.typePage) : []
const pageBtns = targetNodes.btns ? targetNodes.btns.filter(btn => btn.typePage) : []
const selectedTitle = multiCheck && selectedKeys.length > 1 ? `${selectedKeys.length}` : targetNodes.node.name
const selectedEvtKey = multiCheck && selectedKeys.length > 1 ? selectedKeys.join(';') : targetNodes.node.id
return (
<div className={styles.treeV}>
<div className={classNames(styles.treeVLeft, { [styles.treeVCollapse]: collapse })}>
<Icon
type={collapse ? 'menu-unfold' : 'menu-fold'}
title={collapse ? '展开' : '收起'}
onClick={() => this.setState({ collapse: !collapse })}
className={styles.treeVFold}
/>
<div onClick={() => collapse && this.setState({ collapse: false })} className={styles.treeVWrap}>
<span className={styles.treeVTitle}>{treeConfig.labelName}</span>
<div className={styles.treeVDivider}></div>
<div className={styles.treeVSelect}>
<div title={selectedTitle || '无'} className={styles.treeVSelectTitle}>当前选中{selectedTitle || '无'}</div>
<div className={styles.treeVSelectBtns}>
{
!multiCheck || selectedKeys.length === 1
? toolBtns.map(tb => tb.component)
: null
}
</div>
</div>
<Input.Search
allowClear
onChange={this.handleSearch}
placeholder='关键字搜索'
className={styles.treeVSearch}
/>
<div className={styles.treeVTree}>
<Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={200}>
<Tree
showIcon
checkStrictly
checkable={multiCheck}
selectable={!multiCheck}
checkedKeys={selectedKeys}
selectedKeys={selectedKeys}
{...expandProps}
switcherIcon={<Icon type='caret-down' style={{ fontSize: 18 }} />}
onSelect={this.handleTreeNodesSelect}
onCheck={this.handleTreeNodesCheck}
>
{this.renderTreeNodes(searchTreeData.length ? searchTreeData : treeData, thumbData)}
</Tree>
</Scrollbars>
</div>
</div>
</div>
<div key={selectedEvtKey} className={classNames(styles.treeVRight, { [styles.treeVCollapse]: collapse })}>
{
pageBtns.length
? (
pageBtns.length > 1
? (
<Tabs
activeKey={activeKey}
renderTabBar={() => this.renderTabBar(pageBtns)}
animated={false}
className='combination-page__mainTab'
>
{
pageBtns.map(pb => (
<Tabs.TabPane key={pb.btnConfig.id} tab={pb.btnConfig.label}>{pb.component}</Tabs.TabPane>
))
}
</Tabs>
)
: pageBtns[0].component
)
: null
}
</div>
</div>
)
}
}
export default connect(({ login, app }) => ({ login, app }))(VPage)