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

This commit is contained in:
王嘉豪 2024-05-08 09:30:57 +08:00 committed by GitHub
commit b89dfdfc78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 2762 additions and 841 deletions

View File

@ -6,13 +6,11 @@
<a href="https://github.com/dataease/dataease"><img src="https://img.shields.io/github/stars/dataease/dataease?color=%231890FF&style=flat-square" alt="Stars"></a>
</p>
------------------------------
## 什么是 DataEase
DataEase 是开源的数据可视化分析工具帮助用户快速分析数据并洞察业务趋势从而实现业务的改进与优化DataEase 支持丰富的数据源连接能够通过拖拉拽方式快速制作图表并可以方便的与他人分享
<p align="center">
<img src="https://github.com/dataease/dataease/assets/41712985/f951e258-a328-43a9-aa37-ee470d37ed63" alt="DataEase 概览图" border="0" />
</p>
DataEase 是开源的数据可视化分析工具 BI 工具 帮助用户快速分析数据并洞察业务趋势从而实现业务的改进与优化DataEase 支持丰富的数据源连接能够通过拖拉拽方式快速制作图表并可以方便的与他人分享
**DataEase 的优势**
@ -23,22 +21,49 @@ DataEase 是开源的数据可视化分析工具,帮助用户快速分析数
**DataEase 支持的数据源**
<p align="center">
<img src="https://dataease.io/images/dataSource/excel.jpg" alt="excel" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/mysql.png" alt="mysql" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/oracle.jpg" alt="oracle" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/sqlservel.jpg" alt="sqlserver" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/mariadb.jpg" alt="mariadb" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/clickhouse.jpg" alt="clickhouse" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/doris.jpg" alt="doris" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/mongodb.jpg" alt="mongodb" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/redshift.jpg" alt="redshift" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/DB2.jpg" alt="DB2" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/TiDB.jpg" alt="TiDB" border="0" width="155" height="107"/>
<img src="https://dataease.io/images/dataSource/StarRocks.jpg" alt="StarRocks" border="0" width="155" height="107"/>
</p>
- OLTP 数据库 MySQLOracleSQL ServerPostgreSQLMariaDBDb2TiDBMongoDB-BI ;
- OLAP 数据库 ClickHouseApache DorisApache ImpalaStarRocks ;
- 数据仓库/数据湖 Amazon RedShift ;
- 数据文件 ExcelCSV ;
- API 数据源
**DataEase 的技术栈**
## 快速开始
```
# 准备一台 2核4G 以上 Linux 服务器 root 运行以下一键安装脚本
curl -sSL https://dataease.oss-cn-hangzhou.aliyuncs.com/quick_start_v2.sh | bash
# 用户名: admin
# 密码: DataEase@123456
```
你也可以通过 [1Panel 应用商店](https://dataease.io/docs/v2/installation/1panel_installation/) 快速部署 DataEase
如果是用于生产环境推荐使用 [离线安装包方式](https://dataease.io/docs/v2/installation/offline_INSTL_and_UPG/) 进行安装部署
如你有更多问题可以查看在线文档或者通过论坛与我们交流
- [在线文档](https://dataease.io/docs/)
- [社区论坛](https://bbs.fit2cloud.com/c/de/6)
- [入门视频](https://www.bilibili.com/video/BV1Z84y1X7eF/)
- [模版市场](https://templates.dataease.cn/)
## DataEase UI 展示
<table style="border-collapse: collapse; border: 1px solid black;">
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/dataease/dataease/assets/41712985/8dbed4e1-39f0-4392-aa8c-d1fd83ba42eb" alt="DataEase 工作台" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/dataease/dataease/assets/41712985/7c54cb07-51ef-4bb6-a931-8a95c64c7e11" alt="DataEase 仪表板" /></td>
</tr>
<tr>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/dataease/dataease/assets/41712985/ffa79361-a7b3-4486-b14a-f3fd3a28f01a" alt="DataEase 数据源" /></td>
<td style="padding: 5px;background-color:#fff;"><img src= "https://github.com/dataease/dataease/assets/41712985/bb28f4e4-636e-4ab0-85c5-1dfbd7a5397e" alt="DataEase 模板中心" /></td>
</tr>
</table>
## DataEase 的技术栈
- 前端[Vue.js](https://vuejs.org/)[Element](https://element.eleme.cn/)
- 图库[AntV](https://antv.vision/zh)
@ -47,14 +72,13 @@ DataEase 是开源的数据可视化分析工具,帮助用户快速分析数
- 数据处理[Apache Calcite](https://github.com/apache/calcite/)[Apache SeaTunnel](https://github.com/apache/seatunnel)
- 基础设施[Docker](https://www.docker.com/)
## DataEase 快速入门
## 我们的其他明星开源项目
- [在线文档](https://dataease.io/docs/)
- [社区论坛](https://bbs.fit2cloud.com/c/de/6)
- [快速入门视频](https://www.bilibili.com/video/BV1Z84y1X7eF/)
- [嵌入式 BI 体验](https://embedded-bi.dataease.cn/)
- [源码部署指南](https://dataease.io/docs/v2/installation/deployment_installation/)
- [模版市场](https://templates.dataease.cn/)
- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机
- [1Panel](https://github.com/1panel-dev/1panel/) - 现代化开源的 Linux 服务器运维管理面板
- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具
- [MaxKB](https://github.com/1panel-dev/MaxKB/) - 基于 LLM 大语言模型的开源知识库问答系统
- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源的测试管理和接口测试工具
## License

View File

@ -13,6 +13,12 @@
<artifactId>core-backend</artifactId>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>io.dataease</groupId>
<artifactId>api-base</artifactId>
@ -37,6 +43,12 @@
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>${calcite-core.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
<classifier>de</classifier>
</dependency>
<dependency>
@ -101,6 +113,22 @@
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium-java.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<version>${angus-mail.version}</version>
<exclusions>
<exclusion>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-activation</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
@ -270,15 +298,15 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<finalName>CoreApplication</finalName>
<layout>ZIP</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<finalName>CoreApplication</finalName>
<layout>ZIP</layout>
</configuration>
</execution>
</executions>
</plugin>

View File

@ -530,7 +530,7 @@ public class ChartDataManage {
String originSql = SQLProvider.createQuerySQL(sqlMeta, false, true, view);// 明细表强制加排序
String limit = ((pageInfo.getGoPage() != null && pageInfo.getPageSize() != null) ? " LIMIT " + pageInfo.getPageSize() + " OFFSET " + (pageInfo.getGoPage() - 1) * pageInfo.getPageSize() : "");
querySql = originSql + limit;
totalPageSql = "SELECT COUNT(*) FROM (" + originSql + ") COUNT_TEMP";
totalPageSql = "SELECT COUNT(*) FROM (" + SQLProvider.createQuerySQL(sqlMeta, false, false, view) + ") COUNT_TEMP";
} else if (StringUtils.containsIgnoreCase(view.getType(), "quadrant")) {
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields), crossDs, dsMap);
yAxis.addAll(extBubble);

View File

@ -1,6 +1,6 @@
package io.dataease.commons.utils;
import io.dataease.task.dao.auto.entity.CoreSysTask;
import io.dataease.utils.LogUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
@ -12,19 +12,9 @@ import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
/**
* @author song.tianyang
* @Date 2020/12/17 4:06 下午
* @Description CRON解析类
*/
public class CronUtils {
/**
* 解析表达式获取CronTrigger
*
* @param cron
* @return
*/
public static CronTrigger getCronTrigger(String cron) {
if (!CronExpression.isValidExpression(cron)) {
throw new RuntimeException("cron :" + cron + "表达式解析错误");
@ -32,28 +22,6 @@ public class CronUtils {
return TriggerBuilder.newTrigger().withIdentity("Calculate Date").withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
}
/**
* 获取以指定时间为开始时间的下一次执行时间
*
* @param cron
* @param start
* @return
*/
public static Date getNextTriggerTime(String cron, Date start) {
if (start == null) {
return getNextTriggerTime(cron);
} else {
CronTrigger trigger = getCronTrigger(cron);
return trigger.getFireTimeAfter(start);
}
}
/**
* 获取以当前日期为准的下一次执行时间
*
* @param cron
* @return
*/
public static Date getNextTriggerTime(String cron) {
Date date = null;
try {
@ -61,47 +29,11 @@ public class CronUtils {
Date startDate = trigger.getStartTime();
date = trigger.getFireTimeAfter(startDate);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
return date;
}
public static String cron(CoreSysTask taskEntity) {
if (taskEntity.getRateType() == -1) {
return taskEntity.getRateVal();
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = sdf.parse(taskEntity.getRateVal());
} catch (ParseException e) {
e.printStackTrace();
}
Calendar instance = Calendar.getInstance();
instance.setTime(date);
if (taskEntity.getRateType() == 0) {
return instance.get(Calendar.SECOND) + " " +
instance.get(Calendar.MINUTE) + " " +
instance.get(Calendar.HOUR_OF_DAY) + " * * ?";
}
if (taskEntity.getRateType() == 1) {
return instance.get(Calendar.SECOND) + " " +
instance.get(Calendar.MINUTE) + " " +
instance.get(Calendar.HOUR_OF_DAY) + " ? * " +
getDayOfWeek(instance);
}
if (taskEntity.getRateType() == 2) {
return instance.get(Calendar.SECOND) + " " +
instance.get(Calendar.MINUTE) + " " +
instance.get(Calendar.HOUR_OF_DAY) + " " +
instance.get(Calendar.DATE) + " * ?";
}
return null;
}
public static String cron() {
Calendar instance = Calendar.getInstance();
instance.add(Calendar.SECOND, 5);
@ -110,6 +42,44 @@ public class CronUtils {
instance.get(Calendar.HOUR_OF_DAY) + " * * ?";
}
public static String cron(Integer rateType, String rateVal) {
if (rateType == 0) {
return rateVal;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
try {
date = sdf.parse(rateVal);
} catch (ParseException e) {
e.printStackTrace();
}
Calendar instance = Calendar.getInstance();
assert date != null;
instance.setTime(date);
if (rateType == 1) {
return instance.get(Calendar.SECOND) + " " +
instance.get(Calendar.MINUTE) + " " +
instance.get(Calendar.HOUR_OF_DAY) + " * * ?";
}
if (rateType == 2) {
return instance.get(Calendar.SECOND) + " " +
instance.get(Calendar.MINUTE) + " " +
instance.get(Calendar.HOUR_OF_DAY) + " ? * " +
getDayOfWeek(instance);
}
if (rateType == 3) {
return instance.get(Calendar.SECOND) + " " +
instance.get(Calendar.MINUTE) + " " +
instance.get(Calendar.HOUR_OF_DAY) + " " +
instance.get(Calendar.DATE) + " * ?";
}
return null;
}
private static String getDayOfWeek(Calendar instance) {
int index = instance.get(Calendar.DAY_OF_WEEK);
index = (index % 7) + 1;
@ -120,7 +90,7 @@ public class CronUtils {
public static Boolean taskExpire(Long endTime) {
if (ObjectUtils.isEmpty(endTime))
return false;
Long now = System.currentTimeMillis();
long now = System.currentTimeMillis();
return now > endTime;
}

View File

@ -7,19 +7,27 @@ import io.dataease.dataset.dto.DatasourceSchemaDTO;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.utils.JsonUtil;
import net.sf.jsqlparser.expression.*;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.*;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
import net.sf.jsqlparser.util.deparser.SelectDeParser;
import org.apache.calcite.config.Lex;
import org.apache.calcite.sql.*;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.util.SqlShuttle;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -32,6 +40,362 @@ public class SqlparserUtils {
private static final String SubstitutedSql = " 'DE-BI' = 'DE-BI' ";
private static final String SubstitutedSqlVirtualData = " 1 > 2 ";
public static String removeVariables(final String sql, String dsType) throws Exception {
String tmpSql = sql.replaceAll("(?m)^\\s*$[\n\r]{0,}", "");
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(tmpSql);
boolean hasVariables = false;
while (matcher.find()) {
hasVariables = true;
tmpSql = tmpSql.replace(matcher.group(), SubstitutedParams);
}
if (!hasVariables && !tmpSql.contains(SubstitutedParams)) {
return tmpSql;
}
Statement statement = CCJSqlParserUtil.parse(tmpSql);
Select select = (Select) statement;
if (select.getSelectBody() instanceof PlainSelect) {
return handlePlainSelect((PlainSelect) select.getSelectBody(), select, dsType);
} else {
StringBuilder result = new StringBuilder();
SetOperationList setOperationList = (SetOperationList) select.getSelectBody();
for (int i = 0; i < setOperationList.getSelects().size(); i++) {
result.append(handlePlainSelect((PlainSelect) setOperationList.getSelects().get(i), null, dsType));
if (i < setOperationList.getSelects().size() - 1) {
result.append(" ").append(setOperationList.getOperations().get(i).toString()).append(" ");
}
}
return result.toString();
}
}
private static String handlePlainSelect(PlainSelect plainSelect, Select statementSelect, String dsType) throws Exception {
handleSelectItems(plainSelect, dsType);
handleFromItems(plainSelect, dsType);
handleJoins(plainSelect, dsType);
return handleWhere(plainSelect, statementSelect, dsType);
}
private static void handleSelectItems(PlainSelect plainSelect, String dsType) throws Exception {
List<SelectItem<?>> selectItems = new ArrayList<>();
for (SelectItem selectItem : plainSelect.getSelectItems()) {
try {
if (selectItem.getExpression() instanceof ParenthesedSelect) {
ParenthesedSelect parenthesedSelect = (ParenthesedSelect) selectItem.getExpression();
parenthesedSelect.setSelect((Select) CCJSqlParserUtil.parse(removeVariables(((Select) selectItem.getExpression()).getPlainSelect().toString(), dsType)));
selectItem.setExpression(parenthesedSelect);
}
} catch (Exception e) {
e.printStackTrace();
}
selectItems.add(selectItem);
}
plainSelect.setSelectItems(selectItems);
}
private static void handleFromItems(PlainSelect plainSelect, String dsType) throws Exception {
FromItem fromItem = plainSelect.getFromItem();
if (fromItem instanceof ParenthesedSelect) {
if (((ParenthesedSelect) fromItem).getSelect() instanceof SetOperationList) {
StringBuilder result = new StringBuilder();
SetOperationList setOperationList = (SetOperationList) ((ParenthesedSelect) fromItem).getSelect().getSelectBody();
for (int i = 0; i < setOperationList.getSelects().size(); i++) {
result.append(handlePlainSelect((PlainSelect) setOperationList.getSelects().get(i), null, dsType));
if (i < setOperationList.getSelects().size() - 1) {
result.append(" ").append(setOperationList.getOperations().get(i).toString()).append(" ");
}
}
} else {
PlainSelect selectBody = ((ParenthesedSelect) fromItem).getSelect().getPlainSelect();
Select subSelectTmp = (Select) CCJSqlParserUtil.parse(removeVariables(selectBody.toString(), dsType));
((ParenthesedSelect) fromItem).setSelect(subSelectTmp.getSelectBody());
if (dsType.equals(DatasourceConfiguration.DatasourceType.oracle.getType())) {
if (fromItem.getAlias() != null) {
fromItem.setAlias(new Alias(fromItem.getAlias().toString(), false));
}
} else {
if (fromItem.getAlias() == null) {
throw new Exception("Failed to parse sql, Every derived table must have its own alias");
}
fromItem.setAlias(new Alias(fromItem.getAlias().toString(), false));
}
}
plainSelect.setFromItem(fromItem);
}
}
private static void handleJoins(PlainSelect plainSelect, String dsType) throws Exception {
List<Join> joins = plainSelect.getJoins();
if (joins != null) {
List<Join> joinsList = new ArrayList<>();
for (Join join : joins) {
FromItem rightItem = join.getRightItem();
if (rightItem instanceof ParenthesedSelect) {
PlainSelect selectBody = ((ParenthesedSelect) rightItem).getPlainSelect();
Select subSelectTmp = (Select) CCJSqlParserUtil.parse(removeVariables(selectBody.toString(), dsType));
PlainSelect subPlainSelect = ((PlainSelect) subSelectTmp.getSelectBody());
((ParenthesedSelect) rightItem).setSelect(subPlainSelect);
if (dsType.equals(DatasourceConfiguration.DatasourceType.oracle.getType())) {
rightItem.setAlias(new Alias(rightItem.getAlias().toString(), false));
} else {
if (rightItem.getAlias() == null) {
throw new Exception("Failed to parse sql, Every derived table must have its own alias");
}
rightItem.setAlias(new Alias(rightItem.getAlias().toString(), false));
}
join.setRightItem(rightItem);
}
joinsList.add(join);
}
plainSelect.setJoins(joinsList);
}
}
private static String handleWhere(PlainSelect plainSelect, Select statementSelect, String dsType) throws Exception {
Expression expr = plainSelect.getWhere();
if (expr == null) {
return handleWith(plainSelect, statementSelect, dsType);
}
StringBuilder stringBuilder = new StringBuilder();
BinaryExpression binaryExpression = null;
try {
binaryExpression = (BinaryExpression) expr;
} catch (Exception e) {
}
if (binaryExpression != null) {
boolean hasSubBinaryExpression = binaryExpression instanceof AndExpression || binaryExpression instanceof OrExpression;
if (!hasSubBinaryExpression && !(binaryExpression.getLeftExpression() instanceof BinaryExpression) && !(binaryExpression.getLeftExpression() instanceof InExpression) && (hasVariable(binaryExpression.getLeftExpression().toString()) || hasVariable(binaryExpression.getRightExpression().toString()))) {
stringBuilder.append(SubstitutedSql);
} else {
expr.accept(getExpressionDeParser(stringBuilder));
}
} else {
expr.accept(getExpressionDeParser(stringBuilder));
}
plainSelect.setWhere(CCJSqlParserUtil.parseCondExpression(stringBuilder.toString()));
return handleWith(plainSelect, statementSelect, dsType);
}
private static String handleWith(PlainSelect plainSelect, Select select, String dsType) throws Exception {
if (select != null && CollectionUtils.isNotEmpty(select.getWithItemsList())) {
for (Iterator<WithItem> iter = select.getWithItemsList().iterator(); iter.hasNext(); ) {
WithItem withItem = iter.next();
ParenthesedSelect parenthesedSelect = (ParenthesedSelect) withItem.getSelect();
parenthesedSelect.setSelect((Select) CCJSqlParserUtil.parse(removeVariables(parenthesedSelect.getSelect().toString(), dsType)));
}
}
return plainSelect.toString();
}
private static ExpressionDeParser getExpressionDeParser(StringBuilder stringBuilder) {
SelectDeParser selectDeParser = new SelectDeParser(stringBuilder);
ExpressionDeParser expressionDeParser = new ExpressionDeParser(null, stringBuilder) {
@Override
public void visit(Parenthesis parenthesis) {
getBuffer().append("(");
parenthesis.getExpression().accept(this);
getBuffer().append(")");
}
@Override
public void visit(OrExpression orExpression) {
visitBinaryExpr(orExpression, "OR");
}
@Override
public void visit(AndExpression andExpression) {
visitBinaryExpr(andExpression, andExpression.isUseOperator() ? " && " : " AND ");
}
@Override
public void visit(Between between) {
if (hasVariable(between.getBetweenExpressionStart().toString()) || hasVariable(between.getBetweenExpressionEnd().toString())) {
getBuffer().append(SubstitutedSql);
} else {
getBuffer().append(between.getLeftExpression()).append(" BETWEEN ").append(between.getBetweenExpressionStart()).append(" AND ").append(between.getBetweenExpressionEnd());
}
}
@Override
public void visit(MinorThan minorThan) {
if (hasVariable(minorThan.getLeftExpression().toString()) || hasVariable(minorThan.getRightExpression().toString())) {
getBuffer().append(SubstitutedSql);
return;
}
getBuffer().append(minorThan.getLeftExpression());
getBuffer().append(" < ");
getBuffer().append(minorThan.getRightExpression());
}
@Override
public void visit(MinorThanEquals minorThan) {
if (hasVariable(minorThan.getLeftExpression().toString()) || hasVariable(minorThan.getRightExpression().toString())) {
getBuffer().append(SubstitutedSql);
return;
}
getBuffer().append(minorThan.getLeftExpression());
getBuffer().append(" <= ");
getBuffer().append(minorThan.getRightExpression());
}
@Override
public void visit(GreaterThanEquals minorThan) {
if (hasVariable(minorThan.getLeftExpression().toString()) || hasVariable(minorThan.getRightExpression().toString())) {
getBuffer().append(SubstitutedSql);
return;
}
getBuffer().append(minorThan.getLeftExpression());
getBuffer().append(" >= ");
getBuffer().append(minorThan.getRightExpression());
}
@Override
public void visit(GreaterThan greaterThan) {
if (hasVariable(greaterThan.getLeftExpression().toString()) || hasVariable(greaterThan.getRightExpression().toString())) {
getBuffer().append(SubstitutedSql);
return;
}
getBuffer().append(greaterThan.getLeftExpression());
getBuffer().append(" > ");
getBuffer().append(greaterThan.getRightExpression());
}
@Override
public void visit(ExpressionList expressionList) {
for (Iterator<Expression> iter = expressionList.getExpressions().iterator(); iter.hasNext(); ) {
Expression expression = iter.next();
expression.accept(this);
if (iter.hasNext()) {
buffer.append(", ");
}
}
}
@Override
public void visit(LikeExpression likeExpression) {
if (hasVariable(likeExpression.toString())) {
getBuffer().append(SubstitutedSql);
return;
}
visitBinaryExpression(likeExpression, (likeExpression.isNot() ? " NOT" : "") + (likeExpression.isCaseInsensitive() ? " ILIKE " : " LIKE "));
if (likeExpression.getEscape() != null) {
buffer.append(" ESCAPE '").append(likeExpression.getEscape()).append('\'');
}
}
@Override
public void visit(InExpression inExpression) {
if (inExpression.getRightExpression() != null && hasVariable(inExpression.getRightExpression().toString()) && !(inExpression.getRightExpression() instanceof ParenthesedSelect)) {
stringBuilder.append(SubstitutedSql);
return;
}
inExpression.getLeftExpression().accept(this);
if (inExpression.isNot()) {
getBuffer().append(" " + " NOT IN " + " ");
} else {
getBuffer().append(" IN ");
}
if (inExpression.getRightExpression() != null && inExpression.getRightExpression() instanceof ParenthesedSelect) {
try {
ParenthesedSelect subSelect = (ParenthesedSelect) inExpression.getRightExpression();
Select select = (Select) CCJSqlParserUtil.parse(removeVariables(subSelect.getPlainSelect().toString(), ""));
subSelect.setSelect(select);
inExpression.setRightExpression(subSelect);
} catch (Exception e) {
e.printStackTrace();
}
inExpression.getRightExpression().accept(this);
}
if (inExpression.getRightExpression() instanceof ParenthesedExpressionList) {
buffer.append(inExpression.getRightExpression());
}
}
@Override
public void visit(ParenthesedSelect subSelect) {
StringBuilder stringBuilder = new StringBuilder();
Expression in = ((PlainSelect) subSelect.getSelectBody()).getWhere();
if (in instanceof BinaryExpression && hasVariable(in.toString())) {
stringBuilder.append(SubstitutedParams);
} else {
in.accept(getExpressionDeParser(stringBuilder));
}
try {
Expression where = CCJSqlParserUtil.parseCondExpression(stringBuilder.toString());
((PlainSelect) subSelect.getSelectBody()).setWhere(where);
getBuffer().append(subSelect.getSelectBody());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void visit(Select selectBody) {
getBuffer().append(selectBody.toString());
}
private void visitBinaryExpr(BinaryExpression expr, String operator) {
boolean hasSubBinaryExpression = false;
if (expr.getLeftExpression() instanceof Parenthesis) {
try {
Parenthesis parenthesis = (Parenthesis) expr.getLeftExpression();
BinaryExpression leftBinaryExpression = (BinaryExpression) parenthesis.getExpression();
hasSubBinaryExpression = leftBinaryExpression instanceof AndExpression || leftBinaryExpression instanceof OrExpression;
} catch (Exception e) {
}
}
if (expr.getLeftExpression() instanceof BinaryExpression) {
try {
BinaryExpression leftBinaryExpression = (BinaryExpression) expr.getLeftExpression();
hasSubBinaryExpression = leftBinaryExpression instanceof AndExpression || leftBinaryExpression instanceof OrExpression;
} catch (Exception e) {
e.printStackTrace();
}
}
if ((expr.getLeftExpression() instanceof BinaryExpression || expr.getLeftExpression() instanceof Parenthesis) && !hasSubBinaryExpression && hasVariable(expr.getLeftExpression().toString())) {
getBuffer().append(SubstitutedSql);
} else {
expr.getLeftExpression().accept(this);
}
getBuffer().append(" " + operator + " ");
hasSubBinaryExpression = false;
if (expr.getRightExpression() instanceof Parenthesis) {
Parenthesis parenthesis = (Parenthesis) expr.getRightExpression();
BinaryExpression rightBinaryExpression = (BinaryExpression) parenthesis.getExpression();
hasSubBinaryExpression = rightBinaryExpression instanceof AndExpression || rightBinaryExpression instanceof OrExpression;
}
if (expr.getRightExpression() instanceof BinaryExpression) {
try {
BinaryExpression rightBinaryExpression = (BinaryExpression) expr.getRightExpression();
hasSubBinaryExpression = rightBinaryExpression instanceof AndExpression || rightBinaryExpression instanceof OrExpression;
} catch (Exception e) {
}
}
if ((expr.getRightExpression() instanceof Parenthesis || expr.getRightExpression() instanceof BinaryExpression || expr.getRightExpression() instanceof Function) && !hasSubBinaryExpression && hasVariable(expr.getRightExpression().toString())) {
getBuffer().append(SubstitutedSql);
} else {
expr.getRightExpression().accept(this);
}
}
};
return expressionDeParser;
}
private static boolean hasVariable(String sql) {
return sql.contains(SubstitutedParams);
}
public static String removeVariables(final String sql) {
String tmpSql = sql;
Pattern pattern = Pattern.compile(regex);
@ -167,7 +531,8 @@ public class SqlparserUtils {
}
try {
sql = removeVariables(sql);
DatasourceSchemaDTO ds = dsMap.entrySet().iterator().next().getValue();
sql = removeVariables(sql, ds.getType());
// replace keyword '`'
if (!isCross) {

View File

@ -616,7 +616,7 @@ public class DatasetDataManage {
if (ObjectUtils.isNotEmpty(distinctDataList)) {
for (String[] ele : distinctDataList) {
Map<String, Object> map = new LinkedHashMap<>();
for (int i = 0; i < ele.length; i++) {
for (int i = 0; i < fields.size(); i++) {
String val = ele[i];
DatasetTableFieldDTO field = fields.get(i);
if (desensitizationList.containsKey(field.getDataeaseName())) {

View File

@ -142,6 +142,7 @@ public class DatasetGroupManage {
@XpackInteract(value = "authResourceTree", before = false)
public void innerEdit(DatasetGroupInfoDTO datasetGroupInfoDTO) {
checkName(datasetGroupInfoDTO);
CoreDatasetGroup coreDatasetGroup = BeanUtils.copyBean(new CoreDatasetGroup(), datasetGroupInfoDTO);
coreDatasetGroup.setLastUpdateTime(System.currentTimeMillis());
coreDatasetGroupMapper.updateById(coreDatasetGroup);
@ -150,6 +151,7 @@ public class DatasetGroupManage {
@XpackInteract(value = "authResourceTree", before = false)
public void innerSave(DatasetGroupInfoDTO datasetGroupInfoDTO) {
checkName(datasetGroupInfoDTO);
CoreDatasetGroup coreDatasetGroup = BeanUtils.copyBean(new CoreDatasetGroup(), datasetGroupInfoDTO);
coreDatasetGroupMapper.insert(coreDatasetGroup);
coreOptRecentManage.saveOpt(coreDatasetGroup.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASET, OptConstants.OPT_TYPE.NEW);

View File

@ -198,9 +198,6 @@ public class PermissionManage {
if (StringUtils.isNotEmpty(userEntity.getName())) {
expressionTree = expressionTree.replaceAll("\\$\\{sysParams\\.userName}", userEntity.getName());
}
if (StringUtils.isNotEmpty(userEntity.getLabel())) {
expressionTree = expressionTree.replaceAll("\\$\\{sysParams\\.userLabel}", userEntity.getLabel());
}
record.setExpressionTree(expressionTree);
DatasetRowPermissionsTreeObj tree = JsonUtil.parseObject(expressionTree, DatasetRowPermissionsTreeObj.class);
record.setTree(tree);

View File

@ -11,6 +11,7 @@ import io.dataease.datasource.dao.ext.mapper.DataSourceExtMapper;
import io.dataease.datasource.dao.ext.po.DataSourceNodePO;
import io.dataease.datasource.dto.DatasourceNodeBO;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.license.config.XpackInteract;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
@ -73,10 +74,31 @@ public class DataSourceManage {
@XpackInteract(value = "datasourceResourceTree", before = false)
public void innerSave(CoreDatasource coreDatasource) {
checkName(coreDatasource);
coreDatasourceMapper.insert(coreDatasource);
coreOptRecentManage.saveOpt(coreDatasource.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE, OptConstants.OPT_TYPE.NEW);
}
public void checkName(CoreDatasource dto) {
QueryWrapper<CoreDatasource> wrapper = new QueryWrapper<>();
if (ObjectUtils.isNotEmpty(dto.getPid())) {
wrapper.eq("pid", dto.getPid());
}
if (StringUtils.isNotEmpty(dto.getName())) {
wrapper.eq("name", dto.getName());
}
if (ObjectUtils.isNotEmpty(dto.getId())) {
wrapper.ne("id", dto.getId());
}
if (ObjectUtils.isNotEmpty(dto.getType()) && dto.getType().equalsIgnoreCase("folder")) {
wrapper.ne("type", dto.getType());
}
List<CoreDatasource> list = coreDatasourceMapper.selectList(wrapper);
if (list.size() > 0) {
DEException.throwException(Translator.get("i18n_ds_name_exists"));
}
}
@XpackInteract(value = "datasourceResourceTree", before = false)
public void innerEdit(CoreDatasource coreDatasource) {
@ -108,6 +130,7 @@ public class DataSourceManage {
sourceData.setUpdateBy(AuthUtils.getUser().getUserId());
sourceData.setPid(dataSourceDTO.getPid());
sourceData.setName(dataSourceDTO.getName());
checkName(sourceData);
coreDatasourceMapper.updateById(sourceData);
coreOptRecentManage.saveOpt(sourceData.getId(), OptConstants.OPT_RESOURCE_TYPE.DATASOURCE, OptConstants.OPT_TYPE.UPDATE);
}

View File

@ -18,11 +18,9 @@ import io.dataease.datasource.request.DatasourceRequest;
import io.dataease.datasource.request.EngineRequest;
import io.dataease.datasource.server.DatasourceServer;
import io.dataease.datasource.server.DatasourceTaskServer;
import io.dataease.datasource.server.EngineServer;
import io.dataease.exception.DEException;
import io.dataease.job.sechedule.ExtractDataJob;
import io.dataease.job.sechedule.ScheduleManager;
import io.dataease.utils.JsonUtil;
import io.dataease.job.schedule.ExtractDataJob;
import io.dataease.job.schedule.ScheduleManager;
import io.dataease.utils.LogUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;

View File

@ -461,7 +461,17 @@ public class CalciteProvider {
}
resultSet = statement.executeQuery(getTableFiledSql(datasourceRequest));
while (resultSet.next()) {
datasetTableFields.add(getTableFieldDesc(datasourceRequest, resultSet));
TableField tableFieldDesc = getTableFieldDesc(datasourceRequest, resultSet);
boolean repeat = false;
for (TableField ele : datasetTableFields) {
if (StringUtils.equalsIgnoreCase(ele.getOriginName(), tableFieldDesc.getOriginName())) {
repeat = true;
break;
}
}
if (!repeat) {
datasetTableFields.add(tableFieldDesc);
}
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
@ -971,6 +981,23 @@ public class CalciteProvider {
}
}
public void updateDsPoolAfterCheckStatus(DatasourceDTO datasourceDTO) throws DEException {
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, datasourceDTO);
datasourceSchemaDTO.setSchemaAlias(String.format(SQLConstants.SCHEMA, datasourceSchemaDTO.getId()));
DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setDsList(Map.of(datasourceSchemaDTO.getId(), datasourceSchemaDTO));
try {
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();
if (rootSchema.getSubSchema(datasourceSchemaDTO.getSchemaAlias()) == null) {
buildSchema(datasourceRequest, calciteConnection);
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
}
}
public void delete(CoreDatasource datasource) throws DEException {
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, datasource);

View File

@ -35,8 +35,8 @@ import io.dataease.datasource.request.DatasourceRequest;
import io.dataease.engine.constant.SQLConstants;
import io.dataease.exception.DEException;
import io.dataease.i18n.Translator;
import io.dataease.job.sechedule.CheckDsStatusJob;
import io.dataease.job.sechedule.ScheduleManager;
import io.dataease.job.schedule.CheckDsStatusJob;
import io.dataease.job.schedule.ScheduleManager;
import io.dataease.license.config.XpackInteract;
import io.dataease.log.DeLog;
import io.dataease.model.BusiNodeRequest;
@ -655,9 +655,7 @@ public class DatasourceServer implements DatasourceApi {
BeanUtils.copyBean(datasourceDTO, coreDatasource);
try {
checkDatasourceStatus(coreDatasource);
if(StringUtils.isNotEmpty(lastStatus) && StringUtils.isNotEmpty(coreDatasource.getStatus()) && lastStatus.equalsIgnoreCase("Error") && coreDatasource.getStatus().equalsIgnoreCase("Success")){
calciteProvider.update(datasourceDTO);
}
calciteProvider.updateDsPoolAfterCheckStatus(datasourceDTO);
} catch (Exception e) {
coreDatasource.setStatus("Error");
DEException.throwException(e.getMessage());

View File

@ -1,11 +1,13 @@
package io.dataease.job.sechedule;
package io.dataease.job.schedule;
import io.dataease.datasource.server.DatasourceServer;
import io.dataease.utils.CommonBeanFactory;
import io.dataease.utils.LogUtil;
import jakarta.annotation.Resource;
import org.quartz.*;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.stereotype.Component;
@Component

View File

@ -1,4 +1,4 @@
package io.dataease.job.sechedule;
package io.dataease.job.schedule;
import io.dataease.utils.LogUtil;
import org.quartz.*;

View File

@ -0,0 +1,70 @@
package io.dataease.job.schedule;
import io.dataease.commons.utils.CronUtils;
import io.dataease.license.config.XpackInteract;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class DeTaskExecutor {
protected static final String IS_TEMP_TASK = "isTempTask";
@Resource
private ScheduleManager scheduleManager;
@XpackInteract(value = "xpackTaskExecutor", replace = true)
public boolean execute(Long taskId) {
return false;
}
@XpackInteract(value = "xpackTaskExecutor", replace = true)
public boolean executeTemplate(Long taskId) {
return false;
}
public void addOrUpdateTask(Long taskId, String cron, Long startTime, Long endTime) {
if (CronUtils.taskExpire(endTime)) {
return;
}
String key = taskId.toString();
JobKey jobKey = new JobKey(key, key);
TriggerKey triggerKey = new TriggerKey(key, key);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("taskId", taskId);
jobDataMap.put(IS_TEMP_TASK, false);
Date end = null;
if (ObjectUtils.isNotEmpty(endTime)) end = new Date(endTime);
scheduleManager.addOrUpdateCronJob(jobKey, triggerKey, DeXpackScheduleJob.class, cron, new Date(startTime), end, jobDataMap);
}
public void fireNow(Long taskId) throws Exception{
String key = taskId.toString();
JobKey jobKey = new JobKey(key, key);
scheduleManager.fireNow(jobKey);
}
public void addTempTask(Long taskId, Long startTime) {
String key = taskId.toString();
JobKey jobKey = new JobKey(key, key);
TriggerKey triggerKey = new TriggerKey(key, key);
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put(IS_TEMP_TASK, true);
String cron = CronUtils.cron();
jobDataMap.put("taskId", taskId);
scheduleManager.addOrUpdateCronJob(jobKey, triggerKey, DeXpackScheduleJob.class, cron, new Date(startTime), null, jobDataMap);
}
public void removeTask(Long taskId) {
String key = taskId.toString();
JobKey jobKey = new JobKey(key);
TriggerKey triggerKey = new TriggerKey(key);
scheduleManager.removeJob(jobKey, triggerKey);
}
}

View File

@ -0,0 +1,34 @@
package io.dataease.job.schedule;
import io.dataease.utils.CommonBeanFactory;
import jakarta.annotation.Resource;
import org.quartz.*;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class DeXpackScheduleJob implements Job {
@Resource
private DeTaskExecutor deTaskExecutor;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Trigger trigger = jobExecutionContext.getTrigger();
JobKey jobKey = trigger.getJobKey();
JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
Long taskId = jobDataMap.getLong("taskId");
boolean isTempTask = jobDataMap.getBoolean("isTempTask");
boolean taskLoaded = false;
if (isTempTask) {
taskLoaded = deTaskExecutor.executeTemplate(taskId);
} else {
taskLoaded = deTaskExecutor.execute(taskId);
}
if (!taskLoaded) {
Objects.requireNonNull(CommonBeanFactory.getBean(ScheduleManager.class)).removeJob(jobKey, trigger.getKey());
}
}
}

View File

@ -1,14 +1,13 @@
package io.dataease.job.sechedule;
package io.dataease.job.schedule;
import io.dataease.datasource.manage.DatasourceSyncManage;
import io.dataease.datasource.server.DatasourceServer;
import io.dataease.utils.CommonBeanFactory;
import org.quartz.JobExecutionContext;
import org.springframework.stereotype.Component;
@Component
public class ExtractDataJob extends DeScheduleJob{
public class ExtractDataJob extends DeScheduleJob {
private DatasourceSyncManage datasourceSyncManage;
public ExtractDataJob() {

View File

@ -1,4 +1,4 @@
package io.dataease.job.sechedule;
package io.dataease.job.schedule;
import com.fit2cloud.quartz.anno.QuartzScheduled;
import io.dataease.datasource.server.DatasourceServer;
@ -6,7 +6,6 @@ import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class Schedular {

View File

@ -1,4 +1,4 @@
package io.dataease.job.sechedule;
package io.dataease.job.schedule;
import io.dataease.exception.DEException;
@ -8,7 +8,6 @@ import jakarta.annotation.Resource;
import org.quartz.*;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@ -31,7 +30,7 @@ public class ScheduleManager {
* @throws SchedulerException
*/
public void addSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class<? extends Job> cls, int repeatIntervalTime,
JobDataMap jobDataMap) throws SchedulerException {
JobDataMap jobDataMap) throws SchedulerException {
JobBuilder jobBuilder = JobBuilder.newJob(cls).withIdentity(jobKey);
@ -64,7 +63,7 @@ public class ScheduleManager {
* @param jobDataMap
*/
public void addCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, Date startTime,
Date endTime, JobDataMap jobDataMap) {
Date endTime, JobDataMap jobDataMap) {
try {
LogUtil.info("addCronJob: " + triggerKey.getName() + "," + triggerKey.getGroup());
@ -105,7 +104,7 @@ public class ScheduleManager {
}
public void addCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, Date startTime,
Date endTime) {
Date endTime) {
addCronJob(jobKey, triggerKey, jobClass, cron, startTime, endTime, null);
}
@ -315,7 +314,7 @@ public class ScheduleManager {
* @throws SchedulerException
*/
public void addOrUpdateSimpleJob(JobKey jobKey, TriggerKey triggerKey, Class clz,
int intervalTime, JobDataMap jobDataMap) throws SchedulerException {
int intervalTime, JobDataMap jobDataMap) throws SchedulerException {
if (scheduler.checkExists(triggerKey)) {
modifySimpleJobTime(triggerKey, intervalTime);
@ -326,20 +325,20 @@ public class ScheduleManager {
}
public void addOrUpdateSingleJob(JobKey jobKey, TriggerKey triggerKey, Class clz,
Date date, JobDataMap jobDataMap) throws DEException {
Date date, JobDataMap jobDataMap) throws DEException {
try {
if (scheduler.checkExists(triggerKey)) {
modifySingleJobTime(triggerKey, date);
} else {
addSingleJob(jobKey, triggerKey, clz, date, jobDataMap);
}
}catch (Exception e){
} catch (Exception e) {
DEException.throwException(e);
}
}
public void addOrUpdateSingleJob(JobKey jobKey, TriggerKey triggerKey, Class clz,
Date date) throws SchedulerException {
Date date) throws SchedulerException {
addOrUpdateSingleJob(jobKey, triggerKey, clz, date, null);
}
@ -359,7 +358,7 @@ public class ScheduleManager {
* @throws SchedulerException
*/
public void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, Date startTime,
Date endTime, JobDataMap jobDataMap) throws DEException {
Date endTime, JobDataMap jobDataMap) throws DEException {
LogUtil.info("AddOrUpdateCronJob: " + jobKey.getName() + "," + triggerKey.getGroup());
try {
@ -368,13 +367,13 @@ public class ScheduleManager {
} else {
addCronJob(jobKey, triggerKey, jobClass, cron, startTime, endTime, jobDataMap);
}
}catch (Exception e){
} catch (Exception e) {
DEException.throwException(e);
}
}
public void addOrUpdateCronJob(JobKey jobKey, TriggerKey triggerKey, Class jobClass, String cron, Date startTime,
Date endTime) throws SchedulerException {
Date endTime) throws SchedulerException {
addOrUpdateCronJob(jobKey, triggerKey, jobClass, cron, startTime, endTime, null);
}

View File

@ -0,0 +1,28 @@
package io.dataease.listener;
import io.dataease.license.utils.LogUtil;
import io.dataease.template.manage.TemplateLocalParseManage;
import jakarta.annotation.Resource;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value = 3)
public class TemplateInitListener implements ApplicationListener<ApplicationReadyEvent> {
@Resource
private TemplateLocalParseManage templateLocalParseManage;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
LogUtil.info("=====Template init from code [Start]=====");
try{
templateLocalParseManage.doInit();
}catch (Exception e){
LogUtil.error("=====Template init from code ERROR=====");
}
LogUtil.info("=====Template init from code [End]=====");
}
}

View File

@ -1,167 +0,0 @@
package io.dataease.task.dao.auto.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author fit2cloud
* @since 2023-04-12
*/
@TableName("core_sys_task")
public class CoreSysTask implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 任务ID
*/
@TableId(value = "task_id", type = IdType.AUTO)
private Long taskId;
/**
* 任务名称
*/
private String taskName;
/**
* 任务类型
*/
private String taskType;
/**
* 开始时间
*/
private Long startTime;
/**
* 结束时间
*/
private Long endTime;
/**
* 频率方式
*/
private Integer rateType;
/**
* 频率值
*/
private String rateVal;
/**
* 创建者ID
*/
private Long creator;
/**
* 创建时间
*/
private Long createTime;
/**
* 运行状态
*/
private Boolean status;
public Long getTaskId() {
return taskId;
}
public void setTaskId(Long taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
public Long getStartTime() {
return startTime;
}
public void setStartTime(Long startTime) {
this.startTime = startTime;
}
public Long getEndTime() {
return endTime;
}
public void setEndTime(Long endTime) {
this.endTime = endTime;
}
public Integer getRateType() {
return rateType;
}
public void setRateType(Integer rateType) {
this.rateType = rateType;
}
public String getRateVal() {
return rateVal;
}
public void setRateVal(String rateVal) {
this.rateVal = rateVal;
}
public Long getCreator() {
return creator;
}
public void setCreator(Long creator) {
this.creator = creator;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
@Override
public String toString() {
return "CoreSysTask{" +
"taskId = " + taskId +
", taskName = " + taskName +
", taskType = " + taskType +
", startTime = " + startTime +
", endTime = " + endTime +
", rateType = " + rateType +
", rateVal = " + rateVal +
", creator = " + creator +
", createTime = " + createTime +
", status = " + status +
"}";
}
}

View File

@ -1,16 +0,0 @@
package io.dataease.task.dao.auto.mapper;
import io.dataease.task.dao.auto.entity.CoreSysTask;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author fit2cloud
* @since 2023-04-12
*/
public interface CoreSysTaskMapper extends BaseMapper<CoreSysTask> {
}

View File

@ -0,0 +1,137 @@
package io.dataease.template.dao.auto.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
*
* </p>
*
* @author fit2cloud
* @since 2024-05-07
*/
@TableName("de_template_version")
public class DeTemplateVersion implements Serializable {
private static final long serialVersionUID = 1L;
@TableId("installed_rank")
private Integer installedRank;
private String version;
private String description;
private String type;
private String script;
private Integer checksum;
private String installedBy;
private LocalDateTime installedOn;
private Integer executionTime;
private Boolean success;
public Integer getInstalledRank() {
return installedRank;
}
public void setInstalledRank(Integer installedRank) {
this.installedRank = installedRank;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getScript() {
return script;
}
public void setScript(String script) {
this.script = script;
}
public Integer getChecksum() {
return checksum;
}
public void setChecksum(Integer checksum) {
this.checksum = checksum;
}
public String getInstalledBy() {
return installedBy;
}
public void setInstalledBy(String installedBy) {
this.installedBy = installedBy;
}
public LocalDateTime getInstalledOn() {
return installedOn;
}
public void setInstalledOn(LocalDateTime installedOn) {
this.installedOn = installedOn;
}
public Integer getExecutionTime() {
return executionTime;
}
public void setExecutionTime(Integer executionTime) {
this.executionTime = executionTime;
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
@Override
public String toString() {
return "DeTemplateVersion{" +
"installedRank = " + installedRank +
", version = " + version +
", description = " + description +
", type = " + type +
", script = " + script +
", checksum = " + checksum +
", installedBy = " + installedBy +
", installedOn = " + installedOn +
", executionTime = " + executionTime +
", success = " + success +
"}";
}
}

View File

@ -0,0 +1,18 @@
package io.dataease.template.dao.auto.mapper;
import io.dataease.template.dao.auto.entity.DeTemplateVersion;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author fit2cloud
* @since 2024-05-07
*/
@Mapper
public interface DeTemplateVersionMapper extends BaseMapper<DeTemplateVersion> {
}

View File

@ -0,0 +1,98 @@
package io.dataease.template.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.api.visualization.request.DataVisualizationBaseRequest;
import io.dataease.license.utils.LogUtil;
import io.dataease.template.dao.auto.entity.DeTemplateVersion;
import io.dataease.template.dao.auto.mapper.DeTemplateVersionMapper;
import io.dataease.utils.JsonUtil;
import io.dataease.visualization.server.StaticResourceServer;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.repository.init.ResourceReader;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
/**
* @author : WangJiaHao
* @date : 2024/5/7
*/
@Service
public class TemplateLocalParseManage {
@Resource
private StaticResourceServer staticResourceServer;
@Resource
private DeTemplateVersionMapper deTemplateVersionMapper;
@Resource(type = ResourceLoader.class)
private ResourceLoader resourceLoader;
public void doInit() throws Exception {
org.springframework.core.io.Resource[] templateFiles = getAllFilesInResourceDirectory("template");
if (templateFiles != null && templateFiles.length > 0) {
for (int i = 0; i < templateFiles.length; i++) {
org.springframework.core.io.Resource templateFile = templateFiles[i];
String templateName = templateFile.getFilename();
QueryWrapper<DeTemplateVersion> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("script", templateName);
if (!deTemplateVersionMapper.exists(queryWrapper)) {
DeTemplateVersion version = new DeTemplateVersion();
version.setScript(templateName);
version.setInstalledOn(LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES));
try {
String content = new String(templateFile.getInputStream().readAllBytes());;
DataVisualizationBaseRequest template = JsonUtil.parseObject(content, DataVisualizationBaseRequest.class);
parseCore(template);
version.setSuccess(true);
deTemplateVersionMapper.insert(version);
} catch (Exception e) {
LogUtil.error("De Template Version Error : " + templateName);
version.setSuccess(false);
deTemplateVersionMapper.insert(version);
break;
}
}
}
}
}
public void parseCore(DataVisualizationBaseRequest template) {
// 解析静态文件并保存
staticResourceServer.saveFilesToServe(template.getStaticResource());
}
public org.springframework.core.io.Resource[] getAllFilesInResourceDirectory(String directoryName) throws Exception {
// 创建一个 PathMatchingResourcePatternResolver 对象
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(resourceLoader);
// 获取 classpath template 目录下所有文件的 Resource 数组
org.springframework.core.io.Resource[] resources = resolver.getResources("classpath:template/*");
return resources;
}
public static String readFileContent(File file) throws IOException {
StringBuilder content = new StringBuilder();
try (InputStream inputStream = Files.newInputStream(file.toPath());
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
String line;
while ((line = bufferedReader.readLine()) != null) {
content.append(line);
}
}
return content.toString();
}
}

View File

@ -8,3 +8,10 @@ update data_visualization_info set version = 2;
ALTER TABLE `visualization_template`
ADD COLUMN `version` int NULL DEFAULT 3 COMMENT '使用资源的版本';
update visualization_template set version = 2;
update
core_chart_view as a,
core_chart_view as b
set
a.x_axis = b.x_axis_ext,
a.x_axis_ext = b.x_axis
where a.id = b.id and a.type = 'table-pivot';

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,7 @@ i18n_menu.datasource=\u6570\u636E\u6E90
i18n_menu.user=\u7528\u6237\u7BA1\u7406
i18n_menu.org=\u7EC4\u7EC7\u7BA1\u7406
i18n_menu.auth=\u6743\u9650\u914D\u7F6E
i18n_menu.report=\u5B9A\u65F6\u62A5\u544A
i18n_menu.sync=\u540C\u6B65\u7BA1\u7406
i18n_menu.summary=\u6982\u89C8
i18n_menu.ds=\u6570\u636E\u8FDE\u63A5\u7BA1\u7406

File diff suppressed because one or more lines are too long

View File

@ -24,7 +24,7 @@
"axios": "^1.3.3",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.9",
"element-plus-secondary": "^0.5.6",
"element-plus-secondary": "^0.5.8",
"element-resize-detector": "^1.2.4",
"file-saver": "^2.0.5",
"html-to-image": "^1.11.11",

View File

@ -26,9 +26,9 @@
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<!--<fileset>
<directory>dist</directory>
</fileset>
</fileset>-->
<fileset>
<directory>node_modules</directory>
</fileset>

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 4H19.5C19.7761 4 20 4.22386 20 4.5V5.5C20 5.77614 19.7761 6 19.5 6H2.5C2.22386 6 2 5.77614 2 5.5V4.5C2 4.22386 2.22386 4 2.5 4ZM2.5 18H19.5C19.7761 18 20 18.2239 20 18.5V19.5C20 19.7761 19.7761 20 19.5 20H2.5C2.22386 20 2 19.7761 2 19.5V18.5C2 18.2239 2.22386 18 2.5 18ZM2.5 11H13.5C13.7761 11 14 11.2239 14 11.5V12.5C14 12.7761 13.7761 13 13.5 13H2.5C2.22386 13 2 12.7761 2 12.5V11.5C2 11.2239 2.22386 11 2.5 11Z" fill="#1F2329"/>
<path d="M21.4107 12.3072C21.3607 12.3901 21.2913 12.4596 21.2083 12.5096L16.9643 15.0679C16.6829 15.2375 16.3173 15.1469 16.1477 14.8655C16.0918 14.7728 16.0623 14.6667 16.0623 14.5584L16.0622 9.44156C16.0622 9.11302 16.3285 8.84667 16.6571 8.84666C16.7653 8.84666 16.8715 8.87619 16.9642 8.93208L21.2084 11.4906C21.4897 11.6602 21.5803 12.0258 21.4107 12.3072Z" fill="#1F2329"/>
</svg>

After

Width:  |  Height:  |  Size: 930 B

View File

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.43781 4H21.4378C21.7139 4 21.9378 4.22386 21.9378 4.5V5.5C21.9378 5.77614 21.7139 6 21.4378 6H4.43781C4.16166 6 3.93781 5.77614 3.93781 5.5V4.5C3.93781 4.22386 4.16166 4 4.43781 4ZM4.43781 18H21.4378C21.7139 18 21.9378 18.2239 21.9378 18.5V19.5C21.9378 19.7761 21.7139 20 21.4378 20H4.43781C4.16166 20 3.93781 19.7761 3.93781 19.5V18.5C3.93781 18.2239 4.16166 18 4.43781 18ZM10.4378 11H21.4378C21.7139 11 21.9378 11.2239 21.9378 11.5V12.5C21.9378 12.7761 21.7139 13 21.4378 13H10.4378C10.1617 13 9.93781 12.7761 9.93781 12.5V11.5C9.93781 11.2239 10.1617 11 10.4378 11Z" fill="#1F2329"/>
<path d="M2.0855 12.3072C2.13548 12.3901 2.20494 12.4596 2.28785 12.5096L6.53189 15.0679C6.81326 15.2375 7.17886 15.1469 7.34848 14.8655C7.40435 14.7729 7.43388 14.6667 7.43389 14.5584L7.43401 9.44158C7.43401 9.11303 7.16768 8.84669 6.83914 8.84668C6.73089 8.84668 6.6247 8.87621 6.532 8.93209L2.28785 11.4906C2.00647 11.6602 1.91588 12.0259 2.0855 12.3072Z" fill="#1F2329"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,7 @@
<svg width="19" height="20" viewBox="0 0 19 20" fill="" xmlns="http://www.w3.org/2000/svg">
<path d="M15.4224 1.07745C15.5787 1.23373 15.6665 1.44569 15.6665 1.66671V8.33337H13.9998V2.50004H2.33317V17.5H6.49984V19.1667H1.49984C1.27882 19.1667 1.06686 19.0789 0.910582 18.9226C0.754301 18.7663 0.666504 18.5544 0.666504 18.3334V1.66671C0.666504 1.44569 0.754301 1.23373 0.910582 1.07745C1.06686 0.921172 1.27882 0.833374 1.49984 0.833374H14.8332C15.0542 0.833374 15.2661 0.921172 15.4224 1.07745Z" fill=""/>
<path d="M4.4165 5.00004C4.18638 5.00004 3.99984 5.18659 3.99984 5.41671V6.25004C3.99984 6.48016 4.18639 6.66671 4.4165 6.66671H11.9165C12.1466 6.66671 12.3332 6.48016 12.3332 6.25004V5.41671C12.3332 5.18659 12.1466 5.00004 11.9165 5.00004H4.4165Z" fill=""/>
<path d="M3.99984 8.75004C3.99984 8.51992 4.18638 8.33337 4.4165 8.33337H7.74984C7.97996 8.33337 8.1665 8.51992 8.1665 8.75004V9.58337C8.1665 9.81349 7.97996 10 7.74984 10H4.4165C4.18638 10 3.99984 9.81349 3.99984 9.58337V8.75004Z" fill=""/>
<path d="M13.8187 14.3479H15.3495C15.4646 14.3479 15.5578 14.4411 15.5578 14.5562V15.4439C15.5578 15.5589 15.4646 15.6522 15.3495 15.6522H12.7227C12.6076 15.6522 12.5143 15.5589 12.5143 15.4439V12.8171C12.5143 12.702 12.6076 12.6087 12.7227 12.6087H13.6104C13.7254 12.6087 13.8187 12.702 13.8187 12.8171V14.3479Z" fill=""/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.1665 15C8.1665 17.7613 10.4052 20 13.1665 20C15.9278 20 18.1665 17.7613 18.1665 15C18.1665 12.2387 15.9278 10 13.1665 10C10.4052 10 8.1665 12.2387 8.1665 15ZM15.5235 17.3571C14.8984 17.9822 14.0506 18.3334 13.1665 18.3334C12.2825 18.3334 11.4346 17.9822 10.8095 17.3571C10.1844 16.7319 9.83317 15.8841 9.83317 15C9.83317 14.116 10.1844 13.2681 10.8095 12.643C11.4346 12.0179 12.2825 11.6667 13.1665 11.6667C14.0506 11.6667 14.8984 12.0179 15.5235 12.643C16.1487 13.2681 16.4998 14.116 16.4998 15C16.4998 15.8841 16.1487 16.7319 15.5235 17.3571Z" fill=""/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -103,21 +103,19 @@ onUnmounted(() => {
<template>
<el-row class="custom-main">
<div class="scale-area">
<el-input
<el-input-number
@keydown.stop
@keyup.stop
type="number"
size="small"
effect="dark"
v-model="scale"
effect="dark"
:min="10"
:max="200"
:controls="false"
size="small"
controls-position="right"
@change="handleScaleChange()"
class="scale-input-number"
>
<template #suffix> % </template>
</el-input>
/>
<el-icon @click="scaleDecrease(1)" class="hover-icon-custom" style="margin-right: 12px">
<Icon name="dv-min"></Icon
></el-icon>
@ -160,16 +158,19 @@ onUnmounted(() => {
}
.scale-input-number {
height: 28px;
width: 80px !important;
height: 24px;
line-height: 24px;
width: 80px;
margin-right: 16px;
input {
-webkit-appearance: none;
-moz-appearance: textfield;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
:deep(.ed-input__wrapper) {
position: relative;
padding: 0 38px 0 8px;
&::after {
position: absolute;
content: '%';
right: 35px;
top: 1px;
}
}
}

View File

@ -29,7 +29,7 @@ const timestampFormatDate = value => {
if (!value) {
return '-'
}
return new Date(value)['format']()
return new Date(value).toLocaleString()
}
const valueText = (field, val, options) => {
for (let index = 0; index < options.length; index++) {

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ref, watch } from 'vue'
import { nextTick, ref, watch } from 'vue'
import { Icon } from '@/components/icon-custom'
import { ElButton, ElDivider, ElIcon } from 'element-plus-secondary'
import { propTypes } from '@/utils/propTypes'
@ -29,10 +29,16 @@ const clearFilter = (index?: number) => {
emits('clearFilter', index)
}
const clearFilterAll = () => {
emits('clearFilter', 'empty')
}
watch(
props.filterTexts,
() => props.filterTexts,
() => {
showScroll.value = container.value && container.value.scrollWidth > container.value.offsetWidth
nextTick(() => {
showScroll.value = container.value?.scrollWidth > container.value?.offsetWidth
})
},
{ deep: true }
)
@ -53,11 +59,28 @@ watch(
<Icon name="icon_close_outlined"></Icon>
</el-icon>
</p>
<el-button
type="text"
class="clear-btn clear-btn-inner"
v-if="!showScroll"
@click="clearFilterAll"
>
<template #icon>
<Icon name="icon_delete-trash_outlined"></Icon>
</template>
清空条件</el-button
>
</div>
<el-icon @click="scrollNext" class="arrow-right arrow-filter" v-if="showScroll">
<Icon name="icon_right_outlined"></Icon>
</el-icon>
<el-button type="text" class="clear-btn" @click="clearFilter()">
<el-button
type="text"
class="clear-btn"
style="height: 24px; line-height: 24px"
v-if="showScroll"
@click="clearFilterAll"
>
<template #icon>
<Icon name="icon_delete-trash_outlined"></Icon>
</template>
@ -102,8 +125,9 @@ watch(
i {
position: absolute;
right: 2px;
right: 6px;
top: 50%;
font-size: 12px;
transform: translateY(-50%);
cursor: pointer;
}
@ -150,6 +174,10 @@ watch(
overflow-x: auto;
white-space: nowrap;
height: 24px;
.clear-btn-inner {
margin-top: -16px;
}
}
}
</style>

View File

@ -174,20 +174,12 @@ watch(
}
)
const ALIGN_MAP = {
'top-align': {
display: 'flex',
'flex-direction': 'column',
'justify-content': 'flex-start'
},
'top-align': {},
'center-align': {
display: 'flex',
'flex-direction': 'column',
'justify-content': 'center'
margin: 'auto'
},
'bottom-align': {
display: 'flex',
'flex-direction': 'column',
'justify-content': 'flex-end'
'margin-top': 'auto'
}
}
const wrapperStyle = computed(() => {
@ -468,6 +460,7 @@ defineExpose({
<style lang="less" scoped>
.rich-main-class {
display: flex;
font-size: initial;
width: 100%;
height: 100%;
@ -585,7 +578,6 @@ defineExpose({
.custom-text-content {
width: 100%;
height: 100%;
overflow-y: auto;
outline: none !important;
border: none !important;

View File

@ -41,11 +41,11 @@ export default {
},
'center-align': {
component: null,
tooltip: '居中'
tooltip: '居中,只在内容未溢出时生效'
},
'bottom-align': {
component: null,
tooltip: '置底'
tooltip: '置底,只在内容未溢出时生效'
}
}
for (const key in btnMap) {

View File

@ -86,7 +86,6 @@ const { datasetFieldList } = comInfo(element.value.id)
const setCustomStyle = val => {
const {
show,
borderShow,
borderColor,
bgColorShow,
@ -102,10 +101,6 @@ const setCustomStyle = val => {
textColorShow,
title
} = val
if (!show) {
Object.assign(customStyle, { ...defaultStyle })
return
}
customStyle.background = bgColorShow ? bgColor || '' : ''
customStyle.border = borderShow ? borderColor || '' : ''
customStyle.btnList = [...btnList]

View File

@ -437,6 +437,23 @@ const validate = () => {
return false
}
if (!ele.defaultValueCheck) {
const isMultiple = +ele.displayType === 7 || ele.multiple
ele.selectValue = isMultiple ? [] : undefined
ele.defaultValue = isMultiple ? [] : undefined
return false
}
if (ele.displayType === '1') {
if (!ele.defaultValueCheck) return false
if (ele.timeType === 'fixed') {
if (!ele.defaultValue) {
ElMessage.error('默认时间不能为空!')
return true
}
}
}
if (+ele.displayType === 7) {
if (!ele.defaultValueCheck) return false
if (ele.timeType === 'fixed') {
@ -520,12 +537,6 @@ const validate = () => {
return true
}
if (!ele.defaultValueCheck) {
const isMultiple = +ele.displayType === 7 || ele.multiple
ele.selectValue = isMultiple ? [] : undefined
ele.defaultValue = isMultiple ? [] : undefined
}
let displayTypeField = null
if (
ele.checkedFields.some(id => {
@ -656,9 +667,7 @@ const init = (queryId: string) => {
.forEach(ele => {
const { dimensionList, quotaList } = ele.fields
ele.list = [...dimensionList, ...quotaList]
if (!datasetMap[ele.id]) {
datasetMap[ele.id] = ele
}
datasetMap[ele.id] = ele
})
fields.value = datasetFieldList.value
.map(ele => {
@ -734,7 +743,7 @@ const handleCondition = item => {
curComponent.value = conditions.value.find(ele => ele.id === item.id)
multiple.value = curComponent.value.multiple
if (!curComponent.value.dataset.fields.length && curComponent.value.dataset.id) {
if (curComponent.value.dataset.id) {
getOptions(curComponent.value.dataset.id, curComponent.value)
}
datasetFieldList.value.forEach(ele => {
@ -1337,9 +1346,11 @@ defineExpose({
</el-tree-select>
</div>
<div class="value">
<span class="label">查询字段</span>
<el-select
@change="handleFieldChange"
placeholder="查询字段"
class="search-field"
v-model="curComponent.field.id"
>
<template v-if="curComponent.field.id" #prefix>
@ -1383,7 +1394,12 @@ defineExpose({
</el-select>
</div>
<div class="value">
<el-select placeholder="显示字段" v-model="curComponent.displayId">
<span class="label">显示字段</span>
<el-select
placeholder="显示字段"
class="search-field"
v-model="curComponent.displayId"
>
<template v-if="curComponent.displayId" #prefix>
<el-icon>
<Icon
@ -1429,6 +1445,7 @@ defineExpose({
</el-select>
</div>
<div class="value">
<span class="label">排序字段</span>
<el-select
clearable
placeholder="请选择排序字段"
@ -2212,15 +2229,28 @@ defineExpose({
}
.value {
.ed-select {
width: 321px;
}
width: 321px;
.value {
margin-top: 8px;
&:first-child {
margin-top: -0.5px;
}
}
.ed-select {
width: 321px;
.search-field {
width: 257px;
}
.sort-field {
width: 176px;
}
.label {
line-height: 32px;
font-size: 14px;
margin-right: 8px;
}
}
}

View File

@ -174,7 +174,7 @@ const handleFieldIdChange = (val: EnumValue) => {
const mapValue = setDefaultMapValue(
Array.isArray(selectValue.value) ? [...selectValue.value] : [selectValue.value]
)
if (mapValue.length !== config.value.defaultMapValue.length) {
if (mapValue?.length !== config.value.defaultMapValue?.length) {
shouldReSearch = true
} else if (!mapValue.every(value => config.value.defaultMapValue.includes(value))) {
shouldReSearch = true

View File

@ -14,7 +14,6 @@ import 'vant/es/popup/style'
import 'vant/es/date-picker/style'
import 'vant/es/picker-group/style'
import 'vant/es/time-picker/style'
import { Icon } from '@/components/icon-custom'
interface SelectConfig {
selectValue: any

View File

@ -85,9 +85,14 @@ const getValueByDefaultValueCheckOrFirstLoad = (
defaultMapValue: any,
optionValueSource: number,
mapValue: any,
displayType: string
displayType: string,
displayId: string
) => {
if (optionValueSource === 1 && defaultMapValue?.length && ![1, 7].includes(+displayType)) {
if (
optionValueSource === 1 &&
(defaultMapValue?.length || displayId) &&
![1, 7].includes(+displayType)
) {
if (firstLoad) {
return defaultValueCheck ? defaultMapValue : multiple ? [] : ''
}
@ -208,6 +213,7 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
isTree = false,
timeGranularity = 'date',
displayType,
displayId,
multiple
} = item
@ -271,7 +277,8 @@ export const searchQuery = (queryComponentList, filter, curComponentId, firstLoa
defaultMapValue,
optionValueSource,
mapValue,
displayType
displayType,
displayId
)
}
if (

View File

@ -42,6 +42,7 @@ onMounted(() => {
.ai-popper-tips {
z-index: 10001 !important;
padding: 24px !important;
box-shadow: none !important;
border: 0px !important;
background: var(--ed-color-primary) !important;
.ed-popper__arrow::before {
@ -69,6 +70,7 @@ onMounted(() => {
text-align: right;
button {
border: 0px !important;
border-color: #ffffff !important;
font-weight: 500;
color: rgba(51, 112, 255, 1) !important;
}

View File

@ -0,0 +1,41 @@
<script lang="ts" setup>
const props = defineProps({
isCollapse: Boolean
})
const emits = defineEmits(['setCollapse'])
const setCollapse = () => {
emits('setCollapse', !props.isCollapse)
}
</script>
<template>
<div class="de-collapse-bar" @click="setCollapse">
<el-icon>
<Icon :name="!isCollapse ? 'icon_side-fold_outlined' : 'icon_side-expand_outlined'"></Icon>
</el-icon>
{{ !isCollapse ? '收起导航' : '' }}
</div>
</template>
<style lang="less" scoped>
.de-collapse-bar {
position: fixed;
cursor: pointer;
z-index: 10;
left: 0;
bottom: 0;
height: 48px;
padding: 14px 22px;
font-size: 14px;
font-weight: 400;
line-height: 22px;
color: #646a73;
display: flex;
align-items: center;
.ed-icon {
font-size: 20px;
margin-right: 10px;
}
}
</style>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { ref, computed } from 'vue'
import { computed } from 'vue'
import { ElMenu } from 'element-plus-secondary'
import { useRoute, useRouter } from 'vue-router'
import { isExternal } from '@/utils/validate'
@ -12,7 +12,10 @@ const tempColor = computed(() => {
(appearanceStore.themeColor === 'custom' ? appearanceStore.customColor : '#3370FF') + '1A'
}
})
const isCollapse = ref(false)
defineProps({
collapse: Boolean
})
const route = useRoute()
const { push } = useRouter()
const menuList = computed(() => route.matched[0]?.children || [])
@ -38,7 +41,7 @@ const menuSelect = (index: string, indexPath: string[]) => {
@select="menuSelect"
:default-active="activeIndex"
class="el-menu-vertical"
:collapse="isCollapse"
:collapse="collapse"
>
<MenuItem v-for="menu in menuList" :key="menu.path" :menu="menu"></MenuItem>
</el-menu>

View File

@ -3,7 +3,7 @@ import { h } from 'vue'
import { Icon } from '@/components/icon-custom'
import { ElMenuItem, ElSubMenu, ElIcon } from 'element-plus-secondary'
const title = props => {
const titleWithIcon = props => {
const { title, icon } = props.menu?.meta || {}
return [
h(ElIcon, null, { default: () => h(Icon, { className: 'logo', name: icon }) }),
@ -21,16 +21,18 @@ const MenuItem = props => {
ElSubMenu,
{ index: path },
{
title: () => title(props),
title: () => titleWithIcon(props),
default: () => children.map(ele => h(MenuItem, { menu: ele }))
}
)
}
const { title, icon } = props.menu?.meta || {}
return h(
ElMenuItem,
{ index: path },
{
title: () => title(props)
title: h('span', null, { default: () => title }),
default: h(ElIcon, null, { default: () => h(Icon, { className: 'logo', name: icon }) })
}
)
}

View File

@ -1,10 +1,11 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import Header from './components/Header.vue'
import HeaderSystem from './components/HeaderSystem.vue'
import Sidebar from './components/Sidebar.vue'
import Menu from './components/Menu.vue'
import Main from './components/Main.vue'
import CollapseBar from './components/CollapseBar.vue'
import { ElContainer } from 'element-plus-secondary'
import { useRoute } from 'vue-router'
const route = useRoute()
@ -12,6 +13,10 @@ const systemMenu = computed(() => route.path.includes('system'))
const settingMenu = computed(() => route.path.includes('sys-setting'))
const marketMenu = computed(() => route.path.includes('template-market'))
const toolboxMenu = computed(() => route.path.includes('toolbox'))
const isCollapse = ref(false)
const setCollapse = () => {
isCollapse.value = !isCollapse.value
}
</script>
<template>
@ -22,10 +27,22 @@ const toolboxMenu = computed(() => route.path.includes('toolbox'))
/>
<Header v-else></Header>
<el-container class="layout-container">
<Sidebar v-if="systemMenu || settingMenu || toolboxMenu" class="layout-sidebar">
<div v-if="systemMenu" class="org-config-center">组织管理中心</div>
<Menu :style="{ height: systemMenu ? 'calc(100% - 48px)' : '100%' }"></Menu>
</Sidebar>
<template v-if="systemMenu || settingMenu || toolboxMenu">
<Sidebar v-if="!isCollapse" class="layout-sidebar">
<div @click="setCollapse" v-if="systemMenu && !isCollapse" class="org-config-center">
组织管理中心
</div>
<Menu :style="{ height: systemMenu ? 'calc(100% - 48px)' : '100%' }"></Menu>
</Sidebar>
<el-aside class="layout-sidebar layout-sidebar-collapse" v-else>
<Menu
:collapse="isCollapse"
:style="{ height: systemMenu ? 'calc(100% - 48px)' : '100%' }"
></Menu>
</el-aside>
<CollapseBar @setCollapse="setCollapse" :isCollapse="isCollapse"></CollapseBar>
</template>
<Main
class="layout-main"
:class="{ 'with-sider': systemMenu || settingMenu || toolboxMenu }"
@ -47,6 +64,20 @@ const toolboxMenu = computed(() => route.path.includes('toolbox'))
.layout-container {
.layout-sidebar {
height: calc(100vh - 56px);
position: relative;
&::after {
content: '';
width: 100%;
height: 1px;
background: #1f232926;
position: absolute;
bottom: 48px;
left: 0;
}
}
.layout-sidebar-collapse {
width: 64px;
}
.org-config-center {

View File

@ -1110,7 +1110,14 @@ export default {
quadrant: '象限',
font_size: '字号',
word_size_range: '字号区间',
word_spacing: '文字间隔'
word_spacing: '文字间隔',
table_layout_mode: '展示形式',
table_layout_grid: '平铺展示',
table_layout_tree: '树形展示',
top_n_desc: '合并数据',
top_n_input_1: '显示 Top',
top_n_input_2: ', 其余合并至其他',
top_n_label: '其他项名称'
},
dataset: {
scope_edit: '仅编辑时生效',
@ -2143,9 +2150,9 @@ export default {
},
pblink: {
key_pwd: '请输入密码打开链接',
input_placeholder: '请输入4位数字或字母',
input_placeholder: '请输入410位数字或字母',
pwd_error: '密码错误',
pwd_format_error: '请输入4位数字或字母',
pwd_format_error: '请输入410位数字或字母',
sure_bt: '确定',
back_parent: '返回上一级'
},
@ -2163,6 +2170,16 @@ export default {
platformOid: '第三方平台用户组织',
platformRid: '第三方平台用户角色'
},
setting_email: {
title: '邮件设置',
host: 'SMTP主机',
port: 'SMTP端口',
account: 'SMTP账号',
pwd: 'SMTP密码',
reci: '测试收件人',
ssl: 'SSL',
tsl: 'TSL'
},
template_manage: {
name_already_exists_type: '分类名称已存在',
the_same_category: '同一分类下该模板名称已存在'
@ -2250,5 +2267,42 @@ export default {
appearance: {
give_up: '放弃更新',
save_apply: '保存并应用'
},
report: {
title: '定时报告',
task_name: '任务名称',
last_exec_time: '上次执行时间',
last_exec_result: '上次执行结果',
task_status: '任务状态',
next_exec_time: '下次执行时间',
creator: '创建人',
create_time: '创建时间',
status_wait: '等待发送',
status_stop: '任务停止',
status_finish: '任务结束',
status_send: '发送中',
search_tips: '通过任务名称搜索',
report_title: '任务列表',
instance_title: '任务日志',
add_task: '添加任务',
lark_groups: '飞书群',
send_setting: '发送设置',
start_time: '开始时间',
end_time: '结束时间',
once_a_day: '每天',
once_a_week: '每周',
once_a_month: '每月',
week_mon: '一',
week_tue: '二',
week_wed: '三',
week_thu: '四',
week_fri: '五',
week_sat: '六',
week_sun: '日',
every_exec: '执行一次',
date: '日',
last_status_running: '运行中',
last_status_fail: '失败',
last_status_success: '成功'
}
}

View File

@ -96,6 +96,10 @@ declare interface ChartBasicStyle {
* 表格分页大小
*/
tablePageSize: number
/**
* 表格展示形式,平铺和树形
*/
tableLayoutMode: 'grid' | 'tree'
/**
* 仪表盘样式
*/
@ -213,6 +217,18 @@ declare interface ChartBasicStyle {
* 地图缩放按钮背景颜色
*/
zoomBackground: string
/**
* 是否合并数据为其他
*/
calcTopN: boolean
/**
* 只展示 TopN 其他合并为一项
*/
topN: number
/**
* 其他项的标签
*/
topNLabel: string
}
/**
* 表头属性
@ -694,6 +710,8 @@ declare interface MapCfg {
* 象限属性
*/
declare interface QuadrantAttr {
xBaseline?: number
yBaseline?: number
lineStyle: QuadrantLineStyle
regionStyle: QuadrantCommonStyle[]
labels: QuadrantLabelConf[]

View File

@ -28,6 +28,11 @@ declare type EditorPropertyInner = {
[key in EditorProperty]?: string[]
}
declare type EditorSelectorSpec = {
[key in EditorProperty]?: {
title: string
}
}
/**
* 轴类型
*/

View File

@ -7,7 +7,7 @@ import { usePermissionStoreWithOut, pathValid, getFirstAuthMenu } from '@/store/
import { usePageLoading } from '@/hooks/web/usePageLoading'
import { getRoleRouters } from '@/api/common'
import { useCache } from '@/hooks/web/useCache'
import { isMobile } from '@/utils/utils'
import { isMobile, checkPlatform } from '@/utils/utils'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { useEmbedded } from '@/store/modules/embedded'
@ -28,6 +28,7 @@ const embeddedRouteWhiteList = ['/dataset-embedded', '/dataset-form', '/dataset-
router.beforeEach(async (to, from, next) => {
start()
loadStart()
checkPlatform()
if (isMobile()) {
done()
loadDone()

View File

@ -2,22 +2,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import type { App } from 'vue'
export const routes: AppRouteRecordRaw[] = [
{
path: '/dvCanvas',
name: 'dvCanvas',
hidden: true,
meta: {},
component: () => import('@/views/data-visualization/index.vue')
},
{
path: '/dashboard',
name: 'dashboard',
hidden: true,
meta: {},
component: () => import('@/views/dashboard/index.vue')
}
]
export const routes: AppRouteRecordRaw[] = []
const router = createRouter({
history: createWebHashHistory(),

View File

@ -1,5 +1,7 @@
import { BusiTreeNode } from '@/models/tree/TreeNode'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export function deepCopy(target) {
if (target === null || target === undefined) {
return target
@ -87,6 +89,24 @@ export const isPlatformClient = () => {
return !!getQueryString('client') || getQueryString('state')?.includes('client')
}
export const checkPlatform = () => {
const flagArray = ['/casbi', 'oidcbi']
const pathname = window.location.pathname
if (
!flagArray.some(flag => pathname.includes(flag)) &&
!isLarkPlatform() &&
!isPlatformClient()
) {
return cleanPlatformFlag()
}
return true
}
export const cleanPlatformFlag = () => {
const platformKey = 'out_auth_platform'
wsCache.delete(platformKey)
return false
}
export function isMobile() {
return (
navigator.userAgent.match(

View File

@ -68,10 +68,18 @@ const props = defineProps({
default: () => {
return {}
}
},
selectorSpec: {
type: Object as PropType<EditorSelectorSpec>,
required: false,
default: () => {
return {}
}
}
})
const { chart, themes, properties, propertyInnerAll, commonBackgroundPop } = toRefs(props)
const { chart, themes, properties, propertyInnerAll, commonBackgroundPop, selectorSpec } =
toRefs(props)
const emit = defineEmits([
'onColorChange',
'onMiscChange',
@ -316,7 +324,7 @@ watch(
:effect="themes"
v-if="showProperties('misc-style-selector')"
name="size"
title="大小"
:title="selectorSpec['misc-style-selector']?.title"
>
<misc-style-selector
:property-inner="propertyInnerAll['misc-style-selector']"

View File

@ -2,7 +2,6 @@
import { useI18n } from '@/hooks/web/useI18n'
import { toRefs } from 'vue'
import { COLOR_PANEL } from '@/views/chart/components/editor/util/chart'
import CollapseSwitchItem from '@/components/collapse-switch-item/src/CollapseSwitchItem.vue'
const { t } = useI18n()
const state = {
@ -24,13 +23,8 @@ const { chart } = toRefs(props)
<div class="attr-style">
<el-row class="de-collapse-style">
<el-collapse v-model="state.styleActiveNames" class="style-collapse">
<collapse-switch-item
themes="light"
v-model="chart.customStyle.component.show"
name="component"
:title="t('visualization.module')"
>
<el-form label-position="top" :disabled="!chart.customStyle.component.show">
<el-collapse-item themes="light" name="component" :title="t('visualization.module')">
<el-form label-position="top">
<el-form-item class="form-item margin-bottom-8">
<el-checkbox size="small" v-model="chart.customStyle.component.titleShow">
{{ t('chart.show') + t('chart.title') }}
@ -158,7 +152,7 @@ const { chart } = toRefs(props)
</el-radio-group>
</el-form-item>
</el-form>
</collapse-switch-item>
</el-collapse-item>
</el-collapse>
</el-row>
</div>

View File

@ -5,7 +5,11 @@ import { useI18n } from '@/hooks/web/useI18n'
import CustomColorStyleSelect from '@/views/chart/components/editor/editor-style/components/CustomColorStyleSelect.vue'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { SERIES_NUMBER_FIELD } from '@antv/s2'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { t } = useI18n()
const props = defineProps({
chart: {
@ -198,6 +202,23 @@ onMounted(() => {
</el-checkbox>
</el-form-item>
<el-form-item
class="form-item"
v-if="showProperty('tableLayoutMode')"
:label="t('chart.table_layout_mode')"
:class="'form-item-' + themes"
>
<el-radio-group
size="small"
:effect="themes"
v-model="state.basicStyleForm.tableLayoutMode"
@change="changeBasicStyle('tableLayoutMode')"
>
<el-radio label="grid">{{ t('chart.table_layout_grid') }}</el-radio>
<el-radio label="tree">{{ t('chart.table_layout_tree') }}</el-radio>
</el-radio-group>
</el-form-item>
<div class="alpha-setting" v-if="showProperty('alpha')">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.not_alpha') }}
@ -449,6 +470,7 @@ onMounted(() => {
<el-select
v-model="state.fieldColumnWidth.fieldId"
:effect="themes"
:disabled="batchOptStatus"
@change="changeFieldColumn()"
>
<el-option
@ -465,6 +487,7 @@ onMounted(() => {
:min="0"
:max="100"
:effect="themes"
:disabled="batchOptStatus"
@change="changeFieldColumnWidth()"
>
<template #append>%</template>
@ -745,6 +768,46 @@ onMounted(() => {
</el-form-item>
<!-- pie/rose start -->
<div v-show="showProperty('topN')" class="top-n-setting">
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox v-model="state.basicStyleForm.calcTopN" @change="changeBasicStyle('calcTopN')">
{{ $t('chart.top_n_desc') }}
</el-checkbox>
</el-form-item>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
v-show="state.basicStyleForm.calcTopN"
>
<span>{{ $t('chart.top_n_input_1') }}</span>
<el-input-number
v-model="state.basicStyleForm.topN"
controls-position="right"
size="small"
:min="1"
:max="100"
:precision="0"
:step-strictly="true"
:value-on-clear="5"
@change="changeBasicStyle('topN')"
/>
<span>{{ $t('chart.top_n_input_2') }}</span>
</el-form-item>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.top_n_label')"
v-show="state.basicStyleForm.calcTopN"
>
<el-input
:effect="themes"
v-model="state.basicStyleForm.topNLabel"
size="small"
:maxlength="50"
@change="changeBasicStyle('topNLabel')"
/>
</el-form-item>
</div>
<div class="alpha-setting" v-if="showProperty('innerRadius')">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.pie_inner_radius_percent') }}
@ -888,4 +951,13 @@ onMounted(() => {
}
}
}
.top-n-setting {
.ed-input-number {
width: 80px !important;
margin: 0 2px;
}
:deep(span) {
font-size: 12px;
}
}
</style>

View File

@ -1,8 +1,13 @@
<script lang="tsx" setup>
import { computed, onMounted, PropType, reactive, watch } from 'vue'
import { computed, inject, onMounted, PropType, reactive, ref, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL, DEFAULT_QUADRANT_STYLE } from '@/views/chart/components/editor/util/chart'
import { useEmitt } from '@/hooks/web/useEmitt'
useEmitt({
name: 'quadrant-default-baseline',
callback: args => quadrantDefaultBaseline(args)
})
const quotaData = ref<Axis[]>(inject('quotaData'))
const { t } = useI18n()
const props = defineProps({
@ -63,6 +68,10 @@ const fillOpacityList = computed(() => {
const changeStyle = () => {
emit('onChangeQuadrantForm', state.quadrantForm)
}
const quadrantDefaultBaseline = quadrant => {
state.quadrantForm.xBaseline = quadrant.xBaseline
state.quadrantForm.yBaseline = quadrant.yBaseline
}
const init = () => {
const chart = JSON.parse(JSON.stringify(props.chart))
@ -136,6 +145,42 @@ onMounted(() => {
</el-tooltip>
</el-form-item>
</div>
<div style="display: flex">
<el-form-item
class="form-item"
label="X 轴恒线"
:class="'form-item-' + themes"
style="padding-left: 4px"
>
<el-input-number
controls-position="right"
style="width: 100%"
:effect="props.themes"
v-model="state.quadrantForm.xBaseline"
:precision="0"
:min="0"
size="small"
@change="changeStyle()"
/>
</el-form-item>
<el-form-item
class="form-item"
label="Y 轴恒线"
:class="'form-item-' + themes"
style="padding-left: 4px"
>
<el-input-number
controls-position="right"
style="width: 100%"
:effect="props.themes"
v-model="state.quadrantForm.yBaseline"
:precision="0"
:min="0"
size="small"
@change="changeStyle()"
/>
</el-form-item>
</div>
</template>
<div
v-for="(l, index) in state.quadrantForm.labels"

View File

@ -1394,7 +1394,7 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
const obj = cloneDeep(arr[i])
state.moveId = obj.id as unknown as number
view.value[type].push(obj)
const e = { newDraggableIndex: view.value.xAxis.length - 1 }
const e = { newDraggableIndex: view.value[type].length - 1 }
if ('drillFields' === type) {
addDrill(e)
} else if (type === 'customFilter') {
@ -2074,6 +2074,7 @@ const drop = (ev: MouseEvent, type = 'xAxis') => {
v-if="chartStyleShow"
:properties="chartViewInstance.properties"
:property-inner-all="chartViewInstance.propertyInner"
:selector-spec="chartViewInstance.selectorSpec"
:common-background-pop="curComponent?.commonBackground"
:chart="view"
:themes="themes"

View File

@ -1382,7 +1382,11 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = {
innerRadius: 60,
showZoom: true,
zoomButtonColor: '#aaa',
zoomBackground: '#fff'
zoomBackground: '#fff',
tableLayoutMode: 'grid',
calcTopN: false,
topN: 5,
topNLabel: '其他'
}
export const BASE_VIEW_CONFIG = {

View File

@ -50,14 +50,15 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
drawChart(drawOptions: G2PlotDrawOptions<G2Liquid>): G2Liquid {
const { chart, container } = drawOptions
if (chart?.data) {
const initOptions: LiquidOptions = {
percent: 0
}
const options = this.setupOptions(chart, initOptions)
// 开始渲染
return new G2Liquid(container, options)
if (!chart.data?.series) {
return
}
const initOptions: LiquidOptions = {
percent: 0
}
const options = this.setupOptions(chart, initOptions)
// 开始渲染
return new G2Liquid(container, options)
}
protected configTheme(chart: Chart, options: LiquidOptions): LiquidOptions {

View File

@ -19,6 +19,7 @@ export const CHART_MIX_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
'basic-style-selector': [
'colors',
'alpha',
'gradient',
'lineWidth',
'lineSymbol',
'lineSymbolSize',

View File

@ -3,7 +3,14 @@ import {
G2PlotDrawOptions
} from '@/views/chart/components/js/panel/types/impl/g2plot'
import { DualAxes, DualAxesOptions } from '@antv/g2plot/esm/plots/dual-axes'
import { getAnalyse, getLabel, getPadding, getYAxis, getYAxisExt } from '../../common/common_antv'
import {
getAnalyse,
getLabel,
getPadding,
getYAxis,
getYAxisExt,
setGradientColor
} from '../../common/common_antv'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { cloneDeep, isEmpty, defaultTo, map, filter } from 'lodash-es'
import { valueFormatter } from '@/views/chart/components/js/formatter'
@ -63,20 +70,31 @@ export class ColumnLineMix extends G2PlotChartView<DualAxesOptions, DualAxes> {
})
// custom color
const customAttr = parseJson(chart.customAttr)
const color = customAttr.basicStyle.colors
let color = customAttr.basicStyle.colors
color = color.map(ele => {
const tmp = hexColorToRGBA(ele, customAttr.basicStyle.alpha)
if (customAttr.basicStyle.gradient) {
return setGradientColor(tmp, true, 270)
} else {
return tmp
}
})
// options
const initOptions: DualAxesOptions = {
data: [data1, data2],
xField: 'field',
yField: ['value', 'valueExt'], //这里不能设置成一样的
appendPadding: getPadding(chart),
color,
geometryOptions: [
{
geometry: data1Type
geometry: data1Type,
color: color[0]
},
{
geometry: data2Type
geometry: data2Type,
color: color[1]
}
],
interactions: [

View File

@ -65,40 +65,41 @@ export class Gauge extends G2PlotChartView<GaugeOptions, G2Gauge> {
drawChart(drawOptions: G2PlotDrawOptions<G2Gauge>): G2Gauge {
const { chart, container, scale } = drawOptions
if (chart?.data) {
// options
const initOptions: GaugeOptions = {
percent: 0,
appendPadding: getPadding(chart),
axis: {
tickInterval: 0.2,
label: {
style: {
fontSize: getScaleValue(12, scale) // 刻度值字体大小
},
formatter: function (v) {
const r = parseFloat(v)
return v === '0' || !r ? v : r * 100 + '%'
}
if (!chart.data?.series) {
return
}
// options
const initOptions: GaugeOptions = {
percent: 0,
appendPadding: getPadding(chart),
axis: {
tickInterval: 0.2,
label: {
style: {
fontSize: getScaleValue(12, scale) // 刻度值字体大小
},
tickLine: {
length: getScaleValue(12, scale) * -1, // 刻度线长度
style: {
lineWidth: getScaleValue(1, scale) // 刻度线宽度
}
},
subTickLine: {
count: 4, // 子刻度数
length: getScaleValue(6, scale) * -1, // 子刻度线长度
style: {
lineWidth: getScaleValue(1, scale) // 子刻度线宽度
}
formatter: function (v) {
const r = parseFloat(v)
return v === '0' || !r ? v : r * 100 + '%'
}
},
tickLine: {
length: getScaleValue(12, scale) * -1, // 刻度线长度
style: {
lineWidth: getScaleValue(1, scale) // 刻度线宽度
}
},
subTickLine: {
count: 4, // 子刻度数
length: getScaleValue(6, scale) * -1, // 子刻度线长度
style: {
lineWidth: getScaleValue(1, scale) // 子刻度线宽度
}
}
}
const options = this.setupOptions(chart, initOptions, scale)
return new G2Gauge(container, options)
}
const options = this.setupOptions(chart, initOptions, scale)
return new G2Gauge(container, options)
}
protected configMisc(chart: Chart, options: GaugeOptions): GaugeOptions {

View File

@ -7,7 +7,6 @@ import { flow, parseJson } from '../../../util'
import { valueFormatter } from '../../../formatter'
import { useI18n } from '@/hooks/web/useI18n'
import { isEmpty } from 'lodash-es'
import { Datum } from '@antv/g2plot/esm/types/common'
import { DEFAULT_QUADRANT_STYLE } from '@/views/chart/components/editor/util/chart'
const { t } = useI18n()
@ -31,7 +30,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
]
propertyInner: EditorPropertyInner = {
'basic-style-selector': ['colors', 'alpha', 'scatterSymbol', 'scatterSymbolSize'],
'label-selector': ['fontSize', 'color', 'labelFormatter'],
'label-selector': ['fontSize', 'color'],
'tooltip-selector': ['fontSize', 'color', 'backgroundColor', 'seriesTooltipFormatter'],
'x-axis-selector': [
'position',
@ -118,7 +117,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
}
public drawChart(drawOptions: G2PlotDrawOptions<G2Scatter>) {
const { chart, container, action } = drawOptions
const { chart, container, action, quadrantDefaultBaseline } = drawOptions
if (!chart.data?.data) {
return
}
@ -150,6 +149,8 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
const dataLength = chart.data?.data.length / chart.data?.fields.length
for (let index = 0; index < dataLength; index++) {
const tmpData = {
dimensionList: groupedData[xFieldObj.name][index].dimensionList,
quotaList: groupedData[xFieldObj.name][index].quotaList,
[xFieldObj.name]: groupedData[xFieldObj.name][index].value
}
if (groupedData[yFieldObj.name]) {
@ -167,21 +168,41 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
}
data.push(tmpData)
}
chart.customAttr['quadrant'].xBaseline = (
data.reduce((valueSoFar, currentItem) => {
return valueSoFar + currentItem[xFieldObj.name]
}, 0) / data.length
// x轴基准线 默认值
const xBaseline = (
(data.reduce((valueSoFar, currentItem) => {
return Math.max(valueSoFar, currentItem[xFieldObj.name])
}, 0) +
data.reduce((valueSoFar, currentItem) => {
return Math.min(valueSoFar, currentItem[xFieldObj.name])
}, Infinity)) /
2
).toFixed()
chart.customAttr['quadrant'].yBaseline = (
data.reduce((valueSoFar, currentItem) => {
return valueSoFar + currentItem[yFieldObj.name]
}, 0) / data.length
// y轴基准线 默认值
const yBaseline = (
(data.reduce((valueSoFar, currentItem) => {
return Math.max(valueSoFar, currentItem[yFieldObj.name])
}, 0) +
data.reduce((valueSoFar, currentItem) => {
return Math.min(valueSoFar, currentItem[yFieldObj.name])
}, Infinity)) /
2
).toFixed()
const defaultBaselineQuadrant = {
...chart.customAttr['quadrant']
}
// 新建图表
if (!defaultBaselineQuadrant.xBaseline) {
// 默认基准线值
defaultBaselineQuadrant.xBaseline = xBaseline
defaultBaselineQuadrant.yBaseline = yBaseline
}
const colorField = colorFieldObj.name ? { colorField: colorFieldObj.name } : {}
const quadrant = chart.customAttr['quadrant'] ? { quadrant: chart.customAttr['quadrant'] } : {}
const baseOptions: ScatterOptions = {
...colorField,
...quadrant,
quadrant: {
...defaultBaselineQuadrant
},
data: data,
xField: xFieldObj.name,
yField: yFieldObj.name,
@ -195,6 +216,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
const options = this.setupOptions(chart, baseOptions)
const newChart = new G2Scatter(container, options)
newChart.on('point:click', action)
newChart.on('click', () => quadrantDefaultBaseline(defaultBaselineQuadrant))
return newChart
}
@ -205,7 +227,7 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
if (chart.extBubble?.length) {
return {
...options,
size: [5, 30],
size: [4, 30],
sizeField: extBubbleObj.name,
shape: basicStyle.scatterSymbol
}
@ -283,18 +305,15 @@ export class Quadrant extends G2PlotChartView<ScatterOptions, G2Scatter> {
const l = customAttr.label
if (l.show) {
label = {
position: l.position,
offsetY: 5,
offset: 0,
style: {
fill: l.color,
fontSize: l.fontSize
},
formatter: function (param: Datum, item) {
const text = String(param[chart.xAxis[0]?.['originName']])
const radius = item.size
const textWidth = text.length * 10
return textWidth > 2 * radius ? '' : param[chart.xAxis[0]?.['originName']]
}
content: datum => {
return datum[chart.xAxis[0]?.['originName']]
},
layout: [{ type: 'limit-in-shape' }]
}
} else {
label = false

View File

@ -41,6 +41,11 @@ export class Radar extends G2PlotChartView<RadarOptions, G2Radar> {
],
'legend-selector': ['icon', 'orient', 'color', 'fontSize', 'hPosition', 'vPosition']
}
selectorSpec: EditorSelectorSpec = {
'misc-style-selector': {
title: `${t('chart.tooltip_axis')}`
}
}
axis: AxisType[] = ['xAxis', 'yAxis', 'drill', 'filter', 'extLabel', 'extTooltip']
axisConfig: AxisConfig = {
xAxis: {

View File

@ -23,7 +23,10 @@ const DEFAULT_DATA = []
export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
axis: AxisType[] = PIE_AXIS_TYPE
properties = PIE_EDITOR_PROPERTY
propertyInner = PIE_EDITOR_PROPERTY_INNER
propertyInner: EditorPropertyInner = {
...PIE_EDITOR_PROPERTY_INNER,
'basic-style-selector': ['colors', 'alpha', 'radius', 'topN']
}
axisConfig = PIE_AXIS_CONFIG
drawChart(drawOptions: G2PlotDrawOptions<G2Pie>): G2Pie {
@ -212,9 +215,37 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
protected configBasicStyle(chart: Chart, options: PieOptions): PieOptions {
const customAttr = parseJson(chart.customAttr)
const { basicStyle } = customAttr
const { data } = options
if (data?.length && basicStyle.calcTopN && data.length > basicStyle.topN) {
data.sort((a, b) => b.value - a.value)
const otherItems = data.splice(basicStyle.topN)
const initOtherItem = {
...data[0],
dynamicTooltipValue: [],
field: basicStyle.topNLabel,
name: basicStyle.topNLabel,
value: 0
}
const dynamicTotalMap: Record<string, number> = {}
otherItems.reduce((p, n) => {
p.value += n.value ?? 0
n.dynamicTooltipValue?.forEach(val => {
dynamicTotalMap[val.fieldId] = (dynamicTotalMap[val.fieldId] || 0) + val.value
})
return p
}, initOtherItem)
for (const key in dynamicTotalMap) {
initOtherItem.dynamicTooltipValue.push({
fieldId: key,
value: dynamicTotalMap[key]
})
}
data.push(initOtherItem)
}
return {
...options,
radius: customAttr.basicStyle.radius / 100
radius: basicStyle.radius / 100
}
}
setupDefaultOptions(chart: ChartObj): ChartObj {
@ -253,14 +284,15 @@ export class Pie extends G2PlotChartView<PieOptions, G2Pie> {
export class PieDonut extends Pie {
propertyInner: EditorPropertyInner = {
...PIE_EDITOR_PROPERTY_INNER,
'basic-style-selector': ['colors', 'alpha', 'radius', 'innerRadius']
'basic-style-selector': ['colors', 'alpha', 'radius', 'innerRadius', 'topN']
}
protected configBasicStyle(chart: Chart, options: PieOptions): PieOptions {
const customAttr = parseJson(chart.customAttr)
const tmp = super.configBasicStyle(chart, options)
const { basicStyle } = parseJson(chart.customAttr)
return {
...options,
radius: customAttr.basicStyle.radius / 100,
innerRadius: customAttr.basicStyle.innerRadius / 100
...tmp,
radius: basicStyle.radius / 100,
innerRadius: basicStyle.innerRadius / 100
}
}

View File

@ -1,5 +1,5 @@
import { S2ChartView, S2DrawOptions } from '@/views/chart/components/js/panel/types/impl/s2'
import { S2Event, S2Options, TableSheet, TableColCell, ViewMeta } from '@antv/s2'
import { S2Event, S2Options, TableSheet, TableColCell, ViewMeta, TableDataCell } from '@antv/s2'
import { parseJson } from '@/views/chart/components/js/util'
import { formatterItem, valueFormatter } from '@/views/chart/components/js/formatter'
import { copyContent, getCurrentField } from '@/views/chart/components/js/panel/common/common_table'
@ -128,6 +128,9 @@ export class TableNormal extends S2ChartView<TableSheet> {
}
return new TableColCell(node, sheet, config)
}
s2Options.dataCell = viewMeta => {
return new TableDataCell(viewMeta, viewMeta?.spreadsheet)
}
}
// tooltip
this.configTooltip(s2Options)
@ -167,7 +170,7 @@ export class TableNormal extends S2ChartView<TableSheet> {
// hover
const { showColTooltip } = customAttr.tableHeader
if (showColTooltip) {
newChart.on(S2Event.COL_CELL_HOVER, event => this.showTooltip(newChart, event))
newChart.on(S2Event.COL_CELL_HOVER, event => this.showTooltip(newChart, event, meta))
}
const { showTooltip } = customAttr.tableCell
if (showTooltip) {

View File

@ -37,7 +37,13 @@ export class TablePivot extends S2ChartView<PivotSheet> {
'showRowTooltip'
],
'table-total-selector': ['row', 'col'],
'basic-style-selector': ['tableColumnMode', 'tableBorderColor', 'tableScrollBarColor', 'alpha']
'basic-style-selector': [
'tableColumnMode',
'tableBorderColor',
'tableScrollBarColor',
'alpha',
'tableLayoutMode'
]
}
axis: AxisType[] = ['xAxis', 'xAxisExt', 'yAxis', 'filter']
axisConfig: AxisConfig = {
@ -59,7 +65,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
const { container, chart, chartObj, action } = drawOption
const containerDom = document.getElementById(container)
const { xAxis: columnFields, xAxisExt: rowFields, yAxis: valueFields } = chart
const { xAxisExt: columnFields, xAxis: rowFields, yAxis: valueFields } = chart
const [c, r, v] = [columnFields, rowFields, valueFields].map(arr =>
arr.map(i => i.dataeaseName)
)
@ -104,7 +110,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
// total config
const customAttr = parseJson(chart.customAttr)
const { tableTotal } = customAttr
const { tableTotal, basicStyle } = customAttr
tableTotal.row.subTotalsDimensions = r
tableTotal.col.subTotalsDimensions = c
@ -182,7 +188,8 @@ export class TablePivot extends S2ChartView<PivotSheet> {
height: containerDom.offsetHeight,
style: this.configStyle(chart),
totals: tableTotal,
conditions: this.configConditions(chart)
conditions: this.configConditions(chart),
hierarchyType: basicStyle.tableLayoutMode ?? 'grid'
}
// 开始渲染

View File

@ -273,9 +273,7 @@ export function getLegend(chart: Chart) {
offsetY = 0
} else if (l.vPosition === 'bottom') {
if (chart.drill) {
offsetY = -16
} else {
offsetY = -4
offsetY = -12
}
} else {
offsetY = 0
@ -292,9 +290,9 @@ export function getLegend(chart: Chart) {
offsetY = 0
} else if (l.vPosition === 'bottom') {
if (chart.drill) {
offsetY = -22
offsetY = -18
} else {
offsetY = -10
offsetY = -6
}
} else {
offsetY = 0
@ -309,6 +307,7 @@ export function getLegend(chart: Chart) {
marker: {
symbol: legendSymbol
},
itemHeight: l.fontSize + 4,
radio: false,
pageNavigator: {
marker: {

View File

@ -25,6 +25,11 @@ export interface G2PlotDrawOptions<O> extends AntVDrawOptions<O> {
* 缩放比例
*/
scale?: number
/**
* 特殊处理象限图设置分割线的默认值
* @param args
*/
quadrantDefaultBaseline?: (...args: any) => void
}
/**

View File

@ -26,6 +26,7 @@ export abstract class AbstractChartView {
abstract propertyInner: EditorPropertyInner
abstract axis: AxisType[]
abstract axisConfig: AxisConfig
abstract selectorSpec: EditorSelectorSpec
/**
* 在新建和切换图表的时候处理默认值
* @param chart 数据库图表对象
@ -91,6 +92,11 @@ export abstract class AntVAbstractChartView extends AbstractChartView {
limit: 1
}
}
selectorSpec: EditorSelectorSpec = {
'misc-style-selector': {
title: `${t('chart.size')}`
}
}
protected constructor(library: ChartLibraryType, name: string, defaultData?: any[]) {
super(ChartRenderType.ANT_V, library, name, defaultData)
}

View File

@ -16,10 +16,11 @@ import { BASE_VIEW_CONFIG } from '../../editor/util/chart'
import { customAttrTrans, customStyleTrans, recursionTransObj } from '@/utils/canvasStyle'
import { deepCopy } from '@/utils/utils'
import { trackBarStyleCheck } from '@/utils/canvasUtils'
import { useEmitt } from '@/hooks/web/useEmitt'
const dvMainStore = dvMainStoreWithOut()
const { nowPanelTrackInfo, nowPanelJumpInfo, mobileInPc } = storeToRefs(dvMainStore)
const { emitter } = useEmitt()
const props = defineProps({
element: {
type: Object,
@ -149,7 +150,8 @@ const renderG2Plot = (chart, chartView: G2PlotChartView<any, any>) => {
container: containerId,
chart: chart,
scale: 1,
action
action,
quadrantDefaultBaseline
})
myChart?.render()
}
@ -296,7 +298,9 @@ const trackMenu = computed(() => {
}
return trackMenuInfo
})
const quadrantDefaultBaseline = defaultQuadrant => {
emitter.emit('quadrant-default-baseline', defaultQuadrant)
}
defineExpose({
calcData,
renderChart,

View File

@ -189,6 +189,7 @@ const initScroll = () => {
const senior = actualChart.senior
if (
senior?.scrollCfg?.open &&
chartData.value.tableRow?.length &&
(view.value.type === 'table-normal' || (view.value.type === 'table-info' && !state.showPage))
) {
//

View File

@ -128,7 +128,8 @@ const state = reactive({
fontSynthesis: 'style weight',
width: 'fit-content',
maxWidth: '100%',
wordBreak: 'break-word'
wordBreak: 'break-word',
whiteSpace: 'pre-wrap'
} as CSSProperties,
drillFilters: [],
drillClickDimensionList: []
@ -589,7 +590,7 @@ const marginBottom = computed<string | 0>(() => {
if (titleShow.value || trackMenu.value.length > 0 || state.title_remark.show) {
return 12 * scale.value + 'px'
}
return 12
return 0
})
const iconSize = computed<string>(() => {

View File

@ -117,22 +117,15 @@ const resourceTypeList = computed(() => {
label: '使用模板新建',
svgName: 'dv-use-template',
command: 'newFromTemplate'
},
{
label: '新建文件夹',
divided: true,
svgName: 'dv-folder',
command: 'newFolder'
}
]
const del = {
label: '新建文件夹',
divided: true,
svgName: 'dv-folder',
command: 'newFolder'
}
if (isDataEaseBi.value) {
del.divided = false
return [del]
}
return [...list, del]
return list
})
const menuList = computed(() => {
@ -152,10 +145,7 @@ const menuList = computed(() => {
command: 'delete',
svgName: 'dv-delete',
divided: true
}
]
const edit = [
},
{
label: '编辑',
command: 'edit',
@ -167,12 +157,7 @@ const menuList = computed(() => {
svgName: 'dv-copy-dark'
}
]
if (isDataEaseBi.value) {
return list
}
return [...list, ...edit]
return list
})
const dvId = embeddedStore.dvId || router.currentRoute.value.query.dvId
@ -298,7 +283,11 @@ const operation = (cmd: string, data: BusiTreeNode, nodeType: string) => {
curCanvasType.value === 'dataV'
? `#/dvCanvas?opt=copy&pid=${params.pid}&dvId=${data.data}`
: `#/dashboard?opt=copy&pid=${params.pid}&resourceId=${data.data}`
const newWindow = window.open(baseUrl, '_blank')
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
}
const newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
initOpenHandler(newWindow)
})
} else {
@ -345,7 +334,11 @@ function createNewObject() {
const resourceEdit = resourceId => {
const baseUrl = curCanvasType.value === 'dataV' ? '#/dvCanvas?dvId=' : '#/dashboard?resourceId='
const newWindow = window.open(baseUrl + resourceId, '_blank')
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
}
const newWindow = window.open(embeddedBaseUrl + baseUrl + resourceId, '_blank')
initOpenHandler(newWindow)
}
@ -361,10 +354,14 @@ const resourceCreateFinish = templateData => {
? '#/dvCanvas?opt=create&createType=template'
: '#/dashboard?opt=create&createType=template'
let newWindow = null
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
}
if (state.templateCreatePid) {
newWindow = window.open(baseUrl + `&pid=${state.templateCreatePid}`, '_blank')
newWindow = window.open(embeddedBaseUrl + baseUrl + `&pid=${state.templateCreatePid}`, '_blank')
} else {
newWindow = window.open(baseUrl, '_blank')
newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
}
initOpenHandler(newWindow)
}
@ -580,7 +577,7 @@ defineExpose({
>
<el-icon
v-on:click.stop
v-if="data.leaf && !isDataEaseBi"
v-if="data.leaf"
class="hover-icon"
@click="resourceEdit(data.id)"
>

View File

@ -27,7 +27,7 @@ const timestampFormatDate = value => {
if (!value) {
return '-'
}
return new Date(value)['format']()
return new Date(value).toLocaleString()
}
</script>

View File

@ -100,10 +100,14 @@ const onMobileConfig = () => {
}
const loadFinish = ref(false)
const newWindowFromDiv = ref(false)
let p = null
const XpackLoaded = () => p(true)
//
onMounted(async () => {
if (window.location.hash.includes('#/dashboard')) {
newWindowFromDiv.value = true
}
await new Promise(r => (p = r))
loadFinish.value = true
useEmitt({
@ -185,7 +189,7 @@ onUnmounted(() => {
<template>
<div
class="dv-common-layout dv-teleport-query"
:class="isDataEaseBi && 'dataease-w-h'"
:class="isDataEaseBi && !newWindowFromDiv && 'dataease-w-h'"
v-if="loadFinish && !mobileConfig"
>
<DbToolbar />

View File

@ -208,10 +208,13 @@ const checkPer = async resourceId => {
}
const loadFinish = ref(false)
const newWindowFromDiv = ref(false)
let p = null
const XpackLoaded = () => p(true)
onMounted(async () => {
if (window.location.hash.includes('#/dvCanvas')) {
newWindowFromDiv.value = true
}
await new Promise(r => (p = r))
loadFinish.value = true
window.addEventListener('blur', releaseAttachKey)
@ -311,7 +314,11 @@ eventBus.on('handleNew', handleNew)
</script>
<template>
<div ref="dvLayout" class="dv-common-layout" :class="isDataEaseBi && 'dataease-w-h'">
<div
ref="dvLayout"
class="dv-common-layout"
:class="isDataEaseBi && !newWindowFromDiv && 'dataease-w-h'"
>
<DvToolbar />
<div class="custom-dv-divider" />
<el-container

View File

@ -16,7 +16,7 @@ import { XpackComponent } from '@/components/plugin'
import { logoutHandler } from '@/utils/logout'
import DeImage from '@/assets/login-desc-de.png'
import elementResizeDetectorMaker from 'element-resize-detector'
import { isLarkPlatform, isPlatformClient } from '@/utils/utils'
import { checkPlatform, cleanPlatformFlag } from '@/utils/utils'
import xss from 'xss'
const { wsCache } = useCache()
const appStore = useAppStoreWithOut()
@ -134,23 +134,6 @@ const showLoginImage = computed<boolean>(() => {
return !(loginContainerWidth.value < 889)
})
const checkPlatform = () => {
const flagArray = ['/casbi', 'oidcbi']
const pathname = window.location.pathname
if (
!flagArray.some(flag => pathname.includes(flag)) &&
!isLarkPlatform() &&
!isPlatformClient()
) {
cleanPlatformFlag()
}
}
const cleanPlatformFlag = () => {
const platformKey = 'out_auth_platform'
wsCache.delete(platformKey)
preheat.value = false
}
const preheat = ref(true)
const showLoginErrorMsg = () => {
if (!loginErrorMsg.value) {
@ -219,7 +202,9 @@ const loadArrearance = () => {
}
onMounted(() => {
loadArrearance()
checkPlatform()
if (!checkPlatform()) {
preheat.value = false
}
if (localStorage.getItem('DE-GATEWAY-FLAG')) {
const msg = localStorage.getItem('DE-GATEWAY-FLAG')
loginErrorMsg.value = decodeURIComponent(msg)
@ -272,7 +257,7 @@ onMounted(() => {
<img v-if="loginLogoUrl && axiosFinished" :src="loginLogoUrl" alt="" />
</div>
<div class="login-welcome">
{{ slogan || '欢迎使用 DataEase 数据可视化分析平台' }}
{{ slogan || '欢迎使用 DataEase 数据可视化分析工具' }}
</div>
<div class="login-form">
<el-tabs v-model="activeName" @tab-click="handleClick" class="default-login-tabs">

View File

@ -67,7 +67,7 @@ const rule = reactive<FormRules>({
{ required: true, message: t('pblink.key_pwd'), trigger: 'blur' },
{
required: true,
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/,
pattern: /^[A-Za-z\d!@#$%^&*()_+]{4,10}$/,
message: t('pblink.pwd_format_error'),
trigger: 'blur'
}

View File

@ -228,6 +228,7 @@ const copyInfo = async () => {
ElMessage.warning('链接格式错误,请重新填写!')
return
}
formatLinkAddr()
await toClipboard(linkAddr.value)
ElMessage.success(t('common.copy_success'))
} catch (e) {

View File

@ -197,6 +197,7 @@ const copyInfo = async () => {
ElMessage.warning('链接格式错误,请重新填写!')
return
}
formatLinkAddr()
await toClipboard(linkAddr.value)
ElMessage.success(t('common.copy_success'))
} catch (e) {

View File

@ -5,6 +5,7 @@
<span>{{ curTitle }}</span>
</div>
<div>
<el-button v-if="testConnectText" secondary @click="check">{{ testConnectText }}</el-button>
<el-button type="primary" @click="edit">{{ t('commons.edit') }}</el-button>
<el-button v-if="showValidate" type="primary" @click="check">{{
t('datasource.validate')
@ -109,6 +110,10 @@ const props = defineProps({
type: Boolean,
default: false
},
testConnectText: {
type: String,
default: null
},
copyList: {
type: Array as PropType<string[]>,
default: () => []

View File

@ -8,8 +8,13 @@
<map-setting v-if="activeName === 'map'" />
<basic-info v-if="activeName === 'basic'" />
<engine-info v-if="activeName === 'engine'" />
<xpack-component
jsname="L21lbnUvc2V0dGluZy9lbWFpbC9pbmRleA=="
v-if="activeName === 'email'"
/>
</div>
</div>
<xpack-component jsname="L2NvbXBvbmVudC9tZW51LWhhbmRsZXIvRW1haWxIYW5kbGVy" @loaded="addTable" />
</template>
<script lang="ts" setup>
@ -18,16 +23,23 @@ import { useI18n } from '@/hooks/web/useI18n'
import MapSetting from './map/MapSetting.vue'
import BasicInfo from './basic/BasicInfo.vue'
import EngineInfo from '@/views/system/parameter/engine/EngineInfo.vue'
import { XpackComponent } from '@/components/plugin'
/* import EmailInfo from './email/EmailInfo.vue' */
const { t } = useI18n()
const tabArray = [
const tabArray = ref([
{ label: '基础设置', name: 'basic' },
/* { label: '邮件设置', name: 'email' }, */
{ label: '地图设置', name: 'map' },
{ label: '引擎设置', name: 'engine' }
]
])
const activeName = ref('basic')
const addTable = tab => {
if (!tabArray.value.some(item => item.name === tab['name'])) {
tabArray.value.splice(1, 0, tab)
}
}
</script>
<style lang="less">
.router-title {

View File

@ -162,6 +162,8 @@
<script setup lang="ts">
import { searchMarket } from '@/api/templateMarket'
import { useEmbedded } from '@/store/modules/embedded'
import { useAppStoreWithOut } from '@/store/modules/app'
import elementResizeDetectorMaker from 'element-resize-detector'
import { nextTick, reactive, watch, onMounted, ref, computed } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
@ -175,7 +177,8 @@ import { XpackComponent } from '@/components/plugin'
import { Base64 } from 'js-base64'
const { t } = useI18n()
const { wsCache } = useCache()
const embeddedStore = useEmbedded()
const appStore = useAppStoreWithOut()
const interactiveStore = interactiveStoreWithOut()
// full marketPreview createPreview
@ -187,7 +190,7 @@ const close = () => {
}
const title = computed(() => (state.curPosition === 'branch' ? '模板中心' : '使用模板新建'))
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
const state = reactive({
initReady: true,
curPosition: 'branch',
@ -430,10 +433,14 @@ const apply = template => {
'&templateParams=' +
Base64.encode(JSON.stringify(templateTemplate))
let newWindow = null
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
}
if (state.pid) {
newWindow = window.open(baseUrl + `&pid=${state.pid}`, '_blank')
newWindow = window.open(embeddedBaseUrl + baseUrl + `&pid=${state.pid}`, '_blank')
} else {
newWindow = window.open(baseUrl, '_blank')
newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
}
initOpenHandler(newWindow)
}

View File

@ -221,7 +221,22 @@ const closeSqlNode = () => {
!state.nodeList[0].children?.length &&
changeSqlId.value.length === 1
) {
editUnion.value = true
currentNode.value = state.nodeList[0]
const { datasourceId, id, info, tableName } = currentNode.value
getTableField({
datasourceId,
id,
info,
tableName,
type: 'sql'
}).then(res => {
nodeField.value = res as unknown as Field[]
nodeField.value.forEach(ele => {
ele.checked = true
})
state.nodeList[0].currentDsFields = cloneDeep(res)
editUnion.value = true
})
changeSqlId.value = []
}
if (state.visualNode?.confirm) {
@ -1015,8 +1030,12 @@ const emits = defineEmits(['addComplete', 'joinEditor', 'updateAllfields', 'chan
>
<template #header v-if="currentNode">
<div class="info">
<span class="name">{{ currentNode.tableName }}</span>
<span class="ds">{{ t('auth.datasource') }}:{{ getDsName(currentNode.datasourceId) }}</span>
<span :title="currentNode.tableName" class="name ellipsis">{{
currentNode.tableName
}}</span>
<span :title="getDsName(currentNode.datasourceId)" class="ds ellipsis"
>{{ t('auth.datasource') }}:{{ getDsName(currentNode.datasourceId) }}</span
>
</div>
</template>
<union-field-list
@ -1070,10 +1089,12 @@ const emits = defineEmits(['addComplete', 'joinEditor', 'updateAllfields', 'chan
font-weight: 500;
font-size: 16px;
color: #1f2329;
max-width: 500px;
}
.ds {
font-weight: 400;
font-size: 14px;
max-width: 500px;
color: #646a73;
}
}

View File

@ -293,6 +293,7 @@ const confirmCustomTime = () => {
})
delete currentField.value.name
recoverSelection()
updateCustomTime.value = false
} else {
ruleFormRef.value.validate(valid => {
if (valid) {
@ -309,7 +310,6 @@ const confirmCustomTime = () => {
}
})
}
updateCustomTime.value = false
}
watch(searchTable, val => {
@ -701,10 +701,10 @@ let num = +new Date()
const expandedD = ref(true)
const expandedQ = ref(true)
const setGuid = (arr, id, datasourceId) => {
const setGuid = (arr, id, datasourceId, oldArr) => {
arr.forEach(ele => {
if (!ele.id) {
ele.id = `${++num}`
ele.id = oldArr.find(itx => itx.originName === ele.originName)?.id || `${++num}`
ele.datasetTableId = id
ele.datasourceId = datasourceId
}
@ -756,6 +756,17 @@ const setFieldAll = () => {
tabChange('manage')
fieldUnion.value?.clearState()
}
const dfsNode = (arr, id) => {
return arr.reduce((pre, next) => {
if (next.id === id) {
pre = [...next.currentDsFields]
} else if (next.children?.length) {
pre = dfsNode(next.children, id)
}
return pre
}, [])
}
const confirmEditUnion = () => {
const { node, parent } = fieldUnion.value
const to = node.id
@ -770,8 +781,11 @@ const confirmEditUnion = () => {
return
}
setGuid(node.currentDsFields, node.id, node.datasourceId)
setGuid(parent.currentDsFields, parent.id, parent.datasourceId)
const nodeOldCurrentDsFields = dfsNode(datasetDrag.value.getNodeList(), to)
const parentOldCurrentDsFields = dfsNode(datasetDrag.value.getNodeList(), from)
setGuid(node.currentDsFields, node.id, node.datasourceId, nodeOldCurrentDsFields)
setGuid(parent.currentDsFields, parent.id, parent.datasourceId, parentOldCurrentDsFields)
const top = cloneDeep(node)
const bottom = cloneDeep(parent)
datasetDrag.value.setStateBack(top, bottom)

View File

@ -670,7 +670,7 @@ const getMenuList = (val: boolean) => {
<div
class="dataset-content"
:class="{
auto: isIframe
auto: isIframe || isDataEaseBi
}"
>
<template v-if="!state.datasetTree.length && mounted">

View File

@ -20,7 +20,7 @@ const valueOptions = valueEnum.map(formatEnum)
const sysParams = ['eq', 'not_eq', 'like', 'not like', 'in', 'not in']
const textOptionsForSysParams = sysParams.map(formatEnum)
const sysParamsEnum = ['userId', 'userName', 'userEmail', 'userLabel']
const sysParamsEnum = ['userId', 'userName', 'userEmail']
const sysParamsIlns = sysParamsEnum.map(_ => {
return { value: `\${sysParams.${_}}`, label: `auth.sysParams_type.${toLine(_)}` }

View File

@ -310,7 +310,9 @@ const tabList = shallowRef([])
const initSearch = () => {
handleCurrentChange(1)
state.filterTable = tableData.value.filter(ele => ele.tableName.includes(nickName.value))
state.filterTable = tableData.value.filter(ele =>
ele.tableName.toLowerCase().includes(nickName.value.toLowerCase())
)
state.paginationConfig.total = state.filterTable.length
}
@ -876,7 +878,7 @@ const getMenuList = (val: boolean) => {
<div
class="datasource-content"
:class="{
auto: isIframe,
auto: isIframe || isDataEaseBi,
h100: isDataEaseBi || isIframe
}"
>

View File

@ -14,6 +14,8 @@ import { ElMessage } from 'element-plus-secondary'
import { useCache } from '@/hooks/web/useCache'
import DeResourceCreateOptV2 from '@/views/common/DeResourceCreateOptV2.vue'
import { Base64 } from 'js-base64'
import { useEmbedded } from '@/store/modules/embedded'
import { useAppStoreWithOut } from '@/store/modules/app'
const userStore = useUserStoreWithOut()
const interactiveStore = interactiveStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
@ -25,6 +27,8 @@ const { wsCache } = useCache()
const { push } = useRouter()
const router = useRouter()
const resourceCreateOpt = ref(null)
const embeddedStore = useEmbedded()
const appStore = useAppStoreWithOut()
const quickCreationList = shallowRef([
{
@ -96,6 +100,7 @@ const state = reactive({
loading: false,
dvCreateForm: {
resourceName: null,
templateId: null,
name: null,
pid: null,
nodeType: 'panel',
@ -201,14 +206,21 @@ const templatePreview = previewId => {
const templateApply = template => {
state.dvCreateForm.name = template.title
state.dvCreateForm.templateUrl = template.metas.theme_repo
state.dvCreateForm.resourceName = template.id
state.dvCreateForm.nodeType = template.templateType
if (template.source === 'market') {
state.dvCreateForm.newFrom = 'new_market_template'
state.dvCreateForm.templateUrl = template.metas.theme_repo
state.dvCreateForm.resourceName = template.id
} else {
state.dvCreateForm.newFrom = 'new_inner_template'
state.dvCreateForm.templateId = template.id
}
apply()
}
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
const apply = () => {
if (!state.dvCreateForm.templateUrl) {
if (state.dvCreateForm.newFrom === 'new_market_template' && !state.dvCreateForm.templateUrl) {
ElMessage.warning('未获取模板下载链接请联系模板市场官方')
return false
}
@ -223,7 +235,27 @@ const apply = () => {
: '#/dashboard?opt=create&createType=template') +
'&templateParams=' +
Base64.encode(JSON.stringify(templateTemplate))
window.open(baseUrl, '_blank')
let newWindow = null
let embeddedBaseUrl = ''
if (isDataEaseBi.value) {
embeddedBaseUrl = embeddedStore.baseUrl
}
if (state.pid) {
newWindow = window.open(embeddedBaseUrl + baseUrl + `&pid=${state.pid}`, '_blank')
} else {
newWindow = window.open(embeddedBaseUrl + baseUrl, '_blank')
}
initOpenHandler(newWindow)
}
const openHandler = ref(null)
const initOpenHandler = newWindow => {
if (openHandler?.value) {
const pm = {
methodName: 'initOpenHandler',
args: newWindow
}
openHandler.value.invokeMethod(pm)
}
}
const toTemplateMarket = () => {

@ -1 +1 @@
Subproject commit 2ce5674f7b01532b42b5a3b95fb31ea520a765bd
Subproject commit 1ce831779b69626bdb865b36454b7f91ce17eeb7

View File

@ -320,6 +320,34 @@ function clear_images() {
fi
}
function backup() {
need_stop=0
if [[ -z $1 ]];then
echo "如需备份 DataEase 数据,建议您先停止 DataEase 服务,以保证备份数据的完整性。"
read -r -p "即将备份 DataEase 数据,是否需要停止 DataEase 服务? [Y/n] " input
case $input in
[yY][eE][sS]|[yY])
echo "Yes"
need_stop=1
;;
[nN][oO]|[nN])
echo "No"
;;
*)
echo "无效输入..."
exit 1
;;
esac
elif [[ "$1" == "stop" ]];then
need_stop=1
fi
if [[ $need_stop == 1 ]];then
service dataease stop
else
echo "不停服进行备份"
fi
backup_file_name=dataease-backup-$(date +%Y%m%d)_$(date +%H%M%S).tar.gz
tar --exclude=logs/dataease -zcf $backup_file_name -C $DE_RUNNING_BASE .
if [ $? -ne 0 ]; then
@ -328,6 +356,10 @@ function backup() {
else
echo "备份成功,备份文件 : $backup_file_name"
fi
if [[ $need_stop == 1 ]];then
service dataease start
fi
}
function restore() {
if [[ -z $target ]];then
@ -367,7 +399,7 @@ function main() {
upgrade
;;
backup)
backup
backup $target
;;
restore)
restore $target

View File

@ -1,271 +1,315 @@
#!/bin/bash
INSTALL_TYPE='install'
title_count=1
CURRENT_DIR=$(
cd "$(dirname "$0")"
pwd
)
echo "$(date)" | tee -a ${CURRENT_DIR}/install.log
function log() {
message="[DATAEASE Log]: $1 "
echo -e "${message}" 2>&1 | tee -a ${CURRENT_DIR}/install.log
echo -e "${1}" 2>&1 | tee -a ${CURRENT_DIR}/install.log
}
function log_title () {
log "${title_count}. ${1}"
let title_count++
}
function log_content () {
log "\t${1}"
}
function prop {
[ -f "$1" ] | grep -P "^\s*[^#]?${2}=.*$" $1 | cut -d'=' -f2
}
docker_config_folder="/etc/docker"
compose_files="-f docker-compose.yml"
function check_and_prepare_env_params() {
log "当前时间 : $(date)"
log_title "检查安装环境并初始化环境变量"
INSTALL_TYPE='install'
if [ -f /usr/bin/dectl ]; then
# 获取已安装的 DataEase 的运行目录
cd ${CURRENT_DIR}
DE_BASE=$(grep "^DE_BASE=" /usr/bin/dectl | cut -d'=' -f2)
sed -i -e "s#DE_BASE=.*#DE_BASE=${DE_BASE}#g" dectl
\cp dectl /usr/local/bin && chmod +x /usr/local/bin/dectl
if [ -f /usr/bin/dectl ]; then
# 获取已安装的 DataEase 的运行目录
DE_BASE=$(grep "^DE_BASE=" /usr/bin/dectl | cut -d'=' -f2)
sed -i -e "s#DE_BASE=.*#DE_BASE=${DE_BASE}#g" dectl
\cp dectl /usr/local/bin && chmod +x /usr/local/bin/dectl
log "停止 DataEase 服务"
if [[ -f /etc/systemd/system/dataease.service ]];then
systemctl stop dataease
else
dectl stop
fi
INSTALL_TYPE='upgrade'
v2_version=$(dectl version | head -n 2 | grep "v2.")
if [[ -z $v2_version ]];then
echo "系统当前版本不是 DataEase v2 版本系列,不支持升级到 v2请检查离线包版本。"
exit 1;
fi
fi
set -a
if [[ -d $DE_BASE ]] && [[ -f $DE_BASE/dataease2.0/.env ]]; then
source $DE_BASE/dataease2.0/.env
INSTALL_TYPE='upgrade'
conf_install_mode=$(prop $CURRENT_DIR/install.conf DE_INSTALL_MODE)
if [[ $DE_INSTALL_MODE == 'community' ]] && [[ $conf_install_mode == 'enterprise' ]];then
DE_INSTALL_MODE=$conf_install_mode
export DE_INSTALL_MODE
fi
else
source ${CURRENT_DIR}/install.conf
INSTALL_TYPE='install'
fi
set +a
read available_disk <<< $(df -H --output=avail ${DE_BASE} | tail -1)
available_disk=${available_disk%?}
available_disk=${available_disk%.*}
if [[ $available_disk -lt 20 ]];then
log "\033[31m[警告] DataEase 运行目录所在磁盘剩余空间不足 20G 可能无法正常启动!\033[0m"
fi
DE_RUN_BASE=$DE_BASE/dataease2.0
conf_folder=${DE_RUN_BASE}/conf
templates_folder=${DE_RUN_BASE}/templates
echo -e "======================= 开始安装 =======================" 2>&1 | tee -a ${CURRENT_DIR}/install.log
mkdir -p ${DE_RUN_BASE}
cp -r ./dataease/* ${DE_RUN_BASE}/
cd $DE_RUN_BASE
env | grep DE_ >.env
mkdir -p ${DE_RUN_BASE}/{cache,logs,conf}
mkdir -p ${DE_RUN_BASE}/data/{mysql,static-resource,map,etcd_data,geo,appearance}
mkdir -p ${DE_RUN_BASE}/apisix/logs
mkdir -p ${DE_RUN_BASE}/task/logs
chmod 777 ${DE_RUN_BASE}/apisix/logs ${DE_RUN_BASE}/data/etcd_data ${DE_RUN_BASE}/task/logs
if [ "${DE_EXTERNAL_MYSQL}" = "false" ]; then
compose_files="${compose_files} -f docker-compose-mysql.yml"
sed -i -e "s/^ DE_MYSQL_HOST/ ${DE_MYSQL_HOST}/g" docker-compose.yml
sed -i -e "s/^. DE_MYSQL_HOST/ ${DE_MYSQL_HOST}/g" docker-compose-mysql.yml
else
sed -i -e "/^ depends_on/,+2d" docker-compose.yml
fi
log "拷贝配置文件模板文件 -> $conf_folder"
cd $DE_RUN_BASE
cp -r $templates_folder/* $conf_folder
log "根据安装配置参数调整配置文件"
cd ${templates_folder}
templates_files=( application.yml mysql.env )
for i in ${templates_files[@]}; do
if [ -f $i ]; then
envsubst < $i > $conf_folder/$i
fi
done
cd ${CURRENT_DIR}
sed -i -e "s#DE_BASE=.*#DE_BASE=${DE_BASE}#g" dectl
\cp dectl /usr/local/bin && chmod +x /usr/local/bin/dectl
if [ ! -f /usr/bin/dectl ]; then
ln -s /usr/local/bin/dectl /usr/bin/dectl 2>/dev/null
fi
if which getenforce >/dev/null 2>&1 && [ $(getenforce) == "Enforcing" ];then
log "关闭 SELINUX"
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
fi
#Install docker & docker-compose
##Install Latest Stable Docker Release
if which docker >/dev/null 2>&1; then
log "检测到 Docker 已安装,跳过安装步骤"
log "启动 Docker "
service docker start >/dev/null 2>&1 | tee -a ${CURRENT_DIR}/install.log
else
if [[ -d docker ]]; then
log "离线安装 docker"
cp docker/bin/* /usr/bin/
cp docker/service/docker.service /etc/systemd/system/
chmod +x /usr/bin/docker*
chmod 644 /etc/systemd/system/docker.service
log "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
else
log "在线安装 docker"
curl -fsSL https://resource.fit2cloud.com/get-docker-linux.sh -o get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log
if [[ ! -f get-docker.sh ]];then
log "docker 在线安装脚本下载失败,请稍候重试"
exit 1
log_content "停止 DataEase 服务"
if [[ -f /etc/systemd/system/dataease.service ]];then
systemctl stop dataease
else
dectl stop
fi
sudo sh get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log
log "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
fi
INSTALL_TYPE='upgrade'
if [ ! -d "$docker_config_folder" ];then
mkdir -p "$docker_config_folder"
fi
docker version >/dev/null 2>&1
if [ $? -ne 0 ]; then
log "docker 安装失败"
exit 1
else
log "docker 安装成功"
fi
fi
##Install Latest Stable Docker Compose Release
docker-compose version >/dev/null 2>&1
if [ $? -ne 0 ]; then
docker compose version >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo 'docker compose "$@"' > /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose
else
if [[ -d docker ]]; then
log "离线安装 docker-compose"
cp docker/bin/docker-compose /usr/bin/
chmod +x /usr/bin/docker-compose
else
log "在线安装 docker-compose"
curl -L https://resource.fit2cloud.com/docker/compose/releases/download/v2.16.0/docker-compose-$(uname -s | tr A-Z a-z)-$(uname -m) -o /usr/local/bin/docker-compose 2>&1 | tee -a ${CURRENT_DIR}/install.log
if [[ ! -f /usr/local/bin/docker-compose ]];then
log "docker-compose 下载失败,请稍候重试"
exit 1
fi
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
v2_version=$(dectl version | head -n 2 | grep "v2.")
if [[ -z $v2_version ]];then
echo "系统当前版本不是 DataEase v2 版本系列,不支持升级到 v2请检查离线包版本。"
exit 1;
fi
fi
docker-compose version >/dev/null
if [ $? -ne 0 ]; then
log "docker-compose 安装失败"
exit 1
set -a
if [[ -d $DE_BASE ]] && [[ -f $DE_BASE/dataease2.0/.env ]]; then
source $DE_BASE/dataease2.0/.env
INSTALL_TYPE='upgrade'
conf_install_mode=$(prop $CURRENT_DIR/install.conf DE_INSTALL_MODE)
if [[ $DE_INSTALL_MODE == 'community' ]] && [[ $conf_install_mode == 'enterprise' ]];then
DE_INSTALL_MODE=$conf_install_mode
export DE_INSTALL_MODE
fi
log_content "升级安装"
else
log "docker-compose 安装成功"
source ${CURRENT_DIR}/install.conf
INSTALL_TYPE='install'
mkdir -p ${DE_BASE}
log_content "全新安装"
fi
else
log "检测到 Docker Compose 已安装,跳过安装步骤"
fi
set +a
export COMPOSE_HTTP_TIMEOUT=180
cd ${CURRENT_DIR}
read available_disk <<< $(df -H --output=avail ${DE_BASE} | tail -1)
available_disk=${available_disk%?}
available_disk=${available_disk%.*}
if [[ $available_disk -lt 20 ]];then
log_content "\033[31m[警告] DataEase 运行目录所在磁盘剩余空间不足 20G 可能无法正常启动!\033[0m"
fi
}
for i in $(docker images --format '{{.Repository}}:{{.Tag}}' | grep dataease); do
current_images[${#current_images[@]}]=${i##*/}
done
function set_run_base_path() {
log_title "设置运行目录"
DE_RUN_BASE=$DE_BASE/dataease2.0
CONF_FOLDER=${DE_RUN_BASE}/conf
TEMPLATES_FOLDER=${DE_RUN_BASE}/templates
log_content "运行目录 $DE_RUN_BASE"
log_content "配置文件目录 $CONF_FOLDER"
}
# 加载镜像
if [[ -d images ]]; then
log "加载镜像"
for i in $(ls images); do
if [[ "${current_images[@]}" =~ "${i%.tar.gz}" ]]; then
echo "ignore image $i"
else
docker load -i images/$i 2>&1 | tee -a ${CURRENT_DIR}/install.log
function prepare_de_run_base() {
log_title "初始化运行目录"
cd ${CURRENT_DIR}
mkdir -p ${DE_RUN_BASE}
log_content "复制安装文件到运行目录"
cp -r ./dataease/* ${DE_RUN_BASE}/
cd $DE_RUN_BASE
env | grep DE_ >.env
mkdir -p ${DE_RUN_BASE}/{cache,logs,conf}
mkdir -p ${DE_RUN_BASE}/data/{mysql,static-resource,map,etcd_data,geo,appearance}
mkdir -p ${DE_RUN_BASE}/apisix/logs
mkdir -p ${DE_RUN_BASE}/task/logs
chmod 777 ${DE_RUN_BASE}/apisix/logs ${DE_RUN_BASE}/data/etcd_data ${DE_RUN_BASE}/task/logs
if [ "${DE_EXTERNAL_MYSQL}" = "false" ]; then
sed -i -e "s/^ DE_MYSQL_HOST/ ${DE_MYSQL_HOST}/g" docker-compose.yml
sed -i -e "s/^. DE_MYSQL_HOST/ ${DE_MYSQL_HOST}/g" docker-compose-mysql.yml
else
sed -i -e "/^ depends_on/,+2d" docker-compose.yml
fi
log_content "调整配置文件参数"
cd $DE_RUN_BASE
cp -r $TEMPLATES_FOLDER/* $CONF_FOLDER
cd ${TEMPLATES_FOLDER}
templates_files=( application.yml mysql.env )
for i in ${templates_files[@]}; do
if [ -f $i ]; then
envsubst < $i > $CONF_FOLDER/$i
fi
done
else
DEVERSION=$(cat ${CURRENT_DIR}/dataease/templates/version)
curl -sfL https://resource.fit2cloud.com/installation-log.sh | sh -s de ${INSTALL_TYPE} ${DEVERSION}
cd -
fi
}
if [[ -f /etc/init.d/dataease ]];then
if which chkconfig >/dev/null 2>&1;then
chkconfig dataease >/dev/null
if [ $? -eq 0 ]; then
chkconfig --del dataease
function update_dectl() {
log_title "安装 dectl 命令行工具"
log_content "安装至 /usr/local/bin/dectl & /usr/bin/dectl"
cd ${CURRENT_DIR}
sed -i -e "s#DE_BASE=.*#DE_BASE=${DE_BASE}#g" dectl
\cp dectl /usr/local/bin && chmod +x /usr/local/bin/dectl
if [ ! -f /usr/bin/dectl ]; then
ln -s /usr/local/bin/dectl /usr/bin/dectl 2>/dev/null
fi
}
function prepare_system_settings() {
log_title "修改操作系统相关设置"
if which getenforce >/dev/null 2>&1 && [ $(getenforce) == "Enforcing" ];then
log_content "关闭 SELINUX"
setenforce 0
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config
fi
if which firewall-cmd >/dev/null 2>&1; then
if systemctl is-active firewalld &>/dev/null ;then
log_content "开启防火墙端口 ${DE_PORT}"
firewall-cmd --zone=public --add-port=${DE_PORT}/tcp --permanent
firewall-cmd --reload
else
log_content "防火墙未开启,忽略端口开放"
fi
fi
rm -f /etc/init.d/dataease
fi
}
if [[ ! -f /etc/systemd/system/dataease.service ]];then
log "配置 dataease Service"
cp ${DE_RUN_BASE}/bin/dataease/dataease.service /etc/systemd/system/
chmod 644 /etc/systemd/system/dataease.service
log "配置开机自启动"
systemctl enable dataease >/dev/null 2>&1; systemctl daemon-reload | tee -a ${CURRENT_DIR}/install.log
fi
function install_docker() {
log_title "安装 docker"
#Install docker
##Install Latest Stable Docker Release
cd ${CURRENT_DIR}
if [[ $(grep "vm.max_map_count" /etc/sysctl.conf | wc -l) -eq 0 ]];then
sysctl -w vm.max_map_count=2000000 >/dev/null 2>&1
echo "vm.max_map_count=2000000" >> /etc/sysctl.conf >/dev/null 2>&1
elif (( $(grep "vm.max_map_count" /etc/sysctl.conf | awk -F'=' '{print $2}') < 2000000 ));then
sysctl -w vm.max_map_count=2000000 >/dev/null 2>&1
sed -i 's/^vm\.max_map_count.*/vm\.max_map_count=2000000/' /etc/sysctl.conf
fi
if [ $(grep "net.ipv4.ip_forward" /etc/sysctl.conf | wc -l) -eq 0 ];then
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf >/dev/null 2>&1
else
sed -i 's/^net\.ipv4\.ip_forward.*/net\.ipv4\.ip_forward=1/' /etc/sysctl.conf
fi
if which firewall-cmd >/dev/null 2>&1; then
if systemctl is-active firewalld &>/dev/null ;then
log "防火墙端口开放"
firewall-cmd --zone=public --add-port=${DE_PORT}/tcp --permanent
firewall-cmd --reload
if which docker >/dev/null 2>&1; then
log_content "检测到 Docker 已安装,跳过安装步骤"
log_content "启动 Docker "
service docker start >/dev/null 2>&1 | tee -a ${CURRENT_DIR}/install.log
else
log "防火墙未开启,忽略端口开放"
if [[ -d docker ]]; then
log_content "离线安装 docker"
cp docker/bin/* /usr/bin/
cp docker/service/docker.service /etc/systemd/system/
chmod +x /usr/bin/docker*
chmod 644 /etc/systemd/system/docker.service
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
else
log_content "在线安装 docker"
curl -fsSL https://resource.fit2cloud.com/get-docker-linux.sh -o get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log
if [[ ! -f get-docker.sh ]];then
log_content "docker 在线安装脚本下载失败,请稍候重试"
exit 1
fi
sudo sh get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
fi
docker_config_folder="/etc/docker"
if [ ! -d "$docker_config_folder" ];then
mkdir -p "$docker_config_folder"
fi
docker version >/dev/null 2>&1
if [ $? -ne 0 ]; then
log_content "docker 安装失败"
exit 1
else
log_content "docker 安装成功"
fi
fi
fi
}
log "启动服务"
systemctl start dataease 2>&1 | tee -a ${CURRENT_DIR}/install.log
function install_docker_compose() {
log_title "安装 docker-compose"
#Install docker-compose
cd ${CURRENT_DIR}
##Install Latest Stable Docker Compose Release
docker-compose version >/dev/null 2>&1
if [ $? -ne 0 ]; then
docker compose version >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo 'docker compose "$@"' > /usr/bin/docker-compose
chmod +x /usr/bin/docker-compose
else
if [[ -d docker ]]; then
log_content "离线安装 docker-compose"
cp docker/bin/docker-compose /usr/bin/
chmod +x /usr/bin/docker-compose
else
log_content "在线安装 docker-compose"
curl -L https://resource.fit2cloud.com/docker/compose/releases/download/v2.16.0/docker-compose-$(uname -s | tr A-Z a-z)-$(uname -m) -o /usr/local/bin/docker-compose 2>&1 | tee -a ${CURRENT_DIR}/install.log
if [[ ! -f /usr/local/bin/docker-compose ]];then
log_content "docker-compose 下载失败,请稍候重试"
exit 1
fi
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
fi
fi
access_port=$DE_PORT
if [[ $DE_INSTALL_MODE != "community" ]];then
access_port=9080
fi
echo -e "======================= 安装完成 =======================\n" 2>&1 | tee -a ${CURRENT_DIR}/install.log
echo -e "系统登录信息如下:\n\t访问地址: http://服务器IP:$access_port\n\t用户名: admin\n\t初始密码: DataEase@123456" 2>&1 | tee -a ${CURRENT_DIR}/install.log
docker-compose version >/dev/null
if [ $? -ne 0 ]; then
log_content "docker-compose 安装失败"
exit 1
else
log_content "docker-compose 安装成功"
fi
else
log_content "检测到 Docker Compose 已安装,跳过安装步骤"
fi
export COMPOSE_HTTP_TIMEOUT=180
}
function load_de_images() {
log_title "加载 DataEase 镜像"
cd ${CURRENT_DIR}
for i in $(docker images --format '{{.Repository}}:{{.Tag}}' | grep dataease); do
current_images[${#current_images[@]}]=${i##*/}
done
# 加载镜像
if [[ -d images ]]; then
for i in $(ls images); do
if [[ "${current_images[@]}" =~ "${i%.tar.gz}" ]]; then
log_content "已存在镜像 ${i%.tar.gz}"
else
log_content "加载镜像 ${i%.tar.gz}"
docker load -i images/$i >/dev/null 2>&1 | tee -a ${CURRENT_DIR}/install.log
fi
done
else
DEVERSION=$(cat ${CURRENT_DIR}/dataease/templates/version)
curl -sfL https://resource.fit2cloud.com/installation-log.sh | sh -s de ${INSTALL_TYPE} ${DEVERSION}
fi
}
function set_de_service() {
log_title "配置 DataEase 服务"
if [[ -f /etc/init.d/dataease ]];then
if which chkconfig >/dev/null 2>&1;then
chkconfig dataease >/dev/null
if [ $? -eq 0 ]; then
chkconfig --del dataease
fi
fi
rm -f /etc/init.d/dataease
fi
if [[ ! -f /etc/systemd/system/dataease.service ]];then
log_content "配置 dataease Service"
cp ${DE_RUN_BASE}/bin/dataease/dataease.service /etc/systemd/system/
chmod 644 /etc/systemd/system/dataease.service
log_content "配置开机自启动"
systemctl enable dataease >/dev/null 2>&1; systemctl daemon-reload | tee -a ${CURRENT_DIR}/install.log
fi
}
function start_de_service() {
log_title "启动 DataEase 服务"
systemctl start dataease 2>&1 | tee -a ${CURRENT_DIR}/install.log
access_port=$DE_PORT
if [[ $DE_INSTALL_MODE != "community" ]];then
access_port=9080
fi
echo
echo -e "======================= 安装完成 =======================\n" 2>&1 | tee -a ${CURRENT_DIR}/install.log
echo -e "系统登录信息如下:\n\t访问地址: http://服务器IP:$access_port\n\t用户名: admin\n\t初始密码: DataEase@123456" 2>&1 | tee -a ${CURRENT_DIR}/install.log
}
function main() {
check_and_prepare_env_params
set_run_base_path
prepare_de_run_base
update_dectl
prepare_system_settings
install_docker
install_docker_compose
load_de_images
set_de_service
start_de_service
}
main

16
pom.xml
View File

@ -13,7 +13,7 @@
</modules>
<properties>
<dataease.version>2.5.0</dataease.version>
<dataease.version>2.6.0</dataease.version>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
@ -22,7 +22,7 @@
<maven-compiler-plugin.version>3.9.0</maven-compiler-plugin.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<h2.version>2.2.220</h2.version>
<knife4j.version>4.4.0</knife4j.version>
<calcite-core.version>1.35.7</calcite-core.version>
@ -32,7 +32,7 @@
<velocity.version>2.3</velocity.version>
<maven.antrun.version>3.1.0</maven.antrun.version>
<ehcache.version>3.10.8</ehcache.version>
<bcprov.version>1.74</bcprov.version>
<bcprov.version>1.78</bcprov.version>
<junit.version>4.13.2</junit.version>
<httpclient.version>4.5.14</httpclient.version>
<httpcore.version>4.4.16</httpcore.version>
@ -42,6 +42,10 @@
<flatten-maven.version>1.3.0</flatten-maven.version>
<maven.resources.version>3.3.1</maven.resources.version>
<maven.exec.version>3.1.0</maven.exec.version>
<guava.version>33.0.0-jre</guava.version>
<selenium-java.version>4.19.1</selenium-java.version>
<angus-mail.version>2.0.3</angus-mail.version>
<mysql-connector-j.version>8.2.0</mysql-connector-j.version>
</properties>
<dependencyManagement>
@ -106,6 +110,12 @@
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql-connector-j.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -0,0 +1,13 @@
package io.dataease.api.communicate.api;
import io.dataease.api.communicate.dto.MessageDTO;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@Hidden
public interface CommunicateApi {
@PostMapping("/send")
void send(@RequestBody MessageDTO dto);
}

View File

@ -0,0 +1,27 @@
package io.dataease.api.communicate.dto;
import io.dataease.constant.MessageEnum;
import lombok.Data;
import java.io.File;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
@Data
public class MessageDTO implements Serializable {
@Serial
private static final long serialVersionUID = -1499402825211940277L;
private List<String> recipientList;
private String title;
private byte[] content;
private List<File> fileList;
private List<MessageFile> messageFileList;
private MessageEnum messageEnum;
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.communicate.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class MessageFile implements Serializable {
@Serial
private static final long serialVersionUID = 7140452847688399889L;
private String fileName;
private byte[] body;
}

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