mirror of
https://github.com/tencentmusic/supersonic.git
synced 2025-12-14 22:25:19 +00:00
[feature](webapp) merge query steps to one card
This commit is contained in:
@@ -159,7 +159,7 @@ const BarChart: React.FC<Props> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const hasFilterSection = dimensionFilters?.length > 0;
|
||||
// const hasFilterSection = dimensionFilters?.length > 0;
|
||||
|
||||
const prefixCls = `${PREFIX_CLS}-bar`;
|
||||
|
||||
@@ -167,11 +167,11 @@ const BarChart: React.FC<Props> = ({
|
||||
<div>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
<div className={`${prefixCls}-indicator-name`}>{metricColumn?.name}</div>
|
||||
{(hasFilterSection || drillDownDimension) && (
|
||||
{drillDownDimension && (
|
||||
<div className={`${prefixCls}-filter-section-wrapper`}>
|
||||
(
|
||||
<div className={`${prefixCls}-filter-section`}>
|
||||
<FilterSection chatContext={chatContext} entityInfo={entityInfo} />
|
||||
{/* <FilterSection chatContext={chatContext} entityInfo={entityInfo} /> */}
|
||||
{drillDownDimension && (
|
||||
<div className={`${prefixCls}-filter-item`}>
|
||||
<div className={`${prefixCls}-filter-item-label`}>下钻维度:</div>
|
||||
@@ -183,13 +183,13 @@ const BarChart: React.FC<Props> = ({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{dateInfo && (
|
||||
{/* {dateInfo && (
|
||||
<div className={`${prefixCls}-date-range`}>
|
||||
{dateInfo.startDate === dateInfo.endDate
|
||||
? dateInfo.startDate
|
||||
: `${dateInfo.startDate} ~ ${dateInfo.endDate}`}
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
<Spin spinning={loading}>
|
||||
<div className={`${prefixCls}-chart`} ref={chartRef} />
|
||||
</Spin>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { PREFIX_CLS } from '../../../common/constants';
|
||||
import { formatMetric } from '../../../utils/utils';
|
||||
import { formatMetric, formatNumberWithCN } from '../../../utils/utils';
|
||||
import ApplyAuth from '../ApplyAuth';
|
||||
import { DrillDownDimensionType, MsgDataType } from '../../../common/type';
|
||||
import PeriodCompareItem from './PeriodCompareItem';
|
||||
import DrillDownDimensions from '../../DrillDownDimensions';
|
||||
import { Spin } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import FilterSection from '../FilterSection';
|
||||
import { SwapOutlined } from '@ant-design/icons';
|
||||
import { useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
@@ -26,7 +27,7 @@ const MetricCard: React.FC<Props> = ({
|
||||
const { queryMode, queryColumns, queryResults, entityInfo, aggregateInfo, chatContext } = data;
|
||||
|
||||
const { metricInfos } = aggregateInfo || {};
|
||||
const { dateInfo, dimensionFilters } = chatContext || {};
|
||||
const { dateInfo } = chatContext || {};
|
||||
const { startDate } = dateInfo || {};
|
||||
|
||||
const indicatorColumn = queryColumns?.find(column => column.showType === 'NUMBER');
|
||||
@@ -34,25 +35,31 @@ const MetricCard: React.FC<Props> = ({
|
||||
|
||||
const prefixCls = `${PREFIX_CLS}-metric-card`;
|
||||
|
||||
const matricCardClass = classNames(prefixCls, {
|
||||
[`${PREFIX_CLS}-metric-card-dsl`]: queryMode === 'DSL',
|
||||
});
|
||||
|
||||
const indicatorClass = classNames(`${prefixCls}-indicator`, {
|
||||
[`${prefixCls}-indicator-period-compare`]: metricInfos?.length > 0,
|
||||
});
|
||||
|
||||
const hasFilterSection = dimensionFilters?.length > 0;
|
||||
const [isNumber, setIsNumber] = useState(false);
|
||||
const handleNumberClick = () => {
|
||||
setIsNumber(!isNumber);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
<div className={matricCardClass}>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
{indicatorColumn?.name ? (
|
||||
<div className={`${prefixCls}-indicator-name`}>{indicatorColumn?.name}</div>
|
||||
) : (
|
||||
<div style={{ height: 32 }} />
|
||||
<div style={{ height: 6 }} />
|
||||
)}
|
||||
{(hasFilterSection || drillDownDimension) && (
|
||||
{drillDownDimension && (
|
||||
<div className={`${prefixCls}-filter-section-wrapper`}>
|
||||
(
|
||||
<div className={`${prefixCls}-filter-section`}>
|
||||
<FilterSection chatContext={chatContext} entityInfo={entityInfo} />
|
||||
{drillDownDimension && (
|
||||
<div className={`${prefixCls}-filter-item`}>
|
||||
<div className={`${prefixCls}-filter-item-label`}>下钻维度:</div>
|
||||
@@ -70,8 +77,15 @@ const MetricCard: React.FC<Props> = ({
|
||||
{indicatorColumn && !indicatorColumn?.authorized ? (
|
||||
<ApplyAuth model={entityInfo?.modelInfo.name || ''} onApplyAuth={onApplyAuth} />
|
||||
) : (
|
||||
<div className={`${prefixCls}-indicator-value`}>
|
||||
{formatMetric(queryResults?.[0]?.[indicatorColumnName]) || '-'}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||
<div className={`${prefixCls}-indicator-value`}>
|
||||
{isNumber
|
||||
? formatMetric(queryResults?.[0]?.[indicatorColumnName]) || '-'
|
||||
: formatNumberWithCN(+queryResults?.[0]?.[indicatorColumnName])}
|
||||
</div>
|
||||
<div className={`${prefixCls}-indicator-switch`}>
|
||||
<SwapOutlined onClick={handleNumberClick} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{metricInfos?.length > 0 && (
|
||||
@@ -90,7 +104,6 @@ const MetricCard: React.FC<Props> = ({
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
drillDownDimension={drillDownDimension}
|
||||
onSelectDimension={onSelectDimension}
|
||||
isMetricCard
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
|
||||
.@{metric-card-prefix-cls} {
|
||||
width: 100%;
|
||||
height: 130px;
|
||||
height: 162px;
|
||||
row-gap: 4px;
|
||||
|
||||
&-dsl {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
&-top-bar {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -73,6 +77,13 @@
|
||||
color: var(--chat-blue);
|
||||
}
|
||||
|
||||
&-indicator-switch {
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 18px;
|
||||
margin-left: 6px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&-period-compare {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -108,8 +119,6 @@
|
||||
}
|
||||
|
||||
&-drill-down-dimensions {
|
||||
position: absolute;
|
||||
bottom: -44px;
|
||||
left: -16;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { PREFIX_CLS } from '../../../common/constants';
|
||||
import { formatMetric } from '../../../utils/utils';
|
||||
import { formatMetric, formatNumberWithCN } from '../../../utils/utils';
|
||||
import { AggregateInfoType } from '../../../common/type';
|
||||
import PeriodCompareItem from '../MetricCard/PeriodCompareItem';
|
||||
import { SwapOutlined } from '@ant-design/icons';
|
||||
import { useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
aggregateInfo: AggregateInfoType;
|
||||
@@ -14,18 +16,34 @@ const MetricInfo: React.FC<Props> = ({ aggregateInfo }) => {
|
||||
|
||||
const prefixCls = `${PREFIX_CLS}-metric-info`;
|
||||
|
||||
const [isNumber, setIsNumber] = useState(false);
|
||||
const handleNumberClick = () => {
|
||||
setIsNumber(!isNumber);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
<div className={`${prefixCls}-indicator`}>
|
||||
<div className={`${prefixCls}-date`}>{date}</div>
|
||||
<div className={`${prefixCls}-indicator-value`}>{formatMetric(value)}</div>
|
||||
{metricInfos?.length > 0 && (
|
||||
<div className={`${prefixCls}-period-compare`}>
|
||||
{Object.keys(statistics).map((key: any) => (
|
||||
<PeriodCompareItem title={key} value={metricInfos[0].statistics[key]} />
|
||||
))}
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||
<div className={`${prefixCls}-indicator-value`}>
|
||||
{isNumber ? formatMetric(value) : formatNumberWithCN(+value)}
|
||||
</div>
|
||||
)}
|
||||
<div className={`${prefixCls}-indicator-switch`}>
|
||||
<SwapOutlined onClick={handleNumberClick} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${prefixCls}-bottom-section`}>
|
||||
<div className={`${prefixCls}-date`}>
|
||||
最新数据日期:<span className={`${prefixCls}-date-value`}>{date}</span>
|
||||
</div>
|
||||
{metricInfos?.length > 0 && (
|
||||
<div className={`${prefixCls}-period-compare`}>
|
||||
{Object.keys(statistics).map((key: any) => (
|
||||
<PeriodCompareItem title={key} value={metricInfos[0].statistics[key]} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import { ColumnType } from '../../../common/type';
|
||||
import NoPermissionChart from '../NoPermissionChart';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
model?: string;
|
||||
@@ -201,12 +202,16 @@ const MetricTrendChart: React.FC<Props> = ({
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||
|
||||
const flowTrendChartClass = classNames(`${prefixCls}-flow-trend-chart`, {
|
||||
[`${prefixCls}-flow-trend-chart-single`]: !categoryColumnName,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!metricField.authorized ? (
|
||||
<NoPermissionChart model={model || ''} onApplyAuth={onApplyAuth} />
|
||||
) : (
|
||||
<div className={`${prefixCls}-flow-trend-chart`} ref={chartRef} />
|
||||
<div className={flowTrendChartClass} ref={chartRef} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Spin } from 'antd';
|
||||
import Table from '../Table';
|
||||
import DrillDownDimensions from '../../DrillDownDimensions';
|
||||
import MetricInfo from './MetricInfo';
|
||||
import FilterSection from '../FilterSection';
|
||||
import MetricOptions from '../../MetricOptions';
|
||||
|
||||
type Props = {
|
||||
data: MsgDataType;
|
||||
@@ -25,6 +25,7 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
const dateOptions = DATE_TYPES[chatContext?.dateInfo?.period] || DATE_TYPES.DAY;
|
||||
|
||||
const [columns, setColumns] = useState<ColumnType[]>([]);
|
||||
const [defaultMetricField, setDefaultMetricField] = useState<FieldType>();
|
||||
const [activeMetricField, setActiveMetricField] = useState<FieldType>();
|
||||
const [dataSource, setDataSource] = useState<any[]>([]);
|
||||
const [currentDateOption, setCurrentDateOption] = useState<number>();
|
||||
@@ -58,7 +59,9 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
})?.value;
|
||||
|
||||
setColumns(queryColumns || []);
|
||||
setActiveMetricField(chatContext?.metrics?.[0]);
|
||||
const metricField = chatContext?.metrics?.[0];
|
||||
setDefaultMetricField(metricField);
|
||||
setActiveMetricField(metricField);
|
||||
setDataSource(queryResults);
|
||||
setCurrentDateOption(initialDateOption);
|
||||
setDimensions(chatContext?.dimensions);
|
||||
@@ -107,7 +110,7 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
});
|
||||
};
|
||||
|
||||
const onSwitchMetric = (metricField: FieldType) => {
|
||||
const onSwitchMetric = (metricField?: FieldType) => {
|
||||
setActiveMetricField(metricField);
|
||||
onLoadData({
|
||||
dateInfo: {
|
||||
@@ -116,7 +119,7 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
unit: currentDateOption || chatContext.dateInfo.unit,
|
||||
},
|
||||
dimensions: drillDownDimension ? [...(dimensions || []), drillDownDimension] : undefined,
|
||||
metrics: [metricField],
|
||||
metrics: [metricField || defaultMetricField],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -139,44 +142,25 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
return null;
|
||||
}
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||
const isMultipleMetric = chatContext?.metrics?.length > 1;
|
||||
const existDrillDownDimension = queryMode.includes('METRIC') && !isEntityMode;
|
||||
|
||||
const hasFilterSection = dimensionFilters?.length > 0;
|
||||
const prefixCls = `${CLS_PREFIX}-metric-trend`;
|
||||
|
||||
return (
|
||||
<div className={prefixCls}>
|
||||
<div className={`${prefixCls}-charts`}>
|
||||
<div className={`${prefixCls}-top-bar`}>
|
||||
{chatContext.metrics.length > 0 && (
|
||||
<div className={`${prefixCls}-metric-fields`}>
|
||||
{chatContext.metrics.slice(0, 5).map((metricField: FieldType) => {
|
||||
const metricFieldClass = classNames(`${prefixCls}-metric-field`, {
|
||||
[`${prefixCls}-metric-field-active`]:
|
||||
activeMetricField?.bizName === metricField.bizName &&
|
||||
chatContext.metrics.length > 1,
|
||||
[`${prefixCls}-metric-field-single`]: chatContext.metrics.length === 1,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
className={metricFieldClass}
|
||||
key={metricField.bizName}
|
||||
onClick={() => {
|
||||
if (chatContext.metrics.length > 1) {
|
||||
onSwitchMetric(metricField);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{metricField.name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{(hasFilterSection || drillDownDimension) && (
|
||||
<div
|
||||
className={`${prefixCls}-metric-fields ${prefixCls}-metric-field-single`}
|
||||
key={activeMetricField?.bizName}
|
||||
>
|
||||
{activeMetricField?.name}
|
||||
</div>
|
||||
{drillDownDimension && (
|
||||
<div className={`${prefixCls}-filter-section-wrapper`}>
|
||||
(
|
||||
<div className={`${prefixCls}-filter-section`}>
|
||||
<FilterSection chatContext={chatContext} />
|
||||
{drillDownDimension && (
|
||||
<div className={`${prefixCls}-filter-item`}>
|
||||
<div className={`${prefixCls}-filter-item-label`}>下钻维度:</div>
|
||||
@@ -192,7 +176,7 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{aggregateInfoValue?.metricInfos?.length > 0 && (
|
||||
{!isMobile && aggregateInfoValue?.metricInfos?.length > 0 && (
|
||||
<MetricInfo aggregateInfo={aggregateInfoValue} />
|
||||
)}
|
||||
<div className={`${prefixCls}-date-options`}>
|
||||
@@ -236,13 +220,25 @@ const MetricTrend: React.FC<Props> = ({ data, chartIndex, triggerResize, onApply
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{queryMode.includes('METRIC') && !isEntityMode && (
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext.modelId}
|
||||
drillDownDimension={drillDownDimension}
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
{(isMultipleMetric || existDrillDownDimension) && (
|
||||
<div className={`${prefixCls}-bottom-tools`}>
|
||||
{isMultipleMetric && (
|
||||
<MetricOptions
|
||||
metrics={chatContext.metrics}
|
||||
defaultMetric={defaultMetricField}
|
||||
currentMetric={activeMetricField}
|
||||
onSelectMetric={onSwitchMetric}
|
||||
/>
|
||||
)}
|
||||
{existDrillDownDimension && (
|
||||
<DrillDownDimensions
|
||||
modelId={chatContext.modelId}
|
||||
drillDownDimension={drillDownDimension}
|
||||
dimensionFilters={chatContext.dimensionFilters}
|
||||
onSelectDimension={onSelectDimension}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Spin>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-color-third);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&-filter-section {
|
||||
@@ -58,7 +59,7 @@
|
||||
&-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -83,11 +84,15 @@
|
||||
height: 230px;
|
||||
}
|
||||
|
||||
&-flow-trend-chart-single {
|
||||
height: 180px;
|
||||
}
|
||||
|
||||
&-charts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
row-gap: 12px;
|
||||
row-gap: 4px;
|
||||
}
|
||||
|
||||
&-metric-fields {
|
||||
@@ -123,11 +128,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-metric-field-active {
|
||||
color: #fff !important;
|
||||
background-color: var(--chat-blue);
|
||||
}
|
||||
|
||||
&-metric-field-single {
|
||||
padding-left: 0;
|
||||
font-weight: 500;
|
||||
@@ -165,6 +165,13 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-bottom-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&-active-identifier {
|
||||
position: absolute;
|
||||
bottom: -6px;
|
||||
@@ -184,14 +191,8 @@
|
||||
.@{metric-info-prefix-cls} {
|
||||
&-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
&-date {
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 12px;
|
||||
align-items: baseline;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
&-indicator-value {
|
||||
@@ -203,12 +204,33 @@
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
&-period-compare {
|
||||
width: 100%;
|
||||
&-bottom-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 20px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
&-date {
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&-date-value {
|
||||
color: var(--chat-blue);
|
||||
}
|
||||
|
||||
&-indicator-switch {
|
||||
color: var(--text-color-fourth);
|
||||
font-size: 18px;
|
||||
margin-left: 6px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&-period-compare {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 20px;
|
||||
margin-top: 2px;
|
||||
font-size: 13px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@@ -68,8 +68,7 @@ const Table: React.FC<Props> = ({ data, size, onApplyAuth }) => {
|
||||
}
|
||||
columns={tableColumns}
|
||||
dataSource={queryResults}
|
||||
style={{ width: '100%' }}
|
||||
// scroll={{ x: 'max-content' }}
|
||||
style={{ width: '100%', overflowX: 'auto' }}
|
||||
rowClassName={getRowClassName}
|
||||
size={size}
|
||||
/>
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
@table-prefix-cls: ~'@{supersonic-chat-prefix}-table';
|
||||
|
||||
.@{table-prefix-cls} {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 20px;
|
||||
margin-top: 6px;
|
||||
|
||||
&-photo {
|
||||
display: flex;
|
||||
@@ -68,9 +67,13 @@
|
||||
|
||||
.ant-table-tbody {
|
||||
.ant-table-cell {
|
||||
padding: 15px 0;
|
||||
color: #333;
|
||||
padding: 12px 2px;
|
||||
color: var(--text-color);
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-pagination.ant-pagination {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { CLS_PREFIX } from '../../../common/constants';
|
||||
import { MsgDataType } from '../../../common/type';
|
||||
import { isProd } from '../../../utils/utils';
|
||||
|
||||
type Props = {
|
||||
id: string | number;
|
||||
data: MsgDataType;
|
||||
};
|
||||
|
||||
const DEFAULT_HEIGHT = 800;
|
||||
|
||||
const WebPage: React.FC<Props> = ({ id, data }) => {
|
||||
const [pluginUrl, setPluginUrl] = useState('');
|
||||
const [height, setHeight] = useState(DEFAULT_HEIGHT);
|
||||
|
||||
const prefixCls = `${CLS_PREFIX}-web-page`;
|
||||
|
||||
const {
|
||||
name,
|
||||
webPage: { url, params },
|
||||
} = data.response || {};
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const messageData = event.data;
|
||||
const { type, payload } = messageData;
|
||||
if (type === 'changeMiniProgramContainerSize') {
|
||||
const { msgId, height } = payload;
|
||||
if (`${msgId}` === `${id}`) {
|
||||
setHeight(height);
|
||||
// updateMessageContainerScroll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (messageData === 'storyResize') {
|
||||
const ifr: any = document.getElementById(`reportIframe_${id}`);
|
||||
const iDoc = ifr.contentDocument || ifr.document || ifr.contentWindow;
|
||||
setTimeout(() => {
|
||||
setHeight(isProd() ? calcPageHeight(iDoc) : DEFAULT_HEIGHT);
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', handleMessage);
|
||||
return () => {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}, [handleMessage]);
|
||||
|
||||
function calcPageHeight(doc: any) {
|
||||
const titleAreaEl = doc.getElementById('titleArea');
|
||||
const titleAreaHeight = Math.max(
|
||||
titleAreaEl?.clientHeight || 0,
|
||||
titleAreaEl?.scrollHeight || 0
|
||||
);
|
||||
const dashboardGridEl = doc.getElementsByClassName('dashboardGrid')?.[0];
|
||||
const dashboardGridHeight = Math.max(
|
||||
dashboardGridEl?.clientHeight || 0,
|
||||
dashboardGridEl?.scrollHeight || 0
|
||||
);
|
||||
return Math.max(titleAreaHeight + dashboardGridHeight + 10, DEFAULT_HEIGHT);
|
||||
}
|
||||
|
||||
const initData = () => {
|
||||
const heightValue =
|
||||
params?.find((option: any) => option.paramType === 'FORWARD' && option.key === 'height')
|
||||
?.value || DEFAULT_HEIGHT;
|
||||
setHeight(heightValue);
|
||||
let urlValue = url;
|
||||
const valueParams = (params || [])
|
||||
.filter((option: any) => option.paramType !== 'FORWARD')
|
||||
.reduce((result: any, item: any) => {
|
||||
result[item.key] = item.value;
|
||||
return result;
|
||||
}, {});
|
||||
if (urlValue.includes('?type=dashboard') || urlValue.includes('?type=widget')) {
|
||||
const filterData = encodeURIComponent(
|
||||
JSON.stringify(
|
||||
urlValue.includes('dashboard')
|
||||
? {
|
||||
global: valueParams,
|
||||
}
|
||||
: {
|
||||
local: valueParams,
|
||||
}
|
||||
)
|
||||
);
|
||||
urlValue = urlValue.replace(
|
||||
'?',
|
||||
`?miniProgram=true&reportName=${name}&filterData=${filterData}&`
|
||||
);
|
||||
urlValue =
|
||||
!isProd() && !urlValue.includes('http') ? `http://s2.tmeoa.com${urlValue}` : urlValue;
|
||||
} else {
|
||||
const params = Object.keys(valueParams || {}).map(key => `${key}=${valueParams[key]}`);
|
||||
if (params.length > 0) {
|
||||
if (url.includes('?')) {
|
||||
urlValue = urlValue.replace('?', `?${params.join('&')}&`);
|
||||
} else {
|
||||
urlValue = `${urlValue}?${params.join('&')}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
// onReportLoaded(heightValue + 190);
|
||||
setPluginUrl(urlValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
// <div className={prefixCls} style={{ height }}>
|
||||
<iframe
|
||||
id={`reportIframe_${id}`}
|
||||
name={`reportIframe_${id}`}
|
||||
src={pluginUrl}
|
||||
style={{ width: '100%', height, border: 'none' }}
|
||||
title="reportIframe"
|
||||
allowFullScreen
|
||||
/>
|
||||
// </div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WebPage;
|
||||
@@ -1,12 +1,13 @@
|
||||
import { isMobile } from '../../utils/utils';
|
||||
import Bar from './Bar';
|
||||
import Message from './Message';
|
||||
import MetricCard from './MetricCard';
|
||||
import MetricTrend from './MetricTrend';
|
||||
import Table from './Table';
|
||||
import { ColumnType, DrillDownDimensionType, MsgDataType } from '../../common/type';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { queryData } from '../../service';
|
||||
import classNames from 'classnames';
|
||||
import { PREFIX_CLS } from '../../common/constants';
|
||||
|
||||
type Props = {
|
||||
question: string;
|
||||
@@ -17,7 +18,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, triggerResize }) => {
|
||||
const { queryColumns, queryResults, chatContext, entityInfo, queryMode } = data;
|
||||
const { queryColumns, queryResults, chatContext, queryMode } = data;
|
||||
|
||||
const [columns, setColumns] = useState<ColumnType[]>(queryColumns);
|
||||
const [dataSource, setDataSource] = useState<any[]>(queryResults);
|
||||
@@ -25,6 +26,8 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
const [drillDownDimension, setDrillDownDimension] = useState<DrillDownDimensionType>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const prefixCls = `${PREFIX_CLS}-chat-msg`;
|
||||
|
||||
useEffect(() => {
|
||||
setColumns(queryColumns);
|
||||
setDataSource(queryResults);
|
||||
@@ -39,9 +42,11 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
const categoryField = columns.filter(item => item.showType === 'CATEGORY');
|
||||
const metricFields = columns.filter(item => item.showType === 'NUMBER');
|
||||
|
||||
const isDslMetricCard =
|
||||
queryMode === 'DSL' && singleData && metricFields.length === 1 && columns.length === 1;
|
||||
|
||||
const isMetricCard =
|
||||
(queryMode.includes('METRIC') ||
|
||||
(queryMode === 'DSL' && singleData && metricFields.length === 1 && columns.length === 1)) &&
|
||||
(queryMode.includes('METRIC') || isDslMetricCard) &&
|
||||
(singleData || chatContext?.dateInfo?.startDate === chatContext?.dateInfo?.endDate);
|
||||
|
||||
const isText =
|
||||
@@ -51,6 +56,14 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
queryMode === 'METRIC_INTERPRET') &&
|
||||
singleData;
|
||||
|
||||
const isTable =
|
||||
!isText &&
|
||||
!isMetricCard &&
|
||||
(categoryField.length > 1 ||
|
||||
queryMode === 'ENTITY_DETAIL' ||
|
||||
queryMode === 'ENTITY_DIMENSION' ||
|
||||
(categoryField.length === 1 && metricFields.length === 0));
|
||||
|
||||
const onLoadData = async (value: any) => {
|
||||
setLoading(true);
|
||||
const { data } = await queryData({
|
||||
@@ -72,47 +85,51 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
});
|
||||
};
|
||||
|
||||
const getTextContent = () => {
|
||||
let text = dataSource[0][columns[0].nameEn];
|
||||
let htmlCode: string;
|
||||
const match = text.match(/```html([\s\S]*?)```/);
|
||||
htmlCode = match && match[1].trim();
|
||||
if (htmlCode) {
|
||||
text = text.replace(/```html([\s\S]*?)```/, '');
|
||||
}
|
||||
let scriptCode: string;
|
||||
let scriptSrc: string;
|
||||
if (htmlCode) {
|
||||
scriptSrc = htmlCode.match(/<script src="([\s\S]*?)"><\/script>/)?.[1] || '';
|
||||
scriptCode =
|
||||
htmlCode.match(/<script type="text\/javascript">([\s\S]*?)<\/script>/)?.[1] || '';
|
||||
if (scriptSrc) {
|
||||
const script = document.createElement('script');
|
||||
script.src = scriptSrc;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
if (scriptCode) {
|
||||
const script = document.createElement('script');
|
||||
script.innerHTML = scriptCode;
|
||||
setTimeout(() => {
|
||||
document.body.appendChild(script);
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
lineHeight: '24px',
|
||||
width: 'fit-content',
|
||||
maxWidth: '100%',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
{htmlCode ? <pre>{text}</pre> : text}
|
||||
{!!htmlCode && <div dangerouslySetInnerHTML={{ __html: htmlCode }} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getMsgContent = () => {
|
||||
if (isText) {
|
||||
let text = dataSource[0][columns[0].nameEn];
|
||||
let htmlCode: string;
|
||||
const match = text.match(/```html([\s\S]*?)```/);
|
||||
htmlCode = match && match[1].trim();
|
||||
if (htmlCode) {
|
||||
text = text.replace(/```html([\s\S]*?)```/, '');
|
||||
}
|
||||
let scriptCode: string;
|
||||
let scriptSrc: string;
|
||||
if (htmlCode) {
|
||||
scriptSrc = htmlCode.match(/<script src="([\s\S]*?)"><\/script>/)?.[1] || '';
|
||||
scriptCode =
|
||||
htmlCode.match(/<script type="text\/javascript">([\s\S]*?)<\/script>/)?.[1] || '';
|
||||
if (scriptSrc) {
|
||||
const script = document.createElement('script');
|
||||
script.src = scriptSrc;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
if (scriptCode) {
|
||||
const script = document.createElement('script');
|
||||
script.innerHTML = scriptCode;
|
||||
setTimeout(() => {
|
||||
document.body.appendChild(script);
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
lineHeight: '24px',
|
||||
width: 'fit-content',
|
||||
maxWidth: '100%',
|
||||
overflowX: 'hidden',
|
||||
}}
|
||||
>
|
||||
{htmlCode ? <pre>{text}</pre> : text}
|
||||
{!!htmlCode && <div dangerouslySetInnerHTML={{ __html: htmlCode }} />}
|
||||
</div>
|
||||
);
|
||||
return getTextContent();
|
||||
}
|
||||
if (isMetricCard) {
|
||||
return (
|
||||
@@ -124,12 +141,7 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (
|
||||
categoryField.length > 1 ||
|
||||
queryMode === 'ENTITY_DETAIL' ||
|
||||
queryMode === 'ENTITY_DIMENSION' ||
|
||||
(categoryField.length === 1 && metricFields.length === 0)
|
||||
) {
|
||||
if (isTable) {
|
||||
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
|
||||
}
|
||||
if (dateField && metricFields.length > 0) {
|
||||
@@ -157,33 +169,22 @@ const ChatMsg: React.FC<Props> = ({ question, data, chartIndex, isMobileMode, tr
|
||||
return <Table data={{ ...data, queryColumns: columns, queryResults: dataSource }} />;
|
||||
};
|
||||
|
||||
let width = '100%';
|
||||
if (isText) {
|
||||
width = 'fit-content';
|
||||
} else if (isMetricCard) {
|
||||
width = '370px';
|
||||
} else if (categoryField.length > 1 && !isMobile && !isMobileMode) {
|
||||
if (columns.length === 1) {
|
||||
width = '600px';
|
||||
} else if (columns.length === 2) {
|
||||
width = '1000px';
|
||||
}
|
||||
}
|
||||
// let width = '100%';
|
||||
// if (isText) {
|
||||
// width = 'fit-content';
|
||||
// } else if (isMetricCard) {
|
||||
// width = isDslMetricCard ? '290px' : '370px';
|
||||
// } else if (categoryField.length > 1 && !isMobile && !isMobileMode) {
|
||||
// if (columns.length === 1) {
|
||||
// width = '600px';
|
||||
// } else if (columns.length === 2) {
|
||||
// width = '1000px';
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<Message
|
||||
position="left"
|
||||
chatContext={chatContext}
|
||||
entityInfo={entityInfo}
|
||||
title={question}
|
||||
isMobileMode={isMobileMode}
|
||||
width={width}
|
||||
maxWidth={isText && !isMobile ? '80%' : undefined}
|
||||
queryMode={queryMode}
|
||||
>
|
||||
{getMsgContent()}
|
||||
</Message>
|
||||
);
|
||||
const chartMsgClass = classNames({ [prefixCls]: !isTable });
|
||||
|
||||
return <div className={chartMsgClass}>{getMsgContent()}</div>;
|
||||
};
|
||||
|
||||
export default ChatMsg;
|
||||
|
||||
10
webapp/packages/chat-sdk/src/components/ChatMsg/style.less
Normal file
10
webapp/packages/chat-sdk/src/components/ChatMsg/style.less
Normal file
@@ -0,0 +1,10 @@
|
||||
@import '../../styles/index.less';
|
||||
|
||||
@chat-msg-prefix-cls: ~'@{supersonic-chat-prefix}-chat-msg';
|
||||
|
||||
.@{chat-msg-prefix-cls} {
|
||||
padding: 6px 14px 12px;
|
||||
border: 1px solid var(--border-color-base);
|
||||
border-radius: 4px;
|
||||
background: #f5f8fb;
|
||||
}
|
||||
Reference in New Issue
Block a user