feat(工作台): 工作台首页联调卡片&柱状图饼图样式调整

This commit is contained in:
xinxin.wu 2024-11-14 19:36:59 +08:00 committed by Craftsman
parent 622cc8c814
commit 9bd017a800
21 changed files with 542 additions and 164 deletions

View File

@ -17,7 +17,9 @@ import type {
import { import {
EditDashboardLayoutUrl, EditDashboardLayoutUrl,
GetDashboardLayoutUrl, GetDashboardLayoutUrl,
WorkApiCaseCountDetailUrl,
WorkApiChangeListUrl, WorkApiChangeListUrl,
WorkApiCountDetailUrl,
WorkAssociateCaseDetailUrl, WorkAssociateCaseDetailUrl,
WorkbenchApiCaseListUrl, WorkbenchApiCaseListUrl,
WorkbenchBugListUrl, WorkbenchBugListUrl,
@ -26,13 +28,18 @@ import {
WorkbenchScenarioListUrl, WorkbenchScenarioListUrl,
WorkbenchTestPlanListUrl, WorkbenchTestPlanListUrl,
WorkbenchTestPlanStatisticUrl, WorkbenchTestPlanStatisticUrl,
WorkBugByMeCreatedUrl,
WorkBugCountDetailUrl,
WorkBugHandleByMeUrl,
WorkBugHandlerDetailUrl, WorkBugHandlerDetailUrl,
WorkCaseCountDetailUrl, WorkCaseCountDetailUrl,
WorkCaseReviewDetailUrl, WorkCaseReviewDetailUrl,
WorkHandleUserOptionsUrl,
WorkMemberViewDetailUrl, WorkMemberViewDetailUrl,
WorkMyCreatedDetailUrl, WorkMyCreatedDetailUrl,
WorkProOverviewDetailUrl, WorkProOverviewDetailUrl,
WorkReviewListUrl, WorkReviewListUrl,
WorkScenarioCaseCountDetailUrl,
WorkTodoBugListUrl, WorkTodoBugListUrl,
WorkTodoPlanListUrl, WorkTodoPlanListUrl,
WorkTodoReviewListUrl, WorkTodoReviewListUrl,
@ -75,15 +82,15 @@ export function workbenchApiCaseList(data: TableQueryParams) {
// 工作台首页概览 // 工作台首页概览
export function workProOverviewDetail(data: WorkHomePageDetail) { export function workProOverviewDetail(data: WorkHomePageDetail) {
return MSR.post<OverViewOfProject>({ url: WorkProOverviewDetailUrl, data }); return MSR.post<OverViewOfProject>({ url: WorkProOverviewDetailUrl, data }, { ignoreCancelToken: true });
} }
// 我创建的 // 我创建的
export function workMyCreatedDetail(data: WorkHomePageDetail) { export function workMyCreatedDetail(data: WorkHomePageDetail) {
return MSR.post<OverViewOfProject>({ url: WorkMyCreatedDetailUrl, data }); return MSR.post<OverViewOfProject>({ url: WorkMyCreatedDetailUrl, data }, { ignoreCancelToken: true });
} }
// 人员概览 // 人员概览
export function workMemberViewDetail(data: WorkHomePageDetail) { export function workMemberViewDetail(data: WorkHomePageDetail) {
return MSR.post<OverViewOfProject>({ url: WorkMemberViewDetailUrl, data }); return MSR.post<OverViewOfProject>({ url: WorkMemberViewDetailUrl, data }, { ignoreCancelToken: true });
} }
// 获取用户布局 // 获取用户布局
export function getDashboardLayout(orgId: string) { export function getDashboardLayout(orgId: string) {
@ -97,21 +104,21 @@ export function editDashboardLayout(data: SelectedCardItem[], orgId: string) {
// 工作台-首页-用例数 // 工作台-首页-用例数
export function workCaseCountDetail(data: WorkHomePageDetail) { export function workCaseCountDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkCaseCountDetailUrl, data }); return MSR.post<PassRateDataType>({ url: WorkCaseCountDetailUrl, data }, { ignoreCancelToken: true });
} }
// 工作台-首页-关联用例数 // 工作台-首页-关联用例数
export function workAssociateCaseDetail(data: WorkHomePageDetail) { export function workAssociateCaseDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkAssociateCaseDetailUrl, data }); return MSR.post<PassRateDataType>({ url: WorkAssociateCaseDetailUrl, data }, { ignoreCancelToken: true });
} }
// 工作台-首页-用例评审数 // 工作台-首页-用例评审数
export function workCaseReviewDetail(data: WorkHomePageDetail) { export function workCaseReviewDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkCaseReviewDetailUrl, data }); return MSR.post<PassRateDataType>({ url: WorkCaseReviewDetailUrl, data }, { ignoreCancelToken: true });
} }
// 工作台-首页-缺陷处理人 // 工作台-首页-缺陷处理人
export function workBugHandlerDetail(data: WorkHomePageDetail) { export function workBugHandlerDetail(data: WorkHomePageDetail) {
return MSR.post<OverViewOfProject>({ url: WorkBugHandlerDetailUrl, data }); return MSR.post<OverViewOfProject>({ url: WorkBugHandlerDetailUrl, data }, { ignoreCancelToken: true });
} }
// 工作台-首页-接口变更 // 工作台-首页-接口变更
@ -130,6 +137,41 @@ export function workReviewList(data: WorkHomePageDetail) {
); );
} }
// 工作台-首页-缺陷数量
export function workBugCountDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkBugCountDetailUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-我创建的缺陷
export function workBugByMeCreated(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkBugByMeCreatedUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-待我处理的缺陷
export function workBugHandleByMe(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkBugHandleByMeUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-接口数量
export function workApiCountDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkApiCountDetailUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-接口用例数量
export function workApiCaseCountDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkApiCaseCountDetailUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-场景用例数量
export function workScenarioCaseCountDetail(data: WorkHomePageDetail) {
return MSR.post<PassRateDataType>({ url: WorkScenarioCaseCountDetailUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-缺陷处理人列表
export function workHandleUserOptions(projectId: string) {
return MSR.get({ url: WorkHandleUserOptionsUrl, params: projectId }, { ignoreCancelToken: true });
}
// 待办-用例评审列表 // 待办-用例评审列表
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

@ -19,3 +19,10 @@ export const WorkBugHandlerDetailUrl = '/dashboard/bug_handle_user'; // 工作
export const WorkApiChangeListUrl = '/dashboard/api_change'; // 工作台-首页-接口变更 export const WorkApiChangeListUrl = '/dashboard/api_change'; // 工作台-首页-接口变更
export const WorkCaseReviewDetailUrl = '/dashboard/review_case_count'; // 工作台-首页-用例评审数 export const WorkCaseReviewDetailUrl = '/dashboard/review_case_count'; // 工作台-首页-用例评审数
export const WorkReviewListUrl = '/dashboard/reviewing_by_me'; // 工作台-首页-待我评审 export const WorkReviewListUrl = '/dashboard/reviewing_by_me'; // 工作台-首页-待我评审
export const WorkApiCountDetailUrl = '/dashboard/api_count'; // 工作台-首页-接口数量
export const WorkApiCaseCountDetailUrl = '/dashboard/api_case_count'; // 工作台-首页-接口用例数量
export const WorkScenarioCaseCountDetailUrl = '/dashboard/scenario_count'; // 工作台-首页-场景用例数量
export const WorkHandleUserOptionsUrl = '/dashboard/bug_handle_user/list'; // 工作台-首页-缺陷处理人列表
export const WorkBugCountDetailUrl = '/dashboard/bug_count'; // 工作台-首页-缺陷数量
export const WorkBugByMeCreatedUrl = '/dashboard/create_bug_by_me'; // 工作台-首页-我创建的缺陷
export const WorkBugHandleByMeUrl = '/dashboard/handle_bug_by_me'; // 工作台-首页-待我处理的缺陷

View File

@ -5,7 +5,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, ref } from 'vue'; import { nextTick, ref } from 'vue';
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts'; import { BarChart, CustomChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
import { import {
DataZoomComponent, DataZoomComponent,
GraphicComponent, GraphicComponent,
@ -21,6 +21,7 @@
use([ use([
CanvasRenderer, CanvasRenderer,
BarChart, BarChart,
CustomChart,
LineChart, LineChart,
PieChart, PieChart,
RadarChart, RadarChart,

View File

@ -31,7 +31,7 @@ export interface SelectedCardItem {
// 查询入参 // 查询入参
export interface WorkHomePageDetail extends TableQueryParams { export interface WorkHomePageDetail extends TableQueryParams {
dayNumber: number | null; dayNumber: number | string;
startTime: number | null; startTime: number | null;
endTime: number | null; endTime: number | null;
projectIds: string[]; projectIds: string[];
@ -40,9 +40,9 @@ export interface WorkHomePageDetail extends TableQueryParams {
} }
export interface TimeFormParams { export interface TimeFormParams {
dayNumber: number | null; dayNumber: number | string;
startTime: number | null; startTime: number;
endTime: number | null; endTime: number;
} }
export interface OverViewOfProject { export interface OverViewOfProject {

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>
@ -23,25 +24,25 @@
<div class="case-count-item"> <div class="case-count-item">
<div v-for="(ele, index) of executionTimeValue" :key="index" class="case-count-item-content"> <div v-for="(ele, index) of executionTimeValue" :key="index" class="case-count-item-content">
<div class="case-count-item-title">{{ ele.name }}</div> <div class="case-count-item-title">{{ ele.name }}</div>
<div class="case-count-item-number">{{ addCommasToNumber(ele.count) }}</div> <div class="case-count-item-number">{{ hasPermission ? addCommasToNumber(ele.count as number) : '-' }}</div>
</div> </div>
</div> </div>
<div class="case-count-item"> <div class="case-count-item">
<div v-for="(ele, index) of apiCountValue" :key="index" class="case-count-item-content"> <div v-for="(ele, index) of apiCountValue" :key="index" class="case-count-item-content">
<div class="case-count-item-title">{{ ele.name }}</div> <div class="case-count-item-title">{{ ele.name }}</div>
<div class="case-count-item-number">{{ addCommasToNumber(ele.count) }}</div> <div class="case-count-item-number">{{ hasPermission ? addCommasToNumber(ele.count as number) : '-' }}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="case-ratio-wrapper mt-[16px]"> <div class="case-ratio-wrapper mt-[16px]">
<div class="case-ratio-item"> <div class="case-ratio-item">
<RatioPie :data="coverData" :rate-config="coverTitleConfig" /> <RatioPie :has-permission="hasPermission" :data="coverData" :rate-config="coverTitleConfig" />
</div> </div>
<div class="case-ratio-item"> <div class="case-ratio-item">
<RatioPie :data="caseExecuteData" :rate-config="executeTitleConfig" /> <RatioPie :has-permission="hasPermission" :data="caseExecuteData" :rate-config="executeTitleConfig" />
</div> </div>
<div class="case-ratio-item"> <div class="case-ratio-item">
<RatioPie :data="casePassData" :rate-config="casePassTitleConfig" /> <RatioPie :has-permission="hasPermission" :data="casePassData" :rate-config="casePassTitleConfig" />
</div> </div>
</div> </div>
</div> </div>
@ -57,6 +58,7 @@
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import RatioPie from './ratioPie.vue'; import RatioPie from './ratioPie.vue';
import { workApiCaseCountDetail, workScenarioCaseCountDetail } 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 { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
@ -72,30 +74,34 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
const projectId = ref<string>(innerProjectIds.value[0]); const projectId = ref<string>(innerProjectIds.value[0]);
const executionTimeValue = ref<{ name: string; count: number }[]>([ const executionTimeValue = ref<{ name: string; count: number | string }[]>([
{ {
name: '执行次数', name: '执行次数',
count: 100, count: '-',
}, },
]); ]);
const apiCountValue = ref<{ name: string; count: number }[]>([ const apiCountValue = ref<{ name: string; count: number | string }[]>([
{ {
name: name:
props.item.key === WorkCardEnum.API_CASE_COUNT props.item.key === WorkCardEnum.API_CASE_COUNT
? t('workbench.homePage.apiUseCasesNumber') ? t('workbench.homePage.apiUseCasesNumber')
: t('workbench.homePage.scenarioUseCasesNumber'), : t('workbench.homePage.scenarioUseCasesNumber'),
count: 100, count: '-',
}, },
{ {
name: t('workbench.homePage.misstatementCount'), name: t('workbench.homePage.misstatementCount'),
count: 100, count: '-',
}, },
]); ]);
@ -129,6 +135,7 @@
name: t('common.executed'), name: t('common.executed'),
}, },
]); ]);
const casePassData = ref<{ name: string; value: number }[]>([ const casePassData = ref<{ name: string; value: number }[]>([
{ {
value: 0, value: 0,
@ -143,8 +150,11 @@
const coverTitleConfig = computed(() => { const coverTitleConfig = computed(() => {
return { return {
name: t('workbench.homePage.apiCoverage'), name: t('workbench.homePage.apiCoverage'),
count: '80%',
color: ['#EDEDF1', '#00C261'], color: ['#EDEDF1', '#00C261'],
tooltipText:
props.item.key === WorkCardEnum.API_CASE_COUNT
? t('workbench.homePage.apiCaseCountCoverRateTooltip')
: t('workbench.homePage.scenarioCaseCountCoverRateTooltip'),
}; };
}); });
@ -152,13 +162,13 @@
return props.item.key === WorkCardEnum.API_CASE_COUNT return props.item.key === WorkCardEnum.API_CASE_COUNT
? { ? {
name: t('workbench.homePage.caseExecutionRate'), name: t('workbench.homePage.caseExecutionRate'),
count: '80%', color: ['#00C261', '#EDEDF1'],
color: ['#EDEDF1', '#00C261'], tooltipText: t('workbench.homePage.apiCaseCountExecuteRateTooltip'),
} }
: { : {
name: t('workbench.homePage.sceneExecutionRate'), name: t('workbench.homePage.sceneExecutionRate'),
count: '80%',
color: ['#EDEDF1', '#00C261'], color: ['#EDEDF1', '#00C261'],
tooltipText: t('workbench.homePage.scenarioCaseCountExecuteRateTooltip'),
}; };
}); });
@ -166,17 +176,77 @@
return props.item.key === WorkCardEnum.API_CASE_COUNT return props.item.key === WorkCardEnum.API_CASE_COUNT
? { ? {
name: t('workbench.homePage.casePassedRate'), name: t('workbench.homePage.casePassedRate'),
count: '80%',
color: ['#00C261', '#ED0303'], color: ['#00C261', '#ED0303'],
tooltipText: t('workbench.homePage.apiCaseCountPassRateTooltip'),
} }
: { : {
name: t('workbench.homePage.executionRate'), name: t('workbench.homePage.executionRate'),
count: '80%',
color: ['#00C261', '#ED0303'], color: ['#00C261', '#ED0303'],
tooltipText: t('workbench.homePage.scenarioCaseCountPassRateTooltip'),
}; };
}); });
function initApiOrScenarioCount() {} const hasPermission = ref<boolean>(false);
async function initApiOrScenarioCount() {
try {
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: [],
};
let detail;
if (props.item.key === WorkCardEnum.API_CASE_COUNT) {
detail = await workApiCaseCountDetail(params);
} else {
detail = await workScenarioCaseCountDetail(params);
}
hasPermission.value = detail.errorCode !== 109001;
caseExecuteData.value = (detail.statusStatisticsMap?.execRate || []).map((e) => {
return {
...e,
value: e.count,
};
});
casePassData.value = (detail.statusStatisticsMap?.passRate || []).map((e) => {
return {
...e,
value: e.count,
};
});
if (hasPermission.value) {
//
executionTimeValue.value = detail.statusStatisticsMap?.execCount || [];
//
const valueKey = props.item.key === WorkCardEnum.API_CASE_COUNT ? 'apiCaseCount' : 'apiScenarioCount';
apiCountValue.value = detail.statusStatisticsMap?.[valueKey] || [];
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
function changeProject() {
nextTick(() => {
initApiOrScenarioCount();
emit('change');
});
}
onMounted(() => {
initApiOrScenarioCount();
});
watch( watch(
() => innerProjectIds.value, () => innerProjectIds.value,
@ -184,7 +254,6 @@
if (val) { if (val) {
const [newProjectId] = val; const [newProjectId] = val;
projectId.value = newProjectId; projectId.value = newProjectId;
initApiOrScenarioCount();
} }
} }
); );

View File

@ -30,7 +30,7 @@
v-on="propsEvent" v-on="propsEvent"
> >
<template #num="{ record }"> <template #num="{ record }">
<MsButton type="text">{{ record.num || '-' }}</MsButton> <MsButton type="text" @click="openDetail(record)">{{ record.num || '-' }}</MsButton>
</template> </template>
<template v-if="isNoPermission" #empty> <template v-if="isNoPermission" #empty>
<div class="w-full"> <div class="w-full">
@ -61,9 +61,14 @@
import { workApiChangeList } from '@/api/modules/workbench'; import { workApiChangeList } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { ApiDefinitionDetail } from '@/models/apiTest/management';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage'; import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
const { openNewPage } = useOpenNewPage();
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
@ -72,6 +77,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -99,6 +108,7 @@
slotName: 'name', slotName: 'name',
dataIndex: 'name', dataIndex: 'name',
width: 200, width: 200,
showTooltip: true,
}, },
{ {
title: 'apiTestManagement.path', title: 'apiTestManagement.path',
@ -147,6 +157,15 @@
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'), updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
}) })
); );
//
function openDetail(record: ApiDefinitionDetail) {
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, {
dId: record.id,
pId: projectId.value,
});
}
const isNoPermission = ref<boolean>(false); const isNoPermission = ref<boolean>(false);
async function initData() { async function initData() {
try { try {
@ -169,7 +188,10 @@
} }
function changeProject() { function changeProject() {
initData(); nextTick(() => {
initData();
emit('change');
});
} }
onMounted(() => { onMounted(() => {

View File

@ -43,26 +43,31 @@
* @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 TabCard from './tabCard.vue'; import TabCard from './tabCard.vue';
import { workApiCountDetail } 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 { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { handlePieData, handleUpdateTabPie } from '../utils'; import { handlePieData, handleUpdateTabPie } from '../utils';
const { t } = useI18n();
const appStore = useAppStore();
const props = defineProps<{ const props = defineProps<{
item: SelectedCardItem; item: SelectedCardItem;
projectIds: string[]; projectIds: string[];
}>(); }>();
const { t } = useI18n();
const appStore = useAppStore(); const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
@ -79,18 +84,9 @@
}) })
); );
const options = ref({});
// TODO
const detail = ref<PassRateDataType>({
statusStatisticsMap: null,
statusPercentList: null,
errorCode: 109001,
});
const coverValueList = ref<{ value: string | number; label: string; name: string }[]>([]); const coverValueList = ref<{ value: string | number; label: string; name: string }[]>([]);
const passValueList = ref<{ value: string | number; label: string; name: string }[]>([]); const completeValueList = ref<{ value: string | number; label: string; name: string }[]>([]);
const coverOptions = ref<Record<string, any>>({}); const coverOptions = ref<Record<string, any>>({});
const completeOptions = ref<Record<string, any>>({}); const completeOptions = ref<Record<string, any>>({});
const apiCountTabList = computed(() => { const apiCountTabList = computed(() => {
@ -105,7 +101,7 @@
{ {
label: '', label: '',
value: 'pass', value: 'pass',
valueList: passValueList.value, valueList: completeValueList.value,
options: { ...completeOptions.value }, options: { ...completeOptions.value },
tooltip: 'workbench.homePage.apiCountCompleteRateTooltip', tooltip: 'workbench.homePage.apiCountCompleteRateTooltip',
}, },
@ -115,10 +111,10 @@
const apiCountOptions = ref({}); const apiCountOptions = ref({});
const hasPermission = ref<boolean>(false); const hasPermission = ref<boolean>(false);
function initApiCount() { async function initApiCount() {
try { try {
const { startTime, endTime, dayNumber } = timeForm.value; const { startTime, endTime, dayNumber } = timeForm.value;
const params = { const detail = await workApiCountDetail({
current: 1, current: 1,
pageSize: 5, pageSize: 5,
startTime: dayNumber ? null : startTime, startTime: dayNumber ? null : startTime,
@ -127,28 +123,29 @@
projectIds: innerProjectIds.value, projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId, organizationId: appStore.currentOrgId,
handleUsers: [], handleUsers: [],
}; });
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value; const { statusStatisticsMap, statusPercentList, errorCode } = detail;
hasPermission.value = errorCode !== 109001; hasPermission.value = errorCode !== 109001;
apiCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList); apiCountOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
// // TODO
const { options: covOptions, valueList: coverList } = handleUpdateTabPie( // const { options: covOptions, valueList: coverList } = handleUpdateTabPie(
statusStatisticsMap?.cover || [], // statusStatisticsMap?.cover || [],
hasPermission.value, // hasPermission.value,
`${props.item.key}-cover` // `${props.item.key}-cover`
); // );
coverValueList.value = coverList; // coverValueList.value = coverList;
coverOptions.value = covOptions; // coverOptions.value = covOptions;
//
const { options: comOptions, valueList: completedList } = handleUpdateTabPie( const { options: comOptions, valueList: completedList } = handleUpdateTabPie(
statusStatisticsMap?.cover || [], statusStatisticsMap?.completionRate || [],
hasPermission.value, hasPermission.value,
`${props.item.key}-complete` `${props.item.key}-complete`
); );
passValueList.value = completedList; completeValueList.value = completedList;
completeOptions.value = comOptions; completeOptions.value = comOptions;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -157,11 +154,15 @@
} }
function changeProject() { function changeProject() {
initApiCount(); nextTick(() => {
initApiCount();
emit('change');
});
} }
onMounted(() => { onMounted(() => {
initApiCount(); initApiCount();
emit('change');
}); });
watch( watch(

View File

@ -64,6 +64,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -149,7 +153,11 @@
} }
function changeProject() { function changeProject() {
initCaseCount(); emit('change');
nextTick(() => {
initCaseCount();
emit('change');
});
} }
onMounted(() => { onMounted(() => {

View File

@ -61,6 +61,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -127,7 +131,10 @@
} }
function changeProject() { function changeProject() {
initApiCount(); nextTick(() => {
initApiCount();
emit('change');
});
} }
onMounted(() => { onMounted(() => {

View File

@ -46,10 +46,16 @@
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 { workBugByMeCreated, workBugCountDetail, workBugHandleByMe } 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,
WorkHomePageDetail,
} from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum } from '@/enums/workbenchEnum';
import { handlePieData, handleUpdateTabPie } from '../utils'; import { handlePieData, handleUpdateTabPie } from '../utils';
@ -62,6 +68,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -81,25 +91,16 @@
const legacyOptions = ref<Record<string, any>>({}); const legacyOptions = ref<Record<string, any>>({});
// TODO
const detail = ref<PassRateDataType>({
statusStatisticsMap: {
legacy: [
{ name: '遗留率', count: 10 },
{ name: '缺陷总数', count: 2 },
{ name: '遗留缺陷数', count: 1 },
],
},
statusPercentList: [
{ status: 'AAA', count: 1, percentValue: '10%' },
{ status: 'BBB', count: 3, percentValue: '0%' },
{ status: 'CCC', count: 6, percentValue: '0%' },
],
errorCode: 0,
});
const countOptions = ref<Record<string, any>>({}); const countOptions = ref<Record<string, any>>({});
type SelectedBugCountKeys = WorkCardEnum.BUG_COUNT | WorkCardEnum.HANDLE_BUG_BY_ME | WorkCardEnum.CREATE_BUG_BY_ME;
const currentBugCount: (data: WorkHomePageDetail) => Promise<PassRateDataType> = {
[WorkCardEnum.BUG_COUNT]: workBugCountDetail,
[WorkCardEnum.HANDLE_BUG_BY_ME]: workBugHandleByMe,
[WorkCardEnum.CREATE_BUG_BY_ME]: workBugByMeCreated,
}[props.item.key as SelectedBugCountKeys];
const hasPermission = ref<boolean>(false); const hasPermission = ref<boolean>(false);
async function initCount() { async function initCount() {
try { try {
@ -114,13 +115,16 @@
organizationId: appStore.currentOrgId, organizationId: appStore.currentOrgId,
handleUsers: [], handleUsers: [],
}; };
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value;
const detail = await currentBugCount(params);
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
hasPermission.value = errorCode !== 109001; hasPermission.value = errorCode !== 109001;
countOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList); countOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
const { options, valueList } = handleUpdateTabPie( const { options, valueList } = handleUpdateTabPie(
statusStatisticsMap?.legacy || [], statusStatisticsMap?.retentionRate || [],
hasPermission.value, hasPermission.value,
`${props.item.key}-legacy` `${props.item.key}-legacy`
); );
@ -137,7 +141,10 @@
}); });
function changeProject() { function changeProject() {
initCount(); nextTick(() => {
initCount();
emit('change');
});
} }
onMounted(() => { onMounted(() => {
@ -147,19 +154,21 @@
watch( watch(
() => innerProjectIds.value, () => innerProjectIds.value,
(val) => { (val) => {
if (val) { const [newProjectId] = val;
const [newProjectId] = val; projectId.value = newProjectId;
projectId.value = newProjectId; },
} {
immediate: true,
} }
); );
watch( watch(
() => projectId.value, () => projectId.value,
(val) => { (val) => {
if (val) { innerProjectIds.value = [val];
innerProjectIds.value = [val]; },
} {
immediate: true,
} }
); );

View File

@ -45,8 +45,7 @@
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, workHandleUserOptions } from '@/api/modules/workbench';
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 { characterLimit } from '@/utils';
@ -62,6 +61,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -130,21 +133,27 @@
async function getMemberOptions() { async function getMemberOptions() {
const [newProjectId] = innerProjectIds.value; const [newProjectId] = innerProjectIds.value;
const res = await getProjectOptions(newProjectId); const res = await workHandleUserOptions(newProjectId);
memberOptions.value = res.map((e: any) => ({ memberOptions.value = res.map((e: any) => ({
label: e.name, label: e.text,
value: e.id, value: e.value,
})); }));
} }
function changeProject() { function changeProject() {
memberIds.value = []; memberIds.value = [];
getMemberOptions(); getMemberOptions();
getDefectMemberDetail(); nextTick(() => {
getDefectMemberDetail();
emit('change');
});
} }
function changeMember() { function changeMember() {
getDefectMemberDetail(); nextTick(() => {
getDefectMemberDetail();
emit('change');
});
} }
watch( watch(
@ -193,4 +202,14 @@
}); });
</script> </script>
<style scoped></style> <style scoped lang="less">
:deep(.arco-select-view-multiple.arco-select-view-size-medium .arco-select-view-tag) {
margin-top: 1px;
margin-bottom: 1px;
max-width: 80px;
height: auto;
min-height: 24px;
line-height: 22px;
vertical-align: middle;
}
</style>

View File

@ -17,6 +17,7 @@
:has-all-select="true" :has-all-select="true"
:default-all-select="!(props.item.projectIds || []).length" :default-all-select="!(props.item.projectIds || []).length"
:at-least-one="true" :at-least-one="true"
@change="changeProject"
> >
</MsSelect> </MsSelect>
</div> </div>
@ -45,6 +46,7 @@
import { contentTabList } from '@/config/workbench'; import { contentTabList } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { addCommasToNumber } from '@/utils';
import type { import type {
ModuleCardItem, ModuleCardItem,
@ -62,6 +64,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const appStore = useAppStore(); const appStore = useAppStore();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
@ -111,14 +117,41 @@
// data // data
options.value.series = detail.projectCountList.map((item) => { options.value.series = detail.projectCountList.map((item) => {
const countData = item.count.map((e) => {
return {
name: item.name,
value: e !== 0 ? e : undefined,
tooltip: {
show: true,
trigger: 'item',
enterable: true,
formatter(params: any) {
const html = `
<div class="w-[186px] h-[50px] p-[16px] flex items-center justify-between">
<div class=" flex items-center">
<div class="mb-[2px] mr-[8px] h-[8px] w-[8px] rounded-sm bg-[${params.color}]" style="background:${
params.color
}"></div>
<div class="one-line-text max-w-[100px]"" style="color:#959598">${params.name}</div>
</div>
<div class="text-[#323233] font-medium">${addCommasToNumber(params.value)}</div>
</div>
`;
return html;
},
},
};
});
return { return {
name: item.name, name: item.name,
type: 'bar', type: 'bar',
barWidth: 12, barWidth: 12,
legendHoverLink: true,
large: true,
itemStyle: { itemStyle: {
borderRadius: [2, 2, 0, 0], // borderRadius: [2, 2, 0, 0], //
}, },
data: item.count, data: countData,
}; };
}); });
} }
@ -149,6 +182,10 @@
} }
} }
function changeProject() {
emit('change');
}
onMounted(() => { onMounted(() => {
initOverViewDetail(); initOverViewDetail();
}); });

View File

@ -27,6 +27,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>
@ -64,6 +65,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -138,8 +143,19 @@
} }
function changeProject() { function changeProject() {
memberIds.value = [];
getMemberOptions(); getMemberOptions();
initOverViewMemberDetail(); nextTick(() => {
initOverViewMemberDetail();
emit('change');
});
}
function changeMember() {
nextTick(() => {
initOverViewMemberDetail();
emit('change');
});
} }
watch( watch(

View File

@ -1,6 +1,16 @@
<template> <template>
<div class="rate-content"> <div class="rate-content relative">
<MsChart :options="options" width="200px" /> <div class="relative flex h-full w-full items-center justify-center">
<a-tooltip
v-if="props.rateConfig.tooltipText"
:mouse-enter-delay="500"
:content="t(props.rateConfig.tooltipText || '')"
position="bottom"
>
<div class="tooltip-rate-tooltip"></div>
</a-tooltip>
<MsChart :options="options" width="146px" />
</div>
</div> </div>
</template> </template>
@ -10,18 +20,22 @@
import MsChart from '@/components/pure/chart/index.vue'; import MsChart from '@/components/pure/chart/index.vue';
import { toolTipConfig } from '@/config/testPlan'; import { toolTipConfig } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
data: { name: string; value: number }[]; data: { name: string; value: number }[];
hasPermission: boolean;
rateConfig: { rateConfig: {
name: string; name: string;
count: string;
color: string[]; color: string[];
tooltipText: string;
}; };
}>(); }>();
const commonOptionConfig = ref({ const options = ref<Record<string, any>>({
title: { title: {
show: true, show: true,
text: '', text: '',
@ -47,6 +61,7 @@
}, },
tooltip: { tooltip: {
...toolTipConfig, ...toolTipConfig,
show: !!props.hasPermission,
}, },
legend: { legend: {
orient: 'vertical', orient: 'vertical',
@ -103,26 +118,54 @@
}, },
data: [], data: [],
}, },
graphic: {
type: 'text',
left: 'center',
bottom: 10,
style: {
text: t('workbench.homePage.notHasResPermission'),
fontSize: 14,
fill: '#959598',
backgroundColor: '#F9F9FE',
padding: [6, 16, 6, 16],
borderRadius: 4,
},
invisible: false,
},
}); });
const options = ref({});
function initOptions() { function initOptions() {
const { name, count, color } = props.rateConfig; const { name, color } = props.rateConfig;
options.value = {
...commonOptionConfig.value, if (props.hasPermission) {
title: { options.value.series.data = props.data.slice(1);
...commonOptionConfig.value.title,
text: name, options.value.legend.formatter = (seriousName: string) => {
subtext: count, const item = props.data.find((e) => e.name === seriousName);
}, return `{a|${seriousName}} {b|${addCommasToNumber(item?.value || 0)}}`;
series: { };
...commonOptionConfig.value.series,
data: [...props.data], options.value.title.subtext = `${props.data[0].value ?? 0}%`;
color, } else {
}, options.value.series.data = [];
}; options.value.title.subtext = `-%`;
}
options.value.graphic.invisible = !!props.hasPermission;
options.value.tooltip.show = !!props.hasPermission;
options.value.title.text = name;
options.value.series.color = color;
} }
watch(
() => props.data,
(val) => {
if (val) {
initOptions();
}
}
);
onMounted(() => { onMounted(() => {
initOptions(); initOptions();
}); });
@ -130,6 +173,14 @@
<style scoped lang="less"> <style scoped lang="less">
.rate-content { .rate-content {
height: 152px; height: 158px;
}
.tooltip-rate-tooltip {
position: absolute;
top: 10px;
z-index: 9;
width: 78px;
height: 78px;
border-radius: 50%;
} }
</style> </style>

View File

@ -57,6 +57,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -120,7 +124,10 @@
} }
function changeProject() { function changeProject() {
getRelatedCaseCount(); nextTick(() => {
getRelatedCaseCount();
emit('change');
});
} }
onMounted(() => { onMounted(() => {

View File

@ -57,12 +57,16 @@
import { handlePieData, handleUpdateTabPie } from '../utils'; import { handlePieData, handleUpdateTabPie } from '../utils';
const { t } = useI18n();
const appStore = useAppStore();
const props = defineProps<{ const props = defineProps<{
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const { t } = useI18n(); const emit = defineEmits<{
const appStore = useAppStore(); (e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
@ -190,12 +194,16 @@
passOptions.value = passedOptions; passOptions.value = passedOptions;
completeOptions.value = comOptions; completeOptions.value = comOptions;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console
console.log(error); console.log(error);
} }
} }
function changeProject() { function changeProject() {
initTestPlanCount(); nextTick(() => {
initTestPlanCount();
emit('change');
});
} }
onMounted(() => { onMounted(() => {

View File

@ -27,6 +27,13 @@
class="mt-[16px]" class="mt-[16px]"
v-on="propsEvent" v-on="propsEvent"
> >
<template #num="{ record }">
<a-tooltip :content="`${record.num}`">
<a-button type="text" class="px-0 !text-[14px] !leading-[22px]" @click="openDetail(record.id)">
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
</a-button>
</a-tooltip>
</template>
<template #passRateColumn> <template #passRateColumn>
<div class="flex items-center text-[var(--color-text-3)]"> <div class="flex items-center text-[var(--color-text-3)]">
{{ t('caseManagement.caseReview.passRate') }} {{ t('caseManagement.caseReview.passRate') }}
@ -58,6 +65,11 @@
}} }}
</a-tag> </a-tag>
</template> </template>
<template #createUserName="{ record }">
<a-tooltip :content="`${record.createUserName}`" position="tl">
<div class="one-line-text">{{ record.createUserName }}</div>
</a-tooltip>
</template>
<template v-if="isNoPermission" #empty> <template v-if="isNoPermission" #empty>
<div class="w-full"> <div class="w-full">
<slot name="empty"> <slot name="empty">
@ -86,9 +98,13 @@
import { workReviewList } from '@/api/modules/workbench'; import { workReviewList } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
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 { CaseManagementRouteEnum } from '@/enums/routeEnum';
const { openNewPage } = useOpenNewPage();
const appStore = useAppStore(); const appStore = useAppStore();
@ -98,6 +114,10 @@
item: SelectedCardItem; item: SelectedCardItem;
}>(); }>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', { const innerProjectIds = defineModel<string[]>('projectIds', {
required: true, required: true,
}); });
@ -122,9 +142,10 @@
}, },
{ {
title: 'caseManagement.caseReview.reviewName', title: 'caseManagement.caseReview.reviewName',
slotName: 'reviewName', slotName: 'name',
dataIndex: 'reviewName', dataIndex: 'name',
width: 200, width: 200,
showTooltip: true,
}, },
{ {
title: 'caseManagement.caseReview.passRate', title: 'caseManagement.caseReview.passRate',
@ -139,6 +160,12 @@
showDrag: true, showDrag: true,
width: 100, width: 100,
}, },
{
title: 'common.creator',
slotName: 'createUserName',
dataIndex: 'createUser',
width: 200,
},
]; ];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(workReviewList, { const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(workReviewList, {
@ -169,8 +196,15 @@
} }
} }
function openDetail(id: number) {
openNewPage(CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL, { id });
}
function changeProject() { function changeProject() {
initData(); nextTick(() => {
initData();
emit('change');
});
} }
onMounted(() => { onMounted(() => {

View File

@ -56,57 +56,77 @@
v-if="[WorkCardEnum.CREATE_BY_ME, WorkCardEnum.PROJECT_VIEW].includes(item.key)" v-if="[WorkCardEnum.CREATE_BY_ME, WorkCardEnum.PROJECT_VIEW].includes(item.key)"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
@change="changeHandler"
/> />
<OverviewMember <OverviewMember
v-else-if="item.key === WorkCardEnum.PROJECT_MEMBER_VIEW" v-else-if="item.key === WorkCardEnum.PROJECT_MEMBER_VIEW"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
v-model:handleUsers="item.handleUsers" v-model:handleUsers="item.handleUsers"
:item="item" :item="item"
@change="changeHandler"
/>
<CaseCount
v-else-if="item.key === WorkCardEnum.CASE_COUNT"
v-model:projectIds="item.projectIds"
:item="item"
@change="changeHandler"
/> />
<CaseCount v-else-if="item.key === WorkCardEnum.CASE_COUNT" v-model:projectIds="item.projectIds" :item="item" />
<RelatedCaseCount <RelatedCaseCount
v-else-if="item.key === WorkCardEnum.ASSOCIATE_CASE_COUNT" v-else-if="item.key === WorkCardEnum.ASSOCIATE_CASE_COUNT"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
@change="changeHandler"
/> />
<CaseReviewedCount <CaseReviewedCount
v-else-if="item.key === WorkCardEnum.REVIEW_CASE_COUNT" v-else-if="item.key === WorkCardEnum.REVIEW_CASE_COUNT"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
@change="changeHandler"
/> />
<WaitReviewList <WaitReviewList
v-else-if="item.key === WorkCardEnum.REVIEWING_BY_ME" v-else-if="item.key === WorkCardEnum.REVIEWING_BY_ME"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
@change="changeHandler"
/> />
<ApiAndScenarioCase <ApiAndScenarioCase
v-else-if="[WorkCardEnum.API_CASE_COUNT, WorkCardEnum.SCENARIO_COUNT].includes(item.key)" v-else-if="[WorkCardEnum.API_CASE_COUNT, WorkCardEnum.SCENARIO_COUNT].includes(item.key)"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:type="item.key" :type="item.key"
:item="item" :item="item"
@change="changeHandler"
/> />
<ApiChangeList <ApiChangeList
v-else-if="item.key === WorkCardEnum.API_CHANGE" v-else-if="item.key === WorkCardEnum.API_CHANGE"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
@change="changeHandler"
/> />
<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" v-model:handleUsers="item.handleUsers"
:item="item" :item="item"
@change="changeHandler"
/> />
<DefectCount <DefectCount
v-else-if="countOfBug.includes(item.key)" v-else-if="countOfBug.includes(item.key)"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
:type="item.key" :type="item.key"
@change="changeHandler"
/>
<ApiCount
v-else-if="item.key === WorkCardEnum.API_COUNT"
v-model:projectIds="item.projectIds"
:item="item"
@change="changeHandler"
/> />
<ApiCount v-else-if="item.key === WorkCardEnum.API_COUNT" v-model:projectIds="item.projectIds" :item="item" />
<TestPlanCount <TestPlanCount
v-else-if="item.key === WorkCardEnum.TEST_PLAN_COUNT" v-else-if="item.key === WorkCardEnum.TEST_PLAN_COUNT"
v-model:projectIds="item.projectIds" v-model:projectIds="item.projectIds"
:item="item" :item="item"
@change="changeHandler"
/> />
</div> </div>
</div> </div>
@ -142,13 +162,13 @@
import DefectMemberBar from '@/views/workbench/homePage/components/defectMemberBar.vue'; import DefectMemberBar from '@/views/workbench/homePage/components/defectMemberBar.vue';
import OverviewMember from '@/views/workbench/homePage/components/overviewMember.vue'; import OverviewMember from '@/views/workbench/homePage/components/overviewMember.vue';
import { getDashboardLayout } from '@/api/modules/workbench'; import { editDashboardLayout, getDashboardLayout } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useUserStore } from '@/store'; import { useUserStore } from '@/store';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { getLocalStorage, setLocalStorage } from '@/utils/local-storage'; import { getLocalStorage, setLocalStorage } from '@/utils/local-storage';
import { SelectedCardItem } from '@/models/workbench/homePage'; import { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum } from '@/enums/workbenchEnum';
const userStore = useUserStore(); const userStore = useUserStore();
@ -159,12 +179,12 @@
const rangeTime = ref<number[]>([]); const rangeTime = ref<number[]>([]);
const tempRange = ref<(Date | string | number)[]>(['00:00:00', '00:00:00']); const tempRange = ref<(Date | string | number)[]>(['00:00:00', '00:00:00']);
const initTime = { const initTime: TimeFormParams = {
dayNumber: 3, dayNumber: 3,
startTime: 0, startTime: 0,
endTime: 0, endTime: 0,
}; };
const timeForm = ref({ ...initTime }); const timeForm = ref<TimeFormParams>({ ...initTime });
function resetTime() { function resetTime() {
timeForm.value = { ...initTime }; timeForm.value = { ...initTime };
@ -173,13 +193,16 @@
// //
function handleChangeTime(value: string | number | boolean, ev: Event) { function handleChangeTime(value: string | number | boolean, ev: Event) {
resetTime(); if (value) {
timeForm.value.dayNumber = value as number; resetTime();
setLocalStorage(`WORK_TIME_${userStore.id}`, JSON.stringify(timeForm.value)); timeForm.value.dayNumber = value as number;
setLocalStorage(`WORK_TIME_${userStore.id}`, JSON.stringify(timeForm.value));
}
} }
// //
function handleTimeSelect(value: (Date | string | number | undefined)[]) { function handleTimeSelect(value: (Date | string | number | undefined)[]) {
if (value) { if (value) {
timeForm.value.dayNumber = '';
timeForm.value.startTime = 0; timeForm.value.startTime = 0;
timeForm.value.endTime = 0; timeForm.value.endTime = 0;
const start = (value as number[])[0]; const start = (value as number[])[0];
@ -228,6 +251,15 @@
initDefaultList(); initDefaultList();
} }
async function changeHandler() {
try {
await editDashboardLayout(defaultWorkList.value, appStore.currentOrgId);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
onMounted(() => { onMounted(() => {
initDefaultList(); initDefaultList();
const defaultTime = getLocalStorage(`WORK_TIME_${userStore.id}`); const defaultTime = getLocalStorage(`WORK_TIME_${userStore.id}`);
@ -236,11 +268,25 @@
} else { } else {
timeForm.value = JSON.parse(defaultTime); timeForm.value = JSON.parse(defaultTime);
const { startTime, endTime } = timeForm.value; const { startTime, endTime } = timeForm.value;
rangeTime.value = [startTime, endTime]; if (startTime && endTime) {
rangeTime.value = [startTime, endTime];
}
} }
}); });
provide('timeForm', timeForm); const time = ref({ ...timeForm.value });
watch(
() => timeForm.value,
(val) => {
if (val.dayNumber || (val.endTime && val.startTime)) {
time.value = { ...val };
}
},
{ deep: true }
);
provide('timeForm', time);
</script> </script>
<style scoped lang="less"> <style scoped lang="less">

View File

@ -112,4 +112,5 @@ export default {
'workbench.homePage.executeRate': 'Execution Rate', 'workbench.homePage.executeRate': 'Execution Rate',
'workbench.homePage.completeRate': 'Completion Rate', 'workbench.homePage.completeRate': 'Completion Rate',
'workbench.homePage.legacyRate': 'Legacy Rate', 'workbench.homePage.legacyRate': 'Legacy Rate',
'workbench.homePage.unit': 'Unit: Number',
}; };

View File

@ -98,4 +98,5 @@ export default {
'workbench.homePage.executeRate': '执行率', 'workbench.homePage.executeRate': '执行率',
'workbench.homePage.completeRate': '完成率', 'workbench.homePage.completeRate': '完成率',
'workbench.homePage.legacyRate': '遗留率', 'workbench.homePage.legacyRate': '遗留率',
'workbench.homePage.unit': '单位:个',
}; };

View File

@ -10,9 +10,13 @@ import { WorkCardEnum } from '@/enums/workbenchEnum';
const { t } = useI18n(); const { t } = useI18n();
// 通用颜色配置 // 通用颜色配置
export const commonColorConfig = [ export const commonColorConfig = [
'#783887',
'#FFC14E',
'#2DFCEF',
'#811FA3', '#811FA3',
'#00D1FF',
'#FFA53D',
'#00C261', '#00C261',
'#3370FF',
'#AA4FBF', '#AA4FBF',
'#FFA1FF', '#FFA1FF',
'#EE50A3', '#EE50A3',
@ -23,15 +27,17 @@ export const commonColorConfig = [
'#62D256', '#62D256',
'#14E1C6', '#14E1C6',
'#50CEFB', '#50CEFB',
'#3370FF',
'#2B5FD9', '#2B5FD9',
'#76F0FF',
'#935AF6', '#935AF6',
'#DC9BFF', '#DC9BFF',
'#FFC75E',
'#D34400', '#D34400',
'#F4D0BF', '#F4D0BF',
'#F4D0BF', '#FBD3E8',
'#D9F457',
'#0089D1', '#0089D1',
'#0089D1',
'#62D256',
'#87F578', '#87F578',
]; ];
@ -63,21 +69,9 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
}, },
displayMode: 'single', displayMode: 'single',
enterable: true, enterable: true,
// TODO 单例模式 axisPointer: {
// formatter(params: any) { type: 'shadow',
// const html = ` },
// <div class="w-[186px] h-[50px] p-[16px] flex items-center justify-between">
// <div class=" flex items-center">
// <div class="mb-[2px] mr-[8px] h-[8px] w-[8px] rounded-sm bg-[${params.color}]" style="background:${
// params.color
// }"></div>
// <div style="color:#959598">${params.name}</div>
// </div>
// <div class="text-[#323233] font-medium">${addCommasToNumber(params.value)}</div>
// </div>
// `;
// return html;
// },
formatter(params: any) { formatter(params: any) {
const html = ` const html = `
<div class="w-[186px] ms-scroll-bar max-h-[206px] overflow-y-auto p-[16px] gap-[8px] flex flex-col"> <div class="w-[186px] ms-scroll-bar max-h-[206px] overflow-y-auto p-[16px] gap-[8px] flex flex-col">
@ -129,8 +123,8 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
}, },
yAxis: [ yAxis: [
{ {
type: 'value', type: 'log',
name: '单位:个', // 设置单位 name: t('workbench.homePage.unit'), // 设置单位
position: 'left', position: 'left',
nameTextStyle: { nameTextStyle: {
fontSize: 12, fontSize: 12,
@ -146,8 +140,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
type: 'dashed', // 水平线线型,可选 'solid'、'dashed'、'dotted' type: 'dashed', // 水平线线型,可选 'solid'、'dashed'、'dotted'
}, },
}, },
min: 0, min: 1,
max: 1,
}, },
], ],
graphic: { graphic: {
@ -166,7 +159,6 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[]): Record<s
}, },
colorBy: 'series', colorBy: 'series',
series: [], series: [],
barCategoryGap: '50%', // 控制 X 轴分布居中效果
legend: { legend: {
width: '60%', width: '60%',
show: true, show: true,