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

This commit is contained in:
taojinlong 2024-10-09 16:03:19 +08:00
commit 2d2ef22b3a
125 changed files with 3531 additions and 1903 deletions

View File

@ -250,6 +250,120 @@ public class DefaultChartHandler extends AbstractChartPlugin {
return res;
}
protected List<ChartViewFieldDTO> getAssistFields(List<ChartSeniorAssistDTO> list, List<ChartViewFieldDTO> yAxis, List<ChartViewFieldDTO> xAxis) {
List<ChartViewFieldDTO> res = new ArrayList<>();
for (ChartSeniorAssistDTO dto : list) {
DatasetTableFieldDTO curField = dto.getCurField();
ChartViewFieldDTO field = null;
String alias = "";
for (int i = 0; i < yAxis.size(); i++) {
ChartViewFieldDTO yField = yAxis.get(i);
if (Objects.equals(yField.getId(), curField.getId())) {
field = yField;
alias = String.format(SQLConstants.FIELD_ALIAS_Y_PREFIX, i);
break;
}
}
if (ObjectUtils.isEmpty(field) && CollectionUtils.isNotEmpty(xAxis)) {
for (int i = 0; i < xAxis.size(); i++) {
ChartViewFieldDTO xField = xAxis.get(i);
if (StringUtils.equalsIgnoreCase(String.valueOf(xField.getId()), String.valueOf(curField.getId()))) {
field = xField;
alias = String.format(SQLConstants.FIELD_ALIAS_X_PREFIX, i);
break;
}
}
}
if (ObjectUtils.isEmpty(field)) {
continue;
}
ChartViewFieldDTO chartViewFieldDTO = new ChartViewFieldDTO();
BeanUtils.copyBean(chartViewFieldDTO, curField);
chartViewFieldDTO.setSummary(dto.getSummary());
chartViewFieldDTO.setOriginName(alias);// yAxis的字段别名就是查找的字段名
res.add(chartViewFieldDTO);
}
return res;
}
public List<ChartSeniorAssistDTO> getDynamicThresholdFields(ChartViewDTO view) {
List<ChartSeniorAssistDTO> list = new ArrayList<>();
Map<String, Object> senior = view.getSenior();
if (ObjectUtils.isEmpty(senior)) {
return list;
}
ChartSeniorThresholdCfgDTO thresholdCfg = JsonUtil.parseObject((String) JsonUtil.toJSONString(senior.get("threshold")), ChartSeniorThresholdCfgDTO.class);
if (null == thresholdCfg || !thresholdCfg.isEnable()) {
return list;
}
List<TableThresholdDTO> tableThreshold = thresholdCfg.getTableThreshold();
if (ObjectUtils.isEmpty(tableThreshold)) {
return list;
}
List<ChartSeniorThresholdDTO> conditionsList = tableThreshold.stream()
.filter(item -> !ObjectUtils.isEmpty(item))
.map(TableThresholdDTO::getConditions)
.flatMap(List::stream)
.filter(condition -> StringUtils.equalsAnyIgnoreCase(condition.getType(), "dynamic"))
.toList();
List<ChartSeniorAssistDTO> assistDTOs = conditionsList.stream()
.flatMap(condition -> getConditionFields(condition).stream())
.filter(this::solveThresholdCondition)
.toList();
list.addAll(assistDTOs);
return list;
}
private boolean solveThresholdCondition(ChartSeniorAssistDTO fieldDTO) {
Long fieldId = fieldDTO.getFieldId();
String summary = fieldDTO.getValue();
if (ObjectUtils.isEmpty(fieldId) || StringUtils.isEmpty(summary)) {
return false;
}
DatasetTableFieldDTO datasetTableFieldDTO = datasetTableFieldManage.selectById(fieldId);
if (ObjectUtils.isEmpty(datasetTableFieldDTO)) {
return false;
}
ChartViewFieldDTO datasetTableField = new ChartViewFieldDTO();
BeanUtils.copyBean(datasetTableField, datasetTableFieldDTO);
fieldDTO.setCurField(datasetTableField);
fieldDTO.setSummary(summary);
return true;
}
private List<ChartSeniorAssistDTO> getConditionFields(ChartSeniorThresholdDTO condition) {
List<ChartSeniorAssistDTO> list = new ArrayList<>();
if ("between".equals(condition.getTerm())) {
if (!StringUtils.equalsIgnoreCase(condition.getDynamicMaxField().getSummary(), "value")) {
list.add(of(condition.getDynamicMaxField()));
}
if (!StringUtils.equalsIgnoreCase(condition.getDynamicMinField().getSummary(), "value")) {
list.add(of(condition.getDynamicMinField()));
}
} else {
if (!StringUtils.equalsIgnoreCase(condition.getDynamicField().getSummary(), "value")) {
list.add(of(condition.getDynamicField()));
}
}
return list;
}
private ChartSeniorAssistDTO of(ThresholdDynamicFieldDTO dynamicField) {
ChartSeniorAssistDTO conditionField = new ChartSeniorAssistDTO();
conditionField.setFieldId(Long.parseLong(dynamicField.getFieldId()));
conditionField.setValue(dynamicField.getSummary());
return conditionField;
}
protected String assistSQL(String sql, List<ChartViewFieldDTO> assistFields) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < assistFields.size(); i++) {
@ -464,7 +578,8 @@ public class DefaultChartHandler extends AbstractChartPlugin {
if (StringUtils.isNotEmpty(compareCalc.getType())
&& !StringUtils.equalsIgnoreCase(compareCalc.getType(), "none")) {
if (Arrays.asList(ChartConstants.M_Y).contains(compareCalc.getType())) {
if (StringUtils.equalsIgnoreCase(compareCalc.getField() + "", filterDTO.getFieldId()) && filterDTO.getFilterType() == 0) {
if (StringUtils.equalsIgnoreCase(compareCalc.getField() + "", filterDTO.getFieldId())
&& (filterDTO.getFilterType() == 0 || filterDTO.getFilterType() == 2)) {
// -1 year
try {
Calendar calendar = Calendar.getInstance();

View File

@ -120,11 +120,11 @@ public class YoyChartHandler extends DefaultChartHandler {
expandedResult.setQuerySql(originSql);
}
// 同环比数据排序
expandedResult.setOriginData(sortData(view, expandedResult.getOriginData()));
expandedResult.setOriginData(sortData(view, expandedResult.getOriginData(),formatResult));
return expandedResult;
}
public static List<String[]> sortData(ChartViewDTO view, List<String[]> data) {
public static List<String[]> sortData(ChartViewDTO view, List<String[]> data, AxisFormatResult formatResult) {
// 维度排序
List<ChartViewFieldDTO> xAxisSortList = view.getXAxis().stream().filter(x -> !StringUtils.equalsIgnoreCase("none", x.getSort())).toList();
// 指标排序
@ -135,11 +135,9 @@ public class YoyChartHandler extends DefaultChartHandler {
ChartViewFieldDTO firstYAxis = yAxisSortList.getFirst();
boolean asc = firstYAxis.getSort().equalsIgnoreCase("asc");
// 维度指标
List<ChartViewFieldDTO> allAxisList = Stream.of(
view.getXAxis(),
view.getXAxisExt(),
view.getYAxis()
).flatMap(List::stream).toList();
List<ChartViewFieldDTO> allAxisList = new ArrayList<>();
allAxisList.addAll(formatResult.getAxisMap().get(ChartAxis.xAxis));
allAxisList.addAll(formatResult.getAxisMap().get(ChartAxis.yAxis));
int index = findIndex(allAxisList, firstYAxis.getId());
return sortData(data, asc, index);
}

View File

@ -12,8 +12,9 @@ import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.extensions.view.dto.*;
import io.dataease.extensions.view.util.ChartDataUtil;
import io.dataease.extensions.view.util.FieldUtil;
import io.dataease.utils.JsonUtil;
import io.dataease.utils.BeanUtils;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@ -21,6 +22,8 @@ import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Component
public class TableInfoHandler extends DefaultChartHandler {
@ -135,6 +138,32 @@ public class TableInfoHandler extends DefaultChartHandler {
calcResult.setContext(filterResult.getContext());
calcResult.setQuerySql(querySql);
calcResult.setOriginData(data);
try {
var dynamicAssistFields = getDynamicThresholdFields(view);
Set<Long> fieldIds = xAxis.stream().map(ChartViewFieldDTO::getId).collect(Collectors.toSet());
List<ChartViewFieldDTO> finalXAxis = xAxis;
dynamicAssistFields.forEach(i -> {
if (!fieldIds.contains(i.getFieldId())) {
ChartViewFieldDTO fieldDTO = new ChartViewFieldDTO();
BeanUtils.copyBean(fieldDTO, i.getCurField());
finalXAxis.add(fieldDTO);
}
});
var yAxis = formatResult.getAxisMap().get(ChartAxis.yAxis);
var assistFields = getAssistFields(dynamicAssistFields, yAxis, xAxis);
if (CollectionUtils.isNotEmpty(assistFields)) {
var req = new DatasourceRequest();
req.setDsList(dsMap);
var assistSql = assistSQL(querySql, assistFields);
req.setQuery(assistSql);
logger.debug("calcite assistSql sql: " + assistSql);
var assistData = (List<String[]>) provider.fetchResultField(req).get("data");
calcResult.setAssistData(assistData);
calcResult.setDynamicAssistFields(dynamicAssistFields);
}
} catch (Exception e) {
e.printStackTrace();
}
return calcResult;
}
}

View File

@ -1,9 +1,18 @@
package io.dataease.chart.charts.impl.table;
import io.dataease.chart.charts.impl.YoyChartHandler;
import io.dataease.extensions.datasource.dto.DatasourceRequest;
import io.dataease.extensions.datasource.dto.DatasourceSchemaDTO;
import io.dataease.extensions.datasource.model.SQLMeta;
import io.dataease.extensions.datasource.provider.Provider;
import io.dataease.extensions.view.dto.*;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* @author jianneng
* @date 2024/9/11 11:37
@ -12,4 +21,29 @@ import org.springframework.stereotype.Component;
public class TableNormalHandler extends YoyChartHandler {
@Getter
private String type = "table-normal";
@Override
public <T extends ChartCalcDataResult> T calcChartResult(ChartViewDTO view, AxisFormatResult formatResult, CustomFilterResult filterResult, Map<String, Object> sqlMap, SQLMeta sqlMeta, Provider provider) {
var dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap");
var result = (T) super.calcChartResult(view, formatResult, filterResult, sqlMap, sqlMeta, provider);
try {
var originSql = result.getQuerySql();
var dynamicAssistFields = getDynamicThresholdFields(view);
var yAxis = formatResult.getAxisMap().get(ChartAxis.yAxis);
var assistFields = getAssistFields(dynamicAssistFields, yAxis);
if (CollectionUtils.isNotEmpty(assistFields)) {
var req = new DatasourceRequest();
req.setDsList(dsMap);
var assistSql = assistSQL(originSql, assistFields);
req.setQuery(assistSql);
logger.debug("calcite assistSql sql: " + assistSql);
var assistData = (List<String[]>) provider.fetchResultField(req).get("data");
result.setAssistData(assistData);
result.setDynamicAssistFields(dynamicAssistFields);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}

View File

@ -17,6 +17,7 @@ import io.dataease.utils.BeanUtils;
import io.dataease.utils.IDUtils;
import io.dataease.utils.JsonUtil;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import reactor.util.function.Tuple2;
@ -35,6 +36,25 @@ public class TablePivotHandler extends GroupChartHandler {
T result = super.calcChartResult(view, formatResult, filterResult, sqlMap, sqlMeta, provider);
Map<String, Object> customCalc = calcCustomExpr(view, filterResult, sqlMap, sqlMeta, provider);
result.getData().put("customCalc", customCalc);
try {
var dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap");
var originSql = result.getQuerySql();
var dynamicAssistFields = getDynamicThresholdFields(view);
var yAxis = formatResult.getAxisMap().get(ChartAxis.yAxis);
var assistFields = getAssistFields(dynamicAssistFields, yAxis);
if (CollectionUtils.isNotEmpty(assistFields)) {
var req = new DatasourceRequest();
req.setDsList(dsMap);
var assistSql = assistSQL(originSql, assistFields);
req.setQuery(assistSql);
logger.debug("calcite assistSql sql: " + assistSql);
var assistData = (List<String[]>) provider.fetchResultField(req).get("data");
result.setAssistData(assistData);
result.setDynamicAssistFields(dynamicAssistFields);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@ -54,7 +74,9 @@ public class TablePivotHandler extends GroupChartHandler {
var rowAxis = view.getXAxis();
var colAxis = view.getXAxisExt();
var dataMap = new HashMap<String, Object>();
var quotaIds = view.getYAxis().stream().map(ChartViewFieldDTO::getDataeaseName).collect(Collectors.toSet());
if (CollectionUtils.isEmpty(rowAxis)) {
return dataMap;
}
// 行总计列维度聚合加上自定义字段
var row = tableTotal.getRow();
if (row.isShowGrandTotals()) {
@ -96,7 +118,7 @@ public class TablePivotHandler extends GroupChartHandler {
}
// 列总计行维度聚合加上自定义字段
var col = tableTotal.getCol();
if (col.isShowGrandTotals()) {
if (col.isShowGrandTotals() && CollectionUtils.isNotEmpty(colAxis)) {
var yAxis = getCustomFields(view, col.getCalcTotals().getCfg());
if (!yAxis.isEmpty()) {
var result = getData(sqlMeta, rowAxis, yAxis, allFields, crossDs, dsMap, view, provider, needOrder);
@ -109,7 +131,7 @@ public class TablePivotHandler extends GroupChartHandler {
}
}
// 列小计行维度聚合自定义指标数 * (列维度的数量 - 1)
if (col.isShowSubTotals()) {
if (col.isShowSubTotals() && colAxis.size() >= 2) {
var yAxis = getCustomFields(view, col.getCalcSubTotals().getCfg());
if (!yAxis.isEmpty()) {
var tmpData = new ArrayList<Map<String, Object>>();
@ -153,7 +175,7 @@ public class TablePivotHandler extends GroupChartHandler {
}
}
// 行总计里面的列小计
if (row.isShowGrandTotals() && col.isShowSubTotals()) {
if (row.isShowGrandTotals() && col.isShowSubTotals() && colAxis.size() >= 2) {
var yAxis = getCustomFields(view, col.getCalcTotals().getCfg());
if (!yAxis.isEmpty()) {
var tmpData = new ArrayList<Map<String, Object>>();
@ -174,7 +196,7 @@ public class TablePivotHandler extends GroupChartHandler {
}
}
// 列总计里面的行小计
if (col.isShowGrandTotals() && row.isShowGrandTotals()) {
if (col.isShowGrandTotals() && row.isShowGrandTotals() && rowAxis.size() >= 2) {
var yAxis = getCustomFields(view, row.getCalcTotals().getCfg());
if (!yAxis.isEmpty()) {
var tmpData = new ArrayList<Map<String, Object>>();
@ -195,7 +217,7 @@ public class TablePivotHandler extends GroupChartHandler {
}
}
// 行小计和列小计相交部分
if (row.isShowSubTotals() && col.isShowSubTotals()) {
if (row.isShowSubTotals() && col.isShowSubTotals() && colAxis.size() >= 2 && rowAxis.size() >= 2) {
var yAxis = getCustomFields(view, col.getCalcTotals().getCfg());
if (!yAxis.isEmpty()) {
var tmpData = new ArrayList<List<Map<String, Object>>>();
@ -230,6 +252,14 @@ public class TablePivotHandler extends GroupChartHandler {
private Map<String, Object> buildCustomCalcResult(List<String[]> data, List<ChartViewFieldDTO> dimAxis, List<ChartViewFieldDTO> quotaAxis) {
var rootResult = new HashMap<String, Object>();
if (CollectionUtils.isEmpty(dimAxis)) {
var rowData = data.getFirst();
for (int i = 0; i < rowData.length; i++) {
var qAxis = quotaAxis.get(i);
rootResult.put(qAxis.getDataeaseName(), rowData[i]);
}
return rootResult;
}
for (int i = 0; i < data.size(); i++) {
var rowData = data.get(i);
Map<String, Object> curSubMap = rootResult;

View File

@ -1,9 +1,9 @@
package io.dataease.home;
import io.dataease.license.utils.LicenseUtil;
import io.dataease.home.manage.DeIndexManage;
import io.dataease.utils.ModelUtils;
import io.dataease.utils.RsaUtils;
import org.springframework.beans.factory.annotation.Value;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@ -13,8 +13,9 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping
public class RestIndexController {
@Value("${dataease.xpack-front-distributed:false}")
private boolean xpackFrontDistributed;
@Resource
private DeIndexManage deIndexManage;
@GetMapping("/dekey")
@ResponseBody
@ -31,8 +32,8 @@ public class RestIndexController {
@GetMapping("/xpackModel")
@ResponseBody
public boolean xpackModel() {
return xpackFrontDistributed && LicenseUtil.licenseValid();
public Boolean xpackModel() {
return deIndexManage.xpackModel();
}
}

View File

@ -0,0 +1,13 @@
package io.dataease.home.manage;
import io.dataease.license.config.XpackInteract;
import org.springframework.stereotype.Component;
@Component
public class DeIndexManage {
@XpackInteract(value = "deIndexManage", replace = true)
public Boolean xpackModel() {
return null;
}
}

View File

@ -5,14 +5,17 @@ import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import io.dataease.api.permissions.login.dto.PwdLoginDTO;
import io.dataease.auth.bo.TokenUserBO;
import io.dataease.auth.config.SubstituleLoginConfig;
import io.dataease.auth.vo.TokenVO;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.utils.LogUtil;
import io.dataease.utils.Md5Utils;
import io.dataease.utils.RsaUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@Component
@ConditionalOnMissingBean(name = "loginServer")
@ -21,11 +24,26 @@ import org.springframework.web.bind.annotation.RestController;
public class SubstituleLoginServer {
@PostMapping("/login/localLogin")
public TokenVO localLogin(PwdLoginDTO dto) {
public TokenVO localLogin(@RequestBody PwdLoginDTO dto) {
String name = dto.getName();
name = RsaUtils.decryptStr(name);
String pwd = dto.getPwd();
pwd = RsaUtils.decryptStr(pwd);
dto.setName(name);
dto.setPwd(pwd);
if (!StringUtils.equals("admin", name)) {
DEException.throwException("仅admin账号可用");
}
if (!StringUtils.equals(pwd, SubstituleLoginConfig.getPwd())) {
DEException.throwException(Translator.get("i18n_login_name_pwd_err"));
}
TokenUserBO tokenUserBO = new TokenUserBO();
tokenUserBO.setUserId(1L);
tokenUserBO.setDefaultOid(1L);
String md5Pwd = "83d923c9f1d8fcaa46cae0ed2aaa81b5";
String md5Pwd = Md5Utils.md5(pwd);
return generate(tokenUserBO, md5Pwd);
}

View File

@ -5,8 +5,6 @@ ALTER TABLE `visualization_outer_params_info`
update visualization_outer_params_info
set required =0;
ALTER TABLE `xpack_report_info`
ADD COLUMN `show_watermark` tinyint(1) NOT NULL DEFAULT 0 COMMENT '显示水印' AFTER `rid`;
ALTER TABLE `visualization_link_jump_info`
ADD COLUMN `window_size` varchar(255) NULL DEFAULT 'middle' COMMENT '窗口大小large middle small';

View File

@ -0,0 +1 @@
INSERT INTO area (id, level, name, pid) VALUES ('156440315', 'district', '大鹏新区', '156440300');

View File

@ -1,13 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/dataease.svg" />
<link rel="icon" type="image/svg+xml" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title></title>
</head>
<body>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/pages/index/main.ts"></script>
</body>
</body>
</html>

View File

@ -3,10 +3,8 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
<title>DataEase</title>
</head>
<body>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 741 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 744 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 744 KiB

After

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 147 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 97 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.0893 1.91058C17.933 1.7543 17.721 1.6665 17.5 1.6665H2.50002C2.27901 1.6665 2.06705 1.7543 1.91076 1.91058C1.75448 2.06686 1.66669 2.27882 1.66669 2.49984V17.4998C1.66669 17.7209 1.75448 17.9328 1.91076 18.0891C2.06705 18.2454 2.27901 18.3332 2.50002 18.3332H17.5C17.721 18.3332 17.933 18.2454 18.0893 18.0891C18.2456 17.9328 18.3334 17.7209 18.3334 17.4998V2.49984C18.3334 2.27882 18.2456 2.06686 18.0893 1.91058ZM10.8334 5.83323H13.75C13.8605 5.83323 13.9665 5.87713 14.0446 5.95527C14.1228 6.03341 14.1667 6.13939 14.1667 6.2499V7.08323C14.1667 7.19374 14.1228 7.29972 14.0446 7.37786C13.9665 7.456 13.8605 7.4999 13.75 7.4999H10.8334V13.7499C10.8334 13.8046 10.8226 13.8588 10.8016 13.9094C10.7807 13.9599 10.75 14.0058 10.7113 14.0445C10.6726 14.0832 10.6267 14.1139 10.5761 14.1348C10.5256 14.1558 10.4714 14.1666 10.4167 14.1666H9.58335C9.52864 14.1666 9.47446 14.1558 9.4239 14.1348C9.37335 14.1139 9.32742 14.0832 9.28873 14.0445C9.25004 14.0058 9.21934 13.9599 9.1984 13.9094C9.17747 13.8588 9.16669 13.8046 9.16669 13.7499V7.4999H6.25002C6.13951 7.4999 6.03353 7.456 5.95539 7.37786C5.87725 7.29972 5.83335 7.19374 5.83335 7.08323V6.2499C5.83335 6.13939 5.87725 6.03341 5.95539 5.95527C6.03353 5.87713 6.13951 5.83323 6.25002 5.83323H9.16669C9.16669 5.83323 10.8334 5.85657 10.8334 5.83323Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.0893 1.91058C17.933 1.7543 17.721 1.6665 17.5 1.6665H2.50002C2.27901 1.6665 2.06705 1.7543 1.91076 1.91058C1.75448 2.06686 1.66669 2.27882 1.66669 2.49984V17.4998C1.66669 17.7209 1.75448 17.9328 1.91076 18.0891C2.06705 18.2454 2.27901 18.3332 2.50002 18.3332H17.5C17.721 18.3332 17.933 18.2454 18.0893 18.0891C18.2456 17.9328 18.3334 17.7209 18.3334 17.4998V2.49984C18.3334 2.27882 18.2456 2.06686 18.0893 1.91058ZM10.8334 5.83323H13.75C13.8605 5.83323 13.9665 5.87713 14.0446 5.95527C14.1228 6.03341 14.1667 6.13939 14.1667 6.2499V7.08323C14.1667 7.19374 14.1228 7.29972 14.0446 7.37786C13.9665 7.456 13.8605 7.4999 13.75 7.4999H10.8334V13.7499C10.8334 13.8046 10.8226 13.8588 10.8016 13.9094C10.7807 13.9599 10.75 14.0058 10.7113 14.0445C10.6726 14.0832 10.6267 14.1139 10.5761 14.1348C10.5256 14.1558 10.4714 14.1666 10.4167 14.1666H9.58335C9.52864 14.1666 9.47446 14.1558 9.4239 14.1348C9.37335 14.1139 9.32742 14.0832 9.28873 14.0445C9.25004 14.0058 9.21934 13.9599 9.1984 13.9094C9.17747 13.8588 9.16669 13.8046 9.16669 13.7499V7.4999H6.25002C6.13951 7.4999 6.03353 7.456 5.95539 7.37786C5.87725 7.29972 5.83335 7.19374 5.83335 7.08323V6.2499C5.83335 6.13939 5.87725 6.03341 5.95539 5.95527C6.03353 5.87713 6.13951 5.83323 6.25002 5.83323H9.16669C9.16669 5.83323 10.8334 5.85657 10.8334 5.83323Z" fill="#307EFF"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 63 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 739 KiB

After

Width:  |  Height:  |  Size: 65 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -531,7 +531,7 @@ const initOpenHandler = newWindow => {
is-label
themes="light"
placement="bottom"
:base-width="315"
:base-width="328"
:icon-name="dvMedia"
title="媒体"
>

View File

@ -195,7 +195,7 @@ const componentBackgroundStyle = computed(() => {
if (backgroundColorSelect && backgroundColor) {
colorRGBA = backgroundColor
}
if (backgroundImageEnable) {
if (backgroundImageEnable || (config.value.innerType === 'VQuery' && backgroundColorSelect)) {
if (backgroundType === 'outerImage' && typeof outerImage === 'string') {
style['background'] = `url(${imgUrlTrans(outerImage)}) no-repeat ${colorRGBA}`
} else {

View File

@ -126,6 +126,9 @@ const baseComponentData = computed(() =>
)
const canvasStyle = computed(() => {
let style = {}
if (isMainCanvas(canvasId.value) && !isDashboard()) {
style['overflowY'] = 'hidden !important'
}
if (canvasStyleData.value && canvasStyleData.value.width && isMainCanvas(canvasId.value)) {
style = {
...getCanvasStyle(canvasStyleData.value),
@ -383,7 +386,9 @@ const filterBtnShow = computed(
const datasetParamsInit = item => {
customDatasetParamsRef.value?.optInit(item)
}
const dataVPreview = computed(
() => dvInfo.value.type === 'dataV' && canvasId.value === 'canvas-main'
)
defineExpose({
restore
})
@ -394,7 +399,7 @@ defineExpose({
:id="domId"
class="canvas-container"
:style="canvasStyle"
:class="{ 'de-download-custom': downloadStatus }"
:class="{ 'de-download-custom': downloadStatus, 'datav-preview': dataVPreview }"
ref="previewCanvas"
@mousedown="handleMouseDown"
>
@ -467,4 +472,8 @@ defineExpose({
.fix-button {
position: fixed !important;
}
.datav-preview {
overflow-y: hidden !important;
}
</style>

View File

@ -521,13 +521,14 @@ const handleMouseDownOnShape = e => {
const left = curX - startX + startLeft
pos['top'] = top
pos['left'] = left
// Tab(30px 30px)
// Tab(30px 30px 30px)
//
// 使curX 使 tab + +
if (
!isMainCanvas(canvasId.value) &&
!isGroupCanvas(canvasId.value) &&
!isGroupArea.value &&
(left < -30 || left + componentWidth - canvasWidth > 30)
(top < -30 || left < -30 || left + componentWidth - canvasWidth > 30)
) {
contentDisplay.value = false
dvMainStore.setMousePointShadowMap({
@ -869,7 +870,7 @@ const componentBackgroundStyle = computed(() => {
if (backgroundColorSelect && backgroundColor) {
colorRGBA = backgroundColor
}
if (backgroundImageEnable) {
if (backgroundImageEnable || (element.value.innerType === 'VQuery' && backgroundColorSelect)) {
if (backgroundType === 'outerImage' && typeof outerImage === 'string') {
style['background'] = `url(${imgUrlTrans(outerImage)}) no-repeat ${colorRGBA}`
} else {

View File

@ -12,6 +12,7 @@ import * as vueRouter from 'vue-router'
import { useEmitt } from '@/hooks/web/useEmitt'
import request from '@/config/axios'
const { wsCache } = useCache()
import { isNull } from '@/utils/utils'
const plugin = ref()
@ -103,11 +104,15 @@ onMounted(async () => {
let distributed = false
if (wsCache.get(key) === null) {
const res = await xpackModelApi()
wsCache.set('xpack-model-distributed', res.data)
wsCache.set('xpack-model-distributed', isNull(res.data) ? 'null' : res.data)
distributed = res.data
} else {
distributed = wsCache.get(key)
}
if (isNull(distributed)) {
emits('loadFail')
return
}
if (distributed) {
const moduleName = getModuleName()
if (window[moduleName]) {

View File

@ -12,6 +12,7 @@ import * as echarts from 'echarts'
import router from '@/router'
import tinymce from 'tinymce/tinymce'
import { useEmitt } from '@/hooks/web/useEmitt'
import { isNull } from '@/utils/utils'
const { wsCache } = useCache()
@ -107,11 +108,16 @@ onMounted(async () => {
let distributed = false
if (wsCache.get(key) === null) {
const res = await xpackModelApi()
wsCache.set('xpack-model-distributed', res.data)
const resData = isNull(res.data) ? 'null' : res.data
wsCache.set('xpack-model-distributed', resData)
distributed = res.data
} else {
distributed = wsCache.get(key)
}
if (isNull(distributed)) {
emits('loadFail')
return
}
if (distributed) {
if (window['DEXPack']) {
const xpack = await window['DEXPack'].mapping[attrs.jsname]

View File

@ -141,6 +141,12 @@
placeholder="选择边框..."
@change="onBackgroundChange"
>
<template v-if="state.commonBackground.innerImage" #prefix>
<border-option-prefix
inner-image-color="state.commonBackground.innerImageColor"
:url="state.commonBackground.innerImage"
></border-option-prefix>
</template>
<el-option
v-for="(item, index) in state.BackgroundShowMap['default']"
:key="index"
@ -223,6 +229,8 @@ import elementResizeDetectorMaker from 'element-resize-detector'
import { ElMessage } from 'element-plus-secondary'
import BoardItem from '@/components/visualization/component-background/BoardItem.vue'
import ImgViewDialog from '@/custom-component/ImgViewDialog.vue'
import PictureOptionPrefix from '@/custom-component/picture-group/PictureOptionPrefix.vue'
import BorderOptionPrefix from '@/components/visualization/component-background/BorderOptionPrefix.vue'
const snapshotStore = snapshotStoreWithOut()
const { t } = useI18n()
const emits = defineEmits(['onBackgroundChange'])

View File

@ -0,0 +1,41 @@
<template>
<div class="img-option-prefix">
<Icon
><component
:style="{ color: innerImageColor, width: '20px', height: '20px' }"
class="svg-icon svg-background"
:is="iconBoardMap[mainIconClass(url)]"
></component
></Icon>
</div>
</template>
<script setup lang="ts">
import { iconBoardMap } from '@/components/icon-group/board-list'
import { toRefs } from 'vue'
import { Icon } from '@/components/icon-custom'
const props = withDefaults(
defineProps<{
url: any
innerImageColor: string
}>(),
{}
)
const { innerImageColor } = toRefs(props)
const mainIconClass = url => {
return url.replace('board/', '').replace('.svg', '')
}
</script>
<style scoped lang="less">
.img-option-prefix {
width: 20px;
height: 20px;
display: flex;
flex-direction: column;
align-content: center;
}
</style>

View File

@ -143,7 +143,9 @@ const stopEvent = e => {
<el-collapse-item :effect="themes" title="位置" name="position" v-if="positionComponentShow">
<component-position :themes="themes" />
</el-collapse-item>
<slot name="dataset" />
<slot name="carousel" />
<slot name="threshold" />
<el-collapse-item
:effect="themes"
title="背景"

View File

@ -2,6 +2,7 @@
import iconVideo from '@/assets/svg/icon-video.svg'
import dvPictureShow from '@/assets/svg/dv-picture-show.svg'
import iconStream from '@/assets/svg/icon-stream.svg'
import pictureGroupOrigin from '@/assets/svg/picture-group-origin.svg'
import { toRefs } from 'vue'
import eventBus from '@/utils/eventBus'
import DragComponent from '@/custom-component/component-group/DragComponent.vue'
@ -31,8 +32,8 @@ const props = defineProps({
})
const { dvModel } = toRefs(props)
const newComponent = params => {
eventBus.emit('handleNew', { componentName: params, innerType: params })
const newComponent = (componentName, innerType) => {
eventBus.emit('handleNew', { componentName: componentName, innerType: innerType })
}
const handleDragStart = e => {
@ -47,25 +48,36 @@ const handleDragEnd = e => {
<template>
<div class="group" @dragstart="handleDragStart" @dragend="handleDragEnd">
<drag-component
class="media-component"
:themes="themes"
:icon="dvPictureShow"
label="图片"
drag-info="Picture&Picture"
v-on:click="newComponent('Picture')"
v-on:click="newComponent('Picture', 'Picture')"
></drag-component>
<drag-component
class="media-component"
:themes="themes"
:icon="iconVideo"
label="视频"
drag-info="DeVideo&DeVideo"
v-on:click="newComponent('DeVideo')"
v-on:click="newComponent('DeVideo', 'DeVideo')"
></drag-component>
<drag-component
class="media-component"
:themes="themes"
:icon="iconStream"
label="流媒体"
drag-info="DeStreamMedia&DeStreamMedia"
v-on:click="newComponent('DeStreamMedia')"
v-on:click="newComponent('DeStreamMedia', 'DeStreamMedia')"
></drag-component>
<drag-component
class="media-component"
:themes="themes"
:icon="pictureGroupOrigin"
label="图片组"
drag-info="UserView&picture-group"
v-on:click="newComponent('UserView', 'picture-group')"
></drag-component>
</div>
</template>
@ -73,6 +85,9 @@ const handleDragEnd = e => {
<style lang="less" scoped>
.group {
padding: 12px 8px;
display: inline-flex;
}
.media-component {
float: left;
margin: 0 6px !important;
}
</style>

View File

@ -1,200 +1,60 @@
<script setup lang="ts">
import CommonAttr from '@/custom-component/common/CommonAttr.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { storeToRefs } from 'pinia'
import { ElIcon, ElMessage } from 'element-plus-secondary'
import { ref, onMounted, onBeforeUnmount, watch, PropType, computed } from 'vue'
import { beforeUploadCheck, uploadFileResult } from '@/api/staticResource'
import { imgUrlTrans } from '@/utils/imgUtils'
import eventBus from '@/utils/eventBus'
import ImgViewDialog from '@/custom-component/ImgViewDialog.vue'
import { useI18n } from '@/hooks/web/useI18n'
import { toRefs } from 'vue'
const { t } = useI18n()
import { PropType } from 'vue'
import PictureGroupUploadAttr from '@/custom-component/picture-group/PictureGroupUploadAttr.vue'
import PictureGroupDatasetSelect from '@/custom-component/picture-group/PictureGroupDatasetSelect.vue'
import CarouselSetting from '@/custom-component/common/CarouselSetting.vue'
import PictureGroupThreshold from '@/custom-component/picture-group/PictureGroupThreshold.vue'
const props = defineProps({
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
element: {
type: Object,
default() {
return {
propValue: {
urlList: []
}
}
}
}
})
const dvMainStore = dvMainStoreWithOut()
const snapshotStore = snapshotStoreWithOut()
const { element } = toRefs(props)
const { curComponent } = storeToRefs(dvMainStore)
const fileList = ref([])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const uploadDisabled = ref(false)
const files = ref(null)
const maxImageSize = 15000000
const handlePictureCardPreview = file => {
dialogImageUrl.value = file.url
dialogVisible.value = true
}
const handleRemove = (_, fileList) => {
uploadDisabled.value = false
element.value.propValue['urlList'] = []
fileList.value = []
snapshotStore.recordSnapshotCache()
}
async function upload(file) {
uploadFileResult(file.file, fileUrl => {
snapshotStore.recordSnapshotCache()
element.value.propValue.urlList.push({ name: file.file.name, url: fileUrl })
})
}
const onStyleChange = () => {
snapshotStore.recordSnapshotCache()
}
const goFile = () => {
files.value.click()
}
const reUpload = e => {
const file = e.target.files[0]
if (file.size > maxImageSize) {
sizeMessage()
return
}
uploadFileResult(file, fileUrl => {
snapshotStore.recordSnapshotCache()
element.value.propValue.url = fileUrl
fileList.value = [{ name: file.name, url: imgUrlTrans(element.value.propValue.url) }]
})
}
const sizeMessage = () => {
ElMessage.success('图片大小不符合')
}
const fileListInit = () => {
fileList.value = []
if (element.value.propValue.urlList && element.value.propValue.urlList.length > 0) {
element.value.propValue.urlList.forEach(urlInfo => {
fileList.value.push({ name: urlInfo.name, url: imgUrlTrans(urlInfo.url) })
})
}
}
const init = () => {
fileListInit()
}
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
watch(
() => element.value.propValue.url,
() => {
init()
}
)
onMounted(() => {
init()
eventBus.on('uploadImg', goFile)
})
onBeforeUnmount(() => {
eventBus.off('uploadImg', goFile)
})
const { curComponent, canvasViewInfo } = storeToRefs(dvMainStore)
</script>
<template>
<el-collapse-item :effect="themes" title="图片组" name="picture">
<input
id="input"
ref="files"
type="file"
accept=".jpeg,.jpg,.png,.gif,.svg"
hidden
@click="
e => {
e.target.value = ''
}
"
@change="reUpload"
/>
<el-row class="img-area" :class="`img-area_${themes}`">
<el-col style="width: 130px !important">
<el-upload
<div class="attr-list de-collapse-style">
<CommonAttr
:themes="themes"
limit="10"
action=""
accept=".jpeg,.jpg,.png,.gif,.svg"
class="avatar-uploader"
list-type="picture-card"
:class="{ disabled: uploadDisabled }"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUploadCheck"
:http-request="upload"
:file-list="fileList"
:element="curComponent"
:background-color-picker-width="197"
:background-border-select-width="197"
>
<el-icon><Plus /></el-icon>
</el-upload>
<img-view-dialog v-model="dialogVisible" :image-url="dialogImageUrl"></img-view-dialog>
</el-col>
</el-row>
<el-row>
<span
style="margin-top: 2px"
v-if="!curComponent.propValue.url"
class="image-hint"
:class="`image-hint_${themes}`"
<template v-slot:dataset>
<picture-group-dataset-select
:themes="themes"
:view="canvasViewInfo[curComponent ? curComponent.id : 'default']"
>
支持JPGPNGGIFSVG
</span>
<el-button
size="small"
style="margin: 8px 0 0 -4px"
v-if="curComponent.propValue.url"
text
@click="goFile"
>
重新上传
</el-button>
</el-row>
<el-row class="pic-adaptor">
<el-form-item
v-if="curComponent.style.adaptation"
class="form-item form-item-custom"
label="图片适应方式"
size="small"
:effect="themes"
>
<el-radio-group
size="small"
v-model="curComponent.style.adaptation"
@change="onStyleChange"
:effect="themes"
>
<el-radio label="adaptation" :effect="themes">适应组件</el-radio>
<el-radio label="original" :effect="themes">原始尺寸</el-radio>
<el-radio label="equiratio" :effect="themes">等比适应</el-radio>
</el-radio-group>
</el-form-item>
</el-row>
</el-collapse-item>
</picture-group-dataset-select>
</template>
<picture-group-upload-attr
:themes="themes"
:element="curComponent"
></picture-group-upload-attr>
<template v-slot:carousel>
<carousel-setting
v-if="curComponent?.innerType === 'picture-group'"
:element="curComponent"
:themes="themes"
></carousel-setting>
</template>
<template v-slot:threshold>
<picture-group-threshold
:themes="themes"
:view="canvasViewInfo[curComponent ? curComponent.id : 'default']"
></picture-group-threshold>
</template>
</CommonAttr>
</div>
</template>
<style lang="less" scoped>
@ -254,6 +114,8 @@ onBeforeUnmount(() => {
}
}
.img-area {
height: 80px;
width: 80px;
margin-top: 10px;
overflow: hidden;
@ -308,9 +170,9 @@ onBeforeUnmount(() => {
}
}
.form-item-custom {
.form-item-dark {
.ed-radio {
margin-right: 2px !important;
margin-right: 4px !important;
}
}

View File

@ -0,0 +1,205 @@
<script setup lang="ts">
import { PropType, reactive, toRefs } from 'vue'
import { BASE_VIEW_CONFIG } from '@/views/chart/components/editor/util/chart'
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { useEmitt } from '@/hooks/web/useEmitt'
const snapshotStore = snapshotStoreWithOut()
const props = defineProps({
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
view: {
type: Object as PropType<ChartObj>,
required: false,
default() {
return { ...BASE_VIEW_CONFIG }
}
}
})
const { view } = toRefs(props)
const state = reactive({})
const onDatasetUpdate = () => {
useEmitt().emitter.emit('calcData-' + view.value.id, view)
snapshotStore.recordSnapshotCache('calc', view.value.id)
}
</script>
<template>
<el-collapse-item :effect="themes" title="数据集" name="dataset">
<dataset-select
ref="datasetSelector"
v-model="view.tableId"
style="flex: 1"
:view-id="view.id"
:themes="themes"
@on-dataset-change="onDatasetUpdate"
:state-obj="state"
/>
</el-collapse-item>
</template>
<style lang="less" scoped>
.de-collapse-style {
:deep(.ed-collapse-item__header) {
height: 36px !important;
line-height: 36px !important;
font-size: 12px !important;
padding: 0 !important;
font-weight: 500 !important;
.ed-collapse-item__arrow {
margin: 0 6px 0 8px;
}
}
:deep(.ed-collapse-item__content) {
padding: 16px 8px 0;
}
:deep(.ed-form-item) {
display: block;
margin-bottom: 8px;
}
:deep(.ed-form-item__label) {
justify-content: flex-start;
}
}
.disabled :deep(.el-upload--picture-card) {
display: none;
}
.avatar-uploader :deep(.ed-upload) {
width: 80px;
height: 80px;
line-height: 90px;
}
.avatar-uploader :deep(.ed-upload-list li) {
width: 80px !important;
height: 80px !important;
}
:deep(.ed-upload--picture-card) {
background: #eff0f1;
border: 1px dashed #dee0e3;
border-radius: 4px;
.ed-icon {
color: #1f2329;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
.img-area {
height: 80px;
width: 80px;
margin-top: 10px;
overflow: hidden;
&.img-area_dark {
:deep(.ed-upload-list__item).is-success {
border-color: #434343;
}
:deep(.ed-upload--picture-card) {
background: #373737;
border-color: #434343;
.ed-icon {
color: #ebebeb;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
}
&.img-area_light {
:deep(.ed-upload-list__item).is-success {
border-color: #dee0e3;
}
}
}
.image-hint {
color: #8f959e;
size: 14px;
line-height: 22px;
font-weight: 400;
margin-top: 2px;
&.image-hint_dark {
color: #757575;
}
}
.re-update-span {
cursor: pointer;
color: var(--ed-color-primary);
size: 14px;
line-height: 22px;
font-weight: 400;
}
.pic-adaptor {
margin: 8px 0 16px 0;
:deep(.ed-form-item__content) {
margin-top: 8px !important;
}
}
.form-item-dark {
.ed-radio {
margin-right: 4px !important;
}
}
.drag-data {
padding-top: 8px;
padding-bottom: 16px;
.tree-btn {
width: 100%;
margin-top: 8px;
background: #fff;
height: 32px;
border-radius: 4px;
border: 1px solid #dcdfe6;
display: flex;
color: #cccccc;
align-items: center;
cursor: pointer;
justify-content: center;
font-size: 12px;
&.tree-btn--dark {
background: rgba(235, 235, 235, 0.05);
border-color: #5f5f5f;
}
&.active {
color: #3370ff;
border-color: #3370ff;
}
}
&.no-top-border {
border-top: none !important;
}
&.no-top-padding {
padding-top: 0 !important;
}
&:nth-child(n + 2) {
border-top: 1px solid @side-outline-border-color;
}
&:first-child {
border-top: none !important;
}
}
</style>

View File

@ -0,0 +1,211 @@
<script setup lang="ts">
import { PropType, toRefs } from 'vue'
import { BASE_VIEW_CONFIG } from '@/views/chart/components/editor/util/chart'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import Threshold from '@/views/chart/components/editor/editor-senior/components/Threshold.vue'
import { CollapseSwitchItem } from '@/components/collapse-switch-item'
import { useI18n } from '@/hooks/web/useI18n'
const snapshotStore = snapshotStoreWithOut()
const { t } = useI18n()
const props = defineProps({
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
view: {
type: Object as PropType<ChartObj>,
required: false,
default() {
return { ...BASE_VIEW_CONFIG }
}
}
})
const { view } = toRefs(props)
const onThresholdChange = val => {
// do
view.value.senior.threshold = val
snapshotStore.recordSnapshotCache('calcData', view.value.id)
}
</script>
<template>
<collapse-switch-item
:effect="themes"
:title="t('chart.threshold')"
:change-model="view.senior.threshold"
v-model="view.senior.threshold.enable"
name="threshold"
@modelChange="onThresholdChange"
>
<threshold
:themes="themes"
:chart="view"
:property-inner="['tableThreshold']"
@onThresholdChange="onThresholdChange"
/>
</collapse-switch-item>
</template>
<style lang="less" scoped>
.de-collapse-style {
:deep(.ed-collapse-item__header) {
height: 36px !important;
line-height: 36px !important;
font-size: 12px !important;
padding: 0 !important;
font-weight: 500 !important;
.ed-collapse-item__arrow {
margin: 0 6px 0 8px;
}
}
:deep(.ed-collapse-item__content) {
padding: 16px 8px 0;
}
:deep(.ed-form-item) {
display: block;
margin-bottom: 8px;
}
:deep(.ed-form-item__label) {
justify-content: flex-start;
}
}
.disabled :deep(.el-upload--picture-card) {
display: none;
}
.avatar-uploader :deep(.ed-upload) {
width: 80px;
height: 80px;
line-height: 90px;
}
.avatar-uploader :deep(.ed-upload-list li) {
width: 80px !important;
height: 80px !important;
}
:deep(.ed-upload--picture-card) {
background: #eff0f1;
border: 1px dashed #dee0e3;
border-radius: 4px;
.ed-icon {
color: #1f2329;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
.img-area {
height: 80px;
width: 80px;
margin-top: 10px;
overflow: hidden;
&.img-area_dark {
:deep(.ed-upload-list__item).is-success {
border-color: #434343;
}
:deep(.ed-upload--picture-card) {
background: #373737;
border-color: #434343;
.ed-icon {
color: #ebebeb;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
}
&.img-area_light {
:deep(.ed-upload-list__item).is-success {
border-color: #dee0e3;
}
}
}
.image-hint {
color: #8f959e;
size: 14px;
line-height: 22px;
font-weight: 400;
margin-top: 2px;
&.image-hint_dark {
color: #757575;
}
}
.re-update-span {
cursor: pointer;
color: var(--ed-color-primary);
size: 14px;
line-height: 22px;
font-weight: 400;
}
.pic-adaptor {
margin: 8px 0 16px 0;
:deep(.ed-form-item__content) {
margin-top: 8px !important;
}
}
.form-item-dark {
.ed-radio {
margin-right: 4px !important;
}
}
.drag-data {
padding-top: 8px;
padding-bottom: 16px;
.tree-btn {
width: 100%;
margin-top: 8px;
background: #fff;
height: 32px;
border-radius: 4px;
border: 1px solid #dcdfe6;
display: flex;
color: #cccccc;
align-items: center;
cursor: pointer;
justify-content: center;
font-size: 12px;
&.tree-btn--dark {
background: rgba(235, 235, 235, 0.05);
border-color: #5f5f5f;
}
&.active {
color: #3370ff;
border-color: #3370ff;
}
}
&.no-top-border {
border-top: none !important;
}
&.no-top-padding {
padding-top: 0 !important;
}
&:nth-child(n + 2) {
border-top: 1px solid @side-outline-border-color;
}
&:first-child {
border-top: none !important;
}
}
</style>

View File

@ -0,0 +1,358 @@
<script setup lang="ts">
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { storeToRefs } from 'pinia'
import { ElIcon, ElMessage } from 'element-plus-secondary'
import { ref, onMounted, onBeforeUnmount, watch, PropType, computed } from 'vue'
import { beforeUploadCheck, uploadFileResult } from '@/api/staticResource'
import { imgUrlTrans } from '@/utils/imgUtils'
import eventBus from '@/utils/eventBus'
import ImgViewDialog from '@/custom-component/ImgViewDialog.vue'
import { useI18n } from '@/hooks/web/useI18n'
import { toRefs } from 'vue'
const { t } = useI18n()
const props = defineProps({
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
element: {
type: Object,
default() {
return {
propValue: {
urlList: []
}
}
}
}
})
const dvMainStore = dvMainStoreWithOut()
const snapshotStore = snapshotStoreWithOut()
const { element } = toRefs(props)
const { curComponent } = storeToRefs(dvMainStore)
const fileList = ref([])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const uploadDisabled = ref(false)
const files = ref(null)
const maxImageSize = 15000000
const handlePictureCardPreview = file => {
dialogImageUrl.value = file.url
dialogVisible.value = true
}
const handleRemove = (_, fileList) => {
uploadDisabled.value = false
element.value.propValue['urlList'] = []
fileList.value = []
snapshotStore.recordSnapshotCache()
}
async function upload(file) {
uploadFileResult(file.file, fileUrl => {
snapshotStore.recordSnapshotCache()
element.value.propValue.urlList.push({ name: file.file.name, url: fileUrl })
})
}
const onStyleChange = () => {
snapshotStore.recordSnapshotCache()
}
const goFile = () => {
files.value.click()
}
const reUpload = e => {
const file = e.target.files[0]
if (file.size > maxImageSize) {
sizeMessage()
return
}
uploadFileResult(file, fileUrl => {
snapshotStore.recordSnapshotCache()
element.value.propValue.url = fileUrl
fileList.value = [{ name: file.name, url: imgUrlTrans(element.value.propValue.url) }]
})
}
const sizeMessage = () => {
ElMessage.success('图片大小不符合')
}
const fileListInit = () => {
fileList.value = []
if (element.value.propValue.urlList && element.value.propValue.urlList.length > 0) {
element.value.propValue.urlList.forEach(urlInfo => {
fileList.value.push({ name: urlInfo.name, url: imgUrlTrans(urlInfo.url) })
})
}
}
const init = () => {
fileListInit()
}
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
watch(
() => element.value.propValue.url,
() => {
init()
}
)
onMounted(() => {
init()
eventBus.on('uploadImg', goFile)
})
onBeforeUnmount(() => {
eventBus.off('uploadImg', goFile)
})
</script>
<template>
<el-collapse-item :effect="themes" title="图片组" name="picture">
<input
id="input"
ref="files"
type="file"
accept=".jpeg,.jpg,.png,.gif,.svg"
hidden
@click="
e => {
e.target.value = ''
}
"
@change="reUpload"
/>
<el-row class="img-area" :class="`img-area_${themes}`">
<el-col style="width: 130px !important">
<el-upload
:themes="themes"
limit="10"
action=""
accept=".jpeg,.jpg,.png,.gif,.svg"
class="avatar-uploader"
list-type="picture-card"
:class="{ disabled: uploadDisabled }"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUploadCheck"
:http-request="upload"
:file-list="fileList"
>
<el-icon><Plus /></el-icon>
</el-upload>
<img-view-dialog v-model="dialogVisible" :image-url="dialogImageUrl"></img-view-dialog>
</el-col>
</el-row>
<el-row>
<span
style="margin-top: 2px"
v-if="!curComponent.propValue.url"
class="image-hint"
:class="`image-hint_${themes}`"
>
支持JPGPNGGIFSVG
</span>
<el-button
size="small"
style="margin: 8px 0 0 -4px"
v-if="curComponent.propValue.url"
text
@click="goFile"
>
重新上传
</el-button>
</el-row>
<el-row class="pic-adaptor">
<el-form-item
v-if="curComponent.style.adaptation"
class="form-item form-item-custom"
label="图片适应方式"
size="small"
:effect="themes"
>
<el-radio-group
size="small"
v-model="curComponent.style.adaptation"
@change="onStyleChange"
:effect="themes"
>
<el-radio label="adaptation" :effect="themes">适应组件</el-radio>
<el-radio label="original" :effect="themes">原始尺寸</el-radio>
<el-radio label="equiratio" :effect="themes">等比适应</el-radio>
</el-radio-group>
</el-form-item>
</el-row>
</el-collapse-item>
</template>
<style lang="less" scoped>
.de-collapse-style {
:deep(.ed-collapse-item__header) {
height: 36px !important;
line-height: 36px !important;
font-size: 12px !important;
padding: 0 !important;
font-weight: 500 !important;
.ed-collapse-item__arrow {
margin: 0 6px 0 8px;
}
}
:deep(.ed-collapse-item__content) {
padding: 16px 8px 0;
}
:deep(.ed-form-item) {
display: block;
margin-bottom: 8px;
}
:deep(.ed-form-item__label) {
justify-content: flex-start;
}
}
.disabled :deep(.el-upload--picture-card) {
display: none;
}
.avatar-uploader :deep(.ed-upload) {
width: 80px;
height: 80px;
line-height: 90px;
}
.avatar-uploader :deep(.ed-upload-list li) {
width: 80px !important;
height: 80px !important;
}
:deep(.ed-upload--picture-card) {
background: #eff0f1;
border: 1px dashed #dee0e3;
border-radius: 4px;
.ed-icon {
color: #1f2329;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
.img-area {
margin-top: 10px;
overflow: hidden;
&.img-area_dark {
:deep(.ed-upload-list__item).is-success {
border-color: #434343;
}
:deep(.ed-upload--picture-card) {
background: #373737;
border-color: #434343;
.ed-icon {
color: #ebebeb;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
}
&.img-area_light {
:deep(.ed-upload-list__item).is-success {
border-color: #dee0e3;
}
}
}
.image-hint {
color: #8f959e;
size: 14px;
line-height: 22px;
font-weight: 400;
margin-top: 2px;
&.image-hint_dark {
color: #757575;
}
}
.re-update-span {
cursor: pointer;
color: var(--ed-color-primary);
size: 14px;
line-height: 22px;
font-weight: 400;
}
.pic-adaptor {
margin: 8px 0 16px 0;
:deep(.ed-form-item__content) {
margin-top: 8px !important;
}
}
.form-item-custom {
.ed-radio {
margin-right: 2px !important;
}
}
.drag-data {
padding-top: 8px;
padding-bottom: 16px;
.tree-btn {
width: 100%;
margin-top: 8px;
background: #fff;
height: 32px;
border-radius: 4px;
border: 1px solid #dcdfe6;
display: flex;
color: #cccccc;
align-items: center;
cursor: pointer;
justify-content: center;
font-size: 12px;
&.tree-btn--dark {
background: rgba(235, 235, 235, 0.05);
border-color: #5f5f5f;
}
&.active {
color: #3370ff;
border-color: #3370ff;
}
}
&.no-top-border {
border-top: none !important;
}
&.no-top-padding {
padding-top: 0 !important;
}
&:nth-child(n + 2) {
border-top: 1px solid @side-outline-border-color;
}
&:first-child {
border-top: none !important;
}
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<div class="img-option">
<div class="img-area" :class="{ 'selected-active': active }">
<img draggable="false" :src="imgUrlTrans(urlInfo.url)" />
</div>
<span :title="urlInfo.name" class="name-area">{{ urlInfo.name }}</span>
</div>
</template>
<script setup lang="ts">
import { toRefs } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
const props = withDefaults(
defineProps<{
urlInfo: any
active: boolean
}>(),
{}
)
const { urlInfo, active } = toRefs(props)
</script>
<style scoped lang="less">
.img-option {
margin: 0 5px !important;
width: 88px;
height: 88px;
display: flex;
flex-direction: column;
align-content: center;
.selected-active {
border: 1px solid var(--ed-color-primary-99, rgba(51, 112, 255, 0.6));
}
.img-area {
&:hover {
border: 1px dashed var(--ed-color-primary-99, rgba(51, 112, 255, 0.6));
}
border-radius: 4px;
background-color: #f5f6f7;
width: 80px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
img {
width: 100%;
height: 100%;
}
}
.name-area {
margin: 6px 4px;
width: 80px;
line-height: 20px;
font-size: 12px !important;
font-weight: 400;
text-align: center;
white-space: nowrap; /* 不换行 */
overflow: hidden; /* 隐藏超出的内容 */
text-overflow: ellipsis; /* 用省略号表示被隐藏的部分 */
color: rgba(100, 106, 115, 1);
}
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<div class="img-option-prefix">
<img draggable="false" :src="imgUrlTrans(url)" />
</div>
</template>
<script setup lang="ts">
import { imgUrlTrans } from '@/utils/imgUtils'
withDefaults(
defineProps<{
url: any
}>(),
{}
)
</script>
<style scoped lang="less">
.img-option-prefix {
width: 20px;
height: 20px;
display: flex;
flex-direction: column;
align-content: center;
img {
border-radius: 4px;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -6,6 +6,7 @@
@keyup.stop
@dblclick="setEdit"
@click="onClick"
:style="richTextStyle"
>
<chart-error v-if="isError" :err-msg="errMsg" />
<Editor
@ -163,7 +164,28 @@ const init = ref({
inline: true, //
branding: false,
icons: 'vertical-content',
vertical_align: element.value.propValue.verticalAlign
vertical_align: element.value.propValue.verticalAlign,
setup: function (editor) {
//
editor.on('ObjectResizeStart', function (e) {
const { target, width, height } = e
if (target.nodeName === 'TABLE') {
//
// e.width = width / props.scale
// e.height = height / props.scale
}
})
//
editor.on('ObjectResized', function (e) {
const { target, width, height } = e
if (target.nodeName === 'TABLE') {
//
// target.style.width = `${width * props.scale}px`
// target.style.height = `${height scaleFactor}px`
}
})
}
})
const editStatus = computed(() => {
@ -182,6 +204,7 @@ watch(
canEdit.value = false
reShow()
myValue.value = assignment(element.value.propValue.textValue)
console.log('===myValue.value=' + myValue.value)
ed.setContent(myValue.value)
}
}
@ -241,6 +264,23 @@ const initCurFieldsChange = () => {
}
}
const jumpTargetAdaptor = () => {
setTimeout(() => {
const paragraphs = document.querySelectorAll('p')
paragraphs.forEach(p => {
// p onclick event.stopPropagation
if (
p.getAttribute('onclick') &&
p.getAttribute('onclick').includes('event.stopPropagation()')
) {
return // stopPropagation
}
// onclick
p.setAttribute('onclick', 'event.stopPropagation()')
})
}, 1000)
}
const assignment = content => {
const on = content.match(/\[(.+?)\]/g)
if (on) {
@ -266,8 +306,10 @@ const assignment = content => {
//De
content = content.replace(/href="#\//g, 'href="/#/')
content = content.replace(/href=\\"#\//g, 'href=\\"/#/')
content = content.replace(/href=\\"#\//g, 'href=\\"/#/')
resetSelect()
initFontFamily(content)
jumpTargetAdaptor()
return content
}
const initFontFamily = htmlText => {
@ -561,6 +603,8 @@ const conditionAdaptor = (chart: Chart) => {
return res
}
const richTextStyle = computed(() => [{ '--de-canvas-scale': props.scale }])
onMounted(() => {
viewInit()
})
@ -583,6 +627,12 @@ defineExpose({
width: 0px !important;
height: 0px !important;
}
::v-deep(p) {
zoom: var(--de-canvas-scale);
}
::v-deep(td span) {
zoom: var(--de-canvas-scale);
}
}
:deep(.ol) {

View File

@ -58,18 +58,19 @@ const props = defineProps({
const { element, view, active, searchCount, scale } = toRefs(props)
const autoStyle = computed(() => {
if (element.value.innerType === 'rich-text') {
return {
position: 'absolute',
height: 100 / scale.value + '%!important',
width: 100 / scale.value + '%!important',
left: 50 * (1 - 1 / scale.value) + '%', // 2
top: 50 * (1 - 1 / scale.value) + '%', // 2
transform: 'scale(' + scale.value + ')'
} as CSSProperties
} else {
return {}
}
// if (element.value.innerType === 'rich-text') {
// return {
// position: 'absolute',
// height: 100 / scale.value + '%!important',
// width: 100 / scale.value + '%!important',
// left: 50 * (1 - 1 / scale.value) + '%', // 2
// top: 50 * (1 - 1 / scale.value) + '%', // 2
// transform: 'scale(' + scale.value + ') translateZ(0)'
// } as CSSProperties
// } else {
// return {}
// }
})
const emits = defineEmits(['onPointClick'])

View File

@ -18,7 +18,8 @@ import {
onBeforeMount,
CSSProperties,
shallowRef,
provide
provide,
nextTick
} from 'vue'
import { storeToRefs } from 'pinia'
import { useI18n } from '@/hooks/web/useI18n'
@ -57,12 +58,15 @@ const props = defineProps({
})
const { element, view, scale } = toRefs(props)
const { t } = useI18n()
const vQueryRef = ref()
const dvMainStore = dvMainStoreWithOut()
const { curComponent, canvasViewInfo, mobileInPc, firstLoadMap } = storeToRefs(dvMainStore)
const canEdit = ref(false)
const queryConfig = ref()
const defaultStyle = {
border: '',
placeholderSize: 14,
placeholderShow: true,
background: '',
text: '',
layout: 'horizontal',
@ -111,6 +115,27 @@ const btnStyle = computed(() => {
return style
})
const btnPlainStyle = computed(() => {
const style = {
backgroundColor: 'transparent',
borderColor: customStyle.btnColor,
color: customStyle.btnColor
} as CSSProperties
if (customStyle.fontSizeBtn) {
style.fontSize = customStyle.fontSizeBtn + 'px'
}
if (customStyle.fontWeightBtn) {
style.fontWeight = customStyle.fontWeightBtn
}
if (customStyle.fontStyleBtn) {
style.fontStyle = customStyle.fontStyleBtn
}
return style
})
const curComponentView = computed(() => {
return (canvasViewInfo.value[element.value.id] || {}).customStyle
})
@ -130,7 +155,6 @@ const setCustomStyle = val => {
layout,
titleShow,
titleColor,
textColorShow,
title,
fontSize,
fontWeight,
@ -143,12 +167,22 @@ const setCustomStyle = val => {
queryConditionSpacing,
labelColorBtn,
btnColor,
placeholderSize,
placeholderShow,
labelShow
} = val
customStyle.background = bgColorShow ? bgColor || '' : ''
customStyle.border = borderShow ? borderColor || '' : ''
customStyle.btnList = [...btnList]
customStyle.layout = layout
customStyle.placeholderShow = placeholderShow ?? true
customStyle.placeholderSize = placeholderSize ?? 14
nextTick(() => {
vQueryRef.value.style.setProperty(
'--ed-component-size',
`${customStyle.placeholderSize + 18}px`
)
})
customStyle.titleShow = titleShow
customStyle.titleColor = titleColor
customStyle.labelColor = labelShow ? labelColor || '' : ''
@ -156,7 +190,7 @@ const setCustomStyle = val => {
customStyle.fontWeight = labelShow ? fontWeight || '' : ''
customStyle.fontStyle = labelShow ? fontStyle || '' : ''
customStyle.title = title
customStyle.text = textColorShow ? text || '' : ''
customStyle.text = customStyle.placeholderShow ? text || '' : ''
customStyle.titleLayout = titleLayout
customStyle.fontSizeBtn = fontSizeBtn || '14'
customStyle.fontWeightBtn = fontWeightBtn
@ -271,6 +305,12 @@ const getCascadeList = () => {
return props.element.cascade
}
const getPlaceholder = computed(() => {
return {
placeholderShow: customStyle.placeholderShow
}
})
const isConfirmSearch = id => {
if (componentWithSure.value) return
queryDataForId(id)
@ -282,6 +322,7 @@ provide('release-unmount-select', releaseSelect)
provide('query-data-for-id', queryDataForId)
provide('com-width', getQueryConditionWidth)
provide('cascade-list', getCascadeList)
provide('placeholder', getPlaceholder)
onBeforeUnmount(() => {
emitter.off(`addQueryCriteria${element.value.id}`)
@ -449,6 +490,10 @@ watch(
}
)
const boxWidth = computed(() => {
return `${customStyle.placeholderSize}px`
})
const queryData = () => {
let requiredName = ''
const emitterList = (element.value.propValue || []).reduce((pre, next) => {
@ -536,14 +581,14 @@ const autoStyle = computed(() => {
width: 100 / scale.value + '%!important',
left: 50 * (1 - 1 / scale.value) + '%', // 2
top: 50 * (1 - 1 / scale.value) + '%', // 2
transform: 'scale(' + scale.value + ')',
transform: 'scale(' + scale.value + ') translateZ(0)',
opacity: element.value?.style?.opacity || 1
} as CSSProperties
})
</script>
<template>
<div class="v-query-container" :style="autoStyle" @keydown.stop @keyup.stop>
<div class="v-query-container" ref="vQueryRef" :style="autoStyle" @keydown.stop @keyup.stop>
<p v-if="customStyle.titleShow" class="title" :style="titleStyle">
{{ customStyle.title }}
</p>
@ -619,10 +664,20 @@ const autoStyle = computed(() => {
</div>
</div>
<div class="query-button" v-if="!!listVisible.length">
<el-button @click.stop="clearData" v-if="customStyle.btnList.includes('clear')" secondary>
<el-button
@click.stop="clearData"
:style="btnPlainStyle"
v-if="customStyle.btnList.includes('clear')"
plain
>
{{ t('commons.clear') }}
</el-button>
<el-button @click.stop="resetData" v-if="customStyle.btnList.includes('reset')" secondary>
<el-button
@click.stop="resetData"
:style="btnPlainStyle"
v-if="customStyle.btnList.includes('reset')"
plain
>
{{ t('chart.reset') }}
</el-button>
<el-button
@ -653,6 +708,15 @@ const autoStyle = computed(() => {
height: 100%;
overflow: auto;
position: relative;
--ed-font-size-base: v-bind(boxWidth);
:deep(.ed-tag) {
--ed-tag-font-size: v-bind(boxWidth);
}
:deep(.ed-select-v2) {
font-size: v-bind(boxWidth);
}
.no-list-label {
width: 100%;

View File

@ -325,10 +325,11 @@ defineExpose({
<template>
<div class="list-item top-item" v-if="curComponent.displayType === '8'" @click.stop>
<div class="label">设置默认值</div>
<div class="value">
<div class="value" :class="curComponent.hideConditionSwitching && 'hide-condition_switching'">
<div class="condition-type">
<el-select
class="condition-value-select"
v-if="!curComponent.hideConditionSwitching"
popper-class="condition-value-select-popper"
v-model="curComponent.defaultConditionValueOperatorF"
>
@ -346,6 +347,7 @@ defineExpose({
<div class="condition-type" v-if="[1, 2].includes(curComponent.conditionType)">
<sapn class="condition-type-tip">{{ curComponent.conditionType === 1 ? '与' : '或' }}</sapn>
<el-select
v-if="!curComponent.hideConditionSwitching"
class="condition-value-select"
popper-class="condition-value-select-popper"
v-model="curComponent.defaultConditionValueOperatorS"
@ -710,6 +712,16 @@ defineExpose({
margin-top: -0.5px;
}
}
&.hide-condition_switching {
.bottom-line {
width: 307px !important;
&.next-line {
width: 288px !important;
}
}
}
}
.value {
.sort-field {

View File

@ -1,4 +1,3 @@
getLastStart
<script lang="ts" setup>
import more_v from '@/assets/svg/more_v.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
@ -1020,6 +1019,7 @@ const weightlessness = () => {
const parameterCompletion = () => {
const attributes = {
timeType: 'fixed',
hideConditionSwitching: false,
required: false,
defaultMapValue: [],
mapValue: [],
@ -1067,7 +1067,7 @@ const parameterCompletion = () => {
treeFieldList: []
}
Object.entries(attributes).forEach(([key, val]) => {
!curComponent.value[key] && (curComponent.value[key] = val)
curComponent.value[key] ?? (curComponent.value[key] = val)
})
if (!curComponent.value.timeRange.relativeToCurrentRange) {
@ -2138,6 +2138,9 @@ defineExpose({
</div>
</div>
</div>
<div style="margin-bottom: 10.5px" v-if="curComponent.displayType === '8'">
<el-checkbox v-model="curComponent.hideConditionSwitching" label="隐藏条件切换" />
</div>
<condition-default-configuration
ref="defaultConfigurationRef"
@handleTimeTypeChange="handleTimeTypeChange"

View File

@ -25,6 +25,7 @@ interface SelectConfig {
displayType: string
showEmpty: boolean
id: string
placeholder: string
resultMode: number
displayId: string
sort: string
@ -73,6 +74,7 @@ const loading = ref(false)
const multiple = ref(false)
const options = shallowRef([])
const unMountSelect: Ref = inject('unmount-select')
const placeholder: Ref = inject('placeholder')
const releaseSelect = inject('release-unmount-select', Function, true)
const queryDataForId = inject('query-data-for-id', Function, true)
const isConfirmSearch = inject('is-confirm-search', Function, true)
@ -80,6 +82,12 @@ const queryConditionWidth = inject('com-width', Function, true)
const cascadeList = inject('cascade-list', Function, true)
const setCascadeDefault = inject('set-cascade-default', Function, true)
const placeholderText = computed(() => {
if (placeholder.value.placeholderShow) {
return props.config.placeholder
}
return ' '
})
const cascade = computed(() => {
return cascadeList() || []
})
@ -583,6 +591,7 @@ defineExpose({
key="multiple"
ref="mult"
v-model="selectValue"
:placeholder="placeholderText"
v-loading="loading"
filterable
@change="handleValueChange"
@ -604,6 +613,7 @@ defineExpose({
v-else
v-model="selectValue"
key="single"
:placeholder="placeholderText"
v-loading="loading"
@change="handleValueChange"
clearable

View File

@ -1,19 +1,29 @@
<script lang="ts" setup>
import { toRefs, onBeforeMount, type PropType, inject, computed, nextTick } from 'vue'
import { toRefs, onBeforeMount, type PropType, type Ref, inject, computed, nextTick } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
interface SelectConfig {
id: string
conditionValueOperatorF: string
conditionValueF: string
hideConditionSwitching: boolean
conditionValueOperatorS: string
conditionValueS: string
placeholder: string
defaultConditionValueOperatorF: string
defaultConditionValueF: string
defaultConditionValueOperatorS: string
defaultConditionValueS: string
conditionType: number
}
const placeholder: Ref = inject('placeholder')
const placeholderText = computed(() => {
if (placeholder.value.placeholderShow) {
return props.config.placeholder
}
return ' '
})
const operators = [
{
@ -50,6 +60,7 @@ const props = defineProps({
default: false
}
})
const { config } = toRefs(props)
const setParams = () => {
const {
@ -90,6 +101,7 @@ const lineWidth = computed(() => {
<div class="condition-type">
<el-select
class="condition-value-select"
v-if="!config.hideConditionSwitching"
@change="handleValueChange"
:effect="dvInfo.type === 'dataV' ? 'dark' : ''"
popper-class="condition-value-select-popper"
@ -100,6 +112,7 @@ const lineWidth = computed(() => {
</el-select>
<el-input
:style="selectStyle"
:placeholder="placeholderText"
@blur="handleValueChange"
class="condition-value-input"
v-model="config.conditionValueF"
@ -109,6 +122,7 @@ const lineWidth = computed(() => {
<div class="condition-type" v-if="[1, 2].includes(config.conditionType)">
<sapn class="condition-type-tip">{{ config.conditionType === 1 ? '与' : '或' }}</sapn>
<el-select
v-if="!config.hideConditionSwitching"
class="condition-value-select"
@change="handleValueChange"
:effect="dvInfo.type === 'dataV' ? 'dark' : ''"
@ -121,6 +135,7 @@ const lineWidth = computed(() => {
<el-input
:style="selectStyle"
@blur="handleValueChange"
:placeholder="placeholderText"
class="condition-value-input"
v-model="config.conditionValueS"
/>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { toRefs, PropType, ref, onBeforeMount, watch, nextTick, computed, inject } from 'vue'
import { toRefs, PropType, ref, Ref, onBeforeMount, watch, nextTick, computed, inject } from 'vue'
import { type DatePickType } from 'element-plus-secondary'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import type { ManipulateType } from 'dayjs'
@ -25,6 +25,7 @@ interface SelectConfig {
timeGranularity: DatePickType
timeGranularityMultiple: DatePickType
timeRange: TimeRange
placeholder: string
setTimeRange: boolean
}
@ -62,6 +63,13 @@ const props = defineProps({
default: false
}
})
const placeholder: Ref = inject('placeholder')
const placeholderText = computed(() => {
if (placeholder.value.placeholderShow) {
return props.config.placeholder
}
return ' '
})
const selectValue = ref()
const multiple = ref(false)
const dvMainStore = dvMainStoreWithOut()
@ -412,9 +420,10 @@ const formatDate = computed(() => {
['datetimerange', 'daterange'].includes(config.timeGranularityMultiple) ? shortcuts : []
"
@change="handleValueChange"
:editable="false"
:range-separator="$t('cron.to')"
:start-placeholder="$t('datasource.start_time')"
:end-placeholder="$t('datasource.end_time')"
:start-placeholder="placeholderText"
:end-placeholder="placeholderText"
/>
<el-date-picker
v-else
@ -422,7 +431,7 @@ const formatDate = computed(() => {
:type="config.timeGranularity"
@change="handleValueChange"
:style="selectStyle"
:placeholder="$t('commons.date.select_date_time')"
:placeholder="placeholderText"
/>
<div
v-if="dvMainStore.mobileInPc"

View File

@ -8,6 +8,7 @@ import {
onMounted,
computed,
inject,
Ref,
shallowRef
} from 'vue'
import { cloneDeep, debounce } from 'lodash-es'
@ -20,6 +21,7 @@ interface SelectConfig {
checkedFieldsMap: object
displayType: string
id: string
placeholder: string
checkedFields: string[]
treeFieldList: Array<any>
dataset: {
@ -53,6 +55,14 @@ const props = defineProps({
default: false
}
})
const placeholder: Ref = inject('placeholder')
const placeholderText = computed(() => {
if (placeholder.value.placeholderShow) {
return props.config.placeholder
}
return ' '
})
const { config } = toRefs(props)
const multiple = ref(false)
@ -231,6 +241,7 @@ const selectStyle = computed(() => {
:render-after-expand="false"
show-checkbox
showBtn
:placeholder="placeholderText"
collapse-tags
:showWholePath="showWholePath"
collapse-tags-tooltip
@ -245,6 +256,7 @@ const selectStyle = computed(() => {
:data="treeOptionList"
check-strictly
clearable
:placeholder="placeholderText"
:render-after-expand="false"
v-else-if="!multiple && !loading"
key="singleTree"
@ -256,6 +268,7 @@ const selectStyle = computed(() => {
v-model="fakeValue"
v-loading="loading"
:data="[]"
:placeholder="placeholderText"
:render-after-expand="false"
v-else
key="fakeTree"

View File

@ -365,7 +365,11 @@ export default {
please_insert_end: 'End Time Column Name',
save_form: 'Save Form',
default: 'default',
default_built_in: 'Built-in Database'
default_built_in: 'Built-in Database',
lt_check: 'need less than {0}',
gt_check: 'need greater than {0}',
le_check: 'need less than or equal to {0}',
ge_check: 'need greater than or equal to {0}'
},
database: {
nvarchar: 'Nvarchar',

View File

@ -266,7 +266,11 @@ export default {
please_insert_end: '請輸入結束時間',
save_form: '保存表單',
default: '默認',
default_built_in: '內建數據庫'
default_built_in: '內建數據庫',
lt_check: '值需要小于{0}',
gt_check: '值需要大于{0}',
le_check: '值需要小于等于{0}',
ge_check: '值需要大于等于{0}'
},
database: {
nvarchar: '字符串',

View File

@ -996,8 +996,9 @@ export default {
drag_block_value_axis_left: '左值轴',
drag_block_value_axis_right: '右值轴',
drag_block_table_data_column: '数据列',
drag_block_pie_angel: '扇区角度',
drag_block_pie_angle: '扇区角度',
drag_block_pie_label: '扇区标签',
drag_block_pie_radius: '扇区半径',
drag_block_gauge_angel: '指针角度',
drag_block_label_value: '值',
drag_block_funnel_width: '漏斗层宽',
@ -2703,7 +2704,11 @@ export default {
please_insert_end: '请输入结束时间',
save_form: '保存表单',
default: '默认',
default_built_in: '内建数据库'
default_built_in: '内建数据库',
lt_check: '值需要小于{0}',
gt_check: '值需要大于{0}',
le_check: '值需要小于等于{0}',
ge_check: '值需要大于等于{0}'
},
database: {
nvarchar: '字符串',

View File

@ -281,6 +281,14 @@ declare interface ChartBasicStyle {
* 汇总表总计标签
*/
summaryLabel: string
/**
* 符号地图符号大小最小值
*/
mapSymbolSizeMin: number
/**
* 符号地图符号大小最大值
*/
mapSymbolSizeMax: number
}
/**
* 表头属性
@ -860,6 +868,10 @@ declare interface ChartLabelAttr {
* 总计标签格式化设置
*/
totalFormatter: BaseFormatter
/**
* 柱状图堆叠指标
*/
showStackQuota: boolean
}
/**
* 提示设置

View File

@ -198,6 +198,28 @@ declare interface Threshold {
* url
*/
url: string
/**
* 类型固定值动态值
*/
type: 'fixed' | 'dynamic'
/**
* 动态值字段
*/
dynamicField: ThresholdDynamicField
/**
* 动态值最小值字段 仅当term为between时使用
*/
dynamicMinField: ThresholdDynamicField
/**
* 动态值最大值字段 仅当term为between时使用
*/
dynamicMaxField: ThresholdDynamicField
}
declare interface ThresholdDynamicField {
fieldId: string
summary: string
field: ChartViewField
}
/**

View File

@ -88,6 +88,10 @@ declare type AxisSpec = {
* 轴提示
*/
tooltip?: string
/**
* 允许为空
*/
allowEmpty?: boolean
}
/**
* 图表编辑表单

View File

@ -8,6 +8,7 @@ import eventBus from '@/utils/eventBus'
import { adaptCurThemeCommonStyle } from '@/utils/canvasStyle'
import { composeStoreWithOut } from '@/store/modules/data-visualization/compose'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { maxYComponentCount } from '@/utils/canvasUtils'
const dvMainStore = dvMainStoreWithOut()
const composeStore = composeStoreWithOut()
@ -62,7 +63,7 @@ export const copyStore = defineStore('copy', {
}
// dataV 数据大屏
newComponent.x = newComponent.sizeX * xPositionOffset + 1
newComponent.y = 200
newComponent.y = maxYComponentCount() + 10
// dataV 数据大屏
newComponent.style.left = 0
newComponent.style.top = 0

View File

@ -482,6 +482,8 @@ export const dvMainStore = defineStore('dataVisualization', {
fontStyleBtn: '',
queryConditionWidth: 227,
nameboxSpacing: 8,
placeholderShow: true,
placeholderSize: 14,
queryConditionSpacing: 16,
labelColorBtn: '#ffffff',
btnColor: '#3370ff'

View File

@ -731,3 +731,14 @@ export function componentPreSort(componentData) {
})
}
}
export function maxYComponentCount() {
if (componentData.value.length === 0) {
return 1
} else {
return componentData.value
.filter(item => item.y)
.map(item => item.y + item.sizeY) // 计算每个元素的 y + sizeY
.reduce((max, current) => Math.max(max, current), 0)
}
}

View File

@ -85,8 +85,8 @@ export default function findComponent(key) {
export function findComponentAttr(component) {
const key =
component.component === 'UserView' && component.innerType === 'Picture'
? 'PictureAttr'
component.component === 'UserView' && component.innerType === 'picture-group'
? 'PictureGroupAttr'
: component.component + 'Attr'
return componentsMap[key]
}

View File

@ -51,7 +51,8 @@ const removeCache = () => {
if (
key.startsWith('de-plugin-') ||
key === 'de-platform-client' ||
key === 'pwd-validity-period'
key === 'pwd-validity-period' ||
key === 'xpack-model-distributed'
) {
wsCache.delete(key)
}

View File

@ -184,3 +184,7 @@ export function cutTargetTree(tree: BusiTreeNode[], targetId: string | number) {
export const isLink = () => {
return window.location.hash.startsWith('#/de-link/')
}
export const isNull = arg => {
return typeof arg === 'undefined' || arg === null || arg === 'null'
}

View File

@ -87,6 +87,7 @@ const removeDistributeModule = () => {
localStorage.removeItem(key)
}
const importLic = file => {
removeDistributeModule()
const reader = new FileReader()
reader.onload = function (e) {
const licKey = e.target.result
@ -97,7 +98,6 @@ const importLic = file => {
reader.readAsText(file)
}
const validateHandler = (param, success) => {
removeDistributeModule()
validateApi(param).then(success)
}
const getLicense = result => {

View File

@ -249,7 +249,10 @@ const quickCalc = param => {
break
case 'setting':
//
//
if (chart.value.type !== 'indicator') {
resetValueFormatter(item.value)
}
editCompare()
break
case 'percent':
@ -370,57 +373,6 @@ onMounted(() => {
class="drop-style"
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
>
<!-- <el-dropdown-item @click.prevent v-if="chart.type === 'chart-mix'">
<el-dropdown
:effect="themes"
placement="right-start"
popper-class="data-dropdown_popper_mr9"
style="width: 100%"
@command="switchChartType"
>
<span class="el-dropdown-link inner-dropdown-menu menu-item-padding">
<span class="menu-item-content">
<el-icon>
<Icon name="icon_dashboard_outlined" ><icon_dashboard_outlined class="svg-icon" /></Icon>
</el-icon>
<span>{{ t('chart.chart_type') }}</span>
</span>
<el-icon>
<Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon>
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu
:effect="themes"
class="drop-style sub"
:class="themes === 'dark' ? 'dark-dimension-quota' : ''"
>
<el-dropdown-item class="menu-item-padding" :command="beforeSwitchType('bar')">
<span
class="sub-menu-content"
:class="'bar' === item.chartType ? 'content-active' : ''"
>
{{ t('chart.chart_bar') }}
<el-icon class="sub-menu-content&#45;&#45;icon">
<Icon name="icon_done_outlined" v-if="'bar' === item.chartType" ><icon_done_outlined class="svg-icon" /></Icon>
</el-icon>
</span>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" :command="beforeSwitchType('line')">
<span
class="sub-menu-content"
:class="'line' === item.chartType ? 'content-active' : ''"
>
{{ t('chart.chart_line') }}
<el-icon class="sub-menu-content&#45;&#45;icon">
<Icon name="icon_done_outlined" v-if="'line' === item.chartType" ><icon_done_outlined class="svg-icon" /></Icon>
</el-icon>
</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-dropdown-item>-->
<el-dropdown-item
@click.prevent
v-if="chart.type !== 'table-info' && item.summary !== ''"
@ -681,7 +633,7 @@ onMounted(() => {
props.type !== 'extLabel' &&
props.type !== 'extTooltip' &&
props.type !== 'extBubble' &&
!chart.type.includes('chart-mix')
!['chart-mix', 'indicator', 'liquid', 'gauge'].includes(chart.type)
"
:divided="chart.type !== 'table-info'"
>
@ -763,7 +715,7 @@ onMounted(() => {
:command="beforeClickItem('filter')"
:divided="chart.type.includes('chart-mix')"
>
<span>{{ t('chart.filter') }}...</span>
<span>{{ t('chart.filter') }}</span>
</el-dropdown-item>
<el-dropdown-item
@ -773,7 +725,7 @@ onMounted(() => {
:command="beforeClickItem('formatter')"
>
<el-icon />
<span>{{ t('chart.value_formatter') }}...</span>
<span>{{ t('chart.value_formatter') }}</span>
</el-dropdown-item>
<el-dropdown-item class="menu-item-padding" :command="beforeClickItem('rename')">

View File

@ -25,7 +25,7 @@ const props = defineProps({
>{{ t('chart.drag_block_value_axis') }}</span
>
<span v-else-if="props.view.type && props.view.type.includes('pie')">{{
t('chart.drag_block_pie_angel')
t('chart.drag_block_pie_angle')
}}</span>
<span v-else-if="props.view.type && props.view.type.includes('funnel')">{{
t('chart.drag_block_funnel_width')

View File

@ -32,6 +32,7 @@ import { cloneDeep, defaultsDeep } from 'lodash-es'
import BubbleAnimateCfg from '@/views/chart/components/editor/editor-senior/components/BubbleAnimateCfg.vue'
import { XpackComponent } from '@/components/plugin'
import CarouselSetting from '@/custom-component/common/CarouselSetting.vue'
import { Icon } from 'vant'
const dvMainStore = dvMainStoreWithOut()
const { nowPanelTrackInfo, nowPanelJumpInfo, dvInfo, componentData, curComponent } =
@ -338,28 +339,34 @@ const removeJumpSenior = () => {
已设置
</span>
<button
:class="'label-' + props.themes"
class="circle-button_icon"
:title="t('chart.delete')"
:class="'label-' + props.themes"
:style="{ margin: '0 8px' }"
@click="removeLinkageSenior"
>
<el-icon>
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
<Icon
><icon_deleteTrash_outlined
:class="chart.linkageActive && 'primary-color'"
class="svg-icon"
/></Icon>
</el-icon>
</button>
</template>
<button
:class="'label-' + props.themes"
class="circle-button_icon"
:title="t('chart.edit')"
:class="'label-' + props.themes"
@click="linkageSetOpen"
:disabled="!chart.linkageActive"
>
<el-icon>
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
<Icon
><icon_edit_outlined
:class="chart.linkageActive && 'primary-color'"
class="svg-icon"
/></Icon>
</el-icon>
</button>
</span>
@ -381,28 +388,34 @@ const removeJumpSenior = () => {
已设置
</span>
<button
:class="'label-' + props.themes"
class="circle-button_icon"
:title="t('chart.delete')"
:class="'label-' + props.themes"
:style="{ margin: '0 8px' }"
@click="removeJumpSenior"
>
<el-icon>
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
<Icon
><icon_deleteTrash_outlined
:class="chart.jumpActive && 'primary-color'"
class="svg-icon"
/></Icon>
</el-icon>
</button>
</template>
<button
:class="'label-' + props.themes"
class="circle-button_icon"
:title="t('chart.edit')"
:class="'label-' + props.themes"
@click="linkJumpSetOpen"
:disabled="!chart.jumpActive"
>
<el-icon>
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
<Icon
><icon_edit_outlined
:class="chart.jumpActive && 'primary-color'"
class="svg-icon"
/></Icon>
</el-icon>
</button>
</span>
@ -492,7 +505,7 @@ span {
font-style: normal;
font-weight: 400;
line-height: 20px;
color: #a6a6a6 !important;
color: #a6a6a6;
}
.inner-container {

View File

@ -166,7 +166,11 @@ onMounted(() => {
@click="editLine"
>
<el-icon>
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
<Icon
><icon_edit_outlined
:class="state.assistLineCfg.enable && 'primary-color'"
class="svg-icon"
/></Icon>
</el-icon>
</button>
</span>
@ -363,7 +367,7 @@ span {
font-style: normal;
font-weight: 400;
line-height: 20px;
color: #a6a6a6 !important;
color: #a6a6a6;
&.ed-button {
color: var(--ed-color-primary) !important;
}

View File

@ -193,6 +193,7 @@ const changeTableThreshold = () => {
ElMessage.error(t('chart.exp_can_not_empty'))
return
}
if (ele.type !== 'dynamic') {
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
@ -229,13 +230,51 @@ const changeTableThreshold = () => {
return
}
}
} else {
if (ele.term === 'between') {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
(!ele.dynamicMinField?.fieldId || !ele.dynamicMaxField?.fieldId)
) {
ElMessage.error(t('chart.field_can_not_empty'))
return
}
} else {
if (
!ele.term.includes('null') &&
!ele.term.includes('empty') &&
!ele.dynamicField?.fieldId
) {
ElMessage.error(t('chart.field_can_not_empty'))
return
}
}
}
}
}
state.thresholdForm.tableThreshold = JSON.parse(JSON.stringify(state.tableThresholdArr))
changeThreshold()
closeTableThreshold()
}
const getFieldName = field => (field.chartShowName ? field.chartShowName : field.name)
const getDynamicStyleLabel = (item, fieldObj) => {
const handleSummary = field => {
if (!field?.field) {
return ''
}
if (field.summary === 'value') {
return getFieldName(field.field) + '(' + t('chart.field') + ')'
} else {
let suffix = field.summary === 'avg' ? t('chart.drag_block_label_value') : ''
return getFieldName(field.field) + '(' + t('chart.' + field.summary) + suffix + ')'
}
}
if (item.type === 'dynamic') {
return handleSummary(fieldObj)
}
}
init()
</script>
@ -519,7 +558,6 @@ init()
style="flex-direction: column"
>
<div class="field-style" :class="{ 'field-style-dark': themes === 'dark' }">
<span>
<el-icon>
<Icon :className="`field-icon-${fieldType[fieldItem.field.deType]}`"
><component
@ -529,7 +567,6 @@ init()
></component
></Icon>
</el-icon>
</span>
<span :title="fieldItem.field.name" class="field-text">{{
fieldItem.field.name
}}</span>
@ -577,7 +614,17 @@ init()
</span>
<span v-else-if="item.term === 'default'" title="默认"> 默认 </span>
</div>
<div style="flex: 1; margin: 0 8px">
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.fix') }}
</span>
</div>
<div v-else style="flex: 1; margin: 0 8px">
<span style="margin: 0 8px">
{{ t('chart.dynamic') }}
</span>
</div>
<div v-if="item.type !== 'dynamic'" style="flex: 1; margin: 0 8px">
<span
v-if="
!item.term.includes('null') &&
@ -594,11 +641,44 @@ init()
!item.term.includes('empty') &&
item.term === 'between'
"
:title="item.min + ' ≤= ' + t('chart.drag_block_label_value') + ' ≤ ' + item.max"
>
{{ item.min }}&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;{{ item.max }}
</span>
<span v-else>&nbsp;</span>
</div>
<div v-else style="flex: 1; margin: 0 8px">
<span
v-if="
!item.term.includes('null') &&
!item.term.includes('default') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:title="getDynamicStyleLabel(item, item.dynamicField) + ''"
>
{{ getDynamicStyleLabel(item, item.dynamicField) }}</span
>
<span
v-else-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term === 'between'
"
:title="
getDynamicStyleLabel(item, item.dynamicMinField) +
'≤' +
t('chart.drag_block_label_value') +
'≤' +
getDynamicStyleLabel(item, item.dynamicMaxField)
"
>
{{ getDynamicStyleLabel(item, item.dynamicMinField) }}{{
t('chart.drag_block_label_value')
}}{{ getDynamicStyleLabel(item, item.dynamicMaxField) }}
</span>
<span v-else>&nbsp;</span>
</div>
<template v-if="chart.type === 'picture-group'">
<div title="显示图片" class="pic-group-main">
<img
@ -688,7 +768,7 @@ init()
v-model="state.editTableThresholdDialog"
:title="t('chart.threshold')"
:visible="state.editTableThresholdDialog"
width="800px"
width="1050px"
class="dialog-css"
append-to-body
>
@ -826,12 +906,21 @@ span {
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
:nth-child(1) {
width: 48px;
}
:nth-child(2) {
width: 40px !important;
}
:nth-child(3) {
width: 30px !important;
}
&:deep(span) {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
cursor: default;
display: block;
}
}
@ -868,9 +957,7 @@ span {
display: flex;
align-items: center;
justify-content: flex-start;
background: #f5f6f7;
&.field-style-dark {
background: #1a1a1a;
}
@ -898,7 +985,7 @@ span {
border-radius: 2px;
}
.pic-group-img {
width: 100%;
height: 100%;
width: 100% !important;
height: 100% !important;
}
</style>

View File

@ -7,6 +7,8 @@ import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL } from '../../../util/chart'
import { fieldType } from '@/utils/attr'
import { iconFieldMap } from '@/components/icon-group/field-list'
import PictureItem from '@/custom-component/picture-group/PictureItem.vue'
import PictureOptionPrefix from '@/custom-component/picture-group/PictureOptionPrefix.vue'
const { t } = useI18n()
@ -429,13 +431,25 @@ init()
>
<div class="color-title">展示图片</div>
<el-form-item class="form-item">
<el-select v-model="item.url" @change="changeThreshold">
<el-select
v-model="item.url"
@change="changeThreshold"
popper-class="picture-group-select"
>
<template v-if="item.url" #prefix>
<picture-option-prefix :url="item.url"></picture-option-prefix>
</template>
<el-option
v-for="urlInfo in element.propValue.urlList"
:key="urlInfo.url"
:label="urlInfo.name"
:value="urlInfo.url"
/>
>
<picture-item
:active="item.url === urlInfo.url"
:url-info="urlInfo"
></picture-item>
</el-option>
</el-select>
</el-form-item>
</div>
@ -597,3 +611,30 @@ span {
padding: 0 11px;
}
</style>
<style lang="less">
.picture-group-select {
min-width: 50px !important;
width: 304px;
.ed-scrollbar__view {
display: grid !important;
grid-template-columns: repeat(3, 1fr) !important;
}
.ed-select-dropdown__item {
height: 100px !important;
text-align: center;
padding: 0px 5px;
}
.ed-select-dropdown__item.selected::after {
display: none;
}
.ed-select-dropdown__item.hover {
background-color: rgba(0, 0, 0, 0) !important;
}
.ed-select-dropdown__item.selected {
background-color: rgba(0, 0, 0, 0) !important;
}
}
</style>

View File

@ -7,6 +7,7 @@ import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL } from '../../../util/chart'
import { fieldType } from '@/utils/attr'
import { iconFieldMap } from '@/components/icon-group/field-list'
import { cloneDeep } from 'lodash-es'
const { t } = useI18n()
@ -30,7 +31,11 @@ const thresholdCondition = {
color: '#ff0000ff',
backgroundColor: '#ffffff00',
min: '0',
max: '1'
max: '1',
type: 'fixed',
dynamicField: { summary: 'value' },
dynamicMinField: { summary: 'value' },
dynamicMaxField: { summary: 'value' }
}
const textOptions = [
{
@ -180,12 +185,13 @@ const init = () => {
state.thresholdArr = JSON.parse(JSON.stringify(props.threshold)) as TableThreshold[]
initFields()
}
const initOptions = item => {
if (item.field) {
if ([0, 5, 7].includes(item.field.deType)) {
const initOptions = (item, fieldObj) => {
if (fieldObj) {
if ([0, 5, 7].includes(fieldObj.deType)) {
item.options = JSON.parse(JSON.stringify(textOptions))
} else if (item.field.deType === 1) {
} else if (fieldObj.deType === 1) {
item.options = JSON.parse(JSON.stringify(dateOptions))
item.type = 'fixed'
} else {
item.options = JSON.parse(JSON.stringify(valueOptions))
}
@ -225,7 +231,13 @@ const changeThreshold = () => {
}
const addConditions = item => {
item.conditions.push(JSON.parse(JSON.stringify(thresholdCondition)))
const newCondition = JSON.parse(JSON.stringify(thresholdCondition))
//
const tableCell = props.chart?.customAttr?.tableCell
if (tableCell) {
newCondition.backgroundColor = cloneDeep(tableCell.tableItemBgColor)
}
item.conditions.push(newCondition)
changeThreshold()
}
const removeCondition = (item, index) => {
@ -239,13 +251,106 @@ const addField = item => {
state.fields.forEach(ele => {
if (item.fieldId === ele.id) {
item.field = JSON.parse(JSON.stringify(ele))
initOptions(item)
initOptions(item, item.field)
}
if (item.dynamicField?.fieldId === ele.id) {
item.dynamicField.field = JSON.parse(JSON.stringify(ele))
initOptions(item, item.dynamicField.field)
}
if (item.dynamicMinField?.fieldId === ele.id) {
item.dynamicMinField.field = JSON.parse(JSON.stringify(ele))
initOptions(item, item.dynamicMinField.field)
}
if (item.dynamicMaxField?.fieldId === ele.id) {
item.dynamicMaxField.field = JSON.parse(JSON.stringify(ele))
initOptions(item, item.dynamicMaxField.field)
}
})
}
changeThreshold()
}
const fieldOptions = [
{ label: t('chart.field_fixed'), value: 'fixed' },
{ label: t('chart.field_dynamic'), value: 'dynamic' }
]
const dynamicSummaryOptions = [
{
id: 'value',
name: t('chart.field') + t('chart.drag_block_label_value')
},
{
id: 'avg',
name: t('chart.avg') + t('chart.drag_block_label_value')
},
{
id: 'max',
name: t('chart.max')
},
{
id: 'min',
name: t('chart.min')
}
]
const getConditionsFields = (fieldItem, conditionItem, conditionItemField) => {
const fieldItemDeType = state.fields.filter(ele => ele.id === fieldItem.fieldId)?.[0]?.deType
if (fieldItemDeType === undefined || fieldItemDeType === null) {
conditionItem.fieldId = null
conditionItemField.fieldId = null
}
const result = state.fields.filter(item => item.deType === fieldItemDeType) ?? []
if (!result.find(ele => ele.id === conditionItemField.fieldId)) {
conditionItemField.fieldId = result[0]?.id
addField(conditionItem)
}
return result
}
const getDynamicSummaryOptions = itemId => {
const deType = state.fields.filter(ele => ele.id === itemId)?.[0]?.deType
if (deType === 1) {
//
return dynamicSummaryOptions.filter(ele => {
return ele.id !== 'avg'
})
} else if (deType === 0 || deType === 5) {
//
return dynamicSummaryOptions.filter(ele => {
return ele.id === 'value'
})
} else {
return dynamicSummaryOptions
}
}
const isNotEmptyAndNull = item => {
return !item.term.includes('null') && !item.term.includes('empty')
}
const isBetween = item => {
return item.term === 'between'
}
const isDynamic = item => {
return item.type === 'dynamic'
}
const changeConditionItemType = item => {
if (item.type === 'dynamic') {
item.dynamicField.summary = 'value'
item.dynamicMinField.summary = 'value'
item.dynamicMaxField.summary = 'value'
}
}
const getFieldOptions = fieldItem => {
const deType = state.fields.filter(ele => ele.id === fieldItem.fieldId)?.[0]?.deType
if (deType === 1) {
return fieldOptions.filter(ele => ele.value === 'fixed')
} else {
return fieldOptions
}
}
init()
</script>
@ -310,9 +415,9 @@ init()
v-for="(item, index) in fieldItem.conditions"
:key="index"
class="line-item"
:gutter="10"
:gutter="12"
>
<el-col :span="4">
<el-col :span="3">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group
@ -330,13 +435,27 @@ init()
</el-select>
</el-form-item>
</el-col>
<el-col :span="2" v-if="isNotEmptyAndNull(item)" style="padding-left: 0 !important">
<el-form-item class="form-item">
<el-select
v-model="item.type"
class="select-item"
@change="changeConditionItemType(item)"
style="width: 100%"
>
<el-option
v-for="opt in getFieldOptions(fieldItem)"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</el-col>
<!--不是between 不是动态值-->
<el-col
v-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:span="10"
v-if="isNotEmptyAndNull(item) && !isBetween(item) && !isDynamic(item)"
:span="12"
style="text-align: center"
>
<el-form-item class="form-item">
@ -359,8 +478,72 @@ init()
/>
</el-form-item>
</el-col>
<el-col v-if="item.term === 'between'" :span="4" style="text-align: center">
<!--不是between 是动态值-->
<!--动态值 字段-->
<el-col v-if="isNotEmptyAndNull(item) && !isBetween(item) && isDynamic(item)" :span="6">
<el-form-item class="form-item">
<el-select
v-model="item.dynamicField.fieldId"
@change="addField(item)"
style="width: 100%"
>
<el-option
class="series-select-option"
v-for="itemFieldOption in getConditionsFields(
fieldItem,
item,
item.dynamicField
)"
:key="itemFieldOption.id"
:label="itemFieldOption.name"
:value="itemFieldOption.id"
:disabled="chart.type === 'table-info' && itemFieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(itemFieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[itemFieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ itemFieldOption.name }}
</el-option>
</el-select>
</el-form-item>
</el-col>
<!--动态值聚合方式-->
<el-col
v-if="isNotEmptyAndNull(item) && !isBetween(item) && isDynamic(item)"
:span="6"
style="text-align: center"
>
<el-form-item class="form-item">
<el-select
:placeholder="t('chart.aggregation')"
v-model="item.dynamicField.summary"
@change="changeThreshold"
style="width: 100%"
>
<el-option
v-for="opt in getDynamicSummaryOptions(item.dynamicField.fieldId)"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</el-col>
<!--是between 不是动态值-->
<!--between 开始值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.min"
@ -372,14 +555,21 @@ init()
/>
</el-form-item>
</el-col>
<el-col v-if="item.term === 'between'" :span="2" style="text-align: center">
<span style="margin: 0 4px">
&nbsp;&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;&nbsp;
<el-col
v-if="isBetween(item) && !isDynamic(item)"
:span="2"
style="margin-top: 4px; text-align: center"
>
<span style="margin: 0 -5px">
&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;
</span>
</el-col>
<el-col v-if="item.term === 'between'" :span="4" style="text-align: center">
<!--between 结束值-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && !isDynamic(item)"
:span="5"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.max"
@ -392,11 +582,127 @@ init()
</el-form-item>
</el-col>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
<!--是between 动态值-->
<!--开始值 动态值字段-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="minField"
:span="3"
>
<div class="color-title">{{ t('chart.textColor') }}</div>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMinField.fieldId" @change="addField(item)">
<el-option
class="series-select-option"
v-for="itemFieldOption in getConditionsFields(
fieldItem,
item,
item.dynamicMinField
)"
:key="itemFieldOption.id"
:label="itemFieldOption.name"
:value="itemFieldOption.id"
:disabled="chart.type === 'table-info' && itemFieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(itemFieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[itemFieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ itemFieldOption.name }}
</el-option>
</el-select>
</el-form-item>
</el-col>
<!--开始值 动态值聚合方式-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="minValue"
:span="2"
style="padding-left: 0 !important"
>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMinField.summary" @change="changeThreshold">
<el-option
v-for="opt in getDynamicSummaryOptions(item.dynamicMinField.fieldId)"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
v-if="isBetween(item) && isDynamic(item)"
class="term"
:span="2"
style="margin-top: 4px; text-align: center"
>
<span style="margin: 0 -5px">
&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;
</span>
</el-col>
<!--结束值 动态值字段-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="maxField"
:span="3"
>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMaxField.fieldId" @change="addField(item)">
<el-option
class="series-select-option"
v-for="itemFieldOption in getConditionsFields(
fieldItem,
item,
item.dynamicMaxField
)"
:key="itemFieldOption.id"
:label="itemFieldOption.name"
:value="itemFieldOption.id"
:disabled="chart.type === 'table-info' && itemFieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
><component
:class="`field-icon-${
fieldType[[2, 3].includes(itemFieldOption.deType) ? 2 : 0]
}`"
class="svg-icon"
:is="iconFieldMap[fieldType[itemFieldOption.deType]]"
></component
></Icon>
</el-icon>
{{ itemFieldOption.name }}
</el-option>
</el-select>
</el-form-item>
</el-col>
<!--结束值 动态值聚合方式-->
<el-col
v-if="isNotEmptyAndNull(item) && isBetween(item) && isDynamic(item)"
class="maxValue"
:span="2"
style="padding-left: 0 !important"
>
<el-form-item class="form-item">
<el-select v-model="item.dynamicMaxField.summary" @change="changeThreshold">
<el-option
v-for="opt in getDynamicSummaryOptions(item.dynamicMaxField.fieldId)"
:key="opt.id"
:label="opt.name"
:value="opt.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item class="form-item" :label="t('chart.textColor')">
<el-color-picker
is-custom
size="large"
@ -407,12 +713,9 @@ init()
@change="changeThreshold"
/>
</el-form-item>
</div>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
>
<div class="color-title">{{ t('chart.backgroundColor') }}</div>
<el-form-item class="form-item">
</el-col>
<el-col :span="3">
<el-form-item class="form-item" :label="t('chart.backgroundColor')">
<el-color-picker
is-custom
size="large"
@ -423,10 +726,9 @@ init()
@change="changeThreshold"
/>
</el-form-item>
</div>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
>
</el-col>
<el-col :span="1">
<div style="display: flex; align-items: center; justify-content: center">
<el-button
class="circle-button m-icon-btn"
text
@ -439,6 +741,7 @@ init()
</el-icon>
</el-button>
</div>
</el-col>
</el-row>
</el-row>

View File

@ -28,7 +28,7 @@ import FlowMapLineSelector from '@/views/chart/components/editor/editor-style/co
import FlowMapPointSelector from '@/views/chart/components/editor/editor-style/components/FlowMapPointSelector.vue'
import CommonEvent from '@/custom-component/common/CommonEvent.vue'
import CommonBorderSetting from '@/custom-component/common/CommonBorderSetting.vue'
import PictureGroupAttr from '@/custom-component/picture-group/Attr.vue'
import PictureGroupUploadAttr from '@/custom-component/picture-group/PictureGroupUploadAttr.vue'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, batchOptStatus, curComponent } = storeToRefs(dvMainStore)
@ -612,11 +612,11 @@ watch(
@onChangeYAxisExtForm="onChangeYAxisExtForm"
/>
</collapse-switch-item>
<PictureGroupAttr
<PictureGroupUploadAttr
v-if="pictureGroupShow"
:themes="themes"
:element="curComponent"
></PictureGroupAttr>
></PictureGroupUploadAttr>
</el-collapse>
</el-row>
</div>

View File

@ -1,16 +1,19 @@
<script lang="tsx" setup>
import { ElMessage } from 'element-plus-secondary'
import icon_bold_outlined from '@/assets/svg/icon_bold_outlined.svg'
import { beforeUploadCheck, uploadFileResult } from '@/api/staticResource'
import ImgViewDialog from '@/custom-component/ImgViewDialog.vue'
import icon_italic_outlined from '@/assets/svg/icon_italic_outlined.svg'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
import { useI18n } from '@/hooks/web/useI18n'
import { PropType, toRefs, computed } from 'vue'
import { PropType, toRefs, computed, reactive, watch, ref, onMounted } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
import { COLOR_PANEL } from '@/views/chart/components/editor/util/chart'
import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue'
import { cloneDeep } from 'lodash-es'
const { t } = useI18n()
const state = {
styleActiveNames: ['basicStyle']
}
const styleActiveNames = ref(['basicStyle'])
const props = defineProps({
element: {
@ -41,18 +44,112 @@ for (let i = 10; i <= 60; i = i + 2) {
value: i + ''
})
}
const snapshotStore = snapshotStoreWithOut()
const files = ref(null)
const state = reactive({
commonBackground: {},
fileList: [],
dialogImageUrl: '',
dialogVisible: false
})
watch(
() => props.commonBackgroundPop,
() => {
init()
}
)
watch(
() => props.element.id,
() => {
initParams()
}
)
const currentPlaceholder = ref()
const currentSearch = ref({
placeholder: ''
})
const handleCurrentPlaceholder = val => {
const obj = props.element.propValue.find(ele => {
return ele.id === val
}) || {
placeholder: ''
}
if (obj.placeholder === undefined) {
obj.placeholder = ''
}
currentSearch.value = obj
}
const init = () => {
state.commonBackground = cloneDeep(props.commonBackgroundPop)
if (state.commonBackground['outerImage']) {
state.fileList.push({ url: imgUrlTrans(state.commonBackground['outerImage']) })
} else {
state.fileList = []
}
}
const handleRemove = () => {
state.commonBackground['outerImage'] = null
state.fileList = []
onBackgroundChange()
}
const handlePictureCardPreview = file => {
state.dialogImageUrl = file.url
state.dialogVisible = true
}
const upload = file => {
return uploadFileResult(file.file, fileUrl => {
state.commonBackground['outerImage'] = fileUrl
state.fileList = [{ url: imgUrlTrans(state.commonBackground['outerImage']) }]
onBackgroundChange()
})
}
const goFile = () => {
files.value.click()
}
const onBackgroundChange = () => {
snapshotStore.recordSnapshotCache()
commonBackgroundPop.value.outerImage = state.commonBackground['outerImage']
}
onMounted(() => {
init()
})
const reUpload = e => {
const file = e.target.files[0]
if (file.size > 15000000) {
ElMessage.success('图片大小不符合')
return
}
uploadFileResult(file, fileUrl => {
state.commonBackground['outerImage'] = fileUrl
state.fileList = [{ url: imgUrlTrans(state.commonBackground['outerImage']) }]
onBackgroundChange()
})
}
const checkBold = type => {
if (!chart.value.customStyle.component.labelShow) return
chart.value.customStyle.component[type] = chart.value.customStyle.component[type] ? '' : 'bold'
}
const handleCurrentPlaceholderChange = () => {
snapshotStore.recordSnapshotCache()
}
const checkItalic = type => {
if (!chart.value.customStyle.component.labelShow) return
chart.value.customStyle.component[type] = chart.value.customStyle.component[type] ? '' : 'italic'
}
const { chart, commonBackgroundPop } = toRefs(props)
if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
const initParams = () => {
if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
chart.value.customStyle.component = {
...chart.value.customStyle.component,
labelShow: true,
@ -68,13 +165,41 @@ if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
labelColorBtn: '#ffffff',
btnColor: '#3370ff'
}
}
if (!chart.value.customStyle.component.hasOwnProperty('placeholderShow')) {
chart.value.customStyle.component = {
...chart.value.customStyle.component,
placeholderShow: true,
placeholderSize: 14
}
}
if (props.element.propValue.length) {
currentPlaceholder.value = props.element.propValue[0].id
handleCurrentPlaceholder(props.element.propValue[0].id)
}
}
initParams()
</script>
<template>
<div class="attr-style">
<input
id="input"
ref="files"
type="file"
accept=".jpeg,.jpg,.png,.gif,.svg"
hidden
@click="
e => {
e.target.value = ''
}
"
@change="reUpload"
/>
<el-row class="de-collapse-style">
<el-collapse v-model="state.styleActiveNames" class="style-collapse">
<el-collapse v-model="styleActiveNames" class="style-collapse">
<el-collapse-item :effect="themes" name="basicStyle" :title="t('chart.basic_style')">
<el-form label-position="top">
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
@ -122,6 +247,82 @@ if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
</el-checkbox>
</el-form-item>
<el-form-item
style="padding-left: 20px"
class="form-item margin-bottom-8"
:class="'form-item-' + themes"
>
<el-radio-group
:disabled="!commonBackgroundPop.backgroundColorSelect"
:effect="themes"
v-model="commonBackgroundPop.backgroundType"
>
<el-radio
key="innerImage"
v-if="commonBackgroundPop.backgroundType === 'innerImage'"
label="innerImage"
:effect="themes"
>
背景颜色
</el-radio>
<el-radio key="color" v-else label="color" :effect="themes"> 背景颜色 </el-radio>
<el-radio label="outerImage" :effect="themes"> 背景图片 </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="commonBackgroundPop.backgroundType === 'outerImage'"
style="padding-left: 20px"
class="form-item margin-bottom-8"
:class="'form-item-' + themes"
>
<div
class="indented-item"
:class="{
disabled: !commonBackgroundPop.backgroundColorSelect
}"
>
<div class="avatar-uploader-container" :class="`img-area_${themes}`">
<el-upload
action=""
:effect="themes"
accept=".jpeg,.jpg,.png,.gif,.svg"
class="avatar-uploader"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUploadCheck"
:http-request="upload"
:file-list="state.fileList"
:disabled="!commonBackgroundPop.backgroundColorSelect"
>
<el-icon><Plus /></el-icon>
</el-upload>
<el-row>
<span
style="margin-top: 2px"
v-if="!state.commonBackground['outerImage']"
class="image-hint"
:class="`image-hint_${themes}`"
>
支持JPGPNGGIFSVG
</span>
<el-button
size="small"
style="margin: 8px 0 0 -4px"
v-if="state.commonBackground['outerImage']"
text
:disabled="!commonBackgroundPop.backgroundColorSelect"
@click="goFile"
>
重新上传
</el-button>
</el-row>
</div>
<img-view-dialog v-model="state.dialogVisible" :image-url="state.dialogImageUrl" />
</div>
</el-form-item>
<el-form-item
v-else
class="form-item"
style="padding-left: 20px"
:class="'form-item-' + themes"
@ -166,24 +367,63 @@ if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
<el-checkbox
:effect="themes"
size="small"
v-model="chart.customStyle.component.textColorShow"
v-model="chart.customStyle.component.placeholderShow"
>
提示文字颜色
提示
</el-checkbox>
</el-form-item>
<el-form-item
label="文本"
class="form-item"
style="padding-left: 20px"
:class="'form-item-' + themes"
>
<div style="display: flex; align-items: center; width: 100%">
<el-color-picker
:effect="themes"
:trigger-width="108"
:trigger-width="56"
is-custom
v-model="chart.customStyle.component.text"
:disabled="!chart.customStyle.component.textColorShow"
:disabled="!chart.customStyle.component.placeholderShow"
:predefine="predefineColors"
/>
<el-input-number
v-model="chart.customStyle.component.placeholderSize"
:min="10"
:max="20"
style="margin-left: 8px"
step-strictly
:effect="themes"
controls-position="right"
/>
</div>
<div style="display: flex; align-items: center; width: 100%; margin-top: 8px">
<el-select
v-model="currentPlaceholder"
@change="handleCurrentPlaceholder"
style="width: 100%"
>
<el-option
v-for="item in element.propValue"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
</el-form-item>
<el-form-item
label="提示词"
class="form-item"
style="padding-left: 20px"
:class="'form-item-' + themes"
>
<el-input
:effect="themes"
@change="handleCurrentPlaceholderChange"
:disabled="!chart.customStyle.component.placeholderShow || !currentPlaceholder"
v-model.lazy="currentSearch.placeholder"
/>
</el-form-item>
<el-form-item class="form-item margin-bottom-8" :class="'form-item-' + themes">
<el-checkbox
@ -461,6 +701,85 @@ if (!chart.value.customStyle.component.hasOwnProperty('labelShow')) {
overflow-y: auto;
height: 100%;
width: 100%;
.indented-item {
width: 100%;
display: flex;
.fill {
flex: 1;
}
&.disabled {
cursor: not-allowed;
color: #8f959e;
:deep(.avatar-uploader) {
width: 90px;
pointer-events: none;
}
:deep(.ed-upload--picture-card) {
cursor: not-allowed;
}
.img-area_dark {
:deep(.ed-upload--picture-card) {
.ed-icon {
color: #5f5f5f;
}
}
}
.img-area_light {
:deep(.ed-upload--picture-card) {
.ed-icon {
color: #bbbfc4;
}
}
}
&:hover {
.ed-icon {
color: #8f959e;
}
}
}
}
.avatar-uploader {
width: 90px;
height: 80px;
overflow: hidden;
}
.avatar-uploader {
width: 90px;
:deep(.ed-upload) {
width: 80px;
height: 80px;
line-height: 90px;
}
:deep(.ed-upload-list li) {
width: 80px !important;
height: 80px !important;
}
:deep(.ed-upload--picture-card) {
background: #eff0f1;
border: 1px dashed #dee0e3;
border-radius: 4px;
.ed-icon {
color: #1f2329;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
}
}
.form-item {

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { onMounted, PropType, reactive, watch } from 'vue'
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import {
COLOR_PANEL,
DEFAULT_BASIC_STYLE,
@ -236,6 +236,10 @@ const mapSymbolOptions = [
{ name: t('chart.line_symbol_diamond'), value: 'rhombus' }
]
const customSymbolicMapSizeRange = computed(() => {
let { extBubble } = JSON.parse(JSON.stringify(props.chart))
return ['symbolic-map'].includes(props.chart.type) && extBubble?.length > 0
})
onMounted(() => {
init()
})
@ -476,11 +480,53 @@ onMounted(() => {
:max="40"
v-model="state.basicStyleForm.mapSymbolSize"
@change="changeBasicStyle('mapSymbolSize')"
:disabled="customSymbolicMapSizeRange"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.size') }}区间
</label>
<el-row style="flex: 1">
<el-col :span="12">
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-input
type="number"
:effect="themes"
v-model="state.basicStyleForm.mapSymbolSizeMin"
:min="0"
:max="100"
class="basic-input-number"
:controls="false"
@change="changeBasicStyle('mapSymbolSizeMin')"
:disabled="!customSymbolicMapSizeRange"
>
<template #suffix> PX </template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-input
type="number"
:effect="themes"
v-model="state.basicStyleForm.mapSymbolSizeMax"
:min="0"
:max="100"
class="basic-input-number"
:controls="false"
@change="changeBasicStyle('mapSymbolSizeMax')"
:disabled="!customSymbolicMapSizeRange"
>
<template #suffix> PX </template>
</el-input>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.not_alpha') }}

View File

@ -12,6 +12,7 @@ import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import Icon from '../../../../../../components/icon-custom/src/Icon.vue'
import { iconFieldMap } from '@/components/icon-group/field-list'
import { parseJson } from '../../../js/util'
const { t } = useI18n()
@ -242,6 +243,7 @@ const init = () => {
if (chart.customAttr) {
const customAttr = chart.customAttr
if (customAttr.label) {
configCompat(customAttr.label)
state.labelForm = defaultsDeep(customAttr.label, cloneDeep(COMPUTED_DEFAULT_LABEL.value))
if (chartType.value === 'liquid' && state.labelForm.fontSize < fontSizeList.value[0].value) {
state.labelForm.fontSize = fontSizeList.value[0].value
@ -249,6 +251,13 @@ const init = () => {
initSeriesLabel()
formatterSelector.value?.blur()
}
//
initPosition()
}
}
const configCompat = (labelAttr: DeepPartial<ChartLabelAttr>) => {
if (labelAttr.showStackQuota === undefined) {
labelAttr.showStackQuota = labelAttr.show
}
}
const checkLabelContent = contentProp => {
@ -320,11 +329,13 @@ const showPositionV = computed(() => {
}
return false
})
watch(
() => props.chart.customAttr.basicStyle.layout,
() => {
const layout = props.chart.customAttr.basicStyle.layout
function initBidirectionalBarPosition() {
if (chartType.value === 'bidirectional-bar') {
const layout = props.chart.customAttr.basicStyle.layout
const oldPosition = state?.labelForm?.position
if (state?.labelForm?.position === 'inner' || state?.labelForm?.position === 'outer') {
state.labelForm.position = 'middle'
}
if (layout === 'horizontal') {
if (state?.labelForm?.position === 'top') {
state.labelForm.position = 'right'
@ -341,12 +352,56 @@ watch(
state.labelForm.position = 'top'
}
}
if (oldPosition !== state.labelForm.position) {
changeLabelAttr('position')
}
}
}
function initPosition() {
if (chartType.value === 'bidirectional-bar') {
initBidirectionalBarPosition()
} else {
const oldPosition = state?.labelForm?.position
if (showProperty('rPosition')) {
if (state?.labelForm?.position !== 'inner') {
state.labelForm.position = 'outer'
}
} else if (showProperty('hPosition')) {
if (state?.labelForm?.position === 'top') {
state.labelForm.position = 'right'
} else if (state?.labelForm?.position === 'bottom') {
state.labelForm.position = 'left'
} else if (state?.labelForm?.position === 'inner' || state?.labelForm?.position === 'outer') {
state.labelForm.position = 'middle'
}
} else if (showProperty('vPosition')) {
if (state?.labelForm?.position === 'left') {
state.labelForm.position = 'bottom'
} else if (state?.labelForm?.position === 'right') {
state.labelForm.position = 'top'
} else if (state?.labelForm?.position === 'inner' || state?.labelForm?.position === 'outer') {
state.labelForm.position = 'middle'
}
}
if (oldPosition !== state.labelForm.position) {
changeLabelAttr('position')
}
}
}
watch(
() => props.chart.customAttr.basicStyle.layout,
() => {
initBidirectionalBarPosition()
},
{ deep: true }
)
watch(chartType, (value, oldValue) => {
initPosition()
})
const allFields = computed(() => {
return defaultTo(props.allFields, []).map(item => ({
key: item.dataeaseName,
@ -403,6 +458,36 @@ const conversionPrecision = [
label-position="top"
>
<el-row v-show="showEmpty" style="margin-bottom: 12px"> 无其他可设置的属性</el-row>
<div>
<el-form-item
v-if="showProperty('showStackQuota')"
class="form-item"
:class="'form-item-' + themes"
style="display: inline-block; margin-right: 8px"
>
<el-checkbox
size="small"
:effect="themes"
v-model="state.labelForm.showStackQuota"
@change="changeLabelAttr('showStackQuota')"
:label="t('chart.quota')"
/>
</el-form-item>
<el-form-item
v-if="showProperty('showTotal')"
class="form-item"
:class="'form-item-' + themes"
style="display: inline-block"
>
<el-checkbox
size="small"
:effect="themes"
v-model="state.labelForm.showTotal"
@change="changeLabelAttr('showTotal')"
:label="t('chart.total_show')"
/>
</el-form-item>
</div>
<div v-if="!isGroupBar">
<el-space>
<el-form-item
@ -665,15 +750,6 @@ const conversionPrecision = [
/>
</el-form-item>
</template>
<el-form-item v-if="showProperty('showTotal')" class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.labelForm.showTotal"
@change="changeLabelAttr('showTotal')"
:label="t('chart.total_show')"
/>
</el-form-item>
<template v-if="false && showProperty('totalFormatter')">
<el-divider class="m-divider" :class="{ 'divider-dark': themes === 'dark' }" />
<div v-show="state.labelForm.showTotal">

View File

@ -250,7 +250,7 @@ const init = () => {
const showProperty = prop => {
const instance = chartViewManager.getChartView(props.chart.render, props.chart.type)
if (instance) {
return instance.propertyInner['tooltip-selector'].includes(prop)
return instance.propertyInner['tooltip-selector']?.includes(prop)
}
return props.propertyInner?.includes(prop)
}

View File

@ -836,6 +836,7 @@ const calcData = (view, resetDrill = false, updateQuery = '') => {
const updateChartData = view => {
curComponent.value['state'] = 'ready'
useEmitt().emitter.emit('checkShowEmpty', { allFields: allFields.value, view: view })
calcData(view, true, 'updateQuery')
}
@ -1074,7 +1075,19 @@ const onAssistLineChange = val => {
const onThresholdChange = val => {
view.value.senior.threshold = val
let type = undefined
view.value.senior.threshold?.tableThreshold?.some(item => {
if (item.conditions.some(i => i.type === 'dynamic')) {
type = 'calcData'
return true
}
return false
})
if (type) {
calcData(view.value)
} else {
renderChart(view.value)
}
}
const onMapMappingChange = val => {
@ -1766,13 +1779,14 @@ const deleteChartFieldItem = id => {
</div>
<el-popover show-arrow :offset="8" placement="bottom" width="200" trigger="click">
<template #reference>
<el-icon
v-show="route.path !== '/dvCanvas'"
style="margin-left: 4px; cursor: pointer"
<el-icon style="margin-left: 4px; cursor: pointer"
><Icon><dvInfoSvg class="svg-icon" /></Icon
></el-icon>
</template>
<div style="margin-bottom: 4px; font-size: 14px; color: #646a73">图表ID</div>
<div style="font-size: 14px; color: #1f2329">
{{ view.id }}
</div>
</el-popover>
</div>
</el-row>
@ -3468,12 +3482,7 @@ const deleteChartFieldItem = id => {
class="name-edit-form no-margin-bottom"
prop="chartShowName"
>
<el-input
v-model="state.itemForm.chartShowName"
class="text"
:maxlength="20"
clearable
/>
<el-input v-model="state.itemForm.chartShowName" class="text" clearable />
</el-form-item>
</el-form>
</div>

View File

@ -339,7 +339,8 @@ export const DEFAULT_LABEL: ChartLabelAttr = {
showTotal: false,
totalFontSize: 12,
totalColor: '#FFF',
totalFormatter: formatterItem
totalFormatter: formatterItem,
showStackQuota: false
}
export const DEFAULT_TOOLTIP: ChartTooltipAttr = {
show: true,
@ -1494,7 +1495,7 @@ export const CHART_TYPE_CONFIGS = [
},
{
category: 'other',
title: '富文本',
title: '其他',
display: 'hidden',
details: [
{
@ -1503,6 +1504,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'rich-text',
title: '富文本',
icon: 'rich-text'
},
{
render: 'custom',
category: 'quota',
value: 'picture-group',
title: '图片组',
icon: 'picture-group'
}
]
}
@ -1570,7 +1578,9 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = {
showSummary: false,
summaryLabel: '总计',
seriesColor: [],
layout: 'horizontal'
layout: 'horizontal',
mapSymbolSizeMin: 4,
mapSymbolSizeMax: 30
}
export const BASE_VIEW_CONFIG = {

View File

@ -106,6 +106,14 @@ const noChildrenFieldChart = chart => {
return ['area', 'bar'].includes(chart.type)
}
/**
* 支持最值图表的折线图面积图柱状图分组柱状图
* @param chart
*/
const supportExtremumChartType = chart => {
return ['line', 'area', 'bar', 'bar-group'].includes(chart.type)
}
const chartContainerId = chart => {
return chart.container + '_'
}
@ -131,6 +139,10 @@ function removeDivsWithPrefix(parentDivId, prefix) {
export const extremumEvt = (newChart, chart, _options, container) => {
chart.container = container
if (!supportExtremumChartType(chart)) {
clearExtremum(chart)
return
}
const { label: labelAttr } = parseJson(chart.customAttr)
const { yAxis } = parseJson(chart)
newChart.once('beforerender', ev => {
@ -145,8 +157,12 @@ export const extremumEvt = (newChart, chart, _options, container) => {
}
let showExtremum = false
if (noChildrenFieldChart(chart) || yAxis.length > 1) {
const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find(
d => d.name === minItem._origin.category || d.name === maxItem._origin.category
const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find(d =>
d.chartShowName
? d.chartShowName
: d.name === minItem._origin.category || d.chartShowName
? d.chartShowName
: d.name === maxItem._origin.category
)
showExtremum = seriesLabelFormatter?.showExtremum
} else {
@ -248,8 +264,10 @@ export const createExtremumPoint = (chart, ev) => {
let attr
let showExtremum = false
if (noChildrenFieldChart(chart) || yAxis.length > 1) {
const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find(
d => d.name === pointObj._origin.category
const seriesLabelFormatter = labelAttr.seriesLabelFormatter.find(d =>
d.chartShowName
? d.chartShowName === pointObj._origin.category
: d.name === pointObj._origin.category
)
showExtremum = seriesLabelFormatter?.showExtremum
attr = seriesLabelFormatter

View File

@ -18,7 +18,11 @@ import {
BAR_EDITOR_PROPERTY,
BAR_EDITOR_PROPERTY_INNER
} from '@/views/chart/components/js/panel/charts/bar/common'
import { getPadding, setGradientColor } from '@/views/chart/components/js/panel/common/common_antv'
import {
getLabel,
getPadding,
setGradientColor
} from '@/views/chart/components/js/panel/common/common_antv'
import { useI18n } from '@/hooks/web/useI18n'
import { DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart'
import { clearExtremum, extremumEvt } from '@/views/chart/components/js/extremumUitl'
@ -97,6 +101,7 @@ export class Bar extends G2PlotChartView<ColumnOptions, Column> {
async drawChart(drawOptions: G2PlotDrawOptions<Column>): Promise<Column> {
const { chart, container, action } = drawOptions
if (!chart?.data?.data?.length) {
chart.container = container
clearExtremum(chart)
return
}
@ -270,25 +275,55 @@ export class StackBar extends Bar {
'showTotal',
'totalColor',
'totalFontSize',
'totalFormatter'
'totalFormatter',
'showStackQuota'
],
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'tooltipFormatter', 'show']
}
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
const baseOptions = super.configLabel(chart, options)
if (!baseOptions.label) {
return baseOptions
let label = getLabel(chart)
if (!label) {
return options
}
options = { ...options, label }
const { label: labelAttr } = parseJson(chart.customAttr)
baseOptions.label.style.fill = labelAttr.color
const label = {
...baseOptions.label,
if (labelAttr.showStackQuota || labelAttr.showStackQuota === undefined) {
label.style.fill = labelAttr.color
label = {
...label,
formatter: function (param: Datum) {
return valueFormatter(param.value, labelAttr.labelFormatter)
}
}
} else {
label = false
}
if (labelAttr.showTotal) {
const formatterCfg = labelAttr.labelFormatter ?? formatterItem
each(groupBy(options.data, 'field'), (values, key) => {
const total = values.reduce((a, b) => a + b.value, 0)
const value = valueFormatter(total, formatterCfg)
if (!options.annotations) {
options = {
...options,
annotations: []
}
}
options.annotations.push({
type: 'text',
position: [key, total],
content: `${value}`,
style: {
textAlign: 'center',
fontSize: labelAttr.fontSize,
fill: labelAttr.color
},
offsetY: -(parseInt(labelAttr.fontSize as unknown as string) / 2)
})
})
}
return {
...baseOptions,
...options,
label
}
}
@ -347,38 +382,6 @@ export class StackBar extends Bar {
return options
}
protected configTotalLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
if (!options.label) {
return options
}
const { label } = parseJson(chart.customAttr)
if (label.showTotal) {
const formatterCfg = label.labelFormatter ?? formatterItem
each(groupBy(options.data, 'field'), (values, key) => {
const total = values.reduce((a, b) => a + b.value, 0)
const value = valueFormatter(total, formatterCfg)
if (!options.annotations) {
options = {
...options,
annotations: []
}
}
options.annotations.push({
type: 'text',
position: [key, total],
content: `${value}`,
style: {
textAlign: 'center',
fontSize: label.fontSize,
fill: label.color
},
offsetY: -(parseInt(label.fontSize as unknown as string) / 2)
})
})
}
return options
}
public setupSeriesColor(chart: ChartObj, data?: any[]): ChartBasicStyle['seriesColor'] {
return setUpStackSeriesColor(chart, data)
}
@ -396,8 +399,7 @@ export class StackBar extends Bar {
this.configYAxis,
this.configSlider,
this.configAnalyse,
this.configData,
this.configTotalLabel
this.configData
)(chart, options, {}, this)
}
@ -510,6 +512,25 @@ export class GroupStackBar extends StackBar {
}
}
protected configLabel(chart: Chart, options: ColumnOptions): ColumnOptions {
const baseOptions = super.configLabel(chart, options)
if (!baseOptions.label) {
return baseOptions
}
const { label: labelAttr } = parseJson(chart.customAttr)
baseOptions.label.style.fill = labelAttr.color
const label = {
...baseOptions.label,
formatter: function (param: Datum) {
return valueFormatter(param.value, labelAttr.labelFormatter)
}
}
return {
...baseOptions,
label
}
}
protected configTooltip(chart: Chart, options: ColumnOptions): ColumnOptions {
const tooltipAttr = parseJson(chart.customAttr).tooltip
if (!tooltipAttr.show) {

View File

@ -31,6 +31,10 @@ const DEFAULT_DATA = []
export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
type: 'd'
},
yAxis: {
name: `${t('chart.drag_block_value_axis')} / ${t('chart.quota')}`,
type: 'q'
@ -301,6 +305,14 @@ export class HorizontalBar extends G2PlotChartView<BarOptions, Bar> {
* 堆叠条形图
*/
export class HorizontalStackBar extends HorizontalBar {
axisConfig = {
...this['axisConfig'],
extStack: {
name: `${t('chart.stack_item')} / ${t('chart.dimension')}`,
type: 'd',
limit: 1
}
}
propertyInner = {
...this['propertyInner'],
'label-selector': ['color', 'fontSize', 'hPosition', 'labelFormatter'],
@ -360,7 +372,7 @@ export class HorizontalStackBar extends HorizontalBar {
if (mainSort || subSort) {
return options
}
const quotaSort = yAxis?.[0].sort !== 'none'
const quotaSort = yAxis?.[0]?.sort !== 'none'
if (!quotaSort || !extStack.length || !yAxis.length) {
return options
}

View File

@ -15,7 +15,6 @@ const { t } = useI18n()
export class ProgressBar extends G2PlotChartView<BarOptions, G2Progress> {
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.form_type')} / ${t('chart.dimension')}`,
type: 'd',

View File

@ -23,7 +23,6 @@ const DEFAULT_DATA = []
*/
export class RangeBar extends G2PlotChartView<BarOptions, Bar> {
axisConfig = {
...this['axisConfig'],
yAxis: {
name: `${t('chart.drag_block_value_start')} / ${t('chart.time_dimension_or_quota')}`,
limit: 1,

View File

@ -4,6 +4,8 @@ import { flow, hexColorToRGBA, parseJson } from '../../../util'
import { valueFormatter } from '../../../formatter'
import { getPadding, getTooltipSeriesTotalMap, setGradientColor } from '../../common/common_antv'
import { isEmpty } from 'lodash-es'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
/**
* 瀑布图
@ -62,6 +64,17 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
]
}
axis: AxisType[] = ['xAxis', 'yAxis', 'filter', 'drill', 'extLabel', 'extTooltip']
axisConfig = {
xAxis: {
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
type: 'd'
},
yAxis: {
name: `${t('chart.drag_block_value_axis')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
}
}
async drawChart(drawOptions: G2PlotDrawOptions<G2Waterfall>): Promise<G2Waterfall> {
const { chart, container, action } = drawOptions
if (!chart.data?.data) {

View File

@ -44,6 +44,10 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
axis: AxisType[] = [...LINE_AXIS_TYPE]
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
type: 'd'
},
yAxis: {
name: `${t('chart.drag_block_value_axis')} / ${t('chart.quota')}`,
type: 'q'
@ -98,6 +102,7 @@ export class Area extends G2PlotChartView<AreaOptions, G2Area> {
async drawChart(drawOptions: G2PlotDrawOptions<G2Area>): Promise<G2Area> {
const { chart, container, action } = drawOptions
if (!chart.data?.data?.length) {
chart.container = container
clearExtremum(chart)
return
}
@ -288,6 +293,14 @@ export class StackArea extends Area {
'label-selector': ['fontSize', 'color', 'labelFormatter'],
'tooltip-selector': ['fontSize', 'color', 'tooltipFormatter', 'show']
}
axisConfig = {
...this['axisConfig'],
extStack: {
name: `${t('chart.stack_item')} / ${t('chart.dimension')}`,
type: 'd',
limit: 1
}
}
protected configLabel(chart: Chart, options: AreaOptions): AreaOptions {
const customAttr = parseJson(chart.customAttr)
const labelAttr = customAttr.label

View File

@ -42,6 +42,15 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
axis: AxisType[] = [...LINE_AXIS_TYPE, 'xAxisExt']
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
type: 'd'
},
xAxisExt: {
name: `${t('chart.chart_group')} / ${t('chart.dimension')}`,
type: 'd',
limit: 1
},
yAxis: {
name: `${t('chart.drag_block_value_axis')} / ${t('chart.quota')}`,
type: 'q'
@ -50,6 +59,7 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
async drawChart(drawOptions: G2PlotDrawOptions<G2Line>): Promise<G2Line> {
const { chart, action, container } = drawOptions
if (!chart.data?.data?.length) {
chart.container = container
clearExtremum(chart)
return
}
@ -288,8 +298,11 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
}
protected configLegend(chart: Chart, options: LineOptions): LineOptions {
const optionTmp = super.configLegend(chart, options)
if (!optionTmp.legend) {
return optionTmp
}
const xAxisExt = chart.xAxisExt[0]
if (optionTmp.legend && xAxisExt?.customSort?.length > 0) {
if (xAxisExt?.customSort?.length > 0) {
// 图例自定义排序
const l = optionTmp.legend
const basicStyle = parseJson(chart.customAttr).basicStyle
@ -302,11 +315,8 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
marker: {
symbol: l.marker.symbol,
style: {
r: 6,
fill: 'rgba(0,0,0,0)',
lineWidth: 2,
lineJoin: 'round',
stroke: basicStyle.colors[index % basicStyle.colors.length]
r: 4,
fill: basicStyle.colors[index % basicStyle.colors.length]
}
}
})
@ -321,6 +331,12 @@ export class Line extends G2PlotChartView<LineOptions, G2Line> {
legend
}
}
optionTmp.legend.marker.style = style => {
return {
r: 4,
fill: style.stroke
}
}
return optionTmp
}
protected setupOptions(chart: Chart, options: LineOptions): LineOptions {

View File

@ -46,7 +46,6 @@ export class StockLine extends G2PlotChartView<MixOptions, Mix> {
}
axis: AxisType[] = ['xAxis', 'yAxis', 'filter', 'extLabel', 'extTooltip']
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `日期 / ${t('chart.dimension')}`,
limit: 1,

View File

@ -47,18 +47,21 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
flowMapStartName: {
name: `起点名称 / ${t('chart.dimension')}`,
type: 'd',
limit: 1
limit: 1,
allowEmpty: true
},
flowMapEndName: {
name: `终点名称 / ${t('chart.dimension')}`,
type: 'd',
limit: 1
limit: 1,
allowEmpty: true
},
yAxis: {
name: `线条粗细 / ${t('chart.quota')}`,
type: 'q',
limit: 1,
tooltip: '该指标生效时样式中线条配置的线条宽度属性将失效'
tooltip: '该指标生效时样式中线条配置的线条宽度属性将失效',
allowEmpty: true
}
}
constructor() {

View File

@ -52,13 +52,16 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
xAxisExt: {
name: `颜色 / ${t('chart.dimension')}`,
type: 'd',
limit: 1
limit: 1,
allowEmpty: true
},
extBubble: {
name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
type: 'q',
limit: 1,
tooltip: '该指标生效时样式基础样式中的大小属性将失效'
tooltip:
'该指标生效时样式基础样式中的大小属性将失效同时可在样式基础样式中的大小区间配置大小区间',
allowEmpty: true
}
}
constructor() {
@ -158,8 +161,16 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
const xAxis = deepCopy(chart.xAxis)
const xAxisExt = deepCopy(chart.xAxisExt)
const extBubble = deepCopy(chart.extBubble)
const { mapSymbolOpacity, mapSymbolSize, mapSymbol, mapSymbolStrokeWidth, colors, alpha } =
deepCopy(basicStyle)
const {
mapSymbolOpacity,
mapSymbolSize,
mapSymbol,
mapSymbolStrokeWidth,
colors,
alpha,
mapSymbolSizeMin,
mapSymbolSizeMax
} = deepCopy(basicStyle)
const colorsWithAlpha = colors.map(color => hexColorToRGBA(color, alpha))
let colorIndex = 0
// 存储已分配的颜色
@ -179,7 +190,7 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
return {
...item,
color,
size: item[sizeKey] ?? mapSymbolSize,
size: parseInt(item[sizeKey]) ?? mapSymbolSize,
name: identifier
}
})
@ -212,7 +223,7 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
})
}
if (sizeKey) {
pointLayer.size('size', [4, 30])
pointLayer.size('size', [mapSymbolSizeMin, mapSymbolSizeMax])
} else {
pointLayer.size(mapSymbolSize)
}

View File

@ -88,11 +88,11 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
'extTooltip'
]
axisConfig: AxisConfig = {
...this['axisConfig'],
extBubble: {
name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
limit: 1,
allowEmpty: true
},
xAxis: {
name: `${t('chart.form_type')} / ${t('chart.dimension')}`,
@ -413,7 +413,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
if (!(xAxis?.length && yAxis?.length && yAxisExt?.length)) {
return []
}
const tmp = data[0].data
const tmp = data?.[0]?.data
return setUpSingleDimensionSeriesColor(chart, tmp)
}
protected setupOptions(chart: Chart, options: ScatterOptions) {

View File

@ -24,7 +24,6 @@ const DEFAULT_DATA = []
*/
export class RangeBar extends G2PlotChartView<SankeyOptions, Sankey> {
axisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis_start')} / ${t('chart.dimension')}`,
limit: 1,

View File

@ -75,7 +75,10 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
}
axis: AxisType[] = ['xAxis', 'yAxis', 'extBubble', 'filter', 'drill', 'extLabel', 'extTooltip']
axisConfig: AxisConfig = {
...this['axisConfig'],
xAxis: {
name: `${t('chart.drag_block_type_axis')} / ${t('chart.dimension')}`,
type: 'd'
},
yAxis: {
...this['axisConfig'].yAxis,
limit: undefined
@ -83,7 +86,8 @@ export class Scatter extends G2PlotChartView<ScatterOptions, G2Scatter> {
extBubble: {
name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
limit: 1,
allowEmpty: true
}
}
async drawChart(drawOptions: G2PlotDrawOptions<G2Scatter>): Promise<G2Scatter> {

View File

@ -56,7 +56,7 @@ export const PIE_AXIS_CONFIG: AxisConfig = {
type: 'd'
},
yAxis: {
name: `${t('chart.drag_block_pie_label')} / ${t('chart.quota')}`,
name: `${t('chart.drag_block_pie_angle')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
}

View File

@ -19,12 +19,21 @@ import { valueFormatter } from '@/views/chart/components/js/formatter'
import { Datum } from '@antv/g2plot/esm/types/common'
import { add } from 'mathjs'
import isEmpty from 'lodash-es/isEmpty'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
export class Rose extends G2PlotChartView<RoseOptions, G2Rose> {
axis: AxisType[] = PIE_AXIS_TYPE
properties: EditorProperty[] = PIE_EDITOR_PROPERTY
propertyInner: EditorPropertyInner = PIE_EDITOR_PROPERTY_INNER
axisConfig = PIE_AXIS_CONFIG
axisConfig: AxisConfig = {
...PIE_AXIS_CONFIG,
yAxis: {
name: `${t('chart.drag_block_pie_radius')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
}
}
async drawChart(drawOptions: G2PlotDrawOptions<G2Rose>): Promise<G2Rose> {
const { chart, container, action } = drawOptions

View File

@ -1,4 +1,12 @@
import { S2Event, S2Options, TableColCell, TableDataCell, TableSheet, ViewMeta } from '@antv/s2'
import {
type LayoutResult,
S2Event,
S2Options,
TableColCell,
TableDataCell,
TableSheet,
ViewMeta
} from '@antv/s2'
import { formatterItem, valueFormatter } from '../../../formatter'
import { parseJson } from '../../../util'
import { S2ChartView, S2DrawOptions } from '../../types/impl/s2'
@ -65,7 +73,7 @@ export class TableInfo extends S2ChartView<TableSheet> {
const containerDom = document.getElementById(container)
// fields
let fields = chart.data.fields
let fields = chart.data?.fields ?? []
const columns = []
const meta = []
const axisMap = chart.xAxis.reduce((pre, cur) => {
@ -187,11 +195,66 @@ export class TableInfo extends S2ChartView<TableSheet> {
}
} else {
// header interaction
chart.container = container
this.configHeaderInteraction(chart, s2Options)
}
// 开始渲染
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
// 自适应铺满
if (customAttr.basicStyle.tableColumnMode === 'adapt') {
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
newChart.store.set('lastLayoutResult', newChart.facet.layoutResult)
})
newChart.on(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, (ev: LayoutResult) => {
const status = newChart.store.get('status')
if (status === 'default') {
return
}
const lastLayoutResult = newChart.store.get('lastLayoutResult') as LayoutResult
if (status === 'expanded' && lastLayoutResult) {
// 拖拽表头定义宽度和上一次布局对比保留除已拖拽列之外的宽度
const widthByFieldValue = newChart.options.style?.colCfg?.widthByFieldValue
const lastLayoutWidthMap: Record<string, number> = lastLayoutResult?.colLeafNodes.reduce(
(p, n) => {
p[n.value] = widthByFieldValue?.[n.value] ?? n.width
return p
},
{}
)
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = lastLayoutWidthMap[n.value]
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = totalWidth
} else {
// 第一次渲染初始化把图片字段固定为 120 进行计算
const urlFields = fields.filter(field => field.deType === 7).map(f => f.dataeaseName)
const totalWidthWithImg = ev.colLeafNodes.reduce((p, n) => {
return p + (urlFields.includes(n.field) ? 120 : n.width)
}, 0)
if (containerDom.offsetWidth <= totalWidthWithImg) {
// 图库计算的布局宽度已经大于等于容器宽度不需要再扩大不处理
newChart.store.set('status', 'default')
return
}
// 图片字段固定 120, 剩余宽度按比例均摊到其他字段进行扩大
const totalWidthWithoutImg = ev.colLeafNodes.reduce((p, n) => {
return p + (urlFields.includes(n.field) ? 0 : n.width)
}, 0)
const restWidth = containerDom.offsetWidth - urlFields.length * 120
const scale = restWidth / totalWidthWithoutImg
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = urlFields.includes(n.field) ? 120 : n.width * scale
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = Math.min(containerDom.offsetWidth, totalWidth)
newChart.store.set('status', 'expanded')
}
})
}
// click
newChart.on(S2Event.DATA_CELL_CLICK, ev => {
const cell = newChart.getCell(ev.target)

View File

@ -4,6 +4,7 @@ import { copyContent, SortTooltip } from '@/views/chart/components/js/panel/comm
import { S2ChartView, S2DrawOptions } from '@/views/chart/components/js/panel/types/impl/s2'
import { parseJson } from '@/views/chart/components/js/util'
import {
type LayoutResult,
S2Event,
S2Options,
SHAPE_STYLE_MAP,
@ -165,6 +166,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
}
} else {
// header interaction
chart.container = container
this.configHeaderInteraction(chart, s2Options)
}
@ -207,7 +209,63 @@ export class TableNormal extends S2ChartView<TableSheet> {
}
// 开始渲染
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)
// 总计紧贴在单元格后面
if (customAttr.basicStyle.showSummary) {
newChart.on(S2Event.LAYOUT_BEFORE_RENDER, () => {
const totalHeight =
customAttr.tableHeader.tableTitleHeight * 2 +
customAttr.tableCell.tableItemHeight * (newData.length - 1)
if (totalHeight < newChart.options.height) {
// 6 是阴影高度
newChart.options.height =
totalHeight < newChart.options.height - 6 ? totalHeight + 6 : totalHeight
}
})
}
// 自适应铺满
if (customAttr.basicStyle.tableColumnMode === 'adapt') {
newChart.on(S2Event.LAYOUT_RESIZE_COL_WIDTH, () => {
newChart.store.set('lastLayoutResult', newChart.facet.layoutResult)
})
newChart.on(S2Event.LAYOUT_AFTER_HEADER_LAYOUT, (ev: LayoutResult) => {
const status = newChart.store.get('status')
if (status === 'default') {
return
}
const lastLayoutResult = newChart.store.get('lastLayoutResult') as LayoutResult
if (status === 'expanded' && lastLayoutResult) {
// 拖拽表头定义宽度和上一次布局对比保留除已拖拽列之外的宽度
const widthByFieldValue = newChart.options.style?.colCfg?.widthByFieldValue
const lastLayoutWidthMap: Record<string, number> = lastLayoutResult?.colLeafNodes.reduce(
(p, n) => {
p[n.value] = widthByFieldValue?.[n.value] ?? n.width
return p
},
{}
)
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = lastLayoutWidthMap[n.value]
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = totalWidth
} else {
const scale = containerDom.offsetWidth / ev.colsHierarchy.width
if (scale <= 1) {
// 图库计算的布局宽度已经大于等于容器宽度不需要再扩大不处理
newChart.store.set('status', 'default')
return
}
const totalWidth = ev.colLeafNodes.reduce((p, n) => {
n.width = n.width * scale
n.x = p
return p + n.width
}, 0)
ev.colsHierarchy.width = Math.min(containerDom.offsetWidth, totalWidth)
newChart.store.set('status', 'expanded')
}
})
}
// click
newChart.on(S2Event.DATA_CELL_CLICK, ev => {
const cell = newChart.getCell(ev.target)

View File

@ -107,7 +107,8 @@ export class TablePivot extends S2ChartView<PivotSheet> {
},
xAxisExt: {
name: `${t('chart.drag_block_table_data_column')} / ${t('chart.dimension')}`,
type: 'd'
type: 'd',
allowEmpty: true
},
yAxis: {
name: `${t('chart.drag_block_table_data_column')} / ${t('chart.quota')}`,
@ -508,7 +509,7 @@ function getCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
const rowPath = getTreePath(query, row)
const colPath = getTreePath(query, col)
const path = [...rowPath, ...colPath]
const { data } = colSubTotal[subLevel]
const { data } = colSubTotal?.[subLevel]
let val
if (path.length && data) {
path.push(quotaField)
@ -525,6 +526,11 @@ function getCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
path.push(quotaField)
val = get(rowTotal.data, path)
}
// 列维度为空行维度不为空
if (!col.length && row.length) {
const path = [query[EXTRA_FIELD]]
val = get(rowTotal.data, path)
}
return val
}
// 行小计
@ -534,7 +540,7 @@ function getCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
const colPath = getTreePath(query, col)
const rowPath = getTreePath(query, row)
const path = [...colPath, ...rowPath]
const { data } = rowSubTotal[rowLevel]
const { data } = rowSubTotal?.[rowLevel]
let val
if (path.length && rowSubTotal) {
path.push(quotaField)
@ -546,7 +552,7 @@ function getCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
if (status.isRowTotal && status.isColSubTotal) {
const { colSubInRowTotal } = customCalc
const colLevel = getSubLevel(query, col)
const { data } = colSubInRowTotal[colLevel]
const { data } = colSubInRowTotal?.[colLevel]
const colPath = getTreePath(query, col)
let val
if (colPath.length && colSubInRowTotal) {
@ -559,7 +565,7 @@ function getCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
if (status.isColTotal && status.isRowSubTotal) {
const { rowSubInColTotal } = customCalc
const rowSubLevel = getSubLevel(query, row)
const data = rowSubInColTotal[rowSubLevel]?.data
const data = rowSubInColTotal?.[rowSubLevel]?.data
const path = getTreePath(query, row)
let val
if (path.length && rowSubInColTotal) {
@ -573,7 +579,7 @@ function getCustomCalcResult(query, axisMap, status: TotalStatus, customCalc) {
const { rowSubInColSub } = customCalc
const rowSubLevel = getSubLevel(query, row)
const colSubLevel = getSubLevel(query, col)
const { data } = rowSubInColSub[rowSubLevel][colSubLevel]
const { data } = rowSubInColSub?.[rowSubLevel]?.[colSubLevel]
const rowPath = getTreePath(query, row)
const colPath = getTreePath(query, col)
const path = [...rowPath, ...colPath]

View File

@ -308,7 +308,10 @@ export function getLegend(chart: Chart) {
offsetX: offsetX,
offsetY: offsetY,
marker: {
symbol: legendSymbol
symbol: legendSymbol,
style: {
r: 4
}
},
itemHeight: l.fontSize + 4,
radio: false,

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