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

This commit is contained in:
fit2cloud-chenyw 2024-10-29 16:57:59 +08:00 committed by GitHub
commit 273c8ae275
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 5245 additions and 274 deletions

View File

@ -104,7 +104,7 @@ public class CopilotManage {
Map<Long, DatasourceSchemaDTO> dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap"); Map<Long, DatasourceSchemaDTO> dsMap = (Map<Long, DatasourceSchemaDTO>) sqlMap.get("dsMap");
boolean crossDs = Utils.isCrossDs(dsMap); boolean crossDs = Utils.isCrossDs(dsMap);
if (crossDs) { if (crossDs) {
DEException.throwException("跨源数据集不支持该功能"); DEException.throwException(Translator.get("i18n_copilot_cross_ds_error"));
} }
// 调用copilot service 获取SQL和chart struct将返回SQL中表名替换成数据集SQL // 调用copilot service 获取SQL和chart struct将返回SQL中表名替换成数据集SQL
@ -302,7 +302,7 @@ public class CopilotManage {
if (StringUtils.equalsIgnoreCase(receiveDTO.getChart().getType(), "pie")) { if (StringUtils.equalsIgnoreCase(receiveDTO.getChart().getType(), "pie")) {
AxisFieldDTO column = receiveDTO.getChart().getColumn(); AxisFieldDTO column = receiveDTO.getChart().getColumn();
if (fields.size() != 2 || column == null) { if (fields.size() != 2 || column == null) {
DEException.throwException("当前字段不足以构建饼图: " + JsonUtil.toJSONString(receiveDTO)); DEException.throwException("build pie error: " + JsonUtil.toJSONString(receiveDTO));
} }
AxisDTO axisDTO = new AxisDTO(); AxisDTO axisDTO = new AxisDTO();
AxisFieldDTO x = new AxisFieldDTO(); AxisFieldDTO x = new AxisFieldDTO();
@ -318,7 +318,7 @@ public class CopilotManage {
y.setName(column.getName()); y.setName(column.getName());
y.setValue(column.getValue()); y.setValue(column.getValue());
} else { } else {
DEException.throwException("当前字段不足以构建饼图: " + JsonUtil.toJSONString(receiveDTO)); DEException.throwException("build pie error: " + JsonUtil.toJSONString(receiveDTO));
} }
axisDTO.setX(x); axisDTO.setX(x);
axisDTO.setY(y); axisDTO.setY(y);

View File

@ -15,6 +15,7 @@ import io.dataease.api.ds.vo.ExcelSheetData;
import io.dataease.datasource.dao.auto.entity.CoreDatasource; import io.dataease.datasource.dao.auto.entity.CoreDatasource;
import io.dataease.exception.DEException; import io.dataease.exception.DEException;
import io.dataease.extensions.datasource.dto.DatasetTableDTO; import io.dataease.extensions.datasource.dto.DatasetTableDTO;
import io.dataease.extensions.datasource.dto.DatasourceDTO;
import io.dataease.extensions.datasource.dto.DatasourceRequest; import io.dataease.extensions.datasource.dto.DatasourceRequest;
import io.dataease.extensions.datasource.dto.TableField; import io.dataease.extensions.datasource.dto.TableField;
import io.dataease.utils.AuthUtils; import io.dataease.utils.AuthUtils;
@ -40,6 +41,21 @@ public class ExcelUtils {
private static TypeReference<List<TableField>> TableFieldListTypeReference = new TypeReference<List<TableField>>() { private static TypeReference<List<TableField>> TableFieldListTypeReference = new TypeReference<List<TableField>>() {
}; };
private static TypeReference<List<ExcelSheetData>> sheets = new TypeReference<List<ExcelSheetData>>() {
};
public static void mergeSheets(CoreDatasource requestDatasource, DatasourceDTO sourceData) {
List<ExcelSheetData> newSheets = JsonUtil.parseList(requestDatasource.getConfiguration(), sheets);
List<String> tableNames = newSheets.stream().map(ExcelSheetData::getDeTableName).collect(Collectors.toList());
List<ExcelSheetData> oldSheets = JsonUtil.parseList(sourceData.getConfiguration(), sheets);
for (ExcelSheetData oldSheet : oldSheets) {
if (!tableNames.contains(oldSheet.getDeTableName())) {
newSheets.add(oldSheet);
}
}
requestDatasource.setConfiguration(JsonUtil.toJSONString(newSheets).toString());
}
public static List<DatasetTableDTO> getTables(DatasourceRequest datasourceRequest) throws DEException { public static List<DatasetTableDTO> getTables(DatasourceRequest datasourceRequest) throws DEException {
List<DatasetTableDTO> tableDescs = new ArrayList<>(); List<DatasetTableDTO> tableDescs = new ArrayList<>();
try { try {

View File

@ -404,7 +404,7 @@ public class DatasourceServer implements DatasourceApi {
List<String> tables = ExcelUtils.getTables(datasourceRequest).stream().map(DatasetTableDTO::getTableName).collect(Collectors.toList()); List<String> tables = ExcelUtils.getTables(datasourceRequest).stream().map(DatasetTableDTO::getTableName).collect(Collectors.toList());
if (dataSourceDTO.getEditType() == 0) { if (dataSourceDTO.getEditType() == 0) {
toCreateTables = tables; toCreateTables = tables;
toDeleteTables = sourceTables; toDeleteTables = sourceTables.stream().filter(s -> tables.contains(s)).collect(Collectors.toList());
for (String deleteTable : toDeleteTables) { for (String deleteTable : toDeleteTables) {
try { try {
datasourceSyncManage.dropEngineTable(deleteTable); datasourceSyncManage.dropEngineTable(deleteTable);
@ -422,6 +422,7 @@ public class DatasourceServer implements DatasourceApi {
} }
datasourceSyncManage.extractExcelData(requestDatasource, "all_scope"); datasourceSyncManage.extractExcelData(requestDatasource, "all_scope");
dataSourceManage.checkName(dataSourceDTO); dataSourceManage.checkName(dataSourceDTO);
ExcelUtils.mergeSheets(requestDatasource, sourceData);
dataSourceManage.innerEdit(requestDatasource); dataSourceManage.innerEdit(requestDatasource);
} else { } else {
datasourceSyncManage.extractExcelData(requestDatasource, "add_scope"); datasourceSyncManage.extractExcelData(requestDatasource, "add_scope");
@ -735,11 +736,15 @@ public class DatasourceServer implements DatasourceApi {
} }
} }
private static final Integer replace = 0;
private static final Integer append = 1;
public ExcelFileData excelUpload(@RequestParam("file") MultipartFile file, @RequestParam("id") long datasourceId, @RequestParam("editType") Integer editType) throws DEException { public ExcelFileData excelUpload(@RequestParam("file") MultipartFile file, @RequestParam("id") long datasourceId, @RequestParam("editType") Integer editType) throws DEException {
CoreDatasource coreDatasource = datasourceMapper.selectById(datasourceId);
ExcelUtils excelUtils = new ExcelUtils(); ExcelUtils excelUtils = new ExcelUtils();
ExcelFileData excelFileData = excelUtils.excelSaveAndParse(file); ExcelFileData excelFileData = excelUtils.excelSaveAndParse(file);
if (editType == 1 || editType == 0) { //按照excel sheet 名称匹配 if (Objects.equals(editType, append)) { //按照excel sheet 名称匹配替换0追加1
CoreDatasource coreDatasource = datasourceMapper.selectById(datasourceId);
if (coreDatasource != null) { if (coreDatasource != null) {
DatasourceRequest datasourceRequest = new DatasourceRequest(); DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setDatasource(transDTO(coreDatasource)); datasourceRequest.setDatasource(transDTO(coreDatasource));
@ -757,7 +762,6 @@ public class DatasourceServer implements DatasourceApi {
oldTableFields.sort((o1, o2) -> { oldTableFields.sort((o1, o2) -> {
return o1.getName().compareTo(o2.getName()); return o1.getName().compareTo(o2.getName());
}); });
if (isEqual(newTableFields, oldTableFields)) { if (isEqual(newTableFields, oldTableFields)) {
sheet.setDeTableName(datasetTableDTO.getTableName()); sheet.setDeTableName(datasetTableDTO.getTableName());
excelSheetDataList.add(sheet); excelSheetDataList.add(sheet);
@ -770,8 +774,21 @@ public class DatasourceServer implements DatasourceApi {
} }
excelFileData.setSheets(excelSheetDataList); excelFileData.setSheets(excelSheetDataList);
} }
} } else {
if (coreDatasource != null) {
DatasourceRequest datasourceRequest = new DatasourceRequest();
datasourceRequest.setDatasource(transDTO(coreDatasource));
List<DatasetTableDTO> datasetTableDTOS = ExcelUtils.getTables(datasourceRequest);
for (ExcelSheetData sheet : excelFileData.getSheets()) {
for (DatasetTableDTO datasetTableDTO : datasetTableDTOS) {
if (excelDataTableName(datasetTableDTO.getTableName()).equals(sheet.getTableName()) || isCsv(file.getOriginalFilename())) {
sheet.setDeTableName(datasetTableDTO.getTableName());
}
}
}
}
}
for (ExcelSheetData sheet : excelFileData.getSheets()) { for (ExcelSheetData sheet : excelFileData.getSheets()) {
for (int i = 0; i < sheet.getFields().size() - 1; i++) { for (int i = 0; i < sheet.getFields().size() - 1; i++) {
for (int j = i + 1; j < sheet.getFields().size(); j++) { for (int j = i + 1; j < sheet.getFields().size(); j++) {
@ -1155,8 +1172,11 @@ public class DatasourceServer implements DatasourceApi {
if (!Arrays.asList("API", "Excel", "folder").contains(coreDatasource.getType())) { if (!Arrays.asList("API", "Excel", "folder").contains(coreDatasource.getType())) {
calciteProvider.updateDsPoolAfterCheckStatus(datasourceDTO); calciteProvider.updateDsPoolAfterCheckStatus(datasourceDTO);
} }
} catch (DEException e) {
datasourceDTO.setStatus("Error");
DEException.throwException(e.getMessage());
} catch (Exception e) { } catch (Exception e) {
coreDatasource.setStatus("Error"); datasourceDTO.setStatus("Error");
DEException.throwException(e.getMessage()); DEException.throwException(e.getMessage());
} finally { } finally {
coreDatasource.setStatus(datasourceDTO.getStatus()); coreDatasource.setStatus(datasourceDTO.getStatus());

View File

@ -62,3 +62,45 @@ i18n_day=Day
i18n_hour=Hour i18n_hour=Hour
i18n_minute=Minute i18n_minute=Minute
i18n_second=Second i18n_second=Second
i18n_no_datasource_permission_to_create_column=No datasource permission, cannot create column
i18n_df_folder_cannot_to_search=Folder cannot for search data
i18n_df_no_primary_key=No primary key
i18n_df_cannot_operate_folder=Cannot Operate Folder
i18n_df_cannot_be_none=[%s] Cannot be null
i18n_df_value_cannot_be_none=[%s] value: %s Cannot be null
i18n_df_value_exists_in_database=[%s] value: %s Exists in database
i18n_df_data=Data
i18n_df_start=Start
i18n_df_end=End
i18n_df_datasource_not_found=datasource not found
i18n_df_datasource_does_not_enable_data_filling=function of dataFilling is not enabled
i18n_df_builtin_datasource=builtin datasource
i18n_df_folder_required=folder required
i18n_df_form_not_exists=form not exists
i18n_df_name_can_not_empty=name can not empty
i18n_df_template=template
i18n_df_task_status_is_null_or_finished=task status is null or finished
i18n_df_task_need_task_id=task need taskID
i18n_df_not_current_task_user=not current task user
i18n_df_miss_parameter=miss parameter
i18n_df_no_running_instance=no running instance
i18n_df_value=value
i18n_df_format_error=parse error
i18n_df_cannot_earlier_than=cannot earlier than
i18n_df_cannot_be_all_null=cannot be all null
i18n_df_value_not_in_range=value not in range
i18n_df_value_value_not_in_range=value: %s not in range
i18n_df_required=required
i18n_df_must_unique=must be unique
i18n_df_excel_parsing_error=Excel parse error
i18n_df_excel_is_empty=Excel is empty
i18n_df_excel_template_column_not_fit=count of template columns are not fit
i18n_df_selection=selection
i18n_df_date_format=date format
i18n_df_integer=integer
i18n_df_decimal=decimal
i18n_df_multiple_value_split=use ';' to split multiple value
i18n_df_email_type=email type
i18n_df_phone_type=phone type

View File

@ -81,3 +81,48 @@ i18n_day=\u5929
i18n_hour=\u5C0F\u65F6 i18n_hour=\u5C0F\u65F6
i18n_minute=\u5206\u949F i18n_minute=\u5206\u949F
i18n_second=\u79D2 i18n_second=\u79D2
i18n_no_datasource_permission_to_create_column=\u65E0\u6570\u636E\u6E90\u8BBF\u95EE\u6743\u9650\uFF0C\u65E0\u6CD5\u521B\u5EFA\u8868\u5B57\u6BB5
i18n_df_folder_cannot_to_search=\u6587\u4EF6\u5939\u4E0D\u80FD\u67E5\u8BE2\u6570\u636E
i18n_df_no_primary_key=\u6CA1\u6709\u4E3B\u952E
i18n_df_cannot_operate_folder=\u4E0D\u80FD\u64CD\u4F5C\u6587\u4EF6\u5939
i18n_df_cannot_be_none=[%s] \u4E0D\u80FD\u4E3A\u7A7A
i18n_df_value_cannot_be_none=[%s] \u503C: %s \u4E0D\u80FD\u4E3A\u7A7A
i18n_df_value_exists_in_database=[%s] \u503C: %s \u5728\u6570\u636E\u5E93\u4E2D\u5DF2\u5B58\u5728, \u4E0D\u80FD\u91CD\u590D
i18n_df_data=\u6570\u636E
i18n_df_start=\u5F00\u59CB
i18n_df_end=\u7ED3\u675F
i18n_df_datasource_not_found=\u6CA1\u6709\u627E\u5230\u6570\u636E\u6E90
i18n_df_datasource_does_not_enable_data_filling=\u8BE5\u6570\u636E\u6E90\u6CA1\u6709\u542F\u7528\u6570\u636E\u586B\u62A5\u914D\u7F6E
i18n_df_builtin_datasource=\u5185\u5EFA\u6570\u636E\u5E93
i18n_df_folder_required=\u76EE\u5F55\u5FC5\u9009
i18n_df_form_not_exists=\u8868\u5355\u4E0D\u5B58\u5728
i18n_df_name_can_not_empty=\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
i18n_df_template=\u6A21\u677F
i18n_df_task_status_is_null_or_finished=\u4EFB\u52A1\u72B6\u6001\u4E3A\u7A7A\u6216\u5DF2\u5B8C\u6210
i18n_df_task_need_task_id=\u9700\u6307\u5B9A\u4EFB\u52A1ID
i18n_df_not_current_task_user=\u4E0D\u662F\u5F53\u524D\u4EFB\u52A1\u7684\u76EE\u6807\u7528\u6237
i18n_df_miss_parameter=\u7F3A\u5931\u53C2\u6570
i18n_df_no_running_instance=\u5F53\u524D\u4EFB\u52A1\u6682\u65F6\u65E0\u8FD0\u884C\u5B9E\u4F8B
i18n_df_value=\u503C
i18n_df_format_error=\u683C\u5F0F\u89E3\u6790\u9519\u8BEF
i18n_df_cannot_earlier_than=\u4E0D\u80FD\u65E9\u4E8E
i18n_df_cannot_be_all_null=\u4E0D\u80FD\u53EA\u6709\u4E00\u4E2A\u4E3A\u7A7A
i18n_df_value_not_in_range=\u503C\u4E0D\u5728\u8303\u56F4\u5185
i18n_df_value_value_not_in_range=\u503C: %s \u4E0D\u5728\u8303\u56F4\u5185
i18n_df_required=\u5FC5\u586B
i18n_df_must_unique=\u4E0D\u5141\u8BB8\u91CD\u590D\u503C
i18n_df_excel_parsing_error=Excel\u89E3\u6790\u9519\u8BEF
i18n_df_excel_is_empty=\u8BE5Excel\u6CA1\u6709\u6570\u636E
i18n_df_excel_template_column_not_fit=\u6A21\u677F\u5B57\u6BB5\u4E2A\u6570\u4E0D\u5339\u914D
i18n_df_selection=\u9009\u9879\u503C\u4E3A
i18n_df_date_format=\u65E5\u671F\u683C\u5F0F
i18n_df_integer=\u6574\u5F62\u6570\u5B57
i18n_df_decimal=\u5C0F\u6570\u6570\u5B57
i18n_df_multiple_value_split=\u591A\u4E2A\u503C\u4F7F\u7528\u5206\u53F7";"\u5206\u5272
i18n_df_email_type=\u90AE\u7BB1\u683C\u5F0F
i18n_df_phone_type=\u624B\u673A\u53F7\u683C\u5F0F
i18n_copilot_cross_ds_error=\u8DE8\u6E90\u6570\u636E\u96C6\u4E0D\u652F\u6301\u8BE5\u529F\u80FD

View File

@ -2,23 +2,38 @@ login.validator.name1=\u8CEC\u865F/\u90F5\u7BB1/\u624B\u6A5F\u865F\u4E0D\u80FD\u
login.validator.pwd1=\u5BC6\u78BC\u4E0D\u80FD\u70BA\u7A7A login.validator.pwd1=\u5BC6\u78BC\u4E0D\u80FD\u70BA\u7A7A
i18n_menu.home=\u9996\u9801 i18n_menu.home=\u9996\u9801
i18n_menu.workbranch=\u5DE5\u4F5C\u81FA i18n_menu.workbranch=\u5DE5\u4F5C\u53F0
i18n_menu.visualized=\u53EF\u8996\u5316 i18n_menu.visualized=\u53EF\u8996\u5316
i18n_menu.template=\u6A21\u7248 i18n_menu.template=\u6A21\u7248
i18n_menu.application=\u61C9\u7528 i18n_menu.application=\u61C9\u7528
i18n_menu.system=\u7CFB\u7D71\u7BA1\u7406 i18n_menu.system=\u7CFB\u7D71\u7BA1\u7406
i18n_menu.template-market=\u6A21\u677F\u5E02\u5834
i18n_menu.template-setting=\u6A21\u677F\u7BA1\u7406
i18n_menu.view=\u6578\u64DA\u5C55\u793A i18n_menu.view=\u6578\u64DA\u5C55\u793A
i18n_menu.data=\u6578\u64DA\u6E96\u5099 i18n_menu.data=\u6578\u64DA\u51C6\u5099
i18n_menu.panel=\u5100\u8868\u677F i18n_menu.panel=\u5100\u8868\u677F
i18n_menu.data-filling-manage=\u6578\u64DA\u586B\u5831
i18n_menu.screen=\u6578\u64DA\u5927\u5C4F i18n_menu.screen=\u6578\u64DA\u5927\u5C4F
i18n_menu.dataset=\u6578\u64DA\u96C6 i18n_menu.dataset=\u6578\u64DA\u96C6
i18n_menu.datasource=\u6578\u64DA\u6E90 i18n_menu.datasource=\u6578\u64DA\u6E90
i18n_menu.user=\u7528\u6236\u7BA1\u7406 i18n_menu.user=\u7528\u6236\u7BA1\u7406
i18n_menu.org=\u7D44\u7E54\u7BA1\u7406 i18n_menu.org=\u7D44\u7E54\u7BA1\u7406
i18n_menu.auth=\u6B0A\u9650\u914D\u7F6E i18n_menu.auth=\u6B0A\u9650\u914D\u7F6E
i18n_menu.sysVariable=\u7CFB\u7EDF\u53D8\u91CF i18n_menu.report=\u5B9A\u6642\u5831\u544A
i18n_field_name_repeat=\u5177\u6709\u91CD\u8907\u7684\u6B04\u4F4D\u540D\u7A31\uFF1A i18n_menu.sync=\u540C\u6B65\u7BA1\u7406
i18n_menu.association=\u8840\u7DE3\u5206\u6790
i18n_menu.threshold=\u544A\u8B66\u7BA1\u7406
i18n_menu.summary=\u6982\u89BD
i18n_menu.ds=\u6578\u64DA\u9023\u63A5\u7BA1\u7406
i18n_menu.task=\u4EFB\u52D9\u7BA1\u7406
i18n_menu.embedded=\u5D4C\u5165\u5F0F\u7BA1\u7406
i18n_menu.plugin=\u63D2\u4EF6\u7BA1\u7406
i18n_menu.platform=\u5E73\u53F0\u5C0D\u63A5
i18n_menu.appearance=\u5916\u89C0\u914D\u7F6E
i18n_menu.sysVariable=\u7CFB\u7D71\u8B8A\u91CF
i18n_menu.sysTypeface=\u5B57\u9AD4\u7BA1\u7406
i18n_menu.font=\u5B57\u9AD4\u7BA1\u7406
i18n_field_name_repeat=\u6709\u91CD\u5FA9\u5B57\u6BB5\u540D\uFF1A
i18n_pid_not_eq_id=\u79FB\u52D5\u76EE\u6A19\u4E0D\u80FD\u662F\u81EA\u5DF1\u6216\u5B50\u76EE\u9304 i18n_pid_not_eq_id=\u79FB\u52D5\u76EE\u6A19\u4E0D\u80FD\u662F\u81EA\u5DF1\u6216\u5B50\u76EE\u9304
i18n_ds_name_exists=\u8A72\u5206\u7D44\u4E0B\u540D\u7A31\u91CD\u5FA9 i18n_ds_name_exists=\u8A72\u5206\u7D44\u4E0B\u540D\u7A31\u91CD\u5FA9
i18n_table_id_can_not_empty=\u67E5\u8A62\u7BC0\u9EDE\u4E0D\u80FD\u70BA\u7A7A i18n_table_id_can_not_empty=\u67E5\u8A62\u7BC0\u9EDE\u4E0D\u80FD\u70BA\u7A7A
@ -32,14 +47,14 @@ i18n_union_field_can_not_empty=\u95DC\u806F\u5B57\u6BB5\u4E0D\u80FD\u70BA\u7A7A
i18n_table_duplicate=\u76F8\u540C\u7BC0\u9EDE\u9700\u91CD\u65B0\u62D6\u5165\u624D\u80FD\u7E7C\u7E8C\u65B0\u5EFA\u6578\u64DA\u96C6 i18n_table_duplicate=\u76F8\u540C\u7BC0\u9EDE\u9700\u91CD\u65B0\u62D6\u5165\u624D\u80FD\u7E7C\u7E8C\u65B0\u5EFA\u6578\u64DA\u96C6
i18n_no_column_permission=\u6C92\u6709\u5217\u6B0A\u9650 i18n_no_column_permission=\u6C92\u6709\u5217\u6B0A\u9650
i18n_fetch_error=SQL\u57F7\u884C\u5931\u6557\uFF0C\u8ACB\u6AA2\u67E5\u8868\u3001\u5B57\u6BB5\u3001\u95DC\u806F\u95DC\u7CFB\u7B49\u4FE1\u606F\u662F\u5426\u6B63\u78BA\u4E26\u91CD\u65B0\u7DE8\u8F2F\u3002 i18n_fetch_error=SQL\u57F7\u884C\u5931\u6557\uFF0C\u8ACB\u6AA2\u67E5\u8868\u3001\u5B57\u6BB5\u3001\u95DC\u806F\u95DC\u7CFB\u7B49\u4FE1\u606F\u662F\u5426\u6B63\u78BA\u4E26\u91CD\u65B0\u7DE8\u8F2F\u3002
i18n_no_datasource_permission=\u65E0\u6570\u636E\u6E90\u8BBF\u95EE\u6743\u9650 i18n_no_datasource_permission=\u7121\u6578\u64DA\u6E90\u8A2A\u554F\u6B0A\u9650
i18n_no_dataset_permission=\u65e0\u6570\u636e\u96c6\u8bbf\u95ee\u6743\u9650 i18n_no_dataset_permission=\u7121\u6578\u64DA\u96C6\u8A2A\u554F\u6B0A\u9650
i18n_not_full=\u7576\u524D\u6578\u64DA\u6E90\u4E0D\u652F\u6301\u5168\u9023\u63A5 i18n_not_full=\u7576\u524D\u6578\u64DA\u6E90\u4E0D\u652F\u6301\u5168\u9023\u63A5
i18n_field_circular_ref=\u5B57\u6BB5\u5B58\u5728\u5FAA\u74B0\u5F15\u7528 i18n_field_circular_ref=\u5B57\u6BB5\u5B58\u5728\u5FAA\u74B0\u5F15\u7528
i18n_chart_not_handler=\u7121\u6CD5\u8655\u7406\u8A72\u5716\u8868\u985E\u578B i18n_chart_not_handler=\u7121\u6CD5\u8655\u7406\u8A72\u5716\u8868\u985E\u578B
i18n_chart_delete=\u8996\u5716\u4E0D\u5B58\u5728 i18n_chart_delete=\u5716\u8868\u4E0D\u5B58\u5728
i18n_no_ds=\u6578\u64DA\u96C6\u4E0D\u5B58\u5728\u6216\u6C92\u6709\u6B0A\u9650 i18n_no_ds=\u6578\u64DA\u96C6\u4E0D\u5B58\u5728\u6216\u6C92\u6709\u6B0A\u9650
i18n_datasource_delete=\u6578\u64DA\u6E90\u4E0D\u5B58\u5728 i18n_datasource_delete=\u6578\u64DA\u6E90\u4E0D\u5B58\u5728
i18n_gauge_field_change=\u6240\u7528\u5B57\u6BB5\u767C\u751F\u8B8A\u66F4\uFF0C\u8ACB\u91CD\u65B0\u7DE8\u8F2F i18n_gauge_field_change=\u6240\u7528\u5B57\u6BB5\u767C\u751F\u8B8A\u66F4\uFF0C\u8ACB\u91CD\u65B0\u7DE8\u8F2F
@ -53,9 +68,12 @@ i18n_invalid_ds=\u6578\u64DA\u6E90\u7121\u6548
i18n_user_disable=\u7528\u6236\u5DF2\u88AB\u7981\u7528\uFF0C\u7121\u6CD5\u767B\u9304 i18n_user_disable=\u7528\u6236\u5DF2\u88AB\u7981\u7528\uFF0C\u7121\u6CD5\u767B\u9304
i18n_login_name_pwd_err=\u7528\u6236\u540D\u6216\u5BC6\u78BC\u932F\u8AA4 i18n_login_name_pwd_err=\u7528\u6236\u540D\u6216\u5BC6\u78BC\u932F\u8AA4
i18n_error_login_type=\u767B\u9304\u985E\u578B\u932F\u8AA4 i18n_error_login_type=\u767B\u9304\u985E\u578B\u932F\u8AA4
i18n_schema_is_empty=schema\u70BA\u7A7A\uFF01 i18n_schema_is_empty=schema \u70BA\u7A7A\uFF01
i18n_table_name_repeat=\u540D\u7A31\u91CD\u8907: i18n_table_name_repeat=\u540D\u7A31\u91CD\u5FA9:
i18n_sql_not_empty=sql\u4E0D\u80FD\u70BA\u7A7A i18n_sql_not_empty=sql \u4E0D\u80FD\u70BA\u7A7A
i18n_menu.parameter=\u7CFB\u7D71\u53C3\u6578
i18n_user_old_pwd_error=\u539F\u59CB\u5BC6\u78BC\u932F\u8AA4
i18n_menu.toolbox-log=\u64CD\u4F5C\u65E5\u5FD7
i18n_year=\u5E74 i18n_year=\u5E74
i18n_month=\u6708 i18n_month=\u6708
@ -63,3 +81,46 @@ i18n_day=\u5929
i18n_hour=\u5C0F\u6642 i18n_hour=\u5C0F\u6642
i18n_minute=\u5206\u9418 i18n_minute=\u5206\u9418
i18n_second=\u79D2 i18n_second=\u79D2
i18n_no_datasource_permission_to_create_column=\u7121\u6578\u64DA\u6E90\u8A2A\u554F\u6B0A\u9650\uFF0C\u7121\u6CD5\u5275\u5EFA\u8868\u5B57\u6BB5
i18n_df_folder_cannot_to_search=\u6587\u4EF6\u593E\u4E0D\u80FD\u67E5\u8A62\u6578\u64DA
i18n_df_no_primary_key=\u6C92\u6709\u4E3B\u9375
i18n_df_cannot_operate_folder=\u4E0D\u80FD\u64CD\u4F5C\u6587\u4EF6\u593E
i18n_df_cannot_be_none=[%s] \u4E0D\u80FD\u70BA\u7A7A
i18n_df_value_cannot_be_none=[%s] \u503C: %s \u4E0D\u80FD\u70BA\u7A7A
i18n_df_value_exists_in_database=[%s] \u503C: %s \u5728\u6578\u64DA\u5EAB\u4E2D\u5DF2\u5B58\u5728, \u4E0D\u80FD\u91CD\u5FA9
i18n_df_data=\u6578\u64DA
i18n_df_start=\u958B\u59CB
i18n_df_end=\u7D50\u675F
i18n_df_datasource_not_found=\u6C92\u6709\u627E\u5230\u6578\u64DA\u6E90
i18n_df_datasource_does_not_enable_data_filling=\u8A72\u6578\u64DA\u6E90\u6C92\u6709\u555F\u7528\u6578\u64DA\u586B\u5831\u914D\u7F6E
i18n_df_builtin_datasource=\u5167\u5EFA\u6578\u64DA\u5EAB
i18n_df_folder_required=\u76EE\u9304\u5FC5\u9078
i18n_df_form_not_exists=\u8868\u55AE\u4E0D\u5B58\u5728
i18n_df_name_can_not_empty=\u540D\u7A31\u4E0D\u80FD\u70BA\u7A7A
i18n_df_template=\u6A21\u677F
i18n_df_task_status_is_null_or_finished=\u4EFB\u52D9\u72C0\u614B\u70BA\u7A7A\u6216\u5DF2\u5B8C\u6210
i18n_df_task_need_task_id=\u9700\u6307\u5B9A\u4EFB\u52D9ID
i18n_df_not_current_task_user=\u4E0D\u662F\u7576\u524D\u4EFB\u52D9\u7684\u76EE\u6A19\u7528\u6236
i18n_df_miss_parameter=\u7F3A\u5931\u53C3\u6578
i18n_df_no_running_instance=\u7576\u524D\u4EFB\u52D9\u66AB\u6642\u7121\u904B\u884C\u5BE6\u4F8B
i18n_df_value=\u503C
i18n_df_format_error=\u683C\u5F0F\u89E3\u6790\u932F\u8AA4
i18n_df_cannot_earlier_than=\u4E0D\u80FD\u65E9\u4E8E
i18n_df_cannot_be_all_null=\u4E0D\u80FD\u53EA\u6709\u4E00\u500B\u70BA\u7A7A
i18n_df_value_not_in_range=\u503C\u4E0D\u5728\u8303\u570D\u5167
i18n_df_value_value_not_in_range=\u503C: %s \u4E0D\u5728\u8303\u570D\u5167
i18n_df_required=\u5FC5\u586B
i18n_df_must_unique=\u4E0D\u5141\u8A31\u91CD\u5FA9\u503C
i18n_df_excel_parsing_error=Excel\u89E3\u6790\u932F\u8AA4
i18n_df_excel_is_empty=\u8A72Excel\u6C92\u6709\u6578\u64DA
i18n_df_excel_template_column_not_fit=\u6A21\u677F\u5B57\u6BB5\u500B\u6578\u4E0D\u5339\u914D
i18n_df_selection=\u9078\u9805\u503C\u70BA
i18n_df_date_format=\u65E5\u671F\u683C\u5F0F
i18n_df_integer=\u6574\u5F62\u6578\u5B57
i18n_df_decimal=\u5C0F\u6578\u6578\u5B57
i18n_df_multiple_value_split=\u591A\u500B\u503C\u4F7F\u7528\u5206\u865F";"\u5206\u5272
i18n_df_email_type=\u90F5\u7BB1\u683C\u5F0F
i18n_df_phone_type=\u624B\u6A5F\u865F\u683C\u5F0F

View File

@ -415,5 +415,9 @@ tinymce.addI18n('zh_CN', {
"Whole words": "\u5168\u5b57\u5339\u914d", "Whole words": "\u5168\u5b57\u5339\u914d",
"Spellcheck": "\u62fc\u5199\u68c0\u67e5", "Spellcheck": "\u62fc\u5199\u68c0\u67e5",
"Caption": "\u6807\u9898", "Caption": "\u6807\u9898",
"Insert template": "\u63d2\u5165\u6a21\u677f" "Insert template": "\u63d2\u5165\u6a21\u677f",
"Cut column": "\u526a\u5207\u5217",
"Copy column": "\u590d\u5236\u5217",
"Paste column before": "\u7c98\u8d34\u5230\u524d\u65b9",
"Paste column after": "\u7c98\u8d34\u5230\u540e\u65b9"
}); });

View File

@ -0,0 +1,12 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4327_361639)">
<path d="M6 11.5C9.03757 11.5 11.5 9.03757 11.5 6C11.5 2.96243 9.03757 0.5 6 0.5C2.96243 0.5 0.5 2.96243 0.5 6C0.5 9.03757 2.96243 11.5 6 11.5Z" fill="#F54A45"/>
<path d="M5.75 3.5C5.61193 3.5 5.5 3.61193 5.5 3.75V6.75C5.5 6.88807 5.61193 7 5.75 7H6.25C6.38807 7 6.5 6.88807 6.5 6.75V3.75C6.5 3.61193 6.38807 3.5 6.25 3.5H5.75Z" fill="white"/>
<path d="M5.75 7.5C5.61193 7.5 5.5 7.61193 5.5 7.75V8.25C5.5 8.38807 5.61193 8.5 5.75 8.5H6.25C6.38807 8.5 6.5 8.38807 6.5 8.25V7.75C6.5 7.61193 6.38807 7.5 6.25 7.5H5.75Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_4327_361639">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 785 B

View File

@ -56,6 +56,7 @@ import treemapOrigin from '@/assets/svg/treemap-origin.svg'
import waterfallOrigin from '@/assets/svg/waterfall-origin.svg' import waterfallOrigin from '@/assets/svg/waterfall-origin.svg'
import wordCloudOrigin from '@/assets/svg/word-cloud-origin.svg' import wordCloudOrigin from '@/assets/svg/word-cloud-origin.svg'
import tHeatmapOrigin from '@/assets/svg/t-heatmap-origin.svg' import tHeatmapOrigin from '@/assets/svg/t-heatmap-origin.svg'
import pictureGroupOrigin from '@/assets/svg/picture-group-origin.svg'
import dvMore from '@/assets/svg/dv-more.svg' import dvMore from '@/assets/svg/dv-more.svg'
import dvExpandDown from '@/assets/svg/dv-expand-down.svg' import dvExpandDown from '@/assets/svg/dv-expand-down.svg'
import dvExpandRight from '@/assets/svg/dv-expand-right.svg' import dvExpandRight from '@/assets/svg/dv-expand-right.svg'
@ -324,6 +325,7 @@ const iconMap = {
'waterfall-origin': waterfallOrigin, 'waterfall-origin': waterfallOrigin,
'word-cloud-origin': wordCloudOrigin, 'word-cloud-origin': wordCloudOrigin,
't-heatmap-origin': tHeatmapOrigin, 't-heatmap-origin': tHeatmapOrigin,
'picture-group-origin': pictureGroupOrigin,
group: group group: group
} }
const getIconName = item => { const getIconName = item => {

View File

@ -1,11 +1,15 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, toRefs } from 'vue' import { computed, nextTick, PropType, ref, toRefs } from 'vue'
import { ElIcon } from 'element-plus-secondary' import { ElIcon, ElMessage } from 'element-plus-secondary'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain' import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
const dvMainStore = dvMainStoreWithOut() const dvMainStore = dvMainStoreWithOut()
const { canvasCollapse } = storeToRefs(dvMainStore) const { canvasCollapse } = storeToRefs(dvMainStore)
let componentNameEdit = ref(false)
let inputComponentName = ref('')
let componentNameInputAttr = ref(null)
const snapshotStore = snapshotStoreWithOut()
const props = defineProps({ const props = defineProps({
scrollWidth: { scrollWidth: {
required: false, required: false,
@ -32,18 +36,64 @@ const props = defineProps({
type: String, type: String,
default: 'defaultSide' default: 'defaultSide'
}, },
view: {
type: Object as PropType<ChartObj>,
required: false
},
slideIndex: {
type: Number,
required: false,
default: 0
},
themes: {
type: String as PropType<EditorTheme>,
default: 'light'
},
title: String title: String
}) })
const { width, asidePosition, sideName, themeInfo } = toRefs(props) const { width, asidePosition, sideName, themeInfo, view, themes } = toRefs(props)
const collapseChange = () => { const collapseChange = () => {
canvasCollapse.value[sideName.value] = !canvasCollapse.value[sideName.value] canvasCollapse.value[sideName.value] = !canvasCollapse.value[sideName.value]
} }
const widthShow = computed(() => `${canvasCollapse.value[sideName.value] ? 36 : width.value}px`) const widthShow = computed(() => `${canvasCollapse.value[sideName.value] ? 36 : width.value}px`)
const isViewTitle = computed(() => view.value && ['picture-group'].includes(view.value.type))
const slideStyle = computed(() => { const slideStyle = computed(() => {
return { '--de-scroll-width': props.scrollWidth + 'px', width: widthShow.value } return { '--de-scroll-width': props.scrollWidth + 'px', width: widthShow.value }
}) })
const closeEditComponentName = () => {
componentNameEdit.value = false
if (!inputComponentName.value || !inputComponentName.value.trim()) {
return
}
if (inputComponentName.value.trim() === view.value.title) {
return
}
if (inputComponentName.value.trim().length > 64 || inputComponentName.value.trim().length < 2) {
ElMessage.warning('名称字段长度2-64个字符')
editComponentName()
return
}
view.value.title = inputComponentName.value
inputComponentName.value = ''
}
const editComponentName = () => {
if (isViewTitle.value) {
componentNameEdit.value = true
inputComponentName.value = view.value.title
nextTick(() => {
componentNameInputAttr.value.focus()
})
}
}
const onComponentNameChange = () => {
snapshotStore.recordSnapshotCache()
}
</script> </script>
<template> <template>
@ -55,7 +105,17 @@ const slideStyle = computed(() => {
:style="slideStyle" :style="slideStyle"
> >
<el-row align="middle" :class="'title-' + themeInfo" justify="space-between"> <el-row align="middle" :class="'title-' + themeInfo" justify="space-between">
<span v-if="!canvasCollapse[sideName]">{{ title }}</span> <div
:id="'attr-slide-component-name' + slideIndex"
v-if="!canvasCollapse[sideName]"
class="name-area-attr"
style="max-width: 180px; text-overflow: ellipsis; white-space: nowrap"
:style="{ width: componentNameEdit ? '300px' : 'auto' }"
:class="{ 'component-name-dark': themeInfo === 'dark' }"
@dblclick="editComponentName"
>
{{ isViewTitle ? view.title : title }}
</div>
<el-icon <el-icon
:title="title" :title="title"
:class="['custom-icon-' + themeInfo, 'collapse-icon-' + themeInfo]" :class="['custom-icon-' + themeInfo, 'collapse-icon-' + themeInfo]"
@ -79,6 +139,16 @@ const slideStyle = computed(() => {
<div class="collapse-title" v-if="canvasCollapse[sideName]"> <div class="collapse-title" v-if="canvasCollapse[sideName]">
<span>{{ title }}</span> <span>{{ title }}</span>
</div> </div>
<Teleport v-if="componentNameEdit" :to="'#attr-slide-component-name' + slideIndex">
<input
ref="componentNameInputAttr"
v-model="inputComponentName"
width="100%"
:effect="themeInfo"
@change="onComponentNameChange"
@blur="closeEditComponentName"
/>
</Teleport>
</div> </div>
</template> </template>
@ -184,4 +254,38 @@ const slideStyle = computed(() => {
.ed-scrollbar__bar.is-vertical { .ed-scrollbar__bar.is-vertical {
width: var(--de-scroll-width); width: var(--de-scroll-width);
} }
.name-area-attr {
position: relative;
line-height: 24px;
height: 24px;
font-size: 14px !important;
overflow: hidden;
cursor: pointer;
input {
position: absolute;
left: 0;
width: 100%;
outline: none;
border: 1px solid #295acc;
border-radius: 4px;
padding: 0 4px;
height: 100%;
}
}
.component-name-dark {
input {
position: absolute;
left: 0;
width: 100%;
color: @dv-canvas-main-font-color;
background-color: #050e21;
outline: none;
border: 1px solid #295acc;
border-radius: 4px;
padding: 0 4px;
height: 100%;
}
}
</style> </style>

View File

@ -132,6 +132,7 @@ const canEdit = ref(false)
// //
const tinymceId = 'tinymce-view-' + element.value.id + '-' + suffixId.value const tinymceId = 'tinymce-view-' + element.value.id + '-' + suffixId.value
const myValue = ref('') const myValue = ref('')
const cellDragStart = ref(0)
const systemFontFamily = appearanceStore.fontList.map(item => item.name) const systemFontFamily = appearanceStore.fontList.map(item => item.name)
const curFontFamily = () => { const curFontFamily = () => {
@ -171,25 +172,107 @@ const init = ref({
branding: false, branding: false,
icons: 'vertical-content', icons: 'vertical-content',
vertical_align: element.value.propValue.verticalAlign, vertical_align: element.value.propValue.verticalAlign,
table_default_attributes: {
width: '400' // 使 table_default_attributes
},
table_default_styles: {
width: '400px' // 使 table_default_styles px
},
setup: function (editor) { setup: function (editor) {
// let cloneHandle = null //
editor.on('ObjectResizeStart', function (e) { let originalHandle = null //
const { target, width, height } = e editor.on('init', () => {
if (target.nodeName === 'TABLE') { const doc = editor.getDoc()
// // mouseupmousedown mousemove
// e.width = width / props.scale // 1. bar,
// e.height = height / props.scale // barbarbar
} // tinymce
}) doc.addEventListener('mousedown', event => {
nextTick(() => {
originalHandle = event.target.closest('.ephox-snooker-resizer-bar-dragging')
if (originalHandle) {
//
cloneHandle = originalHandle.cloneNode(true)
cloneHandle.style.zIndex = 9999 //
originalHandle.style.display = 'none' //
//
const parentDiv = originalHandle.parentNode //
parentDiv.appendChild(cloneHandle) //
}
})
})
// // mousemove
editor.on('ObjectResized', function (e) { doc.addEventListener('mousemove', event => {
const { target, width, height } = e if (cloneHandle) {
if (target.nodeName === 'TABLE') { // //
// if (cloneHandle.offsetHeight > cloneHandle.offsetWidth) {
// target.style.width = `${width * props.scale}px` //
// target.style.height = `${height scaleFactor}px` const offsetX = event.movementX * props.scale // 使
cloneHandle.style.left = `${cloneHandle.offsetLeft + offsetX}px` //
} else {
//
const offsetY = event.movementY * props.scale // 使
cloneHandle.style.top = `${cloneHandle.offsetTop + offsetY}px` //
}
}
})
// mouseup
doc.addEventListener('mouseup', event => {
if (cloneHandle) {
//
originalHandle.style.display = ''
if (cloneHandle) {
cloneHandle.parentNode.removeChild(cloneHandle) //
}
cloneHandle = null
originalHandle = null
}
})
// .mce-resizehandle
const adjustResizeHandles = (aLeft, aTop) => {
nextTick(() => {
const nodeRt = doc.getElementById('mceResizeHandlene')
const nodeRb = doc.getElementById('mceResizeHandlese')
const nodeLb = doc.getElementById('mceResizeHandlesw')
if (nodeRt) {
nodeRt.style.left = `${aLeft}px`
}
if (nodeRb) {
nodeRb.style.left = `${aLeft}px`
nodeRb.style.top = `${aTop}px`
}
if (nodeLb) {
nodeLb.style.top = `${aTop}px`
}
})
} }
// ObjectSelected
editor.on('ObjectSelected', event => {
if (event.target.nodeName === 'TABLE') {
adjustResizeHandles(
event.target.offsetWidth + event.target.offsetLeft,
event.target.offsetHeight + event.target.offsetTop
)
}
})
// ObjectResized
//
// cornerresize
editor.on('ObjectResized', function (e) {
const { target, width, height, origin } = e
if (target.nodeName === 'TABLE' && origin.indexOf('corner') > -1) {
//
target.style.width = `${width}px`
target.style.height = `${height}px`
} else if (target.nodeName === 'TABLE' && origin.indexOf('bar-col') > -1) {
// do nothing
}
})
}) })
} }
}) })
@ -634,12 +717,6 @@ defineExpose({
width: 0px !important; width: 0px !important;
height: 0px !important; height: 0px !important;
} }
::v-deep(p) {
zoom: var(--de-canvas-scale);
}
::v-deep(td span) {
zoom: var(--de-canvas-scale);
}
} }
:deep(.ol) { :deep(.ol) {

View File

@ -0,0 +1,751 @@
<template>
<div
class="rich-main-class"
:class="{ 'edit-model': canEdit }"
@keydown.stop
@keyup.stop
@dblclick="setEdit"
@click="onClick"
:style="richTextStyle"
>
<chart-error v-if="isError" :err-msg="errMsg" />
<Editor
v-if="editShow && !isError"
v-model="myValue"
class="custom-text-content"
:style="wrapperStyle"
:id="tinymceId"
:init="init"
:disabled="!canEdit || disabled"
/>
<div
class="rich-placeholder"
:class="{ 'rich-placeholder--dark': themes === 'dark' }"
v-if="showPlaceHolder"
>
{{ init.outer_placeholder }}
</div>
</div>
</template>
<script setup lang="ts">
import { formatDataEaseBi } from '@/utils/url'
import tinymce from 'tinymce/tinymce' // tinymcehidden
import Editor from '@tinymce/tinymce-vue' //
import 'tinymce/themes/silver/theme' //
import 'tinymce/icons/default' // icon
//
import 'tinymce/plugins/advlist' //
import 'tinymce/plugins/autolink' //
import 'tinymce/plugins/link' //
import 'tinymce/plugins/image' //
import 'tinymce/plugins/lists' //
import 'tinymce/plugins/charmap' //
import 'tinymce/plugins/media' //
import 'tinymce/plugins/wordcount' //
import 'tinymce/plugins/table' //
import 'tinymce/plugins/contextmenu' // contextmenu
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/pagebreak'
import '@npkg/tinymce-plugins/letterspacing'
import './plugins' //
import { computed, nextTick, reactive, ref, toRefs, watch, onMounted, PropType } from 'vue'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import eventBus from '@/utils/eventBus'
import { guid } from '@/views/visualized/data/dataset/form/util'
import { getData } from '@/api/chart'
import { storeToRefs } from 'pinia'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import ChartError from '@/views/chart/components/views/components/ChartError.vue'
import { useEmitt } from '@/hooks/web/useEmitt'
import { valueFormatter } from '@/views/chart/components/js/formatter'
import { parseJson } from '@/views/chart/components/js/util'
import { mappingColor } from '@/views/chart/components/js/panel/common/common_table'
import { CHART_FONT_FAMILY } from '@/views/chart/components/editor/util/chart'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
const snapshotStore = snapshotStoreWithOut()
const errMsg = ref('')
const dvMainStore = dvMainStoreWithOut()
const { canvasViewInfo } = storeToRefs(dvMainStore)
const isError = ref(false)
const appearanceStore = useAppearanceStoreWithOut()
const props = defineProps({
scale: {
type: Number,
required: false,
default: 1
},
element: {
type: Object
},
editMode: {
type: String,
require: false,
default: 'edit'
},
active: {
type: Boolean,
require: false,
default: false
},
//
disabled: {
type: Boolean,
default: false
},
showPosition: {
type: String,
required: false,
default: 'preview'
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
//id
suffixId: {
type: String,
required: false,
default: 'common'
}
})
const { element, editMode, active, disabled, showPosition, suffixId } = toRefs(props)
const state = reactive({
emptyValue: '-',
data: null,
viewDataInfo: null,
totalItems: 0,
firstRender: true,
previewFirstRender: true
})
const dataRowSelect = ref({})
const dataRowNameSelect = ref({})
const dataRowNameSelectSource = ref({})
const dataRowFiledName = ref([])
const initReady = ref(false)
const editShow = ref(true)
const canEdit = ref(false)
//
const tinymceId = 'tinymce-view-' + element.value.id + '-' + suffixId.value
const myValue = ref('')
const systemFontFamily = appearanceStore.fontList.map(item => item.name)
const curFontFamily = () => {
let result = ''
CHART_FONT_FAMILY.concat(
appearanceStore.fontList.map(ele => ({
name: ele.name,
value: ele.name
}))
).forEach(font => {
result = result + font.name + '=' + font.name + ';'
})
return result
}
const init = ref({
selector: '#' + tinymceId,
toolbar_items_size: 'small',
language_url: formatDataEaseBi('./tinymce-dataease-private/langs/zh_CN.js'), // publicstatic
language: 'zh_CN',
skin_url: formatDataEaseBi('./tinymce-dataease-private/skins/ui/oxide'), //
content_css: formatDataEaseBi('./tinymce-dataease-private/skins/content/default/content.css'),
plugins:
'vertical-content advlist autolink link image lists charmap media wordcount table contextmenu directionality pagebreak letterspacing', //
//
toolbar:
'undo redo | fontselect fontsizeselect |forecolor backcolor bold italic letterspacing |underline strikethrough link lineheight| formatselect |' +
'top-align center-align bottom-align | alignleft aligncenter alignright | bullist numlist |' +
' blockquote subscript superscript removeformat | table image ',
toolbar_location: '/',
font_formats: curFontFamily(),
fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 28px 32px 36px 42px 48px 56px 72px', //
menubar: false,
placeholder: '',
outer_placeholder: '双击输入文字',
inline: true, //
branding: false,
icons: 'vertical-content',
vertical_align: element.value.propValue.verticalAlign,
setup: function (editor) {
//
editor.on('ObjectResizeStart', function (e) {
const { target, width, height } = e
if (target.nodeName === 'TABLE') {
//
// e.width = width / props.scale
// e.height = height / props.scale
}
})
//
editor.on('ObjectResized', function (e) {
const { target, width, height } = e
if (target.nodeName === 'TABLE') {
//
// target.style.width = `${width * props.scale}px`
// target.style.height = `${height scaleFactor}px`
}
})
}
})
const editStatus = computed(() => {
return editMode.value === 'edit'
})
watch(
() => active.value,
val => {
if (!val) {
const ed = tinymce.editors[tinymceId]
if (canEdit.value) {
element.value.propValue.textValue = ed?.getContent()
}
element.value['editing'] = false
canEdit.value = false
reShow()
myValue.value = assignment(element.value.propValue.textValue)
ed.setContent(myValue.value)
}
}
)
watch(
() => myValue.value,
() => {
if (canEdit.value) {
const ed = tinymce.editors[tinymceId]
element.value.propValue.textValue = ed?.getContent()
}
if (initReady.value && canEdit.value) {
snapshotStore.recordSnapshotCache('renderChart', element.value.id)
initFontFamily(myValue.value)
}
}
)
const ALIGN_MAP = {
'top-align': {},
'center-align': {
margin: 'auto'
},
'bottom-align': {
'margin-top': 'auto'
}
}
const wrapperStyle = computed(() => {
const align = element.value.propValue.verticalAlign
if (!align) {
return {}
}
return ALIGN_MAP[align]
})
useEmitt({
name: 'vertical-change-' + tinymceId,
callback: align => {
element.value.propValue.verticalAlign = align
}
})
const viewInit = () => {
useEmitt({
name: 'fieldSelect-' + element.value.id,
callback: function (val) {
fieldSelect(val)
}
})
tinymce.init({})
myValue.value = assignment(element.value.propValue.textValue)
}
const initCurFieldsChange = () => {
if (!canEdit.value) {
myValue.value = assignment(element.value.propValue.textValue)
const ed = tinymce.editors[tinymceId]
ed.setContent(myValue.value)
}
}
const jumpTargetAdaptor = () => {
setTimeout(() => {
const paragraphs = document.querySelectorAll('p')
paragraphs.forEach(p => {
// p onclick event.stopPropagation
if (
p.getAttribute('onclick') &&
p.getAttribute('onclick').includes('event.stopPropagation()')
) {
return // stopPropagation
}
// onclick
p.setAttribute('onclick', 'event.stopPropagation()')
})
}, 1000)
}
const assignment = content => {
const on = content.match(/\[(.+?)\]/g)
if (on) {
const thresholdStyleInfo = conditionAdaptor(state.viewDataInfo)
on.forEach(itm => {
if (dataRowFiledName.value.includes(itm)) {
const ele = itm.slice(1, -1)
let value = dataRowNameSelect.value[ele] !== undefined ? dataRowNameSelect.value[ele] : null
let targetValue = !!value ? value : state.emptyValue
if (thresholdStyleInfo && thresholdStyleInfo[ele]) {
const thresholdStyle = thresholdStyleInfo[ele]
targetValue = `<span style="color:${thresholdStyle.color};background-color: ${thresholdStyle.backgroundColor}">${targetValue}</span>`
}
if (initReady.value) {
content = content.replace(itm, targetValue)
} else {
content = content.replace(itm, !!value ? targetValue : '[获取中...]')
}
}
})
}
content = content.replace('class="base-selected"', '')
//De
content = content.replace(/href="#\//g, 'href="/#/')
content = content.replace(/href=\\"#\//g, 'href=\\"/#/')
content = content.replace(/href=\\"#\//g, 'href=\\"/#/')
resetSelect()
initFontFamily(content)
jumpTargetAdaptor()
return content
}
const initFontFamily = htmlText => {
const regex = /font-family:\s*([^;"]+);/g
let match
while ((match = regex.exec(htmlText)) !== null) {
const font = match[1].trim()
if (systemFontFamily.includes(font)) {
appearanceStore.setCurrentFont(font)
}
}
}
const fieldSelect = field => {
const ed = tinymce.editors[tinymceId]
const fieldId = 'changeText-' + guid()
const value =
'<span id="' +
fieldId +
'"><span class="mceNonEditable" contenteditable="false" data-mce-content="[' +
field.name +
']">[' +
field.name +
']</span></span>'
const attachValue = '<span id="attachValue">&nbsp;</span>'
ed.insertContent(value)
ed.insertContent(attachValue)
snapshotStore.resetStyleChangeTimes()
}
const onClick = () => {
if (canEdit.value) {
const node = tinymce.activeEditor.selection.getNode()
resetSelect(node)
}
}
const resetSelect = (node?) => {
const edInner = tinymce.get(tinymceId)
if (edInner?.dom) {
const nodeArray = edInner.dom.select('.base-selected')
if (nodeArray) {
nodeArray.forEach(nodeInner => {
nodeInner.removeAttribute('class')
})
}
if (node) {
const pNode = node.parentElement
if (pNode && pNode.id && pNode.id.indexOf('changeText') > -1) {
const innerId = '#' + pNode.id
const domTest = edInner.dom.select(innerId)[0]
domTest.setAttribute('class', 'base-selected')
edInner.selection.select(domTest)
}
}
}
}
const computedCanEdit = computed<boolean>(() => {
return (
['canvas', 'canvasDataV', 'edit'].includes(showPosition.value) &&
editStatus.value &&
canEdit.value === false &&
!isError.value &&
!disabled.value
)
})
const showPlaceHolder = computed<boolean>(() => {
return (
computedCanEdit.value && (myValue.value == undefined || myValue.value == '') && !isError.value
)
})
const editActive = computed<boolean>(() => {
if (element.value.canvasId.includes('Group') && !active.value) {
return false
} else {
return true
}
})
const setEdit = () => {
setTimeout(() => {
if (computedCanEdit.value && editActive.value) {
canEdit.value = true
element.value['editing'] = true
myValue.value = element.value.propValue.textValue
const ed = tinymce.editors[tinymceId]
ed.setContent(myValue.value)
reShow()
}
})
}
const reShow = () => {
editShow.value = false
nextTick(() => {
editShow.value = true
editCursor()
})
}
const editCursor = () => {
setTimeout(() => {
const myDiv = document.getElementById(tinymceId)
//
const range = document.createRange()
const sel = window.getSelection()
if (myDiv.childNodes) {
range.setStart(myDiv.childNodes[myDiv.childNodes.length - 1], 1)
range.collapse(false)
sel.removeAllRanges()
sel.addRange(range)
}
//
if (myDiv.focus) {
myDiv.focus()
}
tinymce.init({
selector: tinymceId,
plugins: 'table',
setup: function (editor) {
editor.on('init', function () {
console.info('====init====')
})
}
})
}, 100)
}
const updateEmptyValue = view => {
state.emptyValue =
view?.senior?.functionCfg?.emptyDataStrategy === 'custom'
? view.senior.functionCfg.emptyDataCustomValue || ''
: '-'
}
const calcData = (view: Chart, callback) => {
isError.value = false
updateEmptyValue(view)
if (view.tableId || view['dataFrom'] === 'template') {
const v = JSON.parse(JSON.stringify(view))
getData(v)
.then(res => {
if (res.code && res.code !== 0) {
isError.value = true
errMsg.value = res.msg
} else {
state.data = res?.data
state.viewDataInfo = res
state.totalItems = res?.totalItems
const curViewInfo = canvasViewInfo.value[element.value.id]
curViewInfo['curFields'] = res.data.fields
dvMainStore.setViewDataDetails(element.value.id, res)
initReady.value = true
initCurFields(res)
}
callback?.()
nextTick(() => {
initReady.value = true
})
})
.catch(() => {
nextTick(() => {
initReady.value = true
})
callback?.()
})
} else if (!view.tableId) {
state.data = []
state.viewDataInfo = {}
state.totalItems = 0
const curViewInfo = canvasViewInfo.value[element.value.id]
if (curViewInfo) {
curViewInfo['curFields'] = []
dvMainStore.setViewDataDetails(element.value.id, state.viewDataInfo)
initReady.value = true
initCurFields(curViewInfo)
}
initReady.value = true
callback?.()
nextTick(() => {
initReady.value = true
})
} else {
nextTick(() => {
initReady.value = true
})
callback?.()
}
}
const initCurFields = chartDetails => {
dataRowFiledName.value = []
dataRowSelect.value = {}
dataRowNameSelect.value = {}
dataRowNameSelectSource.value = {} //
if (chartDetails.data && chartDetails.data.sourceFields) {
const checkAllAxisStr =
JSON.stringify(chartDetails.xAxis) +
JSON.stringify(chartDetails.xAxisExt) +
JSON.stringify(chartDetails.yAxis) +
JSON.stringify(chartDetails.yAxisExt)
chartDetails.data.sourceFields.forEach(field => {
if (checkAllAxisStr.indexOf(field.id) > -1) {
dataRowFiledName.value.push(`[${field.name}]`)
}
})
if (checkAllAxisStr.indexOf('"记录数*"') > -1) {
dataRowFiledName.value.push(`[记录数*]`)
}
// Get the corresponding relationship between id and value
const nameIdMap = chartDetails.data.fields.reduce((pre, next) => {
pre[next['dataeaseName']] = next['id']
return pre
}, {})
const sourceFieldNameIdMap = chartDetails.data.fields.reduce((pre, next) => {
pre[next['dataeaseName']] = next['name']
return pre
}, {})
const rowData = chartDetails.data.tableRow[0]
if (chartDetails.type === 'rich-text') {
let yAxis = JSON.parse(JSON.stringify(chartDetails.yAxis))
const yDataeaseNames = []
const yDataeaseNamesCfg = []
yAxis.forEach(yItem => {
yDataeaseNames.push(yItem.dataeaseName)
yDataeaseNamesCfg[yItem.dataeaseName] = yItem.formatterCfg
})
}
const valueFieldMap: Record<string, Axis> = chartDetails.yAxis.reduce((p, n) => {
p[n.dataeaseName] = n
return p
}, {})
for (const key in rowData) {
dataRowSelect.value[nameIdMap[key]] = rowData[key]
let rowDataValue = rowData[key]
const rowDataValueSource = rowData[key]
const f = valueFieldMap[key]
if (f && f.formatterCfg) {
rowDataValue = valueFormatter(rowDataValue, f.formatterCfg)
}
dataRowNameSelect.value[sourceFieldNameIdMap[key]] = rowDataValue
dataRowNameSelectSource.value[sourceFieldNameIdMap[key]] = rowDataValueSource
}
}
element.value.propValue['innerType'] = chartDetails.type
element.value.propValue['render'] = chartDetails.render
if (chartDetails.type === 'rich-text') {
nextTick(() => {
initCurFieldsChange()
eventBus.emit('initCurFields-' + element.value.id)
})
}
}
//
const renderChart = viewInfo => {
//do renderView
updateEmptyValue(viewInfo)
initCurFieldsChange()
eventBus.emit('initCurFields-' + element.value.id)
}
const conditionAdaptor = (chart: Chart) => {
if (!chart || !chart.senior) {
return
}
const { threshold } = parseJson(chart.senior)
if (!threshold.enable) {
return
}
const res = {}
const conditions = threshold.tableThreshold ?? []
if (conditions?.length > 0) {
for (let i = 0; i < conditions.length; i++) {
const field = conditions[i]
let defaultValueColor = 'none'
let defaultBgColor = 'none'
res[field.field.name] = {
color: mappingColor(
dataRowNameSelectSource.value[field.field.name],
defaultValueColor,
field,
'color'
),
backgroundColor: mappingColor(
dataRowNameSelectSource.value[field.field.name],
defaultBgColor,
field,
'backgroundColor'
)
}
}
}
return res
}
const richTextStyle = computed(() => [{ '--de-canvas-scale': props.scale }])
onMounted(() => {
viewInit()
})
defineExpose({
calcData,
renderChart
})
</script>
<style lang="less" scoped>
.rich-main-class {
display: flex;
font-size: initial;
width: 100%;
height: 100%;
overflow-y: auto !important;
position: relative;
div::-webkit-scrollbar {
width: 0px !important;
height: 0px !important;
}
}
:deep(.ol) {
display: block !important;
list-style-type: decimal;
margin-block-start: 1em !important;
margin-block-end: 1em !important;
margin-inline-start: 0px !important;
margin-inline-end: 0px !important;
padding-inline-start: 40px !important;
}
:deep(ul) {
display: block !important;
list-style-type: disc;
margin-block-start: 1em !important;
margin-block-end: 1em !important;
margin-inline-start: 0px !important;
margin-inline-end: 0px !important;
padding-inline-start: 40px !important;
}
:deep(li) {
margin-left: 20px;
display: list-item !important;
text-align: -webkit-match-parent !important;
}
:deep(.base-selected) {
background-color: #b4d7ff;
}
:deep(p) {
margin: 0px;
padding: 0px;
}
.edit-model {
cursor: text;
}
.mceNonEditable {
background: rgba(51, 112, 255, 0.4);
}
.tox-tinymce-inline {
left: var(--drawLeft);
right: var(--drawRight);
}
</style>
<style lang="less">
.tox {
border-radius: 4px !important;
border-bottom: 1px solid #ccc !important;
z-index: 1000;
}
.tox-tbtn {
height: auto !important;
}
.tox-collection__item-label {
p {
color: #1a1a1a !important;
}
h1 {
color: #1a1a1a !important;
}
h2 {
color: #1a1a1a !important;
}
h3 {
color: #1a1a1a !important;
}
h4 {
color: #1a1a1a !important;
}
h5 {
color: #1a1a1a !important;
}
h6 {
color: #1a1a1a !important;
}
pre {
color: #1a1a1a !important;
}
}
.rich-placeholder {
position: absolute;
top: 50%;
left: 50%;
color: #646a73;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
transform: translate(-50%, -50%);
&.rich-placeholder--dark {
color: #fff;
}
}
.custom-text-content {
width: 100%;
overflow-y: auto;
outline: none !important;
border: none !important;
ol {
list-style-type: decimal;
}
}
</style>

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, toRefs, PropType } from 'vue' import { computed, toRefs, PropType, CSSProperties } from 'vue'
import Chart from '@/views/chart/components/views/index.vue' import Chart from '@/views/chart/components/views/index.vue'
const props = defineProps({ const props = defineProps({
@ -63,19 +63,11 @@ const props = defineProps({
const { element, view, active, searchCount, scale } = toRefs(props) const { element, view, active, searchCount, scale } = toRefs(props)
const autoStyle = computed(() => { const autoStyle = computed(() => {
return {} if (element.value.innerType === 'rich-text') {
// if (element.value.innerType === 'rich-text') { return { zoom: scale.value }
// return { } else {
// position: 'absolute', return {}
// height: 100 / scale.value + '%!important', }
// width: 100 / scale.value + '%!important',
// left: 50 * (1 - 1 / scale.value) + '%', // 2
// top: 50 * (1 - 1 / scale.value) + '%', // 2
// transform: 'scale(' + scale.value + ') translateZ(0)'
// } as CSSProperties
// } else {
// return {}
// }
}) })
const emits = defineEmits(['onPointClick']) const emits = defineEmits(['onPointClick'])

View File

@ -441,6 +441,11 @@ const resetData = () => {
next.conditionValueS = next.defaultConditionValueS next.conditionValueS = next.defaultConditionValueS
next.conditionValueOperatorS = next.defaultConditionValueOperatorS next.conditionValueOperatorS = next.defaultConditionValueOperatorS
if (next.displayType === '22') {
next.numValueEnd = next.defaultNumValueEnd
next.numValueStart = next.defaultNumValueStart
}
if (!next.defaultValueCheck) { if (!next.defaultValueCheck) {
next.defaultValue = next.multiple || +next.displayType === 7 ? [] : undefined next.defaultValue = next.multiple || +next.displayType === 7 ? [] : undefined
} }
@ -450,6 +455,19 @@ const resetData = () => {
? [...next.defaultMapValue] ? [...next.defaultMapValue]
: next.defaultMapValue : next.defaultMapValue
} }
;(props.element.cascade || []).forEach(ele => {
ele.forEach(item => {
const comId = item.datasetId.split('--')[1]
if (next.id === comId) {
item.currentSelectValue = Array.isArray(next.selectValue)
? next.selectValue
: [next.selectValue].filter(itx => ![null, undefined].includes(itx))
useEmitt().emitter.emit(`${item.datasetId.split('--')[1]}-select`)
}
})
})
const keyList = Object.entries(next.checkedFieldsMap) const keyList = Object.entries(next.checkedFieldsMap)
.filter(ele => next.checkedFields.includes(ele[0])) .filter(ele => next.checkedFields.includes(ele[0]))
.filter(ele => !!ele[1]) .filter(ele => !!ele[1])
@ -461,6 +479,14 @@ const resetData = () => {
} }
const clearData = () => { const clearData = () => {
;(props.element.cascade || []).forEach(ele => {
ele.forEach(item => {
if (item.currentSelectValue?.length) {
useEmitt().emitter.emit(`${item.datasetId.split('--')[1]}-select`)
item.currentSelectValue = []
}
})
})
;(list.value || []).reduce((pre, next) => { ;(list.value || []).reduce((pre, next) => {
next.selectValue = next.multiple || +next.displayType === 7 ? [] : undefined next.selectValue = next.multiple || +next.displayType === 7 ? [] : undefined
if (next.optionValueSource === 1 && next.defaultMapValue?.length) { if (next.optionValueSource === 1 && next.defaultMapValue?.length) {
@ -468,6 +494,11 @@ const clearData = () => {
} }
next.conditionValueF = '' next.conditionValueF = ''
next.conditionValueS = '' next.conditionValueS = ''
if (next.displayType === '22') {
next.numValueEnd = undefined
next.numValueStart = undefined
}
const keyList = Object.entries(next.checkedFieldsMap) const keyList = Object.entries(next.checkedFieldsMap)
.filter(ele => next.checkedFields.includes(ele[0])) .filter(ele => next.checkedFields.includes(ele[0]))
.filter(ele => !!ele[1]) .filter(ele => !!ele[1])

View File

@ -32,7 +32,7 @@ const props = defineProps({
timeGranularityMultiple: { timeGranularityMultiple: {
type: Object as PropType<DatePickType>, type: Object as PropType<DatePickType>,
default: () => { default: () => {
return 'yearrange' return { type: 'yearrange' } as PropType<DatePickType>
} }
} }
}) })

View File

@ -41,7 +41,7 @@ const props = defineProps({
timeGranularityMultiple: { timeGranularityMultiple: {
type: Object as PropType<DatePickType>, type: Object as PropType<DatePickType>,
default: () => { default: () => {
return 'yearrange' return { type: 'yearrange' } as PropType<DatePickType>
} }
} }
}) })

View File

@ -1,6 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import copilot from '@/assets/svg/copilot.svg' import copilot from '@/assets/svg/copilot.svg'
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const visible = ref(true) const visible = ref(true)
const emits = defineEmits(['confirm']) const emits = defineEmits(['confirm'])
@ -21,13 +23,13 @@ onMounted(() => {
show-arrow show-arrow
> >
<div class="copilot-popper-tips-content"> <div class="copilot-popper-tips-content">
<p class="title">Copilot 对话分析</p> <p class="title">{{ t('copilot.talking_analysis') }}</p>
<p class="constant"> <p class="constant">
你好我是 Copilot 对话分析 {{ t('copilot.hello') }}
<br />点击一下开启可视化图表解答模式<br />&nbsp; <br />{{ t('copilot.click_talk') }}<br />&nbsp;
</p> </p>
<div class="bottom"> <div class="bottom">
<el-button size="middle" @click="confirm"> 我知道了 </el-button> <el-button size="middle" @click="confirm"> {{ t('copilot.know') }} </el-button>
</div> </div>
</div> </div>
<template #reference> <template #reference>

View File

@ -36,6 +36,136 @@ export default {
delete_success: 'Delete success', delete_success: 'Delete success',
no_auth_tips: 'Missing menu permissions, please contact the administrator' no_auth_tips: 'Missing menu permissions, please contact the administrator'
}, },
commons: {
date: {
permanent: 'Permanent',
one_year: 'One year',
six_months: 'Six months',
three_months: 'Three months',
of_range_1_59: 'Minutes out of range [1-59]',
of_range_1_23: 'Hours out of range [1-23]'
}
},
system: {
click_to_show: 'Click to show',
click_to_hide: 'Click to hide',
basic_settings: 'Basic settings',
and_0_seconds: '0 minutes and 0 seconds',
time_0_seconds: 'Minutes (execution time: 0 seconds)',
and_0_seconds_de: 'Hours (execution time: 0 minutes and 0 seconds)',
fonts_before_deleting: 'Please set other fonts as default fonts before deleting.',
sure_to_delete:
'After the current font is deleted, the components using the font will use the default font. Are you sure to delete?',
setting_successful: 'Setting successful',
font_management: 'Font management',
search_font_name: 'Search font name',
a_new_font: 'Create a new font',
add_font: 'Add font',
default_font: 'Default font',
system_built_in: 'System built-in',
update_time: 'Update time:',
font_file: 'Font file:',
upload_font_file: 'Upload font file',
replace_font_file: 'Replace font file',
as_default_font: 'Set as default font',
the_font_name: 'Please enter the font name',
in_ttf_format: 'Only supports uploading font files in ttf format!',
character_length_1_50: 'Character length 1-50',
upload_font_file_de: 'Please upload font file',
font_name: 'Font name',
font_file_de: 'Font file',
be_the_same: 'New and old passwords cannot be the same',
twice_are_inconsistent: 'The passwords entered twice are inconsistent',
log_in_again: 'Change successfully, please log in again',
original_password: 'Original password',
the_original_password: 'Please enter the original password',
new_password: 'New password',
the_new_password: 'Please enter the new password',
confirm_password: 'Confirm password',
the_confirmation_password: 'Please enter the confirmation password',
map_settings: 'Map settings',
engine_settings: 'Engine settings',
normal_login: 'Normal login',
to_take_effect:
'Request timeout (unit: seconds, note: save and refresh the browser to take effect)',
and_platform_docking: 'Scope includes authentication settings and platform docking',
not_enabled: 'Not enabled',
default_organization: 'Default organization',
normal_role: 'Normal role',
engine_type: 'Engine type',
on_the_left: 'Please select a region on the left',
region_code: 'Region code',
superior_region: 'Superior region',
coordinate_file: 'Coordinate file',
delete_this_node: 'Are you sure you want to delete this node?',
at_the_end:
'Country code consists of three digits, province, city, district, county, and township codes consist of two digits; non-national regions need to add 0 at the end',
non_zero_three_digit_number: 'Please enter a non-zero three-digit number',
or_11_digits: 'Please enter 9 or 11 digits',
contact_the_administrator: 'If execution fails, please contact the administrator',
upload_json_files: 'Can only upload json files',
maximum_upload_200m: 'Maximum upload 200M',
geographic_information: 'Geographic information',
superior_region_first: 'Please select the superior region first',
region_name: 'Region name',
appearance_configuration: 'Appearance configuration',
platform_display_theme: 'Platform display theme',
navigation_background_color: 'Top navigation background color',
dark_color: 'Dark color',
light_color: 'Light color',
theme_color: 'Theme color',
default_blue: 'Default (blue)',
custom_color_value: 'Custom color value',
platform_login_settings: 'Platform login settings',
page_preview: 'Page preview',
restore_default: 'Restore default',
supports_custom_settings: 'Default is DataEase login interface, supports custom settings',
replace_image: 'Replace image',
website_name: 'Website name',
web_page_tab: 'Platform name displayed on web page tab',
under_product_logo: 'Slogan under product logo',
footer: 'Footer',
footer_content: 'Footer content',
platform_settings: 'Platform settings',
top_navigation_logo: 'Top navigation logo',
not_exceeding_200kb:
'Logo displayed in top navigation menu; recommended size 134 x 34, supports JPG, PNG, size not exceeding 200KB',
help_document: 'Help document',
ai_assistant_button: 'AI assistant button',
copilot_button: 'Copilot button',
document_button: 'Document button',
about_button: 'About button',
mobile_login_settings: 'Mobile login settings',
user_login: 'User login',
in_user_name: 'Please fill in user name',
fill_in_password: 'Please fill in password',
supports_custom_settings_de:
'Default is DataEase mobile login interface, supports custom settings',
login_logo: 'Login Logo',
platform: 'The default interface is DataEase platform, which supports customized settings',
not_exceeding_200kb_de:
'Logo on the right side of the login page, recommended size 120*30, supports JPG, PNG, SVG, size not exceeding 200KB',
login_background_image: 'Login background image',
not_exceeding_5m:
'Background image on the left, recommended size 375*480 for vector graphics, recommended size 1125*1440 for bitmaps; supports JPG, PNG, SVG, size not exceeding 5M',
hidden_in_iframe: 'Hidden in Iframe',
available_to_everyone: 'Open source BI tool available to everyone',
the_website_name: 'Please enter the website name',
enter_the_slogan: 'Please enter the slogan',
the_help_document: 'Please enter the help document',
assistant: 'Please choose whether to display the AI assistant',
to_display_copilot: 'Please choose whether to display Copilot',
display_the_document: 'Please choose whether to display the document',
display_the_about: 'Please choose whether to display the about',
website_logo: 'Website Logo',
not_exceeding_200kb_de_:
'The logo displayed on the top website, recommended size 48 x 48, support JPG, PNG, SVG, size not exceeding 200KB',
not_exceeding_200kb_de_right:
'Logo on the right side of the login page, recommended size 204 x 52, support JPG, PNG, SVG, size not exceeding 200KB',
not_exceeding_5m_de:
'Background image on the left, recommended size 640 x 900 for vector graphics, recommended size 1280 x 1800 for bitmaps; support JPG, PNG, SVG, size not exceeding 5M',
tab: 'Tab'
},
components: { components: {
dashboard_style: 'Dashboard style', dashboard_style: 'Dashboard style',
overall_configuration: 'Overall configuration', overall_configuration: 'Overall configuration',
@ -333,9 +463,46 @@ export default {
pwd_format: 'Length should be 5 to 15' pwd_format: 'Length should be 5 to 15'
}, },
user: { user: {
change_password: 'Change password' change_password: 'Change password',
feishu: 'Feishu',
dingtalk: 'DingTalk',
wechat_for_business: 'WeChat for Business',
international_feishu: 'International Feishu',
user_management: 'User Management',
cannot_be_modified: 'System Administrator Status cannot be modified',
cannot_be_modified_de: 'Current User Status cannot be modified',
has_been_disabled: 'User has been disabled',
selected_user: 'Selected: {msg} user',
cannot_be_empty: 'Variable cannot be empty!',
set_variable_value: 'Please set variable value:',
be_an_integer: 'Variable value must be an integer:',
be_less_than: 'Cannot be less than:',
be_greater_than: 'Cannot be greater than:',
than_start_time: ', cannot be less than start time:',
than_end_time: ', cannot be greater than end time:',
variable: 'Variable',
variable_value: 'Variable value',
enter_a_value: 'Please enter a value',
contact_the_administrator: 'If execution fails, please contact the administrator',
data_import_successful: 'Data import successful',
imported_1_data: 'Successfully imported {msg} data',
import_1_data: ', failed to import {msg} data',
can: 'Can',
download_error_report: 'Download error report',
modify_and_re_import: ', modify and re-import',
return_to_view: 'Return to view',
continue_importing: 'Continue importing',
data_import_failed: 'Partial data import failed',
data_import_failed_de: 'Data import failed'
}, },
org: { org: {
resource_migration: 'Resource migration',
migration_type: 'Migration type',
migrate_resources_only: 'Migrate resources only',
and_authorization_related: 'Migrate resources and authorization related',
target_organization: 'Target organization',
target_directory: 'Target directory',
resource_type: 'Resource type',
org_title: 'Organization', org_title: 'Organization',
org_move: 'Migration', org_move: 'Migration',
add: 'Add', add: 'Add',
@ -349,6 +516,11 @@ export default {
admin_parent_tips: '(default root organization)' admin_parent_tips: '(default root organization)'
}, },
auth: { auth: {
permission_configuration: 'Permission configuration',
was_not_obtained: 'The resource node was not obtained',
search_name: 'Search name',
loading: 'Loading...',
on_the_left: 'Please select the node on the left',
row_column: 'Row and column permission settings', row_column: 'Row and column permission settings',
row_permission: 'Row permission rules', row_permission: 'Row permission rules',
enable_row: 'Enable row permissions', enable_row: 'Enable row permissions',
@ -435,6 +607,42 @@ export default {
new_folder: 'New Folder', new_folder: 'New Folder',
form_manage: 'Form Manage', form_manage: 'Form Manage',
my_job: 'My Job', my_job: 'My Job',
disable_data_fill_hint:
'After closing Data Filling, the form data will fail to submit. Are you sure you want to close?',
enable_data_fill_hint:
'After enabled, it allows for the creation of new tables in the data source database, and stores form data into these tables.',
todo: 'Todo',
finished: 'Finished',
expired: 'Expired',
required_select: 'Required',
condition: 'Condition',
add_condition: 'Add condition',
disable_edit: 'Non-editable',
enable_edit: 'Editable',
select_component: 'Select component',
set_condition: 'Set condition',
move_to: 'Move to',
rename: 'Rename',
delete: 'Delete',
move_success: 'Move success',
rename_success: 'Rename success',
create_success: 'Create success',
create_form: 'Create Form',
create_folder: 'Create Folder',
order_by_create_time_asc: 'Order by create time asc',
order_by_create_time_desc: 'Order by create time desc',
order_by_name_asc: 'Order by name asc',
order_by_name_desc: 'Order by name desc',
delete_folder_hint:
'After deletion, all resources under this folder will be removed. Please proceed with caution.',
confirm_delete_folder: 'Confirm to delete folder',
confirm_delete_form: 'Confirm to delete form',
confirm_delete_multiple_data: 'Confirm to delete {0} data',
confirm_delete_data: 'Confirm to delete data',
no_form: 'No form',
on_the_left: 'Select form on the left side',
exporting: 'During exporting, you can goto ',
progress_to_download: 'to see progress, and download',
form: { form: {
mobile_number_format_is_incorrect: 'Incorrect format of mobile phone number', mobile_number_format_is_incorrect: 'Incorrect format of mobile phone number',
email_format_is_incorrect: 'The mailbox format is incorrect', email_format_is_incorrect: 'The mailbox format is incorrect',
@ -545,6 +753,8 @@ export default {
datetime: 'Datetime' datetime: 'Datetime'
}, },
data: { data: {
data_not_exists: 'Data dose not exists',
cannot_select_all: 'Cannot select all',
commit_time: 'Commit Time', commit_time: 'Commit Time',
confirm_delete: 'Confirm delete?', confirm_delete: 'Confirm delete?',
add_data: 'Add Data', add_data: 'Add Data',
@ -562,6 +772,23 @@ export default {
data_not_found: '] Not Found' data_not_found: '] Not Found'
}, },
task: { task: {
time_check_5_minute_later_than_current:
'Cannot be earlier than 5 minutes after the current time.',
time_check_later_than_current: 'Cannot be earlier than the current time.',
time_check_earlier_than_end: 'Cannot be later than the end time.',
time_check_later_than_start: 'Cannot be earlier than the start time.',
confirm_exit_without_save:
'The current changes have not been saved. Are you sure you want to exit?',
deliver_now: 'Deliver now',
deliver_scheduled: 'Deliver by schedule',
logic_filter: 'Logic filter',
enum_filter: 'Enum filter',
cannot_be_all_disabled: 'Not all components can be disabled.',
template_hint_title: 'The settings instructions are as follows',
template_hint_1:
'When a component is set to be non-editable, users are not allowed to modify it when filling out the form.',
template_hint_2:
'When a component is set to be editable, users are allowed to modify it when filling out the form.',
finish_rate_hint: 'Finished Rate = Finished Task Count / Assigned Task Count * 100%', finish_rate_hint: 'Finished Rate = Finished Task Count / Assigned Task Count * 100%',
distribute_frequency: 'Distribute Frequency', distribute_frequency: 'Distribute Frequency',
one_time: 'One Time', one_time: 'One Time',

File diff suppressed because it is too large Load Diff

View File

@ -391,7 +391,122 @@ export default {
system: { system: {
user: '用户', user: '用户',
role: '角色', role: '角色',
addUser: '@:common.add@:system.user' addUser: '@:common.add@:system.user',
click_to_show: '点击显示',
click_to_hide: '点击隐藏',
basic_settings: '基础设置',
and_0_seconds: '0分0秒',
time_0_seconds: '分钟执行时间0秒',
and_0_seconds_de: '小时执行时间0分0秒',
fonts_before_deleting: '请先将其他字体设置为默认字体再进行删除',
sure_to_delete: '当前字体被删除后使用该字体的组件将会使用默认字体确定删除?',
setting_successful: '设置成功',
font_management: '字体管理',
search_font_name: '搜索字体名称',
a_new_font: '新建字体',
add_font: '添加字体',
default_font: '默认字体',
system_built_in: '系统内置',
update_time: '更新时间',
font_file: '字库文件',
upload_font_file: '上传字库文件',
replace_font_file: '替换字库文件',
as_default_font: '设为默认字体',
the_font_name: '请输入字体名称',
in_ttf_format: '只支持上传ttf格式的字体文件!',
character_length_1_50: '字符长度1-50',
upload_font_file_de: '请上传字库文件',
font_name: '字体名称',
font_file_de: '字库文件',
be_the_same: '新旧密码不能相同',
twice_are_inconsistent: '两次输入的密码不一致',
log_in_again: '修改成功请重新登录',
original_password: '原始密码',
the_original_password: '请输入原始密码',
new_password: '新密码',
the_new_password: '请输入新密码',
confirm_password: '确认密码',
the_confirmation_password: '请输入确认密码',
map_settings: '地图设置',
engine_settings: '引擎设置',
normal_login: '普通登录',
to_take_effect: '请求超时时间(单位注意保存后刷新浏览器生效)',
and_platform_docking: '作用域包括认证设置和平台对接',
not_enabled: '未开启',
default_organization: '默认组织',
normal_role: '普通角色',
engine_type: '引擎类型',
on_the_left: '请在左侧选择区域',
region_code: '区域代码',
superior_region: '上级区域',
coordinate_file: '坐标文件',
delete_this_node: '确定删除此节点吗',
at_the_end:
'国家代码由三位数字组成区县乡镇代码由两位数字组成非国家区域需要再后面补0',
non_zero_three_digit_number: '请输入非0的三位数字',
or_11_digits: '请输入9或11位数字',
contact_the_administrator: '执行失败请联系管理员',
upload_json_files: '只能上传json文件',
maximum_upload_200m: '最大上传200M',
geographic_information: '地理信息',
superior_region_first: '请先选择上级区域',
region_name: '区域名称',
appearance_configuration: '外观配置',
platform_display_theme: '平台显示主题',
navigation_background_color: '顶部导航背景色',
dark_color: '暗色',
light_color: '浅色',
theme_color: '主题色',
default_blue: '默认 (蓝色) ',
custom_color_value: '自定义色值',
platform_login_settings: '平台登录设置',
page_preview: '页面预览',
restore_default: '恢复默认',
platform: '默认为 {msg} 平台界面支持自定义设置',
supports_custom_settings: '默认为 {msg} 登录界面支持自定义设置',
replace_image: '替换图片',
website_name: '网站名称',
web_page_tab: '显示在网页 Tab 的平台名称',
under_product_logo: '产品 Logo 下的 Slogan',
footer: '页脚',
footer_content: '页脚内容',
platform_settings: '平台设置',
top_navigation_logo: '顶部导航 Logo',
not_exceeding_200kb:
'顶部导航菜单显示的 Logo建议尺寸 134 x 34支持JPGPNG大小不超过 200KB',
help_document: '帮助文档',
ai_assistant_button: 'AI 助手按钮',
copilot_button: 'Copilot 按钮',
document_button: '文档按钮',
about_button: '关于按钮',
mobile_login_settings: '移动端登录设置',
user_login: '用户登录',
in_user_name: '请填写用户名',
fill_in_password: '请填写密码',
supports_custom_settings_de: '默认为 {msg} 移动端登录界面支持自定义设置',
login_logo: '登录 Logo',
not_exceeding_200kb_de:
'登录页面右侧 Logo建议尺寸 120*30支持 JPGPNGSVG大小不超过 200KB',
login_background_image: '登录背景图',
not_exceeding_5m:
'左侧背景图矢量图建议尺寸375*480位图建议尺寸1125*1440支持 JPGPNGSVG大小不超过5M',
hidden_in_iframe: 'Iframe中隐藏',
available_to_everyone: '人人可用的开源 BI 工具',
the_website_name: '请输入网站名称',
enter_the_slogan: '请输入Slogan',
the_help_document: '请输入帮助文档',
assistant: '请选择是否展示AI助手',
to_display_copilot: '请选择是否展示Copilot',
display_the_document: '请选择是否展示文档',
display_the_about: '请选择是否展示关于',
website_logo: '网站 Logo',
not_exceeding_200kb_de_:
'顶部网站显示的 Logo建议尺寸 48 x 48支持 JPGPNGSVG大小不超过 200KB',
not_exceeding_200kb_de_right:
'登录页面右侧 Logo建议尺寸 204 x 52支持 JPGPNGSVG大小不超过 200KB',
not_exceeding_5m_de:
'左侧背景图矢量图建议尺寸 640 x 900位图建议尺寸 1280 x 1800支持 JPGPNGSVG大小不超过 5M',
tab: '页签'
}, },
components: { components: {
dashboard_style: '仪表板风格', dashboard_style: '仪表板风格',
@ -455,7 +570,37 @@ export default {
phone_format: '请填写正确格式手机号', phone_format: '请填写正确格式手机号',
email_format_is_incorrect: '请填写正确格式邮箱', email_format_is_incorrect: '请填写正确格式邮箱',
enable_success: '已启用', enable_success: '已启用',
disable_success: '已禁用' disable_success: '已禁用',
feishu: '飞书',
dingtalk: '钉钉',
wechat_for_business: '企业微信',
international_feishu: '国际飞书',
user_management: '用户管理',
cannot_be_modified: '系统管理员状态不可修改',
cannot_be_modified_de: '不能修改当前用户状态',
has_been_disabled: '用户已被禁用',
selected_user: '已选: {msg} 个用户',
cannot_be_empty: '变量不能为空',
set_variable_value: '请设置变量值',
be_an_integer: '变量值必须为整数',
be_less_than: '不能小于',
be_greater_than: '不能大于',
than_start_time: '不能小于开始时间',
than_end_time: '不能大于结束时间',
variable: '变量',
variable_value: '变量值',
enter_a_value: '请输入数值',
contact_the_administrator: '执行失败请联系管理员',
data_import_successful: '导入数据成功',
imported_1_data: '成功导入数据 {msg} ',
import_1_data: '导入失败 {msg} ',
can: '可',
download_error_report: '下载错误报告',
modify_and_re_import: '修改后重新导入',
return_to_view: '返回查看',
continue_importing: '继续导入',
data_import_failed: '部分数据导入失败',
data_import_failed_de: '数据导入失败'
}, },
userimport: { userimport: {
buttonText: '批量导入', buttonText: '批量导入',
@ -516,6 +661,15 @@ export default {
empty_description: '请先选择左侧角色' empty_description: '请先选择左侧角色'
}, },
org: { org: {
resource_migration: '资源迁移',
migration_type: '迁移类型',
migrate_resources_only: '仅迁移资源',
and_authorization_related: '迁移资源及授权相关',
target_organization: '目标组织',
target_directory: '目标目录',
resource_type: '资源类型',
user_dimension: '按用户配置',
resource_dimension: '按资源配置',
org_title: '组织管理', org_title: '组织管理',
org_move: '组织迁移', org_move: '组织迁移',
add: '添加组织', add: '添加组织',
@ -537,6 +691,11 @@ export default {
please_login_per_changed: '当前用户权限已变更请重新登录' please_login_per_changed: '当前用户权限已变更请重新登录'
}, },
auth: { auth: {
permission_configuration: '权限配置',
was_not_obtained: '未获取到资源节点',
search_name: '搜索名称',
loading: '加载中···',
on_the_left: '请选择左侧节点',
sysParams_type: { sysParams_type: {
user_id: '账号', user_id: '账号',
user_name: '姓名', user_name: '姓名',
@ -546,8 +705,6 @@ export default {
dept: '组织', dept: '组织',
role: '角色' role: '角色'
}, },
user_dimension: '按用户配置',
resource_dimension: '按资源配置',
user: '用户', user: '用户',
role: '角色', role: '角色',
resource: '资源权限', resource: '资源权限',
@ -2142,7 +2299,13 @@ export default {
data_time_error: '开始日期不能大于结束日期', data_time_error: '开始日期不能大于结束日期',
one_day: '一天', one_day: '一天',
one_week: '一周', one_week: '一周',
one_month: '一个月' one_month: '一个月',
permanent: '永久',
one_year: '一年',
six_months: '半年',
three_months: '三个月',
of_range_1_59: '分钟超出范围1-59',
of_range_1_23: '小时超出范围1-23'
}, },
adv_search: { adv_search: {
title: '高级搜索', title: '高级搜索',
@ -2776,6 +2939,39 @@ export default {
new_folder: '新建文件夹', new_folder: '新建文件夹',
form_manage: '表单管理', form_manage: '表单管理',
my_job: '我的填报', my_job: '我的填报',
disable_data_fill_hint: '关闭数据填报后表单数据将提交失败确定关闭',
enable_data_fill_hint: '启用后允许在数据源数据库中新建表并将表单数据存放至表中',
todo: '待填报',
finished: '已填报',
expired: '已过期',
required_select: '必选',
condition: '过滤值',
add_condition: '添加条件',
disable_edit: '禁止编辑',
enable_edit: '允许编辑',
select_component: '请选择组件',
set_condition: '设置条件',
move_to: '移动到',
rename: '重命名',
delete: '删除',
move_success: '移动成功',
rename_success: '重命名成功',
create_success: '新建成功',
create_form: '新建表单',
create_folder: '新建文件夹',
order_by_create_time_asc: '按创建时间升序',
order_by_create_time_desc: '按创建时间降序',
order_by_name_asc: '按照名称升序',
order_by_name_desc: '按照名称降序',
delete_folder_hint: '删除后此文件夹下的所有资源都会被删除请谨慎操作',
confirm_delete_folder: '确定删除该文件夹吗',
confirm_delete_form: '确定删除该表单吗',
confirm_delete_multiple_data: '确定删除 {0} 条数据吗',
confirm_delete_data: '确定删除数据',
no_form: '暂无表单',
on_the_left: '请在左侧选择表单',
exporting: '后台导出中,可前往',
progress_to_download: '查看进度进行下载',
form: { form: {
mobile_number_format_is_incorrect: '手机号码格式不正确', mobile_number_format_is_incorrect: '手机号码格式不正确',
email_format_is_incorrect: '邮箱格式不正确', email_format_is_incorrect: '邮箱格式不正确',
@ -2885,6 +3081,8 @@ export default {
datetime: '日期' datetime: '日期'
}, },
data: { data: {
data_not_exists: '数据不存在',
cannot_select_all: '不能全选',
commit_time: '提交时间', commit_time: '提交时间',
confirm_delete: '确认删除?', confirm_delete: '确认删除?',
add_data: '添加数据', add_data: '添加数据',
@ -2902,6 +3100,19 @@ export default {
data_not_found: ']的数据不存在' data_not_found: ']的数据不存在'
}, },
task: { task: {
time_check_5_minute_later_than_current: '不能小于当前时间5分钟后',
time_check_later_than_current: '不能小于当前时间',
time_check_earlier_than_end: '不能大于结束时间',
time_check_later_than_start: '不能小于开始时间',
confirm_exit_without_save: '当前的更改尚未保存,确定退出吗?',
deliver_now: '立即下发',
deliver_scheduled: '定时下发',
logic_filter: '条件筛选',
enum_filter: '枚举筛选',
cannot_be_all_disabled: '所有组件不能全为禁止',
template_hint_title: '设置说明如下',
template_hint_1: '当组件被设置为禁止编辑时用户填写表单时不允许修改',
template_hint_2: '当组件被设置为允许编辑时用户填写表单时允许修改',
finish_rate_hint: '填报完成率=已填报数据条数/下发填报条数*100%', finish_rate_hint: '填报完成率=已填报数据条数/下发填报条数*100%',
distribute_frequency: '发送频率', distribute_frequency: '发送频率',
one_time: '仅下发一次', one_time: '仅下发一次',
@ -2976,5 +3187,53 @@ export default {
module_name: '阈值告警', module_name: '阈值告警',
setting: '阈值告警设置', setting: '阈值告警设置',
no_view_tip: '请在设置阈值告警前先保存' no_view_tip: '请在设置阈值告警前先保存'
},
relation: {
no_permission: '没有查看权限',
datasource: '数据源',
dataset: '数据集',
dashboard: '仪表板',
dataV: '数据大屏',
analysis: '血缘分析',
resource_type: '资源类型',
pls_choose: '请选择',
choose_resource: '选择资源',
list_chart: '列表视图',
mind_map: '脑图',
index: '序号',
datasource_name: '数据源名称',
dataset_name: '数据源集名称',
dashboard_name: '仪表板名称',
dataV_name: '数据大屏名称',
retract: '收起',
expand: '展开',
node_info: '节点详情',
node_name: '节点名称',
creator: '创建人',
last_update_time: '最近更新时间',
dependent: '资源依赖',
new_page: '新页面打开'
},
copilot: {
talking_analysis: 'Copilot 对话分析',
hello: '你好我是 Copilot 对话分析',
click_talk: '点击一下开启可视化图表解答模式',
know: '我知道了',
ds_prefix: '当前数据集为',
ds_suffix: '切换数据集将清空当前会话',
confirm: '确定要切换数据集吗',
choose_dataset: '选择数据集',
pls_choose_dataset: '请选择数据集',
chart: '图表',
line: '折线图',
bar: '柱状图',
pie: '饼图',
sorry: '抱歉根据已知信息无法回答这个问题请重新描述你的问题或提供更多信息',
hello1: '您好我是 Copilot很高兴为你服务',
answer: '回答中',
example: '您可以问我: 2020年各个销售部门销售额占比的饼图',
switch_chart: '切换图表类型',
switch_table: '切换至明细表',
download: '下载'
} }
} }

View File

@ -272,6 +272,10 @@ declare interface ChartMiscStyle {
axisLabel: SplitAxisLabel axisLabel: SplitAxisLabel
splitLine: SplitAxisLine splitLine: SplitAxisLine
splitArea: SplitSplitArea splitArea: SplitSplitArea
/**
* 轴值设置
*/
axisValue: AxisValue
} }
declare interface SplitLineStyle { declare interface SplitLineStyle {
color: string color: string

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onBeforeMount, reactive, inject } from 'vue' import { ref, onBeforeMount, reactive, inject, nextTick } from 'vue'
import { initCanvasData } from '@/utils/canvasUtils' import { initCanvasData, onInitReady } from '@/utils/canvasUtils'
import { interactiveStoreWithOut } from '@/store/modules/interactive' import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { useEmbedded } from '@/store/modules/embedded' import { useEmbedded } from '@/store/modules/embedded'
import { check } from '@/utils/CrossPermission' import { check } from '@/utils/CrossPermission'
@ -125,6 +125,9 @@ onBeforeMount(async () => {
} }
return false return false
}) })
nextTick(() => {
onInitReady({ resourceId: chartId })
})
} }
) )
}) })

View File

@ -782,6 +782,20 @@ export function findComponentById(componentId) {
return result return result
} }
export function onInitReady(params) {
try {
console.info('Canvas initReady')
const targetPm = {
type: 'dataease-embedded-interactive',
eventName: 'canvas_init_ready',
args: params
}
window.parent.postMessage(targetPm, '*')
} catch (e) {
console.warn('de_inner_params send error')
}
}
export function mobileViewStyleSwitch(component) { export function mobileViewStyleSwitch(component) {
if (component) { if (component) {
const viewInfo = canvasViewInfo.value[component.id] const viewInfo = canvasViewInfo.value[component.id]

View File

@ -3,7 +3,7 @@ import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
import { computed, onMounted, reactive, watch } from 'vue' import { computed, onMounted, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { DEFAULT_MISC } from '@/views/chart/components/editor/util/chart' import { DEFAULT_MISC } from '@/views/chart/components/editor/util/chart'
import { ElMessage, ElRow } from 'element-plus-secondary' import { ElRow } from 'element-plus-secondary'
import { fieldType } from '@/utils/attr' import { fieldType } from '@/utils/attr'
import { cloneDeep, defaultsDeep } from 'lodash-es' import { cloneDeep, defaultsDeep } from 'lodash-es'
import { useEmitt } from '@/hooks/web/useEmitt' import { useEmitt } from '@/hooks/web/useEmitt'
@ -25,6 +25,14 @@ useEmitt({
name: 'word-cloud-default-data-range', name: 'word-cloud-default-data-range',
callback: args => wordCloudDefaultDataRange(args) callback: args => wordCloudDefaultDataRange(args)
}) })
useEmitt({
name: 'gauge-default-data',
callback: args => gaugeDefaultDataRange(args)
})
useEmitt({
name: 'liquid-default-data',
callback: args => gaugeDefaultDataRange(args)
})
const emit = defineEmits(['onMiscChange']) const emit = defineEmits(['onMiscChange'])
watch( watch(
@ -59,7 +67,10 @@ const state = reactive({
minField: {}, minField: {},
maxField: {}, maxField: {},
liquidMaxField: {}, liquidMaxField: {},
quotaData: [] quotaData: [],
// y
liquidProcessedNoYAxis: false,
gaugeProcessedNoYAxis: false
}) })
const liquidShapeOptions = [ const liquidShapeOptions = [
@ -71,9 +82,6 @@ const liquidShapeOptions = [
] ]
const changeMisc = (prop = '', refresh = false) => { const changeMisc = (prop = '', refresh = false) => {
if (state.miscForm.gaugeMax <= state.miscForm.gaugeMin) {
ElMessage.error(t('chart.max_more_than_mix'))
}
emit('onMiscChange', { data: state.miscForm, requestData: refresh }, prop) emit('onMiscChange', { data: state.miscForm, requestData: refresh }, prop)
} }
@ -93,13 +101,93 @@ const initField = () => {
if (state.miscForm.liquidMaxField.id) { if (state.miscForm.liquidMaxField.id) {
state.liquidMaxField = getQuotaField(state.miscForm.liquidMaxField.id) state.liquidMaxField = getQuotaField(state.miscForm.liquidMaxField.id)
} }
initDynamicDefaultField()
}
const initDynamicDefaultField = () => {
if (state.quotaData.length > 0) {
// quotaData chart.yAxis[0].id
const yAxisId = props.chart.yAxis?.[0]?.id
const yAxisExists = state.quotaData.find(ele => ele.id === yAxisId)
//
if (!yAxisExists && (state.miscForm.liquidMaxField.id || state.miscForm.gaugeMaxField.id)) {
if (props.chart.type === 'liquid' && !state.liquidProcessedNoYAxis) {
state.liquidProcessedNoYAxis = true
state.miscForm.liquidMaxField.id = ''
state.miscForm.liquidMaxField.summary = ''
state.liquidMaxField = getQuotaField(state.miscForm.liquidMaxField.id)
changeMisc('liquidMaxField', false)
} else {
if (!state.gaugeProcessedNoYAxis) {
state.gaugeProcessedNoYAxis = true
state.miscForm.gaugeMaxField.id = ''
state.miscForm.gaugeMaxField.summary = ''
state.maxField = {}
changeMisc('gaugeMaxField', false)
}
}
} else {
if (props.chart.type === 'liquid') {
if (state.miscForm.liquidMaxType === 'dynamic') {
state.miscForm.liquidMax = undefined
// quotaData liquidMaxField.id
const liquidMaxFieldExists = state.quotaData.find(
ele => ele.id === state.miscForm.liquidMaxField.id
)
if (!liquidMaxFieldExists) {
if (yAxisId) {
state.liquidProcessedNoYAxis = false
// liquidMaxField.id
state.miscForm.liquidMaxField.id = yAxisExists ? yAxisId : state.quotaData[0]?.id
// summary maxField
state.miscForm.liquidMaxField.summary = 'sum'
state.maxField = getQuotaField(state.miscForm.liquidMaxField.id)
// changeMisc
if (yAxisExists) {
changeMisc('liquidMaxField', true)
}
}
}
}
if (!state.miscForm.liquidMax && state.miscForm.liquidMaxType === 'fix') {
state.miscForm.liquidMax = cloneDeep(defaultMaxValue.liquidMax)
}
} else {
if (state.miscForm.gaugeMaxType === 'dynamic') {
state.miscForm.gaugeMax = undefined
// quotaData gaugeMaxField.id
const gaugeMaxFieldExists = state.quotaData.find(
ele => ele.id === state.miscForm.gaugeMaxField.id
)
if (!gaugeMaxFieldExists) {
if (yAxisId) {
state.gaugeProcessedNoYAxis = false
// gaugeMaxField.id
state.miscForm.gaugeMaxField.id = yAxisExists ? yAxisId : state.quotaData[0]?.id
// summary maxField
state.miscForm.gaugeMaxField.summary = 'sum'
state.maxField = getQuotaField(state.miscForm.gaugeMaxField.id)
if (yAxisExists) {
// changeMisc
changeMisc('gaugeMaxField', true)
}
}
}
}
if (!state.miscForm.gaugeMax && state.miscForm.gaugeMaxType === 'fix') {
state.miscForm.gaugeMax = cloneDeep(defaultMaxValue.gaugeMax)
}
}
}
}
} }
const changeQuotaField = (type: string, resetSummary?: boolean) => { const changeQuotaField = (type: string, resetSummary?: boolean) => {
if (type === 'min') { if (type === 'min') {
if (state.miscForm.gaugeMinType === 'dynamic') { if (state.miscForm.gaugeMinType === 'dynamic') {
if (!state.miscForm.gaugeMinField.id) { if (!state.miscForm.gaugeMinField.id) {
state.miscForm.gaugeMinField.id = state.quotaData[0]?.id state.miscForm.gaugeMinField.id = props.chart.yAxis?.[0]?.id
} }
if (!state.miscForm.gaugeMinField.summary) { if (!state.miscForm.gaugeMinField.summary) {
state.miscForm.gaugeMinField.summary = 'count' state.miscForm.gaugeMinField.summary = 'count'
@ -122,14 +210,21 @@ const changeQuotaField = (type: string, resetSummary?: boolean) => {
} }
} else if (type === 'max') { } else if (type === 'max') {
if (props.chart.type === 'liquid') { if (props.chart.type === 'liquid') {
if (state.miscForm.liquidMaxType === 'dynamic') {
state.miscForm.liquidMax = undefined
} else {
if (!state.miscForm.liquidMax) {
state.miscForm.liquidMax = cloneDeep(defaultMaxValue.liquidMax)
}
}
if (!state.miscForm.liquidMaxField.id) { if (!state.miscForm.liquidMaxField.id) {
state.miscForm.liquidMaxField.id = state.quotaData[0]?.id state.miscForm.liquidMaxField.id = props.chart.yAxis?.[0]?.id
} }
if (!state.miscForm.liquidMaxField.summary) { if (!state.miscForm.liquidMaxField.summary) {
state.miscForm.liquidMaxField.summary = 'count' state.miscForm.liquidMaxField.summary = 'sum'
} }
if (resetSummary) { if (resetSummary) {
state.miscForm.liquidMaxField.summary = 'count' state.miscForm.liquidMaxField.summary = 'sum'
} }
if (state.miscForm.liquidMaxField.id && state.miscForm.liquidMaxField.summary) { if (state.miscForm.liquidMaxField.id && state.miscForm.liquidMaxField.summary) {
state.maxField = getQuotaField(state.miscForm.liquidMaxField.id) state.maxField = getQuotaField(state.miscForm.liquidMaxField.id)
@ -137,20 +232,24 @@ const changeQuotaField = (type: string, resetSummary?: boolean) => {
} }
} else { } else {
if (state.miscForm.gaugeMaxType === 'dynamic') { if (state.miscForm.gaugeMaxType === 'dynamic') {
state.miscForm.gaugeMax = undefined
if (!state.miscForm.gaugeMaxField.id) { if (!state.miscForm.gaugeMaxField.id) {
state.miscForm.gaugeMaxField.id = state.quotaData[0]?.id state.miscForm.gaugeMaxField.id = props.chart.yAxis?.[0]?.id
} }
if (!state.miscForm.gaugeMaxField.summary) { if (!state.miscForm.gaugeMaxField.summary) {
state.miscForm.gaugeMaxField.summary = 'count' state.miscForm.gaugeMaxField.summary = 'sum'
} }
if (resetSummary) { if (resetSummary) {
state.miscForm.gaugeMaxField.summary = 'count' state.miscForm.gaugeMaxField.summary = 'sum'
} }
if (state.miscForm.gaugeMaxField.id && state.miscForm.gaugeMaxField.summary) { if (state.miscForm.gaugeMaxField.id && state.miscForm.gaugeMaxField.summary) {
state.maxField = getQuotaField(state.miscForm.gaugeMaxField.id) state.maxField = getQuotaField(state.miscForm.gaugeMaxField.id)
changeMisc('gaugeMaxField', true) changeMisc('gaugeMaxField', true)
} }
} else { } else {
if (!state.miscForm.gaugeMax) {
state.miscForm.gaugeMax = cloneDeep(defaultMaxValue.gaugeMax)
}
if (state.miscForm.gaugeMinType === 'dynamic') { if (state.miscForm.gaugeMinType === 'dynamic') {
if (state.miscForm.gaugeMinField.id && state.miscForm.gaugeMinField.summary) { if (state.miscForm.gaugeMinField.id && state.miscForm.gaugeMinField.summary) {
changeMisc('gaugeMaxField', true) changeMisc('gaugeMaxField', true)
@ -187,6 +286,39 @@ const wordCloudDefaultDataRange = args => {
state.miscForm.wordCloudAxisValueRange.min = args.data.min state.miscForm.wordCloudAxisValueRange.min = args.data.min
state.miscForm.wordCloudAxisValueRange.fieldId = props.chart.yAxis?.[0]?.id state.miscForm.wordCloudAxisValueRange.fieldId = props.chart.yAxis?.[0]?.id
} }
const defaultMaxValue = {
gaugeMax: undefined,
liquidMax: undefined
}
const gaugeDefaultDataRange = args => {
if (args.data.type === 'gauge') {
defaultMaxValue.gaugeMax = cloneDeep(args.data.max)
if (!state.miscForm.gaugeMax) {
state.miscForm.gaugeMax = cloneDeep(defaultMaxValue.gaugeMax)
}
}
if (args.data.type === 'liquid') {
defaultMaxValue.liquidMax = cloneDeep(args.data.max)
if (!state.miscForm.liquidMax) {
state.miscForm.liquidMax = cloneDeep(defaultMaxValue.liquidMax)
}
}
}
/**
* 校验最大值的输入
*/
const changeMaxValidate = prop => {
if (prop === 'gaugeMax') {
if (!state.miscForm.gaugeMax) {
state.miscForm.gaugeMax = cloneDeep(defaultMaxValue.gaugeMax)
}
} else {
if (!state.miscForm.liquidMax) {
state.miscForm.liquidMax = cloneDeep(defaultMaxValue.liquidMax)
}
}
changeMisc(prop)
}
onMounted(() => { onMounted(() => {
initField() initField()
init() init()
@ -357,7 +489,7 @@ onMounted(() => {
v-model="state.miscForm.gaugeMax" v-model="state.miscForm.gaugeMax"
size="small" size="small"
controls-position="right" controls-position="right"
@change="changeMisc('gaugeMax')" @blur="changeMaxValidate('gaugeMax')"
/> />
</el-form-item> </el-form-item>
<el-row <el-row
@ -505,7 +637,7 @@ onMounted(() => {
:min="1" :min="1"
size="small" size="small"
controls-position="right" controls-position="right"
@change="changeMisc('liquidMax')" @blur="changeMaxValidate('liquidMax')"
/> />
</el-form-item> </el-form-item>

View File

@ -2,6 +2,7 @@
import { computed, onMounted, reactive, watch } from 'vue' import { computed, onMounted, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL, DEFAULT_MISC_STYLE } from '@/views/chart/components/editor/util/chart' import { COLOR_PANEL, DEFAULT_MISC_STYLE } from '@/views/chart/components/editor/util/chart'
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
const { t } = useI18n() const { t } = useI18n()
@ -14,6 +15,10 @@ const props = withDefaults(
{ themes: 'dark' } { themes: 'dark' }
) )
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const predefineColors = COLOR_PANEL const predefineColors = COLOR_PANEL
const state = reactive({ const state = reactive({
@ -57,6 +62,9 @@ const init = () => {
} }
if (customStyle.xAxis) { if (customStyle.xAxis) {
state.miscForm = customStyle.misc state.miscForm = customStyle.misc
if (!state.miscForm.axisValue) {
state.miscForm.axisValue = JSON.parse(JSON.stringify(DEFAULT_MISC_STYLE)).axisValue
}
} }
} }
} }
@ -138,6 +146,89 @@ onMounted(() => {
@change="changeMiscStyle('axisColor')" @change="changeMiscStyle('axisColor')"
/> />
</el-form-item> </el-form-item>
<template v-if="showProperty('axisValue')">
<div
style="display: flex; flex-direction: row; justify-content: space-between; padding: 8px 0"
>
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">
{{ t('chart.axis_value') }}
<el-tooltip class="item" :effect="toolTip" placement="top">
<template #content><span v-html="t('chart.axis_tip')"></span></template>
<span style="vertical-align: middle">
<el-icon style="cursor: pointer">
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
</el-icon>
</span>
</el-tooltip>
</label>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="props.themes"
v-model="state.miscForm.axisValue.auto"
@change="changeMiscStyle('axisValue.auto')"
>
{{ t('chart.axis_auto') }}
</el-checkbox>
</el-form-item>
</div>
<template v-if="showProperty('axisValue') && !state.miscForm.axisValue.auto">
<el-row :gutter="8">
<el-col :span="12">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.axis_value_max')"
>
<el-input-number
controls-position="right"
:effect="props.themes"
v-model.number="state.miscForm.axisValue.max"
@change="changeMiscStyle('axisValue.max')"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.axis_value_min')"
>
<el-input-number
:effect="props.themes"
controls-position="right"
v-model.number="state.miscForm.axisValue.min"
@change="changeMiscStyle('axisValue.min')"
/>
</el-form-item>
</el-col>
</el-row>
<label class="custom-form-item-label" :class="'custom-form-item-label--' + themes">
{{ t('chart.axis_value_split_count') }}
<el-tooltip class="item" :effect="toolTip" placement="top">
<template #content>期望的坐标轴刻度数量非最终结果</template>
<span style="vertical-align: middle">
<el-icon style="cursor: pointer">
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
</el-icon>
</span>
</el-tooltip>
</label>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-input-number
style="width: 100%"
:effect="props.themes"
controls-position="right"
v-model.number="state.miscForm.axisValue.splitCount"
@change="changeMiscStyle('axisValue.splitCount')"
/>
</el-form-item>
</template>
</template>
</el-form> </el-form>
</template> </template>
@ -145,4 +236,17 @@ onMounted(() => {
.form-item-checkbox { .form-item-checkbox {
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
.custom-form-item-label {
margin-bottom: 4px;
line-height: 20px;
color: #646a73;
font-size: 12px;
font-style: normal;
font-weight: 400;
padding: 2px 12px 0 0;
&.custom-form-item-label--dark {
color: #a6a6a6;
}
}
</style> </style>

View File

@ -1591,7 +1591,7 @@ const setCacheId = () => {
!cacheId || !cacheId ||
!!view.value.tableId || !!view.value.tableId ||
templateStatusShow.value || templateStatusShow.value ||
view.value.type === 'rich-text' ['rich-text', 'picture-group'].includes(view.value.type)
) )
return return
view.value.tableId = cacheId as unknown as number view.value.tableId = cacheId as unknown as number

View File

@ -232,12 +232,12 @@ export const DEFAULT_MISC: ChartMiscAttr = {
summary: '' summary: ''
}, },
gaugeMin: 0, gaugeMin: 0,
gaugeMaxType: 'fix', gaugeMaxType: 'dynamic',
gaugeMaxField: { gaugeMaxField: {
id: '', id: '',
summary: '' summary: ''
}, },
gaugeMax: 1, gaugeMax: undefined,
gaugeStartAngle: 225, gaugeStartAngle: 225,
gaugeEndAngle: -45, gaugeEndAngle: -45,
nameFontSize: 18, nameFontSize: 18,
@ -258,8 +258,8 @@ export const DEFAULT_MISC: ChartMiscAttr = {
nameFontShadow: false, nameFontShadow: false,
treemapWidth: 80, treemapWidth: 80,
treemapHeight: 80, treemapHeight: 80,
liquidMax: 1, liquidMax: undefined,
liquidMaxType: 'fix', liquidMaxType: 'dynamic',
liquidMaxField: { liquidMaxField: {
id: '', id: '',
summary: '' summary: ''
@ -731,6 +731,13 @@ export const DEFAULT_MISC_STYLE: ChartMiscStyle = {
}, },
splitArea: { splitArea: {
show: true show: true
},
axisValue: {
auto: true,
min: 10,
max: 100,
split: 10,
splitCount: 10
} }
} }
export const DEFAULT_FUNCTION_CFG: ChartFunctionCfg = { export const DEFAULT_FUNCTION_CFG: ChartFunctionCfg = {

View File

@ -53,8 +53,8 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
} }
async drawChart(drawOptions: G2PlotDrawOptions<G2Liquid>): Promise<G2Liquid> { async drawChart(drawOptions: G2PlotDrawOptions<G2Liquid>): Promise<G2Liquid> {
const { chart, container } = drawOptions const { chart, container, action } = drawOptions
if (!chart.data?.series) { if (!chart.data?.series || !chart.yAxis.length) {
return return
} }
const initOptions: LiquidOptions = { const initOptions: LiquidOptions = {
@ -62,8 +62,17 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
} }
const options = this.setupOptions(chart, initOptions) const options = this.setupOptions(chart, initOptions)
const { Liquid: G2Liquid } = await import('@antv/g2plot/esm/plots/liquid') const { Liquid: G2Liquid } = await import('@antv/g2plot/esm/plots/liquid')
// 开始渲染 const newChart = new G2Liquid(container, options)
return new G2Liquid(container, options) newChart.on('afterrender', () => {
action({
from: 'liquid',
data: {
type: 'liquid',
max: chart.data?.series[chart.data?.series.length - 1]?.data[0]
}
})
})
return newChart
} }
protected configTheme(chart: Chart, options: LiquidOptions): LiquidOptions { protected configTheme(chart: Chart, options: LiquidOptions): LiquidOptions {
@ -100,10 +109,11 @@ export class Liquid extends G2PlotChartView<LiquidOptions, G2Liquid> {
let max, radius, shape let max, radius, shape
if (customAttr.misc) { if (customAttr.misc) {
const misc = customAttr.misc const misc = customAttr.misc
const defaultLiquidMax = chart.data?.series[chart.data?.series.length - 1]?.data[0]
if (misc.liquidMaxType === 'dynamic') { if (misc.liquidMaxType === 'dynamic') {
max = chart.data?.series[chart.data?.series.length - 1]?.data[0] max = defaultLiquidMax
} else { } else {
max = misc.liquidMax ? misc.liquidMax : DEFAULT_MISC.liquidMax max = misc.liquidMax ? misc.liquidMax : defaultLiquidMax
} }
radius = (misc.liquidSize ? misc.liquidSize : DEFAULT_MISC.liquidSize) / 100 radius = (misc.liquidSize ? misc.liquidSize : DEFAULT_MISC.liquidSize) / 100
shape = misc.liquidShape ?? DEFAULT_MISC.liquidShape shape = misc.liquidShape ?? DEFAULT_MISC.liquidShape

View File

@ -276,6 +276,7 @@ export class SymbolicMap extends L7ChartView<Scene, L7Config> {
await scene.addImage(`customIcon`, svgStrToUrl(svgEle.outerHTML)) await scene.addImage(`customIcon`, svgStrToUrl(svgEle.outerHTML))
pointLayer.shape('customIcon') pointLayer.shape('customIcon')
} else { } else {
pointLayer.shape(mapSymbol)
pointLayer.color(colorsWithAlpha[0]) pointLayer.color(colorsWithAlpha[0])
pointLayer.style({ pointLayer.style({
stroke: colorsWithAlpha[0], stroke: colorsWithAlpha[0],

View File

@ -67,8 +67,8 @@ export class Gauge extends G2PlotChartView<GaugeOptions, G2Gauge> {
} }
async drawChart(drawOptions: G2PlotDrawOptions<G2Gauge>): Promise<G2Gauge> { async drawChart(drawOptions: G2PlotDrawOptions<G2Gauge>): Promise<G2Gauge> {
const { chart, container, scale } = drawOptions const { chart, container, scale, action } = drawOptions
if (!chart.data?.series) { if (!chart.data?.series || !chart.yAxis.length) {
return return
} }
// options // options
@ -99,7 +99,17 @@ export class Gauge extends G2PlotChartView<GaugeOptions, G2Gauge> {
} }
const options = this.setupOptions(chart, initOptions, { scale }) const options = this.setupOptions(chart, initOptions, { scale })
const { Gauge: G2Gauge } = await import('@antv/g2plot/esm/plots/gauge') const { Gauge: G2Gauge } = await import('@antv/g2plot/esm/plots/gauge')
return new G2Gauge(container, options) const newChart = new G2Gauge(container, options)
newChart.on('afterrender', () => {
action({
from: 'gauge',
data: {
type: 'gauge',
max: chart.data?.series[chart.data?.series.length - 1]?.data[0]
}
})
})
return newChart
} }
protected configMisc( protected configMisc(
@ -116,13 +126,13 @@ export class Gauge extends G2PlotChartView<GaugeOptions, G2Gauge> {
min = chart.data?.series[chart.data?.series.length - 2]?.data[0] min = chart.data?.series[chart.data?.series.length - 2]?.data[0]
max = chart.data?.series[chart.data?.series.length - 1]?.data[0] max = chart.data?.series[chart.data?.series.length - 1]?.data[0]
} else if (misc.gaugeMinType !== 'dynamic' && misc.gaugeMaxType === 'dynamic') { } else if (misc.gaugeMinType !== 'dynamic' && misc.gaugeMaxType === 'dynamic') {
min = misc.gaugeMin ? misc.gaugeMin : DEFAULT_MISC.gaugeMin min = misc.gaugeMin || misc.gaugeMin === 0 ? misc.gaugeMin : DEFAULT_MISC.gaugeMin
max = chart.data?.series[chart.data?.series.length - 1]?.data[0] max = chart.data?.series[chart.data?.series.length - 1]?.data[0]
} else if (misc.gaugeMinType === 'dynamic' && misc.gaugeMaxType !== 'dynamic') { } else if (misc.gaugeMinType === 'dynamic' && misc.gaugeMaxType !== 'dynamic') {
min = chart.data?.series[chart.data?.series.length - 1]?.data[0] min = chart.data?.series[chart.data?.series.length - 1]?.data[0]
max = misc.gaugeMax ? misc.gaugeMax : DEFAULT_MISC.gaugeMax max = misc.gaugeMax ? misc.gaugeMax : DEFAULT_MISC.gaugeMax
} else { } else {
min = misc.gaugeMin ? misc.gaugeMin : DEFAULT_MISC.gaugeMin min = misc.gaugeMin || misc.gaugeMin === 0 ? misc.gaugeMin : DEFAULT_MISC.gaugeMin
max = misc.gaugeMax ? misc.gaugeMax : DEFAULT_MISC.gaugeMax max = misc.gaugeMax ? misc.gaugeMax : DEFAULT_MISC.gaugeMax
} }
startAngle = (misc.gaugeStartAngle * Math.PI) / 180 startAngle = (misc.gaugeStartAngle * Math.PI) / 180

View File

@ -27,7 +27,7 @@ export class Radar extends G2PlotChartView<RadarOptions, G2Radar> {
'basic-style-selector': ['colors', 'alpha', 'radarShape', 'seriesColor'], 'basic-style-selector': ['colors', 'alpha', 'radarShape', 'seriesColor'],
'label-selector': ['seriesLabelFormatter'], 'label-selector': ['seriesLabelFormatter'],
'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'seriesTooltipFormatter', 'show'], 'tooltip-selector': ['color', 'fontSize', 'backgroundColor', 'seriesTooltipFormatter', 'show'],
'misc-style-selector': ['showName', 'color', 'fontSize', 'axisColor'], 'misc-style-selector': ['showName', 'color', 'fontSize', 'axisColor', 'axisValue'],
'title-selector': [ 'title-selector': [
'show', 'show',
'title', 'title',
@ -213,6 +213,22 @@ export class Radar extends G2PlotChartView<RadarOptions, G2Radar> {
} }
} }
} }
const axisValue = misc.axisValue
if (!axisValue?.auto) {
const axisYAxis = {
...yAxis,
min: axisValue.min,
max: axisValue.max,
minLimit: axisValue.min,
maxLimit: axisValue.max,
tickCount: axisValue.splitCount
}
return {
...options,
xAxis,
yAxis: axisYAxis
}
}
return { return {
...options, ...options,
xAxis, xAxis,

View File

@ -23,7 +23,12 @@ import {
type PivotSheet, type PivotSheet,
type Node, type Node,
type Meta, type Meta,
S2DataConfig S2DataConfig,
SpreadSheet,
InteractionStateName,
InteractionName,
DataCellBrushSelection,
TableDataCell
} from '@antv/s2' } from '@antv/s2'
import { keys, intersection, filter, cloneDeep, merge, find, repeat } from 'lodash-es' import { keys, intersection, filter, cloneDeep, merge, find, repeat } from 'lodash-es'
import { createVNode, render } from 'vue' import { createVNode, render } from 'vue'
@ -31,6 +36,7 @@ import TableTooltip from '@/views/chart/components/editor/common/TableTooltip.vu
import Exceljs from 'exceljs' import Exceljs from 'exceljs'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import { ElMessage } from 'element-plus-secondary' import { ElMessage } from 'element-plus-secondary'
import { matrix } from 'mathjs'
export function getCustomTheme(chart: Chart): S2Theme { export function getCustomTheme(chart: Chart): S2Theme {
const headerColor = hexColorToRGBA( const headerColor = hexColorToRGBA(
@ -971,12 +977,76 @@ export function configTooltip(chart: Chart, option: S2Options) {
} }
} }
export function copyContent(s2Instance, event, fieldMeta) { export function copyContent(s2Instance: SpreadSheet, event, fieldMeta) {
event.preventDefault() event.preventDefault()
const cell = s2Instance.getCell(event.target) const cell = s2Instance.getCell(event.target)
const valueField = cell.getMeta().valueField const valueField = cell.getMeta().valueField
const cellMeta = cell.getMeta() const cellMeta = cell.getMeta()
let content const selectState = s2Instance.interaction.getState()
let content = ''
// 多选
if (selectState.stateName === InteractionStateName.SELECTED) {
const { cells } = selectState
if (!cells?.length) {
return
}
const brushSelection = s2Instance.interaction.interactions.get(
InteractionName.BRUSH_SELECTION
) as DataCellBrushSelection
const selectedCells: TableDataCell[] = brushSelection.getScrollBrushRangeCells(cells)
selectedCells.sort((a, b) => {
const aMeta = a.getMeta()
const bMeta = b.getMeta()
if (aMeta.rowIndex !== bMeta.rowIndex) {
return aMeta.rowIndex - bMeta.rowIndex
}
return aMeta.colIndex - bMeta.colIndex
})
// 点击已选的就复制未选的就忽略
let validClick = false
const matrix = selectedCells.reduce((p, n) => {
if (
n.getMeta().colIndex === cellMeta.colIndex &&
n.getMeta().rowIndex === cellMeta.rowIndex
) {
validClick = true
}
const arr = p[n.getMeta().rowIndex]
if (!arr) {
p[n.getMeta().rowIndex] = [n]
} else {
arr.push(n)
}
return p
}, {}) as Record<number, TableDataCell[]>
if (validClick) {
keys(matrix).forEach(k => {
const arr = matrix[k] as TableDataCell[]
arr.forEach((cell, index) => {
const cellMeta = cell.getMeta()
const value = cellMeta.data?.[cellMeta.valueField]
const metaObj = find(fieldMeta, m => m.field === valueField)
let fieldVal = value?.toString()
if (metaObj) {
fieldVal = metaObj.formatter(value)
}
if (fieldVal === undefined) {
fieldVal = ''
}
if (index !== arr.length - 1) {
fieldVal += '\t'
}
content += fieldVal
})
content = content + '\n'
})
if (content) {
copyString(content, true)
}
}
s2Instance.interaction.clearState()
return
}
// 单元格 // 单元格
if (cellMeta?.data) { if (cellMeta?.data) {
const value = cellMeta.data[valueField] const value = cellMeta.data[valueField]

View File

@ -317,13 +317,24 @@ const pointClickTrans = () => {
} }
} }
const action = param => { const actionDefault = param => {
if (param.from === 'map') { if (param.from === 'map') {
emitter.emit('map-default-range', param) emitter.emit('map-default-range', param)
return
} }
if (param.from === 'word-cloud') { if (param.from === 'word-cloud') {
emitter.emit('word-cloud-default-data-range', param) emitter.emit('word-cloud-default-data-range', param)
}
if (param.from === 'gauge') {
emitter.emit('gauge-default-data', param)
}
if (param.from === 'liquid') {
emitter.emit('liquid-default-data', param)
}
}
const action = param => {
if (param.from) {
actionDefault(param)
return return
} }
state.pointParam = param.data state.pointParam = param.data

View File

@ -583,6 +583,10 @@ const checkFieldIsAllowEmpty = (allField?) => {
showEmpty.value = false showEmpty.value = false
if (view.value?.render && view.value?.type) { if (view.value?.render && view.value?.type) {
const chartView = chartViewManager.getChartView(view.value.render, view.value.type) const chartView = chartViewManager.getChartView(view.value.render, view.value.type)
//
if (!chartView) {
return
}
const map = parseJson(view.value.customAttr).map const map = parseJson(view.value.customAttr).map
if (['bubble-map', 'map'].includes(view.value?.type) && !map?.id) { if (['bubble-map', 'map'].includes(view.value?.type) && !map?.id) {
showEmpty.value = true showEmpty.value = true

View File

@ -11,6 +11,8 @@ import { Column, Line, Pie } from '@antv/g2plot'
import { useElementSize } from '@vueuse/core' import { useElementSize } from '@vueuse/core'
import { downloadCanvas } from '@/utils/imgUtils' import { downloadCanvas } from '@/utils/imgUtils'
import ExcelJS from 'exceljs' import ExcelJS from 'exceljs'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
interface Copilot { interface Copilot {
msgType: string msgType: string
question: string question: string
@ -199,7 +201,7 @@ const downloadChart = () => {
exportExcel() exportExcel()
return return
} }
downloadCanvas('img', chartTypeRef.value, '图表') downloadCanvas('img', chartTypeRef.value, t('copilot.chart'))
} }
watch( watch(
() => props.copilotInfo.loading, () => props.copilotInfo.loading,
@ -223,17 +225,17 @@ const activeCommand = ref('')
const curTypeList = [ const curTypeList = [
{ {
label: '折线图', label: t('copilot.line'),
value: 'line', value: 'line',
icon: icon_chartLineC icon: icon_chartLineC
}, },
{ {
label: '柱状图', label: t('copilot.bar'),
icon: icon_dashboard_outlinedC, icon: icon_dashboard_outlinedC,
value: 'bar' value: 'bar'
}, },
{ {
label: '饼图', label: t('copilot.pie'),
icon: icon_pie_outlinedC, icon: icon_pie_outlinedC,
value: 'pie' value: 'pie'
} }
@ -248,7 +250,7 @@ const tips = computed(() => {
return chart.title return chart.title
} }
if (msgStatus === 0) { if (msgStatus === 0) {
return '抱歉,根据已知信息无法回答这个问题,请重新描述你的问题或提供更多信息~' return t('copilot.sorry')
} else if (msgType === 'user') { } else if (msgType === 'user') {
return question return question
} }
@ -274,15 +276,15 @@ const tips = computed(() => {
</el-icon> </el-icon>
<div ref="content" class="content"> <div ref="content" class="content">
<div v-if="isWelcome" class="question-or-title" style="font-size: 16px; font-weight: 500"> <div v-if="isWelcome" class="question-or-title" style="font-size: 16px; font-weight: 500">
您好我是 Copilot很高兴为你服务 {{ t('copilot.hello1') }}
</div> </div>
<div v-else-if="isAnswer" class="question-or-title" style="font-size: 16px; font-weight: 500"> <div v-else-if="isAnswer" class="question-or-title" style="font-size: 16px; font-weight: 500">
回答中<span class="dot">...</span> {{ t('copilot.answer') }}<span class="dot">...</span>
</div> </div>
<div v-else class="question-or-title"> <div v-else class="question-or-title">
{{ tips }} {{ tips }}
</div> </div>
<div v-if="isWelcome" class="is-welcome">您可以问我: 2020年各个销售部门销售额占比的饼图</div> <div v-if="isWelcome" class="is-welcome">{{ t('copilot.example') }}</div>
<div <div
v-else-if="copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1" v-else-if="copilotInfo.msgType === 'api' && copilotInfo.msgStatus === 1"
class="chart-type" class="chart-type"
@ -324,14 +326,14 @@ const tips = computed(() => {
></component ></component
></Icon> ></Icon>
</el-icon> </el-icon>
<el-tooltip effect="dark" content="切换图表类型" placement="top"> <el-tooltip effect="dark" :content="t('copilot.switch_chart')" placement="top">
<div <div
v-show="renderTable || renderTableLocal" v-show="renderTable || renderTableLocal"
@click="switchChartType(activeCommand)" @click="switchChartType(activeCommand)"
class="fake-mask_select" class="fake-mask_select"
></div> ></div>
</el-tooltip> </el-tooltip>
<el-tooltip effect="dark" content="切换图表类型" placement="top"> <el-tooltip effect="dark" :content="t('copilot.switch_chart')" placement="top">
<el-select <el-select
popper-class="copilot-select_popper" popper-class="copilot-select_popper"
class="select-copilot-list" class="select-copilot-list"
@ -350,7 +352,7 @@ const tips = computed(() => {
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
</template> </template>
<el-tooltip effect="dark" content="切换至明细表" placement="top"> <el-tooltip effect="dark" :content="t('copilot.switch_table')" placement="top">
<el-icon <el-icon
:class="(renderTable || renderTableLocal) && 'active'" :class="(renderTable || renderTableLocal) && 'active'"
class="ed-icon_chart" class="ed-icon_chart"
@ -360,7 +362,7 @@ const tips = computed(() => {
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<el-tooltip effect="dark" content="下载" placement="top"> <el-tooltip effect="dark" :content="t('copilot.download')" placement="top">
<el-icon class="ed-icon_chart" @click="downloadChart"> <el-icon class="ed-icon_chart" @click="downloadChart">
<Icon name="chart-download"><chartDownload class="svg-icon" /></Icon> <Icon name="chart-download"><chartDownload class="svg-icon" /></Icon>
</el-icon> </el-icon>

View File

@ -23,6 +23,8 @@ import DialogueChart from '@/views/copilot/DialogueChart.vue'
import { type Tree } from '@/views/visualized/data/dataset/form/CreatDsGroup.vue' import { type Tree } from '@/views/visualized/data/dataset/form/CreatDsGroup.vue'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { iconFieldMap } from '@/components/icon-group/field-list' import { iconFieldMap } from '@/components/icon-group/field-list'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const quota = shallowRef([]) const quota = shallowRef([])
const dimensions = shallowRef([]) const dimensions = shallowRef([])
const datasetTree = shallowRef([]) const datasetTree = shallowRef([])
@ -105,8 +107,8 @@ const handleDatasetChange = () => {
if (!!oldId && !!historyArr.value.length) { if (!!oldId && !!historyArr.value.length) {
currentId = datasetId.value currentId = datasetId.value
datasetId.value = oldId datasetId.value = oldId
const msg = `当前数据集为【${oldName}】,切换数据集将清空当前会话。` const msg = t('copilot.ds_prefix') + oldName + t('copilot.ds_suffix')
ElMessageBox.confirm('确定要切换数据集吗?', { ElMessageBox.confirm(t('copilot.confirm'), {
confirmButtonType: 'primary', confirmButtonType: 'primary',
type: 'warning', type: 'warning',
tip: msg, tip: msg,
@ -209,7 +211,7 @@ const queryAnswer = (event?: KeyboardEvent) => {
<el-icon style="margin-right: 8px; font-size: 24px"> <el-icon style="margin-right: 8px; font-size: 24px">
<Icon name="copilot"><copilot class="svg-icon" /></Icon> <Icon name="copilot"><copilot class="svg-icon" /></Icon>
</el-icon> </el-icon>
Copilot 对话分析 {{ t('copilot.talking_analysis') }}
</div> </div>
<div class="copilot-service"> <div class="copilot-service">
<div class="dialogue"> <div class="dialogue">
@ -243,7 +245,7 @@ const queryAnswer = (event?: KeyboardEvent) => {
</div> </div>
</div> </div>
<div class="dataset-select" :style="{ width: showLeft ? 0 : '280px' }"> <div class="dataset-select" :style="{ width: showLeft ? 0 : '280px' }">
<el-tooltip effect="dark" content="收起" placement="left"> <el-tooltip effect="dark" :content="t('relation.retract')" placement="left">
<p v-show="!showLeft" class="arrow-right" @click="handleShowLeft(true)"> <p v-show="!showLeft" class="arrow-right" @click="handleShowLeft(true)">
<el-icon> <el-icon>
<Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon> <Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon>
@ -251,19 +253,19 @@ const queryAnswer = (event?: KeyboardEvent) => {
</p> </p>
</el-tooltip> </el-tooltip>
<el-tooltip effect="dark" content="展开" placement="left"> <el-tooltip effect="dark" :content="t('relation.expand')" placement="left">
<p v-show="showLeft" class="left-outlined" @click="handleShowLeft(false)"> <p v-show="showLeft" class="left-outlined" @click="handleShowLeft(false)">
<el-icon> <el-icon>
<Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon> <Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon>
</el-icon> </el-icon>
</p> </p>
</el-tooltip> </el-tooltip>
<div class="title-dataset_select">选择数据集</div> <div class="title-dataset_select">{{ t('copilot.choose_dataset') }}</div>
<div style="margin: 0 16px" class="tree-select"> <div style="margin: 0 16px" class="tree-select">
<el-tree-select <el-tree-select
v-model="datasetId" v-model="datasetId"
:data="computedTree" :data="computedTree"
placeholder="请选择数据集" :placeholder="t('copilot.pls_choose_dataset')"
@change="handleDatasetChange" @change="handleDatasetChange"
:props="dsSelectProps" :props="dsSelectProps"
style="width: 100%" style="width: 100%"

View File

@ -7,7 +7,7 @@ import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
import PreviewHead from '@/views/data-visualization/PreviewHead.vue' import PreviewHead from '@/views/data-visualization/PreviewHead.vue'
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue' import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import ArrowSide from '@/views/common/DeResourceArrow.vue' import ArrowSide from '@/views/common/DeResourceArrow.vue'
import { initCanvasData, initCanvasDataPrepare } from '@/utils/canvasUtils' import { initCanvasData, initCanvasDataPrepare, onInitReady } from '@/utils/canvasUtils'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
import { useRequestStoreWithOut } from '@/store/modules/request' import { useRequestStoreWithOut } from '@/store/modules/request'
import { usePermissionStoreWithOut } from '@/store/modules/permission' import { usePermissionStoreWithOut } from '@/store/modules/permission'
@ -108,6 +108,7 @@ const loadCanvasData = (dvId, weight?) => {
dataInitState.value = true dataInitState.value = true
nextTick(() => { nextTick(() => {
dashboardPreview.value.restore() dashboardPreview.value.restore()
onInitReady({ resourceId: dvId })
}) })
} }
) )

View File

@ -13,7 +13,7 @@ import ViewEditor from '@/views/chart/components/editor/index.vue'
import { getDatasetTree } from '@/api/dataset' import { getDatasetTree } from '@/api/dataset'
import { Tree } from '@/views/visualized/data/dataset/form/CreatDsGroup.vue' import { Tree } from '@/views/visualized/data/dataset/form/CreatDsGroup.vue'
import DbCanvasAttr from '@/components/dashboard/DbCanvasAttr.vue' import DbCanvasAttr from '@/components/dashboard/DbCanvasAttr.vue'
import { decompressionPre, initCanvasData } from '@/utils/canvasUtils' import { decompressionPre, initCanvasData, onInitReady } from '@/utils/canvasUtils'
import ChartStyleBatchSet from '@/views/chart/components/editor/editor-style/ChartStyleBatchSet.vue' import ChartStyleBatchSet from '@/views/chart/components/editor/editor-style/ChartStyleBatchSet.vue'
import DeCanvas from '@/views/canvas/DeCanvas.vue' import DeCanvas from '@/views/canvas/DeCanvas.vue'
import { check, compareStorage } from '@/utils/CrossPermission' import { check, compareStorage } from '@/utils/CrossPermission'
@ -160,6 +160,7 @@ const initLocalCanvasData = () => {
snapshotStore.recordSnapshotCache() snapshotStore.recordSnapshotCache()
}, 1500) }, 1500)
} }
onInitReady({ resourceId: resourceId })
}) })
} }
onMounted(async () => { onMounted(async () => {
@ -281,6 +282,7 @@ onUnmounted(() => {
:width="420" :width="420"
:side-name="'componentProp'" :side-name="'componentProp'"
:aside-position="'right'" :aside-position="'right'"
:view="canvasViewInfo[curComponent.id]"
class="left-sidebar" class="left-sidebar"
> >
<component :is="findComponentAttr(curComponent)" :themes="'light'" /> <component :is="findComponentAttr(curComponent)" :themes="'light'" />

View File

@ -3,7 +3,6 @@ import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import DePreview from '@/components/data-visualization/canvas/DePreview.vue' import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { ElScrollbar } from 'element-plus-secondary'
const dvMainStore = dvMainStoreWithOut() const dvMainStore = dvMainStoreWithOut()
const { fullscreenFlag } = storeToRefs(dvMainStore) const { fullscreenFlag } = storeToRefs(dvMainStore)

View File

@ -4,7 +4,7 @@ import { nextTick, onMounted, reactive, ref } from 'vue'
import DePreview from '@/components/data-visualization/canvas/DePreview.vue' import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
import router from '@/router' import router from '@/router'
import { useEmitt } from '@/hooks/web/useEmitt' import { useEmitt } from '@/hooks/web/useEmitt'
import { initCanvasData } from '@/utils/canvasUtils' import { initCanvasData, onInitReady } from '@/utils/canvasUtils'
import { queryTargetVisualizationJumpInfo } from '@/api/visualization/linkJump' import { queryTargetVisualizationJumpInfo } from '@/api/visualization/linkJump'
import { Base64 } from 'js-base64' import { Base64 } from 'js-base64'
import { getOuterParamsInfo } from '@/api/visualization/outerParams' import { getOuterParamsInfo } from '@/api/visualization/outerParams'
@ -28,7 +28,7 @@ const state = reactive({
canvasViewInfoPreview: null, canvasViewInfoPreview: null,
dvInfo: null, dvInfo: null,
curPreviewGap: 0, curPreviewGap: 0,
initState: false initState: true
}) })
const props = defineProps({ const props = defineProps({
@ -116,7 +116,6 @@ const loadCanvasDataAsync = async (dvId, dvType) => {
canvasViewInfoPreview, canvasViewInfoPreview,
curPreviewGap curPreviewGap
}) { }) {
state.initState = false
state.canvasDataPreview = canvasDataResult state.canvasDataPreview = canvasDataResult
state.canvasStylePreview = canvasStyleResult state.canvasStylePreview = canvasStyleResult
state.canvasViewInfoPreview = canvasViewInfoPreview state.canvasViewInfoPreview = canvasViewInfoPreview
@ -125,6 +124,7 @@ const loadCanvasDataAsync = async (dvId, dvType) => {
if (jumpParam) { if (jumpParam) {
dvMainStore.addViewTrackFilter(jumpParam) dvMainStore.addViewTrackFilter(jumpParam)
} }
state.initState = false
dvMainStore.addOuterParamsFilter(attachParam) dvMainStore.addOuterParamsFilter(attachParam)
state.initState = true state.initState = true
if (props.publicLinkStatus) { if (props.publicLinkStatus) {
@ -133,9 +133,13 @@ const loadCanvasDataAsync = async (dvId, dvType) => {
setTitle(dvInfo.name) setTitle(dvInfo.name)
} }
initBrowserTimer() initBrowserTimer()
nextTick(() => {
onInitReady({ resourceId: dvId })
})
} }
) )
} }
const downloadH2 = type => { const downloadH2 = type => {
downloadStatus.value = true downloadStatus.value = true
nextTick(() => { nextTick(() => {

View File

@ -8,7 +8,7 @@ import PreviewHead from '@/views/data-visualization/PreviewHead.vue'
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue' import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
import { initCanvasData, initCanvasDataPrepare } from '@/utils/canvasUtils' import { initCanvasData, initCanvasDataPrepare, onInitReady } from '@/utils/canvasUtils'
import { useRequestStoreWithOut } from '@/store/modules/request' import { useRequestStoreWithOut } from '@/store/modules/request'
import { usePermissionStoreWithOut } from '@/store/modules/permission' import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useMoveLine } from '@/hooks/web/useMoveLine' import { useMoveLine } from '@/hooks/web/useMoveLine'
@ -92,6 +92,9 @@ const loadCanvasData = (dvId, weight?) => {
dvPreviewRef.value?.restore() dvPreviewRef.value?.restore()
}) })
} }
nextTick(() => {
onInitReady({ resourceId: dvId })
})
} }
) )
} }

View File

@ -22,7 +22,8 @@ import {
decompressionPre, decompressionPre,
findDragComponent, findDragComponent,
findNewComponent, findNewComponent,
initCanvasData initCanvasData,
onInitReady
} from '@/utils/canvasUtils' } from '@/utils/canvasUtils'
import CanvasCore from '@/components/data-visualization/canvas/CanvasCore.vue' import CanvasCore from '@/components/data-visualization/canvas/CanvasCore.vue'
import { listenGlobalKeyDown, releaseAttachKey } from '@/utils/DeShortcutKey' import { listenGlobalKeyDown, releaseAttachKey } from '@/utils/DeShortcutKey'
@ -245,6 +246,7 @@ const initLocalCanvasData = async () => {
snapshotStore.recordSnapshotCache('renderChart') snapshotStore.recordSnapshotCache('renderChart')
}, 1500) }, 1500)
} }
onInitReady({ resourceId: resourceId })
}) })
}) })
} }
@ -490,6 +492,8 @@ eventBus.on('handleNew', handleNew)
:side-name="'componentProp'" :side-name="'componentProp'"
:aside-position="'right'" :aside-position="'right'"
class="left-sidebar" class="left-sidebar"
:slide-index="2"
:view="canvasViewInfo[curComponent.id]"
:class="{ 'preview-aside': editMode === 'preview' }" :class="{ 'preview-aside': editMode === 'preview' }"
> >
<component :is="findComponentAttr(curComponent)" /> <component :is="findComponentAttr(curComponent)" />

View File

@ -48,7 +48,9 @@
<el-tooltip <el-tooltip
effect="dark" effect="dark"
:content="pwdItem[item.pkey]['hidden'] ? '点击显示' : '点击隐藏'" :content="
pwdItem[item.pkey]['hidden'] ? t('system.click_to_show') : t('system.click_to_hide')
"
placement="top" placement="top"
> >
<el-button text @click="switchPwd(item.pkey)" class="setting-tip-btn"> <el-button text @click="switchPwd(item.pkey)" class="setting-tip-btn">
@ -64,7 +66,7 @@
</el-tooltip> </el-tooltip>
</div> </div>
<span v-else-if="item.pkey.includes('basic.dsIntervalTime')"> <span v-else-if="item.pkey.includes('basic.dsIntervalTime')">
<span>{{ item.pval + ' ' + executeTime + '执行一次' }}</span> <span>{{ item.pval + ' ' + executeTime + t('common.every_exec') }}</span>
</span> </span>
<span v-else> <span v-else>
<span>{{ item.pval }}</span> <span>{{ item.pval }}</span>
@ -113,7 +115,7 @@ const props = defineProps({
}, },
settingTitle: { settingTitle: {
type: String, type: String,
default: '基础设置' default: ''
}, },
hideHead: { hideHead: {
type: Boolean, type: Boolean,
@ -132,9 +134,9 @@ const props = defineProps({
default: () => [] default: () => []
} }
}) })
const executeTime = ref('0分0秒') const executeTime = ref(t('system.and_0_seconds'))
const curTitle = computed(() => { const curTitle = computed(() => {
return props.settingTitle return props.settingTitle || t('system.basic_settings')
}) })
const copyVal = async val => { const copyVal = async val => {
try { try {
@ -159,8 +161,8 @@ const loadList = () => {
const getExecuteTime = val => { const getExecuteTime = val => {
const options = [ const options = [
{ value: 'minute', label: '分钟执行时间0秒' }, { value: 'minute', label: t('system.time_0_seconds') },
{ value: 'hour', label: '小时执行时间0分0秒' } { value: 'hour', label: t('system.and_0_seconds_de') }
] ]
return options.filter(item => item.value === val)[0].label return options.filter(item => item.value === val)[0].label
} }

View File

@ -2,15 +2,18 @@
import icon_upload_outlined from '@/assets/svg/icon_upload_outlined.svg' import icon_upload_outlined from '@/assets/svg/icon_upload_outlined.svg'
import { ref, reactive } from 'vue' import { ref, reactive } from 'vue'
import { uploadFontFile } from '@/api/font' import { uploadFontFile } from '@/api/font'
import { useI18n } from '@/hooks/web/useI18n'
import FontInfo from './FontInfo.vue' import FontInfo from './FontInfo.vue'
import { ElMessage } from 'element-plus-secondary' import { ElMessage } from 'element-plus-secondary'
import { edit } from '@/api/font' import { edit } from '@/api/font'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
const state = reactive({ const state = reactive({
fileList: null fileList: null
}) })
const loading = ref(false) const loading = ref(false)
const upload = ref() const upload = ref()
const { t } = useI18n()
const uploadExcel = () => { const uploadExcel = () => {
const formData = new FormData() const formData = new FormData()
formData.append('file', state.fileList.raw) formData.append('file', state.fileList.raw)
@ -53,7 +56,7 @@ const defaultForm = {
const ruleForm = reactive(cloneDeep(defaultForm)) const ruleForm = reactive(cloneDeep(defaultForm))
const init = (val, type, item) => { const init = (val, type, item) => {
dialogTitle.value = val || '添加字体' dialogTitle.value = val || t('system.add_font')
action.value = type action.value = type
dialogVisible.value = true dialogVisible.value = true
Object.assign(ruleForm, cloneDeep(defaultForm)) Object.assign(ruleForm, cloneDeep(defaultForm))
@ -67,8 +70,8 @@ const fontDel = () => {
const ruleFormRef = ref() const ruleFormRef = ref()
const rules = { const rules = {
name: [ name: [
{ required: true, message: '请输入字体名称', trigger: 'blur' }, { required: true, message: t('system.the_font_name'), trigger: 'blur' },
{ min: 1, max: 50, message: '字符长度1-50', trigger: 'blur' } { min: 1, max: 50, message: t('system.character_length_1_50'), trigger: 'blur' }
] ]
} }
defineExpose({ defineExpose({
@ -77,7 +80,7 @@ defineExpose({
const beforeAvatarUpload = rawFile => { const beforeAvatarUpload = rawFile => {
if (!rawFile.name.endsWith('.ttf')) { if (!rawFile.name.endsWith('.ttf')) {
ElMessage.error('只支持上传ttf格式的字体文件!') ElMessage.error(t('system.in_ttf_format'))
return false return false
} }
return true return true
@ -105,12 +108,12 @@ const confirm = () => {
if (val) { if (val) {
if (action.value === 'uploadFile') { if (action.value === 'uploadFile') {
if (ruleForm.fileTransName === '') { if (ruleForm.fileTransName === '') {
ElMessage.error('请上传字库文件') ElMessage.error(t('system.upload_font_file_de'))
return return
} }
} }
edit(ruleForm).then(() => { edit(ruleForm).then(() => {
ElMessage.success(dialogTitle.value + '成功') ElMessage.success(dialogTitle.value + t('data_set.success'))
cancel() cancel()
emits('finish') emits('finish')
}) })
@ -136,10 +139,14 @@ const confirm = () => {
label-width="auto" label-width="auto"
class="demo-ruleForm" class="demo-ruleForm"
> >
<el-form-item v-if="action !== 'uploadFile'" label="字体名称" prop="name"> <el-form-item v-if="action !== 'uploadFile'" :label="t('system.font_name')" prop="name">
<el-input placeholder="请输入字体名称" v-model.trim="ruleForm.name" /> <el-input :placeholder="t('system.the_font_name')" v-model.trim="ruleForm.name" />
</el-form-item> </el-form-item>
<el-form-item v-loading="loading" v-if="action !== 'rename'" label="字库文件"> <el-form-item
v-loading="loading"
v-if="action !== 'rename'"
:label="t('system.font_file_de')"
>
<el-upload <el-upload
action="" action=""
:multiple="false" :multiple="false"
@ -158,7 +165,7 @@ const confirm = () => {
<template #icon> <template #icon>
<Icon name="icon_upload_outlined"><icon_upload_outlined class="svg-icon" /></Icon> <Icon name="icon_upload_outlined"><icon_upload_outlined class="svg-icon" /></Icon>
</template> </template>
上传字库文件 {{ t('system.upload_font_file') }}
</el-button> </el-button>
</template> </template>
</el-upload> </el-upload>
@ -182,15 +189,17 @@ const confirm = () => {
v-show="state.fileList" v-show="state.fileList"
> >
<template #trigger> <template #trigger>
<el-button text> 重新上传 </el-button> <el-button text> {{ t('data_source.reupload') }} </el-button>
</template> </template>
</el-upload> </el-upload>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="cancel">取消</el-button> <el-button @click="cancel">{{ t('userimport.cancel') }}</el-button>
<el-button v-loading="loading" type="primary" @click="confirm"> 确定 </el-button> <el-button v-loading="loading" type="primary" @click="confirm">
{{ t('userimport.sure') }}
</el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>

View File

@ -4,12 +4,13 @@ import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import { onMounted, ref, computed } from 'vue' import { onMounted, ref, computed } from 'vue'
import UploadDetail from './UploadDetail.vue' import UploadDetail from './UploadDetail.vue'
import { useAppearanceStoreWithOut } from '@/store/modules/appearance' import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
import { useI18n } from '@/hooks/web/useI18n'
import { deleteById, edit, defaultFont } from '@/api/font' import { deleteById, edit, defaultFont } from '@/api/font'
import { ElMessage, ElMessageBox } from 'element-plus-secondary' import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
const appearanceStore = useAppearanceStoreWithOut() const appearanceStore = useAppearanceStoreWithOut()
const { t } = useI18n()
const fontKeyword = ref('') const fontKeyword = ref('')
const fontList = ref([]) const fontList = ref([])
const basePath = import.meta.env.VITE_API_BASEPATH const basePath = import.meta.env.VITE_API_BASEPATH
@ -35,10 +36,10 @@ const fontListComputed = computed(() => {
const deleteFont = item => { const deleteFont = item => {
if (item.isDefault) { if (item.isDefault) {
ElMessage.warning('请先将其他字体设置为默认字体,再进行删除。') ElMessage.warning(t('system.fonts_before_deleting'))
return return
} }
ElMessageBox.confirm('当前字体被删除后,使用该字体的组件将会使用默认字体,确定删除?', { ElMessageBox.confirm(t('system.sure_to_delete'), {
confirmButtonType: 'danger', confirmButtonType: 'danger',
type: 'warning', type: 'warning',
autofocus: false, autofocus: false,
@ -47,7 +48,7 @@ const deleteFont = item => {
loading.value = true loading.value = true
deleteById(item.id) deleteById(item.id)
.then(() => { .then(() => {
ElMessage.success('删除成功') ElMessage.success(t('common.delete_success'))
listFont() listFont()
getDefaultFont() getDefaultFont()
}) })
@ -62,7 +63,7 @@ const setToDefault = item => {
loading.value = true loading.value = true
edit(item) edit(item)
.then(() => { .then(() => {
ElMessage.success('设置成功') ElMessage.success(t('system.setting_successful'))
getDefaultFont() getDefaultFont()
listFont() listFont()
}) })
@ -115,9 +116,14 @@ onMounted(() => {
<template> <template>
<div class="font-management_system" v-loading="loading"> <div class="font-management_system" v-loading="loading">
<div class="route-title"> <div class="route-title">
字体管理 {{ t('system.font_management') }}
<div class="search-font"> <div class="search-font">
<el-input v-model="fontKeyword" clearable style="width: 240px" placeholder="搜索字体名称"> <el-input
v-model="fontKeyword"
clearable
style="width: 240px"
:placeholder="t('system.search_font_name')"
>
<template #prefix> <template #prefix>
<el-icon> <el-icon>
<Icon name="icon_search-outline_outlined" <Icon name="icon_search-outline_outlined"
@ -127,42 +133,45 @@ onMounted(() => {
</template> </template>
</el-input> </el-input>
<el-button type="primary" @click="uploadFont('新建字体', 'create', {})"> <el-button type="primary" @click="uploadFont(t('system.a_new_font'), 'create', {})">
<template #icon> <template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon> <Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template> </template>
添加字体 {{ t('system.add_font') }}
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="font-content_overflow"> <div class="font-content_overflow">
<div class="font-content_list"> <div class="font-content_list">
<div class="font-content_item" v-for="ele in fontListComputed" :key="ele"> <div class="font-content_item" v-for="ele in fontListComputed" :key="ele">
<span v-if="ele.isDefault" class="font-default">默认字体</span> <span v-if="ele.isDefault" class="font-default">{{ t('system.default_font') }}</span>
<div class="font-name"> <div class="font-name">
{{ ele.name }} <span v-if="ele.isBuiltin" class="font-type"> 系统内置 </span> {{ ele.name }}
<span v-if="ele.isBuiltin" class="font-type"> {{ t('system.system_built_in') }} </span>
</div> </div>
<div :title="ele.fileName" class="font-update_time"> <div :title="ele.fileName" class="font-update_time">
更新时间 {{ new Date(ele.updateTime).toLocaleString() }} {{ t('system.update_time') }} {{ new Date(ele.updateTime).toLocaleString() }}
<span class="line"></span> 字库文件 {{ ele.fileName }} <span class="line"></span> {{ t('system.font_file') }} {{ ele.fileName }}
</div> </div>
<div class="font-upload_btn"> <div class="font-upload_btn">
<el-button <el-button
v-if="!ele.fileTransName" v-if="!ele.fileTransName"
@click="uploadFont('上传字库文件', 'uploadFile', ele)" @click="uploadFont(t('system.upload_font_file'), 'uploadFile', ele)"
secondary secondary
>上传字库文件</el-button >{{ t('system.upload_font_file') }}</el-button
> >
<el-button <el-button
v-if="ele.fileTransName" v-if="ele.fileTransName"
@click="uploadFont('替换字库文件', 'uploadFile', ele)" @click="uploadFont(t('system.replace_font_file'), 'uploadFile', ele)"
secondary secondary
>替换字库文件</el-button >{{ t('system.replace_font_file') }}</el-button
> >
<el-button v-if="!ele.isDefault" @click="setToDefault(ele)" secondary <el-button v-if="!ele.isDefault" @click="setToDefault(ele)" secondary>{{
>设为默认字体</el-button t('system.as_default_font')
> }}</el-button>
<el-button v-if="ele.id !== '1'" @click="deleteFont(ele)" secondary>删除</el-button> <el-button v-if="ele.id !== '1'" @click="deleteFont(ele)" secondary>{{
t('common.delete')
}}</el-button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -19,7 +19,7 @@ const pwdForm = reactive(cloneDeep(defaultForm))
const validatePwd = (_: any, value: any, callback: any) => { const validatePwd = (_: any, value: any, callback: any) => {
if (value === pwdForm.pwd) { if (value === pwdForm.pwd) {
callback(new Error('新旧密码不能相同')) callback(new Error(t('system.be_the_same')))
} }
const pattern = const pattern =
/^.*(?=.{6,20})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[~!@#$%^&*()_+\-\={}|":<>?`[\];',.\/])[a-zA-Z0-9~!@#$%^&*()_+\-\={}|":<>?`[\];',.\/]*$/ /^.*(?=.{6,20})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[~!@#$%^&*()_+\-\={}|":<>?`[\];',.\/])[a-zA-Z0-9~!@#$%^&*()_+\-\={}|":<>?`[\];',.\/]*$/
@ -34,7 +34,7 @@ const validatePwd = (_: any, value: any, callback: any) => {
const validateConfirmPwd = (_: any, value: any, callback: any) => { const validateConfirmPwd = (_: any, value: any, callback: any) => {
if (value !== pwdForm.newPwd) { if (value !== pwdForm.newPwd) {
callback(new Error('两次输入的密码不一致')) callback(new Error(t('system.twice_are_inconsistent')))
} else { } else {
callback() callback()
} }
@ -85,7 +85,7 @@ const save = () => {
const pwd = rsaEncryp(pwdForm.pwd) const pwd = rsaEncryp(pwdForm.pwd)
const newPwd = rsaEncryp(pwdForm.newPwd) const newPwd = rsaEncryp(pwdForm.newPwd)
request.post({ url: '/user/modifyPwd', data: { pwd, newPwd } }).then(() => { request.post({ url: '/user/modifyPwd', data: { pwd, newPwd } }).then(() => {
ElMessage.success('修改成功,请重新登录') ElMessage.success(t('system.log_in_again'))
logoutHandler() logoutHandler()
}) })
} }
@ -103,28 +103,28 @@ const save = () => {
label-width="80px" label-width="80px"
label-position="top" label-position="top"
> >
<el-form-item label="原始密码" prop="pwd"> <el-form-item :label="t('system.original_password')" prop="pwd">
<CustomPassword <CustomPassword
v-model="pwdForm.pwd" v-model="pwdForm.pwd"
show-password show-password
type="password" type="password"
placeholder="请输入原始密码" :placeholder="t('system.the_original_password')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="新密码" prop="newPwd"> <el-form-item :label="t('system.new_password')" prop="newPwd">
<CustomPassword <CustomPassword
v-model="pwdForm.newPwd" v-model="pwdForm.newPwd"
show-password show-password
type="password" type="password"
placeholder="请输入新密码" :placeholder="t('system.the_new_password')"
/> />
</el-form-item> </el-form-item>
<el-form-item label="确认密码" prop="confirm"> <el-form-item :label="t('system.confirm_password')" prop="confirm">
<CustomPassword <CustomPassword
v-model="pwdForm.confirm" v-model="pwdForm.confirm"
show-password show-password
type="password" type="password"
placeholder="请输入确认密码" :placeholder="t('system.the_confirmation_password')"
/> />
</el-form-item> </el-form-item>
<el-button @click="save" type="primary"> <el-button @click="save" type="primary">

View File

@ -2,7 +2,7 @@
<div class="user-center flex-align-center"> <div class="user-center flex-align-center">
<div class="user-center-container"> <div class="user-center-container">
<div class="user-tabs"> <div class="user-tabs">
<div class="tabs-title flex-align-center">用户中心</div> <div class="tabs-title flex-align-center">{{ t('commons.user_center') }}</div>
<el-divider /> <el-divider />
<div class="list-item_primary active"> <div class="list-item_primary active">
{{ t('user.change_password') }} {{ t('user.change_password') }}

View File

@ -9,15 +9,15 @@ const dialogVisible = ref(false)
const loadingInstance = ref(null) const loadingInstance = ref(null)
const basicForm = ref<FormInstance>() const basicForm = ref<FormInstance>()
const options = [ const options = [
{ value: 'minute', label: '分钟执行时间0秒' }, { value: 'minute', label: t('system.time_0_seconds') },
{ value: 'hour', label: '小时执行时间0分0秒' } { value: 'hour', label: t('system.and_0_seconds_de') }
] ]
const pvpOptions = [ const pvpOptions = [
{ value: '0', label: '永久' }, { value: '0', label: t('commons.date.permanent') },
{ value: '1', label: '一年' }, { value: '1', label: t('commons.date.one_year') },
{ value: '2', label: '半年' }, { value: '2', label: t('commons.date.six_months') },
{ value: '3', label: '三个月' }, { value: '3', label: t('commons.date.three_months') },
{ value: '4', label: '一个月' } { value: '4', label: t('commons.date.one_month') }
] ]
const state = reactive({ const state = reactive({
@ -30,7 +30,7 @@ const state = reactive({
orgOptions: [], orgOptions: [],
roleOptions: [], roleOptions: [],
loginOptions: [ loginOptions: [
{ value: '0', label: '普通登录' }, { value: '0', label: t('system.normal_login') },
{ value: '1', label: 'LDAP' }, { value: '1', label: 'LDAP' },
{ value: '2', label: 'OIDC' }, { value: '2', label: 'OIDC' },
{ value: '3', label: 'CAS' }, { value: '3', label: 'CAS' },
@ -69,14 +69,14 @@ const submitForm = async (formEl: FormInstance | undefined) => {
state.form.dsExecuteTime === 'minute' && state.form.dsExecuteTime === 'minute' &&
(Number(state.form.dsIntervalTime) < 1 || Number(state.form.dsIntervalTime) > 59) (Number(state.form.dsIntervalTime) < 1 || Number(state.form.dsIntervalTime) > 59)
) { ) {
ElMessage.error('分钟超出范围【1-59】') ElMessage.error(t('commons.date.of_range_1_59'))
return return
} }
if ( if (
state.form.dsExecuteTime === 'hour' && state.form.dsExecuteTime === 'hour' &&
(Number(state.form.dsIntervalTime) < 1 || Number(state.form.dsIntervalTime) > 23) (Number(state.form.dsIntervalTime) < 1 || Number(state.form.dsIntervalTime) > 23)
) { ) {
ElMessage.error('小时超出范围【1-23】') ElMessage.error(t('commons.date.of_range_1_23'))
return return
} }
const param = buildSettingList() const param = buildSettingList()
@ -210,7 +210,7 @@ defineExpose({
<template> <template>
<el-drawer <el-drawer
title="基础设置" :title="t('system.basic_settings')"
v-model="dialogVisible" v-model="dialogVisible"
custom-class="basic-param-drawer" custom-class="basic-param-drawer"
size="600px" size="600px"
@ -245,7 +245,7 @@ defineExpose({
v-model="state.form[item.pkey]" v-model="state.form[item.pkey]"
/> />
<div v-else-if="item.pkey === 'dsIntervalTime'" class="ds-task-form-inline"> <div v-else-if="item.pkey === 'dsIntervalTime'" class="ds-task-form-inline">
<span></span> <span>{{ t('cron.every') }}</span>
<el-input-number <el-input-number
v-model="state.form.dsIntervalTime" v-model="state.form.dsIntervalTime"
autocomplete="off" autocomplete="off"
@ -264,7 +264,7 @@ defineExpose({
:value="item.value" :value="item.value"
/> />
</el-select> </el-select>
<span class="ds-span">执行一次</span> <span class="ds-span">{{ t('cron.every_exec') }}</span>
</div> </div>
<div v-else-if="item.pkey === 'frontTimeOut'"> <div v-else-if="item.pkey === 'frontTimeOut'">
<el-input-number <el-input-number

View File

@ -3,7 +3,7 @@
ref="infoTemplate" ref="infoTemplate"
:label-tooltips="tooltips" :label-tooltips="tooltips"
setting-key="basic" setting-key="basic"
setting-title="基础设置" :setting-title="t('system.basic_settings')"
:setting-data="state.templateList" :setting-data="state.templateList"
@edit="edit" @edit="edit"
/> />
@ -24,24 +24,24 @@ const editor = ref()
const infoTemplate = ref() const infoTemplate = ref()
const showDefaultLogin = ref(false) const showDefaultLogin = ref(false)
const pvpOptions = [ const pvpOptions = [
{ value: '0', label: '永久' }, { value: '0', label: t('commons.date.permanent') },
{ value: '1', label: '一年' }, { value: '1', label: t('commons.date.one_year') },
{ value: '2', label: '半年' }, { value: '2', label: t('commons.date.six_months') },
{ value: '3', label: '三个月' }, { value: '3', label: t('commons.date.three_months') },
{ value: '4', label: '一个月' } { value: '4', label: t('commons.date.one_month') }
] ]
const tooltips = [ const tooltips = [
{ {
key: 'setting_basic.frontTimeOut', key: 'setting_basic.frontTimeOut',
val: '请求超时时间(单位:秒,注意:保存后刷新浏览器生效)' val: t('system.to_take_effect')
}, },
{ {
key: 'setting_basic.platformOid', key: 'setting_basic.platformOid',
val: '作用域包括认证设置和平台对接' val: t('system.and_platform_docking')
}, },
{ {
key: 'setting_basic.platformRid', key: 'setting_basic.platformRid',
val: '作用域包括认证设置和平台对接' val: t('system.and_platform_docking')
}, },
{ {
key: 'setting_basic.shareDisable', key: 'setting_basic.shareDisable',
@ -66,7 +66,7 @@ const state = reactive({
} }
], ],
loginOptions: [ loginOptions: [
{ value: '0', label: '普通登录' }, { value: '0', label: t('system.normal_login') },
{ value: '1', label: 'LDAP' }, { value: '1', label: 'LDAP' },
{ value: '2', label: 'OIDC' }, { value: '2', label: 'OIDC' },
{ value: '3', label: 'CAS' }, { value: '3', label: 'CAS' },
@ -95,11 +95,11 @@ const search = cb => {
item.pkey === 'basic.shareDisable' || item.pkey === 'basic.shareDisable' ||
item.pkey === 'basic.sharePeRequire' item.pkey === 'basic.sharePeRequire'
) { ) {
item.pval = item.pval === 'true' ? '开启' : '未开启' item.pval = item.pval === 'true' ? t('chart.open') : t('system.not_enabled')
} else if (item.pkey === 'basic.platformOid') { } else if (item.pkey === 'basic.platformOid') {
selectedOid.value = item.pval selectedOid.value = item.pval
await loadOrgOptions() await loadOrgOptions()
item.pval = selectedOName.value || '默认组织' item.pval = selectedOName.value || t('system.default_organization')
} else if (item.pkey === 'basic.platformRid') { } else if (item.pkey === 'basic.platformRid') {
const pval = item.pval const pval = item.pval
if (pval?.length) { if (pval?.length) {
@ -109,11 +109,11 @@ const search = cb => {
if (selectedRName.value.length) { if (selectedRName.value.length) {
item.pval = selectedRName.value.join(',') item.pval = selectedRName.value.join(',')
} else { } else {
item.pval = '普通角色' item.pval = t('system.normal_role')
} }
} else { } else {
selectedRid.value = [] selectedRid.value = []
item.pval = '普通角色' item.pval = t('system.normal_role')
} }
} else if (item.pkey === 'basic.pvp') { } else if (item.pkey === 'basic.pvp') {
selectedPvp.value = item.pval || '0' selectedPvp.value = item.pval || '0'

View File

@ -254,7 +254,7 @@ defineExpose({
<template> <template>
<el-drawer <el-drawer
title="引擎设置" :title="t('system.engine_settings')"
v-model="dialogVisible" v-model="dialogVisible"
custom-class="basic-param-drawer" custom-class="basic-param-drawer"
size="600px" size="600px"

View File

@ -4,7 +4,7 @@
setting-key="basic" setting-key="basic"
showValidate showValidate
style="padding-bottom: 0" style="padding-bottom: 0"
setting-title="引擎设置" :setting-title="t('system.engine_settings')"
:setting-data="templateList" :setting-data="templateList"
@edit="edit" @edit="edit"
@check="validateById" @check="validateById"
@ -91,7 +91,7 @@ const getEngine = () => {
templateList.value = [ templateList.value = [
{ {
pkey: '引擎类型', pkey: t('system.engine_type'),
pval: typeMap[type], pval: typeMap[type],
type: '', type: '',
sort: 0 sort: 0

View File

@ -28,9 +28,9 @@ import { XpackComponent } from '@/components/plugin'
const { t } = useI18n() const { t } = useI18n()
const tabArray = ref([ const tabArray = ref([
{ label: '基础设置', name: 'basic' }, { label: t('system.basic_settings'), name: 'basic' },
{ label: '地图设置', name: 'map' }, { label: t('system.map_settings'), name: 'map' },
{ label: '引擎设置', name: 'engine' } { label: t('system.engine_settings'), name: 'engine' }
]) ])
const activeName = ref('basic') const activeName = ref('basic')

View File

@ -69,7 +69,7 @@
</el-aside> </el-aside>
<el-main class="geometry-main"> <el-main class="geometry-main">
<div class="geo-content-container" v-if="!selectedData"> <div class="geo-content-container" v-if="!selectedData">
<EmptyBackground img-type="noneWhite" description="请在左侧选择区域" /> <EmptyBackground img-type="noneWhite" :description="t('system.on_the_left')" />
</div> </div>
<div v-else class="geo-content-container"> <div v-else class="geo-content-container">
<div class="geo-content-top"> <div class="geo-content-top">
@ -77,13 +77,17 @@
</div> </div>
<div class="geo-content-middle"> <div class="geo-content-middle">
<div class="geo-area"> <div class="geo-area">
<div class="area-label"><span>区域代码</span></div> <div class="area-label">
<span>{{ t('system.region_code') }}</span>
</div>
<div class="area-content"> <div class="area-content">
<span>{{ selectedData.id }}</span> <span>{{ selectedData.id }}</span>
</div> </div>
</div> </div>
<div class="geo-area"> <div class="geo-area">
<div class="area-label"><span>上级区域</span></div> <div class="area-label">
<span>{{ t('system.superior_region') }}</span>
</div>
<div class="area-content"> <div class="area-content">
<span>{{ selectedData.parentName || '-' }}</span> <span>{{ selectedData.parentName || '-' }}</span>
<span v-if="selectedData.pid" class="area-secondary">{{ <span v-if="selectedData.pid" class="area-secondary">{{
@ -93,7 +97,9 @@
</div> </div>
</div> </div>
<div class="geo-content-bottom"> <div class="geo-content-bottom">
<div class="area-label"><span>坐标文件</span></div> <div class="area-label">
<span>{{ t('system.coordinate_file') }}</span>
</div>
<el-scrollbar class="area-content-geo"> <el-scrollbar class="area-content-geo">
<span>{{ selectedData.geoJson }}</span> <span>{{ selectedData.geoJson }}</span>
</el-scrollbar> </el-scrollbar>
@ -145,7 +151,7 @@ const handleNodeClick = async (data: Tree) => {
} }
} }
const delHandler = data => { const delHandler = data => {
ElMessageBox.confirm('确定删除此节点吗', { ElMessageBox.confirm(t('system.delete_this_node'), {
confirmButtonType: 'danger', confirmButtonType: 'danger',
type: 'warning', type: 'warning',
confirmButtonText: t('common.delete'), confirmButtonText: t('common.delete'),

View File

@ -39,9 +39,7 @@ const formatPid = computed(() => {
const pid = state.form.pid const pid = state.form.pid
return pid.replace(/(0+)$/g, '').replace(/\D/g, '') return pid.replace(/(0+)$/g, '').replace(/\D/g, '')
}) })
const codeTips = ref( const codeTips = ref(t('system.at_the_end'))
'国家代码由三位数字组成省、市、区县、乡镇代码由两位数字组成非国家区域需要再后面补0'
)
const pidChange = () => { const pidChange = () => {
state.form.code = null state.form.code = null
} }
@ -50,7 +48,7 @@ const validateCode = (_: any, value: any, callback: any) => {
if (isCountry) { if (isCountry) {
const reg = /^[0-9]\d{2}$/ const reg = /^[0-9]\d{2}$/
if (!reg.test(value) || value === '000') { if (!reg.test(value) || value === '000') {
const msg = '请输入非0的三位数字' const msg = t('system.non_zero_three_digit_number')
callback(new Error(msg)) callback(new Error(msg))
} else { } else {
callback() callback()
@ -59,7 +57,7 @@ const validateCode = (_: any, value: any, callback: any) => {
const fullValue = formatPid.value + value const fullValue = formatPid.value + value
const reg = /^[1-9](\d{8}|\d{10})$/ const reg = /^[1-9](\d{8}|\d{10})$/
if (!reg.test(fullValue)) { if (!reg.test(fullValue)) {
const msg = '请输入9或11位数字' const msg = t('system.or_11_digits')
callback(new Error(msg)) callback(new Error(msg))
} else { } else {
callback() callback()
@ -157,7 +155,7 @@ const handleExceed: UploadProps['onExceed'] = () => {
ElMessage.warning(t('userimport.exceedMsg')) ElMessage.warning(t('userimport.exceedMsg'))
} }
const handleError = () => { const handleError = () => {
ElMessage.warning('执行失败请联系管理员') ElMessage.warning(t('system.contact_the_administrator'))
} }
const setFile = (options: UploadRequestOptions) => { const setFile = (options: UploadRequestOptions) => {
geoFile.value = options.file geoFile.value = options.file
@ -166,12 +164,12 @@ const setFile = (options: UploadRequestOptions) => {
const uploadValidate = file => { const uploadValidate = file => {
const suffix = file.name.substring(file.name.lastIndexOf('.') + 1) const suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
if (suffix !== 'json') { if (suffix !== 'json') {
ElMessage.warning('只能上传json文件') ElMessage.warning(t('system.upload_json_files'))
return false return false
} }
if (file.size / 1024 / 1024 > 200) { if (file.size / 1024 / 1024 > 200) {
ElMessage.warning('最大上传200M') ElMessage.warning(t('system.maximum_upload_200m'))
return false return false
} }
return true return true
@ -191,7 +189,7 @@ defineExpose({
<template> <template>
<el-drawer <el-drawer
title="地理信息" :title="t('system.geographic_information')"
v-model="dialogVisible" v-model="dialogVisible"
custom-class="basic-info-drawer" custom-class="basic-info-drawer"
size="600px" size="600px"
@ -205,7 +203,7 @@ defineExpose({
label-width="80px" label-width="80px"
label-position="top" label-position="top"
> >
<el-form-item label="上级区域" prop="pid"> <el-form-item :label="t('system.superior_region')" prop="pid">
<el-tree-select <el-tree-select
class="map-tree-selector" class="map-tree-selector"
node-key="id" node-key="id"
@ -219,10 +217,10 @@ defineExpose({
/> />
</el-form-item> </el-form-item>
<el-form-item label="区域代码" prop="code"> <el-form-item :label="t('system.region_code')" prop="code">
<template v-slot:label> <template v-slot:label>
<span class="area-code-label"> <span class="area-code-label">
<span>区域代码</span> <span>{{ t('system.region_code') }}</span>
<el-tooltip effect="dark" :content="codeTips" placement="top"> <el-tooltip effect="dark" :content="codeTips" placement="top">
<el-icon class="info-tips" <el-icon class="info-tips"
><Icon name="dv-info"><dvInfo class="svg-icon" /></Icon ><Icon name="dv-info"><dvInfo class="svg-icon" /></Icon
@ -240,19 +238,19 @@ defineExpose({
v-else v-else
class="box-item" class="box-item"
effect="dark" effect="dark"
content="请先选择上级区域" :content="t('system.superior_region_first')"
placement="top" placement="top"
> >
<el-input v-model="state.form.code" disabled /> <el-input v-model="state.form.code" disabled />
</el-tooltip> </el-tooltip>
</el-form-item> </el-form-item>
<el-form-item label="区域名称" prop="name"> <el-form-item :label="t('system.region_name')" prop="name">
<el-input v-model="state.form.name" /> <el-input v-model="state.form.name" />
</el-form-item> </el-form-item>
<div class="geo-label-mask" /> <div class="geo-label-mask" />
<el-form-item label="坐标文件" prop="fileName"> <el-form-item :label="t('system.coordinate_file')" prop="fileName">
<el-upload <el-upload
class="upload-geo" class="upload-geo"
action="" action=""

View File

@ -6,6 +6,7 @@ import icon_dataset from '@/assets/svg/icon_dataset.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg' import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import icon_intoItem_outlined from '@/assets/svg/icon_into-item_outlined.svg' import icon_intoItem_outlined from '@/assets/svg/icon_into-item_outlined.svg'
import icon_rename_outlined from '@/assets/svg/icon_rename_outlined.svg' import icon_rename_outlined from '@/assets/svg/icon_rename_outlined.svg'
import icon_warning_colorful_red from '@/assets/svg/icon_warning_colorful_red.svg'
import dvFolder from '@/assets/svg/dv-folder.svg' import dvFolder from '@/assets/svg/dv-folder.svg'
import dvNewFolder from '@/assets/svg/dv-new-folder.svg' import dvNewFolder from '@/assets/svg/dv-new-folder.svg'
import icon_fileAdd_outlined from '@/assets/svg/icon_file-add_outlined.svg' import icon_fileAdd_outlined from '@/assets/svg/icon_file-add_outlined.svg'
@ -380,7 +381,6 @@ const initSearch = () => {
state.filterTable = tableData.value.filter(ele => state.filterTable = tableData.value.filter(ele =>
ele.tableName.toLowerCase().includes(nickName.value.toLowerCase()) ele.tableName.toLowerCase().includes(nickName.value.toLowerCase())
) )
console.log(tableData.value)
state.paginationConfig.total = state.filterTable.length state.paginationConfig.total = state.filterTable.length
} }
@ -867,7 +867,6 @@ const operation = (cmd: string, data: Tree, nodeType: string) => {
} }
const handleClick = (tabName: TabPaneName) => { const handleClick = (tabName: TabPaneName) => {
console.log(tabName)
switch (tabName) { switch (tabName) {
case 'config': case 'config':
listDatasourceTables({ datasourceId: nodeInfo.id }).then(res => { listDatasourceTables({ datasourceId: nodeInfo.id }).then(res => {
@ -1081,18 +1080,38 @@ const getMenuList = (val: boolean) => {
@node-click="handleNodeClick" @node-click="handleNodeClick"
> >
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node" style="position: relative">
<el-icon :class="data.leaf && 'icon-border'" style="font-size: 18px"> <el-icon :class="data.leaf && 'icon-border'" style="font-size: 18px">
<Icon :static-content="getDsIcon(data)" <Icon :static-content="getDsIcon(data)"
><component class="svg-icon" :is="getDsIconName(data)"></component ><component class="svg-icon" :is="getDsIconName(data)"></component
></Icon> ></Icon>
</el-icon> </el-icon>
<el-icon
style="position: absolute; top: 10px; left: 10px; font-size: 12px"
v-if="data.extraFlag <= -1"
>
<Icon><icon_warning_colorful_red class="svg-icon" /></Icon>
</el-icon>
<span <span
:title="node.label" :title="node.label"
class="label-tooltip ellipsis" class="label-tooltip ellipsis"
:class="data.type === 'Excel' && 'excel'" :class="data.type === 'Excel' && 'excel'"
v-if="data.extraFlag > -1"
>{{ node.label }}</span >{{ node.label }}</span
> >
<el-tooltip
effect="dark"
v-else
:content="`${t('data_set.invalid_data_source')}: ${node.label}`"
placement="top"
>
<span
:title="node.label"
class="label-tooltip ellipsis"
:class="data.type === 'Excel' && 'excel'"
>{{ node.label }}</span
>
</el-tooltip>
<div class="icon-more" v-if="data.weight >= 7"> <div class="icon-more" v-if="data.weight >= 7">
<handle-more <handle-more
icon-size="24px" icon-size="24px"

@ -1 +1 @@
Subproject commit 1087f588ed77a9fa2630c4bd92d71df65559793b Subproject commit 4b8fe0d27463e4a6e238f862ed09bdb3c9c1b871

View File

@ -176,8 +176,6 @@ function install_docker() {
cp docker/service/docker.service /etc/systemd/system/ cp docker/service/docker.service /etc/systemd/system/
chmod +x /usr/bin/docker* chmod +x /usr/bin/docker*
chmod 644 /etc/systemd/system/docker.service chmod 644 /etc/systemd/system/docker.service
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
else else
log_content "在线安装 docker" log_content "在线安装 docker"
curl -fsSL https://resource.fit2cloud.com/get-docker-linux.sh -o get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log curl -fsSL https://resource.fit2cloud.com/get-docker-linux.sh -o get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log
@ -186,13 +184,20 @@ function install_docker() {
exit 1 exit 1
fi fi
sudo sh get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log sudo sh get-docker.sh 2>&1 | tee -a ${CURRENT_DIR}/install.log
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
fi fi
docker_config_folder="/etc/docker" docker_config_folder="/etc/docker"
if [ ! -d "$docker_config_folder" ];then if [ ! -d "$docker_config_folder" ];then
mkdir -p "$docker_config_folder" mkdir -p "$docker_config_folder"
cat <<EOF> $docker_config_folder/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-file": "3",
"max-size": "10m"
}
}
EOF
fi fi
docker version >/dev/null 2>&1 docker version >/dev/null 2>&1
@ -201,6 +206,8 @@ function install_docker() {
exit 1 exit 1
else else
log_content "docker 安装成功" log_content "docker 安装成功"
log_content "启动 docker"
systemctl enable docker >/dev/null 2>&1; systemctl daemon-reload; systemctl start docker 2>&1 | tee -a ${CURRENT_DIR}/install.log
fi fi
fi fi
} }