feat(工作台): 工作台测试计划概览联调

This commit is contained in:
xinxin.wu 2024-12-05 19:27:59 +08:00 committed by Craftsman
parent 95eedaa556
commit fdf4368320
5 changed files with 135 additions and 63 deletions

View File

@ -37,7 +37,7 @@
</div> </div>
<!-- 概览图 --> <!-- 概览图 -->
<div> <div>
<MsChart ref="charRef" height="280px" :options="options" /> <MsChart ref="chartRef" height="280px" :options="options" />
</div> </div>
</div> </div>
</div> </div>
@ -67,7 +67,7 @@
} from '@/models/workbench/homePage'; } from '@/models/workbench/homePage';
import { WorkCardEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum } from '@/enums/workbenchEnum';
import { getColorScheme, getSeriesData } from '../utils'; import { createCustomTooltip, getColorScheme, getSeriesData } from '../utils';
const { t } = useI18n(); const { t } = useI18n();
@ -176,6 +176,7 @@
}); });
} }
} }
const chartRef = ref<InstanceType<typeof MsChart>>();
async function handleRefreshKeyChange() { async function handleRefreshKeyChange() {
await nextTick(() => { await nextTick(() => {
@ -187,8 +188,15 @@
}, 0); }, 0);
} }
onMounted(() => { onMounted(async () => {
initOverViewDetail(); await initOverViewDetail();
setTimeout(() => {
const chartDom = chartRef.value?.chartRef;
if (chartDom && chartDom.chart) {
createCustomTooltip(chartDom);
}
}, 0);
}); });
watch( watch(

View File

@ -37,7 +37,7 @@
</div> </div>
<!-- 概览图 --> <!-- 概览图 -->
<div class="mt-[16px]"> <div class="mt-[16px]">
<MsChart height="300px" :options="options" /> <MsChart ref="chartRef" height="300px" :options="options" />
</div> </div>
</div> </div>
</div> </div>
@ -60,7 +60,7 @@
import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage'; import type { SelectedCardItem, TimeFormParams } from '@/models/workbench/homePage';
import { getColorScheme, getSeriesData } from '../utils'; import { createCustomTooltip, getColorScheme, getSeriesData } from '../utils';
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); const appStore = useAppStore();
@ -132,6 +132,7 @@
value: e.id, value: e.id,
})); }));
} }
const chartRef = ref<InstanceType<typeof MsChart>>();
async function handleProjectChange(isRefreshKey: boolean = false, setAll = false) { async function handleProjectChange(isRefreshKey: boolean = false, setAll = false) {
await nextTick(); await nextTick();
@ -146,7 +147,14 @@
} }
} }
await nextTick(); await nextTick();
initOverViewMemberDetail(); await initOverViewMemberDetail();
setTimeout(() => {
const chartDom = chartRef.value?.chartRef;
if (chartDom && chartDom.chart) {
createCustomTooltip(chartDom);
}
}, 0);
} }
async function changeProject() { async function changeProject() {

View File

@ -6,36 +6,41 @@
<a-tooltip :content="t(props.item.label)" position="tl" :mouse-enter-delay="300"> <a-tooltip :content="t(props.item.label)" position="tl" :mouse-enter-delay="300">
<div class="title one-line-text"> {{ t(props.item.label) }} </div> <div class="title one-line-text"> {{ t(props.item.label) }} </div>
</a-tooltip> </a-tooltip>
<div> <div class="cascader-wrapper-self relative flex justify-end">
<!-- TODO 待处理 -->
<MsCascader <MsCascader
v-model:model-value="innerPlanId" v-model:model-value="selectValue"
mode="native" mode="native"
:default-value="innerPlanId" allow-search
:allow-clear="false"
:default-value="defaultValue"
:options="projectOptions" :options="projectOptions"
:prefix="t('workbench.homePage.plan')" :prefix="t('workbench.homePage.plan')"
:placeholder="t('workbench.homePage.planOfPleaseSelect')" :placeholder="t('workbench.homePage.planOfPleaseSelect')"
:virtual-list-props="{ height: 200 }" :virtual-list-props="{ height: 200 }"
option-size="small" option-size="small"
class="test-plan-panel filter-item w-[240px]" class="test-plan-panel w-[240px]"
label-path-mode label-path-mode
:panel-width="100" path-mode
popup-container=".cascader-wrapper-self"
:search-option-only-label="true"
:load-more="loadMore" :load-more="loadMore"
@change="changeHandler" @change="changeHandler"
> >
<template v-if="labelPath" #label> <template #label>
<a-tooltip :content="labelPath"> <a-tooltip :content="labelPath" :mouse-enter-delay="300">
<div class="one-line-text inline-flex w-full items-center justify-between pr-[8px]" title=""> <div class="one-line-text max-w-[200px] items-center justify-between pr-[8px]">
{{ labelPath }} {{ labelPath }}
</div> </div>
</a-tooltip> </a-tooltip>
</template> </template>
<template #option="{ data }"> <template #option="{ data }">
<a-tooltip :content="t(data.label)" :mouse-enter-delay="300"> <div :class="`flex ${data.isLeaf ? ' w-[130px]' : 'w-[120px]'} items-center`" title="">
<div class="one-line-text w-[120px]" title=""> <a-tooltip :mouse-enter-delay="300" :content="t(data.label)">
<div :class="`one-line-text ${data.isLeaf ? 'max-w-[85%]' : ''}`" title="">
{{ t(data.label) }} {{ t(data.label) }}
</div> </div>
</a-tooltip> </a-tooltip>
</div>
</template> </template>
</MsCascader> </MsCascader>
</div> </div>
@ -81,7 +86,7 @@
</div> </div>
</div> </div>
<div> <div>
<MsChart ref="charRef" height="280px" :options="options" /> <MsChart ref="chartRef" height="280px" :options="options" />
</div> </div>
</div> </div>
</div> </div>
@ -110,7 +115,7 @@
import { TestPlanStatusEnum } from '@/enums/testPlanEnum'; import { TestPlanStatusEnum } from '@/enums/testPlanEnum';
import { WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum'; import { WorkOverviewEnum, WorkOverviewIconEnum } from '@/enums/workbenchEnum';
import { getSeriesData } from '../utils'; import { createCustomTooltip, getSeriesData } from '../utils';
const { t } = useI18n(); const { t } = useI18n();
@ -152,6 +157,7 @@
const options = ref<Record<string, any>>({}); const options = ref<Record<string, any>>({});
const hasPermission = ref<boolean>(false); const hasPermission = ref<boolean>(false);
const chartRef = ref<InstanceType<typeof MsChart>>();
const contentTabList: ModuleCardItem[] = [ const contentTabList: ModuleCardItem[] = [
{ {
@ -205,7 +211,10 @@
}, },
]; ];
const selectValue = ref<string[]>([]);
const detail = ref(); const detail = ref();
const execOptions = ref<Record<string, any>>({ const execOptions = ref<Record<string, any>>({
...commonRatePieOptions, ...commonRatePieOptions,
title: { title: {
@ -241,7 +250,6 @@
const totalCount = detail.value?.caseCountMap?.totalCount ?? 0; const totalCount = detail.value?.caseCountMap?.totalCount ?? 0;
const executeCount = detail.value?.caseCountMap?.executeCount ?? 0; const executeCount = detail.value?.caseCountMap?.executeCount ?? 0;
const executeData = [ const executeData = [
{ {
name: t('common.executed'), name: t('common.executed'),
@ -312,32 +320,36 @@
} }
}); });
const labelPath = ref<string>(''); const labelPath = ref<string>();
const projectOptions = ref<{ value: string; label: string }[]>([]); const projectOptions = ref<{ value: string; label: string }[]>([]);
const childrenData = ref<Record<string, CascaderOption[]>>({});
function getLabelPath(id: string) { function getLabelPath(id: string) {
const modules = findNodePathByKey(projectOptions.value, id, undefined, 'value'); const [newProjectId] = innerProjectIds.value;
const projectName = projectOptions.value.find((e) => e.value === newProjectId)?.label;
const treeList = childrenData.value[newProjectId];
const modules = findNodePathByKey(treeList, id, undefined, 'value');
if (modules) { if (modules) {
const moduleName = (modules || [])?.treePath.map((item: any) => item.label); const moduleName = (modules || [])?.treePath.map((item: any) => item.label);
if (moduleName.length === 1) { if (moduleName.length === 1) {
return moduleName[0]; return `${projectName}/${moduleName[0]}`;
} }
return `${moduleName.join(' / ')}`; return `${projectName}/${moduleName.join(' / ')}`;
} }
} }
async function changeHandler(value: string) { async function changeHandler(value: string) {
innerPlanId.value = value; innerPlanId.value = value[value.length - 1];
innerProjectIds.value = [value[0]];
await nextTick();
labelPath.value = getLabelPath(innerPlanId.value); labelPath.value = getLabelPath(innerPlanId.value);
initOverViewDetail(); initOverViewDetail();
emit('change'); emit('change');
} }
onMounted(() => {
initOverViewDetail();
});
async function loadMore(option: CascaderOption, done: (children?: CascaderOption[]) => void) { async function loadMore(option: CascaderOption, done: (children?: CascaderOption[]) => void) {
try { try {
let testPlanOptionsNode = await getWorkTestPlanListUrl(option.value as string); let testPlanOptionsNode = await getWorkTestPlanListUrl(option.value as string);
@ -350,36 +362,38 @@
}); });
done(testPlanOptionsNode); done(testPlanOptionsNode);
projectOptions.value = projectOptions.value.map((item) => { childrenData.value[option.value as string] = testPlanOptionsNode;
return { labelPath.value = getLabelPath(innerPlanId.value);
...item,
children: testPlanOptionsNode,
};
});
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
} }
} }
let loadingProjectId: string | null = null; async function refreshHandler(newProjectId: string) {
function refreshHandler(newProjectId: string) {
const cascaderOption = projectOptions.value.find((e) => e.value === newProjectId); const cascaderOption = projectOptions.value.find((e) => e.value === newProjectId);
if (cascaderOption) { if (cascaderOption) {
loadMore(cascaderOption, (children) => { loadMore(cascaderOption, (_children) => {
childrenData.value[cascaderOption.value as string] = _children?.length ? _children : [];
projectOptions.value = projectOptions.value.map((item) => { projectOptions.value = projectOptions.value.map((item) => {
return { return {
...item, ...item,
children: cascaderOption.value === item.value ? children : [], children: childrenData.value[item.value]?.length ? childrenData.value[item.value] : null,
isLeaf: false,
}; };
}); });
labelPath.value = getLabelPath(innerPlanId.value);
}); });
} }
initOverViewDetail();
await initOverViewDetail();
setTimeout(() => {
const chartDom = chartRef.value?.chartRef;
if (chartDom && chartDom.chart) {
createCustomTooltip(chartDom);
}
}, 0);
} }
async function handleRefreshKeyChange() { async function handleRefreshKeyChange() {
@ -392,21 +406,17 @@
labelPath.value = getLabelPath(innerPlanId.value); labelPath.value = getLabelPath(innerPlanId.value);
} }
watch( const defaultValue = computed(() => {
() => props.item.projectIds, const [newProjectId] = innerProjectIds.value;
async (val) => { return [newProjectId, innerPlanId.value];
if (val) { });
const [newProjectId] = val;
projectOptions.value = appStore.projectList.map((e) => ({ value: e.id, label: e.name }));
if (loadingProjectId !== newProjectId) { onMounted(() => {
loadingProjectId = newProjectId; projectOptions.value = appStore.projectList.map((e) => ({ value: e.id, label: e.name }));
const [newProjectId] = props.item.projectIds;
selectValue.value = [newProjectId, props.item.planId];
refreshHandler(newProjectId); refreshHandler(newProjectId);
} });
}
},
{ immediate: true }
);
watch( watch(
() => timeForm.value, () => timeForm.value,
@ -457,6 +467,11 @@
@apply flex flex-col justify-center; @apply flex flex-col justify-center;
} }
} }
.cascader-wrapper-self {
:deep(.arco-trigger-position-bl) {
transform: translateX(-8%);
}
}
</style> </style>
<style lang="less"> <style lang="less">

View File

@ -61,7 +61,7 @@
} }
.popover-trigger-content { .popover-trigger-content {
padding: 8px 16px; padding: 8px 16px;
width: 120px; width: 140px;
border-radius: var(--border-radius-small); border-radius: var(--border-radius-small);
background-color: var(--color-text-fff); background-color: var(--color-text-fff);
box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%); box-shadow: 0 4px 10px -1px rgb(100 100 102 / 15%);

View File

@ -9,6 +9,8 @@ import type { ModuleCardItem, OverViewOfProject } from '@/models/workbench/homeP
import { RouteEnum } from '@/enums/routeEnum'; import { RouteEnum } from '@/enums/routeEnum';
import { WorkCardEnum, WorkNavValueEnum } from '@/enums/workbenchEnum'; import { WorkCardEnum, WorkNavValueEnum } from '@/enums/workbenchEnum';
import VCharts from 'vue-echarts';
const { t } = useI18n(); const { t } = useI18n();
// TODO 通用颜色配置注: 目前柱状图只用到了7种色阶其他色阶暂时保留 // TODO 通用颜色配置注: 目前柱状图只用到了7种色阶其他色阶暂时保留
const commonColorConfig: Record<number, string[]> = { const commonColorConfig: Record<number, string[]> = {
@ -152,6 +154,7 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPla
boundaryGap: true, boundaryGap: true,
type: 'category', type: 'category',
data: [], data: [],
triggerEvent: true,
axisLabel: { axisLabel: {
show: true, show: true,
color: '#646466', color: '#646466',
@ -162,13 +165,16 @@ export function getCommonBarOptions(hasRoom: boolean, color: string[], isTestPla
showMaxLabel: true, showMaxLabel: true,
// TOTO 等待优化 // TOTO 等待优化
interval: 0, interval: 0,
triggerEvent: true,
}, },
axisPointer: { axisPointer: {
type: 'shadow', type: 'shadow',
}, },
axisTick: { axisTick: {
show: true,
alignWithLabel: true, alignWithLabel: true,
lineStyle: {
color: 'transparent',
},
}, },
axisLine: { axisLine: {
lineStyle: { lineStyle: {
@ -722,3 +728,38 @@ export function getSeriesData(
return options; return options;
} }
export function createCustomTooltip(chartDom: InstanceType<typeof VCharts>) {
if (chartDom && chartDom.chart) {
const customTooltip = document.createElement('div');
customTooltip.style.position = 'absolute';
customTooltip.style.maxWidth = '300px';
customTooltip.style.padding = '5px';
customTooltip.style.background = 'rgba(0, 0, 0, 0.75)';
customTooltip.style.color = 'var(--color-text-fff)';
customTooltip.style.borderRadius = '4px';
customTooltip.style.display = 'none';
document.body.appendChild(customTooltip);
// 针对x轴 监听鼠标悬浮事件
chartDom.chart.on('mouseover', 'xAxis', (params) => {
const event = params.event?.event as unknown as MouseEvent;
if (params.componentType === 'xAxis') {
const { clientX, clientY } = event;
customTooltip.textContent = `${params.value}`;
customTooltip.style.display = 'block';
customTooltip.style.left = `${clientX - 20}px`;
customTooltip.style.top = `${clientY + 10}px`;
}
});
// 针对x轴 监听鼠标离开事件
chartDom.chart.on('mouseout', 'xAxis', () => {
customTooltip.style.display = 'none';
});
}
}