feat(工作台): 优化饼图图例自适应图例
This commit is contained in:
parent
8d399692b2
commit
fd05fdaa35
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<VCharts v-if="renderChart" :option="options" :autoresize="autoResize" :style="{ width, height }" />
|
<VCharts v-if="renderChart" ref="chartRef" :option="options" :autoresize="autoResize" :style="{ width, height }" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -55,7 +55,13 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderChart = ref(false);
|
const renderChart = ref(false);
|
||||||
|
|
||||||
|
const chartRef = ref<InstanceType<typeof VCharts>>();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
renderChart.value = true;
|
renderChart.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
chartRef,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -38,8 +38,13 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-[148px]">
|
<div class="mt-[16px] h-[148px]">
|
||||||
<MsChart :options="apiCountOptions" />
|
<LegendPieChart
|
||||||
|
v-model:currentPage="currentPage"
|
||||||
|
:has-permission="hasPermission"
|
||||||
|
:data="statusPercentValue"
|
||||||
|
:options="apiCountOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,9 +57,9 @@
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import CardSkeleton from './cardSkeleton.vue';
|
import CardSkeleton from './cardSkeleton.vue';
|
||||||
|
import LegendPieChart, { legendDataType } from './legendPieChart.vue';
|
||||||
import PassRatePie from './passRatePie.vue';
|
import PassRatePie from './passRatePie.vue';
|
||||||
|
|
||||||
import { workApiCountCoverRage, workApiCountDetail } from '@/api/modules/workbench';
|
import { workApiCountCoverRage, workApiCountDetail } from '@/api/modules/workbench';
|
||||||
|
@ -63,7 +68,7 @@
|
||||||
|
|
||||||
import type { ApiCoverageData, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
import type { ApiCoverageData, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
||||||
|
|
||||||
import { handlePieData, handleUpdateTabPie } from '../utils';
|
import { colorMapConfig, handlePieData, handleUpdateTabPie } from '../utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -87,6 +92,7 @@
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
|
|
||||||
const projectId = ref<string>(innerProjectIds.value[0]);
|
const projectId = ref<string>(innerProjectIds.value[0]);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
const timeForm = inject<Ref<TimeFormParams>>(
|
const timeForm = inject<Ref<TimeFormParams>>(
|
||||||
'timeForm',
|
'timeForm',
|
||||||
|
@ -123,6 +129,9 @@
|
||||||
|
|
||||||
const apiCountOptions = ref({});
|
const apiCountOptions = ref({});
|
||||||
const hasPermission = ref<boolean>(false);
|
const hasPermission = ref<boolean>(false);
|
||||||
|
|
||||||
|
const statusPercentValue = ref<legendDataType[]>([]);
|
||||||
|
|
||||||
async function handleCoverData(detail: ApiCoverageData) {
|
async function handleCoverData(detail: ApiCoverageData) {
|
||||||
const { unCoverWithApiDefinition, coverWithApiDefinition, apiCoverage } = detail;
|
const { unCoverWithApiDefinition, coverWithApiDefinition, apiCoverage } = detail;
|
||||||
const coverData: {
|
const coverData: {
|
||||||
|
@ -150,6 +159,7 @@
|
||||||
coverValueList.value = [...coverList];
|
coverValueList.value = [...coverList];
|
||||||
coverOptions.value = { ...covOptions };
|
coverOptions.value = { ...covOptions };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initApiCountRate() {
|
async function initApiCountRate() {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
@ -181,6 +191,13 @@
|
||||||
handleUsers: [],
|
handleUsers: [],
|
||||||
});
|
});
|
||||||
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
|
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
|
||||||
|
statusPercentValue.value = (statusPercentList || []).map((item, index) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
selected: true,
|
||||||
|
color: `${colorMapConfig[props.item.key][index]}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
hasPermission.value = errorCode !== 109001;
|
hasPermission.value = errorCode !== 109001;
|
||||||
apiCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
apiCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
||||||
|
|
|
@ -34,8 +34,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="h-[148px]">
|
<div class="mt-[16px] h-[148px]">
|
||||||
<MsChart :options="caseCountOptions" />
|
<LegendPieChart
|
||||||
|
v-model:currentPage="currentPage"
|
||||||
|
:has-permission="hasPermission"
|
||||||
|
:data="statusPercentValue"
|
||||||
|
:options="caseCountOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,9 +53,9 @@
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import CardSkeleton from './cardSkeleton.vue';
|
import CardSkeleton from './cardSkeleton.vue';
|
||||||
|
import LegendPieChart, { legendDataType } from './legendPieChart.vue';
|
||||||
import PassRatePie from './passRatePie.vue';
|
import PassRatePie from './passRatePie.vue';
|
||||||
|
|
||||||
import { workCaseCountDetail } from '@/api/modules/workbench';
|
import { workCaseCountDetail } from '@/api/modules/workbench';
|
||||||
|
@ -59,7 +64,7 @@
|
||||||
|
|
||||||
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
||||||
|
|
||||||
import { handlePieData, handleUpdateTabPie } from '../utils';
|
import { colorMapConfig, handlePieData, handleUpdateTabPie } from '../utils';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -78,6 +83,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectId = ref<string>(innerProjectIds.value[0]);
|
const projectId = ref<string>(innerProjectIds.value[0]);
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
const timeForm = inject<Ref<TimeFormParams>>(
|
const timeForm = inject<Ref<TimeFormParams>>(
|
||||||
'timeForm',
|
'timeForm',
|
||||||
|
@ -117,6 +123,7 @@
|
||||||
|
|
||||||
const caseCountOptions = ref<Record<string, any>>({});
|
const caseCountOptions = ref<Record<string, any>>({});
|
||||||
const showSkeleton = ref(false);
|
const showSkeleton = ref(false);
|
||||||
|
const statusPercentValue = ref<legendDataType[]>([]);
|
||||||
|
|
||||||
async function initCaseCount() {
|
async function initCaseCount() {
|
||||||
try {
|
try {
|
||||||
|
@ -134,6 +141,14 @@
|
||||||
};
|
};
|
||||||
const detail = await workCaseCountDetail(params);
|
const detail = await workCaseCountDetail(params);
|
||||||
const { statusStatisticsMap, statusPercentList } = detail;
|
const { statusStatisticsMap, statusPercentList } = detail;
|
||||||
|
|
||||||
|
statusPercentValue.value = (statusPercentList || []).map((item, index) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
selected: true,
|
||||||
|
color: `${colorMapConfig[props.item.key][index]}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
hasPermission.value = detail.errorCode !== 109001;
|
hasPermission.value = detail.errorCode !== 109001;
|
||||||
caseCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
caseCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-[148px]">
|
<div class="h-[148px]">
|
||||||
<MsChart :options="caseReviewCountOptions" />
|
<LegendPieChart
|
||||||
|
v-model:currentPage="currentPage"
|
||||||
|
:has-permission="hasPermission"
|
||||||
|
:data="statusPercentValue"
|
||||||
|
:options="caseReviewCountOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,9 +52,9 @@
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import CardSkeleton from './cardSkeleton.vue';
|
import CardSkeleton from './cardSkeleton.vue';
|
||||||
|
import LegendPieChart, { legendDataType } from './legendPieChart.vue';
|
||||||
import PassRatePie from './passRatePie.vue';
|
import PassRatePie from './passRatePie.vue';
|
||||||
|
|
||||||
import { workCaseReviewDetail } from '@/api/modules/workbench';
|
import { workCaseReviewDetail } from '@/api/modules/workbench';
|
||||||
|
@ -58,7 +63,7 @@
|
||||||
|
|
||||||
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
|
||||||
|
|
||||||
import { handlePieData, handleUpdateTabPie } from '../utils';
|
import { colorMapConfig, handlePieData, handleUpdateTabPie } from '../utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -75,6 +80,7 @@
|
||||||
const innerProjectIds = defineModel<string[]>('projectIds', {
|
const innerProjectIds = defineModel<string[]>('projectIds', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
const projectId = ref<string>(innerProjectIds.value[0]);
|
const projectId = ref<string>(innerProjectIds.value[0]);
|
||||||
|
|
||||||
|
@ -106,6 +112,7 @@
|
||||||
|
|
||||||
const hasPermission = ref<boolean>(false);
|
const hasPermission = ref<boolean>(false);
|
||||||
const showSkeleton = ref(false);
|
const showSkeleton = ref(false);
|
||||||
|
const statusPercentValue = ref<legendDataType[]>([]);
|
||||||
|
|
||||||
async function initReviewCount() {
|
async function initReviewCount() {
|
||||||
try {
|
try {
|
||||||
|
@ -126,6 +133,14 @@
|
||||||
hasPermission.value = detail.errorCode !== 109001;
|
hasPermission.value = detail.errorCode !== 109001;
|
||||||
|
|
||||||
const { statusStatisticsMap, statusPercentList } = detail;
|
const { statusStatisticsMap, statusPercentList } = detail;
|
||||||
|
statusPercentValue.value = (statusPercentList || []).map((item, index) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
selected: true,
|
||||||
|
color: `${colorMapConfig[props.item.key][index]}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
caseReviewCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
caseReviewCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
||||||
const { options: coverOptions, valueList } = handleUpdateTabPie(
|
const { options: coverOptions, valueList } = handleUpdateTabPie(
|
||||||
statusStatisticsMap?.cover || [],
|
statusStatisticsMap?.cover || [],
|
||||||
|
|
|
@ -33,8 +33,13 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-[148px]">
|
<div class="flex h-[148px]">
|
||||||
<MsChart :options="countOptions" />
|
<LegendPieChart
|
||||||
|
v-model:currentPage="currentPage"
|
||||||
|
:has-permission="hasPermission"
|
||||||
|
:data="statusPercentValue"
|
||||||
|
:options="countOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -47,9 +52,9 @@
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import CardSkeleton from './cardSkeleton.vue';
|
import CardSkeleton from './cardSkeleton.vue';
|
||||||
|
import LegendPieChart, { legendDataType } from './legendPieChart.vue';
|
||||||
import PassRatePie from './passRatePie.vue';
|
import PassRatePie from './passRatePie.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -69,7 +74,7 @@
|
||||||
} from '@/models/workbench/homePage';
|
} from '@/models/workbench/homePage';
|
||||||
import { WorkCardEnum } from '@/enums/workbenchEnum';
|
import { WorkCardEnum } from '@/enums/workbenchEnum';
|
||||||
|
|
||||||
import { handlePieData, handleUpdateTabPie } from '../utils';
|
import { colorMapConfig, handlePieData, handleUpdateTabPie } from '../utils';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
@ -120,6 +125,7 @@
|
||||||
|
|
||||||
const hasPermission = ref<boolean>(false);
|
const hasPermission = ref<boolean>(false);
|
||||||
const showSkeleton = ref(false);
|
const showSkeleton = ref(false);
|
||||||
|
const statusPercentValue = ref<legendDataType[]>([]);
|
||||||
|
|
||||||
async function initCount() {
|
async function initCount() {
|
||||||
try {
|
try {
|
||||||
|
@ -140,6 +146,13 @@
|
||||||
|
|
||||||
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
|
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
|
||||||
hasPermission.value = errorCode !== 109001;
|
hasPermission.value = errorCode !== 109001;
|
||||||
|
statusPercentValue.value = (statusPercentList || []).map((item, index) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
selected: true,
|
||||||
|
color: `${colorMapConfig[props.item.key][index]}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
countOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
countOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
|
||||||
if (props.item.key === WorkCardEnum.PLAN_LEGACY_BUG) {
|
if (props.item.key === WorkCardEnum.PLAN_LEGACY_BUG) {
|
||||||
|
@ -183,6 +196,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initCount();
|
initCount();
|
||||||
});
|
});
|
||||||
|
|
|
@ -140,7 +140,7 @@
|
||||||
})(countData),
|
})(countData),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
options.value.yAxis[0].max = maxAxis < 100 ? 50 : maxAxis + 50;
|
options.value.yAxis[0].max = maxAxis <= 100 ? 100 : maxAxis + 50;
|
||||||
}
|
}
|
||||||
const showSkeleton = ref(false);
|
const showSkeleton = ref(false);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,172 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex h-full w-full">
|
||||||
|
<div class="w-[180px]">
|
||||||
|
<MsChart ref="chartRef" :options="props.options" />
|
||||||
|
</div>
|
||||||
|
<div v-if="props.hasPermission" class="relative mt-[8px] h-full flex-1">
|
||||||
|
<!-- 图例部分 -->
|
||||||
|
<div class="flex w-full flex-col gap-4">
|
||||||
|
<div
|
||||||
|
v-for="(ele, i) of currentData"
|
||||||
|
:key="`ele.status-${i}`"
|
||||||
|
class="grid flex-1 grid-cols-3 gap-4"
|
||||||
|
@mouseover="handleMouseOver(ele.status)"
|
||||||
|
@mouseout="handleMouseOut(ele.status)"
|
||||||
|
>
|
||||||
|
<div class="flex items-center text-left text-[var(--color-text-3)]">
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
background: ele.selected ? `${ele.color}` : '#D4D4D8',
|
||||||
|
}"
|
||||||
|
class="mr-[8px] h-[8px] w-[8px] cursor-pointer rounded-lg"
|
||||||
|
@click="toggleLegend(ele.status, i)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{{ ele.status }}
|
||||||
|
</div>
|
||||||
|
<div class="text-center">{{ ele.count }}</div>
|
||||||
|
<div class="text-right">{{ ele.percentValue }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="totalPages > 1" class="legend-pagination">
|
||||||
|
<span :class="`toggle-button ${currentPage === 1 ? 'disabled' : ''}`" @click="prevPage">
|
||||||
|
<icon-caret-up
|
||||||
|
:class="`text-[14px] ${
|
||||||
|
currentPage === 1 ? 'text-[var(--color-text-brand)]' : 'text-[var(--color-text-4)]'
|
||||||
|
}`"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span class="mx-[4px] text-[var(--color-text-4)]">{{ currentPage }} / {{ totalPages }}</span>
|
||||||
|
<span :class="`toggle-button ${currentPage === totalPages ? 'disabled' : ''}`" @click="nextPage">
|
||||||
|
<icon-caret-down
|
||||||
|
:class="`text-[14px] ${
|
||||||
|
currentPage === totalPages ? 'text-[var(--color-text-brand)]' : 'text-[var(--color-text-4)]'
|
||||||
|
}`"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex h-full flex-1 items-center justify-center">
|
||||||
|
<div class="rounded bg-[var(--color-text-n9)] px-[16px] py-[4px] text-[var(--color-text-4)]"
|
||||||
|
>{{ t('workbench.homePage.notHasResPermission') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import MsChart from '@/components/pure/chart/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
export interface legendDataType {
|
||||||
|
status: string;
|
||||||
|
count: number;
|
||||||
|
percentValue: string;
|
||||||
|
selected: boolean;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
data: legendDataType[] | null;
|
||||||
|
itemsPerPage?: number; // 每页数量
|
||||||
|
options: Record<string, any>;
|
||||||
|
hasPermission?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
itemsPerPage: 4,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPage = defineModel<number>('currentPage', { required: true });
|
||||||
|
|
||||||
|
const list = ref<legendDataType[]>([...(props.data || [])]);
|
||||||
|
|
||||||
|
// 总页数
|
||||||
|
const totalPages = computed(() => Math.ceil(list.value.length / props.itemsPerPage));
|
||||||
|
|
||||||
|
// 当前页显示的数据
|
||||||
|
const currentData = computed(() => {
|
||||||
|
const startIndex = (currentPage.value - 1) * props.itemsPerPage;
|
||||||
|
const endIndex = startIndex + props.itemsPerPage;
|
||||||
|
return list.value.slice(startIndex, endIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 分页方法
|
||||||
|
const prevPage = () => {
|
||||||
|
if (currentPage.value > 1) currentPage.value -= 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextPage = () => {
|
||||||
|
if (currentPage.value < totalPages.value) currentPage.value += 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换图例
|
||||||
|
const chartRef = ref<InstanceType<typeof MsChart>>();
|
||||||
|
function toggleLegend(name: string, index: number) {
|
||||||
|
const chart = chartRef.value?.chartRef;
|
||||||
|
if (chart) {
|
||||||
|
chart.dispatchAction({
|
||||||
|
type: 'legendToggleSelect',
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (list.value) {
|
||||||
|
list.value[index].selected = !list.value[index].selected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 悬浮放大交互效果
|
||||||
|
function handleMouseOver(name: string) {
|
||||||
|
const chart = chartRef.value?.chartRef;
|
||||||
|
if (chart) {
|
||||||
|
chart.dispatchAction({
|
||||||
|
type: 'highlight',
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 移除放大交互效果
|
||||||
|
function handleMouseOut(name: string) {
|
||||||
|
const chart = chartRef.value?.chartRef;
|
||||||
|
if (chart) {
|
||||||
|
chart.dispatchAction({
|
||||||
|
type: 'downplay',
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(val) => {
|
||||||
|
list.value = [...(val || [])];
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.legend-pagination {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: -10px;
|
||||||
|
left: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
.toggle-button {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -54,7 +54,6 @@
|
||||||
import { contentTabList } from '@/config/workbench';
|
import { contentTabList } from '@/config/workbench';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { characterLimit } from '@/utils';
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ModuleCardItem,
|
ModuleCardItem,
|
||||||
|
@ -128,7 +127,7 @@
|
||||||
options.value.graphic.invisible = invisible;
|
options.value.graphic.invisible = invisible;
|
||||||
options.value.graphic.style.text = text;
|
options.value.graphic.style.text = text;
|
||||||
// x轴
|
// x轴
|
||||||
options.value.xAxis.data = detail.xaxis.map((e) => characterLimit(e, 10));
|
options.value.xAxis.data = detail.xaxis;
|
||||||
|
|
||||||
const { maxAxis, data } = getSeriesData(detail.projectCountList);
|
const { maxAxis, data } = getSeriesData(detail.projectCountList);
|
||||||
options.value.series = data;
|
options.value.series = data;
|
||||||
|
|
|
@ -101,7 +101,7 @@
|
||||||
const { invisible, text } = handleNoDataDisplay(detail.xaxis, hasPermission.value);
|
const { invisible, text } = handleNoDataDisplay(detail.xaxis, hasPermission.value);
|
||||||
options.value.graphic.invisible = invisible;
|
options.value.graphic.invisible = invisible;
|
||||||
options.value.graphic.style.text = text;
|
options.value.graphic.style.text = text;
|
||||||
options.value.xAxis.data = detail.xaxis.map((e) => characterLimit(e, 10));
|
options.value.xAxis.data = detail.xaxis;
|
||||||
|
|
||||||
const { maxAxis, data } = getSeriesData(detail.projectCountList);
|
const { maxAxis, data } = getSeriesData(detail.projectCountList);
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,13 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-[148px]">
|
<div class="mt-[16px] h-[148px]">
|
||||||
<MsChart :options="testPlanCountOptions" />
|
<LegendPieChart
|
||||||
|
v-model:currentPage="currentPage"
|
||||||
|
:has-permission="hasPermission"
|
||||||
|
:data="statusPercentValue"
|
||||||
|
:options="testPlanCountOptions"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,11 +51,10 @@
|
||||||
*/
|
*/
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import MsChart from '@/components/pure/chart/index.vue';
|
|
||||||
import MsSelect from '@/components/business/ms-select';
|
import MsSelect from '@/components/business/ms-select';
|
||||||
import CardSkeleton from './cardSkeleton.vue';
|
import CardSkeleton from './cardSkeleton.vue';
|
||||||
|
import LegendPieChart, { legendDataType } from './legendPieChart.vue';
|
||||||
import PassRatePie from './passRatePie.vue';
|
import PassRatePie from './passRatePie.vue';
|
||||||
import TabCard from './tabCard.vue';
|
|
||||||
|
|
||||||
import { workTestPlanRage } from '@/api/modules/workbench';
|
import { workTestPlanRage } from '@/api/modules/workbench';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
@ -63,7 +67,7 @@
|
||||||
WorkTestPlanRageDetail,
|
WorkTestPlanRageDetail,
|
||||||
} from '@/models/workbench/homePage';
|
} from '@/models/workbench/homePage';
|
||||||
|
|
||||||
import { handlePieData, handleUpdateTabPie } from '../utils';
|
import { colorMapConfig, handlePieData, handleUpdateTabPie } from '../utils';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -80,6 +84,7 @@
|
||||||
const innerProjectIds = defineModel<string[]>('projectIds', {
|
const innerProjectIds = defineModel<string[]>('projectIds', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
const currentPage = ref(1);
|
||||||
|
|
||||||
const projectId = ref<string>(innerProjectIds.value[0]);
|
const projectId = ref<string>(innerProjectIds.value[0]);
|
||||||
|
|
||||||
|
@ -122,6 +127,7 @@
|
||||||
// 测试计划权限
|
// 测试计划权限
|
||||||
const hasPermission = ref<boolean>(false);
|
const hasPermission = ref<boolean>(false);
|
||||||
const showSkeleton = ref(false);
|
const showSkeleton = ref(false);
|
||||||
|
const statusPercentValue = ref<legendDataType[]>([]);
|
||||||
|
|
||||||
async function initTestPlanCount() {
|
async function initTestPlanCount() {
|
||||||
try {
|
try {
|
||||||
|
@ -162,6 +168,14 @@
|
||||||
{ status: t('common.archived'), count: archived, percentValue: '0%' },
|
{ status: t('common.archived'), count: archived, percentValue: '0%' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
statusPercentValue.value = (statusPercentList || []).map((item, index) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
selected: true,
|
||||||
|
color: `${colorMapConfig[props.item.key][index]}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const total = statusPercentList.reduce((sum, item) => sum + item.count, 0);
|
const total = statusPercentList.reduce((sum, item) => sum + item.count, 0);
|
||||||
|
|
||||||
const listStatusPercentList = statusPercentList.map((item) => ({
|
const listStatusPercentList = statusPercentList.map((item) => ({
|
||||||
|
|
|
@ -99,7 +99,8 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
|
||||||
},
|
},
|
||||||
formatter(params: any) {
|
formatter(params: any) {
|
||||||
const html = `
|
const html = `
|
||||||
<div class="w-[186px] ms-scroll-bar max-h-[206px] overflow-y-auto p-[16px] gap-[8px] flex flex-col">
|
<div class="w-[186px] ms-scroll-bar max-h-[236px] overflow-y-auto p-[16px] gap-[8px] flex flex-col">
|
||||||
|
<div class="font-medium max-w-[150px] one-line-text" style="color:#323233">${params[0].axisValueLabel}</div>
|
||||||
${params
|
${params
|
||||||
.map(
|
.map(
|
||||||
(item: any) => `
|
(item: any) => `
|
||||||
|
@ -136,6 +137,9 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
show: true,
|
show: true,
|
||||||
color: '#646466',
|
color: '#646466',
|
||||||
|
width: 120,
|
||||||
|
overflow: 'truncate',
|
||||||
|
ellipsis: '...',
|
||||||
},
|
},
|
||||||
axisTick: {
|
axisTick: {
|
||||||
show: false, // 隐藏刻度线
|
show: false, // 隐藏刻度线
|
||||||
|
@ -274,7 +278,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下方饼图配置
|
// 下方饼图配置
|
||||||
export function getPieCharOptions(key: WorkCardEnum, hasPermission: boolean) {
|
export function getPieCharOptions(key: WorkCardEnum) {
|
||||||
return {
|
return {
|
||||||
title: {
|
title: {
|
||||||
show: true,
|
show: true,
|
||||||
|
@ -298,99 +302,8 @@ export function getPieCharOptions(key: WorkCardEnum, hasPermission: boolean) {
|
||||||
color: colorMapConfig[key],
|
color: colorMapConfig[key],
|
||||||
tooltip: { show: true },
|
tooltip: { show: true },
|
||||||
legend: {
|
legend: {
|
||||||
width: '100%',
|
show: false,
|
||||||
height: 128,
|
|
||||||
type: 'scroll',
|
|
||||||
orient: 'vertical',
|
|
||||||
pageButtonItemGap: 5,
|
|
||||||
pageButtonGap: 5,
|
|
||||||
pageIconColor: '#00000099',
|
|
||||||
pageIconInactiveColor: '#00000042',
|
|
||||||
pageIconSize: [7, 5],
|
|
||||||
pageTextStyle: {
|
|
||||||
color: '#00000099',
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
},
|
||||||
pageButtonPosition: 'end',
|
|
||||||
itemGap: 16,
|
|
||||||
itemWidth: 8,
|
|
||||||
itemHeight: 8,
|
|
||||||
icon: 'circle',
|
|
||||||
bottom: 'center',
|
|
||||||
left: 180,
|
|
||||||
tooltip: {
|
|
||||||
show: false, // 禁用图例的 tooltip
|
|
||||||
},
|
|
||||||
textStyle: {
|
|
||||||
color: '#333',
|
|
||||||
fontSize: 14, // 字体大小
|
|
||||||
textBorderType: 'solid',
|
|
||||||
rich: {
|
|
||||||
a: {
|
|
||||||
width: 50,
|
|
||||||
color: '#959598',
|
|
||||||
fontSize: 12,
|
|
||||||
align: 'left',
|
|
||||||
},
|
|
||||||
b: {
|
|
||||||
width: 50,
|
|
||||||
color: '#323233',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
align: 'right',
|
|
||||||
},
|
|
||||||
c: {
|
|
||||||
width: 50,
|
|
||||||
color: '#323233',
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: 'bold',
|
|
||||||
align: 'right',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
media: [
|
|
||||||
{
|
|
||||||
query: { maxWidth: 600 },
|
|
||||||
option: {
|
|
||||||
legend: {
|
|
||||||
textStyle: {
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { minWidth: 601, maxWidth: 800 },
|
|
||||||
option: {
|
|
||||||
legend: {
|
|
||||||
textStyle: {
|
|
||||||
width: 450,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { minWidth: 801, maxWidth: 1200 },
|
|
||||||
option: {
|
|
||||||
legend: {
|
|
||||||
textStyle: {
|
|
||||||
width: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
query: { minWidth: 1201 },
|
|
||||||
option: {
|
|
||||||
legend: {
|
|
||||||
textStyle: {
|
|
||||||
width: 1000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
series: {
|
series: {
|
||||||
name: '',
|
name: '',
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
|
@ -413,20 +326,6 @@ export function getPieCharOptions(key: WorkCardEnum, hasPermission: boolean) {
|
||||||
},
|
},
|
||||||
data: [],
|
data: [],
|
||||||
},
|
},
|
||||||
graphic: {
|
|
||||||
type: 'text',
|
|
||||||
left: 'center',
|
|
||||||
top: 'middle',
|
|
||||||
style: {
|
|
||||||
text: t('workbench.homePage.notHasResPermission'),
|
|
||||||
fontSize: 14,
|
|
||||||
fill: '#959598',
|
|
||||||
backgroundColor: '#F9F9FE',
|
|
||||||
padding: [6, 16, 6, 16],
|
|
||||||
borderRadius: 4,
|
|
||||||
},
|
|
||||||
invisible: !!hasPermission,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -463,7 +362,7 @@ export function handlePieData(
|
||||||
}[]
|
}[]
|
||||||
| null = []
|
| null = []
|
||||||
) {
|
) {
|
||||||
const options: Record<string, any> = getPieCharOptions(key, hasPermission);
|
const options: Record<string, any> = getPieCharOptions(key);
|
||||||
const lastStatusPercentList = statusPercentList ?? [];
|
const lastStatusPercentList = statusPercentList ?? [];
|
||||||
options.series.data = lastStatusPercentList.map((item) => ({
|
options.series.data = lastStatusPercentList.map((item) => ({
|
||||||
name: item.status,
|
name: item.status,
|
||||||
|
@ -490,13 +389,6 @@ export function handlePieData(
|
||||||
options.series.data = [];
|
options.series.data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置图例的格式化函数,显示百分比
|
|
||||||
options.legend.formatter = (name: string) => {
|
|
||||||
return `{a|${tempObject[name].status}} {b|${addCommasToNumber(tempObject[name].count)}} {c|${
|
|
||||||
tempObject[name].percentValue
|
|
||||||
}}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue