[improvement][semantic-fe] Adding the ability to filter dimensions based on whether they are tags or not. (#417)

* [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.
This commit is contained in:
tristanliu
2023-11-23 16:29:10 +08:00
committed by GitHub
parent c168925f03
commit 30bb9a1dc0
28 changed files with 309 additions and 604 deletions

View File

@@ -1,15 +1,43 @@
import { Space } from 'antd'; import { Space, Typography } from 'antd';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import styles from './index.less';
export interface IProps { export interface IProps {
title: string | ReactNode; title: string | ReactNode;
subTitle?: string; subTitle?: string;
subTitleEditable?: boolean;
onSubTitleChange?: (title: string) => void;
} }
const FormItemTitle: React.FC<IProps> = ({ title, subTitle }) => { const { Paragraph } = Typography;
const FormItemTitle: React.FC<IProps> = ({
title,
subTitle,
subTitleEditable = false,
onSubTitleChange,
}) => {
return ( return (
<Space direction="vertical" size={2}> // <div style={{ display: 'block' }}>
<span>{title}</span>
{subTitle && <span style={{ fontSize: '12px', color: '#6a6a6a' }}>{subTitle}</span>} // </div>
<Space direction="vertical" size={2} style={{ width: '100%' }}>
<div>{title}</div>
<div className={styles.subTitleContainer}>
{subTitleEditable ? (
<Paragraph
editable={{
// editing: true,
onChange: (title: string) => {
onSubTitleChange?.(title);
},
}}
>
{subTitle || '添加描述'}
</Paragraph>
) : (
subTitle && <span style={{ fontSize: '12px', color: '#6a6a6a' }}>{subTitle}</span>
)}
</div>
</Space> </Space>
); );
}; };

View File

@@ -1,39 +1,13 @@
.normalState { .subTitleContainer {
position: static; width: 500px;
height: 100%; :global {
.ant-typography{
.backNormal { font-size: 12px;
display: none; color: #6a6a6a;
} }
} .ant-typography-edit-content {
margin-left: 12px;
.maxState { margin-top: 8px;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999;
.innerWrap {
position: absolute;
right: 0;
bottom: 0;
left: 0;
background: #fff;
}
.backNormal {
display: block;
height: 30px;
padding-right: 20px;
color: #02a7f0;
font-size: 22px;
line-height: 30px;
text-align: right;
.fullscreenExitIcon {
cursor: pointer;
} }
} }
} }

View File

@@ -88,6 +88,10 @@ ol {
padding: 24px !important; padding: 24px !important;
} }
.ant-modal-body {
padding: 24px;
}
.ant-pro-page-container-children-content { .ant-pro-page-container-children-content {
margin: 12px 12px 0 !important; margin: 12px 12px 0 !important;
} }

View File

@@ -54,7 +54,7 @@ const ForgetPwdForm: React.FC<RegisterFormProps> = (props) => {
return ( return (
<Modal <Modal
width={600} width={600}
bodyStyle={{ padding: '32px 40px 48px' }} styles={{ padding: '32px 40px 48px' }}
destroyOnClose destroyOnClose
title="忘记密码" title="忘记密码"
open={createModalVisible} open={createModalVisible}

View File

@@ -56,7 +56,7 @@ const RegisterForm: React.FC<RegisterFormProps> = (props) => {
return ( return (
<Modal <Modal
width={600} width={600}
bodyStyle={{ padding: '32px 40px 48px' }} styles={{ padding: '32px 40px 48px' }}
destroyOnClose destroyOnClose
title="用户注册" title="用户注册"
open={createModalVisible} open={createModalVisible}

View File

@@ -1,12 +1,8 @@
// import { Tabs } from 'antd';
import React from 'react'; import React from 'react';
import { connect } from 'umi'; import { connect } from 'umi';
// import styles from '../components/style.less';
import type { StateType } from '../model'; import type { StateType } from '../model';
import ProCard from '@ant-design/pro-card'; import ProCard from '@ant-design/pro-card';
import EntitySection from '../components/Entity/EntitySection'; import EntitySection from '../components/Entity/EntitySection';
// import RecommendedQuestionsSection from '../components/Entity/RecommendedQuestionsSection';
import { ChatConfigType } from '../enum'; import { ChatConfigType } from '../enum';
import type { Dispatch } from 'umi'; import type { Dispatch } from 'umi';
@@ -16,36 +12,12 @@ type Props = {
}; };
const ChatSettingSection: React.FC<Props> = () => { const ChatSettingSection: React.FC<Props> = () => {
// const isModelItem = [
// {
// label: '指标模式',
// key: 'metric',
// children: <EntitySection chatConfigType={ChatConfigType.AGG} />,
// },
// {
// label: '实体模式',
// key: 'dimenstion',
// children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />,
// },
// {
// label: '推荐问题',
// key: 'recommendedQuestions',
// children: <RecommendedQuestionsSection />,
// },
// ];
return ( return (
<div style={{ width: 900, margin: '20px auto' }}> <div style={{ width: 900, margin: '20px auto' }}>
{/* <Tabs
className={styles.chatSettingSectionTab}
items={isModelItem}
destroyInactiveTabPane
tabPosition="left"
/> */}
<ProCard bordered title="指标模式" style={{ marginBottom: 20 }}> <ProCard bordered title="指标模式" style={{ marginBottom: 20 }}>
<EntitySection chatConfigType={ChatConfigType.AGG} /> <EntitySection chatConfigType={ChatConfigType.AGG} />
</ProCard> </ProCard>
<ProCard bordered title="实体模式" style={{ marginBottom: 20 }}> <ProCard bordered title="标签模式" style={{ marginBottom: 20 }}>
<EntitySection chatConfigType={ChatConfigType.DETAIL} /> <EntitySection chatConfigType={ChatConfigType.DETAIL} />
</ProCard> </ProCard>
</div> </div>

View File

@@ -41,7 +41,7 @@ const ChatSetting: React.FC<Props> = ({
children: <EntitySection chatConfigType={ChatConfigType.AGG} />, children: <EntitySection chatConfigType={ChatConfigType.AGG} />,
}, },
{ {
label: '实体模式', label: '标签模式',
key: 'dimenstion', key: 'dimenstion',
children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />, children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />,
}, },

View File

@@ -1,7 +1,7 @@
import React, { ReactNode, useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { Form, Button, Modal, Steps, message } from 'antd'; import { Form, Button, Modal, Steps, message } from 'antd';
import DataSourceBasicForm from './DataSourceBasicForm'; import DataSourceBasicForm from './DataSourceBasicForm';
import FieldForm from './DataSourceFieldForm'; import DataSourceFieldForm from './DataSourceFieldForm';
import { formLayout } from '@/components/FormHelper/utils'; import { formLayout } from '@/components/FormHelper/utils';
import { EnumDataSourceType } from '../constants'; import { EnumDataSourceType } from '../constants';
import styles from '../style.less'; import styles from '../style.less';
@@ -94,6 +94,8 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
name, name,
isCreateMetric: createMetric, isCreateMetric: createMetric,
dateFormat, dateFormat,
entityNames,
isTag,
} = item; } = item;
const isCreateDimension = createDimension ? 1 : 0; const isCreateDimension = createDimension ? 1 : 0;
const isCreateMetric = createMetric ? 1 : 0; const isCreateMetric = createMetric ? 1 : 0;
@@ -104,6 +106,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
type, type,
isCreateDimension, isCreateDimension,
name, name,
isTag: isTag ? 1 : 0,
}); });
break; break;
case EnumDataSourceType.TIME: case EnumDataSourceType.TIME:
@@ -125,6 +128,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
bizName, bizName,
name, name,
type, type,
entityNames,
}); });
break; break;
case EnumDataSourceType.MEASURES: case EnumDataSourceType.MEASURES:
@@ -325,7 +329,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
return ( return (
<> <>
<div style={{ display: currentStep === 1 ? 'block' : 'none' }}> <div style={{ display: currentStep === 1 ? 'block' : 'none' }}>
<FieldForm <DataSourceFieldForm
fields={fields} fields={fields}
onFieldChange={handleFieldChange} onFieldChange={handleFieldChange}
onSqlChange={(sql) => { onSqlChange={(sql) => {
@@ -410,7 +414,7 @@ const DataSourceCreateForm: React.FC<CreateFormProps> = ({
<Modal <Modal
forceRender forceRender
width={1300} width={1300}
styles={{ padding: '32px 40px 48px' }} // styles={{ padding: '32px 40px 48px' }}
destroyOnClose destroyOnClose
title={`${isEdit ? '编辑' : '新建'}数据源`} title={`${isEdit ? '编辑' : '新建'}数据源`}
maskClosable={false} maskClosable={false}

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { Table, Select, Checkbox, Input, Alert, Space, Tooltip, Form } from 'antd'; import { Table, Select, Checkbox, Input, Alert, Space, Tooltip, Form, Switch } from 'antd';
import TableTitleTooltips from '../../components/TableTitleTooltips'; import TableTitleTooltips from '../../components/TableTitleTooltips';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { ExclamationCircleOutlined } from '@ant-design/icons'; import { ExclamationCircleOutlined } from '@ant-design/icons';
@@ -22,6 +22,8 @@ type FieldItem = {
checked?: number; checked?: number;
dateFormat?: string; dateFormat?: string;
timeGranularity?: string; timeGranularity?: string;
entityNames?: string[];
isTag?: number;
}; };
const FormItem = Form.Item; const FormItem = Form.Item;
@@ -42,7 +44,6 @@ const getCreateFieldName = (type: EnumDataSourceType) => {
? 'isCreateDimension' ? 'isCreateDimension'
: 'isCreateMetric'; : 'isCreateMetric';
return isCreateName; return isCreateName;
// const editState = !isUndefined(record[isCreateName]) ? !!record[isCreateName] : true;
}; };
const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange }) => { const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange }) => {
@@ -117,9 +118,31 @@ const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange })
{ {
title: '扩展配置', title: '扩展配置',
dataIndex: 'extender', dataIndex: 'extender',
width: 180, width: 185,
render: (_: any, record: FieldItem) => { render: (_: any, record: FieldItem) => {
const { type } = record; const { type } = record;
if (type === EnumDataSourceType.PRIMARY) {
const entityNames =
fields.find((field) => field.bizName === record.bizName)?.entityNames || [];
return (
<Space>
<Select
style={{ minWidth: 345 }}
mode="tags"
value={entityNames}
placeholder="输入实体名称后回车确认,支持英文逗号自动分隔"
tokenSeparators={[',']}
onChange={(value) => {
handleFieldChange(record, 'entityNames', value);
}}
maxTagCount={9}
/>
<Tooltip title="主键可以作为一个实体,在此设置一个或多个实体名称">
<ExclamationCircleOutlined />
</Tooltip>
</Space>
);
}
if (type === EnumDataSourceType.MEASURES) { if (type === EnumDataSourceType.MEASURES) {
const agg = fields.find((field) => field.bizName === record.bizName)?.agg; const agg = fields.find((field) => field.bizName === record.bizName)?.agg;
return ( return (
@@ -140,6 +163,25 @@ const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange })
</Select> </Select>
); );
} }
if (type === EnumDataSourceType.CATEGORICAL) {
const isTag = fields.find((field) => field.bizName === record.bizName)?.isTag;
return (
<Space>
<span></span>
<Switch
defaultChecked
size="small"
checked={!!isTag}
onChange={(value) => {
handleFieldChange(record, 'isTag', value);
}}
/>
<Tooltip title="如果勾选,代表维度的取值都是一种“标签”,可用作对实体的圈选">
<ExclamationCircleOutlined />
</Tooltip>
</Space>
);
}
if (type === EnumDataSourceType.TIME) { if (type === EnumDataSourceType.TIME) {
const dateFormat = fields.find((field) => field.bizName === record.bizName)?.dateFormat; const dateFormat = fields.find((field) => field.bizName === record.bizName)?.dateFormat;
const timeGranularity = fields.find( const timeGranularity = fields.find(
@@ -222,7 +264,6 @@ const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange })
[isCreateName]: value, [isCreateName]: value,
}); });
} else { } else {
// handleFieldChange(record, isCreateName, value);
onFieldChange(record.bizName, { onFieldChange(record.bizName, {
...record, ...record,
checked: value, checked: value,
@@ -237,7 +278,6 @@ const FieldForm: React.FC<Props> = ({ fields, sql, onFieldChange, onSqlChange })
disabled={!editState} disabled={!editState}
onChange={(e) => { onChange={(e) => {
const value = e.target.value; const value = e.target.value;
// handleFieldChange(record, 'name', value);
onFieldChange(record.bizName, { onFieldChange(record.bizName, {
...record, ...record,
name: value, name: value,

View File

@@ -5,7 +5,7 @@ import DomainListTree from './components/DomainList';
import styles from './components/style.less'; import styles from './components/style.less';
import type { StateType } from './model'; import type { StateType } from './model';
import { DownOutlined, LeftOutlined } from '@ant-design/icons'; import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { ISemantic } from './data'; import { ISemantic } from './data';
import { getDomainList, getModelList } from './service'; import { getDomainList, getModelList } from './service';
import ChatSettingTab from './ChatSetting/ChatSettingTab'; import ChatSettingTab from './ChatSetting/ChatSettingTab';
@@ -24,17 +24,12 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
const modelId = params.modelId; const modelId = params.modelId;
const menuKey = params.menuKey ? params.menuKey : !Number(modelId) ? 'overview' : ''; const menuKey = params.menuKey ? params.menuKey : !Number(modelId) ? 'overview' : '';
const { selectDomainId, selectModelId, selectDomainName, selectModelName, domainList } = const { selectDomainId, selectModelId, domainList } = domainManger;
domainManger;
const [modelList, setModelList] = useState<ISemantic.IModelItem[]>([]); const [modelList, setModelList] = useState<ISemantic.IModelItem[]>([]);
const [isModel, setIsModel] = useState<boolean>(false); const [isModel, setIsModel] = useState<boolean>(false);
const [open, setOpen] = useState(false); const [collapsedState, setCollapsedState] = useState(true);
const [activeKey, setActiveKey] = useState<string>(menuKey); const [activeKey, setActiveKey] = useState<string>(menuKey);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => { const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
const targetNode = domainList.filter((item: any) => { const targetNode = domainList.filter((item: any) => {
return `${item.id}` === domainId; return `${item.id}` === domainId;
@@ -198,39 +193,43 @@ const OverviewContainer: React.FC<Props> = ({ mode, domainManger, dispatch }) =>
}); });
}; };
const handleCollapsedBtn = () => {
setCollapsedState(!collapsedState);
};
return ( return (
<div className={styles.projectBody}> <div className={styles.projectBody}>
<Helmet title={'语义模型-超音数'} /> <Helmet title={'语义模型-超音数'} />
<div className={styles.projectManger}> <div className={styles.projectManger}>
<div className={styles.sider}> <div className={`${styles.sider} ${!collapsedState ? styles.siderCollapsed : ''}`}>
{/* <div className={styles.domainTitle}> <div className={styles.treeContainer}>
<Space> <DomainListTree
{selectDomainName ? `${selectDomainName}` : '主题域信息'} createDomainBtnVisible={mode === 'domain' ? true : false}
{selectModelName && ( onTreeSelected={(domainData) => {
<> const { id, name } = domainData;
<span style={{ position: 'relative' }}> | </span> cleanModelInfo(id);
<span style={{ fontSize: 16, color: '#296DF3' }}>{selectModelName}</span> dispatch({
</> type: 'domainManger/setSelectDomain',
)} selectDomainId: id,
</Space> selectDomainName: name,
</div> */} domainData,
<DomainListTree });
createDomainBtnVisible={mode === 'domain' ? true : false} }}
onTreeSelected={(domainData) => { onTreeDataUpdate={() => {
setOpen(false); initProjectTree();
const { id, name } = domainData; }}
cleanModelInfo(id); />
dispatch({ </div>
type: 'domainManger/setSelectDomain',
selectDomainId: id, <div
selectDomainName: name, className={styles.siderCollapsedButton}
domainData, onClick={() => {
}); handleCollapsedBtn();
}} }}
onTreeDataUpdate={() => { >
initProjectTree(); {collapsedState ? <LeftOutlined /> : <RightOutlined />}
}} </div>
/> {/* <RightOutlined /> */}
</div> </div>
<div className={styles.content}> <div className={styles.content}>
{selectDomainId ? ( {selectDomainId ? (

View File

@@ -55,6 +55,7 @@ const ClassDataSourceTable: React.FC<Props> = ({ dispatch, domainManger }) => {
title: '操作', title: '操作',
dataIndex: 'x', dataIndex: 'x',
valueType: 'option', valueType: 'option',
width: 100,
render: (_, record) => { render: (_, record) => {
return ( return (
<Space> <Space>

View File

@@ -8,6 +8,7 @@ import { excuteSql } from '../service';
import type { StateType } from '../model'; import type { StateType } from '../model';
import DataSource from '../Datasource'; import DataSource from '../Datasource';
import { IDataSource } from '../data'; import { IDataSource } from '../data';
import styles from './style.less';
const { Meta } = Card; const { Meta } = Card;
type Props = { type Props = {
@@ -95,6 +96,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({
return ( return (
<> <>
<Modal <Modal
className={styles.classDataSourceTypeModal}
open={createDataSourceModalOpen} open={createDataSourceModalOpen}
onCancel={() => { onCancel={() => {
setCreateDataSourceModalOpen(false); setCreateDataSourceModalOpen(false);

View File

@@ -1,6 +1,6 @@
import type { ActionType, ProColumns } from '@ant-design/pro-table'; import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table'; import ProTable from '@ant-design/pro-table';
import { message, Button, Space, Popconfirm, Input, Tag, Dropdown } from 'antd'; import { message, Button, Space, Popconfirm, Input, Tag, Select } from 'antd';
import React, { useRef, useState, useEffect } from 'react'; import React, { useRef, useState, useEffect } from 'react';
import type { Dispatch } from 'umi'; import type { Dispatch } from 'umi';
import { connect } from 'umi'; import { connect } from 'umi';
@@ -168,6 +168,31 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
width: 80, width: 80,
valueEnum: SENSITIVE_LEVEL_ENUM, valueEnum: SENSITIVE_LEVEL_ENUM,
}, },
{
dataIndex: 'isTag',
title: '是否为标签',
// search: false,
renderFormItem: () => (
<Select
placeholder="请选择标签状态"
allowClear
options={[
{ value: 1, label: '是' },
{ value: 0, label: '否' },
]}
/>
),
render: (isTag) => {
switch (isTag) {
case 0:
return '否';
case 1:
return <span style={{ color: '#1677ff' }}></span>;
default:
return <Tag color="default"></Tag>;
}
},
},
{ {
dataIndex: 'status', dataIndex: 'status',
title: '状态', title: '状态',
@@ -220,6 +245,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '操作', title: '操作',
dataIndex: 'x', dataIndex: 'x',
valueType: 'option', valueType: 'option',
width: 200,
render: (_, record) => { render: (_, record) => {
return ( return (
<Space className={styles.ctrlBtnContainer}> <Space className={styles.ctrlBtnContainer}>

View File

@@ -190,6 +190,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '操作', title: '操作',
dataIndex: 'x', dataIndex: 'x',
valueType: 'option', valueType: 'option',
width: 150,
render: (_, record) => { render: (_, record) => {
return ( return (
<Space className={styles.ctrlBtnContainer}> <Space className={styles.ctrlBtnContainer}>

View File

@@ -72,6 +72,7 @@ const DatabaseTable: React.FC<Props> = ({}) => {
title: '操作', title: '操作',
dataIndex: 'x', dataIndex: 'x',
valueType: 'option', valueType: 'option',
width: 100,
render: (_, record) => { render: (_, record) => {
if (!record.hasEditPermission) { if (!record.hasEditPermission) {
return <></>; return <></>;

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Input, Modal, Select, Row, Col, Space, Tooltip } from 'antd'; import { Button, Form, Input, Modal, Select, Row, Col, Space, Tooltip, Switch } from 'antd';
import { SENSITIVE_LEVEL_OPTIONS } from '../constant'; import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
import { formLayout } from '@/components/FormHelper/utils'; import { formLayout } from '@/components/FormHelper/utils';
import SqlEditor from '@/components/SqlEditor'; import SqlEditor from '@/components/SqlEditor';
@@ -7,6 +7,7 @@ import InfoTagList from './InfoTagList';
import { ISemantic } from '../data'; import { ISemantic } from '../data';
import { InfoCircleOutlined } from '@ant-design/icons'; import { InfoCircleOutlined } from '@ant-design/icons';
import { createDimension, updateDimension, mockDimensionAlias } from '../service'; import { createDimension, updateDimension, mockDimensionAlias } from '../service';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import { message } from 'antd'; import { message } from 'antd';
@@ -231,6 +232,26 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
<FormItem name="defaultValues" label="默认值"> <FormItem name="defaultValues" label="默认值">
<InfoTagList /> <InfoTagList />
</FormItem> </FormItem>
<Form.Item
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 <FormItem
name="description" name="description"
label="维度描述" label="维度描述"

View File

@@ -10,7 +10,6 @@ import { createDomain, updateDomain, deleteDomain } from '../service';
import { treeParentKeyLists } from '../utils'; import { treeParentKeyLists } from '../utils';
import ProjectInfoFormProps from './ProjectInfoForm'; import ProjectInfoFormProps from './ProjectInfoForm';
import { constructorClassTreeFromList, addPathInTreeData } from '../utils'; import { constructorClassTreeFromList, addPathInTreeData } from '../utils';
import { PlusCircleOutlined } from '@ant-design/icons';
import styles from './style.less'; import styles from './style.less';
import { ISemantic } from '../data'; import { ISemantic } from '../data';
@@ -180,19 +179,17 @@ const DomainListTree: FC<DomainListProps> = ({
return ( return (
<div className={styles.domainList}> <div className={styles.domainList}>
<div className={styles.searchContainer}> <div className={styles.searchContainer}>
<Row> <Row style={{ gap: 20 }}>
<Col flex="1 1 auto"> <Col flex="1 1 215px">
{/* <Space> */}
<Search <Search
allowClear allowClear
className={styles.search} className={styles.search}
placeholder="请输入主题域名称进行查询" placeholder="请输入主题域名称"
onSearch={onSearch} onSearch={onSearch}
/> />
{/* </Space> */}
</Col> </Col>
{createDomainBtnVisible && ( {createDomainBtnVisible && (
<Col flex="0 0 40px" style={{ display: 'flex', alignItems: 'center' }}> <Col flex="0 0 45px" style={{ display: 'flex', alignItems: 'center' }}>
<Tooltip title="新增顶级域"> <Tooltip title="新增顶级域">
<Button <Button
type="primary" type="primary"
@@ -205,16 +202,6 @@ const DomainListTree: FC<DomainListProps> = ({
}} }}
/> />
</Tooltip> </Tooltip>
{/* <Tooltip title="新增顶级域">
<PlusCircleOutlined
onClick={() => {
setProjectInfoParams({ type: 'top', modelType: 'add' });
setProjectInfoModalVisible(true);
onCreateDomainBtnClick?.();
}}
className={styles.addBtn}
/>
</Tooltip> */}
</Col> </Col>
)} )}
</Row> </Row>

View File

@@ -6,7 +6,7 @@ import ClassDataSourceTable from './ClassDataSourceTable';
import ClassDimensionTable from './ClassDimensionTable'; import ClassDimensionTable from './ClassDimensionTable';
import ClassMetricTable from './ClassMetricTable'; import ClassMetricTable from './ClassMetricTable';
import PermissionSection from './Permission/PermissionSection'; import PermissionSection from './Permission/PermissionSection';
import EntitySettingSection from './Entity/EntitySettingSection'; // import EntitySettingSection from './Entity/EntitySettingSection';
import ChatSettingSection from '../ChatSetting/ChatSettingSection'; import ChatSettingSection from '../ChatSetting/ChatSettingSection';
import OverView from './OverView'; import OverView from './OverView';
import styles from './style.less'; import styles from './style.less';
@@ -99,11 +99,11 @@ const DomainManagerTab: React.FC<Props> = ({
key: 'metric', key: 'metric',
children: <ClassMetricTable />, children: <ClassMetricTable />,
}, },
{ // {
label: '实体', // label: '实体',
key: 'entity', // key: 'entity',
children: <EntitySettingSection />, // children: <EntitySettingSection />,
}, // },
{ {
label: '权限管理', label: '权限管理',
key: 'permissonSetting', key: 'permissonSetting',

View File

@@ -163,7 +163,7 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
</FormItem> </FormItem>
{chatConfigType === ChatConfigType.DETAIL && ( {chatConfigType === ChatConfigType.DETAIL && (
<FormItem name="dataItemIds" label="展示维度/指标"> <FormItem name="dataItemIds" label="圈选结果展示字段">
<Select <Select
mode="multiple" mode="multiple"
allowClear allowClear
@@ -176,63 +176,11 @@ const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
} }
return false; return false;
}} }}
placeholder="请选择展示维度/指标信息" placeholder="请选择圈选结果展示字段"
options={dataItemListOptions} options={dataItemListOptions}
/> />
</FormItem> </FormItem>
)} )}
{chatConfigType === ChatConfigType.AGG && (
<>
{/* <FormItem
name="metricIds"
label={
<FormItemTitle
title={'指标'}
subTitle={'问答搜索结果选择中,如果没有指定指标,将会采用默认指标进行展示'}
/>
}
>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示指标信息"
options={metricListOptions}
/>
</FormItem>
<FormItem
name="ratioMetricIds"
label={
<FormItemTitle
title={'同环比指标'}
subTitle={'问答搜索含有指定的指标,将会同时计算该指标最后一天的同环比'}
/>
}
>
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择同环比指标"
options={metricListOptions}
/>
</FormItem> */}
</>
)}
<FormItem <FormItem
label={ label={
<FormItemTitle <FormItemTitle

View File

@@ -1,19 +1,13 @@
import { Table, Transfer, Checkbox, Button, Space, message, Tooltip } from 'antd'; import { Table, Transfer } 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 difference from 'lodash/difference'; import difference from 'lodash/difference';
import React, { useState, useEffect } from 'react'; import React from 'react';
import { connect } from 'umi'; import { connect } from 'umi';
import type { StateType } from '../../model'; import type { StateType } from '../../model';
import type { IChatConfig } from '../../data'; import type { IChatConfig } from '../../data';
import DimensionValueSettingModal from './DimensionValueSettingModal';
import TransTypeTag from '../TransTypeTag'; import TransTypeTag from '../TransTypeTag';
import TableTitleTooltips from '../../components/TableTitleTooltips'; import { SemanticNodeType, TransType } from '../../enum';
import { RedoOutlined } from '@ant-design/icons';
import { SemanticNodeType, DictTaskState, TransType } from '../../enum';
import { createDictTask, searchDictLatestTaskList } from '@/pages/SemanticModel/service';
import styles from '../style.less';
interface RecordType { interface RecordType {
id: number; id: number;
key: string; key: string;
@@ -29,105 +23,14 @@ type Props = {
[key: string]: any; [key: string]: any;
}; };
type TaskStateMap = Record<string, DictTaskState>; // type TaskStateMap = Record<string, DictTaskState>;
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
domainManger, // domainManger,
knowledgeInfosMap, knowledgeInfosMap,
onKnowledgeInfosMapChange, // onKnowledgeInfosMapChange,
...restProps ...restProps
}) => { }) => {
// const { selectModelId: modelId } = domainManger;
// const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
// useState<boolean>(false);
// const [currentRecord, setCurrentRecord] = useState<RecordType>({} as RecordType);
// const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] =
// useState<IChatConfig.IKnowledgeConfig>();
// const [recordLoadingMap, setRecordLoadingMap] = useState<Record<string, boolean>>({});
// const [taskStateMap, setTaskStateMap] = useState<TaskStateMap>({});
// useEffect(() => {
// queryDictLatestTaskList();
// }, []);
// const updateKnowledgeInfosMap = (record: RecordType, updateData: Record<string, any>) => {
// const { bizName, id } = record;
// const knowledgeMap = {
// ...knowledgeInfosMap,
// };
// const target = knowledgeMap[bizName];
// if (target) {
// knowledgeMap[bizName] = {
// ...target,
// ...updateData,
// };
// } else {
// knowledgeMap[bizName] = {
// itemId: id,
// bizName,
// ...updateData,
// };
// }
// onKnowledgeInfosMapChange?.(knowledgeMap);
// };
// const queryDictLatestTaskList = async () => {
// const { code, data } = await searchDictLatestTaskList({
// modelId,
// });
// if (code !== 200) {
// message.error('获取字典导入任务失败!');
// return;
// }
// const tastMap = data.reduce(
// (stateMap: TaskStateMap, item: { dimId: number; status: DictTaskState }) => {
// const { dimId, status } = item;
// stateMap[dimId] = status;
// return stateMap;
// },
// {},
// );
// setTaskStateMap(tastMap);
// };
// const createDictTaskQuery = async (recordData: RecordType) => {
// setRecordLoadingMap({
// ...recordLoadingMap,
// [recordData.id]: true,
// });
// const { code } = await createDictTask({
// updateMode: 'REALTIME_ADD',
// modelAndDimPair: {
// [modelId]: [recordData.id],
// },
// });
// setRecordLoadingMap({
// ...recordLoadingMap,
// [recordData.id]: false,
// });
// if (code !== 200) {
// message.error('字典导入任务创建失败!');
// return;
// }
// setTimeout(() => {
// queryDictLatestTaskList();
// }, 2000);
// };
// const deleteDictTask = async (recordData: RecordType) => {
// const { code } = await createDictTask({
// updateMode: 'REALTIME_DELETE',
// modelAndDimPair: {
// [modelId]: [recordData.id],
// },
// });
// if (code !== 200) {
// message.error('删除字典导入任务创建失败!');
// }
// };
let rightColumns: ColumnsType<RecordType> = [ let rightColumns: ColumnsType<RecordType> = [
{ {
dataIndex: 'name', dataIndex: 'name',
@@ -141,104 +44,6 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
return <TransTypeTag type={type} />; return <TransTypeTag type={type} />;
}, },
}, },
// {
// dataIndex: 'y',
// title: (
// <TableTitleTooltips
// title="维度值可见"
// tooltips="勾选可见后,维度值将在搜索时可以被联想出来"
// />
// ),
// width: 120,
// render: (_: any, record: RecordType) => {
// const { type, bizName } = record;
// return type === TransType.DIMENSION ? (
// <Checkbox
// checked={knowledgeInfosMap?.[bizName]?.searchEnable}
// onChange={(e: CheckboxChangeEvent) => {
// updateKnowledgeInfosMap(record, { searchEnable: e.target.checked });
// if (!e.target.checked) {
// deleteDictTask(record);
// }
// }}
// onClick={(event) => {
// event.stopPropagation();
// }}
// />
// ) : (
// <></>
// );
// },
// },
// {
// dataIndex: 'taskState',
// width: 130,
// title: (
// <Space>
// 导入字典状态
// <span
// className={styles.taskStateRefreshIcon}
// onClick={() => {
// queryDictLatestTaskList();
// }}
// >
// <Tooltip title="刷新字典任务状态">
// <RedoOutlined />
// </Tooltip>
// </span>
// </Space>
// ),
// render: (_, record) => {
// const { id, type } = record;
// const target = taskStateMap[id];
// if (type === TransType.DIMENSION && target) {
// return DictTaskState[target] || '未知状态';
// }
// return '--';
// },
// },
// {
// title: '操作',
// dataIndex: 'x',
// render: (_: any, record: RecordType) => {
// const { type, bizName, id } = record;
// return type === TransType.DIMENSION ? (
// <Space>
// <Button
// style={{ padding: 0 }}
// key="importDictBtn"
// type="link"
// disabled={!knowledgeInfosMap?.[bizName]?.searchEnable}
// loading={!!recordLoadingMap[id]}
// onClick={(event) => {
// createDictTaskQuery(record);
// event.stopPropagation();
// }}
// >
// 导入字典
// </Button>
// <Button
// style={{ padding: 0 }}
// key="editable"
// type="link"
// disabled={!knowledgeInfosMap?.[bizName]?.searchEnable}
// onClick={(event) => {
// setCurrentRecord(record);
// setCurrentDimensionSettingFormData(
// knowledgeInfosMap?.[bizName]?.knowledgeAdvancedConfig,
// );
// setDimensionValueSettingModalVisible(true);
// event.stopPropagation();
// }}
// >
// 可见维度值设置
// </Button>
// </Space>
// ) : (
// <></>
// );
// },
// },
]; ];
const leftColumns: ColumnsType<RecordType> = [ const leftColumns: ColumnsType<RecordType> = [
@@ -299,17 +104,6 @@ const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
); );
}} }}
</Transfer> </Transfer>
{/* <DimensionValueSettingModal
visible={dimensionValueSettingModalVisible}
initialValues={currentDimensionSettingFormData}
onSubmit={(formValues) => {
updateKnowledgeInfosMap(currentRecord, { knowledgeAdvancedConfig: formValues });
setDimensionValueSettingModalVisible(false);
}}
onCancel={() => {
setDimensionValueSettingModalVisible(false);
}}
/> */}
</> </>
); );
}; };

View File

@@ -209,7 +209,7 @@ const DimensionValueSettingForm: ForwardRefRenderFunction<any, Props> = (
className={styles.form} className={styles.form}
> >
<FormItem <FormItem
style={{ marginTop: 15, marginBottom: -30 }} style={{ marginTop: 15 }}
label={ label={
<FormItemTitle <FormItemTitle
title={ title={
@@ -272,11 +272,11 @@ const DimensionValueSettingForm: ForwardRefRenderFunction<any, Props> = (
</FormItem> </FormItem>
{dimensionVisible && ( {dimensionVisible && (
<> <>
<Divider {/* <Divider
style={{ style={{
marginBottom: 35, marginBottom: 35,
}} }}
/> /> */}
<div style={{ padding: 20, border: '1px solid #eee', borderRadius: 10 }}> <div style={{ padding: 20, border: '1px solid #eee', borderRadius: 10 }}>
<div <div
style={{ style={{

View File

@@ -1,115 +0,0 @@
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { message, Form, Input, Select, Button } from 'antd';
import { updateModel } from '../../service';
import type { ISemantic } from '../../data';
import { formLayout } from '@/components/FormHelper/utils';
import styles from '../style.less';
type Props = {
modelData?: ISemantic.IModelItem;
dimensionList: ISemantic.IDimensionList;
modelId: number;
onSubmit: () => void;
};
const FormItem = Form.Item;
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
{ modelData, dimensionList, modelId, onSubmit },
ref,
) => {
const [form] = Form.useForm();
const [dimensionListOptions, setDimensionListOptions] = useState<any>([]);
const getFormValidateFields = async () => {
return await form.validateFields();
};
useEffect(() => {
form.resetFields();
if (!modelData?.entity) {
return;
}
const { entity } = modelData;
form.setFieldsValue({
...entity,
name: entity.names.join(','),
});
}, [modelData]);
useImperativeHandle(ref, () => ({
getFormValidateFields,
}));
useEffect(() => {
const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => {
return {
label: item.name,
value: item.id,
};
});
setDimensionListOptions(dimensionEnum);
}, [dimensionList]);
const saveEntity = async () => {
const values = await form.validateFields();
const { name = '' } = values;
const { code, msg, data } = await updateModel({
...modelData,
entity: {
...values,
names: name.split(','),
},
id: modelId,
modelId,
});
if (code === 200) {
form.setFieldValue('id', data);
onSubmit?.();
message.success('保存成功');
return;
}
message.error(msg);
};
return (
<>
<Form {...formLayout} form={form} layout="vertical" className={styles.form}>
<FormItem hidden={true} name="id" label="ID">
<Input placeholder="id" />
</FormItem>
<FormItem name="name" label="实体别名">
<Input placeholder="请输入实体别名,多个名称以英文逗号分隔" />
</FormItem>
<FormItem name="entityId" label="唯一标识">
<Select
allowClear
style={{ width: '100%' }}
// filterOption={(inputValue: string, item: any) => {
// const { label } = item;
// if (label.includes(inputValue)) {
// return true;
// }
// return false;
// }}
placeholder="请选择主体标识"
options={dimensionListOptions}
/>
</FormItem>
<FormItem>
<Button
type="primary"
onClick={() => {
saveEntity();
}}
>
</Button>
</FormItem>
</Form>
</>
);
};
export default forwardRef(EntityCreateForm);

View File

@@ -1,67 +0,0 @@
import { message, Space } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../../model';
import { getModelDetail } from '../../service';
import ProCard from '@ant-design/pro-card';
import EntityCreateForm from './EntityCreateForm';
import type { ISemantic } from '../../data';
type Props = {
dispatch: Dispatch;
domainManger: StateType;
};
const EntitySettingSection: React.FC<Props> = ({ domainManger }) => {
const { dimensionList, selectModelId: modelId } = domainManger;
const [modelData, setModelData] = useState<ISemantic.IModelItem>();
const entityCreateRef = useRef<any>({});
const queryDomainData: any = async () => {
const { code, data } = await getModelDetail({
modelId,
});
if (code === 200) {
setModelData(data);
return;
}
message.error('获取问答设置信息失败');
};
const initPage = async () => {
queryDomainData();
};
useEffect(() => {
initPage();
}, [modelId]);
return (
<div style={{ width: 800, margin: '20px auto' }}>
<Space direction="vertical" style={{ width: '100%' }} size={20}>
{
<ProCard title="实体" bordered>
<EntityCreateForm
ref={entityCreateRef}
modelId={Number(modelId)}
modelData={modelData}
dimensionList={dimensionList}
onSubmit={() => {
queryDomainData();
}}
/>
</ProCard>
}
</Space>
</div>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(EntitySettingSection);

View File

@@ -133,6 +133,7 @@ const ModelTable: React.FC<Props> = ({
title: '操作', title: '操作',
dataIndex: 'x', dataIndex: 'x',
valueType: 'option', valueType: 'option',
width: 150,
render: (_, record) => { render: (_, record) => {
return ( return (
<Space className={styles.ctrlBtnContainer}> <Space className={styles.ctrlBtnContainer}>

View File

@@ -12,9 +12,45 @@
display: flex; display: flex;
position: relative; position: relative;
.sider { .sider {
flex: 0 0 350px; flex: 0 0 auto;
width: 285px;
border-right: 5px solid #eee; border-right: 5px solid #eee;
position: relative;
transition: all 0.2s,background 0s;
.treeContainer {
width: 100%;
opacity: 1;
transition: all 0.2s,background 0s;
}
&.siderCollapsed {
width: 20px;
& .treeContainer{
opacity: 0;
}
}
.siderCollapsedButton {
position: absolute;
inset-block-start: 17px;
border: 1px solid #eee;
cursor: pointer;
z-index: 101;
width: 24px;
height: 24px;
text-align: center;
border-radius: 40px;
inset-inline-end: -13px;
transition: transform 0.3s;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: rgba(0, 0, 0, 0.25);
background-color: #ffffff;
box-shadow: 0 2px 8px -2px rgba(0,0,0,0.05), 0 1px 4px -1px rgba(25,15,15,0.07), 0 0 1px 0 rgba(0,0,0,0.08);
&:hover {
color: rgba(0, 0, 0, 0.75);
}
}
.domainTitle { .domainTitle {
margin-bottom: 0; margin-bottom: 0;
font-size: 20px; font-size: 20px;
@@ -49,8 +85,9 @@
} }
.search { .search {
width: calc(100% - 60px); // width: calc(100% - 100px);
margin: 10px 0 10px 35px; width: 100%;
margin: 10px 0 10px 10px;
} }
.tree { .tree {
@@ -190,6 +227,7 @@
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
.searchContainer { .searchContainer {
width: 280px;
padding:3px 0; padding:3px 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
@@ -327,7 +365,8 @@
.breadcrumb{ .breadcrumb{
font-size: 18px; font-size: 18px;
margin: 20px 0 0 20px; margin: 17px 0 0 20px;
padding-bottom: 3px;
:global { :global {
.ant-breadcrumb-link { .ant-breadcrumb-link {
height: 28px; height: 28px;
@@ -336,4 +375,12 @@
font-size: 18px; font-size: 18px;
} }
} }
}
.classDataSourceTypeModal {
:global {
.ant-modal-body{
padding: 0px;
}
}
} }

View File

@@ -30,6 +30,11 @@ export const SENSITIVE_LEVEL_ENUM = SENSITIVE_LEVEL_OPTIONS.reduce(
{}, {},
); );
export const IS_TAG_ENUM = {
1: '是',
0: '否',
};
export const SENSITIVE_LEVEL_COLOR = { export const SENSITIVE_LEVEL_COLOR = {
[SENSITIVE_LEVEL.LOW]: 'lime', [SENSITIVE_LEVEL.LOW]: 'lime',
[SENSITIVE_LEVEL.MID]: 'warning', [SENSITIVE_LEVEL.MID]: 'warning',

View File

@@ -18,6 +18,7 @@ import { getSystemConfig, saveSystemConfig } from '@/services/user';
import ProCard from '@ant-design/pro-card'; import ProCard from '@ant-design/pro-card';
import SelectTMEPerson from '@/components/SelectTMEPerson'; import SelectTMEPerson from '@/components/SelectTMEPerson';
import { SystemConfigParametersItem, SystemConfig } from './types'; import { SystemConfigParametersItem, SystemConfig } from './types';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import { groupBy } from 'lodash'; import { groupBy } from 'lodash';
const FormItem = Form.Item; const FormItem = Form.Item;
@@ -30,6 +31,7 @@ const System: React.FC = () => {
[], [],
); );
const [configSource, setConfigSource] = useState<SystemConfig>(); const [configSource, setConfigSource] = useState<SystemConfig>();
const [paramDescMap, setParamDescMap] = useState<Record<string, string>>({});
useEffect(() => { useEffect(() => {
querySystemConfig(); querySystemConfig();
@@ -37,8 +39,8 @@ const System: React.FC = () => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const querySystemConfig = async () => { const querySystemConfig = async () => {
const { code, data, msg } = await getSystemConfig(); const { code, data, msg } = await getSystemConfig();
if (code === 200) { if (code === 200 && data) {
const { parameters, admins } = data; const { parameters = [], admins = [] } = data;
const groupByConfig = groupBy(parameters, 'module'); const groupByConfig = groupBy(parameters, 'module');
const anchor = Object.keys(groupByConfig).map((key: string) => { const anchor = Object.keys(groupByConfig).map((key: string) => {
return { return {
@@ -50,12 +52,24 @@ const System: React.FC = () => {
setAnchorItems(anchor); setAnchorItems(anchor);
setSystemConfig(groupByConfig); setSystemConfig(groupByConfig);
setInitData(admins, parameters); setInitData(admins, parameters);
initDescMap(parameters);
setConfigSource(data); setConfigSource(data);
} else { } else {
message.error(msg); message.error(msg);
} }
}; };
const initDescMap = (systemConfigParameters: SystemConfigParametersItem[]) => {
const descData = systemConfigParameters.reduce(
(descMap: Record<string, string>, item: SystemConfigParametersItem) => {
descMap[item.name] = item.description;
return descMap;
},
{},
);
setParamDescMap(descData);
};
const setInitData = (admins: string[], systemConfigParameters: SystemConfigParametersItem[]) => { const setInitData = (admins: string[], systemConfigParameters: SystemConfigParametersItem[]) => {
const fieldsValue = systemConfigParameters.reduce( const fieldsValue = systemConfigParameters.reduce(
(fields, item) => { (fields, item) => {
@@ -81,6 +95,7 @@ const System: React.FC = () => {
return { return {
...item, ...item,
value: submitData[name], value: submitData[name],
description: paramDescMap[name],
}; };
} }
return item; return item;
@@ -158,7 +173,7 @@ const System: React.FC = () => {
</FormItem> </FormItem>
); );
case 'list': { case 'list': {
const { candidateValues } = item; const { candidateValues = [] } = item;
const options = candidateValues.map((value) => { const options = candidateValues.map((value) => {
return { label: value, value }; return { label: value, value };
}); });
@@ -170,7 +185,23 @@ const System: React.FC = () => {
break; break;
} }
return ( return (
<FormItem name={name} label={comment} key={name}> <FormItem
name={name}
key={name}
label={
<FormItemTitle
title={comment}
subTitle={paramDescMap[name]}
// subTitleEditable={true}
onSubTitleChange={(title) => {
setParamDescMap({
...paramDescMap,
[name]: title,
});
}}
/>
}
>
{defaultItem} {defaultItem}
</FormItem> </FormItem>
); );

View File

@@ -4,6 +4,7 @@ export type SystemConfigParametersItem = {
comment: string; comment: string;
value: string; value: string;
candidateValues: string[]; candidateValues: string[];
description: string;
}; };
export type SystemConfig = { export type SystemConfig = {