577 lines
21 KiB
JavaScript
577 lines
21 KiB
JavaScript
// 核心库
|
||
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)
|