feat(工作台): 工作台测试计划概览联调
This commit is contained in:
parent
95eedaa556
commit
fdf4368320
|
@ -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(
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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%);
|
||||||
|
|
|
@ -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';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue