Merge branch 'dev' into pr@dev_memory_component

This commit is contained in:
dataeaseShu 2023-03-15 10:44:04 +08:00
commit 33803a6767
69 changed files with 1011 additions and 277 deletions

View File

@ -8,5 +8,5 @@ referer = "referer"
keynode = "keynode"
[files]
extend-exclude = ["public/", "amap-wx/", "m-icon/", "uni-card/", "uni-col/", "uni-link/", "uni-list/", "uni-list-item/", "uni-row/", "migration/", "mapFiles/"]
extend-exclude = ["public/", "amap-wx/", "m-icon/", "uni-card/", "uni-col/", "uni-link/", "uni-list/", "uni-list-item/", "uni-row/", "migration/", "mapFiles/", "frontend/src/views/chart/components/table/TableNormal.vue"]

View File

@ -3,6 +3,7 @@ package io.dataease.auth.api;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.auth.api.dto.LoginDto;
import io.dataease.auth.api.dto.SeizeLoginDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
@ -21,6 +22,13 @@ public interface AuthApi {
@PostMapping("/login")
Object login(LoginDto loginDto) throws Exception;
@ApiOperation("移动端登录")
@PostMapping("/mobileLogin")
Object mobileLogin(LoginDto loginDto) throws Exception;
@PostMapping("/seizeLogin")
Object seizeLogin(SeizeLoginDto loginDto) throws Exception;
@ApiOperation("获取用户信息")
@PostMapping("/userInfo")
CurrentUserDto userInfo();

View File

@ -0,0 +1,13 @@
package io.dataease.auth.api.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class SeizeLoginDto implements Serializable {
private static final long serialVersionUID = -3318473577764636483L;
private String token;
}

View File

@ -4,6 +4,7 @@ import io.dataease.auth.api.AuthApi;
import io.dataease.auth.api.dto.CurrentRoleDto;
import io.dataease.auth.api.dto.CurrentUserDto;
import io.dataease.auth.api.dto.LoginDto;
import io.dataease.auth.api.dto.SeizeLoginDto;
import io.dataease.auth.config.RsaProperties;
import io.dataease.auth.entity.AccountLockStatus;
import io.dataease.auth.entity.SysUserEntity;
@ -28,6 +29,8 @@ import io.dataease.plugins.xpack.oidc.service.OidcXpackService;
import io.dataease.service.sys.SysUserService;
import io.dataease.service.system.SystemParameterService;
import io.dataease.websocket.entity.WsMessage;
import io.dataease.websocket.service.WsService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
@ -61,6 +64,49 @@ public class AuthServer implements AuthApi {
@Resource
private SystemParameterService systemParameterService;
@Autowired
private WsService wsService;
@Override
public Object mobileLogin(@RequestBody LoginDto loginDto) throws Exception {
String username = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, loginDto.getUsername());
String pwd = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, loginDto.getPassword());
AccountLockStatus accountLockStatus = authUserService.lockStatus(username, 0);
if (accountLockStatus.getLocked()) {
String msg = Translator.get("I18N_ACCOUNT_LOCKED");
msg = String.format(msg, username, accountLockStatus.getRelieveTimes().toString());
DataEaseException.throwException(msg);
}
SysUserEntity user = authUserService.getUserByName(username);
if (ObjectUtils.isEmpty(user)) {
AccountLockStatus lockStatus = authUserService.recordLoginFail(username, 0);
DataEaseException.throwException(appendLoginErrorMsg(Translator.get("i18n_id_or_pwd_error"), lockStatus));
}
if (user.getEnabled() == 0) {
AccountLockStatus lockStatus = authUserService.recordLoginFail(username, 0);
DataEaseException.throwException(appendLoginErrorMsg(Translator.get("i18n_user_is_disable"), lockStatus));
}
String realPwd = user.getPassword();
pwd = CodingUtil.md5(pwd);
if (!StringUtils.equals(pwd, realPwd)) {
AccountLockStatus lockStatus = authUserService.recordLoginFail(username, 0);
DataEaseException.throwException(appendLoginErrorMsg(Translator.get("i18n_id_or_pwd_error"), lockStatus));
}
TokenInfo tokenInfo = TokenInfo.builder().userId(user.getUserId()).username(username).build();
String token = JWTUtils.sign(tokenInfo, realPwd, false);
// 记录token操作时间
Map<String, Object> result = new HashMap<>();
result.put("token", token);
ServletUtils.setToken(token);
DeLogUtils.save(SysLogConstants.OPERATE_TYPE.LOGIN, SysLogConstants.SOURCE_TYPE.USER, user.getUserId(), null, null, null);
authUserService.unlockAccount(username, 0);
authUserService.clearCache(user.getUserId());
return result;
}
@Override
public Object login(@RequestBody LoginDto loginDto) throws Exception {
Map<String, Object> result = new HashMap<>();
@ -164,6 +210,23 @@ public class AuthServer implements AuthApi {
return result;
}
@Override
public Object seizeLogin(@RequestBody SeizeLoginDto loginDto) throws Exception {
String token = loginDto.getToken();
Map<String, Object> result = new HashMap<>();
result.put("token", token);
ServletUtils.setToken(token);
TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token);
Long userId = tokenInfo.getUserId();
JWTUtils.seizeSign(userId, token);
DeLogUtils.save(SysLogConstants.OPERATE_TYPE.LOGIN, SysLogConstants.SOURCE_TYPE.USER, userId, null, null, null);
WsMessage message = new WsMessage(userId, "/web-seize-topic", IPUtils.get());
wsService.releaseMessage(message);
authUserService.clearCache(userId);
Thread.sleep(3000L);
return result;
}
private String appendLoginErrorMsg(String msg, AccountLockStatus lockStatus) {
if (ObjectUtils.isEmpty(lockStatus)) return msg;
if (ObjectUtils.isNotEmpty(lockStatus.getRemainderTimes())) {

View File

@ -81,7 +81,9 @@ public class ShiroServiceImpl implements ShiroService {
filterChainDefinitionMap.put("/api/auth/login", ANON);
filterChainDefinitionMap.put("/api/auth/seizeLogin", ANON);
filterChainDefinitionMap.put("/api/auth/logout", ANON);
filterChainDefinitionMap.put("/api/auth/mobileLogin", ANON);
filterChainDefinitionMap.put("/api/auth/isPluginLoaded", ANON);
filterChainDefinitionMap.put("/system/requestTimeOut", ANON);
filterChainDefinitionMap.put("/api/auth/validateName", ANON);

View File

@ -6,15 +6,21 @@ import com.auth0.jwt.JWTCreator.Builder;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import com.google.gson.Gson;
import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.entity.TokenInfo.TokenInfoBuilder;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.model.OnlineUserModel;
import io.dataease.commons.utils.*;
import io.dataease.exception.DataEaseException;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import java.util.Date;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
public class JWTUtils {
@ -68,23 +74,93 @@ public class JWTUtils {
* @return 加密的token
*/
public static String sign(TokenInfo tokenInfo, String secret) {
return sign(tokenInfo, secret, true);
}
private static boolean tokenValid(OnlineUserModel model) {
String token = model.getToken();
// 如果已经加入黑名单 则直接返回无效
boolean invalid = TokenCacheUtils.invalid(token);
if (invalid) return false;
Long loginTime = model.getLoginTime();
if (ObjectUtils.isEmpty(expireTime)) {
expireTime = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, 480L);
}
long expireTimeMillis = expireTime * 60000L;
// 如果当前时间减去登录时间小于超时时间则说明token未过期 返回有效状态
return System.currentTimeMillis() - loginTime < expireTimeMillis;
}
private static String models2Json(OnlineUserModel model, boolean withCurToken, String token) {
Set<OnlineUserModel> models = new LinkedHashSet<>();
models.add(model);
Gson gson = new Gson();
List<OnlineUserModel> userModels = models.stream().map(item -> {
item.setToken(null);
return item;
}).collect(Collectors.toList());
if (withCurToken) {
userModels.get(0).setToken(token);
}
String json = gson.toJson(userModels);
try {
if (ObjectUtils.isEmpty(expireTime)) {
expireTime = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, 480L);
}
long expireTimeMillis = expireTime * 60000L;
Date date = new Date(System.currentTimeMillis() + expireTimeMillis);
Algorithm algorithm = Algorithm.HMAC256(secret);
Builder builder = JWT.create()
.withClaim("username", tokenInfo.getUsername())
.withClaim("userId", tokenInfo.getUserId());
String sign = builder.withExpiresAt(date).sign(algorithm);
return sign;
return URLEncoder.encode(json, "utf-8");
} catch (Exception e) {
return null;
}
}
public static String seizeSign(Long userId, String token) {
Optional.ofNullable(TokenCacheUtils.onlineUserToken(userId)).ifPresent(model -> TokenCacheUtils.add(model.getToken(), userId));
TokenCacheUtils.add2OnlinePools(token, userId);
return IPUtils.get();
}
public static String sign(TokenInfo tokenInfo, String secret, boolean writeOnline) {
Long userId = tokenInfo.getUserId();
String multiLoginType = null;
if (writeOnline && StringUtils.equals("1", (multiLoginType = TokenCacheUtils.multiLoginType()))) {
OnlineUserModel userModel = TokenCacheUtils.onlineUserToken(userId);
if (ObjectUtils.isNotEmpty(userModel) && tokenValid(userModel)) {
HttpServletResponse response = ServletUtils.response();
Cookie cookie_token = new Cookie("MultiLoginError1", models2Json(userModel, false, null));
cookie_token.setPath("/");
cookie_token.setPath("/");
response.addCookie(cookie_token);
DataEaseException.throwException("MultiLoginError1");
}
}
if (ObjectUtils.isEmpty(expireTime)) {
expireTime = CommonBeanFactory.getBean(Environment.class).getProperty("dataease.login_timeout", Long.class, 480L);
}
long expireTimeMillis = expireTime * 60000L;
Date date = new Date(System.currentTimeMillis() + expireTimeMillis);
Algorithm algorithm = Algorithm.HMAC256(secret);
Builder builder = JWT.create()
.withClaim("username", tokenInfo.getUsername())
.withClaim("userId", userId);
String sign = builder.withExpiresAt(date).sign(algorithm);
if (StringUtils.equals("2", multiLoginType)) {
OnlineUserModel userModel = TokenCacheUtils.onlineUserToken(userId);
if (ObjectUtils.isNotEmpty(userModel) && tokenValid(userModel)) {
HttpServletResponse response = ServletUtils.response();
Cookie cookie_token = new Cookie("MultiLoginError2", models2Json(userModel, true, sign));
cookie_token.setPath("/");
response.addCookie(cookie_token);
DataEaseException.throwException("MultiLoginError");
}
}
if (writeOnline && !StringUtils.equals("0", multiLoginType)) {
TokenCacheUtils.add2OnlinePools(sign, userId);
}
return sign;
}
public static String signLink(String resourceId, Long userId, String secret) {
Algorithm algorithm = Algorithm.HMAC256(secret);
if (userId == null) {

View File

@ -127,6 +127,8 @@ public interface ParamConstants {
LOGIN_LIMIT_OPEN("loginlimit.open"),
SCAN_CREATE_USER("loginlimit.scanCreateUser"),
MULTI_LOGIN("loginlimit.multiLogin"),
TEMPLATE_ACCESS_KEY("basic.templateAccessKey");
private String value;

View File

@ -0,0 +1,19 @@
package io.dataease.commons.model;
import lombok.Data;
import net.minidev.json.annotate.JsonIgnore;
import java.io.Serializable;
@Data
public class OnlineUserModel implements Serializable {
private static final long serialVersionUID = 190044376129186283L;
@JsonIgnore
private String token;
private String ip;
private Long loginTime;
}

View File

@ -1,6 +1,9 @@
package io.dataease.commons.utils;
import com.google.gson.Gson;
import io.dataease.commons.model.OnlineUserModel;
import io.dataease.listener.util.CacheUtils;
import io.dataease.service.system.SystemParameterService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
@ -17,10 +20,14 @@ public class TokenCacheUtils {
private static final String KEY = "sys_token_store";
private static final String ONLINE_TOKEN_POOL_KEY = "online_token_store";
private static String cacheType;
private static Long expTime;
private static Gson gson = new Gson();
@Value("${spring.cache.type:ehcache}")
public void setCacheType(String cacheType) {
TokenCacheUtils.cacheType = cacheType;
@ -55,17 +62,6 @@ public class TokenCacheUtils {
CacheUtils.flush(KEY);
}
public static void remove(String token) {
if (useRedis()) {
RedisTemplate redisTemplate = (RedisTemplate) CommonBeanFactory.getBean("redisTemplate");
String key = KEY + token;
if (redisTemplate.hasKey(key)) {
redisTemplate.delete(key);
}
return;
}
CacheUtils.remove(KEY, token);
}
public static boolean invalid(String token) {
if (useRedis()) {
@ -76,4 +72,46 @@ public class TokenCacheUtils {
return ObjectUtils.isNotEmpty(sys_token_store) && StringUtils.isNotBlank(sys_token_store.toString());
}
public static void add2OnlinePools(String token, Long userId) {
OnlineUserModel model = buildModel(token);
if (useRedis()) {
ValueOperations valueOperations = cacheHandler();
valueOperations.set(ONLINE_TOKEN_POOL_KEY + userId, model, expTime, TimeUnit.MINUTES);
return;
}
Long time = expTime * 60;
Double v = time * 0.6;
CacheUtils.put(ONLINE_TOKEN_POOL_KEY, userId, model, time.intValue(), v.intValue());
CacheUtils.flush(ONLINE_TOKEN_POOL_KEY);
}
public static String multiLoginType() {
SystemParameterService service = CommonBeanFactory.getBean(SystemParameterService.class);
return service.multiLoginType();
}
public static OnlineUserModel onlineUserToken(Long userId) {
if (useRedis()) {
ValueOperations valueOperations = cacheHandler();
Object obj = valueOperations.get(ONLINE_TOKEN_POOL_KEY + userId);
if (ObjectUtils.isNotEmpty(obj)) return (OnlineUserModel) obj;
return null;
}
Object o = CacheUtils.get(ONLINE_TOKEN_POOL_KEY, userId);
if (ObjectUtils.isNotEmpty(o)) {
OnlineUserModel userModel = gson.fromJson(gson.toJson(o), OnlineUserModel.class);
return userModel;
}
return null;
}
public static OnlineUserModel buildModel(String token) {
OnlineUserModel model = new OnlineUserModel();
model.setToken(token);
model.setIp(IPUtils.get());
model.setLoginTime(System.currentTimeMillis());
return model;
}
}

View File

@ -6,13 +6,10 @@ import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.service.impl.AuthUserServiceImpl;
import io.dataease.auth.util.JWTUtils;
import io.dataease.commons.utils.*;
import io.dataease.dto.PermissionProxy;
import io.dataease.dto.chart.ViewOption;
import io.dataease.ext.ExtTaskMapper;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.commons.utils.CronUtils;
import io.dataease.commons.utils.LogUtil;
import io.dataease.commons.utils.ServletUtils;
import io.dataease.job.sechedule.ScheduleManager;
import io.dataease.job.sechedule.strategy.TaskHandler;
import io.dataease.plugins.common.base.domain.SysUserAssist;
@ -164,6 +161,7 @@ public class EmailTaskHandler extends TaskHandler implements Job {
AuthUserServiceImpl userService = SpringContextUtil.getBean(AuthUserServiceImpl.class);
SysUserService sysUserService = SpringContextUtil.getBean(SysUserService.class);
List<File> files = null;
String token = null;
try {
XpackEmailTemplateDTO emailTemplateDTO = emailXpackService.emailTemplate(taskInstance.getTaskId());
XpackEmailTaskRequest taskForm = emailXpackService.taskForm(taskInstance.getTaskId());
@ -173,7 +171,7 @@ public class EmailTaskHandler extends TaskHandler implements Job {
}
String panelId = emailTemplateDTO.getPanelId();
String url = panelUrl(panelId);
String token = tokenByUser(user);
token = tokenByUser(user);
XpackPixelEntity xpackPixelEntity = buildPixel(emailTemplateDTO);
LogUtil.info("url is " + url);
LogUtil.info("token is " + token);
@ -349,6 +347,9 @@ public class EmailTaskHandler extends TaskHandler implements Job {
error(taskInstance, e);
LogUtil.error(e.getMessage(), e);
} finally {
if (StringUtils.isNotBlank(token)) {
TokenCacheUtils.add(token, user.getUserId());
}
if (CollectionUtils.isNotEmpty(files)) {
files.forEach(file -> {
if (file.exists()) {
@ -381,7 +382,7 @@ public class EmailTaskHandler extends TaskHandler implements Job {
private String tokenByUser(SysUserEntity user) {
TokenInfo tokenInfo = TokenInfo.builder().userId(user.getUserId()).username(user.getUsername()).build();
String token = JWTUtils.sign(tokenInfo, user.getPassword());
String token = JWTUtils.sign(tokenInfo, user.getPassword(), false);
return token;
}

View File

@ -80,7 +80,7 @@ public class XDingtalkServer {
return dingtalkXpackService.getQrParam();
}
private ModelAndView privateCallBack(String code, Boolean withoutLogin) {
private ModelAndView privateCallBack(String code, Boolean withoutLogin, Boolean isMobile) {
ModelAndView modelAndView = new ModelAndView("redirect:/");
HttpServletResponse response = ServletUtils.response();
DingtalkXpackService dingtalkXpackService = null;
@ -109,7 +109,7 @@ public class XDingtalkServer {
}
TokenInfo tokenInfo = TokenInfo.builder().userId(sysUserEntity.getUserId()).username(sysUserEntity.getUsername()).build();
String realPwd = sysUserEntity.getPassword();
String token = JWTUtils.sign(tokenInfo, realPwd);
String token = JWTUtils.sign(tokenInfo, realPwd, !isMobile);
ServletUtils.setToken(token);
DeLogUtils.save(SysLogConstants.OPERATE_TYPE.LOGIN, SysLogConstants.SOURCE_TYPE.USER, sysUserEntity.getUserId(), null, null, null);
@ -144,13 +144,14 @@ public class XDingtalkServer {
}
@GetMapping("/callBackWithoutLogin")
public ModelAndView callBackWithoutLogin(@RequestParam("code") String code) {
return privateCallBack(code, true);
public ModelAndView callBackWithoutLogin(@RequestParam("code") String code, @RequestParam("mobile") String mobile) {
boolean isMobile = StringUtils.equals("1", mobile);
return privateCallBack(code, true, isMobile);
}
@GetMapping("/callBack")
public ModelAndView callBack(@RequestParam("code") String code, @RequestParam("state") String state) {
return privateCallBack(code, false);
return privateCallBack(code, false, false);
}
private void bindError(HttpServletResponse response, String url, String errorMsg) {

View File

@ -92,11 +92,12 @@ public class XLarkServer {
}
@GetMapping("/callBackWithoutLogin")
public ModelAndView callBackWithoutLogin(@RequestParam("code") String code) {
return privateCallBack(code, null, true);
public ModelAndView callBackWithoutLogin(@RequestParam("code") String code, @RequestParam("mobile") String mobile) {
boolean isMobile = StringUtils.equals("1", mobile);
return privateCallBack(code, null, true, isMobile);
}
private ModelAndView privateCallBack(String code, String state, Boolean withoutLogin) {
private ModelAndView privateCallBack(String code, String state, Boolean withoutLogin, Boolean isMobile) {
ModelAndView modelAndView = new ModelAndView("redirect:/");
HttpServletResponse response = ServletUtils.response();
LarkXpackService larkXpackService = null;
@ -132,7 +133,7 @@ public class XLarkServer {
}
TokenInfo tokenInfo = TokenInfo.builder().userId(sysUserEntity.getUserId()).username(sysUserEntity.getUsername()).build();
String realPwd = sysUserEntity.getPassword();
String token = JWTUtils.sign(tokenInfo, realPwd);
String token = JWTUtils.sign(tokenInfo, realPwd, !isMobile);
ServletUtils.setToken(token);
DeLogUtils.save(SysLogConstants.OPERATE_TYPE.LOGIN, SysLogConstants.SOURCE_TYPE.USER, sysUserEntity.getUserId(), null, null, null);
@ -168,7 +169,7 @@ public class XLarkServer {
@GetMapping("/callBack")
public ModelAndView callBack(@RequestParam("code") String code, @RequestParam("state") String state) {
return privateCallBack(code, state, false);
return privateCallBack(code, state, false, false);
}
private void bindError(HttpServletResponse response, String url, String errorMsg) {

View File

@ -777,6 +777,7 @@ public class JdbcProvider extends DefaultJdbcProvider {
case StarRocks:
MysqlConfiguration mysqlConfiguration = new Gson().fromJson(datasource.getConfiguration(), MysqlConfiguration.class);
mysqlConfiguration.getJdbc();
break;
case redshift:
RedshiftConfiguration redshiftConfiguration = new Gson().fromJson(datasource.getConfiguration(), RedshiftConfiguration.class);
if(redshiftConfiguration.getDataBase().length() > 64 || redshiftConfiguration.getDataBase().length() < 1){
@ -785,6 +786,7 @@ public class JdbcProvider extends DefaultJdbcProvider {
if(!redshiftConfiguration.getDataBase().matches("\"^[a-z][a-z0-9_+.@-]*$\"")){
throw new Exception("Invalid database name");
}
break;
default:
break;
}

View File

@ -269,7 +269,8 @@ public class MysqlQueryProvider extends QueryProvider {
ChartViewFieldDTO x = xAxis.get(i);
String originField;
if (ObjectUtils.isNotEmpty(x.getExtField()) && x.getExtField() == 2) {
// 解析origin name中有关联的字段生成sql表达式
// 计算字段和视图字段规则为 函数([原始字段id])这边把[原始字段id] 换成 表名.原始字段id
// 解析origin name中有关联的字段生成sql表达式
originField = calcFieldRegex(x.getOriginName(), tableObj);
} else if (ObjectUtils.isNotEmpty(x.getExtField()) && x.getExtField() == 1) {
originField = String.format(MysqlConstants.KEYWORD_FIX, tableObj.getTableAlias(), x.getDataeaseName());
@ -1058,12 +1059,13 @@ public class MysqlQueryProvider extends QueryProvider {
}
if (field.getDeType() == 1) {
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5 || field.getDeExtractType() == 1) {
whereName = String.format(MysqlConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : MysqlConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(MysqlConstants.DATE_FORMAT, originName, format);
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(MysqlConstants.CAST, originName, MysqlConstants.DEFAULT_INT_FORMAT) + "/1000";
whereName = String.format(MysqlConstants.FROM_UNIXTIME, cast, MysqlConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(MysqlConstants.FROM_UNIXTIME, cast, format);
}
} else if (field.getDeType() == 0 && field.getDeExtractType() == 0) {
whereName = String.format(MysqlConstants.CAST, originName, MysqlConstants.CHAR);

View File

@ -0,0 +1,10 @@
package io.dataease.provider.query;
/**
* @Author Junjun
*/
public class SQLUtils {
public static String transKeyword(String value) {
return value.replaceAll("'", "\\\\'");
}
}

View File

@ -31,6 +31,7 @@ import org.stringtemplate.v4.STGroup;
import org.stringtemplate.v4.STGroupFile;
import javax.annotation.Resource;
import java.text.Format;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;
@ -1141,15 +1142,16 @@ public class CKQueryProvider extends QueryProvider {
}
if (field.getDeType() == DeTypeConstants.DE_TIME) {
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == DeTypeConstants.DE_STRING || field.getDeExtractType() == 5) {
whereName = String.format(CKConstants.toDateTime, originName);
whereName = String.format(CKConstants.formatDateTime, String.format(CKConstants.toDateTime, originName), format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_FLOAT || field.getDeExtractType() == DeTypeConstants.DE_FLOAT || field.getDeExtractType() == 4) {
String cast = String.format(CKConstants.toFloat64, originName);
whereName = String.format(CKConstants.toDateTime, cast);
whereName = String.format(CKConstants.formatDateTime, String.format(CKConstants.toDateTime, cast), format);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
whereName = String.format(CKConstants.formatDateTime, originName, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
@ -1168,7 +1170,7 @@ public class CKQueryProvider extends QueryProvider {
}
String whereName = "";
if (request.getIsTree()) {
if (request.getIsTree() && whereNameList.size() > 1) {
whereName = "CONCAT(" + StringUtils.join(whereNameList, ",',',") + ")";
} else {
whereName = whereNameList.get(0);

View File

@ -1115,23 +1115,24 @@ public class Db2QueryProvider extends QueryProvider {
}
if (field.getDeType() == DeTypeConstants.DE_TIME) {
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == DeTypeConstants.DE_STRING || field.getDeExtractType() == 5) {
if (StringUtils.isNotEmpty(field.getDateFormat())) {
originName = String.format(Db2Constants.TO_DATE, originName, field.getDateFormat());
} else {
originName = String.format(Db2Constants.STR_TO_DATE, originName);
}
whereName = String.format(Db2Constants.DATE_FORMAT, originName, Db2Constants.DEFAULT_DATE_FORMAT);
whereName = String.format(Db2Constants.DATE_FORMAT, originName, format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_INT || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(Db2Constants.CAST, originName, Db2Constants.DEFAULT_INT_FORMAT);
whereName = String.format(Db2Constants.FROM_UNIXTIME, cast, Db2Constants.DEFAULT_DATE_FORMAT);
whereName = String.format(Db2Constants.FROM_UNIXTIME, cast, format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_TIME) {
if (field.getType().equalsIgnoreCase("TIME")) {
whereName = String.format(Db2Constants.FORMAT_TIME, originName, Db2Constants.DEFAULT_DATE_FORMAT);
whereName = String.format(Db2Constants.FORMAT_TIME, originName, format);
} else if (field.getType().equalsIgnoreCase("DATE")) {
whereName = String.format(Db2Constants.FORMAT_DATE, originName, Db2Constants.DEFAULT_DATE_FORMAT);
whereName = String.format(Db2Constants.FORMAT_DATE, originName, format);
} else {
whereName = originName;
}

View File

@ -1080,15 +1080,16 @@ public class EsQueryProvider extends QueryProvider {
}
if (field.getDeType() == 1) {
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
whereName = String.format(EsSqlLConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : EsSqlLConstants.DEFAULT_DATE_FORMAT, EsSqlLConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(EsSqlLConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : EsSqlLConstants.DEFAULT_DATE_FORMAT, format);
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(EsSqlLConstants.CAST, originName, "timestamp");
whereName = String.format(EsSqlLConstants.DATETIME_FORMAT, cast, EsSqlLConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(EsSqlLConstants.DATETIME_FORMAT, cast, format);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
whereName = String.format(EsSqlLConstants.DATETIME_FORMAT, originName, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {

View File

@ -1035,17 +1035,17 @@ public class HiveQueryProvider extends QueryProvider {
} else {
originName = String.format(HiveConstants.KEYWORD_FIX, tableObj.getTableAlias(), field.getOriginName());
}
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeType() == DeTypeConstants.DE_TIME) {
if (field.getDeExtractType() == DeTypeConstants.DE_STRING || field.getDeExtractType() == 5) {
whereName = String.format(HiveConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : HiveConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(HiveConstants.DATE_FORMAT, originName, format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_INT || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(HiveConstants.CAST, originName, HiveConstants.DEFAULT_INT_FORMAT) + "/1000";
whereName = String.format(HiveConstants.FROM_UNIXTIME, cast, HiveConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(HiveConstants.FROM_UNIXTIME, cast, format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_TIME) {
whereName = originName;
whereName = String.format(HiveConstants.DATE_FORMAT, originName, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {

View File

@ -1032,16 +1032,17 @@ public class ImpalaQueryProvider extends QueryProvider {
originName = String.format(ImpalaConstants.KEYWORD_FIX, tableObj.getTableAlias(), field.getOriginName());
}
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeType() == DeTypeConstants.DE_TIME) {
if (field.getDeExtractType() == DeTypeConstants.DE_STRING || field.getDeExtractType() == 5) {
whereName = String.format(ImpalaConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : ImpalaConstants.DEFAULT_DATE_FORMAT ,ImpalaConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(ImpalaConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : ImpalaConstants.DEFAULT_DATE_FORMAT, format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_INT || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(ImpalaConstants.CAST, originName, ImpalaConstants.DEFAULT_INT_FORMAT) + "/1000";
whereName = String.format(ImpalaConstants.FROM_UNIXTIME, cast, ImpalaConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(ImpalaConstants.FROM_UNIXTIME, cast, format);
}
if (field.getDeExtractType() == DeTypeConstants.DE_TIME) {
whereName = originName;
whereName = String.format(ImpalaConstants.DATE_FORMAT, originName, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
@ -1069,7 +1070,12 @@ public class ImpalaQueryProvider extends QueryProvider {
String whereValue = "";
if (StringUtils.containsIgnoreCase(request.getOperator(), "in")) {
whereValue = "('" + StringUtils.join(value, "','") + "')";
DatasetTableField field = fieldList.get(0);
if (!request.getIsTree() && (field.getDeExtractType() == DeTypeConstants.DE_INT || field.getDeExtractType() == DeTypeConstants.DE_FLOAT || field.getDeExtractType() == DeTypeConstants.DE_BOOL)) {
whereValue = "(" + StringUtils.join(value, ",") + ")";
} else {
whereValue = "('" + StringUtils.join(value, "','") + "')";
}
} else if (StringUtils.containsIgnoreCase(request.getOperator(), "like")) {
whereValue = "'%" + value.get(0) + "%'";
} else if (StringUtils.containsIgnoreCase(request.getOperator(), "between")) {
@ -1154,6 +1160,7 @@ public class ImpalaQueryProvider extends QueryProvider {
String format = transDateFormat(x.getDateStyle(), x.getDatePattern());
if (x.getDeExtractType() == DeTypeConstants.DE_STRING) {
fieldName = String.format(ImpalaConstants.STR_TO_DATE, originField, StringUtils.isNotEmpty(x.getDateFormat()) ? x.getDateFormat() : ImpalaConstants.DEFAULT_DATE_FORMAT ,ImpalaConstants.DEFAULT_DATE_FORMAT);
fieldName = String.format(ImpalaConstants.DATE_FORMAT, fieldName, format);
} else {
String cast = String.format(ImpalaConstants.CAST, originField, ImpalaConstants.DEFAULT_INT_FORMAT) + "/1000";
String from_unixtime = String.format(ImpalaConstants.FROM_UNIXTIME, cast, ImpalaConstants.DEFAULT_DATE_FORMAT);

View File

@ -1062,15 +1062,25 @@ public class MysqlQueryProvider extends QueryProvider {
}
if (field.getDeType() == 1) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
whereName = String.format(MySQLConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : MysqlConstants.DEFAULT_DATE_FORMAT);
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5 || field.getDeExtractType() == 1) {
if (StringUtils.equalsIgnoreCase(request.getDateStyle(),"y_Q")){
whereName = String.format(format,
String.format(MysqlConstants.DATE_FORMAT, originName, "%Y"),
String.format(MysqlConstants.QUARTER, String.format(MysqlConstants.DATE_FORMAT, originName, MysqlConstants.DEFAULT_DATE_FORMAT)));
} else {
whereName = String.format(MySQLConstants.DATE_FORMAT, originName, format);
}
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(MySQLConstants.CAST, originName, MySQLConstants.DEFAULT_INT_FORMAT) + "/1000";
whereName = String.format(MySQLConstants.FROM_UNIXTIME, cast, MySQLConstants.DEFAULT_DATE_FORMAT);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
if (StringUtils.equalsIgnoreCase(request.getDateStyle(),"y_Q")){
whereName = String.format(format,
String.format(MysqlConstants.DATE_FORMAT, cast, "%Y"),
String.format(MysqlConstants.QUARTER, String.format(MysqlConstants.DATE_FORMAT, field, MysqlConstants.DEFAULT_DATE_FORMAT)));
} else {
whereName = String.format(MySQLConstants.DATE_FORMAT, cast, format);
}
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {

View File

@ -1205,17 +1205,17 @@ public class OracleQueryProvider extends QueryProvider {
} else {
originName = String.format(OracleConstants.KEYWORD_FIX, tableObj.getTableAlias(), field.getOriginName());
}
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeType() == 1) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
whereName = String.format(OracleConstants.TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : OracleConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(OracleConstants.TO_DATE, originName, format);
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(OracleConstants.CAST, originName, OracleConstants.DEFAULT_INT_FORMAT) + "/1000";
whereName = String.format(OracleConstants.FROM_UNIXTIME, cast, OracleConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(OracleConstants.FROM_UNIXTIME, cast, format);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
whereName = String.format(OracleConstants.TO_CHAR, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {

View File

@ -1072,15 +1072,18 @@ public class PgQueryProvider extends QueryProvider {
}
if (field.getDeType() == 1) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
whereName = String.format(PgConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : PgConstants.DEFAULT_DATE_FORMAT);
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5 || field.getDeExtractType() == 1) {
String timestamp = String.format(PgConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : PgConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(PgConstants.DATE_FORMAT, timestamp, format);
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(PgConstants.CAST, originName, "bigint");
whereName = String.format(PgConstants.FROM_UNIXTIME, cast);
String timestamp = String.format(PgConstants.FROM_UNIXTIME, cast);
whereName = String.format(PgConstants.DATE_FORMAT, timestamp, format);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
whereName = String.format(PgConstants.DATE_FORMAT, originName, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {

View File

@ -1067,15 +1067,18 @@ public class RedshiftQueryProvider extends QueryProvider {
}
if (field.getDeType() == 1) {
String format = transDateFormat(request.getDateStyle(), request.getDatePattern());
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
whereName = String.format(PgConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : PgConstants.DEFAULT_DATE_FORMAT);
String timestamp = String.format(PgConstants.STR_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : PgConstants.DEFAULT_DATE_FORMAT);
whereName = String.format(PgConstants.DATE_FORMAT, timestamp, format);
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(PgConstants.CAST, originName, "bigint");
whereName = String.format(PgConstants.FROM_UNIXTIME, cast);
String timestamp = String.format(PgConstants.FROM_UNIXTIME, cast);
whereName = String.format(PgConstants.DATE_FORMAT, timestamp, format);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
whereName = String.format(PgConstants.DATE_FORMAT, originName, format);
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {

View File

@ -1080,15 +1080,12 @@ public class SqlserverQueryProvider extends QueryProvider {
}
if (field.getDeType() == 1) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
whereName = String.format(SqlServerSQLConstants.STRING_TO_DATE, originName, StringUtils.isNotEmpty(field.getDateFormat()) ? field.getDateFormat() : SqlServerSQLConstants.DEFAULT_DATE_FORMAT);
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5 || field.getDeExtractType() == 1) {
whereName = transDateFormat(request.getDateStyle(), request.getDatePattern(), originName);
}
if (field.getDeExtractType() == 2 || field.getDeExtractType() == 3 || field.getDeExtractType() == 4) {
String cast = String.format(SqlServerSQLConstants.LONG_TO_DATE, originName + "/1000");
whereName = String.format(SqlServerSQLConstants.FROM_UNIXTIME, cast);
}
if (field.getDeExtractType() == 1) {
whereName = originName;
whereName = transDateFormat(request.getDateStyle(), request.getDatePattern(), cast);;
}
} else if (field.getDeType() == 2 || field.getDeType() == 3) {
if (field.getDeExtractType() == 0 || field.getDeExtractType() == 5) {
@ -1107,7 +1104,7 @@ public class SqlserverQueryProvider extends QueryProvider {
}
String whereName = "";
if (request.getIsTree()) {
if (request.getIsTree() && whereNameList.size() > 1) {
whereName = "CONCAT(" + StringUtils.join(whereNameList, ",',',") + ")";
} else {
whereName = whereNameList.get(0);

View File

@ -34,6 +34,7 @@ import io.dataease.plugins.common.base.mapper.DatasetTableFieldMapper;
import io.dataease.plugins.common.base.mapper.PanelViewMapper;
import io.dataease.plugins.common.constants.DatasetType;
import io.dataease.plugins.common.constants.datasource.SQLConstants;
import io.dataease.plugins.common.dto.chart.ChartCustomFilterItemDTO;
import io.dataease.plugins.common.dto.chart.ChartFieldCompareDTO;
import io.dataease.plugins.common.dto.chart.ChartFieldCustomFilterDTO;
import io.dataease.plugins.common.dto.chart.ChartViewFieldDTO;
@ -49,6 +50,7 @@ import io.dataease.plugins.view.entity.*;
import io.dataease.plugins.view.service.ViewPluginService;
import io.dataease.plugins.xpack.auth.dto.request.ColumnPermissionItem;
import io.dataease.provider.ProviderFactory;
import io.dataease.provider.query.SQLUtils;
import io.dataease.service.chart.util.ChartDataBuild;
import io.dataease.service.dataset.*;
import io.dataease.service.datasource.DatasourceService;
@ -71,9 +73,8 @@ import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @Author gin
@ -848,21 +849,40 @@ public class ChartViewService {
boolean isDrill = false;
List<ChartDrillRequest> drillRequestList = chartExtRequest.getDrill();
if (CollectionUtils.isNotEmpty(drillRequestList) && (drill.size() > drillRequestList.size())) {
// 如果是从子维度开始下钻那么先把主维度的条件先加上去
if (CollectionUtils.isNotEmpty(xAxisExt) && StringUtils.equalsIgnoreCase(drill.get(0).getId(), xAxisExt.get(0).getId())) {
ChartDrillRequest head = drillRequestList.get(0);
for (int i = 0; i < xAxisBase.size(); i++) {
ChartDimensionDTO dimensionDTO = head.getDimensionList().get(i);
DatasetTableField datasetTableField = dataSetTableFieldsService.get(dimensionDTO.getId());
ChartExtFilterRequest tmp = new ChartExtFilterRequest();
tmp.setFieldId(tmp.getFieldId());
tmp.setValue(Collections.singletonList(dimensionDTO.getValue()));
tmp.setOperator("in");
tmp.setDatasetTableField(datasetTableField);
extFilterList.add(tmp);
drillFilters.add(tmp);
ArrayList<ChartViewFieldDTO> fieldsToFilter = new ArrayList<>();
// 如果是从子维度开始下钻那么其他维度的条件要先加上去
// 分组和堆叠
if (StringUtils.containsIgnoreCase(view.getType(), "group")) {
// 分组堆叠
if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {
// 分组和堆叠字段都有才有效
if (CollectionUtils.isNotEmpty(xAxisExt) && CollectionUtils.isNotEmpty(extStack)) {
// 从分组字段下钻就加上堆叠字段的条件
if (StringUtils.equalsIgnoreCase(drill.get(0).getId(), xAxisExt.get(0).getId())) {
fieldsToFilter.addAll(xAxisBase);
fieldsToFilter.addAll(extStack);
}
// 从堆叠字段下钻就加上分组字段的条件
if (StringUtils.equalsIgnoreCase(drill.get(0).getId(), extStack.get(0).getId())) {
fieldsToFilter.addAll(xAxisBase);
fieldsToFilter.addAll(xAxisExt);
}
}
} else if (CollectionUtils.isNotEmpty(xAxisExt) &&
StringUtils.equalsIgnoreCase(drill.get(0).getId(), xAxisExt.get(0).getId())) {
fieldsToFilter.addAll(xAxisBase);
}
} else if (StringUtils.containsIgnoreCase(view.getType(), "stack") &&
CollectionUtils.isNotEmpty(extStack) &&
StringUtils.equalsIgnoreCase(drill.get(0).getId(), extStack.get(0).getId())) {
// 堆叠
fieldsToFilter.addAll(xAxisBase);
}
ChartDrillRequest head = drillRequestList.get(0);
Map<String, String> dimValMap = head.getDimensionList().stream().collect(Collectors.toMap(ChartDimensionDTO::getId, ChartDimensionDTO::getValue));
Map<String, ChartViewFieldDTO> fieldMap = Stream.of(xAxisBase, xAxisExt, extStack).
flatMap(Collection::stream).
collect(Collectors.toMap(ChartViewFieldDTO::getId, o -> o));
for (int i = 0; i < drillRequestList.size(); i++) {
ChartDrillRequest request = drillRequestList.get(i);
ChartViewFieldDTO chartViewFieldDTO = drill.get(i);
@ -870,26 +890,14 @@ public class ChartViewService {
// 将钻取值作为条件传递将所有钻取字段作为xAxis并加上下一个钻取字段
if (StringUtils.equalsIgnoreCase(requestDimension.getId(), chartViewFieldDTO.getId())) {
isDrill = true;
DatasetTableField datasetTableField = dataSetTableFieldsService.get(requestDimension.getId());
ChartViewFieldDTO d = new ChartViewFieldDTO();
BeanUtils.copyBean(d, datasetTableField);
ChartExtFilterRequest drillFilter = new ChartExtFilterRequest();
drillFilter.setFieldId(requestDimension.getId());
drillFilter.setValue(Collections.singletonList(requestDimension.getValue()));
drillFilter.setOperator("in");
drillFilter.setDatasetTableField(datasetTableField);
extFilterList.add(drillFilter);
drillFilters.add(drillFilter);
if (!checkDrillExist(xAxis, extStack, d, view)) {
xAxis.add(d);
fieldsToFilter.add(chartViewFieldDTO);
dimValMap.put(requestDimension.getId(), requestDimension.getValue());
if (!checkDrillExist(xAxis, extStack, requestDimension.getId(), view)) {
xAxis.add(chartViewFieldDTO);
}
//
if (i == drillRequestList.size() - 1) {
ChartViewFieldDTO nextDrillField = drill.get(i + 1);
if (!checkDrillExist(xAxis, extStack, nextDrillField, view)) {
if (!checkDrillExist(xAxis, extStack, nextDrillField.getId(), view)) {
// get drill list first element's sort,then assign to nextDrillField
nextDrillField.setSort(getDrillSort(xAxis, drill.get(0)));
xAxis.add(nextDrillField);
@ -898,6 +906,19 @@ public class ChartViewService {
}
}
}
for (int i = 0; i < fieldsToFilter.size(); i++) {
ChartViewFieldDTO tmpField = fieldsToFilter.get(i);
ChartExtFilterRequest tmpFilter = new ChartExtFilterRequest();
DatasetTableField datasetTableField = dataSetTableFieldsService.get(tmpField.getId());
tmpFilter.setDatasetTableField(datasetTableField);
tmpFilter.setOperator("in");
tmpFilter.setDateStyle(fieldMap.get(tmpField.getId()).getDateStyle());
tmpFilter.setDatePattern(fieldMap.get(tmpField.getId()).getDatePattern());
tmpFilter.setFieldId(tmpField.getId());
tmpFilter.setValue(Collections.singletonList(dimValMap.get(tmpField.getId())));
extFilterList.add(tmpFilter);
drillFilters.add(tmpFilter);
}
}
// 判断连接方式直连或者定时抽取 table.mode
@ -922,6 +943,25 @@ public class ChartViewService {
assistFields = getAssistFields(dynamicAssistFields, yAxis);
}
// 处理过滤条件中的单引号
fieldCustomFilter = fieldCustomFilter.stream().peek(ele -> {
if (CollectionUtils.isNotEmpty(ele.getEnumCheckField())) {
List<String> collect = ele.getEnumCheckField().stream().map(SQLUtils::transKeyword).collect(Collectors.toList());
ele.setEnumCheckField(collect);
}
if (CollectionUtils.isNotEmpty(ele.getFilter())) {
List<ChartCustomFilterItemDTO> collect = ele.getFilter().stream().peek(f -> f.setValue(SQLUtils.transKeyword(f.getValue()))).collect(Collectors.toList());
ele.setFilter(collect);
}
}).collect(Collectors.toList());
extFilterList = extFilterList.stream().peek(ele -> {
if (CollectionUtils.isNotEmpty(ele.getValue())) {
List<String> collect = ele.getValue().stream().map(SQLUtils::transKeyword).collect(Collectors.toList());
ele.setValue(collect);
}
}).collect(Collectors.toList());
// 如果是插件视图 走插件内部的逻辑
if (ObjectUtils.isNotEmpty(view.getIsPlugin()) && view.getIsPlugin()) {
Map<String, List<ChartViewFieldDTO>> fieldMap = ObjectUtils.isEmpty(extFieldsMap) ? new LinkedHashMap<>() : extFieldsMap;
@ -1540,17 +1580,17 @@ public class ChartViewService {
}
}
private boolean checkDrillExist(List<ChartViewFieldDTO> xAxis, List<ChartViewFieldDTO> extStack, ChartViewFieldDTO dto, ChartViewWithBLOBs view) {
private boolean checkDrillExist(List<ChartViewFieldDTO> xAxis, List<ChartViewFieldDTO> extStack, String fieldId, ChartViewWithBLOBs view) {
if (CollectionUtils.isNotEmpty(xAxis)) {
for (ChartViewFieldDTO x : xAxis) {
if (StringUtils.equalsIgnoreCase(x.getId(), dto.getId())) {
if (StringUtils.equalsIgnoreCase(x.getId(), fieldId)) {
return true;
}
}
}
if (StringUtils.containsIgnoreCase(view.getType(), "stack") && CollectionUtils.isNotEmpty(extStack)) {
for (ChartViewFieldDTO x : extStack) {
if (StringUtils.equalsIgnoreCase(x.getId(), dto.getId())) {
if (StringUtils.equalsIgnoreCase(x.getId(), fieldId)) {
return true;
}
}
@ -1797,7 +1837,8 @@ public class ChartViewService {
}
private String handleVariable(String sql, ChartExtRequest requestList, QueryProvider qp, DataSetTableDTO table, Datasource ds) throws Exception {
List<SqlVariableDetails> sqlVariables = new Gson().fromJson(table.getSqlVariableDetails(), new TypeToken<List<SqlVariableDetails>>() {}.getType());
List<SqlVariableDetails> sqlVariables = new Gson().fromJson(table.getSqlVariableDetails(), new TypeToken<List<SqlVariableDetails>>() {
}.getType());
if (requestList != null && CollectionUtils.isNotEmpty(requestList.getFilter())) {
for (ChartExtFilterRequest chartExtFilterRequest : requestList.getFilter()) {
if (CollectionUtils.isEmpty(chartExtFilterRequest.getValue())) {

View File

@ -21,6 +21,7 @@ import io.dataease.service.datasource.DatasourceService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -34,6 +35,7 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import io.dataease.ext.*;
import springfox.documentation.annotations.Cacheable;
@Service
@Transactional(rollbackFor = Exception.class)
@ -125,6 +127,13 @@ public class SystemParameterService {
boolean open = StringUtils.equals("true", param.getParamValue());
result.setScanCreateUser(open ? "true" : "false");
}
if (StringUtils.equals(param.getParamKey(), ParamConstants.BASIC.MULTI_LOGIN.getValue())) {
String paramValue = param.getParamValue();
result.setMultiLogin("0");
if (StringUtils.isNotBlank(paramValue)) {
result.setMultiLogin(paramValue);
}
}
}
}
@ -150,6 +159,7 @@ public class SystemParameterService {
public CasSaveResult editBasic(List<SystemParameter> parameters) {
CasSaveResult casSaveResult = afterSwitchDefaultLogin(parameters);
BasicInfo basicInfo = basicInfo();
String oldMultiLogin = this.getValue("loginlimit.multiLogin");
for (int i = 0; i < parameters.size(); i++) {
SystemParameter parameter = parameters.get(i);
SystemParameterExample example = new SystemParameterExample();
@ -163,6 +173,10 @@ public class SystemParameterService {
example.clear();
}
datasourceService.updateDatasourceStatusJob(basicInfo, parameters);
String newMultiLogin = this.getValue("loginlimit.multiLogin");
if (!StringUtils.equals(oldMultiLogin, newMultiLogin)) {
clearMultiLoginCache();
}
return casSaveResult;
}
@ -364,4 +378,17 @@ public class SystemParameterService {
return basicInfo;
}
@Cacheable(value = "multiLogin")
public String multiLoginType() {
String value = getValue("loginlimit.multiLogin");
if (StringUtils.isBlank(value)) {
value = "0";
}
return value;
}
@CacheEvict("multiLogin")
public void clearMultiLoginCache() {
}
}

View File

@ -17,7 +17,7 @@ import java.util.*;
*/
@Service
public class ReptileService {
String blogUrl = "https://blog.fit2cloud.com/?cat=321";
String blogUrl = "https://blog.fit2cloud.com/categories/dataease";
//获取最新的前几条数据
private static int infoCount=5;
@ -28,23 +28,20 @@ public class ReptileService {
config.setSocketTimeout(5000);
//爬取最新数据
Document doc = Jsoup.parse(HttpClientUtil.get(blogUrl, config));
Elements elementsContent = doc.getElementsByAttributeValue("rel", "bookmark");
Elements elementsTime = doc.getElementsByTag("time");
Elements elementsContent = doc.getElementsByClass("aspect-w-16");
for(int i = 0;i<infoCount;i++){
Element info = elementsContent.get(i*3);
Element info = elementsContent.get(i).children().get(0);
Map<String, String> infoMap = new HashMap();
infoMap.put("title",info.attr("title"));
infoMap.put("href",info.attr("href"));
infoMap.put("time",elementsTime.get(i).childNode(0).outerHtml());
result.add(infoMap);
}
} catch (Exception e) {
e.printStackTrace();
//ignore
Map<String, String> infoMap = new HashMap();
infoMap.put("title","支持移动端展示数据源新增对DB2的支持DataEase开源数据可视化分析平台v1.6.0发布");
infoMap.put("href","https://blog.fit2cloud.com/?p=3200");
infoMap.put("time","2022年1月10日");
infoMap.put("title","模板学堂丨妙用Tab组件制作多屏仪表板并实现自动轮播");
infoMap.put("href","https://blog.fit2cloud.com/?p=7c524ae9-69f1-4687-a5a2-f27971047308");
result.add(infoMap);
}
return result;

View File

@ -52,4 +52,6 @@ END if;
END
;;
delimiter ;
delimiter ;
INSERT INTO `system_parameter` (`param_key`, `param_value`, `type`, `sort`) VALUES ('loginlimit.multiLogin', '0', 'text', '3');

View File

@ -283,7 +283,30 @@
<BootstrapCacheLoaderFactory class="net.sf.ehcache.store.DiskStoreBootstrapCacheLoaderFactory" properties="bootstrapAsynchronously=true" />
</cache>
<cache
name="online_token_store"
eternal="false"
maxElementsInMemory="5000"
maxElementsOnDisk="50000"
overflowToDisk="true"
timeToIdleSeconds="28800"
timeToLiveSeconds="28800"
memoryStoreEvictionPolicy="LRU"
diskPersistent="true">
<BootstrapCacheLoaderFactory class="net.sf.ehcache.store.DiskStoreBootstrapCacheLoaderFactory" properties="bootstrapAsynchronously=true" />
</cache>
<cache
name="multiLogin"
eternal="false"
maxElementsInMemory="100"
maxElementsOnDisk="1000"
overflowToDisk="true"
diskPersistent="true"
timeToIdleSeconds="1800"
timeToLiveSeconds="3600"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>

View File

@ -8,6 +8,15 @@ export function login(data) {
})
}
export function seizeLogin(data) {
return request({
url: '/api/auth/seizeLogin',
method: 'post',
data,
loading: true
})
}
export function getInfo(token) {
return request({
url: '/api/auth/userInfo',

View File

@ -786,7 +786,6 @@ export default {
<style lang="scss" scoped>
.bg {
min-width: 200px;
min-height: 300px;
width: 100%;
height: 100%;
overflow-x: hidden;

View File

@ -136,13 +136,9 @@ export default {
const jumpRequestParam = {
sourcePanelId: jumpParam.sourcePanelId,
sourceViewId: jumpParam.sourceViewId,
sourceFieldId: jumpParam.sourceFieldId,
sourceFieldId: null,
targetPanelId: this.panelId
}
//
if (jumpParam.sourceType && jumpParam.sourceType === 'table-pivot') {
jumpRequestParam.sourceFieldId = null
}
try {
//
queryTargetPanelJumpInfo(jumpRequestParam).then(rsp => {

View File

@ -238,7 +238,7 @@ export default {
.VisualSelects {
.el-scrollbar {
position: relative;
height: 251px;
height: 245px;
overflow: inherit;
overflow-x: hidden;
content-visibility: auto;
@ -270,4 +270,7 @@ export default {
.select-all {
padding: 10px 20px 0 20px;
}
.coustom-de-select {
z-index: 999 !important;
}
</style>

View File

@ -198,7 +198,7 @@ export default {
validateCom(rule, value, callback) {
if (!value) return callback()
const one = Number(value)
if (Number.isInteger(one)) {
if (!Number.isNaN(one)) {
if (one < MIN_NUMBER) {
return callback(new Error(this.$t('denumberrange.out_of_min')))
} else if (one > MAX_NUMBER) {

View File

@ -382,6 +382,7 @@ export default {
thumbnail: 'thumbnail',
confirm_delete: 'Confirm delete',
delete_this_dashboard: 'Are you sure to delete this dashboard?',
delete_this_folder: 'Are you sure to delete this folder?',
confirm_stop: 'Confirm stop',
stop_success: 'Stop success',
treeselect: {
@ -1516,7 +1517,11 @@ export default {
p_right: 'Right',
p_top: 'Top',
p_bottom: 'Bottom',
p_center: 'Center'
p_center: 'Center',
table_auto_break_line: 'Auto Line Feed',
table_break_line_tip: 'If open this option,the table item height will disabled.',
step: 'Step(px)',
no_function: 'Function not enterplease input.'
},
dataset: {
scope_edit: 'Effective only when editing',
@ -2545,7 +2550,7 @@ export default {
please_key_max: 'Please key max value',
out_of_min: 'The min value cannot be less than the min integer -2³²',
out_of_max: 'The max value cannot be more than the max integer 2³²-1',
must_int: 'Please key integer',
must_int: 'Please key number',
min_out_max: 'The min value must be less than the max value',
max_out_min: 'The max value must be more than the min value'
},
@ -2867,5 +2872,14 @@ export default {
reset: 'Reset',
preview: 'Preview',
save: 'Save'
},
multi_login_lang: {
title: 'The current account is online!',
ip: 'IP',
time: 'Login time',
label: 'Prohibit multi-terminal login!',
confirm_title: 'Forced login will cause other clients to go offline',
confirm: 'Whether to force login?',
forced_offline: '`The current account is logged in on the client [${ip}],and you have been pushed off the line!`'
}
}

View File

@ -382,6 +382,7 @@ export default {
thumbnail: '縮略圖',
confirm_delete: '確認刪除',
delete_this_dashboard: '確認删除該儀錶板嗎?',
delete_this_folder: '確認删除該目錄嗎?',
confirm_stop: '確認停止',
stop_success: '停止成功',
treeselect: {
@ -1510,7 +1511,11 @@ export default {
p_right: '右對齊',
p_top: '上對齊',
p_bottom: '下對齊',
p_center: '居中'
p_center: '居中',
table_auto_break_line: '自動換行',
table_break_line_tip: '開啟自動換行,表格行高設置將失效',
step: '步長(px)',
no_function: '函數尚未支持直接引用,請在字段表達式中手動輸入。'
},
dataset: {
scope_edit: '僅編輯時生效',
@ -2539,7 +2544,7 @@ export default {
please_key_max: '請輸入最大值',
out_of_min: '最小值不能小於最小整數-2³²',
out_of_max: '最大值不能大於最大整數2³²-1',
must_int: '請輸入數',
must_int: '請輸入',
min_out_max: '最小值必須小於最大值',
max_out_min: '最大值必須大於最小值'
},
@ -2860,5 +2865,14 @@ export default {
reset: '重置',
preview: '預覽',
save: '保存'
},
multi_login_lang: {
title: '當前賬號已在線!',
ip: 'IP',
time: '登錄時間',
label: '禁止多端登錄!',
confirm_title: '強行登錄會導致其他客戶端掉線',
confirm: '是否強行登錄?',
forced_offline: '`當前賬號在客戶端【${ip}】登錄,您已被擠下線!`'
}
}

View File

@ -1,3 +1,5 @@
import { $confirm } from '@/utils/message'
export default {
fu: {
search_bar: {
@ -381,6 +383,7 @@ export default {
thumbnail: '缩略图',
confirm_delete: '确认删除',
delete_this_dashboard: '确认删除该仪表板吗?',
delete_this_folder: '确认删除该目录吗?',
confirm_stop: '确认停止',
stop_success: '停止成功',
treeselect: {
@ -1509,7 +1512,11 @@ export default {
p_right: '右对齐',
p_top: '上对齐',
p_bottom: '下对齐',
p_center: '居中'
p_center: '居中',
table_auto_break_line: '自动换行',
table_break_line_tip: '开启自动换行,表格行高设置将失效',
step: '步长(px)',
no_function: '函数尚未支持直接引用,请在字段表达式中手动输入。'
},
dataset: {
scope_edit: '仅编辑时生效',
@ -2539,7 +2546,7 @@ export default {
please_key_max: '请输入最大值',
out_of_min: '最小值不能小于最小整数-2³²',
out_of_max: '最大值不能大于最大整数2³²-1',
must_int: '请输入数',
must_int: '请输入',
min_out_max: '最小值必须小于最大值',
max_out_min: '最大值必须大于最小值'
},
@ -2860,5 +2867,14 @@ export default {
reset: '重置',
preview: '预览',
save: '保存'
},
multi_login_lang: {
title: '当前账号已在线!',
ip: 'IP',
time: '登录时间',
label: '禁止多端登录!',
confirm_title: '强行登录会导致其他客户端掉线',
confirm: '是否强行登录?',
forced_offline: '`当前账号在客户端【${ip}】登录,您已被挤下线!`'
}
}

View File

@ -239,6 +239,9 @@ export default {
},
mounted() {
window.addEventListener('beforeunload', (e) => this.beforeunloadHandler(e))
window.addEventListener('unload', (e) => this.unloadHandler(e))
this.initCurrentRoutes()
bus.$on('set-top-menu-info', this.setTopMenuInfo)
bus.$on('set-top-menu-active-info', this.setTopMenuActiveInfo)
@ -251,6 +254,9 @@ export default {
})
},
beforeDestroy() {
window.removeEventListener('beforeunload', (e) => this.beforeunloadHandler(e))
window.removeEventListener('unload', (e) => this.unloadHandler(e))
bus.$off('set-top-menu-info', this.setTopMenuInfo)
bus.$off('set-top-menu-active-info', this.setTopMenuActiveInfo)
bus.$off('set-top-text-info', this.setTopTextInfo)
@ -269,6 +275,16 @@ export default {
})
},
methods: {
beforeunloadHandler() {
this.beforeUnload_time = new Date().getTime()
},
unloadHandler(e) {
this.gap_time = new Date().getTime() - this.beforeUnload_time
if (this.gap_time <= 5) {
this.logout().then(res => {})
}
},
// store
initCurrentRoutes() {
const {

View File

@ -61,6 +61,7 @@ import DeMainContainer from '@/components/dataease/DeMainContainer'
import DeContainer from '@/components/dataease/DeContainer'
import DeAsideContainer from '@/components/dataease/DeAsideContainer'
import bus from '@/utils/bus'
import { showMultiLoginMsg } from '@/utils/index'
import { needModifyPwd, removePwdTips } from '@/api/user'
@ -131,11 +132,22 @@ export default {
},
mounted() {
bus.$on('PanelSwitchComponent', this.panelSwitchComponent)
bus.$on('web-seize-topic-call', this.webMsgTopicCall)
},
beforeDestroy() {
bus.$off('PanelSwitchComponent', this.panelSwitchComponent)
bus.$off('web-seize-topic-call', this.webMsgTopicCall)
},
created() {
showMultiLoginMsg()
},
methods: {
webMsgTopicCall(param) {
const ip = param
const msg = this.$t('multi_login_lang.forced_offline')
this.$error(eval(msg))
bus.$emit('sys-logout')
},
panelSwitchComponent(c) {
this.componentName = c.name
},

View File

@ -1,4 +1,10 @@
import Cookies from 'js-cookie'
import i18n from '@/lang'
import { $error, $confirm } from '@/utils/message'
import { seizeLogin } from '@/api/user'
import router from '@/router'
import store from '@/store'
import { Loading } from 'element-ui'
export function timeSection(date, type, labelFormat = 'yyyy-MM-dd') {
if (!date) {
return null
@ -352,3 +358,45 @@ export const inOtherPlatform = () => {
}
return false
}
export const showMultiLoginMsg = () => {
const multiLoginError1 = Cookies.get('MultiLoginError1')
if (multiLoginError1) {
Cookies.remove('MultiLoginError1')
const infos = JSON.parse(multiLoginError1)
const content = infos.map(info => buildMultiLoginErrorItem(info)).join('</br>')
let msgContent = '<strong>' + i18n.t('multi_login_lang.title') + '</strong>'
msgContent += content + '<p>' + i18n.t('multi_login_lang.label') + '</p>'
$error(msgContent, 10000, true)
}
const multiLoginError2 = Cookies.get('MultiLoginError2')
if (multiLoginError2) {
const infos = JSON.parse(multiLoginError2)
Cookies.remove('MultiLoginError2')
const content = infos.map(info => buildMultiLoginErrorItem(info)).join('</br>')
let msgContent = '<strong>' + i18n.t('multi_login_lang.confirm_title') + '</strong>'
msgContent += content + '<p>' + i18n.t('multi_login_lang.confirm') + '</p>'
$confirm(msgContent, () => seize(infos[0]), {
dangerouslyUseHTMLString: true
})
}
}
const seize = model => {
const loadingInstance = Loading.service({})
const token = model.token
const param = {
token
}
seizeLogin(param).then(res => {
const resultToken = res.data.token
store.dispatch('user/refreshToken', resultToken)
router.push('/')
loadingInstance.close()
})
}
const buildMultiLoginErrorItem = (info) => {
if (!info) return null
const ip = i18n.t('multi_login_lang.ip')
const time = i18n.t('multi_login_lang.time')
return '<p>' + ip + ': ' + info.ip + ', ' + time + ': ' + new Date(info.loginTime).format('yyyy-MM-dd hh:mm:ss') + '</p>'
}

View File

@ -47,12 +47,13 @@ export const $warning = (message, duration) => {
})
}
export const $error = (message, duration) => {
export const $error = (message, duration, useHtml) => {
Message.error({
message: message,
type: 'error',
showClose: true,
duration: duration || 10000
duration: duration || 10000,
dangerouslyUseHTMLString: useHtml
})
}

View File

@ -118,7 +118,7 @@ service.interceptors.response.use(response => {
if (msg.length > 600) {
msg = msg.slice(0, 600)
}
!config.hideMsg && (!headers['authentication-status']) && $error(msg)
!config.hideMsg && (!headers['authentication-status']) && !msg?.startsWith("MultiLoginError") && $error(msg)
return Promise.reject(config.url === '/dataset/table/sqlPreview' ? msg : error)
})
const checkDownError = response => {

View File

@ -77,6 +77,7 @@ export const DEFAULT_SIZE = {
tableColumnWidth: 100,
tableHeaderAlign: 'left',
tableItemAlign: 'right',
tableAutoBreakLine: false,
gaugeMinType: 'fix', // fix or dynamic
gaugeMinField: {
id: '',
@ -462,7 +463,8 @@ export const DEFAULT_THRESHOLD = {
export const DEFAULT_SCROLL = {
open: false,
row: 1,
interval: 2000
interval: 2000,
step: 50
}
// chart config
export const BASE_BAR = {

View File

@ -200,13 +200,31 @@ export function getLabel(chart) {
f.formatterCfg.thousandSeparator = false
}
res = valueFormatter(param.value, f.formatterCfg)
} else if (equalsAny(chart.type, 'bar-group', 'bar-group-stack')) {
} else if (equalsAny(chart.type, 'bar-group')) {
const f = yAxis[0]
if (f.formatterCfg) {
res = valueFormatter(param.value, f.formatterCfg)
} else {
res = valueFormatter(param.value, formatterItem)
}
} else if (equalsAny(chart.type, 'bar-group-stack')) {
const f = yAxis[0]
let formatterCfg = formatterItem
if (f.formatterCfg) {
formatterCfg = f.formatterCfg
}
const labelContent = l.labelContent ?? ['quota']
const contentItems = []
if (labelContent.includes('group')) {
contentItems.push(param.group)
}
if (labelContent.includes('stack')) {
contentItems.push(param.category)
}
if (labelContent.includes('quota')) {
contentItems.push(valueFormatter(param.value, formatterCfg))
}
res = contentItems.join('\n')
} else {
for (let i = 0; i < yAxis.length; i++) {
const f = yAxis[i]
@ -363,7 +381,14 @@ export function getTooltip(chart) {
if (chart.type === 'bar-group') {
obj = { name: param.category, value: param.value }
} else {
obj = { name: param.group, value: param.value }
let name = ''
if (param.group) {
name = param.name + '-'
}
if (param.category) {
name += param.category
}
obj = { name: name, value: param.value }
}
for (let i = 0; i < yAxis.length; i++) {
const f = yAxis[i]

View File

@ -886,7 +886,8 @@ export const TYPE_CONFIGS = [
'show',
'fontSize',
'color',
'position-v'
'position-v',
'labelContent'
],
'tooltip-selector-ant-v': [
'show',
@ -1847,6 +1848,7 @@ export const TYPE_CONFIGS = [
'tableItemBgColor',
'tableHeaderFontColor',
'tableFontColor',
'tableBorderColor',
'tableScrollBarColor',
'alpha'
],
@ -1857,7 +1859,8 @@ export const TYPE_CONFIGS = [
'tableItemHeight',
'tableColumnWidth',
'showIndex',
'indexLabel'
'indexLabel',
'tableAutoBreakLine'
],
'title-selector': [
'show',
@ -1887,6 +1890,7 @@ export const TYPE_CONFIGS = [
'tableItemBgColor',
'tableHeaderFontColor',
'tableFontColor',
'tableBorderColor',
'tableScrollBarColor',
'alpha'
],
@ -1899,7 +1903,8 @@ export const TYPE_CONFIGS = [
'tableItemHeight',
'tableColumnWidth',
'showIndex',
'indexLabel'
'indexLabel',
'tableAutoBreakLine'
],
'title-selector': [
'show',
@ -3549,3 +3554,13 @@ export function resetRgbOpacity(sourceColor, times) {
}
return sourceColor
}
export function getDefaultLabelContent(chart) {
if (chart?.type?.includes('pie')) {
return ['dimension', 'proportion']
}
if (chart?.type?.includes('bar')) {
return ['quota']
}
return []
}

View File

@ -31,6 +31,7 @@
</el-form-item>
<span v-show="scrollForm.open">
<el-form-item
v-show="!isAutoBreakLine"
:label="$t('chart.row')"
class="form-item"
>
@ -43,6 +44,20 @@
@change="changeScrollCfg"
/>
</el-form-item>
<el-form-item
v-show="isAutoBreakLine"
:label="$t('chart.step')"
class="form-item"
>
<el-input-number
v-model="scrollForm.step"
:min="1"
:max="10000"
:precision="0"
size="mini"
@change="changeScrollCfg"
/>
</el-form-item>
<el-form-item
:label="$t('chart.interval') + '(ms)'"
class="form-item"
@ -63,7 +78,7 @@
</template>
<script>
import { DEFAULT_SCROLL } from '@/views/chart/chart/chart'
import { DEFAULT_SCROLL, DEFAULT_SIZE } from '@/views/chart/chart/chart'
export default {
name: 'ScrollCfg',
@ -75,7 +90,8 @@ export default {
},
data() {
return {
scrollForm: JSON.parse(JSON.stringify(DEFAULT_SCROLL))
scrollForm: JSON.parse(JSON.stringify(DEFAULT_SCROLL)),
isAutoBreakLine: false
}
},
watch: {
@ -100,10 +116,26 @@ export default {
}
if (senior.scrollCfg) {
this.scrollForm = senior.scrollCfg
this.scrollForm.step = senior.scrollCfg.step ? senior.scrollCfg.step : DEFAULT_SCROLL.step
} else {
this.scrollForm = JSON.parse(JSON.stringify(DEFAULT_SCROLL))
}
}
if (chart.customAttr) {
let customAttr = null
if (Object.prototype.toString.call(chart.customAttr) === '[object Object]') {
customAttr = JSON.parse(JSON.stringify(chart.customAttr))
} else {
customAttr = JSON.parse(chart.customAttr)
}
if (customAttr.size) {
if (this.chart.render === 'antv') {
this.isAutoBreakLine = false
} else {
this.isAutoBreakLine = customAttr.size.tableAutoBreakLine ? customAttr.size.tableAutoBreakLine : DEFAULT_SIZE.tableAutoBreakLine
}
}
}
},
changeScrollCfg() {
this.$emit('onScrollCfgChange', this.scrollForm)

View File

@ -314,11 +314,6 @@ export default {
{ name: this.$t('chart.reserve_zero'), value: 0 },
{ name: this.$t('chart.reserve_one'), value: 1 },
{ name: this.$t('chart.reserve_two'), value: 2 }
],
labelContentOptions: [
{ name: this.$t('chart.dimension'), value: 'dimension' },
{ name: this.$t('chart.quota'), value: 'quota' },
{ name: this.$t('chart.proportion'), value: 'proportion' }
]
}
},
@ -403,6 +398,25 @@ export default {
showProperty(property) {
return this.propertyInner.includes(property)
}
},
computed: {
labelContentOptions() {
if (this.chart.type.includes('pie')) {
return [
{ name: this.$t('chart.dimension'), value: 'dimension' },
{ name: this.$t('chart.quota'), value: 'quota' },
{ name: this.$t('chart.proportion'), value: 'proportion' }
]
}
if (this.chart.type.includes('bar')) {
return [
{ name: this.$t('chart.chart_group'), value: 'group' },
{ name: this.$t('chart.stack_item'), value: 'stack' },
{ name: this.$t('chart.quota'), value: 'quota' }
]
}
return []
}
}
}
</script>

View File

@ -283,6 +283,30 @@
/>
</el-select>
</el-form-item>
<el-form-item
v-show="showProperty('tableAutoBreakLine')"
label-width="100px"
:label="$t('chart.table_auto_break_line')"
class="form-item"
>
<el-checkbox
v-model="sizeForm.tableAutoBreakLine"
@change="changeBarSizeCase('tableAutoBreakLine')"
>{{ $t('chart.open') }}</el-checkbox>
<el-tooltip
class="item"
effect="dark"
placement="bottom"
>
<div slot="content">
{{ $t('chart.table_break_line_tip') }}
</div>
<i
class="el-icon-info"
style="cursor: pointer;color: gray;font-size: 12px;"
/>
</el-tooltip>
</el-form-item>
<el-form-item
v-show="showProperty('tableTitleFontSize')"
label-width="100px"
@ -345,6 +369,7 @@
>
<el-slider
v-model="sizeForm.tableItemHeight"
:disabled="sizeForm.tableAutoBreakLine"
:min="36"
:max="100"
show-input
@ -1127,6 +1152,8 @@ export default {
this.sizeForm.hPosition = this.sizeForm.hPosition ? this.sizeForm.hPosition : DEFAULT_SIZE.hPosition
this.sizeForm.vPosition = this.sizeForm.vPosition ? this.sizeForm.vPosition : DEFAULT_SIZE.vPosition
this.sizeForm.tableAutoBreakLine = this.sizeForm.tableAutoBreakLine ? this.sizeForm.tableAutoBreakLine : DEFAULT_SIZE.tableAutoBreakLine
}
}
},

View File

@ -4,7 +4,10 @@
:style="bg_class"
style="padding: 8px;width: 100%;height: 100%;overflow: hidden;"
>
<el-row style="height: 100%;">
<el-row
style="height: 100%;"
:style="cssVars"
>
<p
v-show="title_show"
ref="title"
@ -93,7 +96,7 @@
<script>
import { hexColorToRGBA } from '../../chart/util'
import eventBus from '@/components/canvas/utils/eventBus'
import { DEFAULT_COLOR_CASE, DEFAULT_SIZE, NOT_SUPPORT_PAGE_DATASET } from '@/views/chart/chart/chart'
import { DEFAULT_COLOR_CASE, DEFAULT_SCROLL, DEFAULT_SIZE, NOT_SUPPORT_PAGE_DATASET } from '@/views/chart/chart/chart'
import { mapState } from 'vuex'
import DePagination from '@/components/deCustomCm/pagination.js'
@ -178,7 +181,13 @@ export default {
color: '#606266'
},
not_support_page_dataset: NOT_SUPPORT_PAGE_DATASET,
mergeCells: []
mergeCells: [],
cssStyleParams: {
borderColor: DEFAULT_COLOR_CASE.tableBorderColor,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}
}
},
computed: {
@ -208,7 +217,16 @@ export default {
},
...mapState([
'previewCanvasScale'
])
]),
cssVars() {
return {
'--color': this.cssStyleParams.borderColor,
'--overflow': this.cssStyleParams.overflow,
'--text-overflow': this.cssStyleParams.textOverflow,
'--white-space': this.cssStyleParams.whiteSpace
}
}
},
watch: {
chart: function() {
@ -329,6 +347,7 @@ export default {
calcHeightRightNow() {
this.$nextTick(() => {
if (this.$refs.tableContainer) {
const attr = JSON.parse(this.chart.customAttr)
let pageHeight = 0
if (this.showPage) {
pageHeight = 36
@ -338,23 +357,32 @@ export default {
let tableHeight
if (this.chart.data) {
if (this.chart.type === 'table-info') {
tableHeight = (this.currentPage.pageSize + 2) * 36 - pageHeight
if (this.showPage) {
tableHeight = this.currentPage.pageSize * attr.size.tableItemHeight + attr.size.tableTitleHeight
} else {
tableHeight = this.chart.data.tableRow.length * attr.size.tableItemHeight + attr.size.tableTitleHeight
}
} else if (this.chart.data.detailFields?.length) {
let rowLength = 0
this.chart.data.tableRow.forEach(row => {
rowLength += (row?.details?.length || 1)
})
tableHeight = (rowLength + 2) * 36 - pageHeight
tableHeight = rowLength * attr.size.tableItemHeight + 2 * attr.size.tableTitleHeight
} else {
tableHeight = (this.chart.data.tableRow.length + 2) * 36 - pageHeight
tableHeight = this.chart.data.tableRow.length * attr.size.tableItemHeight + 2 * attr.size.tableTitleHeight
}
} else {
tableHeight = 0
}
if (tableHeight > tableMaxHeight) {
const breakLine = attr.size.tableAutoBreakLine ? attr.size.tableAutoBreakLine : DEFAULT_SIZE.tableAutoBreakLine
if (breakLine) {
this.height = tableMaxHeight + 'px'
} else {
this.height = 'auto'
if (tableHeight > tableMaxHeight) {
this.height = tableMaxHeight + 'px'
} else {
this.height = 'auto'
}
}
if (this.enableScroll) {
@ -379,6 +407,7 @@ export default {
this.table_item_class.color = customAttr.color.tableFontColor
this.table_item_class.background = hexColorToRGBA(customAttr.color.tableItemBgColor, customAttr.color.alpha)
this.scrollBarColor = customAttr.color.tableScrollBarColor ? customAttr.color.tableScrollBarColor : DEFAULT_COLOR_CASE.tableScrollBarColor
this.cssStyleParams.borderColor = customAttr.color.tableBorderColor ? customAttr.color.tableBorderColor : DEFAULT_COLOR_CASE.tableBorderColor
}
if (customAttr.size) {
this.table_header_class.fontSize = customAttr.size.tableTitleFontSize + 'px'
@ -400,6 +429,17 @@ export default {
} else {
this.indexLabel = customAttr.size.indexLabel
}
const autoBreakLine = customAttr.size.tableAutoBreakLine ? customAttr.size.tableAutoBreakLine : DEFAULT_SIZE.tableAutoBreakLine
if (autoBreakLine) {
this.cssStyleParams.overflow = 'hidden'
this.cssStyleParams.textOverflow = 'auto'
this.cssStyleParams.whiteSpace = 'normal'
} else {
this.cssStyleParams.overflow = 'hidden'
this.cssStyleParams.textOverflow = 'ellipsis'
this.cssStyleParams.whiteSpace = 'nowrap'
}
}
this.table_item_class_stripe = JSON.parse(JSON.stringify(this.table_item_class))
//
@ -552,8 +592,18 @@ export default {
if (rowHeight < 36) {
rowHeight = 36
}
const attr = JSON.parse(this.chart.customAttr)
const breakLine = attr.size.tableAutoBreakLine ? attr.size.tableAutoBreakLine : DEFAULT_SIZE.tableAutoBreakLine
this.scrollTimer = setInterval(() => {
const top = rowHeight * senior.scrollCfg.row
let top = 0
if (breakLine) {
top = senior.scrollCfg.step ? senior.scrollCfg.step : DEFAULT_SCROLL.step
} else {
top = rowHeight * senior.scrollCfg.row
}
if (scrollContainer.clientHeight + scrollContainer.scrollTop < scrollContainer.scrollHeight) {
this.scrollTop += top
} else {
@ -628,4 +678,32 @@ export default {
.table-class{
scrollbar-color: var(--scroll-bar-color) transparent;
}
.table-class {
::v-deep .elx-table.border--full .elx-body--column,
::v-deep .elx-table.border--full .elx-footer--column,
::v-deep .elx-table.border--full .elx-header--column {
background-image: linear-gradient(var(--color, #e8eaec), var(--color, #e8eaec)), linear-gradient(var(--color, #e8eaec), var(--color, #e8eaec)) !important;
}
::v-deep .elx-table--border-line {
border: 1px solid var(--color, #e8eaec) !important;
}
::v-deep .elx-table .elx-table--header-wrapper .elx-table--header-border-line {
border-bottom: 1px solid var(--color, #e8eaec) !important;
}
::v-deep .elx-table .elx-table--footer-wrapper {
border-top: 1px solid var(--color, #e8eaec) !important;
}
::v-deep .elx-checkbox .elx-checkbox--label,
::v-deep .elx-radio .elx-radio--label,
::v-deep .elx-radio-button .elx-radio--label,
::v-deep .elx-table .elx-body--column.col--ellipsis:not(.col--actived) > .elx-cell,
::v-deep .elx-table .elx-footer--column.col--ellipsis:not(.col--actived) > .elx-cell,
::v-deep .elx-table .elx-header--column.col--ellipsis:not(.col--actived) > .elx-cell{
overflow: var(--overflow, 'hidden');
text-overflow: var(--text-overflow, 'ellipsis');
white-space: var(--white-space, 'nowrap');
}
}
</style>

View File

@ -513,6 +513,7 @@ import {
} from '../chart/chart'
import { checkViewTitle } from '@/components/canvas/utils/utils'
import { adaptCurTheme } from '@/components/canvas/utils/style'
import { getDefaultLabelContent } from '@/views/chart/chart/util'
export default {
name: 'Group',
@ -1060,6 +1061,7 @@ export default {
if (type === 'pie-donut-rose') {
attr.size.pieInnerRadius = Math.round(attr.size.pieOuterRadius * 0.5)
}
attr.label.labelContent = getDefaultLabelContent(view)
} else if (type.includes('line')) {
attr.label.position = 'top'
} else if (type.includes('treemap')) {

View File

@ -260,26 +260,32 @@
clearable
/>
<el-row class="function-height">
<el-popover
v-for="(item,index) in functionData"
:key="index"
class="function-pop"
placement="right"
width="200"
trigger="hover"
:open-delay="500"
>
<p class="pop-title">{{ item.name }}</p>
<p class="pop-info">{{ item.func }}</p>
<p class="pop-info">{{ item.desc }}</p>
<span
slot="reference"
class="function-style"
@click="insertParamToCodeMirror(item.func)"
>{{
item.func
}}</span>
</el-popover>
<div v-if="functionData && functionData.length > 0">
<el-popover
v-for="(item,index) in functionData"
:key="index"
class="function-pop"
placement="right"
width="200"
trigger="hover"
:open-delay="500"
>
<p class="pop-title">{{ item.name }}</p>
<p class="pop-info">{{ item.func }}</p>
<p class="pop-info">{{ item.desc }}</p>
<span
slot="reference"
class="function-style"
@click="insertParamToCodeMirror(item.func)"
>{{
item.func
}}</span>
</el-popover>
</div>
<div
v-else
class="class-na"
>{{ $t('chart.no_function') }}</div>
</el-row>
</div>
</div>

View File

@ -28,7 +28,12 @@
:data="view"
:tab-status="tabStatus"
/>
<svg-icon slot="reference" class="icon-class" style="position:absolute; margin-left: 30px; top:14px;cursor: pointer;" icon-class="icon_info_filled" />
<svg-icon
slot="reference"
class="icon-class"
style="position:absolute; margin-left: 30px; top:14px;cursor: pointer;"
icon-class="icon_info_filled"
/>
</el-popover>
<span
class="title-text view-title-name"
@ -188,7 +193,7 @@
@command="chartFieldEdit"
>
<span class="el-dropdown-link">
<i class="el-icon-s-tools"/>
<i class="el-icon-s-tools" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
@ -263,7 +268,7 @@
@command="chartFieldEdit"
>
<span class="el-dropdown-link">
<i class="el-icon-s-tools"/>
<i class="el-icon-s-tools" />
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
@ -358,7 +363,7 @@
style="padding: 6px;"
>
{{ $t('chart.change_chart_type') }}
<i class="el-icon-caret-bottom"/>
<i class="el-icon-caret-bottom" />
</el-button>
</el-popover>
</span>
@ -549,8 +554,8 @@
>
<span class="data-area-label">
<span v-if="view.type && view.type.includes('table')">{{
$t('chart.drag_block_table_data_column')
}}</span>
$t('chart.drag_block_table_data_column')
}}</span>
<span
v-else-if="view.type && (view.type.includes('bar') || view.type.includes('line') || view.type.includes('scatter') || view.type === 'chart-mix' || view.type === 'waterfall' || view.type === 'area')"
>{{ $t('chart.drag_block_type_axis') }}</span>
@ -558,18 +563,18 @@
v-else-if="view.type && view.type.includes('pie')"
>{{ $t('chart.drag_block_pie_label') }}</span>
<span v-else-if="view.type && view.type.includes('funnel')">{{
$t('chart.drag_block_funnel_split')
}}</span>
$t('chart.drag_block_funnel_split')
}}</span>
<span v-else-if="view.type && view.type.includes('radar')">{{
$t('chart.drag_block_radar_label')
}}</span>
$t('chart.drag_block_radar_label')
}}</span>
<span v-else-if="view.type && view.type === 'map'">{{ $t('chart.area') }}</span>
<span v-else-if="view.type && view.type.includes('treemap')">{{
$t('chart.drag_block_treemap_label')
}}</span>
$t('chart.drag_block_treemap_label')
}}</span>
<span v-else-if="view.type && view.type === 'word-cloud'">{{
$t('chart.drag_block_word_cloud_label')
}}</span>
$t('chart.drag_block_word_cloud_label')
}}</span>
<span v-else-if="view.type && view.type === 'label'">{{ $t('chart.drag_block_label') }}</span>
<span v-show="view.type !== 'richTextView'"> / </span>
<span v-if="view.type && view.type !== 'table-info'">{{ $t('chart.dimension') }}</span>
@ -693,8 +698,8 @@
>
<span class="data-area-label">
<span v-if="view.type && view.type.includes('table')">{{
$t('chart.drag_block_table_data_column')
}}</span>
$t('chart.drag_block_table_data_column')
}}</span>
<span
v-else-if="view.type && (view.type.includes('bar') || view.type.includes('line') || view.type.includes('scatter') || view.type === 'waterfall' || view.type === 'area')"
>{{ $t('chart.drag_block_value_axis') }}</span>
@ -702,30 +707,30 @@
v-else-if="view.type && view.type.includes('pie')"
>{{ $t('chart.drag_block_pie_angel') }}</span>
<span v-else-if="view.type && view.type.includes('funnel')">{{
$t('chart.drag_block_funnel_width')
}}</span>
$t('chart.drag_block_funnel_width')
}}</span>
<span v-else-if="view.type && view.type.includes('radar')">{{
$t('chart.drag_block_radar_length')
}}</span>
$t('chart.drag_block_radar_length')
}}</span>
<span v-else-if="view.type && view.type.includes('gauge')">{{
$t('chart.drag_block_gauge_angel')
}}</span>
$t('chart.drag_block_gauge_angel')
}}</span>
<span
v-else-if="view.type && view.type.includes('text')"
>{{ $t('chart.drag_block_label_value') }}</span>
<span v-else-if="view.type && view.type === 'map'">{{ $t('chart.chart_data') }}</span>
<span v-else-if="view.type && view.type.includes('tree')">{{
$t('chart.drag_block_treemap_size')
}}</span>
$t('chart.drag_block_treemap_size')
}}</span>
<span v-else-if="view.type && view.type === 'chart-mix'">{{
$t('chart.drag_block_value_axis_main')
}}</span>
$t('chart.drag_block_value_axis_main')
}}</span>
<span
v-else-if="view.type && view.type === 'liquid'"
>{{ $t('chart.drag_block_progress') }}</span>
<span v-else-if="view.type && view.type === 'word-cloud'">{{
$t('chart.drag_block_word_cloud_size')
}}</span>
$t('chart.drag_block_word_cloud_size')
}}</span>
<span v-show="view.type !== 'richTextView'"> / </span>
<span>{{ $t('chart.quota') }}</span>
<i
@ -1197,7 +1202,7 @@
:title="$t('panel.position_adjust_component')"
:name="'positionAdjust'"
>
<position-adjust/>
<position-adjust />
</el-collapse-item>
</el-collapse>
</div>
@ -1353,7 +1358,7 @@
width="800px"
class="dialog-css"
>
<quota-filter-editor :item="quotaItem"/>
<quota-filter-editor :item="quotaItem" />
<div
slot="footer"
class="dialog-footer"
@ -1380,7 +1385,7 @@
width="800px"
class="dialog-css"
>
<dimension-filter-editor :item="dimensionItem"/>
<dimension-filter-editor :item="dimensionItem" />
<div
slot="footer"
class="dialog-footer"
@ -1743,6 +1748,7 @@ import CalcChartFieldEdit from '@/views/chart/view/CalcChartFieldEdit'
import { equalsAny } from '@/utils/StringUtils'
import PositionAdjust from '@/views/chart/view/PositionAdjust'
import MarkMapDataEditor from '@/views/chart/components/map/MarkMapDataEditor'
import { getDefaultLabelContent } from '@/views/chart/chart/util'
export default {
name: 'ChartEdit',
@ -2002,6 +2008,7 @@ export default {
bus.$off('show-quota-edit-filter', this.showQuotaEditFilter)
bus.$off('show-quota-edit-compare', this.showQuotaEditCompare)
bus.$off('show-edit-filter', this.showEditFilter)
bus.$off('show-edit-formatter', this.valueFormatter)
bus.$off('calc-data', this.calcData)
bus.$off('plugins-calc-style', this.calcStyle)
bus.$off('plugin-chart-click', this.chartClick)
@ -2072,6 +2079,7 @@ export default {
bus.$on('show-quota-edit-filter', this.showQuotaEditFilter)
bus.$on('show-quota-edit-compare', this.showQuotaEditCompare)
bus.$on('show-edit-filter', this.showEditFilter)
bus.$on('show-edit-formatter', this.valueFormatter)
bus.$on('calc-data', this.calcData)
bus.$on('plugins-calc-style', this.calcStyle)
bus.$on('plugin-chart-click', this.chartClick)
@ -2892,19 +2900,22 @@ export default {
//
changeChart() {
this.view.dataFrom = 'dataset'
const optType = this.view.tableId === this.changeTable.id ? 'same' : 'change'
// this.save(true, 'chart', false)
this.view.tableId = this.changeTable.id
//
post('/chart/field/deleteByChartId/' + this.param.id + '/' + this.panelInfo.id, null).then(response => {
// reset gauge
this.view.customAttr.size.gaugeMinType = 'fix'
this.view.customAttr.size.gaugeMaxType = 'fix'
this.calcData(true, 'chart', false)
this.initTableData(this.view.tableId, optType)
//
if (optType === 'change') {
this.view.dataFrom = 'dataset'
this.view.tableId = this.changeTable.id
post('/chart/field/deleteByChartId/' + this.param.id + '/' + this.panelInfo.id, null).then(response => {
// reset gauge
this.view.customAttr.size.gaugeMinType = 'fix'
this.view.customAttr.size.gaugeMaxType = 'fix'
this.calcData(true, 'chart', false)
this.initTableData(this.view.tableId, optType)
this.closeChangeChart()
})
} else {
this.closeChangeChart()
})
}
},
fieldFilter(val) {
@ -3303,6 +3314,7 @@ export default {
this.view.customAttr.label.position = 'middle'
}
}
customAttr.label.labelContent = getDefaultLabelContent(this.view)
// reset custom colors
this.view.customAttr.color.seriesColors = []
},

View File

@ -236,24 +236,30 @@
clearable
/>
<el-row class="function-height">
<el-popover
v-for="(item, index) in functionData"
:key="index"
class="function-pop"
placement="right"
width="200"
trigger="hover"
:open-delay="500"
>
<p class="pop-title">{{ item.name }}</p>
<p class="pop-info">{{ item.func }}</p>
<p class="pop-info">{{ item.desc }}</p>
<span
slot="reference"
class="function-style"
@click="insertParamToCodeMirror(item.func)"
>{{ item.func }}</span>
</el-popover>
<div v-if="functionData && functionData.length > 0">
<el-popover
v-for="(item, index) in functionData"
:key="index"
class="function-pop"
placement="right"
width="200"
trigger="hover"
:open-delay="500"
>
<p class="pop-title">{{ item.name }}</p>
<p class="pop-info">{{ item.func }}</p>
<p class="pop-info">{{ item.desc }}</p>
<span
slot="reference"
class="function-style"
@click="insertParamToCodeMirror(item.func)"
>{{ item.func }}</span>
</el-popover>
</div>
<div
v-else
class="class-na"
>{{ $t('chart.no_function') }}</div>
</el-row>
</div>
</div>

View File

@ -212,7 +212,7 @@
import { encrypt } from '@/utils/rsaEncrypt'
import { ldapStatus, oidcStatus, getPublicKey, pluginLoaded, defaultLoginType, wecomStatus, dingtalkStatus, larkStatus, larksuiteStatus, casStatus, casLoginPage } from '@/api/user'
import { getSysUI } from '@/utils/auth'
import { changeFavicon } from '@/utils/index'
import { changeFavicon, showMultiLoginMsg } from '@/utils/index'
import { initTheme } from '@/utils/ThemeUtil'
import PluginCom from '@/views/system/plugin/PluginCom'
import Cookies from 'js-cookie'
@ -395,6 +395,7 @@ export default {
this.$error(Cookies.get('LarksuiteError'))
}
this.clearLarksuiteMsg()
showMultiLoginMsg()
},
methods: {
@ -476,14 +477,18 @@ export default {
this.$store.dispatch('user/login', user).then(() => {
this.$router.push({ path: this.redirect || '/' })
this.loading = false
}).catch(() => {
}).catch((e) => {
this.loading = false
e?.response?.data?.message?.startsWith('MultiLoginError') && this.showMessage()
})
} else {
return false
}
})
},
showMessage() {
showMultiLoginMsg()
},
changeLoginType(val) {
if (val !== 2 && val !== 7) return
this.clearOidcMsg()

View File

@ -92,7 +92,7 @@ export default {
.filter-field {
border-radius: 4px;
height: 40px;
overflow-x: overlay;
.field-content {
position: relative;
display: table;

View File

@ -850,7 +850,7 @@ export default {
delete(data) {
const params = {
title: 'commons.delete_this_dashboard',
title: data.nodeType === 'folder'?'commons.delete_this_folder':'commons.delete_this_dashboard',
type: 'danger',
cb: () => {
delGroup(data.id).then((response) => {
@ -899,7 +899,7 @@ export default {
groupTree(this.groupForm, !userCache).then((res) => {
localStorage.setItem('panel-main-tree', JSON.stringify(res.data || []))
if (!userCache) {
this.tData = res.data
this.tData = res.data || []
}
if (this.responseSource === 'appApply') {
this.fromAppActive()

View File

@ -159,6 +159,13 @@
component-name="LoginLimitSetting"
/>
<plugin-com
v-if="isPluginLoaded"
ref="MultiLoginLimit"
:form="formInline"
component-name="MultiLoginLimit"
/>
<plugin-com
v-if="isPluginLoaded && scanOpen"
ref="ScanLimitSetting"
@ -429,6 +436,12 @@ export default {
paramValue: this.formInline.scanCreateUser,
type: 'text',
sort: 3
},
{
paramKey: 'loginlimit.multiLogin',
paramValue: this.formInline.multiLogin,
type: 'text',
sort: 3
}
]

View File

@ -60,7 +60,7 @@
<span class="content_middle_title">{{ $t('wizard.latest_developments') }}</span>
<div class="content_middle_more"><a
target="_blank"
href="https://blog.fit2cloud.com/?cat=321"
href="https://blog.fit2cloud.com/categories/dataease"
>{{ $t('wizard.more') }}<i class="el-icon-arrow-right"/></a></div>
</el-row>
<el-row>

View File

@ -2,7 +2,6 @@ import bus from '@/utils/bus'
import SockJS from 'sockjs-client'
import Stomp from 'stompjs'
import store from '@/store'
class DeWebsocket {
constructor() {
this.ws_url = '/websocket'
@ -11,6 +10,10 @@ class DeWebsocket {
{
topic: '/web-msg-topic',
event: 'web-msg-topic-call'
},
{
topic: '/web-seize-topic',
event: 'web-seize-topic-call'
}
]
this.timer = null

View File

@ -30,8 +30,7 @@ module.exports = {
overlay: {
warnings: false,
errors: true
},
before: require('./mock/mock-server.js')
}
},
pages: {

View File

@ -2,7 +2,7 @@ import request from '@/common/js/request'
export function login(data) {
return request({
url: '/api/auth/login',
url: '/api/auth/mobileLogin',
method: 'post',
data
})

View File

@ -60,10 +60,13 @@ checkAuth(error.response)
} else {
msg = error.message
}
if (msg?.startsWith('MultiLoginError')) {
return Promise.reject(error)
}
uni.showToast({
icon: 'error',
title: msg
});
})
return Promise.reject(error)
})
const logout = () => {

View File

@ -31,7 +31,8 @@
"loginbtn": "Login",
"pwdFmtError": "Password Must More Than 6 Characters",
"uOrpwdError": "Invalid Account Or Password",
"accFmtError": "Account Must More Than 1 Characters"
"accFmtError": "Account Must More Than 1 Characters",
"multiLogin": "The current account is online,Prohibit multi-terminal login"
},
"home": {
"tab1": "My Favorites",

View File

@ -31,7 +31,8 @@
"loginbtn": "登录",
"pwdFmtError": "密码最短为1个字符",
"accFmtError": "账号最短为1个字符",
"uOrpwdError": "无效账号或密码"
"uOrpwdError": "无效账号或密码",
"multiLogin": "当前账号已在线,禁止多端登录"
},
"home": {
"tab1": "我的收藏",

View File

@ -32,7 +32,8 @@
"loginbtn": "登錄",
"pwdFmtError": "密碼最短為6個字符",
"uOrpwdError": "無效賬號或密碼",
"accFmtError": "帳號最短為1個字符"
"accFmtError": "帳號最短為1個字符",
"multiLogin": "當前賬號已在線,禁止多端登錄"
},
"home": {
"tab1": "我的收藏",

View File

@ -106,9 +106,13 @@
}).catch(error => {
this.loginBtnLoading = false
let msg = error.response.data.message
if (msg?.startsWith('MultiLoginError')) {
msg = this.$t('login.multiLogin')
}
uni.showToast({
icon: 'error',
title: error.response.data.message,
title: msg,
});
})
},

View File

@ -36,7 +36,8 @@
</template>
<script>
import { setToken, getLanguage } from '@/common/utils'
import {getUserInfo, setUserInfo} from '@/common/utils'
import { getUserInfo, setUserInfo } from '@/common/utils'
import { logout } from '@/api/auth'
export default {
data() {
return {
@ -85,11 +86,15 @@ export default {
});
},
logout() {
setToken(null)
setUserInfo(null)
uni.reLaunch({
url: '/'
});
const callBack = () => {
setToken(null)
setUserInfo(null)
uni.reLaunch({
url: '/'
});
}
logout().then(res => {callBack()}).catch(e => {callBack()})
}
}
}

View File

@ -5,10 +5,10 @@ module.exports = {
LinkTokenKey: 'LINK-PWD-TOKEN',
title: 'DataEase',
WHITE_LIST: [
'/api/auth/login',
'/api/auth/getPublicKey',
'/system/ui/info',
'/system/ui/image/'
'/api/auth/login',
'/api/auth/getPublicKey',
'/system/ui/info',
'/system/ui/image/'
],
RECENT_KEY: 'recently',