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 {
EditDashboardLayoutUrl,
GetDashboardLayoutUrl,
WorkApiCaseCountDetailUrl,
WorkApiChangeListUrl,
WorkApiCountDetailUrl,
WorkAssociateCaseDetailUrl,
WorkbenchApiCaseListUrl,
WorkbenchBugListUrl,
@ -26,13 +28,18 @@ import {
WorkbenchScenarioListUrl,
WorkbenchTestPlanListUrl,
WorkbenchTestPlanStatisticUrl,
WorkBugByMeCreatedUrl,
WorkBugCountDetailUrl,
WorkBugHandleByMeUrl,
WorkBugHandlerDetailUrl,
WorkCaseCountDetailUrl,
WorkCaseReviewDetailUrl,
WorkHandleUserOptionsUrl,
WorkMemberViewDetailUrl,
WorkMyCreatedDetailUrl,
WorkProOverviewDetailUrl,
WorkReviewListUrl,
WorkScenarioCaseCountDetailUrl,
WorkTodoBugListUrl,
WorkTodoPlanListUrl,
WorkTodoReviewListUrl,
@ -75,15 +82,15 @@ export function workbenchApiCaseList(data: TableQueryParams) {
// 工作台首页概览
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) {
return MSR.post<OverViewOfProject>({ url: WorkMyCreatedDetailUrl, data });
return MSR.post<OverViewOfProject>({ url: WorkMyCreatedDetailUrl, data }, { ignoreCancelToken: true });
}
// 人员概览
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) {
@ -97,21 +104,21 @@ export function editDashboardLayout(data: SelectedCardItem[], orgId: string) {
// 工作台-首页-用例数
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) {
return MSR.post<PassRateDataType>({ url: WorkAssociateCaseDetailUrl, data });
return MSR.post<PassRateDataType>({ url: WorkAssociateCaseDetailUrl, data }, { ignoreCancelToken: true });
}
// 工作台-首页-用例评审数
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) {
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) {
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 WorkCaseReviewDetailUrl = '/dashboard/review_case_count'; // 工作台-首页-用例评审数
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>
import { nextTick, ref } from 'vue';
import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
import { BarChart, CustomChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
import {
DataZoomComponent,
GraphicComponent,
@ -21,6 +21,7 @@
use([
CanvasRenderer,
BarChart,
CustomChart,
LineChart,
PieChart,
RadarChart,

View File

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

View File

@ -14,6 +14,7 @@
:search-keys="['name']"
class="!w-[240px]"
:prefix="t('workbench.homePage.project')"
@change="changeProject"
>
</MsSelect>
</div>
@ -23,25 +24,25 @@
<div class="case-count-item">
<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-number">{{ addCommasToNumber(ele.count) }}</div>
<div class="case-count-item-number">{{ hasPermission ? addCommasToNumber(ele.count as number) : '-' }}</div>
</div>
</div>
<div class="case-count-item">
<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-number">{{ addCommasToNumber(ele.count) }}</div>
<div class="case-count-item-number">{{ hasPermission ? addCommasToNumber(ele.count as number) : '-' }}</div>
</div>
</div>
</div>
<div class="case-ratio-wrapper mt-[16px]">
<div class="case-ratio-item">
<RatioPie :data="coverData" :rate-config="coverTitleConfig" />
<RatioPie :has-permission="hasPermission" :data="coverData" :rate-config="coverTitleConfig" />
</div>
<div class="case-ratio-item">
<RatioPie :data="caseExecuteData" :rate-config="executeTitleConfig" />
<RatioPie :has-permission="hasPermission" :data="caseExecuteData" :rate-config="executeTitleConfig" />
</div>
<div class="case-ratio-item">
<RatioPie :data="casePassData" :rate-config="casePassTitleConfig" />
<RatioPie :has-permission="hasPermission" :data="casePassData" :rate-config="casePassTitleConfig" />
</div>
</div>
</div>
@ -57,6 +58,7 @@
import MsSelect from '@/components/business/ms-select';
import RatioPie from './ratioPie.vue';
import { workApiCaseCountDetail, workScenarioCaseCountDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { addCommasToNumber } from '@/utils';
@ -72,30 +74,34 @@
item: SelectedCardItem;
}>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', {
required: true,
});
const projectId = ref<string>(innerProjectIds.value[0]);
const executionTimeValue = ref<{ name: string; count: number }[]>([
const executionTimeValue = ref<{ name: string; count: number | string }[]>([
{
name: '执行次数',
count: 100,
count: '-',
},
]);
const apiCountValue = ref<{ name: string; count: number }[]>([
const apiCountValue = ref<{ name: string; count: number | string }[]>([
{
name:
props.item.key === WorkCardEnum.API_CASE_COUNT
? t('workbench.homePage.apiUseCasesNumber')
: t('workbench.homePage.scenarioUseCasesNumber'),
count: 100,
count: '-',
},
{
name: t('workbench.homePage.misstatementCount'),
count: 100,
count: '-',
},
]);
@ -129,6 +135,7 @@
name: t('common.executed'),
},
]);
const casePassData = ref<{ name: string; value: number }[]>([
{
value: 0,
@ -143,8 +150,11 @@
const coverTitleConfig = computed(() => {
return {
name: t('workbench.homePage.apiCoverage'),
count: '80%',
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
? {
name: t('workbench.homePage.caseExecutionRate'),
count: '80%',
color: ['#EDEDF1', '#00C261'],
color: ['#00C261', '#EDEDF1'],
tooltipText: t('workbench.homePage.apiCaseCountExecuteRateTooltip'),
}
: {
name: t('workbench.homePage.sceneExecutionRate'),
count: '80%',
color: ['#EDEDF1', '#00C261'],
tooltipText: t('workbench.homePage.scenarioCaseCountExecuteRateTooltip'),
};
});
@ -166,17 +176,77 @@
return props.item.key === WorkCardEnum.API_CASE_COUNT
? {
name: t('workbench.homePage.casePassedRate'),
count: '80%',
color: ['#00C261', '#ED0303'],
tooltipText: t('workbench.homePage.apiCaseCountPassRateTooltip'),
}
: {
name: t('workbench.homePage.executionRate'),
count: '80%',
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(
() => innerProjectIds.value,
@ -184,7 +254,6 @@
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
initApiOrScenarioCount();
}
}
);

View File

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

View File

@ -43,26 +43,31 @@
* @desc 接口数量
*/
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsChart from '@/components/pure/chart/index.vue';
import MsSelect from '@/components/business/ms-select';
import PassRatePie from './passRatePie.vue';
import TabCard from './tabCard.vue';
import { workApiCountDetail } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { handlePieData, handleUpdateTabPie } from '../utils';
const { t } = useI18n();
const appStore = useAppStore();
const props = defineProps<{
item: SelectedCardItem;
projectIds: string[];
}>();
const { t } = useI18n();
const appStore = useAppStore();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', {
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 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 completeOptions = ref<Record<string, any>>({});
const apiCountTabList = computed(() => {
@ -105,7 +101,7 @@
{
label: '',
value: 'pass',
valueList: passValueList.value,
valueList: completeValueList.value,
options: { ...completeOptions.value },
tooltip: 'workbench.homePage.apiCountCompleteRateTooltip',
},
@ -115,10 +111,10 @@
const apiCountOptions = ref({});
const hasPermission = ref<boolean>(false);
function initApiCount() {
async function initApiCount() {
try {
const { startTime, endTime, dayNumber } = timeForm.value;
const params = {
const detail = await workApiCountDetail({
current: 1,
pageSize: 5,
startTime: dayNumber ? null : startTime,
@ -127,28 +123,29 @@
projectIds: innerProjectIds.value,
organizationId: appStore.currentOrgId,
handleUsers: [],
};
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value;
});
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
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;
// TODO
// const { options: covOptions, valueList: coverList } = handleUpdateTabPie(
// statusStatisticsMap?.cover || [],
// hasPermission.value,
// `${props.item.key}-cover`
// );
// coverValueList.value = coverList;
// coverOptions.value = covOptions;
//
const { options: comOptions, valueList: completedList } = handleUpdateTabPie(
statusStatisticsMap?.cover || [],
statusStatisticsMap?.completionRate || [],
hasPermission.value,
`${props.item.key}-complete`
);
passValueList.value = completedList;
completeValueList.value = completedList;
completeOptions.value = comOptions;
} catch (error) {
// eslint-disable-next-line no-console
@ -157,11 +154,15 @@
}
function changeProject() {
initApiCount();
nextTick(() => {
initApiCount();
emit('change');
});
}
onMounted(() => {
initApiCount();
emit('change');
});
watch(

View File

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

View File

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

View File

@ -46,10 +46,16 @@
import MsSelect from '@/components/business/ms-select';
import PassRatePie from './passRatePie.vue';
import { workBugByMeCreated, workBugCountDetail, workBugHandleByMe } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import type { PassRateDataType, SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import type {
PassRateDataType,
SelectedCardItem,
TimeFormParams,
WorkHomePageDetail,
} from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum';
import { handlePieData, handleUpdateTabPie } from '../utils';
@ -62,6 +68,10 @@
item: SelectedCardItem;
}>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', {
required: true,
});
@ -81,25 +91,16 @@
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>>({});
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);
async function initCount() {
try {
@ -114,13 +115,16 @@
organizationId: appStore.currentOrgId,
handleUsers: [],
};
const { statusStatisticsMap, statusPercentList, errorCode } = detail.value;
const detail = await currentBugCount(params);
const { statusStatisticsMap, statusPercentList, errorCode } = detail;
hasPermission.value = errorCode !== 109001;
countOptions.value = handlePieData(props.item.key, hasPermission.value, statusPercentList);
const { options, valueList } = handleUpdateTabPie(
statusStatisticsMap?.legacy || [],
statusStatisticsMap?.retentionRate || [],
hasPermission.value,
`${props.item.key}-legacy`
);
@ -137,7 +141,10 @@
});
function changeProject() {
initCount();
nextTick(() => {
initCount();
emit('change');
});
}
onMounted(() => {
@ -147,19 +154,21 @@
watch(
() => innerProjectIds.value,
(val) => {
if (val) {
const [newProjectId] = val;
projectId.value = newProjectId;
}
const [newProjectId] = val;
projectId.value = newProjectId;
},
{
immediate: true,
}
);
watch(
() => projectId.value,
(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 MsSelect from '@/components/business/ms-select';
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
import { workBugHandlerDetail } from '@/api/modules/workbench';
import { workBugHandlerDetail, workHandleUserOptions } from '@/api/modules/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { characterLimit } from '@/utils';
@ -62,6 +61,10 @@
item: SelectedCardItem;
}>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', {
required: true,
});
@ -130,21 +133,27 @@
async function getMemberOptions() {
const [newProjectId] = innerProjectIds.value;
const res = await getProjectOptions(newProjectId);
const res = await workHandleUserOptions(newProjectId);
memberOptions.value = res.map((e: any) => ({
label: e.name,
value: e.id,
label: e.text,
value: e.value,
}));
}
function changeProject() {
memberIds.value = [];
getMemberOptions();
getDefectMemberDetail();
nextTick(() => {
getDefectMemberDetail();
emit('change');
});
}
function changeMember() {
getDefectMemberDetail();
nextTick(() => {
getDefectMemberDetail();
emit('change');
});
}
watch(
@ -193,4 +202,14 @@
});
</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"
:default-all-select="!(props.item.projectIds || []).length"
:at-least-one="true"
@change="changeProject"
>
</MsSelect>
</div>
@ -45,6 +46,7 @@
import { contentTabList } from '@/config/workbench';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { addCommasToNumber } from '@/utils';
import type {
ModuleCardItem,
@ -62,6 +64,10 @@
item: SelectedCardItem;
}>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const appStore = useAppStore();
const innerProjectIds = defineModel<string[]>('projectIds', {
@ -111,14 +117,41 @@
// data
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 {
name: item.name,
type: 'bar',
barWidth: 12,
legendHoverLink: true,
large: true,
itemStyle: {
borderRadius: [2, 2, 0, 0], //
},
data: item.count,
data: countData,
};
});
}
@ -149,6 +182,10 @@
}
}
function changeProject() {
emit('change');
}
onMounted(() => {
initOverViewDetail();
});

View File

@ -27,6 +27,7 @@
:multiple="true"
:has-all-select="true"
:default-all-select="true"
@change="changeMember"
>
</MsSelect>
</div>
@ -64,6 +65,10 @@
item: SelectedCardItem;
}>();
const emit = defineEmits<{
(e: 'change'): void;
}>();
const innerProjectIds = defineModel<string[]>('projectIds', {
required: true,
});
@ -138,8 +143,19 @@
}
function changeProject() {
memberIds.value = [];
getMemberOptions();
initOverViewMemberDetail();
nextTick(() => {
initOverViewMemberDetail();
emit('change');
});
}
function changeMember() {
nextTick(() => {
initOverViewMemberDetail();
emit('change');
});
}
watch(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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