mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-12 12:37:55 +00:00
[improvement][headless-fe] Added view management functionality. (#701)
* [improvement][semantic-fe] Add model alias setting & Add view permission restrictions to the model permission management tab. [improvement][semantic-fe] Add permission control to the action buttons for the main domain; apply high sensitivity filtering to the authorization of metrics/dimensions. [improvement][semantic-fe] Optimize the editing mode in the dimension/metric/datasource components to use the modelId stored in the database for data, instead of relying on the data from the state manager. * [improvement][semantic-fe] Add time granularity setting in the data source configuration. * [improvement][semantic-fe] Dictionary import for dimension values supported in Q&A visibility * [improvement][semantic-fe] Modification of data source creation prompt wording" * [improvement][semantic-fe] metric market experience optimization * [improvement][semantic-fe] enhance the analysis of metric trends * [improvement][semantic-fe] optimize the presentation of metric trend permissions * [improvement][semantic-fe] add metric trend download functionality * [improvement][semantic-fe] fix the dimension initialization issue in metric correlation * [improvement][semantic-fe] Fix the issue of database changes not taking effect when creating based on an SQL data source. * [improvement][semantic-fe] Optimizing pagination logic and some CSS styles * [improvement][semantic-fe] Fixing the API for the indicator list by changing "current" to "pageNum" * [improvement][semantic-fe] Fixing the default value setting for the indicator list * [improvement][semantic-fe] Adding batch operations for indicators/dimensions/models * [improvement][semantic-fe] Replacing the single status update API for indicators/dimensions with a batch update API * [improvement][semantic-fe] Redesigning the indicator homepage to incorporate trend charts and table functionality for indicators * [improvement][semantic-fe] Optimizing the logic for setting dimension values and editing data sources, and adding system settings functionality * [improvement][semantic-fe] Upgrading antd version to 5.x, extracting the batch operation button component, optimizing the interaction for system settings, and expanding the configuration generation types for list-to-select component. * [improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not. * [improvement][semantic-fe] Adding the ability to edit relationships between models in the canvas. * [improvement][semantic-fe] Updating the datePicker component to use dayjs instead. * [improvement][semantic-fe] Fixing the issue with passing the model ID for dimensions in the indicator market. * [improvement][semantic-fe] Fixing the abnormal state of the popup when creating a model. * [improvement][semantic-fe] Adding permission logic for bulk operations in the indicator market. * [improvement][semantic-fe] Adding the ability to download and transpose data. * [improvement][semantic-fe] Fixing the initialization issue with the date selection component in the indicator details page when switching time granularity. * [improvement][semantic-fe] Fixing the logic error in the dimension value setting. * [improvement][semantic-fe] Fixing the synchronization issue with the question and answer settings information. * [improvement][semantic-fe] Optimizing the canvas functionality for better performance and user experience. * [improvement][semantic-fe] Optimizing the update process for drawing model relationship edges in the canvas. * [improvement][semantic-fe] Changing the line type for canvas connections. * [improvement][semantic-fe] Replacing the initialization variable from "semantic" to "headless". * [improvement][semantic-fe] Fixing the missing migration issue for default drill-down dimension configuration in model editing. Additionally, optimizing the data retrieval method for initializing fields in the model. * [improvement][semantic-fe] Updating the logic for the fieldName. * [improvement][semantic-fe] Adjusting the position of the metrics tab. * [improvement][semantic-fe] Changing the 字段名称 to 英文名称. * [improvement][semantic-fe] Fix metric measurement deletion. * [improvement][semantic-fe] UI optimization for metric details page. * [improvement][semantic-fe] UI optimization for metric details page. * [improvement][semantic-fe] UI adjustment for metric details page. * [improvement][semantic-fe] The granularity field in the time type of model editing now supports setting it as empty. * [improvement][semantic-fe] Added field type and metric type to the metric creation options. * [improvement][semantic-fe] The organization structure selection feature has been added to the permission management. * [improvement][semantic-fe] Improved user experience for the metric list. * [improvement][semantic-fe] fix update the metric list. * [improvement][headless-fe] Added view management functionality.
This commit is contained in:
@@ -40,13 +40,7 @@ const ROUTES = [
|
|||||||
name: 'semanticModel',
|
name: 'semanticModel',
|
||||||
envEnableList: [ENV_KEY.SEMANTIC],
|
envEnableList: [ENV_KEY.SEMANTIC],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/database',
|
|
||||||
name: 'database',
|
|
||||||
hideInMenu: true,
|
|
||||||
component: './SemanticModel/components/Database/DatabaseTable',
|
|
||||||
envEnableList: [ENV_KEY.SEMANTIC],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/metric',
|
path: '/metric',
|
||||||
name: 'metric',
|
name: 'metric',
|
||||||
@@ -85,6 +79,13 @@ const ROUTES = [
|
|||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
component: './Login',
|
component: './Login',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/database',
|
||||||
|
name: 'database',
|
||||||
|
// hideInMenu: true,
|
||||||
|
component: './SemanticModel/components/Database/DatabaseTable',
|
||||||
|
envEnableList: [ENV_KEY.SEMANTIC],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/system',
|
path: '/system',
|
||||||
name: 'system',
|
name: 'system',
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { InputNumber } from 'antd';
|
||||||
|
|
||||||
|
const DisabledWheelNumberInput: React.FC<any> = ({ ...rest }) => {
|
||||||
|
const ref = useRef<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
ref.current.addEventListener('wheel', handleWheel);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleWheel = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
return <InputNumber ref={ref} {...rest} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DisabledWheelNumberInput;
|
||||||
@@ -298,7 +298,7 @@ const DataSourceFieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSq
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Alert
|
{/* <Alert
|
||||||
style={{ marginBottom: '10px' }}
|
style={{ marginBottom: '10px' }}
|
||||||
banner
|
banner
|
||||||
message={
|
message={
|
||||||
@@ -306,7 +306,7 @@ const DataSourceFieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSq
|
|||||||
为了保障同一个模型下维度/指标列表唯一,消除歧义,若本模型下的多个数据源存在相同的字段名并且都勾选了快速创建,系统默认这些相同字段的指标维度是同一个,同时列表中将只显示第一次创建的指标/维度。
|
为了保障同一个模型下维度/指标列表唯一,消除歧义,若本模型下的多个数据源存在相同的字段名并且都勾选了快速创建,系统默认这些相同字段的指标维度是同一个,同时列表中将只显示第一次创建的指标/维度。
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/> */}
|
||||||
<Table<FieldItem>
|
<Table<FieldItem>
|
||||||
dataSource={fields}
|
dataSource={fields}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { message } from 'antd';
|
import { message, Tabs, Button, Space } from 'antd';
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { getMetricData, getDimensionList, getDrillDownDimension } from '../service';
|
import { getMetricData, getDimensionList, getDrillDownDimension } from '../service';
|
||||||
import { connect, useParams } from 'umi';
|
import { connect, useParams, history } from 'umi';
|
||||||
import type { StateType } from '../model';
|
import type { StateType } from '../model';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
|
import { ArrowLeftOutlined } from '@ant-design/icons';
|
||||||
import MetricTrendSection from '@/pages/SemanticModel/Metric/components/MetricTrendSection';
|
import MetricTrendSection from '@/pages/SemanticModel/Metric/components/MetricTrendSection';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import DimensionAndMetricRelationModal from '../components/DimensionAndMetricRelationModal';
|
import DimensionAndMetricRelationModal from '../components/DimensionAndMetricRelationModal';
|
||||||
import MetricInfoSider from './MetricInfoSider';
|
import MetricInfoSider from './MetricInfoSider';
|
||||||
|
import type { TabsProps } from 'antd';
|
||||||
|
|
||||||
type Props = Record<string, any>;
|
type Props = Record<string, any>;
|
||||||
|
|
||||||
@@ -17,6 +19,9 @@ const MetricDetail: React.FC<Props> = () => {
|
|||||||
const [metricRelationModalOpenState, setMetricRelationModalOpenState] = useState<boolean>(false);
|
const [metricRelationModalOpenState, setMetricRelationModalOpenState] = useState<boolean>(false);
|
||||||
const [metircData, setMetircData] = useState<ISemantic.IMetricItem>();
|
const [metircData, setMetircData] = useState<ISemantic.IMetricItem>();
|
||||||
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||||
|
const [drillDownDimension, setDrillDownDimension] = useState<ISemantic.IDrillDownDimensionItem[]>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
const [relationDimensionOptions, setRelationDimensionOptions] = useState<
|
const [relationDimensionOptions, setRelationDimensionOptions] = useState<
|
||||||
{ value: string; label: string; modelId: number }[]
|
{ value: string; label: string; modelId: number }[]
|
||||||
>([]);
|
>([]);
|
||||||
@@ -38,6 +43,7 @@ const MetricDetail: React.FC<Props> = () => {
|
|||||||
const queryDrillDownDimension = async (metricId: number) => {
|
const queryDrillDownDimension = async (metricId: number) => {
|
||||||
const { code, data, msg } = await getDrillDownDimension(metricId);
|
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||||
if (code === 200 && Array.isArray(data)) {
|
if (code === 200 && Array.isArray(data)) {
|
||||||
|
setDrillDownDimension(data);
|
||||||
const ids = data.map((item) => item.dimensionId);
|
const ids = data.map((item) => item.dimensionId);
|
||||||
queryDimensionList(ids);
|
queryDimensionList(ids);
|
||||||
return data;
|
return data;
|
||||||
@@ -70,15 +76,57 @@ const MetricDetail: React.FC<Props> = () => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tabItems: TabsProps['items'] = [
|
||||||
|
{
|
||||||
|
key: 'metricTrend',
|
||||||
|
label: '图表',
|
||||||
|
children: (
|
||||||
|
<MetricTrendSection
|
||||||
|
metircData={metircData}
|
||||||
|
relationDimensionOptions={relationDimensionOptions}
|
||||||
|
dimensionList={dimensionList}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// key: 'metricCaliberInput',
|
||||||
|
// label: '基础信息',
|
||||||
|
// children: <></>,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// key: 'metricDataRemark',
|
||||||
|
// label: '备注',
|
||||||
|
// children: <></>,
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.metricDetailWrapper}>
|
<div className={styles.metricDetailWrapper}>
|
||||||
<div className={styles.metricDetail}>
|
<div className={styles.metricDetail}>
|
||||||
<div className={styles.tabContainer}>
|
<div className={styles.tabContainer}>
|
||||||
<MetricTrendSection
|
<Tabs
|
||||||
metircData={metircData}
|
defaultActiveKey="metricTrend"
|
||||||
relationDimensionOptions={relationDimensionOptions}
|
items={tabItems}
|
||||||
dimensionList={dimensionList}
|
tabBarExtraContent={{
|
||||||
|
right: (
|
||||||
|
<Button
|
||||||
|
size="middle"
|
||||||
|
type="link"
|
||||||
|
key="backListBtn"
|
||||||
|
onClick={() => {
|
||||||
|
history.push('/metric/market');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
<ArrowLeftOutlined />
|
||||||
|
返回列表页
|
||||||
|
</Space>
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
size="large"
|
||||||
|
className={styles.metricDetailTab}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.siderContainer}>
|
<div className={styles.siderContainer}>
|
||||||
@@ -91,20 +139,20 @@ const MetricDetail: React.FC<Props> = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<DimensionAndMetricRelationModal
|
||||||
|
metricItem={metircData}
|
||||||
|
relationsInitialValue={drillDownDimension}
|
||||||
|
open={metricRelationModalOpenState}
|
||||||
|
onCancel={() => {
|
||||||
|
setMetricRelationModalOpenState(false);
|
||||||
|
}}
|
||||||
|
onSubmit={(relations) => {
|
||||||
|
queryMetricData(metricId);
|
||||||
|
queryDrillDownDimension(metricId);
|
||||||
|
setMetricRelationModalOpenState(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DimensionAndMetricRelationModal
|
|
||||||
metricItem={metircData}
|
|
||||||
relationsInitialValue={metircData?.relateDimension?.drillDownDimensions}
|
|
||||||
open={metricRelationModalOpenState}
|
|
||||||
onCancel={() => {
|
|
||||||
setMetricRelationModalOpenState(false);
|
|
||||||
}}
|
|
||||||
onSubmit={(relations) => {
|
|
||||||
queryMetricData(metricId);
|
|
||||||
queryDrillDownDimension(metricId);
|
|
||||||
setMetricRelationModalOpenState(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import moment from 'moment';
|
|||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
||||||
import MetricStar from './components/MetricStar';
|
|
||||||
import { ColumnsConfig } from '../components/MetricTableColumnRender';
|
import { ColumnsConfig } from '../components/MetricTableColumnRender';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -161,13 +160,19 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ProColumns[] = [
|
const columns: ProColumns[] = [
|
||||||
// {
|
{
|
||||||
// dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
// title: 'ID',
|
title: 'ID',
|
||||||
// },
|
width: 80,
|
||||||
|
fixed: 'left',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: '指标',
|
title: '指标',
|
||||||
|
// width: '20%',
|
||||||
|
width: 280,
|
||||||
|
fixed: 'left',
|
||||||
render: ColumnsConfig.metricInfo.render,
|
render: ColumnsConfig.metricInfo.render,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -190,16 +195,32 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
return <> {record.modelName}</>;
|
return <> {record.modelName}</>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'sensitiveLevel',
|
||||||
|
title: '敏感度',
|
||||||
|
width: 150,
|
||||||
|
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||||
|
render: ColumnsConfig.sensitiveLevel.render,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
dataIndex: 'description',
|
||||||
|
title: '描述',
|
||||||
|
search: false,
|
||||||
|
width: 300,
|
||||||
|
render: ColumnsConfig.description.render,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
width: 120,
|
width: 180,
|
||||||
search: false,
|
search: false,
|
||||||
render: ColumnsConfig.state.render,
|
render: ColumnsConfig.state.render,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'description',
|
dataIndex: 'createdBy',
|
||||||
title: '描述',
|
title: '创建人',
|
||||||
|
// width: 150,
|
||||||
search: false,
|
search: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -214,6 +235,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'x',
|
dataIndex: 'x',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
|
width: 180,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
if (record.hasAdminRes) {
|
if (record.hasAdminRes) {
|
||||||
return (
|
return (
|
||||||
@@ -334,7 +356,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
metricList={dataSource}
|
metricList={dataSource}
|
||||||
disabledEdit={true}
|
disabledEdit={true}
|
||||||
onMetricChange={(metricItem: ISemantic.IMetricItem) => {
|
onMetricChange={(metricItem: ISemantic.IMetricItem) => {
|
||||||
history.push(`/metric/detail/${metricItem.modelId}/${metricItem.bizName}`);
|
history.push(`/metric/detail/${metricItem.id}`);
|
||||||
}}
|
}}
|
||||||
onDeleteBtnClick={(metricItem: ISemantic.IMetricItem) => {
|
onDeleteBtnClick={(metricItem: ISemantic.IMetricItem) => {
|
||||||
deleteMetricQuery(metricItem.id);
|
deleteMetricQuery(metricItem.id);
|
||||||
@@ -355,6 +377,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={pagination}
|
pagination={pagination}
|
||||||
size="large"
|
size="large"
|
||||||
|
scroll={{ x: 1500 }}
|
||||||
tableAlertRender={() => {
|
tableAlertRender={() => {
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Tag, Space, Tooltip } from 'antd';
|
import { Tag, Space, Tooltip, Typography } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
import type { StateType } from '../model';
|
import type { StateType } from '../model';
|
||||||
@@ -11,13 +11,15 @@ import {
|
|||||||
PartitionOutlined,
|
PartitionOutlined,
|
||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
AreaChartOutlined,
|
AreaChartOutlined,
|
||||||
DeleteOutlined,
|
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
|
import { isString } from 'lodash';
|
||||||
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_COLOR } from '../constant';
|
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_COLOR } from '../constant';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import MetricStar from './components/MetricStar';
|
import MetricStar from './components/MetricStar';
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
metircData: ISemantic.IMetricItem;
|
metircData: ISemantic.IMetricItem;
|
||||||
domainManger: StateType;
|
domainManger: StateType;
|
||||||
@@ -40,7 +42,6 @@ const MetricInfoSider: React.FC<Props> = ({
|
|||||||
<Space>
|
<Space>
|
||||||
<MetricStar metricId={metircData?.id} initState={metircData?.isCollect} />
|
<MetricStar metricId={metircData?.id} initState={metircData?.isCollect} />
|
||||||
{metircData?.name}
|
{metircData?.name}
|
||||||
{metircData?.alias && `[${metircData.alias}]`}
|
|
||||||
{metircData?.hasAdminRes && (
|
{metircData?.hasAdminRes && (
|
||||||
<span
|
<span
|
||||||
className={styles.gotoMetricListIcon}
|
className={styles.gotoMetricListIcon}
|
||||||
@@ -105,6 +106,44 @@ const MetricInfoSider: React.FC<Props> = ({
|
|||||||
</Space>
|
</Space>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isArrayOfValues(metircData?.tags) && (
|
||||||
|
<div className={styles.item}>
|
||||||
|
<span className={styles.itemLable}>别名: </span>
|
||||||
|
<span className={styles.itemValue}>
|
||||||
|
<Space size={2} wrap>
|
||||||
|
{isString(metircData?.alias) &&
|
||||||
|
metircData?.alias.split(',').map((aliasName: string) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color="#eee"
|
||||||
|
key={aliasName}
|
||||||
|
style={{
|
||||||
|
borderRadius: 44,
|
||||||
|
maxWidth: 90,
|
||||||
|
minWidth: 40,
|
||||||
|
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
maxWidth: 80,
|
||||||
|
color: 'rgb(95, 116, 141)',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
ellipsis={{ tooltip: aliasName }}
|
||||||
|
>
|
||||||
|
{aliasName}
|
||||||
|
</Text>
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<span className={styles.itemLable}>描述: </span>
|
<span className={styles.itemLable}>描述: </span>
|
||||||
<span className={styles.itemValue}>{metircData?.description}</span>
|
<span className={styles.itemValue}>{metircData?.description}</span>
|
||||||
|
|||||||
@@ -98,11 +98,11 @@ const MetricFilter: React.FC<Props> = ({ initFilterValues = {}, onFiltersChange
|
|||||||
</div>
|
</div>
|
||||||
</StandardFormRow>
|
</StandardFormRow>
|
||||||
<Space size={40}>
|
<Space size={40}>
|
||||||
<StandardFormRow key="showType" title="切换为卡片" block>
|
{/* <StandardFormRow key="showType" title="切换为卡片" block>
|
||||||
<FormItem name="showType" valuePropName="checked">
|
<FormItem name="showType" valuePropName="checked">
|
||||||
<Switch size="small" />
|
<Switch size="small" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</StandardFormRow>
|
</StandardFormRow> */}
|
||||||
{/* <StandardFormRow key="onlyShowMe" title="仅显示我的" block>
|
{/* <StandardFormRow key="onlyShowMe" title="仅显示我的" block>
|
||||||
<FormItem name="onlyShowMe" valuePropName="checked">
|
<FormItem name="onlyShowMe" valuePropName="checked">
|
||||||
<Switch size="small" />
|
<Switch size="small" />
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ import RemoteSelect, { RemoteSelectImperativeHandle } from '@/components/RemoteS
|
|||||||
import { queryDimValue } from '@/pages/SemanticModel/service';
|
import { queryDimValue } from '@/pages/SemanticModel/service';
|
||||||
import { OperatorEnum } from '@/pages/SemanticModel/enum';
|
import { OperatorEnum } from '@/pages/SemanticModel/enum';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
|
import { isArrayOfValues } from '@/utils/utils';
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dimensionOptions: OptionsItem[];
|
dimensionOptions: (OptionsItem & { modelId: number })[];
|
||||||
modelId: number;
|
modelId: number;
|
||||||
periodDate?: { startDate: string; endDate: string; dateField: string };
|
periodDate?: { startDate: string; endDate: string; dateField: string };
|
||||||
value?: FormData;
|
value?: FormData;
|
||||||
onChange?: (value: FormData) => void;
|
onChange?: (value: FormData) => void;
|
||||||
|
afterSolt?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FormData = {
|
export type FormData = {
|
||||||
@@ -24,16 +26,15 @@ export type FormData = {
|
|||||||
|
|
||||||
const MetricTrendDimensionFilter: React.FC<Props> = ({
|
const MetricTrendDimensionFilter: React.FC<Props> = ({
|
||||||
dimensionOptions,
|
dimensionOptions,
|
||||||
modelId,
|
|
||||||
value,
|
value,
|
||||||
periodDate,
|
periodDate,
|
||||||
|
afterSolt,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const dimensionValueSearchRef = useRef<RemoteSelectImperativeHandle>();
|
const dimensionValueSearchRef = useRef<RemoteSelectImperativeHandle>();
|
||||||
const queryParams = useRef<{ dimensionBizName?: string }>({});
|
const queryParams = useRef<{ dimensionBizName?: string }>({});
|
||||||
const [formData, setFormData] = useState<FormData>({ operator: OperatorEnum.IN } as FormData);
|
const [formData, setFormData] = useState<FormData>({ operator: OperatorEnum.IN } as FormData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return;
|
return;
|
||||||
@@ -59,7 +60,10 @@ const MetricTrendDimensionFilter: React.FC<Props> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { dimensionBizName } = queryParams.current;
|
const { dimensionBizName } = queryParams.current;
|
||||||
const targetOptions = dimensionOptions.find((item) => item.value === dimensionBizName) || {};
|
const targetOptions = dimensionOptions.find((item) => item.value === dimensionBizName);
|
||||||
|
if (!targetOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { code, data } = await queryDimValue({
|
const { code, data } = await queryDimValue({
|
||||||
...queryParams.current,
|
...queryParams.current,
|
||||||
value: searchValue,
|
value: searchValue,
|
||||||
@@ -95,7 +99,6 @@ const MetricTrendDimensionFilter: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
onValuesChange={(value, values) => {
|
onValuesChange={(value, values) => {
|
||||||
const { operator, dimensionValue } = values;
|
const { operator, dimensionValue } = values;
|
||||||
|
|
||||||
if (multipleValueOperator.includes(operator) && isString(dimensionValue)) {
|
if (multipleValueOperator.includes(operator) && isString(dimensionValue)) {
|
||||||
const tempDimensionValue = [dimensionValue];
|
const tempDimensionValue = [dimensionValue];
|
||||||
setFormData({ ...values, dimensionValue: tempDimensionValue });
|
setFormData({ ...values, dimensionValue: tempDimensionValue });
|
||||||
@@ -152,12 +155,14 @@ const MetricTrendDimensionFilter: React.FC<Props> = ({
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
|
disabled={!(formData.dimensionBizName && isArrayOfValues(formData.dimensionValue))}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const formValues = form.getFieldsValue();
|
const formValues = form.getFieldsValue();
|
||||||
onChange?.(formValues);
|
onChange?.(formValues);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{afterSolt}
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import MetricTrendDimensionFilter from './MetricTrendDimensionFilter';
|
|||||||
import type { FormData } from './MetricTrendDimensionFilter';
|
import type { FormData } from './MetricTrendDimensionFilter';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dimensionOptions: OptionsItem[];
|
dimensionOptions: (OptionsItem & { modelId: number })[];
|
||||||
modelId: number;
|
modelId: number;
|
||||||
value?: FormData;
|
value?: FormData;
|
||||||
periodDate?: { startDate: string; endDate: string; dateField: string };
|
periodDate?: { startDate: string; endDate: string; dateField: string };
|
||||||
onChange?: (value: FormData[]) => void;
|
onChange?: (value: FormData[]) => void;
|
||||||
|
afterSolt?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DimensionOptionsMapItem = {
|
type DimensionOptionsMapItem = {
|
||||||
@@ -20,6 +21,7 @@ const MetricTrendDimensionFilterContainer: React.FC<Props> = ({
|
|||||||
dimensionOptions,
|
dimensionOptions,
|
||||||
modelId,
|
modelId,
|
||||||
periodDate,
|
periodDate,
|
||||||
|
afterSolt,
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -56,6 +58,7 @@ const MetricTrendDimensionFilterContainer: React.FC<Props> = ({
|
|||||||
setFilterData(data);
|
setFilterData(data);
|
||||||
onChange?.(data);
|
onChange?.(data);
|
||||||
}}
|
}}
|
||||||
|
afterSolt={afterSolt}
|
||||||
/>
|
/>
|
||||||
<Space size={8} wrap style={{ marginTop: 10 }}>
|
<Space size={8} wrap style={{ marginTop: 10 }}>
|
||||||
{filterData.map((item: FormData, index: number) => {
|
{filterData.map((item: FormData, index: number) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { message, Row, Col, Button, Space, Select, Form, Tooltip } from 'antd';
|
import { message, Row, Col, Button, Space, Select, Form, Tooltip } from 'antd';
|
||||||
import { queryStruct } from '@/pages/SemanticModel/service';
|
import { queryStruct } from '@/pages/SemanticModel/service';
|
||||||
import { DownloadOutlined, PoweroffOutlined, ArrowLeftOutlined } from '@ant-design/icons';
|
import { DownloadOutlined, PoweroffOutlined, SearchOutlined } from '@ant-design/icons';
|
||||||
import TrendChart from '@/pages/SemanticModel/Metric/components/MetricTrend';
|
import TrendChart from '@/pages/SemanticModel/Metric/components/MetricTrend';
|
||||||
import MetricTrendDimensionFilterContainer from './MetricTrendDimensionFilterContainer';
|
import MetricTrendDimensionFilterContainer from './MetricTrendDimensionFilterContainer';
|
||||||
import MDatePicker from '@/components/MDatePicker';
|
import MDatePicker from '@/components/MDatePicker';
|
||||||
@@ -11,7 +11,6 @@ import StandardFormRow from '@/components/StandardFormRow';
|
|||||||
import MetricTable from './Table';
|
import MetricTable from './Table';
|
||||||
import { ColumnConfig } from '../data';
|
import { ColumnConfig } from '../data';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { history } from 'umi';
|
|
||||||
import { ISemantic } from '../../data';
|
import { ISemantic } from '../../data';
|
||||||
import { DateFieldMap } from '@/pages/SemanticModel/constant';
|
import { DateFieldMap } from '@/pages/SemanticModel/constant';
|
||||||
import ProCard from '@ant-design/pro-card';
|
import ProCard from '@ant-design/pro-card';
|
||||||
@@ -119,6 +118,9 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
setMetricColumnConfig(targetConfig);
|
setMetricColumnConfig(targetConfig);
|
||||||
}
|
}
|
||||||
setDownloadBtnDisabledState(false);
|
setDownloadBtnDisabledState(false);
|
||||||
|
if (dimensionGroup[dimensionGroup.length - 1]) {
|
||||||
|
setGroupByDimensionFieldName(dimensionGroup[dimensionGroup.length - 1]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (code === 401 || code === 400) {
|
if (code === 401 || code === 400) {
|
||||||
setAuthMessage(msg);
|
setAuthMessage(msg);
|
||||||
@@ -135,7 +137,7 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
if (metircData?.id) {
|
if (metircData?.id) {
|
||||||
getMetricTrendData({ ...queryParams });
|
getMetricTrendData({ ...queryParams });
|
||||||
}
|
}
|
||||||
}, [metircData, periodDate]);
|
}, [metircData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.metricTrendSection}>
|
<div className={styles.metricTrendSection}>
|
||||||
@@ -186,8 +188,6 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
const params = { ...queryParams, dimensionGroup: value || [] };
|
const params = { ...queryParams, dimensionGroup: value || [] };
|
||||||
setQueryParams(params);
|
setQueryParams(params);
|
||||||
getMetricTrendData({ ...params });
|
|
||||||
setGroupByDimensionFieldName(value[value.length - 1]);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -212,8 +212,20 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
dimensionFilters,
|
dimensionFilters,
|
||||||
};
|
};
|
||||||
setQueryParams(params);
|
setQueryParams(params);
|
||||||
getMetricTrendData({ ...params });
|
|
||||||
}}
|
}}
|
||||||
|
afterSolt={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
size="middle"
|
||||||
|
loading={metricTrendLoading}
|
||||||
|
onClick={() => {
|
||||||
|
getMetricTrendData({ ...queryParams });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查 询
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</StandardFormRow>
|
</StandardFormRow>
|
||||||
@@ -221,31 +233,21 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col flex="0 1" />
|
<Col flex="0 1" />
|
||||||
</Row>
|
</Row>
|
||||||
<Button
|
{/* <Row style={{ paddingLeft: 82, paddingBottom: 8 }}>
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 20,
|
|
||||||
right: 20,
|
|
||||||
}}
|
|
||||||
size="middle"
|
|
||||||
type="link"
|
|
||||||
key="backListBtn"
|
|
||||||
onClick={() => {
|
|
||||||
history.push('/metric/market');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
<ArrowLeftOutlined />
|
|
||||||
返回列表页
|
|
||||||
</Space>
|
|
||||||
</Button>
|
|
||||||
{/* <div className={styles.btnWrapper}>
|
|
||||||
|
|
||||||
</div> */}
|
</Row> */}
|
||||||
</div>
|
</div>
|
||||||
{authMessage && <div style={{ color: '#d46b08', marginBottom: 15 }}>{authMessage}</div>}
|
|
||||||
<div className={styles.sectionBox}>
|
<div className={styles.sectionBox}>
|
||||||
<ProCard size="small" title="数据趋势">
|
<ProCard
|
||||||
|
size="small"
|
||||||
|
title={
|
||||||
|
<>
|
||||||
|
<span>数据趋势</span>
|
||||||
|
{authMessage && <div style={{ color: '#d46b08' }}>{authMessage}</div>}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
<TrendChart
|
<TrendChart
|
||||||
data={metricTrendData}
|
data={metricTrendData}
|
||||||
isPer={
|
isPer={
|
||||||
@@ -272,7 +274,7 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
</ProCard>
|
</ProCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.sectionBox}>
|
<div className={styles.sectionBox} style={{ paddingBottom: 0 }}>
|
||||||
<ProCard
|
<ProCard
|
||||||
size="small"
|
size="small"
|
||||||
title="数据明细"
|
title="数据明细"
|
||||||
@@ -307,7 +309,7 @@ const MetricTrendSection: React.FC<Props> = ({
|
|||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div style={{ minHeight: '528px' }}>
|
<div style={{ minHeight: '450px' }}>
|
||||||
<MetricTable
|
<MetricTable
|
||||||
loading={metricTrendLoading}
|
loading={metricTrendLoading}
|
||||||
columnConfig={tableColumnConfig}
|
columnConfig={tableColumnConfig}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Table } from 'antd';
|
|||||||
import type { ColumnsType } from 'antd/es/table';
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import styles from '../style.less';
|
|
||||||
import { ColumnConfig } from '../data';
|
import { ColumnConfig } from '../data';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -57,16 +56,15 @@ const MetricTable: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ height: '100%' }}>
|
<div style={{ height: '100%' }}>
|
||||||
{Array.isArray(columns) && columns.length > 0 && (
|
{/* {Array.isArray(columns) && columns.length > 0 && ( */}
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
scroll={{ x: 200, y: 700 }}
|
scroll={{ x: 200, y: 700 }}
|
||||||
// pagination={{ defaultPageSize: 20 }}
|
loading={loading}
|
||||||
loading={loading}
|
onChange={() => {}}
|
||||||
onChange={() => {}}
|
/>
|
||||||
/>
|
{/* )} */}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -121,6 +121,23 @@
|
|||||||
.metricDetailWrapper {
|
.metricDetailWrapper {
|
||||||
height: calc(100vh - 56px);
|
height: calc(100vh - 56px);
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
.metricDetailTab {
|
||||||
|
:global {
|
||||||
|
.ant-tabs-nav {
|
||||||
|
background-color: rgb(255, 255, 255);
|
||||||
|
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||||
|
margin: 10px 20px 0 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.ant-tabs-tab {
|
||||||
|
padding: 12px 0;
|
||||||
|
color: #344767;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
.metricDetail {
|
.metricDetail {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
@@ -158,27 +175,27 @@
|
|||||||
.siderContainer {
|
.siderContainer {
|
||||||
background-color: rgb(255, 255, 255);
|
background-color: rgb(255, 255, 255);
|
||||||
width: 450px;
|
width: 450px;
|
||||||
min-height: 100vh;
|
min-height: calc(100vh - 78px);
|
||||||
margin: 20px 20px 20px 0;
|
margin: 10px 20px 20px 0;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: rgba(0, 0, 0, 0.08) 6px 0px 16px 0px, rgba(0, 0, 0, 0.12) 3px 0px 6px -4px, rgba(0, 0, 0, 0.05) 9px 0px 28px 8px;
|
box-shadow: rgba(0, 0, 0, 0.08) 6px 0px 16px 0px, rgba(0, 0, 0, 0.12) 3px 0px 6px -4px, rgba(0, 0, 0, 0.05) 9px 0px 28px 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.metricTrendSection {
|
|
||||||
.sectionBox {
|
.sectionBox {
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-shadow: #888888 0px 0px 1px, rgba(29, 41, 57, 0.08) 0px 1px 3px;
|
box-shadow: #888888 0px 0px 1px, rgba(29, 41, 57, 0.08) 0px 1px 3px;
|
||||||
background-color: rgb(255, 255, 255);
|
background-color: rgb(255, 255, 255);
|
||||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.metricInfoSider{
|
.metricInfoSider{
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
color: #344767;
|
color: #344767;
|
||||||
|
|||||||
@@ -103,12 +103,14 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
const filterData = dataSourceRef.current.reduce(
|
const filterData = dataSourceRef.current.reduce(
|
||||||
(data: ISemantic.IDomainSchemaRelaList, item: ISemantic.IDomainSchemaRelaItem) => {
|
(data: ISemantic.IDomainSchemaRelaList, item: ISemantic.IDomainSchemaRelaItem) => {
|
||||||
const { dimensions, metrics } = item;
|
const { dimensions, metrics } = item;
|
||||||
const dimensionsList = dimensions.filter((dimension) => {
|
const dimensionsList =
|
||||||
return dimension.name.includes(text);
|
dimensions?.filter((dimension) => {
|
||||||
});
|
return dimension.name.includes(text);
|
||||||
const metricsList = metrics.filter((metric) => {
|
}) || [];
|
||||||
return metric.name.includes(text);
|
const metricsList =
|
||||||
});
|
metrics?.filter((metric) => {
|
||||||
|
return metric.name.includes(text);
|
||||||
|
}) || [];
|
||||||
data.push({
|
data.push({
|
||||||
...item,
|
...item,
|
||||||
dimensions: dimensionsList,
|
dimensions: dimensionsList,
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { ISemantic } from '../../data';
|
||||||
|
|
||||||
|
import { TransType } from '../../enum';
|
||||||
|
import DimensionMetricVisibleTransfer from '../../components/Entity/DimensionMetricVisibleTransfer';
|
||||||
|
import { wrapperTransTypeAndId } from '../../components/Entity/utils';
|
||||||
|
|
||||||
|
export type ModelCreateFormModalProps = {
|
||||||
|
dimensionList: ISemantic.IDimensionItem[];
|
||||||
|
metricList: ISemantic.IMetricItem[];
|
||||||
|
modelId?: number;
|
||||||
|
selectedTransferKeys: React.Key[];
|
||||||
|
onCancel: () => void;
|
||||||
|
onSubmit: (values: any, selectedKeys: React.Key[]) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DimensionMetricTransferModal: React.FC<ModelCreateFormModalProps> = ({
|
||||||
|
modelId,
|
||||||
|
selectedTransferKeys,
|
||||||
|
metricList,
|
||||||
|
dimensionList,
|
||||||
|
onSubmit,
|
||||||
|
}) => {
|
||||||
|
const [sourceList, setSourceList] = useState<any[]>([]);
|
||||||
|
const [selectedItemList, setSelectedItemList] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const addItemKey = (item: any, transType: TransType) => {
|
||||||
|
const { id } = item;
|
||||||
|
const key = wrapperTransTypeAndId(transType, id);
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
transType,
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sourceDimensionList = dimensionList.reduce((mergeList: any[], item) => {
|
||||||
|
mergeList.push(addItemKey(item, TransType.DIMENSION));
|
||||||
|
return mergeList;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hasDimensionList = selectedItemList
|
||||||
|
.filter((item) => {
|
||||||
|
return item.typeEnum === TransType.DIMENSION;
|
||||||
|
})
|
||||||
|
.reduce((modelDimensionList: ISemantic.IDimensionItem[], item) => {
|
||||||
|
const hasItem = sourceDimensionList.find((dataListItem: ISemantic.IDimensionItem) => {
|
||||||
|
return dataListItem.id === item.id;
|
||||||
|
});
|
||||||
|
if (!hasItem) {
|
||||||
|
modelDimensionList.push(addItemKey(item, TransType.DIMENSION));
|
||||||
|
}
|
||||||
|
return modelDimensionList;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sourceMetricList = metricList.reduce((mergeList: any[], item) => {
|
||||||
|
mergeList.push(addItemKey(item, TransType.METRIC));
|
||||||
|
return mergeList;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const hasMetricList = selectedItemList
|
||||||
|
.filter((item) => {
|
||||||
|
return item.typeEnum === TransType.METRIC;
|
||||||
|
})
|
||||||
|
.reduce((modelMetricList: ISemantic.IMetricItem[], item) => {
|
||||||
|
const hasItem = sourceMetricList.find((dataListItem: ISemantic.IMetricItem) => {
|
||||||
|
return dataListItem.id === item.id;
|
||||||
|
});
|
||||||
|
if (!hasItem) {
|
||||||
|
modelMetricList.push(addItemKey(item, TransType.METRIC));
|
||||||
|
}
|
||||||
|
return modelMetricList;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
setSourceList([
|
||||||
|
...sourceDimensionList,
|
||||||
|
...sourceMetricList,
|
||||||
|
...hasDimensionList,
|
||||||
|
...hasMetricList,
|
||||||
|
]);
|
||||||
|
}, [dimensionList, metricList]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DimensionMetricVisibleTransfer
|
||||||
|
titles={['未关联维度/指标', '已关联维度/指标']}
|
||||||
|
listStyle={{
|
||||||
|
width: 520,
|
||||||
|
height: 600,
|
||||||
|
}}
|
||||||
|
targetList={selectedTransferKeys}
|
||||||
|
sourceList={sourceList}
|
||||||
|
onChange={(newTargetKeys: string[]) => {
|
||||||
|
const removeDimensionList: ISemantic.IDimensionItem[] = [];
|
||||||
|
const removeMetricList: ISemantic.IMetricItem[] = [];
|
||||||
|
const dimensionItemChangeList = dimensionList.reduce(
|
||||||
|
(dimensionChangeList: any[], item: any) => {
|
||||||
|
if (newTargetKeys.includes(wrapperTransTypeAndId(TransType.DIMENSION, item.id))) {
|
||||||
|
dimensionChangeList.push(item);
|
||||||
|
} else {
|
||||||
|
removeDimensionList.push(item.id);
|
||||||
|
}
|
||||||
|
return dimensionChangeList;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const metricItemChangeList = metricList.reduce((metricChangeList: any[], item: any) => {
|
||||||
|
if (newTargetKeys.includes(wrapperTransTypeAndId(TransType.METRIC, item.id))) {
|
||||||
|
metricChangeList.push(item);
|
||||||
|
} else {
|
||||||
|
removeMetricList.push(item.id);
|
||||||
|
}
|
||||||
|
return metricChangeList;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
setSelectedItemList([...dimensionItemChangeList, ...metricItemChangeList]);
|
||||||
|
|
||||||
|
// 如果不是当前选中model中的指标或者维度,则先从本地数据中删除,避免后续请求数据更新时产生视觉上的界面闪烁
|
||||||
|
const preUpdateSourceData = sourceList.filter((item) => {
|
||||||
|
const { typeEnum, id } = item;
|
||||||
|
if (typeEnum === TransType.DIMENSION) {
|
||||||
|
if (modelId !== item.modelId && removeDimensionList.includes(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeEnum === TransType.METRIC) {
|
||||||
|
if (modelId !== item.modelId && removeMetricList.includes(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
setSourceList([...preUpdateSourceData]);
|
||||||
|
|
||||||
|
const viewModelConfigs = [...dimensionItemChangeList, ...metricItemChangeList].reduce(
|
||||||
|
(config, item) => {
|
||||||
|
const { modelId, id, typeEnum } = item;
|
||||||
|
if (config[modelId]) {
|
||||||
|
if (typeEnum === TransType.DIMENSION) {
|
||||||
|
config[modelId].dimensions.push(id);
|
||||||
|
}
|
||||||
|
if (typeEnum === TransType.METRIC) {
|
||||||
|
config[modelId].metrics.push(id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config[modelId] = {
|
||||||
|
id: modelId,
|
||||||
|
metrics: typeEnum === TransType.METRIC ? [id] : [],
|
||||||
|
dimensions: typeEnum === TransType.DIMENSION ? [id] : [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
onSubmit?.(viewModelConfigs, newTargetKeys);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DimensionMetricTransferModal;
|
||||||
@@ -0,0 +1,773 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Steps,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Radio,
|
||||||
|
Switch,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Result,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Space,
|
||||||
|
Tooltip,
|
||||||
|
Tag,
|
||||||
|
} from 'antd';
|
||||||
|
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
|
import MetricMeasuresFormTable from './MetricMeasuresFormTable';
|
||||||
|
import { SENSITIVE_LEVEL_OPTIONS, METRIC_DEFINE_TYPE } from '../constant';
|
||||||
|
import { formLayout } from '@/components/FormHelper/utils';
|
||||||
|
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||||
|
import styles from './style.less';
|
||||||
|
import { getMetricsToCreateNewMetric, getModelDetail, getDrillDownDimension } from '../service';
|
||||||
|
import MetricMetricFormTable from './MetricMetricFormTable';
|
||||||
|
import MetricFieldFormTable from './MetricFieldFormTable';
|
||||||
|
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
||||||
|
import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||||
|
import { createMetric, updateMetric, mockMetricAlias, getMetricTags } from '../service';
|
||||||
|
import { ISemantic } from '../data';
|
||||||
|
import { history } from 'umi';
|
||||||
|
|
||||||
|
export type CreateFormProps = {
|
||||||
|
datasourceId?: number;
|
||||||
|
domainId: number;
|
||||||
|
modelId: number;
|
||||||
|
createModalVisible: boolean;
|
||||||
|
metricItem: any;
|
||||||
|
onCancel?: () => void;
|
||||||
|
onSubmit?: (values: any) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { Step } = Steps;
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
const queryParamsTypeParamsKey = {
|
||||||
|
[METRIC_DEFINE_TYPE.MEASURE]: 'metricDefineByMeasureParams',
|
||||||
|
[METRIC_DEFINE_TYPE.METRIC]: 'metricDefineByMetricParams',
|
||||||
|
[METRIC_DEFINE_TYPE.FIELD]: 'metricDefineByFieldParams',
|
||||||
|
};
|
||||||
|
|
||||||
|
const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
||||||
|
datasourceId,
|
||||||
|
domainId,
|
||||||
|
modelId,
|
||||||
|
onCancel,
|
||||||
|
createModalVisible,
|
||||||
|
metricItem,
|
||||||
|
onSubmit,
|
||||||
|
}) => {
|
||||||
|
const isEdit = !!metricItem?.id;
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
const formValRef = useRef({} as any);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const updateFormVal = (val: any) => {
|
||||||
|
const formVal = {
|
||||||
|
...formValRef.current,
|
||||||
|
...val,
|
||||||
|
};
|
||||||
|
formValRef.current = formVal;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [classMeasureList, setClassMeasureList] = useState<ISemantic.IMeasure[]>([]);
|
||||||
|
|
||||||
|
const [exprTypeParamsState, setExprTypeParamsState] = useState<{
|
||||||
|
[METRIC_DEFINE_TYPE.MEASURE]: ISemantic.IMeasureTypeParams;
|
||||||
|
[METRIC_DEFINE_TYPE.METRIC]: ISemantic.IMetricTypeParams;
|
||||||
|
[METRIC_DEFINE_TYPE.FIELD]: ISemantic.IFieldTypeParams;
|
||||||
|
}>({
|
||||||
|
[METRIC_DEFINE_TYPE.MEASURE]: {
|
||||||
|
measures: [],
|
||||||
|
expr: '',
|
||||||
|
},
|
||||||
|
[METRIC_DEFINE_TYPE.METRIC]: {
|
||||||
|
metrics: [],
|
||||||
|
expr: '',
|
||||||
|
},
|
||||||
|
[METRIC_DEFINE_TYPE.FIELD]: {
|
||||||
|
fields: [],
|
||||||
|
expr: '',
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
// const [exprTypeParamsState, setExprTypeParamsState] = useState<ISemantic.IMeasure[]>([]);
|
||||||
|
|
||||||
|
const [defineType, setDefineType] = useState(METRIC_DEFINE_TYPE.MEASURE);
|
||||||
|
|
||||||
|
const [createNewMetricList, setCreateNewMetricList] = useState<ISemantic.IMetricItem[]>([]);
|
||||||
|
const [fieldList, setFieldList] = useState<string[]>([]);
|
||||||
|
const [isPercentState, setIsPercentState] = useState<boolean>(false);
|
||||||
|
const [isDecimalState, setIsDecimalState] = useState<boolean>(false);
|
||||||
|
const [hasMeasuresState, setHasMeasuresState] = useState<boolean>(true);
|
||||||
|
const [llmLoading, setLlmLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [tagOptions, setTagOptions] = useState<{ label: string; value: string }[]>([]);
|
||||||
|
|
||||||
|
const [metricRelationModalOpenState, setMetricRelationModalOpenState] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [drillDownDimensions, setDrillDownDimensions] = useState<
|
||||||
|
ISemantic.IDrillDownDimensionItem[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const [drillDownDimensionsConfig, setDrillDownDimensionsConfig] = useState<
|
||||||
|
ISemantic.IDrillDownDimensionItem[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const forward = () => setCurrentStep(currentStep + 1);
|
||||||
|
const backward = () => setCurrentStep(currentStep - 1);
|
||||||
|
|
||||||
|
const queryModelDetail = async () => {
|
||||||
|
// const { code, data } = await getMeasureListByModelId(modelId);
|
||||||
|
const { code, data } = await getModelDetail({ modelId: modelId || metricItem?.modelId });
|
||||||
|
if (code === 200) {
|
||||||
|
if (Array.isArray(data?.modelDetail?.fields)) {
|
||||||
|
setFieldList(data.modelDetail.fields);
|
||||||
|
}
|
||||||
|
if (Array.isArray(data?.modelDetail?.measures)) {
|
||||||
|
setClassMeasureList(data.modelDetail.measures);
|
||||||
|
if (datasourceId) {
|
||||||
|
const hasMeasures = data.some(
|
||||||
|
(item: ISemantic.IMeasure) => item.datasourceId === datasourceId,
|
||||||
|
);
|
||||||
|
setHasMeasuresState(hasMeasures);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setClassMeasureList([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryDrillDownDimension = async (metricId: number) => {
|
||||||
|
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||||
|
if (code === 200 && Array.isArray(data)) {
|
||||||
|
setDrillDownDimensionsConfig(data);
|
||||||
|
}
|
||||||
|
if (code !== 200) {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
queryModelDetail();
|
||||||
|
queryMetricsToCreateNewMetric();
|
||||||
|
queryMetricTags();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleNext = async () => {
|
||||||
|
const fieldsValue = await form.validateFields();
|
||||||
|
const submitForm = {
|
||||||
|
...formValRef.current,
|
||||||
|
...fieldsValue,
|
||||||
|
metricDefineType: defineType,
|
||||||
|
[queryParamsTypeParamsKey[defineType]]: exprTypeParamsState[defineType],
|
||||||
|
};
|
||||||
|
updateFormVal(submitForm);
|
||||||
|
if (currentStep < 1) {
|
||||||
|
forward();
|
||||||
|
} else {
|
||||||
|
await saveMetric(submitForm);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initData = () => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
bizName,
|
||||||
|
description,
|
||||||
|
sensitiveLevel,
|
||||||
|
typeParams,
|
||||||
|
dataFormat,
|
||||||
|
dataFormatType,
|
||||||
|
alias,
|
||||||
|
tags,
|
||||||
|
metricDefineType,
|
||||||
|
metricDefineByMeasureParams,
|
||||||
|
metricDefineByMetricParams,
|
||||||
|
metricDefineByFieldParams,
|
||||||
|
} = metricItem;
|
||||||
|
const isPercent = dataFormatType === 'percent';
|
||||||
|
const isDecimal = dataFormatType === 'decimal';
|
||||||
|
const initValue = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
bizName,
|
||||||
|
sensitiveLevel,
|
||||||
|
description,
|
||||||
|
tags,
|
||||||
|
// isPercent,
|
||||||
|
dataFormatType: dataFormatType || '',
|
||||||
|
alias: alias && alias.trim() ? alias.split(',') : [],
|
||||||
|
dataFormat: dataFormat || {
|
||||||
|
decimalPlaces: 2,
|
||||||
|
needMultiply100: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const editInitFormVal = {
|
||||||
|
...formValRef.current,
|
||||||
|
...initValue,
|
||||||
|
};
|
||||||
|
if (metricDefineType === METRIC_DEFINE_TYPE.MEASURE) {
|
||||||
|
const { measures, expr } = metricDefineByMeasureParams || {};
|
||||||
|
setExprTypeParamsState({
|
||||||
|
...exprTypeParamsState,
|
||||||
|
[METRIC_DEFINE_TYPE.MEASURE]: {
|
||||||
|
measures: measures || [],
|
||||||
|
expr: expr || '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (metricDefineType === METRIC_DEFINE_TYPE.METRIC) {
|
||||||
|
const { metrics, expr } = metricDefineByMetricParams || {};
|
||||||
|
setExprTypeParamsState({
|
||||||
|
...exprTypeParamsState,
|
||||||
|
[METRIC_DEFINE_TYPE.METRIC]: {
|
||||||
|
metrics: metrics || [],
|
||||||
|
expr: expr || '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (metricDefineType === METRIC_DEFINE_TYPE.FIELD) {
|
||||||
|
const { fields, expr } = metricDefineByFieldParams || {};
|
||||||
|
setExprTypeParamsState({
|
||||||
|
...exprTypeParamsState,
|
||||||
|
[METRIC_DEFINE_TYPE.FIELD]: {
|
||||||
|
fields: fields || [],
|
||||||
|
expr: expr || '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
updateFormVal(editInitFormVal);
|
||||||
|
form.setFieldsValue(initValue);
|
||||||
|
setDefineType(metricDefineType);
|
||||||
|
setIsPercentState(isPercent);
|
||||||
|
setIsDecimalState(isDecimal);
|
||||||
|
queryDrillDownDimension(metricItem?.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEdit) {
|
||||||
|
initData();
|
||||||
|
}
|
||||||
|
}, [metricItem]);
|
||||||
|
|
||||||
|
const isEmptyConditions = (
|
||||||
|
metricDefineType: METRIC_DEFINE_TYPE,
|
||||||
|
metricDefineParams:
|
||||||
|
| ISemantic.IMeasureTypeParams
|
||||||
|
| ISemantic.IMetricTypeParams
|
||||||
|
| ISemantic.IFieldTypeParams,
|
||||||
|
) => {
|
||||||
|
if (metricDefineType === METRIC_DEFINE_TYPE.MEASURE) {
|
||||||
|
const { measures } = (metricDefineParams as ISemantic.IMeasureTypeParams) || {};
|
||||||
|
if (!(Array.isArray(measures) && measures.length > 0)) {
|
||||||
|
message.error('请添加一个度量');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (metricDefineType === METRIC_DEFINE_TYPE.METRIC) {
|
||||||
|
const { metrics } = (metricDefineParams as ISemantic.IMetricTypeParams) || {};
|
||||||
|
if (!(Array.isArray(metrics) && metrics.length > 0)) {
|
||||||
|
message.error('请添加一个指标');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (metricDefineType === METRIC_DEFINE_TYPE.FIELD) {
|
||||||
|
const { fields } = (metricDefineParams as ISemantic.IFieldTypeParams) || {};
|
||||||
|
if (!(Array.isArray(fields) && fields.length > 0)) {
|
||||||
|
message.error('请添加一个字段');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveMetric = async (fieldsValue: any) => {
|
||||||
|
const queryParams = {
|
||||||
|
modelId: isEdit ? metricItem.modelId : modelId,
|
||||||
|
relateDimension: {
|
||||||
|
...(metricItem?.relateDimension || {}),
|
||||||
|
drillDownDimensions,
|
||||||
|
},
|
||||||
|
...fieldsValue,
|
||||||
|
};
|
||||||
|
const { alias, dataFormatType } = queryParams;
|
||||||
|
queryParams.alias = Array.isArray(alias) ? alias.join(',') : '';
|
||||||
|
if (!queryParams[queryParamsTypeParamsKey[defineType]]?.expr) {
|
||||||
|
message.error('请输入度量表达式');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!dataFormatType) {
|
||||||
|
delete queryParams.dataFormat;
|
||||||
|
}
|
||||||
|
if (isEmptyConditions(defineType, queryParams[queryParamsTypeParamsKey[defineType]])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let saveMetricQuery = createMetric;
|
||||||
|
if (queryParams.id) {
|
||||||
|
saveMetricQuery = updateMetric;
|
||||||
|
}
|
||||||
|
const { code, msg } = await saveMetricQuery(queryParams);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success('编辑指标成功');
|
||||||
|
onSubmit?.(queryParams);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(msg);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatorMetricAlias = async () => {
|
||||||
|
setLlmLoading(true);
|
||||||
|
const { code, data } = await mockMetricAlias({ ...metricItem });
|
||||||
|
const formAlias = form.getFieldValue('alias');
|
||||||
|
setLlmLoading(false);
|
||||||
|
if (code === 200) {
|
||||||
|
form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
|
||||||
|
} else {
|
||||||
|
message.error('大语言模型解析异常');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryMetricTags = async () => {
|
||||||
|
const { code, data } = await getMetricTags();
|
||||||
|
if (code === 200) {
|
||||||
|
// form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
|
||||||
|
setTagOptions(
|
||||||
|
Array.isArray(data)
|
||||||
|
? data.map((tag: string) => {
|
||||||
|
return { label: tag, value: tag };
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message.error('获取指标标签失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const queryMetricsToCreateNewMetric = async () => {
|
||||||
|
const { code, data } = await getMetricsToCreateNewMetric({
|
||||||
|
modelId: modelId || metricItem?.modelId,
|
||||||
|
});
|
||||||
|
if (code === 200) {
|
||||||
|
setCreateNewMetricList(data);
|
||||||
|
} else {
|
||||||
|
message.error('获取指标标签失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (currentStep === 1) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '0 0 20px 24px',
|
||||||
|
// borderBottom: '1px solid #eee',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Radio.Group
|
||||||
|
buttonStyle="solid"
|
||||||
|
value={defineType}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDefineType(e.target.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Radio.Button value={METRIC_DEFINE_TYPE.MEASURE}>按度量</Radio.Button>
|
||||||
|
<Radio.Button value={METRIC_DEFINE_TYPE.METRIC}>按指标</Radio.Button>
|
||||||
|
<Radio.Button value={METRIC_DEFINE_TYPE.FIELD}>按字段</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</div>
|
||||||
|
{defineType === METRIC_DEFINE_TYPE.MEASURE && (
|
||||||
|
<>
|
||||||
|
<MetricMeasuresFormTable
|
||||||
|
datasourceId={datasourceId}
|
||||||
|
typeParams={exprTypeParamsState[METRIC_DEFINE_TYPE.MEASURE]}
|
||||||
|
measuresList={classMeasureList}
|
||||||
|
onFieldChange={(measures: ISemantic.IMeasure[]) => {
|
||||||
|
setExprTypeParamsState((prevState) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
[METRIC_DEFINE_TYPE.MEASURE]: {
|
||||||
|
...prevState[METRIC_DEFINE_TYPE.MEASURE],
|
||||||
|
measures,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onSqlChange={(expr: string) => {
|
||||||
|
setExprTypeParamsState((prevState) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
[METRIC_DEFINE_TYPE.MEASURE]: {
|
||||||
|
...prevState[METRIC_DEFINE_TYPE.MEASURE],
|
||||||
|
expr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{defineType === METRIC_DEFINE_TYPE.METRIC && (
|
||||||
|
<>
|
||||||
|
<p className={styles.desc}>
|
||||||
|
通过
|
||||||
|
<Tag color="#2499ef14" className={styles.markerTag}>
|
||||||
|
字段
|
||||||
|
</Tag>
|
||||||
|
和
|
||||||
|
<Tag color="#2499ef14" className={styles.markerTag}>
|
||||||
|
度量
|
||||||
|
</Tag>
|
||||||
|
创建的指标可用来创建新的指标
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<MetricMetricFormTable
|
||||||
|
typeParams={exprTypeParamsState[METRIC_DEFINE_TYPE.METRIC]}
|
||||||
|
metricList={createNewMetricList}
|
||||||
|
onFieldChange={(metrics: ISemantic.IMetricTypeParamsItem[]) => {
|
||||||
|
setExprTypeParamsState((prevState) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
[METRIC_DEFINE_TYPE.METRIC]: {
|
||||||
|
...prevState[METRIC_DEFINE_TYPE.METRIC],
|
||||||
|
metrics,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onSqlChange={(expr: string) => {
|
||||||
|
setExprTypeParamsState((prevState) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
[METRIC_DEFINE_TYPE.METRIC]: {
|
||||||
|
...prevState[METRIC_DEFINE_TYPE.METRIC],
|
||||||
|
expr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{defineType === METRIC_DEFINE_TYPE.FIELD && (
|
||||||
|
<>
|
||||||
|
<MetricFieldFormTable
|
||||||
|
typeParams={exprTypeParamsState[METRIC_DEFINE_TYPE.FIELD]}
|
||||||
|
fieldList={fieldList}
|
||||||
|
onFieldChange={(fields: ISemantic.IFieldTypeParamsItem[]) => {
|
||||||
|
setExprTypeParamsState((prevState) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
[METRIC_DEFINE_TYPE.FIELD]: {
|
||||||
|
...prevState[METRIC_DEFINE_TYPE.FIELD],
|
||||||
|
fields,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onSqlChange={(expr: string) => {
|
||||||
|
setExprTypeParamsState((prevState) => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
[METRIC_DEFINE_TYPE.FIELD]: {
|
||||||
|
...prevState[METRIC_DEFINE_TYPE.FIELD],
|
||||||
|
expr,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormItem hidden={true} name="id" label="ID">
|
||||||
|
<Input placeholder="id" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="name"
|
||||||
|
label="指标名称"
|
||||||
|
rules={[{ required: true, message: '请输入指标名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="名称不可重复" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="bizName"
|
||||||
|
label="英文名称"
|
||||||
|
rules={[{ required: true, message: '请输入英文名称' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="名称不可重复" disabled={isEdit} />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="别名">
|
||||||
|
<Row>
|
||||||
|
<Col flex="1 1 200px">
|
||||||
|
<FormItem name="alias" noStyle>
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||||
|
tokenSeparators={[',']}
|
||||||
|
maxTagCount={9}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Col>
|
||||||
|
{isEdit && (
|
||||||
|
<Col flex="0 1 75px">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
loading={llmLoading}
|
||||||
|
size="small"
|
||||||
|
style={{ top: '2px' }}
|
||||||
|
onClick={() => {
|
||||||
|
generatorMetricAlias();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Space>
|
||||||
|
智能填充
|
||||||
|
<Tooltip title="智能填充将根据指标相关信息,使用大语言模型获取指标别名">
|
||||||
|
<InfoCircleOutlined />
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="tags" label="标签">
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||||
|
tokenSeparators={[',']}
|
||||||
|
maxTagCount={9}
|
||||||
|
options={tagOptions}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="sensitiveLevel"
|
||||||
|
label="敏感度"
|
||||||
|
rules={[{ required: true, message: '请选择敏感度' }]}
|
||||||
|
>
|
||||||
|
<Select placeholder="请选择敏感度">
|
||||||
|
{SENSITIVE_LEVEL_OPTIONS.map((item) => (
|
||||||
|
<Option key={item.value} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="description"
|
||||||
|
label={
|
||||||
|
<TableTitleTooltips
|
||||||
|
title="业务口径"
|
||||||
|
overlayInnerStyle={{ width: 600 }}
|
||||||
|
tooltips={
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
在录入指标时,请务必详细填写指标口径。口径描述对于理解指标的含义、计算方法和使用场景至关重要。一个清晰、准确的口径描述可以帮助其他用户更好地理解和使用该指标,避免因为误解而导致错误的数据分析和决策。在填写口径时,建议包括以下信息:
|
||||||
|
</p>
|
||||||
|
<p>1. 指标的计算方法:详细说明指标是如何计算的,包括涉及的公式、计算步骤等。</p>
|
||||||
|
<p>2. 数据来源:描述指标所依赖的数据来源,包括数据表、字段等信息。</p>
|
||||||
|
<p>3. 使用场景:说明该指标适用于哪些业务场景,以及如何在这些场景中使用该指标。</p>
|
||||||
|
<p>4. 任何其他相关信息:例如数据更新频率、数据质量要求等。</p>
|
||||||
|
<p>
|
||||||
|
请确保口径描述清晰、简洁且易于理解,以便其他用户能够快速掌握指标的核心要点。
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
rules={[{ required: true, message: '请输入业务口径' }]}
|
||||||
|
>
|
||||||
|
<TextArea placeholder="请输入业务口径" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
label={
|
||||||
|
<FormItemTitle
|
||||||
|
title={'下钻维度配置'}
|
||||||
|
subTitle={'配置下钻维度后,将可以在指标卡中进行下钻'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setMetricRelationModalOpenState(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
设 置
|
||||||
|
</Button>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
label={
|
||||||
|
<FormItemTitle
|
||||||
|
title={'数据格式化'}
|
||||||
|
// subTitle={'开启后,指标数据展示时会根据配置进行格式化,如0.02 -> 2%'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="dataFormatType"
|
||||||
|
>
|
||||||
|
<Radio.Group buttonStyle="solid" size="middle">
|
||||||
|
<Radio.Button value="">默认</Radio.Button>
|
||||||
|
<Radio.Button value="decimal">小数</Radio.Button>
|
||||||
|
<Radio.Button value="percent">百分比</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</FormItem>
|
||||||
|
|
||||||
|
{(isPercentState || isDecimalState) && (
|
||||||
|
<FormItem
|
||||||
|
label={
|
||||||
|
<FormItemTitle
|
||||||
|
title={'小数位数'}
|
||||||
|
subTitle={`对小数位数进行设置,如保留两位,0.021252 -> 0.02${
|
||||||
|
isPercentState ? '%' : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name={['dataFormat', 'decimalPlaces']}
|
||||||
|
>
|
||||||
|
<InputNumber placeholder="请输入需要保留小数位数" style={{ width: '300px' }} />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
{isPercentState && (
|
||||||
|
<>
|
||||||
|
<FormItem
|
||||||
|
label={
|
||||||
|
<FormItemTitle
|
||||||
|
title={'原始值是否乘以100'}
|
||||||
|
subTitle={'如 原始值0.001 ->展示值0.1% '}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name={['dataFormat', 'needMultiply100']}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const renderFooter = () => {
|
||||||
|
if (!hasMeasuresState) {
|
||||||
|
return <Button onClick={onCancel}>取消</Button>;
|
||||||
|
}
|
||||||
|
if (currentStep === 1) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button style={{ float: 'left' }} onClick={backward}>
|
||||||
|
上一步
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
<Button type="primary" onClick={handleNext}>
|
||||||
|
完成
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
<Button type="primary" onClick={handleNext}>
|
||||||
|
下一步
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
forceRender
|
||||||
|
width={800}
|
||||||
|
style={{ top: 48 }}
|
||||||
|
// styles={{ padding: '32px 40px 48px' }}
|
||||||
|
destroyOnClose
|
||||||
|
title={`${isEdit ? '编辑' : '新建'}指标`}
|
||||||
|
maskClosable={false}
|
||||||
|
open={createModalVisible}
|
||||||
|
footer={renderFooter()}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
{hasMeasuresState ? (
|
||||||
|
<>
|
||||||
|
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
|
||||||
|
<Step title="基本信息" />
|
||||||
|
<Step title="度量信息" />
|
||||||
|
</Steps>
|
||||||
|
<Form
|
||||||
|
{...formLayout}
|
||||||
|
form={form}
|
||||||
|
initialValues={{
|
||||||
|
...formValRef.current,
|
||||||
|
dataFormatType: '',
|
||||||
|
}}
|
||||||
|
onValuesChange={(value, values: any) => {
|
||||||
|
const { isPercent, dataFormatType } = values;
|
||||||
|
// if (isPercent !== undefined) {
|
||||||
|
// setIsPercentState(isPercent);
|
||||||
|
// }
|
||||||
|
if (dataFormatType === 'percent') {
|
||||||
|
setIsPercentState(true);
|
||||||
|
setIsDecimalState(false);
|
||||||
|
}
|
||||||
|
if (dataFormatType === 'decimal') {
|
||||||
|
setIsPercentState(false);
|
||||||
|
setIsDecimalState(true);
|
||||||
|
}
|
||||||
|
if (!dataFormatType) {
|
||||||
|
setIsPercentState(false);
|
||||||
|
setIsDecimalState(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className={styles.form}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</Form>
|
||||||
|
<DimensionAndMetricRelationModal
|
||||||
|
metricItem={metricItem}
|
||||||
|
relationsInitialValue={drillDownDimensionsConfig}
|
||||||
|
open={metricRelationModalOpenState}
|
||||||
|
onCancel={() => {
|
||||||
|
setMetricRelationModalOpenState(false);
|
||||||
|
}}
|
||||||
|
onSubmit={(relations) => {
|
||||||
|
setDrillDownDimensions(relations);
|
||||||
|
setMetricRelationModalOpenState(false);
|
||||||
|
}}
|
||||||
|
onRefreshRelationData={() => {
|
||||||
|
queryDrillDownDimension(metricItem?.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Result
|
||||||
|
status="warning"
|
||||||
|
subTitle="当前数据源缺少度量,无法创建指标。请前往数据源配置中,将字段设置为度量"
|
||||||
|
extra={
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
key="console"
|
||||||
|
onClick={() => {
|
||||||
|
history.replace(`/model/${domainId}/${modelId || metricItem?.modelId}/dataSource`);
|
||||||
|
onCancel?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
去创建
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetricInfoCreateForm;
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import { Form, Button, Modal, Input, Select, Steps } from 'antd';
|
||||||
|
import styles from '../../components/style.less';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import { formLayout } from '@/components/FormHelper/utils';
|
||||||
|
import { createView, updateView } from '../../service';
|
||||||
|
import { ISemantic } from '../../data';
|
||||||
|
import { isString } from 'lodash';
|
||||||
|
import ViewModelConfigTable from './ViewModelConfigTable';
|
||||||
|
import ViewModelConfigTransfer from './ViewModelConfigTransfer';
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
export type ModelCreateFormModalProps = {
|
||||||
|
domainId: number;
|
||||||
|
viewItem: any;
|
||||||
|
modelList: ISemantic.IModelItem[];
|
||||||
|
onCancel: () => void;
|
||||||
|
onSubmit: (values: any) => void;
|
||||||
|
};
|
||||||
|
const { Step } = Steps;
|
||||||
|
const ViewCreateFormModal: React.FC<ModelCreateFormModalProps> = ({
|
||||||
|
viewItem,
|
||||||
|
domainId,
|
||||||
|
onCancel,
|
||||||
|
onSubmit,
|
||||||
|
modelList,
|
||||||
|
}) => {
|
||||||
|
const [currentStep, setCurrentStep] = useState(0);
|
||||||
|
|
||||||
|
const [formVals, setFormVals] = useState<ISemantic.IModelItem>({
|
||||||
|
...viewItem,
|
||||||
|
currentModel: modelList[0]?.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [submitData, setSubmitData] = useState({});
|
||||||
|
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||||
|
const [modalWidth, setModalWidth] = useState<number>(800);
|
||||||
|
const [selectedModelItem, setSelectedModelItem] = useState<ISemantic.IModelItem | undefined>(
|
||||||
|
modelList[0],
|
||||||
|
);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const configTableRef = useRef<any>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
...viewItem,
|
||||||
|
});
|
||||||
|
}, [viewItem]);
|
||||||
|
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
const fieldsValue = await form.validateFields();
|
||||||
|
const viewModelConfigsMap = configTableRef?.current.getViewModelConfigs() || {};
|
||||||
|
const queryData: ISemantic.IModelItem = {
|
||||||
|
...formVals,
|
||||||
|
...fieldsValue,
|
||||||
|
...submitData,
|
||||||
|
viewDetail: {
|
||||||
|
viewModelConfigs: Object.values(viewModelConfigsMap),
|
||||||
|
},
|
||||||
|
domainId,
|
||||||
|
};
|
||||||
|
setFormVals(queryData);
|
||||||
|
setSaveLoading(true);
|
||||||
|
const { code, msg } = await (!queryData.id ? createView : updateView)(queryData);
|
||||||
|
setSaveLoading(false);
|
||||||
|
if (code === 200) {
|
||||||
|
onSubmit?.(queryData);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const footer = (
|
||||||
|
<>
|
||||||
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
<Button type="primary" loading={saveLoading} onClick={handleConfirm}>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const forward = () => {
|
||||||
|
setModalWidth(1200);
|
||||||
|
setCurrentStep(currentStep + 1);
|
||||||
|
};
|
||||||
|
const backward = () => {
|
||||||
|
setModalWidth(800);
|
||||||
|
setCurrentStep(currentStep - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNext = async () => {
|
||||||
|
const fieldsValue = await form.validateFields();
|
||||||
|
const submitForm = {
|
||||||
|
...submitData,
|
||||||
|
...fieldsValue,
|
||||||
|
};
|
||||||
|
setSubmitData(submitForm);
|
||||||
|
if (currentStep < 1) {
|
||||||
|
forward();
|
||||||
|
} else {
|
||||||
|
// await saveMetric(submitForm);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderFooter = () => {
|
||||||
|
if (currentStep === 1) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button style={{ float: 'left' }} onClick={backward}>
|
||||||
|
上一步
|
||||||
|
</Button>
|
||||||
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
{/* <Button type="primary" onClick={handleNext}> */}
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
handleConfirm();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
完成
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={onCancel}>取消</Button>
|
||||||
|
<Button type="primary" onClick={handleNext}>
|
||||||
|
下一步
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (currentStep === 1) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FormItem
|
||||||
|
name="currentModel"
|
||||||
|
label="选择模型"
|
||||||
|
rules={[{ required: true, message: '请选择模型!' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder="请选择模型,获取当前模型下指标维度信息"
|
||||||
|
onChange={(val) => {
|
||||||
|
const modelItem = modelList.find((item) => item.id === val);
|
||||||
|
setSelectedModelItem(modelItem);
|
||||||
|
}}
|
||||||
|
options={modelList.map((item) => {
|
||||||
|
return { label: item.name, value: item.id };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<ViewModelConfigTransfer
|
||||||
|
modelItem={selectedModelItem}
|
||||||
|
viewItem={viewItem}
|
||||||
|
ref={configTableRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormItem
|
||||||
|
name="name"
|
||||||
|
label="视图名称"
|
||||||
|
rules={[{ required: true, message: '请输入视图名称!' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="视图名称不可重复" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="bizName"
|
||||||
|
label="视图英文名称"
|
||||||
|
rules={[{ required: true, message: '请输入视图英文名称!' }]}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入视图英文名称" />
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="alias"
|
||||||
|
label="别名"
|
||||||
|
getValueFromEvent={(value) => {
|
||||||
|
return Array.isArray(value) ? value.join(',') : '';
|
||||||
|
}}
|
||||||
|
getValueProps={(value) => {
|
||||||
|
return {
|
||||||
|
value: isString(value) ? value.split(',') : [],
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
mode="tags"
|
||||||
|
placeholder="输入别名后回车确认,多别名输入、复制粘贴支持英文逗号自动分隔"
|
||||||
|
tokenSeparators={[',']}
|
||||||
|
maxTagCount={9}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem name="description" label="视图描述">
|
||||||
|
<Input.TextArea placeholder="视图描述" />
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
width={modalWidth}
|
||||||
|
destroyOnClose
|
||||||
|
title={'视图信息'}
|
||||||
|
open={true}
|
||||||
|
// footer={footer}
|
||||||
|
footer={renderFooter()}
|
||||||
|
onCancel={onCancel}
|
||||||
|
>
|
||||||
|
<Steps style={{ marginBottom: 28 }} size="small" current={currentStep}>
|
||||||
|
<Step title="基本信息" />
|
||||||
|
<Step title="关联信息" />
|
||||||
|
</Steps>
|
||||||
|
<Form
|
||||||
|
{...formLayout}
|
||||||
|
form={form}
|
||||||
|
initialValues={{
|
||||||
|
...formVals,
|
||||||
|
}}
|
||||||
|
onValuesChange={(value, values) => {}}
|
||||||
|
className={styles.form}
|
||||||
|
>
|
||||||
|
{renderContent()}
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewCreateFormModal;
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import { message } from 'antd';
|
||||||
|
// import { InfoCircleOutlined } from '@ant-design/icons';
|
||||||
|
import React, { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||||
|
import type { Ref } from 'react';
|
||||||
|
import DimensionMetricTransferModal from './DimensionMetricTransferModal';
|
||||||
|
// import styles from '../../components/style.less';
|
||||||
|
import { TransType } from '../../enum';
|
||||||
|
import { getDimensionList, queryMetric } from '../../service';
|
||||||
|
import { wrapperTransTypeAndId } from '../../components/Entity/utils';
|
||||||
|
import { ISemantic } from '../../data';
|
||||||
|
import { isArrayOfValues } from '@/utils/utils';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
// modelList: ISemantic.IModelItem[];
|
||||||
|
viewItem: ISemantic.IViewItem;
|
||||||
|
modelItem?: ISemantic.IModelItem;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
const ViewModelConfigTransfer: React.FC<Props> = forwardRef(
|
||||||
|
({ viewItem, modelItem }: Props, ref: Ref<any>) => {
|
||||||
|
// const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
|
const [selectedTransferKeys, setSelectedTransferKeys] = useState<React.Key[]>([]);
|
||||||
|
|
||||||
|
const [viewModelConfigsMap, setViewModelConfigsMap] = useState({});
|
||||||
|
|
||||||
|
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||||
|
const [metricList, setMetricList] = useState<ISemantic.IMetricItem[]>([]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getViewModelConfigs: () => {
|
||||||
|
return viewModelConfigsMap;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const queryDimensionListByIds = async (ids: number[]) => {
|
||||||
|
if (!isArrayOfValues(ids)) {
|
||||||
|
queryDimensionList([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { code, data, msg } = await getDimensionList({ ids });
|
||||||
|
if (code === 200 && Array.isArray(data?.list)) {
|
||||||
|
queryDimensionList(data.list);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryMetricListByIds = async (ids: number[]) => {
|
||||||
|
if (!isArrayOfValues(ids)) {
|
||||||
|
queryMetricList([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { code, data, msg } = await queryMetric({ ids });
|
||||||
|
if (code === 200 && Array.isArray(data?.list)) {
|
||||||
|
queryMetricList(data.list);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryDimensionList = async (selectedDimensionList: ISemantic.IDimensionItem[]) => {
|
||||||
|
const { code, data, msg } = await getDimensionList({ modelId: modelItem?.id });
|
||||||
|
if (code === 200 && Array.isArray(data?.list)) {
|
||||||
|
const mergeList = selectedDimensionList.reduce(
|
||||||
|
(modelDimensionList: ISemantic.IDimensionItem[], item) => {
|
||||||
|
const hasItem = data.list.find((dataListItem: ISemantic.IDimensionItem) => {
|
||||||
|
return dataListItem.id === item.id;
|
||||||
|
});
|
||||||
|
if (!hasItem) {
|
||||||
|
return [item, ...modelDimensionList];
|
||||||
|
}
|
||||||
|
return modelDimensionList;
|
||||||
|
},
|
||||||
|
data.list,
|
||||||
|
);
|
||||||
|
setDimensionList(mergeList);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryMetricList = async (selectedMetricList: ISemantic.IMetricItem[]) => {
|
||||||
|
const { code, data, msg } = await queryMetric({ modelId: modelItem?.id });
|
||||||
|
if (code === 200 && Array.isArray(data?.list)) {
|
||||||
|
const mergeList = selectedMetricList.reduce(
|
||||||
|
(modelMetricList: ISemantic.IMetricItem[], item) => {
|
||||||
|
const hasItem = data.list.find((dataListItem: ISemantic.IMetricItem) => {
|
||||||
|
return dataListItem.id === item.id;
|
||||||
|
});
|
||||||
|
if (!hasItem) {
|
||||||
|
return [item, ...modelMetricList];
|
||||||
|
}
|
||||||
|
return modelMetricList;
|
||||||
|
},
|
||||||
|
data.list,
|
||||||
|
);
|
||||||
|
setMetricList(mergeList);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const viewModelConfigs = viewItem?.viewDetail?.viewModelConfigs;
|
||||||
|
if (Array.isArray(viewModelConfigs)) {
|
||||||
|
const idList: number[] = [];
|
||||||
|
const transferKeys: React.Key[] = [];
|
||||||
|
const viewConfigMap = {};
|
||||||
|
const allMetrics: number[] = [];
|
||||||
|
const allDimensions: number[] = [];
|
||||||
|
viewModelConfigs.forEach((item: ISemantic.IViewModelConfigItem) => {
|
||||||
|
const { id, metrics, dimensions } = item;
|
||||||
|
idList.push(id);
|
||||||
|
allMetrics.push(...metrics);
|
||||||
|
allDimensions.push(...dimensions);
|
||||||
|
viewConfigMap[id] = { ...item };
|
||||||
|
if (Array.isArray(metrics)) {
|
||||||
|
metrics.forEach((metricId: number) => {
|
||||||
|
transferKeys.push(wrapperTransTypeAndId(TransType.METRIC, metricId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Array.isArray(dimensions)) {
|
||||||
|
dimensions.forEach((dimensionId: number) => {
|
||||||
|
transferKeys.push(wrapperTransTypeAndId(TransType.DIMENSION, dimensionId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSelectedTransferKeys(transferKeys);
|
||||||
|
// setSelectedRowKeys(idList);
|
||||||
|
setViewModelConfigsMap(viewConfigMap);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const viewModelConfigs = isArrayOfValues(Object.values(viewModelConfigsMap))
|
||||||
|
? (Object.values(viewModelConfigsMap) as ISemantic.IViewModelConfigItem[])
|
||||||
|
: viewItem?.viewDetail?.viewModelConfigs;
|
||||||
|
if (isArrayOfValues(viewModelConfigs)) {
|
||||||
|
const allMetrics: number[] = [];
|
||||||
|
const allDimensions: number[] = [];
|
||||||
|
viewModelConfigs.forEach((item: ISemantic.IViewModelConfigItem) => {
|
||||||
|
const { metrics, dimensions } = item;
|
||||||
|
allMetrics.push(...metrics);
|
||||||
|
allDimensions.push(...dimensions);
|
||||||
|
});
|
||||||
|
queryDimensionListByIds(allDimensions);
|
||||||
|
queryMetricListByIds(allMetrics);
|
||||||
|
} else {
|
||||||
|
queryDimensionList([]);
|
||||||
|
queryMetricList([]);
|
||||||
|
}
|
||||||
|
}, [modelItem]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DimensionMetricTransferModal
|
||||||
|
modelId={modelItem?.id}
|
||||||
|
dimensionList={dimensionList}
|
||||||
|
metricList={metricList}
|
||||||
|
selectedTransferKeys={selectedTransferKeys}
|
||||||
|
onSubmit={(
|
||||||
|
submitData: Record<string, ISemantic.IViewModelConfigItem>,
|
||||||
|
selectedKeys: React.Key[],
|
||||||
|
) => {
|
||||||
|
const viewModelConfigs = Object.values(submitData) as ISemantic.IViewModelConfigItem[];
|
||||||
|
|
||||||
|
if (isArrayOfValues(viewModelConfigs)) {
|
||||||
|
const allMetrics: number[] = [];
|
||||||
|
const allDimensions: number[] = [];
|
||||||
|
viewModelConfigs.forEach((item: ISemantic.IViewModelConfigItem) => {
|
||||||
|
const { metrics, dimensions } = item;
|
||||||
|
allMetrics.push(...metrics);
|
||||||
|
allDimensions.push(...dimensions);
|
||||||
|
});
|
||||||
|
queryDimensionListByIds(allDimensions);
|
||||||
|
queryMetricListByIds(allMetrics);
|
||||||
|
}
|
||||||
|
setViewModelConfigsMap(submitData);
|
||||||
|
setSelectedTransferKeys(selectedKeys);
|
||||||
|
}}
|
||||||
|
onCancel={() => {}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ViewModelConfigTransfer;
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
import type { ActionType, ProColumns } from '@ant-design/pro-table';
|
||||||
|
import ProTable from '@ant-design/pro-table';
|
||||||
|
import { message, Button, Space, Popconfirm, Input, Tag } from 'antd';
|
||||||
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
import { StatusEnum } from '../../enum';
|
||||||
|
import type { Dispatch } from 'umi';
|
||||||
|
import { connect } from 'umi';
|
||||||
|
import type { StateType } from '../../model';
|
||||||
|
import { deleteView, updateView, getViewList } from '../../service';
|
||||||
|
import ViewCreateFormModal from './ViewCreateFormModal';
|
||||||
|
import moment from 'moment';
|
||||||
|
import styles from '../../components/style.less';
|
||||||
|
import { ISemantic } from '../../data';
|
||||||
|
import { ColumnsConfig } from '../../components/MetricTableColumnRender';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
disabledEdit?: boolean;
|
||||||
|
modelList: ISemantic.IModelItem[];
|
||||||
|
dispatch: Dispatch;
|
||||||
|
domainManger: StateType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ViewTable: React.FC<Props> = ({ disabledEdit = false, modelList, domainManger }) => {
|
||||||
|
const { selectDomainId } = domainManger;
|
||||||
|
const [viewItem, setViewItem] = useState<ISemantic.IViewItem>();
|
||||||
|
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
|
||||||
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
|
const updateViewStatus = async (modelData: ISemantic.IViewItem) => {
|
||||||
|
setSaveLoading(true);
|
||||||
|
const { code, msg } = await updateView({
|
||||||
|
...modelData,
|
||||||
|
});
|
||||||
|
setSaveLoading(false);
|
||||||
|
if (code === 200) {
|
||||||
|
queryViewList();
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [viewList, setViewList] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
queryViewList();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const queryViewList = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const { code, data, msg } = await getViewList(selectDomainId);
|
||||||
|
setLoading(false);
|
||||||
|
if (code === 200) {
|
||||||
|
setViewList(data);
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ProColumns[] = [
|
||||||
|
{
|
||||||
|
dataIndex: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'name',
|
||||||
|
title: '视图名称',
|
||||||
|
search: false,
|
||||||
|
// render: (_, record) => {
|
||||||
|
// return <a>{_}</a>;
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'alias',
|
||||||
|
title: '别名',
|
||||||
|
width: 150,
|
||||||
|
ellipsis: true,
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'bizName',
|
||||||
|
title: '英文名称',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'status',
|
||||||
|
title: '状态',
|
||||||
|
search: false,
|
||||||
|
render: ColumnsConfig.state.render,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'createdBy',
|
||||||
|
title: '创建人',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'description',
|
||||||
|
title: '描述',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'updatedAt',
|
||||||
|
title: '更新时间',
|
||||||
|
search: false,
|
||||||
|
render: (value: any) => {
|
||||||
|
return value && value !== '-' ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!disabledEdit) {
|
||||||
|
columns.push({
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'x',
|
||||||
|
valueType: 'option',
|
||||||
|
width: 150,
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
<Space className={styles.ctrlBtnContainer}>
|
||||||
|
<a
|
||||||
|
key="metricEditBtn"
|
||||||
|
onClick={() => {
|
||||||
|
setViewItem(record);
|
||||||
|
setCreateDataSourceModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</a>
|
||||||
|
{record.status === StatusEnum.ONLINE ? (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
key="editStatusOfflineBtn"
|
||||||
|
onClick={() => {
|
||||||
|
updateViewStatus({
|
||||||
|
...record,
|
||||||
|
status: StatusEnum.OFFLINE,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
停用
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
key="editStatusOnlineBtn"
|
||||||
|
onClick={() => {
|
||||||
|
updateViewStatus({
|
||||||
|
...record,
|
||||||
|
status: StatusEnum.ONLINE,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
启用
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Popconfirm
|
||||||
|
title="确认删除?"
|
||||||
|
okText="是"
|
||||||
|
cancelText="否"
|
||||||
|
onConfirm={async () => {
|
||||||
|
const { code, msg } = await deleteView(record.id);
|
||||||
|
if (code === 200) {
|
||||||
|
queryViewList();
|
||||||
|
} else {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<a key="modelDeleteBtn">删除</a>
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProTable
|
||||||
|
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
||||||
|
actionRef={actionRef}
|
||||||
|
rowKey="id"
|
||||||
|
search={false}
|
||||||
|
columns={columns}
|
||||||
|
loading={loading}
|
||||||
|
dataSource={viewList}
|
||||||
|
tableAlertRender={() => {
|
||||||
|
return false;
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
options={{ reload: false, density: false, fullScreen: false }}
|
||||||
|
toolBarRender={() =>
|
||||||
|
disabledEdit
|
||||||
|
? [<></>]
|
||||||
|
: [
|
||||||
|
<Button
|
||||||
|
key="create"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
setViewItem(undefined);
|
||||||
|
setCreateDataSourceModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
创建视图
|
||||||
|
</Button>,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{createDataSourceModalOpen && (
|
||||||
|
<ViewCreateFormModal
|
||||||
|
domainId={selectDomainId}
|
||||||
|
viewItem={viewItem}
|
||||||
|
modelList={modelList}
|
||||||
|
onSubmit={() => {
|
||||||
|
queryViewList();
|
||||||
|
setCreateDataSourceModalOpen(false);
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setCreateDataSourceModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||||
|
domainManger,
|
||||||
|
}))(ViewTable);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ISemantic } from '../data';
|
||||||
|
import { connect } from 'umi';
|
||||||
|
import type { Dispatch } from 'umi';
|
||||||
|
import type { StateType } from '../model';
|
||||||
|
import ViewTable from './components/ViewTable';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
disabledEdit?: boolean;
|
||||||
|
modelList: ISemantic.IModelItem[];
|
||||||
|
onModelChange?: (model?: ISemantic.IModelItem) => void;
|
||||||
|
domainManger: StateType;
|
||||||
|
dispatch: Dispatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
const View: React.FC<Props> = ({ modelList, disabledEdit = false, onModelChange }) => {
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '15px 20px' }}>
|
||||||
|
<ViewTable modelList={modelList} disabledEdit={disabledEdit} onModelChange={onModelChange} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(({ domainManger }: { domainManger: StateType }) => ({
|
||||||
|
domainManger,
|
||||||
|
}))(View);
|
||||||
@@ -6,7 +6,7 @@ import type { Dispatch } from 'umi';
|
|||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
import type { StateType } from '../model';
|
import type { StateType } from '../model';
|
||||||
import { StatusEnum } from '../enum';
|
import { StatusEnum } from '../enum';
|
||||||
import { SENSITIVE_LEVEL_ENUM } from '../constant';
|
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_OPTIONS } from '../constant';
|
||||||
import {
|
import {
|
||||||
getModelList,
|
getModelList,
|
||||||
getDimensionList,
|
getDimensionList,
|
||||||
@@ -15,10 +15,11 @@ import {
|
|||||||
} from '../service';
|
} from '../service';
|
||||||
import DimensionInfoModal from './DimensionInfoModal';
|
import DimensionInfoModal from './DimensionInfoModal';
|
||||||
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
import DimensionValueSettingModal from './DimensionValueSettingModal';
|
||||||
// import { updateDimension } from '../service';
|
|
||||||
import { ISemantic, IDataSource } from '../data';
|
import { ISemantic, IDataSource } from '../data';
|
||||||
|
import TableHeaderFilter from './TableHeaderFilter';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
||||||
|
import { ColumnsConfig } from './MetricTableColumnRender';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -31,6 +32,8 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
|
||||||
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
const [dimensionItem, setDimensionItem] = useState<ISemantic.IDimensionItem>();
|
||||||
const [dataSourceList, setDataSourceList] = useState<IDataSource.IDataSourceItem[]>([]);
|
const [dataSourceList, setDataSourceList] = useState<IDataSource.IDataSourceItem[]>([]);
|
||||||
|
const [tableData, setTableData] = useState<ISemantic.IMetricItem[]>([]);
|
||||||
|
const [filterParams, setFilterParams] = useState<Record<string, any>>({});
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
const [dimensionValueSettingList, setDimensionValueSettingList] = useState<
|
const [dimensionValueSettingList, setDimensionValueSettingList] = useState<
|
||||||
@@ -38,38 +41,37 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
>([]);
|
>([]);
|
||||||
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [pagination] = useState({
|
|
||||||
|
const defaultPagination = {
|
||||||
current: 1,
|
current: 1,
|
||||||
pageSize: 99999,
|
pageSize: 20,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
};
|
||||||
|
const [pagination, setPagination] = useState(defaultPagination);
|
||||||
|
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
|
|
||||||
const queryDimensionList = async (params: any) => {
|
const queryDimensionList = async (params: any) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { code, data, msg } = await getDimensionList({
|
const { code, data, msg } = await getDimensionList({
|
||||||
...params,
|
|
||||||
...pagination,
|
...pagination,
|
||||||
|
...params,
|
||||||
modelId,
|
modelId,
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
const { list } = data || {};
|
const { list, pageSize, pageNum, total } = data || {};
|
||||||
let resData: any = {};
|
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
resData = {
|
setPagination({
|
||||||
data: list || [],
|
...pagination,
|
||||||
success: true,
|
pageSize: Math.min(pageSize, 100),
|
||||||
};
|
current: pageNum,
|
||||||
|
total,
|
||||||
|
});
|
||||||
|
setTableData(list);
|
||||||
} else {
|
} else {
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
resData = {
|
setTableData([]);
|
||||||
data: [],
|
|
||||||
total: 0,
|
|
||||||
success: false,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return resData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryDataSourceList = async () => {
|
const queryDataSourceList = async () => {
|
||||||
@@ -81,25 +83,14 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||||
|
}, [filterParams]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryDataSourceList();
|
queryDataSourceList();
|
||||||
}, [modelId]);
|
}, [modelId]);
|
||||||
|
|
||||||
// const updateDimensionStatus = async (dimensionData: ISemantic.IDimensionItem) => {
|
|
||||||
// const { code, msg } = await updateDimension(dimensionData);
|
|
||||||
// if (code === 200) {
|
|
||||||
// actionRef?.current?.reload();
|
|
||||||
// dispatch({
|
|
||||||
// type: 'domainManger/queryDimensionList',
|
|
||||||
// payload: {
|
|
||||||
// modelId,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// message.error(msg);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
|
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
|
||||||
if (Array.isArray(ids) && ids.length === 0) {
|
if (Array.isArray(ids) && ids.length === 0) {
|
||||||
return;
|
return;
|
||||||
@@ -111,7 +102,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
actionRef?.current?.reload();
|
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'domainManger/queryDimensionList',
|
type: 'domainManger/queryDimensionList',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -127,6 +118,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
{
|
{
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
|
fixed: 'left',
|
||||||
width: 80,
|
width: 80,
|
||||||
order: 100,
|
order: 100,
|
||||||
search: false,
|
search: false,
|
||||||
@@ -135,46 +127,26 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
dataIndex: 'key',
|
dataIndex: 'key',
|
||||||
title: '维度搜索',
|
title: '维度搜索',
|
||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
renderFormItem: () => <Input placeholder="请输入ID/维度名称/英文名称" />,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: '维度名称',
|
title: '维度',
|
||||||
|
fixed: 'left',
|
||||||
|
width: 280,
|
||||||
|
render: ColumnsConfig.dimensionInfo.render,
|
||||||
search: false,
|
search: false,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
dataIndex: 'alias',
|
|
||||||
title: '别名',
|
|
||||||
width: 150,
|
|
||||||
ellipsis: true,
|
|
||||||
search: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
dataIndex: 'bizName',
|
|
||||||
title: '英文名称',
|
|
||||||
search: false,
|
|
||||||
// order: 9,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
dataIndex: 'sensitiveLevel',
|
dataIndex: 'sensitiveLevel',
|
||||||
title: '敏感度',
|
title: '敏感度',
|
||||||
width: 80,
|
width: 150,
|
||||||
valueEnum: SENSITIVE_LEVEL_ENUM,
|
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||||
|
render: ColumnsConfig.sensitiveLevel.render,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'isTag',
|
dataIndex: 'isTag',
|
||||||
title: '是否为标签',
|
title: '是否为标签',
|
||||||
// search: false,
|
width: 120,
|
||||||
renderFormItem: () => (
|
|
||||||
<Select
|
|
||||||
placeholder="请选择标签状态"
|
|
||||||
allowClear
|
|
||||||
options={[
|
|
||||||
{ value: 1, label: '是' },
|
|
||||||
{ value: 0, label: '否' },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
render: (isTag) => {
|
render: (isTag) => {
|
||||||
switch (isTag) {
|
switch (isTag) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -189,27 +161,14 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
{
|
{
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
width: 80,
|
width: 150,
|
||||||
search: false,
|
search: false,
|
||||||
render: (status) => {
|
render: ColumnsConfig.state.render,
|
||||||
switch (status) {
|
|
||||||
case StatusEnum.ONLINE:
|
|
||||||
return <Tag color="success">已启用</Tag>;
|
|
||||||
case StatusEnum.OFFLINE:
|
|
||||||
return <Tag color="warning">未启用</Tag>;
|
|
||||||
case StatusEnum.INITIALIZED:
|
|
||||||
return <Tag color="processing">初始化</Tag>;
|
|
||||||
case StatusEnum.DELETED:
|
|
||||||
return <Tag color="default">已删除</Tag>;
|
|
||||||
default:
|
|
||||||
return <Tag color="default">未知</Tag>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'createdBy',
|
dataIndex: 'createdBy',
|
||||||
title: '创建人',
|
title: '创建人',
|
||||||
width: 100,
|
width: 180,
|
||||||
search: false,
|
search: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -217,6 +176,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
title: '描述',
|
title: '描述',
|
||||||
search: false,
|
search: false,
|
||||||
|
render: ColumnsConfig.description.render,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -233,7 +193,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
title: '操作',
|
title: '操作',
|
||||||
dataIndex: 'x',
|
dataIndex: 'x',
|
||||||
valueType: 'option',
|
valueType: 'option',
|
||||||
width: 200,
|
width: 250,
|
||||||
render: (_, record) => {
|
render: (_, record) => {
|
||||||
return (
|
return (
|
||||||
<Space className={styles.ctrlBtnContainer}>
|
<Space className={styles.ctrlBtnContainer}>
|
||||||
@@ -292,7 +252,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
const { code, msg } = await deleteDimension(record.id);
|
const { code, msg } = await deleteDimension(record.id);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
setDimensionItem(undefined);
|
setDimensionItem(undefined);
|
||||||
actionRef.current?.reload();
|
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||||
} else {
|
} else {
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
}
|
}
|
||||||
@@ -336,27 +296,102 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProTable
|
<ProTable
|
||||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft} ${styles.disabledSearchTable}`}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
request={queryDimensionList}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
search={{
|
headerTitle={
|
||||||
defaultCollapsed: false,
|
<TableHeaderFilter
|
||||||
collapseRender: () => {
|
components={[
|
||||||
return <></>;
|
{
|
||||||
},
|
label: '维度搜索',
|
||||||
}}
|
component: (
|
||||||
|
<Input.Search
|
||||||
|
style={{ width: 280 }}
|
||||||
|
placeholder="请输入ID/维度名称/英文名称"
|
||||||
|
onSearch={(value) => {
|
||||||
|
setFilterParams((preState) => {
|
||||||
|
return {
|
||||||
|
...preState,
|
||||||
|
key: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '敏感度',
|
||||||
|
component: (
|
||||||
|
<Select
|
||||||
|
style={{ width: 140 }}
|
||||||
|
options={SENSITIVE_LEVEL_OPTIONS}
|
||||||
|
placeholder="请选择敏感度"
|
||||||
|
allowClear
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterParams((preState) => {
|
||||||
|
return {
|
||||||
|
...preState,
|
||||||
|
sensitiveLevel: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '标签状态',
|
||||||
|
component: (
|
||||||
|
<Select
|
||||||
|
style={{ width: 145 }}
|
||||||
|
placeholder="请选择标签状态"
|
||||||
|
allowClear
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterParams((preState) => {
|
||||||
|
return {
|
||||||
|
...preState,
|
||||||
|
isTag: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
options={[
|
||||||
|
{ value: 1, label: '是' },
|
||||||
|
{ value: 0, label: '否' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
search={false}
|
||||||
|
// search={{
|
||||||
|
// optionRender: false,
|
||||||
|
// collapsed: false,
|
||||||
|
// }}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
...rowSelection,
|
...rowSelection,
|
||||||
}}
|
}}
|
||||||
|
dataSource={tableData}
|
||||||
|
pagination={pagination}
|
||||||
tableAlertRender={() => {
|
tableAlertRender={() => {
|
||||||
return false;
|
return false;
|
||||||
}}
|
}}
|
||||||
size="small"
|
size="large"
|
||||||
|
scroll={{ x: 1500 }}
|
||||||
options={{ reload: false, density: false, fullScreen: false }}
|
options={{ reload: false, density: false, fullScreen: false }}
|
||||||
|
onChange={(data: any) => {
|
||||||
|
const { current, pageSize, total } = data;
|
||||||
|
const currentPagin = {
|
||||||
|
current,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
};
|
||||||
|
setPagination(currentPagin);
|
||||||
|
queryDimensionList({ ...filterParams, ...currentPagin });
|
||||||
|
}}
|
||||||
toolBarRender={() => [
|
toolBarRender={() => [
|
||||||
<Button
|
<Button
|
||||||
key="create"
|
key="create"
|
||||||
@@ -388,7 +423,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
dataSourceList={dataSourceList}
|
dataSourceList={dataSourceList}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
setCreateModalVisible(false);
|
setCreateModalVisible(false);
|
||||||
actionRef?.current?.reload();
|
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'domainManger/queryDimensionList',
|
type: 'domainManger/queryDimensionList',
|
||||||
payload: {
|
payload: {
|
||||||
@@ -417,7 +452,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
setDimensionValueSettingModalVisible(false);
|
setDimensionValueSettingModalVisible(false);
|
||||||
}}
|
}}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
actionRef?.current?.reload();
|
queryDimensionList({ ...filterParams, ...defaultPagination });
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'domainManger/queryDimensionList',
|
type: 'domainManger/queryDimensionList',
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
batchUpdateMetricStatus,
|
batchUpdateMetricStatus,
|
||||||
batchDownloadMetric,
|
batchDownloadMetric,
|
||||||
} from '../service';
|
} from '../service';
|
||||||
|
|
||||||
import MetricInfoCreateForm from './MetricInfoCreateForm';
|
import MetricInfoCreateForm from './MetricInfoCreateForm';
|
||||||
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
|
||||||
|
import TableHeaderFilter from './TableHeaderFilter';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
@@ -39,6 +39,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
total: 0,
|
total: 0,
|
||||||
};
|
};
|
||||||
const [pagination, setPagination] = useState(defaultPagination);
|
const [pagination, setPagination] = useState(defaultPagination);
|
||||||
|
|
||||||
const [filterParams, setFilterParams] = useState<Record<string, any>>({});
|
const [filterParams, setFilterParams] = useState<Record<string, any>>({});
|
||||||
|
|
||||||
const actionRef = useRef<ActionType>();
|
const actionRef = useRef<ActionType>();
|
||||||
@@ -94,10 +95,19 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columns: ProColumns[] = [
|
const columns: ProColumns[] = [
|
||||||
|
{
|
||||||
|
dataIndex: 'id',
|
||||||
|
title: 'ID',
|
||||||
|
width: 80,
|
||||||
|
fixed: 'left',
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'name',
|
dataIndex: 'name',
|
||||||
title: '指标',
|
title: '指标',
|
||||||
width: '30%',
|
width: 280,
|
||||||
|
fixed: 'left',
|
||||||
|
// width: '30%',
|
||||||
search: false,
|
search: false,
|
||||||
render: ColumnsConfig.metricInfo.render,
|
render: ColumnsConfig.metricInfo.render,
|
||||||
},
|
},
|
||||||
@@ -105,54 +115,35 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
dataIndex: 'key',
|
dataIndex: 'key',
|
||||||
title: '指标搜索',
|
title: '指标搜索',
|
||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
renderFormItem: () => (
|
|
||||||
<Input.Search
|
|
||||||
placeholder="请输入ID/指标名称/英文名称/标签"
|
|
||||||
onSearch={(value) => {
|
|
||||||
setFilterParams((preState) => {
|
|
||||||
return {
|
|
||||||
...preState,
|
|
||||||
key: value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'sensitiveLevel',
|
dataIndex: 'sensitiveLevel',
|
||||||
title: '敏感度',
|
title: '敏感度',
|
||||||
hideInTable: true,
|
width: 160,
|
||||||
valueEnum: SENSITIVE_LEVEL_ENUM,
|
valueEnum: SENSITIVE_LEVEL_ENUM,
|
||||||
renderFormItem: () => (
|
render: ColumnsConfig.sensitiveLevel.render,
|
||||||
<Select
|
|
||||||
options={SENSITIVE_LEVEL_OPTIONS}
|
|
||||||
placeholder="请选择敏感度"
|
|
||||||
allowClear
|
|
||||||
onChange={(value) => {
|
|
||||||
setFilterParams((preState) => {
|
|
||||||
return {
|
|
||||||
...preState,
|
|
||||||
sensitiveLevel: value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
dataIndex: 'description',
|
dataIndex: 'description',
|
||||||
title: '描述',
|
title: '描述',
|
||||||
|
width: 300,
|
||||||
search: false,
|
search: false,
|
||||||
|
render: ColumnsConfig.description.render,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
width: 200,
|
width: 160,
|
||||||
search: false,
|
search: false,
|
||||||
render: ColumnsConfig.state.render,
|
render: ColumnsConfig.state.render,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
dataIndex: 'createdBy',
|
||||||
|
title: '创建人',
|
||||||
|
width: 150,
|
||||||
|
search: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'updatedAt',
|
dataIndex: 'updatedAt',
|
||||||
title: '更新时间',
|
title: '更新时间',
|
||||||
@@ -274,14 +265,53 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProTable
|
<ProTable
|
||||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft} ${styles.disabledSearchTable} `}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
|
headerTitle={
|
||||||
|
<TableHeaderFilter
|
||||||
|
components={[
|
||||||
|
{
|
||||||
|
label: '指标搜索',
|
||||||
|
component: (
|
||||||
|
<Input.Search
|
||||||
|
style={{ width: 280 }}
|
||||||
|
placeholder="请输入ID/指标名称/英文名称/标签"
|
||||||
|
onSearch={(value) => {
|
||||||
|
setFilterParams((preState) => {
|
||||||
|
return {
|
||||||
|
...preState,
|
||||||
|
key: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '敏感度',
|
||||||
|
component: (
|
||||||
|
<Select
|
||||||
|
style={{ width: 140 }}
|
||||||
|
options={SENSITIVE_LEVEL_OPTIONS}
|
||||||
|
placeholder="请选择敏感度"
|
||||||
|
allowClear
|
||||||
|
onChange={(value) => {
|
||||||
|
setFilterParams((preState) => {
|
||||||
|
return {
|
||||||
|
...preState,
|
||||||
|
sensitiveLevel: value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
search={{
|
search={false}
|
||||||
optionRender: false,
|
|
||||||
collapsed: false,
|
|
||||||
}}
|
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
...rowSelection,
|
...rowSelection,
|
||||||
@@ -303,6 +333,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
|||||||
setPagination(currentPagin);
|
setPagination(currentPagin);
|
||||||
queryMetricList({ ...filterParams, ...currentPagin });
|
queryMetricList({ ...filterParams, ...currentPagin });
|
||||||
}}
|
}}
|
||||||
|
scroll={{ x: 1500 }}
|
||||||
sticky={{ offsetHeader: 0 }}
|
sticky={{ offsetHeader: 0 }}
|
||||||
size="large"
|
size="large"
|
||||||
options={{ reload: false, density: false, fullScreen: false }}
|
options={{ reload: false, density: false, fullScreen: false }}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const DatabaseTable: React.FC<Props> = ({}) => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ margin: 20 }}>
|
<div style={{ margin: 20 }}>
|
||||||
<ProTable
|
<ProTable
|
||||||
className={`${styles.classTable} ${styles.classTableSelectColumnAlignLeft}`}
|
// className={`${styles.classTable}`}
|
||||||
actionRef={actionRef}
|
actionRef={actionRef}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type Props = {
|
|||||||
metricItem?: ISemantic.IMetricItem;
|
metricItem?: ISemantic.IMetricItem;
|
||||||
relationsInitialValue?: ISemantic.IDrillDownDimensionItem[];
|
relationsInitialValue?: ISemantic.IDrillDownDimensionItem[];
|
||||||
onSubmit: (relations: ISemantic.IDrillDownDimensionItem[]) => void;
|
onSubmit: (relations: ISemantic.IDrillDownDimensionItem[]) => void;
|
||||||
|
onRefreshRelationData?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
||||||
@@ -19,6 +20,7 @@ const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
|||||||
relationsInitialValue,
|
relationsInitialValue,
|
||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
onRefreshRelationData,
|
||||||
}) => {
|
}) => {
|
||||||
const [relationList, setRelationList] = useState<ISemantic.IDrillDownDimensionItem[]>([]);
|
const [relationList, setRelationList] = useState<ISemantic.IDrillDownDimensionItem[]>([]);
|
||||||
|
|
||||||
@@ -30,9 +32,11 @@ const DimensionAndMetricRelationModal: React.FC<Props> = ({
|
|||||||
drillDownDimensions: relationList,
|
drillDownDimensions: relationList,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const { code, msg } = await updateMetric(queryParams);
|
const { code, msg } = await updateMetric(queryParams);
|
||||||
if (code === 200) {
|
if (code === 200) {
|
||||||
onSubmit(relationList);
|
onSubmit(relationList);
|
||||||
|
onRefreshRelationData?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
message.error(msg);
|
message.error(msg);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Table, Transfer, Checkbox, message } from 'antd';
|
import { Table, Transfer, Checkbox, message, Tooltip } from 'antd';
|
||||||
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
|
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
|
||||||
import type { TransferItem } from 'antd/es/transfer';
|
import type { TransferItem } from 'antd/es/transfer';
|
||||||
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
|
||||||
@@ -9,13 +9,15 @@ import type { StateType } from '../model';
|
|||||||
import TransTypeTag from './TransTypeTag';
|
import TransTypeTag from './TransTypeTag';
|
||||||
import TableTitleTooltips from '../components/TableTitleTooltips';
|
import TableTitleTooltips from '../components/TableTitleTooltips';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import { getDimensionList, getDimensionInModelCluster } from '../service';
|
import { EnvironmentOutlined } from '@ant-design/icons';
|
||||||
|
import { getDimensionInModelCluster } from '../service';
|
||||||
import { SemanticNodeType, TransType } from '../enum';
|
import { SemanticNodeType, TransType } from '../enum';
|
||||||
|
|
||||||
interface RecordType {
|
interface RecordType {
|
||||||
id: number;
|
id: number;
|
||||||
key: string;
|
key: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
disabled?: boolean;
|
||||||
transType: TransType.DIMENSION | TransType.METRIC;
|
transType: TransType.DIMENSION | TransType.METRIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
const [dimensionList, setDimensionList] = useState<ISemantic.IDimensionItem[]>([]);
|
||||||
|
const [transferData, setTransferData] = useState<RecordType[]>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryDimensionList();
|
queryDimensionList();
|
||||||
}, [metricItem, relationsInitialValue]);
|
}, [metricItem, relationsInitialValue]);
|
||||||
@@ -53,16 +55,29 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const data = dimensionList.map((item) => {
|
||||||
|
const transType = TransType.DIMENSION;
|
||||||
|
const { id } = item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
transType,
|
||||||
|
disabled: checkedMap[id]?.inheritFromModel,
|
||||||
|
key: `${id}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setTransferData(data);
|
||||||
|
}, [checkedMap, dimensionList]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!Array.isArray(relationsInitialValue)) {
|
if (!Array.isArray(relationsInitialValue)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ids = relationsInitialValue.map((item) => `${item.dimensionId}`);
|
const ids = relationsInitialValue.map((item) => `${item.dimensionId}`);
|
||||||
const relationMap = relationsInitialValue.reduce((relationCheckedMap, item: any) => {
|
const relationMap = relationsInitialValue.reduce((relationCheckedMap, item: any) => {
|
||||||
const { dimensionId, necessary } = item;
|
const { dimensionId } = item;
|
||||||
relationCheckedMap[dimensionId] = {
|
relationCheckedMap[dimensionId] = {
|
||||||
dimensionId: Number(dimensionId),
|
...item,
|
||||||
necessary: necessary,
|
|
||||||
};
|
};
|
||||||
return relationCheckedMap;
|
return relationCheckedMap;
|
||||||
}, {});
|
}, {});
|
||||||
@@ -101,11 +116,15 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
(relationList: ISemantic.IDrillDownDimensionItem[], dimensionId: string) => {
|
(relationList: ISemantic.IDrillDownDimensionItem[], dimensionId: string) => {
|
||||||
const target = relationCheckedMap[dimensionId];
|
const target = relationCheckedMap[dimensionId];
|
||||||
if (target) {
|
if (target) {
|
||||||
|
if (target.inheritFromModel === true && !target.necessary) {
|
||||||
|
return relationList;
|
||||||
|
}
|
||||||
relationList.push(target);
|
relationList.push(target);
|
||||||
} else {
|
} else {
|
||||||
relationList.push({
|
relationList.push({
|
||||||
dimensionId: Number(dimensionId),
|
dimensionId: Number(dimensionId),
|
||||||
necessary: false,
|
necessary: false,
|
||||||
|
inheritFromModel: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return relationList;
|
return relationList;
|
||||||
@@ -175,15 +194,7 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
<Transfer
|
<Transfer
|
||||||
showSearch
|
showSearch
|
||||||
titles={['未关联维度', '已关联维度']}
|
titles={['未关联维度', '已关联维度']}
|
||||||
dataSource={dimensionList.map((item) => {
|
dataSource={transferData}
|
||||||
const transType = TransType.DIMENSION;
|
|
||||||
const { id } = item;
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
transType,
|
|
||||||
key: `${id}`,
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
listStyle={{
|
listStyle={{
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 600,
|
height: 600,
|
||||||
@@ -207,11 +218,16 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
onItemSelectAll,
|
onItemSelectAll,
|
||||||
onItemSelect,
|
onItemSelect,
|
||||||
selectedKeys: listSelectedKeys,
|
selectedKeys: listSelectedKeys,
|
||||||
|
disabled: listDisabled,
|
||||||
}) => {
|
}) => {
|
||||||
const columns = direction === 'left' ? leftColumns : rightColumns;
|
const columns = direction === 'left' ? leftColumns : rightColumns;
|
||||||
const rowSelection: TableRowSelection<TransferItem> = {
|
const rowSelection: TableRowSelection<TransferItem> = {
|
||||||
|
getCheckboxProps: (item) => ({ disabled: listDisabled || item.disabled }),
|
||||||
onSelectAll(selected, selectedRows) {
|
onSelectAll(selected, selectedRows) {
|
||||||
const treeSelectedKeys = selectedRows.map(({ key }) => key);
|
const treeSelectedKeys = selectedRows
|
||||||
|
.filter((item) => !item.disabled)
|
||||||
|
.map(({ key }) => key);
|
||||||
|
|
||||||
const diffKeys = selected
|
const diffKeys = selected
|
||||||
? difference(treeSelectedKeys, listSelectedKeys)
|
? difference(treeSelectedKeys, listSelectedKeys)
|
||||||
: difference(listSelectedKeys, treeSelectedKeys);
|
: difference(listSelectedKeys, treeSelectedKeys);
|
||||||
@@ -221,6 +237,16 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
onItemSelect(key as string, selected);
|
onItemSelect(key as string, selected);
|
||||||
},
|
},
|
||||||
selectedRowKeys: listSelectedKeys,
|
selectedRowKeys: listSelectedKeys,
|
||||||
|
renderCell: function (checked, record, index, originNode) {
|
||||||
|
if (checkedMap[record.id]?.inheritFromModel === true) {
|
||||||
|
return (
|
||||||
|
<Tooltip title="来自模型默认设置维度">
|
||||||
|
<EnvironmentOutlined style={{ color: '#0958d9' }} />
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return originNode;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -229,10 +255,17 @@ const DimensionMetricRelationTableTransfer: React.FC<Props> = ({
|
|||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={filteredItems as any}
|
dataSource={filteredItems as any}
|
||||||
size="small"
|
size="small"
|
||||||
|
rowClassName={(record) => {
|
||||||
|
if (checkedMap[record.id]?.inheritFromModel) {
|
||||||
|
return 'inherit-from-model-row';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{ y: 450 }}
|
scroll={{ y: 450 }}
|
||||||
onRow={({ key }) => ({
|
onRow={({ key, disabled: itemDisabled }) => ({
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
if (itemDisabled || listDisabled) return;
|
||||||
onItemSelect(key as string, !listSelectedKeys.includes(key as string));
|
onItemSelect(key as string, !listSelectedKeys.includes(key as string));
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const titleRender = (node: any) => {
|
const titleRender = (node: any) => {
|
||||||
const { id, name, path, hasEditPermission } = node as any;
|
const { id, name, path, hasEditPermission, parentId } = node as any;
|
||||||
return (
|
return (
|
||||||
<div className={styles.projectItem}>
|
<div className={styles.projectItem}>
|
||||||
<span
|
<span
|
||||||
@@ -126,7 +126,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
|||||||
{name}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
{createDomainBtnVisible && hasEditPermission && (
|
{createDomainBtnVisible && hasEditPermission && (
|
||||||
<span className={styles.operation}>
|
<span className={`${styles.operation} ${parentId ? styles.rowHover : ''}`}>
|
||||||
{Array.isArray(path) && path.length < 2 && (
|
{Array.isArray(path) && path.length < 2 && (
|
||||||
<PlusOutlined
|
<PlusOutlined
|
||||||
className={styles.icon}
|
className={styles.icon}
|
||||||
@@ -179,7 +179,7 @@ const DomainListTree: FC<DomainListProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.domainList}>
|
<div className={styles.domainList}>
|
||||||
<div className={styles.searchContainer}>
|
<div className={styles.searchContainer}>
|
||||||
<Row style={{ gap: 20 }}>
|
<Row style={{ gap: 10 }}>
|
||||||
<Col flex="1 1 215px">
|
<Col flex="1 1 215px">
|
||||||
<Search
|
<Search
|
||||||
allowClear
|
allowClear
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import { HomeOutlined, FundViewOutlined } from '@ant-design/icons';
|
|||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import SemanticGraphCanvas from '../SemanticGraphCanvas';
|
import SemanticGraphCanvas from '../SemanticGraphCanvas';
|
||||||
import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
|
import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
|
||||||
import CommonDimensionTable from './CommonDimension/CommonDimensionTable';
|
import View from '../View';
|
||||||
import DatabaseTable from '../components/Database/DatabaseTable';
|
// import CommonDimensionTable from './CommonDimension/CommonDimensionTable';
|
||||||
|
// import DatabaseTable from '../components/Database/DatabaseTable';
|
||||||
|
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
|
|
||||||
@@ -54,6 +55,18 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '视图管理',
|
||||||
|
key: 'viewManange',
|
||||||
|
children: (
|
||||||
|
<View
|
||||||
|
modelList={modelList}
|
||||||
|
onModelChange={(model) => {
|
||||||
|
handleModelChange(model);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '画布',
|
label: '画布',
|
||||||
key: 'xflow',
|
key: 'xflow',
|
||||||
@@ -68,11 +81,11 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
key: 'permissonSetting',
|
key: 'permissonSetting',
|
||||||
children: <PermissionSection permissionTarget={'domain'} />,
|
children: <PermissionSection permissionTarget={'domain'} />,
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
label: '数据库管理',
|
// label: '数据库管理',
|
||||||
key: 'database',
|
// key: 'database',
|
||||||
children: <DatabaseTable />,
|
// children: <DatabaseTable />,
|
||||||
},
|
// },
|
||||||
// {
|
// {
|
||||||
// label: '公共维度',
|
// label: '公共维度',
|
||||||
// key: 'commonDimension',
|
// key: 'commonDimension',
|
||||||
@@ -88,12 +101,12 @@ const DomainManagerTab: React.FC<Props> = ({
|
|||||||
|
|
||||||
const isModelItem = [
|
const isModelItem = [
|
||||||
{
|
{
|
||||||
label: '指标',
|
label: '指标管理',
|
||||||
key: 'metric',
|
key: 'metric',
|
||||||
children: <ClassMetricTable />,
|
children: <ClassMetricTable />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '维度',
|
label: '维度管理',
|
||||||
key: 'dimenstion',
|
key: 'dimenstion',
|
||||||
children: <ClassDimensionTable />,
|
children: <ClassDimensionTable />,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,12 +23,8 @@ type Props = {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
// type TaskStateMap = Record<string, DictTaskState>;
|
|
||||||
|
|
||||||
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
||||||
// domainManger,
|
|
||||||
knowledgeInfosMap,
|
knowledgeInfosMap,
|
||||||
// onKnowledgeInfosMapChange,
|
|
||||||
...restProps
|
...restProps
|
||||||
}) => {
|
}) => {
|
||||||
let rightColumns: ColumnsType<RecordType> = [
|
let rightColumns: ColumnsType<RecordType> = [
|
||||||
@@ -58,6 +54,11 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
|
|||||||
return <TransTypeTag type={type} />;
|
return <TransTypeTag type={type} />;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
dataIndex: 'modelName',
|
||||||
|
title: '所属模型',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
if (!knowledgeInfosMap) {
|
if (!knowledgeInfosMap) {
|
||||||
rightColumns = leftColumns;
|
rightColumns = leftColumns;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { IChatConfig } from '../../data';
|
|||||||
import DimensionMetricVisibleTableTransfer from './DimensionMetricVisibleTableTransfer';
|
import DimensionMetricVisibleTableTransfer from './DimensionMetricVisibleTableTransfer';
|
||||||
|
|
||||||
interface RecordType {
|
interface RecordType {
|
||||||
key: string;
|
key: React.Key;
|
||||||
name: string;
|
name: string;
|
||||||
type: 'dimension' | 'metric';
|
type: 'dimension' | 'metric';
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,10 @@ interface RecordType {
|
|||||||
type Props = {
|
type Props = {
|
||||||
knowledgeInfosMap?: IChatConfig.IKnowledgeInfosItemMap;
|
knowledgeInfosMap?: IChatConfig.IKnowledgeInfosItemMap;
|
||||||
sourceList: any[];
|
sourceList: any[];
|
||||||
targetList: string[];
|
targetList: React.Key[];
|
||||||
titles?: string[];
|
titles?: string[];
|
||||||
onKnowledgeInfosMapChange?: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
onKnowledgeInfosMapChange?: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
|
||||||
onChange?: (params?: any) => void;
|
onChange?: (params?: any) => void;
|
||||||
// transferProps?: Record<string, any>;
|
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,25 +28,27 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
|
|||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const [transferData, setTransferData] = useState<RecordType[]>([]);
|
const [transferData, setTransferData] = useState<RecordType[]>([]);
|
||||||
const [targetKeys, setTargetKeys] = useState<string[]>(targetList);
|
const [targetKeys, setTargetKeys] = useState<React.Key[]>(targetList);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTransferData(
|
setTransferData(
|
||||||
sourceList.map(({ key, id, name, bizName, transType }) => {
|
sourceList.map(({ key, id, name, bizName, transType, modelName }) => {
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
name,
|
name,
|
||||||
bizName,
|
bizName,
|
||||||
id,
|
id,
|
||||||
|
modelName,
|
||||||
type: transType,
|
type: transType,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, [sourceList]);
|
const keyList: React.Key[] = sourceList.map((item) => item.key);
|
||||||
|
const filterTargetList = targetList.filter((key: React.Key) => {
|
||||||
useEffect(() => {
|
return keyList.includes(key);
|
||||||
setTargetKeys(targetList);
|
});
|
||||||
}, [targetList]);
|
setTargetKeys(filterTargetList);
|
||||||
|
}, [sourceList, targetList]);
|
||||||
|
|
||||||
const handleChange = (newTargetKeys: string[]) => {
|
const handleChange = (newTargetKeys: string[]) => {
|
||||||
setTargetKeys(newTargetKeys);
|
setTargetKeys(newTargetKeys);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { message, Space } from 'antd';
|
import { message, Space } from 'antd';
|
||||||
import { useEffect, useState, forwardRef, useImperativeHandle, Ref } from 'react';
|
import { useEffect, useState, forwardRef, useImperativeHandle, Ref } from 'react';
|
||||||
import type { ForwardRefRenderFunction } from 'react';
|
|
||||||
import type { Dispatch } from 'umi';
|
import type { Dispatch } from 'umi';
|
||||||
import { connect } from 'umi';
|
import { connect } from 'umi';
|
||||||
import type { StateType } from '../../model';
|
import type { StateType } from '../../model';
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { SENSITIVE_LEVEL_OPTIONS, METRIC_DEFINE_TYPE } from '../constant';
|
|||||||
import { formLayout } from '@/components/FormHelper/utils';
|
import { formLayout } from '@/components/FormHelper/utils';
|
||||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import { getMetricsToCreateNewMetric, getModelDetail } from '../service';
|
import { getMetricsToCreateNewMetric, getModelDetail, getDrillDownDimension } from '../service';
|
||||||
import MetricMetricFormTable from './MetricMetricFormTable';
|
import MetricMetricFormTable from './MetricMetricFormTable';
|
||||||
import MetricFieldFormTable from './MetricFieldFormTable';
|
import MetricFieldFormTable from './MetricFieldFormTable';
|
||||||
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
import DimensionAndMetricRelationModal from './DimensionAndMetricRelationModal';
|
||||||
@@ -31,7 +31,6 @@ import TableTitleTooltips from '../components/TableTitleTooltips';
|
|||||||
import { createMetric, updateMetric, mockMetricAlias, getMetricTags } from '../service';
|
import { createMetric, updateMetric, mockMetricAlias, getMetricTags } from '../service';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
|
|
||||||
export type CreateFormProps = {
|
export type CreateFormProps = {
|
||||||
datasourceId?: number;
|
datasourceId?: number;
|
||||||
@@ -113,7 +112,11 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
|
|
||||||
const [drillDownDimensions, setDrillDownDimensions] = useState<
|
const [drillDownDimensions, setDrillDownDimensions] = useState<
|
||||||
ISemantic.IDrillDownDimensionItem[]
|
ISemantic.IDrillDownDimensionItem[]
|
||||||
>(metricItem?.relateDimension?.drillDownDimensions || []);
|
>([]);
|
||||||
|
|
||||||
|
const [drillDownDimensionsConfig, setDrillDownDimensionsConfig] = useState<
|
||||||
|
ISemantic.IDrillDownDimensionItem[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const forward = () => setCurrentStep(currentStep + 1);
|
const forward = () => setCurrentStep(currentStep + 1);
|
||||||
const backward = () => setCurrentStep(currentStep - 1);
|
const backward = () => setCurrentStep(currentStep - 1);
|
||||||
@@ -139,6 +142,17 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
setClassMeasureList([]);
|
setClassMeasureList([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queryDrillDownDimension = async (metricId: number) => {
|
||||||
|
const { code, data, msg } = await getDrillDownDimension(metricId);
|
||||||
|
if (code === 200 && Array.isArray(data)) {
|
||||||
|
setDrillDownDimensionsConfig(data);
|
||||||
|
}
|
||||||
|
if (code !== 200) {
|
||||||
|
message.error(msg);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
queryModelDetail();
|
queryModelDetail();
|
||||||
queryMetricsToCreateNewMetric();
|
queryMetricsToCreateNewMetric();
|
||||||
@@ -234,6 +248,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
setDefineType(metricDefineType);
|
setDefineType(metricDefineType);
|
||||||
setIsPercentState(isPercent);
|
setIsPercentState(isPercent);
|
||||||
setIsDecimalState(isDecimal);
|
setIsDecimalState(isDecimal);
|
||||||
|
queryDrillDownDimension(metricItem?.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -719,7 +734,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
</Form>
|
</Form>
|
||||||
<DimensionAndMetricRelationModal
|
<DimensionAndMetricRelationModal
|
||||||
metricItem={metricItem}
|
metricItem={metricItem}
|
||||||
relationsInitialValue={drillDownDimensions}
|
relationsInitialValue={drillDownDimensionsConfig}
|
||||||
open={metricRelationModalOpenState}
|
open={metricRelationModalOpenState}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setMetricRelationModalOpenState(false);
|
setMetricRelationModalOpenState(false);
|
||||||
@@ -728,6 +743,9 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
|
|||||||
setDrillDownDimensions(relations);
|
setDrillDownDimensions(relations);
|
||||||
setMetricRelationModalOpenState(false);
|
setMetricRelationModalOpenState(false);
|
||||||
}}
|
}}
|
||||||
|
onRefreshRelationData={() => {
|
||||||
|
queryDrillDownDimension(metricItem?.id);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Space, Tag, Typography } from 'antd';
|
import { Space, Tag, Typography } from 'antd';
|
||||||
import { StatusEnum } from '../enum';
|
import { StatusEnum } from '../enum';
|
||||||
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_COLOR } from '../constant';
|
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_COLOR } from '../constant';
|
||||||
import { TagsOutlined, UserOutlined, FieldNumberOutlined, ReadOutlined } from '@ant-design/icons';
|
import { TagsOutlined, ReadOutlined } from '@ant-design/icons';
|
||||||
import { history } from 'umi';
|
import { history } from 'umi';
|
||||||
import { ISemantic } from '../data';
|
import { ISemantic } from '../data';
|
||||||
import { isString } from 'lodash';
|
import { isString } from 'lodash';
|
||||||
@@ -9,51 +9,167 @@ import { isArrayOfValues } from '@/utils/utils';
|
|||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
import MetricStar from '../Metric/components/MetricStar';
|
import MetricStar from '../Metric/components/MetricStar';
|
||||||
|
|
||||||
const { Text, Link } = Typography;
|
const { Text, Paragraph } = Typography;
|
||||||
|
|
||||||
export const ColumnsConfig = {
|
export const ColumnsConfig = {
|
||||||
metricInfo: {
|
description: {
|
||||||
render: (_, record: ISemantic.IMetricItem) => {
|
render: (_, record: ISemantic.IMetricItem) => {
|
||||||
const { name, alias, bizName, sensitiveLevel, createdBy, tags, id, isCollect } = record;
|
const { description } = record;
|
||||||
|
return (
|
||||||
|
<Paragraph
|
||||||
|
ellipsis={{ tooltip: description, rows: 3 }}
|
||||||
|
style={{ width: 250, marginBottom: 0 }}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</Paragraph>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dimensionInfo: {
|
||||||
|
render: (_, record: ISemantic.IDimensionItem) => {
|
||||||
|
const { name, alias, bizName } = record;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<Space>
|
<Space>
|
||||||
<MetricStar metricId={id} initState={isCollect} />
|
<span style={{ fontWeight: 500 }}>{name}</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5, marginLeft: 0 }}>
|
||||||
|
{bizName}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{alias && (
|
||||||
|
<div style={{ marginTop: 8 }}>
|
||||||
|
<Space direction="vertical" size={4}>
|
||||||
|
{alias && (
|
||||||
|
<Space size={4} style={{ color: '#5f748d', fontSize: 12, margin: '5px 0 5px 0' }}>
|
||||||
|
<ReadOutlined />
|
||||||
|
<div style={{ width: 'max-content' }}>别名:</div>
|
||||||
|
<span style={{ marginLeft: 2 }}>
|
||||||
|
<Space size={[0, 8]} wrap>
|
||||||
|
{isString(alias) &&
|
||||||
|
alias.split(',').map((aliasName: string) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color="#eee"
|
||||||
|
key={aliasName}
|
||||||
|
style={{
|
||||||
|
borderRadius: 44,
|
||||||
|
maxWidth: 90,
|
||||||
|
minWidth: 40,
|
||||||
|
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
maxWidth: 80,
|
||||||
|
color: 'rgb(95, 116, 141)',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
ellipsis={{ tooltip: aliasName }}
|
||||||
|
>
|
||||||
|
{aliasName}
|
||||||
|
</Text>
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metricInfo: {
|
||||||
|
render: (_, record: ISemantic.IMetricItem) => {
|
||||||
|
const { name, alias, bizName, tags, id, isCollect } = record;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<Space>
|
||||||
<a
|
<a
|
||||||
className={styles.textLink}
|
className={styles.textLink}
|
||||||
style={{ fontWeight: 500, marginRight: 10 }}
|
style={{ fontWeight: 500 }}
|
||||||
onClick={() => {
|
onClick={(event: any) => {
|
||||||
history.push(`/metric/detail/${id}`);
|
history.push(`/metric/detail/${id}`);
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
}}
|
}}
|
||||||
|
href={`/webapp/metric/detail/${id}`}
|
||||||
>
|
>
|
||||||
{name}
|
{name}
|
||||||
</a>
|
</a>
|
||||||
<Tag
|
<MetricStar metricId={id} initState={isCollect} />
|
||||||
|
{/* <Tag
|
||||||
color={SENSITIVE_LEVEL_COLOR[sensitiveLevel]}
|
color={SENSITIVE_LEVEL_COLOR[sensitiveLevel]}
|
||||||
style={{ lineHeight: '16px', position: 'relative', top: '-1px' }}
|
style={{ lineHeight: '16px', position: 'relative', top: '-1px' }}
|
||||||
>
|
>
|
||||||
{SENSITIVE_LEVEL_ENUM[sensitiveLevel]}
|
{SENSITIVE_LEVEL_ENUM[sensitiveLevel]}
|
||||||
</Tag>
|
</Tag> */}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5 }}>{bizName}</div>
|
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5, marginLeft: 0 }}>
|
||||||
|
{bizName}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ marginTop: 8 }}>
|
{(alias || isArrayOfValues(tags)) && (
|
||||||
<Space direction="vertical" size={4}>
|
<div style={{ marginTop: 8 }}>
|
||||||
{alias && (
|
<Space direction="vertical" size={4}>
|
||||||
<Space size={4} style={{ color: '#5f748d', fontSize: 12 }}>
|
{alias && (
|
||||||
<ReadOutlined />
|
<Space size={4} style={{ color: '#5f748d', fontSize: 12, margin: '5px 0 5px 0' }}>
|
||||||
<div style={{ width: 'max-content' }}>别名:</div>
|
<ReadOutlined />
|
||||||
<span style={{ marginLeft: 2 }}>
|
<div style={{ width: 'max-content' }}>别名:</div>
|
||||||
<Space size={[0, 8]} wrap>
|
<span style={{ marginLeft: 2 }}>
|
||||||
{isString(alias) &&
|
<Space size={[0, 8]} wrap>
|
||||||
alias.split(',').map((aliasName: string) => {
|
{isString(alias) &&
|
||||||
|
alias.split(',').map((aliasName: string) => {
|
||||||
|
return (
|
||||||
|
<Tag
|
||||||
|
color="#eee"
|
||||||
|
key={aliasName}
|
||||||
|
style={{
|
||||||
|
borderRadius: 44,
|
||||||
|
maxWidth: 90,
|
||||||
|
minWidth: 40,
|
||||||
|
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
maxWidth: 80,
|
||||||
|
color: 'rgb(95, 116, 141)',
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 12,
|
||||||
|
}}
|
||||||
|
ellipsis={{ tooltip: aliasName }}
|
||||||
|
>
|
||||||
|
{aliasName}
|
||||||
|
</Text>
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Space>
|
||||||
|
</span>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isArrayOfValues(tags) && (
|
||||||
|
<Space size={4} style={{ color: '#5f748d', fontSize: 12, margin: '5px 0 5px 0' }}>
|
||||||
|
<TagsOutlined />
|
||||||
|
<div style={{ width: 'max-content' }}>标签:</div>
|
||||||
|
<span style={{ marginLeft: 2 }}>
|
||||||
|
<Space size={[0, 8]} wrap>
|
||||||
|
{tags.map((tag: string) => {
|
||||||
return (
|
return (
|
||||||
<Tag
|
<Tag
|
||||||
color="#eee"
|
color="#eee"
|
||||||
key={aliasName}
|
key={tag}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 44,
|
borderRadius: 44,
|
||||||
maxWidth: 90,
|
maxWidth: 90,
|
||||||
@@ -68,93 +184,90 @@ export const ColumnsConfig = {
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
}}
|
}}
|
||||||
ellipsis={{ tooltip: aliasName }}
|
ellipsis={{ tooltip: tag }}
|
||||||
>
|
>
|
||||||
{aliasName}
|
{tag}
|
||||||
</Text>
|
</Text>
|
||||||
</Tag>
|
</Tag>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Space>
|
</Space>
|
||||||
</span>
|
</span>
|
||||||
</Space>
|
</Space>
|
||||||
)}
|
)}
|
||||||
|
{/* <Space size={10}>
|
||||||
{isArrayOfValues(tags) && (
|
<Space
|
||||||
<Space size={2} style={{ color: '#5f748d', fontSize: 12 }}>
|
size={2}
|
||||||
<TagsOutlined />
|
style={{ color: '#5f748d', fontSize: 12, position: 'relative', top: '1px' }}
|
||||||
<div style={{ width: 'max-content' }}>标签:</div>
|
>
|
||||||
<span style={{ marginLeft: 2 }}>
|
<FieldNumberOutlined style={{ fontSize: 16, position: 'relative', top: '1px' }} />
|
||||||
<Space size={[0, 8]} wrap>
|
<span>:</span>
|
||||||
{tags.map((tag: string) => {
|
<span style={{ marginLeft: 0 }}>{id}</span>
|
||||||
return (
|
</Space>
|
||||||
<Tag
|
<Space size={2} style={{ color: '#5f748d', fontSize: 12 }}>
|
||||||
color="#eee"
|
<UserOutlined />
|
||||||
key={tag}
|
<span>:</span>
|
||||||
style={{
|
<span style={{ marginLeft: 0 }}>{createdBy}</span>
|
||||||
borderRadius: 44,
|
</Space>
|
||||||
maxWidth: 90,
|
</Space> */}
|
||||||
minWidth: 40,
|
|
||||||
backgroundColor: 'rgba(18, 31, 67, 0.04)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
maxWidth: 80,
|
|
||||||
color: 'rgb(95, 116, 141)',
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 12,
|
|
||||||
}}
|
|
||||||
ellipsis={{ tooltip: tag }}
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</Text>
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Space>
|
|
||||||
</span>
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
<Space size={10}>
|
|
||||||
<Space
|
|
||||||
size={2}
|
|
||||||
style={{ color: '#5f748d', fontSize: 12, position: 'relative', top: '1px' }}
|
|
||||||
>
|
|
||||||
<FieldNumberOutlined style={{ fontSize: 16, position: 'relative', top: '1px' }} />
|
|
||||||
<span>:</span>
|
|
||||||
<span style={{ marginLeft: 0 }}>{id}</span>
|
|
||||||
</Space>
|
|
||||||
<Space size={2} style={{ color: '#5f748d', fontSize: 12 }}>
|
|
||||||
<UserOutlined />
|
|
||||||
<span>:</span>
|
|
||||||
<span style={{ marginLeft: 0 }}>{createdBy}</span>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
</Space>
|
||||||
</Space>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sensitiveLevel: {
|
||||||
|
render: (_, record: ISemantic.IMetricItem) => {
|
||||||
|
const { sensitiveLevel } = record;
|
||||||
|
return SENSITIVE_LEVEL_COLOR[sensitiveLevel] ? (
|
||||||
|
<Tag
|
||||||
|
color={SENSITIVE_LEVEL_COLOR[sensitiveLevel]}
|
||||||
|
style={{
|
||||||
|
borderRadius: '40px',
|
||||||
|
padding: '2px 16px',
|
||||||
|
fontSize: '13px',
|
||||||
|
// color: '#8ca3ba',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{SENSITIVE_LEVEL_ENUM[sensitiveLevel]}
|
||||||
|
</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
borderRadius: '40px',
|
||||||
|
padding: '2px 16px',
|
||||||
|
fontSize: '13px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
未知
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
state: {
|
state: {
|
||||||
render: (status) => {
|
render: (status) => {
|
||||||
let tagProps = {
|
let tagProps: { color: string; label: string; style?: any } = {
|
||||||
color: 'default',
|
color: 'default',
|
||||||
label: '未知',
|
label: '未知',
|
||||||
|
style: {},
|
||||||
};
|
};
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case StatusEnum.ONLINE:
|
case StatusEnum.ONLINE:
|
||||||
tagProps = {
|
tagProps = {
|
||||||
color: 'success',
|
// color: 'success',
|
||||||
// color: '#87d068',
|
color: 'geekblue',
|
||||||
label: '已启用',
|
label: '已启用',
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case StatusEnum.OFFLINE:
|
case StatusEnum.OFFLINE:
|
||||||
tagProps = {
|
tagProps = {
|
||||||
color: 'warning',
|
color: 'default',
|
||||||
label: '未启用',
|
label: '未启用',
|
||||||
|
style: {
|
||||||
|
color: 'rgb(95, 116, 141)',
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case StatusEnum.INITIALIZED:
|
case StatusEnum.INITIALIZED:
|
||||||
@@ -180,6 +293,7 @@ export const ColumnsConfig = {
|
|||||||
padding: '2px 16px',
|
padding: '2px 16px',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
...tagProps.style,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{tagProps.label}
|
{tagProps.label}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { connect } from 'umi';
|
|||||||
import type { StateType } from '../model';
|
import type { StateType } from '../model';
|
||||||
import { deleteModel, updateModel } from '../service';
|
import { deleteModel, updateModel } from '../service';
|
||||||
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
|
import ClassDataSourceTypeModal from './ClassDataSourceTypeModal';
|
||||||
|
import { ColumnsConfig } from './MetricTableColumnRender';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import styles from './style.less';
|
import styles from './style.less';
|
||||||
@@ -85,20 +86,7 @@ const ModelTable: React.FC<Props> = ({ modelList, disabledEdit = false, onModelC
|
|||||||
dataIndex: 'status',
|
dataIndex: 'status',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
search: false,
|
search: false,
|
||||||
render: (status) => {
|
render: ColumnsConfig.state.render,
|
||||||
switch (status) {
|
|
||||||
case StatusEnum.ONLINE:
|
|
||||||
return <Tag color="success">已启用</Tag>;
|
|
||||||
case StatusEnum.OFFLINE:
|
|
||||||
return <Tag color="warning">未启用</Tag>;
|
|
||||||
case StatusEnum.INITIALIZED:
|
|
||||||
return <Tag color="processing">初始化</Tag>;
|
|
||||||
case StatusEnum.DELETED:
|
|
||||||
return <Tag color="default">已删除</Tag>;
|
|
||||||
default:
|
|
||||||
return <Tag color="default">未知</Tag>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dataIndex: 'createdBy',
|
dataIndex: 'createdBy',
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Props = {
|
|||||||
|
|
||||||
const OverView: React.FC<Props> = ({ modelList, disabledEdit = false, onModelChange }) => {
|
const OverView: React.FC<Props> = ({ modelList, disabledEdit = false, onModelChange }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px' }}>
|
<div style={{ padding: '15px 20px' }}>
|
||||||
<ModelTable modelList={modelList} disabledEdit={disabledEdit} onModelChange={onModelChange} />
|
<ModelTable modelList={modelList} disabledEdit={disabledEdit} onModelChange={onModelChange} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ const PermissionAdminForm: React.FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<SelectTMEPerson placeholder="请邀请团队成员" />
|
<SelectTMEPerson placeholder="请邀请团队成员" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* {APP_TARGET === 'inner' && ( */}
|
{/* {APP_TARGET === 'inner'} */}
|
||||||
<FormItem name="adminOrgs" label="按组织">
|
<FormItem name="adminOrgs" label="按组织">
|
||||||
<SelectPartner
|
<SelectPartner
|
||||||
type="selectedDepartment"
|
type="selectedDepartment"
|
||||||
@@ -111,7 +111,6 @@ const PermissionAdminForm: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* )} */}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={
|
label={
|
||||||
<FormItemTitle
|
<FormItemTitle
|
||||||
@@ -128,7 +127,7 @@ const PermissionAdminForm: React.FC<Props> = ({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
{!isOpenState && (
|
{!isOpenState && (
|
||||||
<>
|
<>
|
||||||
{/* {APP_TARGET === 'inner' && ( */}
|
{/* {APP_TARGET === 'inner' && } */}
|
||||||
<FormItem name="viewOrgs" label="按组织">
|
<FormItem name="viewOrgs" label="按组织">
|
||||||
<SelectPartner
|
<SelectPartner
|
||||||
type="selectedDepartment"
|
type="selectedDepartment"
|
||||||
@@ -137,7 +136,6 @@ const PermissionAdminForm: React.FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* )} */}
|
|
||||||
<FormItem name="viewers" label="按个人">
|
<FormItem name="viewers" label="按个人">
|
||||||
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const PermissionCreateDrawer: React.FC<Props> = ({
|
|||||||
const basicInfoFormRef = useRef<any>(null);
|
const basicInfoFormRef = useRef<any>(null);
|
||||||
const [selectedDimensionKeyList, setSelectedDimensionKeyList] = useState<string[]>([]);
|
const [selectedDimensionKeyList, setSelectedDimensionKeyList] = useState<string[]>([]);
|
||||||
const [selectedMetricKeyList, setSelectedMetricKeyList] = useState<string[]>([]);
|
const [selectedMetricKeyList, setSelectedMetricKeyList] = useState<string[]>([]);
|
||||||
|
|
||||||
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
|
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
|
||||||
|
|
||||||
const saveAuth = async () => {
|
const saveAuth = async () => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import styles from '../style.less';
|
|||||||
type Props = {
|
type Props = {
|
||||||
permissonData: any;
|
permissonData: any;
|
||||||
onSubmit?: (data?: any) => void;
|
onSubmit?: (data?: any) => void;
|
||||||
onValuesChange?: (value, values) => void;
|
onValuesChange?: (value: any, values: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
@@ -17,7 +17,6 @@ const PermissionCreateForm: ForwardRefRenderFunction<any, Props> = (
|
|||||||
{ permissonData, onValuesChange },
|
{ permissonData, onValuesChange },
|
||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const { APP_TARGET } = process.env;
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -51,7 +50,6 @@ const PermissionCreateForm: ForwardRefRenderFunction<any, Props> = (
|
|||||||
<FormItem name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
<FormItem name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
||||||
<Input placeholder="请输入名称" />
|
<Input placeholder="请输入名称" />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* {APP_TARGET === 'inner' && ( */}
|
|
||||||
<FormItem name="authorizedDepartmentIds" label="按组织">
|
<FormItem name="authorizedDepartmentIds" label="按组织">
|
||||||
<SelectPartner
|
<SelectPartner
|
||||||
type="selectedDepartment"
|
type="selectedDepartment"
|
||||||
@@ -60,7 +58,6 @@ const PermissionCreateForm: ForwardRefRenderFunction<any, Props> = (
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* )} */}
|
|
||||||
|
|
||||||
<FormItem name="authorizedUsers" label="按个人">
|
<FormItem name="authorizedUsers" label="按个人">
|
||||||
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
<SelectTMEPerson placeholder="请选择需要授权的个人" />
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Space } from 'antd';
|
||||||
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
|
import styles from './style.less';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
components: {
|
||||||
|
label: string;
|
||||||
|
component: ReactNode;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const TableHeaderFilter: React.FC<Props> = ({ components }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Space className={styles.tableHeaderTitle} size={20}>
|
||||||
|
{components.map(({ label, component }) => (
|
||||||
|
<Space size={8} key={`TableHeaderFilter-${label}`}>
|
||||||
|
<span className={styles.headerTitleLabel}>{label}:</span>
|
||||||
|
{component}
|
||||||
|
</Space>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableHeaderFilter;
|
||||||
@@ -75,44 +75,47 @@
|
|||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
// border-bottom: 1px solid #d9d9d9;
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
// width: calc(100% - 100px);
|
width: 205px;
|
||||||
width: 100%;
|
margin: 10px 0 10px 16px;
|
||||||
margin: 10px 0 10px 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree {
|
.tree {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
padding-right: 18px;
|
||||||
.projectItem {
|
.projectItem {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
line-height: 30px;
|
line-height: 25px;
|
||||||
height: 30px;
|
height: 25px;
|
||||||
|
color: rgb(95, 116, 141);
|
||||||
|
font-weight: 500;
|
||||||
.projectItemTitle {
|
.projectItemTitle {
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.operation {
|
.operation {
|
||||||
.icon {
|
.icon {
|
||||||
margin-left: 6px;
|
margin-left: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.rowHover {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
.rowHover {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,6 +182,8 @@
|
|||||||
}
|
}
|
||||||
.ant-tabs-tab {
|
.ant-tabs-tab {
|
||||||
padding-bottom:15px;
|
padding-bottom:15px;
|
||||||
|
color: rgb(95, 116, 141);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.backBtn {
|
.backBtn {
|
||||||
@@ -237,6 +242,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.disabledSearchTable {
|
||||||
|
padding-top:20px;
|
||||||
|
}
|
||||||
|
|
||||||
.classTable {
|
.classTable {
|
||||||
// padding: 0 20px;
|
// padding: 0 20px;
|
||||||
:global {
|
:global {
|
||||||
@@ -420,3 +429,14 @@
|
|||||||
color: #69b1ff;
|
color: #69b1ff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tableHeaderTitle {
|
||||||
|
margin-left: 20px;
|
||||||
|
.headerTitleLabel {
|
||||||
|
color: #667085;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
width: max-content;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,9 +37,11 @@ export const IS_TAG_ENUM = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SENSITIVE_LEVEL_COLOR = {
|
export const SENSITIVE_LEVEL_COLOR = {
|
||||||
[SENSITIVE_LEVEL.LOW]: 'geekblue',
|
[SENSITIVE_LEVEL.LOW]: 'default',
|
||||||
[SENSITIVE_LEVEL.MID]: 'warning',
|
// [SENSITIVE_LEVEL.MID]: 'orange',
|
||||||
[SENSITIVE_LEVEL.HIGH]: 'error',
|
[SENSITIVE_LEVEL.MID]: 'geekblue',
|
||||||
|
[SENSITIVE_LEVEL.HIGH]: 'volcano',
|
||||||
|
// [SENSITIVE_LEVEL.HIGH]: '#1677ff',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SEMANTIC_NODE_TYPE_CONFIG = {
|
export const SEMANTIC_NODE_TYPE_CONFIG = {
|
||||||
|
|||||||
@@ -139,6 +139,31 @@ export declare namespace ISemantic {
|
|||||||
updatedAt: ISODateString;
|
updatedAt: ISODateString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IViewModelConfigItem {
|
||||||
|
id: number;
|
||||||
|
includesAll: boolean;
|
||||||
|
metrics: number[];
|
||||||
|
dimensions: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IViewItem {
|
||||||
|
createdBy: UserName;
|
||||||
|
updatedBy: UserName;
|
||||||
|
createdAt: ISODateString;
|
||||||
|
updatedAt: ISODateString;
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
bizName: string;
|
||||||
|
description: string;
|
||||||
|
status?: StatusEnum;
|
||||||
|
typeEnum?: any;
|
||||||
|
sensitiveLevel: number;
|
||||||
|
domainId: number;
|
||||||
|
viewDetail: {
|
||||||
|
viewModelConfigs: IViewModelConfigItem[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface IDimensionItem {
|
interface IDimensionItem {
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
updatedBy: string;
|
updatedBy: string;
|
||||||
@@ -209,6 +234,7 @@ export declare namespace ISemantic {
|
|||||||
|
|
||||||
interface IDrillDownDimensionItem {
|
interface IDrillDownDimensionItem {
|
||||||
dimensionId: number;
|
dimensionId: number;
|
||||||
|
inheritFromModel?: boolean;
|
||||||
necessary?: boolean;
|
necessary?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ export enum ChatConfigType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum TransType {
|
export enum TransType {
|
||||||
DIMENSION = 'dimension',
|
DIMENSION = 'DIMENSION',
|
||||||
METRIC = 'metric',
|
METRIC = 'METRIC',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SemanticNodeType {
|
export enum SemanticNodeType {
|
||||||
DATASOURCE = 'datasource',
|
DATASOURCE = 'DATASOURCE',
|
||||||
DIMENSION = 'dimension',
|
DIMENSION = 'DIMENSION',
|
||||||
METRIC = 'metric',
|
METRIC = 'METRIC',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MetricTypeWording {
|
export enum MetricTypeWording {
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ export async function queryStruct({
|
|||||||
args: null,
|
args: null,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
orders: [],
|
orders: [{ column: dateField, direction: 'desc' }],
|
||||||
metricFilters: [],
|
metricFilters: [],
|
||||||
params: [],
|
params: [],
|
||||||
dateInfo: {
|
dateInfo: {
|
||||||
@@ -560,3 +560,29 @@ export function getDatabaseParameters(): Promise<any> {
|
|||||||
export function getDatabaseDetail(id: number): Promise<any> {
|
export function getDatabaseDetail(id: number): Promise<any> {
|
||||||
return request.get(`${process.env.API_BASE_URL}database/${id}`);
|
return request.get(`${process.env.API_BASE_URL}database/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getViewList(domainId: number): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}view/getViewList`, {
|
||||||
|
method: 'GET',
|
||||||
|
params: { domainId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createView(data: any): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}view`, {
|
||||||
|
method: 'POST',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function updateView(data: any): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}view`, {
|
||||||
|
method: 'PUT',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteView(viewId: number): Promise<any> {
|
||||||
|
return request(`${process.env.API_BASE_URL}view/${viewId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ISemantic } from './data';
|
|||||||
import type { DataNode } from 'antd/lib/tree';
|
import type { DataNode } from 'antd/lib/tree';
|
||||||
import { Form, Input, InputNumber, Switch, Select } from 'antd';
|
import { Form, Input, InputNumber, Switch, Select } from 'antd';
|
||||||
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
|
||||||
|
import DisabledWheelNumberInput from '@/components/DisabledWheelNumberInput';
|
||||||
import { ConfigParametersItem } from '../System/types';
|
import { ConfigParametersItem } from '../System/types';
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
@@ -20,7 +21,7 @@ export const changeTreeData = (treeData: API.DomainList, auth?: boolean): DataNo
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const addPathInTreeData = (treeData: API.DomainList, loopPath: any[] = []): any => {
|
export const addPathInTreeData = (treeData: API.DomainList, loopPath: any[] = []): any => {
|
||||||
return treeData.map((item: any) => {
|
return treeData?.map((item: any) => {
|
||||||
const { children, parentId = [] } = item;
|
const { children, parentId = [] } = item;
|
||||||
const path = loopPath.slice();
|
const path = loopPath.slice();
|
||||||
path.push(parentId);
|
path.push(parentId);
|
||||||
@@ -39,7 +40,7 @@ export const addPathInTreeData = (treeData: API.DomainList, loopPath: any[] = []
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const constructorClassTreeFromList = (list: any[], parentId: number = 0) => {
|
export const constructorClassTreeFromList = (list: any[], parentId: number = 0) => {
|
||||||
const tree = list.reduce((nodeList, nodeItem) => {
|
const tree = list?.reduce((nodeList, nodeItem) => {
|
||||||
if (nodeItem.parentId == parentId) {
|
if (nodeItem.parentId == parentId) {
|
||||||
const children = constructorClassTreeFromList(list, nodeItem.id);
|
const children = constructorClassTreeFromList(list, nodeItem.id);
|
||||||
if (children.length) {
|
if (children.length) {
|
||||||
@@ -47,7 +48,7 @@ export const constructorClassTreeFromList = (list: any[], parentId: number = 0)
|
|||||||
}
|
}
|
||||||
nodeItem.key = nodeItem.id;
|
nodeItem.key = nodeItem.id;
|
||||||
nodeItem.value = nodeItem.id;
|
nodeItem.value = nodeItem.id;
|
||||||
nodeItem.title = nodeItem.name;
|
nodeItem.title = nodeItem.name || nodeItem.categoryName;
|
||||||
nodeList.push(nodeItem);
|
nodeList.push(nodeItem);
|
||||||
}
|
}
|
||||||
return nodeList;
|
return nodeList;
|
||||||
@@ -57,7 +58,7 @@ export const constructorClassTreeFromList = (list: any[], parentId: number = 0)
|
|||||||
|
|
||||||
export const treeParentKeyLists = (treeData: API.DomainList): string[] => {
|
export const treeParentKeyLists = (treeData: API.DomainList): string[] => {
|
||||||
let keys: string[] = [];
|
let keys: string[] = [];
|
||||||
treeData.forEach((item: any) => {
|
treeData?.forEach((item: any) => {
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
keys.push(item.id);
|
keys.push(item.id);
|
||||||
keys = keys.concat(treeParentKeyLists(item.children));
|
keys = keys.concat(treeParentKeyLists(item.children));
|
||||||
@@ -133,7 +134,8 @@ export const findLeafNodesFromDomainList = (
|
|||||||
|
|
||||||
export const genneratorFormItemList = (itemList: ConfigParametersItem[]) => {
|
export const genneratorFormItemList = (itemList: ConfigParametersItem[]) => {
|
||||||
return itemList.map((item) => {
|
return itemList.map((item) => {
|
||||||
const { dataType, name, comment, placeholder, description, require } = item;
|
const { dataType, name, comment, placeholder, description, require, value } = item;
|
||||||
|
|
||||||
let defaultItem = <Input />;
|
let defaultItem = <Input />;
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case 'string':
|
case 'string':
|
||||||
@@ -148,7 +150,10 @@ export const genneratorFormItemList = (itemList: ConfigParametersItem[]) => {
|
|||||||
defaultItem = <TextArea placeholder={placeholder} style={{ height: 100 }} />;
|
defaultItem = <TextArea placeholder={placeholder} style={{ height: 100 }} />;
|
||||||
break;
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
defaultItem = <InputNumber placeholder={placeholder} style={{ width: '100%' }} />;
|
// defaultItem = <InputNumber placeholder={placeholder} style={{ width: '100%' }} />;
|
||||||
|
defaultItem = (
|
||||||
|
<DisabledWheelNumberInput placeholder={placeholder} style={{ width: '100%' }} />
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 'bool':
|
case 'bool':
|
||||||
return (
|
return (
|
||||||
|
|||||||
BIN
webapp/packages/supersonic-fe/webapp_1225.zip
Normal file
BIN
webapp/packages/supersonic-fe/webapp_1225.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user