Merge pull request #10128 from dataease/dev

merge v1.18.20
This commit is contained in:
fit2cloudrd 2024-06-05 21:25:29 +08:00 committed by GitHub
commit ef5291b0d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
118 changed files with 4830 additions and 740 deletions

View File

@ -16,6 +16,7 @@ yoy = "yoy"
YOY = "YOY"
Leafs = "Leafs"
leafs = "leafs"
hiden = "hiden"
[files]
extend-exclude = [

View File

@ -270,6 +270,10 @@
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
<exclusion>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -342,8 +346,15 @@
<artifactId>ashot</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math4-legacy</artifactId>
<version>4.0-beta1</version>
</dependency>
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,33 +1,34 @@
package io.dataease.commons.utils;
import org.apache.commons.lang3.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.*;
public class DateUtils {
public static final String DATE_PATTERM = "yyyy-MM-dd";
public static final String DATE_PATTERN = "yyyy-MM-dd";
public static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static Date getDate(String dateString) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM);
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
return dateFormat.parse(dateString);
}
public static Date getTime(String timeString) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(TIME_PATTERN);
return dateFormat.parse(timeString);
}
public static String getDateString(Date date) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM);
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
return dateFormat.format(date);
}
public static String getDateString(long timeStamp) throws Exception {
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERM);
SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_PATTERN);
return dateFormat.format(timeStamp);
}
@ -47,10 +48,10 @@ public class DateUtils {
}
public static Date dateSum (Date date,int countDays){
public static Date dateSum(Date date, int countDays) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
calendar.add(Calendar.DAY_OF_MONTH,countDays);
calendar.add(Calendar.DAY_OF_MONTH, countDays);
return calendar.getTime();
}
@ -70,7 +71,7 @@ public class DateUtils {
try {
calendar.setTime(date);
calendar.set(Calendar.DAY_OF_WEEK, calendar.getActualMinimum(Calendar.DAY_OF_WEEK));
calendar.add(Calendar.DAY_OF_MONTH,weekDayAdd);
calendar.add(Calendar.DAY_OF_MONTH, weekDayAdd);
//第一天的时分秒是 00:00:00 这里直接取日期默认就是零点零分
Date thisWeekFirstTime = getDate(getDateString(calendar.getTime()));
@ -78,12 +79,12 @@ public class DateUtils {
calendar.clear();
calendar.setTime(date);
calendar.set(Calendar.DAY_OF_WEEK, calendar.getActualMaximum(Calendar.DAY_OF_WEEK));
calendar.add(Calendar.DAY_OF_MONTH,weekDayAdd);
calendar.add(Calendar.DAY_OF_MONTH, weekDayAdd);
//最后一天的时分秒应当是23:59:59 处理方式是增加一天计算日期再-1
calendar.add(Calendar.DAY_OF_MONTH,1);
calendar.add(Calendar.DAY_OF_MONTH, 1);
Date nextWeekFirstDay = getDate(getDateString(calendar.getTime()));
Date thisWeekLastTime = getTime(getTimeString(nextWeekFirstDay.getTime()-1));
Date thisWeekLastTime = getTime(getTimeString(nextWeekFirstDay.getTime() - 1));
returnMap.put("firstTime", thisWeekFirstTime);
returnMap.put("lastTime", thisWeekLastTime);
@ -95,14 +96,91 @@ public class DateUtils {
}
/**
* 获取当天的起始时间Date
* @param time 指定日期 2020-12-13 06:12:42
* @return 当天起始时间 2020-12-13 00:00:00
*
* @param time 指定日期 2020-12-13 06:12:42
* @return 当天起始时间 2020-12-13 00:00:00
* @throws Exception
*/
public static Date getDayStartTime(Date time) throws Exception {
return getDate(getDateString(time));
}
public static List<String> getForecastPeriod(String baseTime, int period, String dateStyle, String pattern) throws ParseException {
String split = "-";
if (StringUtils.equalsIgnoreCase(pattern, "date_split")) {
split = "/";
}
List<String> result = new ArrayList<>(period);
switch (dateStyle) {
case "y":
int baseYear = Integer.parseInt(baseTime);
for (int i = 1; i <= period; i++) {
result.add(baseYear + i + "");
}
break;
case "y_Q":
String[] yQ = baseTime.split(split);
int year = Integer.parseInt(yQ[0]);
int quarter = Integer.parseInt(yQ[1].split("Q")[1]);
for (int i = 0; i < period; i++) {
quarter = quarter % 4 + 1;
if (quarter == 1) {
year += 1;
}
result.add(year + split + "Q" + quarter);
}
break;
case "y_M":
String[] yM = baseTime.split(split);
int y = Integer.parseInt(yM[0]);
int month = Integer.parseInt(yM[1]);
for (int i = 0; i < period; i++) {
month = month % 12 + 1;
if (month == 1) {
y += 1;
}
String padMonth = month < 10 ? "0" + month : "" + month;
result.add(y + split + padMonth);
}
break;
case "y_W":
String[] yW = baseTime.split(split);
int yy = Integer.parseInt(yW[0]);
int w = Integer.parseInt(yW[1].split("W")[1]);
for (int i = 0; i < period; i++) {
Calendar calendar = Calendar.getInstance();
calendar.setMinimalDaysInFirstWeek(7);
calendar.setFirstDayOfWeek(Calendar.MONDAY);
calendar.set(Calendar.YEAR, yy);
calendar.set(Calendar.MONTH, Calendar.DECEMBER);
calendar.set(Calendar.DAY_OF_MONTH, 31);
int lastWeek = calendar.get(Calendar.WEEK_OF_YEAR);
w += 1;
if (w > lastWeek) {
yy += 1;
w = 1;
}
result.add(yy + split + "W" + w);
}
break;
case "y_M_d":
SimpleDateFormat sdf = new SimpleDateFormat("yyyy" + split + "MM" + split + "dd");
Calendar calendar = Calendar.getInstance();
Date baseDate = sdf.parse(baseTime);
calendar.setTime(baseDate);
for (int i = 0; i < period; i++) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
Date curDate = calendar.getTime();
String date = sdf.format(curDate);
result.add(date);
}
break;
default:
break;
}
return result;
}
}

View File

@ -1,8 +1,17 @@
package io.dataease.commons.utils;
import groovy.lang.Tuple2;
import org.apache.commons.math4.legacy.stat.regression.SimpleRegression;
import org.apache.commons.statistics.distribution.NormalDistribution;
import org.apache.commons.statistics.distribution.TDistribution;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
public class MathUtils {
private static final NormalDistribution NORMAL_DISTRIBUTION = NormalDistribution.of(0.0, 1.0);
/**
* 获取百分比
@ -12,8 +21,57 @@ public class MathUtils {
* @return
*/
public static double getPercentWithDecimal(double value) {
return new BigDecimal(value * 100)
.setScale(1, BigDecimal.ROUND_HALF_UP)
.doubleValue();
return new BigDecimal(value * 100).setScale(1, RoundingMode.HALF_UP).doubleValue();
}
/**
* 获取预测数据的置信区间这边计算的是预测值的置信区间还有一个是预测值的预测区间公式不一样注意区分.
* 参考资料 <a href="https://zhuanlan.zhihu.com/p/366307027">知乎</a>,
* <a href="https://real-statistics.com/regression/confidence-and-prediction-intervals/">real-statistics</a>
* @param data 原始数据
* @param forecastValue 预测得到的数据
* @param forecastData 将原数据 x 代入回归方程得到的拟合数据
* @param alpha 置信水平
* @param degreeOfFreedom 自由度t分布使用
* @return 预测值的置信区间数组
*/
public static double[][] getConfidenceInterval(double[][] data, double[] forecastValue, double[][] forecastData, double alpha, int degreeOfFreedom) {
// y 平均方差
double totalPow = 0;
double xTotal = 0;
for (int i = 0; i < data.length; i++) {
double xVal = data[i][0];
xTotal += xVal;
double realVal = data[i][1];
double predictVal = forecastValue[i];
totalPow += Math.pow((realVal - predictVal), 2);
}
double xAvg = xTotal / data.length;
double yMseSqrt = Math.sqrt(totalPow / (forecastValue.length - 2));
// x 均值方差
double xSubPow = 0;
for (int i = 0; i < data.length; i++) {
double xVal = data[i][0];
xSubPow += Math.pow(xVal - xAvg, 2);
}
// t/z , 样本数 < 30 t 分布 > 30 z 分布,
double tzFactor;
if (data.length <= 30) {
tzFactor = TDistribution.of(degreeOfFreedom).inverseCumulativeProbability(1 - (1 - alpha) / 2);
} else {
tzFactor = NORMAL_DISTRIBUTION.inverseCumulativeProbability(1 - (1 - alpha) / 2);
}
double[][] result = new double[forecastData.length][2];
for (int i = 0; i < forecastData.length; i++) {
double xVal = forecastData[i][0];
double curSubPow = Math.pow(xVal - xAvg, 2);
double sqrt = Math.sqrt(1.0 / data.length + curSubPow / xSubPow);
double lower = forecastData[i][1] - tzFactor * yMseSqrt * sqrt;
double upper = forecastData[i][1] + tzFactor * yMseSqrt * sqrt;
result[i][0] = lower;
result[i][1] = upper;
}
return result;
}
}

View File

@ -51,10 +51,16 @@ public class DataFillController {
return dataFillService.saveForm(dataFillForm);
}
@ApiIgnore
@PostMapping("/form/updateName")
public ResultHolder updateFormName(@RequestBody DataFillFormWithBLOBs dataFillForm) throws Exception {
return dataFillService.updateForm(dataFillForm, null);
}
@ApiIgnore
@PostMapping("/form/update")
public ResultHolder updateForm(@RequestBody DataFillFormWithBLOBs dataFillForm) throws Exception {
return dataFillService.updateForm(dataFillForm, null);
return dataFillService.updateForm(dataFillForm);
}
@ApiIgnore

View File

@ -76,6 +76,8 @@ public class DatasourceController {
datasource.setCreateTime(null);
datasource.setType(updataDsRequest.getType());
datasource.setUpdateTime(System.currentTimeMillis());
datasource.setEnableDataFill(updataDsRequest.getEnableDataFill());
datasource.setEnableDataFillCreateTable(updataDsRequest.getEnableDataFillCreateTable());
if (StringUtils.isNotEmpty(updataDsRequest.getId())) {
datasource.setId(updataDsRequest.getId());
}

View File

@ -16,4 +16,6 @@ public class UpdataDsRequest {
@ApiModelProperty(value = "配置详情", required = true)
private String configuration;
private boolean configurationEncryption = false;
private Boolean enableDataFill;
private Boolean enableDataFillCreateTable;
}

View File

@ -57,6 +57,7 @@ public class PanelGroupController {
@ApiOperation("查询树")
@PostMapping("/tree")
@I18n
public List<PanelGroupDTO> tree(@RequestBody PanelGroupRequest request) {
return panelGroupService.tree(request);
}
@ -185,12 +186,7 @@ public class PanelGroupController {
@DePermissionProxy(value = "proxy")
@I18n
public void innerExportDetails(@RequestBody PanelViewDetailsRequest request) throws Exception {
if("dataset".equals(request.getDownloadType())){
DataSetExportRequest exportRequest = panelGroupService.composeDatasetExportRequest(request);
exportCenterService.addTask(exportRequest.getId(), "dataset", exportRequest);
}else{
exportCenterService.addTask(request.getViewId(), "chart", request);
}
exportCenterService.addTask(request.getViewId(), "chart", request);
}
@ApiOperation("更新仪表板状态")

View File

@ -51,4 +51,7 @@ public class ChartExtRequest {
private Boolean excelExportFlag = false;
@ApiModelProperty(hidden = true)
private String downloadType;
}

View File

@ -44,4 +44,6 @@ public class PanelViewDetailsRequest {
private String downloadType;
private String viewType;
}

View File

@ -0,0 +1,35 @@
package io.dataease.dto.chart;
import lombok.Data;
@Data
public class ChartSeniorForecastDTO {
/**
* 是否开启预测
*/
private boolean enable;
/**
* 预测周期
*/
private int period;
/**
* 是否使用所有数据进行预测
*/
private boolean allPeriod;
/**
* 用于预测的数据量
*/
private int trainingPeriod;
/**
* 置信区间
*/
private float confidenceInterval;
/**
* 预测用的算法/模型
*/
private String algorithm;
/**
* 多项式阶数
*/
private int degree;
}

View File

@ -0,0 +1,14 @@
package io.dataease.dto.chart;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
@Data
public class ForecastDataDTO {
@JsonIgnore
private double xVal;
@JsonIgnore
private double yVal;
private double lower;
private double upper;
}

View File

@ -0,0 +1,11 @@
package io.dataease.dto.chart;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class ForecastDataVO<D, Q> extends ForecastDataDTO {
private D dimension;
private Q quota;
}

View File

@ -48,6 +48,31 @@ public class MysqlExtDDLProvider extends DefaultExtDDLProvider {
return creatTableSql.replace("TABLE_NAME", table).replace("Column_Fields", fieldSql);
}
@Override
public String addTableColumnSql(String table, List<ExtTableField> formFieldsToCreate, List<ExtTableField> formFieldsToModify) {
String modifyTableSql = "ALTER TABLE `$TABLE_NAME$` $Column_Fields$ ;";
List<ExtTableField.TableField> fields = convertTableFields(false, formFieldsToCreate);
List<ExtTableField.TableField> fieldsToModify = convertTableFields(false, formFieldsToModify);
String fieldSql = convertTableFieldsString(table, fields, true, fieldsToModify);
return modifyTableSql.replace("$TABLE_NAME$", table).replace("$Column_Fields$", fieldSql);
}
@Override
public String dropTableColumnSql(String table, List<ExtTableField> formFields) {
String modifyTableSql = "ALTER TABLE `$TABLE_NAME$` $Column_Fields$ ;";
List<ExtTableField.TableField> fields = convertTableFields(false, formFields);
StringBuilder str = new StringBuilder();
str.append("\n");
for (int i = 0; i < fields.size(); i++) {
ExtTableField.TableField field = fields.get(i);
str.append("drop column ");
str.append("`").append(field.getColumnName()).append("` ");
if (i != fields.size() - 1) {
str.append(",\n");
}
}
return modifyTableSql.replace("$TABLE_NAME$", table).replace("$Column_Fields$", str.toString());
}
@Override
public String searchSql(String table, List<TableField> formFields, String whereSql, long limit, long offset) {
@ -120,18 +145,21 @@ public class MysqlExtDDLProvider extends DefaultExtDDLProvider {
for (ExtTableField formField : formFields) {
ExtTableField.TableField.TableFieldBuilder fieldBuilder = ExtTableField.TableField.builder()
.columnName(formField.getSettings().getMapping().getColumnName())
.oldColumnName(formField.getSettings().getMapping().getOldColumnName())
.type(formField.getSettings().getMapping().getType())
.comment(formField.getSettings().getName())
.required(formField.getSettings().isRequired());
if (StringUtils.equalsIgnoreCase(formField.getType(), "dateRange")) {
ExtTableField.TableField f1 = fieldBuilder
.columnName(formField.getSettings().getMapping().getColumnName1())
.oldColumnName(formField.getSettings().getMapping().getOldColumnName1())
.comment(formField.getSettings().getName() + " start")
.build();
list.add(f1);
ExtTableField.TableField f2 = BeanUtils.copyBean(new ExtTableField.TableField(), f1);
f2.setColumnName(formField.getSettings().getMapping().getColumnName2());
f2.setOldColumnName(formField.getSettings().getMapping().getOldColumnName2());
f2.setComment(formField.getSettings().getName() + " end");
list.add(f2);
} else {
@ -238,12 +266,48 @@ public class MysqlExtDDLProvider extends DefaultExtDDLProvider {
}
private String convertTableFieldsString(String table, List<ExtTableField.TableField> fields) {
StringBuilder str = new StringBuilder();
return convertTableFieldsString(table, fields, false);
}
private String convertTableFieldsString(String table, List<ExtTableField.TableField> fields, boolean notCreateTable) {
return convertTableFieldsString(table, fields, notCreateTable, null);
}
private String convertTableFieldsString(String table, List<ExtTableField.TableField> fields, boolean notCreateTable, List<ExtTableField.TableField> fieldsToModify) {
StringBuilder str = new StringBuilder();
if (notCreateTable) {
str.append("\n");
} else {
str.append("(\n");
}
if (notCreateTable && CollectionUtils.isNotEmpty(fieldsToModify)) {
for (int i = 0; i < fieldsToModify.size(); i++) {
ExtTableField.TableField field = fieldsToModify.get(i);
str.append("change ");
//column name
str.append("`").append(field.getOldColumnName()).append("` ");
str.append("`").append(field.getColumnName()).append("` ");
appendTypes(str, field);
//换行
if (i < fieldsToModify.size() - 1) {
str.append(",\n");
}
}
if (CollectionUtils.isNotEmpty(fields)) {
str.append(",\n");
}
}
str.append("(\n");
ExtTableField.TableField primaryKeyField = null;
for (ExtTableField.TableField field : fields) {
for (int i = 0; i < fields.size(); i++) {
ExtTableField.TableField field = fields.get(i);
if (field.isPrimaryKey()) {
primaryKeyField = field;
}
@ -252,66 +316,27 @@ public class MysqlExtDDLProvider extends DefaultExtDDLProvider {
/*if (checkSqlInjection(field.getColumnName())) {
throw new RuntimeException("包含SQL注入的参数请检查参数");
}*/
if (notCreateTable) {
str.append("add ");
}
//column name
str.append("`").append(field.getColumnName()).append("` ");
//type
switch (field.getType()) {
case nvarchar:
str.append("NVARCHAR(");
if (field.getSize() != null && field.getSize() > 0) {
str.append(field.getSize());
} else {
str.append(256);
}
str.append(") ");
break;
case number:
str.append("BIGINT(");
if (field.getSize() != null && field.getSize() > 0) {
str.append(field.getSize());
} else {
str.append(20);
}
str.append(") ");
break;
case decimal:
str.append("DECIMAL(");
if (field.getSize() != null && field.getSize() > 0) {
str.append(field.getSize());
} else {
str.append(20);
}
str.append(",");
if (field.getAccuracy() != null && field.getAccuracy() >= 0) {
str.append(field.getAccuracy());
} else {
str.append(8);
}
str.append(") ");
break;
case datetime:
str.append("DATETIME ");
break;
default:
str.append("LONGTEXT ");
break;
}
//必填
if (field.isRequired()) {
str.append("NOT NULL ");
}
//comment
str.append("COMMENT '").append(field.getComment()).append("' ");
appendTypes(str, field);
//换行
str.append(",\n");
if (i < fields.size() - 1) {
str.append(",\n");
} else {
if (primaryKeyField != null) {
str.append(",\n");
}
}
}
if (primaryKeyField != null) {
if (!notCreateTable && primaryKeyField != null) {
str.append("constraint `")
.append(table)
.append("_pk` ")
@ -321,11 +346,65 @@ public class MysqlExtDDLProvider extends DefaultExtDDLProvider {
.append("`)");
}
str.append("\n)\n");
if (!notCreateTable) {
str.append("\n)\n");
}
return str.toString();
}
private static void appendTypes(StringBuilder str, ExtTableField.TableField field) {
//type
switch (field.getType()) {
case nvarchar:
str.append("NVARCHAR(");
if (field.getSize() != null && field.getSize() > 0) {
str.append(field.getSize());
} else {
str.append(256);
}
str.append(") ");
break;
case number:
str.append("BIGINT(");
if (field.getSize() != null && field.getSize() > 0) {
str.append(field.getSize());
} else {
str.append(20);
}
str.append(") ");
break;
case decimal:
str.append("DECIMAL(");
if (field.getSize() != null && field.getSize() > 0) {
str.append(field.getSize());
} else {
str.append(20);
}
str.append(",");
if (field.getAccuracy() != null && field.getAccuracy() >= 0) {
str.append(field.getAccuracy());
} else {
str.append(8);
}
str.append(") ");
break;
case datetime:
str.append("DATETIME ");
break;
default:
str.append("LONGTEXT ");
break;
}
//必填 考虑到表单编辑的情况调整为代码判断
/*if (field.isRequired()) {
str.append("NOT NULL ");
}*/
//comment
str.append("COMMENT '").append(field.getComment()).append("' ");
}
@Override
public List<String> createTableIndexSql(String table, List<ExtIndexField> indexFields) {
List<String> list = new ArrayList<>();
@ -338,11 +417,21 @@ public class MysqlExtDDLProvider extends DefaultExtDDLProvider {
return list;
}
@Override
public List<String> dropTableIndexSql(String table, List<ExtIndexField> indexFields) {
List<String> list = new ArrayList<>();
for (ExtIndexField indexField : indexFields) {
String sql = "drop index `$INDEX_NAME$` on `$TABLE_NAME$`;";
list.add(sql.replace("$TABLE_NAME$", table).replace("$INDEX_NAME$", indexField.getName()));
}
return list;
}
private String convertTableIndexSql(String table, ExtIndexField indexField) {
StringBuilder column = new StringBuilder();
if (CollectionUtils.isEmpty(indexField.getColumns())) {
/*if (CollectionUtils.isEmpty(indexField.getColumns())) {
return null;
}
}*/
//check inject
/*if (checkSqlInjection(table) || checkSqlInjection(indexField.getName())) {

View File

@ -1450,7 +1450,7 @@ public class MysqlQueryProvider extends QueryProvider {
if (request.getDatasetTableField().getDeType() == 1) {
if (request.getDatasetTableField().getDeExtractType() == 1) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startTime = simpleDateFormat.format(new Date(Long.parseLong(value.get(0)) - 1000));
String startTime = simpleDateFormat.format(new Date(Long.parseLong(value.get(0))));
String endTime = simpleDateFormat.format(new Date(Long.parseLong(value.get(1))));
whereValue = String.format(MySQLConstants.WHERE_BETWEEN, startTime, endTime);
} else {

View File

@ -11,6 +11,7 @@ import io.dataease.commons.constants.JdbcConstants;
import io.dataease.commons.model.PluginViewSetImpl;
import io.dataease.commons.utils.AuthUtils;
import io.dataease.commons.utils.BeanUtils;
import io.dataease.commons.utils.DateUtils;
import io.dataease.commons.utils.LogUtil;
import io.dataease.controller.request.chart.*;
import io.dataease.controller.response.ChartDetail;
@ -52,6 +53,8 @@ import io.dataease.plugins.view.service.ViewPluginService;
import io.dataease.plugins.xpack.auth.dto.request.ColumnPermissionItem;
import io.dataease.provider.query.SQLUtils;
import io.dataease.service.chart.util.ChartDataBuild;
import io.dataease.service.chart.util.dataForecast.ForecastAlgo;
import io.dataease.service.chart.util.dataForecast.ForecastAlgoManager;
import io.dataease.service.dataset.*;
import io.dataease.service.datasource.DatasourceService;
import io.dataease.service.engine.EngineService;
@ -71,6 +74,7 @@ import javax.annotation.Resource;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
@ -334,7 +338,26 @@ public class ChartViewService {
if (CommonConstants.VIEW_DATA_FROM.TEMPLATE.equals(view.getDataFrom())) {
return extendDataService.getChartDataInfo(id, view);
} else {
return calcData(view, request, request.isCache());
String[] dsHeader = null;
Integer[] dsTypes = null;
//downloadType = dataset 为下载原始名字 这里做数据转换模拟 table-info类型图表导出
if("dataset".equals(request.getDownloadType())){
view.setType("table-info");
List<DatasetTableField> sourceFields = dataSetTableFieldsService.getFieldsByTableId(view.getTableId());
dsHeader = sourceFields.stream()
.map(DatasetTableField::getName)
.toArray(String[]::new);
dsTypes = sourceFields.stream()
.map(DatasetTableField::getDeType)
.toArray(Integer[]::new);
view.setXAxis(JSONObject.toJSONString(sourceFields));
}
ChartViewDTO result = calcData(view, request, request.isCache());
if("dataset".equals(request.getDownloadType())){
result.getData().put("header",dsHeader);
result.getData().put("dsTypes",dsTypes);
}
return result;
}
} catch (Exception e) {
@ -1090,7 +1113,7 @@ public class ChartViewService {
logger.info("plugin_sql:" + sql);
Map<String, Object> mapTableNormal = ChartDataBuild.transTableNormal(fieldMap, view, data, desensitizationList);
return uniteViewResult(datasourceRequest.getQuery(), mapChart, mapTableNormal, view, isDrill, drillFilters, dynamicAssistFields, assistData);
return uniteViewResult(datasourceRequest.getQuery(), mapChart, mapTableNormal, view, isDrill, drillFilters, dynamicAssistFields, assistData, Collections.emptyList());
// 如果是插件到此结束
}
@ -1361,6 +1384,16 @@ public class ChartViewService {
}
tempYAxis.addAll(yAxis);
// forecast
List<? extends ForecastDataVO<?, ?>> forecastData = Collections.emptyList();
JSONObject senior = JSONObject.parseObject(view.getSenior());
JSONObject forecastObj = senior.getJSONObject("forecastCfg");
if (forecastObj != null) {
ChartSeniorForecastDTO forecastCfg = forecastObj.toJavaObject(ChartSeniorForecastDTO.class);
if (forecastCfg.isEnable()) {
forecastData = forecastData(forecastCfg, data, xAxis, yAxis, view);
}
}
for (int i = 0; i < tempYAxis.size(); i++) {
ChartViewFieldDTO chartViewFieldDTO = tempYAxis.get(i);
ChartFieldCompareDTO compareCalc = chartViewFieldDTO.getCompareCalc();
@ -1430,7 +1463,7 @@ public class ChartViewService {
item[dataIndex] = null;
} else {
item[dataIndex] = new BigDecimal(cValue)
.divide(new BigDecimal(lastValue), 8, RoundingMode.HALF_UP)
.divide(new BigDecimal(lastValue).abs(), 8, RoundingMode.HALF_UP)
.subtract(new BigDecimal(1))
.setScale(8, RoundingMode.HALF_UP)
.toString();
@ -1622,12 +1655,43 @@ public class ChartViewService {
mapTableNormal = ChartDataBuild.transTableNormal(xAxis, yAxis, view, data, extStack, desensitizationList);
}
chartViewDTO = uniteViewResult(datasourceRequest.getQuery(), mapChart, mapTableNormal, view, isDrill, drillFilters, dynamicAssistFields, assistData);
chartViewDTO = uniteViewResult(datasourceRequest.getQuery(), mapChart, mapTableNormal, view, isDrill, drillFilters, dynamicAssistFields, assistData, forecastData);
chartViewDTO.setTotalPage(totalPage);
chartViewDTO.setTotalItems(totalItems);
return chartViewDTO;
}
private List<? extends ForecastDataVO<?,?>> forecastData(ChartSeniorForecastDTO forecastCfg, List<String[]> data, List<ChartViewFieldDTO> xAxis, List<ChartViewFieldDTO> yAxis, ChartViewDTO view) throws ParseException {
List<String[]> trainingData = data;
if (!forecastCfg.isAllPeriod() && data.size() > forecastCfg.getTrainingPeriod()) {
trainingData = data.subList(data.size() - forecastCfg.getTrainingPeriod(), data.size() - 1);
}
if (xAxis.size() == 1 && xAxis.get(0).getDeType() == 1) {
// 先处理时间类型, 默认数据是有序递增的
String lastTime = data.get(data.size() - 1)[0];
ChartViewFieldDTO timeAxis = xAxis.get(0);
List<String> forecastPeriod = DateUtils.getForecastPeriod(lastTime, forecastCfg.getPeriod(), timeAxis.getDateStyle(), timeAxis.getDatePattern());
if(!forecastPeriod.isEmpty()){
ForecastAlgo algo = ForecastAlgoManager.getAlgo(forecastCfg.getAlgorithm());
List<ForecastDataDTO> forecastData = algo.forecast(forecastCfg, trainingData, view);
if (forecastPeriod.size() == forecastData.size()) {
List<ForecastDataVO<String, Double>> result = new ArrayList<>();
for (int i = 0; i < forecastPeriod.size(); i++) {
String period = forecastPeriod.get(i);
ForecastDataDTO forecastDataItem = forecastData.get(i);
ForecastDataVO<String, Double> tmp = new ForecastDataVO<>();
BeanUtils.copyBean(tmp, forecastDataItem);
tmp.setDimension(period);
tmp.setQuota(forecastDataItem.getYVal());
result.add(tmp);
}
return result;
}
}
}
return List.of();
}
// 对结果排序
public List<String[]> resultCustomSort(List<ChartViewFieldDTO> xAxis, List<String[]> data) {
List<String[]> res = new ArrayList<>(data);
@ -1684,11 +1748,12 @@ public class ChartViewService {
return "SELECT " + stringBuilder + " FROM (" + sql + ") tmp";
}
public ChartViewDTO uniteViewResult(String sql, Map<String, Object> chartData, Map<String, Object> tableData, ChartViewDTO view, Boolean isDrill, List<ChartExtFilterRequest> drillFilters, List<ChartSeniorAssistDTO> dynamicAssistFields, List<String[]> assistData) {
public ChartViewDTO uniteViewResult(String sql, Map<String, Object> chartData, Map<String, Object> tableData, ChartViewDTO view, Boolean isDrill, List<ChartExtFilterRequest> drillFilters, List<ChartSeniorAssistDTO> dynamicAssistFields, List<String[]> assistData, List<? extends ForecastDataVO<?, ?>> forecastData) {
Map<String, Object> map = new HashMap<>();
map.putAll(chartData);
map.putAll(tableData);
map.put("forecastData", forecastData);
List<DatasetTableField> sourceFields = dataSetTableFieldsService.getFieldsByTableId(view.getTableId());
map.put("sourceFields", sourceFields);

View File

@ -48,11 +48,15 @@ public class ViewExportExcel {
Map<String, ChartExtRequest> stringChartExtRequestMap = buildViewRequest(panelDto, justView);
List<File> results = new ArrayList<>();
List<ExcelSheetModel> sheets = viewIds.stream().map(viewId -> viewFiles(viewId, stringChartExtRequestMap.get(viewId))).collect(Collectors.toList());
File excelFile = ExcelUtils.exportExcel(sheets, panelDto.getName(), panelDto.getId() + "_" + taskId);
File excelFile = ExcelUtils.exportExcel(sheets, getSafeFileName(panelDto.getName()), panelDto.getId() + "_" + taskId);
results.add(excelFile);
return results;
}
private String getSafeFileName(String fileName) {
return fileName.replace("/", "_");
}
private Map<String, ChartExtRequest> buildViewRequest(PanelGroupDTO panelDto, Boolean justView) {
String componentsJson = panelDto.getPanelData();

View File

@ -0,0 +1,21 @@
package io.dataease.service.chart.util.dataForecast;
import io.dataease.dto.chart.ChartSeniorForecastDTO;
import io.dataease.dto.chart.ChartViewDTO;
import io.dataease.dto.chart.ForecastDataDTO;
import lombok.Getter;
import java.util.List;
@Getter
public abstract class ForecastAlgo {
private final String algoType;
public ForecastAlgo() {
this.algoType = this.getAlgoType();
ForecastAlgoManager.register(this);
}
public abstract List<ForecastDataDTO> forecast(ChartSeniorForecastDTO forecastCfg, List<String[]> data, ChartViewDTO view);
}

View File

@ -0,0 +1,17 @@
package io.dataease.service.chart.util.dataForecast;
import io.dataease.service.chart.util.dataForecast.impl.LinearRegressionAlgo;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ForecastAlgoManager {
private static final Map<String, ForecastAlgo> FORECAST_ALGO_MAP = new ConcurrentHashMap<>();
public static ForecastAlgo getAlgo(String algoType) {
return FORECAST_ALGO_MAP.get(algoType);
}
public static void register(ForecastAlgo forecastAlgo) {
FORECAST_ALGO_MAP.put(forecastAlgo.getAlgoType(), forecastAlgo);
}
}

View File

@ -0,0 +1,59 @@
package io.dataease.service.chart.util.dataForecast.impl;
import com.alibaba.fastjson.JSONObject;
import io.dataease.commons.utils.MathUtils;
import io.dataease.dto.chart.ChartSeniorForecastDTO;
import io.dataease.dto.chart.ChartViewDTO;
import io.dataease.dto.chart.ForecastDataDTO;
import io.dataease.plugins.common.dto.chart.ChartViewFieldDTO;
import io.dataease.service.chart.util.dataForecast.ForecastAlgo;
import lombok.Getter;
import org.apache.commons.math4.legacy.stat.regression.SimpleRegression;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Getter
public class LinearRegressionAlgo extends ForecastAlgo {
private final String algoType = "linear-regression";
@Override
public List<ForecastDataDTO> forecast(ChartSeniorForecastDTO forecastCfg, List<String[]> data, ChartViewDTO view) {
List<ChartViewFieldDTO> xAxis = JSONObject.parseArray(view.getXAxis(), ChartViewFieldDTO.class);
final List<double[]> forecastData = new ArrayList<>(data.size());
// 先按连续的数据处理
for (int i = 0; i < data.size(); i++) {
String val = data.get(i)[xAxis.size()];
double value = Double.parseDouble(val);
forecastData.add(new double[]{i, value});
}
double[][] matrix = forecastData.toArray(double[][]::new);
SimpleRegression regression = new SimpleRegression();
regression.addData(matrix);
double[][] forecastMatrix = new double[forecastCfg.getPeriod()][2];
for (int i = 0; i < forecastCfg.getPeriod(); i++) {
double xVal = data.size() + i;
double predictVal = regression.predict(xVal);
forecastMatrix[i] = new double[]{xVal, predictVal};
}
final double[] forecastValue = new double[forecastData.size()];
for (int i = 0; i < forecastData.size(); i++) {
double xVal = forecastData.get(i)[0];
forecastValue[i] = regression.predict(xVal);
}
double[][] confidenceInterval = MathUtils.getConfidenceInterval(forecastData.toArray(new double[0][0]), forecastValue, forecastMatrix, forecastCfg.getConfidenceInterval(), forecastData.size() - 2);
final List<ForecastDataDTO> result = new ArrayList<>(forecastCfg.getPeriod());
for (int i = 0; i < forecastMatrix.length; i++) {
ForecastDataDTO tmp = new ForecastDataDTO();
tmp.setXVal(forecastMatrix[i][0]);
tmp.setYVal(forecastMatrix[i][1]);
tmp.setLower(confidenceInterval[i][0]);
tmp.setUpper(confidenceInterval[i][1]);
result.add(tmp);
}
return result;
}
}

View File

@ -0,0 +1,71 @@
package io.dataease.service.chart.util.dataForecast.impl;
import com.alibaba.fastjson.JSONObject;
import io.dataease.commons.utils.MathUtils;
import io.dataease.dto.chart.ChartSeniorForecastDTO;
import io.dataease.dto.chart.ChartViewDTO;
import io.dataease.dto.chart.ForecastDataDTO;
import io.dataease.plugins.common.dto.chart.ChartViewFieldDTO;
import io.dataease.service.chart.util.dataForecast.ForecastAlgo;
import lombok.Getter;
import org.apache.commons.math4.legacy.analysis.function.Logistic;
import org.apache.commons.math4.legacy.fitting.PolynomialCurveFitter;
import org.apache.commons.math4.legacy.fitting.WeightedObservedPoints;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Getter
public class PolynomialRegressionAlgo extends ForecastAlgo {
private final String algoType = "polynomial-regression";
@Override
public List<ForecastDataDTO> forecast(ChartSeniorForecastDTO forecastCfg, List<String[]> data, ChartViewDTO view) {
List<ChartViewFieldDTO> xAxis = JSONObject.parseArray(view.getXAxis(), ChartViewFieldDTO.class);
WeightedObservedPoints points = new WeightedObservedPoints();
double[][] originData = new double[data.size()][2];
// 先按连续的数据处理
for (int i = 0; i < data.size(); i++) {
String val = data.get(i)[xAxis.size()];
double value = Double.parseDouble(val);
points.add(i, value);
originData[i] = new double[]{i, value};
}
PolynomialCurveFitter filter = PolynomialCurveFitter.create(forecastCfg.getDegree());
// 返回的是多次项系数, y = 3 + 2x + x*2 则为 [3,2,1]
double[] coefficients = filter.fit(points.toList());
double[][] forecastMatrix = new double[forecastCfg.getPeriod()][2];
for (int i = 0; i < forecastCfg.getPeriod(); i++) {
double xVal = data.size() + i;
double predictVal = getPolynomialValue(xVal, coefficients);
forecastMatrix[i] = new double[]{xVal, predictVal};
}
final double[] forecastValue = new double[data.size()];
for (int i = 0; i < data.size(); i++) {
double xVal = originData[i][0];
forecastValue[i] = getPolynomialValue(xVal, coefficients);
}
int df = data.size() - forecastCfg.getDegree() - 1;
double[][] confidenceInterval = MathUtils.getConfidenceInterval(originData, forecastValue, forecastMatrix, forecastCfg.getConfidenceInterval(), df);
final List<ForecastDataDTO> result = new ArrayList<>(forecastCfg.getPeriod());
for (int i = 0; i < forecastMatrix.length; i++) {
ForecastDataDTO tmp = new ForecastDataDTO();
tmp.setXVal(forecastMatrix[i][0]);
tmp.setYVal(forecastMatrix[i][1]);
tmp.setLower(confidenceInterval[i][0]);
tmp.setUpper(confidenceInterval[i][1]);
result.add(tmp);
}
return result;
}
private double getPolynomialValue(double x, double[] coefficients) {
double result = 0.0;
for (int i = 0; i < coefficients.length; i++) {
result += coefficients[i] * Math.pow(x, i);
}
return result;
}
}

View File

@ -37,12 +37,35 @@ public class CommentWriteHandler implements RowWriteHandler {
Drawing<?> drawingPatriarch = writeSheetHolder.getSheet().createDrawingPatriarch();
for (int i = 0; i < fields.size(); i++) {
ExtTableField field = fields.get(i);
String required = field.getSettings().isRequired() ? "必填" : "";
String required = field.getSettings().isRequired() ? "必填 " : "";
String unique = field.getSettings().isUnique() && StringUtils.equalsIgnoreCase("input", field.getType()) ? "不允许重复值" : "";
String example = "";
StringBuilder options = new StringBuilder();
switch (field.getSettings().getMapping().getType()) {
case datetime:
example = "\n(日期格式: yyyy/MM/dd" + (field.getSettings().isEnableTime() ? " HH:mm:ss" : "") + ")";
String dateFormat = "yyyy/MM/dd";
switch (field.getSettings().getDateType()) {
case "year":
dateFormat = "yyyy";
break;
case "month":
case "monthrange":
dateFormat = "yyyy/MM";
break;
case "datetime":
case "datetimerange":
dateFormat = "yyyy/MM/dd HH:mm:ss";
break;
case "date":
case "daterange":
dateFormat = "yyyy/MM/dd";
break;
default:
if (field.getSettings().isEnableTime()) { //兼容旧版
dateFormat = "yyyy/MM/dd HH:mm:ss";
}
}
example = "\n(日期格式: " + dateFormat + ")";
break;
case number:
example = "\n(整形数字)";
@ -68,7 +91,7 @@ public class CommentWriteHandler implements RowWriteHandler {
break;
}
if (StringUtils.isBlank(required) && StringUtils.isBlank(example) && StringUtils.isBlank(options.toString())) {
if (StringUtils.isBlank(required) && StringUtils.isBlank(unique) && StringUtils.isBlank(example) && StringUtils.isBlank(options.toString())) {
continue;
}

View File

@ -13,6 +13,7 @@ import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.controller.request.datafill.DataFillFormTableDataRequest;
import io.dataease.controller.response.datafill.DataFillFormTableDataResponse;
import io.dataease.dto.datafill.DataFillCommitLogDTO;
import io.dataease.dto.datasource.MysqlConfiguration;
import io.dataease.ext.ExtDataFillFormMapper;
import io.dataease.i18n.Translator;
import io.dataease.plugins.common.base.domain.DataFillFormWithBLOBs;
@ -45,6 +46,8 @@ import java.math.RoundingMode;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
@ -68,6 +71,63 @@ public class DataFillDataService {
private final static Gson gson = new Gson();
private Datasource getBuiltInDataSource() {
MysqlConfiguration mysqlConfiguration = new MysqlConfiguration();
Pattern WITH_SQL_FRAGMENT = Pattern.compile("jdbc:mysql://(.*):(\\d+)/(.*)");
Matcher matcher = WITH_SQL_FRAGMENT.matcher(env.getProperty("spring.datasource.url"));
if (!matcher.find()) {
return null;
}
mysqlConfiguration.setHost(matcher.group(1));
mysqlConfiguration.setPort(Integer.valueOf(matcher.group(2)));
String[] databasePrams = matcher.group(3).split("\\?");
mysqlConfiguration.setDataBase(databasePrams[0]);
if (databasePrams.length == 2) {
mysqlConfiguration.setExtraParams(databasePrams[1]);
}
if (StringUtils.isNotEmpty(mysqlConfiguration.getExtraParams()) && !mysqlConfiguration.getExtraParams().contains("connectionCollation")) {
mysqlConfiguration.setExtraParams(mysqlConfiguration.getExtraParams() + "&connectionCollation=utf8mb4_general_ci");
}
mysqlConfiguration.setUsername(env.getProperty("spring.datasource.username"));
mysqlConfiguration.setPassword(env.getProperty("spring.datasource.password"));
Datasource datasource = new Datasource();
datasource.setId("default-built-in");
datasource.setType("mysql");
datasource.setName(Translator.get("I18N_DATA_FILL_DATASOURCE_DEFAULT_BUILT_IN"));
datasource.setConfiguration(new Gson().toJson(mysqlConfiguration));
return datasource;
}
public Datasource getDataSource(String datasourceId) {
return getDataSource(datasourceId, false);
}
public Datasource getDataSource(String datasourceId, boolean withCreatePrivileges) {
Datasource ds = null;
if (StringUtils.equals("default-built-in", datasourceId)) {
ds = getBuiltInDataSource();
} else {
if (!withCreatePrivileges) {
ds = datasource.get(datasourceId);
} else {
ds = datasource.getDataSourceDetails(datasourceId);
if (ds.getEnableDataFill() && ds.getEnableDataFillCreateTable()) {
ds.setConfiguration(new String(java.util.Base64.getDecoder().decode(ds.getConfiguration())));
} else {
return null;
}
}
}
if (ds == null) {
DataEaseException.throwException(Translator.get("I18N_DATA_FILL_DATASOURCE_NOT_EXIST"));
}
return ds;
}
public static void setLowerCaseRequest(Datasource ds, Provider datasourceProvider, ExtDDLProvider extDDLProvider, DatasourceRequest datasourceRequest) throws Exception {
DatasourceTypes datasourceType = DatasourceTypes.valueOf(ds.getType());
switch (datasourceType) {
@ -94,7 +154,7 @@ public class DataFillDataService {
List<ExtTableField> fields = gson.fromJson(dataFillForm.getForms(), new TypeToken<List<ExtTableField>>() {
}.getType());
Datasource ds = datasource.get(dataFillForm.getDatasource());
Datasource ds = getDataSource(dataFillForm.getDatasource());
Provider datasourceProvider = ProviderFactory.getProvider(ds.getType());
DatasourceRequest datasourceRequest = new DatasourceRequest();
@ -125,6 +185,10 @@ public class DataFillDataService {
//核对一下字段
for (ExtTableField field : fields) {
if (field.isRemoved()) {
continue;
}
if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
String name1 = field.getSettings().getMapping().getColumnName1();
extTableFieldTypeMap.put(name1, field.getSettings().getMapping().getType());
@ -264,7 +328,7 @@ public class DataFillDataService {
if (StringUtils.equals(dataFillForm.getNodeType(), "folder")) {
return;
}
Datasource ds = datasource.get(dataFillForm.getDatasource());
Datasource ds = getDataSource(dataFillForm.getDatasource());
ExtDDLProvider extDDLProvider = ProviderFactory.gerExtDDLProvider(ds.getType());
@ -316,7 +380,7 @@ public class DataFillDataService {
List<ExtTableField> fields = gson.fromJson(dataFillForm.getForms(), new TypeToken<List<ExtTableField>>() {
}.getType());
Datasource ds = datasource.get(dataFillForm.getDatasource());
Datasource ds = getDataSource(dataFillForm.getDatasource());
Provider datasourceProvider = ProviderFactory.getProvider(ds.getType());
ExtDDLProvider extDDLProvider = ProviderFactory.gerExtDDLProvider(ds.getType());
@ -367,6 +431,10 @@ public class DataFillDataService {
Map<String, Object> data = row.getData();
for (ExtTableField field : fields) {
if (field.isRemoved()) {
continue;
}
String name = field.getSettings().getMapping().getColumnName();
if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
@ -404,7 +472,25 @@ public class DataFillDataService {
Map<String, Object> data = row.getData();
//一条条去判断
for (ExtTableField field : fields) {
if (field.isRemoved()) {
continue;
}
String name = field.getSettings().getMapping().getColumnName();
if (!StringUtils.equalsIgnoreCase(field.getType(), "dateRange") && data.get(name) == null) {
if (field.getSettings().isRequired()) {
DataEaseException.throwException("[" + field.getSettings().getName() + "] 不能为空");
}
} else if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
if (field.getSettings().isRequired()) {
String name1 = field.getSettings().getMapping().getColumnName1();
String name2 = field.getSettings().getMapping().getColumnName2();
if (data.get(name1) == null || data.get(name2) == null) {
DataEaseException.throwException("[" + field.getSettings().getName() + "] 不能为空");
}
}
}
if (StringUtils.equalsIgnoreCase(field.getType(), "input")) { //input框支持unique
if (field.getSettings().isUnique() && data.get(name) != null) {
DatasourceRequest.TableFieldWithValue uniqueField = new DatasourceRequest.TableFieldWithValue()
@ -472,6 +558,9 @@ public class DataFillDataService {
searchFields.add(pk);
for (ExtTableField field : fields) {
if (field.isRemoved()) {
continue;
}
if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
String name1 = field.getSettings().getMapping().getColumnName1();
String name2 = field.getSettings().getMapping().getColumnName2();
@ -538,6 +627,9 @@ public class DataFillDataService {
DatasourceRequest.TableFieldWithValue pk = gson.fromJson(gson.toJson(pkField), DatasourceRequest.TableFieldWithValue.class).setValue(rowId);
for (ExtTableField field : fields) {
if (field.isRemoved()) {
continue;
}
if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
String name1 = field.getSettings().getMapping().getColumnName1();
String name2 = field.getSettings().getMapping().getColumnName2();
@ -659,6 +751,9 @@ public class DataFillDataService {
List<ExtTableField> fields = new ArrayList<>();
Map<String, String> dateRangeNameMap = new HashMap<>();
for (ExtTableField field : formFields) {
if (field.isRemoved()) {
continue;
}
if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
dateRangeNameMap.put(field.getId(), field.getSettings().getName());
@ -847,16 +942,53 @@ public class DataFillDataService {
return null;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); //默认会拿到这种格式的
if (field.getSettings().isEnableTime()) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
switch (field.getSettings().getDateType()) {
case "year":
sdf = new SimpleDateFormat("yyyy");
break;
case "month":
case "monthrange":
sdf = new SimpleDateFormat("yyyy-MM");
break;
case "datetime":
case "datetimerange":
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
break;
case "date":
case "daterange":
sdf = new SimpleDateFormat("yyyy-MM-dd");
break;
default:
if (field.getSettings().isEnableTime()) { //兼容旧版
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
}
Date date = null;
try {
date = sdf.parse(excelRowData);
} catch (ParseException e) {
sdf = new SimpleDateFormat("yyyy/MM/dd"); //以防万一也加上这种
if (field.getSettings().isEnableTime()) {
sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
switch (field.getSettings().getDateType()) {
case "year":
sdf = new SimpleDateFormat("yyyy");
break;
case "month":
case "monthrange":
sdf = new SimpleDateFormat("yyyy/MM");
break;
case "datetime":
case "datetimerange":
sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
break;
case "date":
case "daterange":
sdf = new SimpleDateFormat("yyyy/MM/dd");
break;
default:
if (field.getSettings().isEnableTime()) { //兼容旧版
sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
}
}
date = sdf.parse(excelRowData);
}

View File

@ -11,7 +11,6 @@ import io.dataease.commons.constants.SysLogConstants;
import io.dataease.commons.utils.*;
import io.dataease.controller.ResultHolder;
import io.dataease.controller.request.datafill.DataFillFormRequest;
import io.dataease.dto.DatasourceDTO;
import io.dataease.dto.datafill.DataFillFormDTO;
import io.dataease.ext.ExtDataFillFormMapper;
import io.dataease.i18n.Translator;
@ -28,7 +27,6 @@ import io.dataease.plugins.datasource.provider.ExtDDLProvider;
import io.dataease.plugins.datasource.provider.Provider;
import io.dataease.plugins.datasource.provider.ProviderFactory;
import io.dataease.provider.datasource.JdbcProvider;
import io.dataease.service.datasource.DatasourceService;
import io.dataease.service.sys.SysAuthService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -52,8 +50,7 @@ public class DataFillService {
private DataFillFormMapper dataFillFormMapper;
@Resource
private ExtDataFillFormMapper extDataFillFormMapper;
@Resource
private DatasourceService datasource;
@Resource
private SysAuthService sysAuthService;
@Resource
@ -86,25 +83,43 @@ public class DataFillService {
List<ExtTableField> fields = gson.fromJson(dataFillForm.getForms(), new TypeToken<List<ExtTableField>>() {
}.getType());
List<ExtIndexField> indexes = new ArrayList<>();
List<ExtIndexField> indexesToCreate = new ArrayList<>();
if (dataFillForm.getCreateIndex()) {
indexes = gson.fromJson(dataFillForm.getTableIndexes(), new TypeToken<List<ExtIndexField>>() {
List<ExtIndexField> indexes = gson.fromJson(dataFillForm.getTableIndexes(), new TypeToken<List<ExtIndexField>>() {
}.getType());
Map<String, String> indexColumnItems = new HashMap<>();
fields.forEach(f -> {
if (StringUtils.equalsIgnoreCase(f.getType(), "dateRange")) {
indexColumnItems.put(f.getId() + "_1", f.getSettings().getMapping().getColumnName1());
indexColumnItems.put(f.getId() + "_2", f.getSettings().getMapping().getColumnName2());
} else {
indexColumnItems.put(f.getId(), f.getSettings().getMapping().getColumnName());
}
});
indexes.forEach(f -> {
ExtIndexField index = gson.fromJson(gson.toJson(f), ExtIndexField.class);
index.getColumns().forEach(c -> {
//根据id获取实际的column名
c.setColumn(indexColumnItems.get(c.getColumn()));
});
indexesToCreate.add(index);
});
}
DatasourceDTO datasourceDTO = datasource.getDataSourceDetails(dataFillForm.getDatasource());
datasourceDTO.setConfiguration(new String(java.util.Base64.getDecoder().decode(datasourceDTO.getConfiguration())));
Datasource ds = dataFillDataService.getDataSource(dataFillForm.getDatasource(), true);
DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setDatasource(datasourceDTO);
datasourceRequest.setDatasource(ds);
datasourceRequest.setQuery("SELECT VERSION()");
JdbcProvider jdbcProvider = CommonBeanFactory.getBean(JdbcProvider.class);
String version = jdbcProvider.getData(datasourceRequest).get(0)[0];
//拼sql
ExtDDLProvider extDDLProvider = ProviderFactory.gerExtDDLProvider(datasourceDTO.getType());
ExtDDLProvider extDDLProvider = ProviderFactory.gerExtDDLProvider(ds.getType());
String sql = extDDLProvider.createTableSql(dataFillForm.getTableName(), fields);
//创建表
datasourceRequest.setQuery(sql);
@ -112,7 +127,7 @@ public class DataFillService {
if (dataFillForm.getCreateIndex()) {
try {
List<String> sqls = extDDLProvider.createTableIndexSql(dataFillForm.getTableName(), indexes);
List<String> sqls = extDDLProvider.createTableIndexSql(dataFillForm.getTableName(), indexesToCreate);
for (String indexSql : sqls) {
datasourceRequest.setQuery(indexSql);
@ -176,6 +191,151 @@ public class DataFillService {
return ResultHolder.success(dataFillForm.getId());
}
@DeCleaner(value = DePermissionType.DATA_FILL, key = "pid")
public ResultHolder updateForm(DataFillFormWithBLOBs dataFillForm) throws Exception {
if (!CommonBeanFactory.getBean(AuthUserService.class).pluginLoaded()) {
DataEaseException.throwException("invalid");
}
Assert.notNull(dataFillForm.getId(), "id cannot be null");
checkName(dataFillForm.getId(), dataFillForm.getName(), dataFillForm.getPid(), dataFillForm.getNodeType(), DataFillConstants.OPT_TYPE_UPDATE);
DataFillFormWithBLOBs oldForm = dataFillFormMapper.selectByPrimaryKey(dataFillForm.getId());
List<ExtTableField> oldFields = gson.fromJson(oldForm.getForms(), new TypeToken<List<ExtTableField>>() {
}.getType());
List<String> oldFieldIds = oldFields.stream().map(ExtTableField::getId).collect(Collectors.toList());
List<String> oldIndexNames;
if (oldForm.getCreateIndex()) {
List<ExtIndexField> oldIndexes = gson.fromJson(oldForm.getTableIndexes(), new TypeToken<List<ExtIndexField>>() {
}.getType());
oldIndexNames = oldIndexes.stream().map(ExtIndexField::getName).collect(Collectors.toList());
} else {
oldIndexNames = new ArrayList<>();
}
List<ExtTableField> fields = gson.fromJson(dataFillForm.getForms(), new TypeToken<List<ExtTableField>>() {
}.getType());
List<ExtIndexField> indexes = new ArrayList<>();
if (dataFillForm.getCreateIndex()) {
indexes = gson.fromJson(dataFillForm.getTableIndexes(), new TypeToken<List<ExtIndexField>>() {
}.getType());
}
List<ExtTableField> fieldsToCreate = fields.stream().filter(f -> !oldFieldIds.contains(f.getId())).collect(Collectors.toList());
//这里是表单中被删除的字段
List<String> fieldsIds = fields.stream().map(ExtTableField::getId).collect(Collectors.toList());
List<ExtTableField> removedFields = oldFields.stream().filter(f -> !fieldsIds.contains(f.getId()) && !f.isRemoved()).collect(Collectors.toList());
List<ExtTableField> alreadyRemovedFields = oldFields.stream().filter(ExtTableField::isRemoved).collect(Collectors.toList());
//需要修改列名的字段
List<ExtTableField> fieldsToModify = removedFields.stream().filter(f -> !f.isRemoved()).collect(Collectors.toList());
fieldsToModify.forEach(f -> {
f.setRemoved(true);
if (StringUtils.equalsIgnoreCase(f.getType(), "dateRange")) {
f.getSettings().getMapping().setOldColumnName1(f.getSettings().getMapping().getColumnName1());
f.getSettings().getMapping().setOldColumnName2(f.getSettings().getMapping().getColumnName2());
f.getSettings().getMapping().setColumnName1(f.getId() + "_1");
f.getSettings().getMapping().setColumnName2(f.getId() + "_2");
} else {
f.getSettings().getMapping().setOldColumnName(f.getSettings().getMapping().getColumnName());
f.getSettings().getMapping().setColumnName(f.getId());
}
});
Map<String, String> indexColumnItems = new HashMap<>();
fields.forEach(f -> {
if (StringUtils.equalsIgnoreCase(f.getType(), "dateRange")) {
indexColumnItems.put(f.getId() + "_1", f.getSettings().getMapping().getColumnName1());
indexColumnItems.put(f.getId() + "_2", f.getSettings().getMapping().getColumnName2());
} else {
indexColumnItems.put(f.getId(), f.getSettings().getMapping().getColumnName());
}
});
List<ExtIndexField> indexesToCreate = new ArrayList<>();
indexes.stream()
.filter(f -> !oldIndexNames.contains(f.getName())).collect(Collectors.toList())
.forEach(f -> {
ExtIndexField index = gson.fromJson(gson.toJson(f), ExtIndexField.class);
index.getColumns().forEach(c -> {
//根据id获取实际的column名
c.setColumn(indexColumnItems.get(c.getColumn()));
});
indexesToCreate.add(index);
});
if (CollectionUtils.isNotEmpty(fieldsToCreate) || CollectionUtils.isNotEmpty(indexesToCreate) || CollectionUtils.isNotEmpty(fieldsToModify)) {
Datasource ds = dataFillDataService.getDataSource(dataFillForm.getDatasource());
DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setDatasource(ds);
datasourceRequest.setQuery("SELECT VERSION()");
JdbcProvider jdbcProvider = CommonBeanFactory.getBean(JdbcProvider.class);
String version = jdbcProvider.getData(datasourceRequest).get(0)[0];
//拼sql
ExtDDLProvider extDDLProvider = ProviderFactory.gerExtDDLProvider(ds.getType());
if (CollectionUtils.isNotEmpty(fieldsToCreate) || CollectionUtils.isNotEmpty(fieldsToModify)) {
String sql = extDDLProvider.addTableColumnSql(dataFillForm.getTableName(), fieldsToCreate, fieldsToModify);
//创建
datasourceRequest.setQuery(sql);
jdbcProvider.exec(datasourceRequest);
}
if (CollectionUtils.isNotEmpty(indexesToCreate)) {
List<ExtIndexField> successCreatedIndex = new ArrayList<>();
try {
List<String> sqls = extDDLProvider.createTableIndexSql(dataFillForm.getTableName(), indexesToCreate);
for (int i = 0; i < sqls.size(); i++) {
String indexSql = sqls.get(i);
datasourceRequest.setQuery(indexSql);
jdbcProvider.exec(datasourceRequest);
successCreatedIndex.add(indexesToCreate.get(i));
}
} catch (Exception e) {
if (CollectionUtils.isNotEmpty(successCreatedIndex)) {
List<String> sqls = extDDLProvider.dropTableIndexSql(dataFillForm.getTableName(), indexesToCreate);
for (String sql : sqls) {
try {
datasourceRequest.setQuery(sql);
jdbcProvider.exec(datasourceRequest);
} catch (Exception e1) {
//e1.printStackTrace();
}
}
}
if (CollectionUtils.isNotEmpty(fieldsToCreate)) {
// 执行到这里说明表已经建成功了创建index出错那就需要回滚删除创建的字段
datasourceRequest.setQuery(extDDLProvider.dropTableColumnSql(dataFillForm.getTableName(), fields));
jdbcProvider.exec(datasourceRequest);
}
throw e;
}
}
}
//处理被删除的form
if (CollectionUtils.isNotEmpty(fieldsToModify) || CollectionUtils.isNotEmpty(alreadyRemovedFields)) {
List<ExtTableField> finalFields = new ArrayList<>(fields);
finalFields.addAll(fieldsToModify);
finalFields.addAll(alreadyRemovedFields);
dataFillForm.setForms(new Gson().toJson(finalFields));
}
dataFillForm.setUpdateTime(new Date());
dataFillFormMapper.updateByPrimaryKeySelective(dataFillForm);
DeLogUtils.save(SysLogConstants.OPERATE_TYPE.MODIFY, SysLogConstants.SOURCE_TYPE.DATA_FILL_FORM, dataFillForm.getId(), dataFillForm.getPid(), null, null);
return ResultHolder.success(dataFillForm.getId());
}
private void checkName(String id, String name, String pid, String nodeType, String optType) {
DataFillFormExample example = new DataFillFormExample();
if (DataFillConstants.OPT_TYPE_INSERT.equalsIgnoreCase(optType)) {
@ -239,7 +399,7 @@ public class DataFillService {
result.setCreatorName(sysUsers.get(0).getNickName());
}
Datasource d = datasource.get(result.getDatasource());
Datasource d = dataFillDataService.getDataSource(result.getDatasource());
if (d != null) {
result.setDatasourceName(d.getName());
}
@ -321,7 +481,7 @@ public class DataFillService {
} else {
DataFillFormWithBLOBs dataFillForm = dataFillFormMapper.selectByPrimaryKey(formId);
Datasource ds = datasource.get(dataFillForm.getDatasource());
Datasource ds = dataFillDataService.getDataSource(dataFillForm.getDatasource());
Provider datasourceProvider = ProviderFactory.getProvider(ds.getType());
ExtDDLProvider extDDLProvider = ProviderFactory.gerExtDDLProvider(ds.getType());
@ -356,6 +516,9 @@ public class DataFillService {
List<ExtTableField> fields = new ArrayList<>();
for (ExtTableField field : formFields) {
if (field.isRemoved()) {
continue;
}
if (StringUtils.equalsIgnoreCase(field.getType(), "dateRange")) {
ExtTableField start = gson.fromJson(gson.toJson(field), ExtTableField.class);
start.getSettings().getMapping().setColumnName(start.getSettings().getMapping().getColumnName1());
@ -380,6 +543,9 @@ public class DataFillService {
List<ExtTableField> fields = gson.fromJson(dataFillForm.getForms(), new TypeToken<List<ExtTableField>>() {
}.getType());
for (ExtTableField formField : fields) {
if (formField.isRemoved()) {
continue;
}
String name = formField.getSettings().getName();
if (StringUtils.equalsIgnoreCase(formField.getType(), "dateRange")) {

View File

@ -276,6 +276,14 @@ public class DatasourceService {
public void updateDatasource(String id, Datasource datasource) {
if (datasource.getEnableDataFill() != null) {
Datasource ds = datasourceMapper.selectByPrimaryKey(id);
if (ds.getEnableDataFill()) {
datasource.setEnableDataFill(true);
}
}
DatasourceExample example = new DatasourceExample();
example.createCriteria().andIdEqualTo(id);
Status status = checkDatasourceStatus(datasource);
@ -469,7 +477,8 @@ public class DatasourceService {
record.setVersion(status.getVersion());
record.setStatus(status.getStatus());
datasourceMapper.updateByExampleSelective(record, example);
}catch (Exception ignore){}
} catch (Exception ignore) {
}
try {
handleConnectionPool(datasource, "add");
} catch (Exception e) {
@ -652,13 +661,14 @@ public class DatasourceService {
datasourceMapper.updateByPrimaryKeyWithBLOBs(datasource);
}
public void releaseDsconnections(){
List<DefaultJdbcProvider> providers = (List<DefaultJdbcProvider>)SpringContextUtil.getApplicationContext().getBeansOfType(DefaultJdbcProvider.class).values();
providers.forEach(provider ->{
public void releaseDsconnections() {
List<DefaultJdbcProvider> providers = (List<DefaultJdbcProvider>) SpringContextUtil.getApplicationContext().getBeansOfType(DefaultJdbcProvider.class).values();
providers.forEach(provider -> {
provider.getJdbcConnection().values().forEach(druidDataSource -> {
try {
druidDataSource.close();
}catch (Exception e){}
} catch (Exception e) {
}
});
});
}

View File

@ -294,7 +294,7 @@ public class ExportCenterService {
// with DataType
if ((excelTypes[j].equals(DeTypeConstants.DE_INT) || excelTypes[j].equals(DeTypeConstants.DE_FLOAT)) && rowData[j] != null) {
cell.setCellValue(Double.valueOf(rowData[j].toString()));
} else if(rowData[j] != null){
} else if (rowData[j] != null) {
cell.setCellValue(String.valueOf(rowData[j]));
}
} catch (Exception e) {
@ -309,8 +309,8 @@ public class ExportCenterService {
public void findExcelData(PanelViewDetailsRequest request) {
ChartViewWithBLOBs viewInfo = chartViewService.get(request.getViewId());
request.setDownloadType(viewInfo.getType());
if ("table-info".equals(viewInfo.getType())) {
request.setViewType(viewInfo.getType());
if ("table-info".equals(viewInfo.getType()) || "dataset".equals(request.getDownloadType())) {
try {
ChartExtRequest componentFilterInfo = request.getComponentFilterInfo();
componentFilterInfo.setGoPage(1L);
@ -318,9 +318,14 @@ public class ExportCenterService {
componentFilterInfo.setExcelExportFlag(true);
componentFilterInfo.setProxy(request.getProxy());
componentFilterInfo.setUser(request.getUserId());
componentFilterInfo.setDownloadType(request.getDownloadType());
ChartViewDTO chartViewInfo = chartViewService.getData(request.getViewId(), componentFilterInfo);
List<Object[]> tableRow = (List) chartViewInfo.getData().get("sourceData");
request.setDetails(tableRow);
if("dataset".equals(request.getDownloadType())){
request.setHeader((String[]) chartViewInfo.getData().get("header"));
request.setExcelTypes((Integer[]) chartViewInfo.getData().get("dsTypes"));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -409,7 +414,7 @@ public class ExportCenterService {
cellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
//设置单元格填充样式(使用纯色背景颜色填充)
cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
if ("table-info".equals(request.getDownloadType())) {
if ("table-info".equals(request.getViewType())||"dataset".equals(request.getDownloadType())) {
exportTableDetails(request, wb, cellStyle, detailsSheet);
} else {
Boolean mergeHead = false;
@ -501,7 +506,7 @@ public class ExportCenterService {
// with DataType
if ((excelTypes[j].equals(DeTypeConstants.DE_INT) || excelTypes[j].equals(DeTypeConstants.DE_FLOAT)) && StringUtils.isNotEmpty(cellValObj.toString())) {
cell.setCellValue(Double.valueOf(cellValObj.toString()));
} else if(cellValObj != null){
} else if (cellValObj != null) {
cell.setCellValue(cellValObj.toString());
}
} catch (Exception e) {
@ -533,6 +538,7 @@ public class ExportCenterService {
try (FileOutputStream outputStream = new FileOutputStream(dataPath + "/" + request.getViewName() + ".xlsx")) {
wb.write(outputStream);
outputStream.flush();
}
wb.close();
@ -546,7 +552,7 @@ public class ExportCenterService {
exportTask.setExportPogress("100");
exportTask.setExportStatus("SUCCESS");
setFileSize(dataPath + "/" + dataPath + "/" + request.getViewName() + ".xlsx", exportTask);
setFileSize(dataPath + "/" + request.getViewName() + ".xlsx", exportTask);
} catch (Exception e) {
LogUtil.error("Failed to export data", e);
exportTask.setExportStatus("FAILED");
@ -754,6 +760,7 @@ public class ExportCenterService {
exportTaskMapper.updateByPrimaryKey(exportTask);
}
wb.write(fileOutputStream);
fileOutputStream.flush();
fileOutputStream.close();
wb.close();
}

View File

@ -159,7 +159,7 @@ public class PanelGroupService {
panelGroupRequest.setUserId(userId);
panelGroupRequest.setIsAdmin(AuthUtils.getUser().getIsAdmin());
List<PanelGroupDTO> panelGroupDTOList = extPanelGroupMapper.panelGroupList(panelGroupRequest);
return TreeUtils.mergeTree(panelGroupDTOList, "panel_list");
return TreeUtils.mergeTree(panelGroupDTOList, "0");
}
public List<PanelGroupDTO> defaultTree(PanelGroupRequest panelGroupRequest) {

View File

@ -281,7 +281,7 @@ public class PanelLinkService {
List<PanelLinkMapping> mappings = panelLinkMappingMapper.selectByExample(example);
PanelLinkMapping mapping = mappings.get(0);
String uuid = mapping.getUuid();
return contextPath + SHORT_URL_PREFIX + (StringUtils.isBlank(uuid) ? mapping.getId() : uuid);
return (StringUtils.isNotBlank(contextPath) ? contextPath : "") + SHORT_URL_PREFIX + (StringUtils.isBlank(uuid) ? mapping.getId() : uuid);
}
public String saveTicket(TicketCreator creator) {

View File

@ -1 +0,0 @@
server.servlet.context-path=

View File

@ -0,0 +1,3 @@
alter table datasource
add enable_data_fill tinyint(1) default 0 null comment '开启数据填报',
add enable_data_fill_create_table tinyint(1) default 0 null comment '数据填报允许新建表';

View File

@ -85,7 +85,7 @@ i18n_datasource_delete=Data source is deleted
i18n_dataset_delete=Data set is deleted
i18n_dataset_cross_multiple=Cross multiple dataset is not supported
i18n_dataset_no_permission=Data set no permission
i18n_chart_delete=Chart is delete
i18n_chart_delete=Err1001:Chart is delete
i18n_not_exec_add_sync=There is no completed synchronization task. Incremental synchronization cannot be performed
i18n_excel_header_empty=Excel first row can not empty
i18n_excel_empty_column=There are empty cells in the first row
@ -298,4 +298,6 @@ i18n_month=Month
i18n_day=Day
i18n_hour=Hour
i18n_minute=Minute
i18n_second=Second
i18n_second=Second
I18N_DATA_FILL_DATASOURCE_NOT_EXIST=Datasource not exists
I18N_DATA_FILL_DATASOURCE_DEFAULT_BUILT_IN=Built-in Database

View File

@ -85,7 +85,7 @@ i18n_datasource_delete=\u5F53\u524D\u7528\u5230\u7684\u6570\u636E\u6E90\u5DF2\u8
i18n_dataset_delete=\u5F53\u524D\u7528\u5230\u7684\u6570\u636E\u96C6\u5DF2\u88AB\u5220\u9664
i18n_dataset_cross_multiple=\u7981\u6B62\u8DE8\u6570\u636E\u96C6\u591A\u5B57\u6BB5
i18n_dataset_no_permission=\u5F53\u524D\u7528\u5230\u7684\u6570\u636E\u96C6\u6CA1\u6709\u6743\u9650
i18n_chart_delete=\u5F53\u524D\u7528\u5230\u7684\u89C6\u56FE\u5DF2\u88AB\u5220\u9664
i18n_chart_delete=Err1001: \u5F53\u524D\u7528\u5230\u7684\u89C6\u56FE\u5DF2\u88AB\u5220\u9664
i18n_not_exec_add_sync=\u6CA1\u6709\u5DF2\u5B8C\u6210\u7684\u540C\u6B65\u4EFB\u52A1\uFF0C\u65E0\u6CD5\u8FDB\u884C\u589E\u91CF\u540C\u6B65
i18n_excel_header_empty=Excel\u7B2C\u4E00\u884C\u4E3A\u7A7A
i18n_excel_empty_column=\u7B2C\u4E00\u884C\u5B58\u5728\u7A7A\u5355\u5143\u683C
@ -288,4 +288,6 @@ i18n_month=\u6708
i18n_day=\u5929
i18n_hour=\u5C0F\u65F6
i18n_minute=\u5206\u949F
i18n_second=\u79D2
i18n_second=\u79D2
I18N_DATA_FILL_DATASOURCE_NOT_EXIST=\u6570\u636E\u6E90\u6CA1\u6709\u6743\u9650\u6216\u4E0D\u5B58\u5728
I18N_DATA_FILL_DATASOURCE_DEFAULT_BUILT_IN=\u5185\u5EFA\u6570\u636E\u5E93

View File

@ -84,7 +84,7 @@ i18n_datasource_delete=\u7576\u524D\u7528\u5230\u7684\u6578\u64DA\u6E90\u5DF2\u8
i18n_dataset_delete=\u7576\u524D\u7528\u5230\u7684\u6578\u64DA\u96C6\u5DF2\u88AB\u522A\u9664
i18n_dataset_cross_multiple=\u7981\u6B62\u8DE8\u6578\u64DA\u96C6\u591A\u5B57\u6BB5
i18n_dataset_no_permission=\u7576\u524D\u7528\u5230\u7684\u6578\u64DA\u96C6\u6C92\u6709\u6B0A\u9650
i18n_chart_delete=\u7576\u524D\u7528\u5230\u7684\u8996\u5716\u5DF2\u88AB\u522A\u9664
i18n_chart_delete=Err1001: \u7576\u524D\u7528\u5230\u7684\u8996\u5716\u5DF2\u88AB\u522A\u9664
i18n_not_exec_add_sync=\u6C92\u6709\u5DF2\u7D93\u5B8C\u6210\u7684\u540C\u6B65\u4EFB\u52D9\uFF0C\u7121\u6CD5\u9032\u884C\u589E\u91CF\u540C\u6B65
i18n_excel_header_empty=Excel\u7B2C\u4E00\u884C\u70BA\u7A7A
i18n_excel_empty_column=\u7B2C\u4E00\u884C\u5B58\u5728\u7A7A\u55AE\u5143\u683C
@ -293,4 +293,6 @@ i18n_month=\u6708
i18n_day=\u5929
i18n_hour=\u5C0F\u6642
i18n_minute=\u5206\u9418
i18n_second=\u79D2
i18n_second=\u79D2
I18N_DATA_FILL_DATASOURCE_NOT_EXIST=\u6578\u64DA\u6E90\u6C92\u6709\u6B0A\u9650\u6216\u4E0D\u5B58\u5728
I18N_DATA_FILL_DATASOURCE_DEFAULT_BUILT_IN=\u5167\u5EFA\u6578\u64DA\u5EAB

View File

@ -1,6 +1,6 @@
{
"name": "dataease",
"version": "1.18.19",
"version": "1.18.20",
"description": "dataease front",
"private": true,
"scripts": {

View File

@ -447,7 +447,7 @@ export default {
icon: '',
hyperlinks: HYPERLINKS,
mobileStyle: BASE_MOBILE_STYLE,
propValue: imgUrlTrans(fileUrl),
propValue: fileUrl,
commonBackground: deepCopy(COMMON_BACKGROUND),
style: {
...PIC_STYLE

View File

@ -466,7 +466,7 @@ export default {
initFontSize: 12,
initActiveFontSize: 18,
miniFontSize: 12,
maxFontSize: 48,
maxFontSize: 128,
textAlignOptions: [
{
icon: 'iconfont iconfont-custom icon-juzuo',

View File

@ -843,7 +843,7 @@ export default {
if (this.componentData) {
const componentData = deepCopy(this.componentData)
componentData.forEach(component => {
if (component.type === 'custom') {
if (component.type === 'custom' && !this._isMobile) {
const sourceComponent = this.findSourceComponent(component.id)
if (sourceComponent?.style) {
component.style = deepCopy(this.findSourceComponent(component.id).style)

View File

@ -3,7 +3,7 @@
<img
v-if="!showLink"
:style="imageAdapter"
:src="element.propValue"
:src="imgUrlTrans(element.propValue)"
>
<a
v-if="showLink"
@ -13,14 +13,17 @@
>
<img
:style="imageAdapter"
:src="element.propValue"
:src="imgUrlTrans(element.propValue)"
>
</a>
</div>
</template>
<script>
import {imgUrlTrans} from "@/components/canvas/utils/utils";
export default {
methods: {imgUrlTrans},
props: {
element: {
type: Object,

View File

@ -1001,7 +1001,8 @@ export default {
}
},
getData(id, cache = true, dataBroadcast = false) {
if (this.requestStatus === 'waiting') {
// Err1001
if (this.requestStatus === 'waiting' || (this.message && this.message.indexOf('Err1001'))) {
return
}
if (id) {
@ -1109,12 +1110,6 @@ export default {
return true
}).catch(err => {
console.error('err-' + err)
//
if (!this.innerRefreshTimer && this.editMode === 'preview') {
setTimeout(() => {
this.getData(this.element.propValue.viewId)
}, 120000)
}
this.requestStatus = 'error'
if (err.message && err.message.indexOf('timeout') > -1) {
this.message = this.$t('panel.timeout_refresh')
@ -1131,6 +1126,13 @@ export default {
}
}
}
//
if (!this.innerRefreshTimer && this.editMode === 'preview') {
setTimeout(() => {
this.getData(this.element.propValue.viewId)
}, 120000)
}
this.isFirstLoad = false
return true
}).finally(() => {
@ -1439,8 +1441,18 @@ export default {
}
const customAttr = JSON.parse(this.chart.customAttr)
const currentNode = this.findEntityByCode(aCode || customAttr.areaCode, this.places)
let mappingName = null
if (this.chart.senior) {
const senior = JSON.parse(this.chart.senior)
if (senior?.mapMapping[currentNode.code]) {
const mapping = senior.mapMapping[currentNode.code]
if (mapping[name]) {
mappingName = mapping[name]
}
}
}
if (currentNode && currentNode.children && currentNode.children.length > 0) {
const nextNode = currentNode.children.find(item => item.name === name)
const nextNode = currentNode.children.find(item => item.name === name || (mappingName && item.name === mappingName))
this.currentAcreaNode = nextNode
const current = this.$refs[this.element.propValue.id]
if (this.chart.isPlugin) {

View File

@ -116,6 +116,7 @@ export const customAttrTrans = {
'tableItemFontSize',
'tableTitleHeight',
'tableItemHeight',
'tableColumnWidth',
'dimensionFontSize',
'quotaFontSize',
'spaceSplit', // 间隔

View File

@ -299,7 +299,7 @@ export function colorReverse(OldColorValue) {
}
export function imgUrlTrans(url) {
if (url && typeof url === 'string' && url.indexOf('static-resource') > -1) {
if (url && typeof url === 'string' && url.indexOf('static-resource') > -1 && url.indexOf('http') === -1 && url.indexOf('./') === -1) {
return process.env.VUE_APP_BASE_API + url.replace('/static-resource', 'static-resource')
} else {
return url

View File

@ -62,7 +62,6 @@
:is-relation="isRelation"
:element="element"
:in-draw="inDraw"
:in-screen="inScreen"
@filter-loaded="filterLoaded"
/>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -597,6 +597,9 @@ export default {
},
data_fill: {
data_fill: 'Data Filling',
permission: 'Data Filling Permission',
enable: 'Enable',
enable_hint: 'Cannot disable after enable',
new_folder: 'New Folder',
form_manage: 'Form Manage',
my_job: 'My Job',
@ -605,6 +608,8 @@ export default {
rename: 'Rename',
untitled: 'Untitled',
create_new_form: 'Create New Form',
copy_new_form: 'Copy Form',
edit_form: 'Edit Form',
title: 'Title',
no_form: 'Click to Create New',
form_list_name: 'Form List',
@ -643,6 +648,7 @@ export default {
start_hint_word: 'Start Hint Word',
end_hint_word: 'End Hint Word',
input_type: 'Input Type',
date_type: 'Date Format',
check: 'Check',
set_required: 'Set Required',
set_unique: 'Set Unique',
@ -670,7 +676,9 @@ export default {
add_column: 'Add Column',
please_insert_start: 'Start Time Column Name',
please_insert_end: 'End Time Column Name',
save_form: 'Save Form'
save_form: 'Save Form',
default: 'default',
default_built_in: 'Built-in Database'
},
database: {
nvarchar: 'Nvarchar',
@ -720,7 +728,7 @@ export default {
delete: 'Delete',
no_time_limit: 'No Time Limit',
todo: 'Todo',
finished: 'Finished',
finished: 'Committed',
expired: 'Expired',
task_finish_in: 'Task Finished in ',
@ -1820,7 +1828,18 @@ export default {
trend_line: 'Trend Line',
field_enum: 'Enumeration values',
main_axis_label: 'Main Axis Label',
sub_axis_label: 'Sub Axis Label'
sub_axis_label: 'Sub Axis Label',
forecast_enable: 'Enable forecast',
forecast_all_period: 'Use full data',
forecast_all_period_tip: 'Whether to use all data as training data for predictions',
forecast_training_period: 'Training data',
forecast_training_period_tip: 'Intercept the most recent data from all the data as the training data',
forecast_period: 'Forecast period',
forecast_confidence_interval: 'Confidence interval',
forecast_model: 'Forecast model',
forecast_degree: 'Degree',
linear_regression: 'Linear regression',
polynomial_regression: 'Polynomial regression'
},
dataset: {
scope_edit: 'Effective only when editing',

View File

@ -597,6 +597,9 @@ export default {
},
data_fill: {
data_fill: '數據填報',
permission: '填報權限',
enable: '開啟',
enable_hint: '數據填報開啟后,可將表單數據存放至數據源中,一旦開啟后,后期不允許關閉。',
new_folder: '新建文件夾',
form_manage: '表單管理',
my_job: '我的填報',
@ -605,6 +608,8 @@ export default {
rename: '重命名',
untitled: '未命名表單',
create_new_form: '新建表單',
copy_new_form: '復制表單',
edit_form: '編輯表單',
title: '標題',
no_form: '暫無表單,點擊',
form_list_name: '填報表單',
@ -643,6 +648,7 @@ export default {
start_hint_word: '開始提示詞',
end_hint_word: '結束提示詞',
input_type: '格式類型',
date_type: '展示粒度',
check: '校驗',
set_required: '設置為必填項',
set_unique: '不允許重復值',
@ -670,7 +676,9 @@ export default {
add_column: '新增字段',
please_insert_start: '請輸入開始時間',
please_insert_end: '請輸入結束時間',
save_form: '保存表單'
save_form: '保存表單',
default: '默認',
default_built_in: '內建數據庫'
},
database: {
nvarchar: '字符串',
@ -720,7 +728,7 @@ export default {
delete: '刪除',
no_time_limit: '不限時',
todo: '待辦項',
finished: '已完成',
finished: '已提交',
expired: '已過期',
task_finish_in: '在任務下發',
@ -1813,7 +1821,18 @@ export default {
trend_line: '趨勢線',
field_enum: '枚舉值',
main_axis_label: '主軸標籤',
sub_axis_label: '副軸標籤'
sub_axis_label: '副軸標籤',
forecast_enable: '啟用預測',
forecast_all_period: '全量數據',
forecast_all_period_tip: '是否使用所有數據作為馴良數據進行預測',
forecast_training_period: '訓練數據',
forecast_training_period_tip: '從所有數據中截取最近的數據作為訓練數據',
forecast_period: '預測週期',
forecast_confidence_interval: '置信區間',
forecast_model: '預測模型',
forecast_degree: '階數',
linear_regression: '線性回歸',
polynomial_regression: '多項式擬合'
},
dataset: {
scope_edit: '僅編輯時生效',

View File

@ -595,6 +595,9 @@ export default {
},
data_fill: {
data_fill: '数据填报',
permission: '填报权限',
enable: '开启',
enable_hint: '数据填报开启后,可将表单数据存放至数据源中,一旦开启后,后期不允许关闭。',
new_folder: '新建文件夹',
form_manage: '表单管理',
my_job: '我的填报',
@ -603,6 +606,8 @@ export default {
rename: '重命名',
untitled: '未命名表单',
create_new_form: '新建表单',
copy_new_form: '复制表单',
edit_form: '编辑表单',
title: '标题',
no_form: '暂无表单,点击',
form_list_name: '填报表单',
@ -641,6 +646,7 @@ export default {
start_hint_word: '开始提示词',
end_hint_word: '结束提示词',
input_type: '格式类型',
date_type: '展示粒度',
check: '校验',
set_required: '设置为必填项',
set_unique: '不允许重复值',
@ -668,7 +674,9 @@ export default {
add_column: '新增字段',
please_insert_start: '请输入开始时间',
please_insert_end: '请输入结束时间',
save_form: '保存表单'
save_form: '保存表单',
default: '默认',
default_built_in: '内建数据库'
},
database: {
nvarchar: '字符串',
@ -718,7 +726,7 @@ export default {
delete: '删除',
no_time_limit: '不限时',
todo: '待办项',
finished: '已完成',
finished: '已提交',
expired: '已过期',
task_finish_in: '在任务下发',
task_finish_in_suffix: '内完成填报'
@ -1810,7 +1818,18 @@ export default {
trend_line: '趋势线',
field_enum: '枚举值',
main_axis_label: '主轴标签',
sub_axis_label: '副轴标签'
sub_axis_label: '副轴标签',
forecast_enable: '启用预测',
forecast_all_period: '全量数据',
forecast_all_period_tip: '是否使用所有数据作为训练数据进行预测',
forecast_training_period: '训练数据',
forecast_training_period_tip: '从所有数据中截取最近的数据作为训练数据',
forecast_period: '预测周期',
forecast_confidence_interval: '置信区间',
forecast_model: '预测模型',
forecast_degree: '阶数',
linear_regression: '线性回归',
polynomial_regression: '多项式拟合'
},
dataset: {
goto: ', 前往 ',

View File

@ -114,6 +114,33 @@ export function baseBarOptionAntV(container, chart, action, isGroup, isStack) {
} else {
delete options.groupField
}
// forecast
if (chart.data?.forecastData?.length) {
const { forecastData } = chart.data
const templateData = data?.[data.length - 1]
forecastData.forEach(item => {
data.push({
...templateData,
field: item.dimension,
name: item.dimension,
value: item.quota,
forecast: true
})
})
analyse.push({
type: 'region',
start: xScale => {
const ratio = xScale.ticks ? 1 / xScale.ticks.length : 1
const x = xScale.scale(forecastData[0].dimension) - ratio / 2
return [`${x * 100}%`, '0%']
},
end: (xScale) => {
const ratio = xScale.ticks ? 1 / xScale.ticks.length : 1
const x = xScale.scale(forecastData[forecastData.length - 1].dimension) + ratio / 2
return [`${x * 100}%`, '100%']
}
})
}
// 目前只有百分比堆叠柱状图需要这个属性,先直接在这边判断而不作为参数传过来
options.isPercent = chart.type === 'percentage-bar-stack'
// custom color

View File

@ -261,6 +261,16 @@ export function getSize(chart) {
p[n.fieldId] = n
return p
}, {}) || {}
// 下钻字段使用入口字段的宽度
if (chart.drill) {
const xAxis = JSON.parse(chart.xaxis)
const curDrillField = chart.drillFields[chart.drillFilters.length]
const drillEnterFieldIndex = xAxis.findIndex(item => item.id === chart.drillFilters[0].fieldId)
const drillEnterField = xAxis[drillEnterFieldIndex]
fieldMap[curDrillField.dataeaseName] = {
width: fieldMap[drillEnterField.dataeaseName]?.width
}
}
size.colCfg.width = node => {
const width = node.spreadsheet.container.cfg.el.offsetWidth
if (!s.tableFieldWidth?.length) {

View File

@ -94,6 +94,39 @@ export function baseLineOptionAntV(container, chart, action) {
}
}
}
// forecast
if (chart.data?.forecastData?.length) {
const { forecastData } = chart.data
const templateData = data?.[data.length - 1]
forecastData.forEach(item => {
data.push({
...templateData,
field: item.dimension,
name: item.dimension,
value: item.quota,
forecast: true
})
})
analyse.push({
type: 'region',
start: (xScale) => {
if (forecastData.length > 1) {
return [forecastData[0].dimension, 'min']
}
const ratio = xScale.ticks ? 1 / xScale.ticks.length : 1
const x = xScale.scale(forecastData[0].dimension) - ratio / 2
return [`${x * 100}%`, '0%']
},
end: (xScale) => {
if (forecastData.length > 1) {
return [forecastData[forecastData.length - 1].dimension, 'max']
}
const ratio = xScale.ticks ? 1 / xScale.ticks.length : 1
const x = xScale.scale(forecastData[forecastData.length - 1].dimension) + ratio / 2
return [`${x * 100}%`, '100%']
}
})
}
// custom color
options.color = antVCustomColor(chart)
// 处理空值

View File

@ -116,6 +116,7 @@ export function baseTableInfo(container, chart, action, tableData, pageInfo, vue
// 移除所有下钻字段,调整当前下钻字段到下钻入口位置
fields = fields.filter(item => !drillFilters.includes(item.id))
fields.splice(drillEnterFieldIndex, 0, curDrillField)
nameMap[curDrillField.dataeaseName] = curDrillField
}
fields.forEach(ele => {
const f = nameMap[ele.dataeaseName]
@ -1062,30 +1063,30 @@ function customCalcFunc(query, data, totalCfgMap) {
switch (aggregation) {
case 'SUM': {
return data.reduce((p, n) => {
return p + n[query[EXTRA_FIELD]]
return p + parseFloat(n[query[EXTRA_FIELD]])
}, 0)
}
case 'AVG': {
const sum = data.reduce((p, n) => {
return p + n[query[EXTRA_FIELD]]
return p + parseFloat(n[query[EXTRA_FIELD]])
}, 0)
return sum / data.length
}
case 'MIN': {
const result = minBy(data, n => {
return n[query[EXTRA_FIELD]]
return parseFloat(n[query[EXTRA_FIELD]])
})
return result?.[query[EXTRA_FIELD]]
}
case 'MAX': {
const result = maxBy(data, n => {
return n[query[EXTRA_FIELD]]
return parseFloat(n[query[EXTRA_FIELD]])
})
return result?.[query[EXTRA_FIELD]]
}
default: {
return data.reduce((p, n) => {
return p + n[query[EXTRA_FIELD]]
return p + parseFloat(n[query[EXTRA_FIELD]])
}, 0)
}
}

View File

@ -1,12 +1,13 @@
import {
configPlotTooltipEvent,
getLabel,
getLegend,
getPadding,
getTheme,
getTooltip
} from '@/views/chart/chart/common/common_antv'
import { Treemap } from '@antv/g2plot'
import { formatterItem, valueFormatter } from '@/views/chart/chart/formatter'
import { parseJson } from '@/views/chart/chart/util'
export function baseTreemapOptionAntV(container, chart, action) {
// theme
@ -57,3 +58,35 @@ export function baseTreemapOptionAntV(container, chart, action) {
configPlotTooltipEvent(chart, plot)
return plot
}
function getLabel(chart) {
const { label: labelAttr } = JSON.parse(chart.customAttr)
if (!labelAttr?.show) {
return false
}
const yAxis = parseJson(chart.yaxis)
const labelFormatter = yAxis?.[0].formatterCfg ?? formatterItem
return {
style: {
fill: labelAttr.color,
fontSize: labelAttr.fontSize
},
formatter: function(param) {
const labelContent = labelAttr.labelContent ?? ['quota']
const contentItems = []
if (labelContent.includes('dimension')) {
contentItems.push(param.field)
}
if (labelContent.includes('quota')) {
contentItems.push(valueFormatter(param.value, labelFormatter))
}
if (labelContent.includes('proportion')) {
const percentage = `${(((param.value / param.parent.value) * 10000) / 100).toFixed(
labelAttr.reserveDecimalCount
)}%`
contentItems.push(percentage)
}
return contentItems.join('\n')
}
}
}

View File

@ -1819,7 +1819,9 @@ export const TYPE_CONFIGS = [
'label-selector-ant-v': [
'show',
'fontSize',
'color'
'color',
'labelContent',
'reserveDecimalCount'
],
'tooltip-selector-ant-v': [
'show',

View File

@ -450,11 +450,12 @@ export default {
const base_json = JSON.parse(JSON.stringify(BASE_MAP))
base_json.geo.map = mapId
let themeStyle = null
let panelColor = '#FFFFFF'
if (this.themeStyle) {
themeStyle = JSON.parse(JSON.stringify(this.themeStyle))
if (themeStyle && themeStyle.backgroundColorSelect) {
const panelColor = themeStyle.color
panelColor = themeStyle.color
if (panelColor !== '#FFFFFF') {
const reverseValue = reverseColor(panelColor)
this.buttonTextColor = reverseValue
@ -462,7 +463,7 @@ export default {
this.buttonTextColor = null
}
} else if (this.canvasStyleData.openCommonStyle && this.canvasStyleData.panel.backgroundType === 'color') {
const panelColor = this.canvasStyleData.panel.color
panelColor = this.canvasStyleData.panel.color
if (panelColor !== '#FFFFFF') {
const reverseValue = reverseColor(panelColor)
this.buttonTextColor = reverseValue
@ -474,6 +475,13 @@ export default {
}
}
const chart_option = baseMapOption(base_json, geoJson, chart, this.buttonTextColor, curAreaCode, this.currentSeriesId)
if (chart_option.geo.itemStyle.normal) {
chart_option.geo.itemStyle.normal.areaColor = `${panelColor}33`
} else {
chart_option.geo.itemStyle.normal = {
areaColor: `${panelColor}33`
}
}
if (chart_option.series?.length) {
const dataNames = []
chart_option.series.filter(se => se.type === 'map').forEach(se => {

View File

@ -540,7 +540,10 @@ export default {
initScroll() {
const customAttr = JSON.parse(this.chart.customAttr)
const senior = JSON.parse(this.chart.senior)
if (senior?.scrollCfg?.open && (this.chart.type === 'table-normal' || (this.chart.type === 'table-info' && !this.showPage))) {
if (senior?.scrollCfg?.open) {
if (this.chart.type === 'table-info' && this.showPage) {
return
}
//
this.myChart.facet.timer?.stop()
if (this.myChart.store.get('scrollY') !== 0) {
@ -556,15 +559,27 @@ export default {
}
const rowHeight = customAttr.size.tableItemHeight
const headerHeight = customAttr.size.tableTitleHeight
const scrollBarSize = this.myChart.theme.scrollBar.size
const scrollHeight = rowHeight * this.chart.data.tableRow.length + headerHeight - offsetHeight + scrollBarSize
//
if (scrollHeight < scrollBarSize) {
return
let duration, scrollHeight
if (this.chart.type === 'table-pivot') {
const totalHeight = this.myChart.facet.viewCellHeights.getTotalHeight()
const viewHeight = this.myChart.facet.rowHeader.cfg.viewportHeight
if (totalHeight <= viewHeight) {
return
}
scrollHeight = totalHeight - viewHeight
const scrollViewCount = (totalHeight - viewHeight) / rowHeight
duration = scrollViewCount / senior.scrollCfg.row * senior.scrollCfg.interval
} else {
const scrollBarSize = this.myChart.theme.scrollBar.size
scrollHeight = rowHeight * this.chart.data.tableRow.length + headerHeight - offsetHeight + scrollBarSize
//
if (scrollHeight < scrollBarSize) {
return
}
const viewHeight = offsetHeight - headerHeight - scrollBarSize
const scrollViewCount = this.chart.data.tableRow.length - viewHeight / rowHeight
duration = scrollViewCount / senior.scrollCfg.row * senior.scrollCfg.interval
}
const viewHeight = offsetHeight - headerHeight - scrollBarSize
const scrollViewCount = this.chart.data.tableRow.length - viewHeight / rowHeight
const duration = scrollViewCount / senior.scrollCfg.row * senior.scrollCfg.interval
this.myChart.facet.scrollWithAnimation({
offsetY: {
value: scrollHeight,

View File

@ -48,7 +48,7 @@
<span
v-else-if="compareItem.compareCalc.resultData === 'percent'"
class="exp-style"
>(本期数据 / 上期数据 - 1) * 100%</span>
>(本期数据 / |上期数据| - 1) * 100%</span>
</el-form-item>
</el-form>
</div>

View File

@ -146,7 +146,13 @@
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
<el-dropdown-item
icon="el-icon-edit-outline"
divided
:command="beforeClickItem('rename')"
>
<span>{{ $t('chart.show_name_set') }}</span>
</el-dropdown-item>
<el-dropdown-item
icon="el-icon-delete"
:command="beforeClickItem('remove')"
@ -161,7 +167,7 @@
</template>
<script>
import { getItemType } from '@/views/chart/components/dragItem/utils'
import { getItemType, getOriginFieldName } from '@/views/chart/components/dragItem/utils'
import FieldErrorTips from '@/views/chart/components/dragItem/components/FieldErrorTips'
import bus from '@/utils/bus'
@ -234,6 +240,9 @@ export default {
return
}
switch (param.type) {
case 'rename':
this.showRename()
break
case 'remove':
this.removeItem()
break
@ -246,6 +255,15 @@ export default {
type: type
}
},
showRename() {
this.item.index = this.index
this.item.renameType = 'drill'
if (this.specialType) {
this.item.renameType = this.specialType
}
this.item.dsFieldName = getOriginFieldName(this.dimensionData, this.quotaData, this.item)
this.$emit('onNameEdit', this.item)
},
removeItem() {
this.item.index = this.index
this.$emit('onDimensionItemRemove', this.item)

View File

@ -0,0 +1,276 @@
<template>
<div>
<el-form
ref="forecastForm"
:model="forecastCfg"
:rules="rules"
label-width="80px"
size="mini"
@submit.native.prevent
>
<el-form-item
class="form-item"
:label="$t('chart.forecast_enable')"
>
<el-checkbox
v-model="forecastCfg.enable"
@change="onForecastChange"
/>
</el-form-item>
<el-form-item
class="form-item"
:label="$t('chart.forecast_all_period')"
>
<el-checkbox
v-model="forecastCfg.allPeriod"
:disabled="!forecastCfg.enable"
@change="onForecastChange"
/>
<el-tooltip
class="item"
effect="dark"
placement="bottom"
>
<div
slot="content"
>
{{ $t('chart.forecast_all_period_tip') }}
</div>
<i
class="el-icon-info"
style="cursor: pointer;color: #606266;margin-left: 4px;"
/>
</el-tooltip>
</el-form-item>
<el-form-item
v-if="!forecastCfg.allPeriod"
class="form-item"
prop="trainingPeriod"
:label="$t('chart.forecast_training_period')"
>
<el-input-number
v-model="forecastCfg.trainingPeriod"
:disabled="!forecastCfg.enable"
:min="5"
:max="1000"
:step="1"
:precision="0"
step-strictly
size="mini"
@change="onForecastChange"
/>
<el-tooltip
class="item"
effect="dark"
placement="bottom"
>
<div
slot="content"
>{{ $t('chart.forecast_training_period_tip') }}
</div>
<i
class="el-icon-info"
style="cursor: pointer;color: #606266;margin-left: 4px;"
/>
</el-tooltip>
</el-form-item>
<el-form-item
class="form-item"
prop="period"
:label="$t('chart.forecast_period')"
>
<el-input-number
v-model="forecastCfg.period"
:disabled="!forecastCfg.enable"
:min="1"
:max="100"
:precision="0"
step-strictly
size="mini"
@change="onForecastChange"
/>
</el-form-item>
<el-form-item
v-show="false"
class="form-item"
:label="$t('chart.forecast_confidence_interval')"
>
<el-select
v-model="forecastCfg.ciType"
:disabled="!forecastCfg.enable"
@change="onForecastChange"
>
<el-option
v-for="item in ciOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-show="false"
v-if="forecastCfg.ciType === 'custom'"
class="form-item"
prop="confidenceInterval"
:label="$t('chart.custom_case')"
>
<el-input-number
v-model="forecastCfg.confidenceInterval"
:disabled="!forecastCfg.enable"
:max="0.99"
:min="0.75"
:step="0.01"
:precision="2"
step-strictly
size="mini"
@change="onForecastChange"
/>
</el-form-item>
<el-form-item
class="form-item"
:label="$t('chart.forecast_model')"
>
<el-select
v-model="forecastCfg.algorithm"
:disabled="!forecastCfg.enable"
@change="onForecastChange"
>
<el-option
v-for="item in algorithmOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
v-if="forecastCfg.algorithm === 'polynomial-regression'"
class="form-item"
prop="degree"
:label="$t('chart.forecast_degree')"
>
<el-input-number
v-model="forecastCfg.degree"
:disabled="!forecastCfg.enable"
:max="10"
:min="1"
:precision="0"
step-strictly
size="mini"
@change="onForecastChange"
/>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
name: 'DataForecast',
props: {
chart: {
required: true,
type: Object
}
},
data() {
return {
forecastCfg: {
enable: false,
period: 3,
allPeriod: true,
trainingPeriod: 120,
confidenceInterval: 0.95,
ciType: 0.95,
algorithm: 'linear-regression',
customCi: 0.95,
degree: 3
},
algorithmOptions: [
{ name: this.$t('chart.linear_regression'), value: 'linear-regression' },
{ name: this.$t('chart.polynomial_regression'), value: 'polynomial-regression' }
],
ciOptions: [
{ name: '90%', value: 0.90 },
{ name: '95%', value: 0.95 },
{ name: '99%', value: 0.99 },
{ name: '自定义', value: 'custom' }
],
rules: {
trainingPeriod: [{ required: true, trigger: 'change', message: this.$t('commons.cannot_be_null') }],
period: [{ required: true, trigger: 'change', message: this.$t('commons.cannot_be_null') }],
degree: [{ required: true, trigger: 'change', message: this.$t('commons.cannot_be_null') }],
confidenceInterval: [{ required: true, trigger: 'change', message: this.$t('commons.cannot_be_null') }]
}
}
},
watch: {
chart: {
handler: function() {
this.init()
}
}
},
mounted() {
this.init()
},
methods: {
init() {
const chart = JSON.parse(JSON.stringify(this.chart))
if (chart.senior) {
let senior = null
if (Object.prototype.toString.call(chart.senior) === '[object Object]') {
senior = JSON.parse(JSON.stringify(chart.senior))
} else {
senior = JSON.parse(chart.senior)
}
if (senior.forecastCfg) {
this.forecastCfg = senior.forecastCfg
}
}
},
onForecastChange() {
this.$refs.forecastForm.validate((valid) => {
if (!valid) {
return
}
if (this.forecastCfg.ciType !== 'custom') {
this.forecastCfg.confidenceInterval = this.forecastCfg.ciType
}
this.$emit('onForecastChange', this.forecastCfg)
})
}
}
}
</script>
<style lang="scss" scoped>
.form-item-slider ::v-deep .el-form-item__label {
font-size: 12px;
line-height: 38px;
}
.form-item-range-slider ::v-deep .el-form-item__content {
padding-right: 6px
}
.form-item ::v-deep .el-form-item__label {
font-size: 12px;
}
.form-item ::v-deep .el-checkbox__label {
font-size: 12px;
}
.form-item ::v-deep .el-radio__label {
font-size: 12px;
}
.el-select-dropdown__item {
padding: 0 20px;
}
span {
font-size: 12px
}
</style>

View File

@ -16,6 +16,7 @@
@change="changeScrollCfg"
>{{ $t('chart.open') }}</el-checkbox>
<el-tooltip
v-show="chart.type === 'table-info'"
class="item"
effect="dark"
placement="bottom"

View File

@ -350,13 +350,6 @@ export default {
},
computed: {
labelContentOptions() {
if (this.chart.type.includes('pie')) {
return [
{ name: this.$t('chart.dimension'), value: 'dimension' },
{ name: this.$t('chart.quota'), value: 'quota' },
{ name: this.$t('chart.proportion'), value: 'proportion' }
]
}
if (this.chart.type.includes('bar')) {
return [
{ name: this.$t('chart.chart_group'), value: 'group' },
@ -364,7 +357,11 @@ export default {
{ name: this.$t('chart.quota'), value: 'quota' }
]
}
return []
return [
{ name: this.$t('chart.dimension'), value: 'dimension' },
{ name: this.$t('chart.quota'), value: 'quota' },
{ name: this.$t('chart.proportion'), value: 'proportion' }
]
}
},
watch: {

View File

@ -1186,6 +1186,7 @@
:chart="chart"
@onDimensionItemChange="drillItemChange"
@onDimensionItemRemove="drillItemRemove"
@onNameEdit="showRename"
@onCustomSort="item => onCustomSort(item, 'drillFields')"
/>
</transition-group>
@ -1361,6 +1362,18 @@
@onTrendLineChange="onTrendLineChange"
/>
</el-collapse-item>
<el-collapse-item
v-if="showDataForecastCfg"
name="data-forecast"
title="数据预测"
>
<data-forecast
class="attr-selector"
:chart="chart"
:quota-data="view.yaxis"
@onForecastChange="onForecastChange"
/>
</el-collapse-item>
</el-collapse>
</el-row>
@ -1937,9 +1950,11 @@ import PositionAdjust from '@/views/chart/view/PositionAdjust'
import MarkMapDataEditor from '@/views/chart/components/map/MarkMapDataEditor'
import TrendLine from '@/views/chart/components/senior/TrendLine'
import ChartTitleUpdate from './ChartTitleUpdate'
import DataForecast from '@/views/chart/components/senior/DataForecast'
export default {
name: 'ChartEdit',
components: {
DataForecast,
PositionAdjust,
ScrollCfg,
CalcChartFieldEdit,
@ -2119,7 +2134,7 @@ export default {
},
computed: {
filedList() {
return [...this.dimension, ...this.quota].filter(ele => ele.id !== 'count')
return [...this.dimension, ...this.quota].filter(ele => ele.id !== 'count' && !ele.chartId)
},
obj() {
return {
@ -2174,7 +2189,7 @@ export default {
equalsAny(this.view.type, 'map', 'text')
},
showScrollCfg() {
return equalsAny(this.view.type, 'table-normal', 'table-info')
return equalsAny(this.view.type, 'table-normal', 'table-info', 'table-pivot')
},
showAnalyseCfg() {
if (this.view.type === 'bidirectional-bar' || this.view.type === 'bar-time-range') {
@ -2191,6 +2206,9 @@ export default {
showTrendLineCfg() {
return this.view.render === 'antv' && equalsAny(this.view.type, 'line')
},
showDataForecastCfg() {
return this.view.render === 'antv' && equalsAny(this.view.type, 'line', 'bar')
},
showThresholdCfg() {
if (this.view.type === 'bidirectional-bar') {
return false
@ -3077,7 +3095,10 @@ export default {
this.view.senior.trendLine = val
this.calcData()
},
onForecastChange(val) {
this.view.senior.forecastCfg = val
this.calcData()
},
onThresholdChange(val) {
this.view.senior.threshold = val
this.calcData()
@ -3182,6 +3203,8 @@ export default {
this.view.xaxisExt[this.itemForm.index].name = this.itemForm.name
} else if (this.itemForm.renameType === 'extStack') {
this.view.extStack[this.itemForm.index].name = this.itemForm.name
} else if (this.itemForm.renameType === 'drill') {
this.view.drillFields[this.itemForm.index].name = this.itemForm.name
}
this.calcData(true)
this.closeRename()
@ -3598,8 +3621,18 @@ export default {
aCode = this.currentAcreaNode.code
}
const currentNode = this.findEntityByCode(aCode || this.view.customAttr.areaCode, this.places)
let mappingName = null
if (this.chart.senior) {
const senior = JSON.parse(this.chart.senior)
if (senior?.mapMapping[currentNode.code]) {
const mapping = senior.mapMapping[currentNode.code]
if (mapping[name]) {
mappingName = mapping[name]
}
}
}
if (currentNode && currentNode.children && currentNode.children.length > 0) {
const nextNode = currentNode.children.find(item => item.name === name)
const nextNode = currentNode.children.find(item => item.name === name || (mappingName && item.name === mappingName))
if (!nextNode || !nextNode.code) return null
this.currentAcreaNode = nextNode
const current = this.$refs.dynamicChart

View File

@ -17,6 +17,7 @@
@click.native="drillJump(index + 1)"
>
<span
class="drill-label"
:style="{'color': textColor}"
:title="filter.value[0]"
>{{ filter.value[0] }}</span>
@ -100,8 +101,12 @@ export default {
margin: 0!important;
}
.drill-item{
max-width: 120px;
overflow: hidden;
cursor: pointer;
}
.drill-label {
display: inline-block;
max-width: 120px;
white-space: nowrap;
overflow: hidden;
}
</style>

View File

@ -1,5 +1,5 @@
<script>
import { forEach, find, concat, cloneDeep, floor } from 'lodash-es'
import { forEach, find, concat, cloneDeep, floor, map, filter, includes } from 'lodash-es'
import { PHONE_REGEX, EMAIL_REGEX } from '@/utils/validate'
import { newFormRowData, saveFormRowData, userFillFormData } from '@/views/dataFilling/form/dataFilling'
@ -89,24 +89,51 @@ export default {
mounted() {
this.formData = []
forEach(this.forms, v => {
const f = cloneDeep(v)
if (f.type === 'dateRange') {
const _start = this.data[f.settings.mapping.columnName1]
const _end = this.data[f.settings.mapping.columnName2]
f.value = [_start, _end]
} else {
const _value = this.data[f.settings.mapping.columnName]
if (f.type === 'select' && f.settings.multiple || f.type === 'checkbox') {
if (_value) {
f.value = JSON.parse(_value)
} else {
f.value = []
}
} else {
f.value = _value
if (!v.removed) {
const f = cloneDeep(v)
if (f.type === 'date' && f.settings.dateType === undefined) { //
f.settings.dateType = f.settings.enableTime ? 'datetime' : 'date'
}
if (f.type === 'dateRange' && f.settings.dateType === undefined) { //
f.settings.dateType = f.settings.enableTime ? 'datetimerange' : 'daterange'
}
if (f.type === 'dateRange') {
const _start = this.data[f.settings.mapping.columnName1]
const _end = this.data[f.settings.mapping.columnName2]
f.value = [_start, _end]
} else {
const _value = this.data[f.settings.mapping.columnName]
if (f.type === 'select' && f.settings.multiple || f.type === 'checkbox') {
if (_value) {
//
if (this.readonly) {
f.value = JSON.parse(_value)
} else {
const options = map(f.settings.options, f => f.value)
f.value = filter(JSON.parse(_value), v => includes(options, v))
}
} else {
f.value = []
}
} else if (f.type === 'select' && !f.settings.multiple || f.type === 'radio') {
if (_value) {
if (!this.readonly) {
const options = map(f.settings.options, f => f.value)
if (!includes(options, _value)) {
f.value = undefined
} else {
f.value = _value
}
} else {
f.value = _value
}
}
} else {
f.value = _value
}
}
this.formData.push(f)
}
this.formData.push(f)
})
},
methods: {
@ -341,46 +368,22 @@ export default {
</el-checkbox>
</el-checkbox-group>
<el-date-picker
v-else-if="item.type === 'date' && !item.settings.enableTime"
v-else-if="item.type === 'date'"
v-model="item.value"
:required="item.settings.required"
:readonly="readonly"
type="date"
:type="item.settings.dateType"
:placeholder="item.settings.placeholder"
style="width: 100%"
size="small"
:picker-options="pickerOptions"
/>
<el-date-picker
v-else-if="item.type === 'date' && item.settings.enableTime"
v-else-if="item.type === 'dateRange'"
v-model="item.value"
:required="item.settings.required"
:readonly="readonly"
type="datetime"
:placeholder="item.settings.placeholder"
style="width: 100%"
size="small"
:picker-options="pickerOptions"
/>
<el-date-picker
v-else-if="item.type === 'dateRange' && !item.settings.enableTime"
v-model="item.value"
:required="item.settings.required"
:readonly="readonly"
type="daterange"
:range-separator="item.settings.rangeSeparator"
:start-placeholder="item.settings.startPlaceholder"
:end-placeholder="item.settings.endPlaceholder"
style="width: 100%"
size="small"
:picker-options="pickerOptions"
/>
<el-date-picker
v-else-if="item.type === 'dateRange' && item.settings.enableTime"
v-model="item.value"
:required="item.settings.required"
:readonly="readonly"
type="datetimerange"
:type="item.settings.dateType"
:range-separator="item.settings.rangeSeparator"
:start-placeholder="item.settings.startPlaceholder"
:end-placeholder="item.settings.endPlaceholder"

View File

@ -72,6 +72,11 @@
:span="8"
>
<!-- 编辑 todo -->
<el-button
v-if="hasDataPermission('manage', param.privileges)"
type="primary"
@click="editForm(param)"
>{{ $t('panel.edit') }}</el-button>
</el-col>
</el-row>
@ -175,9 +180,9 @@
<span
v-if="c.date && scope.row.data[c.props]"
style="white-space:nowrap; width: fit-content"
:title="formatDate(scope.row.data[c.props], c.enableTime)"
:title="formatDate(scope.row.data[c.props], c.dateType)"
>
{{ formatDate(scope.row.data[c.props], c.enableTime) }}
{{ formatDate(scope.row.data[c.props], c.dateType) }}
</span>
<template v-else-if="(c.type === 'select' && c.multiple || c.type === 'checkbox') && scope.row.data[c.props]">
<div
@ -612,13 +617,13 @@ export default {
},
columns: function() {
const _list = []
forEach(this.forms, f => {
forEach(filter(this.forms, f => !f.removed), f => {
if (f.type === 'dateRange') {
_list.push({
props: f.settings?.mapping?.columnName1,
label: f.settings?.name,
date: true,
enableTime: f.settings?.enableTime,
dateType: f.settings?.dateType ? f.settings?.dateType : (f.settings?.enableTime ? 'datetimerange' : 'daterange'),
type: f.type,
multiple: !!f.settings.multiple,
rangeIndex: 0
@ -627,7 +632,7 @@ export default {
props: f.settings?.mapping?.columnName2,
label: f.settings?.name,
date: true,
enableTime: f.settings?.enableTime,
dateType: f.settings?.dateType ? f.settings?.dateType : (f.settings?.enableTime ? 'datetimerange' : 'daterange'),
type: f.type,
multiple: !!f.settings.multiple,
rangeIndex: 1
@ -637,7 +642,7 @@ export default {
props: f.settings?.mapping?.columnName,
label: f.settings?.name,
date: f.type === 'date',
enableTime: f.type === 'date' && f.settings?.enableTime,
dateType: f.type === 'date' ? (f.settings?.dateType ? f.settings?.dateType : (f.settings?.enableTime ? 'datetime' : 'date')) : undefined,
type: f.type,
multiple: !!f.settings.multiple
})
@ -868,6 +873,10 @@ export default {
}*/
},
editForm(param) {
this.$emit('editForm', param)
},
showData(row) {
searchTable(this.param.id, {
primaryKeyValue: row.dataId,
@ -952,14 +961,21 @@ export default {
}).catch(() => {
})
},
formatDate(value, enableTime) {
formatDate(value, dateType) {
if (!value) {
return value
}
if (enableTime) {
return value.format('yyyy-MM-dd hh:mm:ss')
} else {
return value.format('yyyy-MM-dd')
switch (dateType) {
case 'year':
return value.format('yyyy')
case 'month':
case 'monthrange':
return value.format('yyyy-MM')
case 'datetime':
case 'datetimerange':
return value.format('yyyy-MM-dd hh:mm:ss')
default:
return value.format('yyyy-MM-dd')
}
},

View File

@ -2,9 +2,10 @@
import DeContainer from '@/components/dataease/DeContainer.vue'
import DataFillingFormSave from './save.vue'
import clickoutside from 'element-ui/src/utils/clickoutside.js'
import { filter, cloneDeep, find, concat } from 'lodash-es'
import { filter, cloneDeep, find, concat, forEach } from 'lodash-es'
import { v4 as uuidv4 } from 'uuid'
import { EMAIL_REGEX, PHONE_REGEX } from '@/utils/validate'
import { getWithPrivileges } from '@/views/dataFilling/form/dataFilling'
export default {
name: 'DataFillingFormCreate',
@ -24,10 +25,22 @@ export default {
callback()
}
return {
moveId: undefined,
showDrawer: false,
isEdit: false,
disableCreateIndex: false,
requiredRule: { required: true, message: this.$t('commons.required'), trigger: ['blur', 'change'] },
duplicateOptionRule: { validator: checkDuplicateOptionValidator, trigger: ['blur', 'change'] },
dateTypes: [
{ name: this.$t('chart.y'), value: 'year' },
{ name: this.$t('chart.y_M'), value: 'month' },
{ name: this.$t('chart.y_M_d'), value: 'date' },
{ name: this.$t('chart.y_M_d_H_m_s'), value: 'datetime' }
],
dateRangeTypes: [
{ name: this.$t('chart.y_M'), value: 'monthrange' },
{ name: this.$t('chart.y_M_d'), value: 'daterange' },
{ name: this.$t('chart.y_M_d_H_m_s'), value: 'datetimerange' }
],
inputTypes: [
{ type: 'text', name: this.$t('data_fill.form.text'), rules: [] },
{ type: 'number', name: this.$t('data_fill.form.number'), rules: [] },
@ -148,7 +161,8 @@ export default {
id: undefined,
settings: {
name: this.$t('commons.component.date'),
enableTime: false,
enableTime: false, //
dateType: 'date',
placeholder: '',
required: false,
mapping: {
@ -167,7 +181,8 @@ export default {
id: undefined,
settings: {
name: this.$t('commons.component.dateRange'),
enableTime: false,
enableTime: false, //
dateType: 'daterange',
rangeSeparator: '-',
startPlaceholder: '',
endPlaceholder: '',
@ -221,23 +236,88 @@ export default {
return find(this.formSettings.forms, f => f.id === this.selectedItemId)
}
return undefined
},
selectedComponentItemInputTypes() {
if (this.selectedComponentItem && this.selectedComponentItem.type === 'input') {
if (this.isEdit && this.selectedComponentItem.old) {
if (this.selectedComponentItem.settings.inputType === 'number') {
return filter(this.inputTypes, t => t.type === 'number')
} else {
return filter(this.inputTypes, t => t.type !== 'number')
}
}
}
return this.inputTypes
}
},
beforeDestroy() {
},
created() {
this.isEdit = false
this.disableCreateIndex = false
if (this.$route.query.folder !== undefined) {
this.formSettings.folder = this.$route.query.folder
}
if (this.$route.query.level !== undefined) {
this.formSettings.level = this.$route.query.level
}
if (this.$route.query.copy !== undefined) {
const id = this.$route.query.copy
getWithPrivileges(id).then(res => {
const tempData = res.data
this.formSettings.folder = tempData.pid
this.formSettings.level = tempData.level
this.formSettings.forms = JSON.parse(tempData.forms)
})
} else if (this.$route.query.id !== undefined) {
const id = this.$route.query.id
getWithPrivileges(id).then(res => {
this.isEdit = true
const tempData = cloneDeep(res.data)
this.formSettings = tempData
this.formSettings.table = tempData.tableName
this.formSettings.folder = tempData.pid
const tempForms = filter(JSON.parse(res.data.forms), f => !f.removed)
forEach(tempForms, f => {
f.old = true
if (f.type === 'checkbox' || f.type === 'select' && f.settings.multiple) {
f.value = []
}
if (f.type === 'date' && f.settings.dateType === undefined) { //
f.settings.dateType = f.settings.enableTime ? 'datetime' : 'date'
}
if (f.type === 'dateRange' && f.settings.dateType === undefined) { //
f.settings.dateType = f.settings.enableTime ? 'datetimerange' : 'daterange'
}
})
this.formSettings.forms = tempForms
this.formSettings.oldForms = JSON.parse(res.data.forms)
this.formSettings.tableIndexes = JSON.parse(res.data.tableIndexes)
if (res.data.createIndex) {
forEach(this.formSettings.tableIndexes, f => {
f.old = true
})
this.formSettings.oldTableIndexes = JSON.parse(res.data.tableIndexes)
} else {
this.formSettings.oldTableIndexes = []
}
this.disableCreateIndex = res.data.createIndex
})
}
},
methods: {
closeCreate: function() {
// back to forms list
this.$router.replace('/data-filling/forms')
if (this.$route.query.copy) {
this.$router.replace({ name: 'data-filling-form', query: { id: this.$route.query.copy }})
} else if (this.$route.query.id) {
this.$router.replace({ name: 'data-filling-form', query: { id: this.$route.query.id }})
} else {
this.$router.replace('/data-filling/forms')
}
},
onMoveInComponentList(e, originalEvent) {
if (e.relatedContext?.component?.$el?.id === 'form-drag-place') {
@ -245,9 +325,6 @@ export default {
}
return false
},
addInComponentList(e) {
console.log(e)
},
addComponent(e) {
this.formSettings.forms = cloneDeep(this.formSettings.forms)
@ -287,6 +364,10 @@ export default {
copyItem(item, index) {
const copyItem = cloneDeep(item)
copyItem.id = uuidv4()
delete copyItem.old
delete copyItem.settings.mapping.columnName
delete copyItem.settings.mapping.columnName1
delete copyItem.settings.mapping.columnName2
this.formSettings.forms.splice(index + 1, 0, copyItem)
this.selectedItemId = copyItem.id
@ -319,10 +400,8 @@ export default {
item.value = []
} else {
item.value = undefined
if (item.settings.mapping.type === 'text') {
item.settings.mapping.type = undefined
}
}
item.settings.mapping.type = undefined
},
getRules(item) {
let rules = []
@ -418,7 +497,22 @@ export default {
class="toolbar-icon-active icon20"
@click="closeCreate"
/>
<span class="text16 margin-left12">
<span
v-if="$route.query.copy"
class="text16 margin-left12"
>
{{ $t('data_fill.form.copy_new_form') }}
</span>
<span
v-else-if="$route.query.id"
class="text16 margin-left12"
>
{{ $t('data_fill.form.edit_form') }}
</span>
<span
v-else
class="text16 margin-left12"
>
{{ $t('data_fill.form.create_new_form') }}
</span>
</div>
@ -641,43 +735,21 @@ export default {
</el-checkbox>
</el-checkbox-group>
<el-date-picker
v-else-if="item.type === 'date' && !item.settings.enableTime"
v-else-if="item.type === 'date'"
:key="item.id + 'date'"
v-model="item.value"
:required="item.settings.required"
type="date"
:type="item.settings.dateType"
:placeholder="item.settings.placeholder"
style="width: 100%"
size="small"
/>
<el-date-picker
v-else-if="item.type === 'date' && item.settings.enableTime"
:key="item.id + 'dateEnableTime'"
v-else-if="item.type === 'dateRange'"
:key="item.id + 'dateRange'"
v-model="item.value"
:required="item.settings.required"
type="datetime"
:placeholder="item.settings.placeholder"
style="width: 100%"
size="small"
/>
<el-date-picker
v-else-if="item.type === 'dateRange' && !item.settings.enableTime"
:key="item.id + 'dateRangeEnableTime'"
v-model="item.value"
:required="item.settings.required"
type="daterange"
:range-separator="item.settings.rangeSeparator"
:start-placeholder="item.settings.startPlaceholder"
:end-placeholder="item.settings.endPlaceholder"
style="width: 100%"
size="small"
/>
<el-date-picker
v-else-if="item.type === 'dateRange' && item.settings.enableTime"
:key="item.id + 'datetimerangeRangeEnableTime'"
v-model="item.value"
:required="item.settings.required"
type="datetimerange"
:type="item.settings.dateType"
:range-separator="item.settings.rangeSeparator"
:start-placeholder="item.settings.startPlaceholder"
:end-placeholder="item.settings.endPlaceholder"
@ -834,7 +906,7 @@ export default {
@change="selectedComponentItem.settings.mapping.type = undefined"
>
<el-option
v-for="(x) in inputTypes"
v-for="(x) in selectedComponentItemInputTypes"
:key="x.type"
:label="x.name"
:value="x.type"
@ -842,6 +914,27 @@ export default {
</el-select>
</el-form-item>
<el-form-item
v-if="selectedComponentItem.type === 'date' || selectedComponentItem.type === 'dateRange'"
prop="dateType"
class="form-item"
:label="$t('data_fill.form.date_type')"
:rules="[requiredRule]"
>
<el-select
v-model="selectedComponentItem.settings.dateType"
style="width: 100%"
required
>
<el-option
v-for="(x) in selectedComponentItem.type === 'date' ? dateTypes : dateRangeTypes"
:key="x.value"
:label="x.name"
:value="x.value"
/>
</el-select>
</el-form-item>
<div class="right-check-div">
<div class="m-label-container">
<span style="width: unset; font-weight: bold">
@ -874,22 +967,12 @@ export default {
>
<el-checkbox
v-model="selectedComponentItem.settings.multiple"
:disabled="selectedComponentItem.old"
@change="changeSelectMultiple(selectedComponentItem, selectedComponentItem.settings.multiple)"
>
{{ $t('data_fill.form.set_multiple') }}
</el-checkbox>
</el-form-item>
<el-form-item
v-if="selectedComponentItem.type === 'date' || selectedComponentItem.type === 'dateRange'"
prop="multiple"
class="form-item"
>
<el-checkbox
v-model="selectedComponentItem.settings.enableTime"
>
{{ $t('data_fill.form.use_datetime') }}
</el-checkbox>
</el-form-item>
</div>
@ -1001,6 +1084,8 @@ export default {
>
<data-filling-form-save
v-if="showDrawer"
:is-edit="isEdit"
:disable-create-index="disableCreateIndex"
:form.sync="formSettings"
:show-drawer.sync="showDrawer"
/>

View File

@ -16,6 +16,14 @@ export function updateForm(data) {
data
})
}
export function updateFormName(data) {
return request({
url: 'dataFilling/form/updateName',
method: 'post',
loading: true,
data
})
}
export function moveForm(data) {
return request({
url: 'dataFilling/form/move',

View File

@ -3,7 +3,7 @@ import DeContainer from '@/components/dataease/DeContainer.vue'
import DeAsideContainer from '@/components/dataease/DeAsideContainer.vue'
import NoSelect from './NoSelect.vue'
import ViewTable from './ViewTable.vue'
import { listForm, saveForm, updateForm, deleteForm, getWithPrivileges } from '@/views/dataFilling/form/dataFilling'
import { listForm, saveForm, updateFormName, deleteForm, getWithPrivileges } from '@/views/dataFilling/form/dataFilling'
import { forEach, cloneDeep, find } from 'lodash-es'
import { hasPermission } from '@/directive/Permission'
import DataFillingFormMoveSelector from './MoveSelector.vue'
@ -108,12 +108,18 @@ export default {
case 'rename':
this.openUpdateForm(param)
break
case 'edit':
this.editForm(param.data)
break
case 'delete':
this.delete(param.data)
break
case 'move':
this.moveTo(param.data)
break
case 'copy':
this.copyForm(param.data)
break
}
},
moveTo(data) {
@ -135,7 +141,7 @@ export default {
id: this.updateFormData.id,
name: this.updateFormData.name
}
updateForm(data).then(res => {
updateFormName(data).then(res => {
this.closeUpdateForm()
listForm({}).then(res => {
this.formList = res.data || []
@ -170,6 +176,12 @@ export default {
}).catch(() => {
})
},
copyForm(data) {
this.$router.push({ name: 'data-filling-form-create', query: { copy: data.id }})
},
editForm(data) {
this.$router.push({ name: 'data-filling-form-create', query: { id: data.id }})
},
onMoveSuccess() {
this.moveGroup = false
listForm({}).then(res => {
@ -279,7 +291,10 @@ export default {
{{ $t('data_fill.form_manage') }}
</span>
<div style="padding-left: 20px;padding-right: 20px;">
<div
style="padding-left: 20px;padding-right: 20px;"
class="de-tree"
>
<div style="display: flex;flex-direction: row;justify-content: space-between;align-items: center;">
{{ $t('data_fill.form.form_list_name') }}
@ -371,6 +386,18 @@ export default {
</el-dropdown>
</template>
<span
v-if="data.nodeType !== 'folder'"
@click.stop
>
<el-button
icon="el-icon-edit"
type="text"
size="small"
@click="editForm(data)"
/>
</span>
<span
style="margin-left: 12px"
@click.stop
@ -394,12 +421,26 @@ export default {
>
{{ $t('panel.rename') }}
</el-dropdown-item>
<el-dropdown-item
v-if="data.nodeType !== 'folder'"
icon="el-icon-edit"
:command="beforeClickMore('edit', data, node)"
>
{{ $t('panel.edit') }}
</el-dropdown-item>
<el-dropdown-item
icon="el-icon-right"
:command="beforeClickMore('move',data,node)"
:command="beforeClickMore('move', data, node)"
>
{{ $t('dataset.move_to') }}
</el-dropdown-item>
<el-dropdown-item
v-if="data.nodeType !== 'folder'"
icon="el-icon-document-copy"
:command="beforeClickMore('copy', data, node)"
>
{{ $t('dataset.copy') }}
</el-dropdown-item>
<el-dropdown-item
icon="el-icon-delete"
:command="beforeClickMore('delete', data, node)"
@ -427,6 +468,7 @@ export default {
<view-table
v-else
:param="displayFormData"
@editForm="editForm"
/>
</el-main>
@ -628,4 +670,57 @@ export default {
color: var(--primary, #3370ff);
}
}
.de-tree {
.el-tree-node.is-current.is-focusable {
&>.el-tree-node__content {
background-color: var(--deWhiteHover, #e0eaff);
color: var(--primary, #3370ff);
}
}
.el-tree-node__content, .de-el-tree-node__content {
.el-icon-more,
.el-icon-plus {
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
font-size: 12px;
color: #646a73;
cursor: pointer;
}
.el-icon-more:hover,
.el-icon-plus:hover {
background: rgba(31, 35, 41, 0.1);
border-radius: 4px;
}
.el-icon-more:active,
.el-icon-plus:active {
background: rgba(31, 35, 41, 0.2);
border-radius: 4px;
}
}
.el-tree-node__content {
height: 40px;
border-radius: 4px;
&:hover {
background: rgba(31, 35, 41, 0.1);
}
}
.de-el-tree-node__content {
.el-button--text {
padding: 0 !important;
}
.el-icon-more {
width: 32px;
height: 32px;
line-height: 32px;
}
}
}
</style>

View File

@ -1,12 +1,20 @@
<script>
import { filter, forEach, find, split, get } from 'lodash-es'
import { listDatasource, listDatasourceType } from '@/api/system/datasource'
import { listForm, saveForm } from '@/views/dataFilling/form/dataFilling'
import { filter, forEach, find, split, get, groupBy, keys, includes, cloneDeep } from 'lodash-es'
import { listDatasource } from '@/api/system/datasource'
import { listForm, saveForm, updateForm } from '@/views/dataFilling/form/dataFilling'
import { hasDataPermission } from '@/utils/permission'
export default {
name: 'DataFillingFormSave',
props: {
isEdit: {
type: Boolean,
default: false
},
disableCreateIndex: {
type: Boolean,
default: false
},
form: {
type: Object,
required: true
@ -22,18 +30,22 @@ export default {
return callback(new Error(this.$t('commons.component.required')))
}
let count = 0
forEach(this.formData.forms, f => {
if (f.type === 'dateRange') {
if (f.settings.mapping.columnName1 === value) {
count++
}
if (f.settings.mapping.columnName2 === value) {
count++
forEach(this.computedFormList, f => {
if (!f.deleted) {
if (f.type === 'dateRange') {
if (f.settings.mapping.columnName1 === value) {
count++
}
if (f.settings.mapping.columnName2 === value) {
count++
}
} else {
if (f.settings.mapping.columnName === value) {
count++
}
}
} else {
if (f.settings.mapping.columnName === value) {
count++
}
// uuid
}
})
if (count > 1) {
@ -46,7 +58,7 @@ export default {
return callback(new Error(this.$t('commons.component.required')))
}
let count = 0
forEach(this.formData.tableIndexes, f => {
forEach(this.computedTableIndexList, f => {
if (f.name === value) {
count++
}
@ -57,13 +69,19 @@ export default {
callback()
}
const checkInvalidColumnValidator = (rule, value, callback) => {
const f = split(rule.field, '.')[0]
const _index = get(this.formData, f)
if (_index.old) {
// index
callback()
}
if (!value) {
return callback(new Error(this.$t('commons.component.required')))
}
if (this.columnsList.length === 0) {
return callback(new Error(this.$t('data_fill.form.value_not_exists')))
}
if (find(this.columnsList, c => c === value) === undefined) {
if (find(this.columnsList, c => c.value === value) === undefined) {
callback(new Error(this.$t('data_fill.form.value_not_exists')))
}
callback()
@ -102,10 +120,26 @@ export default {
},
computed: {
datasourceList() {
const _types = filter(this.allDatasourceTypes, t => t.type === 'mysql' || t.type === 'mariadb')
forEach(_types, t => {
t.options = filter(this.allDatasourceList, d => d.type === t.type)
})
const dsMap = groupBy(this.allDatasourceList, d => d.type)
const _types = [{
name: this.$t('data_fill.form.default'),
type: 'default',
options: [{
id: 'default-built-in',
name: this.$t('data_fill.form.default_built_in')
}]
}]
if (dsMap) {
forEach(keys(dsMap), type => {
if (type === 'mysql' || type === 'mariadb') {
_types.push({
name: dsMap[type][0]?.typeDesc,
type: type,
options: filter(dsMap[type], d => d.enableDataFill && d.enableDataFillCreateTable)
})
}
})
}
return _types
},
selectDatasets() {
@ -113,25 +147,84 @@ export default {
this.flattenFolder(this.folders, result)
return result
},
columnsList() {
computedFormList() {
if (this.isEdit) {
const _list = []
const columnIds = []
for (let i = 0; i < this.formData.forms.length; i++) {
const row = cloneDeep(this.formData.forms[i])
columnIds.push(row.id)
_list.push(row)
}
for (let i = 0; i < this.formData.oldForms.length; i++) {
const row = cloneDeep(this.formData.oldForms[i])
if (includes(columnIds, row.id)) {
continue
}
row.deleted = true
_list.push(row)
}
return _list
} else {
return this.formData.forms
}
},
computedTableIndexList() {
if (this.isEdit) {
const _list = []
const columnIds = []
for (let i = 0; i < this.formData.tableIndexes.length; i++) {
const row = this.formData.tableIndexes[i]
columnIds.push(row.id)
_list.push(row)
}
for (let i = 0; i < this.formData.oldTableIndexes.length; i++) {
const row = this.formData.oldTableIndexes[i]
if (includes(columnIds, row.id)) {
continue
}
columnIds.push(row.id)
_list.push(row)
}
return _list
} else {
return this.formData.tableIndexes
}
},
allColumnsList() {
const _list = []
for (let i = 0; i < this.formData.forms.length; i++) {
const row = this.formData.forms[i]
for (let i = 0; i < this.computedFormList.length; i++) {
const row = this.computedFormList[i]
if (row.type === 'dateRange') {
if (row.settings.mapping.columnName1 !== undefined && row.settings.mapping.columnName1 !== '') {
_list.push(row.settings.mapping.columnName1)
_list.push({
name: !row.deleted ? row.settings.mapping.columnName1 : row.id + '_1',
value: row.id + '_1',
deleted: !!row.deleted
})
}
if (row.settings.mapping.columnName2 !== undefined && row.settings.mapping.columnName2 !== '') {
_list.push(row.settings.mapping.columnName2)
_list.push({
name: !row.deleted ? row.settings.mapping.columnName2 : row.id + '_2',
value: row.id + '_2',
deleted: !!row.deleted
})
}
} else {
if (row.settings.mapping.columnName !== undefined && row.settings.mapping.columnName !== '' && row.settings.mapping.type !== 'text') {
_list.push(row.settings.mapping.columnName)
_list.push({
name: !row.deleted ? row.settings.mapping.columnName : row.id,
value: row.id,
deleted: !!row.deleted
})
}
}
}
return _list
},
columnsList() {
return filter(this.allColumnsList, c => !c.deleted)
}
},
watch: {
@ -152,16 +245,13 @@ export default {
f.settings.mapping.type = f.settings.mapping.typeOptions[0].value
}
})
const p1 = listDatasourceType()
const p2 = listDatasource()
const p3 = listForm({ nodeType: 'folder' })
const p1 = listDatasource()
const p2 = listForm({ nodeType: 'folder' })
Promise.all([p1, p2, p3]).then((val) => {
this.allDatasourceTypes = val[0].data
Promise.all([p1, p2]).then((val) => {
this.allDatasourceList = val[0].data
this.allDatasourceList = val[1].data
this.folders = this.filterListDeep(val[2].data) || []
this.folders = this.filterListDeep(val[1].data) || []
if (this.formData.folder) {
this.$nextTick(() => {
this.$refs.tree.setCurrentKey(this.formData.folder)
@ -266,6 +356,34 @@ export default {
if (!value) return true
return data.name.indexOf(value) !== -1
},
doEdit() {
this.loading = true
this.$refs['mRightForm'].validate((valid) => {
if (valid) {
const data = {
id: this.formData.id,
name: this.formData.name,
tableName: this.formData.table,
datasource: this.formData.datasource,
pid: this.formData.folder,
level: this.formData.level,
forms: JSON.stringify(this.formData.forms),
createIndex: this.formData.createIndex,
tableIndexes: JSON.stringify(this.formData.tableIndexes),
nodeType: 'form'
}
updateForm(data).then(res => {
this.closeSave()
this.$router.replace({ name: 'data-filling-form', query: { id: res.data }})
}).finally(() => {
this.loading = false
})
} else {
this.loading = false
return false
}
})
},
doSave() {
this.loading = true
this.$refs['mRightForm'].validate((valid) => {
@ -415,6 +533,7 @@ export default {
</el-form-item>
<el-form-item
v-if="!isEdit"
prop="datasource"
class="form-item"
:rules="[requiredRule]"
@ -448,6 +567,7 @@ export default {
</el-form-item>
<el-form-item
v-if="!isEdit"
prop="table"
class="form-item"
:rules="[requiredRule]"
@ -495,6 +615,7 @@ export default {
>
<el-input
v-model.trim="scope.row.settings.mapping.columnName"
:disabled="isEdit && scope.row.old"
:placeholder="$t('fu.search_bar.please_input')"
size="small"
maxlength="50"
@ -510,6 +631,7 @@ export default {
>
<el-input
v-model.trim="scope.row.settings.mapping.columnName1"
:disabled="isEdit && scope.row.old"
:placeholder="$t('data_fill.form.please_insert_start')"
size="small"
maxlength="50"
@ -524,6 +646,7 @@ export default {
>
<el-input
v-model.trim="scope.row.settings.mapping.columnName2"
:disabled="isEdit && scope.row.old"
:placeholder="$t('data_fill.form.please_insert_end')"
size="small"
maxlength="50"
@ -545,6 +668,7 @@ export default {
>
<el-select
v-model="scope.row.settings.mapping.type"
:disabled="isEdit && scope.row.old"
:placeholder="$t('data_fill.form.please_select')"
size="small"
required
@ -570,6 +694,7 @@ export default {
>
<el-checkbox
v-model="formData.createIndex"
:disabled="disableCreateIndex"
:label="$t('data_fill.form.create_index')"
size="small"
/>
@ -599,10 +724,12 @@ export default {
<el-form-item
:prop="'tableIndexes['+scope.$index+'].name'"
class="form-item"
:class="scope.row.columns.length === 1 && (isEdit && scope.row.old) ? 'no-margin-bottom' : ''"
:rules="[requiredRule, duplicateIndexRule]"
>
<el-input
v-model="scope.row.name"
:disabled="isEdit && scope.row.old"
:placeholder="$t('fu.search_bar.please_input')"
size="small"
maxlength="50"
@ -648,18 +775,18 @@ export default {
>
<el-select
v-model="indexRow.column"
:disabled="isEdit && scope.row.old"
:placeholder="$t('data_fill.form.please_select')"
size="small"
required
style="width: 100%"
>
<el-option
v-for="(x, $index) in columnsList"
v-for="(x, $index) in (isEdit && scope.row.old ? allColumnsList : columnsList)"
:key="$index"
:value="x"
:label="x"
>{{ x }}
</el-option>
:value="x.value"
:label="x.name"
/>
</el-select>
</el-form-item>
@ -670,6 +797,7 @@ export default {
>
<el-select
v-model="indexRow.order"
:disabled="isEdit && scope.row.old"
:placeholder="$t('data_fill.form.please_select')"
size="small"
required
@ -690,7 +818,7 @@ export default {
</el-select>
</el-form-item>
<div
v-if="scope.row.columns.length > 1"
v-if="scope.row.columns.length > 1 && !(isEdit && scope.row.old)"
class="btn-item"
@click="removeIndexColumn(scope.row.columns, $index)"
>
@ -698,7 +826,7 @@ export default {
</div>
</div>
<el-button
v-if="scope.row.columns.length < 5"
v-if="scope.row.columns.length < 5 && !(isEdit && scope.row.old)"
type="text"
@click="addColumn(scope.row.columns)"
>+ {{ $t('data_fill.form.add_column') }}
@ -709,6 +837,7 @@ export default {
<el-table-column width="50">
<template slot-scope="scope">
<div
v-if="!(isEdit && scope.row.old)"
class="btn-item"
@click="removeIndex(scope.$index)"
>
@ -724,10 +853,17 @@ export default {
<el-footer class="de-footer">
<el-button @click="closeSave">{{ $t("commons.cancel") }}</el-button>
<el-button
v-if="!isEdit"
type="primary"
@click="doSave"
>{{ $t("commons.confirm") }}
</el-button>
<el-button
v-else
type="primary"
@click="doEdit"
>{{ $t("commons.confirm") }}
</el-button>
</el-footer>
</el-container>
</template>

View File

@ -212,39 +212,17 @@ export default {
this.selectedFormTitle = res.data.name
this.forms = JSON.parse(res.data.forms)
const _list = []
const dateFormatColumns = []
forEach(this.forms, f => {
if (f.type === 'dateRange') {
_list.push({
props: f.settings?.mapping?.columnName1,
label: f.settings?.name,
date: true,
enableTime: f.settings?.enableTime,
type: f.type,
multiple: !!f.settings.multiple,
rangeIndex: 0
})
_list.push({
props: f.settings?.mapping?.columnName2,
label: f.settings?.name,
date: true,
enableTime: f.settings?.enableTime,
type: f.type,
multiple: !!f.settings.multiple,
rangeIndex: 1
})
dateFormatColumns.push(f.settings?.mapping?.columnName1)
dateFormatColumns.push(f.settings?.mapping?.columnName2)
} else {
_list.push({
props: f.settings?.mapping?.columnName,
label: f.settings?.name,
date: f.type === 'date',
enableTime: f.type === 'date' && f.settings?.enableTime,
type: f.type,
multiple: !!f.settings.multiple
})
if (f.type === 'date') {
dateFormatColumns.push(f.settings?.mapping?.columnName)
}
}
})
const dateFormatColumns = map(filter(_list, c => c.date), 'props')
searchTable(row.formId, {
primaryKeyValue: row.valueId,

View File

@ -970,7 +970,7 @@ export default {
table.info.replace(/\n/g, '\\n').replace(/\r/g, '\\r')
).sql
}
if (JSON.parse(table.info).hasOwn('setKey')) {
if (JSON.parse(table.info).hasOwnProperty('setKey')) {
this.$set(this.param, 'setKey', JSON.parse(table.info).setKey)
this.param.keys = JSON.parse(table.info).keys
}

View File

@ -27,6 +27,7 @@ import { queryPanelJumpInfo, queryTargetPanelJumpInfo } from '@/api/panel/linkJu
import { getNowCanvasComponentData, panelInit } from '@/components/canvas/utils/utils'
import { getOuterParamsInfo } from '@/api/panel/outerParams'
import { mapState } from 'vuex'
import {Base64} from "js-base64";
export default {
name: 'LinkView',
@ -140,7 +141,7 @@ export default {
let attachParam = null
if (attachParamsEncode) {
const Base64 = require('js-base64').Base64
attachParam = JSON.parse(decodeURIComponent(Base64.decode(attachParamsEncode)))
attachParam = JSON.parse(Base64.decode(decodeURIComponent(attachParamsEncode)))
}
if (hasArgs) {
attachParam = Object.assign({}, attachParam, argsObject)

View File

@ -130,7 +130,7 @@
v-show="codeShow"
class="code"
>
<el-row class="code-contaniner">
<el-row class="code-contaniner" :class="isPad && 'is-pad'">
<plugin-com
v-if="codeShow && loginTypes.includes(4) && codeIndex === 4"
ref="WecomQr"
@ -228,6 +228,7 @@ export default {
username: '',
password: ''
},
isPad: false,
loginRules: {
username: [{ required: true, trigger: 'blur', message: this.$t('commons.input_id') }],
password: [{ required: true, trigger: 'blur', message: this.$t('commons.input_pwd') }]
@ -358,7 +359,7 @@ export default {
},
mounted() {
// this.loading = false
this.isPad = /iPad/.test(navigator.userAgent)
},
created() {
@ -715,6 +716,10 @@ export default {
}
.code-contaniner {
height: 410px;
&.is-pad {
height: 365px;
overflow: hidden;
}
}
}
.login-third-item {

View File

@ -201,7 +201,8 @@
<span class="header-title">
{{ $t('panel.panel_list') }}
<el-button
style="float: right; padding-right: 7px; margin-top: -8px"
v-if="hasDataPermission('manage', rootAuth)"
style="float: right; padding-right: 7px; margin-top: -8px; height: 12px"
icon="el-icon-plus"
type="text"
@click="showEditPanel(newFolder)"
@ -534,6 +535,7 @@ export default {
mixins: [msgCfm],
data() {
return {
rootAuth: '',
originResourceTree: [],
curSortType: 'time_desc',
localSortParams: null,
@ -987,15 +989,25 @@ export default {
},
tree(cache = false) {
const modelInfo = localStorage.getItem('panel-main-tree')
const userCache = modelInfo && cache
let preParse
if (modelInfo) {
try {
preParse = JSON.parse(modelInfo)
} catch (e) {
console.warn('panel-main-tree cache error')
}
}
const userCache = preParse && cache
if (userCache) {
this.originResourceTree = JSON.parse(modelInfo)
this.originResourceTree = preParse
this.sortTypeChange(this.localSortParams)
}
groupTree(this.groupForm, !userCache).then((res) => {
localStorage.setItem('panel-main-tree', JSON.stringify(res.data || []))
this.rootAuth = res.data ? res.data[0]?.privileges||'':''
const resMainData = res.data ? res.data[0]?.children || [] : []
localStorage.setItem('panel-main-tree', JSON.stringify(resMainData))
if (!userCache) {
this.originResourceTree = res.data || []
this.originResourceTree = resMainData
this.sortTypeChange(this.localSortParams)
}
if (this.responseSource === 'appApply') {
@ -1013,10 +1025,18 @@ export default {
panelType: 'system'
}
const modelInfo = localStorage.getItem('panel-default-tree')
const userCache = modelInfo && cache
let preParse
if (modelInfo) {
try {
preParse = JSON.parse(modelInfo)
} catch (e) {
console.warn('panel-default-tree cache error')
}
}
const userCache = preParse && cache
if (userCache) {
this.defaultData = JSON.parse(modelInfo)
this.defaultData = preParse
if (showFirst && this.defaultData && this.defaultData.length > 0) {
this.activeDefaultNodeAndClickOnly(this.defaultData[0].id)
}

View File

@ -233,6 +233,44 @@
:component-name="datasourceType.type"
:obj="{ form, disabled }"
/>
<el-form-item
v-if="form.type === 'mysql' || form.type === 'mariadb'"
prop="enableDataFill"
class="data-fill-form-item"
>
<span style="display: inline-block; width: 80px; font-weight: 700; color: #606266;">{{ $t('data_fill.data_fill') }}</span>
<el-checkbox
v-model="form.enableDataFill"
:disabled="disableEditDataFill"
>
{{ $t('data_fill.enable') }}
<el-tooltip
class="item"
effect="dark"
>
<div slot="content">
{{ $t('data_fill.enable_hint') }}
</div>
<i
class="el-icon-info"
style="cursor: pointer;"
/>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item
v-if="(form.type === 'mysql' || form.type === 'mariadb') && form.enableDataFill"
prop="enableDataFill"
label-position="left"
>
<span style="display: inline-block; width: 80px; font-weight: 700; color: #606266;">{{ $t('data_fill.permission') }}</span>
<el-checkbox
v-model="form.enableDataFillCreateTable"
>允许新建表</el-checkbox>
</el-form-item>
</el-form>
</div>
</div>
@ -424,6 +462,7 @@ export default {
],
datasourceHistoryId: [{ required: true, message: i18n.t('dataset.pls_slc_data_source'), trigger: 'blur' }]
},
disableEditDataFill: false,
form: {
configuration: {
initialPoolSize: 5,
@ -758,6 +797,7 @@ export default {
if (res.data.apiConfigurationStr) {
res.data.apiConfiguration = JSON.parse(Base64.decode(res.data.apiConfigurationStr))
}
this.disableEditDataFill = res.data.enableDataFill
this.params = { ...res.data, showModel }
if (showModel === 'copy') {
this.params.id = ''
@ -1364,6 +1404,9 @@ export default {
}
</script>
<style lang="scss" scoped>
.data-fill-form-item {
margin-bottom: 14px !important;
}
.de-ds-cont {
display: flex;
width: 100%;

View File

@ -1,6 +1,6 @@
{
"name": "dataease-mobile",
"version": "1.18.19",
"version": "1.18.20",
"private": true,
"scripts": {
"serve": "npm run dev:h5",

View File

@ -17,6 +17,17 @@ uni-app {
.fix-left-window {
padding-left: var(--window-left);
}
.uni-page-head-ft {
margin-left: 8px;
.uni-page-head-btn {
background: none;
border: 1px solid #BBBFC4;
border-radius: 4px;
.uni-btn-icon {
color: #bbbfc4 !important;
}
}
}
.pc-hide {
display: none !important;

View File

@ -156,3 +156,46 @@ export function getUrlParams(url){
}
return Params
}
export function deepCopy(target) {
if (typeof target === 'object' && target !== null) {
const result = Array.isArray(target) ? [] : {}
for (const key in target) {
if (typeof target[key] === 'object') {
result[key] = deepCopy(target[key])
} else {
result[key] = target[key]
}
}
return result
}
return target
}
export function treeSort(tree, hisSortType, sortType) {
const result = deepCopy(tree)
sortCircle(result, hisSortType, sortType)
return result
}
export function sortCircle(tree, hisSortType, sortType) {
sortPer(tree, hisSortType, sortType)
tree.forEach(node => {
if (node.children && node.children.length > 0) {
sortCircle(node.children, hisSortType, sortType)
}
})
return tree
}
export function sortPer(subTree, hisSortType, sortType) {
if (sortType === 'name_desc') {
subTree.sort((a, b) => b.text.localeCompare(a.text, 'zh-Hans-CN', { sensitivity: 'accent' }))
} else if (sortType === 'name_asc') {
subTree.sort((a, b) => a.text.localeCompare(b.text, 'zh-Hans-CN', { sensitivity: 'accent' }))
} else if (sortType === 'time_asc') {
subTree.reverse()
}
}

View File

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

View File

@ -0,0 +1,316 @@
<template>
<view class="uni-popup-dialog">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
</view>
<view v-if="mode === 'base'" class="uni-dialog-content">
<slot>
<text class="uni-dialog-content-text">{{content}}</text>
</slot>
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" :maxlength="maxlength" v-model="val" :type="inputType"
:placeholder="placeholderText" :focus="focus">
</slot>
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" v-if="showClose" @click="closeDialog">
<text class="uni-dialog-button-text">{{closeText}}</text>
</view>
<view class="uni-dialog-button" :class="showClose?'uni-border-left':''" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
</view>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const {
t
} = initVueI18n(messages)
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {Boolean} focus input模式下是否自动聚焦默认为true
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式
* @value base 基础对话框
* @value input 可输入对话框
* @showClose {Boolean} 是否显示关闭按钮
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @property {Number} maxlength 输入
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: "uniPopupDialog",
mixins: [popup],
emits: ['confirm', 'close', 'update:modelValue', 'input'],
props: {
inputType: {
type: String,
default: 'text'
},
showClose: {
type: Boolean,
default: true
},
// #ifdef VUE2
value: {
type: [String, Number],
default: ''
},
// #endif
// #ifdef VUE3
modelValue: {
type: [Number, String],
default: ''
},
// #endif
placeholder: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'error'
},
mode: {
type: String,
default: 'base'
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
},
cancelText: {
type: String,
default: ''
},
confirmText: {
type: String,
default: ''
},
maxlength: {
type: Number,
default: -1,
},
focus: {
type: Boolean,
default: true,
}
},
data() {
return {
dialogType: 'error',
val: ""
}
},
computed: {
okText() {
return this.confirmText || t("uni-popup.ok")
},
closeText() {
return this.cancelText || t("uni-popup.cancel")
},
placeholderText() {
return this.placeholder || t("uni-popup.placeholder")
},
titleText() {
return this.title || t("uni-popup.title")
}
},
watch: {
type(val) {
this.dialogType = val
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info'
}
},
value(val) {
if (this.maxlength != -1 && this.mode === 'input') {
this.val = val.slice(0, this.maxlength);
} else {
this.val = val
}
},
val(val) {
// #ifdef VUE2
// TODO vue2
this.$emit('input', val);
// #endif
// #ifdef VUE3
// TODO  vue3
this.$emit('update:modelValue', val);
// #endif
}
},
created() {
//
this.popup.disableMask()
// this.popup.closeMask()
if (this.mode === 'input') {
this.dialogType = 'info'
this.val = this.value;
// #ifdef VUE3
this.val = this.modelValue;
// #endif
} else {
this.dialogType = this.type
}
},
methods: {
/**
* 点击确认按钮
*/
onOk() {
if (this.mode === 'input') {
this.$emit('confirm', this.val)
} else {
this.$emit('confirm')
}
if (this.beforeClose) return
this.popup.close()
},
/**
* 点击取消按钮
*/
closeDialog() {
this.$emit('close')
if (this.beforeClose) return
this.popup.close()
},
close() {
this.popup.close()
}
}
}
</script>
<style lang="scss">
.uni-popup-dialog {
width: 300px;
border-radius: 11px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 25px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 20px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6C6C6C;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 16px;
color: #333;
}
.uni-button-color {
color: #007aff;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
border: 1px #eee solid;
height: 40px;
padding: 0 10px;
border-radius: 5px;
color: #555;
}
.uni-popup__success {
color: #4cd964;
}
.uni-popup__warn {
color: #f0ad4e;
}
.uni-popup__error {
color: #dd524d;
}
.uni-popup__info {
color: #909399;
}
</style>

View File

@ -0,0 +1,143 @@
<template>
<view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
<slot>
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
</slot>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间设置为 0 则不会自动关闭
*/
export default {
name: 'uniPopupMessage',
mixins:[popup],
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success'
},
/**
* 消息文字
*/
message: {
type: String,
default: ''
},
/**
* 显示时间设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000
},
maskShow:{
type:Boolean,
default:false
}
},
data() {
return {}
},
created() {
this.popup.maskShow = this.maskShow
this.popup.messageChild = this
},
methods: {
timerClose(){
if(this.duration === 0) return
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.popup.close()
},this.duration)
}
}
}
</script>
<style lang="scss" >
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
}
.uni-popup-message__box {
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
flex: 1;
}
@media screen and (min-width: 500px) {
.fixforpc-width {
margin-top: 20px;
border-radius: 4px;
flex: none;
min-width: 380px;
/* #ifndef APP-NVUE */
max-width: 50%;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500px;
/* #endif */
}
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67C23A;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #E6A23C;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #F56C6C;
}
.uni-popup__info {
background-color: #F2F6FC;
}
.uni-popup__info-text {
color: #909399;
}
</style>

View File

@ -0,0 +1,187 @@
<template>
<view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{cancelText}}</button>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
export default {
name: 'UniPopupShare',
mixins:[popup],
emits:['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
bottomData: [{
text: '微信',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
name: 'ali'
},
{
text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'sina'
},
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
// name: 'copy'
// },
// {
// text: '',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
// name: 'more'
// }
]
}
},
created() {},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
this.close()
},
/**
* 关闭窗口
*/
close() {
if(this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

View File

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "cancel",
"uni-popup.ok": "ok",
"uni-popup.placeholder": "please enter",
"uni-popup.title": "Hint",
"uni-popup.shareTitle": "Share to"
}

View File

@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "确定",
"uni-popup.placeholder": "请输入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

View File

@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "確定",
"uni-popup.placeholder": "請輸入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

View File

@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

View File

@ -0,0 +1,26 @@
export default {
data() {
return {
}
},
created(){
this.popup = this.getParent()
},
methods:{
/**
* 获取父元素实例
*/
getParent(name = 'uniPopup') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
}
}

View File

@ -0,0 +1,90 @@
<template>
<view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
<view @click.stop>
<slot></slot>
</view>
</view>
</template>
<script>
type CloseCallBack = ()=> void;
let closeCallBack:CloseCallBack = () :void => {};
export default {
emits:["close","clickMask"],
data() {
return {
isShow:false,
isOpen:false
}
},
props: {
maskClick: {
type: Boolean,
default: true
},
},
watch: {
// 设置show = true 时,如果没有 open 需要设置为 open
isShow:{
handler(isShow) {
// console.log("isShow",isShow)
if(isShow && this.isOpen == false){
this.isOpen = true
}
},
immediate:true
},
// 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
isOpen:{
handler(isOpen) {
// console.log("isOpen",isOpen)
if(isOpen && this.isShow == false){
this.isShow = true
}
},
immediate:true
}
},
methods:{
open(){
// ...funs : CloseCallBack[]
// if(funs.length > 0){
// closeCallBack = funs[0]
// }
this.isOpen = true;
},
clickMask(){
if(this.maskClick == true){
this.$emit('clickMask')
this.close()
}
},
close(): void{
this.isOpen = false;
this.$emit('close')
closeCallBack()
},
hiden(){
this.isShow = false
},
show(){
this.isShow = true
}
}
}
</script>
<style>
.popup-root {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 100%;
flex: 1;
background-color: rgba(0, 0, 0, 0.3);
justify-content: center;
align-items: center;
z-index: 99;
}
</style>

View File

@ -0,0 +1,503 @@
<template>
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
<view @touchstart="touchstart">
<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
:duration="duration" :show="showTrans" @click="onTap" />
<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
:show="showTrans" @click="onTap">
<view class="uni-popup__wrapper" :style="getStyles" :class="[popupstyle]" @click="clear">
<slot />
</view>
</uni-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js'
// #endif
/**
* PopUp 弹出层
* @description 弹出层组件为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @value message 消息提示
* @value dialog 对话框
* @value share 底部分享示例
* @property {Boolean} animation = [true|false] 是否开启动画
* @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
* @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
* @property {String} backgroundColor 主窗口背景色
* @property {String} maskBackgroundColor 蒙版颜色
* @property {String} borderRadius 设置圆角(左上右上右下和左下) 示例:"10px 10px 10px 10px"
* @property {Boolean} safeArea 是否适配底部安全区
* @event {Function} change 打开关闭弹窗触发e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
export default {
name: 'uniPopup',
components: {
// #ifdef H5
keypress
// #endif
},
emits: ['change', 'maskClick'],
props: {
//
animation: {
type: Boolean,
default: true
},
// top: bottomcenter
// message: ; dialog :
type: {
type: String,
default: 'center'
},
// maskClick
isMaskClick: {
type: Boolean,
default: null
},
// TODO 2 使 isMaskClick
maskClick: {
type: Boolean,
default: null
},
backgroundColor: {
type: String,
default: 'none'
},
safeArea: {
type: Boolean,
default: true
},
maskBackgroundColor: {
type: String,
default: 'rgba(0, 0, 0, 0.4)'
},
borderRadius:{
type: String,
}
},
watch: {
/**
* 监听type类型
*/
type: {
handler: function(type) {
if (!this.config[type]) return
this[this.config[type]](true)
},
immediate: true
},
isDesktop: {
handler: function(newVal) {
if (!this.config[newVal]) return
this[this.config[this.type]](true)
},
immediate: true
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
},
isMaskClick: {
handler: function(val) {
this.mkclick = val
},
immediate: true
},
// H5
showPopup(show) {
// #ifdef H5
// fix by mehaotian h5 穿
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
// #endif
}
},
data() {
return {
duration: 300,
ani: [],
showPopup: false,
showTrans: false,
popupWidth: 0,
popupHeight: 0,
config: {
top: 'top',
bottom: 'bottom',
center: 'center',
left: 'left',
right: 'right',
message: 'top',
dialog: 'center',
share: 'bottom'
},
maskClass: {
position: 'fixed',
bottom: 0,
top: 0,
left: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)'
},
transClass: {
backgroundColor: 'transparent',
borderRadius: this.borderRadius || "0",
position: 'fixed',
left: 0,
right: 0
},
maskShow: true,
mkclick: true,
popupstyle: 'top'
}
},
computed: {
getStyles() {
let res = { backgroundColor: this.bg };
if (this.borderRadius || "0") {
res = Object.assign(res, { borderRadius: this.borderRadius })
}
return res;
},
isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500
},
bg() {
if (this.backgroundColor === '' || this.backgroundColor === 'none') {
return 'transparent'
}
return this.backgroundColor
}
},
mounted() {
const fixSize = () => {
const {
windowWidth,
windowHeight,
windowTop,
safeArea,
screenHeight,
safeAreaInsets
} = uni.getSystemInfoSync()
this.popupWidth = windowWidth
this.popupHeight = windowHeight + (windowTop || 0)
// TODO fix by mehaotian ,ios app ios
if (safeArea && this.safeArea) {
// #ifdef MP-WEIXIN
this.safeAreaInsets = screenHeight - safeArea.bottom
// #endif
// #ifndef MP-WEIXIN
this.safeAreaInsets = safeAreaInsets.bottom
// #endif
} else {
this.safeAreaInsets = 0
}
}
fixSize()
// #ifdef H5
// window.addEventListener('resize', fixSize)
// this.$once('hook:beforeDestroy', () => {
// window.removeEventListener('resize', fixSize)
// })
// #endif
},
// #ifndef VUE3
// TODO vue2
destroyed() {
this.setH5Visible()
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.setH5Visible()
},
// #endif
activated() {
this.setH5Visible(!this.showPopup);
},
deactivated() {
this.setH5Visible(true);
},
created() {
// this.mkclick = this.isMaskClick || this.maskClick
if (this.isMaskClick === null && this.maskClick === null) {
this.mkclick = true
} else {
this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
}
if (this.animation) {
this.duration = 300
} else {
this.duration = 0
}
// TODO message
this.messageChild = null
// TODO
this.clearPropagation = false
this.maskClass.backgroundColor = this.maskBackgroundColor
},
methods: {
setH5Visible(visible = true) {
// #ifdef H5
// fix by mehaotian h5 穿
document.getElementsByTagName('body')[0].style.overflow = visible ? "visible" : "hidden";
// #endif
},
/**
* 公用方法不显示遮罩层
*/
closeMask() {
this.maskShow = false
},
/**
* 公用方法遮罩层禁止点击
*/
disableMask() {
this.mkclick = false
},
// TODO nvue
clear(e) {
// #ifndef APP-NVUE
e.stopPropagation()
// #endif
this.clearPropagation = true
},
open(direction) {
// fix by mehaotian
if (this.showPopup) {
return
}
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
if (!(direction && innerType.indexOf(direction) !== -1)) {
direction = this.type
}
if (!this.config[direction]) {
console.error('缺少类型:', direction)
return
}
this[this.config[direction]]()
this.$emit('change', {
show: true,
type: direction
})
},
close(type) {
this.showTrans = false
this.$emit('change', {
show: false,
type: this.type
})
clearTimeout(this.timer)
// //
// this.customOpen && this.customClose()
this.timer = setTimeout(() => {
this.showPopup = false
}, 300)
},
// TODO
touchstart() {
this.clearPropagation = false
},
onTap() {
if (this.clearPropagation) {
// fix by mehaotian nvue
this.clearPropagation = false
return
}
this.$emit('maskClick')
if (!this.mkclick) return
this.close()
},
/**
* 顶部弹出样式处理
*/
top(type) {
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
this.ani = ['slide-top']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
backgroundColor: this.bg,
borderRadius:this.borderRadius || "0"
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
this.$nextTick(() => {
if (this.messageChild && this.type === 'message') {
this.messageChild.timerClose()
}
})
},
/**
* 底部弹出样式处理
*/
bottom(type) {
this.popupstyle = 'bottom'
this.ani = ['slide-bottom']
this.transClass = {
position: 'fixed',
left: 0,
right: 0,
bottom: 0,
paddingBottom: this.safeAreaInsets + 'px',
backgroundColor: this.bg,
borderRadius:this.borderRadius || "0",
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
/**
* 中间弹出样式处理
*/
center(type) {
this.popupstyle = 'center'
//
// #ifdef MP-WEIXIN
this.ani = ['fade']
// #endif
// #ifndef MP-WEIXIN
this.ani = ['zoom-out', 'fade']
// #endif
this.transClass = {
position: 'fixed',
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center',
borderRadius:this.borderRadius || "0"
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
left(type) {
this.popupstyle = 'left'
this.ani = ['slide-left']
this.transClass = {
position: 'fixed',
left: 0,
bottom: 0,
top: 0,
backgroundColor: this.bg,
borderRadius:this.borderRadius || "0",
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
},
right(type) {
this.popupstyle = 'right'
this.ani = ['slide-right']
this.transClass = {
position: 'fixed',
bottom: 0,
right: 0,
top: 0,
backgroundColor: this.bg,
borderRadius:this.borderRadius || "0",
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO type
if (type) return
this.showPopup = true
this.showTrans = true
}
}
}
</script>
<style lang="scss">
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
/* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */
// padding-bottom: constant(safe-area-inset-bottom);
// padding-bottom: env(safe-area-inset-bottom);
/* #endif */
&.left,
&.right {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
flex: 1;
}
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>

View File

@ -0,0 +1,131 @@
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
// 在iOS10+QQ小程序平台下传给原生的对象一定是个普通对象而不是Proxy对象否则会报parameter should be Object instead of ProxyObject的错误
this.animation = uni.createAnimation({
...options
})
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates(type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if(type === 'rotate'){
unit = 'deg'
}
styles.styles.transform += `${type}(${args+unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun(styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate(animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step(config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run(fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
if(!ref) return
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function(...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation(option, _this) {
if(!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}

View File

@ -0,0 +1,286 @@
<template>
<!-- #ifndef APP-NVUE -->
<view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
<!-- #endif -->
</template>
<script>
import { createAnimation } from './createAnimation'
/**
* Transition 过渡动画
* @description 简单过渡动画组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-right 由右至左过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {Number} duration 过渡动画持续时间
* @property {Object} styles 组件样式 css 样式注意带-连接符的属性需要使用小驼峰写法如`backgroundColor:red`
*/
export default {
name: 'uniTransition',
emits:['click','change'],
props: {
show: {
type: Boolean,
default: false
},
modeClass: {
type: [Array, String],
default() {
return 'fade'
}
},
duration: {
type: Number,
default: 300
},
styles: {
type: Object,
default() {
return {}
}
},
customClass:{
type: String,
default: ''
},
onceRender:{
type:Boolean,
default:false
},
},
data() {
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {}
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.open()
} else {
// close,
if (this.isShow) {
this.close()
}
}
},
immediate: true
}
},
computed: {
//
stylesObject() {
let styles = {
...this.styles,
'transition-duration': this.duration / 1000 + 's'
}
let transform = ''
for (let i in styles) {
let line = this.toLine(i)
transform += line + ':' + styles[i] + ';'
}
return transform
},
//
transformStyles() {
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
}
},
created() {
//
this.config = {
duration: this.duration,
timingFunction: 'ease',
transformOrigin: '50% 50%',
delay: 0
}
this.durationTime = this.duration
},
methods: {
/**
* ref 触发 初始化动画
*/
init(obj = {}) {
if (obj.duration) {
this.durationTime = obj.duration
}
this.animation = createAnimation(Object.assign(this.config, obj),this)
},
/**
* 点击组件触发回调
*/
onClick() {
this.$emit('click', {
detail: this.isShow
})
},
/**
* ref 触发 动画分组
* @param {Object} obj
*/
step(obj, config = {}) {
if (!this.animation) return
for (let i in obj) {
try {
if(typeof obj[i] === 'object'){
this.animation[i](...obj[i])
}else{
this.animation[i](obj[i])
}
} catch (e) {
console.error(`方法 ${i} 不存在`)
}
}
this.animation.step(config)
return this
},
/**
* ref 触发 执行动画
*/
run(fn) {
if (!this.animation) return
this.animation.run(fn)
},
//
open() {
clearTimeout(this.timer)
this.transform = ''
this.isShow = true
let { opacity, transform } = this.styleInit(false)
if (typeof opacity !== 'undefined') {
this.opacity = opacity
}
this.transform = transform
// nextTick wx
this.$nextTick(() => {
// TODO
this.timer = setTimeout(() => {
this.animation = createAnimation(this.config, this)
this.tranfromInit(false).step()
this.animation.run()
this.$emit('change', {
detail: this.isShow
})
}, 20)
})
},
//
close(type) {
if (!this.animation) return
this.tranfromInit(true)
.step()
.run(() => {
this.isShow = false
this.animationData = null
this.animation = null
let { opacity, transform } = this.styleInit(false)
this.opacity = opacity || 1
this.transform = transform
this.$emit('change', {
detail: this.isShow
})
})
},
//
styleInit(type) {
let styles = {
transform: ''
}
let buildStyle = (type, mode) => {
if (mode === 'fade') {
styles.opacity = this.animationType(type)[mode]
} else {
styles.transform += this.animationType(type)[mode] + ' '
}
}
if (typeof this.modeClass === 'string') {
buildStyle(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildStyle(type, mode)
})
}
return styles
},
//
tranfromInit(type) {
let buildTranfrom = (type, mode) => {
let aniNum = null
if (mode === 'fade') {
aniNum = type ? 0 : 1
} else {
aniNum = type ? '-100%' : '0'
if (mode === 'zoom-in') {
aniNum = type ? 0.8 : 1
}
if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1
}
if (mode === 'slide-right') {
aniNum = type ? '100%' : '0'
}
if (mode === 'slide-bottom') {
aniNum = type ? '100%' : '0'
}
}
this.animation[this.animationMode()[mode]](aniNum)
}
if (typeof this.modeClass === 'string') {
buildTranfrom(type, this.modeClass)
} else {
this.modeClass.forEach(mode => {
buildTranfrom(type, mode)
})
}
return this.animation
},
animationType(type) {
return {
fade: type ? 0 : 1,
'slide-top': `translateY(${type ? '0' : '-100%'})`,
'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
'slide-left': `translateX(${type ? '0' : '-100%'})`,
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
}
},
//
animationMode() {
return {
fade: 'opacity',
'slide-top': 'translateY',
'slide-right': 'translateX',
'slide-bottom': 'translateY',
'slide-left': 'translateX',
'zoom-in': 'scale',
'zoom-out': 'scale'
}
},
// 线
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
}
}
</script>
<style></style>

View File

@ -47,7 +47,11 @@
"clearConfirm": "Are You Sure To Clean All History ?",
"noHistory": "There Are No Any History",
"contentPrefix": "The Content Is ",
"contentSuffix": "Will Be Marked As History When You Confirm"
"contentSuffix": "Will Be Marked As History When You Confirm",
"create-time-desc": "Sort by time desc",
"create-time-asc": "Sort by time asc",
"create-name-desc": "Sort by name desc",
"create-name-asc": "Sort by name asc"
},
"me": {
"moreInfo": "More",

View File

@ -48,7 +48,11 @@
"clearConfirm": "确定清除全部历史记录?",
"noHistory": "您还没有历史记录",
"contentPrefix": "您输入的内容为 ",
"contentSuffix": " 如果点击确定,将记录到历史搜索,并返回.如果取消不做操作"
"contentSuffix": " 如果点击确定,将记录到历史搜索,并返回.如果取消不做操作",
"create-time-desc": "按创建时间降序",
"create-time-asc": "按创建时间升序",
"create-name-desc": "按照名称降序",
"create-name-asc": "按照名称升序"
},
"me": {
"moreInfo": "更多信息",

View File

@ -49,7 +49,11 @@
"clearConfirm": "確定清除全部歷史記錄?",
"noHistory": "您還沒有歷史記錄",
"contentPrefix": "您輸入的內容為 ",
"contentSuffix": " 如果點擊確定,將紀錄到歷史紀錄"
"contentSuffix": " 如果點擊確定,將紀錄到歷史紀錄",
"create-time-desc": "按創建時間降序",
"create-time-asc": "按創建時間升序",
"create-name-desc": "按照名稱降序",
"create-name-asc": "按照名稱升序"
},
"me": {
"moreInfo": "更多信息",

View File

@ -41,7 +41,13 @@
"type": "transparent",
"titleColor": "#fff",
"backgroundColor": "#0faeff",
"buttons": [],
"buttons": [
{
"fontSrc":"/static/custom/iconfont.ttf",
"text":"\ue61d",
"fontSize": "24px"
}
],
"searchInput": {
"backgroundColor": "#fff",
"borderRadius": "6px",

View File

@ -28,6 +28,7 @@
<script>
import {requestDir} from '@/api/panel'
import { treeSort } from '@/common/utils'
export default {
data() {
return {
@ -66,7 +67,13 @@ export default {
const param = {pid: pid}
requestDir(param).then(res => {
this.nodes = res.data
this.originResourceTree = res.data
if (localStorage.getItem('TreeSort-panel')) {
const curSortType = localStorage.getItem('TreeSort-panel')
this.sortTypeChange(curSortType)
} else {
this.nodes = res.data
}
}).catch(e => {
})
@ -88,6 +95,9 @@ export default {
uni.navigateTo({
url: './folder?detailDate=' + encodeURIComponent(JSON.stringify(param))
});
},
sortTypeChange(sortType) {
this.nodes = treeSort(this.originResourceTree, sortType, sortType)
}
}
};

View File

@ -1,13 +1,8 @@
<template>
<view class="page de-main">
<!-- <swiper indicator-dots="true">
<swiper-item v-for="(img, key) in imgUrls" :key="key"><image :src="img" /></swiper-item>
</swiper> -->
<view class=" ">
<view class="uni-title">
<uni-list >
<uni-list-item v-for="(node, index) in nodes" :key="index"
:title="node.text"
:showArrow="node.type === 'folder'"
@ -16,19 +11,40 @@
clickable
@click="clickHandler(node)"
rightText="" />
</uni-list>
</view>
</view>
<!-- <view style="height: 1000rpx;"></view> -->
<uni-popup ref="popup" position="top" @change="popupChange">
<view class="action-sheet-container">
<view class="action-sheet-content-container">
<view
class="action-sheet-item"
v-for="(item, index) in pickerValueArray"
:key="index"
:class="{ 'action-sheet-item-selected': selectedIndex === index }"
@click="onActionSheetItemClick(index)"
>
{{ item.label }}
</view>
</view>
<view class="action-sheet-close-container">
<view class="action-sheet-item" @click="closePopup">
关闭
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {requestDir} from '@/api/panel'
import { requestDir } from '@/api/panel'
import uniPopup from '@/components/uni-popup/uni-popup.vue'
import { treeSort } from '@/common/utils'
export default {
components: {
uniPopup
},
data() {
return {
showSwiper: false,
@ -36,7 +52,27 @@ export default {
'../../../static/panelimg/panel2.png',
'../../../static/panelimg/panel1.png'
],
nodes: []
nodes: [],
pickerValueArray: [{
label: this.$t('dir.create-time-asc'),
value: 'time_asc'
},
{
label: this.$t('dir.create-time-desc'),
value: 'time_desc'
},
{
label: this.$t('dir.create-name-asc'),
value: 'name_asc'
},
{
label: this.$t('dir.create-name-desc'),
value: 'name_desc'
}
],
selectedIndex: 1,
originResourceTree: [],
curSortType: 'time_desc'
};
},
onLoad() {
@ -62,7 +98,13 @@ export default {
const param = {pid: pid}
requestDir(param).then(res => {
this.nodes = res.data
this.originResourceTree = res.data
if (localStorage.getItem('TreeSort-panel')) {
this.curSortType = localStorage.getItem('TreeSort-panel')
this.sortTypeChange(this.curSortType)
} else {
this.nodes = res.data
}
uni.stopPullDownRefresh();
}).catch(e => {
uni.stopPullDownRefresh();
@ -85,7 +127,35 @@ export default {
uni.navigateTo({
url: './folder?detailDate=' + encodeURIComponent(JSON.stringify(param))
});
}
},
onActionSheetItemClick(index) {
this.selectedIndex = index;
this.sortTypeChange(this.pickerValueArray[index].value)
const that = this
setTimeout(() => {
that.closePopup()
}, 500)
},
onNavigationBarButtonTap(e) {
let that = this;
if (e.index === 0) {
this.$refs.popup.open('bottom')
uni.hideTabBar()
}
},
popupChange(e) {
if (!e.show) {
uni.showTabBar()
}
},
closePopup() {
this.$refs.popup.close()
},
sortTypeChange(sortType) {
this.nodes = treeSort(this.originResourceTree, this.curSortType, sortType)
this.curSortType = sortType
localStorage.setItem('TreeSort-panel', this.curSortType)
}
}
};
</script>
@ -104,4 +174,36 @@ swiper,
.de-main {
padding-top: 60rpx;
}
.action-sheet-container {
position: fixed;
bottom: 0;
left: 0;
right: 0;
box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
}
.action-sheet-content-container {
background-color: #FFFFFF;
bottom: 0;
left: 0;
right: 0;
}
.action-sheet-close-container {
background-color: #FFFFFF;
bottom: 0;
left: 0;
right: 0;
margin-top: 1px;
}
.action-sheet-item {
padding: 10px;
text-align: center;
font-size: 18px;
color: #666666;
}
.action-sheet-item-selected {
color: #007AFF;
font-weight: bold;
}
</style>

View File

@ -0,0 +1,19 @@
@font-face {
font-family: "iconfont"; /* Project id 4566310 */
src: url('iconfont.woff2?t=1716966170729') format('woff2'),
url('iconfont.woff?t=1716966170729') format('woff'),
url('iconfont.ttf?t=1716966170729') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-paixu-jiangxu:before {
content: "\e61d";
}

View File

@ -0,0 +1 @@
window._iconfont_svg_string_4566310='<svg><symbol id="icon-paixu-jiangxu" viewBox="0 0 1024 1024"><path d="M508.458667 213.333333h384a42.666667 42.666667 0 0 1 0 85.333334h-384a42.666667 42.666667 0 1 1 0-85.333334zM508.458667 469.333333h256a42.666667 42.666667 0 0 1 0 85.333334h-256a42.666667 42.666667 0 1 1 0-85.333334zM508.458667 725.333333h128a42.666667 42.666667 0 0 1 0 85.333334h-128a42.666667 42.666667 0 1 1 0-85.333334zM384 173.056a42.666667 42.666667 0 1 0-85.333333 0v579.669333l-140.501334-140.501333a42.624 42.624 0 1 0-60.330666 60.330667l213.333333 213.333333A42.666667 42.666667 0 0 0 384 855.722667v-682.666667z" fill="#898A8C" ></path></symbol></svg>',function(n){var t=(t=document.getElementsByTagName("script"))[t.length-1],e=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var i,o,a,d,c,s=function(t,e){e.parentNode.insertBefore(t,e)};if(e&&!n.__iconfont__svg__cssinject__){n.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(t){console&&console.log(t)}}i=function(){var t,e=document.createElement("div");e.innerHTML=n._iconfont_svg_string_4566310,(e=e.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",e=e,(t=document.body).firstChild?s(e,t.firstChild):t.appendChild(e))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(i,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),i()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(a=i,d=n.document,c=!1,r(),d.onreadystatechange=function(){"complete"==d.readyState&&(d.onreadystatechange=null,l())})}function l(){c||(c=!0,a())}function r(){try{d.documentElement.doScroll("left")}catch(t){return void setTimeout(r,50)}l()}}(window);

View File

@ -0,0 +1,16 @@
{
"id": "4566310",
"name": "dataease-v1-mobile",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "21571824",
"name": "排序-降序",
"font_class": "paixu-jiangxu",
"unicode": "e61d",
"unicode_decimal": 58909
}
]
}

Binary file not shown.

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