Merge branch 'dev-v2' into pr@dev-v2_dzz

This commit is contained in:
dataeaseShu 2024-01-22 17:14:01 +08:00
commit 7169c34d45
60 changed files with 2077 additions and 271 deletions

View File

@ -7,4 +7,7 @@ assignees: xuwei-fit2cloud, yayanpei-fit2cloud
---
DataEase 版本:
**请描述您的需求或者改进建议.**

View File

@ -3,7 +3,7 @@ name: Bug 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[Bug]"
labels: 状态:待处理
assignees: BBchicken-9527, Shenguobin0102, zrfit
assignees: BBchicken-9527, Shenguobin0102
---

View File

@ -208,7 +208,7 @@ public class ChartDataManage {
return emptyChartViewDTO(view);
}
break;
case "text":
case "indicator":
case "gauge":
case "liquid":
xAxis = new ArrayList<>();
@ -496,7 +496,7 @@ public class ChartDataManage {
ExtWhere2Str.extWhere2sqlOjb(sqlMeta, extFilterList, transFields(allFields));
WhereTree2Str.transFilterTrees(sqlMeta, rowPermissionsTree, transFields(allFields));
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "text", "gauge", "liquid")) {
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "indicator", "gauge", "liquid")) {
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields));
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
} else if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {
@ -684,7 +684,7 @@ public class ChartDataManage {
mapChart = ChartDataBuild.transScatterData(xAxis, yAxis, view, data, extBubble, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "radar")) {
mapChart = ChartDataBuild.transRadarChartData(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "text")
} else if (StringUtils.containsIgnoreCase(view.getType(), "indicator")
|| StringUtils.containsIgnoreCase(view.getType(), "gauge")
|| StringUtils.equalsIgnoreCase("liquid", view.getType())) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
@ -708,7 +708,7 @@ public class ChartDataManage {
mapChart = ChartDataBuild.transScatterDataAntV(xAxis, yAxis, view, data, extBubble, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "radar")) {
mapChart = ChartDataBuild.transRadarChartDataAntV(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "text")
} else if (StringUtils.containsIgnoreCase(view.getType(), "indicator")
|| StringUtils.containsIgnoreCase(view.getType(), "gauge")
|| StringUtils.equalsIgnoreCase("liquid", view.getType())) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
@ -719,6 +719,10 @@ public class ChartDataManage {
} else {
mapChart = ChartDataBuild.transChartDataAntV(xAxis, yAxis, view, data, isDrill);
}
} else if (StringUtils.equalsIgnoreCase(view.getRender(), "custom")) {
if (StringUtils.containsIgnoreCase(view.getType(), "indicator")) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
}
}
// table组件明细表也用于导出数据
Map<String, Object> mapTableNormal = null;
@ -1220,7 +1224,7 @@ public class ChartDataManage {
return new ArrayList<String[]>();
}
break;
case "text":
case "indicator":
case "gauge":
case "liquid":
xAxis = new ArrayList<>();
@ -1276,7 +1280,7 @@ public class ChartDataManage {
Table2SQLObj.table2sqlobj(sqlMeta, null, "(" + sql + ")");
WhereTree2Str.transFilterTrees(sqlMeta, rowPermissionsTree, transFields(allFields));
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "text", "gauge", "liquid")) {
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "indicator", "gauge", "liquid")) {
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields));
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
} else if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {

View File

@ -6,7 +6,6 @@ import io.dataease.api.dataset.dto.DatasetTableDTO;
import io.dataease.api.dataset.dto.SqlVariableDetails;
import io.dataease.api.dataset.union.DatasetGroupInfoDTO;
import io.dataease.api.dataset.vo.DataSetBarVO;
import io.dataease.commons.constants.OptConstants;
import io.dataease.constant.LogOT;
import io.dataease.constant.LogST;
import io.dataease.dataset.manage.DatasetGroupManage;
@ -44,11 +43,13 @@ public class DatasetTreeServer implements DatasetTreeApi {
return datasetGroupManage.save(dto, false);
}
@DeLog(id = "#p0.id", ot = LogOT.MODIFY, st = LogST.DATASET)
@Override
public DatasetNodeDTO move(DatasetGroupInfoDTO dto) throws Exception {
return datasetGroupManage.move(dto);
}
@DeLog(id = "#p0", ot = LogOT.DELETE, st = LogST.DATASET)
@Override
public void delete(Long id) {
datasetGroupManage.delete(id);

View File

@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import io.dataease.api.ds.vo.DatasourceDTO;
import io.dataease.commons.constants.OptConstants;
import io.dataease.constant.DataSourceType;
import io.dataease.constant.LogOT;
import io.dataease.constant.LogST;
import io.dataease.datasource.dao.auto.entity.CoreDatasource;
import io.dataease.datasource.dao.auto.mapper.CoreDatasourceMapper;
import io.dataease.datasource.dao.ext.mapper.DataSourceExtMapper;
@ -12,6 +14,7 @@ import io.dataease.datasource.dao.ext.po.DataSourceNodePO;
import io.dataease.datasource.dto.DatasourceNodeBO;
import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.log.DeLog;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
import io.dataease.operation.manage.CoreOptRecentManage;
@ -25,7 +28,6 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class DataSourceManage {
@ -71,12 +73,14 @@ public class DataSourceManage {
return TreeUtils.mergeTree(nodes, BusiNodeVO.class, false);
}
@DeLog(id = "#p0.id", pid = "#p0.pid", ot = LogOT.CREATE, st = LogST.DATASOURCE)
@XpackInteract(value = "datasourceResourceTree", before = false)
public void innerSave(CoreDatasource coreDatasource) {
coreDatasourceMapper.insert(coreDatasource);
coreOptRecentManage.saveOpt(coreDatasource.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE,OptConstants.OPT_TYPE.NEW);
coreOptRecentManage.saveOpt(coreDatasource.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE, OptConstants.OPT_TYPE.NEW);
}
@DeLog(id = "#p0.id", ot = LogOT.MODIFY, st = LogST.DATASOURCE)
@XpackInteract(value = "datasourceResourceTree", before = false)
public void innerEdit(CoreDatasource coreDatasource) {
UpdateWrapper<CoreDatasource> updateWrapper = new UpdateWrapper<>();
@ -84,9 +88,10 @@ public class DataSourceManage {
coreDatasource.setUpdateTime(System.currentTimeMillis());
coreDatasource.setUpdateBy(AuthUtils.getUser().getUserId());
coreDatasourceMapper.update(coreDatasource, updateWrapper);
coreOptRecentManage.saveOpt(coreDatasource.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE,OptConstants.OPT_TYPE.UPDATE);
coreOptRecentManage.saveOpt(coreDatasource.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE, OptConstants.OPT_TYPE.UPDATE);
}
@DeLog(id = "#p0.id", ot = LogOT.MODIFY, st = LogST.DATASOURCE)
@XpackInteract(value = "datasourceResourceTree", before = false)
public void innerEditStatus(CoreDatasource coreDatasource) {
UpdateWrapper<CoreDatasource> updateWrapper = new UpdateWrapper<>();
@ -94,6 +99,7 @@ public class DataSourceManage {
coreDatasourceMapper.update(coreDatasource, updateWrapper);
}
@DeLog(id = "#p0.id", ot = LogOT.MODIFY, st = LogST.DATASOURCE)
@XpackInteract(value = "datasourceResourceTree", before = false)
public void move(DatasourceDTO dataSourceDTO) {
Long id = dataSourceDTO.getId();
@ -106,6 +112,6 @@ public class DataSourceManage {
sourceData.setPid(dataSourceDTO.getPid());
sourceData.setName(dataSourceDTO.getName());
coreDatasourceMapper.updateById(sourceData);
coreOptRecentManage.saveOpt(sourceData.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE,OptConstants.OPT_TYPE.UPDATE);
coreOptRecentManage.saveOpt(sourceData.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE, OptConstants.OPT_TYPE.UPDATE);
}
}

View File

@ -14,6 +14,8 @@ import io.dataease.api.ds.vo.*;
import io.dataease.commons.constants.TaskStatus;
import io.dataease.commons.utils.CommonThreadPool;
import io.dataease.constant.DataSourceType;
import io.dataease.constant.LogOT;
import io.dataease.constant.LogST;
import io.dataease.dataset.dto.DatasourceSchemaDTO;
import io.dataease.dataset.manage.DatasetDataManage;
import io.dataease.dataset.utils.TableUtils;
@ -35,6 +37,7 @@ import io.dataease.i18n.Translator;
import io.dataease.job.sechedule.CheckDsStatusJob;
import io.dataease.job.sechedule.ScheduleManager;
import io.dataease.license.config.XpackInteract;
import io.dataease.log.DeLog;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
@ -136,7 +139,7 @@ public class DatasourceServer implements DatasourceApi {
dataSourceManage.move(dataSourceDTO);
}
case "rename" -> {
if(StringUtils.isEmpty(dataSourceDTO.getName())){
if (StringUtils.isEmpty(dataSourceDTO.getName())) {
DEException.throwException("名称不能为空!");
}
CoreDatasource datasource = datasourceMapper.selectById(dataSourceDTO.getId());
@ -550,6 +553,7 @@ public class DatasourceServer implements DatasourceApi {
return datasourceDTO;
}
@DeLog(id = "#p0", ot = LogOT.DELETE, st = LogST.DATASOURCE)
@Override
@XpackInteract(value = "datasourceResourceTree", before = false)
public void delete(Long datasourceId) throws DEException {

View File

@ -6,6 +6,7 @@ import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.List;
@ -25,7 +26,7 @@ public class Mysql extends DatasourceConfiguration {
.replace("DATABASE", getDataBase().trim());
} else {
for (String illegalParameter : illegalParameters) {
if (getExtraParams().toLowerCase().contains(illegalParameter.toLowerCase())) {
if (getExtraParams().toLowerCase().contains(illegalParameter.toLowerCase()) || URLDecoder.decode(getExtraParams()).contains(illegalParameter.toLowerCase())) {
DEException.throwException("Illegal parameter: " + illegalParameter);
}
}

View File

@ -44,7 +44,7 @@ public class ResourceMonitorManage {
if (CollectionUtils.isNotEmpty(dsFreeResources)) {
List<PerMonitorNodeBO> dsBos = dsFreeResources.stream().map(node -> {
PerMonitorNodeBO bo = BeanUtils.copyBean(new PerMonitorNodeBO(), node);
bo.setLeaf(StringUtils.equals("folder", node.getType()));
bo.setLeaf(!StringUtils.equals("folder", node.getType()));
return bo;
}).collect(Collectors.toList());
List<PerMonitorNodeBO> dsTree = TreeUtils.mergeTree(dsBos, PerMonitorNodeBO.class, false);
@ -55,7 +55,7 @@ public class ResourceMonitorManage {
if (CollectionUtils.isNotEmpty(datasetFreeResources)) {
List<PerMonitorNodeBO> datasetBos = datasetFreeResources.stream().map(node -> {
PerMonitorNodeBO bo = BeanUtils.copyBean(new PerMonitorNodeBO(), node);
bo.setLeaf(StringUtils.equals("folder", node.getNodeType()));
bo.setLeaf(!StringUtils.equals("folder", node.getNodeType()));
return bo;
}).collect(Collectors.toList());
List<PerMonitorNodeBO> datasetTree = TreeUtils.mergeTree(datasetBos, PerMonitorNodeBO.class, false);
@ -69,7 +69,7 @@ public class ResourceMonitorManage {
List<VisualFreeResource> freeResource = entry.getValue();
List<PerMonitorNodeBO> visualBos = freeResource.stream().map(node -> {
PerMonitorNodeBO bo = BeanUtils.copyBean(new PerMonitorNodeBO(), node);
bo.setLeaf(StringUtils.equals("folder", node.getNodeType()));
bo.setLeaf(!StringUtils.equals("folder", node.getNodeType()));
return bo;
}).collect(Collectors.toList());
result.put(convertBusiFlag(entry.getKey()), TreeUtils.mergeTree(visualBos, PerMonitorNodeBO.class, false));

View File

@ -17,8 +17,10 @@ import io.dataease.chart.manage.ChartViewManege;
import io.dataease.commons.constants.DataVisualizationConstants;
import io.dataease.commons.constants.OptConstants;
import io.dataease.constant.CommonConstants;
import io.dataease.constant.LogOT;
import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.log.DeLog;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
import io.dataease.operation.manage.CoreOptRecentManage;
@ -89,6 +91,17 @@ public class DataVisualizationServer implements DataVisualizationApi {
@Resource
private VisualizationWatermarkMapper watermarkMapper;
@DeLog(id = "#p0", ot = LogOT.READ, stExp = "#p1")
@Override
public DataVisualizationVO findCopyResource(Long dvId, String busiFlag) {
DataVisualizationVO result = findById(dvId, busiFlag);
if(result !=null && result.getPid() == -1){
return result;
}else{
return null;
}
}
@Override
@XpackInteract(value = "dataVisualizationServer", original = true)
public DataVisualizationVO findById(Long dvId, String busiFlag) {
@ -111,6 +124,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
return null;
}
@DeLog(id = "#p0.id", pid = "#p0.pid", ot = LogOT.CREATE, stExp = "#p0.type")
@Override
@Transactional
public String saveCanvas(DataVisualizationBaseRequest request) {
@ -123,11 +137,13 @@ public class DataVisualizationServer implements DataVisualizationApi {
visualizationInfo.setSelfWatermarkStatus(0);
}
Long newDvId = coreVisualizationManage.innerSave(visualizationInfo);
request.setId(newDvId);
//保存视图信
chartDataManage.saveChartViewFromVisualization(request.getComponentData(), newDvId, request.getCanvasViewInfo());
return newDvId.toString();
}
@DeLog(id = "#p0.id", ot = LogOT.MODIFY, stExp = "#p0.type")
@Override
@Transactional
public void updateCanvas(DataVisualizationBaseRequest request) {
@ -168,6 +184,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
* @Description: 更新基础信息
* 为什么单独接口1.基础信息更新频繁数据且数据载量较小2.防止出现更新过多信息的情况造成视图的误删等操作
*/
@DeLog(id = "#p0.id", ot = LogOT.MODIFY, stExp = "#p0.type")
@Override
@Transactional
public void updateBase(DataVisualizationBaseRequest request) {
@ -181,6 +198,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
/**
* @Description: 逻辑删除可视化信息将delete_flag 置为0
*/
@DeLog(id = "#p0", ot = LogOT.DELETE, stExp = "#p1")
@Transactional
@Override
public void deleteLogic(Long dvId, String busiFlag) {
@ -193,6 +211,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
return coreVisualizationManage.tree(request);
}
@DeLog(id = "#p0.id", pid = "#p0.pid", ot = LogOT.MODIFY, stExp = "#p0.type")
@Transactional
@Override
public void move(DataVisualizationBaseRequest request) {

View File

@ -16,6 +16,10 @@ export interface Panel {
updateBy: string
}
export const findCopyResource = async (dvId, busiFlag): Promise<IResponse> => {
return request.get({ url: '/dataVisualization/findCopyResource/' + dvId + '/' + busiFlag })
}
export const findById = async (dvId, busiFlag): Promise<IResponse> => {
let busiFlagResult = busiFlag
if (!busiFlagResult) {

View File

@ -0,0 +1,6 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5912 36.9635C24.5912 37.2397 24.3673 37.4635 24.0912 37.4635H20.8083C20.5322 37.4635 20.3083 37.2397 20.3083 36.9635V18.729H15.5C15.2239 18.729 15 18.5052 15 18.229V14.8324C15 14.5491 15.2355 14.3239 15.5188 14.3171C16.0773 14.3035 16.6182 14.2336 17.1414 14.1073C17.8452 13.9147 18.4685 13.5983 19.0114 13.1582C19.5744 12.6905 20.0469 12.0853 20.429 11.3425C20.7584 10.702 20.9906 9.92865 21.1257 9.0223C21.1633 8.77004 21.3761 8.57772 21.6312 8.57772H24.0912C24.3673 8.57772 24.5912 8.80157 24.5912 9.07772V36.9635Z" fill="#3370FF"/>
<path d="M31.118 19.6781C30.8466 19.6781 30.6241 19.4616 30.6234 19.1902C30.6195 17.7256 30.7625 16.3614 31.0522 15.0977C31.374 13.6671 31.8565 12.4292 32.5 11.3838C33.1434 10.3109 33.9477 9.48556 34.9128 8.90784C35.8981 8.30261 37.0241 8 38.2909 8C39.256 8 40.1709 8.20633 41.0355 8.61898C41.9202 9.03164 42.6944 9.62311 43.3579 10.3934C44.0215 11.1637 44.5442 12.1128 44.9263 13.2407C45.3284 14.3686 45.5295 15.6341 45.5295 17.0371C45.5295 18.4952 45.3586 19.7469 45.0168 20.7923C44.6749 21.8377 44.2225 22.773 43.6595 23.5983C43.0965 24.3961 42.4531 25.1252 41.7292 25.7854C41.0255 26.4457 40.3117 27.1059 39.5878 27.7662C38.8639 28.3989 38.1602 29.0867 37.4765 29.8294C36.7929 30.5722 36.1897 31.4388 35.6669 32.4292H45.1502C45.4263 32.4292 45.6502 32.653 45.6502 32.9292V36.9635C45.6502 37.2397 45.4263 37.4635 45.1502 37.4635H30.1474C30.1474 35.7854 30.3183 34.3274 30.6602 33.0894C31.0221 31.8514 31.5047 30.751 32.1079 29.7882C32.7111 28.7978 33.4149 27.89 34.2191 27.0646C35.0435 26.2393 35.9082 25.4003 36.813 24.5475C37.2755 24.1073 37.7681 23.6671 38.2909 23.227C38.8137 22.7593 39.2862 22.2503 39.7084 21.7001C40.1508 21.1499 40.5127 20.5309 40.7942 19.8432C41.0958 19.1554 41.2467 18.3714 41.2467 17.4911C41.2467 16.088 40.945 15.0014 40.3418 14.2311C39.7587 13.4333 39.0047 13.0344 38.0798 13.0344C37.4564 13.0344 36.9236 13.2407 36.4812 13.6534C36.059 14.0385 35.7171 14.5612 35.4557 15.2215C35.1944 15.8542 35.0033 16.5695 34.8827 17.3673C34.8039 17.9709 34.756 18.5746 34.7389 19.1782C34.7311 19.4542 34.508 19.6781 34.2319 19.6781H31.118Z" fill="#3370FF"/>
<path d="M55.6501 20.8189C55.6501 20.5193 55.912 20.2881 56.2115 20.2953C56.5059 20.3023 56.8114 20.2892 57.128 20.2558C57.6508 20.2008 58.1333 20.0495 58.5757 19.8019C59.0382 19.5268 59.4102 19.1417 59.6917 18.6465C59.9933 18.1513 60.1441 17.4911 60.1441 16.6657C60.1441 15.4278 59.8425 14.4787 59.2393 13.8184C58.636 13.1582 57.9423 12.8281 57.1581 12.8281C56.0723 12.8281 55.248 13.3232 54.6849 14.3136C54.2051 15.1647 53.9452 16.2199 53.9051 17.4793C53.8963 17.758 53.6727 17.9862 53.394 17.9862H50.3428C50.0613 17.9862 49.8346 17.7539 49.8492 17.4729C49.9158 16.1886 50.1001 15.0117 50.4021 13.9422C50.764 12.7043 51.2567 11.6451 51.88 10.7648C52.5234 9.88446 53.2875 9.21045 54.1722 8.74278C55.0569 8.24759 56.0422 8 57.128 8C57.9725 8 58.817 8.17882 59.6615 8.53645C60.506 8.86658 61.26 9.37552 61.9236 10.0633C62.6072 10.751 63.1602 11.5901 63.5824 12.5805C64.0047 13.5708 64.2158 14.7125 64.2158 16.0055C64.2158 17.4085 63.9645 18.6465 63.4618 19.7194C62.9792 20.7923 62.2453 21.5213 61.26 21.9065V21.989C62.4263 22.3466 63.3411 23.1169 64.0047 24.2999C64.6682 25.4828 65 26.8996 65 28.5502C65 30.0633 64.7788 31.4113 64.3365 32.5942C63.9142 33.7772 63.3411 34.7675 62.6173 35.5653C61.8934 36.3631 61.059 36.9684 60.1139 37.381C59.1689 37.7937 58.1836 38 57.1581 38C55.9718 38 54.886 37.7662 53.9008 37.2985C52.9356 36.8308 52.1112 36.1568 51.4276 35.2765C50.7439 34.3686 50.2111 33.2682 49.829 31.9752C49.503 30.8106 49.332 29.4898 49.316 28.0127C49.313 27.7389 49.5365 27.5186 49.8103 27.5186H52.9009C53.172 27.5186 53.3925 27.7347 53.4103 28.0052C53.4497 28.6028 53.5327 29.1834 53.6595 29.7469C53.8203 30.4347 54.0516 31.0399 54.3532 31.5626C54.6548 32.0578 55.0268 32.4567 55.4691 32.7593C55.9316 33.0619 56.4745 33.2132 57.0978 33.2132C58.063 33.2132 58.8773 32.8143 59.5409 32.0165C60.2044 31.1912 60.5362 30.077 60.5362 28.674C60.5362 27.5736 60.3753 26.7345 60.0536 26.1568C59.752 25.5791 59.3599 25.1664 58.8773 24.9188C58.3947 24.6437 57.8619 24.4924 57.2788 24.4649C56.8931 24.4272 56.5168 24.4025 56.15 24.3906C55.874 24.3816 55.6501 24.1585 55.6501 23.8824V20.8189Z" fill="#3370FF"/>
<path d="M64.5 43H15.5C15.2239 43 15 43.2239 15 43.5V48.125C15 48.4011 15.2239 48.625 15.5 48.625H64.5C64.7761 48.625 65 48.4011 65 48.125V43.5C65 43.2239 64.7761 43 64.5 43Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,6 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5912 36.9635C24.5912 37.2397 24.3673 37.4635 24.0912 37.4635H20.8083C20.5322 37.4635 20.3083 37.2397 20.3083 36.9635V18.729H15.5C15.2239 18.729 15 18.5052 15 18.229V14.8324C15 14.5491 15.2355 14.3239 15.5188 14.3171C16.0773 14.3035 16.6182 14.2336 17.1414 14.1073C17.8452 13.9147 18.4685 13.5983 19.0114 13.1582C19.5744 12.6905 20.0469 12.0853 20.429 11.3425C20.7584 10.702 20.9906 9.92865 21.1257 9.0223C21.1633 8.77004 21.3761 8.57772 21.6312 8.57772H24.0912C24.3673 8.57772 24.5912 8.80157 24.5912 9.07772V36.9635Z" fill="#3370FF"/>
<path d="M31.118 19.6781C30.8466 19.6781 30.6241 19.4616 30.6234 19.1902C30.6195 17.7256 30.7625 16.3614 31.0522 15.0977C31.374 13.6671 31.8565 12.4292 32.5 11.3838C33.1434 10.3109 33.9477 9.48556 34.9128 8.90784C35.8981 8.30261 37.0241 8 38.2909 8C39.256 8 40.1709 8.20633 41.0355 8.61898C41.9202 9.03164 42.6944 9.62311 43.3579 10.3934C44.0215 11.1637 44.5442 12.1128 44.9263 13.2407C45.3284 14.3686 45.5295 15.6341 45.5295 17.0371C45.5295 18.4952 45.3586 19.7469 45.0168 20.7923C44.6749 21.8377 44.2225 22.773 43.6595 23.5983C43.0965 24.3961 42.4531 25.1252 41.7292 25.7854C41.0255 26.4457 40.3117 27.1059 39.5878 27.7662C38.8639 28.3989 38.1602 29.0867 37.4765 29.8294C36.7929 30.5722 36.1897 31.4388 35.6669 32.4292H45.1502C45.4263 32.4292 45.6502 32.653 45.6502 32.9292V36.9635C45.6502 37.2397 45.4263 37.4635 45.1502 37.4635H30.1474C30.1474 35.7854 30.3183 34.3274 30.6602 33.0894C31.0221 31.8514 31.5047 30.751 32.1079 29.7882C32.7111 28.7978 33.4149 27.89 34.2191 27.0646C35.0435 26.2393 35.9082 25.4003 36.813 24.5475C37.2755 24.1073 37.7681 23.6671 38.2909 23.227C38.8137 22.7593 39.2862 22.2503 39.7084 21.7001C40.1508 21.1499 40.5127 20.5309 40.7942 19.8432C41.0958 19.1554 41.2467 18.3714 41.2467 17.4911C41.2467 16.088 40.945 15.0014 40.3418 14.2311C39.7587 13.4333 39.0047 13.0344 38.0798 13.0344C37.4564 13.0344 36.9236 13.2407 36.4812 13.6534C36.059 14.0385 35.7171 14.5612 35.4557 15.2215C35.1944 15.8542 35.0033 16.5695 34.8827 17.3673C34.8039 17.9709 34.756 18.5746 34.7389 19.1782C34.7311 19.4542 34.508 19.6781 34.2319 19.6781H31.118Z" fill="#3370FF"/>
<path d="M55.6501 20.8189C55.6501 20.5193 55.912 20.2881 56.2115 20.2953C56.5059 20.3023 56.8114 20.2892 57.128 20.2558C57.6508 20.2008 58.1333 20.0495 58.5757 19.8019C59.0382 19.5268 59.4102 19.1417 59.6917 18.6465C59.9933 18.1513 60.1441 17.4911 60.1441 16.6657C60.1441 15.4278 59.8425 14.4787 59.2393 13.8184C58.636 13.1582 57.9423 12.8281 57.1581 12.8281C56.0723 12.8281 55.248 13.3232 54.6849 14.3136C54.2051 15.1647 53.9452 16.2199 53.9051 17.4793C53.8963 17.758 53.6727 17.9862 53.394 17.9862H50.3428C50.0613 17.9862 49.8346 17.7539 49.8492 17.4729C49.9158 16.1886 50.1001 15.0117 50.4021 13.9422C50.764 12.7043 51.2567 11.6451 51.88 10.7648C52.5234 9.88446 53.2875 9.21045 54.1722 8.74278C55.0569 8.24759 56.0422 8 57.128 8C57.9725 8 58.817 8.17882 59.6615 8.53645C60.506 8.86658 61.26 9.37552 61.9236 10.0633C62.6072 10.751 63.1602 11.5901 63.5824 12.5805C64.0047 13.5708 64.2158 14.7125 64.2158 16.0055C64.2158 17.4085 63.9645 18.6465 63.4618 19.7194C62.9792 20.7923 62.2453 21.5213 61.26 21.9065V21.989C62.4263 22.3466 63.3411 23.1169 64.0047 24.2999C64.6682 25.4828 65 26.8996 65 28.5502C65 30.0633 64.7788 31.4113 64.3365 32.5942C63.9142 33.7772 63.3411 34.7675 62.6173 35.5653C61.8934 36.3631 61.059 36.9684 60.1139 37.381C59.1689 37.7937 58.1836 38 57.1581 38C55.9718 38 54.886 37.7662 53.9008 37.2985C52.9356 36.8308 52.1112 36.1568 51.4276 35.2765C50.7439 34.3686 50.2111 33.2682 49.829 31.9752C49.503 30.8106 49.332 29.4898 49.316 28.0127C49.313 27.7389 49.5365 27.5186 49.8103 27.5186H52.9009C53.172 27.5186 53.3925 27.7347 53.4103 28.0052C53.4497 28.6028 53.5327 29.1834 53.6595 29.7469C53.8203 30.4347 54.0516 31.0399 54.3532 31.5626C54.6548 32.0578 55.0268 32.4567 55.4691 32.7593C55.9316 33.0619 56.4745 33.2132 57.0978 33.2132C58.063 33.2132 58.8773 32.8143 59.5409 32.0165C60.2044 31.1912 60.5362 30.077 60.5362 28.674C60.5362 27.5736 60.3753 26.7345 60.0536 26.1568C59.752 25.5791 59.3599 25.1664 58.8773 24.9188C58.3947 24.6437 57.8619 24.4924 57.2788 24.4649C56.8931 24.4272 56.5168 24.4025 56.15 24.3906C55.874 24.3816 55.6501 24.1585 55.6501 23.8824V20.8189Z" fill="#3370FF"/>
<path d="M64.5 43H15.5C15.2239 43 15 43.2239 15 43.5V48.125C15 48.4011 15.2239 48.625 15.5 48.625H64.5C64.7761 48.625 65 48.4011 65 48.125V43.5C65 43.2239 64.7761 43 64.5 43Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 2.5C4 1.94772 4.44772 1.5 5 1.5H14.8608C14.9949 1.5 15.1234 1.55387 15.2174 1.64951L19.8566 6.36941C19.9485 6.46292 20 6.58879 20 6.7199V21.5C20 22.0523 19.5523 22.5 19 22.5H5C4.44772 22.5 4 22.0523 4 21.5V2.5Z" fill="#3370FF"/>
<path d="M15 1.51978C15.0817 1.54345 15.1567 1.58778 15.2174 1.64952L19.8566 6.36942C19.8946 6.40806 19.9256 6.45223 19.949 6.50001H16.1351C15.5082 6.50001 15 5.99179 15 5.36488V1.51978Z" fill="#2B5FD9"/>
<path d="M8.05554 9.27271C7.7794 9.27271 7.55554 9.49656 7.55554 9.77271V10.5909C7.55554 10.867 7.7794 11.0909 8.05554 11.0909H15.9444C16.2206 11.0909 16.4444 10.867 16.4444 10.5909V9.77271C16.4444 9.49656 16.2206 9.27271 15.9444 9.27271H8.05554Z" fill="white"/>
<path d="M8.05554 13.8182C7.7794 13.8182 7.55554 14.042 7.55554 14.3182V15.1363C7.55554 15.4125 7.7794 15.6363 8.05554 15.6363H11.5C11.7761 15.6363 12 15.4125 12 15.1363V14.3182C12 14.042 11.7761 13.8182 11.5 13.8182H8.05554Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -133,7 +133,10 @@ const componentBackgroundStyle = computed(() => {
innerPadding,
borderRadius
} = config.value.commonBackground
const style = { padding: innerPadding + 'px', borderRadius: borderRadius + 'px' }
const style = {
padding: innerPadding * deepScale.value + 'px',
borderRadius: borderRadius + 'px'
}
let colorRGBA = ''
if (backgroundColorSelect && backgroundColor) {
colorRGBA = backgroundColor

View File

@ -141,7 +141,7 @@ const restore = () => {
cellWidth.value = canvasWidth / pcMatrixCount.value.x
cellHeight.value = canvasHeight / pcMatrixCount.value.y
scaleWidth.value = isMainCanvas(canvasId.value)
? scaleWidth.value * 1.5
? scaleWidth.value * 1.2
: outerScale.value * 100
} else {
changeRefComponentsSizeWithScale(

View File

@ -171,7 +171,7 @@ const state = reactive({
'richTextView',
'liquid',
'gauge',
'text',
'indicator',
'label',
'word-cloud',
'flow-map',
@ -181,7 +181,7 @@ const state = reactive({
'richTextView',
'liquid',
'gauge',
'text',
'indicator',
'label',
'word-cloud',
'flow-map',

View File

@ -102,6 +102,12 @@ onMounted(() => {
})
})
})
const stopEvent = e => {
if (e && e.code === 'Enter') {
e.stopPropagation()
e.preventDefault()
}
}
</script>
<template>
@ -231,6 +237,8 @@ onMounted(() => {
></el-option>
</el-select>
<el-input-number
@keydown="stopEvent"
@keyup="stopEvent"
v-else
size="middle"
style="width: 100%"

View File

@ -0,0 +1,373 @@
<script setup lang="ts">
import { getData } from '@/api/chart'
import { nextTick, ref, reactive, shallowRef, computed, CSSProperties, toRefs, watch } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { customAttrTrans, customStyleTrans, recursionTransObj } from '@/utils/canvasStyle'
import { deepCopy } from '@/utils/utils'
import { cloneDeep, defaultsDeep, defaultTo } from 'lodash-es'
import {
BASE_VIEW_CONFIG,
CHART_CONT_FAMILY_MAP,
DEFAULT_INDICATOR_NAME_STYLE,
DEFAULT_INDICATOR_STYLE
} from '@/views/chart/components/editor/util/chart'
import { valueFormatter } from '@/views/chart/components/js/formatter'
const props = defineProps({
view: {
type: Object,
default() {
return {
propValue: null
}
}
},
showPosition: {
type: String,
required: false,
default: 'canvas'
},
scale: {
type: Number,
required: false,
default: 1
},
terminal: {
type: String,
default: 'pc'
}
})
const { view, showPosition, scale, terminal } = toRefs(props)
const dvMainStore = dvMainStoreWithOut()
const errMsg = ref('')
const isError = ref(false)
const state = reactive({
data: null,
loading: false,
totalItems: 0
})
const chartData = shallowRef<Partial<Chart['data']>>({
fields: []
})
const resultObject = computed(() => {
const list = chartData.value?.series
if (list && list.length > 0) {
return list[0]
}
return undefined
})
const resultName = computed(() => {
return resultObject.value?.name
})
const result = computed(() => {
const list = resultObject.value?.data
let _result = undefined
if (list && list.length > 0) {
_result = list[0]
}
if (_result === null || _result === undefined) {
if (view.value.senior && view.value.senior?.functionCfg?.emptyDataStrategy === 'setZero') {
_result = 0
} else {
return '-'
}
}
return _result
})
const thresholdColor = computed(() => {
if (result.value === '-') {
return undefined
}
const value = result.value
let color = undefined
if (view.value.senior && view.value.senior.threshold?.labelThreshold?.length > 0) {
const senior = view.value.senior
for (let i = 0; i < senior.threshold.labelThreshold.length; i++) {
let flag = false
const t = senior.threshold.labelThreshold[i]
const tv = parseFloat(t.value)
if (t.term === 'eq') {
if (value === tv) {
color = t.color
flag = true
}
} else if (t.term === 'not_eq') {
if (value !== tv) {
color = t.color
flag = true
}
} else if (t.term === 'lt') {
if (value < tv) {
color = t.color
flag = true
}
} else if (t.term === 'gt') {
if (value > tv) {
color = t.color
flag = true
}
} else if (t.term === 'le') {
if (value <= tv) {
color = t.color
flag = true
}
} else if (t.term === 'ge') {
if (value >= tv) {
color = t.color
flag = true
}
} else if (t.term === 'between') {
const min = parseFloat(t.min)
const max = parseFloat(t.max)
if (min <= value && value <= max) {
color = t.color
flag = true
}
}
if (flag) {
break
} else if (i === senior.threshold.labelThreshold.length - 1) {
color = t.color
}
}
}
return color
})
const formattedResult = computed(() => {
let _result = result.value
if (_result === '-') {
return _result
}
//
if (view.value.yAxis && view.value.yAxis.length > 0 && view.value.yAxis[0].formatterCfg) {
return valueFormatter(_result, view.value.yAxis[0].formatterCfg)
}
return _result
})
const emit = defineEmits(['onChartClick', 'onDrillFilters', 'onJumpClick'])
const contentStyle = ref({
display: 'flex',
'flex-direction': 'column',
'align-items': 'center',
'justify-content': 'center',
height: '100%'
})
const indicatorClass = ref<CSSProperties>({
color: DEFAULT_INDICATOR_STYLE.color,
'font-size': DEFAULT_INDICATOR_STYLE.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[DEFAULT_INDICATOR_STYLE.fontFamily],
DEFAULT_INDICATOR_STYLE.fontFamily
),
'font-weight': DEFAULT_INDICATOR_STYLE.isBolder ? 'bold' : 'normal',
'font-style': DEFAULT_INDICATOR_STYLE.isItalic ? 'italic' : 'normal',
'letter-spacing': DEFAULT_INDICATOR_STYLE.letterSpace + 'px',
'text-shadow': DEFAULT_INDICATOR_STYLE.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
})
const indicatorSuffixClass = ref<CSSProperties>({
color: DEFAULT_INDICATOR_STYLE.suffixColor,
'font-size': DEFAULT_INDICATOR_STYLE.suffixFontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[DEFAULT_INDICATOR_STYLE.suffixFontFamily],
DEFAULT_INDICATOR_STYLE.suffixFontFamily
),
'font-weight': DEFAULT_INDICATOR_STYLE.suffixIsBolder ? 'bold' : 'normal',
'font-style': DEFAULT_INDICATOR_STYLE.suffixIsItalic ? 'italic' : 'normal',
'letter-spacing': DEFAULT_INDICATOR_STYLE.suffixLetterSpace + 'px',
'text-shadow': DEFAULT_INDICATOR_STYLE.suffixFontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
})
const showSuffix = ref<boolean>(DEFAULT_INDICATOR_STYLE.suffixEnable)
const suffixContent = ref('')
const indicatorNameShow = ref(false)
const indicatorNameClass = ref<CSSProperties>({
color: DEFAULT_INDICATOR_NAME_STYLE.color,
'font-size': DEFAULT_INDICATOR_NAME_STYLE.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[DEFAULT_INDICATOR_NAME_STYLE.fontFamily],
DEFAULT_INDICATOR_NAME_STYLE.fontFamily
),
'font-weight': DEFAULT_INDICATOR_NAME_STYLE.isBolder ? 'bold' : 'normal',
'font-style': DEFAULT_INDICATOR_NAME_STYLE.isItalic ? 'italic' : 'normal',
'letter-spacing': DEFAULT_INDICATOR_NAME_STYLE.letterSpace + 'px',
'text-shadow': DEFAULT_INDICATOR_NAME_STYLE.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
})
function setThresholdColor(_value, _color) {
if (_color === undefined) {
return
}
if (_value === '-') {
return
}
indicatorClass.value.color = _color
}
watch([result, thresholdColor], (value, oldValue) => {
const _value = value[0]
const _color = value[1]
setThresholdColor(_value, _color)
})
const renderChart = async view => {
if (!view) {
return
}
const chart = deepCopy({
...defaultsDeep(view, cloneDeep(BASE_VIEW_CONFIG)),
data: chartData.value
})
recursionTransObj(customAttrTrans, chart.customAttr, scale.value, terminal.value)
recursionTransObj(customStyleTrans, chart.customStyle, scale.value, terminal.value)
if (chart.customAttr) {
const customAttr = chart.customAttr
if (customAttr.indicator) {
switch (customAttr.indicator.hPosition) {
case 'left':
contentStyle.value['align-items'] = 'flex-start'
break
case 'right':
contentStyle.value['align-items'] = 'flex-end'
break
default:
contentStyle.value['align-items'] = 'center'
}
switch (customAttr.indicator.vPosition) {
case 'top':
contentStyle.value['justify-content'] = 'flex-start'
break
case 'bottom':
contentStyle.value['justify-content'] = 'flex-end'
break
default:
contentStyle.value['justify-content'] = 'center'
}
let color = customAttr.indicator.color
indicatorClass.value = {
color: color,
'font-size': customAttr.indicator.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[customAttr.indicator.fontFamily],
DEFAULT_INDICATOR_STYLE.fontFamily
),
'font-weight': customAttr.indicator.isBolder ? 'bold' : 'normal',
'font-style': customAttr.indicator.isItalic ? 'italic' : 'normal',
'letter-spacing': customAttr.indicator.letterSpace + 'px',
'text-shadow': customAttr.indicator.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
indicatorSuffixClass.value = {
color: customAttr.indicator.suffixColor,
'font-size': customAttr.indicator.suffixFontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[customAttr.indicator.suffixFontFamily],
DEFAULT_INDICATOR_STYLE.suffixFontFamily
),
'font-weight': customAttr.indicator.suffixIsBolder ? 'bold' : 'normal',
'font-style': customAttr.indicator.suffixIsItalic ? 'italic' : 'normal',
'letter-spacing': customAttr.indicator.suffixLetterSpace + 'px',
'text-shadow': customAttr.indicator.suffixFontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
showSuffix.value = customAttr.indicator.suffixEnable
suffixContent.value = defaultTo(customAttr.indicator.suffix, '')
}
if (customAttr.indicatorName && customAttr.indicatorName.show) {
indicatorNameShow.value = true
indicatorNameClass.value = {
color: customAttr.indicatorName.color,
'font-size': customAttr.indicatorName.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[customAttr.indicatorName.fontFamily],
DEFAULT_INDICATOR_NAME_STYLE.fontFamily
),
'font-weight': customAttr.indicatorName.isBolder ? 'bold' : 'normal',
'font-style': customAttr.indicatorName.isItalic ? 'italic' : 'normal',
'letter-spacing': customAttr.indicatorName.letterSpace + 'px',
'text-shadow': customAttr.indicatorName.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
} else {
indicatorNameShow.value = false
}
}
setThresholdColor(result.value, thresholdColor.value)
}
const calcData = (view, callback) => {
if (view.tableId || view['dataFrom'] === 'template') {
state.loading = true
isError.value = false
const v = JSON.parse(JSON.stringify(view))
getData(v)
.then(res => {
if (res.code && res.code !== 0) {
isError.value = true
errMsg.value = res.msg
} else {
chartData.value = res?.data as Partial<Chart['data']>
console.log(chartData.value)
emit('onDrillFilters', res?.drillFilters)
dvMainStore.setViewDataDetails(view.id, chartData.value)
renderChart(res)
}
callback?.()
})
.catch(() => {
callback?.()
})
} else {
if (view.type === 'map') {
renderChart(view)
}
callback?.()
}
}
defineExpose({
calcData,
renderChart
})
</script>
<template>
<div :style="contentStyle">
<div>
<span :style="indicatorClass">{{ formattedResult }}</span>
<span :style="indicatorSuffixClass" v-if="showSuffix">{{ suffixContent }}</span>
</div>
<div v-if="indicatorNameShow">
<span :style="indicatorNameClass">{{ resultName }}</span>
</div>
</div>
</template>
<style scoped lang="less"></style>

View File

@ -293,9 +293,29 @@ const reShow = () => {
editShow.value = false
nextTick(() => {
editShow.value = true
editCursor()
})
}
const editCursor = () => {
setTimeout(() => {
const myDiv = document.getElementById(tinymceId)
//
const range = document.createRange()
const sel = window.getSelection()
if (myDiv.childNodes) {
range.setStart(myDiv.childNodes[myDiv.childNodes.length - 1], 1)
range.collapse(false)
sel.removeAllRanges()
sel.addRange(range)
}
//
if (myDiv.focus) {
myDiv.focus()
}
}, 100)
}
const calcData = (view: Chart, callback) => {
isError.value = false
if (view.tableId || view['dataFrom'] === 'template') {

View File

@ -43,8 +43,8 @@ onMounted(() => {
:show-arrow="false"
popper-class="toolbox-top-popover"
placement="bottom-end"
width="112"
trigger="hover"
width="208"
trigger="click"
>
<top-doc-card
:span="12"
@ -66,10 +66,11 @@ onMounted(() => {
<style lang="less">
.toolbox-top-popover {
height: 82px;
min-width: 112px !important;
min-width: 208px !important;
padding: 16px !important;
display: flex;
.doc-card {
margin: auto !important;
margin: auto;
}
}
</style>

View File

@ -792,6 +792,7 @@ export default {
total: '共',
items: '条数据',
chart_liquid: '水波图',
chart_indicator: '指标卡',
drag_block_progress: '进度指示',
liquid_max: '目标值',
liquid_outline_border: '边框粗细',
@ -922,6 +923,9 @@ export default {
value_formatter_unit: '数量单位',
value_formatter_decimal_count: '小数位数',
value_formatter_suffix: '单位后缀',
indicator_suffix_placeholder: '请输入1-10个字符',
indicator_suffix: '后缀',
indicator_value: '指标值',
value_formatter_thousand_separator: '千分符',
value_formatter_example: '示例',
unit_none: '无',

View File

@ -32,6 +32,39 @@ declare interface ChartStyle {
}
}
declare interface ChartIndicatorStyle {
show: boolean
fontSize: string
color: string
hPosition: 'left' | 'center' | 'right'
vPosition: 'top' | 'center' | 'bottom'
isItalic: boolean
isBolder: boolean
fontFamily: string
letterSpace: string
fontShadow: boolean
suffixEnable: boolean
suffix: string
suffixFontSize: string
suffixColor: string
suffixIsItalic: boolean
suffixIsBolder: boolean
suffixFontFamily: string
suffixLetterSpace: string
suffixFontShadow: boolean
}
declare interface ChartIndicatorNameStyle {
show: boolean
fontSize: string
color: string
isItalic: boolean
isBolder: boolean
fontFamily: string
letterSpace: string
fontShadow: boolean
}
/**
* 标题样式设置
*/
@ -43,7 +76,7 @@ declare interface ChartTextStyle {
/**
* 字体大小
*/
fontSize: string
fontSize: number
/**
* 颜色
*/

View File

@ -20,6 +20,8 @@ declare type EditorProperty =
| 'map-mapping'
| 'jump-set'
| 'linkage'
| 'indicator-value-selector'
| 'indicator-name-selector'
declare type EditorPropertyInner = {
[key in EditorProperty]?: string[]
}

View File

@ -180,6 +180,7 @@ function move(keyCode) {
} else if (keyCode === downKey) {
curComponent.value.style.top = ++curComponent.value.style.top
}
snapshotStore.recordSnapshotCache('key-move')
}
}

View File

@ -124,7 +124,9 @@ export const customAttrTrans = {
'radarSize' // 雷达占比
],
label: ['fontSize'],
tooltip: ['fontSize']
tooltip: ['fontSize'],
indicator: ['fontSize', 'suffixFontSize'],
indicatorName: ['fontSize']
}
export const customStyleTrans = {
text: ['fontSize'],
@ -257,7 +259,8 @@ export const THEME_STYLE_TRANS_SLAVE1 = {
export const THEME_ATTR_TRANS_MAIN = {
label: ['color'],
tooltip: ['color']
tooltip: ['color'],
indicatorName: ['color']
}
export const THEME_ATTR_TRANS_MAIN_SYMBOL = {

View File

@ -5,7 +5,12 @@ import componentList, {
} from '@/custom-component/component-list'
import eventBus from '@/utils/eventBus'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { findById, saveCanvas, updateCanvas } from '@/api/visualization/dataVisualization'
import {
findById,
findCopyResource,
saveCanvas,
updateCanvas
} from '@/api/visualization/dataVisualization'
import { storeToRefs } from 'pinia'
import { getPanelAllLinkageInfo } from '@/api/visualization/linkage'
import { queryVisualizationJumpInfo } from '@/api/visualization/linkJump'
@ -78,7 +83,10 @@ export function commonHandleDragEnd(e, dvModel) {
}
export function initCanvasDataPrepare(dvId, busiFlag, callBack) {
findById(dvId, busiFlag).then(res => {
const copyFlag = busiFlag != null && busiFlag.includes('-copy')
const busiFlagCustom = copyFlag ? busiFlag.split('-')[0] : busiFlag
const method = copyFlag ? findCopyResource : findById
method(dvId, busiFlagCustom).then(res => {
const canvasInfo = res.data
const watermarkInfo = {
...canvasInfo.watermarkInfo,

View File

@ -22,7 +22,9 @@ export const customAttrTrans = {
textStyle: ['fontSize']
},
slider: ['fontSize'],
graphic: ['fontSize']
graphic: ['fontSize'],
indicator: ['fontSize', 'suffixFontSize'],
indicatorName: ['fontSize']
}
export const customStyleTrans = {
text: ['fontSize'],

View File

@ -11,7 +11,7 @@ import {
syncFreeApi,
delFreeApi
} from '@/api/about'
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import { ElMessage, ElMessageBox, Action } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import { useEmitt } from '@/hooks/web/useEmitt'
const dialogVisible = ref(false)
@ -141,14 +141,20 @@ const checkFree = () => {
message: h('div', { class: 'free-sync-tip-box' }, childrenDomList),
showClose: false,
cancelButtonText: '删除',
confirmButtonText: '同步'
cancelButtonClass: 'free-cancel-bt',
showCancelButton: false,
preButtonType: 'danger',
preButtonText: '删除',
showPreButton: true,
confirmButtonText: '同步',
callback: (action: Action) => {
if (action === 'confirm') {
syncFree()
} else {
delFree
}
}
})
.then(() => {
syncFree()
})
.catch(() => {
delFree()
})
}
})
}

View File

@ -85,7 +85,9 @@ watch(
const showValueFormatter = computed<boolean>(() => {
return (
(props.chart.type === 'table-normal' || props.chart.type === 'table-info') &&
(props.chart.type === 'table-normal' ||
props.chart.type === 'table-info' ||
props.chart.type === 'indicator') &&
(props.item.deType === 2 || props.item.deType === 3)
)
})
@ -118,7 +120,7 @@ const isEnableCompare = () => {
// /
if (
t1.length > 0 &&
chart.value.type !== 'text' &&
chart.value.type !== 'indicator' &&
chart.value.type !== 'label' &&
chart.value.type !== 'gauge' &&
chart.value.type !== 'liquid'

View File

@ -36,7 +36,7 @@ const props = defineProps({
<span v-else-if="props.view.type && props.view.type.includes('gauge')">{{
t('chart.drag_block_gauge_angel')
}}</span>
<span v-else-if="props.view.type && props.view.type.includes('text')">{{
<span v-else-if="props.view.type && props.view.type.includes('indicator')">{{
t('chart.drag_block_label_value')
}}</span>
<span v-else-if="props.view.type && props.view.type === 'map'">{{

View File

@ -54,7 +54,7 @@ const init = () => {
}
}
const showIgnoreOption = computed(() => {
return !equalsAny(props.chart.type, 'table-pivot', 'table-info')
return !equalsAny(props.chart.type, 'table-pivot', 'table-info', 'indicator')
})
const showEmptyDataFieldCtrl = computed(() => {

View File

@ -91,6 +91,7 @@ const changeLabelThreshold = () => {
// check line config
for (let i = 0; i < state.thresholdArr.length; i++) {
const ele = state.thresholdArr[i]
console.log(ele)
if (!ele.term || ele.term === '') {
ElMessage.error(t('chart.exp_can_not_empty'))
return
@ -109,7 +110,8 @@ const changeLabelThreshold = () => {
return
}
} else {
if (!ele.value) {
console.log(ele.value === undefined)
if (ele.value === undefined) {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
@ -325,30 +327,54 @@ init()
</el-col>
<!--指标卡-->
<el-col v-if="props.chart.type && props.chart.type === 'text'">
<el-col v-if="props.chart.type && props.chart.type === 'indicator'">
<el-col>
<el-button
:title="t('chart.edit')"
class="circle-button"
type="primary"
text
size="small"
style="width: 24px; margin-left: 4px"
@click="editLabelThreshold"
<div class="inner-container">
<span class="label" :class="'label-' + props.themes">阈值设置</span>
<span class="right-btns">
<span
class="set-text-info"
:class="{ 'set-text-info-dark': themes === 'dark' }"
v-if="state.thresholdForm?.labelThreshold?.length > 0"
>
已设置
</span>
<el-button
:title="t('chart.edit')"
:class="'label-' + props.themes"
:style="{ width: '24px', marginLeft: '6px' }"
:disabled="!state.thresholdForm.enable"
class="circle-button"
text
size="small"
@click="editLabelThreshold"
>
<template #icon>
<el-icon size="14px">
<Icon name="icon_edit_outlined" />
</el-icon>
</template>
</el-button>
</span>
</div>
<div
class="threshold-container"
:class="{ 'threshold-container-dark': themes === 'dark' }"
v-if="state.thresholdForm.labelThreshold.length > 0"
>
<template #icon>
<el-icon size="14px">
<Icon name="icon_edit_outlined" />
</el-icon>
</template>
</el-button>
<el-col style="padding: 0 18px">
<el-row
<div class="field-style" :class="{ 'field-style-dark': themes === 'dark' }">
<span class="field-text" style="padding-left: 12px">
{{ t('chart.indicator_value') }}
</span>
</div>
<div
v-for="(item, index) in state.thresholdForm.labelThreshold"
:key="index"
class="line-style"
>
<el-col :span="6">
<div style="flex: 1">
<span v-if="item.term === 'eq'" :title="t('chart.filter_eq')">{{
t('chart.filter_eq')
}}</span>
@ -370,25 +396,31 @@ init()
<span v-else-if="item.term === 'between'" :title="t('chart.filter_between')">{{
t('chart.filter_between')
}}</span>
</el-col>
<el-col :span="12">
</div>
<div style="flex: 1; margin: 0 8px">
<span v-if="item.term !== 'between'" :title="item.value">{{ item.value }}</span>
<span v-if="item.term === 'between'">
{{ item.min }}&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;{{ item.max }}
</span>
</el-col>
<el-col :span="6">
<span
:style="{
width: '14px',
height: '14px',
backgroundColor: item.color,
border: 'solid 1px #e1e4e8'
}"
/>
</el-col>
</el-row>
</el-col>
</div>
<div
:title="t('chart.textColor')"
:style="{
backgroundColor: item.color
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
<!-- <div
:title="t('chart.backgroundColor')"
:style="{
backgroundColor: item.backgroundColor
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>-->
</div>
</div>
</el-col>
</el-col>

View File

@ -2,6 +2,7 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL } from '../../../util/chart'
import { ElSpace } from 'element-plus-secondary'
const { t } = useI18n()
@ -105,62 +106,88 @@ init()
</template>
</el-button>
<div @keydown.stop @keyup.stop style="max-height: 50vh; overflow-y: auto">
<el-row v-for="(item, index) in state.thresholdArr" :key="index" class="line-item">
<el-col :span="6">
<el-select v-model="item.term" size="small" @change="changeThreshold">
<el-option-group v-for="(group, idx) in valueOptions" :key="idx" :label="group.label">
<el-option
v-for="opt in group.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
<el-row
v-for="(item, index) in state.thresholdArr"
:key="index"
class="line-item"
:gutter="8"
>
<el-col :span="5">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group v-for="(group, idx) in valueOptions" :key="idx" :label="group.label">
<el-option
v-for="opt in group.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-option-group>
</el-select>
</el-form-item>
</el-col>
<el-col :span="9" style="text-align: center">
<el-form-item class="form-item" v-if="item.term !== 'between'">
<el-input-number
controls-position="right"
v-model="item.value"
class="value-item"
:placeholder="t('chart.drag_block_label_value')"
clearable
@change="changeThreshold"
/>
</el-form-item>
<el-space v-if="item.term === 'between'">
<el-form-item class="form-item">
<el-input-number
v-model="item.min"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_min')"
clearable
@change="changeThreshold"
/>
</el-option-group>
</el-select>
</el-form-item>
<div style="display: flex; justify-content: center; min-width: 40px">
<span>{{ t('chart.drag_block_label_value') }}</span>
</div>
<el-form-item class="form-item">
<el-input-number
v-model="item.max"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_max')"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-space>
</el-col>
<el-col :span="14" style="text-align: center">
<el-input-number
v-if="item.term !== 'between'"
controls-position="right"
v-model="item.value"
class="value-item"
:placeholder="t('chart.drag_block_label_value')"
size="small"
clearable
@change="changeThreshold"
/>
<span v-if="item.term === 'between'">
<el-input-number
v-model="item.min"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_min')"
size="small"
clearable
@change="changeThreshold"
/>
<span style="margin: 0 4px">{{ t('chart.drag_block_label_value') }}</span>
<el-input-number
v-model="item.max"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_max')"
size="small"
clearable
@change="changeThreshold"
/>
</span>
</el-col>
<el-col :span="2" style="text-align: center">
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<div class="color-title">{{ t('chart.textColor') }}</div>
<el-color-picker
is-custom
size="large"
v-model="item.color"
show-alpha
class="color-picker-style"
:predefine="predefineColors"
@change="changeThreshold"
/>
</el-col>
<el-col :span="2">
</div>
<!-- <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
size="large"
v-model="item.backgroundColor"
show-alpha
class="color-picker-style"
:predefine="predefineColors"
@change="changeThreshold"
/>
</div>-->
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<el-button
class="circle-button"
type="text"
@ -172,7 +199,7 @@ init()
<Icon name="icon_delete-trash_outlined"></Icon>
</template>
</el-button>
</el-col>
</div>
</el-row>
</div>
</el-col>
@ -233,4 +260,21 @@ span {
width: 28px;
height: 28px;
}
.color-title {
color: #646a73;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
padding: 0 8px;
}
.form-item {
height: 28px !important;
margin-bottom: 0 !important;
:deep(.el-form-item__label) {
font-size: 12px;
}
}
</style>

View File

@ -19,6 +19,8 @@ import TableHeaderSelector from '@/views/chart/components/editor/editor-style/co
import TableCellSelector from '@/views/chart/components/editor/editor-style/components/table/TableCellSelector.vue'
import TableTotalSelector from '@/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue'
import MiscStyleSelector from '@/views/chart/components/editor/editor-style/components/MiscStyleSelector.vue'
import IndicatorValueSelector from '@/views/chart/components/editor/editor-style/components/IndicatorValueSelector.vue'
import IndicatorNameSelector from '@/views/chart/components/editor/editor-style/components/IndicatorNameSelector.vue'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -83,7 +85,9 @@ const emit = defineEmits([
'onTableCellChange',
'onTableTotalChange',
'onChangeMiscStyleForm',
'onExtTooltipChange'
'onExtTooltipChange',
'onIndicatorChange',
'onIndicatorNameChange'
])
const showProperties = (property: EditorProperty) => properties.value?.includes(property)
@ -112,6 +116,14 @@ const onTextChange = (val, prop) => {
state.initReady && emit('onTextChange', val, prop)
}
const onIndicatorChange = (val, prop) => {
state.initReady && emit('onIndicatorChange', val, prop)
}
const onIndicatorNameChange = (val, prop) => {
state.initReady && emit('onIndicatorNameChange', val, prop)
}
const onLegendChange = (val, prop) => {
state.initReady && emit('onLegendChange', val, prop)
}
@ -224,6 +236,39 @@ watch(
component-position="component"
/>
</el-collapse-item>
<el-collapse-item
:effect="themes"
v-if="showProperties('indicator-value-selector')"
name="indicator-value"
title="指标值"
>
<indicator-value-selector
:property-inner="propertyInnerAll['indicator-value-selector']"
:themes="themes"
class="attr-selector"
:chart="chart"
:quota-fields="props.quotaData"
@onIndicatorChange="onIndicatorChange"
/>
</el-collapse-item>
<collapse-switch-item
:themes="themes"
v-model="chart.customAttr.indicatorName.show"
v-if="showProperties('indicator-name-selector')"
:change-model="chart.customAttr.indicatorName"
@modelChange="val => onIndicatorNameChange(val, 'show')"
title="指标名称"
name="indicator-name"
>
<indicator-name-selector
:property-inner="propertyInnerAll['indicator-name-selector']"
:themes="themes"
class="attr-selector"
:chart="chart"
:quota-fields="props.quotaData"
@onIndicatorNameChange="onIndicatorNameChange"
/>
</collapse-switch-item>
<el-collapse-item
:effect="themes"
v-if="showProperties('misc-selector')"

View File

@ -0,0 +1,346 @@
<script lang="ts" setup>
import { PropType, computed, onMounted, reactive, toRefs, watch, nextTick, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import {
COLOR_PANEL,
CHART_FONT_FAMILY,
CHART_FONT_LETTER_SPACE,
DEFAULT_INDICATOR_NAME_STYLE
} from '@/views/chart/components/editor/util/chart'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { ElButton, ElIcon } from 'element-plus-secondary'
import Icon from '@/components/icon-custom/src/Icon.vue'
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
propertyInner: {
type: Array<string>
}
})
const emit = defineEmits(['onIndicatorNameChange'])
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const predefineColors = COLOR_PANEL
const fontFamily = CHART_FONT_FAMILY
const fontLetterSpace = CHART_FONT_LETTER_SPACE
const state = reactive({
indicatorNameForm: JSON.parse(JSON.stringify(DEFAULT_INDICATOR_NAME_STYLE))
})
const { chart } = toRefs(props)
const fontSizeList = computed(() => {
const arr = []
for (let i = 10; i <= 60; i = i + 2) {
arr.push({
name: i + '',
value: i
})
}
return arr
})
const changeTitleStyle = prop => {
emit('onIndicatorNameChange', state.indicatorNameForm, prop)
}
const init = () => {
const customText = defaultsDeep(
cloneDeep(props.chart?.customAttr?.indicatorName),
cloneDeep(DEFAULT_INDICATOR_NAME_STYLE)
)
state.indicatorNameForm = cloneDeep(customText)
//
nextTick(() => {
state.indicatorNameForm.color = customText.color
})
}
onMounted(() => {
init()
})
watch(
() => props.chart?.customAttr?.indicatorName,
() => {
init()
},
{ deep: true }
)
</script>
<template>
<div>
<el-form
ref="indicatorNameForm"
:disabled="!state.indicatorNameForm.show"
:model="state.indicatorNameForm"
label-position="top"
>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:effect="themes"
:label="t('chart.text')"
>
<el-select
:effect="themes"
v-model="state.indicatorNameForm.fontFamily"
:placeholder="t('chart.font_family')"
@change="changeTitleStyle('fontFamily')"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-color-picker
:effect="themes"
v-model="state.indicatorNameForm.color"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTitleStyle('color')"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 56px"
:effect="themes"
v-model="state.indicatorNameForm.fontSize"
:placeholder="t('chart.text_fontsize')"
size="small"
@change="changeTitleStyle('fontSize')"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<el-select
size="small"
:effect="themes"
v-model="state.indicatorNameForm.letterSpace"
:placeholder="t('chart.quota_letter_space')"
@change="changeTitleStyle('letterSpace')"
>
<template #prefix>
<el-icon>
<Icon name="icon_letter-spacing_outlined" />
</el-icon>
</template>
<el-option
v-for="option in fontLetterSpace"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
</div>
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorNameForm.isBolder"
@change="changeTitleStyle('isBolder')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.bolder') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorNameForm.isBolder }"
>
<el-icon>
<Icon name="icon_bold_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorNameForm.isItalic"
@change="changeTitleStyle('isItalic')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.italic') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorNameForm.isItalic }"
>
<el-icon>
<Icon name="icon_italic_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
</el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.indicatorNameForm.fontShadow"
@change="changeTitleStyle('fontShadow')"
>
{{ t('chart.font_shadow') }}
</el-checkbox>
</el-form-item>
</el-form>
</div>
</template>
<style lang="less" scoped>
:deep(.ed-input .ed-select__prefix--light) {
padding-right: 6px;
}
.icon-btn {
font-size: 16px;
line-height: 16px;
width: 24px;
height: 24px;
text-align: center;
border-radius: 4px;
padding-top: 4px;
color: #1f2329;
cursor: pointer;
&.dark {
color: #a6a6a6;
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
}
}
.is-disabled {
.icon-btn {
color: #8f959e;
cursor: not-allowed;
&:hover {
background-color: inherit;
}
&.active {
background-color: #f5f7fa;
&:hover {
background-color: #f5f7fa;
}
}
&.dark {
color: #5f5f5f;
&.active {
background-color: #373737;
&:hover {
background-color: #373737;
}
}
}
}
}
.icon-checkbox {
:deep(.ed-checkbox__input) {
display: none;
}
:deep(.ed-checkbox__label) {
padding: 0;
}
}
.icon-radio-group {
:deep(.ed-radio) {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
:deep(.ed-radio__input) {
display: none;
}
:deep(.ed-radio__label) {
padding: 0;
}
}
.position-divider {
width: 1px;
height: 18px;
margin-bottom: 16px;
background: rgba(31, 35, 41, 0.15);
&.position-divider--dark {
background: rgba(235, 235, 235, 0.15);
}
}
.remark-label {
color: var(--N600, #646a73);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
&.remark-label--dark {
color: var(--N600-Dark, #a6a6a6);
}
}
</style>

View File

@ -0,0 +1,650 @@
<script lang="ts" setup>
import { PropType, computed, onMounted, reactive, toRefs, watch, nextTick, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import {
COLOR_PANEL,
CHART_FONT_FAMILY,
CHART_FONT_LETTER_SPACE,
DEFAULT_INDICATOR_STYLE
} from '@/views/chart/components/editor/util/chart'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { ElButton, ElIcon, ElInput } from 'element-plus-secondary'
import Icon from '@/components/icon-custom/src/Icon.vue'
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
propertyInner: {
type: Array<string>
}
})
const emit = defineEmits(['onIndicatorChange'])
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const predefineColors = COLOR_PANEL
const fontFamily = CHART_FONT_FAMILY
const fontLetterSpace = CHART_FONT_LETTER_SPACE
const state = reactive({
indicatorValueForm: JSON.parse(JSON.stringify(DEFAULT_INDICATOR_STYLE))
})
const { chart } = toRefs(props)
const fontSizeList = computed(() => {
const arr = []
for (let i = 10; i <= 60; i = i + 2) {
arr.push({
name: i + '',
value: i
})
}
return arr
})
const changeTitleStyle = prop => {
emit('onIndicatorChange', state.indicatorValueForm, prop)
}
const init = () => {
const customText = defaultsDeep(
cloneDeep(props.chart?.customAttr?.indicator),
cloneDeep(DEFAULT_INDICATOR_STYLE)
)
state.indicatorValueForm = cloneDeep(customText)
//
nextTick(() => {
state.indicatorValueForm.color = customText.color
})
}
onMounted(() => {
init()
})
watch(
() => props.chart?.customAttr?.indicator,
() => {
init()
},
{ deep: true }
)
</script>
<template>
<div>
<el-form
ref="indicatorValueForm"
:disabled="!state.indicatorValueForm.show"
:model="state.indicatorValueForm"
label-position="top"
>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:effect="themes"
:label="t('chart.text')"
>
<el-select
:effect="themes"
v-model="state.indicatorValueForm.fontFamily"
:placeholder="t('chart.font_family')"
@change="changeTitleStyle('fontFamily')"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-color-picker
:effect="themes"
v-model="state.indicatorValueForm.color"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTitleStyle('color')"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 56px"
:effect="themes"
v-model="state.indicatorValueForm.fontSize"
:placeholder="t('chart.text_fontsize')"
size="small"
@change="changeTitleStyle('fontSize')"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<el-select
:effect="themes"
v-model="state.indicatorValueForm.letterSpace"
:placeholder="t('chart.quota_letter_space')"
@change="changeTitleStyle('letterSpace')"
>
<template #prefix>
<el-icon>
<Icon name="icon_letter-spacing_outlined" />
</el-icon>
</template>
<el-option
v-for="option in fontLetterSpace"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
</div>
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.isBolder"
@change="changeTitleStyle('isBolder')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.bolder') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorValueForm.isBolder }"
>
<el-icon>
<Icon name="icon_bold_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.isItalic"
@change="changeTitleStyle('isItalic')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.italic') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorValueForm.isItalic }"
>
<el-icon>
<Icon name="icon_italic_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<div class="position-divider" :class="'position-divider--' + themes"></div>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-radio-group
:effect="themes"
class="icon-radio-group"
v-model="state.indicatorValueForm.hPosition"
@change="changeTitleStyle('hPosition')"
>
<el-radio :effect="themes" label="left">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_left') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.hPosition === 'left'
}"
>
<el-icon>
<Icon name="icon_left-alignment_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio :effect="themes" label="center">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_center') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.hPosition === 'center'
}"
>
<el-icon>
<Icon name="icon_center-alignment_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio :effect="themes" label="right">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_right') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.hPosition === 'right'
}"
>
<el-icon>
<Icon name="icon_right-alignment_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
</el-radio-group>
</el-form-item>
</el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-radio-group
:effect="themes"
class="icon-radio-group"
v-model="state.indicatorValueForm.vPosition"
@change="changeTitleStyle('vPosition')"
>
<el-radio label="top">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_top') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.vPosition === 'top'
}"
>
<el-icon>
<Icon name="icon_top-align_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio label="center">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_center') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.vPosition === 'center'
}"
>
<el-icon>
<Icon name="icon_vertical-align_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio label="bottom">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_bottom') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.vPosition === 'bottom'
}"
>
<el-icon>
<Icon name="icon_bottom-align_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.indicatorValueForm.fontShadow"
@change="changeTitleStyle('fontShadow')"
>
{{ t('chart.font_shadow') }}
</el-checkbox>
</el-form-item>
<el-divider class="m-divider" :class="{ 'divider-dark': themes === 'dark' }" />
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.indicatorValueForm.suffixEnable"
@change="changeTitleStyle('suffixEnable')"
>
{{ t('chart.indicator_suffix') }}
</el-checkbox>
</el-form-item>
<div style="padding-left: 22px">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input
:disabled="!state.indicatorValueForm.suffixEnable"
v-model="state.indicatorValueForm.suffix"
:placeholder="t('chart.indicator_suffix_placeholder')"
maxlength="10"
@change="changeTitleStyle('suffix')"
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" :effect="themes">
<el-select
:disabled="!state.indicatorValueForm.suffixEnable"
:effect="themes"
v-model="state.indicatorValueForm.suffixFontFamily"
:placeholder="t('chart.font_family')"
@change="changeTitleStyle('suffixFontFamily')"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-color-picker
:disabled="!state.indicatorValueForm.suffixEnable"
:effect="themes"
v-model="state.indicatorValueForm.suffixColor"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTitleStyle('suffixColor')"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
:disabled="!state.indicatorValueForm.suffixEnable"
style="width: 56px"
:effect="themes"
v-model="state.indicatorValueForm.suffixFontSize"
:placeholder="t('chart.text_fontsize')"
size="small"
@change="changeTitleStyle('suffixFontSize')"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<el-select
size="small"
:disabled="!state.indicatorValueForm.suffixEnable"
:effect="themes"
v-model="state.indicatorValueForm.suffixLetterSpace"
:placeholder="t('chart.quota_letter_space')"
@change="changeTitleStyle('suffixLetterSpace')"
>
<template #prefix>
<el-icon>
<Icon name="icon_letter-spacing_outlined" />
</el-icon>
</template>
<el-option
v-for="option in fontLetterSpace"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
</div>
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:disabled="!state.indicatorValueForm.suffixEnable"
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.suffixIsBolder"
@change="changeTitleStyle('suffixIsBolder')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.bolder') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.suffixIsBolder
}"
>
<el-icon>
<Icon name="icon_bold_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:disabled="!state.indicatorValueForm.suffixEnable"
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.suffixIsItalic"
@change="changeTitleStyle('suffixIsItalic')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.italic') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.suffixIsItalic
}"
>
<el-icon>
<Icon name="icon_italic_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
</el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:disabled="!state.indicatorValueForm.suffixEnable"
size="small"
:effect="themes"
v-model="state.indicatorValueForm.suffixFontShadow"
@change="changeTitleStyle('suffixFontShadow')"
>
{{ t('chart.font_shadow') }}
</el-checkbox>
</el-form-item>
</div>
</el-form>
</div>
</template>
<style lang="less" scoped>
:deep(.ed-input .ed-select__prefix--light) {
padding-right: 6px;
}
.icon-btn {
font-size: 16px;
line-height: 16px;
width: 24px;
height: 24px;
text-align: center;
border-radius: 4px;
padding-top: 4px;
color: #1f2329;
cursor: pointer;
&.dark {
color: #a6a6a6;
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
}
}
.is-disabled {
.icon-btn {
color: #8f959e;
cursor: not-allowed;
&:hover {
background-color: inherit;
}
&.active {
background-color: #f5f7fa;
&:hover {
background-color: #f5f7fa;
}
}
&.dark {
color: #5f5f5f;
&.active {
background-color: #373737;
&:hover {
background-color: #373737;
}
}
}
}
}
.icon-checkbox {
:deep(.ed-checkbox__input) {
display: none;
}
:deep(.ed-checkbox__label) {
padding: 0;
}
}
.icon-radio-group {
:deep(.ed-radio) {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
:deep(.ed-radio__input) {
display: none;
}
:deep(.ed-radio__label) {
padding: 0;
}
}
.position-divider {
width: 1px;
height: 18px;
margin-bottom: 16px;
background: rgba(31, 35, 41, 0.15);
&.position-divider--dark {
background: rgba(235, 235, 235, 0.15);
}
}
.remark-label {
color: var(--N600, #646a73);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
&.remark-label--dark {
color: var(--N600-Dark, #a6a6a6);
}
}
.m-divider {
margin: 0 0 16px;
border-color: rgba(31, 35, 41, 0.15);
&.divider-dark {
border-color: rgba(255, 255, 255, 0.15);
}
}
</style>

View File

@ -577,7 +577,7 @@ onMounted(() => {
<!--liquid-end-->
<!--text&label-start-->
<template v-if="props.chart.type.includes('text') || props.chart.type.includes('label')">
<template v-if="props.chart.type.includes('indicator') || props.chart.type.includes('label')">
<el-form-item
:label="t('chart.quota_font_size')"
class="form-item"

View File

@ -657,6 +657,16 @@ const onLabelChange = val => {
renderChart(view.value)
}
const onIndicatorChange = val => {
view.value.customAttr.indicator = val
renderChart(view.value)
}
const onIndicatorNameChange = val => {
view.value.customAttr.indicatorName = val
renderChart(view.value)
}
const onTooltipChange = (chartForm: ChartEditorForm<ChartTooltipAttr>, prop: string) => {
const { data, requestData, render } = chartForm
let tooltipObj = data
@ -1703,6 +1713,8 @@ const onRefreshChange = val => {
@onChangeXAxisForm="onChangeXAxisForm"
@onChangeYAxisForm="onChangeYAxisForm"
@onTextChange="onTextChange"
@onIndicatorChange="onIndicatorChange"
@onIndicatorNameChange="onIndicatorNameChange"
@onLegendChange="onLegendChange"
@onBackgroundChange="onBackgroundChange"
@onBasicStyleChange="onBasicStyleChange"

View File

@ -333,7 +333,7 @@ export const DEFAULT_TABLE_CELL: ChartTableCellAttr = {
}
export const DEFAULT_TITLE_STYLE: ChartTextStyle = {
show: true,
fontSize: '18',
fontSize: 16,
color: '#ffffff',
hPosition: 'left',
vPosition: 'top',
@ -347,9 +347,42 @@ export const DEFAULT_TITLE_STYLE: ChartTextStyle = {
fontShadow: false
}
export const DEFAULT_TITLE_STYLE_BASE: ChartTextStyle = {
export const DEFAULT_INDICATOR_STYLE: ChartIndicatorStyle = {
show: true,
fontSize: '20',
color: '#5470C6',
hPosition: 'center',
vPosition: 'center',
isItalic: false,
isBolder: true,
fontFamily: 'Microsoft YaHei',
letterSpace: '0',
fontShadow: false,
suffixEnable: true,
suffix: '',
suffixFontSize: '14',
suffixColor: '#5470C6',
suffixIsItalic: false,
suffixIsBolder: true,
suffixFontFamily: 'Microsoft YaHei',
suffixLetterSpace: '0',
suffixFontShadow: false
}
export const DEFAULT_INDICATOR_NAME_STYLE: ChartIndicatorNameStyle = {
show: true,
fontSize: '18',
color: '#ffffff',
isItalic: false,
isBolder: true,
fontFamily: 'Microsoft YaHei',
letterSpace: '0',
fontShadow: false
}
export const DEFAULT_TITLE_STYLE_BASE: ChartTextStyle = {
show: true,
fontSize: 16,
hPosition: 'left',
vPosition: 'top',
isItalic: false,
@ -952,6 +985,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'liquid',
title: t('chart.chart_liquid'),
icon: 'liquid'
},
{
render: 'custom',
category: 'quota',
value: 'indicator',
title: t('chart.chart_indicator'),
icon: 'indicator'
}
]
},
@ -1258,6 +1298,8 @@ export const BASE_VIEW_CONFIG = {
tableTotal: DEFAULT_TABLE_TOTAL,
tableHeader: DEFAULT_TABLE_HEADER,
tableCell: DEFAULT_TABLE_CELL,
indicator: DEFAULT_INDICATOR_STYLE,
indicatorName: DEFAULT_INDICATOR_NAME_STYLE,
map: {
id: '',
level: 'world'

View File

@ -112,6 +112,14 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
protected configLabel(chart: Chart, options: LiquidOptions): LiquidOptions {
const customAttr = parseJson(chart.customAttr)
const originVal = options.percent
// 数值过大视图会异常大于 1 无意义
if (originVal > 1) {
options = {
...options,
percent: 1
}
}
if (!customAttr.label?.show) {
return {
...options,
@ -130,9 +138,8 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
fontSize: label.fontSize.toString() + 'px',
color: label.color
},
formatter: function (v) {
const value = v.percent
return valueFormatter(value, labelFormatter)
formatter: () => {
return valueFormatter(originVal, labelFormatter)
}
}
}

View File

@ -0,0 +1,64 @@
import { AbstractChartView, ChartLibraryType, ChartRenderType } from '../../types'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
/**
* 指标卡视图
*/
export class IndicatorChartView extends AbstractChartView {
properties: EditorProperty[] = [
'background-overall-component',
'title-selector',
'indicator-value-selector',
'indicator-name-selector',
'threshold',
'function-cfg'
]
propertyInner: EditorPropertyInner = {
'background-overall-component': ['all'],
'title-selector': [
'title',
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'remarkShow',
'fontFamily',
'letterSpace',
'fontShadow'
],
'indicator-value-selector': [
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'fontFamily',
'letterSpace',
'fontShadow'
],
'indicator-name-selector': [
'title',
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'fontFamily',
'letterSpace',
'fontShadow'
],
'function-cfg': ['emptyDataStrategy']
}
axis: AxisType[] = ['yAxis', 'filter']
axisConfig: AxisConfig = {
yAxis: {
name: `${t('chart.quota')}`,
type: 'q'
}
}
constructor() {
super(ChartRenderType.CUSTOM, ChartLibraryType.INDICATOR, 'indicator')
}
}

View File

@ -1,4 +1,4 @@
import { DataCell, S2Event, S2Options, TableSheet } from '@antv/s2/esm/index'
import { S2Event, S2Options, TableColCell, TableDataCell, TableSheet } from '@antv/s2/esm/index'
import { formatterItem, valueFormatter } from '../../../formatter'
import { parseJson } from '../../../util'
import { S2ChartView, S2DrawOptions } from '../../types/impl/s2'
@ -104,22 +104,25 @@ export class TableInfo extends S2ChartView<TableSheet> {
}
// 开启序号之后第一列就是序号列修改 label 即可
if (s2Options.showSeriesNumber) {
s2Options.colCell = node => {
s2Options.colCell = (node, sheet, config) => {
if (node.colIndex === 0) {
if (!customAttr.tableHeader.indexLabel) {
node.label = ' '
} else {
node.label = customAttr.tableHeader.indexLabel
let indexLabel = customAttr.tableHeader.indexLabel
if (!indexLabel) {
indexLabel = ''
}
const cell = new TableColCell(node, sheet, config)
const shape = cell.getTextShape() as any
shape.attrs.text = indexLabel
return cell
}
return node.belongsCell
return new TableColCell(node, sheet, config)
}
s2Options.dataCell = viewMeta => {
if (viewMeta.colIndex === 0) {
viewMeta.fieldValue =
pageInfo.pageSize * (pageInfo.currentPage - 1) + viewMeta.rowIndex + 1
}
return new DataCell(viewMeta, viewMeta?.spreadsheet)
return new TableDataCell(viewMeta, viewMeta?.spreadsheet)
}
}

View File

@ -6,6 +6,7 @@ import { getCurrentField } from '@/views/chart/components/js/panel/common/common
import { TABLE_EDITOR_PROPERTY, TABLE_EDITOR_PROPERTY_INNER } from './common'
import { useI18n } from '@/hooks/web/useI18n'
import { isNumber } from 'lodash-es'
import { TableColCell } from '@antv/s2'
const { t } = useI18n()
/**
@ -106,15 +107,18 @@ export class TableNormal extends S2ChartView<TableSheet> {
}
// 开启序号之后第一列就是序号列修改 label 即可
if (s2Options.showSeriesNumber) {
s2Options.colCell = node => {
s2Options.colCell = (node, sheet, config) => {
if (node.colIndex === 0) {
if (!customAttr.tableHeader.indexLabel) {
node.label = ' '
} else {
node.label = customAttr.tableHeader.indexLabel
let indexLabel = customAttr.tableHeader.indexLabel
if (!indexLabel) {
indexLabel = ''
}
const cell = new TableColCell(node, sheet, config)
const shape = cell.getTextShape() as any
shape.attrs.text = indexLabel
return cell
}
return node.belongsCell
return new TableColCell(node, sheet, config)
}
}

View File

@ -13,7 +13,8 @@ export enum ChartLibraryType {
L7_PLOT = 'l7plot',
ECHARTS = 'echarts',
S2 = 's2',
RICH_TEXT = 'rich-text'
RICH_TEXT = 'rich-text',
INDICATOR = 'indicator'
}
export abstract class AbstractChartView {

View File

@ -230,7 +230,7 @@ export function getRemark(chart) {
return remark
}
export const quotaViews = ['label', 'richTextView', 'text', 'gauge', 'liquid']
export const quotaViews = ['label', 'richTextView', 'indicator', 'gauge', 'liquid']
export function handleEmptyDataStrategy<O extends PickOptions>(chart: Chart, options: O): O {
const { data } = options as unknown as Options

View File

@ -232,8 +232,8 @@ const trackClick = trackAction => {
const trackMenu = computed(() => {
const trackMenuInfo = []
//
if (!'multiplexing'.includes(showPosition.value)) {
//
if (!['multiplexing', 'viewDialog'].includes(showPosition.value)) {
let linkageCount = 0
let jumpCount = 0
chartData.value?.fields?.forEach(item => {

View File

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import ChartComponentG2Plot from './components/ChartComponentG2Plot.vue'
import DeIndicator from '@/custom-component/indicator/DeIndicator.vue'
import {
computed,
CSSProperties,
@ -606,7 +607,7 @@ const iconSize = computed<string>(() => {
</template>
<div
class="icons-container"
:class="{ 'is-editing': titleEditStatus }"
:class="{ 'is-editing': titleEditStatus, 'icons-container__dark': themes === 'dark' }"
v-if="trackMenu.length > 0 || state.title_remark.show"
>
<el-tooltip :effect="toolTip" placement="top" v-if="state.title_remark.show">
@ -645,6 +646,14 @@ const iconSize = computed<string>(() => {
:active="active"
:show-position="showPosition"
/>
<de-indicator
:scale="scale"
v-else-if="showChartView(ChartLibraryType.INDICATOR)"
:themes="canvasStyleData.dashboard.themeColor"
ref="chartComponent"
:view="view"
:show-position="showPosition"
/>
<chart-component-g2-plot
:scale="scale"
:dynamic-area-id="dynamicAreaId"
@ -703,6 +712,10 @@ const iconSize = computed<string>(() => {
color: #646a73;
&.icons-container__dark {
color: #a6a6a6;
}
&.is-editing {
gap: 6px;
}

View File

@ -81,7 +81,8 @@ onMounted(async () => {
state.sourcePid = pid
if (resourceId) {
dataInitState.value = false
initCanvasData(resourceId, 'dashboard', function () {
const busiFlg = opt === 'copy' ? 'dashboard-copy' : 'dashboard'
initCanvasData(resourceId, busiFlg, function () {
dataInitState.value = true
if (dvInfo.value && opt === 'copy') {
dvInfo.value.dataState = 'prepare'

View File

@ -204,7 +204,8 @@ onMounted(async () => {
initDataset()
if (dvId) {
state.canvasInitStatus = false
initCanvasData(dvId, 'dataV', function () {
const busiFlg = opt === 'copy' ? 'dataV-copy' : 'dataV'
initCanvasData(dvId, busiFlg, function () {
state.canvasInitStatus = true
// afterInit
nextTick(() => {

View File

@ -5,15 +5,11 @@
<el-row
class="bottom-area-show"
:class="{
'create-area':
['branchCreate', 'create'].includes(props.curPosition) ||
!createAuth[template.templateType]
'create-area': !createAuth[template.templateType]
}"
>
<el-row class="demonstration">
{{ template.title }}
</el-row>
<el-row class="template-button">
<el-row class="demonstration"> {{ template.title }} </el-row>
<el-row class="template-button" v-show="createAuth[template.templateType]">
<el-button size="mini" style="width: calc(50% - 18px)" @click="templateInnerPreview">{{
t('visualization.preview')
}}</el-button>

View File

@ -455,7 +455,7 @@ watch(nickName, (val: string) => {
const filterNode = (value: string, data: BusiTreeNode) => {
if (!value) return true
return data.name?.toLocaleLowerCase().includes(value.toLocaleLowerCase())
return data.name?.includes(value)
}
const getMenuList = (val: boolean) => {

View File

@ -2,6 +2,12 @@ import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
export const dsTypes = [
{
type: 'db2',
name: 'Db2',
catalog: 'OLTP',
extraParams: ''
},
{
type: 'mysql',
name: 'MySQL',

View File

@ -535,7 +535,7 @@ const nodeCollapse = data => {
const filterNode = (value: string, data: BusiTreeNode) => {
if (!value) return true
return data.name?.toLocaleLowerCase().includes(value.toLocaleLowerCase())
return data.name?.includes(value)
}
const editDatasource = (editType?: number) => {

View File

@ -238,11 +238,13 @@ const toTemplateMarket = () => {
}
const toTemplateMarketAdd = () => {
const params = {
curPosition: 'branchCreate',
templateType: 'all'
if (havePanelAuth.value || haveScreenAuth.value) {
const params = {
curPosition: 'branchCreate',
templateType: 'all'
}
resourceCreateOpt.value.optInit(params)
}
resourceCreateOpt.value.optInit(params)
}
fillCardInfo()

@ -1 +1 @@
Subproject commit e71e6152874ed49933d3befeee6ea2c9310a94f4
Subproject commit 19d1cc14aaa7ff5297ebe8f3b8ea3c706f164cc3

View File

@ -7,6 +7,9 @@ DE_RUNNING_BASE=${DE_BASE}/dataease2.0
need_init_apisix=false
compose_files="-f docker-compose.yml"
compose_cmd="docker-compose"
server_url="github.com"
current_version=""
latest_version=""
set -a
source ${DE_RUNNING_BASE}/.env
@ -25,6 +28,12 @@ if [[ ! ${DE_EXTERNAL_MYSQL} ]] || [ "${DE_EXTERNAL_MYSQL}" = "false" ]; then
compose_files="${compose_files} -f docker-compose-mysql.yml"
fi
if [[ -x "$(command -v python)" ]]; then
py_cmd='python'
elif [[ -x "$(command -v python3)" ]]; then
py_cmd='python3'
fi
function usage() {
echo "DATAEASE 控制脚本"
echo
@ -107,7 +116,87 @@ function _healthcheck() {
}
function _get_current_version() {
de_current_version=$(grep "^ image:.*dataease:" ${DE_RUNNING_BASE}/docker-compose.yml | awk -F'dataease:' '{print $2}')
echo $de_current_version
if test -z $de_current_version; then
echo "获取当前版本失败,请检查当前版本是否正确"
exit 1
fi
current_version=$de_current_version
}
function _get_available_server() {
git_urls=('github.com')
for git_url in ${git_urls[*]}; do
success="true"
echo -ne "检测 ${git_url} ... "
curl -m 5 -kIs https://${git_url} >/dev/null
if [ $? != 0 ]; then
echo "failed"
success="false"
else
echo "ok"
fi
if [[ ${success} == "true" ]]; then
server_url=${git_url}
break
else
unset server_url
fi
done
if [[ "x${server_url}" == "x" ]]; then
echo "没有找到稳定的下载服务器,请访问 https://community.fit2cloud.com/#/products/dataease/downloads 下载离线安装包"
exit 1
fi
}
function _get_latest_version() {
rm -f /tmp/de_latest_release
$py_cmd - <<EOF
# -*- coding: UTF-8 -*-
import os
import json
import re
latest_release=""
release_pattern="v2\.\d+\.\d+$"
def get_releases(page):
try:
releases=os.popen("curl -s https://api.github.com/repos/dataease/dataease/releases?page=%d" % (page)).read()
releases=[ x["name"] for x in json.loads(releases) if x["prerelease"] == False ]
except Exception as e:
print(str(e))
print("Failed to obtain Release information, please check the network.")
exit(1)
else:
for release in releases:
if re.search(release_pattern,release) != None:
return release
page = 1
while (page <= 3):
latest_release = get_releases(page)
if (latest_release != "" and latest_release != None):
break
page += 1
if latest_release == None or latest_release == "":
print("Failed to obtain latest version, please try again.")
exit(1)
else:
print("latest version is %s" % (latest_release))
# 记录最新版本号
os.popen("echo "+latest_release+" > /tmp/de_latest_release")
EOF
if [ ! -f /tmp/de_latest_release ]; then
echo "获取最新版本失败,请检查网络连接是否正常"
exit 1
fi
latest_version=$(cat /tmp/de_latest_release)
}
function status() {
echo
@ -164,113 +253,20 @@ function reload() {
function version() {
echo
_get_current_version
_get_latest_version
echo "current version is $current_version"
}
function upgrade() {
echo
git_urls=('github.com')
if [[ -x "$(command -v python)" ]]; then
py_cmd='python'
elif [[ -x "$(command -v python3)" ]]; then
py_cmd='python3'
fi
_get_current_version
echo "检测当前版本为${current_version}"
_get_available_server
_get_latest_version
for git_url in ${git_urls[*]}; do
success="true"
for i in {1..3}; do
echo -ne "检测 ${git_url} ... ${i} "
curl -m 5 -kIs https://${git_url} >/dev/null
if [ $? != 0 ]; then
echo "failed"
success="false"
break
else
echo "ok"
fi
done
if [[ ${success} == "true" ]]; then
server_url=${git_url}
break
fi
done
if [[ "x${server_url}" == "x" ]]; then
echo "没有找到稳定的下载服务器,请访问 https://community.fit2cloud.com/#/products/dataease/downloads 下载离线安装包"
exit 1
fi
if [[ "${server_url}" == "gitee.com" ]]; then
owner='fit2cloud-feizhiyun'
repo='DataEase'
else
owner='dataease'
repo='dataease'
fi
export DE_VERSION=$(_get_current_version)
if test -z $DE_VERSION; then
echo "获取当前版本失败,请检查当前版本是否正确"
exit 1
fi
echo "检测当前版本为${DE_VERSION}"
rm -f /tmp/de_latest_release
$py_cmd - <<EOF
# -*- coding: UTF-8 -*-
import os
import json
import re
latest_release=""
release_pattern=""
current_version="$DE_VERSION"
server_url="$server_url"
release_pattern="v2\.\d+\.\d+$"
def get_releases(page):
try:
if server_url == "gitee.com":
releases=os.popen("curl -s https://gitee.com/api/v5/repos/fit2cloud-feizhiyun/DataEase/releases?direction=desc&page=%d" % (page)).read()
else:
releases=os.popen("curl -s https://api.github.com/repos/dataease/dataease/releases?page=%d" % (page)).read()
releases=[ x["name"] for x in json.loads(releases) if x["prerelease"] == False ]
except Exception as e:
print(str(e))
print("Failed to obtain Release information, please check the network.")
exit(1)
else:
for release in releases:
if re.search(release_pattern,release) != None:
return release
page = 1
while (page <= 3):
latest_release = get_releases(page)
if (latest_release != "" and latest_release != None):
break
page += 1
if latest_release == None or latest_release == "":
print("Failed to obtain latest version, please try again.")
exit(1)
else:
print("latest version is %s" % (latest_release))
# 记录最新版本号
os.popen("echo "+latest_release+" > /tmp/de_latest_release")
EOF
if [ ! -f /tmp/de_latest_release ]; then
echo "获取最新版本失败,请检查网络连接是否正常"
exit 1
fi
latest_version=$(cat /tmp/de_latest_release)
if [ "${latest_version}" = "" ]; then
echo "未获取到最新版本"
exit 1
elif [ "${latest_version}" = "${DE_VERSION}" ]; then
elif [ "${latest_version}" = "${current_version}" ]; then
echo "最新版本与当前版本一致,退出升级过程"
exit 0
else
@ -283,11 +279,11 @@ EOF
cd /tmp
installer_file="dataease-online-installer-${latest_version}.tar.gz"
download_url="https://${server_url}/${owner}/${repo}/releases/download/${latest_version}/$installer_file"
download_url="https://${server_url}/dataease/dataease/releases/download/${latest_version}/$installer_file"
curl -LOk -m 60 -o $installer_file $download_url
if [ $? -ne 0 ]; then
echo -e "\e[31m升级失败:连接下载服务器超时!\n可手动下载升级包然后执行\e[1;33m /bin/bash install.sh \e[0;31m离线升级也可以重新执行一次 dectl upgrade 命令。\e[0m"
return 2
exit 1
fi
if [ ! -f $installer_file ]; then

View File

@ -16,4 +16,8 @@ public class LogGridVO implements Serializable {
private String ip;
private Long time;
private boolean success;
private String msg;
}

View File

@ -34,6 +34,11 @@ public interface DataVisualizationApi {
@Operation(summary = "查询可视化资源")
DataVisualizationVO findById(@PathVariable("dvId") Long dvId,@PathVariable("busiFlag") String busiFlag);
@GetMapping("/findCopyResource/{dvId}/{busiFlag}")
@Operation(summary = "查询临时复制资源")
DataVisualizationVO findCopyResource(@PathVariable("dvId") Long dvId,@PathVariable("busiFlag") String busiFlag);
@PostMapping("/saveCanvas")
@DePermit(value = {"#p0.pid + ':manage'"}, busiFlag = "#p0.type")
@Operation(summary = "画布保存")

View File

@ -152,4 +152,8 @@ public interface UserApi {
@Hidden
@GetMapping("/firstEchelon/{limit}")
List<Long> firstEchelon(@PathVariable("limit") Long limit);
@Hidden
@GetMapping("/queryByAccount")
CurUserVO queryByAccount(String account);
}

View File

@ -13,7 +13,9 @@ public @interface DeLog {
String pid() default "";
LogST st();
LogST st() default LogST.PANEL;
LogOT ot();
String stExp() default "";
}