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

This commit is contained in:
taojinlong 2024-11-26 17:37:57 +08:00
commit 52a52dd581
162 changed files with 8901 additions and 3677 deletions

View File

@ -21,11 +21,11 @@ public class MybatisPlusGenerator {
/**
* 业务模块例如datasource,dataset,panel等
*/
private static final String busi = "chart";
private static final String busi = "visualization";
/**
* 这是要生成代码的表名称
*/
private static final String TABLE_NAME = "core_chart_view";
private static final String TABLE_NAME = "visualization_link_jump_target_view_info";
/**
* 下面两个配置基本上不用动

View File

@ -120,7 +120,7 @@ public class YoyChartHandler extends DefaultChartHandler {
expandedResult.setQuerySql(originSql);
}
// 同环比数据排序
expandedResult.setOriginData(sortData(view, expandedResult.getOriginData(),formatResult));
expandedResult.setOriginData(sortData(view, expandedResult.getOriginData(), formatResult));
return expandedResult;
}
@ -128,7 +128,14 @@ public class YoyChartHandler extends DefaultChartHandler {
// 维度排序
List<ChartViewFieldDTO> xAxisSortList = view.getXAxis().stream().filter(x -> !StringUtils.equalsIgnoreCase("none", x.getSort())).toList();
// 指标排序
List<ChartViewFieldDTO> yAxisSortList = view.getYAxis().stream().filter(y -> !StringUtils.equalsIgnoreCase("none", y.getSort())).toList();
List<ChartViewFieldDTO> yAxisSortList = view.getYAxis().stream().filter(y -> {
//需要针对区间条形图的时间类型判断一下
if (StringUtils.equalsIgnoreCase("bar-range", view.getType()) && StringUtils.equalsIgnoreCase(y.getGroupType(), "d") && y.getDeType() == 1) {
return false;
} else {
return !StringUtils.equalsIgnoreCase("none", y.getSort());
}
}).toList();
// 不包含维度排序时指标排序生效
if (!data.isEmpty() && CollectionUtils.isEmpty(xAxisSortList) && CollectionUtils.isNotEmpty(yAxisSortList)) {
// 指标排序仅第一个生效

View File

@ -30,6 +30,7 @@ import io.dataease.license.config.XpackInteract;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.IDUtils;
import io.dataease.utils.JsonUtil;
import io.dataease.utils.LogUtil;
import io.dataease.visualization.dao.auto.entity.DataVisualizationInfo;
import io.dataease.visualization.dao.auto.mapper.DataVisualizationInfoMapper;
import jakarta.annotation.Resource;
@ -41,6 +42,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
/**
@ -127,11 +129,53 @@ public class ChartViewManege {
QueryWrapper<CoreChartView> wrapper = new QueryWrapper<>();
wrapper.eq("scene_id", sceneId);
List<ChartViewDTO> chartViewDTOS = transChart(coreChartViewMapper.selectList(wrapper));
for (ChartViewDTO dto : chartViewDTOS) {
QueryWrapper<CoreDatasetTableField> wp = new QueryWrapper<>();
wp.eq("dataset_group_id", dto.getTableId());
List<CoreDatasetTableField> coreDatasetTableFields = coreDatasetTableFieldMapper.selectList(wp);
dto.setCalParams(Utils.getParams(datasetTableFieldManage.transDTO(coreDatasetTableFields)));
if (!CollectionUtils.isEmpty(chartViewDTOS)) {
List<Long> tableIds = chartViewDTOS.stream()
.map(ChartViewDTO::getTableId).distinct()
.toList();
if (!CollectionUtils.isEmpty(tableIds)) {
QueryWrapper<CoreDatasetTableField> wp = new QueryWrapper<>();
wp.in("dataset_group_id", tableIds);
List<CoreDatasetTableField> coreDatasetTableFields = coreDatasetTableFieldMapper.selectList(wp);
Map<Long, List<CoreDatasetTableField>> groupedByTableId = coreDatasetTableFields.stream()
.collect(Collectors.groupingBy(CoreDatasetTableField::getDatasetGroupId));
if(chartViewDTOS.size()<10){
chartViewDTOS.forEach(dto -> {
if (dto.getTableId() != null) {
dto.setCalParams(Utils.getParams(datasetTableFieldManage.transDTO(groupedByTableId.get(dto.getTableId()))));
}
});
}else{
ExecutorService executor = Executors.newFixedThreadPool(10);
try {
// 超过10个图表要处理启用多线程处理
CountDownLatch latch = new CountDownLatch(chartViewDTOS.size());
chartViewDTOS.forEach(dto -> {
executor.submit(() -> {
try {
if (dto.getTableId() != null) {
dto.setCalParams(Utils.getParams(datasetTableFieldManage.transDTO(groupedByTableId.get(dto.getTableId()))));
}
} finally {
latch.countDown(); // 减少计数器
}
});
});
// 等待所有线程完成
boolean completedInTime = latch.await(200, TimeUnit.SECONDS);
if (!completedInTime) {
throw new InterruptedException("Tasks did not complete within 200 seconds");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LogUtil.error(e);
} finally {
executor.shutdown(); // 确保线程池关闭
}
}
}
}
return chartViewDTOS;
}

View File

@ -606,7 +606,11 @@ public class SqlparserUtils {
&& sqlVariableDetails.getDeType() == 0) {
return "N'" + String.join("', N'", sqlVariableDetails.getValue()) + "'";
} else {
return "'" + String.join("','", sqlVariableDetails.getValue()) + "'";
if (sqlVariableDetails.getDeType() == 2 || sqlVariableDetails.getDeType() == 3) {
return String.join(",", sqlVariableDetails.getValue());
} else {
return "'" + String.join("','", sqlVariableDetails.getValue()) + "'";
}
}
} else if (sqlVariableDetails.getOperator().equals("between")) {
if (sqlVariableDetails.getDeType() == 1) {

View File

@ -113,8 +113,9 @@ public class DatasetDataManage {
sql = provider.transSqlDialect(sql, datasourceRequest.getDsList());
} else {
// parser sql params and replace default value
String originSql = provider.replaceComment(new String(Base64.getDecoder().decode(tableInfoDTO.getSql())));
originSql = SqlparserUtils.handleVariableDefaultValue(originSql, datasetTableDTO.getSqlVariableDetails(), false, false, null, false, datasourceRequest.getDsList(), pluginManage);
String s = new String(Base64.getDecoder().decode(tableInfoDTO.getSql()));
String originSql = SqlparserUtils.handleVariableDefaultValue(s, datasetTableDTO.getSqlVariableDetails(), false, false, null, false, datasourceRequest.getDsList(), pluginManage);
originSql = provider.replaceComment(originSql);
// add sql table schema
sql = SQLUtils.buildOriginPreviewSql(SqlPlaceholderConstants.TABLE_PLACEHOLDER, 0, 0);
@ -403,8 +404,9 @@ public class DatasetDataManage {
// parser sql params and replace default value
String originSql = provider.replaceComment(new String(Base64.getDecoder().decode(dto.getSql())));
originSql = SqlparserUtils.handleVariableDefaultValue(datasetSQLManage.subPrefixSuffixChar(originSql), dto.getSqlVariableDetails(), true, true, null, false, dsMap, pluginManage);
String s = new String(Base64.getDecoder().decode(dto.getSql()));
String originSql = SqlparserUtils.handleVariableDefaultValue(datasetSQLManage.subPrefixSuffixChar(s), dto.getSqlVariableDetails(), true, true, null, false, dsMap, pluginManage);
originSql = provider.replaceComment(originSql);
// sql 作为临时表外层加上limit
String sql;

View File

@ -222,14 +222,17 @@ public class DatasetGroupManage {
}
@XpackInteract(value = "authResourceTree", replace = true)
@XpackInteract(value = "authResourceTree", replace = true, invalid = true)
public List<BusiNodeVO> tree(BusiNodeRequest request) {
QueryWrapper<Object> queryWrapper = new QueryWrapper<>();
if (ObjectUtils.isNotEmpty(request.getLeaf())) {
queryWrapper.eq("node_type", request.getLeaf() ? "dataset" : "folder");
}
String info = CommunityUtils.getInfo();
if (StringUtils.isNotBlank(info)) {
queryWrapper.notExists(String.format(info, "core_dataset_group.id"));
}
queryWrapper.orderByDesc("create_time");
List<DataSetNodePO> pos = coreDataSetExtMapper.query(queryWrapper);
List<DataSetNodeBO> nodes = new ArrayList<>();

View File

@ -451,8 +451,9 @@ public class DatasetSQLManage {
} else if (StringUtils.equalsIgnoreCase(currentDs.getType(), DatasetTableTypeConstants.DATASET_TABLE_SQL)) {
Provider provider = ProviderFactory.getProvider(dsMap.entrySet().iterator().next().getValue().getType());
// parser sql params and replace default value
String sql = provider.replaceComment(new String(Base64.getDecoder().decode(infoDTO.getSql())));
sql = SqlparserUtils.handleVariableDefaultValue(sql, currentDs.getSqlVariableDetails(), false, isFromDataSet, parameters, isCross, dsMap, pluginManage);
String s = new String(Base64.getDecoder().decode(infoDTO.getSql()));
String sql = SqlparserUtils.handleVariableDefaultValue(s, currentDs.getSqlVariableDetails(), false, isFromDataSet, parameters, isCross, dsMap, pluginManage);
sql = provider.replaceComment(sql);
// add table schema
if (isCross) {
sql = SqlUtils.addSchema(sql, tableSchema);

View File

@ -279,18 +279,23 @@ public class DatasetTableFieldManage {
}
public List<DatasetTableFieldDTO> transDTO(List<CoreDatasetTableField> list) {
return list.stream().map(ele -> {
DatasetTableFieldDTO dto = new DatasetTableFieldDTO();
if (ele == null) return null;
BeanUtils.copyBean(dto, ele);
if (StringUtils.isNotEmpty(ele.getParams())) {
TypeReference<List<CalParam>> tokenType = new TypeReference<>() {
};
List<CalParam> calParams = JsonUtil.parseList(ele.getParams(), tokenType);
dto.setParams(calParams);
}
return dto;
}).collect(Collectors.toList());
if(!CollectionUtils.isEmpty(list)){
return list.stream().map(ele -> {
DatasetTableFieldDTO dto = new DatasetTableFieldDTO();
if (ele == null) return null;
BeanUtils.copyBean(dto, ele);
if (StringUtils.isNotEmpty(ele.getParams())) {
TypeReference<List<CalParam>> tokenType = new TypeReference<>() {
};
List<CalParam> calParams = JsonUtil.parseList(ele.getParams(), tokenType);
dto.setParams(calParams);
}
return dto;
}).collect(Collectors.toList());
}else{
return new ArrayList<>();
}
}
private CoreDatasetTableField transDTO2Record(DatasetTableFieldDTO dto) {

View File

@ -19,6 +19,7 @@ import io.dataease.model.BusiNodeVO;
import io.dataease.operation.manage.CoreOptRecentManage;
import io.dataease.utils.AuthUtils;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.CommunityUtils;
import io.dataease.utils.TreeUtils;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
@ -56,13 +57,17 @@ public class DataSourceManage {
return new DatasourceNodeBO(po.getId(), po.getName(), !StringUtils.equals(po.getType(), "folder"), 7, po.getPid(), extraFlag, dataSourceType.name());
}
@XpackInteract(value = "datasourceResourceTree", replace = true)
@XpackInteract(value = "datasourceResourceTree", replace = true, invalid = true)
public List<BusiNodeVO> tree(BusiNodeRequest request) {
QueryWrapper<DataSourceNodePO> queryWrapper = new QueryWrapper<>();
if (ObjectUtils.isNotEmpty(request.getLeaf()) && !request.getLeaf()) {
queryWrapper.eq("type", "folder");
}
String info = CommunityUtils.getInfo();
if (StringUtils.isNotBlank(info)) {
queryWrapper.notExists(String.format(info, "core_datasource.id"));
}
queryWrapper.orderByDesc("create_time");
List<DatasourceNodeBO> nodes = new ArrayList<>();
List<DataSourceNodePO> pos = dataSourceExtMapper.selectList(queryWrapper);

View File

@ -22,6 +22,7 @@ import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.job.schedule.ExtractDataJob;
import io.dataease.job.schedule.ScheduleManager;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.JsonUtil;
import io.dataease.utils.LogUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
@ -30,6 +31,7 @@ import org.quartz.JobKey;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -245,17 +247,7 @@ public class DatasourceSyncManage {
private void extractExcelData(DatasourceRequest datasourceRequest, DatasourceServer.UpdateType extractType, List<TableField> tableFields) throws Exception {
ExcelUtils excelUtils = new ExcelUtils();
List<String[]> dataList = excelUtils.fetchDataList(datasourceRequest);
String engineTableName;
switch (extractType) {
case all_scope:
engineTableName = TableUtils.tmpName(TableUtils.tableName(datasourceRequest.getTable()));
break;
default:
engineTableName = TableUtils.tableName(datasourceRequest.getTable());
break;
}
CoreDeEngine engine = engineManage.info();
EngineRequest engineRequest = new EngineRequest();
engineRequest.setEngine(engine);
EngineProvider engineProvider = ProviderUtil.getEngineProvider(engine.getType());
@ -267,7 +259,7 @@ public class DatasourceSyncManage {
totalPage = dataList.size() / pageNumber;
}
for (int page = 1; page <= totalPage; page++) {
engineRequest.setQuery(engineProvider.insertSql(engineTableName, extractType, dataList, page, pageNumber, tableFields));
engineRequest.setQuery(engineProvider.insertSql(datasourceRequest.getTable(), extractType, dataList, page, pageNumber, tableFields));
calciteProvider.exec(engineRequest);
}
}

View File

@ -437,6 +437,7 @@ public class ExcelUtils {
tableFiled.setFieldType(null);
tableFiled.setName(s);
tableFiled.setOriginName(s);
tableFiled.setChecked(true);
fields.add(tableFiled);
}
List<String[]> data = new ArrayList<>(noModelDataListener.getData());

View File

@ -40,12 +40,16 @@ public class H2EngineProvider extends EngineProvider {
Integer realSize = page * pageNumber < dataList.size() ? page * pageNumber : dataList.size();
for (String[] strings : dataList.subList((page - 1) * pageNumber, realSize)) {
String[] strings1 = new String[strings.length];
int length = 0;
String[] strings1 = new String[tableFields.stream().filter(TableField::isChecked).toList().size()];
for (int i = 0; i < strings.length; i++) {
if (StringUtils.isEmpty(strings[i])) {
strings1[i] = null;
} else {
strings1[i] = strings[i].replace("'", "\\'");
if (tableFields.get(i).isChecked()) {
if (StringUtils.isEmpty(strings[i])) {
strings1[length] = null;
} else {
strings1[length] = strings[i].replace("\\", "\\\\").replace("'", "\\'");
}
length++;
}
}
values.append("('").append(String.join("','", Arrays.asList(strings1)))
@ -81,6 +85,9 @@ public class H2EngineProvider extends EngineProvider {
StringBuilder columnFields = new StringBuilder("`");
StringBuilder key = new StringBuilder();
for (TableField tableField : tableFields) {
if (!tableField.isChecked()) {
continue;
}
if (tableField.isPrimaryKey()) {
key.append("`").append(tableField.getName()).append("`, ");
}

View File

@ -47,19 +47,23 @@ public class MysqlEngineProvider extends EngineProvider {
Integer realSize = page * pageNumber < dataList.size() ? page * pageNumber : dataList.size();
for (String[] strings : dataList.subList((page - 1) * pageNumber, realSize)) {
String[] strings1 = new String[strings.length];
int length = 0;
String[] strings1 = new String[tableFields.stream().filter(TableField::isChecked).toList().size()];
for (int i = 0; i < strings.length; i++) {
if (StringUtils.isEmpty(strings[i])) {
strings1[i] = null;
} else {
strings1[i] = strings[i].replace("\\", "\\\\").replace("'", "\\'");
if (tableFields.get(i).isChecked()) {
if (StringUtils.isEmpty(strings[i])) {
strings1[length] = null;
} else {
strings1[length] = strings[i].replace("\\", "\\\\").replace("'", "\\'");
}
length++;
}
}
values.append("('").append(String.join("','", Arrays.asList(strings1)))
.append("'),");
}
List<TableField> keys = tableFields.stream().filter(TableField::isPrimaryKey).toList();
List<TableField> notKeys = tableFields.stream().filter(tableField -> !tableField.isPrimaryKey()).toList();
List<TableField> keys = tableFields.stream().filter(tableField -> tableField.isPrimaryKey() && tableField.isChecked()).toList();
List<TableField> notKeys = tableFields.stream().filter(tableField -> tableField.isChecked() && !tableField.isPrimaryKey()).toList();
String insetSql = (insertSql + values.substring(0, values.length() - 1)).replaceAll("'null'", "null");
if (CollectionUtils.isNotEmpty(keys) && extractType.equals(DatasourceServer.UpdateType.add_scope)) {
insetSql = insetSql + " ON DUPLICATE KEY UPDATE ";
@ -101,6 +105,9 @@ public class MysqlEngineProvider extends EngineProvider {
StringBuilder columnFields = new StringBuilder("`");
StringBuilder key = new StringBuilder();
for (TableField tableField : tableFields) {
if (!tableField.isChecked()) {
continue;
}
if (tableField.isPrimaryKey()) {
key.append("`").append(tableField.getName()).append("`, ");
}

View File

@ -107,6 +107,8 @@ public class DatasourceServer implements DatasourceApi {
private PluginManageApi pluginManage;
@Autowired(required = false)
private RelationApi relationManage;
@Autowired
private CoreDatasourceMapper coreDatasourceMapper;
public enum UpdateType {
all_scope, add_scope
@ -406,14 +408,13 @@ public class DatasourceServer implements DatasourceApi {
requestDatasource.setEnableDataFill(null);
List<String> sourceTables = ExcelUtils.getTables(sourceTableRequest).stream().map(DatasetTableDTO::getTableName).collect(Collectors.toList());
List<String> tables = ExcelUtils.getTables(datasourceRequest).stream().map(DatasetTableDTO::getTableName).collect(Collectors.toList());
if (dataSourceDTO.getEditType() == 0) {
if (Objects.equals(dataSourceDTO.getEditType(), replace)) {
toCreateTables = tables;
toDeleteTables = sourceTables.stream().filter(s -> tables.contains(s)).collect(Collectors.toList());
for (String deleteTable : toDeleteTables) {
try {
datasourceSyncManage.dropEngineTable(deleteTable);
} catch (Exception e) {
DEException.throwException("Failed to drop table " + deleteTable + ", " + e.getMessage());
} catch (Exception ignore) {
}
}
for (String toCreateTable : toCreateTables) {
@ -424,12 +425,16 @@ public class DatasourceServer implements DatasourceApi {
DEException.throwException("Failed to create table " + toCreateTable + ", " + e.getMessage());
}
}
datasourceSyncManage.extractExcelData(requestDatasource, "all_scope");
commonThreadPool.addTask(() -> {
datasourceSyncManage.extractExcelData(requestDatasource, "all_scope");
});
dataSourceManage.checkName(dataSourceDTO);
ExcelUtils.mergeSheets(requestDatasource, sourceData);
dataSourceManage.innerEdit(requestDatasource);
} else {
datasourceSyncManage.extractExcelData(requestDatasource, "add_scope");
commonThreadPool.addTask(() -> {
datasourceSyncManage.extractExcelData(requestDatasource, "add_scope");
});
dataSourceManage.checkName(dataSourceDTO);
dataSourceManage.innerEdit(requestDatasource);
}
@ -765,6 +770,7 @@ public class DatasourceServer implements DatasourceApi {
ExcelUtils excelUtils = new ExcelUtils();
ExcelFileData excelFileData = excelUtils.excelSaveAndParse(file);
if (Objects.equals(editType, append)) { //按照excel sheet 名称匹配替换0追加1
if (coreDatasource != null) {
DatasourceRequest datasourceRequest = new DatasourceRequest();
@ -774,15 +780,9 @@ public class DatasourceServer implements DatasourceApi {
for (ExcelSheetData sheet : excelFileData.getSheets()) {
for (DatasetTableDTO datasetTableDTO : datasetTableDTOS) {
if (excelDataTableName(datasetTableDTO.getTableName()).equals(sheet.getTableName()) || isCsv(file.getOriginalFilename())) {
List<TableField> newTableFields = deepCopy(sheet.getFields());
newTableFields.sort((o1, o2) -> {
return o1.getName().compareTo(o2.getName());
});
List<TableField> newTableFields = sheet.getFields();
datasourceRequest.setTable(datasetTableDTO.getTableName());
List<TableField> oldTableFields = ExcelUtils.getTableFields(datasourceRequest);
oldTableFields.sort((o1, o2) -> {
return o1.getName().compareTo(o2.getName());
});
if (isEqual(newTableFields, oldTableFields)) {
sheet.setDeTableName(datasetTableDTO.getTableName());
excelSheetDataList.add(sheet);
@ -796,20 +796,29 @@ public class DatasourceServer implements DatasourceApi {
excelFileData.setSheets(excelSheetDataList);
}
} else {
// 替换
if (coreDatasource != null) {
DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setDatasource(transDTO(coreDatasource));
List<DatasetTableDTO> datasetTableDTOS = ExcelUtils.getTables(datasourceRequest);
for (ExcelSheetData sheet : excelFileData.getSheets()) {
boolean find = false;
for (DatasetTableDTO datasetTableDTO : datasetTableDTOS) {
if (excelDataTableName(datasetTableDTO.getTableName()).equals(sheet.getTableName()) || isCsv(file.getOriginalFilename())) {
find = true;
sheet.setDeTableName(datasetTableDTO.getTableName());
datasourceRequest.setTable(datasetTableDTO.getTableName());
List<TableField> oldTableFields = ExcelUtils.getTableFields(datasourceRequest);
mergeFields(sheet.getFields(), oldTableFields);
}
}
if (!find) {
sheet.setNewSheet(true);
}
}
}
}
for (ExcelSheetData sheet : excelFileData.getSheets()) {
for (int i = 0; i < sheet.getFields().size() - 1; i++) {
for (int j = i + 1; j < sheet.getFields().size(); j++) {
@ -823,31 +832,42 @@ public class DatasourceServer implements DatasourceApi {
}
private boolean isEqual(List<TableField> newTableFields, List<TableField> oldTableFields) {
boolean isEqual = true;
if (CollectionUtils.isEmpty(newTableFields) || CollectionUtils.isEmpty(oldTableFields)) {
isEqual = false;
return false;
}
for (int i = 0; i < newTableFields.size(); i++) {
if (!newTableFields.get(i).getName().equals(oldTableFields.get(i).getName())) {
isEqual = false;
break;
newTableFields.forEach(tableField -> tableField.setChecked(false));
for (TableField oldField : oldTableFields) {
if (!oldField.isChecked()) {
continue;
}
if (!newTableFields.get(i).getFieldType().equals(oldTableFields.get(i).getFieldType())) {
if (oldTableFields.get(i).getFieldType().equals("TEXT")) {
continue;
boolean find = false;
for (TableField newField : newTableFields) {
if (oldField.getName().equals(newField.getName())) {
find = true;
newField.setChecked(oldField.isChecked());
newField.setPrimaryKey(oldField.isPrimaryKey());
newField.setLength(oldField.getLength());
break;
}
if (oldTableFields.get(i).getFieldType().equals("DOUBLE")) {
if (newTableFields.get(i).getFieldType().equals("LONG")) {
continue;
}
}
isEqual = false;
break;
}
if (!find) {
return find;
}
}
return true;
}
return isEqual;
private void mergeFields(List<TableField> oldFields, List<TableField> newFields) {
oldFields.forEach(tableField -> tableField.setChecked(false));
for (TableField newField : newFields) {
for (TableField oldField : oldFields) {
if (oldField.getName().equals(newField.getName())) {
newField.setChecked(oldField.isChecked());
newField.setPrimaryKey(oldField.isPrimaryKey());
newField.setLength(oldField.getLength());
}
}
}
}
private boolean isCsv(String fileName) {
@ -1209,4 +1229,25 @@ public class DatasourceServer implements DatasourceApi {
return datasourceDTO;
}
@Override
public DsSimpleVO simple(Long id) {
if (ObjectUtils.isEmpty(id)) DEException.throwException("id is null");
CoreDatasource coreDatasource = coreDatasourceMapper.selectById(id);
if (ObjectUtils.isEmpty(coreDatasource)) return null;
DsSimpleVO vo = new DsSimpleVO();
vo.setName(coreDatasource.getName());
vo.setType(coreDatasource.getType());
vo.setDescription(coreDatasource.getDescription());
String configuration = coreDatasource.getConfiguration();
DatasourceConfiguration config = null;
String host = null;
if (StringUtils.isBlank(configuration)
|| StringUtils.equalsIgnoreCase("[]", configuration)
|| ObjectUtils.isEmpty(config = JsonUtil.parseObject(configuration, DatasourceConfiguration.class))
|| StringUtils.isBlank(host = config.getHost())) {
return vo;
}
vo.setHost(host);
return vo;
}
}

View File

@ -168,7 +168,11 @@ public class ExtWhere2Str {
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = "(" + value.stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")";
} else {
whereValue = "('" + StringUtils.join(value, "','") + "')";
if (request.getDatasetTableField().getDeType() == 2 || request.getDatasetTableField().getDeType() == 3) {
whereValue = "(" + StringUtils.join(value, ",") + ")";
} else {
whereValue = "('" + StringUtils.join(value, "','") + "')";
}
}
}
}

View File

@ -105,6 +105,7 @@ public class MenuManage {
|| coreMenu.getId().equals(50L)
|| coreMenu.getId().equals(60L)
|| coreMenu.getId().equals(61L)
|| coreMenu.getId().equals(80L)
|| coreMenu.getPid().equals(70L);
}
}

View File

@ -0,0 +1,16 @@
package io.dataease.msgCenter;
import io.dataease.api.msgCenter.MsgCenterApi;
import io.dataease.license.config.XpackInteract;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/msg-center")
public class MsgCenterServer implements MsgCenterApi {
@Override
@XpackInteract(value = "msgCenterServer", replace = true)
public long count() {
return 0;
}
}

View File

@ -166,6 +166,10 @@ public class XpackShareManage {
if (StringUtils.isNotBlank(request.getKeyword())) {
queryWrapper.like("v.name", request.getKeyword());
}
String info = CommunityUtils.getInfo();
if (StringUtils.isNotBlank(info)) {
queryWrapper.notExists(String.format(info, "s.resource_id"));
}
queryWrapper.orderBy(true, request.isAsc(), "s.time");
Page<XpackSharePO> page = new Page<>(goPage, pageSize);
return xpackShareExtMapper.query(page, queryWrapper);
@ -179,7 +183,7 @@ public class XpackShareManage {
};
}
@XpackInteract(value = "perFilterShareManage", recursion = true)
@XpackInteract(value = "perFilterShareManage", recursion = true, invalid = true)
public IPage<XpackShareGridVO> query(int pageNum, int pageSize, VisualizationWorkbranchQueryRequest request) {
IPage<XpackSharePO> poiPage = proxy().querySharePage(pageNum, pageSize, request);
List<XpackShareGridVO> vos = proxy().formatResult(poiPage.getRecords());

View File

@ -5,11 +5,11 @@ import java.io.Serializable;
/**
* <p>
*
* 可视化大屏信息表
* </p>
*
* @author fit2cloud
* @since 2024-04-11
* @since 2024-11-22
*/
@TableName("data_visualization_info")
public class DataVisualizationInfo implements Serializable {
@ -59,7 +59,7 @@ public class DataVisualizationInfo implements Serializable {
private String componentData;
/**
* 移动端布局
* 移动端布局0-关闭 1-开启
*/
private Boolean mobileLayout;
@ -128,11 +128,15 @@ public class DataVisualizationInfo implements Serializable {
*/
private Integer version;
/**
* 内容标识
*/
private String contentId;
public String getContentId() {
return contentId;
}
/**
* 内容检查标识
*/
private String checkVersion;
public void setContentId(String contentId) {
this.contentId = contentId;
@ -322,6 +326,18 @@ public class DataVisualizationInfo implements Serializable {
this.version = version;
}
public String getContentId() {
return contentId;
}
public String getCheckVersion() {
return checkVersion;
}
public void setCheckVersion(String checkVersion) {
this.checkVersion = checkVersion;
}
@Override
public String toString() {
return "DataVisualizationInfo{" +
@ -348,6 +364,8 @@ public class DataVisualizationInfo implements Serializable {
", deleteTime = " + deleteTime +
", deleteBy = " + deleteBy +
", version = " + version +
", contentId = " + contentId +
", checkVersion = " + checkVersion +
"}";
}
}

View File

@ -1,15 +1,16 @@
package io.dataease.visualization.dao.auto.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
/**
* <p>
*
* 跳转目标仪表板图表字段配置表
* </p>
*
* @author fit2cloud
* @since 2023-09-22
* @since 2024-11-20
*/
@TableName("visualization_link_jump_target_view_info")
public class VisualizationLinkJumpTargetViewInfo implements Serializable {
@ -18,6 +19,9 @@ public class VisualizationLinkJumpTargetViewInfo implements Serializable {
private Long targetId;
/**
* visualization_link_jump_info 表的 ID
*/
private Long linkJumpInfoId;
/**
@ -25,14 +29,31 @@ public class VisualizationLinkJumpTargetViewInfo implements Serializable {
*/
private Long sourceFieldActiveId;
private Long targetViewId;
/**
* 目标图表ID
*/
private String targetViewId;
private Long targetFieldId;
/**
* 目标字段ID
*/
private String targetFieldId;
/**
* 复制来源
*/
private Long copyFrom;
/**
* 复制来源ID
*/
private Long copyId;
/**
* 联动目标类型 view 图表 filter 过滤组件 outParams 外部参数
*/
private String targetType;
public Long getTargetId() {
return targetId;
}
@ -57,19 +78,19 @@ public class VisualizationLinkJumpTargetViewInfo implements Serializable {
this.sourceFieldActiveId = sourceFieldActiveId;
}
public Long getTargetViewId() {
public String getTargetViewId() {
return targetViewId;
}
public void setTargetViewId(Long targetViewId) {
public void setTargetViewId(String targetViewId) {
this.targetViewId = targetViewId;
}
public Long getTargetFieldId() {
public String getTargetFieldId() {
return targetFieldId;
}
public void setTargetFieldId(Long targetFieldId) {
public void setTargetFieldId(String targetFieldId) {
this.targetFieldId = targetFieldId;
}
@ -89,6 +110,14 @@ public class VisualizationLinkJumpTargetViewInfo implements Serializable {
this.copyId = copyId;
}
public String getTargetType() {
return targetType;
}
public void setTargetType(String targetType) {
this.targetType = targetType;
}
@Override
public String toString() {
return "VisualizationLinkJumpTargetViewInfo{" +
@ -99,6 +128,7 @@ public class VisualizationLinkJumpTargetViewInfo implements Serializable {
", targetFieldId = " + targetFieldId +
", copyFrom = " + copyFrom +
", copyId = " + copyId +
", targetType = " + targetType +
"}";
}
}

View File

@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
* </p>
*
* @author fit2cloud
* @since 2024-11-18
* @since 2024-11-22
*/
@Mapper
public interface DataVisualizationInfoMapper extends BaseMapper<DataVisualizationInfo> {

View File

@ -6,11 +6,11 @@ import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* 跳转目标仪表板图表字段配置表 Mapper 接口
* </p>
*
* @author fit2cloud
* @since 2023-09-22
* @since 2024-11-20
*/
@Mapper
public interface VisualizationLinkJumpTargetViewInfoMapper extends BaseMapper<VisualizationLinkJumpTargetViewInfo> {

View File

@ -4,6 +4,7 @@ import io.dataease.api.visualization.dto.VisualizationLinkJumpDTO;
import io.dataease.api.visualization.request.VisualizationLinkJumpBaseRequest;
import io.dataease.api.visualization.vo.VisualizationLinkJumpInfoVO;
import io.dataease.api.visualization.vo.VisualizationLinkJumpVO;
import io.dataease.api.visualization.vo.VisualizationOutParamsJumpVO;
import io.dataease.api.visualization.vo.VisualizationViewTableVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -36,6 +37,10 @@ public interface ExtVisualizationLinkJumpMapper {
void copyLinkJumpTarget(@Param("copyId")Long copyId);
List<VisualizationLinkJumpVO> findLinkJumpWithDvId(@Param("dvId")Long dvId);
List<VisualizationLinkJumpInfoVO> findLinkJumpInfoWithDvId(@Param("dvId")Long dvId);
List<VisualizationViewTableVO> getViewTableDetails(@Param("dvId")Long dvId);
List<VisualizationOutParamsJumpVO> queryOutParamsTargetWithDvId(@Param("dvId")Long dvId);
}

View File

@ -4,6 +4,7 @@ package io.dataease.visualization.dao.ext.mapper;
import io.dataease.api.dataset.vo.CoreDatasetGroupVO;
import io.dataease.api.visualization.dto.VisualizationOuterParamsDTO;
import io.dataease.api.visualization.dto.VisualizationOuterParamsInfoDTO;
import io.dataease.visualization.dao.auto.entity.VisualizationOuterParamsInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -22,5 +23,7 @@ public interface ExtVisualizationOuterParamsMapper {
List<VisualizationOuterParamsInfoDTO> getVisualizationOuterParamsInfo(@Param("visualizationId") String visualizationId);
List<VisualizationOuterParamsInfo> getVisualizationOuterParamsInfoBase(@Param("visualizationId") String visualizationId);
List<CoreDatasetGroupVO> queryDsWithVisualizationId(@Param("visualizationId") String visualizationId);
}

View File

@ -48,7 +48,7 @@ public class CoreVisualizationManage {
@Resource
private CoreOptRecentManage coreOptRecentManage;
@XpackInteract(value = "visualizationResourceTree", replace = true)
@XpackInteract(value = "visualizationResourceTree", replace = true, invalid = true)
public List<BusiNodeVO> tree(BusiNodeRequest request) {
List<VisualizationNodeBO> nodes = new ArrayList<>();
if (ObjectUtils.isEmpty(request.getLeaf()) || !request.getLeaf()) {
@ -59,6 +59,10 @@ public class CoreVisualizationManage {
queryWrapper.ne("pid", -1);
queryWrapper.eq(ObjectUtils.isNotEmpty(request.getLeaf()), "node_type", ObjectUtils.isNotEmpty(request.getLeaf()) && request.getLeaf() ? "leaf" : "folder");
queryWrapper.eq("type", request.getBusiFlag());
String info = CommunityUtils.getInfo();
if (StringUtils.isNotBlank(info)) {
queryWrapper.notExists(String.format(info, "data_visualization_info.id"));
}
queryWrapper.orderByDesc("create_time");
List<VisualizationNodePO> pos = extMapper.queryNodes(queryWrapper);
if (CollectionUtils.isNotEmpty(pos)) {
@ -158,7 +162,7 @@ public class CoreVisualizationManage {
return CommonBeanFactory.getBean(this.getClass());
}
@XpackInteract(value = "perFilterManage", recursion = true)
@XpackInteract(value = "perFilterManage", recursion = true, invalid = true)
public IPage<VisualizationResourceVO> query(int pageNum, int pageSize, VisualizationWorkbranchQueryRequest request) {
IPage<VisualizationResourcePO> visualizationResourcePOPageIPage = proxy().queryVisualizationPage(pageNum, pageSize, request);
if (ObjectUtils.isEmpty(visualizationResourcePOPageIPage)) {
@ -182,7 +186,7 @@ public class CoreVisualizationManage {
new VisualizationResourceVO(
po.getId(), po.getResourceId(), po.getName(),
po.getType(), String.valueOf(po.getCreator()), String.valueOf(po.getLastEditor()), po.getLastEditTime(),
po.getFavorite(), 9,po.getExtFlag())).toList();
po.getFavorite(), 9, po.getExtFlag())).toList();
}
public IPage<VisualizationResourcePO> queryVisualizationPage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) {
@ -198,6 +202,10 @@ public class CoreVisualizationManage {
if (StringUtils.isNotBlank(request.getKeyword())) {
queryWrapper.like("dvResource.name", request.getKeyword());
}
String info = CommunityUtils.getInfo();
if (StringUtils.isNotBlank(info)) {
queryWrapper.notExists(String.format(info, "core_opt_recent.resource_id"));
}
queryWrapper.orderBy(true, request.isAsc(), "core_opt_recent.time");
Page<VisualizationResourcePO> page = new Page<>(goPage, pageSize);
return extDataVisualizationMapper.findRecent(page, uid, queryWrapper);

View File

@ -11,6 +11,7 @@ import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.utils.AuthUtils;
import io.dataease.utils.CommonBeanFactory;
import io.dataease.utils.CommunityUtils;
import io.dataease.utils.IDUtils;
import io.dataease.visualization.dao.auto.entity.CoreStore;
import io.dataease.visualization.dao.auto.mapper.CoreStoreMapper;
@ -65,7 +66,7 @@ public class VisualizationStoreManage {
return coreStoreMapper.exists(queryWrapper);
}
@XpackInteract(value = "perFilterManage", recursion = true)
@XpackInteract(value = "perFilterManage", recursion = true, invalid = true)
public IPage<VisualizationStoreVO> query(int pageNum, int pageSize, VisualizationWorkbranchQueryRequest request) {
IPage<StorePO> storePOIPage = proxy().queryStorePage(pageNum, pageSize, request);
if (ObjectUtils.isEmpty(storePOIPage)) return null;
@ -89,7 +90,7 @@ public class VisualizationStoreManage {
new VisualizationStoreVO(
po.getStoreId(), po.getResourceId(), po.getName(),
po.getType(), String.valueOf(po.getCreator()), ObjectUtils.isEmpty(po.getEditor()) ? null : String.valueOf(po.getEditor()),
po.getEditTime(), 9,po.getExtFlag())).toList();
po.getEditTime(), 9, po.getExtFlag())).toList();
}
public IPage<StorePO> queryStorePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) {
@ -106,6 +107,10 @@ public class VisualizationStoreManage {
if (StringUtils.isNotBlank(request.getKeyword())) {
queryWrapper.like("v.name", request.getKeyword());
}
String info = CommunityUtils.getInfo();
if (StringUtils.isNotBlank(info)) {
queryWrapper.notExists(String.format(info, "s.resource_id"));
}
queryWrapper.orderBy(true, request.isAsc(), "v.update_time");
Page<StorePO> page = new Page<>(goPage, pageSize);
return coreStoreExtMapper.query(page, queryWrapper);

View File

@ -36,6 +36,7 @@ import io.dataease.exception.DEException;
import io.dataease.extensions.datasource.vo.DatasourceConfiguration;
import io.dataease.extensions.view.dto.ChartViewDTO;
import io.dataease.license.config.XpackInteract;
import io.dataease.license.manage.CoreLicManage;
import io.dataease.log.DeLog;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
@ -133,6 +134,9 @@ public class DataVisualizationServer implements DataVisualizationApi {
@Resource
private CoreBusiManage coreBusiManage;
@Resource
private CoreLicManage coreLicManage;
@Override
public DataVisualizationVO findCopyResource(Long dvId, String busiFlag) {
DataVisualizationVO result = Objects.requireNonNull(CommonBeanFactory.proxy(this.getClass())).findById(new DataVisualizationBaseRequest(dvId, busiFlag));
@ -572,6 +576,15 @@ public class DataVisualizationServer implements DataVisualizationApi {
return extDataVisualizationMapper.findDvType(dvId);
}
@Override
public String updateCheckVersion(Long dvId) {
DataVisualizationInfo updateInfo = new DataVisualizationInfo();
updateInfo.setId(dvId);
updateInfo.setCheckVersion(coreLicManage.getVersion());
visualizationInfoMapper.updateById(updateInfo);
return "";
}
@Override
public DataVisualizationVO decompression(DataVisualizationBaseRequest request) throws Exception {
try {

View File

@ -6,6 +6,7 @@ import io.dataease.api.visualization.dto.VisualizationLinkJumpDTO;
import io.dataease.api.visualization.dto.VisualizationLinkJumpInfoDTO;
import io.dataease.api.visualization.request.VisualizationLinkJumpBaseRequest;
import io.dataease.api.visualization.response.VisualizationLinkJumpBaseResponse;
import io.dataease.api.visualization.vo.VisualizationOutParamsJumpVO;
import io.dataease.api.visualization.vo.VisualizationViewTableVO;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
@ -148,15 +149,18 @@ public class VisualizationLinkJumpService implements VisualizationLinkJumpApi {
public VisualizationComponentDTO viewTableDetailList(Long dvId) {
DataVisualizationInfo dvInfo = dataVisualizationInfoMapper.selectById(dvId);
List<VisualizationViewTableVO> result;
List<VisualizationOutParamsJumpVO> outParamsJumpInfo;
String componentData;
if (dvInfo != null) {
result = extVisualizationLinkJumpMapper.getViewTableDetails(dvId).stream().filter(viewTableInfo -> dvInfo.getComponentData().indexOf(viewTableInfo.getId().toString()) > -1).collect(Collectors.toList());
componentData = dvInfo.getComponentData();
outParamsJumpInfo = extVisualizationLinkJumpMapper.queryOutParamsTargetWithDvId(dvId);
} else {
result = new ArrayList<>();
outParamsJumpInfo = new ArrayList<>();
componentData = "[]";
}
return new VisualizationComponentDTO(componentData,result);
return new VisualizationComponentDTO(componentData,result,outParamsJumpInfo);
}

View File

@ -29,10 +29,7 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -72,6 +69,12 @@ public class VisualizationOuterParamsService implements VisualizationOuterParams
public void updateOuterParamsSet(VisualizationOuterParamsDTO outerParamsDTO) {
String visualizationId = outerParamsDTO.getVisualizationId();
Assert.notNull(visualizationId, "visualizationId cannot be null");
Map<String,String> paramsInfoNameIdMap = new HashMap<>();
List<VisualizationOuterParamsInfo> paramsInfoNameIdList = extOuterParamsMapper.getVisualizationOuterParamsInfoBase(visualizationId);
if(!CollectionUtils.isEmpty(paramsInfoNameIdList)){
paramsInfoNameIdMap = paramsInfoNameIdList.stream()
.collect(Collectors.toMap(VisualizationOuterParamsInfo::getParamName, VisualizationOuterParamsInfo::getParamsInfoId));
}
//清理原有数据
extOuterParamsMapper.deleteOuterParamsTargetWithVisualizationId(visualizationId);
extOuterParamsMapper.deleteOuterParamsInfoWithVisualizationId(visualizationId);
@ -82,17 +85,22 @@ public class VisualizationOuterParamsService implements VisualizationOuterParams
VisualizationOuterParams newOuterParams = new VisualizationOuterParams();
BeanUtils.copyBean(newOuterParams, outerParamsDTO);
outerParamsMapper.insert(newOuterParams);
Map<String, String> finalParamsInfoNameIdMap = paramsInfoNameIdMap;
Optional.ofNullable(outerParamsDTO.getOuterParamsInfoArray()).orElse(new ArrayList<>()).forEach(outerParamsInfo -> {
String paramsInfoId = UUID.randomUUID().toString();
String paramsInfoId = finalParamsInfoNameIdMap.get(outerParamsInfo.getParamName());
if(StringUtils.isEmpty(paramsInfoId)){
paramsInfoId = UUID.randomUUID().toString();
}
outerParamsInfo.setParamsInfoId(paramsInfoId);
outerParamsInfo.setParamsId(paramsId);
VisualizationOuterParamsInfo newOuterParamsInfo = new VisualizationOuterParamsInfo();
BeanUtils.copyBean(newOuterParamsInfo, outerParamsInfo);
outerParamsInfoMapper.insert(newOuterParamsInfo);
String finalParamsInfoId = paramsInfoId;
Optional.ofNullable(outerParamsInfo.getTargetViewInfoList()).orElse(new ArrayList<>()).forEach(targetViewInfo -> {
String targetViewInfoId = UUID.randomUUID().toString();
targetViewInfo.setTargetId(targetViewInfoId);
targetViewInfo.setParamsInfoId(paramsInfoId);
targetViewInfo.setParamsInfoId(finalParamsInfoId);
VisualizationOuterParamsTargetViewInfo newOuterParamsTargetViewInfo = new VisualizationOuterParamsTargetViewInfo();
BeanUtils.copyBean(newOuterParamsTargetViewInfo, targetViewInfo);
outerParamsTargetViewInfoMapper.insert(newOuterParamsTargetViewInfo);

View File

@ -2,7 +2,20 @@ INSERT INTO `core_sys_setting`(`id`, `pkey`, `pval`, `type`, `sort`)
VALUES (1048232869488627719, 'basic.defaultSort', '1', 'text', 13);
INSERT INTO `core_sys_setting`(`id`, `pkey`, `pval`, `type`, `sort`)
VALUES (1048232869488627719, 'basic.defaultOpen', '1', 'text', 14);
VALUES (1048232869488627720, 'basic.defaultOpen', '1', 'text', 14);
ALTER TABLE `data_visualization_info`
ADD COLUMN `content_id` varchar(50) NULL DEFAULT '0' COMMENT '内容标识';
ADD COLUMN `content_id` varchar(50) NULL DEFAULT '0' COMMENT '内容标识';
ALTER TABLE `visualization_link_jump_target_view_info`
ADD COLUMN `target_type` varchar(50) NULL COMMENT '联动目标类型 view 图表 filter 过滤组件 outParams 外部参数';
ALTER TABLE `visualization_link_jump_target_view_info`
MODIFY COLUMN `target_view_id` varchar(50) NULL DEFAULT NULL COMMENT '目标图表ID';
ALTER TABLE `visualization_link_jump_target_view_info`
MODIFY COLUMN `target_field_id` varchar(50) NULL DEFAULT NULL COMMENT '目标字段ID';
update visualization_link_jump_target_view_info set target_type = 'view';
ALTER TABLE `data_visualization_info`
ADD COLUMN `check_version` varchar(50) NULL DEFAULT '1' COMMENT '内容检查标识';
update data_visualization_info set check_version = '1';

View File

@ -2,7 +2,7 @@ INSERT INTO `core_sys_setting`(`id`, `pkey`, `pval`, `type`, `sort`)
VALUES (1048232869488627719, 'basic.defaultSort', '1', 'text', 13);
INSERT INTO `core_sys_setting`(`id`, `pkey`, `pval`, `type`, `sort`)
VALUES (1048232869488627719, 'basic.defaultOpen', '0', 'text', 14);
VALUES (1048232869488627720, 'basic.defaultOpen', '0', 'text', 14);
INSERT INTO `core_menu` VALUES (70, 0, 1, 'msg', NULL, 200, NULL, '/msg', 1, 1, 0);
@ -10,3 +10,15 @@ UPDATE `xpack_setting_authentication` set `synced` = 0 where `name` = 'oidc' or
ALTER TABLE `data_visualization_info`
ADD COLUMN `content_id` varchar(50) NULL DEFAULT '0' COMMENT '内容标识';
ALTER TABLE `visualization_link_jump_target_view_info`
ADD COLUMN `target_type` varchar(50) NULL DEFAULT 'view' COMMENT '联动目标类型 view 图表 filter 过滤组件 outParams 外部参数';
ALTER TABLE `visualization_link_jump_target_view_info`
MODIFY COLUMN `target_view_id` varchar(50) NULL DEFAULT NULL COMMENT '目标图表ID' AFTER `source_field_active_id`,
MODIFY COLUMN `target_field_id` varchar(50) NULL DEFAULT NULL COMMENT '目标字段ID' AFTER `target_view_id`;
update visualization_link_jump_target_view_info set target_type = 'view';
ALTER TABLE `data_visualization_info`
ADD COLUMN `check_version` varchar(50) NULL DEFAULT '1' COMMENT '内容检查标识';
update data_visualization_info set check_version = '1';

View File

@ -103,6 +103,10 @@ i18n_df_decimal=decimal
i18n_df_multiple_value_split=use ';' to split multiple value
i18n_df_email_type=email type
i18n_df_phone_type=phone type
i18n_df_lt_check=need less than %s: %s
i18n_df_gt_check=need greater than %s: %s
i18n_df_le_check=need less than or equal to %s: %s
i18n_df_ge_check=need greater than or equal to %s: %s

View File

@ -123,6 +123,10 @@ i18n_df_decimal=\u5C0F\u6570\u6570\u5B57
i18n_df_multiple_value_split=\u591A\u4E2A\u503C\u4F7F\u7528\u5206\u53F7";"\u5206\u5272
i18n_df_email_type=\u90AE\u7BB1\u683C\u5F0F
i18n_df_phone_type=\u624B\u673A\u53F7\u683C\u5F0F
i18n_df_lt_check=\u503C\u9700\u8981\u5C0F\u4E8E %s: %s
i18n_df_gt_check=\u503C\u9700\u8981\u5927\u4E8E %s: %s
i18n_df_le_check=\u503C\u9700\u8981\u5C0F\u4E8E\u7B49\u4E8E %s: %s
i18n_df_ge_check=\u503C\u9700\u8981\u5927\u4E8E\u7B49\u4E8E %s: %s

View File

@ -123,6 +123,10 @@ i18n_df_decimal=\u5C0F\u6578\u6578\u5B57
i18n_df_multiple_value_split=\u591A\u500B\u503C\u4F7F\u7528\u5206\u865F";"\u5206\u5272
i18n_df_email_type=\u90F5\u7BB1\u683C\u5F0F
i18n_df_phone_type=\u624B\u6A5F\u865F\u683C\u5F0F
i18n_df_lt_check=\u503C\u9700\u8981\u5C0F\u4E8E %s: %s
i18n_df_gt_check=\u503C\u9700\u8981\u5927\u4E8E %s: %s
i18n_df_le_check=\u503C\u9700\u8981\u5C0F\u4E8E\u7B49\u4E8E %s: %s
i18n_df_ge_check=\u503C\u9700\u8981\u5927\u4E8E\u7B49\u4E8E %s: %s

View File

@ -167,7 +167,8 @@
`delete_time`,
`delete_by`,
`version`,
`content_id`
`content_id`,
`check_version`
FROM data_visualization_info
where data_visualization_info.delete_flag = 0
and data_visualization_info.id = #{dvId}

View File

@ -43,9 +43,12 @@
<result column="publicJumpId" jdbcType="VARCHAR" property="publicJumpId"/>
<collection property="targetViewInfoList"
ofType="io.dataease.api.visualization.vo.VisualizationLinkJumpTargetViewInfoVO">
<result column="target_id" jdbcType="BIGINT" property="targetId"/>
<result column="target_view_id" jdbcType="BIGINT" property="targetViewId"/>
<result column="target_field_id" jdbcType="BIGINT" property="targetFieldId"/>
<result column="source_field_active_id" jdbcType="VARCHAR" property="sourceFieldActiveId"/>
<result column="target_type" jdbcType="VARCHAR" property="targetType"/>
<result column="outer_params_name" jdbcType="VARCHAR" property="outerParamsName"/>
</collection>
</resultMap>
<resultMap id="ViewTableFieldDetailsMap" type="io.dataease.api.visualization.vo.VisualizationViewTableVO">
@ -85,9 +88,12 @@
xpack_share.uuid AS publicJumpId,
ifnull( visualization_link_jump_info.checked, 0 ) AS checked,
ifnull( visualization_link_jump_info.attach_params, 0 ) AS attach_params,
visualization_link_jump_target_view_info.target_id,
visualization_link_jump_target_view_info.target_view_id,
visualization_link_jump_target_view_info.target_field_id,
visualization_link_jump_target_view_info.source_field_active_id
visualization_link_jump_target_view_info.target_type,
visualization_link_jump_target_view_info.source_field_active_id,
visualization_outer_params_info.param_name as outer_params_name
FROM
core_chart_view
LEFT JOIN core_dataset_table_field ON core_chart_view.table_id = core_dataset_table_field.dataset_group_id
@ -98,6 +104,7 @@
LEFT JOIN visualization_link_jump_target_view_info ON visualization_link_jump_info.id = visualization_link_jump_target_view_info.link_jump_info_id
LEFT JOIN xpack_share ON xpack_share.creator = #{uid}
AND visualization_link_jump_info.target_dv_id = xpack_share.resource_id
left join visualization_outer_params_info on visualization_outer_params_info.params_info_id = visualization_link_jump_target_view_info.target_view_id
WHERE
core_chart_view.id = #{source_view_id}
AND core_chart_view.type != 'VQuery'
@ -155,6 +162,19 @@
WHERE core_chart_view.id = #{viewId}
</select>
<select id="queryOutParamsTargetWithDvId" resultType="io.dataease.api.visualization.vo.VisualizationOutParamsJumpVO">
SELECT
vopi.params_info_id as id,
vopi.param_name as name,
vopi.param_name as title,
'outerParams' as type
FROM
visualization_outer_params_info vopi
LEFT JOIN visualization_outer_params vop ON vopi.params_id = vop.params_id
WHERE
vop.visualization_id = #{dvId}
</select>
<delete id="deleteJumpTargetViewInfo">
DELETE
ljtv

View File

@ -139,6 +139,17 @@
and popi.checked=1
</select>
<select id="getVisualizationOuterParamsInfoBase" resultType="io.dataease.visualization.dao.auto.entity.VisualizationOuterParamsInfo">
SELECT
vopi.param_name,
vopi.params_info_id
FROM
visualization_outer_params_info vopi
INNER JOIN visualization_outer_params vop ON vop.params_id = vopi.params_id
WHERE
vop.visualization_id = #{visualizationId}
</select>
<select id="queryDsWithVisualizationId" resultMap="BaseDsResultMapDTO">
SELECT DISTINCT
cdg.*,#{visualizationId} as visualizationId

View File

@ -0,0 +1,3 @@
import request from '@/config/axios'
export const msgCountApi = () => request.post({ url: '/msg-center/count', data: {} })

View File

@ -35,6 +35,9 @@ export const findById = async (
return request.post({ url: '/dataVisualization/findById', data })
}
export const updateCheckVersion = dvId =>
request.get({ url: `/dataVisualization/updateCheckVersion/${dvId}` })
export const queryTreeApi = async (data: BusiTreeRequest): Promise<IResponse> => {
return request.post({ url: '/dataVisualization/tree', data }).then(res => {
return res?.data

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="44px" height="44px" viewBox="0 0 44 44" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>模糊修复</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="模糊修复" fill="#BBBFC3" fill-rule="nonzero">
<path d="M4.94539535,3.06976745 C3.90986044,3.06976745 3.06976745,3.90883718 3.06976745,4.94539535 L3.06976745,39.0546046 C3.06976745,40.0901396 3.90883718,40.9302326 4.94539535,40.9302326 L39.0546046,40.9302326 C40.0901396,40.9302326 40.9302326,40.0911628 40.9302326,39.0546046 L40.9302326,4.94539535 C40.9302326,3.90986044 40.0911628,3.06976745 39.0546046,3.06976745 L4.94539535,3.06976745 Z M0,4.94539535 C0,2.21412892 2.21412892,0 4.94539535,0 L39.0546046,0 C41.7858711,0 44,2.21412892 44,4.94539535 L44,39.0546046 C44,41.7858711 41.7858711,44 39.0546046,44 L4.94539535,44 C2.21412892,44 0,41.7858711 0,39.0546046 L0,4.94539535 L0,4.94539535 Z" id="形状"></path>
<path d="M10,11.4868562 C10,10.6656882 10.6656354,10 11.4867383,10 L33.5132617,10 C34.3343646,10 35,10.6656882 35,11.4868562 L35,33.5131438 C35,34.3343118 34.3343646,35 33.5132617,35 L11.4867383,35 C10.6656354,35 10,34.3343118 10,33.5131438 L10,11.4868562 L10,11.4868562 Z M12.9734765,12.9737123 L12.9734765,32.0262877 L32.0265234,32.0262877 L32.0265234,12.9737123 L12.9734765,12.9737123 Z" id="形状"></path>
<path d="M34.5646998,20.3474464 C35.1451,20.9280211 35.1451,21.8691328 34.5646998,22.4497075 L22.4506634,34.5647187 C22.074953,34.9402515 21.5274377,35.0868193 21.0143611,34.9492114 C20.5012846,34.8116035 20.1005953,34.4107259 19.9632291,33.8975852 C19.8258629,33.3844446 19.972689,32.8369991 20.3483993,32.4614663 L32.4624357,20.3474464 C33.0430113,19.7670469 33.9841243,19.7670469 34.5646998,20.3474464 L34.5646998,20.3474464 Z M24.6540264,10.4357952 C25.2344267,11.01637 25.2344267,11.9574817 24.6540264,12.5380565 L12.5380077,24.6530676 C11.9572098,25.233591 11.0157722,25.2333691 10.435248,24.652572 C9.85472385,24.0717749 9.85494572,23.1303387 10.4357436,22.5498153 L22.54978,10.4357952 C22.828627,10.1567699 23.206932,10 23.6014076,10 C23.9958833,10 24.3741883,10.1567699 24.6530352,10.4357952 L24.6540264,10.4357952 Z M34.5646998,10.4357952 C35.1451001,11.01637 35.1451001,11.9574817 34.5646998,12.5380565 L12.5389988,34.5647187 C11.9582009,35.1452421 11.0167634,35.1450202 10.4362392,34.5642231 C9.85571504,33.983426 9.8559369,33.0419897 10.4367348,32.4614663 L32.4624357,10.4357952 C33.0430113,9.85539577 33.9841243,9.85539577 34.5646998,10.4357952 Z" id="形状"></path>
<path d="M11.4859238,18.8111098 C11.8802783,18.8111098 12.258481,18.9677602 12.5373316,19.2465999 C12.8161823,19.5254395 12.972839,19.9036272 12.972839,20.297966 L12.972839,32.0262877 L24.7036082,32.0262877 C25.2348319,32.0262876 25.7257028,32.3096808 25.9913146,32.7697157 C26.2569264,33.2297506 26.2569264,33.796537 25.9913146,34.2565719 C25.7257028,34.7166069 25.2348319,35 24.7036082,35 L11.4869151,35 C10.6657146,35 10,34.3343118 10,33.5131438 L10,20.297966 C10,19.9036272 10.1566567,19.5254395 10.4355073,19.2465999 C10.714358,18.9677602 11.0925607,18.8111098 11.4869151,18.8111098 L11.4859238,18.8111098 Z M18.8084853,11.4868562 C18.8084853,11.0925174 18.965142,10.7143297 19.2439927,10.4354901 C19.5228434,10.1566504 19.9010461,10 20.2954005,10 L33.5130849,10 C34.3342854,10 35,10.6656882 35,11.4868562 L35,24.7030253 C35,25.5241932 34.3342854,26.1898814 33.5130849,26.1898814 C32.6918843,26.1898814 32.0261697,25.5241932 32.0261697,24.7030253 L32.0261697,12.9737123 L20.2954005,12.9737123 C19.9010461,12.9737124 19.5228434,12.8170619 19.2439927,12.5382223 C18.965142,12.2593826 18.8084853,11.881195 18.8084853,11.4868562 L18.8084853,11.4868562 Z" id="形状"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.833344 3.75C0.833344 2.82952 1.57954 2.08333 2.50001 2.08333H7.91668C8.16399 2.08333 8.39853 2.19319 8.55686 2.38318L10.3903 4.58333H15.625C16.5455 4.58333 17.2917 5.32951 17.2917 6.25V8.75C17.2917 9.21024 16.9186 9.58333 16.4583 9.58333H4.40066L2.47513 17.2854C2.37305 17.6937 1.98257 17.9618 1.56486 17.9104C1.14715 17.859 0.833344 17.5042 0.833344 17.0833V3.75ZM7.52636 3.75H2.50001V10.3141L2.94156 8.54789C3.0343 8.17691 3.36762 7.91667 3.75001 7.91667H15.625V6.25H10C9.75269 6.25 9.51815 6.14015 9.35983 5.95015L7.52636 3.75Z" fill=""/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.86165 8.55504C2.95176 8.18059 3.28672 7.91667 3.67186 7.91667H18.3333C18.583 7.91667 18.8195 8.0286 18.9778 8.22168C19.1361 8.41475 19.1994 8.66862 19.1505 8.91343L17.4838 17.2468C17.4059 17.6363 17.0639 17.9167 16.6667 17.9167H1.66665C1.41133 17.9167 1.1701 17.7996 1.01209 17.5991C0.854075 17.3985 0.796715 17.1366 0.856446 16.8884L2.86165 8.55504ZM4.32846 9.58333L2.72429 16.25H15.9835L17.3168 9.58333H4.32846Z" fill=""/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M0.666577 4.60903C0.666577 4.69744 0.701696 4.78222 0.764208 4.84474C0.82672 4.90725 0.911505 4.94237 0.999911 4.94237H13.1666C13.7692 4.94237 14.0626 4.20637 13.6249 3.7917L10.2166 0.5627C10.1848 0.532575 10.1474 0.509006 10.1065 0.493338C10.0656 0.47767 10.0221 0.470212 9.97829 0.471388C9.93452 0.472564 9.89141 0.482352 9.85142 0.500193C9.81143 0.518034 9.77536 0.543579 9.74524 0.575367L9.28391 1.06237C9.2236 1.12601 9.1908 1.21086 9.19261 1.29852C9.19442 1.38618 9.23069 1.4696 9.29358 1.5307L11.4936 3.66837H0.999911C0.956137 3.66837 0.912791 3.67699 0.872349 3.69374C0.831908 3.71049 0.795161 3.73504 0.764208 3.766C0.733255 3.79695 0.708702 3.8337 0.691951 3.87414C0.675199 3.91458 0.666577 3.95793 0.666577 4.0017V4.60903ZM0.833244 9.00003C0.230577 9.00003 -0.0627561 9.73603 0.374911 10.1507L3.78324 13.38C3.81504 13.4101 3.85246 13.4337 3.89336 13.4493C3.93425 13.465 3.97783 13.4724 4.0216 13.4712C4.06537 13.47 4.10848 13.4602 4.14845 13.4423C4.18843 13.4244 4.22449 13.3988 4.25458 13.367L4.71324 12.8834C4.74335 12.8516 4.76689 12.8142 4.78253 12.7733C4.79817 12.7324 4.8056 12.6888 4.80439 12.645C4.80318 12.6012 4.79337 12.5581 4.7755 12.5182C4.75763 12.4782 4.73205 12.4421 4.70024 12.412L2.50658 10.3334H12.9999C13.0883 10.3334 13.1731 10.2982 13.2356 10.2357C13.2981 10.1732 13.3332 10.0884 13.3332 10V9.33337C13.3332 9.24496 13.2981 9.16018 13.2356 9.09766C13.1731 9.03515 13.0883 9.00003 12.9999 9.00003H0.833244Z" fill=""/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -224,7 +224,15 @@ const saveResource = () => {
ElMessage.success(t('common.save_success'))
let url = window.location.href
url = url.replace(/\?opt=create/, `?resourceId=${dvInfo.value.id}`)
window.history.replaceState(null, '', url)
if (!embeddedStore.baseUrl) {
window.history.replaceState(
{
path: url
},
'',
url
)
}
if (appData.value) {
initCanvasData(dvInfo.value.id, 'dashboard', () => {
@ -286,7 +294,12 @@ const backHandler = (url: string) => {
return
}
wsCache.delete('DE-DV-CATCH-' + dvInfo.value.id)
window.open(url, '_self')
wsCache.set('db-info-id', dvInfo.value.id)
if (!!history.state.back) {
history.back()
} else {
window.open(url, '_self')
}
}
const multiplexingCanvasOpen = () => {

View File

@ -23,6 +23,21 @@
</color-button>
</el-space>
</el-form-item>
<el-form-item
v-if="dvInfo.type === 'dashboard'"
class="form-item"
:class="'form-item-' + themes"
label="仪表板字体选择"
>
<el-select :effect="themes" v-model="canvasStyleData.fontFamily" @change="fontFamilyChange()">
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="dvInfo.type === 'dashboard'"
class="form-item"
@ -221,10 +236,12 @@ const dvMainStore = dvMainStoreWithOut()
const { canvasStyleData, dvInfo } = storeToRefs(dvMainStore)
import {
adaptCurThemeCommonStyleAll,
adaptTitleFontFamilyAll,
DARK_THEME_DASHBOARD_BACKGROUND,
LIGHT_THEME_DASHBOARD_BACKGROUND
} from '@/utils/canvasStyle'
import {
CHART_FONT_FAMILY,
DEFAULT_COLOR_CASE_DARK,
DEFAULT_COLOR_CASE_LIGHT,
DEFAULT_TAB_COLOR_CASE_DARK,
@ -245,8 +262,11 @@ import {
COMMON_COMPONENT_BACKGROUND_DARK,
COMMON_COMPONENT_BACKGROUND_LIGHT
} from '@/custom-component/component-list'
import { ElFormItem, ElIcon, ElMessage, ElSpace } from 'element-plus-secondary'
import { ElFormItem, ElIcon, ElSpace } from 'element-plus-secondary'
import Icon from '@/components/icon-custom/src/Icon.vue'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
const appearanceStore = useAppearanceStoreWithOut()
const snapshotStore = snapshotStoreWithOut()
const props = defineProps({
themes: {
@ -254,6 +274,13 @@ const props = defineProps({
default: 'light'
}
})
const fontFamily = CHART_FONT_FAMILY.concat(
appearanceStore.fontList.map(ele => ({
name: ele.name,
value: ele.name
}))
)
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
@ -271,6 +298,15 @@ const onRefreshChange = val => {
}
themeChange()
}
const fontFamilyChange = () => {
appearanceStore.setCurrentFont(canvasStyleData.value.fontFamily)
document.documentElement.style.setProperty(
'--de-canvas_custom_font',
`${canvasStyleData.value.fontFamily}`
)
adaptTitleFontFamilyAll(canvasStyleData.value.fontFamily)
snapshotStore.recordSnapshotCache('renderChart')
}
const themeChange = (modifyName?) => {
if (modifyName === 'themeColor') {

View File

@ -24,7 +24,8 @@ const canvasAttrActiveNames = ref(['size', 'baseSetting', 'background', 'color']
const screenAdaptorList = [
{ label: '宽度优先', value: 'widthFirst' },
{ label: '高度优先', value: 'heightFirst' },
{ label: '铺满全屏', value: 'full' }
{ label: '铺满全屏', value: 'full' },
{ label: '不缩放', value: 'keep' }
]
const init = () => {
nextTick(() => {

View File

@ -166,7 +166,15 @@ const saveResource = () => {
ElMessage.success('保存成功')
let url = window.location.href
url = url.replace(/\?opt=create/, `?dvId=${dvInfo.value.id}`)
window.history.replaceState(null, '', url)
if (!embeddedStore.baseUrl) {
window.history.replaceState(
{
path: url
},
'',
url
)
}
if (appData.value) {
initCanvasData(dvInfo.value.id, 'dataV', () => {
useEmitt().emitter.emit('refresh-dataset-selector')
@ -235,7 +243,12 @@ const backHandler = (url: string) => {
}
dvMainStore.canvasStateChange({ key: 'curPointArea', value: 'base' })
wsCache.delete('DE-DV-CATCH-' + dvInfo.value.id)
window.open(url, '_self')
wsCache.set('dv-info-id', dvInfo.value.id)
if (!!history.state.back) {
history.back()
} else {
window.open(url, '_self')
}
}
const openHandler = ref(null)

View File

@ -172,6 +172,11 @@ const props = defineProps({
type: Boolean,
required: false,
default: true
},
fontFamily: {
type: String,
required: false,
default: 'inherit'
}
})
@ -1628,6 +1633,7 @@ defineExpose({
:dv-info="dvInfo"
:canvas-active="canvasActive"
:show-position="'canvas'"
:font-family="fontFamily"
/>
<component
v-else-if="item.component.includes('Svg')"
@ -1646,6 +1652,7 @@ defineExpose({
:active="item.id === curComponentId"
:canvas-active="canvasActive"
:show-position="'edit'"
:font-family="fontFamily"
/>
<component
v-else
@ -1664,6 +1671,7 @@ defineExpose({
:active="item.id === curComponentId"
:canvas-active="canvasActive"
:show-position="'edit'"
:font-family="fontFamily"
/>
</Shape>
<!-- 右击菜单 -->

View File

@ -107,6 +107,12 @@ const props = defineProps({
type: String,
required: false,
default: 'common'
},
//
fontFamily: {
type: String,
required: false,
default: 'inherit'
}
})
const {
@ -196,6 +202,8 @@ const onMouseEnter = () => {
const componentBackgroundStyle = computed(() => {
if (config.value.commonBackground) {
const {
backdropFilterEnable,
backdropFilter,
backgroundColorSelect,
backgroundColor,
backgroundImageEnable,
@ -243,6 +251,9 @@ const componentBackgroundStyle = computed(() => {
if (config.value.component !== 'UserView') {
style['overflow'] = 'hidden'
}
if (backdropFilterEnable) {
style['backdrop-filter'] = 'blur(' + backdropFilter + 'px)'
}
return style
}
return {}
@ -419,6 +430,7 @@ const showActive = computed(() => props.popActive || (dvMainStore.mobileInPc &&
:disabled="true"
:is-edit="false"
:suffix-id="suffixId"
:font-family="fontFamily"
@onPointClick="onPointClick"
/>
</div>

View File

@ -141,9 +141,12 @@ const canvasStyle = computed(() => {
style['overflowY'] = 'hidden !important'
}
if (canvasStyleData.value && canvasStyleData.value.width && isMainCanvas(canvasId.value)) {
style = {
...getCanvasStyle(canvasStyleData.value),
height: dashboardActive.value
style = getCanvasStyle(canvasStyleData.value)
if (canvasStyleData.value?.screenAdaptor === 'keep') {
style['height'] = canvasStyleData.value?.height + 'px'
style['width'] = canvasStyleData.value?.width + 'px'
} else {
style['height'] = dashboardActive.value
? downloadStatus.value
? getDownloadStatusMainHeight()
: '100%'
@ -151,11 +154,11 @@ const canvasStyle = computed(() => {
canvasStyleData.value?.screenAdaptor === 'widthFirst'
? changeStyleWithScale(canvasStyleData.value?.height, scaleMin.value) + 'px'
: '100%'
style['width'] =
!dashboardActive.value && canvasStyleData.value?.screenAdaptor === 'heightFirst'
? changeStyleWithScale(canvasStyleData.value?.width, scaleHeightPoint.value) + 'px'
: '100%'
}
style['width'] =
!dashboardActive.value && canvasStyleData.value?.screenAdaptor === 'heightFirst'
? changeStyleWithScale(canvasStyleData.value?.width, scaleHeightPoint.value) + 'px'
: '100%'
}
return style
})

View File

@ -131,7 +131,12 @@ import Icon from '@/components/icon-custom/src/Icon.vue'
import ComponentEditBar from '@/components/visualization/ComponentEditBar.vue'
import { useEmitt } from '@/hooks/web/useEmitt'
import ComposeShow from '@/components/data-visualization/canvas/ComposeShow.vue'
import { groupSizeStyleAdaptor, groupStyleRevert, groupStyleRevertBatch } from '@/utils/style'
import {
groupSizeStyleAdaptor,
groupStyleRevert,
groupStyleRevertBatch,
tabInnerStyleRevert
} from '@/utils/style'
import { isDashboard, isGroupCanvas, isMainCanvas, isTabCanvas } from '@/utils/canvasUtils'
import Board from '@/components/de-board/Board.vue'
import { activeWatermarkCheckUser, removeActiveWatermark } from '@/components/watermark/watermark'
@ -789,6 +794,12 @@ const handleMouseDownOnPoint = (point, e) => {
}
const up = () => {
//
if (['DeTabs'].includes(element.value.component) && element.value.resizeInnerKeep) {
console.log('===test3==')
tabInnerStyleRevert(element.value)
}
dashboardActive.value && emit('onMouseUp')
element.value['resizing'] = false
document.removeEventListener('mousemove', move)
@ -881,6 +892,8 @@ const padding3D = computed(() => {
const componentBackgroundStyle = computed(() => {
if (element.value.commonBackground && element.value.component !== 'GroupArea') {
const {
backdropFilterEnable,
backdropFilter,
backgroundColorSelect,
backgroundColor,
backgroundImageEnable,
@ -931,6 +944,9 @@ const componentBackgroundStyle = computed(() => {
if (element.value.component !== 'UserView') {
style['overflow'] = 'hidden'
}
if (backdropFilterEnable) {
style['backdrop-filter'] = 'blur(' + backdropFilter + 'px)'
}
return style
}
return {}
@ -977,6 +993,7 @@ const tabMoveInCheck = async () => {
for (const item of nodes) {
if (
item.className !== undefined &&
typeof item.className === 'string' &&
item.className.split(' ').includes('shape') &&
item.getAttribute('component-id') !== domId.value && //
item.getAttribute('tab-is-check') !== null &&

View File

@ -43,6 +43,8 @@ import waterfall from '@/assets/svg/waterfall.svg'
import wordCloud from '@/assets/svg/word-cloud.svg'
import tHeatmap from '@/assets/svg/t-heatmap.svg'
import pictureGroup from '@/assets/svg/picture-group.svg'
import filter from '@/assets/svg/filter.svg'
import outerParams from '@/assets/svg/icon_params_setting.svg'
const iconChartMap = {
'area-stack': areaStack,
@ -89,7 +91,9 @@ const iconChartMap = {
waterfall: waterfall,
'word-cloud': wordCloud,
't-heatmap': tHeatmap,
'picture-group': pictureGroup
'picture-group': pictureGroup,
filter: filter,
outerParams: outerParams
}
export { iconChartMap }

View File

@ -8,7 +8,7 @@ import { i18n } from '@/plugins/vue-i18n'
import * as Vue from 'vue'
import axios from 'axios'
import * as Pinia from 'pinia'
import * as vueRouter from 'vue-router'
import router from '@/router'
import { useEmitt } from '@/hooks/web/useEmitt'
import request from '@/config/axios'
const { wsCache } = useCache()
@ -122,12 +122,12 @@ onMounted(async () => {
const xpack = await window[moduleName].mapping[attrs.jsname]
plugin.value = xpack.default
} else {
window['Vue'] = Vue
window['Axios'] = axios
window['Pinia'] = Pinia
window['vueRouter'] = vueRouter
window['MittAll'] = useEmitt().emitter.all
window['I18n'] = i18n
window['VueDe'] = Vue
window['AxiosDe'] = axios
window['PiniaDe'] = Pinia
window['vueRouterDe'] = router
window['MittAllDe'] = useEmitt().emitter.all
window['I18nDe'] = i18n
const url = `/xpackComponent/pluginStaticInfo/${moduleName}`
request.get({ url }).then(async res => {
new Function(res.data || res)()

View File

@ -1,6 +1,25 @@
<template>
<div style="width: 100%" ref="bgForm">
<el-form label-position="top" style="width: 100%; margin-bottom: 16px">
<el-form-item
class="form-item no-margin-bottom"
:class="'form-item-' + themes"
label="数据大屏字体选择"
>
<el-select
:effect="themes"
v-model="canvasStyleData.fontFamily"
@change="onFontFamilyChange"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<el-form-item class="form-item no-margin-bottom" :class="'form-item-' + themes">
<el-checkbox
size="small"
@ -63,11 +82,29 @@ import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { ElFormItem, ElIcon } from 'element-plus-secondary'
import Icon from '../icon-custom/src/Icon.vue'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { CHART_FONT_FAMILY } from '@/views/chart/components/editor/util/chart'
import { adaptTitleFontFamilyAll } from '@/utils/canvasStyle'
const snapshotStore = snapshotStoreWithOut()
const dvMainStore = dvMainStoreWithOut()
const { canvasStyleData } = storeToRefs(dvMainStore)
const appearanceStore = useAppearanceStoreWithOut()
const fontFamily = CHART_FONT_FAMILY.concat(
appearanceStore.fontList.map(ele => ({
name: ele.name,
value: ele.name
}))
)
const onFontFamilyChange = () => {
appearanceStore.setCurrentFont(canvasStyleData.value.fontFamily)
document.documentElement.style.setProperty(
'--de-canvas_custom_font',
`${canvasStyleData.value.fontFamily}`
)
adaptTitleFontFamilyAll(canvasStyleData.value.fontFamily)
snapshotStore.recordSnapshotCache('renderChart')
}
const onThemeChange = () => {
snapshotStore.recordSnapshotCache()
}

View File

@ -196,141 +196,275 @@
</el-form-item>
</div>
</div>
<el-row style="margin-bottom: 8px" :gutter="8">
<el-col :span="7"> 源字段 </el-col>
<el-col :span="2"></el-col>
<el-col :span="7" style="margin-left: -2.9%">
{{ t('visualization.link_view_field') }}
</el-col>
<el-col :span="8"></el-col>
</el-row>
<div class="main-scrollbar-container">
<el-scrollbar height="fit-content" max-height="208px">
<div
style="display: flex; margin-bottom: 6px"
v-for="(targetViewInfo, index) in state.linkJumpInfo.targetViewInfoList"
:key="index"
>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.sourceFieldActiveId"
:placeholder="'请选择字段'"
style="width: 100%"
>
<el-option
v-for="curViewField in state.linkJumpCurViewFieldArray"
:key="curViewField.id"
:label="curViewField.name"
:value="curViewField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:class="`field-icon-${fieldType[curViewField.deType]}`"
:is="iconFieldMap[fieldType[curViewField.deType]]"
></component
></Icon>
<span style="float: left; margin-left: 4px; font-size: 14px">{{
curViewField.name
}}</span>
</span>
</el-option>
</el-select>
</div>
<div class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget style="width: 20px; height: 20px" class="svg-icon"
/></Icon>
</div>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.targetViewId"
:disabled="!targetViewInfo.sourceFieldActiveId"
:placeholder="'请选择图表'"
style="width: 100%"
@change="viewInfoOnChange(targetViewInfo)"
>
<el-option
v-for="item in state.currentLinkPanelViewArray"
:key="item.id"
:label="item.title"
:value="item.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon view-type-icon"
style="width: 14px; height: 14px"
:is="iconChartMap[item.type]"
></component
></Icon>
<span style="float: left; margin-left: 4px; font-size: 14px">{{
item.title
}}</span>
</span>
</el-option>
</el-select>
</div>
<div style="flex: 1; margin: 0 8px">
<el-select
v-model="targetViewInfo.targetFieldId"
:placeholder="'请选择字段'"
:disabled="fieldIdDisabledCheck(targetViewInfo)"
style="width: 100%"
>
<el-option
v-for="viewField in state.viewIdFieldArrayMap[
targetViewInfo.targetViewId
]"
:key="viewField.id"
:label="viewField.name"
:value="viewField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:class="`field-icon-${fieldType[viewField.deType]}`"
:is="iconFieldMap[fieldType[viewField.deType]]"
></component
></Icon>
<span style="float: left; margin-left: 4px; font-size: 14px">{{
viewField.name
}}</span>
</span>
</el-option>
</el-select>
</div>
<el-button
class="m-del-icon-btn"
text
@click="deleteLinkJumpField(index)"
<div class="jump-com-list">
<el-tabs size="small" v-model="state.activeCollapse">
<el-tab-pane label="联动图表" name="view"> </el-tab-pane>
<el-tab-pane label="携带查询条件" name="filter"> </el-tab-pane>
</el-tabs>
</div>
<template v-if="state.activeCollapse === 'view'">
<el-row style="margin-bottom: 8px" :gutter="8">
<el-col :span="7"> 源字段 </el-col>
<el-col :span="2"></el-col>
<el-col :span="7" style="margin-left: -2.9%">
{{ t('visualization.link_view_field') }}
</el-col>
<el-col :span="8"></el-col>
</el-row>
<div class="main-scrollbar-container">
<el-scrollbar height="fit-content" max-height="178px">
<div
style="display: flex; margin-bottom: 6px"
v-for="(
targetViewInfo, index
) in state.linkJumpInfo.targetViewInfoList.filter(
item => item.targetType === 'view'
)"
:key="index"
>
<el-icon size="20px">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
<div style="flex: 1">
<el-select
v-model="targetViewInfo.sourceFieldActiveId"
:placeholder="'请选择字段'"
style="width: 100%"
>
<el-option
v-for="curViewField in state.linkJumpCurViewFieldArray"
:key="curViewField.id"
:label="curViewField.name"
:value="curViewField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:class="`field-icon-${fieldType[curViewField.deType]}`"
:is="iconFieldMap[fieldType[curViewField.deType]]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ curViewField.name }}</span
>
</span>
</el-option>
</el-select>
</div>
<div class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget style="width: 20px; height: 20px" class="svg-icon"
/></Icon>
</el-icon>
</div>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.targetViewId"
:disabled="!targetViewInfo.sourceFieldActiveId"
:placeholder="'请选择图表'"
style="width: 100%"
@change="viewInfoOnChange(targetViewInfo)"
>
<el-option
v-for="item in state.currentLinkPanelViewArray.filter(
item => item.type !== 'outerParams'
)"
:key="item.id"
:label="item.title"
:value="item.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon view-type-icon"
style="width: 14px; height: 14px"
:is="iconChartMap[item.type]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ item.title }}</span
>
</span>
</el-option>
</el-select>
</div>
<div style="flex: 1; margin: 0 8px">
<el-select
v-model="targetViewInfo.targetFieldId"
:placeholder="'请选择字段'"
:disabled="fieldIdDisabledCheck(targetViewInfo)"
style="width: 100%"
>
<el-option
v-for="viewField in state.viewIdFieldArrayMap[
targetViewInfo.targetViewId
]"
:key="viewField.id"
:label="viewField.name"
:value="viewField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:class="`field-icon-${fieldType[viewField.deType]}`"
:is="iconFieldMap[fieldType[viewField.deType]]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ viewField.name }}</span
>
</span>
</el-option>
</el-select>
</div>
<el-button
class="m-del-icon-btn"
text
@click="deleteLinkJumpFieldById(targetViewInfo.targetId)"
>
<el-icon size="20px">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-scrollbar>
<el-button
style="margin-top: 8px"
:disabled="!state.linkJumpInfo.targetDvId"
type="primary"
icon="Plus"
text
@click="addLinkJumpField('view')"
>
{{ t('visualization.add_jump_field') }}
</el-button>
</div>
</template>
<template v-if="state.activeCollapse === 'filter'">
<template v-if="state.currentOutParams.length === 0">
<span
>目标仪表板无外部参数因此无法携带条件查询如有需要<a
class="target_jump"
@click="resourceEdit(state.linkJumpInfo.targetDvId)"
>请前往设置外部参数</a
></span
>
</template>
<template v-if="state.currentOutParams.length > 0">
<el-row style="margin-bottom: 8px" :gutter="8">
<el-col :span="12"> 源条件 </el-col>
<el-col :span="1"></el-col>
<el-col :span="10" style="margin-left: -2.9%"> 联动外部参数 </el-col>
</el-row>
<div class="main-scrollbar-container">
<el-scrollbar height="fit-content" max-height="178px">
<div
style="display: flex; margin-bottom: 6px"
v-for="(
targetViewInfo, index
) in state.linkJumpInfo.targetViewInfoList.filter(
item => item.targetType === 'outerParams'
)"
:key="index"
>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.sourceFieldActiveId"
:placeholder="'请选择字段'"
style="width: 100%"
>
<el-option
v-for="curFilterField in state.linkJumpCurFilterFieldArray"
:key="curFilterField.id"
:label="curFilterField.name"
:value="curFilterField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:is="iconChartMap['filter']"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ curFilterField.name }}</span
>
</span>
</el-option>
</el-select>
</div>
<div class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget
style="width: 20px; height: 20px"
class="svg-icon"
/></Icon>
</div>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.targetViewId"
:disabled="!targetViewInfo.sourceFieldActiveId"
:placeholder="'请选择参数'"
style="width: 100%"
@change="viewInfoOnChange(targetViewInfo)"
>
<el-option
v-for="item in state.currentOutParams"
:key="item.id"
:label="item.title"
:value="item.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon view-type-icon"
style="width: 14px; height: 14px"
:is="iconChartMap[item.type]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ item.title }}</span
>
</span>
</el-option>
</el-select>
</div>
<el-button
class="m-del-icon-btn"
text
@click="deleteLinkJumpFieldById(targetViewInfo.targetId)"
>
<el-icon size="20px">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-scrollbar>
<el-button
style="margin-top: 8px"
:disabled="!state.linkJumpInfo.targetDvId"
type="primary"
icon="Plus"
text
@click="addLinkJumpField('outerParams')"
>
{{ t('visualization.add_jump_field') }}
</el-button>
</div>
</el-scrollbar>
<el-button
style="margin-top: 8px"
:disabled="!state.linkJumpInfo.targetDvId"
type="primary"
icon="Plus"
text
@click="addLinkJumpField"
>
{{ t('visualization.add_jump_field') }}
</el-button>
</div>
</template>
</template>
</el-form>
</template>
@ -426,6 +560,10 @@
</el-button>
</el-row>
</div>
<XpackComponent
ref="openHandler"
jsname="L2NvbXBvbmVudC9lbWJlZGRlZC1pZnJhbWUvT3BlbkhhbmRsZXI="
/>
</el-dialog>
</template>
@ -458,18 +596,28 @@ import { Search } from '@element-plus/icons-vue'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import { filterEmptyFolderTree } from '@/utils/canvasUtils'
import { useEmitt } from '@/hooks/web/useEmitt'
import { useAppStoreWithOut } from '@/store/modules/app'
import { XpackComponent } from '@/components/plugin'
import { useCache } from '@/hooks/web/useCache'
import { useEmbedded } from '@/store/modules/embedded'
import { guid } from '@/views/visualized/data/dataset/form/util'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, canvasViewInfo, componentData } = storeToRefs(dvMainStore)
const linkJumpInfoTree = ref(null)
const { t } = useI18n()
const dialogShow = ref(false)
const snapshotStore = snapshotStoreWithOut()
const appStore = useAppStoreWithOut()
const { wsCache } = useCache()
const embeddedStore = useEmbedded()
const resourceType = computed(() =>
dvInfo.value.type === 'dashboard' ? t('work_branch.dashboard') : t('work_branch.big_data_screen')
)
const state = reactive({
activeCollapse: 'view',
loading: false,
showSelected: false,
curJumpViewInfo: {},
@ -506,6 +654,7 @@ const state = reactive({
linkJumpInfoArray: [],
linkJumpInfoXArray: [],
linkJumpCurViewFieldArray: [],
linkJumpCurFilterFieldArray: [], //
mapJumpInfoArray: {},
panelList: [],
linkJumpInfo: null,
@ -526,7 +675,8 @@ const state = reactive({
quotaList: [],
quotaData: [],
dimension: [],
quota: []
quota: [],
currentOutParams: []
})
const outerContentEditor = ref(null)
@ -541,6 +691,7 @@ const dialogInit = viewItem => {
const init = viewItem => {
state.initState = false
state.viewId = viewItem.id
state.activeCollapse = 'view'
const chartDetails = canvasViewInfo.value[state.viewId] as ChartObj
state.curJumpViewInfo = chartDetails
let checkAllAxisStr =
@ -579,12 +730,27 @@ const init = viewItem => {
state.panelList = filterEmptyFolderTree(state.panelList)
})
// 1. 2.
state.linkJumpCurFilterFieldArray = []
componentData.value.forEach(componentItem => {
if (componentItem.component === 'VQuery') {
componentItem.propValue.forEach(filterItem => {
if (filterItem.checkedFields.includes(state.viewId)) {
state.linkJumpCurFilterFieldArray.push({
id: filterItem.id,
name: filterItem.name,
deType: 'filter'
})
}
})
}
})
if (chartDetails.tableId) {
//
getDatasetDetails(chartDetails.tableId).then(res => {
state.curDatasetInfo = res || {}
})
//
listFieldByDatasetGroup(chartDetails.tableId).then(rsp => {
state.linkJumpCurViewFieldArray = []
@ -705,6 +871,17 @@ const getPanelViewList = dvId => {
state.viewIdFieldArrayMap[view.id] = view.tableFields
})
}
// currentLinkPanelViewArray
// currentLinkPanelViewArray 便
state.currentOutParams = rsp.data.outParamsJumpInfo || []
if (state.currentOutParams && state.currentOutParams.length > 0) {
state.currentOutParams.forEach(outerParamsItem => {
state.currentLinkPanelViewArray.push(outerParamsItem)
state.viewIdFieldArrayMap[outerParamsItem.id] = [
{ id: '1000001', name: t('visualization.out_params_no_select') }
]
})
}
//
JSON.parse(rsp.data.bashComponentData).forEach(componentItem => {
if (componentItem.component === 'VQuery') {
@ -723,6 +900,7 @@ const getPanelViewList = dvId => {
})
})
}
const dvNodeClick = data => {
if (data.leaf) {
state.linkJumpInfo.targetViewInfoList = []
@ -730,12 +908,29 @@ const dvNodeClick = data => {
getPanelViewList(data.id)
}
}
const addLinkJumpField = () => {
const addLinkJumpField = (type = 'view') => {
state.linkJumpInfo.targetViewInfoList.push({
targetId: guid(),
targetViewId: '',
targetType: type,
targetFieldId: ''
})
}
const deleteLinkJumpFieldById = targetId => {
if (targetId) {
let indexResult
state.linkJumpInfo.targetViewInfoList.forEach((item, index) => {
if (targetId === item.targetId) {
indexResult = index
}
})
if (indexResult !== undefined) {
state.linkJumpInfo.targetViewInfoList.splice(indexResult, 1)
}
}
}
const deleteLinkJumpField = index => {
state.linkJumpInfo.targetViewInfoList.splice(index, 1)
}
@ -783,6 +978,39 @@ const filterNodeMethod = (value, data) => {
return !value || data.checked
}
const isEmbedded = computed(() => appStore.getIsDataEaseBi || appStore.getIsIframe)
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
const resourceEdit = resourceId => {
const baseUrl = dvInfo.value.type === 'dataV' ? '#/dvCanvas?dvId=' : '#/dashboard?resourceId='
if (isEmbedded.value) {
embeddedStore.clearState()
if (dvInfo.value.type === 'dataV') {
embeddedStore.setDvId(resourceId)
} else {
embeddedStore.setResourceId(resourceId)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
dvInfo.value.type === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
)
return
}
const newWindow = window.open(baseUrl + resourceId, openType)
initOpenHandler(newWindow)
}
const openHandler = ref(null)
const initOpenHandler = newWindow => {
if (openHandler?.value) {
const pm = {
methodName: 'initOpenHandler',
args: newWindow
}
openHandler.value.invokeMethod(pm)
}
}
watch(
() => state.showSelected,
newValue => {
@ -1244,4 +1472,47 @@ span {
display: flex;
align-items: center;
}
.jump-com-list {
width: 100%;
margin-top: -18px;
:deep(.ed-collapse) {
--ed-collapse-header-font-size: 14px;
--ed-collapse-content-font-size: 14px;
}
:deep(.ed-tabs__active-bar) {
height: 2px;
}
& > :deep(.ed-tabs) {
--ed-tabs-header-height: 36px;
margin-bottom: 12px;
position: sticky;
background: #fff;
.ed-tabs__header {
&::before {
content: '';
width: 8px;
height: 1px;
position: absolute;
bottom: 0;
left: 0;
background: #1f232926;
}
}
}
:deep(.ed-tabs__item) {
font-size: 14px;
}
:deep(.ed-tabs__item):not(.is-active) {
color: #646a73;
}
}
.target_jump {
color: var(--ed-color-primary);
cursor: pointer;
}
</style>

View File

@ -596,8 +596,14 @@ const jsonArrayCheck = params => {
const save = () => {
const outerParamsCopy = deepCopy(state.outerParams)
let checkErrorNum = 0
let checkNullErrorNum = 0
let checkMessage = ''
const paramNameArray = []
outerParamsCopy.outerParamsInfoArray?.forEach(outerParamsInfo => {
if (!outerParamsInfo.paramName || paramNameArray.includes(outerParamsInfo.paramName)) {
checkNullErrorNum++
}
paramNameArray.push(outerParamsInfo.paramName)
if (outerParamsInfo.defaultValue && !jsonArrayCheck(outerParamsInfo.defaultValue)) {
checkErrorNum++
checkMessage = checkMessage + `${outerParamsInfo.paramName}`
@ -636,6 +642,14 @@ const save = () => {
})
return
}
if (checkNullErrorNum > 0) {
ElMessage({
message: `存在未配置的参数名或者参数名称重复!`,
type: 'warning',
showClose: true
})
return
}
updateOuterParamsSet(outerParamsCopy).then(() => {
ElMessage({
message: t('commons.save_success'),

View File

@ -3,7 +3,11 @@
<el-dropdown :teleported="false" trigger="click">
<input id="input" ref="trackButton" type="button" hidden />
<template #dropdown>
<el-dropdown-menu class="track-menu" :append-to-body="false">
<el-dropdown-menu
class="track-menu"
:style="{ 'font-family': fontFamily }"
:append-to-body="false"
>
<el-dropdown-item
v-for="(item, key) in trackMenu"
:key="key"
@ -27,6 +31,11 @@ const props = defineProps({
trackMenu: {
type: Array,
required: true
},
fontFamily: {
type: String,
required: false,
default: 'inherit'
}
})
const { trackMenu } = toRefs(props)

View File

@ -53,6 +53,34 @@
</el-col>
</el-row>
<el-form-item class="form-item no-margin-bottom" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.commonBackground.backdropFilterEnable"
@change="onBackgroundChange"
>
{{ $t('chart.backdrop_blur') }}
</el-checkbox>
</el-form-item>
<div class="indented-container">
<div class="indented-item">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input-number
style="width: 100%"
:effect="themes"
controls-position="right"
size="middle"
:min="0"
:max="30"
:disabled="!state.commonBackground.backdropFilterEnable"
v-model="state.commonBackground.backdropFilter"
@change="onBackgroundChange"
/>
</el-form-item>
</div>
</div>
<el-form-item class="form-item no-margin-bottom" :class="'form-item-' + themes">
<el-checkbox
size="small"

View File

@ -11,12 +11,18 @@ const refreshUrl = '/login/refresh'
const expConstants = 10000
const expTimeConstants = 90000
const isExpired = () => {
const exp = wsCache.get('user.exp')
if (!exp) {
return false
}
return exp - new Date().getTime() < expConstants
const time = wsCache.get('user.time')
if (!time) {
return exp - Date.now() < expConstants
}
return Date.now() - time > expTimeConstants
}
const delayExecute = (token: string) => {
@ -25,10 +31,6 @@ const delayExecute = (token: string) => {
cb(token)
})
requestStore.cleanCacheRequest()
/* cachedRequestList.forEach(cb => {
cb(token)
})
cachedRequestList = [] */
}
const getRefreshStatus = () => {
@ -40,7 +42,6 @@ const setRefreshStatus = (status: boolean) => {
const cacheRequest = cb => {
requestStore.addCacheRequest(cb)
// cachedRequestList.push(cb)
}
export const configHandler = config => {
@ -61,6 +62,7 @@ export const configHandler = config => {
.then(res => {
userStore.setToken(res.data.token)
userStore.setExp(res.data.exp)
userStore.setTime(Date.now())
config.headers['X-DE-TOKEN'] = res.data.token
delayExecute(res.data.token)
})

View File

@ -89,6 +89,14 @@ const backgroundCustomShow = computed(() => {
!['CanvasBoard', 'CanvasIcon', 'CircleShape', 'RectShape'].includes(element.value.component))
)
})
const tabTitleShow = computed(() => {
return element.value && element.value.style && element.value.component === 'DeTabs'
})
const styleShow = computed(() => {
return element.value && element.value.style && element.value.component !== 'DeTabs'
})
onMounted(() => {
const erd = elementResizeDetectorMaker()
containerWidth.value = containerRef.value?.offsetWidth
@ -122,8 +130,23 @@ onMounted(() => {
/>
</el-collapse-item>
<slot></slot>
<collapse-switch-item
v-if="tabTitleShow"
v-model="element.style.showTabTitle"
@modelChange="val => onStyleAttrChange({ key: 'showTabTitle', value: val })"
:themes="themes"
title="Tab标签"
name="tabTitle"
class="common-style-area"
>
<common-style-set
@onStyleAttrChange="onStyleAttrChange"
:themes="themes"
:element="element"
></common-style-set>
</collapse-switch-item>
<el-collapse-item
v-if="element && element.style"
v-if="styleShow"
:effect="themes"
title="样式"
name="style"

View File

@ -96,6 +96,7 @@ watch(
class="color-picker-style"
:triggerWidth="65"
is-custom
show-alpha
:predefine="state.predefineColors"
@change="changeStylePre('borderColor')"
>
@ -135,6 +136,7 @@ watch(
class="color-picker-style"
:triggerWidth="65"
is-custom
show-alpha
:effect="themes"
:predefine="state.predefineColors"
@change="changeStylePre('borderColor')"

View File

@ -57,6 +57,7 @@
:prefix-icon="styleColorKey.icon"
:triggerWidth="styleColorKey.width"
is-custom
show-alpha
:predefine="state.predefineColors"
@change="
changeStyle({ key: styleColorKey.value, value: styleForm[styleColorKey.value] })
@ -291,6 +292,7 @@ import dvStyleHeadFontColor from '@/assets/svg/dv-style-headFontColor.svg'
import dvStyleScrollSpeed from '@/assets/svg/dv-style-scroll-speed.svg'
import dvStyleOpacity from '@/assets/svg/dv-style-opacity.svg'
import dvStyleTabHead from '@/assets/svg/dv-style-tab-head.svg'
import dvStyleBlur from '@/assets/svg/dv-style-blur.svg'
import dvStyleFontSize from '@/assets/svg/dv-style-fontSize.svg'
import dvStyleLetterSpacing from '@/assets/svg/dv-style-letterSpacing.svg'
import dvStyleActiveFont from '@/assets/svg/dv-style-activeFont.svg'
@ -371,10 +373,38 @@ const opacitySizeList = [
{ name: '0.9', value: 0.9 },
{ name: '1', value: 1 }
]
const titleHideList = [
{ name: '隐藏', value: true },
{ name: '显示', value: false }
const backdropBlurList = [
{ name: '0', value: 'blur(0px)' },
{ name: '1', value: 'blur(1px)' },
{ name: '2', value: 'blur(2px)' },
{ name: '3', value: 'blur(3px)' },
{ name: '4', value: 'blur(4px)' },
{ name: '5', value: 'blur(5px)' },
{ name: '6', value: 'blur(6px)' },
{ name: '7', value: 'blur(7px)' },
{ name: '8', value: 'blur(8px)' },
{ name: '9', value: 'blur(9px)' },
{ name: '10', value: 'blur(10px)' },
{ name: '11', value: 'blur(11px)' },
{ name: '12', value: 'blur(12px)' },
{ name: '13', value: 'blur(13px)' },
{ name: '14', value: 'blur(14px)' },
{ name: '15', value: 'blur(15px)' },
{ name: '16', value: 'blur(16px)' },
{ name: '17', value: 'blur(17px)' },
{ name: '18', value: 'blur(18px)' },
{ name: '19', value: 'blur(19px)' },
{ name: '20', value: 'blur(20px)' },
{ name: '21', value: 'blur(21px)' },
{ name: '22', value: 'blur(22px)' },
{ name: '23', value: 'blur(23px)' },
{ name: '24', value: 'blur(24px)' },
{ name: '25', value: 'blur(25px)' },
{ name: '26', value: 'blur(26px)' },
{ name: '27', value: 'blur(27px)' },
{ name: '28', value: 'blur(28px)' },
{ name: '29', value: 'blur(29px)' },
{ name: '30', value: 'blur(30px)' }
]
const styleForm = computed<any>(() => element.value.style)
@ -474,11 +504,11 @@ const styleOptionKeyArray = [
icon: dvStyleOpacity
},
{
value: 'titleHide',
label: '标题样式',
customOption: titleHideList,
value: 'backdropFilter',
label: '背景模糊',
customOption: backdropBlurList,
width: '90px',
icon: dvStyleTabHead
icon: dvStyleBlur
}
]

View File

@ -177,12 +177,14 @@ export const MULTI_DIMENSIONAL = {
export const COMMON_COMPONENT_BACKGROUND_BASE = {
backgroundColorSelect: true,
backdropFilterEnable: false,
backgroundImageEnable: false,
backgroundType: 'innerImage',
innerImage: 'board/board_1.svg',
outerImage: null,
innerPadding: 12,
borderRadius: 0
borderRadius: 0,
backdropFilter: 4
}
export const COMMON_COMPONENT_BACKGROUND_LIGHT = {
@ -455,7 +457,8 @@ const list = [
style: {
width: 40,
height: 40,
color: ''
color: '',
backdropFilter: 'blur(0px)'
}
},
{
@ -474,7 +477,8 @@ const list = [
style: {
width: 600,
height: 300,
color: 'rgb(255, 255, 255,1)'
color: 'rgb(255, 255, 255,1)',
backdropFilter: 'blur(0px)'
}
},
{
@ -487,7 +491,8 @@ const list = [
width: 200,
height: 200,
backgroundColor: 'rgba(236,231,231,0.1)',
borderActive: true
borderActive: true,
backdropFilter: 'blur(0px)'
}
},
{
@ -502,7 +507,8 @@ const list = [
borderWidth: 1,
borderStyle: 'solid',
borderColor: '#cccccc',
backgroundColor: 'rgba(236,231,231,0.1)'
backgroundColor: 'rgba(236,231,231,0.1)',
backdropFilter: 'blur(0px)'
}
},
{
@ -516,7 +522,8 @@ const list = [
height: 200,
borderWidth: 1,
borderColor: '#cccccc',
backgroundColor: 'rgba(236,231,231,0.1)'
backgroundColor: 'rgba(236,231,231,0.1)',
backdropFilter: 'blur(0px)'
}
},
{
@ -547,7 +554,8 @@ const list = [
headHorizontalPosition: 'left',
headFontColor: '#000000',
headFontActiveColor: '#000000',
titleHide: false
titleHide: false,
showTabTitle: true
}
},
{

View File

@ -5,8 +5,8 @@
:class="[
headClass,
`ed-tabs-${curThemes}`,
{ 'title-hidde-tab': hideTitle },
{ 'title-show-tab': !hideTitle }
{ 'title-hidde-tab': !showTabTitleFlag },
{ 'title-show-tab': showTabTitleFlag }
]"
class="custom-tabs-head"
ref="tabComponentRef"
@ -19,7 +19,7 @@
:active-color="activeColor"
:border-color="noBorderColor"
:border-active-color="borderActiveColor"
:hide-title="hideTitle"
:hide-title="!showTabTitleFlag"
>
<template :key="tabItem.name" v-for="tabItem in element.propValue">
<el-tab-pane
@ -42,7 +42,7 @@
<el-icon v-if="isEdit"><ArrowDown /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-menu :style="{ 'font-family': fontFamily }">
<el-dropdown-item :command="beforeHandleCommand('editTitle', tabItem)">
编辑标题
</el-dropdown-item>
@ -77,6 +77,7 @@
:canvas-id="element.id + '--' + tabItem.name"
:class="moveActive ? 'canvas-move-in' : ''"
:canvas-active="editableTabsValue === tabItem.name"
:font-family="fontFamily"
></de-canvas>
<de-preview
v-else
@ -90,6 +91,7 @@
:preview-active="editableTabsValue === tabItem.name"
:show-position="showPosition"
:outer-scale="scale"
:font-family="fontFamily"
:outer-search-count="searchCount"
></de-preview>
</div>
@ -190,6 +192,12 @@ const props = defineProps({
type: Number,
required: false,
default: 0
},
//
fontFamily: {
type: String,
required: false,
default: 'inherit'
}
})
const {
@ -217,16 +225,11 @@ const editableTabsValue = ref(null)
const noBorderColor = ref('none')
let currentInstance
const hideTitle = computed(() => {
if (
element.value &&
element.value.style &&
element.value.style.titleHide &&
typeof element.value.style.titleHide === 'boolean'
) {
return element.value.style.titleHide
} else {
const showTabTitleFlag = computed(() => {
if (element.value && element.value.style && element.value.style?.showTabTitle === false) {
return false
} else {
return element.value.style?.showTabTitle
}
})
@ -566,6 +569,9 @@ onBeforeMount(() => {
:deep(.ed-tabs__content) {
height: calc(100% - 46px) !important;
}
:deep(.ed-tabs__item) {
font-family: inherit;
}
}
.ed-tabs-dark {

View File

@ -1,5 +1,5 @@
<template>
<div class="pic-main" @click="onPictureClick">
<div class="pic-main">
<img
draggable="false"
v-if="propValue['url']"
@ -8,7 +8,7 @@
/>
<div v-else class="pic-upload">
<span
><el-button @click="uploadImg" text style="color: #646a73" icon="Plus"
><el-button @click="uploadImg" text style="font-family: inherit; color: #646a73" icon="Plus"
>请上传图片...</el-button
></span
>
@ -55,16 +55,6 @@ const imageAdapter = computed(() => {
}
return style as CSSProperties
})
const onPictureClick = e => {
if (element.value.events && element.value.events.checked) {
if (element.value.events.type === 'displayChange') {
//
nextTick(() => {
dvMainStore.popAreaActiveSwitch()
})
}
}
}
const uploadImg = () => {
nextTick(() => {
eventBus.emit('uploadImg')
@ -77,7 +67,6 @@ const uploadImg = () => {
overflow: hidden;
width: 100%;
height: 100%;
cursor: pointer;
}
.pic-upload {
display: flex;

View File

@ -143,7 +143,7 @@ const curFontFamily = () => {
value: ele.name
}))
).forEach(font => {
result = result + font.name + '=' + font.name + ';'
result = result + font.name + '=' + font.value + ';'
})
return result
}

View File

@ -58,6 +58,11 @@ const props = defineProps({
type: String,
required: false,
default: 'common'
},
fontFamily: {
type: String,
required: false,
default: 'inherit'
}
})
@ -87,6 +92,7 @@ const onPointClick = param => {
:search-count="searchCount"
:disabled="disabled"
:suffixId="suffixId"
:font-family="fontFamily"
@onPointClick="onPointClick"
></chart>
</div>

View File

@ -698,6 +698,7 @@ const autoStyle = computed(() => {
<el-button
:disabled="showPosition === 'preview' || mobileInPc"
@click="addCriteriaConfigOut"
style="font-family: inherit"
text
>
{{ t('v_query.add_query_condition') }}
@ -825,7 +826,6 @@ const autoStyle = computed(() => {
justify-content: center;
color: #646a73;
text-align: center;
font-family: var(--de-custom_font, 'PingFang');
font-size: 16px;
font-style: normal;
font-weight: 400;
@ -841,7 +841,6 @@ const autoStyle = computed(() => {
.title {
color: #1f2329;
font-feature-settings: 'clig' off, 'liga' off;
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-style: normal;
font-weight: 500;
@ -900,7 +899,6 @@ const autoStyle = computed(() => {
text-overflow: ellipsis;
white-space: nowrap;
color: #1f2329;
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-style: normal;
font-weight: 400;

View File

@ -197,7 +197,7 @@ const relativeToCurrentListRange = computed(() => {
value: 'thisMonth'
},
{
label: t('dynamic_month.dynamic_month'),
label: t('dynamic_month.last'),
value: 'lastMonth'
},
{
@ -354,9 +354,9 @@ defineExpose({
<div class="bottom-line"></div>
</div>
<div class="condition-type" v-if="[1, 2].includes(curComponent.conditionType)">
<sapn class="condition-type-tip">{{
<span class="condition-type-tip">{{
curComponent.conditionType === 1 ? t('chart.and') : t('chart.or')
}}</sapn>
}}</span>
<el-select
v-if="!curComponent.hideConditionSwitching"
class="condition-value-select"

View File

@ -7,7 +7,7 @@
class="custom-sort_filter"
width="300px"
>
<div>
<div style="max-height: 400px; overflow-y: auto">
<draggable :list="sortList" animation="300" class="drag-list">
<template #item="{ element }">
<span :key="element.name" class="item-dimension" :title="element">

View File

@ -167,6 +167,13 @@ onBeforeMount(() => {
v-model="selectValue"
:type="config.timeGranularity"
:prefix-icon="Calendar"
:popper-class="'custom-dynamic-time-popper_class'"
:placeholder="$t('commons.date.select_date_time')"
/>
</template>
<style lang="less">
.custom-dynamic-time-popper_class {
font-family: var(--de-canvas_custom_font);
}
</style>

View File

@ -170,8 +170,14 @@ const formatDate = computed(() => {
:type="config.timeGranularityMultiple"
:prefix-icon="Calendar"
:format="formatDate"
:popper-class="'custom-dynamic-time-range-popper_class'"
:range-separator="$t('cron.to')"
:start-placeholder="$t('datasource.start_time')"
:end-placeholder="$t('datasource.end_time')"
/>
</template>
<style lang="less">
.custom-dynamic-time-range-popper_class {
font-family: var(--de-canvas_custom_font);
}
</style>

View File

@ -147,8 +147,15 @@ const formatDate = computed(() => {
:type="timeInterval"
:prefix-icon="Calendar"
:format="formatDate"
:popper-class="'custom-dynamic-time-range-filter-popper_class'"
:range-separator="$t('cron.to')"
:start-placeholder="$t('datasource.start_time')"
:end-placeholder="$t('datasource.end_time')"
/>
</template>
<style lang="less">
.custom-dynamic-time-range-filter-popper_class {
font-family: var(--de-canvas_custom_font);
}
</style>

View File

@ -198,7 +198,7 @@ const relativeToCurrentListRange = computed(() => {
value: 'thisMonth'
},
{
label: t('dynamic_month.dynamic_month'),
label: t('dynamic_month.last'),
value: 'lastMonth'
},
{
@ -283,7 +283,7 @@ const relativeToCurrentListRange = computed(() => {
<div class="setting" v-if="timeRange.intervalType !== 'timeInterval'">
<div class="setting-label">{{ t('dynamic_time.relative') }}</div>
<div class="setting-value select">
<el-select v-model="timeRange.relativeToCurrent">
<el-select :teleported="false" v-model="timeRange.relativeToCurrent">
<el-option
v-for="item in relativeToCurrentList"
:key="item.value"
@ -296,7 +296,7 @@ const relativeToCurrentListRange = computed(() => {
<div class="setting" v-if="timeRange.relativeToCurrent === 'custom'">
<div class="setting-input">
<el-input-number v-model="timeRange.timeNum" :min="0" controls-position="right" />
<el-select v-model="timeRange.relativeToCurrentType">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentType">
<el-option
v-for="item in relativeToCurrentTypeList"
:key="item.value"
@ -304,7 +304,7 @@ const relativeToCurrentListRange = computed(() => {
:value="item.value"
/>
</el-select>
<el-select v-model="timeRange.around">
<el-select :teleported="false" v-model="timeRange.around">
<el-option
v-for="item in aroundList"
:key="item.value"
@ -319,7 +319,7 @@ const relativeToCurrentListRange = computed(() => {
<div class="setting">
<div class="setting-label">{{ t('dynamic_time.relative') }}</div>
<div class="setting-value select">
<el-select v-model="timeRange.relativeToCurrentRange">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentRange">
<el-option
v-for="item in relativeToCurrentListRange"
:key="item.value"
@ -342,7 +342,7 @@ const relativeToCurrentListRange = computed(() => {
:min="0"
controls-position="right"
/>
<el-select v-model="timeRange.relativeToCurrentType">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentType">
<el-option
v-for="item in relativeToCurrentTypeList"
:key="item.value"
@ -350,7 +350,7 @@ const relativeToCurrentListRange = computed(() => {
:value="item.value"
/>
</el-select>
<el-select v-model="timeRange.around">
<el-select :teleported="false" v-model="timeRange.around">
<el-option
v-for="item in aroundList"
:key="item.value"
@ -372,7 +372,7 @@ const relativeToCurrentListRange = computed(() => {
step-strictly
controls-position="right"
/>
<el-select v-model="timeRange.relativeToCurrentTypeRange">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentTypeRange">
<el-option
v-for="item in relativeToCurrentTypeList"
:key="item.value"
@ -380,7 +380,7 @@ const relativeToCurrentListRange = computed(() => {
:value="item.value"
/>
</el-select>
<el-select v-model="timeRange.aroundRange">
<el-select :teleported="false" v-model="timeRange.aroundRange">
<el-option
v-for="item in aroundList"
:key="item.value"

View File

@ -1948,7 +1948,7 @@ const relativeToCurrentListRange = computed(() => {
value: 'thisMonth'
},
{
label: t('dynamic_month.dynamic_month'),
label: t('dynamic_month.last'),
value: 'lastMonth'
},
{

View File

@ -209,7 +209,7 @@ const relativeToCurrentListRange = computed(() => {
value: 'thisMonth'
},
{
label: t('dynamic_month.dynamic_month'),
label: t('dynamic_month.last'),
value: 'lastMonth'
},
{
@ -294,7 +294,7 @@ const relativeToCurrentListRange = computed(() => {
<div class="setting" v-if="timeRange.intervalType !== 'timeInterval'">
<div class="setting-label">{{ t('dynamic_time.relative') }}</div>
<div class="setting-value select">
<el-select v-model="timeRange.relativeToCurrent">
<el-select :teleported="false" v-model="timeRange.relativeToCurrent">
<el-option
v-for="item in relativeToCurrentList"
:key="item.value"
@ -307,7 +307,7 @@ const relativeToCurrentListRange = computed(() => {
<div class="setting" v-if="timeRange.relativeToCurrent === 'custom'">
<div class="setting-input">
<el-input-number v-model="timeRange.timeNum" :min="0" controls-position="right" />
<el-select v-model="timeRange.relativeToCurrentType">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentType">
<el-option
v-for="item in relativeToCurrentTypeList"
:key="item.value"
@ -315,7 +315,7 @@ const relativeToCurrentListRange = computed(() => {
:value="item.value"
/>
</el-select>
<el-select v-model="timeRange.around">
<el-select :teleported="false" v-model="timeRange.around">
<el-option
v-for="item in aroundList"
:key="item.value"
@ -330,7 +330,7 @@ const relativeToCurrentListRange = computed(() => {
<div class="setting">
<div class="setting-label">{{ t('dynamic_time.relative') }}</div>
<div class="setting-value select">
<el-select v-model="timeRange.relativeToCurrentRange">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentRange">
<el-option
v-for="item in relativeToCurrentListRange"
:key="item.value"
@ -356,7 +356,7 @@ const relativeToCurrentListRange = computed(() => {
:min="0"
controls-position="right"
/>
<el-select v-model="timeRange.relativeToCurrentType">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentType">
<el-option
v-for="item in relativeToCurrentTypeList"
:key="item.value"
@ -364,7 +364,7 @@ const relativeToCurrentListRange = computed(() => {
:value="item.value"
/>
</el-select>
<el-select v-model="timeRange.around">
<el-select :teleported="false" v-model="timeRange.around">
<el-option
v-for="item in aroundList"
:key="item.value"
@ -389,7 +389,7 @@ const relativeToCurrentListRange = computed(() => {
step-strictly
controls-position="right"
/>
<el-select v-model="timeRange.relativeToCurrentTypeRange">
<el-select :teleported="false" v-model="timeRange.relativeToCurrentTypeRange">
<el-option
v-for="item in relativeToCurrentTypeList"
:key="item.value"
@ -397,7 +397,7 @@ const relativeToCurrentListRange = computed(() => {
:value="item.value"
/>
</el-select>
<el-select v-model="timeRange.aroundRange">
<el-select :teleported="false" v-model="timeRange.aroundRange">
<el-option
v-for="item in aroundList"
:key="item.value"

View File

@ -290,9 +290,20 @@ const setOldMapValue = arr => {
const customSort = () => {
if (config.value.sortList?.length && config.value.sort === 'customSort') {
options.value.sort(
(a, b) => config.value.sortList.indexOf(a.value) - config.value.sortList.indexOf(b.value)
)
options.value = [
...options.value
.sort(a => {
if (config.value.sortList.indexOf(a.value) !== -1) {
return -1
}
})
.sort((a, b) => {
if (config.value.sortList.indexOf(a.value) === -1) {
return 0
}
return config.value.sortList.indexOf(a.value) - config.value.sortList.indexOf(b.value)
})
]
}
}
@ -515,7 +526,7 @@ const setOptions = (num: number) => {
handleFieldIdChange({
queryId: field.id,
displayId: displayId || field.id,
sort: sort === 'customSort' ? '' : sort,
sort: sort === 'customSort' ? 'asc' : sort,
sortId,
resultMode: config.value.resultMode || 0,
searchText: searchText.value,
@ -646,6 +657,7 @@ defineExpose({
<style lang="less">
.filter-select-popper_class {
--ed-fill-color-light: #f5f7fa47;
font-family: var(--de-canvas_custom_font);
.ed-vl__window.ed-select-dropdown__list {
min-width: 200px;
}

View File

@ -142,9 +142,9 @@ const handleInnerMouseDown = e => {
<div :style="lineWidth" class="bottom-line"></div>
</div>
<div class="condition-type" v-if="[1, 2].includes(config.conditionType)">
<sapn class="condition-type-tip">{{
<span class="condition-type-tip">{{
config.conditionType === 1 ? t('chart.and') : t('chart.or')
}}</sapn>
}}</span>
<el-select
v-if="!config.hideConditionSwitching"
class="condition-value-select"

View File

@ -254,6 +254,7 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
if (item.checkedFields.includes(curComponentId) && item.checkedFieldsMap[curComponentId]) {
let selectValue
const {
id,
selectValue: value,
timeGranularityMultiple,
defaultNumValueEnd,
@ -471,6 +472,7 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
}
filter.push({
filterId: id,
componentId: ele.id,
fieldId,
operator,

View File

@ -8,7 +8,9 @@ export const useMoveLine = (type: Sidebar) => {
const width = ref(wsCache.get(type) || 280)
const getCoordinates = () => {
document.querySelector('.sidebar-move-line').className = 'sidebar-move-line dragging'
if (document.querySelector('.sidebar-move-line')) {
document.querySelector('.sidebar-move-line').className = 'sidebar-move-line dragging'
}
document.addEventListener('mousemove', setCoordinates)
document.addEventListener('mouseup', cancelEvent)
document.querySelector('body').style['user-select'] = 'none'
@ -26,7 +28,9 @@ export const useMoveLine = (type: Sidebar) => {
}
const cancelEvent = () => {
document.querySelector('.sidebar-move-line').className = 'sidebar-move-line'
if (document.querySelector('.sidebar-move-line')) {
document.querySelector('.sidebar-move-line').className = 'sidebar-move-line'
}
document.querySelector('body').style['user-select'] = 'auto'
wsCache.set(type, width.value)
document.removeEventListener('mousemove', setCoordinates)

View File

@ -0,0 +1,55 @@
<script lang="ts" setup>
import iconSetting from '@/assets/svg/icon-setting.svg'
import { useRouter } from 'vue-router'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { computed } from 'vue'
const appearanceStore = useAppearanceStoreWithOut()
const navigateBg = computed(() => appearanceStore.getNavigateBg)
const { push, resolve } = useRouter()
const redirectUser = () => {
const sysMenu = resolve('/sys-setting')
const kidPath = sysMenu.matched[0].children[0].path
push(`${sysMenu.path}/${kidPath}`)
}
</script>
<template>
<el-tooltip class="box-item" effect="dark" content="系统设置" placement="top">
<div
class="sys-setting in-iframe-setting"
:class="{
'is-light-setting': navigateBg && navigateBg === 'light'
}"
>
<el-icon @click="redirectUser">
<Icon class="icon-setting" name="icon-setting"
><iconSetting class="svg-icon icon-setting"
/></Icon>
</el-icon>
</div>
</el-tooltip>
</template>
<style lang="less" scoped>
.sys-setting {
margin: 0 10px 0 0;
padding: 5px;
height: 28px;
width: 28px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
&:hover {
background-color: #1e2738;
}
}
.in-iframe-setting {
margin-left: 10px !important;
}
.is-light-setting {
&:hover {
background-color: #1f23291a !important;
}
}
</style>

View File

@ -11,7 +11,6 @@ import { formatRoute } from '@/router/establish'
import HeaderMenuItem from './HeaderMenuItem.vue'
import { useEmitt } from '@/hooks/web/useEmitt'
import { Icon } from '@/components/icon-custom'
import { ElHeader, ElMenu } from 'element-plus-secondary'
import SystemCfg from './SystemCfg.vue'
import ToolboxCfg from './ToolboxCfg.vue'
import { useRouter, useRoute } from 'vue-router'
@ -24,12 +23,14 @@ import AiComponent from '@/layout/components/AiComponent.vue'
import { findBaseParams } from '@/api/aiComponent'
import AiTips from '@/layout/components/AiTips.vue'
import CopilotCom from '@/layout/components/Copilot.vue'
import DesktopSetting from './DesktopSetting.vue'
const appearanceStore = useAppearanceStoreWithOut()
const { push } = useRouter()
const route = useRoute()
import { useCache } from '@/hooks/web/useCache'
import { useI18n } from '@/hooks/web/useI18n'
import { msgCountApi } from '@/api/msg'
const { wsCache } = useCache('localStorage')
const aiBaseUrl = ref('https://maxkb.fit2cloud.com/ui/chat/2ddd8b594ce09dbb?mode=embed')
const handleIconClick = () => {
@ -119,12 +120,17 @@ const copilotConfirm = () => {
wsCache.set('DE-COPILOT-TIPS-CHECK', 'CHECKED')
showOverlayCopilot.value = false
}
const badgeCount = ref(0)
onMounted(() => {
initShowSystem()
initShowToolbox()
initAiBase()
initCopilotBase()
msgCountApi().then(res => {
badgeCount.value = res?.data || 0
})
})
</script>
@ -184,15 +190,17 @@ onMounted(() => {
<ToolboxCfg v-if="showToolbox" />
<TopDoc v-if="appearanceStore.getShowDoc" />
<el-tooltip effect="dark" :content="$t('v_query.msg_center')" placement="bottom">
<el-icon
class="preview-download_icon"
style="margin-right: 10px"
:class="navigateBg === 'light' && 'is-light-setting'"
>
<Icon name="dv-preview-download"
><msgNotice @click="msgNoticePush" class="svg-icon"
/></Icon>
</el-icon>
<el-badge :hidden="badgeCount === 0" :value="badgeCount" class="item">
<el-icon
class="preview-download_icon"
style="margin-right: 10px"
:class="navigateBg === 'light' && 'is-light-setting'"
>
<Icon name="dv-preview-download"
><msgNotice @click="msgNoticePush" class="svg-icon"
/></Icon>
</el-icon>
</el-badge>
</el-tooltip>
<SystemCfg v-if="showSystem" />
@ -204,6 +212,9 @@ onMounted(() => {
<div v-if="showOverlay && appearanceStore.getShowAi" class="overlay"></div>
<div v-if="showOverlayCopilot && appearanceStore.getShowCopilot" class="overlay"></div>
</div>
<div v-else class="operate-setting">
<desktop-setting />
</div>
</el-header>
</template>

View File

@ -7,10 +7,11 @@ import { useRouter } from 'vue-router'
import AccountOperator from '@/layout/components/AccountOperator.vue'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { useI18n } from '@/hooks/web/useI18n'
import { isDesktop } from '@/utils/ModelUtil'
const appearanceStore = useAppearanceStoreWithOut()
const { push } = useRouter()
const { t } = useI18n()
const desktop = isDesktop()
const props = withDefaults(
defineProps<{
title: string
@ -43,7 +44,7 @@ const navigate = computed(() => appearanceStore.getNavigate)
<span class="work">{{ t('work_branch.back_to_work_branch') }}</span>
</span>
<AccountOperator />
<AccountOperator v-if="!desktop" />
</div>
</el-header>
</template>

View File

@ -18,6 +18,7 @@ import variable from '@/assets/svg/variable.svg'
import watermark from '@/assets/svg/watermark.svg'
import icon_font from '@/assets/svg/icon_font.svg'
import icon_msg_fill from '@/assets/svg/icon_msg_fill.svg'
import icon_free from '@/assets/svg/icon_free.svg'
const iconMap = {
appearance: appearance,
@ -29,7 +30,8 @@ const iconMap = {
variable: variable,
watermark: watermark,
icon_font: icon_font,
icon_msg_fill,
icon_msg_fill: icon_msg_fill,
icon_free: icon_free,
auth: auth,
association: association,
threshold: threshold,

View File

@ -83,9 +83,8 @@ onMounted(() => {
<style lang="less">
.toolbox-top-popover {
height: 82px;
min-width: 208px !important;
padding: 16px !important;
padding: 8px !important;
display: flex;
.doc-card {
margin: auto;

View File

@ -29,17 +29,15 @@ const cardInfoList = [
:show-arrow="false"
popper-class="top-popover"
placement="bottom-end"
width="208"
width="210"
trigger="hover"
>
<el-row>
<top-doc-card
:span="12"
v-for="(item, index) in cardInfoList"
:key="index"
:card-info="item"
></top-doc-card>
</el-row>
<top-doc-card
:span="12"
v-for="(item, index) in cardInfoList"
:key="index"
:card-info="item"
></top-doc-card>
<template #reference>
<div
class="sys-setting"
@ -75,6 +73,11 @@ const cardInfoList = [
<style lang="less">
.top-popover {
padding: 0 0 16px 0 !important;
display: flex;
padding: 8px !important;
flex-wrap: wrap;
.doc-card {
margin: auto;
}
}
</style>

View File

@ -27,33 +27,28 @@ const openBlank = () => {
<template>
<div class="doc-card" @click="openBlank">
<el-row class="base-show">
<Icon class-name="item-top-icon"
><component class="svg-icon item-top-icon" :is="cardInfo.icon"></component
></Icon>
</el-row>
<el-row class="base-show show-content"> {{ cardInfo.name }}</el-row>
<div class="base-show">
<Icon><component class="svg-icon item-top-icon" :is="cardInfo.icon"></component></Icon>
</div>
<div class="base-show show-content">{{ cardInfo.name }}</div>
</div>
</template>
<style lang="less" scoped>
.doc-card {
padding-top: 2px;
margin-top: 16px;
margin-left: 16px;
width: 80px;
height: 50px;
padding: 8px 0;
width: 96px;
height: 66px;
cursor: pointer;
&:hover {
background-color: rgba(30, 39, 56, 0.05);
}
display: flex;
flex-direction: column;
align-items: center;
&:hover,
&:active {
background-color: rgba(30, 39, 56, 0.1);
background-color: #1f23291a;
border-radius: 4px;
}
}
.base-show {
justify-content: center;
}
.show-content {
font-size: 14px;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -492,6 +492,7 @@ export default {
click_to_show: '点击显示',
click_to_hide: '点击隐藏',
basic_settings: '基础设置',
login_settings: '登录设置',
and_0_seconds: '0分0秒',
time_0_seconds: '分钟执行时间0秒',
and_0_seconds_de: '小时执行时间0分0秒',
@ -754,7 +755,7 @@ export default {
reset_success: '重置成功',
modify_cur_pwd: '修改当前用户密码后需要重新登录',
switch_success: '切换成功',
user_name_pattern_error: '只允许数字字母以及._-且必须数字或字母开头',
user_name_pattern_error: "只允许数字字母以及{'@'}._-且必须数字或字母开头",
pwd_pattern_error: '6-20位且至少一位大写字母小写字母数字特殊字符',
special_characters_are_not_supported: '不允许特殊字符',
phone_format: '请填写正确格式手机号',
@ -1245,6 +1246,7 @@ export default {
quick_calc: '快速计算',
show_name_set: '编辑显示名称',
show_name: '显示名称',
backdrop_blur: '背景模糊',
color: '颜色',
color_case: '配色方案',
pls_slc_color_case: '请选择配色方案',
@ -1379,8 +1381,8 @@ export default {
table_header_show_vertical_border: '表头纵边框线',
table_cell_show_horizon_border: '单元格横边框线',
table_cell_show_vertical_border: '单元格纵边框线',
table_col_freeze_tip: '第一列到',
tbale_row_freeze_tip: '第一行到',
table_col_freeze_tip: '冻结前 n ',
table_row_freeze_tip: '冻结前 n ',
table_freeze: '冻结',
stripe: '斑马纹',
start_angle: '起始角度',
@ -1794,7 +1796,8 @@ export default {
p_bottom: '下对齐',
p_center: '居中',
table_auto_break_line: '自动换行',
table_break_line_tip: '开启自动换行表格行高设置将失效',
table_break_line_tip: '开启自动换行表头行高设置将失效',
table_break_line_max_lines: '最大行数',
step: '步长(px)',
no_function: '函数尚未支持直接引用请在字段表达式中手动输入',
chart_flow_map: '流向地图',
@ -2672,6 +2675,7 @@ export default {
column_name: '字段名称'
},
visualization: {
out_params_no_select: '外部参数无需选择',
filter_no_select: '过滤组件无需选择',
forbidden_copy: '当前组件不允许复制',
url_check_error: '跳转错误URL不合法',
@ -3082,7 +3086,10 @@ export default {
shareDisable: '禁用分享',
sharePeRequire: '分享有效期密码必填',
defaultSort: '资源默认排序方式',
defaultOpen: '页面打开方式'
defaultOpen: '页面打开方式',
loginLimit: '限制登录',
loginLimitRate: '限制登录失败次数()',
loginLimitTime: '限制登录失败时间()'
},
resource_sort: {
time_asc: '按创建时间升序',
@ -3532,6 +3539,7 @@ export default {
exporting: '后台导出中,可前往',
progress_to_download: '查看进度进行下载',
form: {
confirm_to_mark_as_complete: '确认标记为完成?',
mobile_number_format_is_incorrect: '手机号码格式不正确',
email_format_is_incorrect: '邮箱格式不正确',
name: '名称',
@ -3823,5 +3831,35 @@ export default {
delete_api_key: '确定删除该 API key ? ',
api_key_desc:
'API Key 是您访问 DataEase API 的密钥具有账户的完全权限请您务必妥善保管不要以任何方式公开 API Key 到外部渠道避免被他人利用造成安全威胁'
},
free: {
title: '游离资源管理',
no_data: '暂无游离资源',
sync: '迁移',
quick: '一键',
batch: '批量',
resource: '资源',
view_association: '查看血缘关系',
quick_sync_tips: '所有仪表板数据大屏数据集数据源将全部迁移到迁移资源文件夹',
batch_sync_tips:
'1. 与选中资源相关的仪表板数据大屏数据集数据源也将一并迁移到对应资源的迁移资源文件夹',
batch_sync_tips1: '2. 迁移文件夹将同时迁移该文件夹下的子文件夹和资源',
quick_del_confirm: '确定删除所有游离资源吗',
quick_del_tips: '资源删除后不可撤销',
quick_sync_confirm: '确定迁移所有游离资源吗',
quick_sync_confirm_tips: '迁移资源后不可撤销请谨慎操作',
batch_sync_confirm: '确定迁移 {0} 项及其相关游离资源吗',
single_sync_confirm: '确定迁移该资源吗',
batch_del_confirm: '确定删除 {0} 项资源吗',
batch_del_confirm_tips: '资源删除后不可撤销请谨慎操作',
del_tips_dataset: '删除数据集会造成相关数据集失效确定删除',
del_tips_datasource: '有数据集正在使用这些数据源删除后数据集不可用确定删除',
single_del_confirm: '确定删除该{0}',
single_del_tips_dataset: '该数据集存在如下血缘关系删除会造成相关视图失效确定删除',
single_del_tips_datasource: ' {0} 个数据集正在使用此数据源删除后数据集不可用确定删除',
folder: '文件夹',
del_folder_tips: '删除后此文件夹下的所有资源都会被删除请谨慎操作',
sync_to_org: '迁移至目标组织',
sync_org_placeholder: '请选择目标组织'
}
}

View File

@ -326,6 +326,14 @@ declare interface ChartBasicStyle {
* 表格鼠标悬浮样式
*/
showHoverStyle: boolean
/**
* 明细表单元格自动换行
*/
autoWrap: boolean
/**
* 最大行数
*/
maxLines?: boolean
}
/**
* 表头属性

View File

@ -147,6 +147,10 @@ declare interface ChartThreshold {
* 文本卡阈值
*/
textLabelThreshold: Threshold[]
/**
* 折线阈值
*/
lineThreshold: TableThreshold[]
}
declare interface TableThreshold {
/**

View File

@ -11,6 +11,7 @@ interface UserState {
oid: string
language: string
exp: number
time: number
}
export const userStore = defineStore('user', {
@ -21,7 +22,8 @@ export const userStore = defineStore('user', {
name: null,
oid: null,
language: 'zh-CN',
exp: null
exp: null,
time: null
}
},
getters: {
@ -42,6 +44,9 @@ export const userStore = defineStore('user', {
},
getExp(): number {
return this.exp
},
getTime(): number {
return this.time
}
},
actions: {
@ -57,7 +62,8 @@ export const userStore = defineStore('user', {
const data = res.data
data.token = wsCache.get('user.token')
data.exp = wsCache.get('user.exp')
const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp']
data.time = wsCache.get('user.time')
const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp', 'time']
keys.forEach(key => {
const dkey = key === 'uid' ? 'id' : key
@ -74,6 +80,10 @@ export const userStore = defineStore('user', {
wsCache.set('user.exp', exp)
this.exp = exp
},
setTime(time: number) {
wsCache.set('user.time', time)
this.time = time
},
setUid(uid: string) {
wsCache.set('user.uid', uid)
this.uid = uid
@ -95,7 +105,7 @@ export const userStore = defineStore('user', {
locale.setLang(language)
},
clear() {
const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp']
const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp', 'time']
keys.forEach(key => wsCache.delete('user.' + key))
}
}

View File

@ -590,6 +590,13 @@ strong {
overflow-y: auto;
}
.preview-content-inner-size-keep {
width: 100%;
height: auto;
overflow-x: auto;
overflow-y: auto;
}
.preview-content-inner-height-first {
width: auto;
height: 100%;
@ -607,3 +614,7 @@ strong {
.ed-message .ed-message__closeBtn:hover {
background: #ebebebe6 !important;
}
.canvas_keep-size {
overflow-x: auto!important;
}

View File

@ -421,6 +421,41 @@ export function adaptCurTheme(customStyle, customAttr) {
}
}
export function adaptTitleFontFamily(fontFamily, viewInfo) {
if (viewInfo) {
viewInfo.customStyle['text']['fontFamily'] = fontFamily
}
}
export function adaptTitleFontFamilyAll(fontFamily) {
const componentData = dvMainStore.componentData
componentData.forEach(item => {
if (item.component === 'UserView') {
const viewDetails = dvMainStore.canvasViewInfo[item.id]
adaptTitleFontFamily(fontFamily, viewDetails)
useEmitt().emitter.emit('renderChart-' + item.id, viewDetails)
} else if (item.component === 'Group') {
item.propValue.forEach(groupItem => {
if (groupItem.component === 'UserView') {
const viewDetails = dvMainStore.canvasViewInfo[item.id]
adaptTitleFontFamily(fontFamily, viewDetails)
useEmitt().emitter.emit('renderChart-' + item.id, viewDetails)
}
})
} else if (item.component === 'DeTabs') {
item.propValue.forEach(tabItem => {
tabItem.componentData.forEach(tabComponent => {
if (tabComponent.component === 'UserView') {
const viewDetails = dvMainStore.canvasViewInfo[item.id]
adaptTitleFontFamily(fontFamily, viewDetails)
useEmitt().emitter.emit('renderChart-' + item.id, viewDetails)
}
})
})
}
})
}
export function adaptCurThemeCommonStyle(component) {
if (['DeTabs'].includes(component.component)) {
component.commonBackground['innerPadding'] = 0

View File

@ -17,7 +17,8 @@ import {
findById,
findCopyResource,
saveCanvas,
updateCanvas
updateCanvas,
updateCheckVersion
} from '@/api/visualization/dataVisualization'
import { storeToRefs } from 'pinia'
import { getPanelAllLinkageInfo } from '@/api/visualization/linkage'
@ -35,8 +36,11 @@ const { inMobile, dvInfo, canvasStyleData, componentData, canvasViewInfo, appDat
storeToRefs(dvMainStore)
const snapshotStore = snapshotStoreWithOut()
import { useI18n } from '@/hooks/web/useI18n'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { useCache } from '@/hooks/web/useCache'
const { t } = useI18n()
const appearanceStore = useAppearanceStoreWithOut()
const { wsCache } = useCache()
export function chartTransStr2Object(targetIn, copy) {
const target = copy === 'Y' ? cloneDeep(targetIn) : targetIn
return target
@ -146,7 +150,7 @@ export function historyItemAdaptor(
}
if (componentItem.component === 'DeTabs') {
componentItem.style['titleHide'] = componentItem.style['titleHide'] || false
componentItem.style['showTabTitle'] = componentItem.style['showTabTitle'] || true
}
componentItem['expand'] = componentItem['expand'] || false
@ -222,7 +226,14 @@ export function historyAdaptor(
attachInfo,
canvasVersion
) {
const curVersion = wsCache.get('x-de-execute-version')
if (canvasInfo?.checkVersion === curVersion) {
return
}
//历史字段适配
canvasStyleResult.component['seniorStyleSetting'] =
canvasStyleResult.component['seniorStyleSetting'] || deepCopy(SENIOR_STYLE_SETTING_LIGHT)
canvasStyleResult['fontFamily'] = canvasStyleResult['fontFamily'] || 'PingFang'
canvasStyleResult.dashboard['showGrid'] = canvasStyleResult.dashboard['showGrid'] || false
canvasStyleResult.dashboard['matrixBase'] = canvasStyleResult.dashboard['matrixBase'] || 4
canvasStyleResult.component['seniorStyleSetting'] =
@ -251,6 +262,9 @@ export function historyAdaptor(
canvasDataResult.forEach(componentItem => {
historyItemAdaptor(componentItem, reportFilterInfo, attachInfo, canvasVersion, canvasInfo)
})
if (canvasInfo && canvasInfo.id) {
updateCheckVersion(canvasInfo.id)
}
}
// 重置仪表板大屏中的其他组件
@ -338,13 +352,15 @@ export function initCanvasDataPrepare(dvId, busiFlag, callBack) {
const canvasStyleResult = JSON.parse(canvasInfo.canvasStyleData)
const canvasViewInfoPreview = canvasInfo.canvasViewInfo
historyAdaptor(canvasStyleResult, canvasDataResult, canvasInfo, attachInfo, canvasVersion)
//历史字段适配
canvasStyleResult.component['seniorStyleSetting'] =
canvasStyleResult.component['seniorStyleSetting'] || deepCopy(SENIOR_STYLE_SETTING_LIGHT)
const curPreviewGap =
dvInfo.type === 'dashboard' && canvasStyleResult['dashboard'].gap === 'yes'
? canvasStyleResult['dashboard'].gapSize
: 0
appearanceStore.setCurrentFont(canvasStyleResult.fontFamily)
document.documentElement.style.setProperty(
'--de-canvas_custom_font',
`${canvasStyleResult.fontFamily}`
)
callBack({ canvasDataResult, canvasStyleResult, dvInfo, canvasViewInfoPreview, curPreviewGap })
})
}

View File

@ -200,7 +200,8 @@ export function getCanvasStyle(canvasStyleData, canvasId = 'canvas-main') {
backgroundColor,
backgroundImageEnable,
fontSize,
mobileSetting
mobileSetting,
fontFamily
} = canvasStyleData
const style = { fontSize: fontSize + 'px', color: canvasStyleData.color }
if (isMainCanvas(canvasId)) {
@ -225,6 +226,7 @@ export function getCanvasStyle(canvasStyleData, canvasId = 'canvas-main') {
style['background'] = `url(${imgUrlTrans(background)}) no-repeat`
}
}
style['font-family'] = fontFamily + '!important'
}
return style
@ -249,16 +251,17 @@ export function createGroupStyle(groupComponent) {
function dataVTabSizeStyleAdaptor(tabComponent) {
const parentStyleAdaptor = { ...tabComponent.style }
const offset = parentStyleAdaptor.showTabTitle ? 46 : 0
const domId =
dvMainStore.editMode === 'edit'
? 'component' + tabComponent.id
: 'enlarge-inner-content' + tabComponent.id
const tabDom = document.getElementById(domId)
if (tabDom) {
parentStyleAdaptor.height = tabDom.clientHeight - 46
parentStyleAdaptor.height = tabDom.clientHeight - offset
parentStyleAdaptor.width = tabDom.clientWidth
} else {
parentStyleAdaptor.height = parentStyleAdaptor.height - 46
parentStyleAdaptor.height = parentStyleAdaptor.height - offset
}
tabComponent.propValue.forEach(tabItem => {
@ -288,6 +291,18 @@ export function groupStyleRevertBatch(groupComponent, parentStyle) {
}
}
export function tabInnerStyleRevert(tabOuterComponent) {
const parentStyle = {
width: tabOuterComponent.style.width,
height: tabOuterComponent.style.height - (tabOuterComponent.style.showTabTitle ? 46 : 0)
}
tabOuterComponent.propValue.forEach(tabItem => {
tabItem.componentData.forEach(tabComponent => {
groupStyleRevert(tabComponent, parentStyle)
})
})
}
export function groupStyleRevert(innerComponent, parentStyle) {
const innerStyle = { ...innerComponent.style }
innerComponent.groupStyle.left = innerStyle.left / parentStyle.width
@ -313,6 +328,6 @@ export function dataVTabComponentAdd(innerComponent, parentComponent) {
innerComponent.style.left = 0
const parentStyleAdaptor = { ...parentComponent.style }
// 去掉tab头部高度
parentStyleAdaptor.height = parentStyleAdaptor.height - 48
parentStyleAdaptor.height = parentStyleAdaptor.height - (parentComponent.showTabTitle ? 46 : 0)
groupStyleRevert(innerComponent, parentStyleAdaptor)
}

View File

@ -39,6 +39,12 @@ const props = defineProps({
type: Number,
required: false,
default: 1
},
//
fontFamily: {
type: String,
required: false,
default: 'inherit'
}
})
const { canvasStyleData, componentData, canvasViewInfo, canvasId, canvasActive, outerScale } =
@ -98,7 +104,9 @@ const handleNewFromCanvasMain = newComponentInfo => {
adaptCurThemeCommonStyle(component)
nextTick(() => {
cyGridster.value.addItemBox(component) //
scrollTo(component.y)
nextTick(() => {
scrollTo(component.y)
})
})
snapshotStore.recordSnapshotCache('renderChart', component.id)
}
@ -319,6 +327,7 @@ defineExpose({
:base-margin-top="baseMarginTop"
:base-width="baseWidth"
:base-height="baseHeight"
:font-family="fontFamily"
@scrollCanvasToTop="scrollTo(1)"
></canvas-core>
</div>

View File

@ -7,6 +7,7 @@ import { DEFAULT_THRESHOLD } from '@/views/chart/components/editor/util/chart'
import TableThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/TableThresholdEdit.vue'
import TextLabelThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/TextLabelThresholdEdit.vue'
import TextThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/TextThresholdEdit.vue'
import LineThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/LineThresholdEdit.vue'
import { fieldType } from '@/utils/attr'
import { defaultsDeep } from 'lodash-es'
import { iconFieldMap } from '@/components/icon-group/field-list'
@ -50,7 +51,9 @@ const state = reactive({
editLabelThresholdDialog: false,
thresholdArr: [],
editTableThresholdDialog: false,
tableThresholdArr: []
tableThresholdArr: [],
editLineThresholdDialog: false,
lineThresholdArr: []
})
const init = () => {
@ -63,6 +66,7 @@ const init = () => {
state.textThresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.textLabelThreshold))
state.thresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.labelThreshold))
state.tableThresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.tableThreshold))
state.lineThresholdArr = JSON.parse(JSON.stringify(state.thresholdForm.lineThreshold ?? []))
}
}
const changeThreshold = () => {
@ -257,6 +261,77 @@ const changeTableThreshold = () => {
changeThreshold()
closeTableThreshold()
}
const lineThresholdChange = val => {
state.lineThresholdArr = val
}
const editLineThreshold = () => {
state.editLineThresholdDialog = true
}
const closeLineThreshold = () => {
state.editLineThresholdDialog = false
}
const changeLineThreshold = () => {
// check line config
for (let i = 0; i < state.lineThresholdArr?.length; i++) {
const field = state.lineThresholdArr[i]
if (!field.fieldId) {
ElMessage.error(t('chart.field_can_not_empty'))
return
}
if (!field.conditions || field.conditions.length === 0) {
ElMessage.error(t('chart.conditions_can_not_empty'))
return
}
for (let j = 0; j < field.conditions.length; j++) {
const ele = field.conditions[j]
if (!ele.term || ele.term === '') {
ElMessage.error(t('chart.exp_can_not_empty'))
return
}
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
(ele.min === '' || ele.max === '')
) {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
(parseFloat(ele.min).toString() === 'NaN' || parseFloat(ele.max).toString() === 'NaN')
) {
ElMessage.error(t('chart.value_error'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.min) > parseFloat(ele.max)
) {
ElMessage.error(t('chart.value_min_max_invalid'))
return
}
} else {
if (!ele.term.includes('null') && !ele.term.includes('empty') && ele.value === '') {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
if (
(field.field.deType === 2 || field.field.deType === 3 || field.field.deType === 4) &&
parseFloat(ele.value).toString() === 'NaN'
) {
ElMessage.error(t('chart.value_error'))
return
}
}
}
}
state.thresholdForm.lineThreshold = JSON.parse(JSON.stringify(state.lineThresholdArr ?? []))
changeThreshold()
closeLineThreshold()
}
const getFieldName = field => (field.chartShowName ? field.chartShowName : field.name)
const getDynamicStyleLabel = (item, fieldObj) => {
@ -713,6 +788,129 @@ init()
</div>
</el-col>
</el-col>
<!--折线-->
<el-col v-show="showProperty('lineThreshold')">
<el-col>
<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?.tableThreshold?.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="editLineThreshold"
>
<template #icon>
<el-icon size="14px">
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
</el-icon>
</template>
</el-button>
</span>
</div>
<div
class="threshold-container"
:class="{ 'threshold-container-dark': themes === 'dark' }"
v-if="state.thresholdForm.lineThreshold?.length > 0"
>
<el-row
v-for="(fieldItem, fieldIndex) in state.thresholdForm.lineThreshold"
:key="fieldIndex"
style="flex-direction: column"
>
<div class="field-style" :class="{ 'field-style-dark': themes === 'dark' }">
<el-icon>
<Icon :className="`field-icon-${fieldType[fieldItem.field.deType]}`"
><component
class="svg-icon"
:class="`field-icon-${fieldType[fieldItem.field.deType]}`"
:is="iconFieldMap[fieldType[fieldItem.field.deType]]"
></component
></Icon>
</el-icon>
<span :title="fieldItem.field.name" class="field-text">{{
fieldItem.field.name
}}</span>
</div>
<div v-for="(item, index) in fieldItem.conditions" :key="index" class="line-style">
<div style="flex: 1">
<span v-if="item.term === 'lt'" :title="t('chart.filter_lt')">
{{ t('chart.filter_lt') }}
</span>
<span v-else-if="item.term === 'gt'" :title="t('chart.filter_gt')">
{{ t('chart.filter_gt') }}
</span>
<span v-else-if="item.term === 'le'" :title="t('chart.filter_le')">
{{ t('chart.filter_le') }}
</span>
<span v-else-if="item.term === 'ge'" :title="t('chart.filter_ge')">
{{ t('chart.filter_ge') }}
</span>
<span v-else-if="item.term === 'between'" :title="t('chart.filter_between')">
{{ t('chart.filter_between') }}
</span>
<span v-else-if="item.term === 'default'" title="默认"> 默认 </span>
</div>
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.fix') }}
</span>
</div>
<div v-else style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.dynamic') }}
</span>
</div>
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span
v-if="
!item.term.includes('null') &&
!item.term.includes('default') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:title="item.value + ''"
>{{ item.value }}</span
>
<span
v-else-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term === 'between'
"
:title="item.min + ' ≤= ' + t('chart.drag_block_label_value') + ' ≤ ' + item.max"
>
{{ item.min }}&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;{{ item.max }}
</span>
<span v-else>&nbsp;</span>
</div>
<template v-if="chart.type !== 'picture-group'">
<div
:title="t('chart.color')"
:style="{
backgroundColor: item.color
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
</template>
</div>
</el-row>
</div>
</el-col>
</el-col>
<!--编辑文本卡阈值-->
<el-dialog
@ -794,6 +992,30 @@ init()
</div>
</template>
</el-dialog>
<!--编辑折线阈值-->
<el-dialog
v-if="state.editLineThresholdDialog"
v-model="state.editLineThresholdDialog"
:title="t('chart.threshold')"
:visible="state.editLineThresholdDialog"
width="1050px"
class="dialog-css"
append-to-body
>
<line-threshold-edit
:threshold="state.thresholdForm.lineThreshold"
:chart="chart"
@onLineThresholdChange="lineThresholdChange"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="closeLineThreshold">{{ t('chart.cancel') }}</el-button>
<el-button type="primary" @click="changeLineThreshold">{{
t('chart.confirm')
}}</el-button>
</div>
</template>
</el-dialog>
</div>
</template>

View File

@ -0,0 +1,499 @@
<script lang="tsx" setup>
import icon_info_filled from '@/assets/svg/icon_info_filled.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import { PropType, reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL } from '../../../util/chart'
import { fieldType } from '@/utils/attr'
import { iconFieldMap } from '@/components/icon-group/field-list'
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object as PropType<ChartObj>,
required: true
},
threshold: {
type: Array,
required: true,
default: () => []
}
})
const emit = defineEmits(['onLineThresholdChange'])
const thresholdCondition = {
term: 'lt',
field: '0',
value: '0',
color: '#ff0000ff',
backgroundColor: '#ffffff00',
min: '0',
max: '1',
type: 'fixed'
}
const valueOptions = [
{
label: '',
options: [
{
value: 'lt',
label: t('chart.filter_lt')
},
{
value: 'gt',
label: t('chart.filter_gt')
}
]
},
{
label: '',
options: [
{
value: 'between',
label: t('chart.filter_between')
}
]
}
]
const predefineColors = COLOR_PANEL
const state = reactive({
thresholdArr: [] as LineThreshold[],
fields: [],
thresholdObj: {
fieldId: '',
field: {},
conditions: []
} as LineThreshold
})
const init = () => {
state.thresholdArr = JSON.parse(JSON.stringify(props.threshold)) as LineThreshold[]
initFields()
}
const initOptions = (item, fieldObj) => {
if (fieldObj) {
item.options = JSON.parse(JSON.stringify(valueOptions))
item.conditions &&
item.conditions.forEach(ele => {
ele.term = ''
})
}
}
const initFields = () => {
let fields = []
const yAxis = JSON.parse(JSON.stringify(props.chart.yAxis))
fields = [...yAxis]
state.fields.splice(0, state.fields.length, ...fields)
//
let change = false
state.thresholdArr.forEach(item => {
const fieldItemObj = state.fields.filter(ele => ele.id === item.fieldId)
if (fieldItemObj.length === 0) {
change = true
item.fieldId = null
}
})
if (change) {
changeThreshold()
}
}
const addThreshold = () => {
state.thresholdArr.push(JSON.parse(JSON.stringify(state.thresholdObj)))
changeThreshold()
}
const removeThreshold = index => {
state.thresholdArr.splice(index, 1)
changeThreshold()
}
const changeThreshold = () => {
emit('onLineThresholdChange', state.thresholdArr)
}
const addConditions = item => {
const newCondition = JSON.parse(JSON.stringify(thresholdCondition))
item.conditions.push(newCondition)
changeThreshold()
}
const removeCondition = (item, index) => {
item.conditions.splice(index, 1)
changeThreshold()
}
const addField = item => {
// get field
if (state.fields && state.fields.length > 0) {
state.fields.forEach(ele => {
if (item.fieldId === ele.id) {
item.field = JSON.parse(JSON.stringify(ele))
initOptions(item, item.field)
}
})
}
changeThreshold()
}
const fieldOptions = [{ label: t('chart.field_fixed'), value: 'fixed' }]
const isNotEmptyAndNull = item => {
return !item.term.includes('null') && !item.term.includes('empty')
}
const isBetween = item => {
return item.term === 'between'
}
const isDynamic = item => {
return item.type === 'dynamic'
}
const getFieldOptions = fieldItem => {
const deType = state.fields.filter(ele => ele.id === fieldItem.fieldId)?.[0]?.deType
if (deType === 1) {
return fieldOptions.filter(ele => ele.value === 'fixed')
} else {
return fieldOptions
}
}
init()
</script>
<template>
<el-col>
<div class="tip">
<Icon name="icon_info_filled" class="icon-style"
><icon_info_filled class="svg-icon icon-style"
/></Icon>
<span style="padding-left: 10px">{{ t('chart.table_threshold_tip') }}</span>
</div>
<div @keydown.stop @keyup.stop style="max-height: 50vh; overflow-y: auto">
<div
v-for="(fieldItem, fieldIndex) in state.thresholdArr"
:key="fieldIndex"
class="field-item"
>
<el-row style="margin-top: 6px; align-items: center; justify-content: space-between">
<el-form-item class="form-item">
<el-select v-model="fieldItem.fieldId" @change="addField(fieldItem)">
<el-option
class="series-select-option"
v-for="fieldOption in state.fields"
:key="fieldOption.id"
:label="fieldOption.name"
:value="fieldOption.id"
:disabled="chart.type === 'table-info' && fieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(fieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[fieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ fieldOption.name }}
</el-option>
</el-select>
</el-form-item>
<el-button
class="circle-button m-icon-btn"
text
:style="{ float: 'right' }"
@click="removeThreshold(fieldIndex)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</el-row>
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #d5d6d8' }">
<el-row
v-for="(item, index) in fieldItem.conditions"
:key="index"
class="line-item"
:gutter="12"
>
<el-col :span="4">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group
v-for="(group, idx) in fieldItem.options"
: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="4" v-if="isNotEmptyAndNull(item)" style="padding-left: 0 !important">
<el-form-item class="form-item">
<el-select v-model="item.type" class="select-item" style="width: 100%">
<el-option
v-for="opt in getFieldOptions(fieldItem)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</el-col>
<!--不是between 不是动态值-->
<el-col
v-if="isNotEmptyAndNull(item) && !isBetween(item) && !isDynamic(item)"
:span="12"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.value"
v-if="[2, 3].includes(fieldItem.field.deType)"
:placeholder="t('chart.drag_block_label_value')"
controls-position="right"
class="value-item"
clearable
@change="changeThreshold"
/>
<el-input
v-model="item.value"
v-else
:placeholder="t('chart.drag_block_label_value')"
controls-position="right"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<!--between 开始值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<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-form-item>
</el-col>
<el-col
v-if="isBetween(item) && !isDynamic(item)"
:span="2"
style="margin-top: 4px; text-align: center"
>
<span style="margin: 0 -5px">
&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;
</span>
</el-col>
<!--between 结束值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<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-col>
<el-col :span="3">
<el-form-item class="form-item" :label="t('chart.textColor')">
<el-color-picker
is-custom
size="large"
v-model="item.color"
show-alpha
class="color-picker-style"
:predefine="predefineColors"
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<el-col :span="1">
<div style="display: flex; align-items: center; justify-content: center">
<el-button
class="circle-button m-icon-btn"
text
@click="removeCondition(fieldItem, index)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-col>
</el-row>
</el-row>
<el-button
style="margin-top: 10px"
class="circle-button"
type="primary"
text
@click="addConditions(fieldItem)"
>
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('chart.add_style') }}
</el-button>
</div>
</div>
<el-button
class="circle-button"
text
type="primary"
style="margin-top: 10px"
@click="addThreshold"
>
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('chart.add_condition') }}
</el-button>
</el-col>
</template>
<style lang="less" scoped>
.field-item {
width: 100%;
border-radius: 4px;
padding: 10px 16px;
margin-top: 10px;
background: #f5f6f7;
}
.line-item {
width: 100%;
display: flex;
justify-content: left;
align-items: center;
margin-top: 16px;
}
.form-item {
height: 28px !important;
:deep(.el-form-item__label) {
font-size: 12px;
}
}
span {
font-size: 12px;
}
.value-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.between-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.select-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.el-select-dropdown__item {
padding: 0 20px;
font-size: 12px;
}
.color-picker-style {
cursor: pointer;
z-index: 1003;
width: 28px;
height: 28px;
}
.color-picker-style :deep(.el-color-picker__trigger) {
width: 28px;
height: 28px;
}
.color-title {
color: #646a73;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
padding: 0 8px;
}
.tip {
font-size: 12px;
background: #d6e2ff;
border-radius: 4px;
padding: 10px 20px;
display: flex;
align-items: center;
}
:deep(.ed-form-item) {
margin-bottom: 0 !important;
}
.icon-style {
width: 14px;
height: 14px;
color: var(--ed-color-primary);
}
.m-icon-btn {
&:hover {
background: rgba(31, 35, 41, 0.1) !important;
}
&:focus {
background: rgba(31, 35, 41, 0.1) !important;
}
&:active {
background: rgba(31, 35, 41, 0.2) !important;
}
}
.series-select-option {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 11px;
}
</style>

View File

@ -297,6 +297,33 @@ initParams()
:predefine="COLOR_PANEL"
/>
</el-form-item>
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="commonBackgroundPop.backdropFilterEnable"
>
{{ $t('chart.backdrop_blur') }}
</el-checkbox>
</el-form-item>
<el-form-item
style="padding-left: 20px"
class="form-item margin-bottom-8"
:class="'form-item-' + themes"
>
<el-input-number
style="width: 100%"
:effect="themes"
controls-position="right"
size="middle"
:min="0"
:max="30"
:disabled="!commonBackgroundPop.backdropFilterEnable"
v-model="commonBackgroundPop.backdropFilter"
/>
</el-form-item>
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
@ -504,6 +531,7 @@ initParams()
<el-select
v-model="currentPlaceholder"
@change="handleCurrentPlaceholder"
:effect="themes"
style="width: 100%"
>
<el-option

Some files were not shown because too many files have changed in this diff Show More