feat: 图表增加指标卡

This commit is contained in:
ulleo 2024-01-19 18:06:33 +08:00
parent eda1b6286f
commit ba18375d9c
25 changed files with 1756 additions and 101 deletions

View File

@ -208,7 +208,7 @@ public class ChartDataManage {
return emptyChartViewDTO(view);
}
break;
case "text":
case "indicator":
case "gauge":
case "liquid":
xAxis = new ArrayList<>();
@ -496,7 +496,7 @@ public class ChartDataManage {
ExtWhere2Str.extWhere2sqlOjb(sqlMeta, extFilterList, transFields(allFields));
WhereTree2Str.transFilterTrees(sqlMeta, rowPermissionsTree, transFields(allFields));
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "text", "gauge", "liquid")) {
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "indicator", "gauge", "liquid")) {
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields));
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
} else if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {
@ -684,7 +684,7 @@ public class ChartDataManage {
mapChart = ChartDataBuild.transScatterData(xAxis, yAxis, view, data, extBubble, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "radar")) {
mapChart = ChartDataBuild.transRadarChartData(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "text")
} else if (StringUtils.containsIgnoreCase(view.getType(), "indicator")
|| StringUtils.containsIgnoreCase(view.getType(), "gauge")
|| StringUtils.equalsIgnoreCase("liquid", view.getType())) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
@ -708,7 +708,7 @@ public class ChartDataManage {
mapChart = ChartDataBuild.transScatterDataAntV(xAxis, yAxis, view, data, extBubble, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "radar")) {
mapChart = ChartDataBuild.transRadarChartDataAntV(xAxis, yAxis, view, data, isDrill);
} else if (StringUtils.containsIgnoreCase(view.getType(), "text")
} else if (StringUtils.containsIgnoreCase(view.getType(), "indicator")
|| StringUtils.containsIgnoreCase(view.getType(), "gauge")
|| StringUtils.equalsIgnoreCase("liquid", view.getType())) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
@ -719,6 +719,10 @@ public class ChartDataManage {
} else {
mapChart = ChartDataBuild.transChartDataAntV(xAxis, yAxis, view, data, isDrill);
}
} else if (StringUtils.equalsIgnoreCase(view.getRender(), "custom")) {
if (StringUtils.containsIgnoreCase(view.getType(), "indicator")) {
mapChart = ChartDataBuild.transNormalChartData(xAxis, yAxis, view, data, isDrill);
}
}
// table组件明细表也用于导出数据
Map<String, Object> mapTableNormal = null;
@ -1220,7 +1224,7 @@ public class ChartDataManage {
return new ArrayList<String[]>();
}
break;
case "text":
case "indicator":
case "gauge":
case "liquid":
xAxis = new ArrayList<>();
@ -1276,7 +1280,7 @@ public class ChartDataManage {
Table2SQLObj.table2sqlobj(sqlMeta, null, "(" + sql + ")");
WhereTree2Str.transFilterTrees(sqlMeta, rowPermissionsTree, transFields(allFields));
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "text", "gauge", "liquid")) {
if (StringUtils.equalsAnyIgnoreCase(view.getType(), "indicator", "gauge", "liquid")) {
Quota2SQLObj.quota2sqlObj(sqlMeta, yAxis, transFields(allFields));
querySql = SQLProvider.createQuerySQL(sqlMeta, true, needOrder, view);
} else if (StringUtils.containsIgnoreCase(view.getType(), "stack")) {

View File

@ -0,0 +1,6 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5912 36.9635C24.5912 37.2397 24.3673 37.4635 24.0912 37.4635H20.8083C20.5322 37.4635 20.3083 37.2397 20.3083 36.9635V18.729H15.5C15.2239 18.729 15 18.5052 15 18.229V14.8324C15 14.5491 15.2355 14.3239 15.5188 14.3171C16.0773 14.3035 16.6182 14.2336 17.1414 14.1073C17.8452 13.9147 18.4685 13.5983 19.0114 13.1582C19.5744 12.6905 20.0469 12.0853 20.429 11.3425C20.7584 10.702 20.9906 9.92865 21.1257 9.0223C21.1633 8.77004 21.3761 8.57772 21.6312 8.57772H24.0912C24.3673 8.57772 24.5912 8.80157 24.5912 9.07772V36.9635Z" fill="#3370FF"/>
<path d="M31.118 19.6781C30.8466 19.6781 30.6241 19.4616 30.6234 19.1902C30.6195 17.7256 30.7625 16.3614 31.0522 15.0977C31.374 13.6671 31.8565 12.4292 32.5 11.3838C33.1434 10.3109 33.9477 9.48556 34.9128 8.90784C35.8981 8.30261 37.0241 8 38.2909 8C39.256 8 40.1709 8.20633 41.0355 8.61898C41.9202 9.03164 42.6944 9.62311 43.3579 10.3934C44.0215 11.1637 44.5442 12.1128 44.9263 13.2407C45.3284 14.3686 45.5295 15.6341 45.5295 17.0371C45.5295 18.4952 45.3586 19.7469 45.0168 20.7923C44.6749 21.8377 44.2225 22.773 43.6595 23.5983C43.0965 24.3961 42.4531 25.1252 41.7292 25.7854C41.0255 26.4457 40.3117 27.1059 39.5878 27.7662C38.8639 28.3989 38.1602 29.0867 37.4765 29.8294C36.7929 30.5722 36.1897 31.4388 35.6669 32.4292H45.1502C45.4263 32.4292 45.6502 32.653 45.6502 32.9292V36.9635C45.6502 37.2397 45.4263 37.4635 45.1502 37.4635H30.1474C30.1474 35.7854 30.3183 34.3274 30.6602 33.0894C31.0221 31.8514 31.5047 30.751 32.1079 29.7882C32.7111 28.7978 33.4149 27.89 34.2191 27.0646C35.0435 26.2393 35.9082 25.4003 36.813 24.5475C37.2755 24.1073 37.7681 23.6671 38.2909 23.227C38.8137 22.7593 39.2862 22.2503 39.7084 21.7001C40.1508 21.1499 40.5127 20.5309 40.7942 19.8432C41.0958 19.1554 41.2467 18.3714 41.2467 17.4911C41.2467 16.088 40.945 15.0014 40.3418 14.2311C39.7587 13.4333 39.0047 13.0344 38.0798 13.0344C37.4564 13.0344 36.9236 13.2407 36.4812 13.6534C36.059 14.0385 35.7171 14.5612 35.4557 15.2215C35.1944 15.8542 35.0033 16.5695 34.8827 17.3673C34.8039 17.9709 34.756 18.5746 34.7389 19.1782C34.7311 19.4542 34.508 19.6781 34.2319 19.6781H31.118Z" fill="#3370FF"/>
<path d="M55.6501 20.8189C55.6501 20.5193 55.912 20.2881 56.2115 20.2953C56.5059 20.3023 56.8114 20.2892 57.128 20.2558C57.6508 20.2008 58.1333 20.0495 58.5757 19.8019C59.0382 19.5268 59.4102 19.1417 59.6917 18.6465C59.9933 18.1513 60.1441 17.4911 60.1441 16.6657C60.1441 15.4278 59.8425 14.4787 59.2393 13.8184C58.636 13.1582 57.9423 12.8281 57.1581 12.8281C56.0723 12.8281 55.248 13.3232 54.6849 14.3136C54.2051 15.1647 53.9452 16.2199 53.9051 17.4793C53.8963 17.758 53.6727 17.9862 53.394 17.9862H50.3428C50.0613 17.9862 49.8346 17.7539 49.8492 17.4729C49.9158 16.1886 50.1001 15.0117 50.4021 13.9422C50.764 12.7043 51.2567 11.6451 51.88 10.7648C52.5234 9.88446 53.2875 9.21045 54.1722 8.74278C55.0569 8.24759 56.0422 8 57.128 8C57.9725 8 58.817 8.17882 59.6615 8.53645C60.506 8.86658 61.26 9.37552 61.9236 10.0633C62.6072 10.751 63.1602 11.5901 63.5824 12.5805C64.0047 13.5708 64.2158 14.7125 64.2158 16.0055C64.2158 17.4085 63.9645 18.6465 63.4618 19.7194C62.9792 20.7923 62.2453 21.5213 61.26 21.9065V21.989C62.4263 22.3466 63.3411 23.1169 64.0047 24.2999C64.6682 25.4828 65 26.8996 65 28.5502C65 30.0633 64.7788 31.4113 64.3365 32.5942C63.9142 33.7772 63.3411 34.7675 62.6173 35.5653C61.8934 36.3631 61.059 36.9684 60.1139 37.381C59.1689 37.7937 58.1836 38 57.1581 38C55.9718 38 54.886 37.7662 53.9008 37.2985C52.9356 36.8308 52.1112 36.1568 51.4276 35.2765C50.7439 34.3686 50.2111 33.2682 49.829 31.9752C49.503 30.8106 49.332 29.4898 49.316 28.0127C49.313 27.7389 49.5365 27.5186 49.8103 27.5186H52.9009C53.172 27.5186 53.3925 27.7347 53.4103 28.0052C53.4497 28.6028 53.5327 29.1834 53.6595 29.7469C53.8203 30.4347 54.0516 31.0399 54.3532 31.5626C54.6548 32.0578 55.0268 32.4567 55.4691 32.7593C55.9316 33.0619 56.4745 33.2132 57.0978 33.2132C58.063 33.2132 58.8773 32.8143 59.5409 32.0165C60.2044 31.1912 60.5362 30.077 60.5362 28.674C60.5362 27.5736 60.3753 26.7345 60.0536 26.1568C59.752 25.5791 59.3599 25.1664 58.8773 24.9188C58.3947 24.6437 57.8619 24.4924 57.2788 24.4649C56.8931 24.4272 56.5168 24.4025 56.15 24.3906C55.874 24.3816 55.6501 24.1585 55.6501 23.8824V20.8189Z" fill="#3370FF"/>
<path d="M64.5 43H15.5C15.2239 43 15 43.2239 15 43.5V48.125C15 48.4011 15.2239 48.625 15.5 48.625H64.5C64.7761 48.625 65 48.4011 65 48.125V43.5C65 43.2239 64.7761 43 64.5 43Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,6 @@
<svg width="80" height="56" viewBox="0 0 80 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M24.5912 36.9635C24.5912 37.2397 24.3673 37.4635 24.0912 37.4635H20.8083C20.5322 37.4635 20.3083 37.2397 20.3083 36.9635V18.729H15.5C15.2239 18.729 15 18.5052 15 18.229V14.8324C15 14.5491 15.2355 14.3239 15.5188 14.3171C16.0773 14.3035 16.6182 14.2336 17.1414 14.1073C17.8452 13.9147 18.4685 13.5983 19.0114 13.1582C19.5744 12.6905 20.0469 12.0853 20.429 11.3425C20.7584 10.702 20.9906 9.92865 21.1257 9.0223C21.1633 8.77004 21.3761 8.57772 21.6312 8.57772H24.0912C24.3673 8.57772 24.5912 8.80157 24.5912 9.07772V36.9635Z" fill="#3370FF"/>
<path d="M31.118 19.6781C30.8466 19.6781 30.6241 19.4616 30.6234 19.1902C30.6195 17.7256 30.7625 16.3614 31.0522 15.0977C31.374 13.6671 31.8565 12.4292 32.5 11.3838C33.1434 10.3109 33.9477 9.48556 34.9128 8.90784C35.8981 8.30261 37.0241 8 38.2909 8C39.256 8 40.1709 8.20633 41.0355 8.61898C41.9202 9.03164 42.6944 9.62311 43.3579 10.3934C44.0215 11.1637 44.5442 12.1128 44.9263 13.2407C45.3284 14.3686 45.5295 15.6341 45.5295 17.0371C45.5295 18.4952 45.3586 19.7469 45.0168 20.7923C44.6749 21.8377 44.2225 22.773 43.6595 23.5983C43.0965 24.3961 42.4531 25.1252 41.7292 25.7854C41.0255 26.4457 40.3117 27.1059 39.5878 27.7662C38.8639 28.3989 38.1602 29.0867 37.4765 29.8294C36.7929 30.5722 36.1897 31.4388 35.6669 32.4292H45.1502C45.4263 32.4292 45.6502 32.653 45.6502 32.9292V36.9635C45.6502 37.2397 45.4263 37.4635 45.1502 37.4635H30.1474C30.1474 35.7854 30.3183 34.3274 30.6602 33.0894C31.0221 31.8514 31.5047 30.751 32.1079 29.7882C32.7111 28.7978 33.4149 27.89 34.2191 27.0646C35.0435 26.2393 35.9082 25.4003 36.813 24.5475C37.2755 24.1073 37.7681 23.6671 38.2909 23.227C38.8137 22.7593 39.2862 22.2503 39.7084 21.7001C40.1508 21.1499 40.5127 20.5309 40.7942 19.8432C41.0958 19.1554 41.2467 18.3714 41.2467 17.4911C41.2467 16.088 40.945 15.0014 40.3418 14.2311C39.7587 13.4333 39.0047 13.0344 38.0798 13.0344C37.4564 13.0344 36.9236 13.2407 36.4812 13.6534C36.059 14.0385 35.7171 14.5612 35.4557 15.2215C35.1944 15.8542 35.0033 16.5695 34.8827 17.3673C34.8039 17.9709 34.756 18.5746 34.7389 19.1782C34.7311 19.4542 34.508 19.6781 34.2319 19.6781H31.118Z" fill="#3370FF"/>
<path d="M55.6501 20.8189C55.6501 20.5193 55.912 20.2881 56.2115 20.2953C56.5059 20.3023 56.8114 20.2892 57.128 20.2558C57.6508 20.2008 58.1333 20.0495 58.5757 19.8019C59.0382 19.5268 59.4102 19.1417 59.6917 18.6465C59.9933 18.1513 60.1441 17.4911 60.1441 16.6657C60.1441 15.4278 59.8425 14.4787 59.2393 13.8184C58.636 13.1582 57.9423 12.8281 57.1581 12.8281C56.0723 12.8281 55.248 13.3232 54.6849 14.3136C54.2051 15.1647 53.9452 16.2199 53.9051 17.4793C53.8963 17.758 53.6727 17.9862 53.394 17.9862H50.3428C50.0613 17.9862 49.8346 17.7539 49.8492 17.4729C49.9158 16.1886 50.1001 15.0117 50.4021 13.9422C50.764 12.7043 51.2567 11.6451 51.88 10.7648C52.5234 9.88446 53.2875 9.21045 54.1722 8.74278C55.0569 8.24759 56.0422 8 57.128 8C57.9725 8 58.817 8.17882 59.6615 8.53645C60.506 8.86658 61.26 9.37552 61.9236 10.0633C62.6072 10.751 63.1602 11.5901 63.5824 12.5805C64.0047 13.5708 64.2158 14.7125 64.2158 16.0055C64.2158 17.4085 63.9645 18.6465 63.4618 19.7194C62.9792 20.7923 62.2453 21.5213 61.26 21.9065V21.989C62.4263 22.3466 63.3411 23.1169 64.0047 24.2999C64.6682 25.4828 65 26.8996 65 28.5502C65 30.0633 64.7788 31.4113 64.3365 32.5942C63.9142 33.7772 63.3411 34.7675 62.6173 35.5653C61.8934 36.3631 61.059 36.9684 60.1139 37.381C59.1689 37.7937 58.1836 38 57.1581 38C55.9718 38 54.886 37.7662 53.9008 37.2985C52.9356 36.8308 52.1112 36.1568 51.4276 35.2765C50.7439 34.3686 50.2111 33.2682 49.829 31.9752C49.503 30.8106 49.332 29.4898 49.316 28.0127C49.313 27.7389 49.5365 27.5186 49.8103 27.5186H52.9009C53.172 27.5186 53.3925 27.7347 53.4103 28.0052C53.4497 28.6028 53.5327 29.1834 53.6595 29.7469C53.8203 30.4347 54.0516 31.0399 54.3532 31.5626C54.6548 32.0578 55.0268 32.4567 55.4691 32.7593C55.9316 33.0619 56.4745 33.2132 57.0978 33.2132C58.063 33.2132 58.8773 32.8143 59.5409 32.0165C60.2044 31.1912 60.5362 30.077 60.5362 28.674C60.5362 27.5736 60.3753 26.7345 60.0536 26.1568C59.752 25.5791 59.3599 25.1664 58.8773 24.9188C58.3947 24.6437 57.8619 24.4924 57.2788 24.4649C56.8931 24.4272 56.5168 24.4025 56.15 24.3906C55.874 24.3816 55.6501 24.1585 55.6501 23.8824V20.8189Z" fill="#3370FF"/>
<path d="M64.5 43H15.5C15.2239 43 15 43.2239 15 43.5V48.125C15 48.4011 15.2239 48.625 15.5 48.625H64.5C64.7761 48.625 65 48.4011 65 48.125V43.5C65 43.2239 64.7761 43 64.5 43Z" fill="#00D6B9"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -171,7 +171,7 @@ const state = reactive({
'richTextView',
'liquid',
'gauge',
'text',
'indicator',
'label',
'word-cloud',
'flow-map',
@ -181,7 +181,7 @@ const state = reactive({
'richTextView',
'liquid',
'gauge',
'text',
'indicator',
'label',
'word-cloud',
'flow-map',

View File

@ -0,0 +1,370 @@
<script setup lang="ts">
import { getData } from '@/api/chart'
import { nextTick, ref, reactive, shallowRef, computed, CSSProperties, toRefs, watch } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { customAttrTrans, customStyleTrans, recursionTransObj } from '@/utils/canvasStyle'
import { deepCopy } from '@/utils/utils'
import { cloneDeep, defaultsDeep, defaultTo } from 'lodash-es'
import {
BASE_VIEW_CONFIG,
CHART_CONT_FAMILY_MAP,
DEFAULT_INDICATOR_NAME_STYLE,
DEFAULT_INDICATOR_STYLE
} from '@/views/chart/components/editor/util/chart'
import { valueFormatter } from '@/views/chart/components/js/formatter'
const props = defineProps({
view: {
type: Object,
default() {
return {
propValue: null
}
}
},
showPosition: {
type: String,
required: false,
default: 'canvas'
},
scale: {
type: Number,
required: false,
default: 1
},
terminal: {
type: String,
default: 'pc'
}
})
const { view, showPosition, scale, terminal } = toRefs(props)
const dvMainStore = dvMainStoreWithOut()
const errMsg = ref('')
const isError = ref(false)
const state = reactive({
data: null,
loading: false,
totalItems: 0
})
const chartData = shallowRef<Partial<Chart['data']>>({
fields: []
})
const resultObject = computed(() => {
const list = chartData.value?.series
if (list && list.length > 0) {
return list[0]
}
return undefined
})
const resultName = computed(() => {
return resultObject.value?.name
})
const result = computed(() => {
const list = resultObject.value?.data
let _result = undefined
if (list && list.length > 0) {
_result = list[0]
}
if (_result === null || _result === undefined) {
if (view.value.senior && view.value.senior?.functionCfg?.emptyDataStrategy === 'setZero') {
_result = 0
} else {
return '-'
}
}
return _result
})
const thresholdColor = computed(() => {
if (result.value === '-') {
return undefined
}
const value = result.value
let color = undefined
if (view.value.senior && view.value.senior.threshold?.labelThreshold?.length > 0) {
const senior = view.value.senior
for (let i = 0; i < senior.threshold.labelThreshold.length; i++) {
let flag = false
const t = senior.threshold.labelThreshold[i]
const tv = parseFloat(t.value)
if (t.term === 'eq') {
if (value === tv) {
color = t.color
flag = true
}
} else if (t.term === 'not_eq') {
if (value !== tv) {
color = t.color
flag = true
}
} else if (t.term === 'lt') {
if (value < tv) {
color = t.color
flag = true
}
} else if (t.term === 'gt') {
if (value > tv) {
color = t.color
flag = true
}
} else if (t.term === 'le') {
if (value <= tv) {
color = t.color
flag = true
}
} else if (t.term === 'ge') {
if (value >= tv) {
color = t.color
flag = true
}
} else if (t.term === 'between') {
const min = parseFloat(t.min)
const max = parseFloat(t.max)
if (min <= value && value <= max) {
color = t.color
flag = true
}
}
if (flag) {
break
} else if (i === senior.threshold.labelThreshold.length - 1) {
color = t.color
}
}
}
return color
})
const formattedResult = computed(() => {
let _result = result.value
if (_result === '-') {
return _result
}
//
if (view.value.yAxis && view.value.yAxis.length > 0 && view.value.yAxis[0].formatterCfg) {
return valueFormatter(_result, view.value.yAxis[0].formatterCfg)
}
return _result
})
const emit = defineEmits(['onChartClick', 'onDrillFilters', 'onJumpClick'])
const contentStyle = ref({
display: 'flex',
'flex-direction': 'column',
'align-items': 'center',
'justify-content': 'center',
height: '100%'
})
const indicatorClass = ref<CSSProperties>({
color: DEFAULT_INDICATOR_STYLE.color,
'font-size': DEFAULT_INDICATOR_STYLE.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[DEFAULT_INDICATOR_STYLE.fontFamily],
DEFAULT_INDICATOR_STYLE.fontFamily
),
'font-weight': DEFAULT_INDICATOR_STYLE.isBolder ? 'bold' : 'normal',
'font-style': DEFAULT_INDICATOR_STYLE.isItalic ? 'italic' : 'normal',
'letter-spacing': DEFAULT_INDICATOR_STYLE.letterSpace + 'px',
'text-shadow': DEFAULT_INDICATOR_STYLE.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
})
const indicatorSuffixClass = ref<CSSProperties>({
color: DEFAULT_INDICATOR_STYLE.suffixColor,
'font-size': DEFAULT_INDICATOR_STYLE.suffixFontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[DEFAULT_INDICATOR_STYLE.suffixFontFamily],
DEFAULT_INDICATOR_STYLE.suffixFontFamily
),
'font-weight': DEFAULT_INDICATOR_STYLE.suffixIsBolder ? 'bold' : 'normal',
'font-style': DEFAULT_INDICATOR_STYLE.suffixIsItalic ? 'italic' : 'normal',
'letter-spacing': DEFAULT_INDICATOR_STYLE.suffixLetterSpace + 'px',
'text-shadow': DEFAULT_INDICATOR_STYLE.suffixFontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
})
const suffixContent = ref('')
const indicatorNameShow = ref(false)
const indicatorNameClass = ref<CSSProperties>({
color: DEFAULT_INDICATOR_NAME_STYLE.color,
'font-size': DEFAULT_INDICATOR_NAME_STYLE.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[DEFAULT_INDICATOR_NAME_STYLE.fontFamily],
DEFAULT_INDICATOR_NAME_STYLE.fontFamily
),
'font-weight': DEFAULT_INDICATOR_NAME_STYLE.isBolder ? 'bold' : 'normal',
'font-style': DEFAULT_INDICATOR_NAME_STYLE.isItalic ? 'italic' : 'normal',
'letter-spacing': DEFAULT_INDICATOR_NAME_STYLE.letterSpace + 'px',
'text-shadow': DEFAULT_INDICATOR_NAME_STYLE.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
})
function setThresholdColor(_value, _color) {
if (_color === undefined) {
return
}
if (_value === '-') {
return
}
indicatorClass.value.color = _color
}
watch([result, thresholdColor], (value, oldValue) => {
const _value = value[0]
const _color = value[1]
setThresholdColor(_value, _color)
})
const renderChart = async view => {
if (!view) {
return
}
const chart = deepCopy({
...defaultsDeep(view, cloneDeep(BASE_VIEW_CONFIG)),
data: chartData.value
})
recursionTransObj(customAttrTrans, chart.customAttr, scale.value, terminal.value)
recursionTransObj(customStyleTrans, chart.customStyle, scale.value, terminal.value)
if (chart.customAttr) {
const customAttr = chart.customAttr
if (customAttr.indicator) {
switch (customAttr.indicator.hPosition) {
case 'left':
contentStyle.value['align-items'] = 'flex-start'
break
case 'right':
contentStyle.value['align-items'] = 'flex-end'
break
default:
contentStyle.value['align-items'] = 'center'
}
switch (customAttr.indicator.vPosition) {
case 'top':
contentStyle.value['justify-content'] = 'flex-start'
break
case 'bottom':
contentStyle.value['justify-content'] = 'flex-end'
break
default:
contentStyle.value['justify-content'] = 'center'
}
let color = customAttr.indicator.color
indicatorClass.value = {
color: color,
'font-size': customAttr.indicator.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[customAttr.indicator.fontFamily],
DEFAULT_INDICATOR_STYLE.fontFamily
),
'font-weight': customAttr.indicator.isBolder ? 'bold' : 'normal',
'font-style': customAttr.indicator.isItalic ? 'italic' : 'normal',
'letter-spacing': customAttr.indicator.letterSpace + 'px',
'text-shadow': customAttr.indicator.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
indicatorSuffixClass.value = {
color: customAttr.indicator.suffixColor,
'font-size': customAttr.indicator.suffixFontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[customAttr.indicator.suffixFontFamily],
DEFAULT_INDICATOR_STYLE.suffixFontFamily
),
'font-weight': customAttr.indicator.suffixIsBolder ? 'bold' : 'normal',
'font-style': customAttr.indicator.suffixIsItalic ? 'italic' : 'normal',
'letter-spacing': customAttr.indicator.suffixLetterSpace + 'px',
'text-shadow': customAttr.indicator.suffixFontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
suffixContent.value = defaultTo(customAttr.indicator.suffix, '')
}
if (customAttr.indicatorName && customAttr.indicatorName.show) {
indicatorNameShow.value = true
indicatorNameClass.value = {
color: customAttr.indicatorName.color,
'font-size': customAttr.indicatorName.fontSize + 'px',
'font-family': defaultTo(
CHART_CONT_FAMILY_MAP[customAttr.indicatorName.fontFamily],
DEFAULT_INDICATOR_NAME_STYLE.fontFamily
),
'font-weight': customAttr.indicatorName.isBolder ? 'bold' : 'normal',
'font-style': customAttr.indicatorName.isItalic ? 'italic' : 'normal',
'letter-spacing': customAttr.indicatorName.letterSpace + 'px',
'text-shadow': customAttr.indicatorName.fontShadow ? '2px 2px 4px' : 'none',
'font-synthesis': 'weight style'
}
} else {
indicatorNameShow.value = false
}
}
setThresholdColor(result.value, thresholdColor.value)
}
const calcData = (view, callback) => {
if (view.tableId || view['dataFrom'] === 'template') {
state.loading = true
isError.value = false
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 {
chartData.value = res?.data as Partial<Chart['data']>
console.log(chartData.value)
emit('onDrillFilters', res?.drillFilters)
dvMainStore.setViewDataDetails(view.id, chartData.value)
renderChart(res)
}
callback?.()
})
.catch(() => {
callback?.()
})
} else {
if (view.type === 'map') {
renderChart(view)
}
callback?.()
}
}
defineExpose({
calcData,
renderChart
})
</script>
<template>
<div :style="contentStyle">
<div>
<span :style="indicatorClass">{{ formattedResult }}</span>
<span :style="indicatorSuffixClass">{{ suffixContent }}</span>
</div>
<div v-if="indicatorNameShow">
<span :style="indicatorNameClass">{{ resultName }}</span>
</div>
</div>
</template>
<style scoped lang="less"></style>

View File

@ -792,6 +792,7 @@ export default {
total: '共',
items: '条数据',
chart_liquid: '水波图',
chart_indicator: '指标卡',
drag_block_progress: '进度指示',
liquid_max: '目标值',
liquid_outline_border: '边框粗细',
@ -922,6 +923,9 @@ export default {
value_formatter_unit: '数量单位',
value_formatter_decimal_count: '小数位数',
value_formatter_suffix: '单位后缀',
indicator_suffix_placeholder: '请输入1-10个字符',
indicator_suffix: '后缀',
indicator_value: '指标值',
value_formatter_thousand_separator: '千分符',
value_formatter_example: '示例',
unit_none: '无',

View File

@ -32,6 +32,38 @@ declare interface ChartStyle {
}
}
declare interface ChartIndicatorStyle {
show: boolean
fontSize: string
color: string
hPosition: 'left' | 'center' | 'right'
vPosition: 'top' | 'center' | 'bottom'
isItalic: boolean
isBolder: boolean
fontFamily: string
letterSpace: string
fontShadow: boolean
suffix: string
suffixFontSize: string
suffixColor: string
suffixIsItalic: boolean
suffixIsBolder: boolean
suffixFontFamily: string
suffixLetterSpace: string
suffixFontShadow: boolean
}
declare interface ChartIndicatorNameStyle {
show: boolean
fontSize: string
color: string
isItalic: boolean
isBolder: boolean
fontFamily: string
letterSpace: string
fontShadow: boolean
}
/**
* 标题样式设置
*/

View File

@ -20,6 +20,8 @@ declare type EditorProperty =
| 'map-mapping'
| 'jump-set'
| 'linkage'
| 'indicator-value-selector'
| 'indicator-name-selector'
declare type EditorPropertyInner = {
[key in EditorProperty]?: string[]
}

View File

@ -124,7 +124,9 @@ export const customAttrTrans = {
'radarSize' // 雷达占比
],
label: ['fontSize'],
tooltip: ['fontSize']
tooltip: ['fontSize'],
indicator: ['fontSize', 'suffixFontSize'],
indicatorName: ['fontSize']
}
export const customStyleTrans = {
text: ['fontSize'],
@ -257,7 +259,8 @@ export const THEME_STYLE_TRANS_SLAVE1 = {
export const THEME_ATTR_TRANS_MAIN = {
label: ['color'],
tooltip: ['color']
tooltip: ['color'],
indicatorName: ['color']
}
export const THEME_ATTR_TRANS_MAIN_SYMBOL = {

View File

@ -22,7 +22,9 @@ export const customAttrTrans = {
textStyle: ['fontSize']
},
slider: ['fontSize'],
graphic: ['fontSize']
graphic: ['fontSize'],
indicator: ['fontSize', 'suffixFontSize'],
indicatorName: ['fontSize']
}
export const customStyleTrans = {
text: ['fontSize'],

View File

@ -85,7 +85,9 @@ watch(
const showValueFormatter = computed<boolean>(() => {
return (
(props.chart.type === 'table-normal' || props.chart.type === 'table-info') &&
(props.chart.type === 'table-normal' ||
props.chart.type === 'table-info' ||
props.chart.type === 'indicator') &&
(props.item.deType === 2 || props.item.deType === 3)
)
})
@ -118,7 +120,7 @@ const isEnableCompare = () => {
// /
if (
t1.length > 0 &&
chart.value.type !== 'text' &&
chart.value.type !== 'indicator' &&
chart.value.type !== 'label' &&
chart.value.type !== 'gauge' &&
chart.value.type !== 'liquid'

View File

@ -36,7 +36,7 @@ const props = defineProps({
<span v-else-if="props.view.type && props.view.type.includes('gauge')">{{
t('chart.drag_block_gauge_angel')
}}</span>
<span v-else-if="props.view.type && props.view.type.includes('text')">{{
<span v-else-if="props.view.type && props.view.type.includes('indicator')">{{
t('chart.drag_block_label_value')
}}</span>
<span v-else-if="props.view.type && props.view.type === 'map'">{{

View File

@ -54,7 +54,7 @@ const init = () => {
}
}
const showIgnoreOption = computed(() => {
return !equalsAny(props.chart.type, 'table-pivot', 'table-info')
return !equalsAny(props.chart.type, 'table-pivot', 'table-info', 'indicator')
})
const showEmptyDataFieldCtrl = computed(() => {

View File

@ -91,6 +91,7 @@ const changeLabelThreshold = () => {
// check line config
for (let i = 0; i < state.thresholdArr.length; i++) {
const ele = state.thresholdArr[i]
console.log(ele)
if (!ele.term || ele.term === '') {
ElMessage.error(t('chart.exp_can_not_empty'))
return
@ -109,7 +110,8 @@ const changeLabelThreshold = () => {
return
}
} else {
if (!ele.value) {
console.log(ele.value === undefined)
if (ele.value === undefined) {
ElMessage.error(t('chart.value_can_not_empty'))
return
}
@ -325,30 +327,54 @@ init()
</el-col>
<!--指标卡-->
<el-col v-if="props.chart.type && props.chart.type === 'text'">
<el-col v-if="props.chart.type && props.chart.type === 'indicator'">
<el-col>
<el-button
:title="t('chart.edit')"
class="circle-button"
type="primary"
text
size="small"
style="width: 24px; margin-left: 4px"
@click="editLabelThreshold"
<div class="inner-container">
<span class="label" :class="'label-' + props.themes">阈值设置</span>
<span class="right-btns">
<span
class="set-text-info"
:class="{ 'set-text-info-dark': themes === 'dark' }"
v-if="state.thresholdForm?.labelThreshold?.length > 0"
>
已设置
</span>
<el-button
:title="t('chart.edit')"
:class="'label-' + props.themes"
:style="{ width: '24px', marginLeft: '6px' }"
:disabled="!state.thresholdForm.enable"
class="circle-button"
text
size="small"
@click="editLabelThreshold"
>
<template #icon>
<el-icon size="14px">
<Icon name="icon_edit_outlined" />
</el-icon>
</template>
</el-button>
</span>
</div>
<div
class="threshold-container"
:class="{ 'threshold-container-dark': themes === 'dark' }"
v-if="state.thresholdForm.labelThreshold.length > 0"
>
<template #icon>
<el-icon size="14px">
<Icon name="icon_edit_outlined" />
</el-icon>
</template>
</el-button>
<el-col style="padding: 0 18px">
<el-row
<div class="field-style" :class="{ 'field-style-dark': themes === 'dark' }">
<span class="field-text" style="padding-left: 12px">
{{ t('chart.indicator_value') }}
</span>
</div>
<div
v-for="(item, index) in state.thresholdForm.labelThreshold"
:key="index"
class="line-style"
>
<el-col :span="6">
<div style="flex: 1">
<span v-if="item.term === 'eq'" :title="t('chart.filter_eq')">{{
t('chart.filter_eq')
}}</span>
@ -370,25 +396,31 @@ init()
<span v-else-if="item.term === 'between'" :title="t('chart.filter_between')">{{
t('chart.filter_between')
}}</span>
</el-col>
<el-col :span="12">
</div>
<div style="flex: 1; margin: 0 8px">
<span v-if="item.term !== 'between'" :title="item.value">{{ item.value }}</span>
<span v-if="item.term === 'between'">
{{ item.min }}&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;{{ item.max }}
</span>
</el-col>
<el-col :span="6">
<span
:style="{
width: '14px',
height: '14px',
backgroundColor: item.color,
border: 'solid 1px #e1e4e8'
}"
/>
</el-col>
</el-row>
</el-col>
</div>
<div
:title="t('chart.textColor')"
:style="{
backgroundColor: item.color
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
<!-- <div
:title="t('chart.backgroundColor')"
:style="{
backgroundColor: item.backgroundColor
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>-->
</div>
</div>
</el-col>
</el-col>

View File

@ -2,6 +2,7 @@
import { reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL } from '../../../util/chart'
import { ElSpace } from 'element-plus-secondary'
const { t } = useI18n()
@ -105,62 +106,88 @@ init()
</template>
</el-button>
<div @keydown.stop @keyup.stop style="max-height: 50vh; overflow-y: auto">
<el-row v-for="(item, index) in state.thresholdArr" :key="index" class="line-item">
<el-col :span="6">
<el-select v-model="item.term" size="small" @change="changeThreshold">
<el-option-group v-for="(group, idx) in valueOptions" :key="idx" :label="group.label">
<el-option
v-for="opt in group.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
<el-row
v-for="(item, index) in state.thresholdArr"
:key="index"
class="line-item"
:gutter="8"
>
<el-col :span="5">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group v-for="(group, idx) in valueOptions" :key="idx" :label="group.label">
<el-option
v-for="opt in group.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-option-group>
</el-select>
</el-form-item>
</el-col>
<el-col :span="9" style="text-align: center">
<el-form-item class="form-item" v-if="item.term !== 'between'">
<el-input-number
controls-position="right"
v-model="item.value"
class="value-item"
:placeholder="t('chart.drag_block_label_value')"
clearable
@change="changeThreshold"
/>
</el-form-item>
<el-space v-if="item.term === 'between'">
<el-form-item class="form-item">
<el-input-number
v-model="item.min"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_min')"
clearable
@change="changeThreshold"
/>
</el-option-group>
</el-select>
</el-form-item>
<div style="display: flex; justify-content: center; min-width: 40px">
<span>{{ t('chart.drag_block_label_value') }}</span>
</div>
<el-form-item class="form-item">
<el-input-number
v-model="item.max"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_max')"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-space>
</el-col>
<el-col :span="14" style="text-align: center">
<el-input-number
v-if="item.term !== 'between'"
controls-position="right"
v-model="item.value"
class="value-item"
:placeholder="t('chart.drag_block_label_value')"
size="small"
clearable
@change="changeThreshold"
/>
<span v-if="item.term === 'between'">
<el-input-number
v-model="item.min"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_min')"
size="small"
clearable
@change="changeThreshold"
/>
<span style="margin: 0 4px">{{ t('chart.drag_block_label_value') }}</span>
<el-input-number
v-model="item.max"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_max')"
size="small"
clearable
@change="changeThreshold"
/>
</span>
</el-col>
<el-col :span="2" style="text-align: center">
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<div class="color-title">{{ t('chart.textColor') }}</div>
<el-color-picker
is-custom
size="large"
v-model="item.color"
show-alpha
class="color-picker-style"
:predefine="predefineColors"
@change="changeThreshold"
/>
</el-col>
<el-col :span="2">
</div>
<!-- <div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<div class="color-title">{{ t('chart.backgroundColor') }}</div>
<el-color-picker
is-custom
size="large"
v-model="item.backgroundColor"
show-alpha
class="color-picker-style"
:predefine="predefineColors"
@change="changeThreshold"
/>
</div>-->
<div style="display: flex; align-items: center; justify-content: center; margin-left: 8px">
<el-button
class="circle-button"
type="text"
@ -172,7 +199,7 @@ init()
<Icon name="icon_delete-trash_outlined"></Icon>
</template>
</el-button>
</el-col>
</div>
</el-row>
</div>
</el-col>
@ -233,4 +260,21 @@ span {
width: 28px;
height: 28px;
}
.color-title {
color: #646a73;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
padding: 0 8px;
}
.form-item {
height: 28px !important;
margin-bottom: 0 !important;
:deep(.el-form-item__label) {
font-size: 12px;
}
}
</style>

View File

@ -19,6 +19,8 @@ import TableHeaderSelector from '@/views/chart/components/editor/editor-style/co
import TableCellSelector from '@/views/chart/components/editor/editor-style/components/table/TableCellSelector.vue'
import TableTotalSelector from '@/views/chart/components/editor/editor-style/components/table/TableTotalSelector.vue'
import MiscStyleSelector from '@/views/chart/components/editor/editor-style/components/MiscStyleSelector.vue'
import IndicatorValueSelector from '@/views/chart/components/editor/editor-style/components/IndicatorValueSelector.vue'
import IndicatorNameSelector from '@/views/chart/components/editor/editor-style/components/IndicatorNameSelector.vue'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -83,7 +85,9 @@ const emit = defineEmits([
'onTableCellChange',
'onTableTotalChange',
'onChangeMiscStyleForm',
'onExtTooltipChange'
'onExtTooltipChange',
'onIndicatorChange',
'onIndicatorNameChange'
])
const showProperties = (property: EditorProperty) => properties.value?.includes(property)
@ -112,6 +116,14 @@ const onTextChange = (val, prop) => {
state.initReady && emit('onTextChange', val, prop)
}
const onIndicatorChange = (val, prop) => {
state.initReady && emit('onIndicatorChange', val, prop)
}
const onIndicatorNameChange = (val, prop) => {
state.initReady && emit('onIndicatorNameChange', val, prop)
}
const onLegendChange = (val, prop) => {
state.initReady && emit('onLegendChange', val, prop)
}
@ -224,6 +236,39 @@ watch(
component-position="component"
/>
</el-collapse-item>
<el-collapse-item
:effect="themes"
v-if="showProperties('indicator-value-selector')"
name="indicator-value"
title="指标值"
>
<indicator-value-selector
:property-inner="propertyInnerAll['indicator-value-selector']"
:themes="themes"
class="attr-selector"
:chart="chart"
:quota-fields="props.quotaData"
@onIndicatorChange="onIndicatorChange"
/>
</el-collapse-item>
<collapse-switch-item
:themes="themes"
v-model="chart.customAttr.indicatorName.show"
v-if="showProperties('indicator-name-selector')"
:change-model="chart.customAttr.indicatorName"
@modelChange="val => onIndicatorNameChange(val, 'show')"
title="指标名称"
name="indicator-name"
>
<indicator-name-selector
:property-inner="propertyInnerAll['indicator-name-selector']"
:themes="themes"
class="attr-selector"
:chart="chart"
:quota-fields="props.quotaData"
@onIndicatorNameChange="onIndicatorNameChange"
/>
</collapse-switch-item>
<el-collapse-item
:effect="themes"
v-if="showProperties('misc-selector')"

View File

@ -0,0 +1,345 @@
<script lang="ts" setup>
import { PropType, computed, onMounted, reactive, toRefs, watch, nextTick, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import {
COLOR_PANEL,
CHART_FONT_FAMILY,
CHART_FONT_LETTER_SPACE,
DEFAULT_INDICATOR_NAME_STYLE
} from '@/views/chart/components/editor/util/chart'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { ElButton, ElIcon } from 'element-plus-secondary'
import Icon from '@/components/icon-custom/src/Icon.vue'
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
propertyInner: {
type: Array<string>
}
})
const emit = defineEmits(['onIndicatorNameChange'])
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const predefineColors = COLOR_PANEL
const fontFamily = CHART_FONT_FAMILY
const fontLetterSpace = CHART_FONT_LETTER_SPACE
const state = reactive({
indicatorNameForm: JSON.parse(JSON.stringify(DEFAULT_INDICATOR_NAME_STYLE))
})
const { chart } = toRefs(props)
const fontSizeList = computed(() => {
const arr = []
for (let i = 10; i <= 60; i = i + 2) {
arr.push({
name: i + '',
value: i
})
}
return arr
})
const changeTitleStyle = prop => {
emit('onIndicatorNameChange', state.indicatorNameForm, prop)
}
const init = () => {
const customText = defaultsDeep(
cloneDeep(props.chart?.customAttr?.indicatorName),
cloneDeep(DEFAULT_INDICATOR_NAME_STYLE)
)
state.indicatorNameForm = cloneDeep(customText)
//
nextTick(() => {
state.indicatorNameForm.color = customText.color
})
}
onMounted(() => {
init()
})
watch(
() => props.chart?.customAttr?.indicatorName,
() => {
init()
},
{ deep: true }
)
</script>
<template>
<div>
<el-form
ref="indicatorNameForm"
:disabled="!state.indicatorNameForm.show"
:model="state.indicatorNameForm"
label-position="top"
>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:effect="themes"
:label="t('chart.text')"
>
<el-select
:effect="themes"
v-model="state.indicatorNameForm.fontFamily"
:placeholder="t('chart.font_family')"
@change="changeTitleStyle('fontFamily')"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-color-picker
:effect="themes"
v-model="state.indicatorNameForm.color"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTitleStyle('color')"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 56px"
:effect="themes"
v-model="state.indicatorNameForm.fontSize"
:placeholder="t('chart.text_fontsize')"
size="small"
@change="changeTitleStyle('fontSize')"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<el-select
:effect="themes"
v-model="state.indicatorNameForm.letterSpace"
:placeholder="t('chart.quota_letter_space')"
@change="changeTitleStyle('letterSpace')"
>
<template #prefix>
<el-icon>
<Icon name="icon_letter-spacing_outlined" />
</el-icon>
</template>
<el-option
v-for="option in fontLetterSpace"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
</div>
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorNameForm.isBolder"
@change="changeTitleStyle('isBolder')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.bolder') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorNameForm.isBolder }"
>
<el-icon>
<Icon name="icon_bold_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorNameForm.isItalic"
@change="changeTitleStyle('isItalic')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.italic') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorNameForm.isItalic }"
>
<el-icon>
<Icon name="icon_italic_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
</el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.indicatorNameForm.fontShadow"
@change="changeTitleStyle('fontShadow')"
>
{{ t('chart.font_shadow') }}
</el-checkbox>
</el-form-item>
</el-form>
</div>
</template>
<style lang="less" scoped>
:deep(.ed-input .ed-select__prefix--light) {
padding-right: 6px;
}
.icon-btn {
font-size: 16px;
line-height: 16px;
width: 24px;
height: 24px;
text-align: center;
border-radius: 4px;
padding-top: 4px;
color: #1f2329;
cursor: pointer;
&.dark {
color: #a6a6a6;
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
}
}
.is-disabled {
.icon-btn {
color: #8f959e;
cursor: not-allowed;
&:hover {
background-color: inherit;
}
&.active {
background-color: #f5f7fa;
&:hover {
background-color: #f5f7fa;
}
}
&.dark {
color: #5f5f5f;
&.active {
background-color: #373737;
&:hover {
background-color: #373737;
}
}
}
}
}
.icon-checkbox {
:deep(.ed-checkbox__input) {
display: none;
}
:deep(.ed-checkbox__label) {
padding: 0;
}
}
.icon-radio-group {
:deep(.ed-radio) {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
:deep(.ed-radio__input) {
display: none;
}
:deep(.ed-radio__label) {
padding: 0;
}
}
.position-divider {
width: 1px;
height: 18px;
margin-bottom: 16px;
background: rgba(31, 35, 41, 0.15);
&.position-divider--dark {
background: rgba(235, 235, 235, 0.15);
}
}
.remark-label {
color: var(--N600, #646a73);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
&.remark-label--dark {
color: var(--N600-Dark, #a6a6a6);
}
}
</style>

View File

@ -0,0 +1,632 @@
<script lang="ts" setup>
import { PropType, computed, onMounted, reactive, toRefs, watch, nextTick, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import {
COLOR_PANEL,
CHART_FONT_FAMILY,
CHART_FONT_LETTER_SPACE,
DEFAULT_INDICATOR_STYLE
} from '@/views/chart/components/editor/util/chart'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { cloneDeep, defaultsDeep } from 'lodash-es'
import { ElButton, ElIcon, ElInput } from 'element-plus-secondary'
import Icon from '@/components/icon-custom/src/Icon.vue'
const dvMainStore = dvMainStoreWithOut()
const { batchOptStatus } = storeToRefs(dvMainStore)
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
propertyInner: {
type: Array<string>
}
})
const emit = defineEmits(['onIndicatorChange'])
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
const predefineColors = COLOR_PANEL
const fontFamily = CHART_FONT_FAMILY
const fontLetterSpace = CHART_FONT_LETTER_SPACE
const state = reactive({
indicatorValueForm: JSON.parse(JSON.stringify(DEFAULT_INDICATOR_STYLE))
})
const { chart } = toRefs(props)
const fontSizeList = computed(() => {
const arr = []
for (let i = 10; i <= 60; i = i + 2) {
arr.push({
name: i + '',
value: i
})
}
return arr
})
const changeTitleStyle = prop => {
emit('onIndicatorChange', state.indicatorValueForm, prop)
}
const init = () => {
const customText = defaultsDeep(
cloneDeep(props.chart?.customAttr?.indicator),
cloneDeep(DEFAULT_INDICATOR_STYLE)
)
state.indicatorValueForm = cloneDeep(customText)
//
nextTick(() => {
state.indicatorValueForm.color = customText.color
})
}
onMounted(() => {
init()
})
watch(
() => props.chart?.customAttr?.indicator,
() => {
init()
},
{ deep: true }
)
</script>
<template>
<div>
<el-form
ref="indicatorValueForm"
:disabled="!state.indicatorValueForm.show"
:model="state.indicatorValueForm"
label-position="top"
>
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:effect="themes"
:label="t('chart.text')"
>
<el-select
:effect="themes"
v-model="state.indicatorValueForm.fontFamily"
:placeholder="t('chart.font_family')"
@change="changeTitleStyle('fontFamily')"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-color-picker
:effect="themes"
v-model="state.indicatorValueForm.color"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTitleStyle('color')"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 56px"
:effect="themes"
v-model="state.indicatorValueForm.fontSize"
:placeholder="t('chart.text_fontsize')"
size="small"
@change="changeTitleStyle('fontSize')"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<el-select
:effect="themes"
v-model="state.indicatorValueForm.letterSpace"
:placeholder="t('chart.quota_letter_space')"
@change="changeTitleStyle('letterSpace')"
>
<template #prefix>
<el-icon>
<Icon name="icon_letter-spacing_outlined" />
</el-icon>
</template>
<el-option
v-for="option in fontLetterSpace"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
</div>
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.isBolder"
@change="changeTitleStyle('isBolder')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.bolder') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorValueForm.isBolder }"
>
<el-icon>
<Icon name="icon_bold_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.isItalic"
@change="changeTitleStyle('isItalic')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.italic') }}
</template>
<div
class="icon-btn"
:class="{ dark: themes === 'dark', active: state.indicatorValueForm.isItalic }"
>
<el-icon>
<Icon name="icon_italic_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<div class="position-divider" :class="'position-divider--' + themes"></div>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-radio-group
:effect="themes"
class="icon-radio-group"
v-model="state.indicatorValueForm.hPosition"
@change="changeTitleStyle('hPosition')"
>
<el-radio :effect="themes" label="left">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_left') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.hPosition === 'left'
}"
>
<el-icon>
<Icon name="icon_left-alignment_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio :effect="themes" label="center">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_center') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.hPosition === 'center'
}"
>
<el-icon>
<Icon name="icon_center-alignment_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio :effect="themes" label="right">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_right') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.hPosition === 'right'
}"
>
<el-icon>
<Icon name="icon_right-alignment_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
</el-radio-group>
</el-form-item>
</el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-radio-group
:effect="themes"
class="icon-radio-group"
v-model="state.indicatorValueForm.vPosition"
@change="changeTitleStyle('vPosition')"
>
<el-radio label="top">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_top') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.vPosition === 'top'
}"
>
<el-icon>
<Icon name="icon_top-align_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio label="center">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_center') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.vPosition === 'center'
}"
>
<el-icon>
<Icon name="icon_vertical-align_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
<el-radio label="bottom">
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.text_pos_bottom') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.vPosition === 'bottom'
}"
>
<el-icon>
<Icon name="icon_bottom-align_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.indicatorValueForm.fontShadow"
@change="changeTitleStyle('fontShadow')"
>
{{ t('chart.font_shadow') }}
</el-checkbox>
</el-form-item>
<el-divider class="m-divider" :class="{ 'divider-dark': themes === 'dark' }" />
<el-form-item
class="form-item"
:class="'form-item-' + themes"
:label="t('chart.indicator_suffix')"
>
<el-input
v-model="state.indicatorValueForm.suffix"
:placeholder="t('chart.indicator_suffix_placeholder')"
maxlength="10"
@change="changeTitleStyle('suffix')"
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" :effect="themes">
<el-select
:effect="themes"
v-model="state.indicatorValueForm.suffixFontFamily"
:placeholder="t('chart.font_family')"
@change="changeTitleStyle('suffixFontFamily')"
>
<el-option
v-for="option in fontFamily"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
<div style="display: flex">
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-right: 4px">
<el-color-picker
:effect="themes"
v-model="state.indicatorValueForm.suffixColor"
class="color-picker-style"
:predefine="predefineColors"
@change="changeTitleStyle('suffixColor')"
is-custom
/>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding: 0 4px">
<el-tooltip content="字号" :effect="toolTip" placement="top">
<el-select
style="width: 56px"
:effect="themes"
v-model="state.indicatorValueForm.suffixFontSize"
:placeholder="t('chart.text_fontsize')"
size="small"
@change="changeTitleStyle('suffixFontSize')"
>
<el-option
v-for="option in fontSizeList"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-tooltip>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes" style="padding-left: 4px">
<el-select
:effect="themes"
v-model="state.indicatorValueForm.suffixLetterSpace"
:placeholder="t('chart.quota_letter_space')"
@change="changeTitleStyle('suffixLetterSpace')"
>
<template #prefix>
<el-icon>
<Icon name="icon_letter-spacing_outlined" />
</el-icon>
</template>
<el-option
v-for="option in fontLetterSpace"
:key="option.value"
:label="option.name"
:value="option.value"
/>
</el-select>
</el-form-item>
</div>
<el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.suffixIsBolder"
@change="changeTitleStyle('suffixIsBolder')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.bolder') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.suffixIsBolder
}"
>
<el-icon>
<Icon name="icon_bold_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
:effect="themes"
class="icon-checkbox"
v-model="state.indicatorValueForm.suffixIsItalic"
@change="changeTitleStyle('suffixIsItalic')"
>
<el-tooltip :effect="toolTip" placement="top">
<template #content>
{{ t('chart.italic') }}
</template>
<div
class="icon-btn"
:class="{
dark: themes === 'dark',
active: state.indicatorValueForm.suffixIsItalic
}"
>
<el-icon>
<Icon name="icon_italic_outlined" />
</el-icon>
</div>
</el-tooltip>
</el-checkbox>
</el-form-item>
</el-space>
<el-form-item class="form-item" :class="'form-item-' + themes">
<el-checkbox
size="small"
:effect="themes"
v-model="state.indicatorValueForm.suffixFontShadow"
@change="changeTitleStyle('suffixFontShadow')"
>
{{ t('chart.font_shadow') }}
</el-checkbox>
</el-form-item>
</el-form>
</div>
</template>
<style lang="less" scoped>
:deep(.ed-input .ed-select__prefix--light) {
padding-right: 6px;
}
.icon-btn {
font-size: 16px;
line-height: 16px;
width: 24px;
height: 24px;
text-align: center;
border-radius: 4px;
padding-top: 4px;
color: #1f2329;
cursor: pointer;
&.dark {
color: #a6a6a6;
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(255, 255, 255, 0.1);
}
}
&.active {
color: #3370ff;
background-color: rgba(51, 112, 255, 0.1);
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
}
}
.is-disabled {
.icon-btn {
color: #8f959e;
cursor: not-allowed;
&:hover {
background-color: inherit;
}
&.active {
background-color: #f5f7fa;
&:hover {
background-color: #f5f7fa;
}
}
&.dark {
color: #5f5f5f;
&.active {
background-color: #373737;
&:hover {
background-color: #373737;
}
}
}
}
}
.icon-checkbox {
:deep(.ed-checkbox__input) {
display: none;
}
:deep(.ed-checkbox__label) {
padding: 0;
}
}
.icon-radio-group {
:deep(.ed-radio) {
margin-right: 8px;
&:last-child {
margin-right: 0;
}
}
:deep(.ed-radio__input) {
display: none;
}
:deep(.ed-radio__label) {
padding: 0;
}
}
.position-divider {
width: 1px;
height: 18px;
margin-bottom: 16px;
background: rgba(31, 35, 41, 0.15);
&.position-divider--dark {
background: rgba(235, 235, 235, 0.15);
}
}
.remark-label {
color: var(--N600, #646a73);
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
&.remark-label--dark {
color: var(--N600-Dark, #a6a6a6);
}
}
.m-divider {
margin: 0 0 16px;
border-color: rgba(31, 35, 41, 0.15);
&.divider-dark {
border-color: rgba(255, 255, 255, 0.15);
}
}
</style>

View File

@ -577,7 +577,7 @@ onMounted(() => {
<!--liquid-end-->
<!--text&label-start-->
<template v-if="props.chart.type.includes('text') || props.chart.type.includes('label')">
<template v-if="props.chart.type.includes('indicator') || props.chart.type.includes('label')">
<el-form-item
:label="t('chart.quota_font_size')"
class="form-item"

View File

@ -657,6 +657,16 @@ const onLabelChange = val => {
renderChart(view.value)
}
const onIndicatorChange = val => {
view.value.customAttr.indicator = val
renderChart(view.value)
}
const onIndicatorNameChange = val => {
view.value.customAttr.indicatorName = val
renderChart(view.value)
}
const onTooltipChange = (chartForm: ChartEditorForm<ChartTooltipAttr>, prop: string) => {
const { data, requestData, render } = chartForm
let tooltipObj = data
@ -1703,6 +1713,8 @@ const onRefreshChange = val => {
@onChangeXAxisForm="onChangeXAxisForm"
@onChangeYAxisForm="onChangeYAxisForm"
@onTextChange="onTextChange"
@onIndicatorChange="onIndicatorChange"
@onIndicatorNameChange="onIndicatorNameChange"
@onLegendChange="onLegendChange"
@onBackgroundChange="onBackgroundChange"
@onBasicStyleChange="onBasicStyleChange"

View File

@ -347,6 +347,38 @@ export const DEFAULT_TITLE_STYLE: ChartTextStyle = {
fontShadow: false
}
export const DEFAULT_INDICATOR_STYLE: ChartIndicatorStyle = {
show: true,
fontSize: '20',
color: '#5470C6',
hPosition: 'center',
vPosition: 'center',
isItalic: false,
isBolder: true,
fontFamily: 'Microsoft YaHei',
letterSpace: '0',
fontShadow: false,
suffix: '',
suffixFontSize: '14',
suffixColor: '#5470C6',
suffixIsItalic: false,
suffixIsBolder: true,
suffixFontFamily: 'Microsoft YaHei',
suffixLetterSpace: '0',
suffixFontShadow: false
}
export const DEFAULT_INDICATOR_NAME_STYLE: ChartIndicatorNameStyle = {
show: true,
fontSize: '18',
color: '#ffffff',
isItalic: false,
isBolder: true,
fontFamily: 'Microsoft YaHei',
letterSpace: '0',
fontShadow: false
}
export const DEFAULT_TITLE_STYLE_BASE: ChartTextStyle = {
show: true,
fontSize: '18',
@ -952,6 +984,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'liquid',
title: t('chart.chart_liquid'),
icon: 'liquid'
},
{
render: 'custom',
category: 'quota',
value: 'indicator',
title: t('chart.chart_indicator'),
icon: 'indicator'
}
]
},
@ -1258,6 +1297,8 @@ export const BASE_VIEW_CONFIG = {
tableTotal: DEFAULT_TABLE_TOTAL,
tableHeader: DEFAULT_TABLE_HEADER,
tableCell: DEFAULT_TABLE_CELL,
indicator: DEFAULT_INDICATOR_STYLE,
indicatorName: DEFAULT_INDICATOR_NAME_STYLE,
map: {
id: '',
level: 'world'

View File

@ -0,0 +1,64 @@
import { AbstractChartView, ChartLibraryType, ChartRenderType } from '../../types'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
/**
* 指标卡视图
*/
export class IndicatorChartView extends AbstractChartView {
properties: EditorProperty[] = [
'background-overall-component',
'title-selector',
'indicator-value-selector',
'indicator-name-selector',
'threshold',
'function-cfg'
]
propertyInner: EditorPropertyInner = {
'background-overall-component': ['all'],
'title-selector': [
'title',
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'remarkShow',
'fontFamily',
'letterSpace',
'fontShadow'
],
'indicator-value-selector': [
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'fontFamily',
'letterSpace',
'fontShadow'
],
'indicator-name-selector': [
'title',
'fontSize',
'color',
'hPosition',
'isItalic',
'isBolder',
'fontFamily',
'letterSpace',
'fontShadow'
],
'function-cfg': ['emptyDataStrategy']
}
axis: AxisType[] = ['yAxis', 'filter']
axisConfig: AxisConfig = {
yAxis: {
name: `${t('chart.quota')}`,
type: 'q'
}
}
constructor() {
super(ChartRenderType.CUSTOM, ChartLibraryType.INDICATOR, 'indicator')
}
}

View File

@ -13,7 +13,8 @@ export enum ChartLibraryType {
L7_PLOT = 'l7plot',
ECHARTS = 'echarts',
S2 = 's2',
RICH_TEXT = 'rich-text'
RICH_TEXT = 'rich-text',
INDICATOR = 'indicator'
}
export abstract class AbstractChartView {

View File

@ -230,7 +230,7 @@ export function getRemark(chart) {
return remark
}
export const quotaViews = ['label', 'richTextView', 'text', 'gauge', 'liquid']
export const quotaViews = ['label', 'richTextView', 'indicator', 'gauge', 'liquid']
export function handleEmptyDataStrategy<O extends PickOptions>(chart: Chart, options: O): O {
const { data } = options as unknown as Options

View File

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import ChartComponentG2Plot from './components/ChartComponentG2Plot.vue'
import DeIndicator from '@/custom-component/indicator/DeIndicator.vue'
import {
computed,
CSSProperties,
@ -645,6 +646,13 @@ const iconSize = computed<string>(() => {
:active="active"
:show-position="showPosition"
/>
<de-indicator
v-else-if="showChartView(ChartLibraryType.INDICATOR)"
:themes="canvasStyleData.dashboard.themeColor"
ref="chartComponent"
:view="view"
:show-position="showPosition"
/>
<chart-component-g2-plot
:scale="scale"
:dynamic-area-id="dynamicAreaId"