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

This commit is contained in:
dataeaseShu 2023-11-27 16:59:17 +08:00
commit 30479ddfc1
73 changed files with 2344 additions and 2555 deletions

View File

@ -14,18 +14,18 @@ public class MybatisPlusGenerator {
* 第一 我嫌麻烦
* 第二 后面配置会放到nacos读起来更麻烦了
*/
private static final String url = "jdbc:mysql://127.0.0.1:3306/de_standalone?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false";
private static final String url = "jdbc:mysql://localhost:3306/dataease?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false";
private static final String username = "root";
private static final String password = "Password123@mysql";
private static final String password = "123456";
/**
* 业务模块例如datasource,dataset,panel等
*/
private static final String busi = "template";
private static final String busi = "operation";
/**
* 这是要生成代码的表名称
*/
private static final String TABLE_NAME = "visualization_template_extend_data";
private static final String TABLE_NAME = "core_opt_recent";
/**
* 下面两个配置基本上不用动

View File

@ -9,7 +9,7 @@ public class OptConstants {
public static final class OPT_TYPE {
//新建
public static final int NEW = 1;
//
//
public static final int UPDATE = 2;
//删除
public static final int DELETE = 3;
@ -26,6 +26,8 @@ public class OptConstants {
public static final int DATASET = 4;
//数据源
public static final int DATASOURCE = 5;
//模版
public static final int TEMPLATE = 6;
}
}

View File

@ -21,12 +21,16 @@ public class DeMvcConfig implements WebMvcConfigurer {
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String workDir = FILE_PROTOCOL + ensureSuffix(WORK_DIR, FILE_SEPARATOR);
String uploadUrlPattern = ensureBoth(URL_SEPARATOR + UPLOAD_URL_PREFIX, AuthConstant.DE_API_PREFIX, URL_SEPARATOR) + "**";
registry.addResourceHandler(uploadUrlPattern)
.addResourceLocations(workDir);
registry.addResourceHandler(uploadUrlPattern).addResourceLocations(workDir);
// map
String mapDir = FILE_PROTOCOL + ensureSuffix(MAP_DIR, FILE_SEPARATOR);
String mapUrlPattern = ensureBoth(MAP_URL, AuthConstant.DE_API_PREFIX, URL_SEPARATOR) + "**";
registry.addResourceHandler(mapUrlPattern)
.addResourceLocations(mapDir);
registry.addResourceHandler(mapUrlPattern).addResourceLocations(mapDir);
String geoDir = FILE_PROTOCOL + ensureSuffix(CUSTOM_MAP_DIR, FILE_SEPARATOR);
String geoUrlPattern = ensureBoth(GEO_URL, AuthConstant.DE_API_PREFIX, URL_SEPARATOR) + "**";
registry.addResourceHandler(geoUrlPattern).addResourceLocations(geoDir);
}
}

View File

@ -16,7 +16,6 @@ import io.dataease.datasource.request.DatasourceRequest;
import io.dataease.datasource.server.EngineServer;
import io.dataease.datasource.type.*;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.utils.BeanUtils;
@ -26,6 +25,7 @@ import io.dataease.utils.LogUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.apache.calcite.adapter.jdbc.JdbcSchema;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;

View File

@ -1,194 +0,0 @@
package io.dataease.engine.func.scalar;
import io.dataease.engine.utils.Utils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ScalarFunctions {
public static String format = "yyyy-MM-dd HH:mm:ss";
public static String minuteFormat = "yyyy-MM-dd HH:mm";
public static String hourFormat = "yyyy-MM-dd HH";
public static String dateOnly = "yyyy-MM-dd";
public static String monthOnly = "yyyy-MM";
public static String yearOnly = "yyyy";
public static String timeOnly = "HH:mm:ss";
public static String date_format(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
format = get_date_format(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String de_date_format(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String str_to_date(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
format = get_date_format(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String de_str_to_date(String date, String format) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date parse = simpleDateFormat.parse(date);
return simpleDateFormat.format(parse);
} catch (Exception e) {
return null;
}
}
public static String cast_date_format(String date, String sourceFormat, String targetFormat) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
sourceFormat = get_date_format(date);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(sourceFormat);
Date parse = simpleDateFormat.parse(date);
SimpleDateFormat s = new SimpleDateFormat(targetFormat);
return s.format(parse);
} catch (Exception e) {
return null;
}
}
public static String de_cast_date_format(String date, String sourceFormat, String targetFormat) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(sourceFormat);
Date parse = simpleDateFormat.parse(date);
SimpleDateFormat s = new SimpleDateFormat(targetFormat);
return s.format(parse);
} catch (Exception e) {
return null;
}
}
public static Long unix_timestamp(String date) {
try {
if (StringUtils.isEmpty(date)) {
return null;
}
return Utils.allDateFormat2Long(date);
} catch (Exception e) {
return null;
}
}
public static String get_date_format(String date) {
// check date split '-' or '/'
String format1 = format;
String minuteFormat1 = minuteFormat;
String hourFormat1 = hourFormat;
String timeOnly1 = timeOnly;
String dateOnly1 = dateOnly;
String monthOnly1 = monthOnly;
String yearOnly1 = yearOnly;
if (date != null && date.contains("/")) {
format1 = format1.replaceAll("-", "/");
minuteFormat1 = minuteFormat1.replaceAll("-", "/");
hourFormat1 = hourFormat1.replaceAll("-", "/");
timeOnly1 = timeOnly1.replaceAll("-", "/");
dateOnly1 = dateOnly1.replaceAll("-", "/");
monthOnly1 = monthOnly1.replaceAll("-", "/");
yearOnly1 = yearOnly1.replaceAll("-", "/");
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format1);
simpleDateFormat.parse(date);
return format1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(minuteFormat1);
simpleDateFormat.parse(date);
return minuteFormat1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(hourFormat1);
simpleDateFormat.parse(date);
return hourFormat1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(timeOnly1);
simpleDateFormat.parse(date);
return timeOnly1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateOnly1);
simpleDateFormat.parse(date);
return dateOnly1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(monthOnly1);
simpleDateFormat.parse(date);
return monthOnly1;
} catch (Exception e) {
}
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(yearOnly1);
simpleDateFormat.parse(date);
return yearOnly1;
} catch (Exception e) {
}
return format1;
}
public static String from_unixtime(Long timestamp, String format) {
try {
if (ObjectUtils.isEmpty(timestamp)) {
return null;
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
Date date = new Date(timestamp);
return simpleDateFormat.format(date);
} catch (Exception e) {
return null;
}
}
public static String concat(String str1, String str2) {
return str1 + str2;
}
}

View File

@ -6,8 +6,8 @@ import io.dataease.api.dataset.union.model.SQLMeta;
import io.dataease.api.dataset.union.model.SQLObj;
import io.dataease.dto.dataset.DatasetTableFieldDTO;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.engine.utils.Utils;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

View File

@ -5,8 +5,8 @@ import io.dataease.api.dataset.union.model.SQLMeta;
import io.dataease.api.dataset.union.model.SQLObj;
import io.dataease.dto.dataset.DatasetTableFieldDTO;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.engine.utils.Utils;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@ -55,7 +55,7 @@ public class ExtWhere2Str {
if (field.getDeType() == 1) {
String date_format;
if (StringUtils.containsIgnoreCase(request.getOperator(), "between")) {
date_format = ScalarFunctions.format;
date_format = "yyyy-MM-dd HH:mm:ss";
} else {
date_format = ScalarFunctions.get_date_format(value.get(0));
}

View File

@ -8,8 +8,8 @@ import io.dataease.api.permissions.dataset.dto.DatasetRowPermissionsTreeObj;
import io.dataease.dto.dataset.DatasetTableFieldDTO;
import io.dataease.engine.constant.ExtFieldConstant;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.engine.func.scalar.ScalarFunctions;
import io.dataease.engine.utils.Utils;
import org.apache.calcite.func.scalar.ScalarFunctions;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

View File

@ -0,0 +1,13 @@
package io.dataease.map.bo;
import io.dataease.map.dao.auto.entity.Area;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
@EqualsAndHashCode(callSuper = true)
@Data
public class AreaBO extends Area implements Serializable {
private boolean custom = false;
}

View File

@ -0,0 +1,15 @@
package io.dataease.map.dao.ext.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class CoreAreaCustom implements Serializable {
private String id;
private String pid;
private String name;
}

View File

@ -0,0 +1,9 @@
package io.dataease.map.dao.ext.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import io.dataease.map.dao.ext.entity.CoreAreaCustom;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CoreAreaCustomMapper extends BaseMapper<CoreAreaCustom> {
}

View File

@ -1,17 +1,34 @@
package io.dataease.map.manage;
import cn.hutool.core.collection.CollectionUtil;
import io.dataease.api.map.dto.GeometryNodeCreator;
import io.dataease.api.map.vo.AreaNode;
import io.dataease.constant.StaticResourceConstants;
import io.dataease.exception.DEException;
import io.dataease.map.bo.AreaBO;
import io.dataease.map.dao.auto.entity.Area;
import io.dataease.map.dao.auto.mapper.AreaMapper;
import io.dataease.map.dao.ext.entity.CoreAreaCustom;
import io.dataease.map.dao.ext.mapper.CoreAreaCustomMapper;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.CommonBeanFactory;
import io.dataease.utils.LogUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static io.dataease.constant.CacheConstant.CommonCacheConstant.WORLD_MAP_CACHE;
@ -19,6 +36,8 @@ import static io.dataease.constant.CacheConstant.CommonCacheConstant.WORLD_MAP_C
public class MapManage {
private final static AreaNode WORLD;
private static final String GEO_PREFIX = "geo_";
static {
WORLD = AreaNode.builder()
.id("000")
@ -30,13 +49,34 @@ public class MapManage {
@Resource
private AreaMapper areaMapper;
@Resource
private CoreAreaCustomMapper coreAreaCustomMapper;
public List<Area> defaultArea() {
return areaMapper.selectList(null);
}
private MapManage proxy() {
return CommonBeanFactory.getBean(MapManage.class);
}
@Cacheable(value = WORLD_MAP_CACHE, key = "'world_map'")
public AreaNode getWorldTree() {
List<Area> areas = areaMapper.selectList(null);
List<Area> areas = proxy().defaultArea();
List<AreaBO> areaBOS = areas.stream().map(item -> BeanUtils.copyBean(new AreaBO(), item)).collect(Collectors.toList());
List<CoreAreaCustom> coreAreaCustoms = coreAreaCustomMapper.selectList(null);
if (CollectionUtils.isNotEmpty(coreAreaCustoms)) {
List<AreaBO> customBoList = coreAreaCustoms.stream().map(item -> {
AreaBO areaBO = BeanUtils.copyBean(new AreaBO(), item);
areaBO.setCustom(true);
return areaBO;
}).toList();
areaBOS.addAll(customBoList);
}
WORLD.setChildren(new ArrayList<>());
var areaNodeMap = new HashMap<String, AreaNode>();
areaNodeMap.put(WORLD.getId(), WORLD);
areas.forEach(area -> {
areaBOS.forEach(area -> {
var node = areaNodeMap.get(area.getId());
if (node == null) {
node = AreaNode.builder().build();
@ -64,5 +104,80 @@ public class MapManage {
return WORLD;
}
@CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'")
@Transactional
public void saveMapGeo(GeometryNodeCreator request, MultipartFile file) {
List<Area> areas = proxy().defaultArea();
String code = getBusiGeoCode(request.getCode());
AtomicReference<String> atomicReference = new AtomicReference<>();
if (areas.stream().anyMatch(area -> {
boolean exist = area.getId().equals(code);
if (exist) {
atomicReference.set(area.getName());
}
return exist;
})) {
DEException.throwException(String.format("Area code [%s] is already exists for [%s]", code, atomicReference.get()));
}
CoreAreaCustom originData = null;
if (ObjectUtils.isNotEmpty(originData = coreAreaCustomMapper.selectById(getDaoGeoCode(code)))) {
DEException.throwException(String.format("Area code [%s] is already exists for [%s]", code, originData.getName()));
}
CoreAreaCustom coreAreaCustom = new CoreAreaCustom();
coreAreaCustom.setId(getDaoGeoCode(code));
coreAreaCustom.setPid(request.getPid());
coreAreaCustom.setName(request.getName());
coreAreaCustomMapper.insert(coreAreaCustom);
File geoFile = buildGeoFile(code);
try {
file.transferTo(geoFile);
} catch (IOException e) {
LogUtil.error(e.getMessage());
DEException.throwException(e);
}
}
@CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'")
@Transactional
public void deleteGeo(String code) {
if (!StringUtils.startsWith(code, GEO_PREFIX)) {
DEException.throwException("内置Geometry禁止删除");
}
coreAreaCustomMapper.deleteById(code);
File file = buildGeoFile(code);
if (file.exists()) {
file.delete();
}
}
private String getDaoGeoCode(String code) {
return StringUtils.startsWith(code, GEO_PREFIX) ? code : (GEO_PREFIX + code);
}
private String getBusiGeoCode(String code) {
return StringUtils.startsWith(code, GEO_PREFIX) ? code.substring(GEO_PREFIX.length()) : code;
}
private File buildGeoFile(String code) {
String id = getBusiGeoCode(code);
String customMapDir = StaticResourceConstants.CUSTOM_MAP_DIR;
String countryCode = countryCode(id);
String fileDirPath = customMapDir + "/" + countryCode + "/";
File dir = new File(fileDirPath);
if (!dir.exists()) {
dir.mkdirs();
}
String filePath = fileDirPath + id + ".json";
return new File(filePath);
}
private String countryCode(String code) {
return code.substring(0, 3);
}
}

View File

@ -0,0 +1,26 @@
package io.dataease.map.server;
import io.dataease.api.map.GeoApi;
import io.dataease.api.map.dto.GeometryNodeCreator;
import io.dataease.map.manage.MapManage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/geometry")
public class GeoServer implements GeoApi {
@Resource
private MapManage mapManage;
@Override
public void saveMapGeo(GeometryNodeCreator request, MultipartFile file) {
mapManage.saveMapGeo(request, file);
}
@Override
public void deleteGeo(String id) {
mapManage.deleteGeo(id);
}
}

View File

@ -9,7 +9,7 @@ import java.io.Serializable;
* </p>
*
* @author fit2cloud
* @since 2023-10-08
* @since 2023-11-26
*/
@TableName("core_opt_recent")
public class CoreOptRecent implements Serializable {
@ -26,6 +26,11 @@ public class CoreOptRecent implements Serializable {
*/
private Long resourceId;
/**
* 资源名称
*/
private String resourceName;
/**
* 用户ID
*/
@ -62,6 +67,14 @@ public class CoreOptRecent implements Serializable {
this.resourceId = resourceId;
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
public Long getUid() {
return uid;
}
@ -99,6 +112,7 @@ public class CoreOptRecent implements Serializable {
return "CoreOptRecent{" +
"id = " + id +
", resourceId = " + resourceId +
", resourceName = " + resourceName +
", uid = " + uid +
", resourceType = " + resourceType +
", optType = " + optType +

View File

@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
* </p>
*
* @author fit2cloud
* @since 2023-10-08
* @since 2023-11-26
*/
@Mapper
public interface CoreOptRecentMapper extends BaseMapper<CoreOptRecent> {

View File

@ -1,13 +1,21 @@
package io.dataease.operation.manage;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.commons.constants.OptConstants;
import io.dataease.operation.dao.auto.entity.CoreOptRecent;
import io.dataease.operation.dao.auto.mapper.CoreOptRecentMapper;
import io.dataease.utils.AuthUtils;
import io.dataease.utils.IDUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class CoreOptRecentManage {
@ -15,19 +23,33 @@ public class CoreOptRecentManage {
@Autowired
private CoreOptRecentMapper coreStoreMapper;
public void saveOpt(Long resourceId,int resourceType,int optType) {
public void saveOpt(Long resourceId, int resourceType, int optType) {
saveOpt(resourceId, null, resourceType, optType);
}
public void saveOpt(String resourceName, int resourceType, int optType) {
saveOpt(null, resourceName, resourceType, optType);
}
public void saveOpt(Long resourceId, String resourceName, int resourceType, int optType) {
Long uid = AuthUtils.getUser().getUserId();
QueryWrapper<CoreOptRecent> updateWrapper = new QueryWrapper<>();
updateWrapper.eq("resource_id",resourceId);
updateWrapper.eq("resource_type",resourceType);
updateWrapper.eq("uid",uid);
if (resourceId != null) {
updateWrapper.eq("resource_id", resourceId);
}
if (StringUtils.isNotEmpty(resourceName)) {
updateWrapper.eq("resource_name", resourceName);
}
updateWrapper.eq("resource_type", resourceType);
updateWrapper.eq("uid", uid);
CoreOptRecent updateParam = new CoreOptRecent();
updateParam.setOptType(optType);
updateParam.setTime(System.currentTimeMillis());
if(coreStoreMapper.update(updateParam,updateWrapper)==0){
if (coreStoreMapper.update(updateParam, updateWrapper) == 0) {
CoreOptRecent optRecent = new CoreOptRecent();
optRecent.setId(IDUtils.snowID());
optRecent.setResourceId(resourceId);
optRecent.setResourceName(resourceName);
optRecent.setResourceType(resourceType);
optRecent.setOptType(optType);
optRecent.setTime(System.currentTimeMillis());
@ -36,4 +58,17 @@ public class CoreOptRecentManage {
}
}
public Map<String, Long> findTemplateRecentUseTime() {
Long uid = AuthUtils.getUser().getUserId();
QueryWrapper<CoreOptRecent> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("resource_type", OptConstants.OPT_RESOURCE_TYPE.TEMPLATE);
queryWrapper.eq("uid", uid);
List<CoreOptRecent> result = coreStoreMapper.selectList(queryWrapper);
if (CollectionUtil.isNotEmpty(result)) {
return result.stream().collect(Collectors.toMap(CoreOptRecent::getResourceName, CoreOptRecent::getTime));
} else {
return new HashMap<>();
}
}
}

View File

@ -2,16 +2,21 @@ package io.dataease.system.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.license.config.XpackInteract;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.dao.auto.mapper.CoreSysSettingMapper;
import io.dataease.system.dao.ext.mapper.ExtCoreSysSettingMapper;
import io.dataease.utils.BeanUtils;
import io.dataease.utils.CommonBeanFactory;
import io.dataease.utils.IDUtils;
import io.dataease.utils.SystemSettingUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -71,26 +76,41 @@ public class SysParameterManage {
return null;
}
public List<SettingItemVO> groupList(String groupKey) {
public List<CoreSysSetting> groupList(String groupKey) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("pkey", groupKey);
queryWrapper.orderByAsc("sort");
List<CoreSysSetting> sysSettings = coreSysSettingMapper.selectList(queryWrapper);
if (!CollectionUtils.isEmpty(sysSettings)) {
return sysSettings.stream().map(item -> BeanUtils.copyBean(new SettingItemVO(), item)).toList();
}
return null;
return coreSysSettingMapper.selectList(queryWrapper);
}
@XpackInteract(value = "perSetting")
public List<SettingItemVO> convert(List<CoreSysSetting> sysSettings) {
return sysSettings.stream().sorted(Comparator.comparing(CoreSysSetting::getSort)).map(item -> BeanUtils.copyBean(new SettingItemVO(), item)).toList();
}
@Transactional
public void saveGroup(List<SettingItemVO> vos, String groupKey) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.likeRight("pkey", groupKey);
coreSysSettingMapper.delete(queryWrapper);
List<CoreSysSetting> sysSettings = vos.stream().map(item -> {
List<CoreSysSetting> sysSettings = vos.stream().filter(vo -> !SystemSettingUtils.xpackSetting(vo.getPkey())).map(item -> {
CoreSysSetting sysSetting = BeanUtils.copyBean(new CoreSysSetting(), item);
sysSetting.setId(IDUtils.snowID());
return sysSetting;
}).collect(Collectors.toList());
extCoreSysSettingMapper.saveBatch(sysSettings);
}
@XpackInteract(value = "perSetting", before = false)
@Transactional
public void saveBasic(List<SettingItemVO> vos) {
String key = "basic.";
proxy().saveGroup(vos, key);
}
private SysParameterManage proxy() {
return CommonBeanFactory.getBean(SysParameterManage.class);
}
}

View File

@ -3,6 +3,7 @@ package io.dataease.system.server;
import io.dataease.api.system.SysParameterApi;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.manage.SysParameterManage;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
@ -36,11 +37,12 @@ public class SysParameterServer implements SysParameterApi {
@Override
public List<SettingItemVO> queryBasicSetting() {
String key = "basic.";
return sysParameterManage.groupList(key);
List<CoreSysSetting> coreSysSettings = sysParameterManage.groupList(key);
return sysParameterManage.convert(coreSysSettings);
}
@Override
public void saveBasicSetting(List<SettingItemVO> settingItemVOS) {
sysParameterManage.saveGroup(settingItemVOS, "basic.");
sysParameterManage.saveBasic(settingItemVOS);
}
}

View File

@ -2,11 +2,13 @@ package io.dataease.template.manage;
import io.dataease.api.template.dto.TemplateManageFileDTO;
import io.dataease.api.template.dto.TemplateMarketDTO;
import io.dataease.api.template.dto.TemplateMarketPreviewInfoDTO;
import io.dataease.api.template.response.*;
import io.dataease.api.template.vo.MarketApplicationSpecVO;
import io.dataease.api.template.vo.MarketMetaDataVO;
import io.dataease.api.template.vo.TemplateCategoryVO;
import io.dataease.exception.DEException;
import io.dataease.operation.manage.CoreOptRecentManage;
import io.dataease.system.manage.SysParameterManage;
import io.dataease.utils.HttpClientConfig;
import io.dataease.utils.HttpClientUtil;
@ -16,10 +18,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -38,6 +37,9 @@ public class TemplateMarketManage {
@Resource
private SysParameterManage sysParameterManage;
@Resource
private CoreOptRecentManage coreOptRecentManage;
/**
* @param templateUrl template url
* @Description Get template file from template market
@ -62,26 +64,91 @@ public class TemplateMarketManage {
return HttpClientUtil.get(url, config);
}
private MarketTemplateV2BaseResponse templateQuery(Map<String, String> templateParams){
String result = marketGet(templateParams.get("template.url") + POSTS_API_V2, null);
MarketTemplateV2BaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateV2BaseResponse.class);
return postsResult;
}
public MarketBaseResponse searchTemplate() {
try {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
String result = marketGet(templateParams.get("template.url") + POSTS_API_V2, null);
MarketTemplateV2BaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateV2BaseResponse.class);
return baseResponseV2Trans(postsResult, templateParams.get("template.url"));
return baseResponseV2Trans(templateQuery(templateParams), templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
private MarketBaseResponse baseResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
public MarketBaseResponse searchTemplateRecommend() {
try {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
return baseResponseV2TransRecommend(templateQuery(templateParams), templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
public MarketPreviewBaseResponse searchTemplatePreview() {
try {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
return basePreviewResponseV2Trans(templateQuery(templateParams), templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
private MarketBaseResponse baseResponseV2TransRecommend(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, Long> useTime = coreOptRecentManage.findTemplateRecentUseTime();
Map<String, String> categoriesMap = getCategoriesBaseV2();
List<TemplateMarketDTO> contents = new ArrayList<>();
v2BaseResponse.getItems().stream().forEach(marketTemplateV2ItemResult -> {
MarketApplicationSpecVO spec = marketTemplateV2ItemResult.getApplication().getSpec();
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel())));
if("Y".equalsIgnoreCase(spec.getSuggest())){
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel()), spec.getTemplateType(), useTime.get(spec.getReadmeName()),"Y"));
}
});
return new MarketBaseResponse(url, contents);
// 最近使用排序
Collections.sort(contents);
return new MarketBaseResponse(url,contents);
}
private MarketBaseResponse baseResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, Long> useTime = coreOptRecentManage.findTemplateRecentUseTime();
Map<String, String> categoriesMap = getCategoriesBaseV2();
List<TemplateMarketDTO> contents = new ArrayList<>();
v2BaseResponse.getItems().stream().forEach(marketTemplateV2ItemResult -> {
MarketApplicationSpecVO spec = marketTemplateV2ItemResult.getApplication().getSpec();
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel()), spec.getTemplateType(), useTime.get(spec.getReadmeName()),spec.getSuggest()));
});
// 最近使用排序
Collections.sort(contents);
return new MarketBaseResponse(url,contents);
}
private MarketPreviewBaseResponse basePreviewResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, Long> useTime = coreOptRecentManage.findTemplateRecentUseTime();
Map<String, String> categoriesMap = getCategoriesBaseV2();
List<String> categories = new ArrayList<>();
List<TemplateMarketPreviewInfoDTO> result = new ArrayList<>();
categoriesMap.forEach((key,value)->{
if(!"全部".equalsIgnoreCase(value)){
categories.add(value);
List<TemplateMarketDTO> contents = new ArrayList<>();
v2BaseResponse.getItems().stream().forEach(marketTemplateV2ItemResult -> {
MarketApplicationSpecVO spec = marketTemplateV2ItemResult.getApplication().getSpec();
if(key.equalsIgnoreCase(spec.getLabel())){
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel()), spec.getTemplateType(), useTime.get(spec.getReadmeName()),spec.getSuggest()));
}
});
Collections.sort(contents);
result.add(new TemplateMarketPreviewInfoDTO(value,contents));
}
});
// 最近使用排序
return new MarketPreviewBaseResponse(url,categories,result);
}
public MarketBaseResponse searchTemplateV1() {
@ -115,6 +182,13 @@ public class TemplateMarketManage {
.collect(Collectors.toList());
}
public List<MarketMetaDataVO> getCategoriesObject() {
List<MarketMetaDataVO> result = getCategoriesV2();
result.add(0, new MarketMetaDataVO("suggest", "推荐"));
result.add(0, new MarketMetaDataVO("recent", "最近使用"));
return result;
}
public Map<String, String> getCategoriesBaseV2() {
Map<String, String> categories = getCategoriesV2().stream()
.collect(Collectors.toMap(MarketMetaDataVO::getSlug, MarketMetaDataVO::getLabel));

View File

@ -2,6 +2,8 @@ package io.dataease.template.service;
import io.dataease.api.template.TemplateMarketApi;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.api.template.response.MarketPreviewBaseResponse;
import io.dataease.api.template.vo.MarketMetaDataVO;
import io.dataease.template.manage.TemplateMarketManage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
@ -23,9 +25,23 @@ public class TemplateMarketService implements TemplateMarketApi {
public MarketBaseResponse searchTemplate() {
return templateMarketManage.searchTemplate();
}
@Override
public MarketBaseResponse searchTemplateRecommend() {
return templateMarketManage.searchTemplateRecommend();
}
@Override
public MarketPreviewBaseResponse searchTemplatePreview() {
return templateMarketManage.searchTemplatePreview();
}
@Override
public List<String> categories() {
return templateMarketManage.getCategories();
}
@Override
public List<MarketMetaDataVO> categoriesObject() {
return templateMarketManage.getCategoriesObject();
}
}

View File

@ -15,11 +15,13 @@ import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.chart.manage.ChartDataManage;
import io.dataease.chart.manage.ChartViewManege;
import io.dataease.commons.constants.DataVisualizationConstants;
import io.dataease.commons.constants.OptConstants;
import io.dataease.constant.CommonConstants;
import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
import io.dataease.operation.manage.CoreOptRecentManage;
import io.dataease.template.dao.auto.entity.VisualizationTemplate;
import io.dataease.template.dao.auto.entity.VisualizationTemplateExtendData;
import io.dataease.template.dao.auto.mapper.VisualizationTemplateExtendDataMapper;
@ -79,6 +81,8 @@ public class DataVisualizationServer implements DataVisualizationApi {
@Resource
private VisualizationTemplateExtendDataMapper templateExtendDataMapper;
@Resource
private CoreOptRecentManage coreOptRecentManage;
@Override
@XpackInteract(value = "dataVisualizationServer", original = true)
@ -257,6 +261,8 @@ public class DataVisualizationServer implements DataVisualizationApi {
staticResource = templateFileInfo.getStaticResource();
name = templateFileInfo.getName();
dvType = templateFileInfo.getDvType();
// 模版市场记录
coreOptRecentManage.saveOpt(request.getResourceName(), OptConstants.OPT_RESOURCE_TYPE.TEMPLATE,OptConstants.OPT_TYPE.NEW);
}
// 解析动态数据
Map<String, String> dynamicDataMap = JsonUtil.parseObject(dynamicData, Map.class);

View File

@ -18,7 +18,7 @@ CREATE TABLE `visualization_template` (
BEGIN;
INSERT INTO `core_menu`
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 0);
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 1);
INSERT INTO `core_menu`
VALUES (20, 15, 2, 'template-setting', 'system/template-setting', 4, 'icon_template', '/template-setting', 0, 1, 1);
COMMIT;
@ -33,3 +33,7 @@ CREATE TABLE `visualization_template_extend_data` (
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `core_opt_recent`
MODIFY COLUMN `resource_id` bigint NULL COMMENT '资源ID' AFTER `id`,
ADD COLUMN `resource_name` varchar(255) NULL COMMENT '资源名称' AFTER `resource_id`;

View File

@ -20,18 +20,38 @@ CREATE TABLE `visualization_template`
BEGIN;
INSERT INTO `core_menu`
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 0);
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 1);
INSERT INTO `core_menu`
VALUES (20, 15, 2, 'template-setting', 'system/template-setting', 4, 'icon_template', '/template-setting', 0, 1, 1);
COMMIT;
DROP TABLE IF EXISTS `visualization_template_extend_data`;
CREATE TABLE `visualization_template_extend_data` (
`id` bigint NOT NULL,
`dv_id` bigint DEFAULT NULL,
`view_id` bigint DEFAULT NULL,
`view_details` longtext,
`copy_from` varchar(255) DEFAULT NULL,
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
CREATE TABLE `visualization_template_extend_data`
(
`id` bigint NOT NULL,
`dv_id` bigint DEFAULT NULL,
`view_id` bigint DEFAULT NULL,
`view_details` longtext,
`copy_from` varchar(255) DEFAULT NULL,
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `core_opt_recent`
MODIFY COLUMN `resource_id` bigint NULL COMMENT '资源ID' AFTER `id`,
ADD COLUMN `resource_name` varchar(255) NULL COMMENT '资源名称' AFTER `resource_id`;
DROP TABLE IF EXISTS `core_area_custom`;
CREATE TABLE `core_area_custom`
(
`id` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`pid` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
BEGIN;
INSERT INTO `core_sys_setting` VALUES (1, 'basic.dsIntervalTime', '6', 'text', 2);
INSERT INTO `core_sys_setting` VALUES (2, 'basic.dsExecuteTime', 'minute', 'text', 3);
COMMIT;

View File

@ -5,9 +5,21 @@ export const getWorldTree = (): Promise<IResponse<AreaNode>> => {
return request.get({ url: '/map/worldTree' })
}
export const getGeoJson = (
country: string,
areaId: string
): Promise<IResponse<FeatureCollection>> => {
return request.get({ url: `/map/${country}/${areaId}.json` })
export const getGeoJson = (areaId: string): Promise<IResponse<FeatureCollection>> => {
let prefix = '/map'
let areaCode = areaId
if (isCustomGeo(areaId)) {
prefix = '/geo'
areaCode = getBusiGeoCode(areaId)
}
const realCountry = areaCode.substring(0, 3)
const url = `${prefix}/${realCountry}/${areaCode}.json`
return request.get({ url })
}
const isCustomGeo = (id: string) => {
return id.startsWith('geo_')
}
const getBusiGeoCode = (id: string) => {
return id.substring(4)
}

View File

@ -6,8 +6,26 @@ export function searchMarket() {
})
}
export function searchMarketRecommend() {
return request.get({
url: '/templateMarket/searchRecommend'
})
}
export function searchMarketPreview() {
return request.get({
url: '/templateMarket/searchPreview'
})
}
export function getCategories() {
return request.get({
url: '/templateMarket/categories'
})
}
export function getCategoriesObject() {
return request.get({
url: '/templateMarket/categoriesObject'
})
}

View File

@ -0,0 +1,31 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_2346_358803)">
<path d="M8 4H20C26.6274 4 32 9.37258 32 16C32 22.6274 26.6274 28 20 28H8V4Z" fill="white"/>
</g>
<g filter="url(#filter1_d_2346_358803)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20 5H8V27H20C26.0751 27 31 22.0751 31 16C31 9.92487 26.0751 5 20 5ZM8 4V28H20C26.6274 28 32 22.6274 32 16C32 9.37258 26.6274 4 20 4H8Z" fill="#DEE0E3"/>
</g>
<path d="M20.1391 16.0005L16.0732 20.0663C15.9756 20.164 15.9756 20.3223 16.0732 20.4199L16.4268 20.7735C16.5244 20.8711 16.6827 20.8711 16.7803 20.7735L21.1997 16.354C21.395 16.1588 21.395 15.8422 21.1997 15.6469L16.7803 11.2275C16.6827 11.1299 16.5244 11.1299 16.4268 11.2275L16.0732 11.5811C15.9756 11.6787 15.9756 11.837 16.0732 11.9346L20.1391 16.0005Z" fill="#3370FF"/>
<defs>
<filter id="filter0_d_2346_358803" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.121569 0 0 0 0 0.137255 0 0 0 0 0.160784 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2346_358803"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2346_358803" result="shape"/>
</filter>
<filter id="filter1_d_2346_358803" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2346_358803"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2346_358803" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,19 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_2356_358817)">
<rect width="24" height="24" rx="12" transform="matrix(-1 0 0 1 32 4)" fill="white"/>
<rect x="-0.5" y="0.5" width="23" height="23" rx="11.5" transform="matrix(-1 0 0 1 31 4)" stroke="#DEE0E3"/>
</g>
<path d="M17.8181 16.0005L21.8839 11.9346C21.9816 11.837 21.9816 11.6787 21.8839 11.5811L21.5304 11.2275C21.4327 11.1299 21.2745 11.1299 21.1768 11.2275L16.7574 15.6469C16.5621 15.8422 16.5621 16.1588 16.7574 16.354L21.1768 20.7735C21.2745 20.8711 21.4327 20.8711 21.5304 20.7735L21.8839 20.4199C21.9816 20.3223 21.9816 20.164 21.8839 20.0663L17.8181 16.0005Z" fill="#1F2329"/>
<defs>
<filter id="filter0_d_2356_358817" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2356_358817"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2356_358817" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,5 +1,14 @@
<svg width="125" height="125" viewBox="0 0 125 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="125" height="125" rx="62.5" fill="#EBEBEB"/>
<path d="M82.625 48.1813H71.125V36.6252H42.375V88.3752H56.75V94.1252H39.5C38.7375 94.1252 38.0062 93.8223 37.4671 93.2831C36.9279 92.744 36.625 92.0127 36.625 91.2502V33.7502C36.625 32.9877 36.9279 32.2564 37.4671 31.7173C38.0062 31.1781 38.7375 30.8752 39.5 30.8752H75.4375L88.375 44.4294V56.7502H82.625V48.1813Z" fill="#BBBFC4"/>
<path d="M75.4375 91.25C78.4004 91.25 81.154 90.3536 83.4417 88.8172L88.8204 94.1958C89.3817 94.7572 90.2919 94.7572 90.8533 94.1958L93.2928 91.7563C93.8542 91.1949 93.8542 90.2847 93.2928 89.7233L87.7941 84.2247C89.0761 82.0741 89.8125 79.5605 89.8125 76.875C89.8125 68.9359 83.3766 62.5 75.4375 62.5C67.4984 62.5 61.0625 68.9359 61.0625 76.875C61.0625 84.8141 67.4984 91.25 75.4375 91.25ZM84.0625 76.875C84.0625 81.6384 80.201 85.5 75.4375 85.5C70.674 85.5 66.8125 81.6384 66.8125 76.875C66.8125 72.1115 70.674 68.25 75.4375 68.25C80.201 68.25 84.0625 72.1115 84.0625 76.875Z" fill="#BBBFC4"/>
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0705 8.95996H65.4094C67.4587 8.95996 69.12 10.6118 69.12 12.6494V53.7236L53.296 70.0591H19.0705C17.0213 70.0591 15.36 68.4073 15.36 66.3697V12.6494C15.36 10.6118 17.0213 8.95996 19.0705 8.95996Z" fill="#D6DAE1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.2959 70.0327V54.9271C53.2959 54.2479 53.8497 53.6973 54.5328 53.6973H69.1199L53.2959 70.0327Z" fill="#B5BEC8"/>
<rect x="48.4399" y="14.0796" width="16" height="20.48" rx="2" fill="#F4F5F9"/>
<rect x="20.2241" y="14.1152" width="20.64" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="20.2505" width="20.64" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="26.6504" width="10.32" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="44.1108" width="44.032" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="48.8833" width="44.032" height="3.2" rx="1" fill="#F4F5F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.8181 34.6489C24.496 34.6489 18.5602 40.6141 18.5602 47.9725C18.5602 55.3309 24.496 61.296 31.8181 61.296C33.6008 61.296 35.3013 60.9425 36.8539 60.3013L43.7463 70.637L47.2995 68.2432L40.4981 58.0439C43.3024 55.6009 45.0761 51.9949 45.0761 47.9725C45.0761 40.6141 39.1403 34.6489 31.8181 34.6489ZM31.8181 38.9472C36.7782 38.9472 40.7993 42.9881 40.7993 47.9728C40.7993 52.9575 36.7782 56.9985 31.8181 56.9985C26.8579 56.9985 22.8369 52.9575 22.8369 47.9728C22.8369 42.9881 26.8579 38.9472 31.8181 38.9472Z" fill="white"/>
<path d="M15.7495 38.3938L15.4811 39.0205L9.58404 36.4913L9.85248 35.8646L15.7495 38.3938Z" fill="#1F2329"/>
<path d="M18.9606 36.2086L18.4367 36.6449L13.3637 30.5493L13.8876 30.113L18.9606 36.2086Z" fill="#1F2329"/>
<path d="M15.8575 42.0183L15.882 42.7005L7.80575 42.9754L7.78133 42.2932L15.8575 42.0183Z" fill="#1F2329"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -58,8 +58,7 @@ const loadComponent = () => {
storeCacheProxy(byteArray)
importProxy(byteArray)
})
.catch(e => {
console.error(e)
.catch(() => {
showNolic()
})
.finally(() => {

View File

@ -130,7 +130,7 @@ service.interceptors.response.use(
return response
} else if (response.data.code === result_code || response.data.code === 50002) {
return response.data
} else if (response.config.url.match(/^\/map\/\d{3}\/\d+\.json$/)) {
} else if (response.config.url.match(/^\/map|geo\/\d{3}\/\d+\.json$/)) {
// TODO 处理静态文件
return response
} else {

View File

@ -0,0 +1,94 @@
<script lang="ts" setup>
import { ElHeader } from 'element-plus-secondary'
import { useRouter } from 'vue-router'
import AccountOperator from '@/layout/components/AccountOperator.vue'
const { push } = useRouter()
const backToMain = () => {
push('/workbranch/index')
}
</script>
<template>
<el-header class="header-flex system-header">
<Icon className="logo" name="logo"></Icon>
<el-divider direction="vertical" />
<span class="system">模版市场</span>
<div class="operate-setting">
<span @click="backToMain" class="work-bar flex-align-center">
<el-icon>
<Icon name="icon_left_outlined"></Icon>
</el-icon>
<span class="work">返回工作台</span>
</span>
<AccountOperator />
</div>
</el-header>
</template>
<style lang="less" scoped>
.system-header {
font-family: PingFang SC;
.logo {
width: 134px;
height: 34px;
}
.ed-divider {
margin: 0 24px;
border-color: rgba(255, 255, 255, 0.3);
}
.system {
color: #fff;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
.work-bar {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
cursor: pointer;
.ed-icon {
margin-right: 4px;
font-size: 16px;
}
}
.avatar {
margin: 0 -7px 0 20px !important;
}
}
.header-flex {
display: flex;
align-items: center;
height: 56px;
background-color: #050e21;
padding: 0 24px;
.operate-setting {
margin-left: auto;
display: flex;
align-items: center;
&:focus {
outline: none;
}
}
}
</style>
<style lang="less">
.header-flex {
.operate-setting {
.ed-icon {
cursor: pointer;
color: rgba(255, 255, 255, 0.8);
font-size: 18px;
}
}
}
</style>

View File

@ -7,14 +7,17 @@ import Menu from './components/Menu.vue'
import Main from './components/Main.vue'
import { ElContainer } from 'element-plus-secondary'
import { useRoute } from 'vue-router'
import HeaderTemplateMarket from '@/layout/components/HeaderTemplateMarket.vue'
const route = useRoute()
const systemMenu = computed(() => route.path.includes('system'))
const settingMenu = computed(() => route.path.includes('sys-setting'))
const templateMarketMenu = computed(() => route.path.includes('template-market'))
</script>
<template>
<div class="common-layout">
<HeaderSystem v-if="settingMenu"></HeaderSystem>
<header-template-market v-if="templateMarketMenu"></header-template-market>
<HeaderSystem v-else-if="settingMenu"></HeaderSystem>
<Header v-else></Header>
<el-container class="layout-container">
<Sidebar v-if="systemMenu || settingMenu" class="layout-sidebar">

View File

@ -2122,5 +2122,10 @@ export default {
geometry: '地理信息',
onlinemap: '在线地图',
empty_desc: '请在左侧输入信息然后保存'
},
setting_basic: {
autoCreateUser: '禁止第三方自动创建用户',
dsIntervalTime: '数据源检测时间间隔',
dsExecuteTime: '数据源检测频率'
}
}

View File

@ -21,6 +21,7 @@ const license: F2CLicense = reactive({
remark: '',
isv: ''
})
const tipsSuffix = ref('')
const build = ref('')
const isAdmin = ref(false)
const fileList = reactive([])
@ -88,11 +89,14 @@ const validateHandler = (param, success) => {
validateApi(param).then(success)
}
const getLicense = result => {
if (result.status === 'valid') {
tipsSuffix.value = result.edition === 'Embedded' ? '套' : '个账号'
}
return {
status: result.status,
corporation: result.license ? result.license.corporation : '',
expired: result.license ? result.license.expired : '',
count: result.license ? result.license.count : '',
count: result.license ? result.license.count : 0,
version: result.license ? result.license.version : '',
edition: result.license ? result.license.edition : '',
serialNo: result.license ? result.license.serialNo : '',
@ -145,7 +149,9 @@ const update = (licKey: string) => {
</div>
<div class="item">
<div class="label">{{ $t('about.auth_num') }}</div>
<div class="value">{{ license.count }}</div>
<div class="value">
{{ license.status === 'valid' ? `${license.count} ${tipsSuffix}` : '' }}
</div>
</div>
<div class="item">
<div class="label">{{ $t('about.version') }}</div>

View File

@ -421,10 +421,8 @@ export const getGeoJsonFile = async (areaId: string): Promise<FeatureCollection>
const mapStore = useMapStoreWithOut()
let geoJson = mapStore.mapCache[areaId]
if (!geoJson) {
const country = areaId.slice(0, 3)
geoJson = await getGeoJson(country, areaId).then(result => {
return result.data
})
const res = await getGeoJson(areaId)
geoJson = res.data
mapStore.setMap({ id: areaId, geoJson })
}
return toRaw(geoJson)

View File

@ -11,7 +11,7 @@
<div class="info-template-content">
<div class="info-content-item" v-for="item in settingList" :key="item.pkey">
<div class="info-item-label">
<span>{{ item.pkey }}</span>
<span>{{ t(item.pkey) }}</span>
<el-tooltip
v-if="tooltipItem[item.pkey]"
effect="dark"
@ -33,7 +33,7 @@
</el-icon>
</el-tooltip>
</div>
<span v-else-if="item.pkey === '数据源检测时间间隔'">
<span v-else-if="item.pkey.includes('basic.dsIntervalTime')">
<span>{{ item.pval + ' ' + executeTime + '执行一次' }}</span>
</span>
<span v-else>{{ item.pval }}</span>
@ -74,7 +74,7 @@ const loadList = () => {
settingList.value = []
if (props.settingData?.length) {
props.settingData.forEach(item => {
if (item.pkey === '数据源检测频率') {
if (item.pkey.includes('basic.dsExecuteTime')) {
executeTime.value = getExecuteTime(item.pval)
} else {
settingList.value.push(item)
@ -93,78 +93,6 @@ const getExecuteTime = val => {
const settingList = ref([] as SettingRecord[])
/* const loadBasic = () => {
settingList.value.push({
pkey: '请求超时时间',
pval: '100',
type: 'text',
sort: 1
})
settingList.value.push({
pkey: '数据源检测时间间隔',
pval: '100',
type: 'text',
sort: 2
})
settingList.value.push({
pkey: '默认登录方式',
pval: '普通登录',
type: 'text',
sort: 3
})
settingList.value.push({
pkey: '默认密码',
pval: 'DataEase@123456',
type: 'pwd',
sort: 4
})
}
const loadEmail = () => {
settingList.value.push({
pkey: 'SMTP主机',
pval: 'smtp.exmail.qq.com',
type: 'text',
sort: 1
})
settingList.value.push({
pkey: 'SMTP端口',
pval: '465',
type: 'text',
sort: 2
})
settingList.value.push({
pkey: 'SMTP账户',
pval: 'test@fit2cloud.com',
type: 'text',
sort: 3
})
settingList.value.push({
pkey: 'SMTP密码',
pval: 'DataEase@123456',
type: 'pwd',
sort: 4
})
settingList.value.push({
pkey: '测试收件人',
pval: 'yawen.chen@fit2cloud.com',
type: 'pwd',
sort: 5
})
settingList.value.push({
pkey: 'SSL',
pval: '开启',
type: 'text',
sort: 6
})
settingList.value.push({
pkey: 'TSL',
pval: '未开启',
type: 'text',
sort: 7
})
} */
const init = () => {
if (props.settingData?.length) {
loadList()

View File

@ -12,17 +12,12 @@ const options = [
{ value: 'minute', label: '分钟执行时间0秒' },
{ value: 'hour', label: '小时执行时间0分0秒' }
]
interface BasicFrom {
autoCreateUser?: boolean
dsIntervalTime?: string
dsExecuteTime?: string
}
const state = reactive({
form: reactive<BasicFrom>({
autoCreateUser: false,
form: reactive({
dsIntervalTime: '30',
dsExecuteTime: 'minute'
})
}),
settingList: []
})
const rule = reactive<FormRules>({
@ -35,44 +30,24 @@ const rule = reactive<FormRules>({
]
})
const edit = row => {
state.form = {
autoCreateUser: row.autoCreateUser === 'true',
dsIntervalTime: row.dsIntervalTime,
dsExecuteTime: row.dsExecuteTime
}
dialogVisible.value = true
}
const emits = defineEmits(['saved'])
const buildSettingList = () => {
const param = { ...state.form }
const item0 = {
pkey: 'basic.autoCreateUser',
pval: param.autoCreateUser.toString(),
type: 'text',
sort: 1
}
const item1 = {
pkey: 'basic.dsIntervalTime',
pval: param.dsIntervalTime,
type: 'text',
sort: 2
}
const item2 = {
pkey: 'basic.dsExecuteTime',
pval: param.dsExecuteTime,
type: 'text',
sort: 3
}
return [item0, item1, item2]
return state.settingList.map(item => {
const pkey = item.pkey.startsWith('basic.') ? item.pkey : `basic.${item.pkey}`
const sort = item.sort
const type = item.type
const pval = state.form[item.pkey]
return { pkey, pval, type, sort }
})
}
const emits = defineEmits(['saved'])
const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
const param = buildSettingList()
if (param.length < 2) {
return
}
showLoading()
request
.post({ url: '/sysParameter/basic/save', data: param })
@ -94,6 +69,7 @@ const submitForm = async (formEl: FormInstance | undefined) => {
}
const resetForm = (formEl: FormInstance | undefined) => {
state.settingList = []
if (!formEl) return
formEl.resetFields()
dialogVisible.value = false
@ -109,6 +85,19 @@ const showLoading = () => {
const closeLoading = () => {
loadingInstance.value?.close()
}
const edit = list => {
state.settingList = list.map(item => {
const pkey = item.pkey
item['label'] = `setting_${pkey}`
item['pkey'] = pkey.split('.')[1]
let pval = item.pval
state.form[item['pkey']] = pval || state.form[item['pkey']]
return item
})
console.log(state.settingList)
dialogVisible.value = true
}
defineExpose({
edit
})
@ -130,11 +119,48 @@ defineExpose({
label-width="80px"
label-position="top"
>
<el-form-item label="禁止扫码创建用户" prop="autoCreateUser">
<el-switch v-model="state.form.autoCreateUser" />
<el-form-item
v-for="item in state.settingList"
:key="item.pkey"
:prop="item.pkey"
:class="{ 'setting-hidden-item': item.pkey === 'dsExecuteTime' }"
:label="t(item.label)"
>
<el-switch
v-if="item.pkey === 'autoCreateUser'"
active-value="true"
inactive-value="false"
v-model="state.form[item.pkey]"
/>
<div v-else-if="item.pkey === 'dsIntervalTime'" class="ds-task-form-inline">
<span></span>
<el-input-number
v-model="state.form.dsIntervalTime"
autocomplete="off"
step-strictly
class="text-left"
:min="1"
:placeholder="t('common.inputText')"
controls-position="right"
type="number"
/>
<el-select v-model="state.form.dsExecuteTime">
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<span class="ds-span">执行一次</span>
</div>
<div v-else />
</el-form-item>
<!-- <el-form-item label="禁止扫码创建用户" prop="autoCreateUser">
<el-switch v-model="state.form.autoCreateUser" />
</el-form-item> -->
<el-form-item label="数据源检测时间间隔" prop="dsIntervalTime">
<!-- <el-form-item label="数据源检测时间间隔" prop="dsIntervalTime">
<div class="ds-task-form-inline">
<span></span>
<el-input-number
@ -157,7 +183,7 @@ defineExpose({
</el-select>
<span class="ds-span">执行一次</span>
</div>
</el-form-item>
</el-form-item> -->
</el-form>
<template #footer>
<span class="dialog-footer">
@ -171,6 +197,9 @@ defineExpose({
</template>
<style scoped lang="less">
.setting-hidden-item {
display: none !important;
}
.ds-task-form-inline {
width: 100%;
display: flex;

View File

@ -17,6 +17,7 @@ import BasicEdit from './BasicEdit.vue'
import request from '@/config/axios'
import { SettingRecord } from '@/views/system/common/SettingTemplate'
import { reactive } from 'vue'
import { cloneDeep } from 'lodash-es'
const editor = ref()
const infoTemplate = ref()
const tooltips = [
@ -26,39 +27,25 @@ const tooltips = [
}
]
const state = reactive({
templateList: [
{
pkey: '禁止扫码创建用户',
pval: '未开启',
type: 'text',
sort: 1
},
{
pkey: '数据源检测时间间隔',
pval: '100',
type: 'text',
sort: 2
},
{
pkey: '数据源检测频率',
pval: 'minute',
type: 'text',
sort: 3
}
] as SettingRecord[]
templateList: [] as SettingRecord[]
})
let originData = []
const search = cb => {
const url = '/sysParameter/basic/query'
originData = []
state.templateList = []
request.get({ url }).then(res => {
originData = cloneDeep(res.data)
const data = res.data
for (let index = 0; index < data.length; index++) {
const item = data[index]
if (index === 0) {
state.templateList[index].pval = item.pval === 'true' ? '开启' : '未开启'
if (item.pkey === 'basic.autoCreateUser') {
item.pval = item.pval === 'true' ? '开启' : '未开启'
} else {
state.templateList[index].pval = item.pval
item.pval = item.pval
}
item.pkey = 'setting_' + item.pkey
state.templateList.push(item)
}
cb && cb()
})
@ -68,14 +55,9 @@ const refresh = () => {
infoTemplate?.value.init()
})
}
search(null)
refresh()
const edit = () => {
const param = {
autoCreateUser: state.templateList[0].pval === '开启' ? 'true' : 'false',
dsIntervalTime: state.templateList[1].pval,
dsExecuteTime: state.templateList[2].pval
}
editor?.value.edit(param)
editor?.value.edit(cloneDeep(originData))
}
</script>

View File

@ -43,6 +43,19 @@
:title="data.name"
v-html="data.colorName && keyword ? data.colorName : data.name"
/>
<span class="geo-operate-container">
<el-tooltip
v-if="data.custom"
class="box-item"
effect="dark"
:content="t('common.delete')"
placement="top"
>
<el-icon @click.stop="delHandler(data)" class="hover-icon">
<Icon name="icon_delete-trash_outlined"></Icon>
</el-icon>
</el-tooltip>
</span>
</span>
</template>
</el-tree>
@ -83,7 +96,7 @@
</div>
</el-main>
</el-container>
<geometry-edit ref="editor" :tree-data="treeData" @saved="loadTreeData" />
<geometry-edit ref="editor" @saved="loadTreeData(false)" />
</template>
<script lang="ts" setup>
@ -95,6 +108,10 @@ import { getGeoJsonFile } from '@/views/chart/components/js/util'
import { cloneDeep } from 'lodash-es'
import { setColorName } from '@/utils/utils'
import GeometryEdit from './GeometryEdit.vue'
import { useCache } from '@/hooks/web/useCache'
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import request from '@/config/axios'
const { wsCache } = useCache()
const { t } = useI18n()
const keyword = ref('')
const treeData = ref([])
@ -104,7 +121,7 @@ interface Tree {
children?: Tree[]
}
const areaTreeRef = ref(null)
const loading = ref(false)
const selectedData = ref(null)
const handleNodeClick = async (data: Tree) => {
@ -119,6 +136,29 @@ const handleNodeClick = async (data: Tree) => {
}
}
}
const delHandler = data => {
ElMessageBox.confirm('确定删除此节点吗', {
confirmButtonType: 'danger',
type: 'warning',
confirmButtonText: t('common.delete'),
cancelButtonText: t('dataset.cancel'),
autofocus: false,
showClose: false
})
.then(() => {
const url = '/geometry/delete/' + data.id
request.post({ url }).then(() => {
if (selectedData.value?.id === data.id) {
selectedData.value = null
}
ElMessage.success(t('common.delete_success'))
loadTreeData(false)
})
})
.catch(() => {
loading.value = false
})
}
const filterResource = val => {
areaTreeRef.value?.filter(val)
}
@ -128,11 +168,18 @@ const filterResourceNode = (value: string, data) => {
return data.name.toLocaleLowerCase().includes(value.toLocaleLowerCase())
}
const loadTreeData = () => {
const loadTreeData = (cache?: boolean) => {
const key = 'de-area-tree'
const result = wsCache.get(key)
if (result && cache) {
treeData.value = result
return
}
getWorldTree()
.then(res => {
const root = res.data
treeData.value = [root]
wsCache.set(key, treeData.value)
})
.catch(e => {
console.error(e)
@ -143,7 +190,7 @@ const add = (pid?: string) => {
editor?.value.edit(pid)
}
loadTreeData()
loadTreeData(true)
</script>
<style lang="less" scoped>
@ -165,7 +212,6 @@ loadTreeData()
line-height: 24px;
}
.add-icon-span {
// display: none;
color: #3370ff;
height: 20px;
width: 20px;
@ -257,5 +303,16 @@ loadTreeData()
box-sizing: content-box;
padding-right: 4px;
overflow: hidden;
justify-content: space-between;
.geo-operate-container {
display: none;
}
&:hover {
.geo-operate-container {
display: contents;
}
}
}
</style>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ref, reactive, PropType } from 'vue'
import { ref, reactive } from 'vue'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import type {
@ -10,16 +10,12 @@ import type {
} from 'element-plus-secondary'
import request from '@/config/axios'
import { GeometryFrom } from './interface'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const { t } = useI18n()
const dialogVisible = ref(false)
const loadingInstance = ref(null)
const geoForm = ref<FormInstance>()
const props = defineProps({
treeData: {
type: Array as PropType<unknown[]>,
default: () => []
}
})
const geoFile = ref()
const fileName = ref()
const state = reactive({
@ -27,7 +23,8 @@ const state = reactive({
pid: null,
code: null,
name: null
})
}),
treeData: []
})
const treeProps = {
value: 'id',
@ -60,6 +57,8 @@ const rule = reactive<FormRules>({
})
const edit = (pid?: string) => {
const key = 'de-area-tree'
state.treeData = wsCache.get(key)
state.form.pid = pid
state.form.code = null
state.form.name = null
@ -75,9 +74,10 @@ const submitForm = async (formEl: FormInstance | undefined) => {
await formEl.validate((valid, fields) => {
if (valid) {
const param = { ...state.form }
const formData = buildFormData(geoFile.value, param)
showLoading()
request
.post({ url: '/sysParameter/map/save', data: param })
.post({ url: '/geometry/save', data: formData, headersType: 'multipart/form-data;' })
.then(res => {
if (!res.msg) {
ElMessage.success(t('common.save_success'))
@ -136,6 +136,14 @@ const uploadValidate = file => {
}
return true
}
const buildFormData = (file, param) => {
const formData = new FormData()
if (file) {
formData.append('file', file)
}
formData.append('request', new Blob([JSON.stringify(param)], { type: 'application/json' }))
return formData
}
defineExpose({
edit
})
@ -163,7 +171,7 @@ defineExpose({
node-key="id"
v-model="state.form.pid"
:props="treeProps"
:data="props.treeData"
:data="state.treeData"
check-strictly
:render-after-expand="false"
:placeholder="t('common.please_select')"

View File

@ -147,12 +147,19 @@ watch(
}
)
const initMarketTemplate = () => {
searchMarket()
const initMarketTemplate = async () => {
await searchMarket()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.currentMarketTemplateShowList = rsp.data.contents
state.hasResult = true
if (props.previewId) {
state.currentMarketTemplateShowList.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
}
})
.catch(() => {
state.networkStatus = false
@ -165,17 +172,6 @@ const initMarketTemplate = () => {
.catch(() => {
state.networkStatus = false
})
if (props.previewId) {
state.currentMarketTemplateShowList.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
}
}
const getGroupTree = () => {
// do getGroupTree
}
const normalizer = node => {
@ -248,7 +244,6 @@ const active = template => {
onMounted(() => {
initMarketTemplate()
getGroupTree()
})
</script>

View File

@ -0,0 +1,579 @@
<template>
<el-row style="width: 100%">
<el-row style="display: table; width: 100%">
<el-col style="float: left" :class="state.asideActive ? 'aside-active' : 'aside-inActive'">
<el-tooltip class="box-item" effect="dark" content="展开" placement="right">
<el-icon v-show="!state.asideActive" class="insert" @click="asideActiveChange(true)">
<Icon name="market-expand"></Icon>
</el-icon>
</el-tooltip>
<el-row v-show="state.asideActive" style="padding: 24px 12px 0">
<el-row style="align-items: center">
<el-breadcrumb separator-icon="ArrowRight">
<el-breadcrumb-item class="custom-breadcrumb-item" @click="closePreview()">{{
t('visualization.template_preview')
}}</el-breadcrumb-item>
<el-breadcrumb-item>预览</el-breadcrumb-item>
</el-breadcrumb>
<el-tooltip class="box-item" effect="dark" content="收起" placement="right">
<el-icon class="insert-retract" @click="asideActiveChange(false)">
<Icon name="market-retract"></Icon>
</el-icon>
</el-tooltip>
</el-row>
<el-row class="margin-top16 search-area">
<el-input
v-model="state.searchText"
size="small"
prefix-icon="Search"
class="title-name-search"
:placeholder="t('visualization.enter_template_name_tips')"
clearable="true"
/>
<el-icon
class="insert-filter filter-icon-span"
:class="state.extFilterActive ? 'filter-icon-active' : ''"
@click="extFilterActiveChange()"
><Filter
/></el-icon>
</el-row>
<el-row v-show="state.extFilterActive">
<el-select
v-model="state.templateType"
class="margin-top16"
size="small"
placeholder="请选择"
>
<el-option
v-for="item in state.templateTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-row>
<el-divider />
</el-row>
<el-row
v-show="state.asideActive"
class="aside-list"
:class="state.extFilterActive ? 'aside-list-filter-active' : ''"
>
<el-collapse v-show="state.hasResult" v-model="activeCategories" class="market-collapse">
<el-collapse-item
themes="light"
v-for="(categoryTemplate, index) in state.marketTemplatePreviewShowList"
:name="categoryTemplate['categoryType']"
:key="index"
:title="categoryTemplate['categoryType']"
>
<template-market-preview-item
v-for="templateItem in categoryTemplate['contents']"
v-show="templateItem.showFlag"
:key="templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:active="active(templateItem)"
@previewTemplate="previewTemplate"
/>
</el-collapse-item>
</el-collapse>
<el-row v-show="!state.hasResult" class="custom-position">
<div style="text-align: center">
<Icon name="no_result" style="margin-bottom: 16px; font-size: 75px"></Icon>
<br />
<span>没有找到相关模版</span>
</div>
</el-row>
</el-row>
</el-col>
<el-col
style="float: left"
class="main-area"
:class="state.asideActive ? 'main-area-active' : ''"
>
<el-row>
<span v-if="state.curTemplate" class="template-title">{{ state.curTemplate.title }}</span>
<div style="flex: 1; text-align: right">
<el-button
style="float: right"
type="primary"
size="small"
@click="templateApply(state.curTemplate)"
>{{ t('visualization.apply_this_template') }}</el-button
>
</div>
</el-row>
<el-row class="img-main">
<img style="height: 100%" :src="state.templatePreviewUrl" alt="" />
</el-row>
</el-col>
</el-row>
</el-row>
</template>
<script setup lang="ts">
import { searchMarketPreview } from '@/api/templateMarket'
import { onMounted, reactive, watch, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import TemplateMarketPreviewItem from '@/views/template-market/component/TemplateMarketPreviewItem.vue'
import { deepCopy } from '@/utils/utils'
const { t } = useI18n()
const props = defineProps({
previewId: {
type: String,
default: null
}
})
const emits = defineEmits(['templateApply', 'closeDialog', 'closePreview'])
const activeCategories = ref([])
const state = reactive({
hasResult: true,
extFilterActive: false,
asideActive: true,
previewVisible: false,
templatePreviewUrl: null,
marketTabs: null,
marketActiveTab: null,
searchText: null,
panelGroupList: [],
curApplyTemplate: null,
folderSelectShow: false,
baseUrl: 'https://dataease.io/templates',
marketTemplatePreviewShowList: [],
categories: [],
networkStatus: true,
curTemplate: null,
templateType: 'all',
templateTypeOptions: [
{
value: 'all',
label: '全部类型'
},
{
value: 'PANEL',
label: '仪表板'
},
{
value: 'SCREEN',
label: '大屏'
}
]
})
watch(
() => state.templateType,
value => {
initTemplateShow()
}
)
watch(
() => state.searchText,
value => {
initTemplateShow()
}
)
watch(
() => props.previewId,
value => {
state.marketTemplatePreviewShowList.forEach(categoryTemplates => {
categoryTemplates.contents.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
})
}
)
const initMarketTemplate = () => {
searchMarketPreview()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.marketTemplatePreviewShowList = rsp.data.contents
state.hasResult = true
state.categories = rsp.data.categories
activeCategories.value = deepCopy(state.categories)
if (props.previewId) {
state.marketTemplatePreviewShowList.forEach(categoryTemplates => {
categoryTemplates.contents.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
})
}
})
.catch(() => {
state.networkStatus = false
})
}
const getGroupTree = () => {
// do getGroupTree
}
const normalizer = node => {
// children=null
if (node.children === null || node.children === 'null') {
delete node.children
}
}
const templateApply = template => {
emits('templateApply', template)
}
const closeDialog = () => {
emits('closeDialog')
}
const handleClick = item => {
//handleClick
}
const initTemplateShow = () => {
state.hasResult = false
state.marketTemplatePreviewShowList.forEach(categoryTemplates => {
categoryTemplates.contents.forEach(template => {
template.showFlag = templateShow(template)
if (template.showFlag) {
state.hasResult = true
}
})
})
activeCategories.value = deepCopy(state.categories)
}
const templateShow = templateItem => {
let templateTypeMarch = false
let searchMarch = false
if (state.templateType === 'all' || templateItem.templateType === state.templateType) {
templateTypeMarch = true
}
if (!state.searchText || templateItem.title.indexOf(state.searchText) > -1) {
searchMarch = true
}
return templateTypeMarch && searchMarch
}
const previewTemplate = template => {
state.curTemplate = template
if (template.thumbnail.indexOf('http') > -1) {
state.templatePreviewUrl = template.thumbnail
} else {
state.templatePreviewUrl = state.baseUrl + template.thumbnail
}
}
const asideActiveChange = prop => {
state.asideActive = prop
}
const extFilterActiveChange = () => {
state.extFilterActive = !state.extFilterActive
state.marketActiveTab = state.marketTabs[0]
}
const closePreview = () => {
emits('closePreview')
}
const active = template => {
return state.curTemplate && state.curTemplate.id === template.id
}
onMounted(() => {
initMarketTemplate()
})
</script>
<style lang="less" scoped>
.market-collapse {
width: 100%;
border: 0;
::v-deep(.ed-collapse-item__content) {
padding: 8px 0;
border: 0;
}
::v-deep(.ed-collapse-item__header) {
border: 0;
}
::v-deep(.ed-collapse-item__wrap) {
border: 0;
background-color: rgba(245, 246, 247, 1);
}
}
.aside-list {
padding: 0px 12px 12px 12px;
width: 100%;
height: calc(100vh - 200px);
overflow-y: auto;
}
.aside-list-filter-active {
height: calc(100vh - 250px);
}
.template-main {
border-radius: 4px;
box-shadow: 0 0 2px 0 rgba(31, 31, 31, 0.15), 0 1px 2px 0 rgba(31, 31, 31, 0.15);
border: solid 2px #fff;
padding-bottom: 24px;
min-height: calc(100vh - 190px);
}
.market-main {
padding: 24px;
}
.title-left {
float: left;
font-size: 20px;
font-weight: 500;
line-height: 28px;
}
.title-right {
float: right;
width: 320px;
}
.dialog-footer-self {
text-align: center;
}
.search-button-self {
text-align: left;
padding-left: 10px;
}
.topbar-icon-active {
cursor: pointer;
transition: 0.1s;
border-radius: 3px;
font-size: 22px;
background-color: rgb(245, 245, 245);
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.custom-position {
height: 70vh;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-flow: row nowrap;
color: #646a73;
font-weight: 400;
width: 100%;
}
.aside-active {
width: 206px;
height: calc(100vh - 56px);
background-color: rgba(245, 246, 247, 1);
}
.aside-inActive {
position: relative;
width: 0px;
}
.main-area-active {
width: calc(100% - 206px) !important;
background: #ffffff;
}
.main-area {
width: 100%;
padding: 24px;
text-align: center;
height: calc(100vh - 56px);
transition: 0.5s;
}
.title-name-search {
width: 140px;
float: left;
}
.icon20 {
font-size: 20px !important;
}
.main-title {
width: 135px;
margin-left: 8px;
font-weight: 500;
font-size: 16px;
line-height: 24px;
color: var(--TextPrimary, #1f2329);
}
.insert-filter {
display: inline-block;
font-weight: 400 !important;
font-family: PingFang SC;
line-height: 1;
white-space: nowrap;
cursor: pointer;
color: var(--TextPrimary, #1f2329);
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: 0.1s;
border-radius: 3px;
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.insert-retract {
position: absolute;
left: 176px;
top: 2px;
display: inline-block;
font-size: 34px;
font-weight: 400 !important;
font-family: PingFang SC;
line-height: 1;
white-space: nowrap;
cursor: pointer;
color: #646a73;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: 0.1s;
border-radius: 3px;
&:active {
color: #000;
border-color: #3a8ee6;
outline: 0;
}
&:hover {
color: #3a8ee6;
}
}
.insert {
font-size: 34px;
margin-top: 24px;
margin-left: -8px;
display: inline-block;
font-weight: 400 !important;
cursor: pointer;
color: #646a73;
transition: 0.1s;
&:active {
color: #000;
border-color: #3a8ee6;
outline: 0;
}
&:hover {
margin-left: -6px;
color: #3a8ee6;
}
}
.template-title {
float: left;
font-weight: 500;
font-size: 20px;
line-height: 28px;
margin-bottom: 24px;
color: var(--TextPrimary, #1f2329);
}
.margin-top16 {
margin-top: 16px;
}
.img-main {
display: inherit;
border-radius: 4px;
background: #0f1114;
overflow-x: auto;
overflow-y: hidden;
height: calc(100% - 50px) !important;
}
.open-button {
cursor: pointer;
font-size: 30px;
position: absolute;
left: 0;
top: 16px;
z-index: 2;
}
//.open-button:hover{
// transition: 0.5s;
// width: 50px;
//}
.open-button:hover {
color: #3a8ee6;
}
.filter-icon-span {
float: left;
border: 1px solid #dcdfe6;
width: 32px;
height: 32px;
border-radius: 4px;
padding: 7px;
margin-left: 8px;
}
.filter-icon-active {
border: 1px solid #3370ff;
color: #3370ff;
}
.filter-icon-active {
border: 1px solid #3370ff;
color: #3370ff;
}
.search-area {
width: 100%;
position: relative;
}
.custom-breadcrumb-item {
cursor: pointer;
::v-deep(.ed-breadcrumb__inner) {
color: rgba(100, 106, 115, 1);
}
}
</style>

View File

@ -0,0 +1,130 @@
<template>
<div class="testcase-template">
<div class="template-img" :style="classBackground" @click.stop="templateInnerPreview" />
<el-row class="bottom-area"> </el-row>
<el-row class="bottom-area-show">
<el-row class="demonstration">
{{ template.title }}
</el-row>
<el-row class="template-button">
<el-button size="mini" style="width: 100px" @click="templateInnerPreview">{{
t('visualization.preview')
}}</el-button>
<el-button size="mini" style="width: 100px" type="primary" @click="apply">{{
t('visualization.apply')
}}</el-button>
</el-row>
</el-row>
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { computed } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
const { t } = useI18n()
const emits = defineEmits(['templateApply', 'templatePreview'])
const props = defineProps({
template: {
type: Object,
default() {
return {}
}
},
baseUrl: {
type: String
},
width: {
type: Number
}
})
const classBackground = computed(() => {
return {
width: props.width + 'px',
height: props.width * 0.58 + 'px',
background: `url(${imgUrlTrans(thumbnailUrl.value)}) no-repeat`,
'background-size': `100% 100%`
}
})
const thumbnailUrl = computed(() => {
if (props.template.thumbnail.indexOf('http') > -1) {
return props.template.thumbnail
} else {
return props.baseUrl + props.template.thumbnail
}
})
const apply = () => {
emits('templateApply', props.template)
}
const templateInnerPreview = e => {
emits('templatePreview', props.template.id)
}
</script>
<style scoped lang="less">
.testcase-template {
position: relative;
display: inline-block;
margin: 0;
border: 1px solid rgba(222, 224, 227, 1);
box-sizing: border-box;
border-radius: 4px;
width: 100%;
background: #fff;
overflow: hidden;
}
.demonstration {
height: 34px;
font-weight: 500;
display: block;
font-size: 14px;
text-align: left;
padding: 8px 12px 4px 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--TextPrimary, #1f2329);
}
.template-img {
background-size: 100% 100%;
margin: 4px 4px 0 4px;
border-radius: 4px 4px 0 0;
}
.template-button {
justify-content: center;
width: 100%;
}
.bottom-area {
height: 38px;
}
.bottom-area-show {
width: 100%;
border-top: 1px solid rgba(222, 224, 227, 1);
position: absolute;
height: 75px;
bottom: -38px;
background: #ffffff;
}
.testcase-template:hover ::v-deep(.bottom-area-show) {
transition: 0.3s;
bottom: 0px;
}
.testcase-template:hover ::v-deep(.template-img) {
outline: solid 1px #4b8fdf;
color: deepskyblue;
cursor: pointer;
}
</style>

View File

@ -1,97 +1,130 @@
<template>
<el-row class="outer-body" v-loading="state.loading">
<!--预览模式-->
<market-preview
<el-row
class="template-outer-body"
:class="{ 'template-outer-body-padding': !previewModel }"
v-loading="state.loading"
>
<market-preview-v2
v-show="previewModel"
:preview-id="state.templatePreviewId"
@closePreview="closePreview"
@templateApply="templateApply"
/>
<!--列表模式-->
<el-row v-show="!previewModel" class="market-main">
<el-row>
<el-col :span="12">
<span class="title-left">{{ t('visualization.template_market') }}</span>
</el-col>
<el-col :span="12">
></market-preview-v2>
<el-row v-show="!previewModel" class="main-container">
<el-row class="market-head">
<span>模版市场 </span>
<el-row class="head-right">
<el-input
class="title-search"
v-model="state.searchText"
prefix-icon="el-icon-search"
prefix-icon="Search"
size="small"
class="title-right"
:placeholder="t('visualization.enter_template_name_tips')"
:clearable="true"
/>
</el-col>
<el-select
class="title-type"
v-model="state.templateType"
size="small"
placeholder="Select"
>
<el-option
v-for="item in state.templateTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-row>
</el-row>
<el-row class="custom-tabs-head">
<el-tabs v-model="state.marketActiveTab" @tab-click="handleClick">
<el-tab-pane
v-for="tabItem in state.marketTabs"
:key="tabItem"
:label="tabItem"
:name="tabItem"
<el-row class="template-area">
<div class="template-left">
<el-tree
menu
:data="state.marketTabs"
:props="state.treeProps"
node-key="label"
default-expand-all
highlight-current
:current-node-key="state.marketActiveTab"
@node-click="nodeClick"
/>
</el-tabs>
</el-row>
<el-row
v-show="state.networkStatus && state.hasResult"
id="template-main"
class="template-main"
>
<el-col
v-for="templateItem in state.currentMarketTemplateShowList"
v-show="templateItem.showFlag"
:key="templateItem.id"
style="padding: 24px 12px 0; text-align: center; flex: 0"
:style="{ width: state.templateSpan }"
>
<template-market-item
:key="'outer-' + templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:width="state.templateCurWidth"
@templateApply="templateApply"
@templatePreview="templatePreview"
/>
</el-col>
</el-row>
<el-row
v-show="state.networkStatus && !state.hasResult"
class="custom-position template-main"
>
<div style="text-align: center">
<Icon name="no_result" style="margin-bottom: 16px; font-size: 75px"></Icon>
<br />
<span>{{ t('commons.no_result') }}</span>
</div>
</el-row>
<el-row v-show="!state.networkStatus" class="custom-position template-main">
{{ t('visualization.market_network_tips') }}
<div
v-show="state.networkStatus && state.hasResult"
id="template-show-area"
class="template-right"
>
<el-col
v-for="templateItem in state.currentMarketTemplateShowList"
v-show="templateItem.showFlag"
:key="templateItem.id"
style="padding: 24px 12px 0; text-align: center; flex: 0"
:style="{ width: state.templateSpan }"
>
<template-market-v2-item
:key="'outer-' + templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:width="state.templateCurWidth"
@templateApply="templateApply"
@templatePreview="templatePreview"
/>
</el-col>
</div>
<el-row v-show="state.networkStatus && !state.hasResult" class="template-empty">
<div style="text-align: center">
<Icon name="no_result" style="margin-bottom: 16px; font-size: 75px"></Icon>
<br />
<span>没有找到相关模版</span>
</div>
</el-row>
<el-row v-show="!state.networkStatus" class="template-empty">
{{ t('visualization.market_network_tips') }}
</el-row>
</el-row>
</el-row>
</el-row>
</template>
<script setup lang="ts">
import { getCategories, searchMarket } from '@/api/templateMarket'
import { getCategoriesObject, searchMarket } from '@/api/templateMarket'
import elementResizeDetectorMaker from 'element-resize-detector'
import { nextTick, reactive, watch, onMounted, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ElMessage } from 'element-plus-secondary'
import MarketPreview from '@/views/template-market/component/MarketPreview.vue'
import TemplateMarketItem from '@/views/template-market/component/TemplateMarketItem.vue'
import { decompression } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
import TemplateMarketV2Item from '@/views/template-market/component/TemplateMarketV2Item.vue'
import MarketPreviewV2 from '@/views/template-market/component/MarketPreviewV2.vue'
const { t } = useI18n()
const { wsCache } = useCache()
const previewModel = ref(false)
const state = reactive({
treeProps: {
value: 'label',
label: 'label'
},
templateType: 'all',
templateTypeOptions: [
{
value: 'all',
label: '全部类型'
},
{
value: 'PANEL',
label: '仪表板'
},
{
value: 'SCREEN',
label: '大屏'
}
],
loading: false,
hasResult: true,
templateMiniWidth: 330,
templateMiniWidth: 270,
templateCurWidth: 310,
templateSpan: '25%',
previewVisible: false,
@ -100,6 +133,7 @@ const state = reactive({
marketActiveTab: null,
searchText: null,
dvCreateForm: {
resourceName: null,
name: null,
pid: null,
nodeType: 'panel',
@ -133,13 +167,6 @@ const state = reactive({
}
})
watch(
() => state.marketActiveTab,
value => {
initTemplateShow()
}
)
watch(
() => state.searchText,
value => {
@ -147,12 +174,22 @@ watch(
}
)
watch(
() => state.templateType,
value => {
initTemplateShow()
}
)
const nodeClick = data => {
state.marketActiveTab = data.label
initTemplateShow()
}
const closePreview = () => {
previewModel.value = false
}
const initMarketTemplate = () => {
searchMarket()
const initMarketTemplate = async () => {
await searchMarket()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.currentMarketTemplateShowList = rsp.data.contents
@ -160,21 +197,28 @@ const initMarketTemplate = () => {
.catch(() => {
state.networkStatus = false
})
getCategories()
getCategoriesObject()
.then(rsp => {
state.marketTabs = rsp.data
state.marketActiveTab = state.marketTabs[0]
state.marketActiveTab = state.marketTabs[0].label
initStyle()
initTemplateShow()
})
.catch(() => {
state.networkStatus = false
})
}
const getGroupTree = () => {
// do getGroupTree
// groupTree({ nodeType: 'folder' }).then(res => {
// state.panelGroupList = res.data
// })
const initStyle = () => {
nextTick(() => {
const tree = document.querySelector('.ed-tree')
// 线
const line = document.createElement('hr')
line.classList.add('custom-line')
// 线
tree.firstElementChild.appendChild(line)
})
}
const normalizer = node => {
// children=null
@ -187,6 +231,7 @@ const templateApply = template => {
state.curApplyTemplate = template
state.dvCreateForm.name = template.title
state.dvCreateForm.templateUrl = template.metas.theme_repo
state.dvCreateForm.resourceName = template.id
apply()
}
@ -231,30 +276,43 @@ const initTemplateShow = () => {
const templateShow = templateItem => {
let categoryMarch = false
let searchMarch = false
templateItem.categories.forEach(category => {
if (category.name === state.marketActiveTab) {
let templateTypeMarch = false
if (state.marketActiveTab === '最近使用') {
if (templateItem.recentUseTime) {
categoryMarch = true
}
})
} else if (state.marketActiveTab === '推荐') {
if (templateItem.suggest === 'Y') {
categoryMarch = true
}
} else {
templateItem.categories.forEach(category => {
if (category.name === state.marketActiveTab) {
categoryMarch = true
}
})
}
if (!state.searchText || templateItem.title.indexOf(state.searchText) > -1) {
searchMarch = true
}
return categoryMarch && searchMarch
if (state.templateType === 'all' || templateItem.templateType === state.templateType) {
templateTypeMarch = true
}
return categoryMarch && searchMarch && templateTypeMarch
}
const templatePreview = previewId => {
state.templatePreviewId = previewId
previewModel.value = true
}
const newPanel = () => {
// do newPanel
}
onMounted(() => {
previewInit()
initMarketTemplate()
getGroupTree()
const erd = elementResizeDetectorMaker()
const templateMainDom = document.getElementById('template-main')
const templateMainDom = document.getElementById('template-show-area')
// div
if (templateMainDom) {
erd.listenTo(templateMainDom, element => {
@ -267,116 +325,86 @@ onMounted(() => {
})
}
})
const previewInit = () => {
const previewId = wsCache.get('template-preview-id')
if (previewId) {
templatePreview(previewId)
wsCache.delete('template-preview-id')
}
}
</script>
<style lang="less" scoped>
.custom-tabs-head {
display: inherit;
padding-bottom: 15px;
::v-deep(.ed-tabs__item) {
font-weight: 400;
font-size: 14px;
}
}
.template-main {
text-align: center;
border-radius: 4px;
padding: 0 12px 24px 12px;
height: calc(100vh - 190px) !important;
overflow-x: hidden;
overflow-y: auto;
background-color: var(--ContentBG, #ffffff);
}
.market-main {
.template-outer-body-padding {
padding: 24px;
display: inherit;
}
.template-outer-body {
.main-container {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
width: 100%;
height: 100%;
.market-head {
height: 56px;
background: #ffffff;
align-items: center;
padding: 12px 24px;
border-bottom: 1px solid rgba(31, 35, 41, 0.15);
span {
font-size: 16px;
font-color: #1f2329;
font-weight: 500;
}
.head-right {
flex: 1;
justify-content: right;
.title-search {
width: 320px;
}
.title-type {
margin-left: 12px;
width: 104px;
}
}
}
.template-area {
height: calc(100vh - 135px);
.template-left {
padding: 8px;
width: 204px;
height: 100%;
overflow-y: auto;
background: #ffffff;
}
.template-right {
flex: 1;
display: inherit;
height: 100%;
background: rgba(239, 240, 241, 1);
overflow-y: auto;
}
.title-left {
float: left;
font-size: 20px;
font-weight: 500;
line-height: 28px;
color: var(--TextPrimary, #1f2329);
}
.title-right {
float: right;
width: 320px;
}
.dialog-footer-self {
text-align: right;
}
.search-button-self {
text-align: left;
padding-left: 10px;
}
.topbar-icon-active {
cursor: pointer;
transition: 0.1s;
border-radius: 3px;
font-size: 22px;
background-color: rgb(245, 245, 245);
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.custom-position {
height: 80vh;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-flow: row nowrap;
color: #646a73;
font-weight: 400;
}
.outer-body {
display: inherit;
width: 100%;
height: calc(100vh - 56px);
background-color: var(--MainBG, #f5f6f7);
}
.market-dialog-css {
::v-deep(.ed-form-item__label) {
width: 100% !important;
text-align: left;
}
::v-deep(.ed-form-item.is-required:not(.is-no-asterisk) > .ed-form-item__label:before) {
display: none;
}
::v-deep(.ed-form-item.is-required:not(.is-no-asterisk) > .ed-form-item__label::after) {
content: '*';
color: #f54a45;
margin-left: 2px;
}
::v-deep(.ed-form-item__content) {
margin-left: 0 !important;
}
::v-deep(.vue-treeselect__input) {
vertical-align: middle;
.template-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: rgba(239, 240, 241, 1);
overflow-y: auto;
}
}
}
}
</style>
<style lang="less">
.custom-line {
margin: 4px;
background: rgba(31, 35, 41, 0.15);
border: 0;
height: 1px;
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<div class="template">
<div class="photo">
<div class="img" :style="classBackground"></div>
</div>
<div class="apply">
<span :title="template.title" class="name ellipsis"> {{ template.title }} </span>
<el-button class="flex-center" secondary @click="templateInnerPreview">{{
t('dataset.preview')
}}</el-button>
<el-button class="flex-center" type="primary" @click="apply">{{
t('commons.apply')
}}</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { computed } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
const { t } = useI18n()
const emits = defineEmits(['templateApply', 'templatePreview'])
const props = defineProps({
template: {
type: Object,
default() {
return {}
}
},
baseUrl: {
type: String
}
})
const classBackground = computed(() => {
return {
background: `url(${imgUrlTrans(thumbnailUrl.value)}) no-repeat`,
'background-size': `100% 100%`
}
})
const thumbnailUrl = computed(() => {
if (props.template.thumbnail.indexOf('http') > -1) {
return props.template.thumbnail
} else {
return props.baseUrl + props.template.thumbnail
}
})
const apply = () => {
emits('templateApply', props.template)
}
const templateInnerPreview = e => {
emits('templatePreview', props.template.id)
}
</script>
<style scoped lang="less">
.template {
overflow: hidden;
border: 1px solid #d9d9d9;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
width: 181px;
height: 141px;
margin-left: 16px;
position: relative;
.photo {
padding: 4px;
padding-bottom: 0;
height: 101px;
width: 100%;
.img {
width: 100%;
height: 100%;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
}
.apply {
padding: 8px 12px;
background: #fff;
border-top: 1px solid #d9d9d9;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
height: 39px;
display: flex;
flex-wrap: wrap;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
justify-content: space-between;
.ed-button {
min-width: 73px;
height: 28px;
display: none;
font-size: 12px;
line-height: 20px;
padding: 0;
margin-top: 8px;
& + .ed-button {
margin-left: 8px;
}
}
.name {
color: #1f2329;
font-family: PingFang SC;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 22px;
width: 100%;
}
}
&:hover {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
.apply {
transition: 0.3s;
height: 73px;
}
.ed-button {
display: block;
}
}
}
</style>

View File

@ -1,14 +1,18 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, shallowRef, computed } from 'vue'
import { ref, shallowRef, computed, reactive, watch, nextTick } from 'vue'
import imgtest from '@/assets/img/dataease-10000Star.jpg'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useRequestStoreWithOut } from '@/store/modules/request'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import ShortcutTable from './ShortcutTable.vue'
import { useUserStoreWithOut } from '@/store/modules/user'
import { useRouter } from 'vue-router'
import { searchMarketRecommend } from '@/api/templateMarket'
import TemplateBranchItem from '@/views/workbranch/TemplateBranchItem.vue'
import { ElMessage } from 'element-plus-secondary'
import { decompression } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
const userStore = useUserStoreWithOut()
const interactiveStore = interactiveStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
@ -16,9 +20,8 @@ const requestStore = useRequestStoreWithOut()
const { t } = useI18n()
const busiDataMap = computed(() => interactiveStore.getData)
const busiCountCardList = ref([])
const showTemplate = ref(false)
const { wsCache } = useCache()
const { push } = useRouter()
const router = useRouter()
const quickCreationList = shallowRef([
@ -45,11 +48,89 @@ const handleExpandFold = () => {
expandFold.value = expandFold.value === 'expand' ? 'fold' : 'expand'
}
const tabBtnList = ['推荐仪表板', t('auth.screen'), '应用模版']
const activeTabBtn = ref('推荐仪表板')
const showTemplate = computed(() => {
return state.networkStatus && state.hasResult
})
const activeTabChange = value => {
activeTabBtn.value = value
}
const tabBtnList = [
{
name: '推荐仪表板',
value: 'PANEL'
},
{
name: t('auth.screen'),
value: 'SCREEN'
},
{
name: '应用模版',
value: 'APP'
}
]
const activeTabBtn = ref('PANEL')
const typeList = quickCreationList.value.map(ele => ele.name)
typeList.unshift('all_types')
const state = reactive({
templateType: 'PANEL',
baseUrl: null,
marketTemplatePreviewShowList: [],
hasResult: false,
networkStatus: true,
loading: false,
dvCreateForm: {
resourceName: null,
name: null,
pid: null,
nodeType: 'panel',
templateUrl: null,
newFrom: 'new_market_template',
panelType: 'self',
panelStyle: {},
panelData: '[]'
}
})
watch(
() => activeTabBtn.value,
value => {
initTemplateShow()
}
)
const initMarketTemplate = async () => {
await searchMarketRecommend()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.marketTemplatePreviewShowList = rsp.data.contents
state.hasResult = true
initTemplateShow()
})
.catch(() => {
state.networkStatus = false
})
}
const initTemplateShow = () => {
state.hasResult = false
state.marketTemplatePreviewShowList.forEach(template => {
template.showFlag = templateShowCur(template)
if (template.showFlag) {
state.hasResult = true
}
})
}
const templateShowCur = templateItem => {
let templateTypeMarch = false
if (activeTabBtn.value === templateItem.templateType) {
templateTypeMarch = true
}
return templateTypeMarch
}
const fillCardInfo = () => {
for (const key in busiDataMap.value) {
if (key !== '3') {
@ -99,7 +180,48 @@ const createDatasource = () => {
const baseUrl = '#/data/datasource?opt=create'
window.open(baseUrl, '_blank')
}
const templatePreview = previewId => {
wsCache.set(`template-preview-id`, previewId)
toTemplateMarket()
}
const templateApply = template => {
state.dvCreateForm.name = template.title
state.dvCreateForm.templateUrl = template.metas.theme_repo
state.dvCreateForm.resourceName = template.id
apply()
}
const apply = () => {
if (!state.dvCreateForm.templateUrl) {
ElMessage.warning('未获取模板下载链接请联系模板市场官方')
return false
}
state.loading = true
decompression(state.dvCreateForm)
.then(response => {
state.loading = false
const templateData = response.data
// do create
wsCache.set(`de-template-data`, JSON.stringify(templateData))
const baseUrl =
templateData.type === 'dataV'
? '#/dvCanvas?opt=create&createType=template'
: '#/dashboard?opt=create&createType=template'
window.open(baseUrl, '_blank')
})
.catch(() => {
state.loading = false
})
}
const toTemplateMarket = () => {
push('/template-market/index')
}
fillCardInfo()
initMarketTemplate()
</script>
<template>
@ -161,11 +283,11 @@ fillCardInfo()
</div>
</div>
<div class="template-market-dashboard">
<div v-if="showTemplate" class="template-market">
<div class="template-market">
<div class="label">
模版市场
<div class="expand-all">
<button class="all flex-center">查看全部</button>
<button class="all flex-center" @click="toTemplateMarket">查看全部</button>
<el-divider direction="vertical" />
<button @click="handleExpandFold" class="expand flex-center">
{{ t(`visualization.${expandFold}`) }}
@ -175,37 +297,37 @@ fillCardInfo()
<template v-if="expandFold === 'fold'">
<div class="tab-btn">
<div
@click="activeTabBtn = ele"
v-for="ele in tabBtnList"
:key="ele"
:class="activeTabBtn === ele && 'active'"
:key="ele.value"
:class="activeTabBtn === ele.value && 'active'"
@click="activeTabChange(ele.value)"
class="main-btn"
>
{{ ele }}
{{ ele.name }}
</div>
</div>
<div class="template-list">
<div class="template">
<div class="photo">
<img :src="imgtest" alt="" />
</div>
<div class="apply">
<span title="电子银行业务分析" class="name ellipsis"> 电子银行业务分析 </span>
<el-button class="flex-center" secondary>{{ t('dataset.preview') }}</el-button>
<el-button class="flex-center" type="primary">{{ t('commons.apply') }}</el-button>
</div>
</div>
<div class="template">
<div class="photo">
<img :src="imgtest" alt="" />
</div>
<div class="apply">
<span title="电子银行业务分析" class="name ellipsis"> 电子银行业务分析 </span>
<el-button class="flex-center" secondary>{{ t('dataset.preview') }}</el-button>
<el-button class="flex-center" type="primary">{{ t('commons.apply') }}</el-button>
</div>
</div>
<div class="template-list" v-show="state.networkStatus && state.hasResult">
<template-branch-item
v-for="(template, index) in state.marketTemplatePreviewShowList"
v-show="template['showFlag']"
:key="index"
:template="template"
:base-url="state.baseUrl"
@templateApply="templateApply"
@templatePreview="templatePreview"
>
</template-branch-item>
</div>
<el-row v-show="state.networkStatus && !state.hasResult" class="template-empty">
<div style="text-align: center">
<Icon name="no_result" class="no-result"></Icon>
<br />
<span class="no-result-tips">没有找到相关模版</span>
</div>
</el-row>
<el-row v-show="!state.networkStatus" class="template-empty">
{{ t('visualization.market_network_tips') }}
</el-row>
</template>
</div>
<shortcut-table :expand="expandFold === 'expand'" />
@ -473,78 +595,25 @@ fillCardInfo()
.template-list {
display: flex;
margin-left: -16px;
.template {
border: 1px solid #d9d9d9;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
width: 181px;
height: 141px;
margin-left: 16px;
position: relative;
.photo {
padding: 4px;
padding-bottom: 0;
height: 101px;
img {
width: 100%;
height: 100%;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
}
.apply {
padding: 8px 12px;
background: #fff;
border-top: 1px solid #d9d9d9;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
height: 39px;
display: flex;
flex-wrap: wrap;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
justify-content: space-between;
.ed-button {
min-width: 73px;
height: 28px;
display: none;
font-size: 12px;
line-height: 20px;
padding: 0;
margin-top: 8px;
& + .ed-button {
margin-left: 8px;
}
}
.name {
color: #1f2329;
font-family: PingFang SC;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 22px;
width: 100%;
}
}
&:hover {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
.apply {
height: 73px;
}
.ed-button {
display: block;
}
}
}
}
}
}
}
.template-empty {
display: flex;
align-items: center;
justify-content: center;
background: rgba(239, 240, 241, 1);
color: rgba(100, 106, 115, 1);
min-height: 60px;
}
.no-result {
width: 74px;
height: 74px;
}
.no-result-tips {
font-size: 14px;
color: rgba(100, 106, 115, 1);
}
</style>

@ -1 +1 @@
Subproject commit 723bb9ff960159769673ac58982f07b18e6aa181
Subproject commit e31c9212056eae3656aa534e3cc778d00001fe98

View File

@ -66,6 +66,7 @@ curl http://127.0.0.1:9180/apisix/admin/services/10 -X PUT -H "X-API-KEY: $DE_AP
"X-DE-TOKEN",
"X-DE-LINK-TOKEN",
"X-EMBEDDED-TOKEN",
"X-DE-ASK-TOKEN",
"Content-Type"
],
"request_method": "POST",

View File

@ -11,6 +11,7 @@ services:
- ${DE_BASE}/dataease2.0/logs:/opt/dataease2.0/logs
- ${DE_BASE}/dataease2.0/data/static-resource:/opt/dataease2.0/data/static-resource
- ${DE_BASE}/dataease2.0/cache:/opt/dataease2.0/cache
- ${DE_BASE}/dataease2.0/data/geo:/opt/dataease2.0/data/geo
depends_on:
DE_MYSQL_HOST:
condition: service_healthy

View File

@ -11,7 +11,6 @@ spring:
username: ${DE_MYSQL_USER}
password: ${DE_MYSQL_PASSWORD}
dataease:
origin-list: localhost:8080,localhost:8100,localhost:9080
apisix-api:
domain: http://apisix:9180
key: DE_APISIX_KEY

View File

@ -25,7 +25,7 @@
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<h2.version>1.4.199</h2.version>
<knife4j.version>4.1.0</knife4j.version>
<calcite-core.version>1.35.0</calcite-core.version>
<calcite-core.version>1.36.0</calcite-core.version>
<commons-dbcp2.version>2.6.0</commons-dbcp2.version>
<antlr.version>3.5.2</antlr.version>
<java-jwt.version>3.12.1</java-jwt.version>

View File

@ -0,0 +1,18 @@
package io.dataease.api.map;
import io.dataease.api.map.dto.GeometryNodeCreator;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
@Tag(name = "地理信息")
public interface GeoApi {
@PostMapping(value = "/save", consumes = {"multipart/form-data"})
void saveMapGeo(@RequestPart("request") GeometryNodeCreator request, @RequestPart(value = "file") MultipartFile file);
@PostMapping("/delete/{id}")
void deleteGeo(@PathVariable("id") String id);
}

View File

@ -0,0 +1,15 @@
package io.dataease.api.map.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class GeometryNodeCreator implements Serializable {
private String code;
private String name;
private String pid;
}

View File

@ -18,6 +18,7 @@ public class AreaNode implements Serializable {
private String level;
private String name;
private String pid;
private boolean custom = false;
/**
* 国家代码
*/

View File

@ -2,6 +2,8 @@ package io.dataease.api.template;
import io.dataease.api.template.request.TemplateMarketSearchRequest;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.api.template.response.MarketPreviewBaseResponse;
import io.dataease.api.template.vo.MarketMetaDataVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -16,8 +18,16 @@ public interface TemplateMarketApi {
@GetMapping("/search")
MarketBaseResponse searchTemplate();
@GetMapping("/searchRecommend")
MarketBaseResponse searchTemplateRecommend();
@GetMapping("/searchPreview")
MarketPreviewBaseResponse searchTemplatePreview();
@GetMapping("/categories")
List<String> categories();
@GetMapping("/categoriesObject")
List<MarketMetaDataVO> categoriesObject() ;
}

View File

@ -12,7 +12,7 @@ import java.util.List;
@Data
@NoArgsConstructor
public class TemplateMarketDTO {
public class TemplateMarketDTO implements Comparable<TemplateMarketDTO> {
private String id;
private String title;
private String status;
@ -20,15 +20,35 @@ public class TemplateMarketDTO {
private String editorType;
private String summary;
private String thumbnail;
private Boolean showFlag = true;
private String suggest = "N";
private Long recentUseTime = 0L;
private String templateType;
private List<MarketCategoryVO> categories;
private MarketMetasVO metas;
public TemplateMarketDTO(String id, String title,String themeRepo,String templateUrl,String categoryName) {
public TemplateMarketDTO(String id, String title, String themeRepo, String templateUrl, String categoryName, String templateType, Long recentUseTime,String suggest) {
this.id = id;
this.title = title;
this.categories = Arrays.asList(new MarketCategoryVO(categoryName),new MarketCategoryVO("全部"));
this.categories = Arrays.asList(new MarketCategoryVO(categoryName), new MarketCategoryVO("全部"));
this.metas = new MarketMetasVO(templateUrl);
this.thumbnail = themeRepo;
this.templateType = templateType;
if (recentUseTime != null) {
this.recentUseTime = recentUseTime;
}
if("Y".equalsIgnoreCase(suggest)){
this.suggest="Y";
}
}
@Override
public int compareTo(TemplateMarketDTO other) {
return Long.compare(other.recentUseTime, this.recentUseTime);
}
}

View File

@ -0,0 +1,24 @@
package io.dataease.api.template.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/27
*/
@Data
@NoArgsConstructor
public class TemplateMarketPreviewInfoDTO {
private String categoryType;
List<TemplateMarketDTO> contents;
public TemplateMarketPreviewInfoDTO(String categoryType, List<TemplateMarketDTO> contents) {
this.categoryType = categoryType;
this.contents = contents;
}
}

View File

@ -0,0 +1,29 @@
package io.dataease.api.template.response;
import io.dataease.api.template.dto.TemplateMarketDTO;
import io.dataease.api.template.dto.TemplateMarketPreviewInfoDTO;
import lombok.Data;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/6 17:43
*/
@Data
public class MarketPreviewBaseResponse {
private String baseUrl;
private List<String> categories;
private List<TemplateMarketPreviewInfoDTO> contents;
public MarketPreviewBaseResponse() {
}
public MarketPreviewBaseResponse(String baseUrl, List<String> categories, List<TemplateMarketPreviewInfoDTO> contents) {
this.baseUrl = baseUrl;
this.categories = categories;
this.contents = contents;
}
}

View File

@ -24,6 +24,9 @@ public class MarketApplicationSpecVO {
private String readmeName;
// 是否推荐
private String suggest;
private List<MarketApplicationSpecScreenshotBaseVO> screenshots;
private List<MarketApplicationSpecLinkVO> links;

View File

@ -1,6 +1,7 @@
package io.dataease.api.template.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Author: wangjiahao
@ -8,8 +9,13 @@ import lombok.Data;
* Description:
*/
@Data
@NoArgsConstructor
public class MarketMetaDataVO {
private String slug;
private String value;
private String label;
public MarketMetaDataVO(String value, String label) {
this.label = label;
}
}

View File

@ -8,6 +8,8 @@ public class DataVisualizationBaseRequest extends DataVisualizationVO {
private String opt;
private String resourceName;
private Boolean moveFromUpdate = false;
private String optType;

View File

@ -0,0 +1,25 @@
package io.dataease.api.permissions.apikey.api;
import io.dataease.api.permissions.apikey.dto.ApikeyEnableEditor;
import io.dataease.api.permissions.apikey.vo.ApiKeyVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface ApiKeyApi {
@PostMapping("/generate")
void generate();
@GetMapping("/query")
List<ApiKeyVO> query();
@PostMapping("/switch")
void switchEnable(@RequestBody ApikeyEnableEditor editor);
@PostMapping("/delete/{id}")
void delete(@PathVariable("id") Long id);
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.permissions.apikey.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApikeyEnableEditor implements Serializable {
private Long id;
private Boolean enable = false;
}

View File

@ -0,0 +1,22 @@
package io.dataease.api.permissions.apikey.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
public class ApiKeyVO implements Serializable {
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
private String accessKey;
private String accessSecret;
private Boolean enable;
private Long createTime;
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.permissions.setting.api;
import io.dataease.api.permissions.setting.vo.PerSettingItemVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface PerSettingApi {
@GetMapping("/basic/query")
List<PerSettingItemVO> basicSetting();
@PostMapping("/baisc/save")
void saveBasic(@RequestBody List<Object> settings);
}

View File

@ -0,0 +1,19 @@
package io.dataease.api.permissions.setting.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class PerSettingItemVO implements Serializable {
private String pkey;
private String pval;
private String type;
private Integer sort;
}

View File

@ -18,6 +18,7 @@ public class AuthConstant {
public final static String USER_IMPORT_ERROR_KEY = "USER-IMPORT-ERROR-KEY";
public final static String LINK_TOKEN_KEY = "X-DE-LINK-TOKEN";
public final static String ASK_TOKEN_KEY = "X-DE-ASK-TOKEN";
public final static String DE_EXECUTE_VERSION = "X-DE-EXECUTE-VERSION";

View File

@ -16,8 +16,10 @@ public class StaticResourceConstants {
public static String WORK_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "static-resource" + FILE_SEPARATOR;
public static String MAP_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "map";
public static String CUSTOM_MAP_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "geo";
public static String MAP_URL = "/map";
public static String GEO_URL = "/geo";
/**
* Upload prefix.

View File

@ -0,0 +1,6 @@
package io.dataease.constant;
public class XpackSettingConstants {
public static final String AUTO_CREATE_USER = "basic.autoCreateUser";
}

View File

@ -0,0 +1,36 @@
package io.dataease.utils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import static java.nio.charset.StandardCharsets.UTF_8;
public class AesUtils {
public static String aesDecrypt(String src, String secretKey, String iv) {
if (StringUtils.isBlank(secretKey)) {
throw new RuntimeException("secretKey is empty");
}
try {
byte[] raw = secretKey.getBytes(UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv1 = new IvParameterSpec(iv.getBytes());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv1);
byte[] encrypted1 = Base64.decodeBase64(src);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, UTF_8);
} catch (BadPaddingException | IllegalBlockSizeException e) {
// 解密的原字符串为非加密字符串则直接返回原字符串
return src;
} catch (Exception e) {
throw new RuntimeException("decrypt errorplease check parameters", e);
}
}
}

View File

@ -0,0 +1,14 @@
package io.dataease.utils;
import cn.hutool.core.collection.ListUtil;
import io.dataease.constant.XpackSettingConstants;
import java.util.List;
public class SystemSettingUtils {
public static boolean xpackSetting(String pkey) {
List<String> xpackSettingList = ListUtil.toList(XpackSettingConstants.AUTO_CREATE_USER);
return xpackSettingList.contains(pkey);
}
}

View File

@ -36,7 +36,7 @@ public class WhitelistUtils {
|| StringUtils.startsWithAny(requestURI, "/static-resource/")
|| StringUtils.startsWithAny(requestURI, "/share/proxyInfo")
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content/")
|| StringUtils.startsWithAny(requestURI, "/platform/")
|| StringUtils.startsWithAny(requestURI, "/geo/")
|| StringUtils.startsWithAny(requestURI, "/map/");
}
}