123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301 |
- import React from 'react'
- import memoizeOne from 'memoize-one'
- import { Input, Select, Row, Col, Tree, Icon } from 'antd'
- import { AntTreeNode, AntTreeNodeSelectedEvent, AntTreeNodeExpandedEvent } from 'antd/lib/tree/Tree'
- const { Search } = Input
- const { Option } = Select
- const { TreeNode } = Tree
- import { ISource, IColumn, ISchema } from 'containers/Source/types'
- import { IView } from '../types'
- import { SQL_DATE_TYPES, SQL_NUMBER_TYPES, SQL_STRING_TYPES } from 'app/globalConstants'
- import { filterSelectOption } from 'app/utils/util'
- import utilStyles from 'assets/less/util.less'
- import Styles from 'containers/View/View.less'
- import { shallowEqual } from 'react-redux'
- interface ISourceTableProps {
- view: IView
- sources: ISource[]
- schema: ISchema
- onViewChange: (propName: keyof(IView), value: string | number) => void
- onSourceSelect: (sourceId: number) => void
- onDatabaseSelect: (sourceId: number, databaseName: string) => void
- onTableSelect: (sourceId: number, databaseName: string, tableName: string) => void
- }
- interface ISourceTableStates {
- filterKeyword: string
- expandedNodeKeys: string[]
- autoExpandTable: boolean
- }
- export class SourceTable extends React.Component<ISourceTableProps, ISourceTableStates> {
- public state: ISourceTableStates = {
- filterKeyword: '',
- expandedNodeKeys: [],
- autoExpandTable: true
- }
- private inputChange = (propName: keyof IView) => (e: React.ChangeEvent<HTMLInputElement>) => {
- this.props.onViewChange(propName, e.target.value)
- }
- private selectSource = (sourceId: number) => {
- const { onViewChange, onSourceSelect } = this.props
- this.setState({
- expandedNodeKeys: [],
- autoExpandTable: true
- })
- onViewChange('sourceId', sourceId)
- onSourceSelect(sourceId)
- }
- private iconDatabase = <Icon key="iconDatabase" title="数据库" type="database" />
- private iconTable = <Icon key="iconTable" title="数据表" type="table" />
- private iconDate = <Icon key="iconDate" title="日期" type="calendar" />
- private iconKey = <Icon key="iconKey" title="主键" type="key" />
- private iconText = <Icon key="iconText" title="文本" type="font-size" />
- private iconValue = <Icon key="iconValue" title="数值" type="calculator" />
- private getColumnIcons (col: IColumn, primaryKeys: string[]) {
- const { type: sqlType, name } = col
- if (primaryKeys.includes(name)) { return this.iconKey }
- if (SQL_STRING_TYPES.includes(sqlType)) { return this.iconText }
- if (SQL_NUMBER_TYPES.includes(sqlType)) { return this.iconValue }
- if (SQL_DATE_TYPES.includes(sqlType)) { return this.iconDate }
- }
- private highlightTitle (title: string, regex: RegExp) {
- if (!title || !regex) { return title }
- return (
- <span
- dangerouslySetInnerHTML={{
- __html: title.replace(regex, `<span class="${utilStyles.highlight}">$1</span>`)
- }}
- />
- )
- }
- private renderTableColumns = memoizeOne((
- sourceId: number, schema: ISchema, filterKeyword: string, onDatabaseSelect: ISourceTableProps['onDatabaseSelect']) => {
- const { mapDatabases, mapTables, mapColumns } = schema
- if (!sourceId) { return null }
- const databasesInfo = mapDatabases[sourceId]
- if (!databasesInfo) { return null }
- const filterReg = filterKeyword ? new RegExp(`(${filterKeyword})`, 'i') : null
- const treeNodes = databasesInfo.reduce((databaseNodes, dbName) => {
- const tablesInfo = mapTables[`${sourceId}_${dbName}`]
- if (!tablesInfo) {
- if (Object.values(databasesInfo).length === 1) {
- onDatabaseSelect(sourceId, dbName)
- return databaseNodes
- }
- databaseNodes.push(<TreeNode icon={this.iconDatabase} title={dbName} key={dbName} isLeaf={false} dataRef={['database', dbName]} />)
- return databaseNodes
- }
- let filterTables = tablesInfo.tables
- if (filterReg) {
- filterTables = filterTables.filter(({ name: tableName }) => {
- if (filterReg.test(tableName)) { return true }
- const columnsInfo = mapColumns[[sourceId, dbName, tableName].join('_')]
- if (!columnsInfo) { return false }
- const hasFilterColumns = columnsInfo.columns.some((col) => filterReg.test(col.name))
- return hasFilterColumns
- })
- }
- const tableNodes = filterTables.map(({ name: tableName }) => {
- const columnsInfo = mapColumns[[sourceId, dbName, tableName].join('_')]
- const columnNodes = !columnsInfo ? null : columnsInfo.columns.reduce((nodes, col) => {
- // if (filterReg && !filterReg.test(col.name)) { return nodes }
- const primaryKeysRemain = [...columnsInfo.primaryKeys]
- const icons = this.getColumnIcons(col, columnsInfo.primaryKeys)
- const columnTitle = this.highlightTitle(col.name, filterReg)
- const currentNode = (
- <TreeNode title={columnTitle} icon={icons} key={`${dbName}_${tableName}_${col.name}`} isLeaf={true} dataRef={['column']} />
- )
- if (primaryKeysRemain.includes(col.name)) {
- // make the primary key column be the top
- nodes.splice(columnsInfo.primaryKeys.length - primaryKeysRemain.length, 0, currentNode)
- primaryKeysRemain.splice(primaryKeysRemain.indexOf(col.name), 1)
- } else {
- nodes.push(currentNode)
- }
- return nodes
- }, [])
- return (<TreeNode icon={this.iconTable} title={tableName} key={`${dbName}_${tableName}`} isLeaf={false} dataRef={['table', dbName, tableName]}>{columnNodes}</TreeNode>)
- })
- const nodes = Object.values(databasesInfo).length === 1 ? tableNodes
- : (<TreeNode icon={this.iconDatabase} title={dbName} key={dbName} isLeaf={false} dataRef={['database']}>{tableNodes}</TreeNode>)
- databaseNodes.push(nodes)
- return databaseNodes
- }, [])
- return treeNodes
- }
- )
- private loadTreeData = (node: AntTreeNode) => new Promise<void>((resolve) => {
- const { dataRef } = node.props
- if (dataRef === 'column') {
- resolve()
- return
- }
- const { schema, view, onDatabaseSelect, onTableSelect } = this.props
- const { sourceId } = view
- const { mapTables, mapColumns } = schema
- const [nodeType, dbName, tableName] = dataRef
- switch (nodeType) {
- case 'database':
- if (!mapTables[`${sourceId}_${dbName}`]) {
- onDatabaseSelect(sourceId, dbName)
- }
- break
- case 'table':
- if (!mapColumns[`${sourceId}_${dbName}_${tableName}`]) {
- onTableSelect(sourceId, dbName, tableName)
- }
- break
- }
- resolve()
- })
- private treeNodeSelect = (_: string[], { node }: AntTreeNodeSelectedEvent) => {
- const { dataRef, eventKey: nodeKey } = node.props
- const [nodeType] = dataRef
- if (nodeType === 'column') { return }
- const { expandedNodeKeys } = this.state
- if (expandedNodeKeys.includes(nodeKey)) { return }
- this.setState({
- expandedNodeKeys: [...expandedNodeKeys, nodeKey],
- autoExpandTable: false
- })
- }
- private treeNodeExpand = (expandedNodeKeys: string[]) => {
- this.setState({
- expandedNodeKeys,
- autoExpandTable: false
- })
- }
- private filterKeywordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- const filterKeyword = e.target.value
- const { schema, view } = this.props
- const { mapTables, mapColumns } = schema
- const expandedNodeKeys = new Set<string>()
- if (filterKeyword) {
- const regex = new RegExp(`(${filterKeyword})`, 'gi')
- Object.values(mapTables).forEach((tablesInfo) => {
- if (!tablesInfo) { return }
- const { tables, dbName, sourceId } = tablesInfo
- if (sourceId !== view.sourceId) { return }
- const shouldExpand = regex.test(dbName) ||
- tables.some(({ name: tableName }) => regex.test(tableName))
- if (shouldExpand) {
- expandedNodeKeys.add(dbName)
- }
- })
- Object.values(mapColumns).forEach((columnsInfo) => {
- if (!columnsInfo) { return }
- const { columns, tableName, dbName, sourceId } = columnsInfo
- if (sourceId !== view.sourceId) { return }
- const shouldExpand = regex.test(tableName) ||
- columns.some(({ name: columnName }) => regex.test(columnName))
- if (shouldExpand) {
- expandedNodeKeys.add(`${dbName}_${tableName}`)
- expandedNodeKeys.add(`${dbName}`)
- }
- })
- }
- this.setState({
- filterKeyword,
- autoExpandTable: true,
- expandedNodeKeys: Array.from(expandedNodeKeys)
- })
- }
- // FIXED: sql 的改动会改变view,此组件依赖view,会进行多余的render,此处进行优化
- public shouldComponentUpdate(nextProps: ISourceTableProps, nextState: ISourceTableStates) {
- const {sources, schema, view: {name, description, sourceId}} = this.props
- if (
- !shallowEqual(nextState, this.state) ||
- nextProps.sources !== sources ||
- nextProps.schema !== schema ||
- nextProps.view.name !== name ||
- nextProps.view.description !== description ||
- nextProps.view.sourceId !== sourceId
- ) {
- return true
- }
- return false
- }
- public render () {
- const { view, sources, schema, onDatabaseSelect } = this.props
- const { filterKeyword, expandedNodeKeys } = this.state
- const { name: viewName, description: viewDesc, sourceId } = view
- return (
- <div className={Styles.sourceTable}>
- <Row gutter={16}>
- <Col span={24}>
- <Input placeholder="名称" value={viewName} onChange={this.inputChange('name')} />
- </Col>
- <Col span={24}>
- <Input placeholder="描述" value={viewDesc} onChange={this.inputChange('description')} />
- </Col>
- <Col span={24}>
- <Select
- showSearch
- dropdownMatchSelectWidth={false}
- placeholder="数据源"
- style={{width: '100%'}}
- value={sourceId}
- onChange={this.selectSource}
- filterOption={filterSelectOption}
- >
- {sources.map(({ id, name }) => (<Option key={id.toString()} value={id}>{name}</Option>))}
- </Select>
- </Col>
- <Col span={24}>
- <Search
- placeholder="搜索表/字段名称"
- value={filterKeyword}
- onChange={this.filterKeywordChange}
- />
- </Col>
- </Row>
- <div className={Styles.tree}>
- <Tree
- showIcon
- key={view.sourceId}
- loadData={this.loadTreeData}
- onSelect={this.treeNodeSelect}
- onExpand={this.treeNodeExpand}
- expandedKeys={expandedNodeKeys}
- >
- {this.renderTableColumns(sourceId, schema, filterKeyword, onDatabaseSelect)}
- </Tree>
- </div>
- </div>
- )
- }
- }
- export default SourceTable
|