[improvement][project] supersonic 0.6.0 version update (#16)

Co-authored-by: lexluo <lexluo@tencent.com>
This commit is contained in:
lexluo09
2023-07-16 21:32:33 +08:00
committed by GitHub
parent a0869dc7bd
commit 041daad1e4
261 changed files with 12031 additions and 3266 deletions

View File

@@ -38,7 +38,8 @@ const ChatMsg: React.FC<Props> = ({
if (
categoryField.length > 1 ||
queryMode === 'ENTITY_DETAIL' ||
queryMode === 'ENTITY_DIMENSION'
queryMode === 'ENTITY_DIMENSION' ||
(categoryField.length === 1 && metricFields.length === 0)
) {
return <Table data={data} />;
}

View File

@@ -1,6 +1,6 @@
// https://umijs.org/config/
import { defineConfig } from 'umi';
import defaultSettings from './defaultSettings';
import defaultSettings, { publicPath } from './defaultSettings';
import themeSettings from './themeSettings';
import proxy from './proxy';
import routes from './routes';
@@ -9,8 +9,6 @@ import ENV_CONFIG from './envConfig';
const { REACT_APP_ENV, RUN_TYPE } = process.env;
const publicPath = '/webapp/';
export default defineConfig({
define: {
// 添加这个自定义的环境变量
@@ -71,14 +69,6 @@ export default defineConfig({
base: publicPath,
publicPath,
outputPath: RUN_TYPE === 'local' ? 'supersonic-webapp' : 'dist',
// https://github.com/zthxxx/react-dev-inspector
plugins: ['react-dev-inspector/plugins/umi/react-inspector'],
inspectorConfig: {
// loader options type and docs see below
exclude: [],
babelPlugins: [],
babelOptions: {},
},
resolve: {
includes: ['src/components'],
},

View File

@@ -21,5 +21,6 @@ const Settings: LayoutSettings & {
ignoreFlatMenu: true,
},
};
export const publicPath = '/webapp/';
export default Settings;

View File

@@ -1,13 +1,5 @@
export default {
dev: {
'/api/chat/': {
target: 'http://localhost:9080',
changeOrigin: true,
},
'/api/semantic/': {
target: 'http://localhost:9081',
changeOrigin: true,
},
'/api/': {
target: 'http://localhost:9080',
changeOrigin: true,

View File

@@ -1,27 +1,34 @@
export const ROUTE_AUTH_CODES = {
export const ROUTE_AUTH_CODES = {};
const ENV_KEY = {
CHAT: 'chat',
CHAT_SETTING: 'chatSetting',
SEMANTIC: 'semantic',
};
const { APP_TARGET } = process.env;
const ROUTES = [
...(APP_TARGET !== 'inner'
? [
{
path: '/chat',
name: 'chat',
component: './Chat',
envEnableList: [ENV_KEY.CHAT],
},
]
: []),
{
path: '/chat',
name: 'chat',
component: './Chat',
access: ROUTE_AUTH_CODES.CHAT,
},
{
path: '/chatSetting',
path: '/chatSetting/:modelId?/:menuKey?',
name: 'chatSetting',
component: './SemanticModel/ChatSetting',
access: ROUTE_AUTH_CODES.CHAT_SETTING,
envEnableList: [ENV_KEY.CHAT],
},
{
path: '/semanticModel',
path: '/semanticModel/:modelId?/:menuKey?',
name: 'semanticModel',
component: './SemanticModel/ProjectManager',
access: ROUTE_AUTH_CODES.SEMANTIC,
envEnableList: [ENV_KEY.SEMANTIC],
},
{
path: '/login',
@@ -32,7 +39,11 @@ const ROUTES = [
},
{
path: '/',
redirect: '/chat',
redirect: APP_TARGET === 'inner' ? '/semanticModel' : '/chat',
envRedirect: {
[ENV_KEY.CHAT]: '/chat',
[ENV_KEY.SEMANTIC]: '/semanticModel',
},
},
{
path: '/401',

View File

@@ -1,3 +1,3 @@
{
"env": "semantic"
"env": ""
}

View File

@@ -1,19 +1,19 @@
import type { Settings as LayoutSettings } from '@ant-design/pro-layout';
import { Spin, Space } from 'antd';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { history } from 'umi';
import type { RunTimeLayoutConfig } from 'umi';
import { AUTH_TOKEN_KEY, FROM_URL_KEY } from '@/common/constants';
import RightContent from '@/components/RightContent';
import S2Icon, { ICON } from '@/components/S2Icon';
import type { Settings as LayoutSettings } from '@ant-design/pro-layout';
import { Space, Spin } from 'antd';
import qs from 'qs';
import { queryCurrentUser } from './services/user';
import { queryToken } from './services/login';
import ScaleLoader from 'react-spinners/ScaleLoader';
import type { RunTimeLayoutConfig } from 'umi';
import { history } from 'umi';
import defaultSettings from '../config/defaultSettings';
import settings from '../config/themeSettings';
import { deleteUrlQuery } from './utils/utils';
import { AUTH_TOKEN_KEY, FROM_URL_KEY } from '@/common/constants';
import { queryToken } from './services/login';
import { queryCurrentUser } from './services/user';
import { traverseRoutes, deleteUrlQuery } from './utils/utils';
import { publicPath } from '../config/defaultSettings';
export { request } from './services/request';
import { ROUTE_AUTH_CODES } from '../config/routes';
const TOKEN_KEY = AUTH_TOKEN_KEY;
@@ -21,8 +21,9 @@ const replaceRoute = '/';
const getRuningEnv = async () => {
try {
// const response = await fetch(`supersonic.config.json`);
// const config = await response.json();
const response = await fetch(`${publicPath}supersonic.config.json`);
const config = await response.json();
return config;
} catch (error) {
console.warn('无法获取配置文件: 运行时环境将以semantic启动');
}
@@ -60,16 +61,7 @@ const getToken = async () => {
};
const getAuthCodes = () => {
const { RUN_TYPE, APP_TARGET } = process.env;
if (RUN_TYPE === 'local') {
return location.host.includes('9080')
? [ROUTE_AUTH_CODES.CHAT, ROUTE_AUTH_CODES.CHAT_SETTING]
: [ROUTE_AUTH_CODES.SEMANTIC];
}
if (APP_TARGET === 'inner') {
return [ROUTE_AUTH_CODES.CHAT_SETTING, ROUTE_AUTH_CODES.SEMANTIC];
}
return [ROUTE_AUTH_CODES.CHAT, ROUTE_AUTH_CODES.CHAT_SETTING, ROUTE_AUTH_CODES.SEMANTIC];
return [];
};
export async function getInitialState(): Promise<{
@@ -79,7 +71,7 @@ export async function getInitialState(): Promise<{
codeList?: string[];
authCodes?: string[];
}> {
await getRuningEnv();
// await getRuningEnv();
const fetchUserInfo = async () => {
try {
const { code, data } = await queryCurrentUser();
@@ -115,6 +107,21 @@ export async function getInitialState(): Promise<{
};
}
export async function patchRoutes({ routes }) {
const config = await getRuningEnv();
if (config && config.env) {
const { env } = config;
const target = routes[0].routes;
if (env) {
const envRoutes = traverseRoutes(target, env);
// 清空原本route;
target.splice(0, 99);
// 写入根据环境转换过的的route
target.push(...envRoutes);
}
}
}
export const layout: RunTimeLayoutConfig = (params) => {
const { initialState } = params as any;
return {
@@ -136,16 +143,6 @@ export const layout: RunTimeLayoutConfig = (params) => {
contentStyle: { ...(initialState?.contentStyle || {}) },
rightContentRender: () => <RightContent />,
disableContentMargin: true,
onPageChange: (location: any) => {
const { pathname } = location;
const { RUN_TYPE, APP_TARGET } = process.env;
if (
(RUN_TYPE === 'local' && !window.location.host.includes('9080') && pathname === '/chat') ||
(APP_TARGET === 'inner' && pathname === '/chat')
) {
history.push('/semanticModel');
}
},
menuHeaderRender: undefined,
childrenRender: (dom) => {
return dom;

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 110 110" class="design-iconfont">
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.372 73.6771L27.3046 72.1376L56.7296 89.9624L85.6441 72.1412L86.5885 73.6735L56.7377 92.0718L26.372 73.6771Z" fill="#252D47"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.7337 14L30.0366 28.8252L55.7337 43.6504L81.4117 28.8252L55.7337 14Z" fill="url(#pe7lhzo0y__paint0_linear_5_2680)"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.0366 28.8252L55.7336 43.6504V74.2892L30.0176 59.464L30.0366 28.8252Z" fill="url(#pe7lhzo0y__paint1_linear_5_2680)"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.7336 43.6504L81.4307 28.8252L81.4117 59.464L55.7336 74.2892L55.7336 43.6504Z" fill="#0D36FF"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.0269 47.8014L9.01038 55.8879L23.0269 63.9743L37.0331 55.8879L23.0269 47.8014Z" fill="#fff"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.01037 55.8879L23.0269 63.9743V80.6864L9 72.5999L9.01037 55.8879Z" fill="url(#pe7lhzo0y__paint2_linear_5_2680)"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.0269 63.9743L37.0435 55.8879L37.0331 72.5999L23.0269 80.6864L23.0269 63.9743Z" fill="url(#pe7lhzo0y__paint3_linear_5_2680)"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.828 47.8014L73.8114 55.8879L87.828 63.9743L101.834 55.8879L87.828 47.8014Z" fill="url(#pe7lhzo0y__paint4_linear_5_2680)"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.8114 55.8879L87.828 63.9743V80.6864L73.801 72.5999L73.8114 55.8879Z" fill="#252D47"></path>
<path fill-rule="evenodd" clip-rule="evenodd" d="M87.8279 63.9743L101.845 55.8879L101.834 72.5999L87.8279 80.6864L87.8279 63.9743Z" fill="url(#pe7lhzo0y__paint5_linear_5_2680)"></path>
<path fill="url(#pe7lhzo0y__paint6_radial_5_2680)" d="M56.5184 79.34609999999999A8.346 8.346 0 1 0 56.5184 96.0381A8.346 8.346 0 1 0 56.5184 79.34609999999999Z"></path>
<defs>
<linearGradient id="pe7lhzo0y__paint0_linear_5_2680" x1="30.0366" y1="14" x2="30.0366" y2="43.6504" gradientUnits="userSpaceOnUse">
<stop stop-color="#4281FF"></stop>
<stop offset="1" stop-color="#528BFF"></stop>
</linearGradient>
<linearGradient id="pe7lhzo0y__paint1_linear_5_2680" x1="30.0176" y1="28.8252" x2="30.0176" y2="74.2892" gradientUnits="userSpaceOnUse">
<stop stop-color="#2E6DFF"></stop>
<stop offset="1" stop-color="#0F47FF"></stop>
</linearGradient>
<linearGradient id="pe7lhzo0y__paint2_linear_5_2680" x1="9" y1="55.8879" x2="9" y2="80.6864" gradientUnits="userSpaceOnUse">
<stop stop-color="#EDF2FA"></stop>
<stop offset="1" stop-color="#E4EBF7"></stop>
</linearGradient>
<linearGradient id="pe7lhzo0y__paint3_linear_5_2680" x1="23.0269" y1="55.8879" x2="23.0269" y2="80.6864" gradientUnits="userSpaceOnUse">
<stop stop-color="#CAD6EB"></stop>
<stop offset="1" stop-color="#D5DFF0"></stop>
</linearGradient>
<linearGradient id="pe7lhzo0y__paint4_linear_5_2680" x1="86.9609" y1="65.4426" x2="97.8976" y2="53.5332" gradientUnits="userSpaceOnUse">
<stop stop-color="#475275"></stop>
<stop offset="1" stop-color="#363F5C"></stop>
</linearGradient>
<linearGradient id="pe7lhzo0y__paint5_linear_5_2680" x1="94.4051" y1="82.9377" x2="105.023" y2="79.1662" gradientUnits="userSpaceOnUse">
<stop stop-color="#171D2E"></stop>
<stop offset="1" stop-color="#252D47"></stop>
</linearGradient>
<radialGradient id="pe7lhzo0y__paint6_radial_5_2680" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 16.692 -16.692 0 54.7478 85.5053)">
<stop stop-color="#4985FF"></stop>
<stop offset="1" stop-color="#0D36FF"></stop>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28" class="design-iconfont">
<path opacity=".3" d="M13.4913 12.3006C13.8051 12.1152 14.1949 12.1152 14.5087 12.3006L23.543 17.6391C24.1981 18.0261 24.1981 18.9739 23.543 19.3609L14.5087 24.6994C14.1949 24.8848 13.8051 24.8848 13.4913 24.6994L4.45695 19.3609C3.80192 18.9739 3.80193 18.0261 4.45695 17.6391L13.4913 12.3006Z" fill="#0052D9"></path>
<g filter="url(#91lpbu3w7__filter0_b_1_7)">
<path d="M13.4913 7.30061C13.8051 7.11518 14.1949 7.11518 14.5087 7.30061L23.543 12.6391C24.1981 13.0261 24.1981 13.9739 23.543 14.3609L14.5087 19.6994C14.1949 19.8848 13.8051 19.8848 13.4913 19.6994L4.45695 14.3609C3.80192 13.9739 3.80193 13.0261 4.45695 12.6391L13.4913 7.30061Z" fill="#166CE4" fill-opacity=".6"></path>
</g>
<path d="M13.4913 2.30061C13.8051 2.11518 14.1949 2.11518 14.5087 2.30061L23.543 7.63907C24.1981 8.02614 24.1981 8.97386 23.543 9.36093L14.5087 14.6994C14.1949 14.8848 13.8051 14.8848 13.4913 14.6994L4.45695 9.36093C3.80192 8.97386 3.80193 8.02614 4.45695 7.63907L13.4913 2.30061Z" fill="#0052D9"></path>
<defs>
<filter id="91lpbu3w7__filter0_b_1_7" x="1.96568" y="5.16154" width="24.0686" height="16.6769" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0"></feFlood>
<feGaussianBlur in="BackgroundImage" stdDeviation="1"></feGaussianBlur>
<feComposite in2="SourceAlpha" operator="in"></feComposite>
<feBlend in="SourceGraphic" in2="effect1_backgroundBlur_1_7"></feBlend>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -6,7 +6,7 @@ import styles from './index.less';
export type HeaderDropdownProps = {
overlayClassName?: string;
overlay: React.ReactNode | (() => React.ReactNode) | any;
// overlay: React.ReactNode | (() => React.ReactNode) | any;
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
} & Omit<DropDownProps, 'overlay'>;

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { LogoutOutlined } from '@ant-design/icons';
import { Menu } from 'antd';
import { useModel } from 'umi';
import HeaderDropdown from '../HeaderDropdown';
import styles from './index.less';
@@ -29,28 +28,30 @@ const { APP_TARGET } = process.env;
const AvatarDropdown: React.FC<GlobalHeaderRightProps> = () => {
const { initialState = {}, setInitialState } = useModel('@@initialState');
const onMenuClick = (event: any) => {
const { key } = event;
if (key === 'logout' && initialState) {
loginOut().then(() => {
setInitialState({ ...initialState, currentUser: undefined });
});
return;
}
};
const { currentUser = {} } = initialState as any;
const menuHeaderDropdown = (
<Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
<Menu.Item key="logout">
<LogoutOutlined />
退
</Menu.Item>
</Menu>
);
const items = [
{
label: (
<>
<LogoutOutlined />
退
</>
),
onClick: (event: any) => {
const { key } = event;
if (key === 'logout' && initialState) {
loginOut().then(() => {
setInitialState({ ...initialState, currentUser: undefined });
});
return;
}
},
key: 'logout',
},
];
return (
<HeaderDropdown overlay={menuHeaderDropdown} disabled={APP_TARGET === 'inner'}>
<HeaderDropdown menu={{ items }} disabled={APP_TARGET === 'inner'}>
<span className={`${styles.action} ${styles.account}`}>
<TMEAvatar className={styles.avatar} size="small" staffName={currentUser.staffName} />
<span className={cx(styles.name, 'anticon')}>{currentUser.staffName}</span>

View File

@@ -215,3 +215,21 @@ ol {
.ant-notification-topRight {
right: 240px !important;
}
.g6ContextMenuContainer {
font-size: 12px;
color: #545454;
}
.g6ContextMenuContainer li {
cursor: pointer;
list-style-type:none;
list-style: none;
margin-left: 0;
}
.g6ContextMenuContainer ul {
width: 100%;
padding: 0;
}
.g6ContextMenuContainer li:hover {
color: #aaa;
}

View File

@@ -24,15 +24,8 @@ export function getAllConversations() {
return request<Result<any>>(`${prefix}/chat/manage/getAll`);
}
export function getMiniProgramList(id: string, type: string) {
return request<Result<any>>(`/openapi/bd-bi/api/polaris/sql/getInterpretList/${id}/${type}`, {
method: 'GET',
skipErrorHandler: true,
});
}
export function getDomainList() {
return request<Result<DomainType[]>>(`${prefix}/semantic/domain/getDomainList`, {
return request<Result<DomainType[]>>(`${prefix}/chat/conf/domainList/view`, {
method: 'GET',
skipErrorHandler: true,
});

View File

@@ -1,15 +1,18 @@
import { Tabs, Popover } from 'antd';
import { Tabs, Popover, message } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet } from 'umi';
import { connect, Helmet, useParams, history } from 'umi';
import ProjectListTree from './components/ProjectList';
import styles from './components/style.less';
import type { StateType } from './model';
import { DownOutlined } from '@ant-design/icons';
import EntitySection from './components/Entity/EntitySection';
import { ISemantic } from './data';
import { getDomainList } from './service';
import OverView from './components/OverView';
import { findLeafNodesFromDomainList } from './utils';
import { ChatConfigType } from './enum';
import type { Dispatch } from 'umi';
const { TabPane } = Tabs;
type Props = {
domainManger: StateType;
dispatch: Dispatch;
@@ -17,8 +20,15 @@ type Props = {
const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
window.RUNNING_ENV = 'chat';
const { selectDomainId, selectDomainName } = domainManger;
const defaultTabKey = 'metric';
const params: any = useParams();
const menuKey = params.menuKey ? params.menuKey : defaultTabKey;
const modelId = params.modelId;
const { selectDomainId, selectDomainName, domainList } = domainManger;
const [modelList, setModelList] = useState<ISemantic.IDomainItem[]>([]);
const [open, setOpen] = useState(false);
const [isModel, setIsModel] = useState<boolean>(false);
const [activeKey, setActiveKey] = useState<string>(menuKey);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
@@ -37,16 +47,103 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
domainId: selectDomainId,
},
});
pushUrlMenu(selectDomainId, menuKey);
}
}, [selectDomainId]);
useEffect(() => {
if (!selectDomainId) {
return;
}
const list = findLeafNodesFromDomainList(domainList, selectDomainId);
setModelList(list);
if (Array.isArray(list) && list.length > 0) {
setIsModel(false);
pushUrlMenu(selectDomainId, 'overview');
setActiveKey('overview');
} else {
setIsModel(true);
const currentMenuKey = menuKey === 'overview' ? defaultTabKey : menuKey;
pushUrlMenu(selectDomainId, currentMenuKey);
setActiveKey(currentMenuKey);
}
}, [domainList, selectDomainId]);
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
const targetNode = domainList.filter((item: any) => {
return `${item.id}` === modelId;
})[0];
if (!targetNode) {
const firstRootNode = domainList.filter((item: any) => {
return item.parentId === 0;
})[0];
if (firstRootNode) {
const { id, name } = firstRootNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: firstRootNode,
});
}
} else {
const { id, name } = targetNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: targetNode,
});
}
};
const initProjectTree = async () => {
const { code, data, msg } = await getDomainList();
if (code === 200) {
if (!selectDomainId) {
initSelectedDomain(data);
}
dispatch({
type: 'domainManger/setDomainList',
payload: { domainList: data },
});
} else {
message.error(msg);
}
};
useEffect(() => {
initProjectTree();
}, []);
const pushUrlMenu = (domainId: number, menuKey: string) => {
history.push(`/chatSetting/${domainId}/${menuKey}`);
};
const tabItem = [
{
label: '子主题域',
key: 'overview',
children: <OverView modelList={modelList} />,
},
];
const isModelItem = [
{
label: '指标场景',
key: 'metric',
children: <EntitySection chatConfigType={ChatConfigType.AGG} />,
},
{
label: '明细场景',
key: 'dimenstion',
children: <EntitySection chatConfigType={ChatConfigType.DETAIL} />,
},
];
return (
<div className={styles.projectBody}>
<Helmet title={'问答设置-超音数'} />
{/* 页面改版取消侧边栏转换为popover形式后因为popover不触发则组件不加载需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑在此引入但并不显示 */}
<div style={{ display: 'none' }}>
<ProjectListTree />
</div>
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
@@ -57,6 +154,7 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
}}
content={
<ProjectListTree
createDomainBtnVisible={false}
onTreeSelected={() => {
setOpen(false);
}}
@@ -78,11 +176,16 @@ const ChatSetting: React.FC<Props> = ({ domainManger, dispatch }) => {
</h2>
{selectDomainId ? (
<>
<Tabs className={styles.tab} defaultActiveKey="chatSetting" destroyInactiveTabPane>
<TabPane className={styles.tabPane} tab="问答设置" key="chatSetting">
<EntitySection />
</TabPane>
</Tabs>
<Tabs
className={styles.tab}
activeKey={activeKey}
destroyInactiveTabPane
onChange={(menuKey: string) => {
setActiveKey(menuKey);
pushUrlMenu(selectDomainId, menuKey);
}}
items={!isModel ? tabItem : isModelItem}
/>
</>
) : (
<h2 className={styles.mainTip}></h2>

View File

@@ -174,7 +174,7 @@ const FieldForm: React.FC<Props> = ({ fields, onFieldChange }) => {
[isCreateName]: 1,
});
}}
placeholder="请输入中文名"
placeholder="请填写名称"
/>
</Checkbox>
);

View File

@@ -1,20 +1,23 @@
import { Tabs, Popover } from 'antd';
import { Tabs, Popover, message } from 'antd';
import React, { useEffect, useState } from 'react';
import { connect, Helmet } from 'umi';
import { connect, Helmet, history, useParams } from 'umi';
import ProjectListTree from './components/ProjectList';
import ClassDataSourceTable from './components/ClassDataSourceTable';
import ClassDimensionTable from './components/ClassDimensionTable';
import ClassMetricTable from './components/ClassMetricTable';
import PermissionSection from './components/Permission/PermissionSection';
import DatabaseSection from './components/Database/DatabaseSection';
import OverView from './components/OverView';
import styles from './components/style.less';
import type { StateType } from './model';
import { DownOutlined } from '@ant-design/icons';
import SemanticFlow from './SemanticFlows';
import { ISemantic } from './data';
import { findLeafNodesFromDomainList } from './utils';
import SemanticGraph from './SemanticGraph';
import { getDomainList } from './service';
import type { Dispatch } from 'umi';
const { TabPane } = Tabs;
type Props = {
domainManger: StateType;
dispatch: Dispatch;
@@ -22,14 +25,89 @@ type Props = {
const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
window.RUNNING_ENV = 'semantic';
const { selectDomainId, selectDomainName } = domainManger;
const defaultTabKey = 'xflow';
const params: any = useParams();
const menuKey = params.menuKey ? params.menuKey : defaultTabKey;
const modelId = params.modelId;
const { selectDomainId, selectDomainName, domainList } = domainManger;
const [modelList, setModelList] = useState<ISemantic.IDomainItem[]>([]);
const [isModel, setIsModel] = useState<boolean>(false);
const [open, setOpen] = useState(false);
const [activeKey, setActiveKey] = useState<string>(menuKey);
const initSelectedDomain = (domainList: ISemantic.IDomainItem[]) => {
const targetNode = domainList.filter((item: any) => {
return `${item.id}` === modelId;
})[0];
if (!targetNode) {
const firstRootNode = domainList.filter((item: any) => {
return item.parentId === 0;
})[0];
if (firstRootNode) {
const { id, name } = firstRootNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: firstRootNode,
});
}
} else {
const { id, name } = targetNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: targetNode,
});
}
};
const initProjectTree = async () => {
const { code, data, msg } = await getDomainList();
if (code === 200) {
if (!selectDomainId) {
initSelectedDomain(data);
}
dispatch({
type: 'domainManger/setDomainList',
payload: { domainList: data },
});
} else {
message.error(msg);
}
};
useEffect(() => {
initProjectTree();
}, []);
useEffect(() => {
if (!selectDomainId) {
return;
}
const list = findLeafNodesFromDomainList(domainList, selectDomainId);
setModelList(list);
if (Array.isArray(list) && list.length > 0) {
setIsModel(false);
pushUrlMenu(selectDomainId, 'overview');
setActiveKey('overview');
} else {
setIsModel(true);
const currentMenuKey = menuKey === 'overview' ? defaultTabKey : menuKey;
pushUrlMenu(selectDomainId, currentMenuKey);
setActiveKey(currentMenuKey);
}
}, [domainList, selectDomainId]);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
const pushUrlMenu = (domainId: number, menuKey: string) => {
history.push(`/semanticModel/${domainId}/${menuKey}`);
};
useEffect(() => {
if (selectDomainId) {
dispatch({
@@ -53,21 +131,68 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
}
}, [selectDomainId]);
useEffect(() => {
const width = document.getElementById('tab');
const switchWarpper: any = document.getElementById('switch');
if (width && switchWarpper) {
switchWarpper.style.width = width.offsetWidth * 0.77 + 'px';
}
});
const tabItem = [
{
label: '子主题域',
key: 'overview',
children: <OverView modelList={modelList} />,
},
{
label: '权限管理',
key: 'permissonSetting',
children: <PermissionSection />,
},
];
const isModelItem = [
// {
// label: '关系可视化',
// key: 'graph',
// children: (
// <div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
// <SemanticGraph domainId={selectDomainId} />
// </div>
// ),
// },
{
label: '可视化建模',
key: 'xflow',
children: (
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<SemanticFlow />
</div>
),
},
{
label: '数据库',
key: 'dataBase',
children: <DatabaseSection />,
},
{
label: '数据源',
key: 'dataSource',
children: <ClassDataSourceTable />,
},
{
label: '维度',
key: 'dimenstion',
children: <ClassDimensionTable key={selectDomainId} />,
},
{
label: '指标',
key: 'metric',
children: <ClassMetricTable />,
},
{
label: '权限管理',
key: 'permissonSetting',
children: <PermissionSection />,
},
];
return (
<div className={styles.projectBody}>
<Helmet title={'语义建模-超音数'} />
{/* 页面改版取消侧边栏转换为popover形式后因为popover不触发则组件不加载需要保留原本页面初始化需要ProjectListTree向model中写入首个主题域数据逻辑在此引入但并不显示 */}
<div style={{ display: 'none' }}>
<ProjectListTree />
</div>
<div className={styles.projectManger}>
<h2 className={styles.title}>
<Popover
@@ -81,6 +206,9 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
onTreeSelected={() => {
setOpen(false);
}}
onTreeDataUpdate={() => {
initProjectTree();
}}
/>
}
trigger="click"
@@ -89,7 +217,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
>
<div className={styles.domainSelector}>
<span className={styles.domainTitle}>
{selectDomainName ? `选择的主题域:${selectDomainName}` : '主题域信息'}
{selectDomainName ? `当前主题域:${selectDomainName}` : '主题域信息'}
</span>
<span className={styles.downIcon}>
<DownOutlined />
@@ -99,33 +227,16 @@ const DomainManger: React.FC<Props> = ({ domainManger, dispatch }) => {
</h2>
{selectDomainId ? (
<>
<Tabs className={styles.tab} defaultActiveKey="xflow" destroyInactiveTabPane>
{/* <TabPane className={styles.tabPane} tab="关系可视化" key="graph">
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<SemanticGraph domainId={selectDomainId} />
</div>
</TabPane> */}
<TabPane className={styles.tabPane} tab="可视化建模" key="xflow">
<div style={{ width: '100%', height: 'calc(100vh - 200px)' }}>
<SemanticFlow />
</div>
</TabPane>
<TabPane className={styles.tabPane} tab="数据库" key="dataBase">
<DatabaseSection />
</TabPane>
<TabPane className={styles.tabPane} tab="数据源" key="dataSource">
<ClassDataSourceTable />
</TabPane>
<TabPane className={styles.tabPane} tab="维度" key="dimenstion">
<ClassDimensionTable key={selectDomainId} />
</TabPane>
<TabPane className={styles.tabPane} tab="指标" key="metric">
<ClassMetricTable />
</TabPane>
<TabPane className={styles.tabPane} tab="权限管理" key="permissonSetting">
<PermissionSection />
</TabPane>
</Tabs>
<Tabs
className={styles.tab}
items={!isModel ? tabItem : isModelItem}
activeKey={activeKey}
destroyInactiveTabPane
onChange={(menuKey: string) => {
setActiveKey(menuKey);
pushUrlMenu(selectDomainId, menuKey);
}}
/>
</>
) : (
<h2 className={styles.mainTip}></h2>

View File

@@ -37,21 +37,21 @@ const DataSourceRelationFormDrawer: React.FC<DataSourceRelationFormDrawerProps>
const dataSourceFromIdentifiers = sourceData?.datasourceDetail?.identifiers || [];
const dataSourceToIdentifiers = targetData?.datasourceDetail?.identifiers || [];
const dataSourceToIdentifiersNames = dataSourceToIdentifiers.map((item) => {
return item.name;
return item.bizName;
});
const keyOptions = dataSourceFromIdentifiers.reduce((options: any[], item: any) => {
const { name } = item;
if (dataSourceToIdentifiersNames.includes(name)) {
const { bizName } = item;
if (dataSourceToIdentifiersNames.includes(bizName)) {
options.push(item);
}
return options;
}, []);
setDataSourceOptions(
keyOptions.map((item: any) => {
const { name } = item;
const { name, bizName } = item;
return {
label: name,
value: name,
value: bizName,
};
}),
);

View File

@@ -156,6 +156,10 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
{
<ClassDataSourceTypeModal
open={createDataSourceModalOpen}
onCancel={() => {
resetSelectedNode();
setCreateDataSourceModalOpen(false);
}}
onTypeChange={(type) => {
if (type === 'fast') {
setDataSourceModalVisible(true);

View File

@@ -0,0 +1,63 @@
import G6 from '@antv/g6';
import '../style.less';
// define the CSS with the id of your menu
// insertCss(`
// #contextMenu {
// position: absolute;
// list-style-type: none;
// padding: 10px 8px;
// left: -150px;
// background-color: rgba(255, 255, 255, 0.9);
// border: 1px solid #e2e2e2;
// border-radius: 4px;
// font-size: 12px;
// color: #545454;
// }
// #contextMenu li {
// cursor: pointer;
// list-style-type:none;
// list-style: none;
// margin-left: 0px;
// }
// #contextMenu li:hover {
// color: #aaa;
// }
// `);
const initContextMenu = () => {
const contextMenu = new G6.Menu({
getContent(evt) {
const itemType = evt!.item!.getType();
console.log(this, evt?.item?._cfg, 333);
const nodeData = evt?.item?._cfg?.model;
const { name } = nodeData as any;
if (nodeData) {
const header = `${name}`;
return `<div class="g6ContextMenuContainer">
<h3>${header}</h3>
<ul>
<li title='2'>编辑</li>
<li title='1'>删除</li>
</ul>
</div>`;
}
return `<div>当前节点信息获取失败</div>`;
},
handleMenuClick(target, item) {
console.log(contextMenu, target, item);
const graph = contextMenu._cfgs.graph;
},
// offsetX and offsetY include the padding of the parent container
// 需要加上父级容器的 padding-left 16 与自身偏移量 10
offsetX: 16 + 10,
// 需要加上父级容器的 padding-top 24 、画布兄弟元素高度、与自身偏移量 10
offsetY: 0,
// the types of items that allow the menu show up
// 在哪些类型的元素上响应
itemTypes: ['node'],
});
return contextMenu;
};
export default initContextMenu;

View File

@@ -7,6 +7,7 @@ import { message, Row, Col, Radio } from 'antd';
import { getDatasourceList, getDomainSchemaRela } from '../service';
import initToolBar from './components/ToolBar';
import initTooltips from './components/ToolTips';
import initContextMenu from './components/ContextMenu';
import G6 from '@antv/g6';
type Props = {
@@ -209,6 +210,7 @@ const DomainManger: React.FC<Props> = ({ domainManger, domainId }) => {
const toolbar = initToolBar();
const tooltip = initTooltips();
const contextMenu = initContextMenu();
const legend = new G6.Legend({
// container: 'legendContainer',
data: {
@@ -303,30 +305,30 @@ const DomainManger: React.FC<Props> = ({ domainManger, domainId }) => {
// },
},
layout: {
type: 'mindmap',
direction: 'H',
getId: function getId(d) {
return d.id;
},
getHeight: function getHeight() {
return 16;
},
getWidth: function getWidth() {
return 16;
},
getVGap: function getVGap() {
return 30;
},
getHGap: function getHGap() {
return 100;
},
// type: 'dendrogram',
// direction: 'LR',
// nodeSep: 200,
// rankSep: 300,
// radial: true,
// type: 'mindmap',
// direction: 'H',
// getId: function getId(d) {
// return d.id;
// },
// getHeight: function getHeight() {
// return 16;
// },
// getWidth: function getWidth() {
// return 16;
// },
// getVGap: function getVGap() {
// return 30;
// },
// getHGap: function getHGap() {
// return 100;
// },
type: 'dendrogram',
direction: 'LR',
nodeSep: 200,
rankSep: 300,
radial: true,
},
plugins: [legend, tooltip, toolbar],
plugins: [legend, tooltip, toolbar, contextMenu],
});
const legendCanvas = legend._cfgs.legendCanvas;

View File

@@ -757,3 +757,4 @@
height: 16px !important;
margin: 0 3px 4px;
}

View File

@@ -5,9 +5,10 @@ const { Meta } = Card;
type Props = {
open: boolean;
onTypeChange: (type: 'fast' | 'normal') => void;
onCancel?: () => void;
};
const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange }) => {
const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange, onCancel }) => {
const [createDataSourceModalOpen, setCreateDataSourceModalOpen] = useState(false);
useEffect(() => {
setCreateDataSourceModalOpen(open);
@@ -19,6 +20,7 @@ const ClassDataSourceTypeModal: React.FC<Props> = ({ open, onTypeChange }) => {
open={createDataSourceModalOpen}
onCancel={() => {
setCreateDataSourceModalOpen(false);
onCancel?.();
}}
footer={null}
centered

View File

@@ -91,6 +91,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{
dataIndex: 'alias',
title: '别名',
search: false,
},
{
dataIndex: 'bizName',

View File

@@ -71,6 +71,7 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
{
dataIndex: 'alias',
title: '别名',
search: false,
},
{
dataIndex: 'bizName',
@@ -91,6 +92,25 @@ const ClassMetricTable: React.FC<Props> = ({ domainManger, dispatch }) => {
title: '描述',
search: false,
},
{
dataIndex: 'type',
title: '指标类型',
// search: false,
valueEnum: {
ATOMIC: '原子指标',
DERIVED: '衍生指标',
},
// render: (type: any) => {
// switch (type) {
// case 'ATOMIC':
// return '原子指标';
// case 'DERIVED':
// return '衍生指标';
// default:
// return '未知';
// }
// },
},
{
dataIndex: 'updatedAt',

View File

@@ -1,8 +1,9 @@
import React, { useEffect, useState } from 'react';
import { Button, Form, Input, Modal, Select } from 'antd';
import { Button, Form, Input, Modal, Select, List } from 'antd';
import { SENSITIVE_LEVEL_OPTIONS } from '../constant';
import { formLayout } from '@/components/FormHelper/utils';
import SqlEditor from '@/components/SqlEditor';
import InfoTagList from './InfoTagList';
import { message } from 'antd';
export type CreateFormProps = {
@@ -31,6 +32,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
users: [],
effectiveTime: 1,
});
const [form] = Form.useForm();
const { setFieldsValue } = form;
@@ -45,6 +47,7 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
};
const setFormVal = () => {
console.log(dimensionItem, 'dimensionItem');
setFieldsValue(dimensionItem);
};
@@ -128,6 +131,9 @@ const DimensionInfoModal: React.FC<CreateFormProps> = ({
))}
</Select>
</FormItem>
<FormItem name="defaultValues" label="默认值">
<InfoTagList />
</FormItem>
<FormItem
name="description"
label="维度描述"

View File

@@ -0,0 +1,291 @@
import { useEffect, useState, forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import { formLayout } from '@/components/FormHelper/utils';
import { message, Form, Input, Select, Button, InputNumber } from 'antd';
import { addDomainExtend, editDomainExtend } from '../../service';
import {
formatRichEntityDataListToIds,
wrapperTransTypeAndId,
splitListToTransTypeId,
} from './utils';
import styles from '../style.less';
import { ISemantic } from '../../data';
import { ChatConfigType, TransType } from '../../enum';
import TransTypeTag from '../TransTypeTag';
type Props = {
entityData: any;
chatConfigKey: string;
chatConfigType: ChatConfigType.DETAIL | ChatConfigType.AGG;
metricList: ISemantic.IMetricItem[];
dimensionList: ISemantic.IDimensionItem[];
domainId: number;
onSubmit: (params?: any) => void;
};
const FormItem = Form.Item;
const Option = Select.Option;
const DefaultSettingForm: ForwardRefRenderFunction<any, Props> = (
{ metricList, dimensionList, domainId, entityData, chatConfigKey, chatConfigType, onSubmit },
ref,
) => {
const [form] = Form.useForm();
const [metricListOptions, setMetricListOptions] = useState<any>([]);
const [unitState, setUnit] = useState<number | null>();
const [periodState, setPeriod] = useState<string>();
const [dataItemListOptions, setDataItemListOptions] = useState<any>([]);
const formatEntityData = formatRichEntityDataListToIds(entityData);
const getFormValidateFields = async () => {
return await form.validateFields();
};
useImperativeHandle(ref, () => ({
getFormValidateFields,
}));
useEffect(() => {
form.resetFields();
setUnit(null);
setPeriod('');
if (!entityData?.chatDefaultConfig) {
return;
}
const { chatDefaultConfig, id } = formatEntityData;
const { period, unit } = chatDefaultConfig;
setUnit(unit);
setPeriod(period);
form.setFieldsValue({
...chatDefaultConfig,
id,
});
if (chatConfigType === ChatConfigType.DETAIL) {
initDataItemValue(chatDefaultConfig);
}
}, [entityData, dataItemListOptions]);
const initDataItemValue = (chatDefaultConfig: {
dimensionIds: number[];
metricIds: number[];
}) => {
const { dimensionIds, metricIds } = chatDefaultConfig;
const dimensionIdString = dimensionIds.map((dimensionId: number) => {
return wrapperTransTypeAndId(TransType.DIMENSION, dimensionId);
});
const metricIdString = metricIds.map((metricId: number) => {
return wrapperTransTypeAndId(TransType.METRIC, metricId);
});
form.setFieldsValue({
dataItemIds: [...dimensionIdString, ...metricIdString],
});
};
useEffect(() => {
const metricOption = metricList.map((item: any) => {
return {
label: item.name,
value: item.id,
};
});
setMetricListOptions(metricOption);
}, [metricList]);
useEffect(() => {
if (Array.isArray(dimensionList) && Array.isArray(metricList)) {
const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => {
const { name, id, bizName } = item;
return {
name,
label: (
<>
<TransTypeTag type={TransType.DIMENSION} />
{name}
</>
),
value: wrapperTransTypeAndId(TransType.DIMENSION, id),
bizName,
id,
transType: TransType.DIMENSION,
};
});
const metricEnum = metricList.map((item: ISemantic.IMetricItem) => {
const { name, id, bizName } = item;
return {
name,
label: (
<>
<TransTypeTag type={TransType.METRIC} />
{name}
</>
),
value: wrapperTransTypeAndId(TransType.METRIC, id),
bizName,
id,
transType: TransType.METRIC,
};
});
setDataItemListOptions([...dimensionEnum, ...metricEnum]);
}
}, [dimensionList, metricList]);
const saveEntity = async () => {
const values = await form.validateFields();
const { id, dataItemIds } = values;
let dimensionConfig = {};
if (dataItemIds) {
const { dimensionIds, metricIds } = splitListToTransTypeId(dataItemIds);
dimensionConfig = {
dimensionIds,
metricIds,
};
}
let saveDomainExtendQuery = addDomainExtend;
if (id) {
saveDomainExtendQuery = editDomainExtend;
}
const params = {
...formatEntityData,
chatDefaultConfig: { ...values, ...dimensionConfig },
};
const { code, msg, data } = await saveDomainExtendQuery({
[chatConfigKey]: params,
domainId,
id,
});
if (code === 200) {
form.setFieldValue('id', data);
onSubmit?.();
message.success('保存成功');
return;
}
message.error(msg);
};
return (
<>
<Form
{...formLayout}
form={form}
layout="vertical"
className={styles.form}
initialValues={{
unit: 7,
period: 'DAY',
}}
>
<FormItem hidden={true} name="id" label="ID">
<Input placeholder="id" />
</FormItem>
{chatConfigType === ChatConfigType.DETAIL && (
<FormItem name="dataItemIds" label="展示维度/指标">
<Select
mode="multiple"
allowClear
style={{ width: '100%' }}
optionLabelProp="name"
filterOption={(inputValue: string, item: any) => {
const { name } = item;
if (name.includes(inputValue)) {
return true;
}
return false;
}}
placeholder="请选择展示维度/指标信息"
options={dataItemListOptions}
/>
</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
label={
<FormItemTitle
title={'时间范围'}
subTitle={'问答搜索结果选择中,如果没有指定时间范围,将会采用默认时间范围'}
/>
}
>
<Input.Group compact>
<span
style={{
display: 'inline-block',
lineHeight: '32px',
marginRight: '8px',
}}
>
{chatConfigType === ChatConfigType.DETAIL ? '前' : '最近'}
</span>
<InputNumber
value={unitState}
style={{ width: '120px' }}
onChange={(value) => {
setUnit(value);
form.setFieldValue('unit', value);
}}
/>
<Select
value={periodState}
style={{ width: '100px' }}
onChange={(value) => {
form.setFieldValue('period', value);
setPeriod(value);
}}
>
<Option value="DAY"></Option>
<Option value="WEEK"></Option>
<Option value="MONTH"></Option>
<Option value="YEAR"></Option>
</Select>
</Input.Group>
</FormItem>
<FormItem name="unit" hidden={true}>
<InputNumber />
</FormItem>
<FormItem name="period" hidden={true}>
<Input />
</FormItem>
<FormItem>
<Button
type="primary"
onClick={() => {
saveEntity();
}}
>
</Button>
</FormItem>
</Form>
</>
);
};
export default forwardRef(DefaultSettingForm);

View File

@@ -0,0 +1,205 @@
import React, { useEffect, useState, useRef } from 'react';
import { Button, Modal, message, Tabs } from 'antd';
import { addDomainExtend, editDomainExtend } from '../../service';
import DimensionMetricVisibleTransfer from './DimensionMetricVisibleTransfer';
import { IChatConfig } from '../../data';
import DimensionValueSettingForm from './DimensionValueSettingForm';
import { TransType } from '../../enum';
import { wrapperTransTypeAndId, formatRichEntityDataListToIds } from './utils';
type Props = {
domainId: number;
entityData: any;
chatConfigKey: string;
settingSourceList: any[];
onCancel: () => void;
visible: boolean;
onSubmit: (params?: any) => void;
};
const dimensionConfig = {
blackIdListKey: 'blackDimIdList',
visibleIdListKey: 'whiteDimIdList',
modalTitle: '问答可见信息',
titles: ['不可见维度/指标', '可见维度/指标'],
};
const DimensionAndMetricVisibleModal: React.FC<Props> = ({
domainId,
visible,
entityData = {},
chatConfigKey,
settingSourceList,
onCancel,
onSubmit,
}) => {
const [selectedKeyList, setSelectedKeyList] = useState<string[]>([]);
const settingTypeConfig = dimensionConfig;
const formatEntityData = formatRichEntityDataListToIds(entityData);
const [knowledgeInfosMap, setKnowledgeInfosMap] = useState<IChatConfig.IKnowledgeInfosItemMap>(
{},
);
const formRef = useRef<any>();
const [globalKnowledgeConfigInitialValues, setGlobalKnowledgeConfigInitialValues] =
useState<IChatConfig.IKnowledgeConfig>();
useEffect(() => {
if (entityData?.visibility && Array.isArray(settingSourceList)) {
const { whiteDimIdList, whiteMetricIdList } = entityData.visibility;
const dimensionIdString = whiteDimIdList.map((dimensionId: number) => {
return wrapperTransTypeAndId(TransType.DIMENSION, dimensionId);
});
const metricIdString = whiteMetricIdList.map((metricId: number) => {
return wrapperTransTypeAndId(TransType.METRIC, metricId);
});
setSelectedKeyList([...dimensionIdString, ...metricIdString]);
}
if (entityData?.globalKnowledgeConfig) {
setGlobalKnowledgeConfigInitialValues(entityData.globalKnowledgeConfig);
}
if (Array.isArray(entityData?.knowledgeInfos)) {
const infoMap = entityData.knowledgeInfos.reduce(
(maps: IChatConfig.IKnowledgeInfosItemMap, item: IChatConfig.IKnowledgeInfosItem) => {
const { bizName } = item;
maps[bizName] = item;
return maps;
},
{},
);
setKnowledgeInfosMap(infoMap);
}
}, [entityData, settingSourceList]);
const saveEntity = async () => {
const globalKnowledgeConfigFormFields = await formRef?.current?.getFormValidateFields?.();
let globalKnowledgeConfig = entityData.globalKnowledgeConfig;
if (globalKnowledgeConfigFormFields) {
globalKnowledgeConfig = globalKnowledgeConfigFormFields;
}
const { id } = entityData;
let saveDomainExtendQuery = addDomainExtend;
if (id) {
saveDomainExtendQuery = editDomainExtend;
}
const blackIdListMap = settingSourceList.reduce(
(ids, item) => {
const { id, transType } = item;
if (!selectedKeyList.includes(wrapperTransTypeAndId(transType, id))) {
if (transType === TransType.DIMENSION) {
ids.blackDimIdList.push(id);
}
if (transType === TransType.METRIC) {
ids.blackMetricIdList.push(id);
}
}
return ids;
},
{
blackDimIdList: [],
blackMetricIdList: [],
},
);
const knowledgeInfos = Object.keys(knowledgeInfosMap).reduce(
(infoList: IChatConfig.IKnowledgeInfosItem[], key: string) => {
const target = knowledgeInfosMap[key];
if (target.searchEnable) {
infoList.push(target);
}
return infoList;
},
[],
);
const params = {
...formatEntityData,
visibility: blackIdListMap,
knowledgeInfos,
...(globalKnowledgeConfig ? { globalKnowledgeConfig } : {}),
};
const { code, msg } = await saveDomainExtendQuery({
[chatConfigKey]: params,
domainId,
id,
});
if (code === 200) {
onSubmit?.();
message.success('保存成功');
return;
}
message.error(msg);
};
const handleTransferChange = (newTargetKeys: string[]) => {
setSelectedKeyList(newTargetKeys);
};
const renderFooter = () => {
return (
<>
<Button onClick={onCancel}></Button>
<Button
type="primary"
onClick={() => {
saveEntity();
}}
>
</Button>
</>
);
};
const tabItem = [
{
label: '可见设置',
key: 'visibleSetting',
children: (
<DimensionMetricVisibleTransfer
onKnowledgeInfosMapChange={(knowledgeInfosMap) => {
setKnowledgeInfosMap(knowledgeInfosMap);
}}
knowledgeInfosMap={knowledgeInfosMap}
titles={settingTypeConfig.titles}
sourceList={settingSourceList}
targetList={selectedKeyList}
onChange={(newTargetKeys) => {
handleTransferChange(newTargetKeys);
}}
/>
),
},
{
label: '全局维度值过滤',
key: 'dimensionValueFilter',
children: (
<DimensionValueSettingForm
initialValues={globalKnowledgeConfigInitialValues}
ref={formRef}
/>
),
},
];
return (
<>
<Modal
width={1200}
destroyOnClose
title={settingTypeConfig.modalTitle}
maskClosable={false}
open={visible}
footer={renderFooter()}
onCancel={onCancel}
>
<Tabs items={tabItem} defaultActiveKey="visibleSetting" />
</Modal>
</>
);
};
export default DimensionAndMetricVisibleModal;

View File

@@ -3,11 +3,13 @@ import type { ForwardRefRenderFunction } from 'react';
import { Form, Button } from 'antd';
import FormItemTitle from '@/components/FormHelper/FormItemTitle';
import { formLayout } from '@/components/FormHelper/utils';
import DimensionMetricVisibleModal from './DimensionMetricVisibleModal';
import DimensionSearchVisibleModal from './DimensionSearchVisibleModal';
import DimensionAndMetricVisibleModal from './DimensionAndMetricVisibleModal';
import { TransType } from '../../enum';
import { wrapperTransTypeAndId } from './utils';
type Props = {
themeData: any;
entityData: any;
chatConfigKey: string;
metricList: any[];
dimensionList: any[];
domainId: number;
@@ -20,18 +22,17 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
domainId,
metricList,
dimensionList,
themeData,
entityData,
chatConfigKey,
onSubmit,
}) => {
const [dimensionModalVisible, setDimensionModalVisible] = useState(false);
const [dimensionSearchModalVisible, setDimensionSearchModalVisible] = useState(false);
const [metricModalVisible, setMetricModalVisible] = useState<boolean>(false);
return (
<>
<Form {...formLayout}>
<FormItem
label={
<FormItemTitle title={'可见维度'} subTitle={'设置可见后,维度将允许在问答中被使用'} />
<FormItemTitle title={'可见维度/指标'} subTitle={'设置可见后,将允许在问答中被使用'} />
}
>
<Button
@@ -43,44 +44,32 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
</Button>
</FormItem>
<FormItem
label={
<FormItemTitle title={'可见指标'} subTitle={'设置可见后,指标将允许在问答中被使用'} />
}
>
<Button
type="primary"
onClick={() => {
setMetricModalVisible(true);
}}
>
</Button>
</FormItem>
<FormItem
label={
<FormItemTitle
title={'可见维度值'}
subTitle={'设置可见后,在可见维度设置的基础上,维度值将在搜索时可以被联想出来'}
/>
}
>
<Button
type="primary"
onClick={() => {
setDimensionSearchModalVisible(true);
}}
>
</Button>
</FormItem>
</Form>
{dimensionModalVisible && (
<DimensionMetricVisibleModal
<DimensionAndMetricVisibleModal
domainId={domainId}
themeData={themeData}
settingSourceList={dimensionList}
settingType="dimension"
entityData={entityData}
chatConfigKey={chatConfigKey}
settingSourceList={[
...dimensionList.map((item) => {
const transType = TransType.DIMENSION;
const { id } = item;
return {
...item,
transType,
key: wrapperTransTypeAndId(transType, id),
};
}),
...metricList.map((item) => {
const transType = TransType.METRIC;
const { id } = item;
return {
...item,
transType,
key: wrapperTransTypeAndId(transType, id),
};
}),
]}
visible={dimensionModalVisible}
onCancel={() => {
setDimensionModalVisible(false);
@@ -91,43 +80,6 @@ const DimensionMetricVisibleForm: ForwardRefRenderFunction<any, Props> = ({
}}
/>
)}
{dimensionSearchModalVisible && (
<DimensionSearchVisibleModal
domainId={domainId}
settingSourceList={dimensionList.filter((item) => {
const blackDimensionList = themeData.visibility?.blackDimIdList;
if (Array.isArray(blackDimensionList)) {
return !blackDimensionList.includes(item.id);
}
return false;
})}
themeData={themeData}
visible={dimensionSearchModalVisible}
onCancel={() => {
setDimensionSearchModalVisible(false);
}}
onSubmit={() => {
onSubmit?.({ from: 'dimensionSearchVisible' });
setDimensionSearchModalVisible(false);
}}
/>
)}
{metricModalVisible && (
<DimensionMetricVisibleModal
domainId={domainId}
themeData={themeData}
settingSourceList={metricList}
settingType="metric"
visible={metricModalVisible}
onCancel={() => {
setMetricModalVisible(false);
}}
onSubmit={() => {
onSubmit?.();
setMetricModalVisible(false);
}}
/>
)}
</>
);
};

View File

@@ -0,0 +1,199 @@
import { Space, Table, Transfer, Checkbox, Tooltip, Button } from 'antd';
import type { ColumnsType, TableRowSelection } from 'antd/es/table/interface';
import type { TransferItem } from 'antd/es/transfer';
import type { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import difference from 'lodash/difference';
import React, { useState } from 'react';
import type { IChatConfig } from '../../data';
import DimensionValueSettingModal from './DimensionValueSettingModal';
import TransTypeTag from '../TransTypeTag';
import { TransType } from '../../enum';
interface RecordType {
id: number;
key: string;
name: string;
bizName: string;
type: TransType.DIMENSION | TransType.METRIC;
}
type Props = {
knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap;
onKnowledgeInfosMapChange: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
[key: string]: any;
};
const DimensionMetricVisibleTableTransfer: React.FC<Props> = ({
knowledgeInfosMap,
onKnowledgeInfosMapChange,
...restProps
}) => {
const [dimensionValueSettingModalVisible, setDimensionValueSettingModalVisible] =
useState<boolean>(false);
const [currentRecord, setCurrentRecord] = useState<any>({});
const [currentDimensionSettingFormData, setCurrentDimensionSettingFormData] =
useState<IChatConfig.IKnowledgeConfig>();
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 rightColumns: ColumnsType<RecordType> = [
{
dataIndex: 'name',
title: '名称',
},
{
dataIndex: 'type',
width: 80,
title: '类型',
render: (type) => {
return <TransTypeTag type={type} />;
},
},
{
dataIndex: 'y',
title: (
<Space>
<span></span>
<Tooltip title="勾选可见后,维度值将在搜索时可以被联想出来">
<ExclamationCircleOutlined />
</Tooltip>
</Space>
),
width: 120,
render: (_, record) => {
const { type, bizName } = record;
return type === TransType.DIMENSION ? (
<Checkbox
checked={knowledgeInfosMap[bizName]?.searchEnable}
onChange={(e: CheckboxChangeEvent) => {
updateKnowledgeInfosMap(record, { searchEnable: e.target.checked });
}}
onClick={(event) => {
event.stopPropagation();
}}
/>
) : (
<></>
);
},
},
{
title: '操作',
dataIndex: 'x',
render: (_, record) => {
const { type, bizName } = record;
return type === TransType.DIMENSION ? (
<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>
) : (
<></>
);
},
},
];
const leftColumns: ColumnsType<RecordType> = [
{
dataIndex: 'name',
title: '名称',
},
{
dataIndex: 'type',
title: '类型',
render: (type) => {
return <TransTypeTag type={type} />;
},
},
];
return (
<>
<Transfer {...restProps}>
{({
direction,
filteredItems,
onItemSelectAll,
onItemSelect,
selectedKeys: listSelectedKeys,
}) => {
const columns = direction === 'left' ? leftColumns : rightColumns;
const rowSelection: TableRowSelection<TransferItem> = {
onSelectAll(selected, selectedRows) {
const treeSelectedKeys = selectedRows.map(({ key }) => key);
const diffKeys = selected
? difference(treeSelectedKeys, listSelectedKeys)
: difference(listSelectedKeys, treeSelectedKeys);
onItemSelectAll(diffKeys as string[], selected);
},
onSelect({ key }, selected) {
onItemSelect(key as string, selected);
},
selectedRowKeys: listSelectedKeys,
};
return (
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={filteredItems as any}
size="small"
pagination={false}
scroll={{ y: 450 }}
onRow={({ key }) => ({
onClick: () => {
onItemSelect(key as string, !listSelectedKeys.includes(key as string));
},
})}
/>
);
}}
</Transfer>
<DimensionValueSettingModal
visible={dimensionValueSettingModalVisible}
initialValues={currentDimensionSettingFormData}
onSubmit={(formValues) => {
updateKnowledgeInfosMap(currentRecord, { knowledgeAdvancedConfig: formValues });
setDimensionValueSettingModalVisible(false);
}}
onCancel={() => {
setDimensionValueSettingModalVisible(false);
}}
/>
</>
);
};
export default DimensionMetricVisibleTableTransfer;

View File

@@ -1,5 +1,7 @@
import { Transfer, Tag } from 'antd';
import { Tag } from 'antd';
import React, { useEffect, useState } from 'react';
import { IChatConfig } from '../../data';
import DimensionMetricVisibleTableTransfer from './DimensionMetricVisibleTableTransfer';
interface RecordType {
key: string;
@@ -8,14 +10,18 @@ interface RecordType {
}
type Props = {
knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap;
sourceList: any[];
targetList: string[];
titles?: string[];
onKnowledgeInfosMapChange: (knowledgeInfosMap: IChatConfig.IKnowledgeInfosItemMap) => void;
onChange?: (params?: any) => void;
transferProps?: Record<string, any>;
};
const DimensionMetricVisibleTransfer: React.FC<Props> = ({
knowledgeInfosMap,
onKnowledgeInfosMapChange,
sourceList = [],
targetList = [],
titles,
@@ -27,11 +33,13 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
useEffect(() => {
setTransferData(
sourceList.map(({ id, name, type }: any) => {
sourceList.map(({ key, id, name, bizName, transType }) => {
return {
key: id,
key,
name,
type,
bizName,
id,
type: transType,
};
}),
);
@@ -48,13 +56,15 @@ const DimensionMetricVisibleTransfer: React.FC<Props> = ({
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Transfer
<DimensionMetricVisibleTableTransfer
knowledgeInfosMap={knowledgeInfosMap}
onKnowledgeInfosMapChange={onKnowledgeInfosMapChange}
dataSource={transferData}
showSearch
titles={titles || ['不可见维度', '可见维度']}
listStyle={{
width: 430,
height: 500,
width: 500,
height: 600,
}}
filterOption={(inputValue: string, item: any) => {
const { name } = item;

View File

@@ -0,0 +1,80 @@
import { useEffect, forwardRef, useImperativeHandle } from 'react';
import type { ForwardRefRenderFunction } from 'react';
import { Form, Input } from 'antd';
import { formLayout } from '@/components/FormHelper/utils';
import { isString } from 'lodash';
import styles from '../style.less';
import SqlEditor from '@/components/SqlEditor';
type Props = {
initialValues: any;
onSubmit?: () => void;
};
const FormItem = Form.Item;
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = ({ initialValues }, ref) => {
const [form] = Form.useForm();
const exchangeFields = ['blackList', 'whiteList', 'ruleList'];
const getFormValidateFields = async () => {
const fields = await form.validateFields();
const fieldValue = Object.keys(fields).reduce((formField, key: string) => {
const targetValue = fields[key];
if (isString(targetValue) && exchangeFields.includes(key)) {
formField[key] = targetValue.split(',');
} else {
formField[key] = targetValue;
}
return formField;
}, {});
return {
...fieldValue,
};
};
useEffect(() => {
form.resetFields();
if (!initialValues) {
return;
}
const fieldValue = Object.keys(initialValues).reduce((formField, key: string) => {
const targetValue = initialValues[key];
if (Array.isArray(targetValue) && exchangeFields.includes(key)) {
formField[key] = targetValue.join(',');
} else {
formField[key] = targetValue;
}
return formField;
}, {});
form.setFieldsValue({
...fieldValue,
});
}, [initialValues]);
useImperativeHandle(ref, () => ({
getFormValidateFields,
}));
return (
<>
<Form {...formLayout} form={form} layout="vertical" className={styles.form}>
<FormItem name="blackList" label="黑名单">
<Input placeholder="多个维度值用英文逗号隔开" />
</FormItem>
<FormItem name="whiteList" label="白名单">
<Input placeholder="多个维度值用英文逗号隔开" />
</FormItem>
<FormItem name="ruleList" label="过滤规则">
<SqlEditor height={'150px'} />
</FormItem>
</Form>
</>
);
};
export default forwardRef(EntityCreateForm);

View File

@@ -0,0 +1,56 @@
import React, { useRef } from 'react';
import { Button, Modal } from 'antd';
import DimensionValueSettingForm from './DimensionValueSettingForm';
type Props = {
initialValues: any;
onCancel?: () => void;
visible: boolean;
onSubmit?: (params?: any) => void;
};
const DimensionValueSettingModal: React.FC<Props> = ({
initialValues,
visible,
onCancel,
onSubmit,
}) => {
const formRef = useRef<any>();
const handleSubmit = async () => {
const formValues = await formRef.current.getFormValidateFields();
onSubmit?.(formValues);
};
const renderFooter = () => {
return (
<>
<Button onClick={onCancel}></Button>
<Button
type="primary"
onClick={() => {
handleSubmit();
}}
>
</Button>
</>
);
};
return (
<>
<Modal
width={600}
destroyOnClose
title={'维度值设置'}
maskClosable={false}
open={visible}
footer={renderFooter()}
onCancel={onCancel}
>
<DimensionValueSettingForm initialValues={initialValues} ref={formRef} />
</Modal>
</>
);
};
export default DimensionValueSettingModal;

View File

@@ -4,27 +4,24 @@ import { message, Form, Input, Select, Button } from 'antd';
import { addDomainExtend, editDomainExtend } from '../../service';
import type { ISemantic, IChatConfig } from '../../data';
import { formLayout } from '@/components/FormHelper/utils';
import { exChangeRichEntityListToIds } from './utils';
import { formatRichEntityDataListToIds } from './utils';
import styles from '../style.less';
type Props = {
entityData: IChatConfig.IEntity;
metricList: ISemantic.IMetricList;
entityData: IChatConfig.IChatRichConfig;
dimensionList: ISemantic.IDimensionList;
domainId: number;
onSubmit: () => void;
};
const FormItem = Form.Item;
const TextArea = Input.TextArea;
const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
{ entityData, metricList, dimensionList, domainId, onSubmit },
{ entityData, dimensionList, domainId, onSubmit },
ref,
) => {
const [form] = Form.useForm();
const [metricListOptions, setMetricListOptions] = useState<any>([]);
const formatEntityData = formatRichEntityDataListToIds(entityData);
const [dimensionListOptions, setDimensionListOptions] = useState<any>([]);
const getFormValidateFields = async () => {
@@ -33,29 +30,19 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
useEffect(() => {
form.resetFields();
if (Object.keys(entityData).length === 0) {
if (!entityData?.entity) {
return;
}
const names = entityData.names || [];
const formatEntityData = exChangeRichEntityListToIds(entityData);
form.setFieldsValue({
...formatEntityData,
name: names.join(','),
...formatEntityData.entity,
id: formatEntityData.id,
});
}, [entityData]);
useImperativeHandle(ref, () => ({
getFormValidateFields,
}));
useEffect(() => {
const metricOption = metricList.map((item: ISemantic.IMetricItem) => {
return {
label: item.name,
value: item.id,
};
});
setMetricListOptions(metricOption);
}, [metricList]);
useEffect(() => {
const dimensionEnum = dimensionList.map((item: ISemantic.IDimensionItem) => {
@@ -75,10 +62,14 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
saveDomainExtendQuery = editDomainExtend;
}
const { code, msg, data } = await saveDomainExtendQuery({
entity: {
...values,
names: name.split(','),
chatDetailConfig: {
...formatEntityData,
entity: {
...values,
names: name.split(','),
},
},
id,
domainId,
});
@@ -99,66 +90,31 @@ const EntityCreateForm: ForwardRefRenderFunction<any, Props> = (
</FormItem>
<FormItem
name="name"
label="实体名"
rules={[{ required: true, message: '请输入实体名称' }]}
label="实体名"
// rules={[{ required: true, message: '请输入实体别名' }]}
>
<TextArea
placeholder="请输入实体名称,多个实体名称以英文逗号分隔"
style={{ height: 100 }}
/>
<Input placeholder="请输入实体别名,多个名称以英文逗号分隔" />
</FormItem>
<FormItem
name="entityIds"
name="entityId"
label="唯一标识"
rules={[{ required: true, message: '请选择实体标识' }]}
// rules={[{ required: true, message: '请选择实体标识' }]}
>
<Select
mode="multiple"
// mode="multiple"
allowClear
style={{ width: '100%' }}
filterOption={(inputValue: string, item: any) => {
const { label } = item;
if (label.includes(inputValue)) {
return true;
}
return false;
}}
// filterOption={(inputValue: string, item: any) => {
// const { label } = item;
// if (label.includes(inputValue)) {
// return true;
// }
// return false;
// }}
placeholder="请选择主体标识"
options={dimensionListOptions}
/>
</FormItem>
<FormItem name={['detailData', 'dimensionIds']} label="维度信息">
<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={dimensionListOptions}
/>
</FormItem>
<FormItem name={['detailData', 'metricIds']} label="指标信息">
<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>
<Button
type="primary"

View File

@@ -3,24 +3,28 @@ import React, { useState, useEffect, useRef } from 'react';
import type { Dispatch } from 'umi';
import { connect } from 'umi';
import type { StateType } from '../../model';
import { getDomainExtendConfig, getDomainExtendDetailConfig } from '../../service';
import { getDomainExtendDetailConfig } from '../../service';
import ProCard from '@ant-design/pro-card';
import EntityCreateForm from './EntityCreateForm';
import MetricSettingForm from './MetricSettingForm';
import DefaultSettingForm from './DefaultSettingForm';
import type { IChatConfig } from '../../data';
import DimensionMetricVisibleForm from './DimensionMetricVisibleForm';
import { ChatConfigType } from '../../enum';
type Props = {
chatConfigType: ChatConfigType.DETAIL | ChatConfigType.AGG;
dispatch: Dispatch;
domainManger: StateType;
};
const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
const EntitySection: React.FC<Props> = ({
domainManger,
dispatch,
chatConfigType = ChatConfigType.DETAIL,
}) => {
const { selectDomainId, dimensionList, metricList } = domainManger;
const [entityData, setEntityData] = useState<IChatConfig.IEntity>({} as IChatConfig.IEntity);
const [themeData, setThemeData] = useState<any>({});
const [entityData, setentityData] = useState<IChatConfig.IChatRichConfig>();
const entityCreateRef = useRef<any>({});
@@ -28,21 +32,19 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
const { code, data } = await getDomainExtendDetailConfig({
domainId: selectDomainId,
});
// getDomainExtendConfig({
// domainId: selectDomainId,
// });
if (code === 200) {
const target = data;
if (target) {
setThemeData(target);
setEntityData({
id: target.id,
...target.entity,
});
const { chatAggRichConfig, chatDetailRichConfig, id, domainId } = data;
if (chatConfigType === ChatConfigType.DETAIL) {
setentityData({ ...chatDetailRichConfig, id, domainId });
}
if (chatConfigType === ChatConfigType.AGG) {
setentityData({ ...chatAggRichConfig, id, domainId });
}
return;
}
message.error('获取主题域解析词失败');
message.error('获取问答设置信息失败');
};
const initPage = async () => {
@@ -56,9 +58,31 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
return (
<div style={{ width: 800, margin: '0 auto' }}>
<Space direction="vertical" style={{ width: '100%' }} size={20}>
{chatConfigType === 'detail' && entityData && (
<ProCard title="实体" bordered>
<EntityCreateForm
ref={entityCreateRef}
domainId={Number(selectDomainId)}
entityData={entityData}
dimensionList={dimensionList.filter((item) => {
const blackDimensionList = entityData?.visibility?.blackDimIdList;
if (Array.isArray(blackDimensionList)) {
return !blackDimensionList.includes(item.id);
}
return false;
})}
onSubmit={() => {
queryThemeListData();
}}
/>
</ProCard>
)}
<ProCard bordered title="问答可见">
<DimensionMetricVisibleForm
themeData={themeData}
chatConfigKey={
chatConfigType === ChatConfigType.DETAIL ? 'chatDetailConfig' : 'chatAggConfig'
}
entityData={entityData || {}}
domainId={Number(selectDomainId)}
metricList={metricList}
dimensionList={dimensionList}
@@ -75,44 +99,28 @@ const EntitySection: React.FC<Props> = ({ domainManger, dispatch }) => {
}}
/>
</ProCard>
<ProCard bordered title="默认指标">
<MetricSettingForm
<ProCard bordered title="默认设置">
<DefaultSettingForm
domainId={Number(selectDomainId)}
themeData={themeData}
// metricList={metricList}
metricList={metricList.filter((item) => {
const blackMetricIdList = themeData.visibility?.blackMetricIdList;
if (Array.isArray(blackMetricIdList)) {
return !blackMetricIdList.includes(item.id);
}
return false;
})}
onSubmit={() => {
queryThemeListData();
}}
/>
</ProCard>
<ProCard title="实体" bordered>
<EntityCreateForm
ref={entityCreateRef}
domainId={Number(selectDomainId)}
entityData={entityData}
// metricList={metricList}
metricList={metricList.filter((item) => {
const blackMetricIdList = themeData.visibility?.blackMetricIdList;
if (Array.isArray(blackMetricIdList)) {
return !blackMetricIdList.includes(item.id);
}
return false;
})}
// dimensionList={dimensionList}
entityData={entityData || {}}
chatConfigType={chatConfigType}
chatConfigKey={
chatConfigType === ChatConfigType.DETAIL ? 'chatDetailConfig' : 'chatAggConfig'
}
dimensionList={dimensionList.filter((item) => {
const blackDimensionList = themeData.visibility?.blackDimIdList;
const blackDimensionList = entityData?.visibility?.blackDimIdList;
if (Array.isArray(blackDimensionList)) {
return !blackDimensionList.includes(item.id);
}
return false;
})}
metricList={metricList.filter((item) => {
const blackMetricIdList = entityData?.visibility?.blackMetricIdList;
if (Array.isArray(blackMetricIdList)) {
return !blackMetricIdList.includes(item.id);
}
return false;
})}
onSubmit={() => {
queryThemeListData();
}}

View File

@@ -1,28 +1,87 @@
import { IChatConfig, ISemantic } from '../../data';
import { TransType } from '../../enum';
type FormatResult = IChatConfig.IChatRichConfig & {
chatDefaultConfig: {
dimensionIds: number[];
metricIds: number[];
};
};
export const formatRichEntityDataListToIds = (
entityData: IChatConfig.IChatRichConfig,
): FormatResult => {
if (!entityData?.chatDefaultConfig) {
return {} as FormatResult;
}
const { chatDefaultConfig, entity } = entityData;
export const exChangeRichEntityListToIds = (entityData: IChatConfig.IEntity) => {
const entityList = entityData.entityIds || [];
const detailData: {
dimensionIds: number[];
metricIds: number[];
} = { dimensionIds: [], metricIds: [] };
const { dimensionList, metricList } = entityData.entityInternalDetailDesc || {};
if (Array.isArray(dimensionList)) {
detailData.dimensionIds = dimensionList.map((item: ISemantic.IDimensionItem) => {
const { dimensions, metrics } = chatDefaultConfig || {};
if (Array.isArray(dimensions)) {
detailData.dimensionIds = dimensions.map((item: ISemantic.IDimensionItem) => {
return item.id;
});
}
if (Array.isArray(metricList)) {
detailData.metricIds = metricList.map((item: ISemantic.IMetricItem) => {
if (Array.isArray(metrics)) {
detailData.metricIds = metrics.map((item: ISemantic.IMetricItem) => {
return item.id;
});
}
const entityIds = entityList.map((item) => {
return item.id;
});
let entitySetting = {};
if (entity) {
const entityItem = entity.dimItem;
const entityId = entityItem?.id || null;
const names = entity.names || [];
entitySetting = {
entity: {
...entity,
entityId,
name: names.join(','),
},
};
}
return {
...entityData,
entityIds,
detailData,
...entitySetting,
chatDefaultConfig: {
...chatDefaultConfig,
...detailData,
},
};
};
export const wrapperTransTypeAndId = (exTransType: TransType, id: number) => {
return `${exTransType}-${id}`;
};
export const splitListToTransTypeId = (dataItemIds: string[]) => {
const idListMap = dataItemIds.reduce(
(
idMap: {
dimensionIds: number[];
metricIds: number[];
},
item: string,
) => {
const [transType, id] = item.split('-');
if (id) {
if (transType === TransType.DIMENSION) {
idMap.dimensionIds.push(Number(id));
}
if (transType === TransType.METRIC) {
idMap.metricIds.push(Number(id));
}
}
return idMap;
},
{
dimensionIds: [],
metricIds: [],
},
);
return idListMap;
};

View File

@@ -0,0 +1,147 @@
import { PlusOutlined } from '@ant-design/icons';
import type { InputRef } from 'antd';
import { Input as AntdInput, Tag, Tooltip } from 'antd';
import React, { useEffect, useRef, useState } from 'react';
import styles from './style.less';
type Props = {
createBtnString?: string;
value?: string[];
onChange?: (valueList: string[]) => void;
};
const InfoTagList: React.FC<Props> = ({ value, createBtnString = '新增', onChange }) => {
const [tags, setTags] = useState<string[]>([]);
const [inputVisible, setInputVisible] = useState(false);
const [inputValue, setInputValue] = useState('');
const [editInputIndex, setEditInputIndex] = useState(-1);
const [editInputValue, setEditInputValue] = useState('');
const inputRef = useRef<InputRef>(null);
const editInputRef = useRef<InputRef>(null);
useEffect(() => {
if (Array.isArray(value)) {
setTags([...value]);
}
}, [value]);
const handleTagChange = (tagList: string[]) => {
console.log(tagList, 'tagList');
onChange?.(tagList);
};
useEffect(() => {
if (inputVisible) {
inputRef.current?.focus();
}
}, [inputVisible]);
useEffect(() => {
editInputRef.current?.focus();
}, [inputValue]);
const handleClose = (removedTag: string) => {
const newTags = tags.filter((tag) => tag !== removedTag);
handleTagChange?.(newTags);
setTags(newTags);
};
const showInput = () => {
setInputVisible(true);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
const handleInputConfirm = () => {
if (inputValue && tags.indexOf(inputValue) === -1) {
const newTags = [...tags, inputValue];
handleTagChange?.(newTags);
setTags(newTags);
}
setInputVisible(false);
setInputValue('');
};
const handleEditInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEditInputValue(e.target.value);
};
const handleEditInputConfirm = () => {
const newTags = [...tags];
newTags[editInputIndex] = editInputValue;
setTags(newTags);
handleTagChange?.(newTags);
setEditInputIndex(-1);
setInputValue('');
};
return (
<div className={styles.infoTagList}>
{tags.map((tag, index) => {
if (editInputIndex === index) {
return (
<AntdInput
ref={editInputRef}
key={tag}
size="small"
className={styles.tagInput}
value={editInputValue}
onChange={handleEditInputChange}
onBlur={handleEditInputConfirm}
onPressEnter={handleEditInputConfirm}
/>
);
}
const isLongTag = tag.length > 20;
const tagElem = (
<Tag
className={styles.editTag}
key={tag}
closable={true}
onClose={() => handleClose(tag)}
>
<span
onDoubleClick={(e) => {
setEditInputIndex(index);
setEditInputValue(tag);
e.preventDefault();
}}
>
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
</span>
</Tag>
);
return isLongTag ? (
<Tooltip title={tag} key={tag}>
{tagElem}
</Tooltip>
) : (
tagElem
);
})}
{inputVisible && (
<AntdInput
ref={inputRef}
type="text"
size="small"
className={styles.tagInput}
value={inputValue}
onChange={handleInputChange}
onBlur={handleInputConfirm}
onPressEnter={handleInputConfirm}
/>
)}
{!inputVisible && (
<Tag className={styles.siteTagPlus} onClick={showInput}>
<PlusOutlined /> {createBtnString}
</Tag>
)}
</div>
);
};
export default InfoTagList;

View File

@@ -88,6 +88,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
typeParams: typeParams,
dataFormat,
dataFormatType,
alias,
} = metricItem as any;
const isPercent = dataFormatType === 'percent';
const initValue = {
@@ -97,6 +98,7 @@ const MetricInfoCreateForm: React.FC<CreateFormProps> = ({
sensitiveLevel,
description,
isPercent,
alias,
dataFormat: dataFormat || {
decimalPlaces: 2,
needMultiply100: false,

View File

@@ -72,7 +72,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
dataIndex: 'constraint',
title: '限定条件',
tooltip:
'所用于过滤的维度需要存在于"维度"列表不需要加where关键字。比如维度A="值1" and 维度B="值2"',
'该限定条件用于在计算指标时限定口径,作用于度量,所用于过滤的维度必须在创建数据源的时候被标记为日期或者维度不需要加where关键字。比如维度A="值1" and 维度B="值2"',
render: (_: any, record: any) => {
const { constraint, name } = record;
const { measures } = measuresParams;
@@ -128,7 +128,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
<ProTable
actionRef={actionRef}
headerTitle="度量列表"
tooltip="一般用于在“指标”列表已有指标的基础上加工新指标比如指标NEW1=指标A/100指标NEW2=指标B/指标C。若需用到多个已有指标可以点击右上角“增加度量”"
tooltip="基于本主题域下所有数据源的度量来创建指标且该列表的度量为了加以区分均已加上数据源名称作为前缀选中度量后可基于这几个度量来写表达式若是选中的度量来自不同的数据源系统将会自动join来计算该指标"
rowKey="name"
columns={columns}
dataSource={measuresParams?.measures || []}
@@ -150,7 +150,7 @@ const MetricMeasuresFormTable: React.FC<Props> = ({
/>
<ProCard
title={'度量表达式'}
tooltip="若为指标NEW1则填写指标A/100。若为指标NEW2则填写指标B/指标C"
tooltip="度量表达式由上面选择的度量组成如选择了度量A和B则可将表达式写成A+B"
>
<SqlEditor
value={exprString}

View File

@@ -0,0 +1,75 @@
import { CheckCard } from '@ant-design/pro-components';
import React from 'react';
import { ISemantic } from '../data';
import { connect } from 'umi';
import icon from '../../../assets/icon/cloudEditor.svg';
import type { Dispatch } from 'umi';
import type { StateType } from '../model';
import { formatNumber } from '../../../utils/utils';
import styles from './style.less';
type Props = {
modelList: ISemantic.IDomainItem[];
domainManger: StateType;
dispatch: Dispatch;
};
const OverView: React.FC<Props> = ({ domainManger, dispatch, modelList }) => {
const { selectDomainId } = domainManger;
const extraNode = (model: ISemantic.IDomainItem) => {
const { metricCnt, dimensionCnt } = model;
return (
<div className={styles.overviewExtraContainer}>
<div className={styles.extraWrapper}>
<div className={styles.extraStatistic}>
<div className={styles.extraTitle}></div>
<div className={styles.extraValue}>
<span className="ant-statistic-content-value">{formatNumber(dimensionCnt || 0)}</span>
</div>
</div>
</div>
<div className={styles.extraWrapper}>
<div className={styles.extraStatistic}>
<div className={styles.extraTitle}></div>
<div className={styles.extraValue}>
<span className="ant-statistic-content-value">{formatNumber(metricCnt || 0)}</span>
</div>
</div>
</div>
</div>
);
};
return (
<>
<CheckCard.Group value={selectDomainId} defaultValue={selectDomainId}>
{modelList &&
modelList.map((model: ISemantic.IDomainItem) => {
return (
<CheckCard
avatar={icon}
title={model.name}
key={model.id}
value={model.id}
// description={model.description || '模型描述...'}
description={extraNode(model)}
onClick={() => {
const { id, name } = model;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: model,
});
}}
/>
);
})}
</CheckCard.Group>
</>
);
};
export default connect(({ domainManger }: { domainManger: StateType }) => ({
domainManger,
}))(OverView);

View File

@@ -6,23 +6,26 @@ import type { FC, Key } from 'react';
import { connect } from 'umi';
import type { Dispatch } from 'umi';
import type { StateType } from '../model';
import { getDomainList, createDomain, updateDomain, deleteDomain } from '../service';
import { createDomain, updateDomain, deleteDomain } from '../service';
import { treeParentKeyLists } from '../utils';
import ProjectInfoFormProps from './ProjectInfoForm';
import { constructorClassTreeFromList, addPathInTreeData } from '../utils';
import { PlusCircleOutlined } from '@ant-design/icons';
import styles from './style.less';
import { ISemantic } from '../data';
const { Search } = Input;
type ProjectListProps = {
selectDomainId: string;
selectDomainId: number;
selectDomainName: string;
domainList: ISemantic.IDomainItem[];
createDomainBtnVisible?: boolean;
dispatch: Dispatch;
onCreateDomainBtnClick?: () => void;
onTreeSelected?: () => void;
onTreeDataUpdate?: () => void;
};
const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode[] => {
@@ -42,9 +45,11 @@ const projectTreeFlat = (projectTree: DataNode[], filterValue: string): DataNode
const ProjectListTree: FC<ProjectListProps> = ({
selectDomainId,
domainList,
createDomainBtnVisible = true,
onCreateDomainBtnClick,
onTreeSelected,
onTreeDataUpdate,
dispatch,
}) => {
const [projectTree, setProjectTree] = useState<DataNode[]>([]);
@@ -54,40 +59,19 @@ const ProjectListTree: FC<ProjectListProps> = ({
const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
const [classList, setClassList] = useState<any[]>([]);
useEffect(() => {
const treeData = addPathInTreeData(constructorClassTreeFromList(domainList));
setProjectTree(treeData);
setClassList(domainList);
setExpandedKeys(treeParentKeyLists(treeData));
}, [domainList]);
const onSearch = (value: any) => {
setFliterValue(value);
};
const initProjectTree = async () => {
const { code, data, msg } = await getDomainList();
if (code === 200) {
const treeData = addPathInTreeData(constructorClassTreeFromList(data));
setProjectTree(treeData);
setClassList(data);
setExpandedKeys(treeParentKeyLists(treeData));
const firstRootNode = data.filter((item: any) => {
return item.parentId === 0;
})[0];
if (firstRootNode) {
const { id, name } = firstRootNode;
dispatch({
type: 'domainManger/setSelectDomain',
selectDomainId: id,
selectDomainName: name,
domainData: firstRootNode,
});
}
} else {
message.error(msg);
}
};
useEffect(() => {
initProjectTree();
}, []);
const handleSelect = (selectedKeys: string, projectName: string) => {
if (selectedKeys === selectDomainId) {
if (`${selectedKeys}` === `${selectDomainId}`) {
return;
}
const targetNodeData = classList.filter((item: any) => {
@@ -108,7 +92,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
if (res.code === 200) {
message.success('编辑分类成功');
setProjectInfoModalVisible(false);
initProjectTree();
onTreeDataUpdate?.();
} else {
message.error(res.msg);
}
@@ -120,7 +104,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
} else if (values.modelType === 'edit') {
await editProject(values);
}
initProjectTree();
onTreeDataUpdate?.();
setProjectInfoModalVisible(false);
};
@@ -130,7 +114,7 @@ const ProjectListTree: FC<ProjectListProps> = ({
if (res.code === 200) {
message.success('编辑项目成功');
setProjectInfoModalVisible(false);
initProjectTree();
onTreeDataUpdate?.();
} else {
message.error(res.msg);
}
@@ -210,18 +194,20 @@ const ProjectListTree: FC<ProjectListProps> = ({
onSearch={onSearch}
/>
</Col>
<Col flex="0 1 50px">
<Tooltip title="新增顶级域">
<PlusCircleOutlined
onClick={() => {
setProjectInfoParams({ type: 'top', modelType: 'add' });
setProjectInfoModalVisible(true);
onCreateDomainBtnClick?.();
}}
className={styles.addBtn}
/>
</Tooltip>
</Col>
{createDomainBtnVisible && (
<Col flex="0 1 50px">
<Tooltip title="新增顶级域">
<PlusCircleOutlined
onClick={() => {
setProjectInfoParams({ type: 'top', modelType: 'add' });
setProjectInfoModalVisible(true);
onCreateDomainBtnClick?.();
}}
className={styles.addBtn}
/>
</Tooltip>
</Col>
)}
</Row>
<Tree
@@ -249,8 +235,13 @@ const ProjectListTree: FC<ProjectListProps> = ({
};
export default connect(
({ domainManger: { selectDomainId, selectDomainName } }: { domainManger: StateType }) => ({
({
domainManger: { selectDomainId, selectDomainName, domainList },
}: {
domainManger: StateType;
}) => ({
selectDomainId,
selectDomainName,
domainList,
}),
)(ProjectListTree);

View File

@@ -0,0 +1,24 @@
import { Tag } from 'antd';
import React from 'react';
import { TransType } from '../enum';
type Props = {
type: TransType;
};
const TransTypeTag: React.FC<Props> = ({ type }) => {
return (
<>
{type === TransType.DIMENSION ? (
<Tag color="blue">{'维度'}</Tag>
) : type === 'metric' ? (
<Tag color="orange">{'指标'}</Tag>
) : (
<></>
)}
</>
);
};
export default TransTypeTag;

View File

@@ -247,3 +247,54 @@
color:#296DF3;
}
}
.overviewExtraContainer {
display: flex;
font-size: 14px;
.extraWrapper {
display: flex;
width: 100%;
.extraStatistic {
display: inline-flex;
color: rgba(42, 46, 54, 0.65);
box-sizing: border-box;
margin: 0;
padding: 0;
font-size: 14px;
line-height: 1.5714285714285714;
list-style: none;
.extraTitle {
font-size: 12px;
margin-inline-end: 6px;
margin-block-end: 0;
margin-bottom: 4px;
color: rgba(42, 46, 54, 0.45);
}
.extraValue {
font-size: 12px;
color: rgba(42, 46, 54, 0.65);
display: inline-block;
direction: ltr;
}
}
}
}
.infoTagList{
.siteTagPlus {
background: #fff;
border-style: dashed;
}
.editTag {
user-select: none;
}
.tagInput {
width: 78px;
margin-right: 8px;
vertical-align: top;
}
[data-theme="dark"] .siteTagPlus {
background: transparent;
border-style: dashed;
}
}

View File

@@ -64,6 +64,29 @@ export declare namespace IDataSource {
}
export declare namespace ISemantic {
interface IDomainItem {
createdBy?: string;
updatedBy?: string;
createdAt?: string;
updatedAt?: string;
id: number;
name: string;
bizName: string;
description: any;
status?: number;
typeEnum?: any;
sensitiveLevel?: number;
parentId: number;
fullPath?: string;
viewers?: any[];
viewOrgs?: any[];
admins?: string[];
adminOrgs?: any[];
isOpen?: number;
dimensionCnt?: number;
metricCnt?: number;
}
interface IDimensionItem {
createdBy: string;
updatedBy: string;
@@ -143,4 +166,56 @@ export declare namespace IChatConfig {
metricList: ISemantic.IMetricList;
};
}
interface IConfig {
id: any;
domainId: number;
domainName: string;
chatAggRichConfig: IChatRichConfig;
chatDetailRichConfig: IChatRichConfig;
bizName: string;
statusEnum: string;
createdBy: string;
updatedBy: string;
createdAt: string;
updatedAt: string;
}
interface IKnowledgeInfosItem {
itemId: number;
bizName: string;
type?: string;
searchEnable?: boolean;
knowledgeAdvancedConfig?: IKnowledgeConfig;
}
type IKnowledgeInfosItemMap = Record<IKnowledgeInfosItem.bizName, IKnowledgeInfosItem>;
interface IKnowledgeConfig {
blackList: string[];
whiteList: string[];
ruleList: string[];
}
interface IChatRichConfig {
id?: number;
visibility: {
blackDimIdList: number[];
blackMetricIdList: number[];
whiteDimIdList: number[];
whiteMetricIdList: number[];
};
entity: {
names: string[];
dimItem: ISemantic.IDimensionItem;
};
knowledgeInfos: IKnowledgeInfosItem[];
globalKnowledgeConfig: IKnowledgeConfig;
chatDefaultConfig: {
dimensions: ISemantic.IDimensionList;
metrics: ISemantic.IMetricList;
unit: number;
period: string;
};
}
}

View File

@@ -0,0 +1,9 @@
export enum ChatConfigType {
DETAIL = 'detail',
AGG = 'agg',
}
export enum TransType {
DIMENSION = 'dimension',
METRIC = 'metric',
}

View File

@@ -1,18 +1,20 @@
import type { Reducer, Effect } from 'umi';
import { message } from 'antd';
import { ISemantic } from './data';
import { getDimensionList, queryMetric, excuteSql, getDatabaseByDomainId } from './service';
export type StateType = {
current: number;
pageSize: number;
selectDomainId: any;
selectDomainId: number;
selectDomainName: string;
dimensionList: any[];
metricList: any[];
dimensionList: ISemantic.IDimensionList;
metricList: ISemantic.IMetricList;
searchParams: Record<string, any>;
domainData: any;
dataBaseResultColsMap: any;
dataBaseConfig: any;
domainData?: ISemantic.IDomainItem;
domainList: ISemantic.IDomainItem[];
};
export type ModelType = {
@@ -26,6 +28,7 @@ export type ModelType = {
};
reducers: {
setSelectDomain: Reducer<StateType>;
setDomainList: Reducer<StateType>;
setPagination: Reducer<StateType>;
setDimensionList: Reducer<StateType>;
setDataBaseScriptColumn: Reducer<StateType>;
@@ -38,14 +41,15 @@ export type ModelType = {
export const defaultState: StateType = {
current: 1,
pageSize: 20,
selectDomainId: undefined,
selectDomainId: 0,
selectDomainName: '',
searchParams: {},
dimensionList: [],
metricList: [],
domainData: {},
domainData: undefined,
dataBaseResultColsMap: {},
dataBaseConfig: {},
domainList: [],
};
const Model: ModelType = {
@@ -119,6 +123,12 @@ const Model: ModelType = {
domainData: action.domainData,
};
},
setDomainList(state = defaultState, action) {
return {
...state,
...action.payload,
};
},
setPagination(state = defaultState, action) {
return {
...state,

View File

@@ -1,5 +1,5 @@
import type { API } from '@/services/API';
import { ISemantic } from './data';
import type { DataNode } from 'antd/lib/tree';
export const changeTreeData = (treeData: API.ProjectList, auth?: boolean): DataNode[] => {
@@ -77,3 +77,50 @@ export const findDepartmentTree: any = (treeData: any[], projectId: string) => {
}
return findDepartmentTree(newStepList, projectId);
};
const isDescendant = (
node: ISemantic.IDomainItem,
parentId: number,
nodes: ISemantic.IDomainItem[],
): boolean => {
// 如果当前节点的 parentId 与指定的 parentId 相同,则说明它是指定节点的子节点
if (node.parentId === parentId) {
return true;
}
// 递归检查当前节点的父节点是否是指定节点的子节点
const parentNode = nodes.find((n) => n.id === node.parentId);
if (parentNode) {
return isDescendant(parentNode, parentId, nodes);
}
// 如果找不到父节点,则说明当前节点不是指定节点的子孙节点
return false;
};
export const findLeafNodesFromDomainList = (
nodes: ISemantic.IDomainItem[],
id: number | null = null,
): ISemantic.IDomainItem[] => {
const leafNodes: ISemantic.IDomainItem[] = [];
// 遍历所有节点
for (const node of nodes) {
let isLeaf = true;
// 检查当前节点是否有子节点
for (const childNode of nodes) {
if (childNode.parentId === node.id) {
isLeaf = false;
break;
}
}
// 如果当前节点是叶子节点,并且满足指定的 id 条件,则将其添加到结果数组中
if (isLeaf && (id === null || isDescendant(node, id, nodes))) {
leafNodes.push(node);
}
}
return leafNodes;
};

View File

@@ -329,8 +329,8 @@ export const getFormattedValueData = (value: number | string, remainZero?: boole
value >= 100000000
? NumericUnit.OneHundredMillion
: value >= 10000
? NumericUnit.EnTenThousand
: NumericUnit.None;
? NumericUnit.EnTenThousand
: NumericUnit.None;
let formattedValue = formatByUnit(value, unit);
formattedValue = formatByDecimalPlaces(
@@ -388,3 +388,37 @@ export function getLeafList(flatNodes: any[]): any[] {
const leafNodes = getLeafNodes(treeNodes);
return leafNodes;
}
export function traverseRoutes(routes, env: string, result: any[] = []) {
if (!Array.isArray(routes)) {
return result;
}
for (let i = 0; i < routes.length; i++) {
const route = routes[i];
if (
(route.envEnableList &&
(route.envEnableList.includes(env) || route.envEnableList.length === 0)) ||
!route.envEnableList
) {
result.push(route);
}
if (route.envRedirect) {
route.redirect = route.envRedirect[env];
}
if (route.routes) {
const filteredRoutes = traverseRoutes(route.routes, env);
if (Array.isArray(filteredRoutes) && filteredRoutes.length > 0) {
result.push({
...route,
routes: filteredRoutes,
});
}
}
}
return result;
}

View File

@@ -0,0 +1 @@
preview.pro.ant.design

View File

@@ -0,0 +1,20 @@
{
"/umi.css": "/webapp/umi.97f57065.css",
"/umi.js": "/webapp/umi.70e4115f.js",
"/public/CNAME": "/webapp/CNAME",
"/public/favicon.ico": "/webapp/favicon.ico",
"/public/home_bg.png": "/webapp/home_bg.png",
"/public/icons/icon-128x128.png": "/webapp/icons/icon-128x128.png",
"/public/icons/icon-192x192.png": "/webapp/icons/icon-192x192.png",
"/public/icons/icon-512x512.png": "/webapp/icons/icon-512x512.png",
"/index.html": "/webapp/index.html",
"/public/logo.svg": "/webapp/logo.svg",
"/public/pro_icon.svg": "/webapp/pro_icon.svg",
"/static/cloudEditor.svg": "/webapp/static/cloudEditor.1a9aa2c1.svg",
"/static/iconfont.woff2?t=1659425018463": "/webapp/static/iconfont.0ac2d58a.woff2",
"/static/iconfont.woff?t=1659425018463": "/webapp/static/iconfont.0de60a33.woff",
"/static/iconfont.ttf?t=1659425018463": "/webapp/static/iconfont.7ae6e4e0.ttf",
"/static/iconfont.svg?t=1659425018463": "/webapp/static/iconfont.92a3f736.svg",
"/public/supersonic.config.json": "/webapp/supersonic.config.json",
"/public/version.js": "/webapp/version.js"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
/>
<title>超音数(SuperSonic)</title>
<link rel="icon" href="/webapp/favicon.ico" type="image/x-icon" />
<meta name="app_version" content="2023-07-16 20:07:06" />
<link rel="stylesheet" href="/webapp/umi.97f57065.css" />
<script>
window.routerBase = "/webapp/";
</script>
<script>
//! umi version: 3.5.41
</script>
</head>
<body>
<noscript>Out-of-the-box mid-stage front/design solution!</noscript>
<div id="root"></div>
<script src="/webapp/umi.70e4115f.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 44 18" class="design-iconfont" width="128" height="128"><path d="M24.7272727,4.26325641e-14 L33.5127273,17.4545455 L26.5345455,17.4545455 L21.1236364,6.70181818 L24.7272727,4.26325641e-14 Z M17.52,4.26325641e-14 L21.1236364,6.70181818 L15.7127273,17.4545455 L8.73090909,17.4545455 L17.52,4.26325641e-14 Z M41.5890909,12.6945455 L43.9818182,17.4545455 L35.0909091,17.4545455 L32.6981818,12.6945455 L41.5890909,12.6945455 Z M12.68,6.32 L7.08,17.4545455 L0.498181818,17.4545455 L6.09818182,6.32 L12.68,6.32 Z M38.4145455,6.32 L40.9090909,11.2727273 L32.0181818,11.2727273 L29.5272727,6.32 L38.4145455,6.32 Z M15.7890909,0.141818182 L13.3963636,4.89818182 L-3.55271368e-14,4.89818182 L2.39272727,0.141818182 L15.7890909,0.141818182 Z M35.2690909,0.141818182 L37.6654545,4.89818182 L28.7745455,4.89818182 L26.3818182,0.141818182 L35.2690909,0.141818182 Z" fill-rule="evenodd" fill="#1890ff"></path></svg>

After

Width:  |  Height:  |  Size: 953 B

View File

@@ -0,0 +1,5 @@
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
<g>
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 677 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -0,0 +1,3 @@
{
"env": ""
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
feVersion={
"commitId": "cf043d8f0099a86e2194ce93623bb384603ef45d",
"updateTime": "Sun Jul 16 2023 20:07:04 GMT+0800 (China Standard Time)"
}