commit
ef5291b0d5
@ -16,6 +16,7 @@ yoy = "yoy"
|
||||
YOY = "YOY"
|
||||
Leafs = "Leafs"
|
||||
leafs = "leafs"
|
||||
hiden = "hiden"
|
||||
|
||||
[files]
|
||||
extend-exclude = [
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -16,4 +16,6 @@ public class UpdataDsRequest {
|
||||
@ApiModelProperty(value = "配置详情", required = true)
|
||||
private String configuration;
|
||||
private boolean configurationEncryption = false;
|
||||
private Boolean enableDataFill;
|
||||
private Boolean enableDataFillCreateTable;
|
||||
}
|
||||
|
||||
@ -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("更新仪表板状态")
|
||||
|
||||
@ -51,4 +51,7 @@ public class ChartExtRequest {
|
||||
|
||||
private Boolean excelExportFlag = false;
|
||||
|
||||
@ApiModelProperty(hidden = true)
|
||||
private String downloadType;
|
||||
|
||||
}
|
||||
|
||||
@ -44,4 +44,6 @@ public class PanelViewDetailsRequest {
|
||||
|
||||
private String downloadType;
|
||||
|
||||
private String viewType;
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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())) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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);
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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")) {
|
||||
|
||||
@ -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) {
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
server.servlet.context-path=
|
||||
@ -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 '数据填报允许新建表';
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dataease",
|
||||
"version": "1.18.19",
|
||||
"version": "1.18.20",
|
||||
"description": "dataease front",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -466,7 +466,7 @@ export default {
|
||||
initFontSize: 12,
|
||||
initActiveFontSize: 18,
|
||||
miniFontSize: 12,
|
||||
maxFontSize: 48,
|
||||
maxFontSize: 128,
|
||||
textAlignOptions: [
|
||||
{
|
||||
icon: 'iconfont iconfont-custom icon-juzuo',
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -116,6 +116,7 @@ export const customAttrTrans = {
|
||||
'tableItemFontSize',
|
||||
'tableTitleHeight',
|
||||
'tableItemHeight',
|
||||
'tableColumnWidth',
|
||||
'dimensionFontSize',
|
||||
'quotaFontSize',
|
||||
'spaceSplit', // 间隔
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
@ -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',
|
||||
|
||||
@ -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: '僅編輯時生效',
|
||||
|
||||
@ -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: ', 前往 ',
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)
|
||||
// 处理空值
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1819,7 +1819,9 @@ export const TYPE_CONFIGS = [
|
||||
'label-selector-ant-v': [
|
||||
'show',
|
||||
'fontSize',
|
||||
'color'
|
||||
'color',
|
||||
'labelContent',
|
||||
'reserveDecimalCount'
|
||||
],
|
||||
'tooltip-selector-ant-v': [
|
||||
'show',
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
276
core/frontend/src/views/chart/components/senior/DataForecast.vue
Normal file
276
core/frontend/src/views/chart/components/senior/DataForecast.vue
Normal 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>
|
||||
@ -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"
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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')
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -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"
|
||||
/>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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%;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dataease-mobile",
|
||||
"version": "1.18.19",
|
||||
"version": "1.18.20",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "npm run dev:h5",
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
45
core/mobile/src/components/uni-popup-dialog/keypress.js
Normal file
45
core/mobile/src/components/uni-popup-dialog/keypress.js
Normal 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
|
||||
316
core/mobile/src/components/uni-popup-dialog/uni-popup-dialog.vue
Normal file
316
core/mobile/src/components/uni-popup-dialog/uni-popup-dialog.vue
Normal 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>
|
||||
@ -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>
|
||||
187
core/mobile/src/components/uni-popup-share/uni-popup-share.vue
Normal file
187
core/mobile/src/components/uni-popup-share/uni-popup-share.vue
Normal 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>
|
||||
7
core/mobile/src/components/uni-popup/i18n/en.json
Normal file
7
core/mobile/src/components/uni-popup/i18n/en.json
Normal 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"
|
||||
}
|
||||
8
core/mobile/src/components/uni-popup/i18n/index.js
Normal file
8
core/mobile/src/components/uni-popup/i18n/index.js
Normal 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
|
||||
}
|
||||
7
core/mobile/src/components/uni-popup/i18n/zh-Hans.json
Normal file
7
core/mobile/src/components/uni-popup/i18n/zh-Hans.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "取消",
|
||||
"uni-popup.ok": "确定",
|
||||
"uni-popup.placeholder": "请输入",
|
||||
"uni-popup.title": "提示",
|
||||
"uni-popup.shareTitle": "分享到"
|
||||
}
|
||||
7
core/mobile/src/components/uni-popup/i18n/zh-Hant.json
Normal file
7
core/mobile/src/components/uni-popup/i18n/zh-Hant.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"uni-popup.cancel": "取消",
|
||||
"uni-popup.ok": "確定",
|
||||
"uni-popup.placeholder": "請輸入",
|
||||
"uni-popup.title": "提示",
|
||||
"uni-popup.shareTitle": "分享到"
|
||||
}
|
||||
45
core/mobile/src/components/uni-popup/keypress.js
Normal file
45
core/mobile/src/components/uni-popup/keypress.js
Normal 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
|
||||
26
core/mobile/src/components/uni-popup/popup.js
Normal file
26
core/mobile/src/components/uni-popup/popup.js
Normal 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;
|
||||
},
|
||||
}
|
||||
}
|
||||
90
core/mobile/src/components/uni-popup/uni-popup.uvue
Normal file
90
core/mobile/src/components/uni-popup/uni-popup.uvue
Normal 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>
|
||||
503
core/mobile/src/components/uni-popup/uni-popup.vue
Normal file
503
core/mobile/src/components/uni-popup/uni-popup.vue
Normal 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: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
|
||||
// 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>
|
||||
131
core/mobile/src/components/uni-transition/createAnimation.js
Normal file
131
core/mobile/src/components/uni-transition/createAnimation.js
Normal 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)
|
||||
}
|
||||
286
core/mobile/src/components/uni-transition/uni-transition.vue
Normal file
286
core/mobile/src/components/uni-transition/uni-transition.vue
Normal 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>
|
||||
@ -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",
|
||||
|
||||
@ -48,7 +48,11 @@
|
||||
"clearConfirm": "确定清除全部历史记录?",
|
||||
"noHistory": "您还没有历史记录",
|
||||
"contentPrefix": "您输入的内容为 ",
|
||||
"contentSuffix": " 如果点击确定,将记录到历史搜索,并返回.如果取消不做操作"
|
||||
"contentSuffix": " 如果点击确定,将记录到历史搜索,并返回.如果取消不做操作",
|
||||
"create-time-desc": "按创建时间降序",
|
||||
"create-time-asc": "按创建时间升序",
|
||||
"create-name-desc": "按照名称降序",
|
||||
"create-name-asc": "按照名称升序"
|
||||
},
|
||||
"me": {
|
||||
"moreInfo": "更多信息",
|
||||
|
||||
@ -49,7 +49,11 @@
|
||||
"clearConfirm": "確定清除全部歷史記錄?",
|
||||
"noHistory": "您還沒有歷史記錄",
|
||||
"contentPrefix": "您輸入的內容為 ",
|
||||
"contentSuffix": " 如果點擊確定,將紀錄到歷史紀錄"
|
||||
"contentSuffix": " 如果點擊確定,將紀錄到歷史紀錄",
|
||||
"create-time-desc": "按創建時間降序",
|
||||
"create-time-asc": "按創建時間升序",
|
||||
"create-name-desc": "按照名稱降序",
|
||||
"create-name-asc": "按照名稱升序"
|
||||
},
|
||||
"me": {
|
||||
"moreInfo": "更多信息",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
19
core/mobile/src/static/custom/iconfont.css
Normal file
19
core/mobile/src/static/custom/iconfont.css
Normal 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";
|
||||
}
|
||||
|
||||
1
core/mobile/src/static/custom/iconfont.js
Normal file
1
core/mobile/src/static/custom/iconfont.js
Normal 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);
|
||||
16
core/mobile/src/static/custom/iconfont.json
Normal file
16
core/mobile/src/static/custom/iconfont.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
core/mobile/src/static/custom/iconfont.ttf
Normal file
BIN
core/mobile/src/static/custom/iconfont.ttf
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user