[improvement][project] supersonic 0.6.0 version update (#16)
Co-authored-by: lexluo <lexluo@tencent.com>
@@ -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} />;
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
},
|
||||
|
||||
@@ -21,5 +21,6 @@ const Settings: LayoutSettings & {
|
||||
ignoreFlatMenu: true,
|
||||
},
|
||||
};
|
||||
export const publicPath = '/webapp/';
|
||||
|
||||
export default Settings;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"env": "semantic"
|
||||
"env": ""
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
After Width: | Height: | Size: 44 KiB |
44
webapp/packages/supersonic-fe/src/assets/icon/linkModel.svg
Normal 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 |
@@ -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 |
@@ -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'>;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -174,7 +174,7 @@ const FieldForm: React.FC<Props> = ({ fields, onFieldChange }) => {
|
||||
[isCreateName]: 1,
|
||||
});
|
||||
}}
|
||||
placeholder="请输入中文名"
|
||||
placeholder="请填写名称"
|
||||
/>
|
||||
</Checkbox>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -156,6 +156,10 @@ const XflowJsonSchemaFormDrawerForm: React.FC<CreateFormProps> = (props) => {
|
||||
{
|
||||
<ClassDataSourceTypeModal
|
||||
open={createDataSourceModalOpen}
|
||||
onCancel={() => {
|
||||
resetSelectedNode();
|
||||
setCreateDataSourceModalOpen(false);
|
||||
}}
|
||||
onTypeChange={(type) => {
|
||||
if (type === 'fast') {
|
||||
setDataSourceModalVisible(true);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -757,3 +757,4 @@
|
||||
height: 16px !important;
|
||||
margin: 0 3px 4px;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -91,6 +91,7 @@ const ClassDimensionTable: React.FC<Props> = ({ domainManger, dispatch }) => {
|
||||
{
|
||||
dataIndex: 'alias',
|
||||
title: '别名',
|
||||
search: false,
|
||||
},
|
||||
{
|
||||
dataIndex: 'bizName',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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="维度描述"
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
|
||||
@@ -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();
|
||||
}}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
export enum ChatConfigType {
|
||||
DETAIL = 'detail',
|
||||
AGG = 'agg',
|
||||
}
|
||||
|
||||
export enum TransType {
|
||||
DIMENSION = 'dimension',
|
||||
METRIC = 'metric',
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
1
webapp/packages/supersonic-fe/supersonic-webapp/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
preview.pro.ant.design
|
||||
@@ -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"
|
||||
}
|
||||
BIN
webapp/packages/supersonic-fe/supersonic-webapp/favicon.ico
Normal file
|
After Width: | Height: | Size: 551 B |
BIN
webapp/packages/supersonic-fe/supersonic-webapp/home_bg.png
Normal file
|
After Width: | Height: | Size: 199 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
34
webapp/packages/supersonic-fe/supersonic-webapp/index.html
Normal 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>
|
||||
1
webapp/packages/supersonic-fe/supersonic-webapp/logo.svg
Normal 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 |
@@ -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 |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"env": ""
|
||||
}
|
||||
4312
webapp/packages/supersonic-fe/supersonic-webapp/umi.70e4115f.js
Normal file
@@ -0,0 +1,4 @@
|
||||
feVersion={
|
||||
"commitId": "cf043d8f0099a86e2194ce93623bb384603ef45d",
|
||||
"updateTime": "Sun Jul 16 2023 20:07:04 GMT+0800 (China Standard Time)"
|
||||
}
|
||||