feat(工作台): 工作台首页联调部分卡片接口覆盖率

This commit is contained in:
xinxin.wu 2024-11-15 17:48:20 +08:00 committed by Craftsman
parent e2ef7e8c93
commit cbc8cd8a7a
15 changed files with 309 additions and 62 deletions

View File

@ -8,6 +8,7 @@ import type { CaseManagementTable } from '@/models/caseManagement/featureCase';
import type { CommonList, TableQueryParams } from '@/models/common';
import type { PassRateCountDetail, TestPlanItem } from '@/models/testPlan/testPlan';
import type {
ApiCoverageData,
OverViewOfProject,
PassRateDataType,
SelectedCardItem,
@ -19,6 +20,7 @@ import {
GetDashboardLayoutUrl,
WorkApiCaseCountDetailUrl,
WorkApiChangeListUrl,
WorkApiCountCoverRateUrl,
WorkApiCountDetailUrl,
WorkAssociateCaseDetailUrl,
WorkbenchApiCaseListUrl,
@ -37,6 +39,7 @@ import {
WorkHandleUserOptionsUrl,
WorkMemberViewDetailUrl,
WorkMyCreatedDetailUrl,
WorkPlanLegacyBugUrl,
WorkProOverviewDetailUrl,
WorkReviewListUrl,
WorkScenarioCaseCountDetailUrl,
@ -172,6 +175,16 @@ export function workHandleUserOptions(projectId: string) {
return MSR.get({ url: WorkHandleUserOptionsUrl, params: projectId }, { ignoreCancelToken: true });
}
// 工作台-首页-测试计划遗留缺陷
export function workPlanLegacyBug(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkPlanLegacyBugUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-接口测试覆盖率
export function workApiCountCoverRage(projectId: string) {
return MSR.get<ApiCoverageData>({ url: WorkApiCountCoverRateUrl, params: projectId }, { ignoreCancelToken: true });
}
// 待办-用例评审列表
export function workbenchTodoReviewList(data: TableQueryParams) {
return MSR.post<CommonList<ReviewItem>>({ url: WorkTodoReviewListUrl, data });

View File

@ -26,3 +26,5 @@ export const WorkHandleUserOptionsUrl = '/dashboard/bug_handle_user/list'; //
export const WorkBugCountDetailUrl = '/dashboard/bug_count'; // 工作台-首页-缺陷数量
export const WorkBugByMeCreatedUrl = '/dashboard/create_bug_by_me'; // 工作台-首页-我创建的缺陷
export const WorkBugHandleByMeUrl = '/dashboard/handle_bug_by_me'; // 工作台-首页-待我处理的缺陷
export const WorkPlanLegacyBugUrl = '/dashboard/plan_legacy_bug'; // 工作台-首页-测试计划遗留缺陷
export const WorkApiCountCoverRateUrl = '/api/definition/rage'; // 工作台-首页-覆盖率

View File

@ -62,12 +62,12 @@ export const defaultCover = [
{
label: 'workbench.homePage.covered',
value: '-',
name: '',
name: 'workbench.homePage.covered',
},
{
label: 'workbench.homePage.notCover',
value: '-',
name: '',
name: 'workbench.homePage.notCover',
},
];
// 评审率

View File

@ -83,3 +83,18 @@ export interface PassRateDataType {
| null;
errorCode: number;
}
export interface ApiCoverageData {
allApiCount: number; // 总的 API 数量
unCoverWithApiDefinition: number; // 未覆盖 API 定义的数量
coverWithApiDefinition: number; // 覆盖了 API 定义的数量
apiCoverage: string; // API 覆盖率
unCoverWithApiCase: number; // 未覆盖 API 测试用例的数量
coverWithApiCase: number; // 覆盖了 API 测试用例的数量
apiCaseCoverage: string; // API 测试用例覆盖率(
unCoverWithApiScenario: number; // 未覆盖 API 场景的数量
coverWithApiScenario: number; // 覆盖了 API 场景的数量
scenarioCoverage: string; // API 场景覆盖率
}

View File

@ -36,7 +36,12 @@
</div>
<div class="case-ratio-wrapper mt-[16px]">
<div class="case-ratio-item">
<RatioPie :has-permission="hasPermission" :data="coverData" :rate-config="coverTitleConfig" />
<RatioPie
:has-permission="hasPermission"
:loading="loading"
:data="coverData"
:rate-config="coverTitleConfig"
/>
</div>
<div class="case-ratio-item">
<RatioPie :has-permission="hasPermission" :data="caseExecuteData" :rate-config="executeTitleConfig" />
@ -54,16 +59,17 @@
* @desc 接口用例数量/场景用例数量
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsSelect from '@/components/business/ms-select';
import RatioPie from './ratioPie.vue';
import { workApiCaseCountDetail, workScenarioCaseCountDetail } from '@/api/modules/workbench';
import { workApiCaseCountDetail, workApiCountCoverRage, workScenarioCaseCountDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { addCommasToNumber } from '@/utils';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import type { ApiCoverageData, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum';
const appStore = useAppStore();
@ -72,6 +78,8 @@
const props = defineProps<{
item: SelectedCardItem;
status?: boolean;
cover?: ApiCoverageData;
}>();
const emit = defineEmits<{
@ -86,7 +94,7 @@
const executionTimeValue = ref<{ name: string; count: number | string }[]>([
{
name: '执行次数',
name: t('workbench.homePage.executionTimes'),
count: '-',
},
]);
@ -114,8 +122,11 @@
})
);
//
const coverData = ref<{ name: string; value: number }[]>([
const initCoverRate = [
{
value: 0,
name: t('workbench.homePage.coverRate'),
},
{
value: 0,
name: t('workbench.homePage.notCover'),
@ -124,7 +135,10 @@
value: 0,
name: t('workbench.homePage.covered'),
},
]);
];
//
const coverData = ref<{ name: string; value: number }[]>(cloneDeep(initCoverRate));
const caseExecuteData = ref<{ name: string; value: number }[]>([
{
value: 0,
@ -187,8 +201,49 @@
});
const hasPermission = ref<boolean>(false);
const loading = ref<boolean>(false);
function handleCoverData(detail: ApiCoverageData) {
const { unCoverWithApiCase, coverWithApiCase, apiCaseCoverage } = detail;
const { unCoverWithApiScenario, coverWithApiScenario, scenarioCoverage } = detail;
const coverage = props.item.key === WorkCardEnum.API_CASE_COUNT ? apiCaseCoverage : scenarioCoverage;
const unCoverWithCase =
props.item.key === WorkCardEnum.API_CASE_COUNT ? unCoverWithApiCase : unCoverWithApiScenario;
const coverWithCase = WorkCardEnum.API_CASE_COUNT ? coverWithApiCase : coverWithApiScenario;
coverData.value = [
{
value: Number(coverage.split('%')[0]),
name: t('workbench.homePage.coverRate'),
},
{
value: unCoverWithCase,
name: t('workbench.homePage.notCover'),
},
{
value: coverWithCase,
name: t('workbench.homePage.covered'),
},
];
}
async function initApiCountRate() {
try {
loading.value = true;
const detail = await workApiCountCoverRage(projectId.value);
handleCoverData(detail);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
async function initApiOrScenarioCount() {
try {
coverData.value = cloneDeep(initCoverRate);
const { startTime, endTime, dayNumber } = timeForm.value;
const params = {
@ -241,6 +296,7 @@
nextTick(() => {
initApiOrScenarioCount();
emit('change');
initApiCountRate();
});
}
@ -248,6 +304,26 @@
initApiOrScenarioCount();
});
watch(
() => props.cover,
(val) => {
if (val) {
handleCoverData(val);
}
},
{
deep: true,
immediate: true,
}
);
watch(
() => props.status,
(val) => {
loading.value = val;
}
);
watch(
() => innerProjectIds.value,
(val) => {

View File

@ -25,6 +25,7 @@
:tooltip-text="tabItem.tooltip"
:options="tabItem.options"
:size="60"
:loading="tabItem.value === 'cover' ? loading : undefined"
:has-permission="hasPermission"
:value-list="tabItem.valueList"
/>
@ -49,11 +50,11 @@
import PassRatePie from './passRatePie.vue';
import TabCard from './tabCard.vue';
import { workApiCountDetail } from '@/api/modules/workbench';
import { workApiCountCoverRage, workApiCountDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import type { ApiCoverageData, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { handlePieData, handleUpdateTabPie } from '../utils';
@ -63,6 +64,8 @@
const props = defineProps<{
item: SelectedCardItem;
projectIds: string[];
status?: boolean;
cover?: ApiCoverageData;
}>();
const emit = defineEmits<{
@ -73,6 +76,8 @@
required: true,
});
const loading = ref<boolean>(false);
const projectId = ref<string>(innerProjectIds.value[0]);
const timeForm = inject<Ref<TimeFormParams>>(
@ -93,14 +98,14 @@
return [
{
label: '',
value: 'execution',
value: 'cover',
valueList: coverValueList.value,
options: { ...coverOptions.value },
tooltip: 'workbench.homePage.apiCountCoverRateTooltip',
},
{
label: '',
value: 'pass',
value: 'complete',
valueList: completeValueList.value,
options: { ...completeOptions.value },
tooltip: 'workbench.homePage.apiCountCompleteRateTooltip',
@ -109,8 +114,47 @@
});
const apiCountOptions = ref({});
const hasPermission = ref<boolean>(false);
function handleCoverData(detail: ApiCoverageData) {
const { unCoverWithApiDefinition, coverWithApiDefinition, apiCoverage } = detail;
const coverData: {
name: string;
count: number;
}[] = [
{
count: Number(apiCoverage.split('%')[0]),
name: t('workbench.homePage.coverRate'),
},
{
count: coverWithApiDefinition,
name: t('workbench.homePage.covered'),
},
{
count: unCoverWithApiDefinition,
name: t('workbench.homePage.notCover'),
},
];
const { options: covOptions, valueList: coverList } = handleUpdateTabPie(
coverData,
hasPermission.value,
`${props.item.key}-cover`
);
coverValueList.value = coverList;
coverOptions.value = covOptions;
}
async function initApiCountRate() {
try {
loading.value = true;
const detail = await workApiCountCoverRage(projectId.value);
handleCoverData(detail);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
async function initApiCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
@ -127,18 +171,8 @@
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
hasPermission.value = errorCode !== 109001;
apiCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
// TODO
// const { options: covOptions, valueList: coverList } = handleUpdateTabPie(
// statusStatisticsMap?.cover || [],
// hasPermission.value,
// `${props.item.key}-cover`
// );
// coverValueList.value = coverList;
// coverOptions.value = covOptions;
//
const { options: comOptions, valueList: completedList } = handleUpdateTabPie(
statusStatisticsMap?.completionRate || [],
@ -157,6 +191,7 @@
nextTick(() => {
initApiCount();
emit('change');
initApiCountRate();
});
}
@ -165,6 +200,26 @@
emit('change');
});
watch(
() => props.cover,
(val) => {
if (val) {
handleCoverData(val);
}
},
{
deep: true,
immediate: true,
}
);
watch(
() => props.status,
(val) => {
loading.value = val;
}
);
watch(
() => innerProjectIds.value,
(val) => {

View File

@ -21,7 +21,7 @@
</template>
<div class="setting-wrapper">
<div class="setting-wrapper-config">
<CardSettingList @add="addCard" />
<CardSettingList v-model:selectedIds="selectedIds" @add="addCard" />
</div>
<div class="setting-wrapper-content">
<!-- 布局开始 -->
@ -131,8 +131,11 @@
const defaultAllProjectType = [WorkCardEnum.PROJECT_VIEW, WorkCardEnum.CREATE_BY_ME];
const selectedIds = ref<WorkCardEnum[]>([]);
//
function addCard(item: childrenWorkConfigItem) {
selectedIds.value.push(item.value);
const newCard: SelectedCardItem = {
id: getGenerateId(),
fullScreen: !!isFullScreenType.includes(item.value),
@ -153,6 +156,7 @@
function deleteHandler(item: SelectedCardItem) {
const index = selectedCardList.value.findIndex((e) => e.id === item.id);
selectedCardList.value.splice(index, 1);
selectedIds.value.splice(index, 1);
}
const confirmLoading = ref<boolean>(false);
@ -183,6 +187,7 @@
(val) => {
if (val) {
selectedCardList.value = cloneDeep(props.list);
selectedIds.value = props.list.map((e) => e.key);
}
},
{

View File

@ -59,6 +59,10 @@
(e: 'add', item: childrenWorkConfigItem): void;
}>();
const innerSelectedIds = defineModel<WorkCardEnum[]>('selectedIds', {
required: true,
});
const configList = ref<WorkConfigCard[]>([
//
{
@ -212,17 +216,14 @@
const searchKeyword = ref(''); //
const filteredConfigList = computed(() => {
if (!searchKeyword.value) {
return configList.value;
}
return configList.value
.map((item) => ({
...item,
children: item.children.filter((child) =>
child.label.toLowerCase().includes(searchKeyword.value.toLowerCase())
),
}))
.filter((item) => item.children.length > 0);
return configList.value.map((item) => ({
...item,
children: item.children.filter(
(child) =>
child.label.toLowerCase().includes(searchKeyword.value.toLowerCase()) &&
!innerSelectedIds.value.includes(child.value)
),
}));
});
//

View File

@ -46,7 +46,12 @@
import MsSelect from '@/components/business/ms-select';
import PassRatePie from './passRatePie.vue';
import { workBugByMeCreated, workBugCountDetail, workBugHandleByMe } from '@/api/modules/workbench';
import {
workBugByMeCreated,
workBugCountDetail,
workBugHandleByMe,
workPlanLegacyBug,
} from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
@ -93,12 +98,17 @@
const countOptions = ref<Record<string, any>>({});
type SelectedBugCountKeys = WorkCardEnum.BUG_COUNT | WorkCardEnum.HANDLE_BUG_BY_ME | WorkCardEnum.CREATE_BUG_BY_ME;
type SelectedBugCountKeys =
| WorkCardEnum.BUG_COUNT
| WorkCardEnum.HANDLE_BUG_BY_ME
| WorkCardEnum.CREATE_BUG_BY_ME
| WorkCardEnum.PLAN_LEGACY_BUG;
const currentBugCount: (data: WorkHomePageDetail) => Promise<PassRateDataType> = {
[WorkCardEnum.BUG_COUNT]: workBugCountDetail,
[WorkCardEnum.HANDLE_BUG_BY_ME]: workBugHandleByMe,
[WorkCardEnum.CREATE_BUG_BY_ME]: workBugByMeCreated,
[WorkCardEnum.PLAN_LEGACY_BUG]: workPlanLegacyBug,
}[props.item.key as SelectedBugCountKeys];
const hasPermission = ref<boolean>(false);

View File

@ -27,7 +27,7 @@
</div>
<!-- 概览图 -->
<div>
<MsChart height="300px" :options="options" />
<MsChart height="260px" :options="options" />
</div>
</div>
</template>
@ -115,9 +115,11 @@
// x
options.value.xAxis.data = cardModuleList.value.map((e) => e.label);
let maxAxis = 5;
// data
options.value.series = detail.projectCountList.map((item) => {
const countData = item.count.map((e) => {
const countData: Record<string, any> = item.count.map((e) => {
return {
name: item.name,
value: e !== 0 ? e : undefined,
@ -142,6 +144,11 @@
},
};
});
const itemMax = Math.max(...item.count);
maxAxis = Math.max(itemMax, maxAxis);
return {
name: item.name,
type: 'bar',
@ -154,6 +161,10 @@
data: countData,
};
});
options.value.yAxis[0].max = maxAxis < 100 ? 50 : maxAxis + 50;
options.value.series[0].barGap = 4;
options.value.series[0].barCategoryGap = 24;
}
async function initOverViewDetail() {
@ -178,6 +189,7 @@
handleData(detail);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}

View File

@ -98,7 +98,11 @@
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, index) => {
const itemMax = Math.max(...item.count);
maxAxis = Math.max(itemMax, maxAxis);
return {
name: t(contentTabList[index].label),
type: 'bar',
@ -110,6 +114,7 @@
},
};
});
options.value.yAxis[0].max = maxAxis < 100 ? 50 : maxAxis + 50;
}
async function initOverViewMemberDetail() {

View File

@ -1,5 +1,5 @@
<template>
<div class="pass-rate-content">
<a-spin class="pass-rate-content" :loading="props.loading">
<div class="relative flex items-center justify-center">
<a-tooltip
v-if="props.tooltipText"
@ -17,7 +17,7 @@
<div class="pass-rate-count">{{ hasPermission ? addCommasToNumber(item.value as number) : '-' }}</div>
</div>
</div>
</div>
</a-spin>
</template>
<script setup lang="ts">
@ -35,6 +35,7 @@
size: number;
tooltipText?: string;
hasPermission: boolean;
loading?: boolean;
valueList: {
label: string;
value: number | string;

View File

@ -1,17 +1,19 @@
<template>
<div class="rate-content relative">
<div class="relative flex h-full w-full items-center justify-center">
<a-tooltip
v-if="props.rateConfig.tooltipText"
:mouse-enter-delay="500"
:content="t(props.rateConfig.tooltipText || '')"
position="bottom"
>
<div class="tooltip-rate-tooltip"></div>
</a-tooltip>
<MsChart :options="options" width="146px" />
<a-spin class="w-full" :loading="props.loading">
<div class="rate-content relative">
<div class="relative flex h-full w-full items-center justify-center">
<a-tooltip
v-if="props.rateConfig.tooltipText"
:mouse-enter-delay="500"
:content="t(props.rateConfig.tooltipText || '')"
position="bottom"
>
<div class="tooltip-rate-tooltip"></div>
</a-tooltip>
<MsChart :options="options" width="146px" />
</div>
</div>
</div>
</a-spin>
</template>
<script setup lang="ts">
@ -28,6 +30,7 @@
const props = defineProps<{
data: { name: string; value: number }[];
hasPermission: boolean;
loading?: boolean;
rateConfig: {
name: string;
color: string[];

View File

@ -94,6 +94,8 @@
v-model:projectIds="item.projectIds"
:type="item.key"
:item="item"
:status="projectLoadingStatus[item.projectIds[0]]"
:cover="requestResults.get(item.projectIds[0])"
@change="changeHandler"
/>
<ApiChangeList
@ -119,7 +121,9 @@
<ApiCount
v-else-if="item.key === WorkCardEnum.API_COUNT"
v-model:projectIds="item.projectIds"
:status="projectLoadingStatus[item.projectIds[0]]"
:item="item"
:cover="requestResults.get(item.projectIds[0])"
@change="changeHandler"
/>
<TestPlanCount
@ -132,8 +136,8 @@
</div>
<NoData
v-if="showNoData || !appStore.projectList.length"
:no-res-permission="!appStore.projectList.length"
:all-screen="!!appStore.projectList.length"
:no-res-permission="!defaultWorkList.length"
:all-screen="!!defaultWorkList.length"
height="h-[calc(100vh-110px)]"
@config="cardSetting"
/>
@ -162,10 +166,11 @@
import DefectMemberBar from '@/views/workbench/homePage/components/defectMemberBar.vue';
import OverviewMember from '@/views/workbench/homePage/components/overviewMember.vue';
import { editDashboardLayout, getDashboardLayout } from '@/api/modules/workbench';
import { editDashboardLayout, getDashboardLayout, workApiCountCoverRage } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import { useUserStore } from '@/store';
import useAppStore from '@/store/modules/app';
import { sleep } from '@/utils';
import { getLocalStorage, setLocalStorage } from '@/utils/local-storage';
import { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
@ -260,8 +265,43 @@
}
}
onMounted(() => {
initDefaultList();
// key ID
const requestResults = ref(new Map());
//
const requestedIds = ref(new Set());
const projectLoadingStatus = ref<Record<string, boolean>>({});
const fetchProjectDetails = async (projectId: string) => {
try {
projectLoadingStatus.value[projectId] = true;
const result = await workApiCountCoverRage(projectId);
requestResults.value.set(projectId, result);
projectLoadingStatus.value[projectId] = false;
await sleep(300);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
projectLoadingStatus.value[projectId] = false;
}
};
// id
async function requestQueue() {
const awaitType = [WorkCardEnum.API_COUNT, WorkCardEnum.API_CASE_COUNT, WorkCardEnum.SCENARIO_COUNT];
const queueList = defaultWorkList.value.filter((item) => awaitType.includes(item.key));
for (let i = 0; i < queueList.length; i++) {
const item = queueList[i];
const [projectId] = item.projectIds;
if (!requestedIds.value.has(projectId)) {
requestedIds.value.add(projectId);
fetchProjectDetails(projectId);
}
}
}
onMounted(async () => {
const defaultTime = getLocalStorage(`WORK_TIME_${userStore.id}`);
if (!defaultTime) {
setLocalStorage(`WORK_TIME_${userStore.id}`, JSON.stringify(timeForm.value));
@ -272,6 +312,8 @@
rangeTime.value = [startTime, endTime];
}
}
await initDefaultList();
requestQueue();
});
const time = ref({ ...timeForm.value });

View File

@ -48,7 +48,7 @@ export const colorMapConfig: Record<string, string[]> = {
[WorkCardEnum.REVIEW_CASE_COUNT]: ['#9441B1', '#00C261', '#D4D4D8', '#3370FF'],
[WorkCardEnum.TEST_PLAN_COUNT]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[WorkCardEnum.PLAN_LEGACY_BUG]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[WorkCardEnum.BUG_COUNT]: ['#FFA200', '#00C261', '#D4D4D8'],
[WorkCardEnum.BUG_COUNT]: ['#FFA200', '#D4D4D8', '#00C261'],
[WorkCardEnum.HANDLE_BUG_BY_ME]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[WorkCardEnum.CREATE_BY_ME]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[WorkCardEnum.API_COUNT]: ['#811FA3', '#00C261', '#3370FF', '#FFA1FF', '#EE50A3', '#FF9964', '#F9F871', '#C3DD40'],
@ -71,6 +71,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
enterable: true,
axisPointer: {
type: 'shadow',
axis: 'auto',
},
formatter(params: any) {
const html = `
@ -81,7 +82,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
<div class="flex h-[18px] items-center justify-between">
<div class="flex items-center">
<div class="mb-[2px] mr-[8px] h-[8px] w-[8px] rounded-sm" style="background:${item.color}"></div>
<div class="one-line-text max-w-[120px]" style="color:#959598">${item.seriesName}</div>
<div class="one-line-text max-w-[100px]" style="color:#959598">${item.seriesName}</div>
</div>
<div class="text-[#323233] font-medium">${addCommasToNumber(item.value)}</div>
</div>
@ -123,9 +124,14 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
},
yAxis: [
{
type: 'log',
type: 'value',
name: t('workbench.homePage.unit'), // 设置单位
position: 'left',
axisLine: {
show: false,
onZero: false, // 禁止 Y 轴强制显示在 0 位置
},
offset: 0, // 第一个 Y 轴默认位置
nameTextStyle: {
fontSize: 12,
color: '#AEAEB2', // 自定义字体大小和颜色
@ -141,6 +147,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
},
},
min: 1,
max: 0,
},
],
graphic: {