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

This commit is contained in:
dataeaseShu 2024-06-19 15:07:32 +08:00
commit e69f17e549
49 changed files with 1436 additions and 251 deletions

View File

@ -214,6 +214,7 @@ public class ChartDataManage {
|| StringUtils.equalsIgnoreCase(view.getType(), "flow-map")
|| StringUtils.equalsIgnoreCase(view.getType(), "sankey")
|| StringUtils.containsIgnoreCase(view.getType(), "chart-mix")
|| StringUtils.equalsIgnoreCase(view.getType(), "symbolic-map")
) {
xAxis.addAll(xAxisExt);
}
@ -740,6 +741,28 @@ public class ChartDataManage {
ExtWhere2Str.extWhere2sqlOjb(sqlMeta, yoyFilterList, transFields(allFields), crossDs, dsMap);
yoySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
}
} else if (StringUtils.equalsIgnoreCase("symbolic-map", view.getType())) {
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields), crossDs, dsMap);
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields), crossDs, dsMap);
List<ChartViewFieldDTO> yFields = new ArrayList<>();
yFields.addAll(chartViewManege.transFieldDTO(Collections.singletonList(chartViewManege.createCountField(view.getTableId()))));
yFields.addAll(extBubble);
yAxis.addAll(yFields);
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields), crossDs, dsMap);
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
List<Long> xAxisIds = xAxis.stream().map(ChartViewFieldDTO::getId).toList();
viewFields.addAll(xAxis);
viewFields.addAll(allFields.stream().filter(field -> !xAxisIds.contains(field.getId())).toList());
if (ObjectUtils.isNotEmpty(viewFields)) {
detailFieldList.addAll(viewFields);
SQLMeta sqlMeta1 = new SQLMeta();
BeanUtils.copyBean(sqlMeta1, sqlMeta);
sqlMeta1.setYFields(new ArrayList<>());
Dimension2SQLObj.dimension2sqlObj(sqlMeta1, detailFieldList, transFields(allFields), crossDs, dsMap);
String originSql = SQLProvider.createQuerySQL(sqlMeta1, false, needOrder, view);
String limit = ((pageInfo.getGoPage() != null && pageInfo.getPageSize() != null) ? " LIMIT " + pageInfo.getPageSize() + " OFFSET " + (pageInfo.getGoPage() - 1) * pageInfo.getPageSize() : "");
detailFieldSql = originSql + limit;
}
} else {
Dimension2SQLObj.dimension2sqlObj(sqlMeta, xAxis, transFields(allFields), crossDs, dsMap);
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields), crossDs, dsMap);
@ -782,6 +805,7 @@ public class ChartDataManage {
}
if (StringUtils.isNotBlank(detailFieldSql)) {
detailFieldSql = SqlUtils.rebuildSQL(detailFieldSql, sqlMeta, crossDs, dsMap);
datasourceRequest.setQuery(detailFieldSql);
detailData = (List<String[]>) calciteProvider.fetchResultField(datasourceRequest).get("data");
}
@ -920,12 +944,12 @@ public class ChartDataManage {
List<String[]> resultData = new ArrayList<>();
for (String[] res1 : data) {
StringBuilder x1 = new StringBuilder();
for (int i = 0; i < xAxis.size() + xAxisExt.size(); i++) {
for (int i = 0; i < xAxis.size(); i++) {
x1.append(res1[i]);
}
for (String[] res2 : yoyData) {
StringBuilder x2 = new StringBuilder();
for (int i = 0; i < xAxis.size() + xAxisExt.size(); i++) {
for (int i = 0; i < xAxis.size(); i++) {
x2.append(res2[i]);
}
if (StringUtils.equals(x1, x2)) {

View File

@ -1193,7 +1193,7 @@ public class ChartDataBuild {
Map<String, Object> map = transTableNormal(fields, null, data, desensitizationList);
List<Map<String, Object>> tableRow = (List<Map<String, Object>>) map.get("tableRow");
final int xEndIndex = detailIndex;
Map<String, List<String[]>> groupDataList = detailData.stream().collect(Collectors.groupingBy(item -> "(" + StringUtils.join(ArrayUtils.subarray(item, 0, xEndIndex), "-de-") + ")"));
Map<String, List<String[]>> groupDataList = detailData.stream().collect(Collectors.groupingBy(item -> "(" + StringUtils.join(ArrayUtils.subarray(item, 0, xEndIndex), ")-de-(") + ")"));
tableRow.forEach(row -> {
String key = xAxis.stream().map(x -> String.format(format, row.get(x.getDataeaseName()).toString())).collect(Collectors.joining("-de-"));

View File

@ -2,17 +2,17 @@ package io.dataease.dataset.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.api.chart.dto.ColumnPermissionItem;
import io.dataease.extensions.view.model.SQLObj;
import io.dataease.dataset.dao.auto.entity.CoreDatasetTableField;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetGroupMapper;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetTableFieldMapper;
import io.dataease.dataset.utils.TableUtils;
import io.dataease.datasource.provider.CalciteProvider;
import io.dataease.extensions.view.dto.DatasetTableFieldDTO;
import io.dataease.engine.constant.ExtFieldConstant;
import io.dataease.engine.func.FunctionConstant;
import io.dataease.engine.utils.Utils;
import io.dataease.exception.DEException;
import io.dataease.extensions.view.dto.DatasetTableFieldDTO;
import io.dataease.extensions.view.model.SQLObj;
import io.dataease.i18n.Translator;
import io.dataease.utils.AuthUtils;
import io.dataease.utils.BeanUtils;
@ -160,6 +160,19 @@ public class DatasetTableFieldManage {
return transDTO(coreDatasetTableFieldMapper.selectList(wrapper));
}
public Map<String, List<DatasetTableFieldDTO>> selectByDatasetGroupIds(List<Long> ids) {
Map<String, List<DatasetTableFieldDTO>> map = new HashMap<>();
for (Long id : ids) {
QueryWrapper<CoreDatasetTableField> wrapper = new QueryWrapper<>();
wrapper.eq("dataset_group_id", id);
wrapper.eq("checked", true);
wrapper.isNull("chart_id");
wrapper.eq("ext_field", 0);
map.put(String.valueOf(id), transDTO(coreDatasetTableFieldMapper.selectList(wrapper)));
}
return map;
}
public List<DatasetTableFieldDTO> selectByFieldIds(List<Long> ids) {
QueryWrapper<CoreDatasetTableField> wrapper = new QueryWrapper<>();
wrapper.in("id", ids);

View File

@ -43,6 +43,11 @@ public class DatasetFieldServer implements DatasetTableApi {
return datasetTableFieldManage.selectByDatasetGroupId(id);
}
@Override
public Map<String, List<DatasetTableFieldDTO>> listByDsIds(List<Long> ids) {
return datasetTableFieldManage.selectByDatasetGroupIds(ids);
}
@Override
public void delete(Long id) {
datasetTableFieldManage.deleteById(id);

View File

@ -0,0 +1,7 @@
package io.dataease.datasource.provider;
/**
* @Author Junjun
*/
public abstract class Provider {
}

View File

@ -14,7 +14,7 @@ export default {
],
build: {
rollupOptions: {
external: id => /de-xpack/.test(id),
external: id => /de-xpack/.test(id) || /extensions-view-3dpie/.test(id),
output: {
// 用于命名代码拆分时创建的共享块的输出命名
chunkFileNames: `assets/chunk/[name]-${pkg.version}-${pkg.name}.js`,

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 742 KiB

View File

@ -47,6 +47,7 @@ const {
} = storeToRefs(dvMainStore)
const dvModel = 'dashboard'
const multiplexingRef = ref(null)
const fullScreeRef = ref(null)
let nameEdit = ref(false)
let inputName = ref('')
let nameInput = ref(null)
@ -97,7 +98,7 @@ const redo = () => {
}
const previewInner = () => {
dvMainStore.setEditMode('preview')
fullScreeRef.value.toggleFullscreen()
}
const previewOuter = () => {
@ -522,7 +523,12 @@ const initOpenHandler = newWindow => {
</el-button>
<template #dropdown>
<el-dropdown-menu class="drop-style">
<de-fullscreen :show-position="'edit'"></de-fullscreen>
<el-dropdown-item @click="previewInner">
<el-icon style="margin-right: 8px; font-size: 16px">
<Icon name="icon_pc_fullscreen" />
</el-icon>
全屏预览
</el-dropdown-item>
<el-dropdown-item @click="previewOuter()">
<el-icon style="margin-right: 8px; font-size: 16px">
<Icon name="dv-preview-outer" />
@ -615,6 +621,7 @@ const initOpenHandler = newWindow => {
/>
<outer-params-set ref="outerParamsSetRef"> </outer-params-set>
</div>
<de-fullscreen show-position="edit" ref="fullScreeRef"></de-fullscreen>
<XpackComponent ref="openHandler" jsname="L2NvbXBvbmVudC9lbWJlZGRlZC1pZnJhbWUvT3BlbkhhbmRsZXI=" />
</template>

View File

@ -94,6 +94,13 @@ onMounted(() => {
useEmitt().emitter.emit('initScroll')
})
}, 1000)
useEmitt({
name: 'canvasScrollRestore',
callback: function () {
//
changeSizeWithScale(scale.value)
}
})
})
onUnmounted(() => {

View File

@ -23,6 +23,7 @@ import ComponentButton from '@/components/visualization/ComponentButton.vue'
import OuterParamsSet from '@/components/visualization/OuterParamsSet.vue'
import MultiplexingCanvas from '@/views/common/MultiplexingCanvas.vue'
import ComponentButtonLabel from '@/components/visualization/ComponentButtonLabel.vue'
import DeFullscreen from '@/components/visualization/common/DeFullscreen.vue'
let nameEdit = ref(false)
let inputName = ref('')
let nameInput = ref(null)
@ -36,6 +37,7 @@ let scaleEdit = 100
const { wsCache } = useCache('localStorage')
const dvModel = 'dataV'
const outerParamsSetRef = ref(null)
const fullScreeRef = ref(null)
const closeEditCanvasName = () => {
nameEdit.value = false
@ -303,7 +305,12 @@ const multiplexingCanvasOpen = () => {
>
编辑
</el-button>
<el-button v-else class="preview-button" @click="preview()" style="float: right">
<el-button
v-else
class="preview-button"
@click="() => fullScreeRef.toggleFullscreen()"
style="float: right"
>
预览
</el-button>
<el-button
@ -335,6 +342,7 @@ const multiplexingCanvasOpen = () => {
ref="resourceGroupOpt"
/>
</div>
<de-fullscreen ref="fullScreeRef" show-position="dvEdit"></de-fullscreen>
<multiplexing-canvas ref="multiplexingRef"></multiplexing-canvas>
<outer-params-set ref="outerParamsSetRef"> </outer-params-set>
<XpackComponent ref="openHandler" jsname="L2NvbXBvbmVudC9lbWJlZGRlZC1pZnJhbWUvT3BlbkhhbmRsZXI=" />

View File

@ -294,7 +294,15 @@ const active = computed(() => {
})
const boardMoveActive = computed(() => {
const CHARTS = ['flow-map', 'map', 'bubble-map', 'table-info', 'table-normal', 'table-pivot']
const CHARTS = [
'flow-map',
'map',
'bubble-map',
'table-info',
'table-normal',
'table-pivot',
'symbolic-map'
]
return CHARTS.includes(element.value.innerType)
})

View File

@ -33,10 +33,15 @@ const generateRamStr = (len: number) => {
}
const importProxy = (bytesArray: any[]) => {
const promise = import(
/* const promise = import(
`../../../../../../../${formatArray(bytesArray[7])}/${formatArray(bytesArray[8])}/${formatArray(
bytesArray[9]
)}/${formatArray(bytesArray[10])}/${formatArray(bytesArray[11])}.vue`
) */
const promise = import(
`../../../../../../../extensions-view-3dpie/${formatArray(bytesArray[8])}/${formatArray(
bytesArray[9]
)}/${formatArray(bytesArray[10])}/${formatArray(bytesArray[11])}.vue`
)
promise
.then((res: any) => {

View File

@ -40,17 +40,32 @@
>仅看已选 <el-switch size="small" v-model="state.showSelected" />
</span>
</el-row>
<el-row class="tree-dataset-head" v-show="sameDsShow"
><span
><el-icon class="toggle-icon" @click="() => (toggleSameDs = !toggleSameDs)">
<CaretBottom v-show="toggleSameDs" />
<CaretRight v-show="!toggleSameDs" /> </el-icon
><span>同数据集</span></span
>
<el-checkbox
v-model="sameDatasetComponentCheckAll"
:indeterminate="checkAllIsIndeterminate"
@change="batchSelectChange"
>全选</el-checkbox
></el-row
>
<el-tree
v-show="toggleSameDs && sameDsShow"
class="custom-tree"
menu
ref="linkageInfoTree"
:empty-text="'暂无可用图表'"
:filter-node-method="filterNodeMethod"
:data="curLinkageTargetViewsInfo"
:data="curLinkageTargetViewsInfoSameDs"
node-key="targetViewId"
highlight-current
:props="state.treeProp"
@node-click="nodeClick"
@node-click="nodeClickPre($event, 'sameDs')"
>
<template #default="{ data }">
<span class="custom-tree-node">
@ -60,7 +75,54 @@
<!---->
<el-checkbox
v-model="data.linkageActive"
@change="targetViewCheckedChange(data)"
@change="targetViewCheckedChange('sameDs', data)"
/>
</span>
</div>
</span>
<span>
<span class="tree-select-field">
<Icon
class-name="view-type-icon"
style="margin-right: 4px"
:name="data.targetViewType"
/>
{{ data.targetViewName }}
</span>
</span>
</span>
</template>
</el-tree>
<el-row class="tree-dataset-head tree-dataset-head-top" v-show="diffDsShow"
><span
><el-icon class="toggle-icon" @click="() => (toggleDiffDs = !toggleDiffDs)">
<CaretBottom v-show="toggleDiffDs" />
<CaretRight v-show="!toggleDiffDs" /> </el-icon
><span>不同数据集</span></span
>
</el-row>
<el-tree
v-show="toggleDiffDs && diffDsShow"
class="custom-tree"
menu
ref="linkageInfoTreeDiffDs"
:empty-text="'暂无可用图表'"
:filter-node-method="filterNodeMethod"
:data="curLinkageTargetViewsInfoDiffDs"
node-key="targetViewId"
highlight-current
:props="state.treeProp"
@node-click="nodeClickPre($event, 'diffDs')"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span>
<div @click.stop>
<span class="auth-span">
<!---->
<el-checkbox
v-model="data.linkageActive"
@change="targetViewCheckedChange('diffDs', data)"
/>
</span>
</div>
@ -208,10 +270,13 @@ import { ACTION_SELECTION } from '@/custom-component/component-list'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, canvasViewInfo, componentData, curComponent } = storeToRefs(dvMainStore)
const linkageInfoTree = ref(null)
const linkageInfoTreeDiffDs = ref(null)
const { t } = useI18n()
const dialogShow = ref(false)
const loading = ref(false)
const curLinkageTargetViewsInfo = ref([])
const curLinkageTargetViewsInfoSameDs = ref([])
const curLinkageTargetViewsInfoDiffDs = ref([])
const snapshotStore = snapshotStoreWithOut()
const state = reactive({
sourceLinkageInfo: {},
@ -220,6 +285,7 @@ const state = reactive({
curDatasetInfo: {},
initState: false,
viewId: null,
tableId: null,
treeProp: {
id: 'targetViewId',
label: 'targetViewName',
@ -227,9 +293,48 @@ const state = reactive({
},
linkageInfo: null
})
const sameDatasetComponentCheckAll = ref(false)
const checkAllIsIndeterminate = ref(false)
const customLinkageActive = ref(deepCopy(ACTION_SELECTION))
const toggleSameDs = ref(true)
const toggleDiffDs = ref(true)
const sameDsTreeSelectedChange = () => {
const checkedCount = curLinkageTargetViewsInfoSameDs.value.filter(
viewInfo => viewInfo.linkageActive
).length
sameDatasetComponentCheckAll.value = checkedCount === curLinkageTargetViewsInfoSameDs.value.length
checkAllIsIndeterminate.value =
checkedCount > 0 && checkedCount < curLinkageTargetViewsInfoSameDs.value.length
}
const batchSelectChange = value => {
// do change
curLinkageTargetViewsInfoSameDs.value.forEach(viewInfo => {
if (value) {
viewInfo.linkageActive = true
sameDatasetComponentCheckAll.value = true
linkageFieldAdaptor(viewInfo)
} else {
viewInfo.linkageActive = false
sameDatasetComponentCheckAll.value = false
}
})
checkAllIsIndeterminate.value = false
}
const sameDsShow = computed(
() => curLinkageTargetViewsInfoSameDs.value && curLinkageTargetViewsInfoSameDs.value.length > 0
)
const diffDsShow = computed(
() => curLinkageTargetViewsInfoDiffDs.value && curLinkageTargetViewsInfoDiffDs.value.length > 0
)
const dialogInit = viewItem => {
state.showSelected = false
dialogShow.value = true
@ -260,16 +365,36 @@ const linkageSetting = curViewId => {
curLinkageTargetViewsInfo.value = curLinkageTargetViewsInfo.value.filter(
viewInfo => viewInfo.targetViewId !== state.viewId
)
curLinkageTargetViewsInfoSameDs.value = curLinkageTargetViewsInfo.value.filter(
viewInfo => viewInfo.tableId === state.tableId
)
curLinkageTargetViewsInfoDiffDs.value = curLinkageTargetViewsInfo.value.filter(
viewInfo => viewInfo.tableId !== state.tableId
)
let firstNode
if (curLinkageTargetViewsInfo.value && curLinkageTargetViewsInfo.value.length > 0) {
firstNode = curLinkageTargetViewsInfo.value[0]
let linkageTreeName
if (curLinkageTargetViewsInfoSameDs.value && curLinkageTargetViewsInfoSameDs.value.length > 0) {
firstNode = curLinkageTargetViewsInfoSameDs.value[0]
linkageTreeName = 'sameDs'
} else if (
curLinkageTargetViewsInfoDiffDs.value &&
curLinkageTargetViewsInfoDiffDs.value.length > 0
) {
firstNode = curLinkageTargetViewsInfoDiffDs.value[0]
linkageTreeName = 'diffDs'
}
state.initState = true
nextTick(() => {
if (firstNode) {
linkageInfoTree.value.setCurrentKey(firstNode.targetViewId)
const linkageTree =
linkageTreeName === 'sameDs' ? linkageInfoTree.value : linkageInfoTreeDiffDs.value
linkageTree.setCurrentKey(firstNode.targetViewId)
}
nodeClick(firstNode)
sameDsTreeSelectedChange()
})
})
}
@ -281,6 +406,7 @@ const init = viewItem => {
const chartDetails = canvasViewInfo.value[state.viewId]
state.curLinkageViewInfo = chartDetails
if (chartDetails.tableId) {
state.tableId = chartDetails.tableId
//
getDatasetDetails(chartDetails.tableId).then(res => {
state.curDatasetInfo = res || {}
@ -348,10 +474,29 @@ const cancelLinkageSetting = () => {
dvMainStore.clearLinkageSettingInfo()
}
const nodeClickPre = (data, treeName) => {
if (treeName === 'sameDs') {
linkageInfoTree.value.setCurrentKey(data.targetViewId)
linkageInfoTreeDiffDs.value.setCurrentKey(null)
} else {
linkageInfoTree.value.setCurrentKey(null)
linkageInfoTreeDiffDs.value.setCurrentKey(data.targetViewId)
}
nodeClick(data)
}
const nodeClick = data => {
state.linkageInfo = data
}
const addLinkageFieldAdaptor = (data, sourceFieldId?, targetFieldId?) => {
const linkageFieldItem = {
sourceField: sourceFieldId,
targetField: targetFieldId
}
data.linkageFields.push(linkageFieldItem)
}
const addLinkageField = (sourceFieldId?, targetFieldId?) => {
const linkageFieldItem = {
sourceField: sourceFieldId,
@ -369,18 +514,25 @@ const linkageFieldAdaptor = async data => {
const targetChartDetails = canvasViewInfo.value[data.targetViewId]
if (targetChartDetails && targetChartDetails.tableId && data.linkageFields.length === 0) {
if (state.curLinkageViewInfo.tableId === targetChartDetails.tableId) {
const curCheckAllAxisStr =
JSON.stringify(state.curLinkageViewInfo.xAxis) +
JSON.stringify(state.curLinkageViewInfo.xAxisExt)
const targetCheckAllAxisStr =
JSON.stringify(targetChartDetails.xAxis) + JSON.stringify(targetChartDetails.xAxisExt)
state.sourceLinkageInfo.targetViewFields.forEach(item => {
if (curCheckAllAxisStr.includes(item.id) && targetCheckAllAxisStr.includes(item.id)) {
addLinkageField(item.id, item.id)
}
})
// 0
if (data.linkageFields && data.linkageFields.length === 0) {
const curCheckAllAxisStr =
JSON.stringify(state.curLinkageViewInfo.xAxis) +
JSON.stringify(state.curLinkageViewInfo.xAxisExt)
const targetCheckAllAxisStr =
JSON.stringify(targetChartDetails.xAxis) + JSON.stringify(targetChartDetails.xAxisExt)
state.sourceLinkageInfo.targetViewFields.forEach(item => {
if (
curCheckAllAxisStr.includes(item.id) &&
targetCheckAllAxisStr.includes(item.id) &&
data.linkageFields
) {
addLinkageFieldAdaptor(data, item.id, item.id)
}
})
}
} else {
addLinkageField('', '')
addLinkageFieldAdaptor(data, '', '')
}
}
}
@ -399,11 +551,18 @@ const sourceLinkageInfoFilter = computed(() => {
}
})
const targetViewCheckedChange = data => {
const targetViewCheckedChange = (treeName, data) => {
nextTick(() => {
linkageInfoTree.value.setCurrentKey(data.targetViewId)
if (treeName === 'sameDs') {
linkageInfoTree.value.setCurrentKey(data.targetViewId)
linkageInfoTreeDiffDs.value.setCurrentKey(null)
} else {
linkageInfoTree.value.setCurrentKey(null)
linkageInfoTreeDiffDs.value.setCurrentKey(data.targetViewId)
}
nodeClick(data)
linkageFieldAdaptor(data)
sameDsTreeSelectedChange()
})
}
const cancel = () => {
@ -419,6 +578,7 @@ watch(
() => state.showSelected,
newValue => {
linkageInfoTree.value?.filter(newValue)
linkageInfoTreeDiffDs.value?.filter(newValue)
}
)
@ -734,7 +894,7 @@ span {
}
.custom-tree {
height: 100%;
max-height: 100%;
overflow-y: auto;
}
.m-del-icon-btn {
@ -758,4 +918,27 @@ span {
display: flex;
align-items: center;
}
.tree-dataset-head {
height: 40px;
font-size: 14px;
align-items: center;
padding: 0 14px;
justify-content: space-between;
span {
font-size: 14px;
font-weight: 400;
text-align: left;
color: #646a73;
}
}
.tree-dataset-head-top {
border-top: 1px solid rgba(31, 35, 41, 0.15);
}
.toggle-icon {
cursor: pointer;
margin-right: 8px;
}
</style>

View File

@ -175,7 +175,8 @@ const state = reactive({
'label',
'word-cloud',
'flow-map',
'bidirectional-bar'
'bidirectional-bar',
'symbolic-map'
],
linkageExcludeViewType: [
'richTextView',
@ -185,7 +186,8 @@ const state = reactive({
'label',
'word-cloud',
'flow-map',
'bidirectional-bar'
'bidirectional-bar',
'symbolic-map'
],
copyData: null,
hyperlinksSetVisible: false,

View File

@ -4,6 +4,7 @@ import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
const dvMainStore = dvMainStoreWithOut()
import screenfull from 'screenfull'
import { onBeforeUnmount, onMounted, toRefs } from 'vue'
import { useEmitt } from '@/hooks/web/useEmitt'
const props = defineProps({
themes: {
@ -25,6 +26,18 @@ const { themes, componentType } = toRefs(props)
const fullscreenChange = () => {
if (screenfull.isEnabled) {
dvMainStore.setFullscreenFlag(screenfull.isFullscreen)
// 使
if (props.showPosition === 'edit') {
if (screenfull.isFullscreen) {
dvMainStore.setEditMode('preview')
} else {
dvMainStore.setEditMode('edit')
}
}
// 使
if (props.showPosition === 'dvEdit') {
useEmitt().emitter.emit('canvasScrollRestore')
}
}
}
@ -35,11 +48,6 @@ const toggleFullscreen = () => {
}
}
const editToggleFullscreen = () => {
dvMainStore.setEditMode('preview')
toggleFullscreen()
}
onMounted(() => {
if (screenfull.isEnabled) {
screenfull.on('change', fullscreenChange)
@ -49,21 +57,12 @@ onMounted(() => {
onBeforeUnmount(() => {
screenfull.off('change', fullscreenChange)
})
defineExpose({
toggleFullscreen
})
</script>
<template>
<el-button v-if="showPosition === 'preview'" secondary @click="toggleFullscreen">
<template #icon>
<icon name="icon_pc_fullscreen"></icon>
</template>
全屏</el-button
>
<el-dropdown-item v-else @click="editToggleFullscreen()">
<el-icon style="margin-right: 8px; font-size: 16px">
<icon name="icon_pc_fullscreen"></icon>
</el-icon>
全屏预览
</el-dropdown-item>
</template>
<template><span></span></template>
<style lang="less" scoped></style>

View File

@ -1229,7 +1229,12 @@ export default {
progress_current: '实际值',
gauge_axis_label: '显示刻度',
gauge_percentage_tick: '百分比刻度',
add_style: '添加样式'
add_style: '添加样式',
map_symbol_marker: '标记',
map_symbol_pentagon: '五角形',
map_symbol_hexagon: '六角形',
map_symbol_octagon: '八角形',
map_symbol_hexagram: '菱形'
},
dataset: {
scope_edit: '仅编辑时生效',

View File

@ -698,6 +698,18 @@ declare interface ChartLabelAttr {
*/
seriesLabelFormatter: SeriesFormatter[]
/**
* 显示字段通过字段名称显示对应的值
* @example
* ['name', 'value']
*/
showFields?: string[]
/**
* 自定义显示内容
*/
customContent?: string
showGap?: boolean
}
/**
@ -732,6 +744,18 @@ declare interface ChartTooltipAttr {
seriesTooltipFormatter: SeriesFormatter[]
showGap?: boolean
/**
* 显示字段通过字段名称显示对应的值
* @example
* ['name', 'value']
*/
showFields?: string[]
/**
* 自定义显示内容
*/
customContent?: string
}
/**

View File

@ -24,6 +24,7 @@ declare type EditorProperty =
| 'indicator-value-selector'
| 'indicator-name-selector'
| 'quadrant-selector'
| 'map-symbolic-selector'
declare type EditorPropertyInner = {
[key in EditorProperty]?: string[]
}

View File

@ -4,7 +4,8 @@ import { deepCopy } from '@/utils/utils'
import {
BASE_VIEW_CONFIG,
DEFAULT_INDICATOR_NAME_STYLE,
DEFAULT_INDICATOR_STYLE
DEFAULT_INDICATOR_STYLE,
SENIOR_STYLE_SETTING_LIGHT
} from '@/views/chart/components/editor/util/chart'
import {
DEFAULT_CANVAS_STYLE_DATA_DARK,
@ -224,6 +225,8 @@ export const dvMainStore = defineStore('dataVisualization', {
},
setCanvasStyle(style) {
style.component['seniorStyleSetting'] =
style.component['seniorStyleSetting'] || deepCopy(SENIOR_STYLE_SETTING_LIGHT)
this.canvasStyleData = style
},
setCanvasViewInfo(canvasViewInfo) {

View File

@ -20,7 +20,7 @@ const props = defineProps({
v-else-if="
props.view.type &&
(includesAny(props.view.type, 'bar', 'line', 'scatter') ||
equalsAny(props.view.type, 'waterfall', 'area', 'area-stack', 'flow-map'))
equalsAny(props.view.type, 'waterfall', 'area', 'area-stack', 'flow-map', 'symbolic-map'))
"
>{{ t('chart.drag_block_value_axis') }}</span
>

View File

@ -75,6 +75,10 @@ const props = defineProps({
default: () => {
return {}
}
},
allFields: {
type: Array,
required: true
}
})
@ -350,6 +354,7 @@ watch(
:themes="themes"
class="attr-selector"
:chart="chart"
:all-fields="props.allFields"
@onLabelChange="onLabelChange"
/>
</collapse-switch-item>
@ -368,6 +373,7 @@ watch(
:property-inner="propertyInnerAll['tooltip-selector']"
:themes="themes"
:chart="chart"
:all-fields="props.allFields"
@onTooltipChange="onTooltipChange"
@onExtTooltipChange="onExtTooltipChange"
/>

View File

@ -198,6 +198,16 @@ const flowLineTypeOptions = [
{ name: t('chart.map_line_type_arc_3d'), value: 'arc3d' }
]
const mapSymbolOptions = [
{ name: t('chart.line_symbol_circle'), value: 'circle' },
{ name: t('chart.line_symbol_rect'), value: 'square' },
{ name: t('chart.line_symbol_triangle'), value: 'triangle' },
{ name: t('chart.map_symbol_pentagon'), value: 'pentagon' },
{ name: t('chart.map_symbol_hexagon'), value: 'hexagon' },
{ name: t('chart.map_symbol_octagon'), value: 'octogon' },
{ name: t('chart.line_symbol_diamond'), value: 'rhombus' }
]
onMounted(() => {
init()
})
@ -326,7 +336,7 @@ onMounted(() => {
/>
</el-select>
</el-form-item>
<div class="map-style" v-if="showProperty('mapStyle') || showProperty('heatMapStyle')">
<div class="map-style" v-if="showProperty('mapBaseStyle') || showProperty('heatMapStyle')">
<el-row style="flex: 1">
<el-col>
<el-form-item
@ -337,7 +347,7 @@ onMounted(() => {
<el-select
:effect="themes"
v-model="state.basicStyleForm.mapStyle"
@change="changeBasicStyle('mapStyle')"
@change="changeBasicStyle('mapBaseStyle')"
>
<el-option
v-for="item in mapStyleOptions"
@ -368,7 +378,7 @@ onMounted(() => {
</el-row>
</div>
</div>
<div class="map-flow-style" v-if="showProperty('mapStyle')">
<div class="map-flow-style" v-if="showProperty('mapLineStyle')">
<el-row style="flex: 1">
<el-col>
<el-form-item
@ -588,6 +598,80 @@ onMounted(() => {
</el-col>
</el-row>
</div>
<div class="map-flow-style" v-if="showProperty('symbolicMapStyle')">
<el-row style="flex: 1">
<el-col>
<el-form-item :label="'符号形状'" class="form-item" :class="'form-item-' + themes">
<el-select
:effect="themes"
v-model="state.basicStyleForm.mapSymbol"
@change="changeBasicStyle('mapSymbol')"
>
<el-option
v-for="item in mapSymbolOptions"
:key="item.name"
:label="item.name"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.size') }}
</label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="40"
v-model="state.basicStyleForm.mapSymbolSize"
@change="changeBasicStyle('mapSymbolSize')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('chart.not_alpha') }}
</label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="10"
v-model="state.basicStyleForm.mapSymbolOpacity"
@change="changeBasicStyle('mapSymbolOpacity')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="alpha-setting">
<label class="alpha-label" :class="{ dark: 'dark' === themes }">
{{ t('visualization.borderWidth') }}
</label>
<el-row style="flex: 1">
<el-col>
<el-form-item class="form-item alpha-slider" :class="'form-item-' + themes">
<el-slider
:effect="themes"
:min="1"
:max="5"
v-model="state.basicStyleForm.mapSymbolStrokeWidth"
@change="changeBasicStyle('mapSymbolStrokeWidth')"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
<!--flow map end-->
<!--map start-->
<el-row :gutter="8">

View File

@ -1,14 +1,16 @@
<script lang="ts" setup>
import { computed, onMounted, PropType, reactive, ref, watch } from 'vue'
import { computed, inject, onMounted, PropType, reactive, ref, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL, DEFAULT_LABEL } from '@/views/chart/components/editor/util/chart'
import { ElSpace } from 'element-plus-secondary'
import { ElIcon, ElSpace } from 'element-plus-secondary'
import { formatterType, unitType } from '../../../js/formatter'
import { defaultsDeep, cloneDeep, intersection, union, defaultTo } from 'lodash-es'
import { includesAny } from '../../util/StringUtils'
import { fieldType } from '@/utils/attr'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import Icon from '../../../../../../components/icon-custom/src/Icon.vue'
import { deepCopy } from '@/utils/utils'
const { t } = useI18n()
@ -17,10 +19,22 @@ const props = defineProps({
type: Object as PropType<ChartObj>,
required: true
},
dimensionData: {
type: Array<any>,
required: false
},
quotaData: {
type: Array<any>,
required: false
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
allFields: {
type: Array<any>,
required: false
},
propertyInner: {
type: Array<string>
}
@ -48,6 +62,7 @@ watch(
},
{ deep: true }
)
const curSeriesFormatter = ref<Partial<SeriesFormatter>>({})
const formatterEditable = computed(() => {
return showProperty('seriesLabelFormatter') && yAxis.value?.length
@ -150,7 +165,6 @@ const state = reactive<{ labelForm: ChartLabelAttr | any }>({
})
const emit = defineEmits(['onLabelChange'])
const changeLabelAttr = prop => {
emit('onLabelChange', state.labelForm, prop)
}
@ -263,6 +277,25 @@ watch(
{ deep: true }
)
const allFields = computed(() => {
return defaultTo(props.allFields, []).map(item => ({
key: item.dataeaseName,
name: item.name,
value: `${item.dataeaseName}@${item.name}`,
disabled: false
}))
})
const defaultPlaceholder = computed(() => {
if (state.labelForm.showFields && state.labelForm.showFields.length > 0) {
return state.labelForm.showFields
.map(field => {
return '${' + field.split('@')[1] + '}'
})
.join(',')
}
return ''
})
onMounted(() => {
init()
})
@ -275,7 +308,7 @@ onMounted(() => {
:model="state.labelForm"
label-position="top"
>
<el-row v-show="showEmpty" style="margin-bottom: 12px"> 无其他可设置的属性 </el-row>
<el-row v-show="showEmpty" style="margin-bottom: 12px"> 无其他可设置的属性</el-row>
<el-space>
<el-form-item
class="form-item"
@ -317,10 +350,56 @@ onMounted(() => {
</el-tooltip>
</el-form-item>
</el-space>
<div v-if="showProperty('showFields')">
<el-form-item :label="t('chart.label')" class="form-item" :class="'form-item-' + themes">
<el-select
size="small"
:effect="themes"
filterable
multiple
collapse-tags
collapse-tags-tooltip
v-model="state.labelForm.showFields"
@change="changeLabelAttr('showFields')"
>
<el-option
v-for="option in allFields"
:key="option.key"
:label="option.name"
:value="option.value"
:disabled="option.disabled"
/>
</el-select>
</el-form-item>
<el-form-item v-if="showProperty('customContent')" class="form-item">
<template #label>
<span class="data-area-label">
<span>
{{ t('chart.content_formatter') }}
</span>
<el-tooltip class="item" :effect="toolTip" placement="bottom">
<template #content>
<div>可以${fieldName}的形式读取字段值不支持换行</div>
</template>
<el-icon class="hint-icon" :class="{ 'hint-icon--dark': themes === 'dark' }">
<Icon name="icon_info_outlined" />
</el-icon>
</el-tooltip>
</span>
</template>
<el-input
style="font-size: smaller; font-weight: normal"
v-model="state.labelForm.customContent"
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
:placeholder="defaultPlaceholder"
@blur="changeLabelAttr('customContent')"
/>
</el-form-item>
</div>
<el-form-item
v-if="showProperty('rPosition')"
:label="t('chart.label_position')"
:label="t('chart.label')"
class="form-item"
:class="'form-item-' + themes"
>
@ -876,22 +955,26 @@ onMounted(() => {
.form-item-checkbox {
margin-bottom: 8px !important;
}
.series-select {
:deep(.ed-select__prefix--light) {
padding-right: unset;
border-right: unset;
}
:deep(.ed-select__prefix--dark) {
padding-right: unset;
border-right: unset;
}
}
.series-select-option {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 11px;
}
.m-divider {
margin: 0 0 16px;
border-color: rgba(31, 35, 41, 0.15);

View File

@ -2,16 +2,18 @@
import { PropType, computed, onMounted, reactive, watch, ref, inject } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL, DEFAULT_TOOLTIP } from '@/views/chart/components/editor/util/chart'
import { ElSpace } from 'element-plus-secondary'
import { ElIcon, ElSpace } from 'element-plus-secondary'
import cloneDeep from 'lodash-es/cloneDeep'
import defaultsDeep from 'lodash-es/defaultsDeep'
import { formatterType, unitType } from '../../../js/formatter'
import { fieldType } from '@/utils/attr'
import { partition } from 'lodash-es'
import { defaultTo, partition } from 'lodash-es'
import chartViewManager from '../../../js/panel'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { useEmitt } from '@/hooks/web/useEmitt'
import Icon from '../../../../../../components/icon-custom/src/Icon.vue'
import { deepCopy } from '@/utils/utils'
const { t } = useI18n()
@ -24,6 +26,10 @@ const props = defineProps({
type: String as PropType<EditorTheme>,
default: 'dark'
},
allFields: {
type: Array<any>,
required: false
},
propertyInner: {
type: Array<string>
}
@ -40,6 +46,7 @@ const quotaData = ref<Axis[]>(inject('quotaData'))
const showSeriesTooltipFormatter = computed(() => {
return showProperty('seriesTooltipFormatter') && !batchOptStatus.value && props.chart.id
})
//
const changeChartType = () => {
if (!showSeriesTooltipFormatter.value) {
@ -89,6 +96,7 @@ const changeDataset = () => {
}
})
}
const AXIS_PROP: AxisType[] = ['yAxis', 'yAxisExt', 'extBubble']
const quotaAxis = computed(() => {
let result = []
@ -356,6 +364,20 @@ const updateAxis = (form: AxisEditForm) => {
}
})
}
const allFields = computed(() => {
return defaultTo(props.allFields, [])
})
const defaultPlaceholder = computed(() => {
if (state.tooltipForm.showFields && state.tooltipForm.showFields.length > 0) {
return state.tooltipForm.showFields
.map(field => {
const v = field.split('@')
return v[1] + ': ${' + field.split('@')[1] + '}'
})
.join('\n')
}
return ''
})
onMounted(() => {
init()
useEmitt({ name: 'addAxis', callback: updateSeriesTooltipFormatter })
@ -374,6 +396,7 @@ onMounted(() => {
label-position="top"
>
<el-form-item
v-if="props.chart.type !== 'symbolic-map'"
:label="t('chart.background') + t('chart.color')"
class="form-item"
:class="'form-item-' + themes"
@ -430,6 +453,54 @@ onMounted(() => {
</el-tooltip>
</el-form-item>
</el-space>
<div v-if="showProperty('showFields')">
<el-form-item :label="t('chart.tooltip')" class="form-item" :class="'form-item-' + themes">
<el-select
size="small"
:effect="themes"
filterable
multiple
collapse-tags
collapse-tags-tooltip
v-model="state.tooltipForm.showFields"
@change="changeTooltipAttr('showFields')"
>
<el-option
v-for="option in allFields"
:key="option.dataeaseName"
:label="option.name"
:value="option.dataeaseName + '@' + option.name"
/>
</el-select>
</el-form-item>
<el-form-item v-if="showProperty('customContent')" class="form-item">
<template #label>
<span class="data-area-label">
<span>
{{ t('chart.content_formatter') }}
</span>
<el-tooltip class="item" :effect="toolTip" placement="bottom">
<template #content>
<div>可以${fieldName}的形式读取字段值支持HTML</div>
</template>
<el-icon class="hint-icon" :class="{ 'hint-icon--dark': themes === 'dark' }">
<Icon name="icon_info_outlined" />
</el-icon>
</el-tooltip>
</span>
</template>
<el-input
style="font-size: smaller; font-weight: normal"
v-model="state.tooltipForm.customContent"
type="textarea"
:autosize="{ minRows: 4, maxRows: 4 }"
:placeholder="defaultPlaceholder"
@blur="changeTooltipAttr('customContent')"
/>
</el-form-item>
</div>
<template v-if="showProperty('tooltipFormatter') && !isBarRangeTime">
<el-form-item
:label="t('chart.value_formatter_type')"

View File

@ -761,7 +761,9 @@ const calcData = (view, resetDrill = false, updateQuery = '') => {
})
}
}
const updateChartDataTest = arg => {
updateChartData(arg)
}
const updateChartData = view => {
curComponent.value['state'] = 'ready'
calcData(view, true, 'updateQuery')
@ -1608,7 +1610,7 @@ const deleteChartFieldItem = id => {
:dimension="state.dimension"
:quota="state.quota"
:themes="themes"
@update-chart-data="updateChartData"
@update-chart-data-test="updateChartDataTest"
/>
<el-tabs
v-else
@ -2458,6 +2460,7 @@ const deleteChartFieldItem = id => {
:themes="themes"
:dimension-data="state.dimension"
:quota-data="state.quota"
:all-fields="allFields"
@onColorChange="onColorChange"
@onMiscChange="onMiscChange"
@onLabelChange="onLabelChange"

View File

@ -1325,6 +1325,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'heat-map',
title: t('chart.chart_heat_map'),
icon: 'heat-map'
},
{
render: 'antv',
category: 'map',
value: 'symbolic-map',
title: '符号地图',
icon: 'symbolic-map'
}
]
},
@ -1454,7 +1461,7 @@ export const DEFAULT_BASIC_STYLE: ChartBasicStyle = {
mapSymbolOpacity: 0.7,
mapSymbolStrokeWidth: 2,
mapSymbol: 'circle',
mapSymbolSize: 20,
mapSymbolSize: 6,
radius: 80,
innerRadius: 60,
showZoom: true,

View File

@ -25,7 +25,7 @@ export class FlowMap extends L7ChartView<Scene, L7Config> {
]
propertyInner: EditorPropertyInner = {
...MAP_EDITOR_PROPERTY_INNER,
'basic-style-selector': ['mapStyle', 'zoom']
'basic-style-selector': ['mapBaseStyle', 'mapLineStyle', 'zoom']
}
axis: AxisType[] = ['xAxis', 'xAxisExt', 'filter']
axisConfig: AxisConfig = {

View File

@ -0,0 +1,339 @@
import { useI18n } from '@/hooks/web/useI18n'
import {
L7ChartView,
L7Config,
L7DrawConfig,
L7Wrapper
} from '@/views/chart/components/js/panel/types/impl/l7'
import { MAP_EDITOR_PROPERTY_INNER } from '@/views/chart/components/js/panel/charts/map/common'
import { flow, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
import { deepCopy } from '@/utils/utils'
import { GaodeMap } from '@antv/l7-maps'
import { Scene } from '@antv/l7-scene'
import { PointLayer } from '@antv/l7-layers'
import { LayerPopup } from '@antv/l7'
import { queryMapKeyApi } from '@/api/setting/sysParameter'
const { t } = useI18n()
/**
* 符号地图
*/
export class SymbolicMap extends L7ChartView<Scene, L7Config> {
properties: EditorProperty[] = [
'background-overall-component',
'basic-style-selector',
'title-selector',
'label-selector',
'tooltip-selector',
'jump-set',
'linkage'
]
propertyInner: EditorPropertyInner = {
...MAP_EDITOR_PROPERTY_INNER,
'basic-style-selector': ['colors', 'alpha', 'mapBaseStyle', 'symbolicMapStyle', 'zoom'],
'label-selector': ['color', 'fontSize', 'showFields', 'customContent'],
'tooltip-selector': ['color', 'fontSize', 'showFields', 'customContent', 'show']
}
axis: AxisType[] = ['xAxis', 'xAxisExt', 'extBubble', 'filter', 'extLabel', 'extTooltip']
axisConfig: AxisConfig = {
xAxis: {
name: `经纬度 / ${t('chart.dimension')}`,
type: 'd',
limit: 2
},
xAxisExt: {
name: `颜色 / ${t('chart.dimension')}`,
type: 'd',
limit: 1
},
extBubble: {
name: `${t('chart.bubble_size')} / ${t('chart.quota')}`,
type: 'q',
limit: 1
}
}
constructor() {
super('symbolic-map', [])
}
async drawChart(drawOption: L7DrawConfig<L7Config>) {
const { chart, container, action } = drawOption
const xAxis = deepCopy(chart.xAxis)
const xAxisExt = deepCopy(chart.xAxisExt)
let basicStyle
let miscStyle
if (chart.customAttr) {
basicStyle = parseJson(chart.customAttr).basicStyle
miscStyle = parseJson(chart.customAttr).misc
}
const mapStyle = `amap://styles/${basicStyle.mapStyle ? basicStyle.mapStyle : 'normal'}`
const key = await this.getMapKey()
// 底层
const scene = new Scene({
id: container,
logoVisible: false,
map: new GaodeMap({
token: key ?? undefined,
style: mapStyle,
pitch: miscStyle.mapPitch,
center: [104.434765, 38.256735],
zoom: 1.8
})
})
if (xAxis?.length < 2 || xAxisExt?.length < 1) {
return new L7Wrapper(scene, undefined)
}
const configList: L7Config[] = []
const symbolicLayer = this.buildSymbolicLayer(chart, basicStyle)
configList.push(symbolicLayer)
const tooltipLayer = this.buildTooltip(chart, symbolicLayer)
if (tooltipLayer) {
scene.addPopup(tooltipLayer)
}
this.buildLabel(chart, configList)
this.configZoomButton(chart, scene)
symbolicLayer.on('click', ev => {
const data = ev.feature
action({
x: ev.x,
y: ev.y,
data: {
data: {
...data,
dimensionList: chart.data.data.filter(item => item.field === ev.feature.field)?.[0]
?.dimensionList,
quotaList: chart.data.data.filter(item => item.field === ev.feature.field)?.[0]
?.quotaList
}
}
})
})
return new L7Wrapper(scene, configList)
}
/**
* 构建符号图层
* @param chart
*/
buildSymbolicLayer = (chart, basicStyle) => {
const xAxis = deepCopy(chart.xAxis)
const xAxisExt = deepCopy(chart.xAxisExt)
const extBubble = deepCopy(chart.extBubble)
const { mapSymbolOpacity, mapSymbolSize, mapSymbol, mapSymbolStrokeWidth, colors, alpha } =
deepCopy(basicStyle)
const c = []
colors.forEach(ele => {
c.push(hexColorToRGBA(ele, alpha))
})
const sizeKey = extBubble.length > 0 ? extBubble[0].dataeaseName : ''
const data = chart.data?.tableRow
? chart.data?.tableRow.map((item, index) => ({
...item,
color: c[index % c.length],
size: item[sizeKey] ? item[sizeKey] : mapSymbolSize,
field:
item[xAxis[0].dataeaseName] +
'000\n' +
item[xAxis[1].dataeaseName] +
'000\n' +
item[xAxisExt[0].dataeaseName],
name: item[xAxisExt[0].dataeaseName]
}))
: []
const color = xAxisExt && xAxisExt.length > 0 ? 'color' : c[0]
const pointLayer = new PointLayer()
.source(data, {
parser: {
type: 'json',
x: xAxis[0].dataeaseName,
y: xAxis[1].dataeaseName
}
})
.shape(mapSymbol)
.color(color)
.style({
stroke: {
field: 'color'
},
strokeWidth: mapSymbolStrokeWidth,
opacity: {
field: (mapSymbolOpacity / 100) * 10
}
})
.active(true)
if (sizeKey) {
pointLayer.size('size', [4, 30])
} else {
pointLayer.size(mapSymbolSize)
}
return pointLayer
}
/**
* 合并详情到 map
* @param details
* @returns {Map<string, any>}
*/
mergeDetailsToMap = details => {
const resultMap = new Map()
details.forEach(item => {
Object.entries(item).forEach(([key, value]) => {
if (resultMap.has(key)) {
const existingValue = resultMap.get(key)
if (existingValue !== value) {
resultMap.set(key, `${existingValue}, ${value}`)
}
} else {
resultMap.set(key, value)
}
})
})
return resultMap
}
/**
* 构建 tooltip
* @param chart
* @param pointLayer
*/
buildTooltip = (chart, pointLayer) => {
const customAttr = chart.customAttr ? parseJson(chart.customAttr) : null
if (customAttr?.tooltip?.show) {
const { tooltip } = deepCopy(customAttr)
let showFields = tooltip.showFields || []
if (!tooltip.showFields || tooltip.showFields.length === 0) {
showFields = [
...chart.xAxisExt.map(i => `${i.dataeaseName}@${i.name}`),
...chart.xAxis.map(i => `${i.dataeaseName}@${i.name}`)
]
}
const htmlPrefix = `<div style='font-size:${tooltip.fontSize}px;color:${tooltip.color}'>`
const htmlSuffix = '</div>'
return new LayerPopup({
items: [
{
layer: pointLayer,
customContent: item => {
const fieldData = {
...item,
...Object.fromEntries(this.mergeDetailsToMap(item.details))
}
const content = this.buildTooltipContent(tooltip, fieldData, showFields)
return `${htmlPrefix}${content}${htmlSuffix}`
}
}
],
trigger: 'hover'
})
}
return undefined
}
/**
* 构建 tooltip 内容
* @param tooltip
* @param fieldData
* @param showFields
* @returns {string}
*/
buildTooltipContent = (tooltip, fieldData, showFields) => {
let content = ''
if (tooltip.customContent) {
content = tooltip.customContent
showFields.forEach(field => {
content = content.replace(`\${${field.split('@')[1]}}`, fieldData[field.split('@')[0]])
})
} else {
showFields.forEach(field => {
//const value = ${fieldData[field.split('@')[0]] as string
content += `${field.split('@')[1]}: ${fieldData[field.split('@')[0]]}<br>`
})
}
return content
}
/**
* 构建 label
* @param chart
* @param configList
*/
buildLabel = (chart, configList) => {
const xAxis = deepCopy(chart.xAxis)
const customAttr = chart.customAttr ? parseJson(chart.customAttr) : null
if (customAttr?.label?.show) {
const { label } = customAttr
const data = chart.data?.tableRow || []
let showFields = label.showFields || []
if (!label.showFields || label.showFields.length === 0) {
showFields = [
...chart.xAxisExt.map(i => `${i.dataeaseName}@${i.name}`),
...chart.xAxis.map(i => `${i.dataeaseName}@${i.name}`)
]
}
data.forEach(item => {
const fieldData = {
...item,
...Object.fromEntries(this.mergeDetailsToMap(item.details))
}
let content = label.customContent || ''
if (content) {
showFields.forEach(field => {
const [fieldKey, fieldName] = field.split('@')
content = content.replace(`\${${fieldName}}`, fieldData[fieldKey])
})
} else {
content = showFields.map(field => fieldData[field.split('@')[0]]).join(',')
}
content = content.replace(/\n/g, '')
item.textLayerContent = content
})
configList.push(
new PointLayer()
.source(data, {
parser: {
type: 'json',
x: xAxis[0].dataeaseName,
y: xAxis[1].dataeaseName
}
})
.shape('textLayerContent', 'text')
.color(label.color)
.size(label.fontSize)
.style({
textAnchor: 'center',
textOffset: [0, 0]
})
)
}
}
getMapKey = async () => {
const key = 'online-map-key'
if (!localStorage.getItem(key)) {
await queryMapKeyApi().then(res => localStorage.setItem(key, res.data))
}
return localStorage.getItem(key)
}
setupDefaultOptions(chart: ChartObj): ChartObj {
chart.customAttr.label = {
...chart.customAttr.label,
show: false
}
chart.customAttr.basicStyle = {
...chart.customAttr.basicStyle,
mapSymbolOpacity: 5
}
return chart
}
protected setupOptions(chart: Chart, config: L7Config): L7Config {
return flow(this.configEmptyDataStrategy, this.configTooltip, this.configLabel)(chart, config)
}
}

View File

@ -5,10 +5,14 @@ import {
ChartLibraryType,
ChartWrapper
} from '@/views/chart/components/js/panel/types'
import { cloneDeep } from 'lodash-es'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { parseJson } from '@/views/chart/components/js/util'
import { ILayer } from '@antv/l7plot'
import { configL7Zoom } from '@/views/chart/components/js/panel/common/common_antv'
import {
configL7Label,
configL7Tooltip,
configL7Zoom
} from '@/views/chart/components/js/panel/common/common_antv'
export type L7DrawConfig<P> = AntVDrawOptions<P>
export interface L7Config extends ILayer {
@ -48,10 +52,12 @@ export class L7Wrapper<
}
handleConfig = (config: L7Config) => {
if (config.handleConfig) {
config.handleConfig?.(this.scene)
} else {
this.scene.addLayer(config)
if (config) {
if (config.handleConfig) {
config.handleConfig?.(this.scene)
} else {
this.scene.addLayer(config)
}
}
}
}
@ -88,6 +94,18 @@ export abstract class L7ChartView<
configL7Zoom(chart, plot)
}
protected configLabel(chart: Chart, options: O): O {
const label = configL7Label(chart)
defaultsDeep(options.label, label)
return options
}
protected configTooltip(chart: Chart, options: O): O {
const tooltip = configL7Tooltip(chart)
defaultsDeep(options.tooltip, tooltip)
return options
}
protected constructor(name: string, defaultData: any[]) {
super(ChartLibraryType.L7, name, defaultData)
}

View File

@ -306,7 +306,11 @@ const action = param => {
}
trackBarStyleCheck(props.element, barStyleTemp, props.scale, trackMenu.value.length)
state.trackBarStyle.left = barStyleTemp.left + 'px'
state.trackBarStyle.top = barStyleTemp.top + 'px'
if (curView.type === 'symbolic-map') {
state.trackBarStyle.top = param.y + 10 + 'px'
} else {
state.trackBarStyle.top = barStyleTemp.top + 'px'
}
viewTrack.value.trackButtonClick()
}
}

View File

@ -603,6 +603,9 @@ const chartAreaShow = computed(() => {
if (view.value.type === 'rich-text') {
return true
}
if (view.value?.plugin?.isPlugin) {
return true
}
if (view.value['dataFrom'] === 'template') {
return true
}

View File

@ -43,6 +43,7 @@ const eventCheck = e => {
const dvMainStore = dvMainStoreWithOut()
const snapshotStore = snapshotStoreWithOut()
const {
fullscreenFlag,
componentData,
curComponent,
canvasStyleData,
@ -234,7 +235,7 @@ onUnmounted(() => {
:class="{ 'preview-content': editMode === 'preview' }"
>
<!-- 中间画布 -->
<main class="center">
<main class="center" :class="{ 'de-screen-full': fullscreenFlag }">
<de-canvas
style="overflow-x: hidden"
v-if="dataInitState"
@ -257,7 +258,6 @@ onUnmounted(() => {
:side-name="'componentProp'"
:aside-position="'right'"
class="left-sidebar"
:class="{ 'preview-aside': editMode === 'preview' }"
>
<component :is="findComponent(curComponent['component'] + 'Attr')" :themes="'light'" />
</dv-sidebar>
@ -268,15 +268,10 @@ onUnmounted(() => {
:width="420"
aside-position="right"
class="left-sidebar"
:class="{ 'preview-aside': editMode === 'preview' }"
>
<DbCanvasAttr></DbCanvasAttr>
</dv-sidebar>
<div
v-show="viewEditorShow"
style="height: 100%"
:class="{ 'preview-aside': editMode === 'preview' }"
>
<div v-show="viewEditorShow" style="height: 100%">
<view-editor
:themes="'light'"
:view="canvasViewInfo[curComponent ? curComponent.id : 'default']"
@ -291,7 +286,6 @@ onUnmounted(() => {
aside-position="right"
class="left-sidebar"
:side-name="'batchOpt'"
:class="{ 'preview-aside': editMode === 'preview' }"
>
<chart-style-batch-set></chart-style-batch-set>
</dv-sidebar>

View File

@ -0,0 +1,89 @@
<script setup lang="ts">
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { ref } from 'vue'
import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
import { storeToRefs } from 'pinia'
const dvMainStore = dvMainStoreWithOut()
const { fullscreenFlag } = storeToRefs(dvMainStore)
const dePreviewRef = ref(null)
const dataInitState = ref(true)
defineProps({
canvasStylePreview: {
required: true,
type: Object
},
canvasDataPreview: {
required: true,
type: Object
},
canvasViewInfoPreview: {
required: true,
type: Object
},
dvInfo: {
required: true,
type: Object
},
curPreviewGap: {
required: false,
type: Number,
default: 0
},
showPosition: {
required: false,
type: String,
default: 'preview'
},
downloadStatus: {
required: false,
type: Boolean,
default: false
}
})
const restore = () => {
dePreviewRef.value.restore()
}
defineExpose({
restore
})
</script>
<template>
<div id="de-preview-content" :class="{ 'de-screen-full': fullscreenFlag }" class="content-outer">
<div class="content-inner">
<de-preview
ref="dePreviewRef"
v-if="canvasStylePreview && dataInitState"
:component-data="canvasDataPreview"
:canvas-style-data="canvasStylePreview"
:canvas-view-info="canvasViewInfoPreview"
:dv-info="dvInfo"
:cur-gap="curPreviewGap"
:show-position="showPosition"
:download-status="downloadStatus"
></de-preview>
</div>
</div>
</template>
<style lang="less">
.content-outer {
width: 100%;
height: calc(100vh - 112px);
background: #f5f6f7;
display: flex;
overflow-y: auto;
align-items: center;
flex-direction: column;
justify-content: center; /* 上下居中 */
.content-inner {
width: 100%;
height: auto;
overflow-x: hidden;
overflow-y: auto;
}
}
</style>

View File

@ -16,6 +16,7 @@ const emit = defineEmits(['reload', 'download', 'downloadAsAppTemplate'])
const { t } = useI18n()
const favorited = ref(false)
const fullScreeRef = ref(null)
const preview = () => {
const url = '#/preview?dvId=' + dvInfo.value.id
const newWindow = window.open(url, '_blank')
@ -103,7 +104,13 @@ const initOpenHandler = newWindow => {
</el-popover>
</div>
<div class="canvas-opt-button">
<de-fullscreen v-if="!isDataEaseBi"></de-fullscreen>
<de-fullscreen ref="fullScreeRef"></de-fullscreen>
<el-button v-if="!isDataEaseBi" secondary @click="() => fullScreeRef.toggleFullscreen()">
<template #icon>
<icon name="icon_pc_fullscreen"></icon>
</template>
全屏</el-button
>
<el-button secondary v-if="!isDataEaseBi" @click="preview()">
<template #icon>
<icon name="icon_pc_outlined"></icon>

View File

@ -3,7 +3,6 @@ import DeResourceTree from '@/views/common/DeResourceTree.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import ArrowSide from '@/views/common/DeResourceArrow.vue'
import { nextTick, onBeforeMount, reactive, ref, computed } from 'vue'
import DePreview from '@/components/data-visualization/canvas/DePreview.vue'
import PreviewHead from '@/views/data-visualization/PreviewHead.vue'
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import { storeToRefs } from 'pinia'
@ -15,11 +14,12 @@ import { useMoveLine } from '@/hooks/web/useMoveLine'
import { Icon } from '@/components/icon-custom'
import { download2AppTemplate, downloadCanvas2 } from '@/utils/imgUtils'
import MultiplexPreviewShow from '@/views/data-visualization/MultiplexPreviewShow.vue'
import DvPreview from '@/views/data-visualization/DvPreview.vue'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, fullscreenFlag } = storeToRefs(dvMainStore)
const { dvInfo } = storeToRefs(dvMainStore)
const previewCanvasContainer = ref(null)
const dvPreview = ref(null)
const dvPreviewRef = ref(null)
const slideShow = ref(true)
const requestStore = useRequestStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
@ -83,7 +83,7 @@ const loadCanvasData = (dvId, weight?) => {
if (props.showPosition === 'preview') {
dvMainStore.updateCurDvInfo(dvInfo)
nextTick(() => {
dvPreview.value?.restore()
dvPreviewRef.value?.restore()
})
}
}
@ -216,25 +216,17 @@ onBeforeMount(() => {
></multiplex-preview-show>
</div>
<div v-if="showPosition === 'preview'" ref="previewCanvasContainer" class="content">
<div
id="de-preview-content"
:class="{ 'de-screen-full': fullscreenFlag }"
class="content-outer"
>
<div class="content-inner">
<de-preview
ref="dvPreview"
v-if="state.canvasStylePreview && dataInitState"
:component-data="state.canvasDataPreview"
:canvas-style-data="state.canvasStylePreview"
:canvas-view-info="state.canvasViewInfoPreview"
:dv-info="state.dvInfo"
:cur-gap="state.curPreviewGap"
:show-position="showPosition"
:download-status="downloadStatus"
></de-preview>
</div>
</div>
<dv-preview
ref="dvPreviewRef"
v-if="state.canvasStylePreview && dataInitState"
:show-position="showPosition"
:canvas-data-preview="state.canvasDataPreview"
:canvas-style-preview="state.canvasStylePreview"
:canvas-view-info-preview="state.canvasViewInfoPreview"
:dv-info="state.dvInfo"
:cur-preview-gap="state.curPreviewGap"
:download-status="downloadStatus"
></dv-preview>
</div>
</template>
<template v-else-if="hasTreeData && mounted">

View File

@ -38,9 +38,11 @@ import { XpackComponent } from '@/components/plugin'
import { Base64 } from 'js-base64'
import CanvasCacheDialog from '@/components/visualization/CanvasCacheDialog.vue'
import { deepCopy } from '@/utils/utils'
import DvPreview from '@/views/data-visualization/DvPreview.vue'
const interactiveStore = interactiveStoreWithOut()
const embeddedStore = useEmbedded()
const { wsCache } = useCache()
const dvPreviewRef = ref(null)
const eventCheck = e => {
if (e.key === 'screen-weight' && !compareStorage(e.oldValue, e.newValue)) {
const opt = embeddedStore.opt || router.currentRoute.value.query.opt
@ -63,6 +65,7 @@ const composeStore = composeStoreWithOut()
const canvasCacheOutRef = ref(null)
const {
fullscreenFlag,
componentData,
curComponent,
isClickComponent,
@ -454,6 +457,15 @@ eventBus.on('handleNew', handleNew)
@load-fail="XpackLoaded"
/>
<canvas-cache-dialog ref="canvasCacheOutRef" @doUseCache="doUseCache"></canvas-cache-dialog>
<dv-preview
v-if="fullscreenFlag"
style="z-index: 10"
ref="dvPreviewRef"
:canvas-data-preview="componentData"
:canvas-style-preview="canvasStyleData"
:canvas-view-info-preview="canvasViewInfo"
:dv-info="dvInfo"
></dv-preview>
</template>
<style lang="less">

View File

@ -1,7 +1,9 @@
<script lang="tsx" setup>
import { ref, reactive, onMounted, PropType, toRefs, watch, onBeforeUnmount } from 'vue'
import { ref, reactive, onMounted, PropType, toRefs, watch, onBeforeUnmount, shallowRef } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { Base64 } from 'js-base64'
import FixedSizeList from 'element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs'
import { useWindowSize } from '@vueuse/core'
import useClipboard from 'vue-clipboard3'
import { ElMessage, ElMessageBox, ElIcon } from 'element-plus-secondary'
import { Icon } from '@/components/icon-custom'
@ -56,7 +58,6 @@ const state = reactive({
sqlData: [],
variablesTmp: [],
dataSourceList: [],
tableData: [],
table: {
name: '',
id: ''
@ -66,6 +67,8 @@ const state = reactive({
}
})
const datasourceTableData = shallowRef([])
const paginationConfig = reactive({
currentPage: 1,
pageSize: 10,
@ -76,6 +79,7 @@ const setActiveName = ({ name, enableCheck }) => {
if (!enableCheck) return
activeName.value = name
}
const { height: windowHeight } = useWindowSize()
const generateColumns = (arr: Field[]) =>
arr.map(ele => ({
@ -295,7 +299,7 @@ const getSQLPreview = () => {
let tableList = []
watch(searchTable, val => {
state.tableData = tableList.filter(ele => ele.tableName.includes(val))
datasourceTableData.value = tableList.filter(ele => ele.tableName.includes(val))
})
const getIconName = (type: string) => {
@ -328,7 +332,7 @@ const dsChange = (val: string) => {
getTables({ datasourceId: val })
.then(res => {
tableList = res || []
state.tableData = [...tableList]
datasourceTableData.value = [...tableList]
})
.finally(() => {
dsLoading.value = false
@ -473,7 +477,7 @@ const mousedownDrag = () => {
<el-icon class="icon-color">
<Icon name="reference-table"></Icon>
</el-icon>
{{ state.tableData.length }}
{{ datasourceTableData.length }}
</span>
</p>
<el-input
@ -489,7 +493,7 @@ const mousedownDrag = () => {
</template>
</el-input>
</div>
<div v-if="!state.tableData.length && searchTable !== ''" class="el-empty">
<div v-if="!datasourceTableData.length && searchTable !== ''" class="el-empty">
<div
class="el-empty__description"
style="margin-top: 80px; color: #5e6d82; text-align: center"
@ -498,97 +502,118 @@ const mousedownDrag = () => {
</div>
</div>
<div v-else class="table-checkbox-list">
<template v-for="ele in state.tableData" :key="ele.tableName">
<div
:class="[{ active: activeName === ele.tableName }]"
class="list-item_primary"
:title="ele.tableName"
@click="setActiveName(ele)"
>
<el-icon class="icon-color">
<Icon name="icon_form_outlined"></Icon>
</el-icon>
<span class="label">{{ ele.tableName }}</span>
<span class="name-copy">
<el-tooltip effect="dark" :content="t('common.copy')" placement="top">
<el-icon class="hover-icon" @click="copyInfo(ele.tableName)">
<Icon name="icon_copy_outlined"></Icon>
</el-icon>
</el-tooltip>
<el-popover
popper-class="sql-table-info"
placement="right"
:width="502"
:persistent="false"
@show="getNodeField(ele)"
trigger="click"
>
<template #reference>
<el-icon class="hover-icon">
<Icon name="icon_info_outlined"></Icon>
<FixedSizeList
:itemSize="40"
:data="datasourceTableData"
:total="datasourceTableData.length"
:width="223"
:height="windowHeight - 350"
:scrollbarAlwaysOn="false"
class-name="el-select-dropdown__list"
layout="vertical"
>
<template #default="{ index, style }">
<div
:class="[{ active: activeName === datasourceTableData[index].tableName }]"
class="list-item_primary"
:style="style"
:title="datasourceTableData[index].tableName"
@click="setActiveName(datasourceTableData[index])"
>
<el-icon class="icon-color">
<Icon name="icon_form_outlined"></Icon>
</el-icon>
<span class="label">{{ datasourceTableData[index].tableName }}</span>
<span class="name-copy">
<el-tooltip effect="dark" :content="t('common.copy')" placement="top">
<el-icon
class="hover-icon"
@click="copyInfo(datasourceTableData[index].tableName)"
>
<Icon name="icon_copy_outlined"></Icon>
</el-icon>
</template>
<div class="table-filed" v-loading="gridDataLoading">
<div class="top flex-align-center">
<div class="title ellipsis">
{{ ele.name || ele.tableName }}
</div>
<el-icon
class="hover-icon de-hover-icon-primary"
@click.stop="copyInfo(ele.name || ele.tableName)"
>
<Icon name="icon_copy_outlined"></Icon>
</el-tooltip>
<el-popover
popper-class="sql-table-info"
placement="right"
:width="502"
:persistent="false"
@show="getNodeField(datasourceTableData[index])"
trigger="click"
>
<template #reference>
<el-icon class="hover-icon">
<Icon name="icon_info_outlined"></Icon>
</el-icon>
<div class="num flex-align-center">
<el-icon>
<Icon name="icon_text-box_outlined"></Icon>
</template>
<div class="table-filed" v-loading="gridDataLoading">
<div class="top flex-align-center">
<div class="title ellipsis">
{{
datasourceTableData[index].name || datasourceTableData[index].tableName
}}
</div>
<el-icon
class="hover-icon de-hover-icon-primary"
@click.stop="
copyInfo(
datasourceTableData[index].name || datasourceTableData[index].tableName
)
"
>
<Icon name="icon_copy_outlined"></Icon>
</el-icon>
{{ gridData.length }}
<div class="num flex-align-center">
<el-icon>
<Icon name="icon_text-box_outlined"></Icon>
</el-icon>
{{ gridData.length }}
</div>
</div>
<div class="table-grid">
<el-table
height="405"
style="width: 100%"
header-cell-class-name="header-cell"
:data="gridData"
>
<el-table-column label="物理字段名">
<template #default="scope">
<div class="flex-align-center icon">
<el-icon>
<Icon
:className="`field-icon-${fieldType[scope.row.deType]}`"
:name="`field_${fieldType[scope.row.deType]}`"
></Icon>
</el-icon>
{{ scope.row.originName }}
</div>
</template>
</el-table-column>
<el-table-column :label="t('common.label')">
<template #default="scope">
{{ scope.row.description || '-' }}
</template>
</el-table-column>
<el-table-column :label="t('common.operate')">
<template #default="scope">
<el-icon
class="hover-icon de-hover-icon-primary"
@click.stop="copyInfo(scope.row.originName)"
>
<Icon name="icon_copy_outlined"></Icon>
</el-icon>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="table-grid">
<el-table
height="405"
style="width: 100%"
header-cell-class-name="header-cell"
:data="gridData"
>
<el-table-column label="物理字段名">
<template #default="scope">
<div class="flex-align-center icon">
<el-icon>
<Icon
:className="`field-icon-${fieldType[scope.row.deType]}`"
:name="`field_${fieldType[scope.row.deType]}`"
></Icon>
</el-icon>
{{ scope.row.originName }}
</div>
</template>
</el-table-column>
<el-table-column :label="t('common.label')">
<template #default="scope">
{{ scope.row.description || '-' }}
</template>
</el-table-column>
<el-table-column :label="t('common.operate')">
<template #default="scope">
<el-icon
class="hover-icon de-hover-icon-primary"
@click.stop="copyInfo(scope.row.originName)"
>
<Icon name="icon_copy_outlined"></Icon>
</el-icon>
</template>
</el-table-column>
</el-table>
</div>
</div>
</el-popover>
</span>
</div>
</template>
</el-popover>
</span>
</div>
</template>
</FixedSizeList>
</div>
</div>
<div class="sql-code-right" :style="{ width: `calc(100% - ${showLeft ? LeftWidth : 0}px)` }">

View File

@ -15,10 +15,12 @@ import {
import { useI18n } from '@/hooks/web/useI18n'
import { useEmitt } from '@/hooks/web/useEmitt'
import { ElIcon, ElMessageBox, ElMessage } from 'element-plus-secondary'
import FixedSizeList from 'element-plus-secondary/es/components/virtual-list/src/components/fixed-size-list.mjs'
import type { Action } from 'element-plus-secondary'
import FieldMore from './FieldMore.vue'
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import { Icon } from '@/components/icon-custom'
import { useWindowSize } from '@vueuse/core'
import CalcFieldEdit from './CalcFieldEdit.vue'
import { useRoute, useRouter } from 'vue-router'
import UnionEdit from './UnionEdit.vue'
@ -201,6 +203,8 @@ const dfsName = (arr, id) => {
return name
}
const { height } = useWindowSize()
const dfsChild = arr => {
return arr.filter(ele => {
if (ele.leaf) {
@ -305,7 +309,9 @@ const confirmCustomTime = () => {
}
watch(searchTable, val => {
state.tableData = tableList.filter(ele => ele.tableName.toLowerCase().includes(val.toLowerCase()))
datasourceTableData.value = tableList.filter(ele =>
ele.tableName.toLowerCase().includes(val.toLowerCase())
)
})
const editeSave = () => {
const union = []
@ -560,7 +566,7 @@ const dsChange = (val: string) => {
return getTables({ datasourceId: val })
.then(res => {
tableList = res || []
state.tableData = [...tableList]
datasourceTableData.value = [...tableList]
})
.finally(() => {
dsLoading.value = false
@ -664,10 +670,10 @@ const state = reactive({
nodeNameList: [],
editArr: [],
dataSourceList: [],
tableData: [],
fieldCollapse: ['dimension', 'quota']
})
const datasourceTableData = shallowRef([])
const getIconName = (type: number) => {
if (type === 1) {
return 'time'
@ -1281,7 +1287,7 @@ const getDsIconName = data => {
<el-icon class="icon-color">
<Icon name="reference-table"></Icon>
</el-icon>
{{ state.tableData.length }}
{{ datasourceTableData.length }}
</span>
</p>
<el-input
@ -1297,7 +1303,7 @@ const getDsIconName = data => {
</template>
</el-input>
</div>
<div v-if="!state.tableData.length && searchTable !== ''" class="el-empty">
<div v-if="!datasourceTableData.length && searchTable !== ''" class="el-empty">
<div
class="el-empty__description"
style="margin-top: 80px; color: #5e6d82; text-align: center"
@ -1319,21 +1325,33 @@ const getDsIconName = data => {
</el-icon>
<span class="label">自定义SQL</span>
</div>
<template v-for="ele in state.tableData" :key="ele.tableName">
<div
class="list-item_primary"
:title="ele.tableName"
@dragstart="$event => dragstart($event, ele)"
@dragend="maskShow = false"
:draggable="true"
@click="setActiveName(ele)"
>
<el-icon class="icon-color">
<Icon name="reference-table"></Icon>
</el-icon>
<span class="label">{{ ele.tableName }}</span>
</div>
</template>
<FixedSizeList
:itemSize="40"
:data="datasourceTableData"
:total="datasourceTableData.length"
:width="223"
:height="height - 305"
:scrollbarAlwaysOn="false"
class-name="el-select-dropdown__list"
layout="vertical"
>
<template #default="{ index, style }">
<div
class="list-item_primary"
:style="style"
:title="datasourceTableData[index].tableName"
@dragstart="$event => dragstart($event, datasourceTableData[index])"
@dragend="maskShow = false"
:draggable="true"
@click="setActiveName(datasourceTableData[index])"
>
<el-icon class="icon-color">
<Icon name="reference-table"></Icon>
</el-icon>
<span class="label">{{ datasourceTableData[index].tableName }}</span>
</div>
</template>
</FixedSizeList>
</div>
</div>
<div class="drag-right" :style="{ width: `calc(100vw - ${showLeft ? LeftWidth : 0}px)` }">
@ -1469,7 +1487,7 @@ const getDsIconName = data => {
style="width: 100%; height: 100%"
>
<el-table-column
:key="column.dataKey"
:key="column.dataKey + column.deType"
v-for="(column, index) in columns"
:prop="column.dataKey"
:label="column.title"

@ -1 +1 @@
Subproject commit 070ee75a6da1e59e8b021462fe0604cbc04b0f64
Subproject commit 0bfb0e20ead5a4711cb54957003b63388067eb8b

View File

@ -67,7 +67,7 @@ function _check_apisix_init() {
function _prepare_apisix() {
if [[ -z $DE_APISIX_KEY ]];then
need_init_apisix=true
DE_APISIX_KEY=$(head -c 32 /dev/urandom | base64)
DE_APISIX_KEY=$(head -c 32 /dev/urandom | base64 | sed 's#/#+#g')
export DE_APISIX_KEY=$DE_APISIX_KEY
cd $DE_RUNNING_BASE
env | grep DE_ >.env
@ -420,6 +420,9 @@ function main() {
--help)
usage
;;
"")
usage
;;
*)
echo "不支持的参数,请使用 help 或 --help 参数获取帮助"
;;

View File

@ -40,6 +40,10 @@ public interface DatasetTableApi {
@PostMapping("listByDatasetGroup/{id}")
List<DatasetTableFieldDTO> listByDatasetGroup(@PathVariable Long id);
@Operation(summary = "获取数据集字段map")
@PostMapping("listByDsIds")
Map<String, List<DatasetTableFieldDTO>> listByDsIds(@RequestBody List<Long> ids);
@Operation(summary = "删除字段")
@PostMapping("delete/{id}")
void delete(@PathVariable Long id);

View File

@ -1,7 +1,7 @@
package io.dataease.api.xpack.component;
import io.dataease.api.xpack.component.vo.XpackMenuVO;
import io.dataease.api.xpack.component.vo.XpackPluginsViewVO;
import io.dataease.extensions.view.vo.XpackPluginsViewVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

View File

@ -1,9 +1,14 @@
package io.dataease.api.xpack.plugin;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.api.xpack.plugin.dto.PluginEditor;
import io.dataease.api.xpack.plugin.vo.PluginVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@ -14,4 +19,12 @@ public interface PluginApi {
@GetMapping("/query")
List<PluginVO> query();
@PostMapping("/install")
void install(@RequestPart(value = "file") MultipartFile file);
@PostMapping("/uninstall/{id}")
void uninstall(@PathVariable("id") String id);
void update(@RequestPart("request") PluginEditor request, @RequestPart(value = "file") MultipartFile file);
}

View File

@ -0,0 +1,14 @@
package io.dataease.api.xpack.plugin.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class PluginEditor implements Serializable {
@Serial
private static final long serialVersionUID = -1793403914368070138L;
private String id;
}

View File

@ -19,6 +19,7 @@ public class StaticResourceConstants {
public static String CUSTOM_MAP_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "geo";
public static String APPEARANCE_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "appearance";
public static String REPORT_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "report";
public static String PLUGIN_DIR = ensureSuffix(USER_HOME, FILE_SEPARATOR) + "plugin";
public static String MAP_URL = "/map";
public static String GEO_URL = "/geo";

View File

@ -1,6 +1,7 @@
package io.dataease.extensions.view.factory;
import io.dataease.extensions.view.template.PluginsChartTemplate;
import io.dataease.extensions.view.vo.XpackPluginsViewVO;
import java.util.List;
import java.util.Map;
@ -21,7 +22,7 @@ public class PluginsChartFactory {
templateMap.put(key, template);
}
public static List<String> getViewConfigList() {
public static List<XpackPluginsViewVO> getViewConfigList() {
return templateMap.values().stream().map(PluginsChartTemplate::getConfig).toList();
}

View File

@ -3,40 +3,34 @@ package io.dataease.extensions.view.template;
import io.dataease.extensions.view.dto.ChartViewDTO;
import io.dataease.extensions.view.dto.ChartViewFieldDTO;
import io.dataease.extensions.view.dto.DatasetTableFieldDTO;
import io.dataease.extensions.view.factory.PluginsChartFactory;
import io.dataease.extensions.view.model.SQLMeta;
import io.dataease.extensions.view.vo.XpackPluginsViewVO;
import io.dataease.license.utils.JsonUtil;
import io.dataease.plugins.template.DataEasePlugin;
import io.dataease.plugins.vo.DataEasePluginVO;
import java.util.List;
import java.util.Map;
public abstract class PluginsChartTemplate {
public abstract class PluginsChartTemplate implements DataEasePlugin {
public abstract String getConfig();
@Override
public void loadPlugin() {
XpackPluginsViewVO viewConfig = getConfig();
PluginsChartFactory.loadTemplate(viewConfig.getRender(), viewConfig.getCategory(), this);
}
public XpackPluginsViewVO getConfig() {
DataEasePluginVO pluginInfo = getPluginInfo();
String config = pluginInfo.getConfig();
return JsonUtil.parseObject(config, XpackPluginsViewVO.class);
}
public abstract Map<String, List<ChartViewFieldDTO>> formatChartAxis(ChartViewDTO view);
/*public Map<String, List<ChartViewFieldDTO>> formatChartAxis(ChartViewDTO view) {
Map<String, List<ChartViewFieldDTO>> result = new HashMap<>();
List<ChartViewFieldDTO> xAxisBase = new ArrayList<>(view.getXAxis());
result.put("xAxisBase", xAxisBase);
List<ChartViewFieldDTO> xAxis = new ArrayList<>(view.getXAxis());
result.put("xAxis", xAxis);
List<ChartViewFieldDTO> xAxisExt = new ArrayList<>(view.getXAxisExt());
result.put("xAxisExt", xAxisExt);
List<ChartViewFieldDTO> yAxis = new ArrayList<>(view.getYAxis());
result.put("yAxis", yAxis);
List<ChartViewFieldDTO> extStack = new ArrayList<>(view.getExtStack());
result.put("extStack", extStack);
List<ChartViewFieldDTO> extBubble = new ArrayList<>(view.getExtBubble());
result.put("extBubble", extBubble);
List<ChartViewFieldDTO> drill = new ArrayList<>(view.getDrillFields());
result.put("drill", drill);
List<ChartViewFieldDTO> viewFields = new ArrayList<>(view.getViewFields());
result.put("viewFields", viewFields);
return result;
}*/
public abstract ChartViewDTO calcResult(SQLMeta sqlMeta, List<ChartViewFieldDTO> xaxis, List<ChartViewFieldDTO> yaxis,
List<DatasetTableFieldDTO> allFields, boolean crossDs, Map<Long, String> dsTypeMap);

View File

@ -1,4 +1,4 @@
package io.dataease.api.xpack.component.vo;
package io.dataease.extensions.view.vo;
import lombok.Data;

View File

@ -27,6 +27,12 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.dataease</groupId>
<artifactId>dataease-license-sdk</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>