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 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 { ApiScenarioTableItem } from '@/models/apiTest/scenario';
import type { BugListItem } from '@/models/bug-management'; import type { BugListItem } from '@/models/bug-management';
import type { ReviewItem } from '@/models/caseManagement/caseReview'; import type { ReviewItem } from '@/models/caseManagement/caseReview';
@ -17,6 +17,7 @@ import type {
import { import {
EditDashboardLayoutUrl, EditDashboardLayoutUrl,
GetDashboardLayoutUrl, GetDashboardLayoutUrl,
WorkApiChangeListUrl,
WorkAssociateCaseDetailUrl, WorkAssociateCaseDetailUrl,
WorkbenchApiCaseListUrl, WorkbenchApiCaseListUrl,
WorkbenchBugListUrl, WorkbenchBugListUrl,
@ -25,10 +26,13 @@ import {
WorkbenchScenarioListUrl, WorkbenchScenarioListUrl,
WorkbenchTestPlanListUrl, WorkbenchTestPlanListUrl,
WorkbenchTestPlanStatisticUrl, WorkbenchTestPlanStatisticUrl,
WorkBugHandlerDetailUrl,
WorkCaseCountDetailUrl, WorkCaseCountDetailUrl,
WorkCaseReviewDetailUrl,
WorkMemberViewDetailUrl, WorkMemberViewDetailUrl,
WorkMyCreatedDetailUrl, WorkMyCreatedDetailUrl,
WorkProOverviewDetailUrl, WorkProOverviewDetailUrl,
WorkReviewListUrl,
WorkTodoBugListUrl, WorkTodoBugListUrl,
WorkTodoPlanListUrl, WorkTodoPlanListUrl,
WorkTodoReviewListUrl, WorkTodoReviewListUrl,
@ -100,6 +104,32 @@ export function workAssociateCaseDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkAssociateCaseDetailUrl, data }); 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) { export function workbenchTodoReviewList(data: TableQueryParams) {
return MSR.post<CommonList<ReviewItem>>({ url: WorkTodoReviewListUrl, data }); 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 WorkMemberViewDetailUrl = '/dashboard/project_member_view'; // 工作台-首页-人员概览
export const WorkCaseCountDetailUrl = '/dashboard/case_count'; // 工作台-首页-用例数量 export const WorkCaseCountDetailUrl = '/dashboard/case_count'; // 工作台-首页-用例数量
export const WorkAssociateCaseDetailUrl = '/dashboard/associate_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; return data;
} }
} catch (err) { } catch (err) {
// TODO 在这里处理拦截设置表格无资源权限
setTableErrorStatus('error'); setTableErrorStatus('error');
propsRes.value.data = []; propsRes.value.data = [];
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(err); console.log(err);
throw err; // 将错误抛出
} finally { } finally {
setLoading(false); setLoading(false);
// debug 模式下打印属性 // 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 { export interface ModuleCardItem {
label: string | number; label: string;
value: string | number; value: string | number;
count?: number; count?: number;
icon?: string; icon?: string;
@ -73,10 +73,13 @@ export type StatusStatisticsMapType = Record<
>; >;
export interface PassRateDataType { export interface PassRateDataType {
statusStatisticsMap: StatusStatisticsMapType; statusStatisticsMap: StatusStatisticsMapType | null;
statusPercentList: { statusPercentList:
| {
status: string; // 状态 status: string; // 状态
count: number; count: number;
percentValue: string; // 百分比 percentValue: string; // 百分比
}[]; }[]
| null;
errorCode: number;
} }

View File

@ -14,6 +14,7 @@
:search-keys="['name']" :search-keys="['name']"
class="!w-[240px]" class="!w-[240px]"
:prefix="t('workbench.homePage.project')" :prefix="t('workbench.homePage.project')"
@change="changeProject"
> >
</MsSelect> </MsSelect>
</div> </div>
@ -29,7 +30,16 @@
v-on="propsEvent" v-on="propsEvent"
> >
<template #num="{ record }"> <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> </template>
</MsBaseTable> </MsBaseTable>
</div> </div>
@ -49,6 +59,7 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import { workApiChangeList } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
@ -85,8 +96,8 @@
}, },
{ {
title: 'project.commonScript.apiName', title: 'project.commonScript.apiName',
slotName: 'apiName', slotName: 'name',
dataIndex: 'apiName', dataIndex: 'name',
width: 200, width: 200,
}, },
{ {
@ -98,14 +109,14 @@
}, },
{ {
title: 'workbench.homePage.associationCASE', title: 'workbench.homePage.associationCASE',
slotName: 'case', slotName: 'caseTotal',
dataIndex: 'case', dataIndex: 'caseTotal',
width: 200, width: 200,
}, },
{ {
title: 'workbench.homePage.associatedScene', title: 'workbench.homePage.associatedScene',
slotName: 'associatedScene', slotName: 'scenarioTotal',
dataIndex: 'associatedScene', dataIndex: 'scenarioTotal',
showDrag: true, showDrag: true,
width: 100, width: 100,
}, },
@ -122,24 +133,25 @@
}, },
]; ];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable( const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
undefined, workApiChangeList,
{ {
columns, columns,
scroll: { x: '100%' }, scroll: { x: '100%' },
selectable: false, selectable: false,
heightUsed: 272, heightUsed: 272,
showSelectAll: false, showSelectAll: false,
validatePermission: true,
}, },
(item) => ({ (item) => ({
...item, ...item,
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'), updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
}) })
); );
function initData() { const isNoPermission = ref<boolean>(false);
async function initData() {
try {
const { startTime, endTime, dayNumber } = timeForm.value; const { startTime, endTime, dayNumber } = timeForm.value;
setLoadListParams({ setLoadListParams({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime, startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime, endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null, dayNumber: dayNumber ?? null,
@ -147,7 +159,17 @@
organizationId: appStore.currentOrgId, organizationId: appStore.currentOrgId,
handleUsers: [], handleUsers: [],
}); });
loadList(); 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(() => { onMounted(() => {
@ -160,7 +182,6 @@
if (val) { if (val) {
const [newProjectId] = val; const [newProjectId] = val;
projectId.value = newProjectId; projectId.value = newProjectId;
initData();
} }
} }
); );
@ -170,7 +191,6 @@
(val) => { (val) => {
if (val) { if (val) {
innerProjectIds.value = [val]; innerProjectIds.value = [val];
initData();
} }
} }
); );

View File

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

View File

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

View File

@ -12,6 +12,7 @@
:search-keys="['name']" :search-keys="['name']"
class="!w-[240px]" class="!w-[240px]"
:prefix="t('workbench.homePage.project')" :prefix="t('workbench.homePage.project')"
@change="changeProject"
> >
</MsSelect> </MsSelect>
</div> </div>
@ -24,6 +25,7 @@
tooltip-text="workbench.homePage.caseReviewCoverRateTooltip" tooltip-text="workbench.homePage.caseReviewCoverRateTooltip"
:size="60" :size="60"
:value-list="coverValueList" :value-list="coverValueList"
:has-permission="hasPermission"
/> />
</div> </div>
</div> </div>
@ -39,18 +41,18 @@
* @desc 用例评审数量 * @desc 用例评审数量
*/ */
import { ref } from 'vue'; import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsChart from '@/components/pure/chart/index.vue'; import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import PassRatePie from './passRatePie.vue'; import PassRatePie from './passRatePie.vue';
import { workCaseReviewDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { PassRateDataType, SelectedCardItem, 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 { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); 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'), label: t('workbench.homePage.covered'),
value: 10000, value: '-',
name: '',
}, },
{ {
label: t('workbench.homePage.notCover'), 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>>({}); 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) => { const hasPermission = ref<boolean>(false);
return { async function initApiCount() {
value: item.count, const { startTime, endTime, dayNumber } = timeForm.value;
label: item.name, const params = {
name: item.name, 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; hasPermission.value = detail.errorCode !== 109001;
options.value.title.text = cover[0].name ?? '';
options.value.title.subtext = `${cover[0].count ?? 0}%`; const { statusStatisticsMap, statusPercentList } = detail;
options.value.series.color = ['#00C261', '#D4D4D8']; 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(() => { onMounted(() => {
@ -134,7 +140,6 @@
if (val) { if (val) {
const [newProjectId] = val; const [newProjectId] = val;
projectId.value = newProjectId; projectId.value = newProjectId;
initApiCount();
} }
} }
); );
@ -144,7 +149,6 @@
(val) => { (val) => {
if (val) { if (val) {
innerProjectIds.value = [val]; innerProjectIds.value = [val];
initApiCount();
} }
} }
); );

View File

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

View File

@ -6,13 +6,13 @@
<MsSelect <MsSelect
v-model:model-value="projectId" v-model:model-value="projectId"
:options="appStore.projectList" :options="appStore.projectList"
allow-clear
allow-search allow-search
value-key="id" value-key="id"
label-key="name" label-key="name"
:search-keys="['name']" :search-keys="['name']"
class="!w-[240px]" class="!w-[240px]"
:prefix="t('workbench.homePage.project')" :prefix="t('workbench.homePage.project')"
@change="changeProject"
> >
</MsSelect> </MsSelect>
<MsSelect <MsSelect
@ -25,6 +25,7 @@
:multiple="true" :multiple="true"
:has-all-select="true" :has-all-select="true"
:default-all-select="true" :default-all-select="true"
@change="changeMember"
> >
</MsSelect> </MsSelect>
</div> </div>
@ -44,12 +45,15 @@
import MsChart from '@/components/pure/chart/index.vue'; import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select'; 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 { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; 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'; import type { SelectOptionData } from '@arco-design/web-vue';
const { t } = useI18n(); const { t } = useI18n();
@ -58,19 +62,18 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const memberIds = ref('');
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
const projectId = computed<string>({ const projectId = ref<string>(innerProjectIds.value[0]);
get: () => {
const [newProject] = innerProjectIds.value; const innerHandleUsers = defineModel<string[]>('handleUsers', {
return newProject; required: true,
},
set: (val) => val,
}); });
const memberIds = ref<string[]>(innerHandleUsers.value);
const timeForm = inject<Ref<TimeFormParams>>( const timeForm = inject<Ref<TimeFormParams>>(
'timeForm', 'timeForm',
ref({ ref({
@ -83,128 +86,91 @@
const memberOptions = ref<SelectOptionData[]>([]); const memberOptions = ref<SelectOptionData[]>([]);
const options = ref<Record<string, any>>({}); const options = ref<Record<string, any>>({});
const members = computed(() => ['张三', '李四', '王五', '小王']);
const hasRoom = computed(() => members.value.length >= 7); const defectStatusColor = ['#811FA3', '#FFA200', '#3370FF', '#F24F4F'];
const seriesData = ref<Record<string, any>[]>([
{ function handleData(detail: OverViewOfProject) {
name: '新创建', 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', type: 'bar',
stack: 'bugMember',
barWidth: 12, barWidth: 12,
stack: 'bug', data: item.count,
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: { itemStyle: {
borderRadius: [2, 2, 0, 0], 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;
} }
onMounted(() => { async function getDefectMemberDetail() {
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( watch(
() => projectId.value, () => projectId.value,
(val) => { (val) => {
if (val) { if (val) {
innerProjectIds.value = [val]; innerProjectIds.value = [val];
getDefectMemberDetail(); }
}
);
watch(
() => memberIds.value,
(val) => {
if (val) {
innerHandleUsers.value = val;
} }
} }
); );
@ -220,6 +186,11 @@
deep: true, deep: true,
} }
); );
onMounted(() => {
getMemberOptions();
getDefectMemberDetail();
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

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

View File

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

View File

@ -14,7 +14,7 @@
<div class="pass-rate-title flex-1"> <div class="pass-rate-title flex-1">
<div v-for="item of props.valueList" :key="item.label" class="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="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> </div>
</div> </div>
@ -34,9 +34,10 @@
options: Record<string, any>; options: Record<string, any>;
size: number; size: number;
tooltipText?: string; tooltipText?: string;
hasPermission: boolean;
valueList: { valueList: {
label: string; label: string;
value: number; value: number | string;
}[]; }[];
}>(); }>();
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
:search-keys="['name']" :search-keys="['name']"
class="!w-[240px]" class="!w-[240px]"
:prefix="t('workbench.homePage.project')" :prefix="t('workbench.homePage.project')"
@change="changeProject"
> >
</MsSelect> </MsSelect>
</div> </div>
@ -57,6 +58,15 @@
}} }}
</a-tag> </a-tag>
</template> </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> </MsBaseTable>
</div> </div>
</div> </div>
@ -74,6 +84,7 @@
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import passRateLine from '@/views/case-management/caseReview/components/passRateLine.vue'; import passRateLine from '@/views/case-management/caseReview/components/passRateLine.vue';
import { workReviewList } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; 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, columns,
scroll: { x: '100%' }, scroll: { x: '100%' },
selectable: false, selectable: false,
@ -138,11 +149,12 @@
showSelectAll: false, showSelectAll: false,
}); });
function initData() { const isNoPermission = ref<boolean>(false);
async function initData() {
try {
const { startTime, endTime, dayNumber } = timeForm.value; const { startTime, endTime, dayNumber } = timeForm.value;
setLoadListParams({ setLoadListParams({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime, startTime: dayNumber ? null : startTime,
endTime: dayNumber ? null : endTime, endTime: dayNumber ? null : endTime,
dayNumber: dayNumber ?? null, dayNumber: dayNumber ?? null,
@ -150,7 +162,15 @@
organizationId: appStore.currentOrgId, organizationId: appStore.currentOrgId,
handleUsers: [], handleUsers: [],
}); });
loadList(); await loadList();
} catch (error) {
isNoPermission.value = error === 'no_project_permission';
// eslint-disable-next-line no-console
}
}
function changeProject() {
initData();
} }
onMounted(() => { onMounted(() => {
@ -162,7 +182,6 @@
(val) => { (val) => {
if (val) { if (val) {
innerProjectIds.value = [val]; innerProjectIds.value = [val];
initData();
} }
} }
); );

View File

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

View File

@ -106,4 +106,10 @@ export default {
'Scenario Pass Rate: Last successful execution scenarios / Total scenarios * 100%', 'Scenario Pass Rate: Last successful execution scenarios / Total scenarios * 100%',
'workbench.homePage.planCaseCountLegacyRateTooltip': 'workbench.homePage.planCaseCountLegacyRateTooltip':
'Legacy Rate: Unresolved defects / All associated defects * 100%', '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.scenarioCaseCountExecuteRateTooltip': '场景执行率:执行过的场景/所有场景 * 100%',
'workbench.homePage.scenarioCaseCountPassRateTooltip': '场景通过率:最后一次执行成功的场景/场景总数*100%', 'workbench.homePage.scenarioCaseCountPassRateTooltip': '场景通过率:最后一次执行成功的场景/场景总数*100%',
'workbench.homePage.planCaseCountLegacyRateTooltip': '遗留率:未关闭缺陷/所有关联的缺陷*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 { useI18n } from '@/hooks/useI18n';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
import type { ModuleCardItem } from '@/models/workbench/homePage'; import { WorkCardEnum } from '@/enums/workbenchEnum';
import { WorkCardEnum, WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum';
const { t } = useI18n(); const { t } = useI18n();
// 通用颜色配置 // 通用颜色配置
@ -37,7 +39,7 @@ export const commonColorConfig = [
export const colorMapConfig: Record<string, string[]> = { export const colorMapConfig: Record<string, string[]> = {
[WorkCardEnum.CASE_COUNT]: ['#ED0303', '#FFA200', '#3370FF', '#D4D4D8'], [WorkCardEnum.CASE_COUNT]: ['#ED0303', '#FFA200', '#3370FF', '#D4D4D8'],
[WorkCardEnum.ASSOCIATE_CASE_COUNT]: ['#00C261', '#3370FF'], [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.TEST_PLAN_COUNT]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[WorkCardEnum.PLAN_LEGACY_BUG]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'], [WorkCardEnum.PLAN_LEGACY_BUG]: ['#9441B1', '#3370FF', '#00C261', '#D4D4D8'],
[WorkCardEnum.BUG_COUNT]: ['#FFA200', '#00C261', '#D4D4D8'], [WorkCardEnum.BUG_COUNT]: ['#FFA200', '#00C261', '#D4D4D8'],
@ -100,13 +102,14 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
], ],
color, color,
grid: { grid: {
top: '36px', top: 36,
left: '10px', left: 0,
right: '10px', right: 0,
bottom: hasRoom ? '54px' : '5px', bottom: hasRoom ? 54 : 5,
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
show: true,
splitLine: false, splitLine: false,
boundaryGap: true, boundaryGap: true,
type: 'category', type: 'category',
@ -128,7 +131,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
{ {
type: 'value', type: 'value',
name: '单位:个', // 设置单位 name: '单位:个', // 设置单位
nameLocation: 'end', position: 'left',
nameTextStyle: { nameTextStyle: {
fontSize: 12, fontSize: 12,
color: '#AEAEB2', // 自定义字体大小和颜色 color: '#AEAEB2', // 自定义字体大小和颜色
@ -143,6 +146,8 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
type: 'dashed', // 水平线线型,可选 'solid'、'dashed'、'dotted' type: 'dashed', // 水平线线型,可选 'solid'、'dashed'、'dotted'
}, },
}, },
min: 0,
max: 1,
}, },
], ],
graphic: { graphic: {
@ -159,7 +164,6 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
}, },
invisible: true, invisible: true,
}, },
colorBy: 'series', colorBy: 'series',
series: [], series: [],
barCategoryGap: '50%', // 控制 X 轴分布居中效果 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 { return {
title: { title: {
show: true, show: true,
@ -272,6 +224,7 @@ export function getPieCharOptions(key: WorkCardEnum) {
tooltip: { tooltip: {
...toolTipConfig, ...toolTipConfig,
position: 'right', position: 'right',
show: !!hasPermission,
}, },
legend: { legend: {
width: '100%', width: '100%',
@ -387,6 +340,20 @@ export function getPieCharOptions(key: WorkCardEnum) {
}, },
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,
},
}; };
} }
@ -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( export function handlePieData(
key: WorkCardEnum, key: WorkCardEnum,
statusPercentList: { hasPermission: boolean,
statusPercentList:
| {
status: string; // 状态 status: string; // 状态
count: number; count: number;
percentValue: string; // 百分比 percentValue: string; // 百分比
}[] }[]
| null = []
) { ) {
const options: Record<string, any> = getPieCharOptions(key); const options: Record<string, any> = getPieCharOptions(key, hasPermission);
options.series.data = statusPercentList.map((item) => ({ const lastStatusPercentList = statusPercentList ?? [];
options.series.data = lastStatusPercentList.map((item) => ({
name: item.status, name: item.status,
value: item.count, value: item.count,
})); }));
@ -527,11 +407,17 @@ export function handlePieData(
// 计算总数和图例格式 // 计算总数和图例格式
const tempObject: Record<string, any> = {}; const tempObject: Record<string, any> = {};
let totalCount = 0; let totalCount = 0;
statusPercentList.forEach((item) => { lastStatusPercentList.forEach((item) => {
tempObject[item.status] = item; tempObject[item.status] = item;
totalCount += item.count; totalCount += item.count;
}); });
// 设置副标题为总数
options.title.subtext = addCommasToNumber(totalCount);
if (!hasPermission) {
options.title.subtext = '-';
}
// 设置图例的格式化函数,显示百分比 // 设置图例的格式化函数,显示百分比
options.legend.formatter = (name: string) => { options.legend.formatter = (name: string) => {
return `{a|${tempObject[name].status}} {b|${addCommasToNumber(tempObject[name].count)}} {c|${ 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; 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,
};
}