数据建模页面

main
wangyunfei 2 weeks ago
parent 875aae94ea
commit e2d1c98d41

10
dummy

@ -0,0 +1,10 @@
{
"cells": [],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

@ -41,8 +41,8 @@
.blockTitle {
font-size: 16px;
font-weight: 600;
color: #1f2f3d;
font-weight: 500;
color: #333333;
}
}
@ -160,7 +160,7 @@
.cardTitle {
font-size: 15px;
font-weight: 600;
font-weight: 500;
color: rgba(0, 0, 0, 1);
margin-bottom: 12px;
}

@ -4,7 +4,7 @@ import styles from './ChangeRecord.less';
const ChangeRecord = () => {
return (
<div className={styles.container}>
<div>待开发</div>
<div>变更记录待开发</div>
</div>
);
};

@ -1,10 +1,327 @@
import React from 'react';
import React, { useMemo, useState } from 'react';
import { Select, Tree, Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { ReactComponent as Icon1 } from '@/assets/business_basic/icon1.svg';
import { ReactComponent as Icon2 } from '@/assets/business_basic/icon2.svg';
import StandardTable from '@/components/StandardTable';
import styles from './HierarchyStructure.less';
const buildingOptions = [
{ label: '工厂建筑', value: 'plant' },
{ label: '仓储设施', value: 'warehouse' },
];
const factoryOptions = [
{ label: '全部工厂', value: 'all' },
{ label: '上海油库', value: 'shanghai' },
{ label: '南京油库', value: 'nanjing' },
];
const rawTreeData = [
{
title: '上海油库',
key: 'shanghai',
tag: { label: '工厂', type: 'depot' },
children: [
{
title: '汽油罐区',
key: 'gasoline-area',
tag: { label: '罐区', type: 'area' },
children: [
{
title: '汽油调合罐组',
key: 'blend-group',
tag: { label: '罐组', type: 'group' },
children: [
{
title: 'T-101 汽油储罐',
key: 't101',
tag: { label: '储罐', type: 'tank' },
children: [
{
title: 'LT-101 液位变送器',
key: 'lt101',
tag: { label: '测点', type: 'sensor' },
},
{
title: 'TT-101 温度变送器',
key: 'tt101',
tag: { label: '测点', type: 'sensor' },
},
{
title: 'PT-101 压力变送器',
key: 'pt101',
tag: { label: '测点', type: 'sensor' },
},
{
title: 'P-101A 输送泵',
key: 'p101a',
tag: { label: '设备', type: 'device' },
},
{
title: 'P-101B 输送泵',
key: 'p101b',
tag: { label: '设备', type: 'device' },
},
{
title: 'SP-101 密度计',
key: 'sp101',
tag: { label: '测点', type: 'sensor' },
},
],
},
{
title: 'T-102 汽油储罐',
key: 't102',
tag: { label: '储罐', type: 'tank' },
},
{
title: 'T-103 汽油储罐',
key: 't103',
tag: { label: '储罐', type: 'tank' },
},
],
},
],
},
{
title: '柴油罐区',
key: 'diesel-area',
tag: { label: '罐区', type: 'area' },
},
{
title: '化危品库',
key: 'hazard',
tag: { label: '库区', type: 'depot' },
},
],
},
];
const TreeNodeTitle = ({ text, tag }) => (
<span className={styles.treeNodeTitle}>
<span className={styles.nodeText}>{text}</span>
{tag ? (
<span className={`${styles.nodeTag} ${styles[`tag-${tag.type}`] || ''}`}>
{tag.label}
</span>
) : null}
</span>
);
const formatTreeNodes = data =>
data.map(item => ({
key: item.key,
title: <TreeNodeTitle text={item.title} tag={item.tag} />,
children: item.children ? formatTreeNodes(item.children) : undefined,
}));
const tableColumns = [
{
title: '层级',
dataIndex: 'level',
key: 'level',
width: 110,
align: 'center',
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 120,
align: 'center',
},
{
title: '代码',
dataIndex: 'code',
key: 'code',
align: 'center',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
align: 'center',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'center',
},
{
title: '最后更新',
dataIndex: 'updatedAt',
key: 'updatedAt',
align: 'center',
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
width: 140,
align: 'center',
render: () => (
<>
<a style={{ marginRight: 12, color: '#006665' }}>查看详情</a>
<a style={{ color: '#2D9E9D' }}>编辑</a>
</>
),
},
];
const tableDataSource = [
{
id: 1,
level: '工厂',
name: '华东油库',
code: 'CN-EAST-OIL',
type: '原油存储',
status: '已激活',
updatedAt: '2025-10-25 22:30:16',
},
{
id: 2,
level: '罐区',
name: '汽油罐区',
code: 'GASOLINE-AREA',
type: '成品油',
status: '已激活',
updatedAt: '2025-10-25 10:28:14',
},
{
id: 3,
level: '罐区',
name: '柴油罐区',
code: 'DIESEL-AREA',
type: '成品油',
status: '已激活',
updatedAt: '2025-10-23 20:58:24',
},
{
id: 4,
level: '罐组',
name: '92汽油调合罐组',
code: 'G92-GROUP',
type: '调合罐组',
status: '维护中',
updatedAt: '2025-10-23 04:59:13',
},
{
id: 5,
level: '储罐',
name: 'T-101',
code: 'TANK-101',
type: '浮顶罐',
status: '维护中',
updatedAt: '2025-10-22 03:03:13',
},
{
id: 6,
level: '设备',
name: 'LT-101',
code: 'LEVEL-101',
type: '液位计',
status: '已激活',
updatedAt: '2025-10-22 03:03:13',
},
];
const HierarchyStructure = () => {
const [buildingType, setBuildingType] = useState('plant');
const [factory, setFactory] = useState('all');
const [selectedKeys, setSelectedKeys] = useState(['t101']);
const [checkedKeys, setCheckedKeys] = useState(['t101']);
const treeData = useMemo(() => formatTreeNodes(rawTreeData), []);
const tableData = useMemo(
() => ({
list: tableDataSource,
pagination: {
currentPage: 1,
pageSize: 10,
total: tableDataSource.length,
},
}),
[],
);
return (
<div className={styles.container}>
<div>待开发</div>
<section className={styles.leftPanel}>
<div className={styles.blockHeader}>
<span className={styles.titleIcon} />
<span className={styles.blockTitle}>工厂层级结构</span>
</div>
<div className={styles.filters}>
<Select
value={buildingType}
onChange={setBuildingType}
options={buildingOptions}
className={styles.filterSelect}
dropdownMatchSelectWidth={false}
size="small"
/>
<Select
value={factory}
onChange={setFactory}
options={factoryOptions}
className={styles.filterSelect}
dropdownMatchSelectWidth={false}
size="small"
/>
</div>
<div className={styles.treeWrapper}>
<Tree
showIcon={false}
showLine={{ showLeafIcon: false }}
defaultExpandAll
checkable
selectable
treeData={treeData}
checkedKeys={checkedKeys}
selectedKeys={selectedKeys}
onCheck={keys => setCheckedKeys(keys.checked || keys)}
onSelect={keys => setSelectedKeys(keys)}
className={styles.tree}
virtual={false}
switcherIcon={nodeProps =>
nodeProps.expanded ? (
<Icon2 width={14} height={14} />
) : (
<Icon1 width={14} height={14} />
)
}
/>
</div>
</section>
<section className={styles.rightPanel}>
<div className={styles.blockHeader}>
<span className={styles.titleIcon} />
<span className={styles.blockTitle}>层级结构</span>
<div className={styles.headerAction}>
<Button
className={styles.addLevelButton}
icon={<PlusOutlined />}
size="small"
>
添加层级
</Button>
</div>
</div>
<div className={styles.tableWrapper}>
<StandardTable
columns={tableColumns}
data={tableData}
rowKey="id"
isshowAlert={false}
pagination={{ pageSize: 10 }}
size="small"
/>
</div>
</section>
</div>
);
};

@ -1,4 +1,245 @@
.container {
padding: 20px;
display: flex;
gap: 16px;
padding: 16px;
background-color: #fff;
min-height: 520px;
.leftPanel {
flex: 0 0 320px;
background: #e9f3ef;
border-radius: 2px;
padding: 20px;
display: flex;
flex-direction: column;
box-shadow: 0 4px 20px rgba(21, 32, 66, 0.04);
}
.rightPanel {
flex: 1;
background: #fff;
border-radius: 12px;
padding: 15px 20px;
box-shadow: 0 4px 20px rgba(21, 32, 66, 0.04);
display: flex;
flex-direction: column;
}
.blockHeader {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
.titleIcon {
width: 3px;
height: 16px;
border-radius: 2px;
background: rgba(0, 102, 101, 1);
display: inline-block;
}
.blockTitle {
font-size: 16px;
font-weight: 500;
color: #333333;
}
.headerAction {
margin-left: auto;
:global(&.ant-btn-sm) {
height: 28px !important;
line-height: 28px;
}
}
}
.filters {
display: flex;
gap: 12px;
margin-bottom: 16px;
.filterSelect {
width: 100%;
:global(.ant-select-selector) {
border: 1px solid rgba(45, 158, 157, 1);
border-radius: 2px;
}
:global(.ant-select-focused .ant-select-selector) {
border-color: rgba(45, 158, 157, 1) !important;
box-shadow: 0 0 0 2px rgba(45, 158, 157, 0.2) !important;
}
:global(.ant-select:hover .ant-select-selector) {
border-color: rgba(45, 158, 157, 1) !important;
}
}
}
.treeWrapper {
flex: 1;
overflow: hidden;
.tree {
background-color: transparent;
:global(.ant-tree-switcher) {
display: flex;
align-items: center !important;
}
:global(.ant-tree-indent-unit:before) {
border-inline-end: 1px solid rgba(0, 102, 101, 1) !important;
}
:global(.ant-tree-switcher-leaf-line:after) {
border-bottom: 1px solid rgba(0, 102, 101, 1) !important;
}
:global(.ant-tree-switcher-leaf-line:before) {
border-inline-end: 1px solid rgba(0, 102, 101, 1) !important;
}
:global(.ant-tree-checkbox-inner) {
border: 1px solid rgba(0, 102, 101, 1);
border-radius: 4px;
&:hover {
background-color: rgba(0, 102, 101, 1);
border: 1px solid rgba(0, 102, 101, 1);
border-radius: 4px;
}
&:after {
background-color: transparent !important;
}
}
:global(.ant-tree-checkbox-checked .ant-tree-checkbox-inner),
:global(.ant-tree-checkbox-indeterminate .ant-tree-checkbox-inner) {
background-color: rgba(0, 102, 101, 1) !important;
border-color: rgba(0, 102, 101, 1) !important;
border-radius: 4px;
}
:global(.ant-tree) {
background-color: transparent;
}
:global(.ant-tree-treenode) {
padding: 2px 0;
}
:global(.ant-tree-node-content-wrapper) {
padding: 2px 4px;
border-radius: 4px;
transition: background 0.2s ease;
&:hover {
background: rgba(196, 225, 214, 0.3);
}
}
:global(.ant-tree-node-selected) {
background: rgba(190, 215, 210, 1) !important;
}
}
}
.tableWrapper {
flex: 1;
:global {
.ant-table-thead>tr>th {
text-align: center;
background: #f0f7f7;
font-weight: 450;
color: #333333;
}
.ant-table-tbody>tr>td {
text-align: center;
font-weight: 400;
color: #4e5856;
}
}
}
}
.treeNodeTitle {
display: inline-flex;
align-items: center;
width: 100%;
gap: 12px;
position: relative;
padding-right: 40px;
margin-right: 80px;
.nodeText {
flex: 1;
color: rgba(0, 0, 0, 1);
font-weight: 400;
font-size: 12px;
}
.nodeTag {
text-align: center;
padding: 0px 6px;
border-radius: 2px;
font-size: 11px;
font-weight: 400;
position: absolute;
right: 0;
margin-right: 0;
&.tag-depot {
background: rgba(196, 225, 214, 1);
color: rgba(0, 0, 0, 1);
}
&.tag-area {
background: rgba(196, 225, 214, 1);
color: rgba(0, 0, 0, 1);
}
&.tag-sensor {
background: rgba(196, 225, 214, 1);
color: rgba(0, 0, 0, 1);
}
&.tag-tank {
background: rgba(196, 225, 214, 1);
color: rgba(0, 0, 0, 1);
}
&.tag-group {
background: rgba(196, 225, 214, 1);
color: rgba(0, 0, 0, 1);
}
&.tag-device {
background: rgba(196, 225, 214, 1);
color: rgba(0, 0, 0, 1);
}
}
}
.addLevelButton {
padding: 8px 12px;
border-radius: 2px;
background: #2e6a5e;
color: #ffffff;
display: inline-flex;
align-items: center;
&:hover,
&:focus {
background: #2e6a5e;
color: #ffffff;
}
}
Loading…
Cancel
Save