feat(工作台): 工作台首页联调部分卡片

This commit is contained in:
xinxin.wu 2024-11-13 19:17:15 +08:00 committed by Craftsman
parent 61c05433fa
commit fced7b284f
23 changed files with 881 additions and 682 deletions

View File

@ -1,6 +1,6 @@
import MSR from '@/api/http/index';
import type { ApiCaseDetail } from '@/models/apiTest/management';
import type { ApiCaseDetail, ApiDefinitionDetail } from '@/models/apiTest/management';
import type { ApiScenarioTableItem } from '@/models/apiTest/scenario';
import type { BugListItem } from '@/models/bug-management';
import type { ReviewItem } from '@/models/caseManagement/caseReview';
@ -17,6 +17,7 @@ import type {
import {
EditDashboardLayoutUrl,
GetDashboardLayoutUrl,
WorkApiChangeListUrl,
WorkAssociateCaseDetailUrl,
WorkbenchApiCaseListUrl,
WorkbenchBugListUrl,
@ -25,10 +26,13 @@ import {
WorkbenchScenarioListUrl,
WorkbenchTestPlanListUrl,
WorkbenchTestPlanStatisticUrl,
WorkBugHandlerDetailUrl,
WorkCaseCountDetailUrl,
WorkCaseReviewDetailUrl,
WorkMemberViewDetailUrl,
WorkMyCreatedDetailUrl,
WorkProOverviewDetailUrl,
WorkReviewListUrl,
WorkTodoBugListUrl,
WorkTodoPlanListUrl,
WorkTodoReviewListUrl,
@ -100,6 +104,32 @@ export function workAssociateCaseDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkAssociateCaseDetailUrl, data });
}
// 工作台-首页-用例评审数
export function workCaseReviewDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkCaseReviewDetailUrl, data });
}
// 工作台-首页-缺陷处理人
export function workBugHandlerDetail(data: WorkHomePageDetail) {
return MSR.post<OverViewOfProject>({ url: WorkBugHandlerDetailUrl, data });
}
// 工作台-首页-接口变更
export function workApiChangeList(data: WorkHomePageDetail) {
return MSR.post<CommonList<ApiDefinitionDetail>>(
{ url: WorkApiChangeListUrl, data },
{ ignoreCancelToken: true, errorMessageMode: 'none' }
);
}
// 工作台-首页-接口变更
export function workReviewList(data: WorkHomePageDetail) {
return MSR.post<CommonList<ReviewItem>>(
{ url: WorkReviewListUrl, data },
{ ignoreCancelToken: true, errorMessageMode: 'none' }
);
}
// 待办-用例评审列表
export function workbenchTodoReviewList(data: TableQueryParams) {
return MSR.post<CommonList<ReviewItem>>({ url: WorkTodoReviewListUrl, data });

View File

@ -15,3 +15,7 @@ export const WorkTodoBugListUrl = '/dashboard/todo/bug/page'; // 工作台-待
export const WorkMemberViewDetailUrl = '/dashboard/project_member_view'; // 工作台-首页-人员概览
export const WorkCaseCountDetailUrl = '/dashboard/case_count'; // 工作台-首页-用例数量
export const WorkAssociateCaseDetailUrl = '/dashboard/associate_case_count'; // 工作台-首页-关联用例数量
export const WorkBugHandlerDetailUrl = '/dashboard/bug_handle_user'; // 工作台-首页-缺陷处理人
export const WorkApiChangeListUrl = '/dashboard/api_change'; // 工作台-首页-接口变更
export const WorkCaseReviewDetailUrl = '/dashboard/review_case_count'; // 工作台-首页-用例评审数
export const WorkReviewListUrl = '/dashboard/reviewing_by_me'; // 工作台-首页-待我评审

View File

@ -259,11 +259,12 @@ export default function useTableProps<T>(
return data;
}
} catch (err) {
// TODO 在这里处理拦截设置表格无资源权限
setTableErrorStatus('error');
propsRes.value.data = [];
// eslint-disable-next-line no-console
console.log(err);
throw err; // 将错误抛出
} finally {
setLoading(false);
// debug 模式下打印属性

View File

@ -0,0 +1,301 @@
import { cloneDeep } from 'lodash-es';
import { commonConfig, toolTipConfig } from '@/config/testPlan';
import type { ModuleCardItem } from '@/models/workbench/homePage';
import { WorkCardEnum, WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum';
export const contentTabList: ModuleCardItem[] = [
{
label: 'workbench.homePage.functionalUseCase',
value: WorkOverviewEnum.FUNCTIONAL,
icon: WorkOverviewIconEnum.FUNCTIONAL,
color: 'rgb(var(--primary-5))',
count: 0,
},
{
label: 'workbench.homePage.useCaseReview',
value: WorkOverviewEnum.CASE_REVIEW,
icon: WorkOverviewIconEnum.CASE_REVIEW,
color: 'rgb(var(--success-6))',
count: 0,
},
{
label: 'workbench.homePage.interfaceAPI',
value: WorkOverviewEnum.API,
icon: WorkOverviewIconEnum.API,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: 'workbench.homePage.interfaceCASE',
value: WorkOverviewEnum.API_CASE,
icon: WorkOverviewIconEnum.API_CASE,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: 'workbench.homePage.interfaceScenario',
value: WorkOverviewEnum.API_SCENARIO,
icon: WorkOverviewIconEnum.API_SCENARIO,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: 'workbench.homePage.apiPlan',
value: WorkOverviewEnum.TEST_PLAN,
icon: WorkOverviewIconEnum.TEST_PLAN,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: 'workbench.homePage.bugCount',
value: WorkOverviewEnum.BUG_COUNT,
icon: WorkOverviewIconEnum.BUG_COUNT,
color: 'rgb(var(--danger-6))',
count: 0,
},
];
// 覆盖率
export const defaultCover = [
{
label: 'workbench.homePage.covered',
value: '-',
name: '',
},
{
label: 'workbench.homePage.notCover',
value: '-',
name: '',
},
];
// 评审率
export const defaultReview = [
{
label: 'workbench.homePage.reviewed',
value: '-',
name: '',
},
{
label: 'workbench.homePage.unReviewed',
value: '-',
name: '',
},
];
// 通过率
export const defaultPass = [
{
label: 'workbench.homePage.havePassed',
value: '-',
name: '',
},
{
label: 'workbench.homePage.notPass',
value: '-',
name: '',
},
];
// 完成率
export const defaultComplete = [
{
label: 'common.completed',
value: 10000,
name: '',
},
{
label: 'common.inProgress',
value: 2000,
name: '',
},
{
label: 'workbench.homePage.unFinish',
value: 2000,
name: '',
},
];
// 执行率
export const defaultExecution = [
{
label: 'common.unExecute',
value: 10000,
name: '',
},
{
label: 'common.executed',
value: 2000,
name: '',
},
];
// 遗留率
export const defaultLegacy = [
{
label: 'workbench.homePage.defectTotal',
value: 10000,
name: '',
},
{
label: 'workbench.homePage.legacyDefectsNumber',
value: 2000,
name: '',
},
];
export const defaultValueMap: Record<string, any> = {
// 用例数量
[WorkCardEnum.CASE_COUNT]: {
review: {
defaultList: cloneDeep(defaultCover),
color: ['#00C261', '#D4D4D8'],
defaultName: 'workbench.homePage.reviewRate',
},
pass: {
defaultList: cloneDeep(defaultPass),
color: ['#00C261', '#ED0303'],
defaultName: 'workbench.homePage.passRate',
},
},
// 关联用例数量
[WorkCardEnum.ASSOCIATE_CASE_COUNT]: {
cover: {
defaultList: cloneDeep(defaultCover),
color: ['#00C261', '#D4D4D8'],
defaultName: 'workbench.homePage.coverRate',
},
},
// 用例评审数
[WorkCardEnum.REVIEW_CASE_COUNT]: {
cover: {
defaultList: cloneDeep(defaultCover),
color: ['#00C261', '#D4D4D8'],
defaultName: 'workbench.homePage.coverRate',
},
},
// 测试计划数
[WorkCardEnum.TEST_PLAN_COUNT]: {
execute: {
defaultList: cloneDeep(defaultExecution),
color: ['#D4D4D8', '#00C261'],
defaultName: 'workbench.homePage.executeRate',
},
pass: {
defaultList: cloneDeep(defaultPass),
color: ['#D4D4D8', '#00C261'],
defaultName: 'workbench.homePage.passRate',
},
complete: {
defaultList: cloneDeep(defaultComplete),
color: ['#00C261', '#3370FF', '#D4D4D8'],
defaultName: 'workbench.homePage.completeRate',
},
},
// 测试计划遗留缺陷
[WorkCardEnum.PLAN_LEGACY_BUG]: {
legacy: {
defaultList: cloneDeep(defaultLegacy),
color: ['#D4D4D8', '#00C261'],
defaultName: 'workbench.homePage.legacyRate',
},
},
// 缺陷数
[WorkCardEnum.BUG_COUNT]: {
legacy: {
defaultList: cloneDeep(defaultLegacy),
color: ['#D4D4D8', '#00C261'],
defaultName: 'workbench.homePage.legacyRate',
},
},
// 待我处理的缺陷
[WorkCardEnum.HANDLE_BUG_BY_ME]: {
legacy: {
defaultList: cloneDeep(defaultLegacy),
color: ['#D4D4D8', '#00C261'],
defaultName: 'workbench.homePage.legacyRate',
},
},
// 接口数量
[WorkCardEnum.API_COUNT]: {
cover: {
defaultList: cloneDeep(defaultCover),
color: ['#00C261', '#D4D4D8'],
defaultName: 'workbench.homePage.coverRate',
},
complete: {
defaultList: cloneDeep(defaultComplete),
color: ['#00C261', '#3370FF', '#D4D4D8'],
defaultName: 'workbench.homePage.completeRate',
},
},
// 我创建的缺陷
[WorkCardEnum.CREATE_BUG_BY_ME]: {
legacy: {
defaultList: cloneDeep(defaultLegacy),
color: ['#D4D4D8', '#00C261'],
defaultName: 'workbench.homePage.legacyRate',
},
},
};
// XX率饼图配置
export const commonRatePieOptions = {
...commonConfig,
title: {
show: true,
text: '',
left: 26,
top: '20%',
textStyle: {
fontSize: 12,
fontWeight: 'normal',
color: '#959598',
},
triggerEvent: true, // 开启鼠标事件
subtext: '0',
subtextStyle: {
fontSize: 12,
color: '#323233',
fontWeight: 'bold',
align: 'center',
lineHeight: 3,
},
textAlign: 'center',
tooltip: {
...toolTipConfig,
position: 'right',
},
},
tooltip: {
...toolTipConfig,
position: 'right',
},
legend: {
show: false,
},
series: {
name: '',
type: 'pie',
color: [],
padAngle: 2,
radius: ['85%', '100%'],
center: [30, '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
scale: false, // 禁用放大效果
label: {
show: false,
fontSize: 40,
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [],
},
};
export default {};

View File

@ -56,7 +56,7 @@ export interface OverViewOfProject {
}
export interface ModuleCardItem {
label: string | number;
label: string;
value: string | number;
count?: number;
icon?: string;
@ -73,10 +73,13 @@ export type StatusStatisticsMapType = Record<
>;
export interface PassRateDataType {
statusStatisticsMap: StatusStatisticsMapType;
statusPercentList: {
status: string; // 状态
count: number;
percentValue: string; // 百分比
}[];
statusStatisticsMap: StatusStatisticsMapType | null;
statusPercentList:
| {
status: string; // 状态
count: number;
percentValue: string; // 百分比
}[]
| null;
errorCode: number;
}

View File

@ -14,6 +14,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -29,7 +30,16 @@
v-on="propsEvent"
>
<template #num="{ record }">
<MsButton type="text">{{ record.num }}</MsButton>
<MsButton type="text">{{ record.num || '-' }}</MsButton>
</template>
<template v-if="isNoPermission" #empty>
<div class="w-full">
<slot name="empty">
<div class="flex h-[40px] flex-col items-center justify-center">
<span class="text-[14px] text-[var(--color-text-4)]">{{ t('common.noResource') }}</span>
</div>
</slot>
</div>
</template>
</MsBaseTable>
</div>
@ -49,6 +59,7 @@
import useTable from '@/components/pure/ms-table/useTable';
import MsSelect from '@/components/business/ms-select';
import { workApiChangeList } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
@ -85,8 +96,8 @@
},
{
title: 'project.commonScript.apiName',
slotName: 'apiName',
dataIndex: 'apiName',
slotName: 'name',
dataIndex: 'name',
width: 200,
},
{
@ -98,14 +109,14 @@
},
{
title: 'workbench.homePage.associationCASE',
slotName: 'case',
dataIndex: 'case',
slotName: 'caseTotal',
dataIndex: 'caseTotal',
width: 200,
},
{
title: 'workbench.homePage.associatedScene',
slotName: 'associatedScene',
dataIndex: 'associatedScene',
slotName: 'scenarioTotal',
dataIndex: 'scenarioTotal',
showDrag: true,
width: 100,
},
@ -122,32 +133,43 @@
},
];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
undefined,
workApiChangeList,
{
columns,
scroll: { x: '100%' },
selectable: false,
heightUsed: 272,
showSelectAll: false,
validatePermission: true,
},
(item) => ({
...item,
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
})
);
function initData() {
const { startTime, endTime, dayNumber } = timeForm.value;
setLoadListParams({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
});
loadList();
const isNoPermission = ref<boolean>(false);
async function initData() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
setLoadListParams({
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
});
await loadList();
isNoPermission.value = false;
} catch (error) {
isNoPermission.value = error === 'no_project_permission';
// eslint-disable-next-line no-console
console.log(error);
}
}
function changeProject() {
initData();
}
onMounted(() => {
@ -160,7 +182,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initData();
}
}
);
@ -170,7 +191,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initData();
}
}
);

View File

@ -12,6 +12,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -24,6 +25,7 @@
:tooltip-text="tabItem.tooltip"
:options="tabItem.options"
:size="60"
:has-permission="hasPermission"
:value-list="tabItem.valueList"
/>
</div>
@ -51,14 +53,9 @@
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type {
PassRateDataType,
SelectedCardItem,
StatusStatisticsMapType,
TimeFormParams,
} from '@/models/workbench/homePage';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { commonRatePieOptions, handlePieData } from '../utils';
import { handlePieData, handleUpdateTabPie } from '../utils';
const props = defineProps<{
item: SelectedCardItem;
@ -82,55 +79,20 @@
})
);
const options = ref(cloneDeep(commonRatePieOptions));
const options = ref({});
// TODO
const detail = ref<PassRateDataType>({
statusStatisticsMap: {
cover: [
{ name: '覆盖率', count: 10 },
{ name: '已覆盖', count: 2 },
{ name: '未覆盖', count: 1 },
],
success: [
{ name: '覆盖率', count: 10 },
{ name: '已覆盖', count: 2 },
{ name: '未覆盖', count: 1 },
],
},
statusPercentList: [
{ status: 'HTTP', count: 1, percentValue: '10%' },
{ status: 'TCP', count: 3, percentValue: '0%' },
{ status: 'BBB', count: 6, percentValue: '0%' },
],
statusStatisticsMap: null,
statusPercentList: null,
errorCode: 109001,
});
const coverValueList = ref([
{
label: t('workbench.homePage.covered'),
value: 10000,
},
{
label: t('workbench.homePage.notCover'),
value: 2000,
},
]);
const passValueList = ref([
{
label: t('common.completed'),
value: 10000,
},
{
label: t('common.inProgress'),
value: 2000,
},
{
label: t('workbench.homePage.unFinish'),
value: 2000,
},
]);
const coverOptions = ref<Record<string, any>>(cloneDeep(options.value));
const completeOptions = ref<Record<string, any>>(cloneDeep(options.value));
const coverValueList = ref<{ value: string | number; label: string; name: string }[]>([]);
const passValueList = ref<{ value: string | number; label: string; name: string }[]>([]);
const coverOptions = ref<Record<string, any>>({});
const completeOptions = ref<Record<string, any>>({});
const apiCountTabList = computed(() => {
return [
{
@ -152,34 +114,7 @@
const apiCountOptions = ref({});
function handlePassRatePercent(data: { name: string; count: number }[]) {
return data.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
}
function handleRatePieData(statusStatisticsMap: StatusStatisticsMapType) {
const { cover, success } = statusStatisticsMap;
coverValueList.value = handlePassRatePercent(cover);
passValueList.value = handlePassRatePercent(success);
coverOptions.value.series.data = handlePassRatePercent(cover);
completeOptions.value.series.data = handlePassRatePercent(success);
coverOptions.value.title.text = cover[0].name ?? '';
coverOptions.value.title.subtext = `${cover[0].count ?? 0}%`;
completeOptions.value.title.text = success[0].name ?? '';
completeOptions.value.title.subtext = `${success[0].count ?? 0}%`;
coverOptions.value.series.color = ['#00C261', '#D4D4D8'];
completeOptions.value.series.color = ['#00C261', '#ED0303'];
}
const hasPermission = ref<boolean>(false);
function initApiCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
@ -193,14 +128,38 @@
organizationId: appStore.currentOrgId,
handleUsers: [],
};
const { statusStatisticsMap, statusPercentList } = detail.value;
apiCountOptions.value = handlePieData(props.item.key, statusPercentList);
handleRatePieData(statusStatisticsMap);
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value;
hasPermission.value = errorCode !== 109001;
apiCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
//
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?.cover || [],
hasPermission.value,
`${props.item.key}-complete`
);
passValueList.value = completedList;
completeOptions.value = comOptions;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
function changeProject() {
initApiCount();
}
onMounted(() => {
initApiCount();
});
@ -211,7 +170,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initApiCount();
}
}
);
@ -221,7 +179,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initApiCount();
}
}
);

View File

@ -12,6 +12,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -25,6 +26,7 @@
:tooltip-text="tabItem.tooltip"
:size="60"
:value-list="tabItem.valueList"
:has-permission="hasPermission"
/>
</div>
</template>
@ -41,7 +43,6 @@
* @desc 用例数量
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select';
@ -53,9 +54,8 @@
import useAppStore from '@/store/modules/app';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { StatusStatisticsMapType } from '@/models/workbench/homePage';
import { commonRatePieOptions, handlePieData } from '../utils';
import { handlePieData, handleUpdateTabPie } from '../utils';
const appStore = useAppStore();
const { t } = useI18n();
@ -79,42 +79,12 @@
})
);
const options = ref(cloneDeep(commonRatePieOptions));
const reviewValueList = ref<{ value: number | string; label: string; name: string }[]>([]);
function handlePassRatePercent(data: { name: string; count: number }[]) {
return data.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
}
const passValueList = ref<{ value: number | string; label: string; name: string }[]>([]);
const reviewValueList = ref([
{
label: t('workbench.homePage.reviewed'),
value: 10000,
},
{
label: t('workbench.homePage.unReviewed'),
value: 2000,
},
]);
const passValueList = ref([
{
label: t('workbench.homePage.havePassed'),
value: 10000,
},
{
label: t('workbench.homePage.notPass'),
value: 2000,
},
]);
const reviewOptions = ref<Record<string, any>>(cloneDeep(options.value));
const passOptions = ref<Record<string, any>>(cloneDeep(options.value));
const reviewOptions = ref<Record<string, any>>({});
const passOptions = ref<Record<string, any>>({});
const caseCountTabList = computed(() => {
return [
{
@ -134,24 +104,10 @@
];
});
// X
function handleRatePieData(statusStatisticsMap: StatusStatisticsMapType) {
const { review, pass } = statusStatisticsMap;
reviewValueList.value = handlePassRatePercent(review);
passValueList.value = handlePassRatePercent(pass);
reviewOptions.value.series.data = handlePassRatePercent(review);
passOptions.value.series.data = handlePassRatePercent(pass);
reviewOptions.value.title.text = review[0].name ?? '';
reviewOptions.value.title.subtext = `${review[0].count ?? 0}%`;
passOptions.value.title.text = pass[0].name ?? '';
passOptions.value.title.subtext = `${pass[0].count ?? 0}%`;
reviewOptions.value.series.color = ['#00C261', '#D4D4D8'];
passOptions.value.series.color = ['#00C261', '#ED0303'];
}
const hasPermission = ref<boolean>(false);
const caseCountOptions = ref<Record<string, any>>({});
async function initCaseCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
@ -167,14 +123,35 @@
};
const detail = await workCaseCountDetail(params);
const { statusStatisticsMap, statusPercentList } = detail;
caseCountOptions.value = handlePieData(props.item.key, statusPercentList);
handleRatePieData(statusStatisticsMap);
hasPermission.value = detail.errorCode !== 109001;
caseCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
const { valueList: reviewValue, options: reviewedOptions } = handleUpdateTabPie(
statusStatisticsMap?.review || [],
hasPermission.value,
`${props.item.key}-review`
);
reviewOptions.value = reviewedOptions;
reviewValueList.value = reviewValue;
const { valueList: passList, options: passOpt } = handleUpdateTabPie(
statusStatisticsMap?.pass || [],
hasPermission.value,
`${props.item.key}-pass`
);
passOptions.value = passOpt;
passValueList.value = passList;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
function changeProject() {
initCaseCount();
}
onMounted(() => {
initCaseCount();
});
@ -185,7 +162,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initCaseCount();
}
}
);
@ -195,7 +171,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initCaseCount();
}
}
);

View File

@ -12,6 +12,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -24,6 +25,7 @@
tooltip-text="workbench.homePage.caseReviewCoverRateTooltip"
:size="60"
:value-list="coverValueList"
:has-permission="hasPermission"
/>
</div>
</div>
@ -39,18 +41,18 @@
* @desc 用例评审数量
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select';
import PassRatePie from './passRatePie.vue';
import { workCaseReviewDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { commonRatePieOptions, handlePieData } from '../utils';
import { handlePieData, handleUpdateTabPie } from '../utils';
const { t } = useI18n();
const appStore = useAppStore();
@ -74,54 +76,58 @@
})
);
const options = ref<Record<string, any>>(cloneDeep(commonRatePieOptions));
const options = ref<Record<string, any>>({});
const coverValueList = ref([
const coverValueList = ref<{ value: number | string; label: string; name: string }[]>([
{
label: t('workbench.homePage.covered'),
value: 10000,
value: '-',
name: '',
},
{
label: t('workbench.homePage.notCover'),
value: 2000,
value: '-',
name: '',
},
]);
// TODO
const detail = ref<PassRateDataType>({
statusStatisticsMap: {
cover: [
{ name: '覆盖率', count: 10 },
{ name: '已覆盖', count: 2 },
{ name: '未覆盖', count: 1 },
],
},
statusPercentList: [
{ status: '未开始', count: 1, percentValue: '10%' },
{ status: '进行中', count: 3, percentValue: '0%' },
{ status: '已完成', count: 6, percentValue: '0%' },
{ status: '已归档', count: 7, percentValue: '0%' },
],
});
const caseReviewCountOptions = ref<Record<string, any>>({});
function initApiCount() {
const { statusStatisticsMap, statusPercentList } = detail.value;
caseReviewCountOptions.value = handlePieData(props.item.key, statusPercentList);
const { cover } = statusStatisticsMap;
coverValueList.value = cover.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
const hasPermission = ref<boolean>(false);
async function initApiCount() {
const { startTime, endTime, dayNumber } = timeForm.value;
const params = {
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
};
try {
const detail: PassRateDataType = await workCaseReviewDetail(params);
options.value.series.data = coverValueList.value;
options.value.title.text = cover[0].name ?? '';
options.value.title.subtext = `${cover[0].count ?? 0}%`;
options.value.series.color = ['#00C261', '#D4D4D8'];
hasPermission.value = detail.errorCode !== 109001;
const { statusStatisticsMap, statusPercentList } = detail;
caseReviewCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
const { options: coverOptions, valueList } = handleUpdateTabPie(
statusStatisticsMap?.cover || [],
hasPermission.value,
`${props.item.key}-cover`
);
coverValueList.value = valueList;
options.value = coverOptions;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
function changeProject() {
initApiCount();
}
onMounted(() => {
@ -134,7 +140,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initApiCount();
}
}
);
@ -144,7 +149,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initApiCount();
}
}
);

View File

@ -12,6 +12,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -19,7 +20,13 @@
<div class="mt-[16px]">
<div class="case-count-wrapper">
<div class="case-count-item mb-[16px]">
<PassRatePie :tooltip-text="tooltip" :options="legacyOptions" :size="60" :value-list="valueList" />
<PassRatePie
:has-permission="hasPermission"
:tooltip-text="tooltip"
:options="legacyOptions"
:size="60"
:value-list="legacyValueList"
/>
</div>
</div>
<div class="h-[148px]">
@ -34,7 +41,6 @@
* @desc 用于缺陷数量待我处理的缺陷数量组件
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select';
@ -43,15 +49,10 @@
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type {
PassRateDataType,
SelectedCardItem,
StatusStatisticsMapType,
TimeFormParams,
} from '@/models/workbench/homePage';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum';
import { commonRatePieOptions, handlePieData } from '../utils';
import { handlePieData, handleUpdateTabPie } from '../utils';
const appStore = useAppStore();
@ -67,21 +68,7 @@
const projectId = ref<string>(innerProjectIds.value[0]);
const valueList = ref<
{
label: string;
value: number;
}[]
>([
{
label: t('workbench.homePage.defectTotal'),
value: 10000,
},
{
label: t('workbench.homePage.legacyDefectsNumber'),
value: 2000,
},
]);
const legacyValueList = ref<{ value: number | string; label: string; name: string }[]>([]);
const timeForm = inject<Ref<TimeFormParams>>(
'timeForm',
@ -92,7 +79,7 @@
})
);
const legacyOptions = ref<Record<string, any>>(cloneDeep(commonRatePieOptions));
const legacyOptions = ref<Record<string, any>>({});
// TODO
const detail = ref<PassRateDataType>({
@ -108,26 +95,12 @@
{ status: 'BBB', count: 3, percentValue: '0%' },
{ status: 'CCC', count: 6, percentValue: '0%' },
],
errorCode: 0,
});
const countOptions = ref({});
function handleRatePieData(statusStatisticsMap: StatusStatisticsMapType) {
const { legacy } = statusStatisticsMap;
valueList.value = legacy.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
legacyOptions.value.series.data = valueList.value;
legacyOptions.value.title.text = legacy[0].name ?? '';
legacyOptions.value.title.subtext = `${legacy[0].count ?? 0}%`;
legacyOptions.value.series.color = ['#D4D4D8', '#00C261'];
}
const countOptions = ref<Record<string, any>>({});
const hasPermission = ref<boolean>(false);
async function initCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
@ -141,9 +114,18 @@
organizationId: appStore.currentOrgId,
handleUsers: [],
};
const { statusStatisticsMap, statusPercentList } = detail.value;
countOptions.value = handlePieData(props.item.key, statusPercentList);
handleRatePieData(statusStatisticsMap);
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value;
hasPermission.value = errorCode !== 109001;
countOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
const { options, valueList } = handleUpdateTabPie(
statusStatisticsMap?.legacy || [],
hasPermission.value,
`${props.item.key}-legacy`
);
legacyValueList.value = valueList;
legacyOptions.value = options;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -154,6 +136,10 @@
return props.item.key === WorkCardEnum.PLAN_LEGACY_BUG ? 'workbench.homePage.planCaseCountLegacyRateTooltip' : '';
});
function changeProject() {
initCount();
}
onMounted(() => {
initCount();
});
@ -164,7 +150,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initCount();
}
}
);
@ -174,7 +159,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initCount();
}
}
);

View File

@ -6,13 +6,13 @@
<MsSelect
v-model:model-value="projectId"
:options="appStore.projectList"
allow-clear
allow-search
value-key="id"
label-key="name"
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
<MsSelect
@ -25,6 +25,7 @@
:multiple="true"
:has-all-select="true"
:default-all-select="true"
@change="changeMember"
>
</MsSelect>
</div>
@ -44,12 +45,15 @@
import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select';
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
import { workBugHandlerDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { characterLimit } from '@/utils';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import type { OverViewOfProject, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { commonColorConfig, getCommonBarOptions } from '../utils';
import { commonColorConfig, getCommonBarOptions, handleNoDataDisplay } from '../utils';
import type { SelectOptionData } from '@arco-design/web-vue';
const { t } = useI18n();
@ -58,19 +62,18 @@
item: SelectedCardItem;
}>();
const memberIds = ref('');
const innerProjectIds = defineModel<string[]>('projectIds', {
required: true,
});
const projectId = computed<string>({
get: () => {
const [newProject] = innerProjectIds.value;
return newProject;
},
set: (val) => val,
const projectId = ref<string>(innerProjectIds.value[0]);
const innerHandleUsers = defineModel<string[]>('handleUsers', {
required: true,
});
const memberIds = ref<string[]>(innerHandleUsers.value);
const timeForm = inject<Ref<TimeFormParams>>(
'timeForm',
ref({
@ -83,128 +86,91 @@
const memberOptions = ref<SelectOptionData[]>([]);
const options = ref<Record<string, any>>({});
const members = computed(() => ['张三', '李四', '王五', '小王']);
const hasRoom = computed(() => members.value.length >= 7);
const seriesData = ref<Record<string, any>[]>([
{
name: '新创建',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [400, 200, 400, 200, 400, 200],
},
{
name: '激活',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '处理中',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '已关闭',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '新创建1',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [400, 200, 400, 200, 400, 200],
},
{
name: '激活1',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '处理中1',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '已关闭1',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '已关闭2',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
// borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
{
name: '已关闭3',
type: 'bar',
barWidth: 12,
stack: 'bug',
itemStyle: {
borderRadius: [2, 2, 0, 0],
},
data: [90, 160, 90, 160, 90, 160],
},
]);
const defectStatusColor = ['#811FA3', '#FFA200', '#3370FF', '#F24F4F'];
function getDefectMemberDetail() {
options.value = getCommonBarOptions(hasRoom.value, [...defectStatusColor, ...commonColorConfig]);
options.value.xAxis.data = members.value;
options.value.series = seriesData.value;
function handleData(detail: OverViewOfProject) {
options.value = getCommonBarOptions(detail.xaxis.length >= 7, [...defectStatusColor, ...commonColorConfig]);
const { invisible, text } = handleNoDataDisplay(detail.xaxis, detail.projectCountList);
options.value.graphic.invisible = invisible;
options.value.graphic.style.text = text;
options.value.xAxis.data = detail.xaxis.map((e) => characterLimit(e, 10));
options.value.series = detail.projectCountList.map((item) => {
return {
name: item.name,
type: 'bar',
stack: 'bugMember',
barWidth: 12,
data: item.count,
itemStyle: {
borderRadius: [2, 2, 0, 0],
},
};
});
}
onMounted(() => {
async function getDefectMemberDetail() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
const detail = await workBugHandlerDetail({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: innerHandleUsers.value,
});
handleData(detail);
} catch (error) {
console.log(error);
}
}
async function getMemberOptions() {
const [newProjectId] = innerProjectIds.value;
const res = await getProjectOptions(newProjectId);
memberOptions.value = res.map((e: any) => ({
label: e.name,
value: e.id,
}));
}
function changeProject() {
memberIds.value = [];
getMemberOptions();
getDefectMemberDetail();
});
}
function changeMember() {
getDefectMemberDetail();
}
watch(
() => innerProjectIds.value,
(val) => {
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
}
}
);
watch(
() => projectId.value,
(val) => {
if (val) {
innerProjectIds.value = [val];
getDefectMemberDetail();
}
}
);
watch(
() => memberIds.value,
(val) => {
if (val) {
innerHandleUsers.value = val;
}
}
);
@ -220,6 +186,11 @@
deep: true,
}
);
onMounted(() => {
getMemberOptions();
getDefectMemberDetail();
});
</script>
<style scoped></style>

View File

@ -42,6 +42,7 @@
import TabCard from './tabCard.vue';
import { workMyCreatedDetail, workProOverviewDetail } from '@/api/modules/workbench';
import { contentTabList } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
@ -53,7 +54,7 @@
} from '@/models/workbench/homePage';
import { WorkCardEnum, WorkOverviewEnum } from '@/enums/workbenchEnum';
import { commonColorConfig, contentTabList, getCommonBarOptions, handleNoDataDisplay } from '../utils';
import { commonColorConfig, getCommonBarOptions, handleNoDataDisplay } from '../utils';
const { t } = useI18n();
@ -92,9 +93,10 @@
function handleData(detail: OverViewOfProject) {
//
const tempAxisData = detail.xaxis.map((xAxisKey) => {
const data = contentTabList.value.find((e) => e.value === xAxisKey);
const data = contentTabList.find((e) => e.value === xAxisKey);
return {
...data,
label: t(data?.label || ''),
count: detail.caseCountMap[xAxisKey as WorkOverviewEnum],
};
});

View File

@ -12,6 +12,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
<MsSelect
@ -48,13 +49,14 @@
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
import { workMemberViewDetail } from '@/api/modules/workbench';
import { contentTabList } from '@/config/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 { commonColorConfig, contentTabList, getCommonBarOptions, handleNoDataDisplay } from '../utils';
import { commonColorConfig, getCommonBarOptions, handleNoDataDisplay } from '../utils';
const { t } = useI18n();
const appStore = useAppStore();
@ -82,19 +84,18 @@
endTime: 0,
})
);
const hasRoom = computed(() => memberIds.value.length >= 7);
const memberOptions = ref<{ label: string; value: string }[]>([]);
const memberOptions = ref<{ label: string; value: string }[]>([]);
const options = ref<Record<string, any>>({});
function handleData(detail: OverViewOfProject) {
options.value = getCommonBarOptions(hasRoom.value, commonColorConfig);
options.value = getCommonBarOptions(detail.xaxis.length >= 7, commonColorConfig);
const { invisible, text } = handleNoDataDisplay(detail.xaxis, detail.projectCountList);
options.value.graphic.invisible = invisible;
options.value.graphic.style.text = text;
options.value.xAxis.data = detail.xaxis.map((e) => characterLimit(e, 10));
options.value.series = detail.projectCountList.map((item, index) => {
return {
name: contentTabList.value[index].label,
name: t(contentTabList[index].label),
type: 'bar',
stack: 'member',
barWidth: 12,
@ -122,6 +123,7 @@
const detail = await workMemberViewDetail(params);
handleData(detail);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
@ -135,6 +137,11 @@
}));
}
function changeProject() {
getMemberOptions();
initOverViewMemberDetail();
}
watch(
() => innerProjectIds.value,
(val) => {
@ -142,8 +149,6 @@
const [newProjectId] = val;
projectId.value = newProjectId;
memberIds.value = [];
getMemberOptions();
initOverViewMemberDetail();
}
}
);
@ -181,6 +186,7 @@
onMounted(() => {
getMemberOptions();
initOverViewMemberDetail();
});
</script>

View File

@ -14,7 +14,7 @@
<div class="pass-rate-title flex-1">
<div v-for="item of props.valueList" :key="item.label" class="flex-1">
<div class="mb-[8px] text-[var(--color-text-4)]">{{ item.label }}</div>
<div class="pass-rate-count">{{ addCommasToNumber(item.value) }}</div>
<div class="pass-rate-count">{{ hasPermission ? addCommasToNumber(item.value as number) : '-' }}</div>
</div>
</div>
</div>
@ -34,9 +34,10 @@
options: Record<string, any>;
size: number;
tooltipText?: string;
hasPermission: boolean;
valueList: {
label: string;
value: number;
value: number | string;
}[];
}>();
</script>

View File

@ -83,7 +83,7 @@
name: '',
type: 'pie',
padAngle: 1,
radius: ['46%', '56%'],
radius: ['50%', '58%'],
center: ['50%', '32%'],
color: [],
avoidLabelOverlap: false,

View File

@ -11,6 +11,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -19,7 +20,8 @@
<div class="case-count-wrapper">
<div class="case-count-item">
<PassRatePie
:options="options"
:options="relatedOptions"
:has-permission="hasPermission"
tooltip-text="workbench.homePage.associateCaseCoverRateTooltip"
:size="60"
:value-list="coverRateValueList"
@ -35,7 +37,6 @@
* @desc 关联用例数量
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsSelect from '@/components/business/ms-select';
import PassRatePie from './passRatePie.vue';
@ -44,9 +45,9 @@
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { commonRatePieOptions } from '../utils';
import { handleUpdateTabPie } from '../utils';
const appStore = useAppStore();
@ -71,14 +72,26 @@
})
);
const options = ref<Record<string, any>>(cloneDeep(commonRatePieOptions));
const relatedOptions = ref<Record<string, any>>({});
const hasPermission = ref<boolean>(false);
const coverRateValueList = ref<{ value: number; label: string; name: string }[]>([]);
const coverRateValueList = ref<{ value: number | string; label: string; name: string }[]>([
{
label: t('workbench.homePage.covered'),
value: '-',
name: '',
},
{
label: t('workbench.homePage.notCover'),
value: '-',
name: '',
},
]);
async function getRelatedCaseCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
const detail = await workAssociateCaseDetail({
const detail: PassRateDataType = await workAssociateCaseDetail({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
@ -88,24 +101,28 @@
organizationId: appStore.currentOrgId,
handleUsers: [],
});
const { cover } = detail.statusStatisticsMap;
coverRateValueList.value = cover.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
options.value.series.data = coverRateValueList.value;
options.value.title.text = cover[0].name ?? '';
options.value.title.subtext = `${cover[0].count ?? 0}%`;
options.value.series.color = ['#00C261', '#D4D4D8'];
hasPermission.value = detail.errorCode !== 109001;
const { statusStatisticsMap } = detail;
const { options, valueList } = handleUpdateTabPie(
statusStatisticsMap?.cover || [],
hasPermission.value,
`${props.item.key}-cover`
);
relatedOptions.value = options;
coverRateValueList.value = valueList;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
function changeProject() {
getRelatedCaseCount();
}
onMounted(() => {
getRelatedCaseCount();
});
@ -125,7 +142,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
getRelatedCaseCount();
}
}
);

View File

@ -31,7 +31,7 @@
const props = defineProps<{
contentTabList: {
label: string | number;
label: string;
value: string | number;
count?: number;
icon?: string;

View File

@ -14,6 +14,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -22,7 +23,12 @@
<TabCard :content-tab-list="testPlanTabList" not-has-padding hidden-border min-width="270px">
<template #item="{ item: tabItem }">
<div class="w-full">
<PassRatePie :options="tabItem.options" :size="60" :value-list="tabItem.valueList" />
<PassRatePie
:has-permission="hasPermission"
:options="tabItem.options"
:size="60"
:value-list="tabItem.valueList"
/>
</div>
</template>
</TabCard>
@ -38,7 +44,6 @@
* @desc 测试计划数量
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select';
@ -48,14 +53,9 @@
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type {
PassRateDataType,
SelectedCardItem,
StatusStatisticsMapType,
TimeFormParams,
} from '@/models/workbench/homePage';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { commonRatePieOptions, handlePieData } from '../utils';
import { handlePieData, handleUpdateTabPie } from '../utils';
const props = defineProps<{
item: SelectedCardItem;
@ -103,51 +103,20 @@
{ status: 'TCP', count: 3, percentValue: '0%' },
{ status: 'BBB', count: 6, percentValue: '0%' },
],
errorCode: 109001,
});
const options = ref(cloneDeep(commonRatePieOptions));
const executionOptions = ref<Record<string, any>>(cloneDeep(options.value));
const passOptions = ref<Record<string, any>>(cloneDeep(options.value));
const completeOptions = ref<Record<string, any>>(cloneDeep(options.value));
const executionOptions = ref<Record<string, any>>({});
const passOptions = ref<Record<string, any>>({});
const completeOptions = ref<Record<string, any>>({});
//
const executionValueList = ref([
{
label: t('common.unExecute'),
value: 10000,
},
{
label: t('common.executed'),
value: 2000,
},
]);
const executionValueList = ref<{ value: number | string; label: string; name: string }[]>([]);
//
const passValueList = ref([
{
label: t('workbench.homePage.havePassed'),
value: 10000,
},
{
label: t('workbench.homePage.notPass'),
value: 2000,
},
]);
const passValueList = ref<{ value: number | string; label: string; name: string }[]>([]);
//
const completeValueList = ref([
{
label: t('common.completed'),
value: 10000,
},
{
label: t('common.inProgress'),
value: 2000,
},
{
label: t('workbench.homePage.unFinish'),
value: 2000,
},
]);
const completeValueList = ref<{ value: number | string; label: string; name: string }[]>([]);
const testPlanTabList = computed(() => {
return [
@ -172,41 +141,8 @@
];
});
function handlePassRatePercent(data: { name: string; count: number }[]) {
return data.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
}
function handleRatePieData(statusStatisticsMap: StatusStatisticsMapType) {
const { execute, pass, complete } = statusStatisticsMap;
executionValueList.value = handlePassRatePercent(execute);
passValueList.value = handlePassRatePercent(pass);
completeValueList.value = handlePassRatePercent(complete);
executionOptions.value.series.data = handlePassRatePercent(execute);
passOptions.value.series.data = handlePassRatePercent(pass);
completeOptions.value.series.data = handlePassRatePercent(complete);
executionOptions.value.title.text = execute[0].name ?? '';
executionOptions.value.title.subtext = `${execute[0].count ?? 0}%`;
passOptions.value.title.text = pass[0].name ?? '';
passOptions.value.title.subtext = `${pass[0].count ?? 0}%`;
completeOptions.value.title.text = complete[0].name ?? '';
completeOptions.value.title.subtext = `${complete[0].count ?? 0}%`;
executionOptions.value.series.color = ['#D4D4D8', '#00C261'];
passOptions.value.series.color = ['#D4D4D8', '#00C261'];
completeOptions.value.series.color = ['#00C261', '#3370FF', '#D4D4D8'];
}
const testPlanCountOptions = ref({});
const hasPermission = ref<boolean>(false);
async function initTestPlanCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
@ -220,18 +156,47 @@
organizationId: appStore.currentOrgId,
handleUsers: [],
};
const { statusStatisticsMap, statusPercentList } = detail.value;
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value;
testPlanCountOptions.value = handlePieData(props.item.key, statusPercentList);
handleRatePieData(statusStatisticsMap);
hasPermission.value = errorCode !== 109001;
testPlanCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
//
const { options: executedOptions, valueList: executedList } = handleUpdateTabPie(
statusStatisticsMap?.execute || [],
hasPermission.value,
`${props.item.key}-execute`
);
//
const { options: passedOptions, valueList: passList } = handleUpdateTabPie(
statusStatisticsMap?.pass || [],
hasPermission.value,
`${props.item.key}-pass`
);
//
const { options: comOptions, valueList: completeList } = handleUpdateTabPie(
statusStatisticsMap?.complete || [],
hasPermission.value,
`${props.item.key}-complete`
);
executionValueList.value = executedList;
passValueList.value = passList;
completeValueList.value = completeList;
executionOptions.value = executedOptions;
passOptions.value = passedOptions;
completeOptions.value = comOptions;
} catch (error) {
console.log(error);
}
}
onMounted(() => {
function changeProject() {
initTestPlanCount();
});
}
onMounted(() => {
initTestPlanCount();
@ -242,7 +207,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initTestPlanCount();
}
}
);
@ -253,7 +217,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initTestPlanCount();
}
}
);

View File

@ -12,6 +12,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -57,6 +58,15 @@
}}
</a-tag>
</template>
<template v-if="isNoPermission" #empty>
<div class="w-full">
<slot name="empty">
<div class="flex h-[40px] flex-col items-center justify-center">
<span class="text-[14px] text-[var(--color-text-4)]">{{ t('common.noResource') }}</span>
</div>
</slot>
</div>
</template>
</MsBaseTable>
</div>
</div>
@ -74,6 +84,7 @@
import MsSelect from '@/components/business/ms-select';
import passRateLine from '@/views/case-management/caseReview/components/passRateLine.vue';
import { workReviewList } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
@ -130,7 +141,7 @@
},
];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(undefined, {
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(workReviewList, {
columns,
scroll: { x: '100%' },
selectable: false,
@ -138,19 +149,28 @@
showSelectAll: false,
});
function initData() {
const { startTime, endTime, dayNumber } = timeForm.value;
setLoadListParams({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
});
loadList();
const isNoPermission = ref<boolean>(false);
async function initData() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
setLoadListParams({
startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null,
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
});
await loadList();
} catch (error) {
isNoPermission.value = error === 'no_project_permission';
// eslint-disable-next-line no-console
}
}
function changeProject() {
initData();
}
onMounted(() => {
@ -162,7 +182,6 @@
(val) => {
if (val) {
innerProjectIds.value = [val];
initData();
}
}
);

View File

@ -93,6 +93,7 @@
<DefectMemberBar
v-else-if="item.key === WorkCardEnum.BUG_HANDLE_USER"
v-model:projectIds="item.projectIds"
v-model:handleUsers="item.handleUsers"
:item="item"
/>
<DefectCount

View File

@ -106,4 +106,10 @@ export default {
'Scenario Pass Rate: Last successful execution scenarios / Total scenarios * 100%',
'workbench.homePage.planCaseCountLegacyRateTooltip':
'Legacy Rate: Unresolved defects / All associated defects * 100%',
'workbench.homePage.reviewRate': 'Review Rate',
'workbench.homePage.passRate': 'Pass Rate',
'workbench.homePage.coverRate': 'Coverage Rate',
'workbench.homePage.executeRate': 'Execution Rate',
'workbench.homePage.completeRate': 'Completion Rate',
'workbench.homePage.legacyRate': 'Legacy Rate',
};

View File

@ -92,4 +92,10 @@ export default {
'workbench.homePage.scenarioCaseCountExecuteRateTooltip': '场景执行率:执行过的场景/所有场景 * 100%',
'workbench.homePage.scenarioCaseCountPassRateTooltip': '场景通过率:最后一次执行成功的场景/场景总数*100%',
'workbench.homePage.planCaseCountLegacyRateTooltip': '遗留率:未关闭缺陷/所有关联的缺陷*100%',
'workbench.homePage.reviewRate': '评审率',
'workbench.homePage.passRate': '通过率',
'workbench.homePage.coverRate': '覆盖率',
'workbench.homePage.executeRate': '执行率',
'workbench.homePage.completeRate': '完成率',
'workbench.homePage.legacyRate': '遗留率',
};

View File

@ -1,9 +1,11 @@
import { commonConfig, toolTipConfig } from '@/config/testPlan';
import { cloneDeep } from 'lodash-es';
import { toolTipConfig } from '@/config/testPlan';
import { commonRatePieOptions, defaultValueMap } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n';
import { addCommasToNumber } from '@/utils';
import type { ModuleCardItem } from '@/models/workbench/homePage';
import { WorkCardEnum, WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum';
import { WorkCardEnum } from '@/enums/workbenchEnum';
const { t } = useI18n();
// 通用颜色配置
@ -37,7 +39,7 @@ export const commonColorConfig = [
export const colorMapConfig: Record<string, string[]> = {
[WorkCardEnum.CASE_COUNT]: ['#ED0303', '#FFA200', '#3370FF', '#D4D4D8'],
[WorkCardEnum.ASSOCIATE_CASE_COUNT]: ['#00C261', '#3370FF'],
[WorkCardEnum.REVIEW_CASE_COUNT]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[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'],
@ -100,13 +102,14 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
],
color,
grid: {
top: '36px',
left: '10px',
right: '10px',
bottom: hasRoom ? '54px' : '5px',
top: 36,
left: 0,
right: 0,
bottom: hasRoom ? 54 : 5,
containLabel: true,
},
xAxis: {
show: true,
splitLine: false,
boundaryGap: true,
type: 'category',
@ -128,7 +131,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
{
type: 'value',
name: '单位:个', // 设置单位
nameLocation: 'end',
position: 'left',
nameTextStyle: {
fontSize: 12,
color: '#AEAEB2', // 自定义字体大小和颜色
@ -143,6 +146,8 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
type: 'dashed', // 水平线线型,可选 'solid'、'dashed'、'dotted'
},
},
min: 0,
max: 1,
},
],
graphic: {
@ -159,7 +164,6 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
},
invisible: true,
},
colorBy: 'series',
series: [],
barCategoryGap: '50%', // 控制 X 轴分布居中效果
@ -194,60 +198,8 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
};
}
export const contentTabList = ref<ModuleCardItem[]>([
{
label: t('workbench.homePage.functionalUseCase'),
value: WorkOverviewEnum.FUNCTIONAL,
icon: WorkOverviewIconEnum.FUNCTIONAL,
color: 'rgb(var(--primary-5))',
count: 0,
},
{
label: t('workbench.homePage.useCaseReview'),
value: WorkOverviewEnum.CASE_REVIEW,
icon: WorkOverviewIconEnum.CASE_REVIEW,
color: 'rgb(var(--success-6))',
count: 0,
},
{
label: t('workbench.homePage.interfaceAPI'),
value: WorkOverviewEnum.API,
icon: WorkOverviewIconEnum.API,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: t('workbench.homePage.interfaceCASE'),
value: WorkOverviewEnum.API_CASE,
icon: WorkOverviewIconEnum.API_CASE,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: t('workbench.homePage.interfaceScenario'),
value: WorkOverviewEnum.API_SCENARIO,
icon: WorkOverviewIconEnum.API_SCENARIO,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: t('workbench.homePage.apiPlan'),
value: WorkOverviewEnum.TEST_PLAN,
icon: WorkOverviewIconEnum.TEST_PLAN,
color: 'rgb(var(--link-6))',
count: 0,
},
{
label: t('workbench.homePage.bugCount'),
value: WorkOverviewEnum.BUG_COUNT,
icon: WorkOverviewIconEnum.BUG_COUNT,
color: 'rgb(var(--danger-6))',
count: 0,
},
]);
// 下方饼图配置
export function getPieCharOptions(key: WorkCardEnum) {
export function getPieCharOptions(key: WorkCardEnum, hasPermission: boolean) {
return {
title: {
show: true,
@ -272,6 +224,7 @@ export function getPieCharOptions(key: WorkCardEnum) {
tooltip: {
...toolTipConfig,
position: 'right',
show: !!hasPermission,
},
legend: {
width: '100%',
@ -387,6 +340,20 @@ export function getPieCharOptions(key: WorkCardEnum) {
},
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,
},
};
}
@ -418,108 +385,21 @@ export function handleNoDataDisplay(
};
}
// XX率饼图配置
export const commonRatePieOptions = {
...commonConfig,
title: {
show: true,
text: '',
left: 26,
top: '20%',
textStyle: {
fontSize: 12,
fontWeight: 'normal',
color: '#959598',
},
triggerEvent: true, // 开启鼠标事件
subtext: '0',
subtextStyle: {
fontSize: 12,
color: '#323233',
fontWeight: 'bold',
align: 'center',
lineHeight: 3,
},
textAlign: 'center',
tooltip: {
...toolTipConfig,
position: 'right',
},
},
tooltip: {
...toolTipConfig,
position: 'right',
},
legend: {
show: false,
},
series: {
name: '',
type: 'pie',
color: [],
padAngle: 2,
radius: ['85%', '100%'],
center: [30, '50%'],
avoidLabelOverlap: false,
label: {
show: false,
position: 'center',
},
emphasis: {
scale: false, // 禁用放大效果
label: {
show: false,
fontSize: 40,
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [],
},
// graphic: [
// {
// type: 'text',
// left: 'center',
// top: '5%',
// style: {
// text: '饼图标题',
// fontSize: 18,
// fontWeight: 'bold',
// fill: '#333',
// cursor: 'pointer'
// },
// onmouseover (params) {
// // 悬浮到标题上时显示提示信息
// // chart.dispatchAction({
// // type: 'showTip',
// // position: [params.event.offsetX, params.event.offsetY],
// // // 配置提示内容
// // formatter: '这是饼图标题的提示内容'
// // });
// },
// onmouseout () {
// // 离开标题时隐藏提示信息
// // chart.dispatchAction({
// // type: 'hideTip'
// // });
// }
// }
// ]
};
// 统一处理下方饼图数据结构
export function handlePieData(
key: WorkCardEnum,
statusPercentList: {
status: string; // 状态
count: number;
percentValue: string; // 百分比
}[]
hasPermission: boolean,
statusPercentList:
| {
status: string; // 状态
count: number;
percentValue: string; // 百分比
}[]
| null = []
) {
const options: Record<string, any> = getPieCharOptions(key);
options.series.data = statusPercentList.map((item) => ({
const options: Record<string, any> = getPieCharOptions(key, hasPermission);
const lastStatusPercentList = statusPercentList ?? [];
options.series.data = lastStatusPercentList.map((item) => ({
name: item.status,
value: item.count,
}));
@ -527,11 +407,17 @@ export function handlePieData(
// 计算总数和图例格式
const tempObject: Record<string, any> = {};
let totalCount = 0;
statusPercentList.forEach((item) => {
lastStatusPercentList.forEach((item) => {
tempObject[item.status] = item;
totalCount += item.count;
});
// 设置副标题为总数
options.title.subtext = addCommasToNumber(totalCount);
if (!hasPermission) {
options.title.subtext = '-';
}
// 设置图例的格式化函数,显示百分比
options.legend.formatter = (name: string) => {
return `{a|${tempObject[name].status}} {b|${addCommasToNumber(tempObject[name].count)}} {c|${
@ -539,8 +425,51 @@ export function handlePieData(
}}`;
};
// 设置副标题为总数
options.title.subtext = addCommasToNumber(totalCount);
return options;
}
// 更新options
export function handleUpdateTabPie(
list: {
name: string;
count: number;
}[],
hasPermission: boolean, // 是否有权限
key: string
) {
const options: Record<string, any> = cloneDeep(commonRatePieOptions);
const typeKey = key.split('-')[0];
const valueKey = key.split('-')[1];
const countList = list || [];
let lastCountList: { value: number | string; label: string; name: string }[] = [];
if (hasPermission) {
lastCountList = countList.slice(1).map((item) => {
return {
value: item.count,
label: item.name,
name: item.name,
};
});
options.series.data = lastCountList;
options.title.text = countList[0].name ?? '';
options.title.subtext = `${countList[0].count ?? 0}%`;
} else {
options.series.data = [];
lastCountList = defaultValueMap[typeKey][valueKey].defaultList.map((e: any) => {
return {
...e,
label: t(e.label),
};
});
options.title.text = t(defaultValueMap[typeKey][valueKey].defaultName);
options.title.subtext = '-%';
}
options.series.color = defaultValueMap[typeKey][valueKey].color;
return {
valueList: lastCountList,
options,
};
}