feat: 增加数据导出中心

This commit is contained in:
taojinlong 2024-05-24 17:37:49 +08:00
commit 3983b592bb
89 changed files with 1996 additions and 453 deletions

View File

@ -557,17 +557,11 @@ public class ChartDataManage {
} else if (StringUtils.containsIgnoreCase(view.getType(), "quadrant")) {
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields), crossDs, dsMap);
yAxis.addAll(extBubble);
if(ObjectUtils.isNotEmpty(view.getExtTooltip())){
yAxis.addAll(new ArrayList<>(view.getExtTooltip()));
}
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields), crossDs, dsMap);
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
if (containDetailField(view) && ObjectUtils.isNotEmpty(viewFields)) {
detailFieldList.addAll(xAxis);
detailFieldList.addAll(viewFields);
Dimension2SQLObj.dimension2sqlObj(sqlMeta, detailFieldList, transFields(allFields), crossDs, dsMap);
String originSql = SQLProvider.createQuerySQL(sqlMeta, false, needOrder, view);
String limit = ((pageInfo.getGoPage() != null && pageInfo.getPageSize() != null) ? " LIMIT " + pageInfo.getPageSize() + " OFFSET " + (pageInfo.getGoPage() - 1) * pageInfo.getPageSize() : "");
detailFieldSql = originSql + limit;
}
} else if (StringUtils.equalsIgnoreCase("bar-range", view.getType())) {
sqlMeta.setChartType(view.getType());
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields), crossDs, dsMap);
@ -791,7 +785,7 @@ public class ChartDataManage {
} else if (StringUtils.containsIgnoreCase(view.getType(), "label")) {
mapChart = ChartDataBuild.transLabelChartData(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "quadrant")) {
mapChart = ChartDataBuild.transQuadrantDataAntV(xAxis, yAxis, view, data, extBubble, isDrill);
mapChart = ChartDataBuild.transMixChartDataAntV(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.equalsIgnoreCase(view.getType(), "bar-range")) {
mapChart = ChartDataBuild.transBarRangeDataAntV(skipBarRange, barRangeDate, xAxisBase, xAxis, yAxis, view, data, isDrill);
} else {

View File

@ -532,6 +532,13 @@ public class DatasetDataManage {
boolean crossDs = false;
Map<Long, DatasourceSchemaDTO> dsMap = null;
if (ObjectUtils.isNotEmpty(request.getSortId())) {
// 如果排序字段和查询字段显示字段不一致则加入到查询列表中
if (!request.getSortId().equals(request.getQueryId()) && !request.getSortId().equals(request.getDisplayId())) {
ids.add(request.getSortId());
}
}
for (Long id : ids) {
DatasetTableFieldDTO field = datasetTableFieldManage.selectById(id);
if (field == null) {

View File

@ -71,6 +71,9 @@ public class TemplateCenterManage {
public String marketGet(String url, String accessKey) {
HttpClientConfig config = new HttpClientConfig();
config.addHeader("API-Authorization", accessKey);
config.setConnectTimeout(5000);
config.setSocketTimeout(10000);
config.setConnectionRequestTimeout(5000);
return HttpClientUtil.get(url, config);
}

View File

@ -109,6 +109,7 @@ public class CoreVisualizationManage {
@XpackInteract(value = "visualizationResourceTree", before = false)
public Long innerSave(DataVisualizationInfo visualizationInfo) {
visualizationInfo.setVersion(3);
return preInnerSave(visualizationInfo);
}
@ -123,7 +124,6 @@ public class CoreVisualizationManage {
visualizationInfo.setCreateTime(System.currentTimeMillis());
visualizationInfo.setUpdateTime(System.currentTimeMillis());
visualizationInfo.setOrgId(AuthUtils.getUser().getDefaultOid());
visualizationInfo.setVersion(3);
mapper.insert(visualizationInfo);
coreOptRecentManage.saveOpt(visualizationInfo.getId(), OptConstants.OPT_RESOURCE_TYPE.VISUALIZATION, OptConstants.OPT_TYPE.NEW);
return visualizationInfo.getId();

View File

@ -15,7 +15,7 @@ INSERT INTO `core_sys_startup_job`
VALUES ('chartFilterMerge', 'chartFilterMerge', 'ready');
COMMIT;
TRUNCATE TABLE `xpack_setting_authentication`;
ALTER TABLE `xpack_setting_authentication`
ADD COLUMN `plugin_json` longtext NULL COMMENT '插件配置' AFTER `relational_ids`;
ALTER TABLE `xpack_setting_authentication`
@ -41,3 +41,14 @@ CREATE TABLE `core_export_task`
`params` longtext NOT NULL COMMENT '过滤参数',
PRIMARY KEY (`id`)
) COMMENT='导出任务表';
DROP TABLE IF EXISTS `xpack_platform_token`;
CREATE TABLE `xpack_platform_token`
(
`id` int NOT NULL,
`token` varchar(255) NOT NULL,
`create_time` bigint NOT NULL,
`exp_time` bigint NOT NULL,
PRIMARY KEY (`id`)
);

View File

@ -25,7 +25,7 @@
"axios": "^1.3.3",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.9",
"element-plus-secondary": "^0.5.8",
"element-plus-secondary": "^0.5.9",
"element-resize-detector": "^1.2.4",
"file-saver": "^2.0.5",
"flv.js": "^1.6.2",

View File

@ -0,0 +1,11 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M41 2.00391V53.0016V54.0016H38V2.00391H41Z" fill="#434343"/>
<path d="M4.5 50C4.22386 50 4 49.7761 4 49.5L4 42.5C4 42.2239 4.22386 42 4.5 42H35.5C35.7761 42 36 42.2239 36 42.5V49.5C36 49.7761 35.7761 50 35.5 50H4.5Z" fill="#00D6B9"/>
<path d="M12.5 38C12.2239 38 12 37.7761 12 37.5V30.5C12 30.2239 12.2239 30 12.5 30H35.5C35.7761 30 36 30.2239 36 30.5V37.5C36 37.7761 35.7761 38 35.5 38H12.5Z" fill="#00D6B9"/>
<path d="M43.5 14C43.2239 14 43 13.7761 43 13.5V6.5C43 6.22386 43.2239 6 43.5 6L71.5 6C71.7761 6 72 6.22386 72 6.5V13.5C72 13.7761 71.7761 14 71.5 14L43.5 14Z" fill="#3370FF"/>
<path d="M43.5 26C43.2239 26 43 25.7761 43 25.5V18.5C43 18.2239 43.2239 18 43.5 18H62.5C62.7761 18 63 18.2239 63 18.5V25.5C63 25.7761 62.7761 26 62.5 26H43.5Z" fill="#3370FF"/>
<path d="M43.5 38C43.2239 38 43 37.7761 43 37.5V30.5C43 30.2239 43.2239 30 43.5 30H57.5C57.7761 30 58 30.2239 58 30.5V37.5C58 37.7761 57.7761 38 57.5 38H43.5Z" fill="#3370FF"/>
<path d="M43.5 50C43.2239 50 43 49.7761 43 49.5V42.5C43 42.2239 43.2239 42 43.5 42H75.5C75.7761 42 76 42.2239 76 42.5V49.5C76 49.7761 75.7761 50 75.5 50H43.5Z" fill="#3370FF"/>
<path d="M16.5 26C16.2239 26 16 25.7761 16 25.5V18.5C16 18.2239 16.2239 18 16.5 18H35.5C35.7761 18 36 18.2239 36 18.5V25.5C36 25.7761 35.7761 26 35.5 26H16.5Z" fill="#00D6B9"/>
<path d="M8.5 14C8.22386 14 8 13.7761 8 13.5V6.5C8 6.22386 8.22386 6 8.5 6L35.5 6C35.7761 6 36 6.22386 36 6.5V13.5C36 13.7761 35.7761 14 35.5 14L8.5 14Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,11 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M41 2.00391V53.0016V54.0016H38V2.00391H41Z" fill="#DEE0E3"/>
<path d="M4.5 50C4.22386 50 4 49.7761 4 49.5L4 42.5C4 42.2239 4.22386 42 4.5 42H35.5C35.7761 42 36 42.2239 36 42.5V49.5C36 49.7761 35.7761 50 35.5 50H4.5Z" fill="#00D6B9"/>
<path d="M12.5 38C12.2239 38 12 37.7761 12 37.5V30.5C12 30.2239 12.2239 30 12.5 30H35.5C35.7761 30 36 30.2239 36 30.5V37.5C36 37.7761 35.7761 38 35.5 38H12.5Z" fill="#00D6B9"/>
<path d="M43.5 14C43.2239 14 43 13.7761 43 13.5V6.5C43 6.22386 43.2239 6 43.5 6L71.5 6C71.7761 6 72 6.22386 72 6.5V13.5C72 13.7761 71.7761 14 71.5 14L43.5 14Z" fill="#3370FF"/>
<path d="M43.5 26C43.2239 26 43 25.7761 43 25.5V18.5C43 18.2239 43.2239 18 43.5 18H62.5C62.7761 18 63 18.2239 63 18.5V25.5C63 25.7761 62.7761 26 62.5 26H43.5Z" fill="#3370FF"/>
<path d="M43.5 38C43.2239 38 43 37.7761 43 37.5V30.5C43 30.2239 43.2239 30 43.5 30H57.5C57.7761 30 58 30.2239 58 30.5V37.5C58 37.7761 57.7761 38 57.5 38H43.5Z" fill="#3370FF"/>
<path d="M43.5 50C43.2239 50 43 49.7761 43 49.5V42.5C43 42.2239 43.2239 42 43.5 42H75.5C75.7761 42 76 42.2239 76 42.5V49.5C76 49.7761 75.7761 50 75.5 50H43.5Z" fill="#3370FF"/>
<path d="M16.5 26C16.2239 26 16 25.7761 16 25.5V18.5C16 18.2239 16.2239 18 16.5 18H35.5C35.7761 18 36 18.2239 36 18.5V25.5C36 25.7761 35.7761 26 35.5 26H16.5Z" fill="#00D6B9"/>
<path d="M8.5 14C8.22386 14 8 13.7761 8 13.5V6.5C8 6.22386 8.22386 6 8.5 6L35.5 6C35.7761 6 36 6.22386 36 6.5V13.5C36 13.7761 35.7761 14 35.5 14L8.5 14Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1716540076970" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7068" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M912 480c-17.7 0-32 14.3-32 32 0 25-2.5 50-7.5 74.2-4.8 23.6-12 46.8-21.4 69-9.2 21.8-20.6 42.8-33.9 62.5-13.2 19.5-28.3 37.8-45 54.5s-35 31.8-54.5 45c-19.7 13.3-40.7 24.7-62.5 33.9-22.2 9.4-45.4 16.6-69 21.4-48.5 9.9-99.9 9.9-148.4 0-23.6-4.8-46.8-12-69-21.4-21.8-9.2-42.8-20.6-62.5-33.9-19.5-13.2-37.8-28.3-54.5-45s-31.8-35-45-54.5c-13.3-19.7-24.7-40.7-33.9-62.5-9.4-22.2-16.6-45.4-21.4-69-5-24.2-7.5-49.2-7.5-74.2s2.5-50 7.5-74.2c4.8-23.6 12-46.8 21.4-69 9.2-21.8 20.6-42.8 33.9-62.5 13.2-19.5 28.3-37.8 45-54.5s35-31.8 54.5-45c19.7-13.3 40.7-24.7 62.5-33.9 22.2-9.4 45.4-16.6 69-21.4 48.5-9.9 99.9-9.9 148.4 0 23.6 4.8 46.8 12 69 21.4 21.8 9.2 42.8 20.6 62.5 33.9 19.5 13.2 37.8 28.3 54.5 45 1.4 1.4 2.8 2.8 4.1 4.2H688c-17.7 0-32 14.3-32 32s14.3 32 32 32h160c17.7 0 32-14.3 32-32V128c0-17.7-14.3-32-32-32s-32 14.3-32 32v77.1c-19.2-19-40.1-36.2-62.4-51.3-23.1-15.6-47.8-29-73.4-39.8-26.1-11-53.4-19.5-81.1-25.2-56.9-11.6-117.1-11.6-174.1 0-27.8 5.7-55.1 14.2-81.1 25.2-25.6 10.8-50.3 24.2-73.4 39.8-22.9 15.4-44.4 33.2-63.9 52.7s-37.3 41-52.7 63.9c-15.6 23.1-29 47.8-39.8 73.4-11 26.1-19.5 53.4-25.2 81.1C83 453.4 80 482.7 80 512s3 58.6 8.8 87c5.7 27.8 14.2 55 25.2 81.1 10.8 25.6 24.2 50.3 39.8 73.4 15.4 22.9 33.2 44.4 52.7 63.9s41 37.3 63.9 52.7c23.1 15.6 47.8 29 73.4 39.8 26.1 11 53.4 19.5 81.1 25.2 28.5 5.8 57.7 8.8 87 8.8s58.6-3 87-8.8c27.8-5.7 55-14.2 81.1-25.2 25.6-10.8 50.3-24.2 73.4-39.8 22.9-15.5 44.4-33.2 63.9-52.7s37.3-41 52.7-63.9c15.6-23.1 29-47.8 39.8-73.4 11-26.1 19.5-53.4 25.2-81.1 5.8-28.5 8.8-57.7 8.8-87 0.2-17.7-14.1-32-31.8-32z" fill="#1875F0" p-id="7069"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1716529317390" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1706" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M810.666667 128H213.333333c-46.933333 0-85.333333 38.4-85.333333 85.333333v597.333334c0 46.933333 38.4 85.333333 85.333333 85.333333h597.333334c46.933333 0 85.333333-38.4 85.333333-85.333333V213.333333c0-46.933333-38.4-85.333333-85.333333-85.333333z m0 640c0 23.466667-19.2 42.666667-42.666667 42.666667H256c-23.466667 0-42.666667-19.2-42.666667-42.666667V256c0-23.466667 19.2-42.666667 42.666667-42.666667h512c23.466667 0 42.666667 19.2 42.666667 42.666667v512z" fill="#297AFF" p-id="1707"></path><path d="M725.333333 317.866667c0-12.8-8.533333-21.333333-21.333333-21.333334H320c-12.8 0-21.333333 8.533333-21.333333 21.333334v42.666666c0 12.8 8.533333 21.333333 21.333333 21.333334h149.333333v234.666666c0 12.8 8.533333 21.333333 21.333334 21.333334h42.666666c12.8 0 21.333333-8.533333 21.333334-21.333334v-234.666666h149.333333c12.8 0 21.333333-8.533333 21.333333-21.333334v-42.666666zM714.666667 682.666667H358.4v-32c0-2.133333-4.266667-4.266667-6.4-2.133334l-40.533333 40.533334c-8.533333 8.533333-8.533333 21.333333 0 29.866666l40.533333 40.533334c2.133333 2.133333 6.4 0 6.4-2.133334V725.333333h356.266667c6.4 0 10.666667-4.266667 10.666666-10.666666v-21.333334c0-6.4-4.266667-10.666667-10.666666-10.666666z" fill="#297AFF" p-id="1708"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1716538241941" class="icon" viewBox="0 0 1166 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4799" xmlns:xlink="http://www.w3.org/1999/xlink" width="227.734375" height="200"><path d="M1016.603881 149.959255A508.396905 508.396905 0 0 0 661.880392 0H47.123419a42.206376 42.206376 0 0 0-42.206376 42.206376 42.206376 42.206376 0 0 0 42.206376 42.206376h607.45527c235.764818 0 427.571694 191.806877 427.571695 427.571695S890.343507 939.577245 654.578689 939.577245s-427.571694-191.82798-427.571694-427.592798h105.51594a42.206376 42.206376 0 0 0 42.206376-42.206376 42.206376 42.206376 0 0 0-42.206376-42.206377H42.206376a42.206376 42.206376 0 0 0-42.206376 42.206377 42.206376 42.206376 0 0 0 42.206376 42.206376h100.387866a511.984447 511.984447 0 0 0 874.009639 362.046295 512.00555 512.00555 0 0 0 0-724.071487z" p-id="4800"></path><path d="M411.237827 298.166945a42.206376 42.206376 0 0 0 42.206376-42.206376 42.206376 42.206376 0 0 0-42.206376-42.206377H120.857958a42.206376 42.206376 0 0 0-42.206376 42.206377 42.206376 42.206376 0 0 0 42.206376 42.206376zM635.205962 232.388308a42.311892 42.311892 0 0 0-42.206376 42.206376v290.379868a42.311892 42.311892 0 0 0 42.206376 42.206377h228.737456a42.206376 42.206376 0 0 0 42.206377-42.206377 42.206376 42.206376 0 0 0-42.206377-42.206376h-186.657699V274.573581A42.206376 42.206376 0 0 0 635.205962 232.388308z" p-id="4801"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 739 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 742 KiB

View File

@ -0,0 +1,11 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2.00391V53.0016V54.0016H4V2.00391H7Z" fill="#434343"/>
<path d="M9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L71.5 6C71.7761 6 72 6.22386 72 6.5V13.5C72 13.7761 71.7761 14 71.5 14L9.5 14Z" fill="#434343"/>
<path d="M9.5 26C9.22386 26 9 25.7761 9 25.5V18.5C9 18.2239 9.22386 18 9.5 18H71.5C71.7761 18 72 18.2239 72 18.5V25.5C72 25.7761 71.7761 26 71.5 26L9.5 26Z" fill="#434343"/>
<path d="M9.5 38C9.22386 38 9 37.7761 9 37.5V30.5C9 30.2239 9.22386 30 9.5 30H71.5C71.7761 30 72 30.2239 72 30.5V37.5C72 37.7761 71.7761 38 71.5 38H9.5Z" fill="#434343"/>
<path d="M9.5 50C9.22386 50 9 49.7761 9 49.5V42.5C9 42.2239 9.22386 42 9.5 42H71.5C71.7761 42 72 42.2239 72 42.5V49.5C72 49.7761 71.7761 50 71.5 50H9.5Z" fill="#434343"/>
<path d="M9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L59.5 6C59.7761 6 60 6.22386 60 6.5V13.5C60 13.7761 59.7761 14 59.5 14L9.5 14Z" fill="#3370FF"/>
<path d="M9.5 26C9.22386 26 9 25.7761 9 25.5V18.5C9 18.2239 9.22386 18 9.5 18H46.5C46.7761 18 47 18.2239 47 18.5V25.5C47 25.7761 46.7761 26 46.5 26H9.5Z" fill="#3370FF"/>
<path d="M9.5 38C9.22386 38 9 37.7761 9 37.5V30.5C9 30.2239 9.22386 30 9.5 30H37.5C37.7761 30 38 30.2239 38 30.5V37.5C38 37.7761 37.7761 38 37.5 38H9.5Z" fill="#3370FF"/>
<path d="M9.5 50C9.22386 50 9 49.7761 9 49.5V42.5C9 42.2239 9.22386 42 9.5 42H21.5C21.7761 42 22 42.2239 22 42.5V49.5C22 49.7761 21.7761 50 21.5 50H9.5Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,11 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7 2.00391V53.0016V54.0016H4V2.00391H7Z" fill="#DEE0E3"/>
<path d="M9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L71.5 6C71.7761 6 72 6.22386 72 6.5V13.5C72 13.7761 71.7761 14 71.5 14L9.5 14Z" fill="#DEE0E3"/>
<path d="M9.5 26C9.22386 26 9 25.7761 9 25.5V18.5C9 18.2239 9.22386 18 9.5 18H71.5C71.7761 18 72 18.2239 72 18.5V25.5C72 25.7761 71.7761 26 71.5 26L9.5 26Z" fill="#DEE0E3"/>
<path d="M9.5 38C9.22386 38 9 37.7761 9 37.5V30.5C9 30.2239 9.22386 30 9.5 30H71.5C71.7761 30 72 30.2239 72 30.5V37.5C72 37.7761 71.7761 38 71.5 38H9.5Z" fill="#DEE0E3"/>
<path d="M9.5 50C9.22386 50 9 49.7761 9 49.5V42.5C9 42.2239 9.22386 42 9.5 42H71.5C71.7761 42 72 42.2239 72 42.5V49.5C72 49.7761 71.7761 50 71.5 50H9.5Z" fill="#DEE0E3"/>
<path d="M9.5 14C9.22386 14 9 13.7761 9 13.5V6.5C9 6.22386 9.22386 6 9.5 6L59.5 6C59.7761 6 60 6.22386 60 6.5V13.5C60 13.7761 59.7761 14 59.5 14L9.5 14Z" fill="#3370FF"/>
<path d="M9.5 26C9.22386 26 9 25.7761 9 25.5V18.5C9 18.2239 9.22386 18 9.5 18H46.5C46.7761 18 47 18.2239 47 18.5V25.5C47 25.7761 46.7761 26 46.5 26H9.5Z" fill="#3370FF"/>
<path d="M9.5 38C9.22386 38 9 37.7761 9 37.5V30.5C9 30.2239 9.22386 30 9.5 30H37.5C37.7761 30 38 30.2239 38 30.5V37.5C38 37.7761 37.7761 38 37.5 38H9.5Z" fill="#3370FF"/>
<path d="M9.5 50C9.22386 50 9 49.7761 9 49.5V42.5C9 42.2239 9.22386 42 9.5 42H21.5C21.7761 42 22 42.2239 22 42.5V49.5C22 49.7761 21.7761 50 21.5 50H9.5Z" fill="#3370FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1716529317390" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1706" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M810.666667 128H213.333333c-46.933333 0-85.333333 38.4-85.333333 85.333333v597.333334c0 46.933333 38.4 85.333333 85.333333 85.333333h597.333334c46.933333 0 85.333333-38.4 85.333333-85.333333V213.333333c0-46.933333-38.4-85.333333-85.333333-85.333333z m0 640c0 23.466667-19.2 42.666667-42.666667 42.666667H256c-23.466667 0-42.666667-19.2-42.666667-42.666667V256c0-23.466667 19.2-42.666667 42.666667-42.666667h512c23.466667 0 42.666667 19.2 42.666667 42.666667v512z" p-id="1707"></path><path d="M725.333333 317.866667c0-12.8-8.533333-21.333333-21.333333-21.333334H320c-12.8 0-21.333333 8.533333-21.333333 21.333334v42.666666c0 12.8 8.533333 21.333333 21.333333 21.333334h149.333333v234.666666c0 12.8 8.533333 21.333333 21.333334 21.333334h42.666666c12.8 0 21.333333-8.533333 21.333334-21.333334v-234.666666h149.333333c12.8 0 21.333333-8.533333 21.333333-21.333334v-42.666666zM714.666667 682.666667H358.4v-32c0-2.133333-4.266667-4.266667-6.4-2.133334l-40.533333 40.533334c-8.533333 8.533333-8.533333 21.333333 0 29.866666l40.533333 40.533334c2.133333 2.133333 6.4 0 6.4-2.133334V725.333333h356.266667c6.4 0 10.666667-4.266667 10.666666-10.666666v-21.333334c0-6.4-4.266667-10.666667-10.666666-10.666666z" p-id="1708"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -219,7 +219,7 @@ const openDataBoardSetting = () => {
}
const openMobileSetting = () => {
if (!dvInfo.value.id) {
if (!dvInfo.value.id || dvInfo.value.dataState === 'prepare') {
ElMessage.warning('请先保存当前页面')
return
}

View File

@ -91,7 +91,7 @@
<el-tooltip class="item" :effect="toolTip" placement="bottom">
<template #content>
<div>
{{ t('visualization.panel_view_result_tips') }}
{{ t('visualization.panel_view_result_tips', [resourceType]) }}
</div>
</template>
<el-icon class="hint-icon" :class="{ 'hint-icon--dark': themes === 'dark' }">

View File

@ -227,7 +227,7 @@ eventBus.on('clearCanvas', clearCanvas)
>
<query-group :dv-model="dvModel"></query-group>
</component-group>
<component-group is-label :base-width="115" icon-name="dv-text" title="文本">
<component-group is-label :base-width="215" icon-name="dv-text" title="文本">
<text-group></text-group>
</component-group>
<component-group
@ -239,7 +239,7 @@ eventBus.on('clearCanvas', clearCanvas)
>
<media-group></media-group>
</component-group>
<component-group is-label :base-width="115" icon-name="dv-more-com" title="更多">
<component-group is-label :base-width="215" icon-name="dv-more-com" title="更多">
<more-com-group></more-com-group>
</component-group>
<component-group is-label :base-width="410" icon-name="dv-material" title="素材">

View File

@ -1450,7 +1450,7 @@ defineExpose({
></canvas-opt-bar>
<!-- 网格线 -->
<drag-shadow
v-if="infoBox && infoBox.moveItem"
v-if="infoBox && infoBox.moveItem && editMode !== 'preview'"
:base-height="baseHeight"
:base-width="baseWidth"
:cur-gap="curGap"

View File

@ -294,7 +294,7 @@ const active = computed(() => {
})
const boardMoveActive = computed(() => {
const CHARTS = ['map', 'bubble-map', 'table-info', 'table-normal', 'table-pivot']
const CHARTS = ['flow-map', 'map', 'bubble-map', 'table-info', 'table-normal', 'table-pivot']
return CHARTS.includes(element.value.innerType)
})

View File

@ -8,7 +8,7 @@
trigger="click"
>
<div class="export-button">
<el-select v-model="pixel" class="pixel-select" size="small">
<el-select v-if="optType === 'enlarge'" v-model="pixel" class="pixel-select" size="small">
<el-option-group v-for="group in pixelOptions" :key="group.label" :label="group.label">
<el-option
v-for="item in group.options"
@ -95,6 +95,7 @@ const optType = ref(null)
const chartComponentDetails = ref(null)
const { dvInfo } = storeToRefs(dvMainStore)
const exportLoading = ref(false)
const sourceViewType = ref()
const DETAIL_TABLE_ATTR: DeepPartial<ChartObj> = {
render: 'antv',
type: 'table-info',
@ -165,6 +166,7 @@ const pixelOptions = [
}
]
const dialogInit = (canvasStyle, view, item, opt) => {
sourceViewType.value = view.type
optType.value = opt
dialogShow.value = true
viewInfo.value = deepCopy(view) as DeepPartial<ChartObj>
@ -193,7 +195,12 @@ const downloadViewImage = () => {
const downloadViewDetails = () => {
const viewDataInfo = dvMainStore.getViewDataDetails(viewInfo.value.id)
const chartExtRequest = dvMainStore.getLastViewRequestInfo(viewInfo.value.id)
const chart = { ...viewInfo.value, chartExtRequest, data: viewDataInfo }
const chart = {
...viewInfo.value,
chartExtRequest,
data: viewDataInfo,
type: sourceViewType.value
}
exportLoading.value = true
exportExcelDownload(chart, () => {
console.log('aa')

View File

@ -84,7 +84,7 @@
@change="changeStyle"
>
<template #prefix>
<el-icon>
<el-icon :class="{ 'dark-icon': themes === 'dark' }">
<Icon :name="styleOptionKey.icon" />
</el-icon>
</template>
@ -266,12 +266,28 @@ const styleMounted = ref({
opacity: 1,
fontSize: 22,
activeFontSize: 22,
letterSpacing: 0,
scrollSpeed: 0,
fontWeight: 'normal',
fontStyle: 'normal',
textAlign: 'center',
color: '#000000'
})
const scrollSpeedList = [
{ name: '停止', value: 0 },
{ name: '1', value: 20 },
{ name: '2', value: 18 },
{ name: '3', value: 16 },
{ name: '4', value: 14 },
{ name: '5', value: 12 },
{ name: '6', value: 10 },
{ name: '7', value: 8 },
{ name: '8', value: 6 },
{ name: '9', value: 4 },
{ name: '10', value: 2 }
]
const opacitySizeList = [
{ name: '0.1', value: 0.1 },
{ name: '0.2', value: 0.2 },
@ -310,6 +326,17 @@ const styleColorKeyArray = [
{ value: 'backgroundColor', label: '背景色', width: 90, icon: 'dv-style-backgroundColor' }
]
const letterSpacingList = computed(() => {
const arr = []
for (let i = 0; i <= 60; i = i + 1) {
arr.push({
name: i + '',
value: i
})
}
return arr
})
const fontSizeList = computed(() => {
const arr = []
for (let i = 10; i <= 60; i = i + 1) {
@ -351,6 +378,13 @@ const borderStyleList = [
//
const styleOptionMountedKeyArray = [
{
value: 'letterSpacing',
label: '字间距',
customOption: letterSpacingList.value,
width: '90px',
icon: 'dv-style-letterSpacing'
},
{
value: 'fontSize',
label: '字体大小',
@ -369,6 +403,13 @@ const styleOptionMountedKeyArray = [
//
const styleOptionKeyArray = [
{
value: 'scrollSpeed',
label: '滚动速度',
customOption: scrollSpeedList,
width: '90px',
icon: 'dv-style-scroll-speed'
},
{
value: 'opacity',
label: '不透明度',

View File

@ -28,8 +28,8 @@ const props = defineProps({
})
const { dvModel } = toRefs(props)
const newComponent = () => {
eventBus.emit('handleNew', { componentName: 'DeTimeClock', innerType: 'DeTimeClock' })
const newComponent = params => {
eventBus.emit('handleNew', { componentName: params, innerType: params })
}
const handleDragStart = e => {
@ -42,17 +42,20 @@ const handleDragEnd = e => {
</script>
<template>
<div
class="group"
@dragstart="handleDragStart"
@dragend="handleDragEnd"
v-on:click="newComponent"
>
<div class="group" @dragstart="handleDragStart" @dragend="handleDragEnd">
<drag-component
:themes="themes"
name="YYYY-MM-DD 08:00:00"
label="日期时间"
drag-info="DeTimeClock&DeTimeClock"
v-on:click="newComponent('DeTimeClock')"
></drag-component>
<drag-component
:themes="themes"
icon="db-more-web"
label="网页"
drag-info="DeFrame&DeFrame"
v-on:click="newComponent('DeFrame')"
></drag-component>
</div>
</template>
@ -60,5 +63,6 @@ const handleDragEnd = e => {
<style lang="less" scoped>
.group {
padding: 12px 8px;
display: inline-flex;
}
</style>

View File

@ -11,7 +11,7 @@ const props = defineProps({
},
dvModel: {
type: String,
default: 'dv'
default: 'dataV'
},
element: {
type: Object,
@ -37,23 +37,27 @@ const handleDragEnd = e => {
commonHandleDragEnd(e, dvModel.value)
}
const newComponent = () => {
eventBus.emit('handleNew', { componentName: 'UserView', innerType: 'rich-text' })
const newComponent = (componentName, innerType) => {
eventBus.emit('handleNew', { componentName: componentName, innerType: innerType })
}
</script>
<template>
<div
class="group"
@dragstart="handleDragStart"
@dragend="handleDragEnd"
v-on:click="newComponent"
>
<div class="group" @dragstart="handleDragStart" @dragend="handleDragEnd">
<drag-component
:themes="themes"
icon="dv-richText"
label="富文本"
drag-info="UserView&rich-text"
v-on:click="newComponent('UserView', 'rich-text')"
></drag-component>
<drag-component
v-if="dvModel === 'dataV'"
:themes="themes"
icon="dv-scroll-text"
label="跑马灯"
drag-info="ScrollText&ScrollText"
v-on:click="newComponent('ScrollText', 'ScrollText')"
></drag-component>
</div>
</template>
@ -61,10 +65,6 @@ const newComponent = () => {
<style lang="less" scoped>
.group {
padding: 12px 8px;
}
.custom_img {
width: 100px;
height: 70px;
cursor: pointer;
display: inline-flex;
}
</style>

View File

@ -253,8 +253,8 @@ const list = [
},
{
component: 'DeVideo',
name: '媒体',
label: '媒体',
name: '视频',
label: '视频',
innerType: 'DeVideo',
editing: false,
canvasActive: false,
@ -272,8 +272,8 @@ const list = [
},
{
component: 'DeStreamMedia',
name: '媒体',
label: '媒体',
name: '媒体',
label: '媒体',
innerType: 'DeStreamMedia',
editing: false,
canvasActive: false,
@ -386,7 +386,7 @@ const list = [
},
{
component: 'CanvasBoard',
name: '图形',
name: '边框',
label: '边框',
propValue: '',
icon: 'other_material_board',
@ -477,6 +477,29 @@ const list = [
headFontColor: '#000000',
headFontActiveColor: '#000000'
}
},
{
component: 'ScrollText',
name: '跑马灯',
label: '跑马灯',
propValue: '双击编辑文字',
innerType: 'ScrollText',
icon: 'scroll-text',
x: 1,
y: 1,
sizeX: 36,
sizeY: 14,
style: {
width: 400,
height: 80,
fontSize: 14,
fontWeight: 400,
letterSpacing: 0,
color: '',
padding: 4,
verticalAlign: 'middle',
scrollSpeed: 0
}
}
]

View File

@ -19,7 +19,7 @@
</el-tooltip>
</span>
</template>
<el-input v-model="state.linkInfoTemp.src" @blur="onBlur" />
<el-input :effect="themes" v-model="state.linkInfoTemp.src" @blur="onBlur" />
</el-form-item>
</el-form>
</el-row>

View File

@ -93,9 +93,10 @@ const result = computed(() => {
const indicatorColor = ref(DEFAULT_INDICATOR_STYLE.color)
const thresholdColor = computed(() => {
let color = indicatorColor.value
let color: string = indicatorColor.value
let backgroundColor: string = DEFAULT_INDICATOR_STYLE.backgroundColor
if (result.value === '-') {
return color
return { color, backgroundColor }
}
const value = result.value
if (
@ -107,42 +108,47 @@ const thresholdColor = computed(() => {
for (let i = 0; i < senior.threshold.labelThreshold.length; i++) {
let flag = false
const t = senior.threshold.labelThreshold[i]
const tv = parseFloat(t.value)
const tv = t.value
if (t.term === 'eq') {
if (value === tv) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
} else if (t.term === 'not_eq') {
if (value !== tv) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
} else if (t.term === 'lt') {
if (value < tv) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
} else if (t.term === 'gt') {
if (value > tv) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
} else if (t.term === 'le') {
if (value <= tv) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
} else if (t.term === 'ge') {
if (value >= tv) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
} else if (t.term === 'between') {
const min = parseFloat(t.min)
const max = parseFloat(t.max)
if (min <= value && value <= max) {
if (t.min <= value && value <= t.max) {
color = t.color
backgroundColor = t.backgroundColor
flag = true
}
}
@ -151,7 +157,7 @@ const thresholdColor = computed(() => {
}
}
}
return color
return { color, backgroundColor }
})
const formattedResult = computed(() => {
@ -175,11 +181,12 @@ const contentStyle = ref<CSSProperties>({
'flex-direction': 'column',
'align-items': 'center',
'justify-content': 'center',
height: '100%'
height: '100%',
'background-color': thresholdColor.value.backgroundColor
})
const indicatorClass = ref<CSSProperties>({
color: thresholdColor.value,
color: thresholdColor.value.color,
'font-size': DEFAULT_INDICATOR_STYLE.fontSize + 'px',
'font-family': defaultTo(
CHART_FONT_FAMILY_MAP[DEFAULT_INDICATOR_STYLE.fontFamily],
@ -280,7 +287,7 @@ const renderChart = async view => {
}
indicatorClass.value = {
color: thresholdColor.value,
color: thresholdColor.value.color,
'font-size': indicator.fontSize + 'px',
'font-family': defaultTo(
CHART_FONT_FAMILY_MAP[indicator.fontFamily],
@ -292,6 +299,7 @@ const renderChart = async view => {
'text-shadow': indicator.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
contentStyle.value['background-color'] = thresholdColor.value.backgroundColor
indicatorSuffixClass.value = {
color: suffixColor,

View File

@ -0,0 +1,56 @@
<script setup lang="ts">
import CommonAttr from '@/custom-component/common/CommonAttr.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { toRefs } from 'vue'
const dvMainStore = dvMainStoreWithOut()
const { curComponent } = storeToRefs(dvMainStore)
const props = defineProps({
themes: {
type: String,
default: 'dark'
}
})
const { themes } = toRefs(props)
</script>
<template>
<div class="attr-list de-collapse-style">
<CommonAttr :themes="themes" :element="curComponent"></CommonAttr>
</div>
</template>
<style lang="less" scoped>
.content {
width: 100%;
font-size: 12px;
padding: 10px;
}
.de-collapse-style {
:deep(.ed-collapse-item__header) {
height: 34px !important;
line-height: 34px !important;
padding: 0 0 0 6px !important;
font-size: 12px !important;
font-weight: 400 !important;
}
:deep(.ed-collapse-item__content) {
padding: 16px !important;
}
:deep(.ed-form-item) {
display: block;
margin-bottom: 8px;
}
:deep(.ed-form-item__label) {
justify-content: flex-start;
}
}
:deep(.ed-textarea__inner) {
color: #ffffff;
background-color: #000000;
}
</style>

View File

@ -0,0 +1,197 @@
<script lang="ts" setup>
import { keycodes } from '@/utils/DeShortcutKey.js'
import eventBus from '@/utils/eventBus'
import { computed, nextTick, onBeforeUnmount, ref } from 'vue'
import { toRefs } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
const canEdit = ref(false)
const ctrlKey = ref(17)
const isCtrlDown = ref(false)
const emit = defineEmits(['input'])
const text = ref(null)
const props = defineProps({
propValue: {
type: String,
required: true,
default: ''
},
element: {
type: Object,
default() {
return {
id: null,
propValue: ''
}
}
}
})
const { element } = toRefs(props)
const dvMainStore = dvMainStoreWithOut()
const { editMode, curComponent } = storeToRefs(dvMainStore)
const onComponentClick = () => {
if (curComponent.value.id !== element.value.id) {
canEdit.value = false
}
}
const handleInput = e => {
emit('input', element.value, e.target.innerHTML)
}
const handleKeydown = e => {
//
canEdit.value && e.stopPropagation()
if (e.keyCode == ctrlKey.value) {
isCtrlDown.value = true
} else if (isCtrlDown.value && canEdit.value && keycodes.includes(e.keyCode)) {
e.stopPropagation()
} else if (e.keyCode == 46) {
// deleteKey
e.stopPropagation()
}
}
const handleKeyup = e => {
//
canEdit.value && e.stopPropagation()
if (e.keyCode == ctrlKey.value) {
isCtrlDown.value = false
}
}
const handleMousedown = e => {
if (canEdit.value) {
e.stopPropagation()
}
}
const clearStyle = e => {
e.preventDefault()
const clp = e.clipboardData
const text = clp.getData('text/plain') || ''
if (text !== '') {
document.execCommand('insertText', false, text)
}
emit('input', element.value, e.target.innerHTML)
}
const handleBlur = e => {
element.value.propValue = e.target.innerHTML || '&nbsp;'
const html = e.target.innerHTML
if (html !== '') {
element.value.propValue = e.target.innerHTML
} else {
element.value.propValue = ''
nextTick(function () {
element.value.propValue = '&nbsp;'
})
}
canEdit.value = false
}
const setEdit = () => {
if (element.value['isLock']) return
canEdit.value = true
//
selectText(text.value)
}
const selectText = element => {
const selection = window.getSelection()
const range = document.createRange()
range.selectNodeContents(element)
selection.removeAllRanges()
selection.addRange(range)
}
onBeforeUnmount(() => {
eventBus.off('componentClick', onComponentClick)
})
const varStyle = computed(() => [{ '--scroll-speed': `${element.value.style.scrollSpeed}s` }])
const textStyle = computed(() => {
return {
verticalAlign: element.value['style'].verticalAlign
}
})
</script>
<template>
<div
v-if="editMode == 'edit'"
:style="varStyle"
class="v-text"
@keydown="handleKeydown"
@keyup="handleKeyup"
>
<div
ref="text"
:contenteditable="canEdit"
:class="{ 'can-edit': canEdit, 'marquee-txt': !canEdit }"
tabindex="0"
:style="textStyle"
@dblclick="setEdit"
@paste="clearStyle"
@mousedown="handleMousedown"
@blur="handleBlur"
@input="handleInput"
v-html="element['propValue']"
></div>
</div>
<div v-else class="v-text preview">
<div class="marquee-txt" :style="textStyle" v-html="element['propValue']"></div>
</div>
</template>
<style lang="less" scoped>
.v-text {
width: 100%;
height: 100%;
display: table;
div {
display: table-cell;
width: 100%;
height: 100%;
outline: none;
word-break: break-all;
padding: 4px;
white-space: nowrap;
}
.can-edit {
cursor: text;
height: 100%;
}
}
.preview {
user-select: none;
}
.marquee {
margin-left: 100px;
width: 300px;
white-space: nowrap;
overflow: hidden;
border: 1px solid #4c7cee;
}
.marquee-txt {
display: inline-block;
padding-left: 100%; /* 从右至左开始滚动 */
animation: marqueeAnimation var(--scroll-speed) linear infinite;
}
@keyframes marqueeAnimation {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(-100%, 0);
}
}
</style>

View File

@ -108,22 +108,11 @@ const isIndeterminate = ref(false)
const datasetTree = shallowRef([])
const fields = ref<DatasetDetail[]>()
const parameters = ref([])
const parametersFilter = computed(() => {
return parameters.value.filter(ele => {
if (curComponent.value.displayType === '2') {
return [2, 3].includes(ele.deType)
}
if (curComponent.value.displayType === '7') {
return [1, 7].includes(ele.deType)
}
return ele.deType === +curComponent.value.displayType
})
})
const { queryElement } = toRefs(props)
const getDetype = (id, arr) => {
return arr.find(ele => ele.id === id)?.deType
return arr.flat().find(ele => ele.id === id)?.deType
}
const visiblePopover = ref(false)
const handleVisiblePopover = ev => {
@ -142,11 +131,19 @@ const showTypeError = computed(() => {
if (!curComponent.value) return false
if (!curComponent.value.checkedFields?.length) return false
if (!fields.value?.length) return false
if (!!curComponent.value.parameters.length) {
const timeArr = curComponent.value.parameters.map(ele => ele.type[1])
if (timeArr.length !== new Set(timeArr).size) {
return true
}
}
let displayTypeField = null
return curComponent.value.checkedFields.some(id => {
const arr = fields.value.find(ele => ele.componentId === id)
const checkId = curComponent.value.checkedFieldsMap?.[id]
const field = (arr?.list || []).find(ele => checkId === ele.id)
const field = Object.values(arr?.fields || {})
.flat()
.find(ele => checkId === ele.id)
if (!field) return false
if (displayTypeField === null) {
displayTypeField = field?.deType
@ -181,12 +178,32 @@ const handleCheckedFieldsChange = (value: string[]) => {
const inputCom = ref()
const setParameters = () => {
const fieldArr = Object.values(curComponent.value.checkedFieldsMap).filter(ele => !!ele)
curComponent.value.parameters = fields.value
.map(ele => Object.values(ele?.fields || {}).flat())
.flat()
.filter(ele => fieldArr.includes(ele.id) && !!ele.variableName)
nextTick(() => {
if (isTimeParameter.value) {
curComponent.value.timeGranularity = typeTimeMap[curComponent.value.parameters[0].type[1]]
}
if (!!curComponent.value.parameters.length) {
curComponent.value.conditionType = 0
}
})
setType()
}
const setType = () => {
if (curComponent.value.checkedFields?.length) {
const [id] = curComponent.value.checkedFields
const arr = fields.value.find(ele => ele.componentId === id)
const checkId = curComponent.value.checkedFieldsMap?.[id]
const field = (arr?.list || []).find(ele => checkId === ele.id)
const field = Object.values(arr?.fields || {})
.flat()
.find(ele => checkId === ele.id)
if (field?.deType !== undefined) {
let displayType = curComponent.value.displayType
@ -220,6 +237,47 @@ const setTypeChange = () => {
})
}
const isTimeParameter = computed(() => {
return curComponent.value.parameters?.some(ele => ele.deType === 1 && !!ele.variableName)
})
const timeList = [
{
label: '年',
value: 'year'
},
{
label: '年月',
value: 'month'
},
{
label: '年月日',
value: 'date'
},
{
label: '年月日时分秒',
value: 'datetime'
}
]
const typeTimeMap = {
'DATETIME-YEAR': 'year',
'YYYY-MM': 'month',
'YYYY/MM': 'month',
'YYYY-MM-DD': 'date',
'YYYY/MM/DD': 'date',
'YYYY-MM-DD HH:mm:ss': 'datetime',
'YYYY/MM/DD HH:mm:ss': 'datetime'
}
const timeParameterList = computed(() => {
if (!isTimeParameter.value) return timeList
const [_, y] = curComponent.value.parameters?.filter(
ele => ele.deType === 1 && !!ele.variableName
)[0].type
return timeList.filter(ele => ele.value === typeTimeMap[y])
})
const cancelClick = () => {
visiblePopover.value = false
dialogVisible.value = false
@ -542,7 +600,9 @@ const validate = () => {
ele.checkedFields.some(id => {
const arr = fields.value.find(itx => itx.componentId === id)
const checkId = ele.checkedFieldsMap?.[id]
const field = (arr?.list || []).find(itx => checkId === itx.id)
const field = Object.values(arr?.fields || {})
.flat()
.find(itx => checkId === itx.id)
if (!field) return false
if (displayTypeField === null) {
displayTypeField = field?.deType
@ -642,14 +702,6 @@ const init = (queryId: string) => {
const datasetMapKeyList = Object.keys(datasetMap)
nextTick(() => {
getSqlParams([
...new Set(datasetFieldList.value.map(ele => ele.tableId).filter(ele => !!ele))
]).then(res => {
parameters.value = res || []
})
})
if (datasetFieldIdList.every(ele => datasetMapKeyList.includes(ele))) {
fields.value = datasetFieldList.value
.map(ele => {
@ -660,15 +712,14 @@ const init = (queryId: string) => {
}
const params = [...new Set(datasetFieldList.value.map(ele => ele.tableId).filter(ele => !!ele))]
if (!params.length) return
getDsDetailsWithPerm(params)
.then(res => {
res
.filter(ele => !!ele)
.forEach(ele => {
const { dimensionList, quotaList } = ele.fields
ele.list = [...dimensionList, ...quotaList]
datasetMap[ele.id] = ele
})
Promise.all([getDsDetailsWithPerm(params), getSqlParams(params)])
.then(([dq, p]) => {
dq.filter(ele => !!ele).forEach(ele => {
ele.activelist = 'dimensionList'
ele.fields.parameterList = p.filter(itx => itx.datasetGroupId === ele.id)
ele.hasParameter = !!ele.fields.parameterList.length
datasetMap[ele.id] = ele
})
fields.value = datasetFieldList.value
.map(ele => {
if (!datasetMap[ele.tableId]) return null
@ -1169,9 +1220,10 @@ defineExpose({
>
<span class="dataset ellipsis">{{ field.name }}</span>
<el-select
@change="setType"
@change="setParameters"
@focus="handleDialogClick"
style="margin-left: 12px"
popper-class="field-select--dqp"
v-if="curComponent.checkedFields.includes(field.componentId)"
v-model="curComponent.checkedFieldsMap[field.componentId]"
clearable
@ -1181,21 +1233,38 @@ defineExpose({
<Icon
:name="`field_${
fieldType[
getDetype(curComponent.checkedFieldsMap[field.componentId], field.list)
getDetype(
curComponent.checkedFieldsMap[field.componentId],
Object.values(field.fields)
)
]
}`"
:className="`field-icon-${
fieldType[
getDetype(curComponent.checkedFieldsMap[field.componentId], field.list)
getDetype(
curComponent.checkedFieldsMap[field.componentId],
Object.values(field.fields)
)
]
}`"
></Icon>
</el-icon>
</template>
<template #header>
<el-tabs stretch class="params-select--header" v-model="field.activelist">
<el-tab-pane label="维度" name="dimensionList"></el-tab-pane>
<el-tab-pane label="指标" name="quotaList"></el-tab-pane>
<el-tab-pane
v-if="field.hasParameter"
label="参数"
name="parameterList"
></el-tab-pane>
</el-tabs>
</template>
<el-option
v-for="ele in field.list"
v-for="ele in field.fields[field.activelist]"
:key="ele.id"
:label="ele.name"
:label="ele.name || ele.variableName"
:value="ele.id"
:disabled="ele.desensitized"
>
@ -1209,8 +1278,8 @@ defineExpose({
:className="`field-icon-${fieldType[ele.deType]}`"
></Icon>
</el-icon>
<span>
{{ ele.name }}
<span :title="ele.name || ele.variableName" class="ellipsis">
{{ ele.name || ele.variableName }}
</span>
</div>
</el-option>
@ -1276,7 +1345,7 @@ defineExpose({
<div class="list-item" v-if="['1', '7'].includes(curComponent.displayType)">
<div class="label">时间粒度</div>
<div class="value">
<template v-if="curComponent.displayType === '7'">
<template v-if="curComponent.displayType === '7' && !isTimeParameter">
<el-select
@change="timeGranularityMultipleChange"
placeholder="请选择时间粒度"
@ -1295,10 +1364,12 @@ defineExpose({
placeholder="请选择时间粒度"
v-model="curComponent.timeGranularity"
>
<el-option label="年" value="year" />
<el-option label="年月" value="month" />
<el-option label="年月日" value="date" />
<el-option label="年月日时分秒" value="datetime" />
<el-option
v-for="ele in timeParameterList"
:key="ele.value"
:label="ele.label"
:value="ele.value"
/>
</el-select>
</template>
</div>
@ -1571,8 +1642,12 @@ defineExpose({
<div class="value">
<el-radio-group class="larger-radio" v-model="curComponent.conditionType">
<el-radio :label="0">单条件</el-radio>
<el-radio :label="1">与条件</el-radio>
<el-radio :label="2">或条件</el-radio>
<el-radio :label="1" :disabled="!!curComponent.parameters.length"
>与条件</el-radio
>
<el-radio :label="2" :disabled="!!curComponent.parameters.length"
>或条件</el-radio
>
</el-radio-group>
</div>
</div>
@ -1698,95 +1773,6 @@ defineExpose({
</el-tooltip>
</div>
</div>
<div v-if="!['8'].includes(curComponent.displayType)" class="list-item">
<div class="label">
<el-tooltip
effect="dark"
content="空数据不支持传参数"
:disabled="!curComponent.showEmpty"
placement="top"
>
<el-checkbox
:disabled="curComponent.showEmpty"
v-model="curComponent.parametersCheck"
label="绑定参数"
/>
</el-tooltip>
</div>
<template v-if="curComponent.parametersCheck">
<div v-if="curComponent.displayType !== '7'" class="parameters">
<el-select
popper-class="dataset-parameters"
value-key="id"
multiple
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="3"
@focus="handleDialogClick"
v-model="curComponent.parameters"
clearable
>
<el-option
v-for="item in parametersFilter"
:key="item.id"
:label="item.variableName"
:value="item"
>
<div class="variable-name ellipsis">{{ item.variableName }}</div>
<el-tooltip effect="dark" :content="item.datasetFullName" placement="top">
<div class="dataset-full-name ellipsis">{{ item.datasetFullName }}</div>
</el-tooltip>
</el-option>
</el-select>
</div>
<div v-else class="parameters-range">
<div class="range-title">开始时间</div>
<div class="range-title">结束时间</div>
<div class="params-start">
<el-select
popper-class="dataset-parameters"
value-key="id"
@focus="handleDialogClick"
v-model="curComponent.parametersStart"
clearable
>
<el-option
v-for="item in parametersFilter"
:key="item.id"
:label="item.variableName"
:value="item"
>
<div class="variable-name ellipsis">{{ item.variableName }}</div>
<el-tooltip effect="dark" :content="item.datasetFullName" placement="top">
<div class="dataset-full-name ellipsis">{{ item.datasetFullName }}</div>
</el-tooltip>
</el-option>
</el-select>
</div>
<div class="params-end">
<el-select
popper-class="dataset-parameters"
value-key="id"
@focus="handleDialogClick"
v-model="curComponent.parametersEnd"
clearable
>
<el-option
v-for="item in parametersFilter"
:key="item.id"
:label="item.variableName"
:value="item"
>
<div class="variable-name ellipsis">{{ item.variableName }}</div>
<el-tooltip effect="dark" :content="item.datasetFullName" placement="top">
<div class="dataset-full-name ellipsis">{{ item.datasetFullName }}</div>
</el-tooltip>
</el-option>
</el-select>
</div>
</div>
</template>
</div>
<div v-if="!['8'].includes(curComponent.displayType)" class="list-item">
<div class="label">
<el-checkbox v-model="curComponent.defaultValueCheck" label="设置默认值" />
@ -1963,6 +1949,19 @@ defineExpose({
</template>
<style lang="less">
.field-select--dqp {
width: 210px;
}
.ed-select-dropdown__header {
padding: 0 8px;
.params-select--header {
--ed-tabs-header-height: 32px;
.ed-tabs__item {
font-weight: 400;
font-size: 15px;
}
}
}
.condition-value-select-popper {
.ed-select-dropdown__item.selected::after {
display: none;

View File

@ -180,7 +180,7 @@ const handleFieldIdChange = (val: EnumValue) => {
oldArr = [...selectValue.value]
oldEnumValueArr = setOldMapValue(oldArr)
}
enumValueArr = [...res, ...oldEnumValueArr] || []
enumValueArr = [...(res || []), ...oldEnumValueArr] || []
options.value = [
...new Set(
(res || [])

View File

@ -204,7 +204,6 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
defaultMapValue,
mapValue,
parameters = [],
parametersCheck = false,
isTree = false,
timeGranularity = 'date',
displayType,
@ -307,16 +306,7 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
fieldId: item.checkedFieldsMap[curComponentId],
operator,
value: result,
parameters: parametersCheck
? +displayType === 7
? [
parametersStart,
parametersEnd?.id
? { ...parametersEnd, id: `${parametersEnd.id}_START_END_SPLIT` }
: parametersEnd
]
: parameters
: [],
parameters,
isTree
})
}

View File

@ -25,6 +25,9 @@ interface LinkItem {
method?: string
}
const linkList = ref([{ id: 5, label: t('common.about'), method: 'toAbout' }] as LinkItem[])
if (!appearanceStore.getShowAbout) {
linkList.value.splice(0, 1)
}
const inPlatformClient = computed(() => !!wsCache.get('de-platform-client'))
@ -118,7 +121,7 @@ if (uid.value === '1') {
<el-popover
ref="popoverRef"
:virtual-ref="buttonRef"
trigger="hover"
trigger="click"
title=""
virtual-triggering
placement="bottom-start"

View File

@ -19,6 +19,7 @@ import AiComponent from '@/layout/components/AiComponent.vue'
import { findBaseParams } from '@/api/aiComponent'
import ExportExcel from '@/views/visualized/data/dataset/ExportExcel.vue'
import AiTips from '@/layout/components/AiTips.vue'
const appearanceStore = useAppearanceStoreWithOut()
const { push } = useRouter()
const route = useRoute()
@ -40,6 +41,7 @@ const activeIndex = computed(() => {
}
return route.path
})
const permissionStore = usePermissionStore()
const ExportExcelRef = ref()
const downloadClick = () => {
@ -117,19 +119,30 @@ onMounted(() => {
</el-menu>
<div class="operate-setting" v-if="!desktop">
<XpackComponent jsname="c3dpdGNoZXI=" />
<el-icon style="margin: 0 10px" class="ai-icon" v-if="aiBaseUrl && !showOverlay">
<el-icon
style="margin: 0 10px"
class="ai-icon"
v-if="aiBaseUrl && !showOverlay && appearanceStore.getShowAi"
>
<Icon name="dv-ai" @click="handleAiClick" />
</el-icon>
<el-icon style="margin: 0 10px">
<Icon name="dv-preview-download" @click="downloadClick" />
</el-icon>
<ai-tips @confirm="aiTipsConfirm" v-if="showOverlay" class="ai-icon-tips"></ai-tips>
<ai-tips
@confirm="aiTipsConfirm"
v-if="showOverlay && appearanceStore.getShowAi"
class="ai-icon-tips"
/>
<ToolboxCfg v-if="showToolbox" />
<TopDoc />
<TopDoc v-if="appearanceStore.getShowDoc" />
<SystemCfg v-if="showSystem" />
<AccountOperator />
<ai-component v-if="aiBaseUrl" :base-url="aiBaseUrl"></ai-component>
<div v-if="showOverlay" class="overlay"></div>
<ai-component
v-if="aiBaseUrl && appearanceStore.getShowAi"
:base-url="aiBaseUrl"
></ai-component>
<div v-if="showOverlay && appearanceStore.getShowAi" class="overlay"></div>
</div>
</el-header>
<ExportExcel ref="ExportExcelRef"></ExportExcel>

View File

@ -2,8 +2,10 @@
import { useRouter } from 'vue-router'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { computed } from 'vue'
const appearanceStore = useAppearanceStoreWithOut()
const navigateBg = computed(() => appearanceStore.getNavigateBg)
const showDoc = computed(() => appearanceStore.getShowDoc)
const { push, resolve } = useRouter()
const redirectUser = () => {
const sysMenu = resolve('/system')
@ -14,7 +16,13 @@ const redirectUser = () => {
<template>
<el-tooltip class="box-item" effect="dark" content="组织管理中心" placement="top">
<div class="sys-setting" :class="{ 'is-light-setting': navigateBg && navigateBg === 'light' }">
<div
class="sys-setting"
:class="{
'is-light-setting': navigateBg && navigateBg === 'light',
'in-iframe-setting': !showDoc
}"
>
<el-icon @click="redirectUser">
<Icon class="icon-setting" name="icon-setting" />
</el-icon>
@ -35,6 +43,9 @@ const redirectUser = () => {
background-color: #1e2738;
}
}
.in-iframe-setting {
margin-left: 10px !important;
}
.is-light-setting {
&:hover {
background-color: var(--ed-menu-hover-bg-color) !important;

View File

@ -2170,7 +2170,7 @@ export default {
link_add_tips_pre: '请在右侧配置网页信息..',
web_add_tips_suf: '添加网页信息...',
panel_view_result_show: '图表结果',
panel_view_result_tips: '选择仪表板会覆盖图表的结果展示数量取值范围1~10000',
panel_view_result_tips: '选择{0}会覆盖图表的结果展示数量取值范围1~10000',
timeout_refresh: '请求超时稍后刷新...',
mobile_layout: '移动端布局',
component_hidden: '隐藏的组件',

View File

@ -232,7 +232,7 @@ declare interface ChartBasicStyle {
/**
* 对称柱状图方向
*/
layout: 'horizontal' | 'vertical'
layout?: 'horizontal' | 'vertical'
}
/**
* 表头属性
@ -482,6 +482,10 @@ declare interface ChartMiscAttr {
* 地图线条宽度
*/
mapLineWidth: number
/**
* 流向地图动画
*/
mapLineAnimate?: boolean
/**
* 流向地图动画间隔
*/
@ -786,6 +790,10 @@ declare interface ChartIndicatorStyle {
* 字体颜色
*/
color: string
/**
* 背景颜色
*/
backgroundColor: string
/**
* 水平位置
*/

View File

@ -123,6 +123,10 @@ declare interface ChartThreshold {
* 仪表盘阈值: x,y,z
*/
gaugeThreshold: string
/**
* 水波图阈值: x,y,z
*/
liquidThreshold: string
/**
* 指标卡阈值
*/

View File

@ -117,6 +117,14 @@ declare interface Axis extends ChartViewField {
* 维度/指标分组类型
*/
groupType: 'q' | 'd'
/**
* 排序规则
*/
sort: 'asc' | 'desc' | 'none' | 'custom_sort'
/**
* 自定义排序项
*/
customSort: string[]
}
declare interface ChartViewField {
/**

View File

@ -1,6 +1,7 @@
<script setup lang="ts">
import { shallowRef, defineAsyncComponent } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { useEmitt } from '@/hooks/web/useEmitt'
const VisualizationEditor = defineAsyncComponent(
() => import('@/views/data-visualization/index.vue')
@ -33,6 +34,15 @@ const componentMap = {
DashboardPanel
}
const changeCurrentComponent = val => {
currentComponent.value = componentMap[val]
}
useEmitt({
name: 'changeCurrentComponent',
callback: changeCurrentComponent
})
currentComponent.value = componentMap[props.componentName]
</script>
<template>

View File

@ -47,7 +47,9 @@ onBeforeMount(async () => {
// div
if (embeddedStore.outerParams) {
try {
attachParam = JSON.parse(embeddedStore.outerParams)
const outerPramsParse = JSON.parse(embeddedStore.outerParams)
attachParam = outerPramsParse.attachParam
dvMainStore.setEmbeddedCallBack(outerPramsParse.callBackFlag || 'no')
} catch (e) {
console.error(e)
ElMessage.error(t('visualization.outer_param_decode_error'))

View File

@ -66,7 +66,9 @@ onBeforeMount(async () => {
// div
if (embeddedStore.outerParams) {
try {
attachParam = JSON.parse(embeddedStore.outerParams)
const outerPramsParse = JSON.parse(embeddedStore.outerParams)
attachParam = outerPramsParse.attachParam
dvMainStore.setEmbeddedCallBack(outerPramsParse.callBackFlag || 'no')
} catch (e) {
console.error(e)
ElMessage.error(t('visualization.outer_param_decode_error'))

View File

@ -6,12 +6,16 @@ import colorFunctions from 'less/lib/less/functions/color.js'
import colorTree from 'less/lib/less/tree/color.js'
const basePath = import.meta.env.VITE_API_BASEPATH
const baseUrl = basePath + '/appearance/image/'
import { isBtnShow } from '@/utils/utils'
interface AppearanceState {
themeColor?: string
customColor?: string
navigateBg?: string
navigate?: string
help?: string
showAi?: string
showDoc?: string
showAbout?: string
bg?: string
login?: string
slogan?: string
@ -33,6 +37,9 @@ export const useAppearanceStore = defineStore('appearanceStore', {
navigateBg: '',
navigate: '',
help: '',
showDoc: '0',
showAi: '0',
showAbout: '0',
bg: '',
login: '',
slogan: '',
@ -106,6 +113,15 @@ export const useAppearanceStore = defineStore('appearanceStore', {
},
getCommunity(): boolean {
return this.community
},
getShowAi(): boolean {
return isBtnShow(this.showAi)
},
getShowDoc(): boolean {
return isBtnShow(this.showDoc)
},
getShowAbout(): boolean {
return isBtnShow(this.showAbout)
}
},
actions: {
@ -160,6 +176,9 @@ export const useAppearanceStore = defineStore('appearanceStore', {
}
this.navigate = data.navigate
this.help = data.help
this.showAi = data.showAi
this.showDoc = data.showDoc
this.showAbout = data.showAbout
this.navigateBg = data.navigateBg
this.themeColor = data.themeColor
this.customColor = data.customColor

View File

@ -32,6 +32,7 @@ export const dvMainStore = defineStore('dataVisualization', {
chartAreaCollapse: false,
datasetAreaCollapse: false
},
embeddedCallBack: 'no', // 嵌入模式是否允许反馈参数
editMode: 'edit', // 编辑器模式 edit preview
mobileInPc: false,
firstLoadMap: [],
@ -173,6 +174,9 @@ export const dvMainStore = defineStore('dataVisualization', {
}
},
actions: {
setEmbeddedCallBack(value) {
this.embeddedCallBack = value
},
setPublicLinkStatus(value) {
this.publicLinkStatus = value
},

View File

@ -1,5 +1,6 @@
import { defineStore } from 'pinia'
import { store } from '../index'
import { clear } from '@/api/sync/syncTaskLog'
interface AppState {
type: string
token: string
@ -10,6 +11,9 @@ interface AppState {
pid: string
chartId: string
resourceId: string
opt: string
createType: string
templateParams: string
}
export const userStore = defineStore('embedded', {
@ -23,13 +27,22 @@ export const userStore = defineStore('embedded', {
dvId: '',
pid: '',
chartId: '',
resourceId: ''
resourceId: '',
opt: '',
createType: '',
templateParams: ''
}
},
getters: {
getType(): string {
return this.type
},
getCreateType(): string {
return this.createType
},
getTemplateParams(): string {
return this.templateParams
},
getToken(): string {
return this.token
},
@ -54,6 +67,9 @@ export const userStore = defineStore('embedded', {
getResourceId(): string {
return this.resourceId
},
getOpt(): string {
return this.opt
},
getIframeData(): any {
return {
embeddedToken: this.token,
@ -71,6 +87,12 @@ export const userStore = defineStore('embedded', {
setType(type: string) {
this.type = type
},
setCreateType(createType: string) {
this.createType = createType
},
setTemplateParams(templateParams: string) {
this.templateParams = templateParams
},
setToken(token: string) {
this.token = token
},
@ -95,6 +117,9 @@ export const userStore = defineStore('embedded', {
setResourceId(resourceId: string) {
this.resourceId = resourceId
},
setOpt(opt: string) {
this.opt = opt
},
setIframeData(data: any) {
this.type = data['type']
this.token = data['embeddedToken']
@ -104,6 +129,14 @@ export const userStore = defineStore('embedded', {
this.chartId = data['chartId']
this.pid = data['pid']
this.resourceId = data['resourceId']
},
clearState() {
this.setPid('')
this.setOpt('')
this.setCreateType('')
this.setTemplateParams('')
this.setResourceId('')
this.setDvId('')
}
}
})

View File

@ -174,17 +174,30 @@ function move(keyCode) {
if (curComponent.value) {
if (keyCode === leftKey) {
curComponent.value.style.left = --curComponent.value.style.left
groupAreaAdaptor(-1, 0)
} else if (keyCode === rightKey) {
curComponent.value.style.left = ++curComponent.value.style.left
groupAreaAdaptor(1, 0)
} else if (keyCode === upKey) {
curComponent.value.style.top = --curComponent.value.style.top
groupAreaAdaptor(0, -1)
} else if (keyCode === downKey) {
curComponent.value.style.top = ++curComponent.value.style.top
groupAreaAdaptor(0, 1)
}
snapshotStore.recordSnapshotCache('key-move')
}
}
function groupAreaAdaptor(leftOffset = 0, topOffset = 0) {
if (curComponent.value.component === 'GroupArea') {
composeStore.areaData.components.forEach(component => {
component.style.top = component.style.top + topOffset
component.style.left = component.style.left + leftOffset
})
}
}
function cut() {
copyStore.cut()
}

View File

@ -7,7 +7,7 @@ import { groupSizeStyleAdaptor } from '@/utils/style'
const dvMainStore = dvMainStoreWithOut()
const { componentData, curComponentIndex, canvasStyleData } = storeToRefs(dvMainStore)
const needToChangeAttrs = ['top', 'left', 'width', 'height', 'fontSize']
const needToChangeAttrs = ['top', 'left', 'width', 'height', 'fontSize', 'letterSpacing']
export function changeSizeWithScale(scale) {
return changeComponentsSizeWithScale(scale)

View File

@ -31,6 +31,8 @@ import DeVideo from '@/custom-component/de-video/Component.vue'
import DeVideoAttr from '@/custom-component/de-video/Attr.vue'
import DeStreamMedia from '@/custom-component/de-stream-media/Component.vue'
import DeStreamMediaAttr from '@/custom-component/de-stream-media/Attr.vue'
import ScrollText from '@/custom-component/scroll-text/Component.vue'
import ScrollTextAttr from '@/custom-component/scroll-text/Attr.vue'
export const componentsMap = {
VText: VText,
VQuery,
@ -64,7 +66,9 @@ export const componentsMap = {
DeVideo: DeVideo,
DeVideoAttr: DeVideoAttr,
DeStreamMedia: DeStreamMedia,
DeStreamMediaAttr: DeStreamMediaAttr
DeStreamMediaAttr: DeStreamMediaAttr,
ScrollText: ScrollText,
ScrollTextAttr: ScrollTextAttr
}
export default function findComponent(key) {

View File

@ -6,6 +6,7 @@ import { storeToRefs } from 'pinia'
import { findResourceAsBase64 } from '@/api/staticResource'
import FileSaver from 'file-saver'
import { deepCopy } from '@/utils/utils'
import { toPng } from 'html-to-image'
const embeddedStore = useEmbedded()
const dvMainStore = dvMainStoreWithOut()
const { canvasStyleData, componentData, canvasViewInfo, canvasViewDataInfo, dvInfo } =
@ -70,6 +71,35 @@ export function download2AppTemplate(downloadType, canvasDom, name, callBack?) {
}
}
export function downloadCanvas2(type, canvasDom, name, callBack?) {
toPng(canvasDom)
.then(dataUrl => {
const a = document.createElement('a')
a.setAttribute('download', name)
a.href = dataUrl
if (type === 'img') {
const a = document.createElement('a')
a.setAttribute('download', name)
a.href = dataUrl
a.click()
document.body.removeChild(a)
} else {
const contentWidth = canvasDom.offsetWidth
const contentHeight = canvasDom.offsetHeight
const lp = contentWidth > contentHeight ? 'l' : 'p'
const PDF = new JsPDF(lp, 'pt', [contentWidth, contentHeight])
PDF.addImage(dataUrl, 'PNG', 0, 0, contentWidth, contentHeight)
PDF.save(name + '.pdf')
}
if (callBack) {
callBack()
}
})
.catch(error => {
console.error('oops, something went wrong!', error)
})
}
export function downloadCanvas(type, canvasDom, name, callBack?) {
// const canvasDom = document.getElementById(canvasId)
if (canvasDom) {

View File

@ -106,7 +106,24 @@ export const cleanPlatformFlag = () => {
wsCache.delete(platformKey)
return false
}
export const isInIframe = () => {
try {
return window.top !== window.self
} catch (error) {
console.error(error)
return true
}
}
export const isBtnShow = (val: string) => {
if (!val || val === '0') {
return true
} else if (val === '1') {
return false
} else {
return !isInIframe()
}
}
export function isMobile() {
return (
navigator.userAgent.match(

View File

@ -138,7 +138,9 @@ const canvasInit = (isFistLoad = true) => {
}
// afterInit
dvMainStore.setDataPrepareState(true)
isFistLoad && snapshotStore.recordSnapshotCache('renderChart')
if (isMainCanvas(canvasId.value.id) && isFistLoad) {
snapshotStore.recordSnapshotCache('renderChart')
}
}, 500)
}

View File

@ -61,10 +61,10 @@ const init = () => {
const changeThreshold = () => {
emit('onThresholdChange', state.thresholdForm)
}
const gaugeThresholdChange = () => {
const changeSplitThreshold = (threshold: string) => {
// check input
if (state.thresholdForm.gaugeThreshold) {
const arr = state.thresholdForm.gaugeThreshold.split(',')
if (threshold) {
const arr = threshold.split(',')
for (let i = 0; i < arr.length; i++) {
const ele = arr[i]
if (parseFloat(ele).toString() === 'NaN' || parseFloat(ele) <= 0 || parseFloat(ele) >= 100) {
@ -246,7 +246,7 @@ init()
style="width: 100px; margin: 0 10px"
size="small"
clearable
@change="gaugeThresholdChange"
@change="changeSplitThreshold"
/>
<span>,100</span>
<el-tooltip effect="dark" placement="bottom">
@ -260,6 +260,36 @@ init()
</el-form-item>
</el-form>
</el-col>
<el-col v-show="showProperty('liquidThreshold')">
<el-form ref="thresholdForm" :model="state.thresholdForm" label-position="top">
<el-form-item
:label="t('chart.threshold_range') + '(%)'"
class="form-item"
label-width="auto"
>
<span>0,</span>
<el-input
:effect="themes"
:placeholder="t('chart.threshold_range')"
:disabled="!state.thresholdForm.enable"
v-model="state.thresholdForm.liquidThreshold"
style="width: 100px; margin: 0 10px"
size="small"
clearable
@change="changeSplitThreshold"
/>
<span>,100</span>
<el-tooltip effect="dark" placement="bottom">
<el-icon style="margin-left: 10px"><InfoFilled /></el-icon>
<template #content>
阈值设置决定水波图颜色为空则不开启阈值范围(0-100)逐级递增
<br />
例如输入 30,70表示分为3段分别为[0,30],(30,70],(70,100]
</template>
</el-tooltip>
</el-form-item>
</el-form>
</el-col>
<!--文本卡-->
<el-col v-if="props.chart.type && props.chart.type === 'label'">
@ -409,14 +439,14 @@ init()
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
<!-- <div
<div
:title="t('chart.backgroundColor')"
:style="{
backgroundColor: item.backgroundColor
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>-->
></div>
</div>
</div>
</el-col>

View File

@ -24,6 +24,7 @@ const thresholdObj = {
field: '0',
value: '0',
color: '#ff0000ff',
backgroundColor: '#fff',
min: '0',
max: '1'
}
@ -175,7 +176,7 @@ init()
@change="changeThreshold"
/>
</div>
<!-- <div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<div class="color-title">{{ t('chart.backgroundColor') }}</div>
<el-color-picker
is-custom
@ -186,7 +187,7 @@ init()
:predefine="predefineColors"
@change="changeThreshold"
/>
</div>-->
</div>
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<el-button
class="circle-button"

View File

@ -221,6 +221,7 @@ watch(
:themes="themes"
:chart="chart"
@onBasicStyleChange="onBasicStyleChange"
@onMiscChange="onMiscChange"
/>
</el-collapse-item>
<collapse-switch-item

View File

@ -1,6 +1,10 @@
<script setup lang="ts">
import { onMounted, PropType, reactive, watch } from 'vue'
import { COLOR_PANEL, DEFAULT_BASIC_STYLE } from '@/views/chart/components/editor/util/chart'
import {
COLOR_PANEL,
DEFAULT_BASIC_STYLE,
DEFAULT_MISC
} from '@/views/chart/components/editor/util/chart'
import { useI18n } from '@/hooks/web/useI18n'
import CustomColorStyleSelect from '@/views/chart/components/editor/editor-style/components/CustomColorStyleSelect.vue'
import { cloneDeep, defaultsDeep } from 'lodash-es'
@ -28,6 +32,7 @@ const showProperty = prop => props.propertyInner?.includes(prop)
const predefineColors = COLOR_PANEL
const state = reactive({
basicStyleForm: JSON.parse(JSON.stringify(DEFAULT_BASIC_STYLE)) as ChartBasicStyle,
miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)) as ChartMiscAttr,
customColor: null,
colorIndex: 0,
fieldColumnWidth: {
@ -38,6 +43,7 @@ const state = reactive({
watch(
[
() => props.chart.customAttr.basicStyle,
() => props.chart.customAttr.misc,
() => props.chart.customAttr.tableHeader,
() => props.chart.xAxis,
() => props.chart.yAxis
@ -47,14 +53,19 @@ watch(
},
{ deep: true }
)
const emit = defineEmits(['onBasicStyleChange'])
const emit = defineEmits(['onBasicStyleChange', 'onMiscChange'])
const changeBasicStyle = (prop?: string, requestData = false) => {
emit('onBasicStyleChange', { data: state.basicStyleForm, requestData }, prop)
}
const changeMisc = prop => {
emit('onMiscChange', { data: state.miscForm, requestData: true }, prop)
}
const init = () => {
const basicStyle = cloneDeep(props.chart.customAttr.basicStyle)
const miscStyle = cloneDeep(props.chart.customAttr.misc)
configCompat(basicStyle)
state.basicStyleForm = defaultsDeep(basicStyle, cloneDeep(DEFAULT_BASIC_STYLE)) as ChartBasicStyle
state.miscForm = defaultsDeep(miscStyle, cloneDeep(DEFAULT_MISC)) as ChartMiscAttr
if (!state.customColor) {
state.customColor = state.basicStyleForm.colors[0]
state.colorIndex = 0
@ -177,6 +188,12 @@ const mapStyleOptions = [
{ name: t('chart.map_style_wine'), value: 'wine' }
]
const flowLineTypeOptions = [
{ name: t('chart.map_line_type_line'), value: 'line' },
{ name: t('chart.map_line_type_arc'), value: 'arc' },
{ name: t('chart.map_line_type_arc_3d'), value: 'arc3d' }
]
onMounted(() => {
init()
})
@ -268,7 +285,236 @@ onMounted(() => {
<el-radio :effect="themes" label="vertical">{{ t('chart.vertical') }}</el-radio>
</el-radio-group>
</el-form-item>
<!--flow map begin-->
<div class="map-setting" v-if="showProperty('mapStyle')">
<div class="map-style">
<el-row style="flex: 1">
<el-col>
<el-form-item
:label="t('chart.chart_map') + t('chart.map_style')"
class="form-item"
:class="'form-item-' + themes"
>
<el-select
:effect="themes"
v-model="state.basicStyleForm.mapStyle"
@change="changeBasicStyle('mapStyle')"
>
<el-option
v-for="item in mapStyleOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.chart_map') + t('chart.map_pitch') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="0"
:max="90"
v-model="state.miscForm.mapPitch"
@change="changeMisc('mapPitch')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
<div class="map-flow-style">
<el-row style="flex: 1">
<el-col>
<el-form-item
:label="t('chart.line') + t('chart.map_line_type')"
class="form-item"
:class="'form-item-' + themes"
>
<el-select
:effect="themes"
v-model="state.miscForm.mapLineType"
@change="changeMisc('mapLineType')"
>
<el-option
v-for="item in flowLineTypeOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.map_line_width') }}
</label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="10"
v-model="state.miscForm.mapLineWidth"
@change="changeMisc('mapLineWidth')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.miscForm.mapLineGradient"
:predefine="predefineColors"
@change="changeMisc('mapLineGradient')"
>
{{ t('chart.line') + t('chart.map_line_linear') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div v-if="state.miscForm.mapLineGradient">
<el-row style="flex: 1" :gutter="8">
<el-col :span="13">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.map_line_color_source_color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.miscForm.mapLineSourceColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeMisc('mapLineSourceColor')"
/>
</el-form-item>
</el-col>
<el-col :span="13">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.map_line_color_target_color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.miscForm.mapLineTargetColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeMisc('mapLineTargetColor')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div v-if="!state.miscForm.mapLineGradient">
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.color')"
>
<el-color-picker
is-custom
class="color-picker-style"
v-model="state.miscForm.mapLineSourceColor"
:persistent="false"
:effect="themes"
:trigger-width="108"
:predefine="predefineColors"
@change="changeMisc('mapLineSourceColor')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.not_alpha') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col :span="13">
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
v-model="state.basicStyleForm.alpha"
@change="changeBasicStyle('alpha')"
/>
</el-form-item>
</el-col>
<el-col :span="11" style="padding-top: 2px">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input
type="number"
:effect="themes"
v-model="state.basicStyleForm.alpha"
:min="0"
:max="100"
class="basic-input-number"
:controls="false"
@change="changeBasicStyle('alpha')"
>
<template #suffix> % </template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</div>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.miscForm.mapLineAnimate"
:predefine="predefineColors"
@change="changeMisc('mapLineAnimate')"
>
{{ t('chart.line') + t('chart.map_line_animate') }}
</el-checkbox>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting" v-if="state.miscForm.mapLineAnimate">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.map_line_animate_duration') }}
</label>
<el-row style="flex: 1" :gutter="8">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="0"
:max="20"
v-model="state.miscForm.mapLineAnimateDuration"
@change="changeMisc('mapLineAnimateDuration')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
</div>
<!--flow map end-->
<!--map start-->
<el-row :gutter="8">
<el-col :span="12" v-if="showProperty('areaBorderColor')">
@ -658,27 +904,6 @@ onMounted(() => {
</el-radio-group>
</el-form-item>
<!--radar end-->
<!--flow map begin-->
<el-form-item
:label="t('chart.map_style')"
class="form-item"
:class="'form-item-' + themes"
v-if="showProperty('mapStyle')"
>
<el-select
:effect="themes"
v-model="state.basicStyleForm.mapStyle"
@change="changeBasicStyle('mapStyle')"
>
<el-option
v-for="item in mapStyleOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<!--flow map end-->
<!--scatter start-->
<el-form-item
:label="t('chart.bubble_symbol')"

View File

@ -224,7 +224,11 @@ onMounted(() => {
</el-radio-group>
</el-form-item>
<div class="position-divider" :class="'position-divider--' + themes"></div>
<div
v-if="showProperty('orient')"
class="position-divider"
:class="'position-divider--' + themes"
></div>
<el-form-item
class="form-item"

View File

@ -137,11 +137,13 @@ onMounted(() => {
label-position="top"
>
<template v-if="showProperty('lineStyle')">
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes"
>{{ t('chart.quadrant') }}{{ t('chart.split_line') }}</label
>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-form-item
:label="t('chart.split_line')"
class="form-item"
:class="'form-item-' + themes"
style="padding-right: 4px"
>
<el-color-picker
v-model="state.quadrantForm.lineStyle.stroke"
class="color-picker-style"
@ -152,6 +154,7 @@ onMounted(() => {
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<template #label>&nbsp;</template>
<el-tooltip :content="t('chart.not_alpha')" :effect="toolTip" placement="top">
<el-select
style="width: 53px"
@ -170,6 +173,7 @@ onMounted(() => {
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<template #label>&nbsp;</template>
<el-tooltip :content="t('chart.funnel_width')" :effect="toolTip" placement="top">
<el-input-number
style="width: 108px"
@ -229,18 +233,14 @@ onMounted(() => {
:label="t('chart.quadrant') + (index + 1)"
class="padding-tab"
>
<div style="flex-direction: row; justify-content: space-between">
<div style="margin-top: 8px">
<template v-if="showProperty('regionStyle')">
<div style="display: flex">
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">{{
t('chart.backgroundColor')
}}</label>
</div>
<div style="display: flex">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
style="padding-right: 4px"
:label="t('chart.backgroundColor')"
>
<el-color-picker
v-model="state.quadrantForm.regionStyle[index].fill"
@ -256,6 +256,7 @@ onMounted(() => {
:class="'form-item-' + themes"
style="padding-left: 4px"
>
<template #label>&nbsp;</template>
<el-tooltip :content="t('chart.not_alpha')" :effect="toolTip" placement="top">
<el-select
style="width: 53px"
@ -285,14 +286,12 @@ onMounted(() => {
@blur="changeStyle()"
/>
</el-form-item>
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">
{{ t('chart.text') }}{{ t('chart.chart_style') }}</label
>
<div style="display: flex">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
style="padding-right: 4px"
:label="t('chart.chart_style')"
>
<el-color-picker
v-model="l.style.fill"
@ -308,6 +307,7 @@ onMounted(() => {
:class="'form-item-' + themes"
style="padding-left: 4px"
>
<template #label>&nbsp;</template>
<el-tooltip :content="t('chart.not_alpha')" :effect="toolTip" placement="top">
<el-select
style="width: 53px"
@ -330,6 +330,7 @@ onMounted(() => {
:class="'form-item-' + themes"
style="padding-left: 4px"
>
<template #label>&nbsp;</template>
<el-tooltip :content="t('chart.font_size')" :effect="toolTip" placement="top">
<el-select
style="width: 108px"

View File

@ -153,15 +153,12 @@ onMounted(() => {
/>
</el-form-item>
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes"
>{{ t('chart.name') }}{{ t('chart.text') }}</label
>
<div style="display: flex">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
v-if="showProperty('color')"
style="padding-right: 4px"
:label="t('chart.chart_style')"
>
<el-color-picker
v-model="state.axisForm.color"
@ -178,6 +175,7 @@ onMounted(() => {
v-if="showProperty('fontSize')"
style="padding-left: 4px"
>
<template #label>&nbsp;</template>
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 108px"

View File

@ -129,15 +129,13 @@ onMounted(() => {
/>
</el-form-item>
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes"
>{{ t('chart.name') }}{{ t('chart.text') }}</label
>
<div style="display: flex">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
v-if="showProperty('color')"
style="padding-right: 4px"
:label="t('chart.chart_style')"
>
<el-color-picker
v-model="state.axisForm.color"
@ -154,6 +152,7 @@ onMounted(() => {
v-if="showProperty('fontSize')"
style="padding-left: 4px"
>
<template #label>&nbsp;</template>
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 108px"

View File

@ -209,12 +209,7 @@ onMounted(() => {
</el-col>
</el-row>
<el-form-item
:label="t('chart.table_show_index')"
class="form-item"
:class="'form-item-' + themes"
v-if="showProperty('showIndex')"
>
<el-form-item class="form-item" :class="'form-item-' + themes" v-if="showProperty('showIndex')">
<el-checkbox
size="small"
:effect="themes"

View File

@ -3044,6 +3044,35 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
border-top: none !important;
}
:deep(.ed-tabs__nav-wrap::after) {
background-color: rgba(31, 35, 41, 0.15);
}
:deep(.ed-tabs__nav-scroll) {
.ed-tabs__item {
height: 35px;
line-height: 35px;
color: var(--ed-color-primary, #3370ff);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-size: 12px;
font-style: normal;
font-weight: 500;
}
}
}
.query-style-tab {
width: 100%;
border-top: solid 1px @side-outline-border-color-light !important;
.tab-container {
.border-bottom-tab(8px);
}
margin-left: 0px !important;
:deep(.ed-tabs__header) {
border-top: none !important;
}
:deep(.ed-tabs__nav-wrap::after) {
background-color: rgba(31, 35, 41, 0.15);
}
@ -3082,6 +3111,35 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
box-shadow: 0 0 0 1px var(--ed-color-primary, #3370ff) inset !important;
}
}
.query-style-tab {
width: 100%;
border-top: solid 1px @main-collapse-border-dark !important;
.tab-container {
.border-bottom-tab(8px);
}
margin-left: 0px !important;
:deep(.ed-tabs__header) {
border-top: none !important;
}
:deep(.ed-tabs__nav-wrap::after) {
background-color: rgba(31, 35, 41, 0.15);
}
:deep(.ed-tabs__nav-scroll) {
.ed-tabs__item {
height: 35px;
line-height: 35px;
color: var(--ed-color-primary, #3370ff);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-size: 12px;
font-style: normal;
font-weight: 500;
}
}
}
}
.chart-edit {

View File

@ -388,6 +388,7 @@ export const DEFAULT_INDICATOR_STYLE: ChartIndicatorStyle = {
fontFamily: 'Microsoft YaHei',
letterSpace: 0,
fontShadow: false,
backgroundColor: '#fff',
suffixEnable: true,
suffix: '',
@ -675,6 +676,7 @@ export const DEFAULT_ASSIST_LINE_CFG: ChartAssistLineCfg = {
export const DEFAULT_THRESHOLD: ChartThreshold = {
enable: false,
gaugeThreshold: '',
liquidThreshold: '',
labelThreshold: [],
tableThreshold: [],
textLabelThreshold: []
@ -1223,14 +1225,14 @@ export const CHART_TYPE_CONFIGS = [
category: 'compare',
value: 'bidirectional-bar',
title: t('chart.chart_bidirectional_bar'),
icon: 'percentage-bar-stack-horizontal'
icon: 'bidirectional-bar'
},
{
render: 'antv',
category: 'compare',
value: 'progress-bar',
title: t('chart.chart_progress_bar'),
icon: 'percentage-bar-stack-horizontal'
icon: 'progress-bar'
}
]
},
@ -1308,6 +1310,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'bubble-map',
title: t('chart.chart_bubble_map'),
icon: 'bubble-map'
},
{
render: 'antv',
category: 'map',
value: 'flow-map',
title: t('chart.chart_flow_map'),
icon: 'flow-map'
}
]
},

View File

@ -19,7 +19,8 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
'basic-style-selector',
'label-selector',
'misc-selector',
'title-selector'
'title-selector',
'threshold'
]
propertyInner: EditorPropertyInner = {
'background-overall-component': ['all'],
@ -37,7 +38,8 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
'fontFamily',
'letterSpace',
'fontShadow'
]
],
threshold: ['liquidThreshold']
}
axis: AxisType[] = ['yAxis', 'filter']
axisConfig: AxisConfig = {
@ -147,6 +149,31 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
}
}
protected configThreshold(chart: Chart, options: LiquidOptions): LiquidOptions {
const senior = parseJson(chart.senior)
if (senior?.threshold?.enable) {
const { liquidThreshold } = senior?.threshold
if (liquidThreshold) {
const { paletteQualitative10: colors } = (options.theme as any).styleSheet
const liquidStyle = () => {
const thresholdArr = liquidThreshold.split(',')
let index = 0
thresholdArr.forEach((v, i) => {
if (options.percent > parseFloat(v) / 100) {
index = i + 1
}
})
return {
fill: colors[index % colors.length],
stroke: colors[index % colors.length]
}
}
return { ...options, liquidStyle }
}
}
return options
}
setupDefaultOptions(chart: ChartObj): ChartObj {
chart.customAttr.label = {
...chart.customAttr.label,
@ -162,7 +189,12 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
}
protected setupOptions(chart: Chart, options: LiquidOptions): LiquidOptions {
return flow(this.configTheme, this.configMisc, this.configLabel)(chart, options)
return flow(
this.configTheme,
this.configMisc,
this.configLabel,
this.configThreshold
)(chart, options)
}
constructor() {
super('liquid', DEFAULT_LIQUID_DATA)

View File

@ -0,0 +1,138 @@
import { useI18n } from '@/hooks/web/useI18n'
import {
L7ChartView,
L7Config,
L7DrawConfig,
L7Wrapper
} from '@/views/chart/components/js/panel/types/impl/l7'
import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/map/common'
import { flow, parseJson } from '@/views/chart/components/js/util'
import { deepCopy } from '@/utils/utils'
import { GaodeMap } from '@antv/l7-maps'
import { Scene } from '@antv/l7-scene'
import { LineLayer } from '@antv/l7-layers'
import { queryMapKeyApi } from '@/api/setting/sysParameter'
const { t } = useI18n()
/**
* 流向地图
*/
export class FlowMap extends L7ChartView<Scene, L7Config> {
properties: EditorProperty[] = [
'background-overall-component',
'basic-style-selector',
'title-selector'
]
propertyInner: EditorPropertyInner = {
...MAP_EDITOR_PROPERTY_INNER,
'basic-style-selector': ['mapStyle', 'zoom']
}
axis: AxisType[] = ['xAxis', 'xAxisExt', 'filter']
axisConfig: AxisConfig = {
xAxis: {
name: `起点经纬度 / ${t('chart.dimension')}`,
type: 'd',
limit: 2
},
xAxisExt: {
name: `终点经纬度 / ${t('chart.dimension')}`,
type: 'd',
limit: 2
}
}
constructor() {
super('flow-map', [])
}
async drawChart(drawOption: L7DrawConfig<L7Config>) {
const { chart, container } = drawOption
const xAxis = deepCopy(chart.xAxis)
const xAxisExt = deepCopy(chart.xAxisExt)
let basicStyle
let miscStyle
if (chart.customAttr) {
basicStyle = parseJson(chart.customAttr).basicStyle
miscStyle = parseJson(chart.customAttr).misc
}
const flowLineStyle = {
type: miscStyle.mapLineType,
size: miscStyle.mapLineWidth,
animate: miscStyle.mapLineAnimate,
animateDuration: miscStyle.mapLineAnimateDuration,
gradient: miscStyle.mapLineGradient,
sourceColor: miscStyle.mapLineSourceColor,
targetColor: miscStyle.mapLineTargetColor,
alpha: basicStyle.alpha
}
const mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
const key = await this.getMapKey()
// 底层
const scene = new Scene({
id: container,
logoVisible: false,
map: new GaodeMap({
token: key ?? undefined,
style: mapStyle,
pitch: miscStyle.mapPitch,
zoom: 2.5
})
})
if (xAxis?.length < 2 || xAxisExt?.length < 2) {
return new L7Wrapper(scene, undefined)
}
const config: L7Config = new LineLayer({
name: 'line',
blend: 'normal',
autoFit: true
})
.source(chart.data?.tableRow ? chart.data.tableRow : [], {
parser: {
type: 'json',
x: xAxis[0].dataeaseName,
y: xAxis[1].dataeaseName,
x1: xAxisExt[0].dataeaseName,
y1: xAxisExt[1].dataeaseName
}
})
.size(flowLineStyle.size)
.shape(flowLineStyle.type)
.animate({
enable: flowLineStyle.animate,
duration: flowLineStyle.animateDuration,
interval: 1,
trailLength: 1
})
if (flowLineStyle.gradient) {
config.style({
sourceColor: flowLineStyle.sourceColor,
targetColor: flowLineStyle.targetColor,
opacity: flowLineStyle.alpha / 100
})
} else {
config
.style({
opacity: flowLineStyle.alpha / 100
})
.color(flowLineStyle.sourceColor)
}
this.configZoomButton(chart, scene)
return new L7Wrapper(scene, config)
}
getMapKey = async () => {
const key = 'online-map-key'
if (!localStorage.getItem(key)) {
await queryMapKeyApi().then(res => localStorage.setItem(key, res.data))
}
return localStorage.getItem(key)
}
setupDefaultOptions(chart: ChartObj): ChartObj {
chart.customAttr.misc.mapLineAnimate = true
return chart
}
protected setupOptions(chart: Chart, config: L7Config): L7Config {
return flow(this.configEmptyDataStrategy)(chart, config)
}
}

View File

@ -4,9 +4,10 @@ import {
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import { ScatterOptions, Scatter as G2Scatter } from '@antv/g2plot/esm/plots/scatter'
import { flow, parseJson } from '../../../util'
import { valueFormatter } from '../../../formatter'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { useI18n } from '@/hooks/web/useI18n'
import { isEmpty } from 'lodash-es'
import { isEmpty, map } from 'lodash-es'
import { cloneDeep, defaultTo } from 'lodash-es'
const { t } = useI18n()
/**
@ -119,58 +120,40 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
if (!chart.data?.data) {
return
}
const { colorFieldObj, sizeFieldObj, xFieldObj, yFieldObj } = this.getFieldObject(chart)
if (!xFieldObj.id || !yFieldObj.id || yFieldObj.id === xFieldObj.id) {
return
}
const data: any[] = []
// 根据指标字段对数据列表进行分组
const groupedData = chart.data?.data
?.filter(item => item['category'] != null)
.reduce((result, item) => {
;(result[item['field']] = result[item['field']] || []).push(item)
return result
}, {})
// 维度字段数据分组
chart.data?.data
?.filter(item => item['category'] === null)
.forEach(item => {
;(groupedData[colorFieldObj.name] = groupedData[colorFieldObj.name] || []).push(
item['field']
)
})
// 去掉groupedData每个key中集合的对象重复项
Object.keys(groupedData).forEach(key => {
groupedData[key] = Array.from(this.getUniqueObjects(groupedData[key]))
// data
const sourceData: Array<any> = cloneDeep(chart.data.data)
const data1 = defaultTo(sourceData[0]?.data, [])
const data2 = defaultTo(sourceData[1]?.data, [])
const data3 = defaultTo(sourceData[2]?.data, [])
const xData = data1.map(item => {
return {
...item,
id: item.quotaList[0]?.id,
field: item.field,
value: item.value
}
})
// 一个指标字段的数据长度视为数据长度也就是有多少数据
const dataLength = chart.data?.data.length / chart.data?.fields.length
for (let index = 0; index < dataLength; index++) {
const tmpData = {
dimensionList: groupedData[xFieldObj.name][index].dimensionList,
quotaList: groupedData[xFieldObj.name][index].quotaList,
[xFieldObj.name]: groupedData[xFieldObj.name][index].value
const yData = data2.map(item => {
return {
...item,
id: item.quotaList[0]?.id,
field: item.field,
value: item.value
}
if (groupedData[yFieldObj.name]) {
tmpData[yFieldObj.name] = groupedData[yFieldObj.name][index].value
})
const eData = data3.map(item => {
return {
...item,
id: item.quotaList[0]?.id,
field: item.field,
value: item.value
}
if (
groupedData[sizeFieldObj.name] &&
sizeFieldObj.name !== yFieldObj.name &&
sizeFieldObj.name !== xFieldObj.name
) {
tmpData[sizeFieldObj.name] = groupedData[sizeFieldObj.name]?.[index].value
}
if (groupedData[colorFieldObj.name]) {
tmpData[colorFieldObj.name] = groupedData[colorFieldObj.name][index]
}
data.push(tmpData)
}
})
// x轴基准线 默认值
const xValues = data.map(item => item[xFieldObj.name])
const xValues = xData.map(item => item.value)
const xBaseline = ((Math.max(...xValues) + Math.min(...xValues)) / 2).toFixed()
// y轴基准线 默认值
const yValues = data.map(item => item[yFieldObj.name])
const yValues = yData.map(item => item.value)
const yBaseline = ((Math.max(...yValues) + Math.min(...yValues)) / 2).toFixed()
const defaultBaselineQuadrant = {
...chart.customAttr['quadrant']
@ -181,15 +164,25 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
defaultBaselineQuadrant.xBaseline = xBaseline
defaultBaselineQuadrant.yBaseline = yBaseline
}
const colorField = colorFieldObj.name ? { colorField: colorFieldObj.name } : {}
const data = map(defaultTo(xData, []), d => {
return {
...d,
yAxis: d.value,
quotaList: d.quotaList
.concat(yData.find(item => item.field === d.field)?.quotaList)
.concat(eData.find(item => item.field === d.field)?.quotaList),
yAxisExt: yData.find(item => item.field === d.field)?.value,
extBubble: eData.find(item => item.field === d.field)?.value
}
})
const baseOptions: ScatterOptions = {
...colorField,
colorField: 'field',
quadrant: {
...defaultBaselineQuadrant
},
data: data,
xField: xFieldObj.name,
yField: yFieldObj.name,
xField: 'yAxis',
yField: 'yAxisExt',
appendPadding: 30,
pointStyle: {
fillOpacity: 0.8,
@ -208,12 +201,11 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
protected configBasicStyle(chart: Chart, options: ScatterOptions): ScatterOptions {
const customAttr = parseJson(chart.customAttr)
const basicStyle = customAttr.basicStyle
const extBubbleObj = { id: chart.extBubble[0]?.id, name: chart.extBubble[0]?.['originName'] }
if (chart.extBubble?.length) {
return {
...options,
size: [4, 30],
sizeField: extBubbleObj.name,
sizeField: 'extBubble',
shape: basicStyle.scatterSymbol
}
}
@ -296,7 +288,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
fontSize: l.fontSize
},
content: datum => {
return datum[chart.xAxis[0]?.['originName']]
return datum['name']
},
layout: [{ type: 'limit-in-shape' }]
}
@ -326,35 +318,47 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
pre[next['seriesId']] = next
return pre
}, {}) as Record<string, SeriesFormatter>
const optionsData = cloneDeep(options.data)
const tooltip: ScatterOptions['tooltip'] = {
showTitle: true,
title: (_title, datum) => {
return datum?.[xAxisTitle['originName']]
return datum?.['name']
},
customItems(originalItems) {
if (!tooltipAttr.seriesTooltipFormatter?.length) {
return originalItems
}
const result = []
originalItems
?.filter(i => i.name !== xAxisTitle['originName'])
.forEach(item => {
Object.keys(formatterMap).forEach(key => {
if (formatterMap[key]['originName'] === item.name) {
const formatter = formatterMap[key]
if (formatter) {
const value =
formatter.groupType === 'q'
? valueFormatter(parseFloat(item.value as string), formatter.formatterCfg)
: item.value
const name = isEmpty(formatter.chartShowName)
? formatter.name
: formatter.chartShowName
result.push({ color: item.color, name, value })
}
originalItems.forEach(item => {
Object.keys(formatterMap).forEach(key => {
if (key.endsWith(item.name)) {
const formatter = formatterMap[key]
if (formatter) {
const value =
formatter.groupType === 'q'
? valueFormatter(parseFloat(item.value as string), formatter.formatterCfg)
: item.value
const name = isEmpty(formatter.chartShowName)
? formatter.name
: formatter.chartShowName
result.push({ color: item.color, name, value })
}
})
}
})
})
const dynamicTooltipValue = optionsData.find(
d => d.field === originalItems[0]['title']
)?.dynamicTooltipValue
if (dynamicTooltipValue.length > 0) {
dynamicTooltipValue.forEach(dy => {
const q = tooltipAttr.seriesTooltipFormatter.filter(i => i.id === dy.fieldId)
if (q && q.length > 0) {
const value = valueFormatter(parseFloat(dy.value as string), q[0].formatterCfg)
const name = isEmpty(q[0].chartShowName) ? q[0].name : q[0].chartShowName
result.push({ color: 'grey', name, value })
}
})
}
return result
}
}

View File

@ -26,6 +26,7 @@ import type { Plot as L7Plot, PlotOptions } from '@antv/l7plot/dist/esm'
import { Zoom } from '@antv/l7'
import { createL7Icon } from '@antv/l7-component/es/utils/icon'
import { DOM } from '@antv/l7-utils'
import { Scene } from '@antv/l7-scene'
export function getPadding(chart: Chart): number[] {
if (chart.drill) {
@ -1023,7 +1024,7 @@ class CustomZoom extends Zoom {
this['updateDisabled']()
}
}
export function configL7Zoom(chart: Chart, plot: L7Plot<PlotOptions>) {
export function configL7Zoom(chart: Chart, plot: L7Plot<PlotOptions> | Scene) {
const { basicStyle } = parseJson(chart.customAttr)
if (
(basicStyle.suspension === false && basicStyle.showZoom === undefined) ||
@ -1031,14 +1032,15 @@ export function configL7Zoom(chart: Chart, plot: L7Plot<PlotOptions>) {
) {
return
}
plot.once('loaded', () => {
const plotScene = plot instanceof Scene ? plot : plot.scene
plotScene.once('loaded', () => {
const zoomOptions = {
initZoom: plot.scene.getZoom(),
center: plot.scene.getCenter(),
initZoom: plotScene.getZoom(),
center: plotScene.getCenter(),
buttonColor: basicStyle.zoomButtonColor,
buttonBackground: basicStyle.zoomBackground
} as any
plot.scene.addControl(new CustomZoom(zoomOptions))
plotScene.addControl(new CustomZoom(zoomOptions))
})
}

View File

@ -0,0 +1,95 @@
import { Scene } from '@antv/l7-scene'
import {
AntVAbstractChartView,
AntVDrawOptions,
ChartLibraryType,
ChartWrapper
} from '@/views/chart/components/js/panel/types'
import { cloneDeep } from 'lodash-es'
import { parseJson } from '@/views/chart/components/js/util'
import { ILayer } from '@antv/l7plot'
import { configL7Zoom } from '@/views/chart/components/js/panel/common/common_antv'
export type L7DrawConfig<P> = AntVDrawOptions<P>
export interface L7Config extends ILayer {
handleConfig?: (arg0: Scene) => void
[key: string]: string | any
}
export class L7Wrapper<
O extends L7Config | Array<L7Config>,
S extends Scene
> extends ChartWrapper<S> {
private readonly config: O | Array<O>
private readonly scene: S | null = null
constructor(scene: S, l7config: O | Array<O> | undefined) {
super()
this.chartInstance = scene
this.config = l7config
this.scene = scene
}
destroy = () => {
if (!this.chartInstance) {
return
}
this.chartInstance?.destroy()
}
render = () => {
if (this.scene && this.config) {
this.scene.on('loaded', () => {
if (Array.isArray(this.config)) {
this.config?.forEach(p => {
this.handleConfig(p)
})
} else {
this.handleConfig(this.config)
}
})
}
}
handleConfig = (config: L7Config) => {
if (config.handleConfig) {
config.handleConfig?.(this.scene)
} else {
this.scene.addLayer(config)
}
}
}
export abstract class L7ChartView<
S extends Scene,
O extends L7Config
> extends AntVAbstractChartView {
public abstract drawChart(drawOption: L7DrawConfig<O>): L7Wrapper<O, S> | any
protected configEmptyDataStrategy(chart: Chart, options: O): O {
const { functionCfg } = parseJson(chart.senior)
const emptyDataStrategy = functionCfg.emptyDataStrategy
if (!emptyDataStrategy || emptyDataStrategy === 'breakLine') {
return options
}
const data = cloneDeep(options.sourceOption.data)
if (emptyDataStrategy === 'setZero') {
data.forEach(item => {
item.value === null && (item.value = 0)
})
}
if (emptyDataStrategy === 'ignoreData') {
for (let i = data.length - 1; i >= 0; i--) {
if (data[i].value === null) {
data.splice(i, 1)
}
}
}
options.sourceOption.data = data
return options
}
protected configZoomButton(chart: Chart, plot: S) {
configL7Zoom(chart, plot)
}
protected constructor(name: string, defaultData: any[]) {
super(ChartLibraryType.L7, name, defaultData)
}
protected abstract setupOptions(chart: Chart, options: O): O
}

View File

@ -11,6 +11,7 @@ export enum ChartRenderType {
export enum ChartLibraryType {
G2_PLOT = 'g2plot',
L7_PLOT = 'l7plot',
L7 = 'l7',
ECHARTS = 'echarts',
S2 = 's2',
RICH_TEXT = 'rich-text',

View File

@ -17,9 +17,11 @@ import { customAttrTrans, customStyleTrans, recursionTransObj } from '@/utils/ca
import { deepCopy } from '@/utils/utils'
import { trackBarStyleCheck } from '@/utils/canvasUtils'
import { useEmitt } from '@/hooks/web/useEmitt'
import { L7ChartView } from '@/views/chart/components/js/panel/types/impl/l7'
const dvMainStore = dvMainStoreWithOut()
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc } = storeToRefs(dvMainStore)
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, embeddedCallBack } =
storeToRefs(dvMainStore)
const { emitter } = useEmitt()
const props = defineProps({
element: {
@ -54,7 +56,13 @@ const props = defineProps({
}
})
const emit = defineEmits(['onChartClick', 'onDrillFilters', 'onJumpClick', 'resetLoading'])
const emit = defineEmits([
'onPointClick',
'onChartClick',
'onDrillFilters',
'onJumpClick',
'resetLoading'
])
const { view, showPosition, scale, terminal } = toRefs(props)
@ -109,7 +117,7 @@ const calcData = async (view, callback) => {
callback?.()
})
} else {
if (['bubble-map', 'map'].includes(view.type)) {
if (['bubble-map', 'map', 'flow-map'].includes(view.type)) {
await renderChart(view, callback)
}
callback?.()
@ -134,6 +142,9 @@ const renderChart = async (view, callback?) => {
case ChartLibraryType.L7_PLOT:
await renderL7Plot(chart, chartView as L7PlotChartView<any, any>, callback)
break
case ChartLibraryType.L7:
await renderL7(chart, chartView as L7ChartView<any, any>, callback)
break
case ChartLibraryType.G2_PLOT:
renderG2Plot(chart, chartView as G2PlotChartView<any, any>)
callback?.()
@ -192,9 +203,34 @@ const renderL7Plot = async (chart: ChartObj, chartView: L7PlotChartView<any, any
}, 500)
}
let mapL7Timer: number
const renderL7 = async (chart: ChartObj, chartView: L7ChartView<any, any>, callback) => {
mapL7Timer && clearTimeout(mapL7Timer)
mapL7Timer = setTimeout(async () => {
myChart?.destroy()
myChart = await chartView.drawChart({
chartObj: myChart,
container: containerId,
chart: chart,
action
})
myChart?.render()
callback?.()
emit('resetLoading')
}, 500)
}
const pointClickTrans = () => {
if (embeddedCallBack.value === 'yes') {
trackClick('pointClick')
}
}
const action = param => {
//
state.pointParam = param.data
//
pointClickTrans()
//
state.linkageActiveParam = {
category: state.pointParam.data.category ? state.pointParam.data.category : 'NO_DATA',
name: state.pointParam.data.name ? state.pointParam.data.name : 'NO_DATA'
@ -246,7 +282,18 @@ const trackClick = trackAction => {
quotaList: quotaList
}
const clickParams = {
option: 'pointClick',
name: checkName,
viewId: view.value.id,
dimensionList: state.pointParam.data.dimensionList,
quotaList: quotaList
}
switch (trackAction) {
case 'pointClick':
emit('onPointClick', clickParams)
break
case 'linkageAndDrill':
dvMainStore.addViewTrackFilter(linkageParam)
emit('onChartClick', param)
@ -307,7 +354,7 @@ defineExpose({
})
let resizeObserver
const TOLERANCE = 0.01
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map']
const RESIZE_MONITOR_CHARTS = ['map', 'bubble-map', 'flow-map']
onMounted(() => {
const containerDom = document.getElementById(containerId)
const { offsetWidth, offsetHeight } = containerDom

View File

@ -29,7 +29,7 @@ import { useEmitt } from '@/hooks/web/useEmitt'
import { trackBarStyleCheck } from '@/utils/canvasUtils'
const dvMainStore = dvMainStoreWithOut()
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, canvasStyleData } =
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc, canvasStyleData, embeddedCallBack } =
storeToRefs(dvMainStore)
const { emitter } = useEmitt()
@ -66,7 +66,7 @@ const props = defineProps({
}
})
const emit = defineEmits(['onChartClick', 'onDrillFilters', 'onJumpClick'])
const emit = defineEmits(['onPointClick', 'onChartClick', 'onDrillFilters', 'onJumpClick'])
const { view, showPosition, scale, terminal } = toRefs(props)
@ -241,10 +241,17 @@ const handleCurrentChange = pageNum => {
const chart = { ...view.value, chartExtRequest: extReq }
calcData(chart, null, false)
}
const pointClickTrans = () => {
if (embeddedCallBack.value === 'yes') {
trackClick('pointClick')
}
}
const action = param => {
//
state.pointParam = param
//
pointClickTrans()
//
if (trackMenu.value.length < 2) {
//
trackClick(trackMenu.value[0])
@ -282,7 +289,19 @@ const trackClick = trackAction => {
quotaList: state.pointParam.data.quotaList,
sourceType: state.pointParam.data.sourceType
}
const clickParams = {
option: 'pointClick',
name: state.pointParam.data.name,
viewId: view.value.id,
dimensionList: state.pointParam.data.dimensionList,
quotaList: state.pointParam.data.quotaList
}
switch (trackAction) {
case 'pointClick':
emit('onPointClick', clickParams)
break
case 'linkageAndDrill':
dvMainStore.addViewTrackFilter(linkageParam)
emit('onChartClick', param)

View File

@ -3,6 +3,7 @@ import { useI18n } from '@/hooks/web/useI18n'
import ChartComponentG2Plot from './components/ChartComponentG2Plot.vue'
import DeIndicator from '@/custom-component/indicator/DeIndicator.vue'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useEmbedded } from '@/store/modules/embedded'
import { XpackComponent } from '@/components/plugin'
import {
computed,
@ -47,6 +48,9 @@ const { t } = useI18n()
const dvMainStore = dvMainStoreWithOut()
let innerRefreshTimer = null
const appStore = useAppStoreWithOut()
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
const isIframe = computed(() => appStore.getIsIframe)
const { nowPanelJumpInfo, publicLinkStatus, dvInfo, curComponent, canvasStyleData, mobileInPc } =
storeToRefs(dvMainStore)
@ -101,7 +105,6 @@ const props = defineProps({
})
const dynamicAreaId = ref('')
const { view, showPosition, element, active, searchCount, scale } = toRefs(props)
const appStore = useAppStoreWithOut()
const titleShow = computed(
() =>
@ -176,6 +179,7 @@ const resultCount = computed(() => {
return canvasStyleData.value.dashboard?.resultCount || null
})
const embeddedStore = useEmbedded()
//
const buildInnerRefreshTimer = (
refreshViewEnable = false,
@ -238,7 +242,6 @@ watch([() => curComponent.value], () => {
})
}
})
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
const chartExtRequest = shallowRef(null)
provide('chartExtRequest', chartExtRequest)
@ -281,6 +284,21 @@ const drillJump = (index: number) => {
calcData(view.value)
}
const onPointClick = param => {
try {
console.info('de_inner_params send')
const msg = {
type: 'de_inner_params',
sourceDvId: dvInfo.value.id,
sourceViewId: view.value.id,
message: Base64.encode(param)
}
window.parent.postMessage(msg, '*')
} catch (e) {
console.warn('de_inner_params send error')
}
}
const chartClick = param => {
//
const xIds = view.value.xAxis.map(ele => ele.id)
@ -332,7 +350,7 @@ const windowsJump = (url, jumpType) => {
try {
const newWindow = window.open(url, jumpType)
initOpenHandler(newWindow)
if (jumpType === '_self') {
if (jumpType === '_self' && !embeddedStore.baseUrl) {
location.reload()
}
} catch (e) {
@ -549,7 +567,7 @@ const chartAreaShow = computed(() => {
return true
}
if (view.value.customAttr.map.id) {
const MAP_CHARTS = ['map', 'bubble-map']
const MAP_CHARTS = ['map', 'bubble-map', 'flow-map']
if (MAP_CHARTS.includes(view.value.type)) {
return true
}
@ -707,9 +725,12 @@ const titleIconStyle = computed(() => {
:view="view"
:show-position="showPosition"
:element="element"
v-else-if="showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT)"
v-else-if="
showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT, ChartLibraryType.L7)
"
ref="chartComponent"
@onChartClick="chartClick"
@onPointClick="onPointClick"
@onDrillFilters="onDrillFilters"
@onJumpClick="jumpClick"
@resetLoading="() => (loading = false)"
@ -721,6 +742,7 @@ const titleIconStyle = computed(() => {
:element="element"
v-else-if="showChartView(ChartLibraryType.S2)"
ref="chartComponent"
@onPointClick="onPointClick"
@onChartClick="chartClick"
@onDrillFilters="onDrillFilters"
@onJumpClick="jumpClick"

View File

@ -3,6 +3,7 @@ import { onMounted, reactive, ref, toRefs, watch, nextTick, computed } from 'vue
import { copyResource, deleteLogic, ResourceOrFolder } from '@/api/visualization/dataVisualization'
import { ElIcon, ElMessage, ElMessageBox, ElScrollbar } from 'element-plus-secondary'
import { Icon } from '@/components/icon-custom'
import { useEmitt } from '@/hooks/web/useEmitt'
import { HandleMore } from '@/components/handle-more'
import DeResourceGroupOpt from '@/views/common/DeResourceGroupOpt.vue'
import { useEmbedded } from '@/store/modules/embedded'
@ -283,11 +284,23 @@ const operation = (cmd: string, data: BusiTreeNode, nodeType: string) => {
curCanvasType.value === 'dataV'
? `#/dvCanvas?opt=copy&pid=${params.pid}&dvId=${data.data}`
: `#/dashboard?opt=copy&pid=${params.pid}&resourceId=${data.data}`
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
embeddedStore.clearState()
embeddedStore.setPid(params.pid as string)
embeddedStore.setOpt('copy')
if (curCanvasType.value === 'dataV') {
embeddedStore.setDvId(data.data)
} else {
embeddedStore.setResourceId(data.data)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'Dashboard'
)
return
}
const newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
const newWindow = window.open(baseUrl, '_blank')
initOpenHandler(newWindow)
})
} else {
@ -306,14 +319,22 @@ const addOperation = (
const baseUrl =
curCanvasType.value === 'dataV' ? '#/dvCanvas?opt=create' : '#/dashboard?opt=create'
let newWindow = null
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
embeddedStore.clearState()
embeddedStore.setOpt('create')
if (data?.id) {
embeddedStore.setPid(data?.id as string)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'Dashboard'
)
return
}
if (data?.id) {
newWindow = window.open(embeddedBaseUrl + baseUrl + `&pid=${data.id}`, '_blank')
newWindow = window.open(baseUrl + `&pid=${data.id}`, '_blank')
} else {
newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
newWindow = window.open(baseUrl, '_blank')
}
initOpenHandler(newWindow)
} else if (cmd === 'newFromTemplate') {
@ -334,11 +355,20 @@ function createNewObject() {
const resourceEdit = resourceId => {
const baseUrl = curCanvasType.value === 'dataV' ? '#/dvCanvas?dvId=' : '#/dashboard?resourceId='
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
embeddedStore.clearState()
if (curCanvasType.value === 'dataV') {
embeddedStore.setDvId(resourceId)
} else {
embeddedStore.setResourceId(resourceId)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'Dashboard'
)
return
}
const newWindow = window.open(embeddedBaseUrl + baseUrl + resourceId, '_blank')
const newWindow = window.open(baseUrl + resourceId, '_blank')
initOpenHandler(newWindow)
}
@ -354,14 +384,23 @@ const resourceCreateFinish = templateData => {
? '#/dvCanvas?opt=create&createType=template'
: '#/dashboard?opt=create&createType=template'
let newWindow = null
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
embeddedStore.clearState()
embeddedStore.setOpt('create')
embeddedStore.setCreateType('template')
if (state.templateCreatePid) {
embeddedStore.setPid(state.templateCreatePid as unknown as string)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
curCanvasType.value === 'dataV' ? 'VisualizationEditor' : 'Dashboard'
)
return
}
if (state.templateCreatePid) {
newWindow = window.open(embeddedBaseUrl + baseUrl + `&pid=${state.templateCreatePid}`, '_blank')
newWindow = window.open(baseUrl + `&pid=${state.templateCreatePid}`, '_blank')
} else {
newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
newWindow = window.open(baseUrl, '_blank')
}
initOpenHandler(newWindow)
}

View File

@ -12,7 +12,7 @@ import { useRequestStoreWithOut } from '@/store/modules/request'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useMoveLine } from '@/hooks/web/useMoveLine'
import { Icon } from '@/components/icon-custom'
import { download2AppTemplate, downloadCanvas } from '@/utils/imgUtils'
import { download2AppTemplate, downloadCanvas, downloadCanvas2 } from '@/utils/imgUtils'
const dvMainStore = dvMainStoreWithOut()
const previewCanvasContainer = ref(null)
@ -98,7 +98,7 @@ const downloadH2 = type => {
downloadStatus.value = true
nextTick(() => {
const vueDom = previewCanvasContainer.value.querySelector('.canvas-container')
downloadCanvas(type, vueDom, state.dvInfo.name, () => {
downloadCanvas2(type, vueDom, state.dvInfo.name, () => {
downloadStatus.value = false
})
})

View File

@ -34,7 +34,7 @@ const canvasCacheOutRef = ref(null)
const eventCheck = e => {
if (e.key === 'panel-weight' && !compareStorage(e.oldValue, e.newValue)) {
const resourceId = embeddedStore.resourceId || router.currentRoute.value.query.resourceId
const { opt } = router.currentRoute.value.query
const opt = embeddedStore.opt || router.currentRoute.value.query.opt
if (!(opt && opt === 'create')) {
check(wsCache.get('panel-weight'), resourceId as string, 4)
}
@ -137,6 +137,13 @@ const initLocalCanvasData = () => {
dvInfo.value.pid = sourcePid
setTimeout(() => {
snapshotStore.recordSnapshotCache()
//
if (opt === 'copy') {
// 使
setTimeout(() => {
snapshotStore.recordSnapshotCache('renderChart')
}, 1000)
}
}, 1500)
}
})
@ -156,7 +163,10 @@ onMounted(async () => {
window.addEventListener('storage', eventCheck)
const resourceId = embeddedStore.resourceId || router.currentRoute.value.query.resourceId
const pid = embeddedStore.pid || router.currentRoute.value.query.pid
const { opt, createType, templateParams } = router.currentRoute.value.query
const opt = embeddedStore.opt || router.currentRoute.value.query.opt
const createType = embeddedStore.createType || router.currentRoute.value.query.createType
const templateParams =
embeddedStore.templateParams || router.currentRoute.value.query.templateParams
const checkResult = await checkPer(resourceId)
if (!checkResult) {
return
@ -269,12 +279,15 @@ onUnmounted(() => {
>
<DbCanvasAttr></DbCanvasAttr>
</dv-sidebar>
<div v-show="viewEditorShow" style="height: 100%">
<div
v-show="viewEditorShow"
style="height: 100%"
:class="{ 'preview-aside': editMode === 'preview' }"
>
<view-editor
:themes="'light'"
:view="canvasViewInfo[curComponent ? curComponent.id : 'default']"
:dataset-tree="state.datasetTree"
:class="{ 'preview-aside': editMode === 'preview' }"
></view-editor>
</div>
<dv-sidebar

View File

@ -105,11 +105,12 @@ let p = null
const XpackLoaded = () => p(true)
onMounted(async () => {
await new Promise(r => (p = r))
const { dvId, dvType } = router.currentRoute.value.query
const { dvId, dvType, callBackFlag } = router.currentRoute.value.query
if (dvId) {
loadCanvasDataAsync(dvId, dvType)
return
}
dvMainStore.setEmbeddedCallBack(callBackFlag || 'no')
dvMainStore.setPublicLinkStatus(props.publicLinkStatus)
})

View File

@ -13,7 +13,7 @@ import { useRequestStoreWithOut } from '@/store/modules/request'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useMoveLine } from '@/hooks/web/useMoveLine'
import { Icon } from '@/components/icon-custom'
import { download2AppTemplate, downloadCanvas } from '@/utils/imgUtils'
import { download2AppTemplate, downloadCanvas, downloadCanvas2 } from '@/utils/imgUtils'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -86,7 +86,7 @@ const download = type => {
downloadStatus.value = true
setTimeout(() => {
const vueDom = previewCanvasContainer.value.querySelector('.canvas-container')
downloadCanvas(type, vueDom, state.dvInfo.name, () => {
downloadCanvas2(type, vueDom, state.dvInfo.name, () => {
downloadStatus.value = false
})
}, 200)

View File

@ -43,7 +43,7 @@ const embeddedStore = useEmbedded()
const { wsCache } = useCache()
const eventCheck = e => {
if (e.key === 'screen-weight' && !compareStorage(e.oldValue, e.newValue)) {
const { opt } = router.currentRoute.value.query
const opt = embeddedStore.opt || router.currentRoute.value.query.opt
if (!(opt && opt === 'create')) {
check(
wsCache.get('screen-weight'),
@ -270,7 +270,10 @@ onMounted(async () => {
}
const dvId = embeddedStore.dvId || router.currentRoute.value.query.dvId
const pid = embeddedStore.pid || router.currentRoute.value.query.pid
const { opt, createType, templateParams } = router.currentRoute.value.query
const templateParams =
embeddedStore.templateParams || router.currentRoute.value.query.templateParams
const createType = embeddedStore.createType || router.currentRoute.value.query.createType
const opt = embeddedStore.opt || router.currentRoute.value.query.opt
const checkResult = await checkPer(dvId)
if (!checkResult) {
return
@ -429,12 +432,15 @@ eventBus.on('handleNew', handleNew)
>
<canvas-attr></canvas-attr>
</dv-sidebar>
<div v-show="viewsPropertiesShow" style="height: 100%">
<div
v-show="viewsPropertiesShow"
style="height: 100%"
:class="{ 'preview-aside': editMode === 'preview' }"
>
<editor
:view="canvasViewInfo[curComponent ? curComponent.id : 'default']"
themes="dark"
:dataset-tree="state.datasetTree"
:class="{ 'preview-aside': editMode === 'preview' }"
></editor>
</div>
</div>

View File

@ -327,6 +327,10 @@ const expChangeHandler = exp => {
})
}
const beforeClose = async done => {
if (!shareEnable.value) {
done()
return
}
const pwdValid = validatePwdFormat()
const uuidValid = await validateUuid()
if (pwdValid && uuidValid) {

View File

@ -167,6 +167,10 @@ watch(
}
)
const hideShare = async () => {
if (!shareEnable.value) {
popoverVisible.value = false
return
}
const pwdValid = validatePwdFormat()
const uuidValid = await validateUuid()
if (pwdValid && uuidValid) {

View File

@ -174,6 +174,7 @@ import { imgUrlTrans } from '@/utils/imgUtils'
import CategoryTemplateV2 from '@/views/template-market/component/CategoryTemplateV2.vue'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { XpackComponent } from '@/components/plugin'
import { useEmitt } from '@/hooks/web/useEmitt'
import { Base64 } from 'js-base64'
const { t } = useI18n()
const { wsCache } = useCache()
@ -434,14 +435,29 @@ const apply = template => {
'&templateParams=' +
encodeURIComponent(Base64.encode(JSON.stringify(templateTemplate)))
let newWindow = null
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
embeddedStore.clearState()
embeddedStore.setCreateType('template')
embeddedStore.setTemplateParams(
encodeURIComponent(Base64.encode(JSON.stringify(templateTemplate)))
)
embeddedStore.setOpt('create')
if (state.pid) {
embeddedStore.setPid(state.pid)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
['dataV', 'SCREEN'].includes(state.dvCreateForm.nodeType)
? 'VisualizationEditor'
: 'Dashboard'
)
return
}
if (state.pid) {
newWindow = window.open(embeddedBaseUrl + baseUrl + `&pid=${state.pid}`, '_blank')
newWindow = window.open(baseUrl + `&pid=${state.pid}`, '_blank')
} else {
newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
newWindow = window.open(baseUrl, '_blank')
}
initOpenHandler(newWindow)
}

View File

@ -313,7 +313,7 @@ const confirmCustomTime = () => {
}
watch(searchTable, val => {
state.tableData = tableList.filter(ele => ele.name.toLowerCase().includes(val.toLowerCase()))
state.tableData = tableList.filter(ele => ele.tableName.toLowerCase().includes(val.toLowerCase()))
})
const editeSave = () => {
const union = []

View File

@ -693,7 +693,10 @@ defineExpose({
<el-option v-for="item in schemas" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item :label="t('datasource.extra_params')">
<el-form-item
:label="t('datasource.extra_params')"
v-if="form.configuration.urlType !== 'jdbcUrl'"
>
<el-input
:placeholder="t('common.inputText') + t('datasource.extra_params')"
v-model="form.configuration.extraParams"
@ -760,7 +763,7 @@ defineExpose({
/>
</el-form-item>
<el-form-item
v-if="form.type == 'oracle'"
v-if="form.type == 'oracle' && form.configuration.urlType !== 'jdbcUrl'"
:label="t('datasource.connection_mode')"
prop="configuration.connectionType"
>

View File

@ -1073,7 +1073,7 @@ const getMenuList = (val: boolean) => {
placement="top"
>
<el-button
@click.stop="createDataset(scope.row.name)"
@click.stop="createDataset(scope.row.tableName)"
text
v-permission="['dataset']"
>

@ -1 +1 @@
Subproject commit eac4bdc0d387c44088dbc7585a09f61b8e45ea12
Subproject commit 690faa2e32bf71f23fdda04a83c3b7a1ed0d368c

53
pom.xml
View File

@ -11,15 +11,19 @@
<modules>
<module>sdk</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
</parent>
<properties>
<dataease.version>2.6.1</dataease.version>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
<spring-boot.version>3.0.2</spring-boot.version>
<maven-compiler-plugin.version>3.9.0</maven-compiler-plugin.version>
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
<spring-cloud.version>2023.0.1</spring-cloud.version>
<spring-boot.version>3.3.0</spring-boot.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
@ -30,17 +34,13 @@
<antlr.version>3.5.2</antlr.version>
<java-jwt.version>3.12.1</java-jwt.version>
<velocity.version>2.3</velocity.version>
<maven.antrun.version>3.1.0</maven.antrun.version>
<ehcache.version>3.10.8</ehcache.version>
<bcprov.version>1.78</bcprov.version>
<junit.version>4.13.2</junit.version>
<httpclient.version>4.5.14</httpclient.version>
<httpcore.version>4.4.16</httpcore.version>
<easyexcel.version>3.3.2</easyexcel.version>
<maven.surefire.version>3.1.2</maven.surefire.version>
<maven.clean.version>3.2.0</maven.clean.version>
<easyexcel.version>3.3.4</easyexcel.version>
<flatten-maven.version>1.3.0</flatten-maven.version>
<maven.resources.version>3.3.1</maven.resources.version>
<maven.exec.version>3.1.0</maven.exec.version>
<guava.version>33.0.0-jre</guava.version>
<selenium-java.version>4.19.1</selenium-java.version>
@ -48,6 +48,7 @@
<mysql-connector-j.version>8.2.0</mysql-connector-j.version>
<itextpdf.version>8.0.4</itextpdf.version>
<flexmark.version>0.62.2</flexmark.version>
<mybatis-spring.version>3.0.3</mybatis-spring.version>
</properties>
<dependencyManagement>
@ -73,6 +74,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
@ -144,35 +150,6 @@
<version>${spring-boot.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven.antrun.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven.surefire.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven.clean.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>${maven.resources.version}</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>

View File

@ -0,0 +1,41 @@
package io.dataease.api.exportCenter;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.api.exportCenter.vo.ExportTaskDTO;
import io.dataease.auth.DeApiPath;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
import static io.dataease.constant.AuthResourceEnum.DATASOURCE;
@Tag(name = "数据导出中心")
@ApiSupport(order = 971)
@DeApiPath(value = "/exportCenter", rt = DATASOURCE)
public interface ExportCenterApi {
@PostMapping("/exportTasks/{status}")
public List<ExportTaskDTO> exportTasks(@PathVariable String status) ;
@GetMapping("/delete/{id}")
public void delete(@PathVariable String id);
@PostMapping("/delete")
public void delete(@RequestBody List<String> ids);
@PostMapping("/deleteAll/{type}")
public void deleteAll(@PathVariable String type);
@GetMapping("/download/{id}")
public void download(@PathVariable String id, HttpServletResponse response) throws Exception ;
@PostMapping("/retry/{id}")
public void retry(@PathVariable String id);
}

View File

@ -0,0 +1,33 @@
package io.dataease.api.exportCenter.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
@Data
public class ExportTaskDTO {
@JsonSerialize(using= ToStringSerializer.class)
private String id;
@JsonSerialize(using= ToStringSerializer.class)
private Long userId;
private String fileName;
private Double fileSize;
private String fileSizeUnit;
private String exportFrom;
private String exportStatus;
private String exportFromType;
private Long exportTime;
private String exportProgress;
private String exportMachineName;
private String exportFromName;
}

View File

@ -93,8 +93,14 @@ public class HttpClientUtil {
httpGet.addHeader(key, header.get(key));
}
HttpResponse response = httpClient.execute(httpGet);
int statusCode = response.getStatusLine().getStatusCode();
return statusCode <= 400;
if (response.getStatusLine().getStatusCode() >= 400) {
String msg = EntityUtils.toString(response.getEntity(), config.getCharset());
if (StringUtils.isEmpty(msg)) {
msg = "StatusCode: " + response.getStatusLine().getStatusCode();
}
throw new Exception(msg);
}
return true;
} catch (Exception e) {
logger.error("HttpClient查询失败", e);
throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient查询失败: " + e.getMessage());