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

This commit is contained in:
taojinlong 2024-11-08 16:45:56 +08:00
commit a86c8f463a
99 changed files with 1454 additions and 310 deletions

View File

@ -52,10 +52,10 @@ public class StackBarHandler extends BarHandler {
if (ObjectUtils.isNotEmpty(extStack) &&
Objects.equals(drillFields.get(0).getId(), extStack.get(0).getId())) {
fieldsToFilter.addAll(view.getXAxis());
groupStackDrill(noDrillFieldAxis, noDrillFilterList, fieldsToFilter, drillFields, drillRequestList);
formatResult.getAxisMap().put(ChartAxis.xAxis, noDrillFieldAxis);
result.setFilterList(noDrillFilterList);
}
groupStackDrill(noDrillFieldAxis, noDrillFilterList, fieldsToFilter, drillFields, drillRequestList);
formatResult.getAxisMap().put(ChartAxis.xAxis, noDrillFieldAxis);
result.setFilterList(noDrillFilterList);
}
return (T) result;
}

View File

@ -316,6 +316,11 @@ public class ChartDataManage {
dillAxis.add(nextDrillField);
fields.add(nextDrillField.getId());
} else {
Optional<ChartViewFieldDTO> axis = xAxis.stream().filter(x -> Objects.equals(x.getId(), nextDrillField.getId())).findFirst();
axis.ifPresent(field -> {
field.setSort(nextDrillField.getSort());
field.setCustomSort(nextDrillField.getCustomSort());
});
dillAxis.add(nextDrillField);
}
}

View File

@ -362,9 +362,9 @@ public class ChartViewThresholdManage {
}
} else {
if (timeFlag == 4) {
return now.withDayOfMonth(1).format(formatter);
return now.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).format(formatter);
} else if (timeFlag == 5) {
return now.plusMonths(1).withDayOfMonth(1).minusDays(1).format(formatter);
return now.plusMonths(1).withDayOfMonth(1).minusDays(1).withHour(0).withMinute(0).withSecond(0).format(formatter);
} else {
return getCustomTimeValue(format, 3, suffix, count, true);
}
@ -381,7 +381,9 @@ public class ChartViewThresholdManage {
LocalDateTime now = LocalDateTime.now();
String fullFormat = "yyyy-MM-dd HH:mm:ss";
int len = format.length();
if (!hasTime) {
if (hasTime) {
now = now.withHour(0).withMinute(0).withSecond(0);
} else {
len = Math.min(len, 10);
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(fullFormat.substring(0, len));

View File

@ -5,6 +5,7 @@ import io.dataease.api.chart.ChartDataApi;
import io.dataease.api.chart.dto.ViewDetailField;
import io.dataease.api.chart.request.ChartExcelRequest;
import io.dataease.api.chart.request.ChartExcelRequestInner;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.constant.ChartConstants;
import io.dataease.chart.manage.ChartDataManage;
import io.dataease.constant.AuthConstant;
@ -76,6 +77,7 @@ public class ChartDataServer implements ChartDataApi {
return Math.toIntExact(Math.min(f2CLicLimitedManage.checkDatasetLimit(), limit));
}
@DeLinkPermit("#p0.sceneId")
@Override
public ChartViewDTO getData(ChartViewDTO chartViewDTO) throws Exception {
try {

View File

@ -1,7 +1,10 @@
package io.dataease.config;
import io.dataease.constant.AuthConstant;
import io.dataease.share.interceptor.LinkInterceptor;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -11,6 +14,8 @@ import static io.dataease.utils.StaticResourceUtils.ensureSuffix;
@Configuration
public class DeMvcConfig implements WebMvcConfigurer {
@Resource
private LinkInterceptor linkInterceptor;
/**
* Configuring static resource path
@ -33,4 +38,9 @@ public class DeMvcConfig implements WebMvcConfigurer {
registry.addResourceHandler(geoUrlPattern).addResourceLocations(geoDir);
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(linkInterceptor).addPathPatterns("/**");
}
}

View File

@ -260,6 +260,7 @@ public class DatasetDataManage {
public Long getDatasetTotal(Long datasetGroupId) throws Exception {
DatasetGroupInfoDTO dto = datasetGroupManage.getForCount(datasetGroupId);
if (ObjectUtils.isEmpty(dto)) return 0L;
if (StringUtils.equalsIgnoreCase(dto.getNodeType(), "dataset")) {
return getDatasetTotal(dto, null, new ChartExtRequest());
}

View File

@ -262,6 +262,9 @@ public class DatasetGroupManage {
List<CoreDatasetTable> coreDatasetTables = coreDatasetTableMapper.selectList(wrapper);
Set<Long> ids = new LinkedHashSet();
coreDatasetTables.forEach(ele -> ids.add(ele.getDatasourceId()));
if (CollectionUtils.isEmpty(ids)) {
DEException.throwException("数据集因异常导致无法使用,请重新创建");
}
QueryWrapper<CoreDatasource> datasourceQueryWrapper = new QueryWrapper<>();
datasourceQueryWrapper.in("id", ids);

View File

@ -209,8 +209,6 @@ public class DatasetSQLManage {
String tablePrefix = "";
String tableSuffix = "";
if (ObjectUtils.isNotEmpty(currentSQLObj.getTableSchema())) {
ts = currentSQLObj.getTableSchema() + ".";
if (isCross) {
tablePrefix = "`";
tableSuffix = "`";
@ -219,6 +217,8 @@ public class DatasetSQLManage {
tablePrefix = datasourceType.getPrefix();
tableSuffix = datasourceType.getSuffix();
}
ts = tablePrefix + currentSQLObj.getTableSchema() + tableSuffix + ".";
}
// build join
join.append(" ").append(joinType).append(" ")

View File

@ -44,7 +44,6 @@ public class TableUtils {
String prefix = "";
String suffix = "";
if (StringUtils.isNotEmpty(sqlObj.getTableSchema())) {
schema = sqlObj.getTableSchema() + ".";
if (isCross) {
prefix = "`";
suffix = "`";
@ -52,6 +51,7 @@ public class TableUtils {
prefix = datasourceType.getPrefix();
suffix = datasourceType.getSuffix();
}
schema = prefix + sqlObj.getTableSchema() + suffix + ".";
}
return schema + prefix + sqlObj.getTableName() + suffix + " " + sqlObj.getTableAlias();
}

View File

@ -147,19 +147,55 @@ public class ExtWhere2Str {
if (value.contains(SQLConstants.EMPTY_SIGN)) {
whereValue = "('" + StringUtils.join(value, "','") + "', '')" + " or " + whereName + " is null ";
} else {
if (StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = "(" + value.stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")";
// tree的情况需额外处理
if (request.getIsTree()) {
List<DatasetTableFieldDTO> datasetTableFieldList = request.getDatasetTableFieldList();
boolean hasN = false;
for (DatasetTableFieldDTO dto : datasetTableFieldList) {
if (StringUtils.containsIgnoreCase(dto.getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(dto.getType(), "NCHAR")) {
hasN = true;
break;
}
}
if (hasN) {
whereValue = "(" + value.stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")";
} else {
whereValue = "('" + StringUtils.join(value, "','") + "')";
}
} else {
whereValue = "('" + StringUtils.join(value, "','") + "')";
if (StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = "(" + value.stream().map(str -> "'" + SQLConstants.MSSQL_N_PREFIX + str + "'").collect(Collectors.joining(",")) + ")";
} else {
whereValue = "('" + StringUtils.join(value, "','") + "')";
}
}
}
} else if (StringUtils.containsIgnoreCase(request.getOperator(), "like")) {
if (StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value.get(0) + "%'";
// tree的情况需额外处理
if (request.getIsTree()) {
List<DatasetTableFieldDTO> datasetTableFieldList = request.getDatasetTableFieldList();
boolean hasN = false;
for (DatasetTableFieldDTO dto : datasetTableFieldList) {
if (StringUtils.containsIgnoreCase(dto.getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(dto.getType(), "NCHAR")) {
hasN = true;
break;
}
}
if (hasN) {
whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value.get(0) + "%'";
} else {
whereValue = "'%" + value.get(0) + "%'";
}
} else {
whereValue = "'%" + value.get(0) + "%'";
if (StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = "'" + SQLConstants.MSSQL_N_PREFIX + "%" + value.get(0) + "%'";
} else {
whereValue = "'%" + value.get(0) + "%'";
}
}
} else if (StringUtils.containsIgnoreCase(request.getOperator(), "between")) {
if (request.getDatasetTableField().getDeType() == 1) {
@ -191,11 +227,29 @@ public class ExtWhere2Str {
if (StringUtils.equals(value.get(0), SQLConstants.EMPTY_SIGN)) {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, "") + " or " + whereName + " is null ";
} else {
if (StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value.get(0));
// tree的情况需额外处理
if (request.getIsTree()) {
List<DatasetTableFieldDTO> datasetTableFieldList = request.getDatasetTableFieldList();
boolean hasN = false;
for (DatasetTableFieldDTO dto : datasetTableFieldList) {
if (StringUtils.containsIgnoreCase(dto.getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(dto.getType(), "NCHAR")) {
hasN = true;
break;
}
}
if (hasN) {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value.get(0));
} else {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value.get(0));
}
} else {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value.get(0));
if (StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NVARCHAR")
|| StringUtils.containsIgnoreCase(request.getDatasetTableField().getType(), "NCHAR")) {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE_CH, value.get(0));
} else {
whereValue = String.format(SQLConstants.WHERE_VALUE_VALUE, value.get(0));
}
}
}
}

View File

@ -0,0 +1,110 @@
package io.dataease.share.interceptor;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import io.dataease.auth.DeLinkPermit;
import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException;
import io.dataease.share.manage.XpackShareManage;
import io.dataease.share.util.LinkTokenUtil;
import io.dataease.utils.LogUtil;
import io.dataease.utils.ServletUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
public class DeLinkAop {
private static final String PARAM_VARIABLE_PREFIX = "p";
private static final String SPRING_EL_FLAG = "#";
private final ExpressionParser parser = new SpelExpressionParser();
@Resource
private XpackShareManage xpackShareManage;
@Around(value = "@annotation(io.dataease.auth.DeLinkPermit)")
public Object logAround(ProceedingJoinPoint point) throws Throwable {
Object[] params = point.getArgs();
String linkToken = ServletUtils.getHead(AuthConstant.LINK_TOKEN_KEY);
if (StringUtils.isNotBlank(linkToken)) {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
DeLinkPermit deLinkPermit = method.getAnnotation(DeLinkPermit.class);
String value = deLinkPermit.value();
if (StringUtils.isBlank(value)) {
value = SPRING_EL_FLAG + PARAM_VARIABLE_PREFIX + "0";
}
Long id = getExpression(params, value);
DecodedJWT jwt = JWT.decode(linkToken);
Long resourceId = jwt.getClaim("resourceId").asLong();
if (!id.equals(resourceId)) {
DEException.throwException("link token invalid");
return false;
}
Long uid = jwt.getClaim("uid").asLong();
String secret = xpackShareManage.queryPwd(resourceId, uid);
if (StringUtils.isBlank(secret)) {
secret = LinkTokenUtil.defaultPwd;
}
Algorithm algorithm = Algorithm.HMAC256(secret);
Verification verification = JWT.require(algorithm);
JWTVerifier verifier = verification.build();
DecodedJWT decode = JWT.decode(linkToken);
algorithm.verify(decode);
verifier.verify(linkToken);
}
try {
return point.proceed(params);
} catch (Exception e) {
LogUtil.error(e.getMessage());
throw e;
}
}
public Long getExpression(Object[] params, String expression) {
StandardEvaluationContext context = buildContext(params);
Object o = resolveValue(expression, context);
if (ObjectUtils.isNotEmpty(o)) return Long.parseLong(o.toString());
return null;
}
private StandardEvaluationContext buildContext(Object[] params) {
StandardEvaluationContext context = new StandardEvaluationContext();
if (params != null && params.length == 1) {
context.setRootObject(params[0]);
}
for (int i = 0; i < Objects.requireNonNull(params).length; i++) {
Object paramValue = params[i];
context.setVariable(PARAM_VARIABLE_PREFIX + i, paramValue);
}
return context;
}
private Object resolveValue(String exp, EvaluationContext context) {
if (StringUtils.contains(exp, SPRING_EL_FLAG)) {
Expression expression = parser.parseExpression(exp);
return expression.getValue(context);
}
return exp;
}
}

View File

@ -0,0 +1,54 @@
package io.dataease.share.interceptor;
import io.dataease.auth.DeLinkPermit;
import io.dataease.constant.AuthConstant;
import io.dataease.exception.DEException;
import io.dataease.utils.ServletUtils;
import io.dataease.utils.WhitelistUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Arrays;
import java.util.List;
@Component
public class LinkInterceptor implements HandlerInterceptor {
private final static String whiteListText = "/user/ipInfo, /apisix/check, /datasetData/enumValue, /datasetData/enumValueObj, /datasetData/getFieldTree, /dekey, /share/validate, /sysParameter/queryOnlineMap, /chartData/innerExportDetails";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String linkToken = ServletUtils.getHead(AuthConstant.LINK_TOKEN_KEY);
if (linkToken == null) {
return true;
}
if (handler instanceof HandlerMethod handlerMethod) {
DeLinkPermit deLinkPermit = handlerMethod.getMethodAnnotation(DeLinkPermit.class);
if (deLinkPermit == null) {
List<String> whiteList = Arrays.stream(StringUtils.split(whiteListText, ",")).map(String::trim).toList();
String requestURI = ServletUtils.request().getRequestURI();
if (StringUtils.startsWith(requestURI, WhitelistUtils.getContextPath())) {
requestURI = requestURI.replaceFirst(WhitelistUtils.getContextPath(), "");
}
if (StringUtils.startsWith(requestURI, AuthConstant.DE_API_PREFIX)) {
requestURI = requestURI.replaceFirst(AuthConstant.DE_API_PREFIX, "");
}
boolean valid = whiteList.contains(requestURI) || WhitelistUtils.match(requestURI);
if (!valid) {
DEException.throwException("分享链接Token不支持访问当前url[" + requestURI + "]");
}
return true;
}
}
return true;
}
}

View File

@ -64,6 +64,15 @@ public class XpackShareManage {
return xpackShareMapper.selectOne(queryWrapper);
}
public String queryPwd(Long resourceId, Long userId) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", userId);
queryWrapper.eq("resource_id", resourceId);
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare)) return null;
return xpackShare.getPwd();
}
@Transactional
public void switcher(Long resourceId) {
XpackShare originData = queryByResource(resourceId);
@ -199,7 +208,7 @@ public class XpackShareManage {
if (ObjectUtils.isEmpty(sharedBase) || !sharedBase.isPeRequire()) return true;
Long exp = share.getExp();
String pwd = share.getPwd();
return StringUtils.isNotBlank(pwd) && ObjectUtils.isNotEmpty(exp);
return StringUtils.isNotBlank(pwd) && ObjectUtils.isNotEmpty(exp) && exp > 0L;
}
public XpackShareProxyVO proxyInfo(XpackShareProxyRequest request) {

View File

@ -9,7 +9,7 @@ import org.apache.commons.lang3.StringUtils;
import java.util.Date;
public class LinkTokenUtil {
private static final String defaultPwd = "link-pwd-fit2cloud";
public static final String defaultPwd = "link-pwd-fit2cloud";
public static String generate(Long uid, Long resourceId, Long exp, String pwd, Long oid) {
pwd = StringUtils.isBlank(pwd) ? defaultPwd : pwd;
Algorithm algorithm = Algorithm.HMAC256(pwd);

View File

@ -1,6 +1,7 @@
package io.dataease.substitute.permissions.user;
import io.dataease.api.permissions.user.vo.CurIpVO;
import io.dataease.api.permissions.user.vo.UserFormVO;
import io.dataease.utils.IPUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -27,6 +28,7 @@ public class SubstituteUserServer {
result.put("language", "zh-CN");
return result;
}
@GetMapping("/personInfo")
public UserFormVO personInfo() {
UserFormVO userFormVO = new UserFormVO();
@ -38,4 +40,13 @@ public class SubstituteUserServer {
userFormVO.setModel("lose");
return userFormVO;
}
@GetMapping("/ipInfo")
public CurIpVO ipInfo() {
CurIpVO curIpVO = new CurIpVO();
curIpVO.setAccount("admin");
curIpVO.setName("管理员");
curIpVO.setIp(IPUtils.get());
return curIpVO;
}
}

View File

@ -11,6 +11,7 @@ import io.dataease.api.visualization.request.DataVisualizationBaseRequest;
import io.dataease.api.visualization.request.VisualizationAppExportRequest;
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.visualization.vo.*;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.chart.manage.ChartDataManage;
@ -142,6 +143,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
}
}
@DeLinkPermit("#p0.id")
@DeLog(id = "#p0.id", ot = LogOT.READ, stExp = "#p0.busiFlag")
@Override
@XpackInteract(value = "dataVisualizationServer", original = true)

View File

@ -7,6 +7,7 @@ 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.VisualizationViewTableVO;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.extensions.datasource.dto.DatasetTableFieldDTO;
@ -67,6 +68,7 @@ public class VisualizationLinkJumpService implements VisualizationLinkJumpApi {
return extVisualizationLinkageMapper.queryTableFieldWithViewId(viewId);
}
@DeLinkPermit
//获取仪表板的跳转信息
@Override
public VisualizationLinkJumpBaseResponse queryVisualizationJumpInfo(Long dvId) {
@ -135,6 +137,7 @@ public class VisualizationLinkJumpService implements VisualizationLinkJumpApi {
});
}
@DeLinkPermit("#p0.targetDvId")
@Override
public VisualizationLinkJumpBaseResponse queryTargetVisualizationJumpInfo(VisualizationLinkJumpBaseRequest request) {
List<VisualizationLinkJumpDTO> result = extVisualizationLinkJumpMapper.getTargetVisualizationJumpInfo(request);

View File

@ -6,6 +6,7 @@ import io.dataease.api.visualization.dto.LinkageInfoDTO;
import io.dataease.api.visualization.dto.VisualizationLinkageDTO;
import io.dataease.api.visualization.request.VisualizationLinkageRequest;
import io.dataease.api.visualization.vo.VisualizationLinkageFieldVO;
import io.dataease.auth.DeLinkPermit;
import io.dataease.chart.dao.auto.entity.CoreChartView;
import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.utils.BeanUtils;
@ -108,6 +109,7 @@ public class VisualizationLinkageService implements VisualizationLinkageApi {
return new BaseRspModel();
}
@DeLinkPermit
@Override
public Map<String, List<String>> getVisualizationAllLinkageInfo(Long dvId) {
List<LinkageInfoDTO> info = extVisualizationLinkageMapper.getPanelAllLinkageInfo(dvId);

View File

@ -2,13 +2,13 @@ package io.dataease.visualization.server;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.ibm.icu.impl.coll.CollationLoader;
import io.dataease.api.dataset.vo.CoreDatasetGroupVO;
import io.dataease.api.dataset.vo.CoreDatasetTableFieldVO;
import io.dataease.api.visualization.VisualizationOuterParamsApi;
import io.dataease.api.visualization.dto.VisualizationOuterParamsDTO;
import io.dataease.api.visualization.dto.VisualizationOuterParamsInfoDTO;
import io.dataease.api.visualization.response.VisualizationOuterParamsBaseResponse;
import io.dataease.auth.DeLinkPermit;
import io.dataease.dataset.dao.auto.entity.CoreDatasetTable;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetTableMapper;
import io.dataease.engine.constant.DeTypeConstants;
@ -29,7 +29,10 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -98,6 +101,7 @@ public class VisualizationOuterParamsService implements VisualizationOuterParams
}
@DeLinkPermit
@Override
public VisualizationOuterParamsBaseResponse getOuterParamsInfo(String visualizationId) {
List<VisualizationOuterParamsInfoDTO> result = extOuterParamsMapper.getVisualizationOuterParamsInfo(visualizationId);

View File

@ -32,6 +32,8 @@ export const queryFormApi = uid => request.get({ url: `/user/queryById/${uid}` }
export const personInfoApi = () => request.get({ url: `/user/personInfo` })
export const ipInfoApi = () => request.get({ url: `/user/ipInfo` })
export const roleCreateApi = data => request.post({ url: '/role/create', data })
export const roleEditApi = data => request.post({ url: '/role/edit', data })

View File

@ -2,7 +2,6 @@
import { useAttrs, computed } from 'vue'
import icon_visible_outlined from '@/assets/svg/icon_visible_outlined.svg'
import icon_invisible_outlined from '@/assets/svg/icon_invisible_outlined.svg'
import { hIcon } from '@/components/icon-custom'
const attrs = useAttrs()
const props = defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue'])
@ -14,15 +13,12 @@ const value = computed({
emits('update:modelValue', value)
}
})
const iconView = hIcon(icon_visible_outlined)
const iconHide = hIcon(icon_invisible_outlined)
</script>
<template>
<el-input
:icon-view-custom="iconView"
:icon-hide-custom="iconHide"
:icon-view-custom="icon_visible_outlined"
:icon-hide-custom="icon_invisible_outlined"
v-bind="attrs"
v-model="value"
/>

View File

@ -20,7 +20,7 @@
<el-checkbox
size="small"
:effect="themes"
v-model="colorForm.basicStyle.gradient"
v-model="colorForm['basicStyle']['gradient']"
@change="changeColorCase('gradient')"
>
{{ $t('chart.gradient') }}{{ $t('chart.color') }}
@ -36,7 +36,7 @@
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
v-model="colorForm.basicStyle.alpha"
v-model="colorForm['basicStyle']['alpha']"
@change="changeColorCase('alpha')"
/>
</el-form-item>
@ -50,7 +50,7 @@
<el-input
type="number"
:effect="themes"
v-model="colorForm.basicStyle.alpha"
v-model="colorForm['basicStyle']['alpha']"
:min="0"
:max="100"
class="alpha-input-number"
@ -78,7 +78,7 @@
<el-form-item :label="t('chart.table_header_bg')" class="form-item">
<el-color-picker
:trigger-width="colorPickerWidth"
v-model="colorForm.tableHeader.tableHeaderBgColor"
v-model="colorForm['tableHeader']['tableHeaderBgColor']"
size="small"
:predefine="predefineColors"
is-custom
@ -91,7 +91,7 @@
<el-form-item :label="t('chart.table_item_bg')" class="form-item">
<el-color-picker
:trigger-width="colorPickerWidth"
v-model="colorForm.tableCell.tableItemBgColor"
v-model="colorForm['tableCell']['tableItemBgColor']"
size="small"
:predefine="predefineColors"
:effect="themes"
@ -106,7 +106,7 @@
<el-form-item :label="t('chart.table_header_font_color')" class="form-item">
<el-color-picker
:trigger-width="colorPickerWidth"
v-model="colorForm.tableHeader.tableHeaderFontColor"
v-model="colorForm['tableHeader']['tableHeaderFontColor']"
:effect="themes"
size="small"
:predefine="predefineColors"
@ -119,7 +119,7 @@
<el-form-item :label="t('chart.table_item_font_color')" class="form-item">
<el-color-picker
:trigger-width="colorPickerWidth"
v-model="colorForm.tableCell.tableFontColor"
v-model="colorForm['tableCell']['tableFontColor']"
size="small"
:predefine="predefineColors"
:effect="themes"
@ -134,7 +134,7 @@
<el-form-item :label="t('chart.table_border_color')" class="form-item">
<el-color-picker
:trigger-width="colorPickerWidth"
v-model="colorForm.basicStyle.tableBorderColor"
v-model="colorForm.basicStyle['tableBorderColor']"
size="small"
:predefine="predefineColors"
:effect="themes"
@ -147,7 +147,7 @@
<el-form-item :label="t('chart.table_scroll_bar_color')" class="form-item">
<el-color-picker
:trigger-width="colorPickerWidth"
v-model="colorForm.basicStyle.tableScrollBarColor"
v-model="colorForm.basicStyle['tableScrollBarColor']"
size="small"
:predefine="predefineColors"
color-format="rgb"

View File

@ -349,7 +349,7 @@ const coordinates = ref([]) //坐标点集合
let lastTask = undefined
let isOverlay = false //
let moveTime = 200 //
let moveTime = 100 //
const itemMaxY = ref(0)
let itemMaxX = 0
@ -874,6 +874,11 @@ function removeItemById(componentId) {
removeItem(index)
}
})
if (!isMainCanvas(canvasId.value)) {
nextTick(() => {
canvasInit()
})
}
}
}
@ -1514,6 +1519,7 @@ defineExpose({
:style="editStyle"
@contextmenu="handleContextMenu"
>
<slot name="canvasDragTips" />
<drag-info v-if="dragInfoShow"></drag-info>
<canvas-opt-bar
v-if="dvInfo.type === 'dataV'"

View File

@ -81,14 +81,26 @@ const copy = () => {
}
const hide = () => {
if (curComponent.value && !isGroupArea.value) {
layerStore.hideComponentWithComponent()
} else if (areaData.value.components.length) {
areaData.value.components.forEach(component => {
layerStore.hideComponentWithComponent(component.id)
})
}
snapshotStore.recordSnapshotCache()
layerStore.hideComponent()
menuOpt('hide')
}
const show = () => {
if (curComponent.value && !isGroupArea.value) {
layerStore.showComponent()
} else if (areaData.value.components.length) {
areaData.value.components.forEach(component => {
layerStore.showComponent(component.id)
})
}
snapshotStore.recordSnapshotCache()
layerStore.showComponent()
menuOpt('show')
}
const categoryChange = type => {

View File

@ -78,6 +78,11 @@ const props = defineProps({
isSelector: {
type: Boolean,
default: false
},
//
showPopBar: {
type: Boolean,
default: false
}
})
@ -91,7 +96,8 @@ const {
previewActive,
downloadStatus,
outerScale,
outerSearchCount
outerSearchCount,
showPopBar
} = toRefs(props)
const domId = 'preview-' + canvasId.value
const scaleWidthPoint = ref(100)
@ -398,7 +404,10 @@ const dataVPreview = computed(
const linkOptBarShow = computed(() => {
return Boolean(
canvasStyleData.value.suspensionButtonAvailable && !inMobile.value && !mobileInPc.value
canvasStyleData.value.suspensionButtonAvailable &&
!inMobile.value &&
!mobileInPc.value &&
showPopBar.value
)
})

View File

@ -156,7 +156,8 @@ const {
tabCollisionActiveId,
tabMoveInActiveId,
tabMoveOutComponentId,
mobileInPc
mobileInPc,
mainScrollTop
} = storeToRefs(dvMainStore)
const { editorMap, areaData, isCtrlOrCmdDown } = storeToRefs(composeStore)
const emit = defineEmits([
@ -558,7 +559,7 @@ const handleMouseDownOnShape = e => {
mouseY:
!isDashboard() && outerTabDom
? outerTabDom.offsetTop + curDom.offsetTop + offsetY + 100
: curY,
: curY + mainScrollTop.value,
width: componentWidth,
height: componentHeight
})

View File

@ -325,7 +325,6 @@ const upload = file => {
}
const onBackgroundChange = () => {
snapshotStore.recordSnapshotCacheToMobile('commonBackground')
emits('onBackgroundChange', state.commonBackground)
}

View File

@ -2,7 +2,7 @@
import { storeToRefs } from 'pinia'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { ref } from 'vue'
import { personInfoApi } from '@/api/user'
import { ipInfoApi } from '@/api/user'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -163,7 +163,7 @@ export function activeWatermarkCheckUser(domId, canvasId, scale = 1) {
scale
)
} else {
personInfoApi().then(res => {
ipInfoApi().then(res => {
userInfo.value = res.data
if (userInfo.value && userInfo.value.model !== 'lose') {
activeWatermark(

View File

@ -53,6 +53,7 @@ const dashboardActive = computed(() => {
const onBackgroundChange = val => {
element.value.commonBackground = val
snapshotStore.recordSnapshotCacheToMobile('commonBackground')
emits('onAttrChange', { custom: 'commonBackground' })
}

View File

@ -15,7 +15,7 @@
:border-color="noBorderColor"
:border-active-color="borderActiveColor"
>
<template :key="tabItem.name" v-for="(tabItem, index) in element.propValue">
<template :key="tabItem.name" v-for="tabItem in element.propValue">
<el-tab-pane
class="el-tab-pane-custom"
:lazy="isEditMode"
@ -54,32 +54,39 @@
</el-dropdown>
</div>
</template>
<de-canvas
v-if="isEdit && !mobileInPc"
:ref="'tabCanvas_' + index"
:component-data="tabItem.componentData"
:canvas-style-data="canvasStyleData"
:canvas-view-info="canvasViewInfo"
:canvas-id="element.id + '--' + tabItem.name"
:class="moveActive ? 'canvas-move-in' : ''"
:canvas-active="editableTabsValue === tabItem.name"
></de-canvas>
<de-preview
v-else
:ref="'dashboardPreview'"
:dv-info="dvInfo"
:cur-gap="curPreviewGap"
:component-data="tabItem.componentData"
:canvas-style-data="{}"
:canvas-view-info="canvasViewInfo"
:canvas-id="element.id + '--' + tabItem.name"
:preview-active="editableTabsValue === tabItem.name"
:show-position="showPosition"
:outer-scale="scale"
:outer-search-count="searchCount"
></de-preview>
</el-tab-pane>
</template>
<div
style="position: absolute; width: 100%; height: 100%"
:key="tabItem.name + '-content'"
v-for="(tabItem, index) in element.propValue"
:class="{ 'switch-hidden': editableTabsValue !== tabItem.name }"
>
<de-canvas
v-if="isEdit && !mobileInPc"
:ref="'tabCanvas_' + index"
:component-data="tabItem.componentData"
:canvas-style-data="canvasStyleData"
:canvas-view-info="canvasViewInfo"
:canvas-id="element.id + '--' + tabItem.name"
:class="moveActive ? 'canvas-move-in' : ''"
:canvas-active="editableTabsValue === tabItem.name"
></de-canvas>
<de-preview
v-else
:ref="'dashboardPreview'"
:dv-info="dvInfo"
:cur-gap="curPreviewGap"
:component-data="tabItem.componentData"
:canvas-style-data="{}"
:canvas-view-info="canvasViewInfo"
:canvas-id="element.id + '--' + tabItem.name"
:preview-active="editableTabsValue === tabItem.name"
:show-position="showPosition"
:outer-scale="scale"
:outer-search-count="searchCount"
></de-preview>
</div>
</de-custom-tab>
<el-dialog
title="编辑标题"
@ -328,8 +335,9 @@ const componentMoveIn = component => {
component.style.left = 0
component.style.top = 0
tabItem.componentData.push(component)
refInstance.addItemBox(component) //
nextTick(() => {
refInstance.addItemBox(component) //
refInstance.canvasInitImmediately()
})
}
} else {
@ -549,7 +557,6 @@ onBeforeMount(() => {
}
.el-tab-pane-custom {
width: 100%;
height: 100%;
}
.canvas-move-in {
border: 2px dotted transparent;
@ -570,4 +577,9 @@ onBeforeMount(() => {
display: flex;
justify-content: center;
}
.switch-hidden {
opacity: 0;
z-index: -1;
}
</style>

View File

@ -2,6 +2,7 @@
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import eventBus from '@/utils/eventBus'
import { isMobile } from '@/utils/utils'
import { ElMessage } from 'element-plus-secondary'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import QueryConditionConfiguration from './QueryConditionConfiguration.vue'
@ -256,6 +257,7 @@ const releaseSelect = id => {
const queryDataForId = id => {
let requiredName = ''
let numName = ''
const emitterList = (element.value.propValue || [])
.filter(ele => ele.id === id)
.reduce((pre, next) => {
@ -288,6 +290,22 @@ const queryDataForId = id => {
requiredName = next.name
}
}
if (next.displayType === '22') {
if (
!isNaN(next.numValueEnd) &&
!isNaN(next.numValueStart) &&
next.numValueEnd < next.numValueStart
) {
numName = next.name
}
if (
[next.numValueEnd, next.numValueStart].filter(itx => ![null, undefined, ''].includes(itx))
.length === 1
) {
requiredName = next.name
}
}
const keyList = Object.entries(next.checkedFieldsMap)
.filter(ele => next.checkedFields.includes(ele[0]))
.filter(ele => !!ele[1])
@ -299,6 +317,10 @@ const queryDataForId = id => {
ElMessage.error(`${requiredName}${t('v_query.before_querying')}`)
return
}
if (!!numName) {
ElMessage.error(`${numName}${t('v_query.the_minimum_value')}`)
return
}
if (!emitterList.length) return
emitterList.forEach(ele => {
emitter.emit(`query-data-${ele}`)
@ -339,6 +361,7 @@ onBeforeUnmount(() => {
})
const updateQueryCriteria = () => {
if (dvMainStore.mobileInPc && !isMobile()) return
Array.isArray(element.value.propValue) &&
element.value.propValue.forEach(ele => {
if (ele.auto) {
@ -380,6 +403,10 @@ onMounted(() => {
emitter.on(`editQueryCriteria${element.value.id}`, editQueryCriteria)
emitter.on(`updateQueryCriteria${element.value.id}`, updateQueryCriteria)
updateQueryCriteria()
if (dvMainStore.mobileInPc && !isMobile()) {
queryData()
}
})
const dragover = () => {
@ -537,6 +564,7 @@ const boxWidth = computed(() => {
const queryData = () => {
let requiredName = ''
let numName = ''
const emitterList = (element.value.propValue || []).reduce((pre, next) => {
if (next.required) {
if (!next.defaultValueCheck) {
@ -569,6 +597,13 @@ const queryData = () => {
}
if (next.displayType === '22') {
if (
!isNaN(next.numValueEnd) &&
!isNaN(next.numValueStart) &&
next.numValueEnd < next.numValueStart
) {
numName = next.name
}
if (
[next.numValueEnd, next.numValueStart].filter(itx => ![null, undefined, ''].includes(itx))
.length === 1
@ -587,8 +622,15 @@ const queryData = () => {
ElMessage.error(`${requiredName}${t('v_query.before_querying')}`)
return
}
if (!!numName) {
ElMessage.error(`${numName}${t('v_query.the_minimum_value')}`)
return
}
if (!emitterList.length) return
dvMainStore.setFirstLoadMap([...new Set([...emitterList, ...firstLoadMap.value])])
if (!(dvMainStore.mobileInPc && !isMobile())) {
dvMainStore.setFirstLoadMap([...new Set([...emitterList, ...firstLoadMap.value])])
}
emitterList.forEach(ele => {
emitter.emit(`query-data-${ele}`)
})

View File

@ -69,7 +69,7 @@ const relativeToCurrentTypeList = computed(() => {
}
return [
{
label: t('chart.chart'),
label: t('dynamic_time.year'),
value: 'year'
},
{

View File

@ -2,6 +2,7 @@
import { toRefs, onBeforeMount, type PropType, type Ref, inject, computed, nextTick } from 'vue'
interface SelectConfig {
id: string
defaultValueCheck: boolean
defaultNumValueEnd: number
numValueEnd: number
numValueStart: number
@ -26,7 +27,8 @@ const props = defineProps({
defaultNumValueEnd: '',
defaultNumValueStart: '',
numValueEnd: '',
numValueStart: ''
numValueStart: '',
defaultValueCheck: false
}
}
},
@ -38,6 +40,11 @@ const props = defineProps({
const { config } = toRefs(props)
const setParams = () => {
if (!config.value.defaultValueCheck) {
config.value.numValueEnd = undefined
config.value.numValueStart = undefined
return
}
const { defaultNumValueEnd, defaultNumValueStart } = config.value
config.value.numValueEnd = defaultNumValueEnd
config.value.numValueStart = defaultNumValueStart

View File

@ -684,13 +684,13 @@ const setParameters = field => {
Object.values(field?.fields || {})
.flat()
.filter(ele => fieldArr.includes(ele.id) && !!ele.variableName)
.concat(curComponent.value.parameters.filter(ele => fieldArr.includes(ele.id)))
)
nextTick(() => {
if (isTimeParameter.value) {
const timeParameter = curComponent.value.parameters.find(ele => ele.deType === 1)
curComponent.value.timeGranularity =
typeTimeMap[
curComponent.value.parameters[0].type[1] || curComponent.value.parameters[0].type[0]
]
typeTimeMap[timeParameter.type[1] || timeParameter.type[0]]
curComponent.value.displayType = '1'
}
@ -1153,6 +1153,26 @@ const validate = () => {
ElMessage.error(t('v_query.be_linked_first'))
return true
}
if (ele.displayType === '22' && ele.defaultValueCheck) {
ele.numValueEnd = ele.defaultNumValueEnd
ele.numValueStart = ele.defaultNumValueStart
if (
(ele.defaultNumValueEnd !== 0 && !ele.defaultNumValueEnd) ||
(ele.defaultNumValueStart !== 0 && !ele.defaultNumValueStart)
) {
ElMessage.error(t('v_query.cannot_be_empty_de'))
return true
}
if (
!isNaN(ele.defaultNumValueEnd) &&
!isNaN(ele.defaultNumValueStart) &&
ele.defaultNumValueEnd < ele.defaultNumValueStart
) {
ElMessage.error(t('v_query.the_minimum_value'))
return true
}
}
let displayTypeField = null
let errorTips = t('v_query.cannot_be_performed')
let hasParameterTimeArrType = 0
@ -2491,7 +2511,11 @@ defineExpose({
</el-checkbox-group>
</div>
</div>
<div v-if="!!curComponent" class="condition-configuration">
<div
v-if="!!curComponent"
class="condition-configuration"
:class="curComponent.auto && 'condition-configuration_hide'"
>
<div class="mask condition" v-if="curComponent.auto"></div>
<div class="title flex-align-center">
{{ t('v_query.query_condition_configuration') }}
@ -2979,7 +3003,7 @@ defineExpose({
<el-radio-group class="larger-radio" v-model="curComponent.conditionType">
<el-radio :label="0">{{ t('v_query.single_condition') }}</el-radio>
<el-radio :label="1" :disabled="!!curComponent.parameters.length">{{
t('v_query.single_condition')
t('v_query.with_condition')
}}</el-radio>
<el-radio :label="2" :disabled="!!curComponent.parameters.length">{{
t('v_query.or_condition')
@ -3294,6 +3318,10 @@ defineExpose({
width: 467px;
position: relative;
overflow-y: auto;
&.condition-configuration_hide {
overflow: hidden;
}
.mask {
left: -1px;
width: calc(100% + 2px);

View File

@ -181,8 +181,12 @@ const getResultNum = (
numValueEnd,
numValueStart,
defaultNumValueStart,
defaultValueCheck,
firstLoad
) => {
if (firstLoad && !defaultValueCheck) {
return []
}
const valueS = firstLoad ? defaultNumValueStart : numValueStart
const valueE = firstLoad ? defaultNumValueEnd : numValueEnd
return [valueS ?? '', valueE ?? ''].filter(ele => ele !== '')
@ -347,6 +351,7 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
numValueEnd,
numValueStart,
defaultNumValueStart,
defaultValueCheck,
firstLoad
)
} else {

View File

@ -2554,6 +2554,7 @@ export default {
variable_mgm: '参数设置'
},
v_query: {
the_minimum_value: '数值区间最大值必须大于最小值',
before_querying: '查询条件是必填项请设置选项值后再进行查询',
here_or_click: '将右侧的字段拖拽到这里 点击',
add_query_condition: '添加查询条件',

View File

@ -192,6 +192,10 @@ declare interface ChartViewField {
* 字段类型
*/
deType: number
/**
* 分组类型
*/
groupType: 'q' | 'd'
}
declare interface Filter {

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { shallowRef, defineAsyncComponent } from 'vue'
import { shallowRef, defineAsyncComponent, ref, onMounted, nextTick } from 'vue'
import { propTypes } from '@/utils/propTypes'
import { useEmitt } from '@/hooks/web/useEmitt'
@ -26,6 +26,8 @@ const DashboardPanel = defineAsyncComponent(
const Preview = defineAsyncComponent(() => import('@/views/data-visualization/PreviewCanvas.vue'))
const DashboardEmpty = defineAsyncComponent(() => import('@/views/mobile/panel/DashboardEmpty.vue'))
const XpackComponent = defineAsyncComponent(() => import('@/components/plugin/src/index.vue'))
const props = defineProps({
componentName: propTypes.string.def('Iframe')
})
@ -46,8 +48,27 @@ const componentMap = {
DashboardEmpty
}
const isDataFilling = ref(false)
const dataFillingPath = ref('')
const changeCurrentComponent = val => {
currentComponent.value = componentMap[val]
isDataFilling.value = false
currentComponent.value = undefined
if (val && val.includes('DataFilling')) {
if (val === 'DataFilling') {
dataFillingPath.value = 'L21lbnUvZGF0YS9kYXRhLWZpbGxpbmcvbWFuYWdlL2luZGV4'
} else if (val === 'DataFillingEditor') {
dataFillingPath.value = 'L21lbnUvZGF0YS9kYXRhLWZpbGxpbmcvbWFuYWdlL2Zvcm0vaW5kZXg='
} else if (val === 'DataFillingHandler') {
dataFillingPath.value = 'L21lbnUvZGF0YS9kYXRhLWZpbGxpbmcvZmlsbC9UYWJQYW5lVGFibGU='
}
nextTick(() => {
currentComponent.value = XpackComponent
isDataFilling.value = true
})
} else {
currentComponent.value = componentMap[val]
}
}
useEmitt({
@ -55,8 +76,14 @@ useEmitt({
callback: changeCurrentComponent
})
currentComponent.value = componentMap[props.componentName]
//currentComponent.value = componentMap[props.componentName]
onMounted(() => {
changeCurrentComponent(props.componentName)
})
</script>
<template>
<component :is="currentComponent"></component>
<component :is="currentComponent" v-if="!isDataFilling"></component>
<template v-else>
<component :is="currentComponent" :jsname="dataFillingPath"></component>
</template>
</template>

View File

@ -75,10 +75,20 @@ const setupAll = async (
dvId: string,
pid: string,
chartId: string,
resourceId: string
resourceId: string,
dfId: string
): Promise<App<Element>> => {
const app = createApp(AppElement, { componentName: type })
app.provide('embeddedParams', { chartId, resourceId, dvId, pid, busiFlag, outerParams, suffixId })
app.provide('embeddedParams', {
chartId,
resourceId,
dfId,
dvId,
pid,
busiFlag,
outerParams,
suffixId
})
await setupI18n(app)
setupStore(app)
setupRouter(app)
@ -94,6 +104,7 @@ const setupAll = async (
embeddedStore.setDvId(dvId)
embeddedStore.setPid(pid)
embeddedStore.setResourceId(resourceId)
embeddedStore.setDfId(dfId)
const directive = await import('@/directive')
directive.installDirective(app)
const res = await import('@/store/modules/user')
@ -131,11 +142,13 @@ class DataEaseBi {
| 'Dashboard'
| 'ScreenPanel'
| 'DashboardPanel'
| 'DataFilling'
dvId: string
busiFlag: 'dashboard' | 'dataV'
outerParams: string
suffixId: string
resourceId: string
dfId: string
pid: string
chartId: string
deOptions: Options
@ -152,6 +165,7 @@ class DataEaseBi {
this.pid = options.pid
this.chartId = options.chartId
this.resourceId = options.resourceId
this.dfId = options.dfId
}
async initialize(options: Options) {
@ -167,7 +181,8 @@ class DataEaseBi {
this.dvId,
this.pid,
this.chartId,
this.resourceId
this.resourceId,
this.dfId
)
}
@ -192,6 +207,7 @@ class DataEaseBi {
this.pid = null
this.chartId = null
this.resourceId = null
this.dfId = null
this.vm = null
}
}

View File

@ -30,6 +30,7 @@ export const composeStore = defineStore('compose', {
},
editorMap: {},
isCtrlOrCmdDown: false,
isSpaceDown: false,
isShiftDown: false,
laterIndex: null, //最后点击组件的索引
editor: null
@ -43,6 +44,10 @@ export const composeStore = defineStore('compose', {
setLaterIndex(value) {
this.laterIndex = value
},
setSpaceDownStatus(value) {
this.isSpaceDown = value
console.log('====isSpaceDown=' + this.isSpaceDown)
},
setIsCtrlOrCmdDownStatus(value) {
this.isCtrlOrCmdDown = value
},

View File

@ -189,7 +189,8 @@ export const dvMainStore = defineStore('dataVisualization', {
// 基础网格信息
baseCellInfo: {},
dataPrepareState: false, //数据准备状态
multiplexingStyleAdapt: true //复用样式跟随主题
multiplexingStyleAdapt: true, //复用样式跟随主题
mainScrollTop: 0 //主画布运动量
}
},
actions: {
@ -1106,26 +1107,30 @@ export const dvMainStore = defineStore('dataVisualization', {
targetInfoList.forEach(targetInfo => {
const targetInfoArray = targetInfo.split('#')
const targetViewId = targetInfoArray[0] // 目标图表
// DE_EMPTY 为清空条件标志
if (element.component === 'UserView' && element.id === targetViewId) {
// 如果目标图表 当前循环组件id相等 则进行条件增减
const targetFieldId = targetInfoArray[1] // 目标图表列ID
const condition = {
fieldId: targetFieldId,
operator: operator,
value: paramValue,
viewIds: [targetViewId]
}
let j = currentFilters.length
while (j--) {
const filter = currentFilters[j]
// 兼容性准备 viewIds 只会存放一个值
if (targetFieldId === filter.fieldId && filter.viewIds.includes(targetViewId)) {
currentFilters.splice(j, 1)
if ('DE_EMPTY' !== paramValueStr) {
// 如果目标图表 当前循环组件id相等 则进行条件增减
const targetFieldId = targetInfoArray[1] // 目标图表列ID
const condition = {
fieldId: targetFieldId,
operator: operator,
value: paramValue,
viewIds: [targetViewId]
}
let j = currentFilters.length
while (j--) {
const filter = currentFilters[j]
// 兼容性准备 viewIds 只会存放一个值
if (targetFieldId === filter.fieldId && filter.viewIds.includes(targetViewId)) {
currentFilters.splice(j, 1)
}
}
// 不存在该条件 条件有效 直接保存该条件
// !filterExist && vValid && currentFilters.push(condition)
currentFilters.push(condition)
}
// 不存在该条件 条件有效 直接保存该条件
// !filterExist && vValid && currentFilters.push(condition)
currentFilters.push(condition)
preActiveComponentIds.push(element.id)
}
if (element.component === 'VQuery') {
@ -1144,25 +1149,33 @@ export const dvMainStore = defineStore('dataVisualization', {
// 0 文本类型 1 数字类型
if (filterItem.multiple) {
// multiple === true 多选
filterItem.selectValue = queryParams
filterItem.defaultValue = queryParams
filterItem['selectValue'] = queryParams
filterItem['defaultValue'] = queryParams
} else {
// 单选
filterItem.selectValue = queryParams[0]
filterItem.defaultValue = queryParams[0]
filterItem['selectValue'] = queryParams[0]
filterItem['defaultValue'] = queryParams[0]
}
filterItem['defaultMapValue'] = queryParams
filterItem['mapValue'] = queryParams
} else if (filterItem.displayType === '1') {
// 1 时间类型
filterItem.selectValue = queryParams[0]
filterItem.defaultValue = queryParams[0]
filterItem['selectValue'] = queryParams[0]
filterItem['defaultValue'] = queryParams[0]
} else if (filterItem.displayType === '7') {
// 7 时间范围类型
filterItem.selectValue = queryParams
filterItem.defaultValue = queryParams
filterItem['selectValue'] = queryParams
filterItem['defaultValue'] = queryParams
} else if (filterItem.displayType === '8') {
// 8 文本搜索
filterItem.conditionValueF = parmaValueSource + ''
filterItem.defaultConditionValueF = parmaValueSource + ''
filterItem['conditionValueF'] = parmaValueSource + ''
filterItem['defaultConditionValueF'] = parmaValueSource + ''
}
if ('DE_EMPTY' === paramValueStr) {
filterItem['selectValue'] = null
filterItem['defaultValue'] = null
filterItem['conditionValueF'] = null
filterItem['defaultConditionValueF'] = null
}
}
})
@ -1242,31 +1255,33 @@ export const dvMainStore = defineStore('dataVisualization', {
// 0 文本类型 1 数字类型
if (filterItem.multiple) {
// multiple === true 多选
filterItem.selectValue = queryParams
filterItem.defaultValue = queryParams
filterItem['selectValue'] = queryParams
filterItem['defaultValue'] = queryParams
} else {
// 单选
filterItem.selectValue = queryParams[0]
filterItem.defaultValue = queryParams[0]
filterItem['selectValue'] = queryParams[0]
filterItem['defaultValue'] = queryParams[0]
}
filterItem['defaultMapValue'] = queryParams
filterItem['mapValue'] = queryParams
} else if (filterItem.displayType === '1') {
// 1 时间类型
filterItem.selectValue = queryParams[0]
filterItem.defaultValue = queryParams[0]
filterItem['selectValue'] = queryParams[0]
filterItem['defaultValue'] = queryParams[0]
} else if (filterItem.displayType === '7') {
// 7 时间范围类型
if (QDItem.timeValue && Array.isArray(QDItem.timeValue)) {
// 如果dimension.timeValue存在值且是数组 目前判断为是时间组件
filterItem.selectValue = QDItem.timeValue
filterItem.defaultValue = QDItem.timeValue
filterItem['selectValue'] = QDItem.timeValue
filterItem['defaultValue'] = QDItem.timeValue
} else {
filterItem.selectValue = queryParams
filterItem.defaultValue = queryParams
filterItem['selectValue'] = queryParams
filterItem['defaultValue'] = queryParams
}
} else if (filterItem.displayType === '8') {
// 8 文本搜索
filterItem.conditionValueF = queryParams[0]
filterItem.defaultConditionValueF = queryParams[0]
filterItem['conditionValueF'] = queryParams[0]
filterItem['defaultConditionValueF'] = queryParams[0]
}
}
})
@ -1330,6 +1345,7 @@ export const dvMainStore = defineStore('dataVisualization', {
type: null,
mobileLayout: false
}
this.mainScrollTop = 0
},
setViewDataDetails(viewId, chartDataInfo) {
this.canvasViewDataInfo[viewId] = chartDataInfo.data

View File

@ -60,6 +60,13 @@ export const layerStore = defineStore('layer', {
}
},
hideComponentWithComponent(componentId?) {
const targetComponent = getComponentById(componentId)
// 隐藏
if (targetComponent) {
targetComponent.isShow = false
}
},
hideComponent(componentId?) {
const targetComponent = getComponentById(componentId)
// 隐藏

View File

@ -12,6 +12,7 @@ interface AppState {
pid: string
chartId: string
resourceId: string
dfId: string
opt: string
createType: string
templateParams: string
@ -38,6 +39,7 @@ export const userStore = defineStore('embedded', {
pid: '',
chartId: '',
resourceId: '',
dfId: '',
opt: '',
createType: '',
templateParams: '',
@ -94,6 +96,9 @@ export const userStore = defineStore('embedded', {
getResourceId(): string {
return this.resourceId
},
getDfId(): string {
return this.dfId
},
getOpt(): string {
return this.opt
},
@ -110,7 +115,8 @@ export const userStore = defineStore('embedded', {
dvId: this.dvId,
chartId: this.chartId,
pid: this.pid,
resourceId: this.resourceId
resourceId: this.resourceId,
dfId: this.dfId
}
}
},
@ -172,6 +178,9 @@ export const userStore = defineStore('embedded', {
setResourceId(resourceId: string) {
this.resourceId = resourceId
},
setDfId(dfId: string) {
this.dfId = dfId
},
setOpt(opt: string) {
this.opt = opt
},
@ -185,6 +194,7 @@ export const userStore = defineStore('embedded', {
this.chartId = data['chartId']
this.pid = data['pid']
this.resourceId = data['resourceId']
this.dfId = data['dfId']
},
async setTokenInfo(tokenInfo: Map<string, object>) {
this.tokenInfo = tokenInfo
@ -195,6 +205,7 @@ export const userStore = defineStore('embedded', {
this.setCreateType('')
this.setTemplateParams('')
this.setResourceId('')
this.setDfId('')
this.setDvId('')
this.setJumpInfoParam('')
this.setOuterUrl('')

View File

@ -38,7 +38,8 @@ const ctrlKey = 17,
dKey = 68, // 删除
deleteKey = 46, // 删除
macDeleteKey = 8, // 删除
eKey = 69 // 清空画布
eKey = 69, // 清空画布
spaceKey = 32 // 空格键
export const keycodes = [8, 37, 38, 39, 40, 66, 67, 68, 69, 71, 76, 80, 83, 85, 86, 88, 89, 90]
@ -118,6 +119,10 @@ export function listenGlobalKeyDown() {
isCtrlOrCommandDown = true
composeStore.setIsCtrlOrCmdDownStatus(true)
releaseKeyCheck('ctrl')
} else if (keyCode === spaceKey) {
composeStore.setSpaceDownStatus(true)
e.preventDefault()
e.stopPropagation()
} else if ((keyCode == deleteKey || keyCode == macDeleteKey) && curComponent.value) {
deleteComponent()
} else if (isCtrlOrCommandDown) {
@ -138,6 +143,10 @@ export function listenGlobalKeyDown() {
} else if (e.keyCode === shiftKey) {
isShiftDown = true
composeStore.setIsShiftDownStatus(false)
} else if (e.keyCode === spaceKey) {
composeStore.setSpaceDownStatus(false)
e.preventDefault()
e.stopPropagation()
}
}

View File

@ -0,0 +1,81 @@
const treeDraggble = (state, key, req, type) => {
let dragNodeParentId = ''
let dragNodeId = ''
let dragNodeIndex = 0
const dfsTreeNode = (arr, parentId) => {
arr.forEach((element, index) => {
if (element.id === dragNodeId) {
dragNodeIndex = index
dragNodeParentId = parentId
}
if (element.children?.length) {
dfsTreeNode(element.children, element.id)
}
})
}
const dfsTreeNodeBack = (arr, parentId, params) => {
arr.forEach(element => {
if (element.id === params.id) {
params.pid = parentId
}
if (element.children?.length) {
dfsTreeNodeBack(element.children, element.id, params)
}
})
}
const dfsTreeNodeReset = (arr, node) => {
arr.forEach(element => {
if (element.id === dragNodeParentId) {
element.children.splice(dragNodeIndex, 0, node)
}
if (element.children?.length) {
dfsTreeNodeReset(element.children, node)
}
})
}
const handleDragStart = node => {
dragNodeId = node.data.id
dfsTreeNode(state[key], '0')
}
const allowDrop = (_, dropNode) => {
return !dropNode.data?.leaf
}
const handleDrop = (draggingNode, dropNode, dropType) => {
const params = {
id: draggingNode.data?.id,
name: draggingNode.data?.name,
nodeType: draggingNode.data?.leaf ? type : 'folder',
pid: '0',
action: 'move'
}
if (dropType === 'inner') {
params.pid = dropNode.data?.id
} else {
dfsTreeNodeBack(state[key], '0', params)
}
req(params).catch(() => {
if (dragNodeParentId === '0') {
state[key].splice(dragNodeIndex, 0, draggingNode.data)
return
}
dfsTreeNodeReset(state[key], draggingNode.data)
})
}
return {
handleDrop,
allowDrop,
handleDragStart
}
}
export { treeDraggble }

View File

@ -0,0 +1,90 @@
import { dvNameCheck, moveResource } from '@/api/visualization/dataVisualization'
const treeDraggbleChart = (state, key, type) => {
let dragNodeParentId = ''
let dragNodeId = ''
let dragNodeIndex = 0
const dfsTreeNode = (arr, parentId) => {
arr.forEach((element, index) => {
if (element.id === dragNodeId) {
dragNodeIndex = index
dragNodeParentId = parentId
}
if (element.children?.length) {
dfsTreeNode(element.children, element.id)
}
})
}
const dfsTreeNodeBack = (arr, parentId, params) => {
arr.forEach(element => {
if (element.id === params.id) {
params.pid = parentId
}
if (element.children?.length) {
dfsTreeNodeBack(element.children, element.id, params)
}
})
}
const dfsTreeNodeReset = (arr, node) => {
arr.forEach(element => {
if (element.id === dragNodeParentId) {
element.children.splice(dragNodeIndex, 0, node)
}
if (element.children?.length) {
dfsTreeNodeReset(element.children, node)
}
})
}
const handleDragStart = node => {
dragNodeId = node.data.id
dfsTreeNode(state[key], '0')
}
const allowDrop = (_, dropNode) => {
return !dropNode.data?.leaf
}
const handleDrop = async (draggingNode, dropNode, dropType) => {
const params = {
id: draggingNode.data?.id,
name: draggingNode.data?.name,
nodeType: draggingNode.data?.leaf ? 'leaf' : 'folder',
pid: '0',
opt: 'move',
type
}
try {
await dvNameCheck(params)
} catch (error) {
console.error(error)
}
delete params.opt
if (dropType === 'inner') {
params.pid = dropNode.data?.id
} else {
dfsTreeNodeBack(state[key], '0', params)
}
moveResource(params).catch(() => {
if (dragNodeParentId === '0') {
state[key].splice(dragNodeIndex, 0, draggingNode.data)
return
}
dfsTreeNodeReset(state[key], draggingNode.data)
})
}
return {
handleDrop,
allowDrop,
handleDragStart
}
}
export { treeDraggbleChart }

View File

@ -58,7 +58,8 @@ const scaleMin = ref(100)
const state = reactive({
screenWidth: 1920,
screenHeight: 1080
screenHeight: 1080,
curScrollTop: 0
})
//
@ -133,6 +134,10 @@ const handleMouseDown = e => {
}
}
const canvasInitImmediately = () => {
cyGridster.value.canvasInit()
}
const canvasInit = (isFistLoad = true) => {
if (canvasActive.value) {
renderState.value = true
@ -218,6 +223,7 @@ const moveOutFromTab = component => {
componentData: componentData.value
})
addItemBox(component)
canvasInit()
}, 500)
}
@ -261,6 +267,12 @@ const scrollTo = y => {
})
}
const scrollCanvas = () => {
if (isMainCanvas(canvasId.value)) {
dvMainStore.mainScrollTop = canvasInner.value.scrollTop
}
}
watch(
() => canvasActive.value,
() => {
@ -272,6 +284,8 @@ watch(
defineExpose({
addItemBox,
canvasInit,
canvasInitImmediately,
getBaseMatrixSize
})
</script>
@ -292,6 +306,7 @@ defineExpose({
@drop="handleDrop"
@dragover="handleDragOver"
@mousedown="handleMouseDown"
@scroll="scrollCanvas"
>
<canvas-core
ref="cyGridster"

View File

@ -1,5 +1,12 @@
<script lang="ts" setup>
import { shallowRef, defineAsyncComponent, ref, onBeforeUnmount, onBeforeMount } from 'vue'
import {
shallowRef,
defineAsyncComponent,
ref,
onBeforeUnmount,
onBeforeMount,
nextTick
} from 'vue'
import { debounce } from 'lodash-es'
import { XpackComponent } from '@/components/plugin'
import { useEmitt } from '@/hooks/web/useEmitt'
@ -22,6 +29,8 @@ const DashboardPanel = defineAsyncComponent(
() => import('@/views/dashboard/DashboardPreviewShow.vue')
)
const AsyncXpackComponent = defineAsyncComponent(() => import('@/components/plugin/src/index.vue'))
const componentMap = {
DashboardEditor,
VisualizationEditor,
@ -47,8 +56,28 @@ onBeforeMount(() => {
onBeforeUnmount(() => {
window.removeEventListener('resize', setStyle)
})
const showComponent = ref(false)
const dataFillingPath = ref('')
const initIframe = (name: string) => {
currentComponent.value = componentMap[name || 'ViewWrapper']
showComponent.value = false
if (name && name.includes('DataFilling')) {
if (name === 'DataFilling') {
dataFillingPath.value = 'L21lbnUvZGF0YS9kYXRhLWZpbGxpbmcvbWFuYWdlL2luZGV4'
} else if (name === 'DataFillingEditor') {
dataFillingPath.value = 'L21lbnUvZGF0YS9kYXRhLWZpbGxpbmcvbWFuYWdlL2Zvcm0vaW5kZXg='
} else if (name === 'DataFillingHandler') {
dataFillingPath.value = 'L21lbnUvZGF0YS9kYXRhLWZpbGxpbmcvZmlsbC9UYWJQYW5lVGFibGU='
}
nextTick(() => {
currentComponent.value = AsyncXpackComponent
showComponent.value = true
})
} else {
currentComponent.value = componentMap[name || 'ViewWrapper']
showComponent.value = true
}
}
useEmitt({
@ -63,6 +92,6 @@ useEmitt({
@init-iframe="initIframe"
/>
<div :style="iframeStyle">
<component :is="currentComponent"></component>
<component :is="currentComponent" :jsname="dataFillingPath" v-if="showComponent"></component>
</div>
</template>

View File

@ -256,7 +256,7 @@ initParams()
<el-row class="de-collapse-style">
<el-collapse v-model="styleActiveNames" class="style-collapse">
<el-collapse-item :effect="themes" name="basicStyle" :title="t('chart.basic_style')">
<el-form label-position="top">
<el-form @keydown.stop.prevent.enter label-position="top">
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
@ -394,7 +394,7 @@ initParams()
</el-form>
</el-collapse-item>
<el-collapse-item :effect="themes" name="addition" title="查询条件">
<el-form label-position="top">
<el-form @keydown.stop.prevent.enter label-position="top">
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
@ -638,7 +638,7 @@ initParams()
</el-form>
</collapse-switch-item>
<el-collapse-item :effect="themes" name="button" :title="t('commons.button')">
<el-form label-position="top">
<el-form @keydown.stop.prevent.enter label-position="top">
<el-form-item
:effect="themes"
class="form-item"

View File

@ -234,9 +234,13 @@ const changeColorOption = (option?) => {
}
}
const resetCustomColor = () => {
state.value.basicStyleForm[seriesColorName.value] = []
changeBasicStyle(seriesColorName.value)
setupSeriesColor()
if (props.chart.type.includes('map')) {
changeColorOption()
} else {
state.value.basicStyleForm[seriesColorName.value] = []
changeBasicStyle(seriesColorName.value)
setupSeriesColor()
}
}
const switchColorCase = () => {

View File

@ -155,11 +155,12 @@ const initMapCustomRange = () => {
}
/**
* 计算自定义区间
* 最大最小值取等分区间的最大最小值
*/
const calcMapCustomRange = () => {
const customRange = getDynamicColorScale(
mapLegendDefaultRange.min,
mapLegendDefaultRange.max,
state.legendForm.miscForm.mapLegendMin,
state.legendForm.miscForm.mapLegendMax,
state.legendForm.miscForm.mapLegendNumber
)
state.legendForm.miscForm.mapLegendCustomRange = []
@ -178,9 +179,7 @@ const calcMapCustomRange = () => {
const changeLegendCustomType = (prop?) => {
const type = state.legendForm.miscForm.mapLegendRangeType
if (type === 'custom') {
state.legendForm.miscForm.mapLegendCustomRange = cloneDeep(
mapLegendCustomRangeCacheList.slice(0, state.legendForm.miscForm.mapLegendNumber + 1)
)
calcMapCustomRange()
} else {
state.legendForm.miscForm.mapLegendCustomRange = []
}

View File

@ -36,7 +36,7 @@ const props = defineProps({
}
})
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { batchOptStatus, mobileInPc } = storeToRefs(dvMainStore)
const predefineColors = COLOR_PANEL
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
@ -45,7 +45,12 @@ const emit = defineEmits(['onTooltipChange', 'onExtTooltipChange'])
const curSeriesFormatter = ref<DeepPartial<SeriesFormatter>>({})
const quotaData = ref<Axis[]>(inject('quotaData'))
const showSeriesTooltipFormatter = computed(() => {
return showProperty('seriesTooltipFormatter') && !batchOptStatus.value && props.chart.id
return (
showProperty('seriesTooltipFormatter') &&
!batchOptStatus.value &&
!mobileInPc.value &&
props.chart.id
)
})
//
@ -488,7 +493,7 @@ onMounted(() => {
</el-form-item>
</el-space>
<div v-if="showProperty('showFields') && !batchOptStatus">
<div v-if="showProperty('showFields') && !batchOptStatus && !mobileInPc">
<el-form-item :label="t('chart.tooltip')" class="form-item" :class="'form-item-' + themes">
<el-select
size="small"

View File

@ -4,6 +4,7 @@ import icon_italic_outlined from '@/assets/svg/icon_italic_outlined.svg'
import icon_leftAlignment_outlined from '@/assets/svg/icon_left-alignment_outlined.svg'
import icon_centerAlignment_outlined from '@/assets/svg/icon_center-alignment_outlined.svg'
import icon_rightAlignment_outlined from '@/assets/svg/icon_right-alignment_outlined.svg'
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL, DEFAULT_TABLE_CELL } from '@/views/chart/components/editor/util/chart'
@ -321,6 +322,7 @@ onMounted(() => {
<el-checkbox
size="small"
:effect="themes"
:disabled="showProperty('mergeCells') && state.tableCellForm.mergeCells"
v-model="state.tableCellForm.tableFreeze"
@change="changeTableCell('tableFreeze')"
>
@ -339,7 +341,10 @@ onMounted(() => {
:effect="themes"
controls-position="right"
v-model="state.tableCellForm.tableColumnFreezeHead"
:disabled="!state.tableCellForm.tableFreeze"
:disabled="
(showProperty('mergeCells') && state.tableCellForm.mergeCells) ||
!state.tableCellForm.tableFreeze
"
:min="0"
:max="100"
@change="changeTableCell('tableColumnFreezeHead')"
@ -357,7 +362,10 @@ onMounted(() => {
:effect="themes"
controls-position="right"
v-model="state.tableCellForm.tableRowFreezeHead"
:disabled="!state.tableCellForm.tableFreeze"
:disabled="
(showProperty('mergeCells') && state.tableCellForm.mergeCells) ||
!state.tableCellForm.tableFreeze
"
:min="0"
:max="100"
@change="changeTableCell('tableRowFreezeHead')"
@ -376,7 +384,17 @@ onMounted(() => {
v-model="state.tableCellForm.mergeCells"
@change="changeTableCell('mergeCells')"
>
{{ t('chart.merge_cells') }}
<span class="data-area-label">
<span style="margin-right: 4px">{{ t('chart.merge_cells') }}</span>
<el-tooltip class="item" effect="dark" placement="bottom">
<template #content>
<div>合并单元格后行列冻结会失效</div>
</template>
<el-icon class="hint-icon" :class="{ 'hint-icon--dark': themes === 'dark' }">
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
</el-icon>
</el-tooltip>
</span>
</el-checkbox>
</el-form-item>
<el-form-item
@ -482,4 +500,12 @@ onMounted(() => {
.mobile-style {
margin-top: 25px;
}
.data-area-label {
text-align: left;
position: relative;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
}
</style>

View File

@ -74,7 +74,7 @@ import {
const embeddedStore = useEmbedded()
const snapshotStore = snapshotStoreWithOut()
const dvMainStore = dvMainStoreWithOut()
const { canvasCollapse, curComponent, componentData, editMode, mobileInPc } =
const { canvasCollapse, curComponent, componentData, editMode, mobileInPc, fullscreenFlag } =
storeToRefs(dvMainStore)
const router = useRouter()
let componentNameEdit = ref(false)
@ -3316,7 +3316,7 @@ const deleteChartFieldItem = id => {
</el-row>
<div
ref="elDrag"
v-loading="fieldLoading"
v-loading="fieldLoading && !fullscreenFlag"
style="height: calc(100% - 137px); min-height: 120px"
>
<div

View File

@ -116,7 +116,7 @@ export const DEFAULT_COLOR_CASE_DARK: DeepPartial<ChartAttr> = {
alpha: 100,
gradient: false,
mapStyle: 'darkblue',
areaBaseColor: '5470C6',
areaBaseColor: '#5470C6',
areaBorderColor: '#EBEEF5',
gaugeStyle: 'default',
tableBorderColor: '#CCCCCC',

View File

@ -19,9 +19,12 @@ import {
BAR_EDITOR_PROPERTY_INNER
} from '@/views/chart/components/js/panel/charts/bar/common'
import {
configPlotTooltipEvent,
getLabel,
getPadding,
setGradientColor
getTooltipContainer,
setGradientColor,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { useI18n } from '@/hooks/web/useI18n'
import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart'
@ -82,6 +85,7 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
newChart = new ColumnClass(container, options)
newChart.on('interval:click', action)
extremumEvt(newChart, chart, options, container)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -308,7 +312,10 @@ export class StackBar extends Bar {
const res = valueFormatter(param.value, tooltipAttr.tooltipFormatter)
obj.value = res ?? ''
return obj
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,
@ -522,7 +529,10 @@ export class GroupStackBar extends StackBar {
const obj = { name: `${param.category} - ${param.group}`, value: param.value }
obj.value = valueFormatter(param.value, tooltipAttr.tooltipFormatter)
return obj
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,
@ -605,7 +615,10 @@ export class PercentageStackBar extends GroupStackBar {
const obj = { name: param.category, value: param.value }
obj.value = (Math.round(param.value * 10000) / 100).toFixed(l.reserveDecimalCount) + '%'
return obj
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -4,10 +4,13 @@ import {
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import { cloneDeep, defaultTo, isEmpty, map } from 'lodash-es'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
getYAxis,
getYAxisExt,
setGradientColor
setGradientColor,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import type {
BidirectionalBar as G2BidirectionalBar,
@ -171,7 +174,7 @@ export class BidirectionalHorizontalBar extends G2PlotChartView<
...sourceData[0]
}
})
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -299,7 +302,10 @@ export class BidirectionalHorizontalBar extends G2PlotChartView<
})
}
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -3,7 +3,13 @@ import {
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import type { Bar, BarOptions } from '@antv/g2plot/esm/plots/bar'
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
setGradientColor,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { cloneDeep } from 'lodash-es'
import {
flow,
@ -93,7 +99,7 @@ export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
const newChart = new Bar(container, options)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -313,7 +319,10 @@ export class HorizontalStackBar extends HorizontalBar {
const res = valueFormatter(param.value, tooltipAttr.tooltipFormatter)
obj.value = res ?? ''
return obj
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,
@ -423,7 +432,10 @@ export class HorizontalPercentageStackBar extends HorizontalStackBar {
const obj = { name: param.category, value: param.value }
obj.value = (Math.round(param.value * 10000) / 100).toFixed(l.reserveDecimalCount) + '%'
return obj
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -1,6 +1,11 @@
import { G2PlotChartView, G2PlotDrawOptions } from '../../types/impl/g2plot'
import { flow, hexColorToRGBA, parseJson } from '../../../util'
import { setGradientColor } from '../../common/common_antv'
import {
configPlotTooltipEvent,
getTooltipContainer,
setGradientColor,
TOOLTIP_TPL
} from '../../common/common_antv'
import { useI18n } from '@/hooks/web/useI18n'
import type { Bar as G2Progress, BarOptions } from '@antv/g2plot/esm/plots/bar'
import {
@ -134,7 +139,7 @@ export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
const newChart = new G2Progress(container, options)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
protected configBasicStyle(chart: Chart, options: BarOptions): BarOptions {
@ -224,7 +229,10 @@ export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
}
})
return result.length == 0 ? originalItems : result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
}
}

View File

@ -3,7 +3,13 @@ import {
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import type { Bar, BarOptions } from '@antv/g2plot/esm/plots/bar'
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
setGradientColor,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { cloneDeep, find } from 'lodash-es'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { valueFormatter } from '@/views/chart/components/js/formatter'
@ -161,7 +167,7 @@ export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
const newChart = new BarClass(container, options)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -232,7 +238,10 @@ export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
}
}
return { value: res, values: param.values, name: param.field }
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
} else {
tooltip = false

View File

@ -2,7 +2,14 @@ import type { WaterfallOptions, Waterfall as G2Waterfall } from '@antv/g2plot/es
import { G2PlotChartView, G2PlotDrawOptions } from '../../types/impl/g2plot'
import { flow, hexColorToRGBA, parseJson } from '../../../util'
import { valueFormatter } from '../../../formatter'
import { getPadding, getTooltipSeriesTotalMap, setGradientColor } from '../../common/common_antv'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
getTooltipSeriesTotalMap,
setGradientColor,
TOOLTIP_TPL
} from '../../common/common_antv'
import { isEmpty } from 'lodash-es'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
@ -92,6 +99,7 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
const { Waterfall: G2Waterfall } = await import('@antv/g2plot/esm/plots/waterfall')
const newChart = new G2Waterfall(container, options)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -224,7 +232,10 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -3,7 +3,13 @@ import {
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import type { Area as G2Area, AreaOptions } from '@antv/g2plot/esm/plots/area'
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
setGradientColor,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { cloneDeep } from 'lodash-es'
import {
flow,
@ -116,6 +122,7 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
newChart.on('point:click', action)
extremumEvt(newChart, chart, options, container)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -352,7 +359,10 @@ export class StackArea extends Area {
value: valueFormatter(param.value, tooltipAttr.tooltipFormatter)
}
return obj
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return { ...options, tooltip }
}

View File

@ -3,7 +3,12 @@ import {
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import type { Line as G2Line, LineOptions } from '@antv/g2plot/esm/plots/line'
import { getPadding } from '../../common/common_antv'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
TOOLTIP_TPL
} from '../../common/common_antv'
import {
flow,
hexColorToRGBA,
@ -116,6 +121,7 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
newChart.on('point:click', action)
extremumEvt(newChart, chart, options, container)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -279,7 +285,10 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -610,6 +610,7 @@ export class StockLine extends G2PlotChartView<MixOptions, Mix> {
label
}
: {
...yAxisOptions['yAxis'],
label,
grid: null,
line: null

View File

@ -79,6 +79,11 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
async drawChart(drawOption: L7DrawConfig<L7Config>) {
const { chart, container } = drawOption
const containerDom = document.getElementById(container)
const rect = containerDom?.getBoundingClientRect()
if (rect?.height <= 0) {
return new L7Wrapper(drawOption.chartObj?.getScene(), [])
}
const xAxis = deepCopy(chart.xAxis)
const xAxisExt = deepCopy(chart.xAxisExt)
const { basicStyle, misc } = deepCopy(parseJson(chart.customAttr))

View File

@ -56,6 +56,11 @@ export class HeatMap extends L7ChartView<Scene, L7Config> {
async drawChart(drawOption: L7DrawConfig<L7Config>) {
const { chart, container } = drawOption
const containerDom = document.getElementById(container)
const rect = containerDom?.getBoundingClientRect()
if (rect?.height <= 0) {
return new L7Wrapper(drawOption.chartObj?.getScene(), [])
}
const xAxis = deepCopy(chart.xAxis)
const yAxis = deepCopy(chart.yAxis)
let basicStyle: DeepPartial<ChartBasicStyle>

View File

@ -10,7 +10,8 @@ import {
getGeoJsonFile,
hexColorToRGBA,
parseJson,
getMaxAndMinValueByData
getMaxAndMinValueByData,
filterEmptyMinValue
} from '@/views/chart/components/js/util'
import {
handleGeoJson,
@ -102,7 +103,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
from: 'map',
data: {
max: maxValue,
min: minValue,
min: minValue ?? filterEmptyMinValue(sourceData, 'value'),
legendNumber: legendNumber
}
})
@ -252,7 +253,10 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
if (colorScale.length) {
options.color['value'] = colorScale.map(item => (item.color ? item.color : item))
if (colorScale[0].value && !misc.mapAutoLegend) {
options.color['scale']['domain'] = [minValue, maxValue]
options.color['scale']['domain'] = [
minValue ?? filterEmptyMinValue(sourceData, 'value'),
maxValue
]
}
}
return options
@ -272,6 +276,33 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
if (!legend.show) {
return options
}
// 内部函数 创建自定义图例的内容
const createLegendCustomContent = showItems => {
const containerDom = createDom(CONTAINER_TPL) as HTMLElement
const listDom = containerDom.getElementsByClassName(LIST_CLASS)[0] as HTMLElement
showItems.forEach(item => {
let value = '-'
if (item.value !== '') {
if (Array.isArray(item.value)) {
item.value.forEach((v, i) => {
item.value[i] = Number.isNaN(v) || v === 'NaN' ? 'NaN' : parseFloat(v).toFixed(0)
})
value = item.value.join('-')
} else {
const tmp = item.value as string
value = Number.isNaN(tmp) || tmp === 'NaN' ? 'NaN' : parseFloat(tmp).toFixed(0)
}
}
const substituteObj = { ...item, value }
const domStr = substitute(ITEM_TPL, substituteObj)
const itemDom = createDom(domStr)
// legend 形状用的
itemDom.style.setProperty('--bgColor', item.color)
listDom.appendChild(itemDom)
})
return listDom
}
const LEGEND_SHAPE_STYLE_MAP = {
circle: {
borderRadius: '50%'
@ -299,7 +330,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
...LEGEND_SHAPE_STYLE_MAP[legend.icon],
width: '9px',
height: '9px',
border: '0.01px solid #f4f4f4'
...(legend.icon === 'triangle' ? {} : { border: '0.01px solid #f4f4f4' })
}
}
}
@ -324,61 +355,17 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
color: rangeColor
})
})
customLegend['items'] = items
const findColorByValue = (value, intervals) => {
if (value) {
for (const interval of intervals) {
if (value >= interval.value[0] && value <= interval.value[1]) {
return interval.color
}
}
}
// 或者可以返回 undefined
return null
}
options.color.value = t => {
const c = findColorByValue(t.value, items)
return c ? c : null
}
customLegend['domStyles'] = {
...customLegend['domStyles'],
'l7plot-legend l7plot-legend__category': {
'box-shadow': '0px 0px 0px 0px',
'background-color': 'var(--bgColor)',
padding: 0
},
'l7plot-legend__list-item': {
'margin-bottom': '3px'
customLegend['customContent'] = (_: string, _items: CategoryLegendListItem[]) => {
if (items?.length) {
return createLegendCustomContent(items)
}
return ''
}
} else {
customLegend['customContent'] = (_: string, items: CategoryLegendListItem[]) => {
const showItems = items?.length > 30 ? items.slice(0, 30) : items
if (showItems?.length) {
const containerDom = createDom(CONTAINER_TPL) as HTMLElement
const listDom = containerDom.getElementsByClassName(LIST_CLASS)[0] as HTMLElement
showItems.forEach(item => {
let value = '-'
if (item.value !== '') {
if (Array.isArray(item.value)) {
item.value.forEach((v, i) => {
item.value[i] = Number.isNaN(v) || v === 'NaN' ? 'NaN' : parseFloat(v).toFixed(0)
})
value = item.value.join('-')
} else {
const tmp = item.value as string
value = Number.isNaN(tmp) || tmp === 'NaN' ? 'NaN' : parseFloat(tmp).toFixed(0)
}
}
const substituteObj = { ...item, value }
const domStr = substitute(ITEM_TPL, substituteObj)
const itemDom = createDom(domStr)
// legend 形状用的
itemDom.style.setProperty('--bgColor', item.color)
listDom.appendChild(itemDom)
})
return listDom
return createLegendCustomContent(showItems)
}
return ''
}

View File

@ -81,6 +81,11 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
async drawChart(drawOption: L7DrawConfig<L7Config>) {
const { chart, container, action } = drawOption
const containerDom = document.getElementById(container)
const rect = containerDom?.getBoundingClientRect()
if (rect?.height <= 0) {
return new L7Wrapper(drawOption.chartObj?.getScene(), [])
}
const xAxis = deepCopy(chart.xAxis)
let basicStyle
let miscStyle
@ -330,6 +335,15 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
return resultMap
}
/**
* 清除 popup
* @param container
*/
clearPopup = container => {
const containerElement = document.getElementById(container)
containerElement?.querySelectorAll('.l7-popup').forEach((element: Element) => element.remove())
}
/**
* 构建 tooltip
* @param chart
@ -337,6 +351,7 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
*/
buildTooltip = (chart, container, pointLayer) => {
const customAttr = chart.customAttr ? parseJson(chart.customAttr) : null
this.clearPopup(container)
if (customAttr?.tooltip?.show) {
const { tooltip } = deepCopy(customAttr)
let showFields = tooltip.showFields || []

View File

@ -12,11 +12,7 @@ export const configCarouselTooltip = (chart, view, data, scene) => {
?.filter(i => i.dimensionList?.length > 0)
.reduce((acc, current) => {
const existingItem = acc.find(obj => {
if (!obj.abbrev || obj.abbrev === 'China') {
return obj.adcode === current.adcode
} else {
return obj.abbrev === current.abbrev
}
return obj.name === current.name || (obj.adcode && obj.adcode === current.adcode)
})
if (!existingItem) {
acc.push(current)

View File

@ -4,12 +4,15 @@ import {
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import type { DualAxes, DualAxesOptions } from '@antv/g2plot/esm/plots/dual-axes'
import {
configPlotTooltipEvent,
getAnalyse,
getLabel,
getPadding,
getTooltipContainer,
getYAxis,
getYAxisExt,
setGradientColor
setGradientColor,
TOOLTIP_TPL
} from '../../common/common_antv'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { cloneDeep, isEmpty, defaultTo, map, filter, union, defaultsDeep } from 'lodash-es'
@ -158,7 +161,7 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
newChart.on('point:click', action)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -544,7 +547,10 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -1,7 +1,7 @@
import type { FunnelOptions, Funnel as G2Funnel } from '@antv/g2plot/esm/plots/funnel'
import { G2PlotChartView, G2PlotDrawOptions } from '../../types/impl/g2plot'
import { flow, parseJson, setUpSingleDimensionSeriesColor } from '@/views/chart/components/js/util'
import { getPadding } from '../../common/common_antv'
import { configPlotTooltipEvent, getPadding } from '../../common/common_antv'
import { useI18n } from '@/hooks/web/useI18n'
import { Datum } from '@antv/g2plot/esm/types/common'
import { valueFormatter } from '@/views/chart/components/js/formatter'
@ -110,6 +110,7 @@ export class Funnel extends G2PlotChartView<FunnelOptions, G2Funnel> {
const { Funnel: G2Funnel } = await import('@antv/g2plot/esm/plots/funnel')
const newChart = new G2Funnel(container, options)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}

View File

@ -8,6 +8,7 @@ import { valueFormatter } from '@/views/chart/components/js/formatter'
import { useI18n } from '@/hooks/web/useI18n'
import { isEmpty, map } from 'lodash-es'
import { cloneDeep, defaultTo } from 'lodash-es'
import { configPlotTooltipEvent, getTooltipContainer, TOOLTIP_TPL } from '../../common/common_antv'
const { t } = useI18n()
/**
@ -209,6 +210,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
newChart.on('point:click', action)
newChart.on('click', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
newChart.on('afterrender', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -379,7 +381,10 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
})
}
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -1,7 +1,7 @@
import type { RadarOptions, Radar as G2Radar } from '@antv/g2plot/esm/plots/radar'
import { G2PlotChartView, G2PlotDrawOptions } from '../../types/impl/g2plot'
import { flow, parseJson } from '../../../util'
import { getPadding } from '../../common/common_antv'
import { configPlotTooltipEvent, getPadding } from '../../common/common_antv'
import { valueFormatter } from '../../../formatter'
import type { Datum } from '@antv/g2plot/esm/types/common'
import { useI18n } from '@/hooks/web/useI18n'
@ -117,6 +117,7 @@ export class Radar extends G2PlotChartView<RadarOptions, G2Radar> {
const { Radar: G2Radar } = await import('@antv/g2plot/esm/plots/radar')
const newChart = new G2Radar(container, options)
newChart.on('point:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}

View File

@ -5,7 +5,12 @@ import {
import type { ScatterOptions, Scatter as G2Scatter } from '@antv/g2plot/esm/plots/scatter'
import { flow, parseJson } from '../../../util'
import { valueFormatter } from '../../../formatter'
import { getPadding } from '../../common/common_antv'
import {
configPlotTooltipEvent,
getPadding,
getTooltipContainer,
TOOLTIP_TPL
} from '../../common/common_antv'
import { useI18n } from '@/hooks/web/useI18n'
import { isEmpty } from 'lodash-es'
@ -133,6 +138,7 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
const { Scatter: G2Scatter } = await import('@antv/g2plot/esm/plots/scatter')
const newChart = new G2Scatter(container, options)
newChart.on('point:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -233,7 +239,10 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -10,8 +10,11 @@ import {
setUpSingleDimensionSeriesColor
} from '@/views/chart/components/js/util'
import {
configPlotTooltipEvent,
getPadding,
getTooltipSeriesTotalMap
getTooltipContainer,
getTooltipSeriesTotalMap,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import {
@ -118,6 +121,7 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
const { Pie: G2Pie } = await import('@antv/g2plot/esm/plots/pie')
const newChart = new G2Pie(container, options)
newChart.on('interval:click', action)
configPlotTooltipEvent(chart, newChart)
return newChart
}
@ -237,7 +241,10 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -10,8 +10,11 @@ import {
PIE_EDITOR_PROPERTY_INNER
} from './common'
import {
configPlotTooltipEvent,
getPadding,
getTooltipSeriesTotalMap
getTooltipContainer,
getTooltipSeriesTotalMap,
TOOLTIP_TPL
} from '@/views/chart/components/js/panel/common/common_antv'
import { parseJson, flow, setUpSingleDimensionSeriesColor } from '@/views/chart/components/js/util'
import { Label } from '@antv/g2plot/lib/types/label'
@ -94,7 +97,7 @@ export class Rose extends G2PlotChartView<RoseOptions, G2Rose> {
const plot = new G2Rose(container, options)
plot.on('interval:click', action)
configPlotTooltipEvent(chart, plot)
return plot
}
@ -213,7 +216,10 @@ export class Rose extends G2PlotChartView<RoseOptions, G2Rose> {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return {
...options,

View File

@ -210,7 +210,7 @@ export class TableInfo extends S2ChartView<TableSheet> {
// tooltip
this.configTooltip(chart, s2Options)
// 合并单元格
this.configMergeCells(chart, s2Options)
this.configMergeCells(chart, s2Options, s2DataConfig)
// 隐藏表头保留顶部的分割线, 禁用表头横向 resize
if (tableHeader.showTableHeader === false) {
s2Options.style.colCfg.height = 1

View File

@ -353,7 +353,10 @@ export class TablePivot extends S2ChartView<PivotSheet> {
if (!isAlphaColor(tableHeaderBgColor)) {
tableHeaderBgColor = hexColorToRGBA(tableHeaderBgColor, basicStyle.alpha)
}
const tableBorderColor = hexColorToRGBA(basicStyle.tableBorderColor, basicStyle.alpha)
let tableBorderColor = basicStyle.tableBorderColor
if (!isAlphaColor(tableBorderColor)) {
tableBorderColor = hexColorToRGBA(tableBorderColor, basicStyle.alpha)
}
const tableHeaderFontColor = hexColorToRGBA(tableHeader.tableHeaderFontColor, basicStyle.alpha)
const fontStyle = tableHeader.isItalic ? 'italic' : 'normal'
const fontWeight = tableHeader.isBolder === false ? 'normal' : 'bold'

View File

@ -30,6 +30,8 @@ import { Scene } from '@antv/l7-scene'
import { type IZoomControlOption } from '@antv/l7-component'
import { PositionType } from '@antv/l7-core'
import { centroid } from '@turf/centroid'
import type { Plot } from '@antv/g2plot'
import type { PickOptions } from '@antv/g2plot/lib/core/plot'
export function getPadding(chart: Chart): number[] {
if (chart.drill) {
@ -124,7 +126,9 @@ export function getTheme(chart: Chart) {
color: tooltipColor,
fontSize: tooltipFontsize + 'px',
background: tooltipBackgroundColor,
boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.1)'
boxShadow: '0 4px 8px 0 rgba(0, 0, 0, 0.1)',
'z-index': 3000,
position: 'fixed'
}
}
},
@ -164,7 +168,8 @@ export function getLabel(chart: Chart) {
'pie-donut',
'radar',
'waterfall',
't-heatmap'
't-heatmap',
'bar'
].includes(chart.type)
) {
layout.push({ type: 'hide-overlap' })
@ -206,7 +211,10 @@ export function getTooltip(chart: Chart) {
formatter: function (param: Datum) {
const value = valueFormatter(param.value, t.tooltipFormatter)
return { name: param.field, value }
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
} else {
tooltip = false
@ -257,7 +265,10 @@ export function getMultiSeriesTooltip(chart: Chart) {
}
})
return result
}
},
container: getTooltipContainer(`tooltip-${chart.id}`),
itemTpl: TOOLTIP_TPL,
enterable: true
}
return tooltip
}
@ -1195,7 +1206,10 @@ function shouldHideZoom(basicStyle: any): boolean {
* @param basicStyle
*/
function getCenter(basicStyle: any): [number, number] {
let center = [DEFAULT_BASIC_STYLE.mapCenter.longitude, DEFAULT_BASIC_STYLE.mapCenter.latitude]
let center: [number, number] = [
DEFAULT_BASIC_STYLE.mapCenter.longitude,
DEFAULT_BASIC_STYLE.mapCenter.latitude
]
if (basicStyle.autoFit === false) {
center = [basicStyle.mapCenter.longitude, basicStyle.mapCenter.latitude]
}
@ -1210,3 +1224,107 @@ function getCenter(basicStyle: any): [number, number] {
function addCustomZoom(plotScene: Scene, newZoomOptions: any): void {
plotScene.addControl(new CustomZoom(newZoomOptions))
}
const G2_TOOLTIP_WRAPPER = 'g2-tooltip-wrapper'
export function getTooltipContainer(id) {
let wrapperDom = document.getElementById(G2_TOOLTIP_WRAPPER)
if (!wrapperDom) {
wrapperDom = document.createElement('div')
wrapperDom.id = G2_TOOLTIP_WRAPPER
document.body.appendChild(wrapperDom)
}
const curDom = document.getElementById(id)
if (curDom) {
curDom.remove()
}
const g2Tooltip = document.createElement('div')
g2Tooltip.setAttribute('id', id)
g2Tooltip.classList.add('g2-tooltip')
// 最多半屏鼠标移入可滚动
g2Tooltip.style.maxHeight = '50%'
g2Tooltip.style.overflowY = 'auto'
g2Tooltip.style.display = 'none'
g2Tooltip.style.position = 'fixed'
g2Tooltip.style.left = '0px'
g2Tooltip.style.top = '0px'
const g2TooltipTitle = document.createElement('div')
g2TooltipTitle.classList.add('g2-tooltip-title')
g2Tooltip.appendChild(g2TooltipTitle)
const g2TooltipList = document.createElement('ul')
g2TooltipList.classList.add('g2-tooltip-list')
g2Tooltip.appendChild(g2TooltipList)
const full = document.getElementsByClassName('fullscreen')
if (full.length) {
full.item(0).appendChild(g2Tooltip)
} else {
wrapperDom.appendChild(g2Tooltip)
}
return g2Tooltip
}
export function configPlotTooltipEvent<O extends PickOptions, P extends Plot<O>>(
chart: Chart,
plot: P
) {
const { tooltip } = parseJson(chart.customAttr)
if (!tooltip.show) {
return
}
// 鼠标可移入, 移入之后保持显示, 移出之后隐藏
plot.options.tooltip.container.addEventListener('mouseenter', e => {
e.target.style.visibility = 'visible'
e.target.style.display = 'block'
})
plot.options.tooltip.container.addEventListener('mouseleave', e => {
e.target.style.visibility = 'hidden'
e.target.style.display = 'none'
})
// 手动处理 tooltip 的显示和隐藏事件需配合源码理解
// https://github.com/antvis/G2/blob/master/src/chart/controller/tooltip.ts#showTooltip
plot.on('tooltip:show', () => {
const tooltipCtl = plot.chart.getController('tooltip')
if (!tooltipCtl) {
return
}
const event = plot.chart.interactions.tooltip?.context?.event
if (tooltipCtl.tooltip) {
// 处理视图放大后再关闭 tooltip dom 被清除
const container = tooltipCtl.tooltip.cfg.container
container.style.display = 'block'
const dom = document.getElementById(container.id)
if (!dom) {
const full = document.getElementsByClassName('fullscreen')
if (full.length) {
full.item(0).appendChild(container)
} else {
const wrapperDom = document.getElementById(G2_TOOLTIP_WRAPPER)
wrapperDom.appendChild(container)
}
}
}
plot.chart.getOptions().tooltip.follow = false
tooltipCtl.title = Math.random().toString()
plot.chart.getTheme().components.tooltip.x = event.clientX
plot.chart.getTheme().components.tooltip.y = event.clientY
})
// https://github.com/antvis/G2/blob/master/src/chart/controller/tooltip.ts#hideTooltip
plot.on('plot:mouseleave', () => {
const tooltipCtl = plot.chart.getController('tooltip')
if (!tooltipCtl) {
return
}
plot.chart.getOptions().tooltip.follow = true
const container = tooltipCtl.tooltip?.cfg?.container
if (container) {
container.style.display = 'none'
}
tooltipCtl.hideTooltip()
})
}
export const TOOLTIP_TPL =
'<li class="g2-tooltip-list-item" data-index={index}>' +
'<span class="g2-tooltip-marker" style="background:{color}"></span>' +
'<span class="g2-tooltip-name">{name}</span>:' +
'<span class="g2-tooltip-value">{value}</span>' +
'</li>'

View File

@ -564,7 +564,7 @@ export function getConditions(chart: Chart) {
if (conditions?.length > 0) {
const { tableCell, basicStyle, tableHeader } = parseJson(chart.customAttr)
const enableTableCrossBG = tableCell.enableTableCrossBG
const valueColor = tableCell.tableFontColor
const valueColor = isAlphaColor(tableCell.tableFontColor) ? tableCell.tableFontColor : hexColorToRGBA(tableCell.tableFontColor, basicStyle.alpha)
const valueBgColor = enableTableCrossBG
? null
: isAlphaColor(tableCell.tableItemBgColor)
@ -629,7 +629,7 @@ export function getConditions(chart: Chart) {
}
export function mappingColor(value, defaultColor, field, type, filedValueMap?, rowData?) {
let color
let color = null
for (let i = 0; i < field.conditions.length; i++) {
let flag = false
const t = field.conditions[i]
@ -1426,29 +1426,35 @@ export async function exportPivotExcel(instance: PivotSheet, chart: ChartObj) {
}
}
export function configMergeCells(chart: Chart, options: S2Options) {
export function configMergeCells(chart: Chart, options: S2Options, dataConfig: S2DataConfig) {
const { mergeCells } = parseJson(chart.customAttr).tableCell
const { showIndex } = parseJson(chart.customAttr).tableHeader
if (mergeCells) {
const xAxis = chart.xAxis
const quotaIndex = xAxis.findIndex(axis => axis.groupType === 'q')
options.frozenColCount = 0
options.frozenRowCount = 0
const fields = chart.data.fields || []
const fieldsMap = fields.reduce((p, n) => {
p[n.dataeaseName] = n
return p
}, {}) || {}
const quotaIndex = dataConfig.meta.findIndex(m => fieldsMap[m.field].groupType === 'q')
const data = chart.data?.tableRow
if (quotaIndex === 0 || !data?.length) {
return
}
const mergedColInfo: number[][][] = [[[0, data.length - 1]]]
const mergedCellsInfo = []
const axisToMerge = xAxis.filter((a, i) => a.hide !== true && (i < quotaIndex || quotaIndex === -1))
const axisToMerge = dataConfig.meta.filter((_, i) => i < quotaIndex || quotaIndex === -1)
axisToMerge.forEach((a, i) => {
const preMergedColInfo = mergedColInfo[i]
const curMergedColInfo = []
mergedColInfo.push(curMergedColInfo)
preMergedColInfo.forEach(range => {
const [start, end] = range
let lastVal = data[start][a.dataeaseName]
let lastVal = data[start][a.field]
let lastIndex = start
for (let index = start; index <= end; index++) {
const curVal = data[index][a.dataeaseName]
const curVal = data[index][a.field]
if (curVal !== lastVal || index === end) {
const curRange = index - lastIndex
if (curRange > 1 ||
@ -1499,7 +1505,10 @@ export function configMergeCells(chart: Chart, options: S2Options) {
}
export function getRowIndex(mergedCellsInfo: MergedCellInfo[][], meta: ViewMeta): number {
let curRangeStartIndex = 0
if (!mergedCellsInfo?.length) {
return meta.rowIndex + 1
}
let curRangeStartIndex = meta.rowIndex
const lostCells = mergedCellsInfo.reduce((p, n) => {
if (n[0].colIndex !== 0) {
return p

View File

@ -64,8 +64,8 @@ export abstract class S2ChartView<P extends SpreadSheet> extends AntVAbstractCha
return getConditions(chart)
}
protected configMergeCells(chart: Chart, option: S2Options) {
configMergeCells(chart, option)
protected configMergeCells(chart: Chart, option: S2Options, dataConfig: S2DataConfig) {
configMergeCells(chart, option, dataConfig)
}
protected showTooltip(s2Instance: P, event, metaConfig: Meta[]) {

View File

@ -1048,3 +1048,23 @@ export function svgStrToUrl(svgStr: string): string {
} catch (e) {}
return file
}
/**
* 获取非空数据的最小值
* @param sourceData
* @param field
* @private
*/
export function filterEmptyMinValue(sourceData, field) {
let notEmptyMinValue = 0
getMaxAndMinValueByData(
sourceData.filter(item => item[field]),
'value',
0,
0,
(max, min) => {
notEmptyMinValue = min
}
)
return notEmptyMinValue
}

View File

@ -4,6 +4,7 @@ import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import dvCopyDark from '@/assets/svg/dv-copy-dark.svg'
import dvDelete from '@/assets/svg/dv-delete.svg'
import dvMove from '@/assets/svg/dv-move.svg'
import { treeDraggbleChart } from '@/utils/treeDraggbleChart'
import dvRename from '@/assets/svg/dv-rename.svg'
import dvDashboardSpine from '@/assets/svg/dv-dashboard-spine.svg'
import dvScreenSpine from '@/assets/svg/dv-screen-spine.svg'
@ -153,7 +154,11 @@ const resourceTypeList = computed(() => {
]
return list
})
const { handleDrop, allowDrop, handleDragStart } = treeDraggbleChart(
state,
'resourceTree',
curCanvasType.value
)
const menuList = computed(() => {
const list = [
{
@ -640,6 +645,10 @@ defineExpose({
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
@node-click="nodeClick"
@node-drag-start="handleDragStart"
:allow-drop="allowDrop"
@node-drop="handleDrop"
draggable
>
<template #default="{ node, data }">
<span class="custom-tree-node">

View File

@ -16,9 +16,12 @@ import { Icon } from '@/components/icon-custom'
import { download2AppTemplate, downloadCanvas2 } from '@/utils/imgUtils'
import { storeToRefs } from 'pinia'
import { ElMessage } from 'element-plus-secondary'
import { personInfoApi } from '@/api/user'
import AppExportForm from '@/components/de-app/AppExportForm.vue'
import { useEmitt } from '@/hooks/web/useEmitt'
import { useUserStoreWithOut } from '@/store/modules/user'
const userStore = useUserStoreWithOut()
const userName = computed(() => userStore.getName)
const appExportFormRef = ref(null)
const dvMainStore = dvMainStoreWithOut()
@ -35,8 +38,7 @@ const state = reactive({
canvasStylePreview: null,
canvasViewInfoPreview: null,
dvInfo: null,
curPreviewGap: 0,
userLoginInfo: {}
curPreviewGap: 0
})
const { fullscreenFlag, canvasViewDataInfo } = storeToRefs(dvMainStore)
@ -141,7 +143,7 @@ const downLoadToAppPre = () => {
appName: state.dvInfo.name,
icon: null,
version: '2.0',
creator: state.userLoginInfo?.name,
creator: userName.value,
required: '2.9.0',
description: null
})
@ -189,18 +191,11 @@ const resourceNodeClick = data => {
}
const previewShowFlag = computed(() => !!dvMainStore.dvInfo?.name)
const findUserData = callback => {
personInfoApi().then(rsp => {
callback(rsp)
})
}
onBeforeMount(() => {
if (showPosition.value === 'preview') {
dvMainStore.canvasDataInit()
}
findUserData(res => {
state.userLoginInfo = res.data
})
})
const sideTreeStatus = ref(true)
const changeSideTreeStatus = val => {

View File

@ -101,5 +101,8 @@ defineExpose({
align-items: center;
flex-direction: column;
justify-content: center; /* 上下居中 */
::-webkit-scrollbar {
display: none;
}
}
</style>

View File

@ -106,7 +106,7 @@ const loadCanvasDataAsync = async (dvId, dvType, ignoreParams = false) => {
}
}
initCanvasData(
await initCanvasData(
dvId,
dvType,
function ({
@ -194,6 +194,7 @@ defineExpose({
:cur-gap="state.curPreviewGap"
:is-selector="props.isSelector"
:download-status="downloadStatus"
:show-pop-bar="true"
></de-preview>
<empty-background v-if="!state.initState" description="参数不能为空" img-type="noneWhite" />
</div>
@ -205,6 +206,9 @@ defineExpose({
</template>
<style lang="less" scoped>
::-webkit-scrollbar {
display: none;
}
.content {
background-color: #ffffff;
width: 100%;
@ -212,9 +216,5 @@ defineExpose({
align-items: center;
overflow-x: hidden;
overflow-y: auto;
::-webkit-scrollbar {
width: 0px !important;
height: 0px !important;
}
}
</style>

View File

@ -17,10 +17,14 @@ import { download2AppTemplate, downloadCanvas2 } from '@/utils/imgUtils'
import MultiplexPreviewShow from '@/views/data-visualization/MultiplexPreviewShow.vue'
import DvPreview from '@/views/data-visualization/DvPreview.vue'
import AppExportForm from '@/components/de-app/AppExportForm.vue'
import { personInfoApi } from '@/api/user'
import { ElMessage } from 'element-plus-secondary'
import { useEmitt } from '@/hooks/web/useEmitt'
import { useUserStoreWithOut } from '@/store/modules/user'
const userStore = useUserStoreWithOut()
const userName = computed(() => userStore.getName)
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, canvasViewDataInfo } = storeToRefs(dvMainStore)
const previewCanvasContainer = ref(null)
@ -136,7 +140,7 @@ const downLoadToAppPre = () => {
appName: state.dvInfo.name,
icon: null,
version: '2.0',
creator: state.userLoginInfo?.name,
creator: userName.value,
required: '2.9.0',
description: null
})
@ -170,8 +174,7 @@ const state = reactive({
canvasStylePreview: null,
canvasViewInfoPreview: null,
dvInfo: null,
curPreviewGap: 0,
userLoginInfo: {}
curPreviewGap: 0
})
const sideTreeStatus = ref(true)
@ -195,12 +198,6 @@ const downLoadApp = appAttachInfo => {
fileDownload('app', appAttachInfo)
}
const findUserData = callback => {
personInfoApi().then(rsp => {
callback(rsp)
})
}
onMounted(() => {
useEmitt({
name: 'canvasDownload',
@ -218,9 +215,6 @@ onBeforeMount(() => {
if (props.showPosition === 'preview') {
dvMainStore.canvasDataInit()
}
findUserData(res => {
state.userLoginInfo = res.data
})
})
</script>

View File

@ -85,7 +85,7 @@ const {
canvasState,
batchOptStatus
} = storeToRefs(dvMainStore)
const { editorMap } = storeToRefs(composeStore)
const { editorMap, isSpaceDown } = storeToRefs(composeStore)
const canvasOut = ref(null)
const canvasInner = ref(null)
const leftSidebarRef = ref(null)
@ -93,6 +93,8 @@ const dvLayout = ref(null)
const canvasCenterRef = ref(null)
const mainHeight = ref(300)
let createType = null
let isDragging = false //
let startX, startY, scrollLeft, scrollTop
const state = reactive({
datasetTree: [],
scaleHistory: null,
@ -103,6 +105,39 @@ const state = reactive({
opt: null
})
//
const enableDragging = e => {
if (isSpaceDown.value) {
//
isDragging = true
startX = e.pageX - canvasOut.value.wrapRef.offsetLeft
startY = e.pageY - canvasOut.value.wrapRef.offsetTop
scrollLeft = canvasOut.value.wrapRef.scrollLeft
scrollTop = canvasOut.value.wrapRef.scrollTop
e.preventDefault()
e.stopPropagation()
}
}
//
const onMouseMove = e => {
if (!isDragging) return
e.preventDefault()
e.stopPropagation()
const x = e.pageX - canvasOut.value.wrapRef.offsetLeft
const y = e.pageY - canvasOut.value.wrapRef.offsetTop
const walkX = x - startX
const walkY = y - startY
canvasOut.value.wrapRef.scrollLeft = scrollLeft - walkX
canvasOut.value.wrapRef.scrollTop = scrollTop - walkY
console.log('====onMouseMove==walkX=' + walkX + ';walkY=' + walkY)
}
//
const disableDragging = () => {
isDragging = false
}
const contentStyle = computed(() => {
const { width, height } = canvasStyleData.value
if (editMode.value === 'preview') {
@ -421,6 +456,8 @@ eventBus.on('handleNew', handleNew)
:class="isDataEaseBi && !newWindowFromDiv && 'dataease-w-h'"
>
<DvToolbar />
<span style="color: blue">---{{ isSpaceDown }}</span>
<div class="custom-dv-divider" />
<el-container
v-if="loadFinish"
@ -456,7 +493,12 @@ eventBus.on('handleNew', handleNew)
@scroll="scrollCanvas"
class="content"
:class="{ 'preview-content': previewStatus }"
@mousedown="enableDragging"
@mouseup="disableDragging"
@mousemove="onMouseMove"
@mouseleave="disableDragging"
>
<div v-if="isSpaceDown" class="canvas-drag" :style="contentStyle"></div>
<div
id="canvas-dv-outer"
ref="canvasInner"
@ -476,7 +518,11 @@ eventBus.on('handleNew', handleNew)
:canvas-style-data="canvasStyleData"
:canvas-view-info="canvasViewInfo"
:canvas-id="state.canvasId"
></canvas-core>
>
<template v-slot:canvasDragTips>
<div class="canvas-drag-tip">按住空格可拖动画布</div>
</template>
</canvas-core>
</div>
</div>
</el-scrollbar>
@ -580,6 +626,7 @@ eventBus.on('handleNew', handleNew)
background-color: rgba(51, 51, 51, 1);
overflow: auto;
.content {
position: relative;
flex: 1;
width: 100%;
overflow: auto;
@ -644,4 +691,19 @@ eventBus.on('handleNew', handleNew)
color: #ebebeb;
}
}
.canvas-drag {
position: absolute;
z-index: 1;
opacity: 0.3;
cursor: pointer;
}
.canvas-drag-tip {
position: absolute;
right: 5px;
bottom: -20px;
font-size: 12px;
color: rgb(169, 175, 184);
}
</style>

View File

@ -6,10 +6,12 @@ import icon_intoItem_outlined from '@/assets/svg/icon_into-item_outlined.svg'
import icon_rename_outlined from '@/assets/svg/icon_rename_outlined.svg'
import dvNewFolder from '@/assets/svg/dv-new-folder.svg'
import icon_fileAdd_outlined from '@/assets/svg/icon_file-add_outlined.svg'
import { moveDatasetTree } from '@/api/dataset'
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
import dvSortAsc from '@/assets/svg/dv-sort-asc.svg'
import dvSortDesc from '@/assets/svg/dv-sort-desc.svg'
import dvFolder from '@/assets/svg/dv-folder.svg'
import { treeDraggble } from '@/utils/treeDraggble'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
import icon_dashboard_outlined from '@/assets/svg/icon_dashboard_outlined.svg'
@ -247,6 +249,13 @@ const infoList = computed(() => {
}
})
const { handleDrop, allowDrop, handleDragStart } = treeDraggble(
state,
'datasetTree',
moveDatasetTree,
'dataset'
)
const generateColumns = (arr: Field[]) =>
arr.map(ele => ({
key: ele.dataeaseName,
@ -852,6 +861,10 @@ const getMenuList = (val: boolean) => {
:filter-node-method="filterNode"
expand-on-click-node
highlight-current
@node-drag-start="handleDragStart"
:allow-drop="allowDrop"
@node-drop="handleDrop"
draggable
@node-expand="nodeExpand"
@node-collapse="nodeCollapse"
:default-expanded-keys="expandedKey"

View File

@ -325,6 +325,16 @@ const setRules = () => {
}
]
}
if (form.value.type === 'es') {
configRules['configuration.url'] = [
{
required: true,
message: t('datasource.please_input_datasource_url'),
trigger: 'change'
}
]
}
rule.value = { ...cloneDeep(configRules), ...cloneDeep(defaultRule) }
}

View File

@ -34,6 +34,7 @@ import {
ElScrollbar,
ElAside
} from 'element-plus-secondary'
import { treeDraggble } from '@/utils/treeDraggble'
import GridTable from '@/components/grid-table/src/GridTable.vue'
import ArrowSide from '@/views/common/DeResourceArrow.vue'
import relationChart from '@/components/relation-chart/index.vue'
@ -744,6 +745,13 @@ const handleEdit = async data => {
editDatasource()
}
const { handleDrop, allowDrop, handleDragStart } = treeDraggble(
state,
'datasourceTree',
move,
'datasource'
)
const handleCopy = async data => {
getById(data.id).then(res => {
let {
@ -1112,6 +1120,10 @@ const getMenuList = (val: boolean) => {
:default-expanded-keys="expandedKey"
:data="state.datasourceTree"
:props="defaultProps"
@node-drag-start="handleDragStart"
:allow-drop="allowDrop"
@node-drop="handleDrop"
draggable
@node-click="handleNodeClick"
>
<template #default="{ node, data }">

@ -1 +1 @@
Subproject commit 15fd94f9b316a6956be8eddaf4ba6196dcccb7a2
Subproject commit 19248ea4885a923cac1563eafdabff5e4e453a1e

View File

@ -201,14 +201,16 @@ function install_docker() {
EOF
fi
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
docker version >/dev/null 2>&1
if [ $? -ne 0 ]; then
log_content "docker 安装失败"
exit 1
else
log_content "docker 安装成功"
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
fi
fi
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,6 +12,7 @@ import io.dataease.extensions.datasource.dto.SimpleDatasourceDTO;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@ -139,6 +140,6 @@ public interface DataFillingApi {
void geFullName(Long pid, List<String> fullName);
@PostMapping("/innerExport/{formId}")
void innerExport(@PathVariable("formId") Long formId) throws Exception;
@PostMapping("/innerExport/{isDataEaseBi}/{formId}")
void innerExport(@PathVariable("formId") Long formId, @PathVariable("isDataEaseBi") boolean isDataEaseBi, HttpServletResponse response) throws Exception;
}

View File

@ -51,6 +51,10 @@ public interface UserApi {
@GetMapping("/personInfo")
UserFormVO personInfo();
@Operation(summary = "查询客户端IP信息")
@GetMapping("/ipInfo")
CurIpVO ipInfo();
@Operation(summary = "创建")
@DePermit("m:read")
@PostMapping("/create")

View File

@ -0,0 +1,18 @@
package io.dataease.api.permissions.user.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class CurIpVO implements Serializable {
@Serial
private static final long serialVersionUID = -3025566841330382707L;
private String account;
private String name;
private String ip;
}

View File

@ -0,0 +1,12 @@
package io.dataease.auth;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeLinkPermit {
String value() default "";
}