forked from github/dataease
Merge branch 'dev-v2' into pr@dev-v2@style_ai2
This commit is contained in:
commit
b89dfdfc78
78
README.md
78
README.md
@ -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 数据库: MySQL、Oracle、SQL Server、PostgreSQL、MariaDB、Db2、TiDB、MongoDB-BI 等;
|
||||
- OLAP 数据库: ClickHouse、Apache Doris、Apache Impala、StarRocks 等;
|
||||
- 数据仓库/数据湖: Amazon RedShift 等;
|
||||
- 数据文件: Excel、CSV 等;
|
||||
- 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
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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())) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
package io.dataease.job.sechedule;
|
||||
package io.dataease.job.schedule;
|
||||
|
||||
import io.dataease.utils.LogUtil;
|
||||
import org.quartz.*;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -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() {
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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]=====");
|
||||
}
|
||||
}
|
@ -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 +
|
||||
"}";
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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 +
|
||||
"}";
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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
@ -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
@ -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",
|
||||
|
@ -26,9 +26,9 @@
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<configuration>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<!--<fileset>
|
||||
<directory>dist</directory>
|
||||
</fileset>
|
||||
</fileset>-->
|
||||
<fileset>
|
||||
<directory>node_modules</directory>
|
||||
</fileset>
|
||||
|
@ -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 |
@ -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 |
7
core/core-frontend/src/assets/svg/report.svg
Normal file
7
core/core-frontend/src/assets/svg/report.svg
Normal 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 |
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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++) {
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -41,11 +41,11 @@ export default {
|
||||
},
|
||||
'center-align': {
|
||||
component: null,
|
||||
tooltip: '居中'
|
||||
tooltip: '居中,只在内容未溢出时生效'
|
||||
},
|
||||
'bottom-align': {
|
||||
component: null,
|
||||
tooltip: '置底'
|
||||
tooltip: '置底,只在内容未溢出时生效'
|
||||
}
|
||||
}
|
||||
for (const key in btnMap) {
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
|
@ -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;
|
||||
}
|
||||
|
41
core/core-frontend/src/layout/components/CollapseBar.vue
Normal file
41
core/core-frontend/src/layout/components/CollapseBar.vue
Normal 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>
|
@ -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>
|
||||
|
@ -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 }) })
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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: '请输入4~10位数字或字母',
|
||||
pwd_error: '密码错误',
|
||||
pwd_format_error: '请输入4位数字或字母',
|
||||
pwd_format_error: '请输入4~10位数字或字母',
|
||||
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: '成功'
|
||||
}
|
||||
}
|
||||
|
@ -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[]
|
||||
|
@ -28,6 +28,11 @@ declare type EditorPropertyInner = {
|
||||
[key in EditorProperty]?: string[]
|
||||
}
|
||||
|
||||
declare type EditorSelectorSpec = {
|
||||
[key in EditorProperty]?: {
|
||||
title: string
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 轴类型
|
||||
*/
|
||||
|
@ -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()
|
||||
|
@ -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(),
|
||||
|
@ -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(
|
||||
|
@ -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']"
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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 = {
|
||||
|
@ -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 {
|
||||
|
@ -19,6 +19,7 @@ export const CHART_MIX_EDITOR_PROPERTY_INNER: EditorPropertyInner = {
|
||||
'basic-style-selector': [
|
||||
'colors',
|
||||
'alpha',
|
||||
'gradient',
|
||||
'lineWidth',
|
||||
'lineSymbol',
|
||||
'lineSymbolSize',
|
||||
|
@ -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: [
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
// 开始渲染
|
||||
|
@ -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: {
|
||||
|
@ -25,6 +25,11 @@ export interface G2PlotDrawOptions<O> extends AntVDrawOptions<O> {
|
||||
* 缩放比例
|
||||
*/
|
||||
scale?: number
|
||||
/**
|
||||
* 特殊处理,象限图设置分割线的默认值
|
||||
* @param args
|
||||
*/
|
||||
quadrantDefaultBaseline?: (...args: any) => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
) {
|
||||
// 防止多次渲染
|
||||
|
@ -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>(() => {
|
||||
|
@ -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)"
|
||||
>
|
||||
|
@ -27,7 +27,7 @@ const timestampFormatDate = value => {
|
||||
if (!value) {
|
||||
return '-'
|
||||
}
|
||||
return new Date(value)['format']()
|
||||
return new Date(value).toLocaleString()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -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 />
|
||||
|
@ -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
|
||||
|
@ -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">
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -228,6 +228,7 @@ const copyInfo = async () => {
|
||||
ElMessage.warning('链接格式错误,请重新填写!')
|
||||
return
|
||||
}
|
||||
formatLinkAddr()
|
||||
await toClipboard(linkAddr.value)
|
||||
ElMessage.success(t('common.copy_success'))
|
||||
} catch (e) {
|
||||
|
@ -197,6 +197,7 @@ const copyInfo = async () => {
|
||||
ElMessage.warning('链接格式错误,请重新填写!')
|
||||
return
|
||||
}
|
||||
formatLinkAddr()
|
||||
await toClipboard(linkAddr.value)
|
||||
ElMessage.success(t('common.copy_success'))
|
||||
} catch (e) {
|
||||
|
@ -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: () => []
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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">
|
||||
|
@ -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(_)}` }
|
||||
|
@ -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
|
||||
}"
|
||||
>
|
||||
|
@ -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 = () => {
|
||||
|
2
de-xpack
2
de-xpack
@ -1 +1 @@
|
||||
Subproject commit 2ce5674f7b01532b42b5a3b95fb31ea520a765bd
|
||||
Subproject commit 1ce831779b69626bdb865b36454b7f91ce17eeb7
|
@ -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
|
||||
|
@ -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
16
pom.xml
@ -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>
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user