feat(工作台): 优化工作台所有柱状图
This commit is contained in:
parent
c71fd05a7c
commit
a51ea8702f
|
@ -1,9 +1,11 @@
|
|||
<template>
|
||||
<VCharts v-if="renderChart" ref="chartRef" :option="options" :autoresize="autoResize" :style="{ width, height }" />
|
||||
<VCharts v-if="chartId" ref="chartRef" :option="options" :autoresize="autoResize" :style="{ width, height }" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
||||
import { BarChart, CustomChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
|
||||
import {
|
||||
|
@ -54,11 +56,15 @@
|
|||
},
|
||||
});
|
||||
|
||||
const renderChart = ref(false);
|
||||
|
||||
const chartRef = ref<InstanceType<typeof VCharts>>();
|
||||
nextTick(() => {
|
||||
renderChart.value = true;
|
||||
|
||||
const chartId = ref('');
|
||||
onMounted(() => {
|
||||
chartId.value = getGenerateId();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
chartId.value = '';
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
export default function bindDataZoomEvent(
|
||||
chartRef: any,
|
||||
options: Record<string, any>,
|
||||
barWidth = 12,
|
||||
barWidthMargin = 6,
|
||||
barNumber = 7,
|
||||
minBarGroupWidth = 24
|
||||
) {
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
|
||||
const handleDataZoom = (params: any) => {
|
||||
const containerWidth = chartDom.getDom().offsetWidth;
|
||||
// 计算缩放百分比
|
||||
const percent = (params.end - params.start) / 100;
|
||||
// 计算单组条形图的宽度(包括间隔和最小宽度)
|
||||
const singleGroupWidth = barWidth * barNumber + barWidthMargin * (barNumber - 1) + minBarGroupWidth;
|
||||
// 计算可视区域内的最大条形图组数
|
||||
const maxVisibleGroups = Math.floor(containerWidth / singleGroupWidth);
|
||||
// 根据缩放百分比,计算需要显示的分类数量
|
||||
const val = options.value.xAxis.data.length * percent;
|
||||
|
||||
const calcCount = Math.ceil(val);
|
||||
// 计算每个标签的宽度
|
||||
const labelWidth = (containerWidth - calcCount * minBarGroupWidth) / calcCount;
|
||||
// 更新图表的配置项,重新设置数据缩放和 x 轴标签的显示
|
||||
chartDom.setOption(
|
||||
{
|
||||
...options.value,
|
||||
dataZoom: [
|
||||
{
|
||||
...options.value.dataZoom[0],
|
||||
start: params.start,
|
||||
end: params.end,
|
||||
maxValueSpan: maxVisibleGroups,
|
||||
},
|
||||
],
|
||||
xAxis: {
|
||||
axisLabel: {
|
||||
width: labelWidth,
|
||||
overflow: 'truncate',
|
||||
ellipsis: '...',
|
||||
interval: 0,
|
||||
},
|
||||
...options.value.xAxis,
|
||||
},
|
||||
},
|
||||
{ notMerge: true }
|
||||
);
|
||||
};
|
||||
|
||||
chartDom.chart.on('dataZoom', handleDataZoom);
|
||||
|
||||
const handleResize = () => {
|
||||
if (chartDom) {
|
||||
const currentOptions = chartDom.chart.getOption();
|
||||
if (currentOptions.dataZoom.length) {
|
||||
handleDataZoom({ start: currentOptions.dataZoom[0].start, end: currentOptions.dataZoom[0].end });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return {
|
||||
clear: () => {
|
||||
chartDom.chart.off('dataZoom', handleDataZoom);
|
||||
window.removeEventListener('resize', handleResize);
|
||||
},
|
||||
handleDataZoom,
|
||||
};
|
||||
}
|
|
@ -189,7 +189,7 @@ export const defaultValueMap: Record<string, any> = {
|
|||
},
|
||||
complete: {
|
||||
defaultList: cloneDeep(defaultComplete),
|
||||
color: ['#00C261', '#3370FF', '#D4D4D8', '#FF9964'],
|
||||
color: ['#D4D4D8', '#3370FF', '#00C261', '#FF9964'],
|
||||
defaultName: 'workbench.homePage.completeRate',
|
||||
},
|
||||
},
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="mt-[16px]">
|
||||
<MsChart height="260px" :options="options" />
|
||||
<MsChart ref="chartRef" height="260px" :options="options" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -47,17 +47,17 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
import MsChart from '@/components/pure/chart/index.vue';
|
||||
import bindDataZoomEvent from '@/components/pure/chart/utils';
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import CardSkeleton from './cardSkeleton.vue';
|
||||
|
||||
import { workBugHandlerDetail, workHandleUserOptions } from '@/api/modules/workbench';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import type { OverViewOfProject, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
||||
|
||||
import { getColorScheme, getCommonBarOptions, handleNoDataDisplay } from '../utils';
|
||||
import { createCustomTooltip, getColorScheme, getSeriesData } from '../utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
@ -97,50 +97,21 @@
|
|||
const hasPermission = ref<boolean>(false);
|
||||
|
||||
function handleData(detail: OverViewOfProject) {
|
||||
options.value = getCommonBarOptions(detail.xaxis.length >= 7, [...defectStatusColor, ...getColorScheme(13)]);
|
||||
const { invisible, text } = handleNoDataDisplay(detail.xaxis, hasPermission.value);
|
||||
options.value.graphic.invisible = invisible;
|
||||
options.value.graphic.style.text = text;
|
||||
options.value.xAxis.data = detail.xaxis.map((e) => characterLimit(e, 10));
|
||||
|
||||
let maxAxis = 5;
|
||||
options.value.series = detail.projectCountList.map((item) => {
|
||||
const countData: Record<string, any>[] = item.count.map((e) => {
|
||||
return {
|
||||
name: item.name,
|
||||
value: e,
|
||||
originValue: e,
|
||||
};
|
||||
});
|
||||
|
||||
const itemMax = Math.max(...item.count);
|
||||
|
||||
maxAxis = Math.max(itemMax, maxAxis);
|
||||
const data = detail.projectCountList.map((e) => {
|
||||
return {
|
||||
name: item.name,
|
||||
type: 'bar',
|
||||
stack: 'bugMember',
|
||||
barWidth: 12,
|
||||
data: countData,
|
||||
itemStyle: {
|
||||
borderRadius: [2, 2, 0, 0],
|
||||
},
|
||||
barMinHeight: ((optionData: Record<string, any>[]) => {
|
||||
optionData.forEach((itemValue: any, index: number) => {
|
||||
if (itemValue.value === 0) optionData[index].value = null;
|
||||
});
|
||||
let hasZero = false;
|
||||
for (let i = 0; i < optionData.length; i++) {
|
||||
if (optionData[i].value === 0) {
|
||||
hasZero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hasZero ? 0 : 5;
|
||||
})(countData),
|
||||
value: '',
|
||||
label: e.name,
|
||||
};
|
||||
});
|
||||
options.value.yAxis[0].max = maxAxis <= 100 ? 100 : maxAxis + 50;
|
||||
|
||||
options.value = getSeriesData(
|
||||
data,
|
||||
detail,
|
||||
[...defectStatusColor, ...getColorScheme(13)],
|
||||
false,
|
||||
true,
|
||||
props.item.fullScreen
|
||||
);
|
||||
}
|
||||
const showSkeleton = ref(false);
|
||||
|
||||
|
@ -176,6 +147,7 @@
|
|||
value: e.value,
|
||||
}));
|
||||
}
|
||||
const chartRef = ref<InstanceType<typeof MsChart>>();
|
||||
|
||||
async function handleProjectChange(isRefreshKey: boolean = false, setAll = false) {
|
||||
await nextTick();
|
||||
|
@ -190,7 +162,13 @@
|
|||
}
|
||||
}
|
||||
await nextTick();
|
||||
getDefectMemberDetail();
|
||||
await getDefectMemberDetail();
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
|
||||
if (chartDom && chartDom.chart) {
|
||||
createCustomTooltip(chartDom);
|
||||
bindDataZoomEvent(chartRef, options);
|
||||
}
|
||||
}
|
||||
|
||||
async function changeProject() {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div v-if="props.contentTabList.length" class="card-list">
|
||||
<div v-for="ele of contentTabList" :key="ele.icon" class="card-list-item">
|
||||
<div class="w-full">
|
||||
<div class="card-title flex items-center gap-[8px]">
|
||||
<div :class="`card-title-icon bg-[${ele?.color}]`">
|
||||
<MsIcon :type="ele.icon" class="text-[var(--color-text-fff)]" size="12" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]"> {{ ele.label }}</div>
|
||||
</div>
|
||||
<div class="card-number !text-[20px] !font-medium"> {{ addCommasToNumber(ele.count || 0) }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NoData v-else :no-permission-text="props.noPermissionText" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import NoData from '../../components/notData.vue';
|
||||
|
||||
import { addCommasToNumber } from '@/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
contentTabList: {
|
||||
label: string;
|
||||
value: string | number;
|
||||
count?: number;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
[key: string]: any;
|
||||
}[];
|
||||
noPermissionText?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card-list {
|
||||
gap: 16px;
|
||||
@apply flex flex-1;
|
||||
.card-list-item {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: 4px;
|
||||
@apply flex-1;
|
||||
.card-title-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
.card-number {
|
||||
margin-left: 28px;
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="my-[16px]">
|
||||
<TabCard
|
||||
<HeaderCard
|
||||
:content-tab-list="cardModuleList"
|
||||
:no-permission-text="hasPermission ? '' : 'workbench.homePage.notHasResPermission'"
|
||||
/>
|
||||
|
@ -50,9 +50,10 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
import MsChart from '@/components/pure/chart/index.vue';
|
||||
import bindDataZoomEvent from '@/components/pure/chart/utils';
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import CardSkeleton from './cardSkeleton.vue';
|
||||
import TabCard from './tabCard.vue';
|
||||
import HeaderCard from './headerCard.vue';
|
||||
|
||||
import { workMyCreatedDetail, workProOverviewDetail } from '@/api/modules/workbench';
|
||||
import { contentTabList } from '@/config/workbench';
|
||||
|
@ -191,12 +192,21 @@
|
|||
onMounted(async () => {
|
||||
await initOverViewDetail();
|
||||
|
||||
setTimeout(() => {
|
||||
nextTick(() => {
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
|
||||
if (chartDom && chartDom.chart) {
|
||||
createCustomTooltip(chartDom);
|
||||
bindDataZoomEvent(chartRef, options);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const unbindDataZoom = bindDataZoomEvent(chartRef, options);
|
||||
if (unbindDataZoom) {
|
||||
unbindDataZoom.clear();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
import MsChart from '@/components/pure/chart/index.vue';
|
||||
import bindDataZoomEvent from '@/components/pure/chart/utils';
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import CardSkeleton from './cardSkeleton.vue';
|
||||
|
||||
|
@ -149,12 +150,12 @@
|
|||
await nextTick();
|
||||
await initOverViewMemberDetail();
|
||||
|
||||
setTimeout(() => {
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
if (chartDom && chartDom.chart) {
|
||||
createCustomTooltip(chartDom);
|
||||
}
|
||||
}, 0);
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
|
||||
if (chartDom && chartDom.chart) {
|
||||
createCustomTooltip(chartDom);
|
||||
bindDataZoomEvent(chartRef, options);
|
||||
}
|
||||
}
|
||||
|
||||
async function changeProject() {
|
||||
|
@ -214,6 +215,13 @@
|
|||
onMounted(() => {
|
||||
handleProjectChange(false);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const unbindDataZoom = bindDataZoomEvent(chartRef, options);
|
||||
if (unbindDataZoom) {
|
||||
unbindDataZoom.clear();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
<template>
|
||||
<div ref="cardWrapperRef">
|
||||
<a-tabs v-if="props.contentTabList.length" default-active-key="1" class="ms-tab-card">
|
||||
<a-tab-pane v-for="item of props.contentTabList" :key="item.value" :title="`${item.label}`">
|
||||
<template #title>
|
||||
<slot name="item" :item="item">
|
||||
<div class="w-full">
|
||||
<div class="card-title flex items-center gap-[8px]">
|
||||
<div :class="`card-title-icon bg-[${item?.color}]`">
|
||||
<MsIcon :type="item.icon" class="text-white" size="12" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]"> {{ item.label }}</div>
|
||||
</div>
|
||||
<div class="card-number !text-[20px] !font-medium"> {{ addCommasToNumber(item.count || 0) }} </div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
<NoData v-else :no-permission-text="props.noPermissionText" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import NoData from '../../components/notData.vue';
|
||||
|
||||
import { addCommasToNumber } from '@/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
contentTabList: {
|
||||
label: string;
|
||||
value: string | number;
|
||||
count?: number;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
[key: string]: any;
|
||||
}[];
|
||||
minWidth?: string;
|
||||
notHasPadding?: boolean;
|
||||
hiddenBorder?: boolean;
|
||||
noPermissionText?: string;
|
||||
}>();
|
||||
|
||||
const width = ref<string | number>();
|
||||
|
||||
const cardWrapperRef = ref<HTMLElement | null>(null);
|
||||
const calculateWidth = debounce(() => {
|
||||
const wrapperContent = cardWrapperRef.value as HTMLElement;
|
||||
if (wrapperContent) {
|
||||
const wrapperTotalWidth = wrapperContent.offsetWidth;
|
||||
const gap = 16;
|
||||
const gapWidth = (props.contentTabList.length - 1) * gap; // 总间隙宽度
|
||||
const itemWidth = Math.floor((wrapperTotalWidth - gapWidth) / props.contentTabList.length);
|
||||
width.value = `${itemWidth}px`;
|
||||
}
|
||||
}, 50);
|
||||
|
||||
let resizeObserver: ResizeObserver;
|
||||
|
||||
onMounted(() => {
|
||||
const wrapperContent = cardWrapperRef.value;
|
||||
if (wrapperContent) {
|
||||
resizeObserver = new ResizeObserver(() => {
|
||||
calculateWidth();
|
||||
});
|
||||
resizeObserver.observe(wrapperContent);
|
||||
}
|
||||
});
|
||||
|
||||
const minwidth = ref();
|
||||
const padding = ref();
|
||||
const color = ref();
|
||||
watch(
|
||||
[() => props.minWidth, () => props.notHasPadding, () => props.hiddenBorder],
|
||||
(val) => {
|
||||
const [newMinWidth, noPadding, isHiddenBorder] = val;
|
||||
minwidth.value = `${newMinWidth || '136px'}`;
|
||||
padding.value = `${noPadding ? '0px' : '16px'}`;
|
||||
color.value = `${isHiddenBorder ? 'transparent' : 'var(--color-text-n8)'}`;
|
||||
calculateWidth();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.contentTabList,
|
||||
(val) => {
|
||||
if (val.length) {
|
||||
calculateWidth();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', calculateWidth);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.ms-tab-card) {
|
||||
.arco-tabs-nav-tab {
|
||||
.arco-tabs-nav-ink {
|
||||
display: none;
|
||||
}
|
||||
.arco-tabs-nav-tab-list {
|
||||
gap: 16px;
|
||||
@apply flex;
|
||||
}
|
||||
.arco-tabs-tab {
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
padding: v-bind(padding) !important;
|
||||
width: v-bind(width);
|
||||
min-width: v-bind(minwidth) !important;
|
||||
border: 1px solid v-bind(color);
|
||||
border-radius: 4px;
|
||||
&.arco-tabs-tab-active {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
&:hover {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
}
|
||||
.arco-tabs-tab-title {
|
||||
@apply w-full;
|
||||
.card-number {
|
||||
margin-left: 28px;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.card-title-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
@apply flex items-center justify-center;
|
||||
}
|
||||
}
|
||||
:deep(.arco-tabs-nav-button) {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
|
@ -191,16 +191,16 @@
|
|||
count: completeRate,
|
||||
},
|
||||
{
|
||||
name: t('common.completed'),
|
||||
count: finished,
|
||||
name: t('common.notStarted'),
|
||||
count: prepared,
|
||||
},
|
||||
{
|
||||
name: t('common.inProgress'),
|
||||
count: running,
|
||||
},
|
||||
{
|
||||
name: t('common.notStarted'),
|
||||
count: prepared,
|
||||
name: t('common.completed'),
|
||||
count: finished,
|
||||
},
|
||||
{
|
||||
name: t('common.archived'),
|
||||
|
|
|
@ -71,19 +71,7 @@
|
|||
<MsChart height="76px" width="76px" :options="execOptions" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-list">
|
||||
<div v-for="ele of cardModuleList" :key="ele.icon" class="card-list-item">
|
||||
<div class="w-full">
|
||||
<div class="card-title flex items-center gap-[8px]">
|
||||
<div :class="`card-title-icon bg-[${ele?.color}]`">
|
||||
<MsIcon :type="ele.icon" class="text-white" size="12" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]"> {{ ele.label }}</div>
|
||||
</div>
|
||||
<div class="card-number !text-[20px] !font-medium"> {{ addCommasToNumber(ele.count || 0) }} </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HeaderCard :content-tab-list="cardModuleList" />
|
||||
</div>
|
||||
<div>
|
||||
<MsChart ref="chartRef" height="280px" :options="options" />
|
||||
|
@ -100,16 +88,18 @@
|
|||
import { CascaderOption } from '@arco-design/web-vue';
|
||||
|
||||
import MsChart from '@/components/pure/chart/index.vue';
|
||||
import bindDataZoomEvent from '@/components/pure/chart/utils';
|
||||
import MsCascader from '@/components/business/ms-cascader/index.vue';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
import CardSkeleton from './cardSkeleton.vue';
|
||||
import HeaderCard from './headerCard.vue';
|
||||
import ThresholdProgress from './thresholdProgress.vue';
|
||||
|
||||
import { getWorkTestPlanListUrl, workTestPlanOverviewDetail } from '@/api/modules/workbench';
|
||||
import { commonRatePieOptions } from '@/config/workbench';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { addCommasToNumber, findNodePathByKey, mapTree } from '@/utils';
|
||||
import { findNodePathByKey, mapTree } from '@/utils';
|
||||
|
||||
import type { ModuleCardItem, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
||||
import { TestPlanStatusEnum } from '@/enums/testPlanEnum';
|
||||
|
@ -388,12 +378,11 @@
|
|||
|
||||
await initOverViewDetail();
|
||||
|
||||
setTimeout(() => {
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
if (chartDom && chartDom.chart) {
|
||||
createCustomTooltip(chartDom);
|
||||
}
|
||||
}, 0);
|
||||
const chartDom = chartRef.value?.chartRef;
|
||||
if (chartDom && chartDom.chart) {
|
||||
createCustomTooltip(chartDom);
|
||||
bindDataZoomEvent(chartRef, options);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefreshKeyChange() {
|
||||
|
@ -455,7 +444,7 @@
|
|||
.threshold-card-item {
|
||||
margin-right: 16px;
|
||||
width: 34%;
|
||||
height: 76px;
|
||||
height: 78px;
|
||||
border: 1px solid var(--color-text-n8);
|
||||
border-radius: 4px;
|
||||
gap: 12px;
|
||||
|
|
|
@ -71,7 +71,7 @@ export const colorMapConfig: Record<string, string[]> = {
|
|||
[WorkCardEnum.CASE_COUNT]: ['#ED0303', '#FFA200', '#3370FF', '#D4D4D8'],
|
||||
[WorkCardEnum.ASSOCIATE_CASE_COUNT]: ['#00C261', '#3370FF'],
|
||||
[WorkCardEnum.REVIEW_CASE_COUNT]: ['#D4D4D8', '#3370FF', '#00C261', '#ED0303', '#FFA200'],
|
||||
[WorkCardEnum.TEST_PLAN_COUNT]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
|
||||
[WorkCardEnum.TEST_PLAN_COUNT]: ['#D4D4D8', '#3370FF', '#00C261', '#FF9964'],
|
||||
[WorkCardEnum.PLAN_LEGACY_BUG]: ['#FFA200', '#3370FF', '#D4D4D8', '#00C261', ...getColorScheme(13)],
|
||||
[WorkCardEnum.BUG_COUNT]: ['#FFA200', '#3370FF', '#D4D4D8', '#00C261', ...getColorScheme(13)],
|
||||
[WorkCardEnum.HANDLE_BUG_BY_ME]: ['#FFA200', '#3370FF', '#D4D4D8', '#00C261', ...getColorScheme(13)],
|
||||
|
@ -81,7 +81,12 @@ export const colorMapConfig: Record<string, string[]> = {
|
|||
};
|
||||
|
||||
// 柱状图
|
||||
export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPlan = false): Record<string, any> {
|
||||
export function getCommonBarOptions(
|
||||
hasRoom: boolean,
|
||||
color: string[],
|
||||
isTestPlan = false,
|
||||
fullScreen = true
|
||||
): Record<string, any> {
|
||||
return {
|
||||
tooltip: [
|
||||
{
|
||||
|
@ -158,12 +163,11 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPla
|
|||
axisLabel: {
|
||||
show: true,
|
||||
color: '#646466',
|
||||
width: 120,
|
||||
width: 100,
|
||||
overflow: 'truncate',
|
||||
ellipsis: '...',
|
||||
showMinLabel: true,
|
||||
showMaxLabel: true,
|
||||
// TOTO 等待优化
|
||||
interval: 0,
|
||||
},
|
||||
axisPointer: {
|
||||
|
@ -186,7 +190,6 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPla
|
|||
{
|
||||
type: 'value',
|
||||
alignTicks: true,
|
||||
name: t('workbench.homePage.unit'), // 设置单位
|
||||
position: 'left',
|
||||
axisLine: {
|
||||
show: false,
|
||||
|
@ -272,12 +275,13 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPla
|
|||
type: 'slider',
|
||||
height: 24,
|
||||
bottom: 10,
|
||||
// TODO 待优化
|
||||
realtime: true,
|
||||
minSpan: 1,
|
||||
maxSpan: 26,
|
||||
maxValueSpan: fullScreen ? 12 : 6,
|
||||
startValue: 0,
|
||||
end: 30,
|
||||
rangeMode: ['value', 'percent'], // 起点按实际值,终点按百分比动态计算
|
||||
endValue: fullScreen ? 12 : 6,
|
||||
rangeMode: ['percent', 'percent'], // 起点按实际值,终点按百分比动态计算
|
||||
showDataShadow: 'auto',
|
||||
showDetail: false,
|
||||
filterMode: 'none',
|
||||
|
@ -538,9 +542,9 @@ export const routeNavigationMap: Record<string, any> = {
|
|||
},
|
||||
complete: {
|
||||
status: [
|
||||
WorkNavValueEnum.TEST_PLAN_COMPLETED, // 测试计划-已完成
|
||||
WorkNavValueEnum.TEST_PLAN_UNDERWAY, // 测试计划-进行中
|
||||
WorkNavValueEnum.TEST_PLAN_PREPARED, // 测试计划-未开始
|
||||
WorkNavValueEnum.TEST_PLAN_UNDERWAY, // 测试计划-进行中
|
||||
WorkNavValueEnum.TEST_PLAN_COMPLETED, // 测试计划-已完成
|
||||
WorkNavValueEnum.TEST_PLAN_ARCHIVED, // 测试计划-已归档
|
||||
],
|
||||
route: RouteEnum.TEST_PLAN_INDEX,
|
||||
|
@ -637,14 +641,16 @@ export function getSeriesData(
|
|||
contentTabList: ModuleCardItem[],
|
||||
detail: OverViewOfProject,
|
||||
colorConfig: string[],
|
||||
isTestPlan = false
|
||||
isTestPlan = false,
|
||||
isStack = false,
|
||||
fullScreen = true
|
||||
) {
|
||||
let options: Record<string, any> = {};
|
||||
|
||||
const { projectCountList, xaxis, errorCode } = detail;
|
||||
const hasPermission = errorCode !== 109001;
|
||||
|
||||
options = getCommonBarOptions(xaxis.length >= 7, colorConfig, isTestPlan);
|
||||
options = getCommonBarOptions(xaxis.length >= 7, colorConfig, isTestPlan, fullScreen);
|
||||
options.xAxis.data = xaxis;
|
||||
const { invisible, text } = handleNoDataDisplay(xaxis, hasPermission);
|
||||
options.graphic.invisible = invisible;
|
||||
|
@ -654,7 +660,7 @@ export function getSeriesData(
|
|||
const seriesData = projectCountList.map((item, sid) => {
|
||||
const countData: Record<string, any>[] = item.count.map((e) => {
|
||||
return {
|
||||
name: t(contentTabList[sid].label),
|
||||
name: t(contentTabList[sid]?.label ?? ''),
|
||||
value: e,
|
||||
originValue: e,
|
||||
tooltip: {
|
||||
|
@ -683,8 +689,8 @@ export function getSeriesData(
|
|||
|
||||
maxAxis = Math.max(itemMax, maxAxis);
|
||||
|
||||
return {
|
||||
name: t(contentTabList[sid].label),
|
||||
const itemSeries: Record<string, any> = {
|
||||
name: t(contentTabList[sid]?.label ?? ''),
|
||||
type: 'bar',
|
||||
barWidth: 12,
|
||||
legendHoverLink: true,
|
||||
|
@ -692,7 +698,6 @@ export function getSeriesData(
|
|||
itemStyle: {
|
||||
borderRadius: [2, 2, 0, 0],
|
||||
},
|
||||
barCategoryGap: 24,
|
||||
data: countData,
|
||||
barMinHeight: ((optionData: Record<string, any>[]) => {
|
||||
optionData.forEach((itemValue: any, index: number) => {
|
||||
|
@ -708,6 +713,12 @@ export function getSeriesData(
|
|||
return hasZero ? 0 : 5;
|
||||
})(countData),
|
||||
};
|
||||
|
||||
if (isStack) {
|
||||
itemSeries.stack = 'stack';
|
||||
}
|
||||
|
||||
return itemSeries;
|
||||
});
|
||||
|
||||
// 动态步长调整函数
|
||||
|
@ -752,7 +763,7 @@ export function createCustomTooltip(chartDom: InstanceType<typeof VCharts>) {
|
|||
customTooltip.textContent = `${params.value}`;
|
||||
customTooltip.style.display = 'block';
|
||||
|
||||
customTooltip.style.left = `${clientX - 20}px`;
|
||||
customTooltip.style.left = `${clientX}px`;
|
||||
customTooltip.style.top = `${clientY + 10}px`;
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue