diff --git a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java index 0fd46ef9a9..c90acdab05 100644 --- a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java +++ b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java @@ -118,6 +118,7 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put("/api/link/resourceDetail/**", "link"); filterChainDefinitionMap.put("/api/link/viewDetail/**", "link"); + filterChainDefinitionMap.put("/api/link/viewLog", "link"); filterChainDefinitionMap.put("/panel/group/exportDetails", ANON); filterChainDefinitionMap.put("/dataset/field/linkMultFieldValues", "link"); filterChainDefinitionMap.put("/dataset/field/linkMappingFieldValues", "link"); diff --git a/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java b/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java index 9a6489ddc8..2c309f8f09 100644 --- a/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java +++ b/backend/src/main/java/io/dataease/commons/constants/SysLogConstants.java @@ -37,6 +37,7 @@ public class SysLogConstants { UNBIND(17, "OPERATE_TYPE_UNBIND"); private Integer value; private String name; + OPERATE_TYPE(Integer value, String name) { this.value = value; this.name = name; @@ -62,7 +63,7 @@ public class SysLogConstants { DATASET(2, "SOURCE_TYPE_DATASET"), PANEL(3, "SOURCE_TYPE_PANEL"), VIEW(4, "SOURCE_TYPE_VIEW"), - /*LINK(5, "SOURCE_TYPE_LINK"),*/ + LINK(5, "SOURCE_TYPE_LINK"), USER(6, "SOURCE_TYPE_USER"), DEPT(7, "SOURCE_TYPE_DEPT"), ROLE(8, "SOURCE_TYPE_ROLE"), diff --git a/backend/src/main/java/io/dataease/commons/utils/DeLogUtils.java b/backend/src/main/java/io/dataease/commons/utils/DeLogUtils.java index d996a2d7f4..89f58261e2 100644 --- a/backend/src/main/java/io/dataease/commons/utils/DeLogUtils.java +++ b/backend/src/main/java/io/dataease/commons/utils/DeLogUtils.java @@ -34,14 +34,14 @@ public class DeLogUtils { DeLogUtils.logService = logService; } - public static SysLogDTO buildLog(OPERATE_TYPE operatetype, SOURCE_TYPE sourcetype, Object sourceIdValue, Object targetId, SOURCE_TYPE target_type ) { + public static SysLogDTO buildLog(OPERATE_TYPE operatetype, SOURCE_TYPE sourcetype, Object sourceIdValue, Object targetId, SOURCE_TYPE target_type) { SysLogDTO sysLogDTO = buildLog(operatetype, sourcetype, sourceIdValue, null, targetId, target_type); if (sourcetype == SysLogConstants.SOURCE_TYPE.DATASOURCE) { FolderItem folderItem = logManager.dsTypeInfoById(sourceIdValue.toString()); List items = new ArrayList<>(); items.add(folderItem); sysLogDTO.setPositions(items); - }else { + } else { List parentsAndSelf = logManager.justParents(sourceIdValue.toString(), sourcetype); sysLogDTO.setPositions(parentsAndSelf); } @@ -66,7 +66,7 @@ public class DeLogUtils { return sysLogDTO; } - public static SysLogDTO buildLog(OPERATE_TYPE operatetype, SOURCE_TYPE sourcetype, Object sourceIdValue, Object positionId, Object targetId, SOURCE_TYPE target_type ) { + public static SysLogDTO buildLog(OPERATE_TYPE operatetype, SOURCE_TYPE sourcetype, Object sourceIdValue, Object positionId, Object targetId, SOURCE_TYPE target_type) { SysLogDTO sysLogDTO = new SysLogDTO(); sysLogDTO.setOperateType(operatetype.getValue()); sysLogDTO.setSourceType(sourcetype.getValue()); @@ -83,14 +83,14 @@ public class DeLogUtils { List items = new ArrayList<>(); items.add(folderItem); sysLogDTO.setPositions(items); - }else { - if(sourcetype == SOURCE_TYPE.DRIVER_FILE){ + } else { + if (sourcetype == SOURCE_TYPE.DRIVER_FILE) { List parentsAndSelf = logManager.parentsAndSelf(positionId.toString(), SOURCE_TYPE.DRIVER); sysLogDTO.setPositions(parentsAndSelf); - }else if(sourcetype == SOURCE_TYPE.VIEW){ + } else if (sourcetype == SOURCE_TYPE.VIEW || sourcetype == SOURCE_TYPE.LINK) { List parentsAndSelf = logManager.parentsAndSelf(positionId.toString(), SOURCE_TYPE.PANEL); sysLogDTO.setPositions(parentsAndSelf); - }else { + } else { List parentsAndSelf = logManager.parentsAndSelf(positionId.toString(), sourcetype); sysLogDTO.setPositions(parentsAndSelf); } diff --git a/backend/src/main/java/io/dataease/commons/utils/IPUtils.java b/backend/src/main/java/io/dataease/commons/utils/IPUtils.java new file mode 100644 index 0000000000..8a9cc8088e --- /dev/null +++ b/backend/src/main/java/io/dataease/commons/utils/IPUtils.java @@ -0,0 +1,45 @@ +package io.dataease.commons.utils; + +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.Arrays; + +public class IPUtils { + + private static final String HEAD_KEYS = "x-forwarded-for, Proxy-Client-IP, WL-Proxy-Client-IP"; + + private static final String UNKNOWN = "unknown"; + + private static final String LOCAL_IP_KEY = "0:0:0:0:0:0:0:1"; + private static final String LOCAL_IP_VAL = "127.0.0.1"; + + public static String get() { + + String ipStr = null; + boolean isProxy = false; + + HttpServletRequest request = null; + try { + request = ServletUtils.request(); + } catch (Exception e) { + LogUtil.error(e.getMessage(), e); + return null; + } + String[] keyArr = HEAD_KEYS.split(","); + for (String key : keyArr) { + String header = request.getHeader(key.trim()); + if (StringUtils.isNotBlank(header) && !StringUtils.equalsIgnoreCase(UNKNOWN, header)) { + ipStr = header; + isProxy = true; + break; + } + } + + if (!isProxy) { + ipStr = request.getRemoteAddr(); + } + ipStr = Arrays.stream(ipStr.split(",")).filter(item -> StringUtils.isNotBlank(item) && !StringUtils.equalsIgnoreCase(UNKNOWN, item.trim())).findFirst().orElse(ipStr); + return StringUtils.equals(LOCAL_IP_KEY, ipStr) ? LOCAL_IP_VAL : ipStr; + } +} diff --git a/backend/src/main/java/io/dataease/controller/panel/api/LinkApi.java b/backend/src/main/java/io/dataease/controller/panel/api/LinkApi.java index 158e0abaca..a27b37eae3 100644 --- a/backend/src/main/java/io/dataease/controller/panel/api/LinkApi.java +++ b/backend/src/main/java/io/dataease/controller/panel/api/LinkApi.java @@ -4,12 +4,14 @@ import com.github.xiaoymin.knife4j.annotations.ApiSupport; import io.dataease.auth.annotation.DePermission; import io.dataease.commons.constants.DePermissionType; import io.dataease.controller.request.chart.ChartExtRequest; +import io.dataease.controller.request.panel.PanelViewLogRequest; import io.dataease.controller.request.panel.link.*; import io.dataease.dto.panel.link.GenerateDto; import io.dataease.dto.panel.link.ValidateDto; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; +import springfox.documentation.annotations.ApiIgnore; import java.util.Map; @@ -58,9 +60,15 @@ public interface LinkApi { @ApiOperation("视图详细信息") @PostMapping("/viewDetail/{viewId}/{panelId}") Object viewDetail(@PathVariable("viewId") String viewId, @PathVariable("panelId") String panelId, - @RequestBody ChartExtRequest requestList) throws Exception; + @RequestBody ChartExtRequest requestList) throws Exception; @ApiOperation("压缩链接") @PostMapping("/shortUrl") String shortUrl(@RequestBody Map param); + + @ApiIgnore + @PostMapping("/viewLog") + void viewLinkLog(@RequestBody LinkViewLogRequest request); + + } diff --git a/backend/src/main/java/io/dataease/controller/panel/server/LinkServer.java b/backend/src/main/java/io/dataease/controller/panel/server/LinkServer.java index d927a86b16..2e0be45aec 100644 --- a/backend/src/main/java/io/dataease/controller/panel/server/LinkServer.java +++ b/backend/src/main/java/io/dataease/controller/panel/server/LinkServer.java @@ -3,6 +3,9 @@ package io.dataease.controller.panel.server; import com.auth0.jwt.JWT; import com.auth0.jwt.interfaces.DecodedJWT; import io.dataease.auth.filter.F2CLinkFilter; +import io.dataease.commons.constants.SysLogConstants; +import io.dataease.commons.utils.DeLogUtils; +import io.dataease.plugins.common.base.domain.PanelGroupWithBLOBs; import io.dataease.plugins.common.base.domain.PanelLink; import io.dataease.controller.panel.api.LinkApi; import io.dataease.controller.request.chart.ChartExtRequest; @@ -111,4 +114,19 @@ public class LinkServer implements LinkApi { String resourceId = param.get("resourceId"); return panelLinkService.getShortUrl(resourceId); } + + @Override + public void viewLinkLog(LinkViewLogRequest request) { + String panelId = request.getPanelId(); + Boolean mobile = request.getMobile(); + Long userId = request.getUserId(); + SysLogConstants.OPERATE_TYPE operateType = SysLogConstants.OPERATE_TYPE.PC_VIEW; + if (ObjectUtils.isNotEmpty(mobile) && mobile) { + operateType = SysLogConstants.OPERATE_TYPE.MB_VIEW; + } + if (ObjectUtils.isEmpty(userId)) return; + PanelGroupWithBLOBs panelGroupWithBLOBs = panelLinkService.resourceInfo(panelId); + String pid = panelGroupWithBLOBs.getPid(); + DeLogUtils.save(operateType, SysLogConstants.SOURCE_TYPE.LINK, panelId, pid, userId, SysLogConstants.SOURCE_TYPE.USER); + } } diff --git a/backend/src/main/java/io/dataease/controller/request/panel/link/LinkViewLogRequest.java b/backend/src/main/java/io/dataease/controller/request/panel/link/LinkViewLogRequest.java new file mode 100644 index 0000000000..6844c5a49d --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/request/panel/link/LinkViewLogRequest.java @@ -0,0 +1,12 @@ +package io.dataease.controller.request.panel.link; + +import io.dataease.controller.request.panel.PanelViewLogRequest; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class LinkViewLogRequest extends PanelViewLogRequest implements Serializable { + + private Long userId; +} diff --git a/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java b/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java index 706d4a2762..66a644b2c4 100644 --- a/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java +++ b/backend/src/main/java/io/dataease/dto/SysLogGridDTO.java @@ -22,4 +22,7 @@ public class SysLogGridDTO implements Serializable { @ApiModelProperty("操作时间") private Long time; + + @ApiModelProperty("IP地址") + private String ip; } diff --git a/backend/src/main/java/io/dataease/service/sys/log/LogManager.java b/backend/src/main/java/io/dataease/service/sys/log/LogManager.java index 3ee86d0cfb..975a48bf73 100644 --- a/backend/src/main/java/io/dataease/service/sys/log/LogManager.java +++ b/backend/src/main/java/io/dataease/service/sys/log/LogManager.java @@ -28,15 +28,12 @@ public class LogManager { protected static final String contentFormat = "【%s】"; - protected static final String positionFormat = "在【%s】"; - - protected static final String format = "给%s【%s】"; protected Gson gson = new Gson(); - protected Type type = new TypeToken>() {}.getType(); - + protected Type type = new TypeToken>() { + }.getType(); @Resource @@ -47,26 +44,28 @@ public class LogManager { public String detailInfo(SysLogWithBLOBs vo) { - String sourceName = vo.getSourceName(); String position = null; String operateTypeName = SysLogConstants.operateTypeName(vo.getOperateType()); operateTypeName = Translator.get(operateTypeName); String sourceTypeName = SysLogConstants.sourceTypeName(vo.getSourceType()); sourceTypeName = Translator.get(sourceTypeName); - String result = operateTypeName + sourceTypeName + String.format(contentFormat, sourceName) + remarkInfo(vo); - + String result = operateTypeName + sourceTypeName + String.format(contentFormat, vo.getSourceName()); + if (vo.getSourceType() != SysLogConstants.SOURCE_TYPE.LINK.getValue()) { + result += remarkInfo(vo, false); + } if ((position = vo.getPosition()) != null) { List folderItems = gson.fromJson(position, type); String template = folderItems.stream().map(folderItem -> folderItem.getName()).collect(Collectors.joining("/")); - String positionResult = String.format(positionFormat, template); - return positionResult + result; + String positionResult = String.format(Translator.get("I18N_LOG_FORMAT_POSITION"), template); + result = positionResult + result; + } + if (vo.getSourceType() == SysLogConstants.SOURCE_TYPE.LINK.getValue()) { + result = remarkInfo(vo, true) + result; } return result; } - - - public String remarkInfo(SysLogWithBLOBs vo) { + public String remarkInfo(SysLogWithBLOBs vo, Boolean isPrefix) { String remakrk = null; if ((remakrk = vo.getRemark()) != null) { String targetTypeName = null; @@ -77,14 +76,16 @@ public class LogManager { Integer targetType = item.getType(); targetTypeName = SysLogConstants.sourceTypeName(targetType); targetTypeName = Translator.get(targetTypeName); - return String.format(format, targetTypeName, template); + if (isPrefix) { + return String.format(Translator.get("I18N_LOG_FORMAT_PREFIX"), targetTypeName, template); + } + return String.format(Translator.get("I18N_LOG_FORMAT"), targetTypeName, template); } } return ""; } - private LogTypeItem parentIds(String id, Integer value) { LogTypeItem result = new LogTypeItem(); String typeValue = ""; @@ -119,7 +120,7 @@ public class LogManager { } - private List parentInfos(List ids, Integer value){ + private List parentInfos(List ids, Integer value) { List folderItems = extSysLogMapper.idAndName(ids, value); if (value == 3) { folderItems.forEach(item -> { @@ -164,7 +165,7 @@ public class LogManager { ArrayList dataSourceTypes = new ArrayList<>(datasourceService.types()); String name = null; for (int i = 0; i < dataSourceTypes.size(); i++) { - if (dataSourceTypes.get(i).getType().equals(typeId)){ + if (dataSourceTypes.get(i).getType().equals(typeId)) { name = dataSourceTypes.get(i).getName(); break; } diff --git a/backend/src/main/java/io/dataease/service/sys/log/LogService.java b/backend/src/main/java/io/dataease/service/sys/log/LogService.java index c668c57e39..913f1991fa 100644 --- a/backend/src/main/java/io/dataease/service/sys/log/LogService.java +++ b/backend/src/main/java/io/dataease/service/sys/log/LogService.java @@ -9,6 +9,7 @@ import io.dataease.commons.constants.ParamConstants; import io.dataease.commons.constants.SysLogConstants; import io.dataease.commons.utils.AuthUtils; import io.dataease.commons.utils.BeanUtils; +import io.dataease.commons.utils.IPUtils; import io.dataease.commons.utils.ServletUtils; import io.dataease.controller.sys.base.ConditionEntity; import io.dataease.controller.sys.request.KeyGridRequest; @@ -28,6 +29,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.*; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -46,6 +48,8 @@ public class LogService { // 仪表板的额外操作 分享以及公共链接 private static Integer[] panel_ext_ope = {4, 5, 8, 9, 10}; + private static Integer[] link_ext_ope = {13, 14}; + // 驱动文件操作 上传, 删除 private static Integer[] driver_file_ope = {11, 3}; @@ -247,6 +251,16 @@ public class LogService { results.add(folderItem); } + for (int i = 0; i < link_ext_ope.length; i++) { + SysLogConstants.SOURCE_TYPE sourceType = SysLogConstants.SOURCE_TYPE.LINK; + FolderItem folderItem = new FolderItem(); + folderItem.setId(link_ext_ope[i] + "-" + sourceType.getValue()); + String operateTypeName = SysLogConstants.operateTypeName(link_ext_ope[i]); + String sourceTypeName = sourceType.getName(); + folderItem.setName(Translator.get(operateTypeName) + Translator.get(sourceTypeName)); + results.add(folderItem); + } + FolderItem userLogin = new FolderItem(); SysLogConstants.OPERATE_TYPE operateTypeLogin = SysLogConstants.OPERATE_TYPE.LOGIN; SysLogConstants.SOURCE_TYPE sourceTypeLogin = SysLogConstants.SOURCE_TYPE.USER; @@ -308,6 +322,7 @@ public class LogService { sysLogGridDTO.setTime(vo.getTime()); sysLogGridDTO.setUser(vo.getNickName()); sysLogGridDTO.setDetail(logManager.detailInfo(vo)); + sysLogGridDTO.setIp(vo.getIp()); return sysLogGridDTO; } @@ -330,12 +345,14 @@ public class LogService { sysLogWithBLOBs.setLoginName(sysLogDTO.getSourceName()); sysLogWithBLOBs.setNickName(sysLogDTO.getSourceName()); } + sysLogWithBLOBs.setIp(IPUtils.get()); sysLogMapper.insert(sysLogWithBLOBs); } public void exportExcel(KeyGridRequest request) throws Exception { + request = logRetentionProxy(request); request = detailRequest(request); String keyWord = request.getKeyWord(); List ids = null; @@ -355,24 +372,25 @@ public class LogService { List details = lists.stream().map(item -> { String operateTypeName = SysLogConstants.operateTypeName(item.getOperateType()); String sourceTypeName = SysLogConstants.sourceTypeName(item.getSourceType()); - String[] row = new String[4]; + String[] row = new String[5]; row[0] = Translator.get(operateTypeName) + " " + Translator.get(sourceTypeName); row[1] = logManager.detailInfo(item); row[2] = item.getNickName(); - row[3] = DateUtil.formatDateTime(new Date(item.getTime())); + row[3] = item.getIp(); + row[4] = DateUtil.formatDateTime(new Date(item.getTime())); return row; }).collect(Collectors.toList()); - String[] headArr = {"操作类型", "详情", "用户", "时间"}; + String[] headArr = {"操作类型", "详情", "用户", "IP地址", "时间"}; details.add(0, headArr); - HSSFWorkbook wb = new HSSFWorkbook(); + XSSFWorkbook wb = new XSSFWorkbook(); //明细sheet - HSSFSheet detailsSheet = wb.createSheet("数据"); + XSSFSheet detailsSheet = wb.createSheet("数据"); //给单元格设置样式 - CellStyle cellStyle = wb.createCellStyle(); - Font font = wb.createFont(); + XSSFCellStyle cellStyle = wb.createCellStyle(); + XSSFFont font = wb.createFont(); //设置字体大小 font.setFontHeightInPoints((short) 12); //设置字体加粗 @@ -386,11 +404,11 @@ public class LogService { if (CollectionUtils.isNotEmpty(details)) { for (int i = 0; i < details.size(); i++) { - HSSFRow row = detailsSheet.createRow(i); + XSSFRow row = detailsSheet.createRow(i); String[] rowData = details.get(i); if (rowData != null) { for (int j = 0; j < rowData.length; j++) { - HSSFCell cell = row.createCell(j); + XSSFCell cell = row.createCell(j); cell.setCellValue(rowData[j]); if (i == 0) {// 头部 cell.setCellStyle(cellStyle); @@ -406,7 +424,7 @@ public class LogService { //文件名称 String fileName = "DataEase操作日志"; String encodeFileName = URLEncoder.encode(fileName, "UTF-8"); - response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName + ".xls"); + response.setHeader("Content-disposition", "attachment;filename=" + encodeFileName + ".xlsx"); wb.write(outputStream); outputStream.flush(); outputStream.close(); diff --git a/backend/src/main/resources/db/migration/V44__1.17.sql b/backend/src/main/resources/db/migration/V44__1.17.sql new file mode 100644 index 0000000000..833626b004 --- /dev/null +++ b/backend/src/main/resources/db/migration/V44__1.17.sql @@ -0,0 +1,4 @@ +ALTER TABLE `sys_log` + CHANGE COLUMN `user_id` `user_id` BIGINT NULL COMMENT '操作人', + CHANGE COLUMN `login_name` `login_name` VARCHAR (255) NULL COMMENT '登录账号', + CHANGE COLUMN `nick_name` `nick_name` VARCHAR (255) NULL COMMENT '姓名'; \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index a3faecf6ec..951dca55a6 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -167,6 +167,7 @@ SOURCE_TYPE_ROLE=ROLE SOURCE_TYPE_DRIVER=DRIVER SOURCE_TYPE_DRIVER_FILE=DRIVER FILE SOURCE_TYPE_MENU=MENU +SOURCE_TYPE_LINK=PUBLIC LINK I18N_OPERATE_TYPE=Operation type I18N_DETAIL=Operation details I18N_USER=Operator @@ -241,3 +242,7 @@ I18N_APP_NO_DATASOURCE=This panel don't have datasource I18N_APP_ONE_DATASOURCE_TIPS=This panel should have only one datasource I18N_PROHIBIT_SCANNING_TO_CREATE_USER=Prohibit scanning code to create user +I18N_LOG_FORMAT_POSITION=IN\u3010%s\u3011 +I18N_LOG_FORMAT=TO %s\u3010%s\u3011 +I18N_LOG_FORMAT_PREFIX=With authority of %s\u3010%s\u3011 + diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 329af67e3d..6ec5cc2064 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -167,6 +167,7 @@ SOURCE_TYPE_ROLE=\u89D2\u8272 SOURCE_TYPE_DRIVER=\u9A71\u52A8 SOURCE_TYPE_DRIVER_FILE=\u9A71\u52A8\u6587\u4EF6 SOURCE_TYPE_MENU=\u83DC\u5355 +SOURCE_TYPE_LINK=\u516C\u5171\u94FE\u63A5 I18N_OPERATE_TYPE=\u64CD\u4F5C\u7C7B\u578B I18N_DETAIL=\u64CD\u4F5C\u8BE6\u60C5 I18N_USER=\u64CD\u4F5C\u4EBA @@ -241,3 +242,7 @@ I18N_APP_NO_DATASOURCE=\u6CA1\u6709\u627E\u5230\u6570\u636E\u6E90 I18N_APP_ONE_DATASOURCE_TIPS=\u8BE5\u4EEA\u8868\u677F\u53EA\u80FD\u5B58\u5728\u4E00\u4E2A\u6570\u636E\u6E90 I18N_PROHIBIT_SCANNING_TO_CREATE_USER=\u7981\u6B62\u626B\u7801\u521B\u5EFA\u7528\u6237\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\uFF01 +I18N_LOG_FORMAT_POSITION=\u5728\u3010%s\u3011 +I18N_LOG_FORMAT=\u7ED9%s\u3010%s\u3011 +I18N_LOG_FORMAT_PREFIX=\u4EE5%s\u3010%s\u3011\u6743\u9650 + diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 1bfea65802..0f0c6d30f2 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -167,6 +167,7 @@ SOURCE_TYPE_ROLE=\u89D2\u8272 SOURCE_TYPE_DRIVER=\u9A45\u52D5 SOURCE_TYPE_DRIVER_FILE=\u9A45\u52D5\u6587\u4EF6 SOURCE_TYPE_MENU=\u83DC\u55AE +SOURCE_TYPE_LINK=\u516C\u5171\u93C8\u63A5 I18N_DRIVER_NOT_DELETE=\u4F7F\u7528\u4E2D\u7684\u9A45\u52D5\u4E0D\u5141\u8A31\u5220\u9664 I18N_DRIVER_REPEAT_NAME=\u540D\u7A31\u91CD\u8907 I18N_DRIVER_NOT_FOUND=\u672A\u627E\u5230\u9A45\u52D5 @@ -236,3 +237,7 @@ I18N_APP_ERROR_DATASET=\u5100\u8868\u677F\u4E2D\u4E0D\u80FD\u5B58\u5728API\u6578 I18N_APP_NO_DATASOURCE=\u6C92\u6709\u627E\u5230\u6578\u64DA\u6E90 I18N_APP_ONE_DATASOURCE_TIPS=\u8A72\u5100\u8868\u677F\u53EA\u80FD\u5B58\u5728\u4E00\u500B\u6578\u64DA\u6E90 I18N_PROHIBIT_SCANNING_TO_CREATE_USER=\u7981\u6B62\u6383\u78BC\u5275\u5EFA\u7528\u6236\uFF0C\u8ACB\u806F\u7CFB\u7BA1\u7406\u54E1\uFF01 + +I18N_LOG_FORMAT_POSITION=\u5728\u3010%s\u3011 +I18N_LOG_FORMAT=\u7D66%s\u3010%s\u3011 +I18N_LOG_FORMAT_PREFIX=\u4EE5%s\u3010%s\u3011\u6B0A\u9650 diff --git a/frontend/src/api/link/index.js b/frontend/src/api/link/index.js index eaa5133cfa..1b25ae6822 100644 --- a/frontend/src/api/link/index.js +++ b/frontend/src/api/link/index.js @@ -50,6 +50,15 @@ export function switchEnablePwd(data) { }) } +export function viewLinkLog(data) { + return request({ + url: 'api/link/viewLog', + method: 'post', + loading: true, + data + }) +} + export function loadGenerate(resourceId) { return request({ url: 'api/link/currentGenerate/' + resourceId, diff --git a/frontend/src/components/suspensionSelector/index.vue b/frontend/src/components/suspensionSelector/index.vue new file mode 100644 index 0000000000..7182735689 --- /dev/null +++ b/frontend/src/components/suspensionSelector/index.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/frontend/src/components/widget/deWidget/inputStyleMixin.js b/frontend/src/components/widget/deWidget/inputStyleMixin.js index 0c8c2c93e4..5efdbd95d4 100644 --- a/frontend/src/components/widget/deWidget/inputStyleMixin.js +++ b/frontend/src/components/widget/deWidget/inputStyleMixin.js @@ -58,7 +58,7 @@ export default { mounted() { if (!this.isFilterComponent) return this.typeTransform().forEach(item => { - const nodeCache = this.$refs.deOutWidget.$refs[item].$el.querySelector('.el-input__inner') || this.$refs.deOutWidget.$refs[item].$el + const nodeCache = this.$refs.deOutWidget?.$refs[item].$el.querySelector('.el-input__inner') || this.$refs.deOutWidget.$refs[item].$el this.styleAttrs.forEach(ele => { nodeCache.style[this.attrsMap[ele]] = this.element.style[ele] this[this.element.serviceName] && this[this.element.serviceName](this.selectRange(item), ele, this.element.style[ele]) @@ -70,7 +70,7 @@ export default { let nodeCache = '' this.styleAttrs.forEach(ele => { if (!nodeCache) { - nodeCache = this.$refs.deOutWidget.$refs[type].$el.querySelector('.el-input__inner') || this.$refs.deOutWidget.$refs[type].$el + nodeCache = this.$refs.deOutWidget?.$refs[type].$el.querySelector('.el-input__inner') || this.$refs.deOutWidget.$refs[type].$el } nodeCache.style[this.attrsMap[ele]] = newValue[ele] this[this.element.serviceName] && this[this.element.serviceName](this.selectRange(type), ele, newValue[ele]) diff --git a/frontend/src/lang/en.js b/frontend/src/lang/en.js index 0a0fbedd08..ab85919e2c 100644 --- a/frontend/src/lang/en.js +++ b/frontend/src/lang/en.js @@ -915,6 +915,7 @@ export default { password_input_error: 'Original password input error' }, chart: { + suspension: 'Suspension', chart_background: 'Component background', solid_color: 'Solid color', split_gradient: 'Split gradient', @@ -2605,7 +2606,8 @@ export default { time: 'Time', export: 'Export', confirm: 'Sure Export ?', - search_by_key: 'Search by key' + search_by_key: 'Search by key', + ip: 'IP' }, plugin_style: { border: 'Border' diff --git a/frontend/src/lang/tw.js b/frontend/src/lang/tw.js index 06efa7fbd1..d62580b15b 100644 --- a/frontend/src/lang/tw.js +++ b/frontend/src/lang/tw.js @@ -915,6 +915,7 @@ export default { password_input_error: '原始密碼輸入錯誤' }, chart: { + suspension: '懸浮', chart_background: '組件背景', solid_color: '純色', split_gradient: '分離漸變', @@ -2606,7 +2607,8 @@ export default { time: '操作時間', export: '導出', confirm: '確定導出嗎?', - search_by_key: '搜索詳情' + search_by_key: '搜索詳情', + ip: 'IP地址' }, plugin_style: { border: '邊框' diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js index acab15b99e..ee387a98f6 100644 --- a/frontend/src/lang/zh.js +++ b/frontend/src/lang/zh.js @@ -914,6 +914,7 @@ export default { password_input_error: '原始密码输入错误' }, chart: { + suspension: '悬浮', chart_background: '组件背景', solid_color: '纯色', split_gradient: '分离渐变', @@ -2606,7 +2607,8 @@ export default { time: '操作时间', export: '导出', confirm: '确定导出吗?', - search_by_key: '搜索详情' + search_by_key: '搜索详情', + ip: 'IP地址' }, plugin_style: { border: '边框' diff --git a/frontend/src/views/chart/chart/chart.js b/frontend/src/views/chart/chart/chart.js index 116da2af59..c2c3bf250a 100644 --- a/frontend/src/views/chart/chart/chart.js +++ b/frontend/src/views/chart/chart/chart.js @@ -124,6 +124,9 @@ export const DEFAULT_SIZE = { showIndex: false, indexLabel: '序号' } +export const DEFAULT_SUSPENSION = { + show: true +} export const DEFAULT_LABEL = { show: false, position: 'top', diff --git a/frontend/src/views/chart/chart/map/map.js b/frontend/src/views/chart/chart/map/map.js index 4e6e9d7da7..bf9f18311c 100644 --- a/frontend/src/views/chart/chart/map/map.js +++ b/frontend/src/views/chart/chart/map/map.js @@ -125,6 +125,11 @@ export function baseMapOption(chart_option, chart, themeStyle, curAreaCode) { if (themeStyle) { chart_option.visualMap.textStyle = { color: themeStyle } } + if (customAttr.suspension && !customAttr.suspension.show) { + chart_option.visualMap.show = false + } else if ('show' in chart_option.visualMap) { + delete chart_option.visualMap.show + } } for (let i = 0; i < valueArr.length; i++) { diff --git a/frontend/src/views/chart/chart/util.js b/frontend/src/views/chart/chart/util.js index 3773ba17b3..7550515c99 100644 --- a/frontend/src/views/chart/chart/util.js +++ b/frontend/src/views/chart/chart/util.js @@ -3073,7 +3073,8 @@ export const TYPE_CONFIGS = [ 'color-selector', 'label-selector', 'tooltip-selector', - 'title-selector' + 'title-selector', + 'suspension-selector' ], propertyInner: { @@ -3107,6 +3108,9 @@ export const TYPE_CONFIGS = [ 'vPosition', 'isItalic', 'isBolder' + ], + 'suspension-selector': [ + 'show' ] } } diff --git a/frontend/src/views/chart/components/ChartComponent.vue b/frontend/src/views/chart/components/ChartComponent.vue index 84c7849e1c..e4cfcfce24 100644 --- a/frontend/src/views/chart/components/ChartComponent.vue +++ b/frontend/src/views/chart/components/ChartComponent.vue @@ -13,7 +13,7 @@ :style="{ borderRadius: borderRadius}" />
@@ -143,7 +143,8 @@ export default { mapCenter: null, linkageActiveParam: null, buttonTextColor: null, - loading: true + loading: true, + showSuspension: true } }, @@ -318,6 +319,9 @@ export default { } if (chart.type === 'map') { const customAttr = JSON.parse(chart.customAttr) + if (customAttr.suspension) { + this.showSuspension = customAttr.suspension.show + } if (!customAttr.areaCode) { this.myChart.clear() return diff --git a/frontend/src/views/chart/view/ChartEdit.vue b/frontend/src/views/chart/view/ChartEdit.vue index a2928853c4..07ae672ac8 100644 --- a/frontend/src/views/chart/view/ChartEdit.vue +++ b/frontend/src/views/chart/view/ChartEdit.vue @@ -951,6 +951,7 @@ @onLegendChange="onLegendChange" @onMarginChange="onMarginChange" @onChangeBackgroundForm="onChangeBackgroundForm" + @onSuspensionChange="onSuspensionChange" /> + + + @@ -317,6 +330,7 @@ import LegendSelectorAntV from '@/views/chart/components/componentStyle/LegendSe import BackgroundColorSelector from '@/views/chart/components/componentStyle/BackgroundColorSelector' import SplitSelector from '@/views/chart/components/componentStyle/SplitSelector' import SplitSelectorAntV from '@/views/chart/components/componentStyle/SplitSelectorAntV' +import SuspensionSelector from '@/components/suspensionSelector' import { mapState } from 'vuex' export default { @@ -344,7 +358,8 @@ export default { SizeSelector, ColorSelector, MarginSelector, - PluginCom + PluginCom, + SuspensionSelector }, props: { chart: { @@ -422,6 +437,10 @@ export default { val['propertyName'] = propertyName this.$emit('onColorChange', val) }, + onSuspensionChange(val, propertyName) { + val['propertyName'] = propertyName + this.$emit('onSuspensionChange', val) + }, onSizeChange(val, propertyName) { val['propertyName'] = propertyName this.$emit('onSizeChange', val) diff --git a/frontend/src/views/dataset/data/ViewTable.vue b/frontend/src/views/dataset/data/ViewTable.vue index 8ee482c617..b78ea0c3d2 100644 --- a/frontend/src/views/dataset/data/ViewTable.vue +++ b/frontend/src/views/dataset/data/ViewTable.vue @@ -462,21 +462,27 @@ export default { this.showExport = false }, exportDatasetRequest() { - if (this.table.id) { - this.table.row = 100000 - this.table.filename = this.exportForm.name - this.table.expressionTree = this.exportForm.expressionTree - exportDataset(this.table).then((res) => { - const blob = new Blob([res], { type: 'application/vnd.ms-excel' }) - const link = document.createElement('a') - link.style.display = 'none' - link.href = URL.createObjectURL(blob) - link.download = this.exportForm.name + '.xlsx' // 下载的文件名 - document.body.appendChild(link) - link.click() - document.body.removeChild(link) - }) - } + this.$refs['exportForm'].validate((valid) => { + if (valid) { + if (this.table.id) { + this.table.row = 100000 + this.table.filename = this.exportForm.name + this.table.expressionTree = this.exportForm.expressionTree + exportDataset(this.table).then((res) => { + const blob = new Blob([res], { type: 'application/vnd.ms-excel' }) + const link = document.createElement('a') + link.style.display = 'none' + link.href = URL.createObjectURL(blob) + link.download = this.exportForm.name + '.xlsx' // 下载的文件名 + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + }) + } + } else { + return false + } + }) } } } diff --git a/frontend/src/views/link/view/index.vue b/frontend/src/views/link/view/index.vue index 2dbbc1cafb..4c4e66ccd6 100644 --- a/frontend/src/views/link/view/index.vue +++ b/frontend/src/views/link/view/index.vue @@ -10,7 +10,8 @@