mh_jy_safe_web/src/baseComponents/TreeBaseComponent/VPage.js
2025-08-25 10:08:30 +08:00

577 lines
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 核心库
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)