[improvement][headless-fe] Revamped the interaction for semantic modeling routing and successfully implemented the switching between dimension and dataset management. (#1934)

Co-authored-by: tristanliu <tristanliu@tencent.com>
This commit is contained in:
Jun Zhang
2024-11-30 20:03:41 +08:00
committed by GitHub
parent 593597fe26
commit 82c63a7f22
42 changed files with 1889 additions and 997 deletions

View File

@@ -2,7 +2,7 @@ import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { ProTable } from '@ant-design/pro-components';
import { message, Button, Space, Popconfirm, Input, Tag, Select } from 'antd';
import React, { useRef, useState, useEffect } from 'react';
import { useModel } from '@umijs/max';
import { useModel, history } from '@umijs/max';
import { StatusEnum, SemanticNodeType } from '../enum';
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_OPTIONS, TAG_DEFINE_TYPE } from '../constant';
import {
@@ -19,6 +19,7 @@ import TableHeaderFilter from '@/components/TableHeaderFilter';
import BatchCtrlDropDownButton from '@/components/BatchCtrlDropDownButton';
import { ColumnsConfig } from './TableColumnRender';
import BatchSensitiveLevelModal from '@/components/BatchCtrlDropDownButton/BatchSensitiveLevelModal';
import { toDimensionEditPage } from '@/pages/SemanticModel/utils';
import styles from './style.less';
type Props = {};
@@ -80,6 +81,9 @@ const ClassDimensionTable: React.FC<Props> = ({}) => {
};
const queryDataSourceList = async () => {
if (!domainId) {
return;
}
const { code, data, msg } = await getModelList(domainId);
if (code === 200) {
setDataSourceList(data);
@@ -94,7 +98,7 @@ const ClassDimensionTable: React.FC<Props> = ({}) => {
useEffect(() => {
queryDataSourceList();
}, [modelId]);
}, [domainId]);
const queryBatchUpdateStatus = async (ids: React.Key[], status: StatusEnum) => {
if (Array.isArray(ids) && ids.length === 0) {
@@ -137,7 +141,17 @@ const ClassDimensionTable: React.FC<Props> = ({}) => {
message.error(msg);
};
const columnsConfig = ColumnsConfig();
// const columnsConfig = ColumnsConfig();
const columnsConfig = ColumnsConfig({
indicatorInfo: {
url: '/model/dimension/:domainId/:modelId/:indicatorId',
onNameClick: (record) => {
const { id } = record;
toDimensionEditPage(domainId, modelId!, id);
return false;
},
},
});
const columns: ProColumns[] = [
{
@@ -216,8 +230,10 @@ const ClassDimensionTable: React.FC<Props> = ({}) => {
key="dimensionEditBtn"
type="link"
onClick={() => {
setDimensionItem(record);
setCreateModalVisible(true);
// setDimensionItem(record);
// setCreateModalVisible(true);
const { id } = record;
toDimensionEditPage(domainId, modelId!, id);
}}
>
@@ -423,8 +439,9 @@ const ClassDimensionTable: React.FC<Props> = ({}) => {
key="create"
type="primary"
onClick={() => {
setDimensionItem(undefined);
setCreateModalVisible(true);
toDimensionEditPage(domainId, modelId!, 0);
// setDimensionItem(undefined);
// setCreateModalVisible(true);
}}
>

View File

@@ -3,7 +3,7 @@ import { ProTable } from '@ant-design/pro-components';
import { message, Button, Space, Popconfirm, Input, Select, Tag } from 'antd';
import React, { useRef, useState, useEffect } from 'react';
import { StatusEnum, SemanticNodeType } from '../enum';
import { useModel, history } from '@umijs/max';
import { useModel } from '@umijs/max';
import { SENSITIVE_LEVEL_ENUM, SENSITIVE_LEVEL_OPTIONS, TAG_DEFINE_TYPE } from '../constant';
import {
queryMetric,
@@ -21,6 +21,7 @@ import TableHeaderFilter from '@/components/TableHeaderFilter';
import styles from './style.less';
import { ISemantic } from '../data';
import { ColumnsConfig } from './TableColumnRender';
import { toMetricEditPage } from '@/pages/SemanticModel/utils';
type Props = {
onEmptyMetricData?: () => void;
@@ -32,7 +33,7 @@ const ClassMetricTable: React.FC<Props> = ({ onEmptyMetricData }) => {
const metricModel = useModel('SemanticModel.metricData');
const { selectDomainId } = domainModel;
const { selectModelId: modelId } = modelModel;
const { MrefreshMetricList, selectMetric, setSelectMetric } = metricModel;
const { MrefreshMetricList, setSelectMetric } = metricModel;
const [batchSensitiveLevelOpenState, setBatchSensitiveLevelOpenState] = useState<boolean>(false);
const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
const [metricItem, setMetricItem] = useState<ISemantic.IMetricItem>();
@@ -145,8 +146,8 @@ const ClassMetricTable: React.FC<Props> = ({ onEmptyMetricData }) => {
const columnsConfig = ColumnsConfig({
indicatorInfo: {
url: '/model/metric/:domainId/:modelId/:indicatorId',
onNameClick: (record: ISemantic.IMetricItem) => {
setSelectMetric(record);
onNameClick: (record) => {
setSelectMetric(record as ISemantic.IMetricItem);
},
},
});
@@ -243,9 +244,8 @@ const ClassMetricTable: React.FC<Props> = ({ onEmptyMetricData }) => {
type="link"
key="metricEditBtn"
onClick={() => {
history.push(`/model/metric/${record.domainId}/${record.modelId}/${record.id}`);
// setMetricItem(record);
// setCreateModalVisible(true);
const { domainId, modelId, id } = record;
toMetricEditPage(domainId, modelId, id);
}}
>
@@ -460,8 +460,9 @@ const ClassMetricTable: React.FC<Props> = ({ onEmptyMetricData }) => {
key="create"
type="primary"
onClick={() => {
setMetricItem(undefined);
setCreateModalVisible(true);
toMetricEditPage(selectDomainId, modelId!, 0);
// setMetricItem(undefined);
// setCreateModalVisible(true);
}}
>

View File

@@ -0,0 +1,55 @@
import { Button } from 'antd';
import React, { useState, useEffect } from 'react';
import { MenuItem } from './type';
import styles from './style.less';
type Props = {
detailData?: any;
currentMenu: MenuItem;
onSave?: (data?: any) => void;
} & { children: React.ReactNode };
const DetailFormWrapper: React.FC<Props> = ({ children, currentMenu, onSave }) => {
const [settingKey, setSettingKey] = useState<string>(currentMenu?.key);
useEffect(() => {
if (currentMenu) {
setSettingKey(currentMenu.key);
}
}, [currentMenu]);
return (
<div className={styles.infoCard}>
<div className={styles.infoCardTitle}>
<span style={{ flex: 'auto' }}>{currentMenu?.text}</span>
<span style={{ flex: 'none' }}>
<Button
type="primary"
onClick={() => {
onSave?.();
}}
>
</Button>
{/* <Button
size="middle"
type="link"
key="backListBtn"
onClick={() => {
history.back();
}}
>
<Space>
<ArrowLeftOutlined />
返回列表页
</Space>
</Button> */}
</span>
</div>
<div className={styles.infoCardContainer}>{children}</div>
</div>
);
};
export default DetailFormWrapper;

View File

@@ -0,0 +1,154 @@
import { Tag, Space, Tooltip } from 'antd';
import React, { useState, useEffect } from 'react';
import dayjs from 'dayjs';
import {
ExportOutlined,
SolutionOutlined,
PartitionOutlined,
SettingOutlined,
} from '@ant-design/icons';
import styles from './style.less';
import IndicatorStar from '../IndicatorStar';
import { toDomainList, toModelList } from '@/pages/SemanticModel/utils';
import { MenuItem } from './type';
type Props = {
detailData: any;
menuKey: string;
menuList: MenuItem[];
onMenuKeyChange?: (key: string, item: MenuItem) => void;
};
const DetailSider: React.FC<Props> = ({ detailData, menuList, menuKey, onMenuKeyChange }) => {
const [settingKey, setSettingKey] = useState<string>(menuKey);
useEffect(() => {
if (menuKey) {
setSettingKey(menuKey);
}
}, [menuKey]);
return (
<div className={styles.DetailInfoSider}>
<div className={styles.sectionContainer}>
{detailData?.id ? (
<div className={styles.title}>
<div className={styles.name}>
<Space>
{detailData?.isCollect !== undefined ? (
<IndicatorStar indicatorId={detailData?.id} initState={detailData?.isCollect} />
) : (
<div style={{ width: 15 }}></div>
)}
{detailData?.name}
{detailData?.hasAdminRes && (
<span
className={styles.gotoMetricListIcon}
onClick={() => {
toModelList(detailData.domainId, detailData.modelId);
}}
>
<Tooltip title="前往所属模型指标列表">
<ExportOutlined />
</Tooltip>
</span>
)}
</Space>
</div>
{detailData?.bizName && <div className={styles.bizName}>{detailData.bizName}</div>}
</div>
) : (
<div className={styles.createTitle}>
<Space>
<SettingOutlined />
</Space>
</div>
)}
<hr className={styles.hr} />
<div className={styles.section} style={{ padding: '16px 0' }}>
<ul className={styles.settingList}>
{menuList.map((item) => {
return (
<li
className={item.key === settingKey ? styles.active : ''}
key={item.key}
onClick={() => {
onMenuKeyChange?.(item.key, item);
setSettingKey(item.key);
}}
>
<div className={styles.icon}>{item.icon}</div>
<div className={styles.content}>
<span className={styles.text}> {item.text}</span>
</div>
</li>
);
})}
</ul>
</div>
{detailData?.id && (
<div className={styles.section} style={{ marginTop: 'auto' }}>
<div className={styles.sectionTitleBox}>
<span className={styles.sectionTitle}>
<Space>
<SolutionOutlined />
</Space>
</span>
</div>
{detailData?.modelName && (
<div className={styles.item}>
<span className={styles.itemLable}>: </span>
<span className={styles.itemValue}>
<Space>
<Tag icon={<PartitionOutlined />} color="#3b5999">
{detailData?.modelName || '模型名为空'}
</Tag>
{detailData?.hasAdminRes && (
<span
className={styles.gotoMetricListIcon}
onClick={() => {
toDomainList(detailData.domainId, 'overview');
}}
>
<Tooltip title="前往模型设置页">
<ExportOutlined />
</Tooltip>
</span>
)}
</Space>
</span>
</div>
)}
<div className={styles.item}>
<span className={styles.itemLable}>: </span>
<span className={styles.itemValue}>{detailData?.createdBy}</span>
</div>
<div className={styles.item}>
<span className={styles.itemLable}>: </span>
<span className={styles.itemValue}>
{detailData?.createdAt
? dayjs(detailData?.createdAt).format('YYYY-MM-DD HH:mm:ss')
: ''}
</span>
</div>
<div className={styles.item}>
<span className={styles.itemLable}>: </span>
<span className={styles.itemValue}>
{detailData?.createdAt
? dayjs(detailData?.updatedAt).format('YYYY-MM-DD HH:mm:ss')
: ''}
</span>
</div>
</div>
)}
</div>
</div>
);
};
export default DetailSider;

View File

@@ -0,0 +1,22 @@
import React from 'react';
import styles from './style.less';
type Props = {
siderNode: React.ReactNode;
containerNode: React.ReactNode;
};
const DetailContainer: React.FC<Props> = ({ siderNode, containerNode }) => {
return (
<>
<div className={styles.DetailWrapper}>
<div className={styles.Detail}>
<div className={styles.siderContainer}>{siderNode}</div>
<div className={styles.tabContainer}>{containerNode}</div>
</div>
</div>
</>
);
};
export default DetailContainer;

View File

@@ -0,0 +1,338 @@
.DetailWrapper {
.Detail {
position: relative;
display: flex;
flex-direction: row;
width: 100%;
padding: 0px;
background-color: transparent;
height: 100%;
.tabContainer {
padding: 12px;
min-height: calc(100vh - 105px);
width: calc(100vw - 350px);
background-color: #fafafb;
}
.siderContainer {
width: 320px;
min-height: calc(100vh - 105px);
border-radius: 6px;
padding: 12px 0 12px 12px;
}
}
}
.DetailInfoSider {
padding: 10px;
color: #344767;
background-color: #fff;
height: 100%;
border: 1px solid #e6ebf1;
border-radius: 6px;
.createTitle {
display: flex;
margin-left: 10px;
min-height: 47px;
margin-bottom: 10px;
color:#344767;
font-weight: 500;
font-size: 16px;
font-family: var(--tencent-font-family);
}
.gotoMetricListIcon {
color: #3182ce;
cursor: pointer;
&:hover {
color: #5493ff;
}
}
.title {
margin: 10px 0;
min-height: 47px;
.name {
font-weight: 600;
font-size: 18px;
}
.bizName {
margin: 5px 0 0 25px;
color: #7b809a;
font-weight: 400;
}
}
.desc {
display: block;
margin-top: 8px;
color: #7b809a;
font-weight: 500;
font-size: 14px;
line-height: 1.9;
}
.subTitle {
margin: 0px;
color: rgb(123, 128, 154);
font-weight: 700;
font-size: 14px;
line-height: 1.25;
letter-spacing: 0.03333em;
text-transform: uppercase;
text-decoration: none;
vertical-align: unset;
opacity: 1;
}
.sectionContainer {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
overflow: scroll;
overflow: hidden;
background-image: none;
border-radius: 6px;
.section {
padding: 16px;
color: rgb(52, 71, 103);
line-height: 1.25;
background: transparent;
box-shadow: none;
opacity: 1;
.sectionTitleBox {
padding: 8px 0;
color: rgb(52, 71, 103);
background: transparent;
box-shadow: none;
opacity: 1;
.sectionTitle {
margin: 0px;
color: rgb(52, 71, 103);
font-weight: 600;
font-size: 16px;
line-height: 1.625;
letter-spacing: 0.0075em;
text-transform: capitalize;
text-decoration: none;
vertical-align: unset;
opacity: 1;
}
}
.item {
display: flex;
padding-top: 8px;
padding-right: 16px;
padding-bottom: 8px;
color: rgb(52, 71, 103);
background: transparent;
box-shadow: none;
opacity: 1;
.itemLable {
min-width: fit-content;
margin: 0px;
margin-right: 10px;
color: #344767;
font-weight: 700;
font-size: 14px;
line-height: 1.5;
letter-spacing: 0.02857em;
text-transform: capitalize;
text-decoration: none;
vertical-align: unset;
opacity: 1;
}
.itemValue {
margin: 0px;
color: #7b809a;
font-weight: 400;
font-size: 14px;
line-height: 1.5;
letter-spacing: 0.02857em;
text-transform: none;
text-decoration: none;
vertical-align: unset;
opacity: 1;
}
}
}
.hr {
flex-shrink: 0;
margin: 0px;
border-color: rgb(242, 244, 247);
// border-width: 0px 0px thin;
border-style: solid;
}
.ctrlBox {
.ctrlList {
position: relative;
margin: 0px;
padding: 8px 0px;
list-style: none;
background-color: rgb(249, 250, 251);
li {
position: relative;
display: flex;
flex-grow: 1;
align-items: center;
justify-content: flex-start;
box-sizing: border-box;
min-width: 0px;
margin: 4px;
padding: 4px 16px;
color: inherit;
text-align: left;
text-decoration: none;
vertical-align: middle;
background-color: transparent;
border: 0px;
border-radius: 0px;
outline: 0px;
cursor: pointer;
transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
appearance: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-box-flex: 1;
-webkit-box-pack: start;
-webkit-box-align: center;
&:hover {
color: #3182ce;
text-decoration: none;
background-color: rgba(16, 24, 40, 0.04);
}
}
.ctrlItemIcon {
flex-shrink: 0;
min-width: unset;
margin-right: 5px;
font-size: 14px;
}
.styles.ctrlItemLable {
display: block;
margin: 0px;
font-weight: 400;
font-size: 14px;
line-height: 1.6;
}
}
}
}
}
.settingList {
list-style: none;
margin: 0px;
position: relative;
padding: 0px;
li {
-webkit-tap-highlight-color: transparent;
background-color: transparent;
outline: 0px;
border: 0px;
margin: 0px;
border-radius: 0px;
cursor: pointer;
user-select: none;
vertical-align: middle;
appearance: none;
display: flex;
flex-grow: 1;
justify-content: flex-start;
align-items: center;
position: relative;
text-decoration: none;
min-width: 0px;
box-sizing: border-box;
text-align: left;
padding: 8px 16px;
transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
&.active {
background-color: rgba(22, 119, 255, 0.08);
.icon {
color: rgb(22, 119, 255);
}
.content {
.text {
color: rgb(22, 119, 255);
}
}
}
.icon {
min-width: 32px;
color: #344767;
flex-shrink: 0;
display: inline-flex;
}
.content {
flex: 1 1 auto;
min-width: 0px;
margin-top: 4px;
margin-bottom: 4px;
.text {
margin: 0px;
color: #344767;
font-size: 16px;
// line-height: 1.57;
// font-family: var(--tencent-font-family);
font-weight: 600;
display: block;
}
}
&:hover {
text-decoration: none;
background-color: rgba(0, 0, 0, 0.04);
}
}
}
.infoCard {
min-height: 100%;
background-color: rgb(255, 255, 255);
color: rgb(38, 38, 38);
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
position: relative;
border: 1px solid rgb(230, 235, 241);
border-radius: 4px;
box-shadow: inherit;
.infoCardTitle {
display: flex;
border-bottom: 1px solid #e6ebf1;
padding: 15px 20px;
align-items: center;
color: rgb(38, 38, 38);
margin: 0px;
font-size: 16px;
font-weight: 600;
line-height: 1.57;
font-family: "tencentFont", sans-serif;
}
.infoCardContainer {
padding: 20px;
height: calc(100vh - 260px);
overflow: scroll;
}
.infoCardFooter {
border-top: 1px solid #e6ebf1;
display: flex;
align-items: center;
justify-content: flex-end;
flex: 0 0 auto;
padding: 20px;
.infoCardFooterContainer {
box-sizing: border-box;
display: flex;
flex-flow: wrap;
// width: 100%;
justify-content: space-between;
align-items: center;
}
}
}

View File

@@ -0,0 +1,5 @@
export type MenuItem = {
icon: React.ReactNode;
key: string;
text: string;
};

View File

@@ -0,0 +1,343 @@
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import type { Ref } from 'react';
import { Button, Form, Input, Select, Row, Col, Space, Tooltip, Switch } from 'antd';
import { SENSITIVE_LEVEL_OPTIONS, TAG_DEFINE_TYPE } from '../constant';
import { formLayout } from '@/components/FormHelper/utils';
import SqlEditor from '@/components/SqlEditor';
import { ISemantic } from '../data';
import {
DIM_OPTIONS,
EnumDataSourceType,
PARTITION_TIME_FORMATTER,
DATE_FORMATTER,
} from '@/pages/SemanticModel/Datasource/constants';
import { InfoCircleOutlined } from '@ant-design/icons';
import {
createDimension,
updateDimension,
mockDimensionAlias,
batchCreateTag,
batchDeleteTag,
} from '../service';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import { message } from 'antd';
import { toModelList } from '@/pages/SemanticModel/utils';
export type CreateFormProps = {
modelId: number;
domainId: number;
dimensionItem?: ISemantic.IDimensionItem;
onCancel: () => void;
onSubmit?: (values?: any) => void;
};
const FormItem = Form.Item;
const { Option } = Select;
const { TextArea } = Input;
const DimensionInfoForm: React.FC<CreateFormProps> = forwardRef(
(
{ modelId, domainId, dimensionItem, onSubmit: handleUpdate }: CreateFormProps,
ref: Ref<any>,
) => {
const isEdit = !!dimensionItem?.id;
const [dimensionValueSettingList, setDimensionValueSettingList] = useState<
ISemantic.IDimensionValueSettingItem[]
>([]);
const [form] = Form.useForm();
const { setFieldsValue, resetFields } = form;
const [llmLoading, setLlmLoading] = useState<boolean>(false);
const [formData, setFormData] = useState<ISemantic.IDimensionItem>();
useImperativeHandle(ref, () => ({
onSave: () => {
return handleSubmit();
},
}));
const handleSubmit = async (dimValueMaps?: ISemantic.IDimensionValueSettingItem[]) => {
const fieldsValue = await form.validateFields();
await saveDimension({
...fieldsValue,
dimValueMaps: dimValueMaps || dimensionValueSettingList,
alias: Array.isArray(fieldsValue.alias) ? fieldsValue.alias.join(',') : '',
});
};
const saveDimension = async (fieldsValue: any) => {
const queryParams = {
modelId: isEdit ? dimensionItem.modelId : modelId,
type: 'categorical',
...fieldsValue,
};
let saveDimensionQuery = createDimension;
if (queryParams.id) {
saveDimensionQuery = updateDimension;
}
const { code, msg, data } = await saveDimensionQuery(queryParams);
if (code === 200) {
if (queryParams.isTag) {
queryBatchExportTag(data.id || dimensionItem?.id);
}
if (dimensionItem?.id && !queryParams.isTag) {
queryBatchDeleteTag(dimensionItem);
}
if (!isEdit) {
toModelList(domainId, modelId, 'dimension');
}
message.success('保存维度成功');
return;
}
message.error(msg);
};
const queryBatchDeleteTag = async (dimensionItem: ISemantic.IDimensionItem) => {
const { code, msg } = await batchDeleteTag([
{
itemIds: [dimensionItem.id],
tagDefineType: TAG_DEFINE_TYPE.DIMENSION,
},
]);
if (code === 200) {
return;
}
message.error(msg);
};
const queryBatchExportTag = async (id: number) => {
const { code, msg } = await batchCreateTag([
{ itemId: id, tagDefineType: TAG_DEFINE_TYPE.DIMENSION },
]);
if (code === 200) {
return;
}
message.error(msg);
};
const setFormVal = () => {
if (dimensionItem) {
const { alias } = dimensionItem;
const dimensionData = {
...dimensionItem,
alias: alias && alias.trim() ? alias.split(',') : [],
};
setFieldsValue(dimensionData);
setFormData(dimensionData);
}
};
useEffect(() => {
if (dimensionItem) {
setFormVal();
if (Array.isArray(dimensionItem.dimValueMaps)) {
setDimensionValueSettingList(dimensionItem.dimValueMaps);
} else {
setDimensionValueSettingList([]);
}
} else {
resetFields();
}
}, [dimensionItem]);
const generatorDimensionAlias = async () => {
const fieldsValue = await form.validateFields();
setLlmLoading(true);
const { code, data } = await mockDimensionAlias({
...dimensionItem,
...fieldsValue,
alias: fieldsValue.alias?.join(','),
});
setLlmLoading(false);
const formAlias = form.getFieldValue('alias');
setLlmLoading(false);
if (code === 200) {
form.setFieldValue('alias', Array.from(new Set([...formAlias, ...data])));
} else {
message.error('大语言模型解析异常');
}
};
const renderContent = () => {
return (
<>
<FormItem hidden={true} name="id" label="ID">
<Input placeholder="id" />
</FormItem>
<FormItem
name="name"
label="维度名称"
rules={[{ required: true, message: '请输入维度名称' }]}
>
<Input placeholder="名称不可重复" />
</FormItem>
<FormItem
hidden={isEdit}
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"
size="small"
loading={llmLoading}
style={{ top: '2px' }}
onClick={() => {
generatorDimensionAlias();
}}
>
<Space>
<Tooltip title="智能填充将根据维度相关信息,使用大语言模型获取维度别名">
<InfoCircleOutlined />
</Tooltip>
</Space>
</Button>
</Col>
)}
</Row>
</FormItem>
<FormItem
name="type"
label="类型"
rules={[{ required: true, message: '请选择维度类型' }]}
>
<Select placeholder="请选择维度类型">
{DIM_OPTIONS.map((item) => (
<Option key={item.value} value={item.value}>
{item.label}
</Option>
))}
</Select>
</FormItem>
{formData?.type &&
[EnumDataSourceType.PARTITION_TIME, EnumDataSourceType.TIME].includes(
formData.type as EnumDataSourceType,
) && (
<FormItem
name={['ext', 'time_format']}
label="时间格式"
rules={[{ required: true, message: '请选择时间格式' }]}
tooltip="请选择数据库中时间字段对应格式"
>
<Select placeholder="请选择维度类型">
{(formData?.type === EnumDataSourceType.TIME
? DATE_FORMATTER
: PARTITION_TIME_FORMATTER
).map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</FormItem>
)}
<FormItem
name="semanticType"
label="类型"
hidden={true}
// rules={[{ required: true, message: '请选择维度类型' }]}
>
<Select placeholder="请选择维度类型">
{['CATEGORY', 'ID', 'DATE'].map((item) => (
<Option key={item} value={item}>
{item}
</Option>
))}
</Select>
</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="commonDimensionId" label="公共维度">
<Select placeholder="请绑定公共维度" allowClear options={commonDimensionOptions} />
</FormItem> */}
{/* <FormItem name="defaultValues" label="默认值">
<InfoTagList />
</FormItem> */}
<Form.Item
hidden={!!!process.env.SHOW_TAG}
label={
<FormItemTitle
title={`设为标签`}
subTitle={`如果勾选,代表维度的取值都是一种'标签',可用作对实体的圈选`}
/>
}
name="isTag"
valuePropName="checked"
getValueFromEvent={(value) => {
return value === true ? 1 : 0;
}}
getValueProps={(value) => {
return {
checked: value === 1,
};
}}
>
<Switch />
</Form.Item>
<FormItem
name="description"
label="维度描述"
rules={[{ required: true, message: '请输入维度描述' }]}
>
<TextArea placeholder="请输入维度描述" />
</FormItem>
<FormItem
name="expr"
label="表达式"
tooltip="表达式中的字段必须在创建模型的时候被标记为日期或者维度"
rules={[{ required: true, message: '请输入表达式' }]}
>
<SqlEditor height={'150px'} />
</FormItem>
</>
);
};
return (
<>
<Form
{...formLayout}
form={form}
onValuesChange={(value, values) => {
setFormData(values);
}}
>
{renderContent()}
</Form>
</>
);
},
);
export default DimensionInfoForm;

View File

@@ -11,6 +11,7 @@ import TableHeaderFilter from '@/components/TableHeaderFilter';
import moment from 'moment';
import styles from './style.less';
import { ISemantic } from '../data';
import { toModelList } from '@/pages/SemanticModel/utils';
type Props = {
disabledEdit?: boolean;
@@ -105,9 +106,7 @@ const ModelTable: React.FC<Props> = ({ modelList, disabledEdit = false, onModelC
<a
onClick={() => {
setSelectModel(record);
history.push(`/model/domain/manager/${domainId}/${id}`);
// onModelChange?.(record);
toModelList(domainId, id);
}}
>
{_}

View File

@@ -13,7 +13,7 @@ import IndicatorStar, { StarType } from '../components/IndicatorStar';
interface IndicatorInfo {
url?: string;
starType?: StarType;
onNameClick?: (record: ISemantic.IMetricItem) => void | boolean;
onNameClick?: (record: ISemantic.IMetricItem | ISemantic.IDimensionItem) => void | boolean;
}
interface ColumnsConfigParams {
@@ -116,18 +116,43 @@ export const ColumnsConfig = (params?: ColumnsConfigParams) => {
},
dimensionInfo: {
render: (_, record: ISemantic.IDimensionItem) => {
const { name, alias, bizName } = record;
const { name, alias, bizName, id, domainId, modelId } = record;
let url = `/demension/detail/${id}`;
if (params?.indicatorInfo) {
url = replaceRouteParams(params.indicatorInfo.url || '', {
domainId: `${domainId}`,
modelId: `${modelId}`,
indicatorId: `${id}`,
});
}
return (
<>
<div>
<Space>
<span style={{ fontWeight: 500 }}>{name}</span>
<a
className={styles.textLink}
style={{ fontWeight: 500 }}
onClick={(event: any) => {
if (params?.indicatorInfo?.onNameClick) {
const state = params.indicatorInfo.onNameClick(record);
if (state === false) {
return;
}
}
history.push(url);
event.preventDefault();
event.stopPropagation();
}}
>
{name}
</a>
{/* <span style={{ fontWeight: 500 }}>{name}</span> */}
</Space>
</div>
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5, marginLeft: 0 }}>
{bizName}
</div>
{renderAliasAndClassifications(alias, undefined)}
{alias && renderAliasAndClassifications(alias, undefined)}
</>
);
},
@@ -136,7 +161,7 @@ export const ColumnsConfig = (params?: ColumnsConfigParams) => {
render: (_, record: ISemantic.IMetricItem) => {
const { name, alias, bizName, classifications, id, isCollect, domainId, modelId } = record;
let url = `/metric/detail/`;
let url = `/metric/detail/${id}`;
let starType: StarType = 'metric';
if (params?.indicatorInfo) {
url = replaceRouteParams(params.indicatorInfo.url || '', {
@@ -174,7 +199,7 @@ export const ColumnsConfig = (params?: ColumnsConfigParams) => {
<div style={{ color: '#5f748d', fontSize: 14, marginTop: 5, marginLeft: 0 }}>
{bizName}
</div>
{renderAliasAndClassifications(alias, classifications)}
{alias && renderAliasAndClassifications(alias, classifications)}
</>
);
},

View File

@@ -419,7 +419,7 @@
.infoCardTitle {
display: flex;
border-bottom: 1px solid #e6ebf1;
padding: 20px 20px 20px 40px;
padding: 15px 20px;
align-items: center;
color: rgb(38, 38, 38);
margin: 0px;