feat(工作台): 我关注的-页面
This commit is contained in:
parent
68f5fb6624
commit
b4990750d1
|
@ -1,10 +1,13 @@
|
|||
<template>
|
||||
<a-select class="w-[260px]" :default-value="innerProject" allow-search @change="selectProject">
|
||||
<template #arrow-icon>
|
||||
<a-select :class="props.class || 'w-[260px]'" :default-value="project" allow-search @change="selectProject">
|
||||
<template v-if="!props.useDefaultArrowIcon" #arrow-icon>
|
||||
<icon-caret-down />
|
||||
</template>
|
||||
<template v-if="$slots.prefix" #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
<a-tooltip v-for="item of projectList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
|
||||
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
|
||||
<a-option :value="item.id" :class="item.id === project ? 'arco-select-option-selected' : ''">
|
||||
{{ item.name }}
|
||||
</a-option>
|
||||
</a-tooltip>
|
||||
|
@ -18,32 +21,23 @@
|
|||
import type { ProjectListItem } from '@/models/setting/project';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
class?: string;
|
||||
useDefaultArrowIcon?: boolean;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:project', val: string): void;
|
||||
(e: 'change', val: string): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const projectList = ref<ProjectListItem[]>([]);
|
||||
const innerProject = ref(props.project || appStore.currentProjectId);
|
||||
|
||||
watch(
|
||||
() => props.project,
|
||||
(val) => {
|
||||
innerProject.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => innerProject.value,
|
||||
(val) => {
|
||||
emit('update:project', val);
|
||||
}
|
||||
);
|
||||
const project = defineModel<string>('project', {
|
||||
default: () => '',
|
||||
});
|
||||
|
||||
onBeforeMount(async () => {
|
||||
if (!project.value) {
|
||||
project.value = appStore.currentProjectId;
|
||||
}
|
||||
try {
|
||||
if (appStore.currentOrgId) {
|
||||
const res = await getProjectList(appStore.getCurrentOrgId);
|
||||
|
@ -60,7 +54,6 @@
|
|||
function selectProject(
|
||||
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
emit('update:project', value as string);
|
||||
emit('change', value as string);
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -186,6 +186,7 @@
|
|||
count?: number;
|
||||
notShowInputSearch?: boolean;
|
||||
viewType?: ViewTypeEnum;
|
||||
viewName?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -201,7 +202,7 @@
|
|||
const visible = ref(false);
|
||||
const filterResult = ref<FilterResult>({ searchMode: 'AND', conditions: [] });
|
||||
|
||||
const currentView = ref(''); // 当前视图
|
||||
const currentView = ref(props.viewName || ''); // 当前视图
|
||||
const internalViews = ref<ViewItem[]>([]);
|
||||
const customViews = ref<ViewItem[]>([]);
|
||||
const viewListLoading = ref(false);
|
||||
|
@ -228,11 +229,19 @@
|
|||
value: e.id,
|
||||
}));
|
||||
}
|
||||
|
||||
const isAdvancedSearchMode = ref(false);
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.viewType) {
|
||||
getMemberOptions();
|
||||
await getUserViewList();
|
||||
currentView.value = internalViews.value[0]?.id;
|
||||
if (props.viewName) {
|
||||
currentView.value = props.viewName;
|
||||
isAdvancedSearchMode.value = currentView.value !== internalViews.value[0].id;
|
||||
} else {
|
||||
currentView.value = internalViews.value[0]?.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -325,7 +334,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const isAdvancedSearchMode = ref(false);
|
||||
const getIsValidValue = (item: ConditionsItem) => {
|
||||
if (typeof item.value === 'boolean') return String(item.value).length;
|
||||
if (typeof item.value === 'number') return item.value;
|
||||
|
|
|
@ -183,7 +183,7 @@
|
|||
}
|
||||
const width = props.otherWidth
|
||||
? `calc(100vw - ${menuWidth.value}px - ${props.otherWidth}px)`
|
||||
: `calc(100vw - ${menuWidth.value}px - 58px)`;
|
||||
: `calc(100vw - ${menuWidth.value}px - 48px)`; // 48px 为卡片左右内边距 32+ 页面右侧内边距16
|
||||
return {
|
||||
overflow: 'auto',
|
||||
width: props.autoWidth ? 'auto' : width,
|
||||
|
|
|
@ -106,13 +106,6 @@
|
|||
const isExpanded = ref(true);
|
||||
const isExpandAnimating = ref(false); // 控制动画类
|
||||
|
||||
watch(
|
||||
() => props.notShowFirst,
|
||||
(val) => {
|
||||
innerSize.value = val ? 0 : initialSize;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.size,
|
||||
(val) => {
|
||||
|
@ -159,6 +152,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.notShowFirst,
|
||||
(val) => {
|
||||
if (val) {
|
||||
collapse();
|
||||
} else {
|
||||
expand();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
expand,
|
||||
collapse,
|
||||
|
|
|
@ -57,4 +57,11 @@ export enum WorkCardEnum {
|
|||
BUG_HANDLE_USER = 'BUG_HANDLE_USER', // 缺陷处理人统计
|
||||
}
|
||||
|
||||
export default {};
|
||||
export enum FeatureEnum {
|
||||
TEST_PLAN = 'TEST_PLAN',
|
||||
TEST_CASE = 'TEST_CASE',
|
||||
CASE_REVIEW = 'CASE_REVIEW',
|
||||
API_CASE = 'API_CASE',
|
||||
API_SCENARIO = 'API_SCENARIO',
|
||||
BUG = 'BUG',
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ const TestPlan: AppRouteRecordRaw = {
|
|||
{
|
||||
path: 'followed',
|
||||
name: WorkbenchRouteEnum.WORKBENCH_INDEX_FOLLOW,
|
||||
component: () => import('@/views/workbench/homePage/index.vue'),
|
||||
component: () => import('@/views/workbench/myFollowed/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.workbenchFollowSort',
|
||||
roles: ['*'],
|
||||
|
|
|
@ -913,6 +913,7 @@ export function customFieldToColumns(customFields: CustomFieldItem[]) {
|
|||
width: 200,
|
||||
options: options || JSON.parse(platformOptionJson),
|
||||
type,
|
||||
internal: field.internal,
|
||||
};
|
||||
return column;
|
||||
});
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { deleteDefinitionSchedule, switchDefinitionSchedule } from '@/api/modules/api-test/management';
|
||||
import { getScheduleProApiCaseList, projectDeleteSchedule } from '@/api/modules/taskCenter/project';
|
||||
import { getScheduleProApiCaseList } from '@/api/modules/taskCenter/project';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
|
|
@ -540,8 +540,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
await initFilterColumn();
|
||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
||||
initFilterColumn();
|
||||
if (props.readOnly) {
|
||||
columns = columns.filter(
|
||||
(item) => !['version', 'createTime', 'updateTime', 'operation'].includes(item.dataIndex as string)
|
||||
|
@ -1137,6 +1136,12 @@
|
|||
initFilterColumn();
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
|
||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<keep-alive :include="cacheStore.cacheViews">
|
||||
<apiTable
|
||||
v-if="activeApiTab.id === 'all' && currentTab === 'api'"
|
||||
ref="apiTableRef"
|
||||
class="flex-1 pt-[8px]"
|
||||
:active-module="props.activeModule"
|
||||
:offspring-ids="props.offspringIds"
|
||||
|
@ -577,10 +578,13 @@
|
|||
}
|
||||
});
|
||||
|
||||
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
||||
const isAdvancedSearchMode = computed(() => apiTableRef.value?.isAdvancedSearchMode);
|
||||
defineExpose({
|
||||
openApiTab,
|
||||
addApiTab,
|
||||
openApiTabAndDebugMock,
|
||||
refreshTable,
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
:view-type="ViewTypeEnum.API_CASE"
|
||||
:filter-config-list="filterConfigList"
|
||||
:search-placeholder="t('apiTestManagement.searchPlaceholder')"
|
||||
:view-name="viewName"
|
||||
@keyword-search="loadCaseList()"
|
||||
@adv-search="handleAdvSearch"
|
||||
@refresh="loadCaseList()"
|
||||
|
@ -309,6 +310,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
|
@ -391,6 +393,7 @@
|
|||
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const tableStore = useTableStore();
|
||||
|
@ -803,6 +806,9 @@
|
|||
type: FilterType.DATE_PICKER,
|
||||
},
|
||||
]);
|
||||
|
||||
const viewName = ref('');
|
||||
|
||||
// 高级检索
|
||||
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
||||
resetSelector();
|
||||
|
@ -1171,8 +1177,16 @@
|
|||
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value as RequestParam, false);
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (route.query.view) {
|
||||
setAdvanceFilter({}, route.query.view as string);
|
||||
viewName.value = route.query.view as string;
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
loadCaseList,
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
|
||||
await tableStore.initColumn(TableKeyEnum.API_TEST_MANAGEMENT_CASE, columns, 'drawer', true);
|
||||
|
|
|
@ -156,8 +156,10 @@
|
|||
}
|
||||
});
|
||||
|
||||
const isAdvancedSearchMode = computed(() => caseTableRef.value?.isAdvancedSearchMode);
|
||||
defineExpose({
|
||||
openCaseTab,
|
||||
openCaseTabAndExecute,
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -379,6 +379,10 @@
|
|||
'PROJECT_API_DEFINITION_CASE:READ+UPDATE',
|
||||
]);
|
||||
|
||||
const isAdvancedSearchMode = computed(() =>
|
||||
currentTab.value === 'api' ? apiRef.value?.isAdvancedSearchMode : caseRef.value?.isAdvancedSearchMode
|
||||
);
|
||||
|
||||
/** 向孙组件提供属性 */
|
||||
provide('defaultCaseParams', readonly(defaultCaseParams));
|
||||
provide('protocols', readonly(protocols));
|
||||
|
@ -390,6 +394,7 @@
|
|||
handleApiUpdateFromModuleTree,
|
||||
handleDeleteApiFromModuleTree,
|
||||
changeActiveApiTabToFirst,
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -196,9 +196,8 @@
|
|||
}
|
||||
};
|
||||
|
||||
const isAdvancedSearchMode = ref(false);
|
||||
function handleAdvSearch(isStartAdvance: boolean) {
|
||||
isAdvancedSearchMode.value = isStartAdvance;
|
||||
const isAdvancedSearchMode = computed(() => managementRef.value?.isAdvancedSearchMode);
|
||||
function handleAdvSearch() {
|
||||
moduleTreeRef.value?.setActiveFolder('all');
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
:view-type="ViewTypeEnum.API_SCENARIO"
|
||||
:filter-config-list="filterConfigList"
|
||||
:search-placeholder="t('api_scenario.table.searchPlaceholder')"
|
||||
:view-name="viewName"
|
||||
@keyword-search="loadScenarioList(true)"
|
||||
@adv-search="handleAdvSearch"
|
||||
@refresh="loadScenarioList(true)"
|
||||
|
@ -481,6 +482,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import dayjs from 'dayjs';
|
||||
|
@ -559,6 +561,7 @@
|
|||
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
|
||||
}>();
|
||||
|
||||
const route = useRoute();
|
||||
const appStore = useAppStore();
|
||||
const cacheStore = useCacheStore();
|
||||
const showExportModal = ref(false);
|
||||
|
@ -1071,6 +1074,9 @@
|
|||
type: FilterType.DATE_PICKER,
|
||||
},
|
||||
]);
|
||||
|
||||
const viewName = ref('');
|
||||
|
||||
// 高级检索
|
||||
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
||||
resetSelector();
|
||||
|
@ -1583,6 +1589,7 @@
|
|||
|
||||
defineExpose({
|
||||
loadScenarioList,
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
|
||||
if (!props.readOnly) {
|
||||
|
@ -1617,6 +1624,10 @@
|
|||
|
||||
onBeforeMount(() => {
|
||||
cacheStore.clearCache();
|
||||
if (route.query.view) {
|
||||
setAdvanceFilter({}, route.query.view as string);
|
||||
viewName.value = route.query.view as string;
|
||||
}
|
||||
if (!isActivated.value) {
|
||||
loadScenarioList();
|
||||
cacheStore.setCache(CacheTabTypeEnum.API_SCENARIO_TABLE);
|
||||
|
|
|
@ -520,9 +520,9 @@
|
|||
}
|
||||
|
||||
const scenarioModuleTreeRef = ref<InstanceType<typeof scenarioModuleTree>>();
|
||||
const isAdvancedSearchMode = ref(false);
|
||||
function handleAdvSearch(isStartAdvance: boolean) {
|
||||
isAdvancedSearchMode.value = isStartAdvance;
|
||||
const apiTableRef = ref<InstanceType<typeof ScenarioTable>>();
|
||||
const isAdvancedSearchMode = computed(() => apiTableRef.value?.isAdvancedSearchMode);
|
||||
function handleAdvSearch() {
|
||||
scenarioModuleTreeRef.value?.setActiveFolder('all');
|
||||
}
|
||||
|
||||
|
@ -564,7 +564,6 @@
|
|||
|
||||
const createRef = ref<InstanceType<typeof create>>();
|
||||
const detailRef = ref<InstanceType<typeof detail>>();
|
||||
const apiTableRef = ref<InstanceType<typeof ScenarioTable>>();
|
||||
const saveLoading = ref(false);
|
||||
|
||||
function handleModuleChange() {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
:filter-config-list="filterConfigList"
|
||||
:custom-fields-config-list="searchCustomFields"
|
||||
:search-placeholder="t('caseManagement.featureCase.searchByNameAndId')"
|
||||
:view-name="viewName"
|
||||
@keyword-search="fetchData()"
|
||||
@adv-search="handleAdvSearch"
|
||||
@refresh="searchData()"
|
||||
|
@ -593,6 +594,8 @@
|
|||
type: FilterType.DATE_PICKER,
|
||||
},
|
||||
]);
|
||||
|
||||
const viewName = ref('');
|
||||
// 高级检索
|
||||
const handleAdvSearch = (filter: FilterResult, id: string) => {
|
||||
resetSelector();
|
||||
|
@ -864,6 +867,10 @@
|
|||
onBeforeMount(() => {
|
||||
// 进入页面时检查当前项目轮训状态
|
||||
checkSyncStatus();
|
||||
if (route.query.view) {
|
||||
setAdvanceFilter({}, route.query.view as string);
|
||||
viewName.value = route.query.view as string;
|
||||
}
|
||||
});
|
||||
|
||||
let customColumns: MsTableColumn = [];
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
:search-placeholder="t('caseManagement.featureCase.searchPlaceholder')"
|
||||
:count="modulesCount[props.activeFolder] || 0"
|
||||
:name="moduleNamePath"
|
||||
:view-name="viewName"
|
||||
@keyword-search="fetchData"
|
||||
@adv-search="handleAdvSearch"
|
||||
@refresh="fetchData()"
|
||||
|
@ -1838,6 +1839,7 @@
|
|||
);
|
||||
|
||||
const isActivated = computed(() => cacheStore.cacheViews.includes(RouteEnum.CASE_MANAGEMENT_CASE));
|
||||
const viewName = ref<string>('');
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
showDetailDrawer.value = false;
|
||||
|
@ -1845,9 +1847,16 @@
|
|||
|
||||
onMounted(() => {
|
||||
if (!isActivated.value) {
|
||||
mountedLoad();
|
||||
// 切换菜单默认还是列表;已经在脑图的时候,刷新浏览器,保持脑图状态
|
||||
showType.value = minderStore.getShowType(MinderKeyEnum.FEATURE_CASE_MINDER);
|
||||
if (route.query.showType) {
|
||||
showType.value = route.query.showType as ShowType;
|
||||
}
|
||||
if (route.query.view) {
|
||||
setAdvanceFilter({}, route.query.view as string);
|
||||
viewName.value = route.query.view as string;
|
||||
}
|
||||
mountedLoad();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
:view-type="ViewTypeEnum.CASE_REVIEW"
|
||||
:filter-config-list="filterConfigList"
|
||||
:search-placeholder="t('caseManagement.caseReview.list.searchPlaceholder')"
|
||||
:view-name="viewName"
|
||||
@keyword-search="searchReview()"
|
||||
@adv-search="handleAdvSearch"
|
||||
@refresh="searchReview()"
|
||||
|
@ -160,7 +161,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
@ -224,6 +225,7 @@
|
|||
const appStore = useAppStore();
|
||||
const cacheStore = useCacheStore();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
@ -570,6 +572,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
const viewName = ref<string>('');
|
||||
|
||||
// 高级检索
|
||||
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
||||
resetSelector();
|
||||
|
@ -774,6 +778,10 @@
|
|||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (route.query.view) {
|
||||
setAdvanceFilter({}, route.query.view as string);
|
||||
viewName.value = route.query.view as string;
|
||||
}
|
||||
if (!isActivated.value) {
|
||||
mountedLoad();
|
||||
searchReview();
|
||||
|
@ -789,6 +797,7 @@
|
|||
|
||||
defineExpose({
|
||||
searchReview,
|
||||
isAdvancedSearchMode,
|
||||
});
|
||||
|
||||
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW, columns, 'drawer', true);
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
defineOptions({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
type ShowType = 'all' | 'reviewByMe' | 'createByMe';
|
||||
|
@ -103,9 +104,8 @@
|
|||
});
|
||||
}
|
||||
|
||||
const isAdvancedSearchMode = ref(false);
|
||||
function handleAdvSearch(isStartAdvance: boolean) {
|
||||
isAdvancedSearchMode.value = isStartAdvance;
|
||||
const isAdvancedSearchMode = computed(() => reviewTableRef.value?.isAdvancedSearchMode);
|
||||
function handleAdvSearch() {
|
||||
folderTreeRef.value?.setActiveFolder('all');
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
:view-type="ViewTypeEnum.TEST_PLAN"
|
||||
:filter-config-list="filterConfigList"
|
||||
:search-placeholder="t('common.searchByIDNameTag')"
|
||||
:view-name="viewName"
|
||||
@keyword-search="fetchData()"
|
||||
@adv-search="handleAdvSearch"
|
||||
@refresh="fetchData()"
|
||||
|
@ -1705,12 +1706,18 @@
|
|||
// }
|
||||
|
||||
const isActivated = computed(() => cacheStore.cacheViews.includes(RouteEnum.TEST_PLAN_INDEX));
|
||||
const viewName = ref('');
|
||||
|
||||
onBeforeMount(() => {
|
||||
if (!isActivated.value) {
|
||||
if (route.query.groupId) {
|
||||
showType.value = testPlanTypeEnum.GROUP;
|
||||
keyword.value = route.query.groupId as string;
|
||||
} else if (route.query.showType) {
|
||||
showType.value = route.query.showType as testPlanTypeEnum;
|
||||
}
|
||||
if (route.query.view) {
|
||||
viewName.value = route.query.view as string;
|
||||
}
|
||||
fetchData();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
<template>
|
||||
<MsCard auto-height simple>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="cursor-pointer font-medium text-[var(--color-text-1)]" @click="goApiCase">
|
||||
{{ t('ms.workbench.myFollowed.feature.API_CASE') }}
|
||||
</div>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" :first-column-width="44" no-disable filter-icon-align-left v-on="propsEvent">
|
||||
<template #num="{ record }">
|
||||
<div class="flex items-center">
|
||||
<MsButton type="text" @click="openCase(record.id)">
|
||||
{{ record.num }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #caseLevel="{ record }">
|
||||
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
|
||||
</template>
|
||||
<!-- 用例等级 -->
|
||||
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
|
||||
<caseLevel :case-level="filterContent.value" />
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<apiStatus :status="record.status" />
|
||||
</template>
|
||||
<template #[FilterSlotNameEnum.API_TEST_CASE_API_STATUS]="{ filterContent }">
|
||||
<apiStatus :status="filterContent.value" />
|
||||
</template>
|
||||
<template #createName="{ record }">
|
||||
<a-tooltip :content="`${record.createName}`" position="tr">
|
||||
<div class="one-line-text">{{ record.createName }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
|
||||
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
|
||||
</template>
|
||||
<template #lastReportStatus="{ record }">
|
||||
<ExecutionStatus
|
||||
:module-type="ReportEnum.API_REPORT"
|
||||
:status="record.lastReportStatus"
|
||||
:class="[!record.lastReportId ? '' : 'cursor-pointer']"
|
||||
@click="showResult(record)"
|
||||
/>
|
||||
</template>
|
||||
<template #passRateColumn>
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t('case.passRate') }}
|
||||
<a-tooltip :content="t('case.passRateTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsCard>
|
||||
<!-- 执行结果抽屉 -->
|
||||
<caseAndScenarioReportDrawer v-model:visible="showExecuteResult" :report-id="activeReportId" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
import caseAndScenarioReportDrawer from '@/views/api-test/components/caseAndScenarioReportDrawer.vue';
|
||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||
|
||||
import { getCasePage } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
|
||||
import { ApiCaseDetail } from '@/models/apiTest/management';
|
||||
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import { casePriorityOptions, caseStatusOptions } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openNewPage } = useOpenNewPage();
|
||||
|
||||
const lastReportStatusListOptions = computed(() => {
|
||||
return Object.keys(ReportStatus).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: t(ReportStatus[key].label),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
fixed: 'left',
|
||||
width: 150,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'case.caseName',
|
||||
dataIndex: 'name',
|
||||
showTooltip: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'case.caseLevel',
|
||||
dataIndex: 'priority',
|
||||
slotName: 'caseLevel',
|
||||
filterConfig: {
|
||||
options: casePriorityOptions,
|
||||
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
|
||||
},
|
||||
width: 100,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.apiStatus',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
filterConfig: {
|
||||
options: caseStatusOptions,
|
||||
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_STATUS,
|
||||
},
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'case.lastReportStatus',
|
||||
dataIndex: 'lastReportStatus',
|
||||
slotName: 'lastReportStatus',
|
||||
filterConfig: {
|
||||
options: lastReportStatusListOptions.value,
|
||||
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS,
|
||||
},
|
||||
showInTable: false,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'case.caseEnvironment',
|
||||
dataIndex: 'environmentName',
|
||||
showTooltip: true,
|
||||
showInTable: false,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'case.tableColumnCreateUser',
|
||||
slotName: 'createName',
|
||||
dataIndex: 'createUser',
|
||||
showInTable: true,
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: 'case.tableColumnCreateTime',
|
||||
dataIndex: 'createTime',
|
||||
showInTable: false,
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getCasePage, {
|
||||
columns,
|
||||
scroll: { x: '100%' },
|
||||
showSetting: false,
|
||||
selectable: false,
|
||||
showSelectAll: false,
|
||||
paginationSize: 'mini',
|
||||
});
|
||||
|
||||
const activeReportId = ref('');
|
||||
const showExecuteResult = ref(false);
|
||||
async function showResult(record: ApiCaseDetail) {
|
||||
if (!record.lastReportId) return;
|
||||
activeReportId.value = record.lastReportId;
|
||||
showExecuteResult.value = true;
|
||||
}
|
||||
|
||||
function openCase(id: number) {
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, { cId: id, pId: props.project });
|
||||
}
|
||||
|
||||
function goApiCase() {
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_MANAGEMENT, {
|
||||
tab: 'case',
|
||||
view: 'my_follow',
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
setLoadListParams({
|
||||
projectId: props.project,
|
||||
viewId: 'my_follow',
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,243 @@
|
|||
<template>
|
||||
<MsCard auto-height simple>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="cursor-pointer font-medium text-[var(--color-text-1)]" @click="goBugList">
|
||||
{{ t('ms.workbench.myFollowed.feature.BUG') }}
|
||||
</div>
|
||||
</div>
|
||||
<MsBaseTable class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
|
||||
<!-- ID -->
|
||||
<template #num="{ record }">
|
||||
<a-button type="text" class="px-0 text-[14px] leading-[22px]" @click="handleShowDetail(record.id)">
|
||||
{{ record.num }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template #relationCaseCount="{ record, rowIndex }">
|
||||
<a-button type="text" class="px-0" @click="showDetail(record.id, rowIndex, 'case')">
|
||||
{{ record.relationCaseCount }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template #statusName="{ record }">
|
||||
{{ record.statusName || '-' }}
|
||||
</template>
|
||||
<template #handleUserTitle>
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t('bugManagement.handleMan') }}
|
||||
<a-tooltip :content="t('bugManagement.handleManTips')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableData } from '@arco-design/web-vue';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { getBugList, getCustomFieldHeader, getCustomOptionHeader } from '@/api/modules/bug-management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { customFieldDataToTableData, customFieldToColumns } from '@/utils';
|
||||
|
||||
import { BugEditCustomField, BugOptionItem } from '@/models/bug-management';
|
||||
import { BugManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { makeColumns } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { openNewPage } = useOpenNewPage();
|
||||
|
||||
let columns: MsTableColumn = [
|
||||
{
|
||||
title: 'bugManagement.ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
width: 100,
|
||||
showInTable: true,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'bugManagement.bugName',
|
||||
dataIndex: 'title',
|
||||
width: 250,
|
||||
showTooltip: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'bugManagement.status',
|
||||
dataIndex: 'status',
|
||||
width: 100,
|
||||
showTooltip: false,
|
||||
slotName: 'statusName',
|
||||
filterConfig: {
|
||||
options: [],
|
||||
labelKey: 'text',
|
||||
},
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'bugManagement.creator',
|
||||
dataIndex: 'createUser',
|
||||
slotName: 'createUser',
|
||||
width: 125,
|
||||
showTooltip: true,
|
||||
showDrag: true,
|
||||
filterConfig: {
|
||||
options: [],
|
||||
labelKey: 'text',
|
||||
},
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'bugManagement.createTime',
|
||||
dataIndex: 'createTime',
|
||||
showDrag: true,
|
||||
width: 199,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'bugManagement.updateUser',
|
||||
dataIndex: 'updateUser',
|
||||
width: 125,
|
||||
showTooltip: true,
|
||||
showDrag: true,
|
||||
filterConfig: {
|
||||
options: [],
|
||||
labelKey: 'text',
|
||||
},
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'bugManagement.updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
showDrag: true,
|
||||
width: 199,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
showInTable: true,
|
||||
},
|
||||
];
|
||||
|
||||
const statusOption = ref<BugOptionItem[]>([]);
|
||||
async function initFilterOptions() {
|
||||
const res = await getCustomOptionHeader(appStore.currentProjectId);
|
||||
statusOption.value = res.statusOption;
|
||||
const filterOptionsMaps: Record<string, any> = {
|
||||
status: res.statusOption,
|
||||
createUser: res.userOption,
|
||||
updateUser: res.userOption,
|
||||
};
|
||||
|
||||
columns = makeColumns(filterOptionsMaps, columns);
|
||||
}
|
||||
|
||||
// 自定义字段
|
||||
const customFields = ref<BugEditCustomField[]>([]);
|
||||
// 获取自定义字段
|
||||
const getCustomFieldColumns = async () => {
|
||||
const res = await getCustomFieldHeader(props.project);
|
||||
customFields.value = res;
|
||||
return customFieldToColumns(res);
|
||||
};
|
||||
|
||||
let customColumns: MsTableColumn = [];
|
||||
async function getColumnHeaders() {
|
||||
try {
|
||||
const res = await getCustomFieldColumns();
|
||||
customColumns = res.filter((item) => {
|
||||
// 严重程度
|
||||
if ((item.title === '严重程度' || item.title === 'Bug Degree') && item.internal) {
|
||||
item.showInTable = true;
|
||||
item.slotName = 'severity';
|
||||
item.filterConfig = {
|
||||
options: item.options || [],
|
||||
labelKey: 'text',
|
||||
};
|
||||
}
|
||||
return (item.title === '严重程度' || item.title === 'Bug Degree') && item.internal;
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
await getColumnHeaders();
|
||||
columns.splice(2, 0, ...customColumns);
|
||||
await initFilterOptions();
|
||||
|
||||
const { propsRes, propsEvent, setLoadListParams, loadList } = useTable(
|
||||
getBugList,
|
||||
{
|
||||
columns,
|
||||
selectable: false,
|
||||
noDisable: false,
|
||||
showSetting: false,
|
||||
paginationSize: 'mini',
|
||||
},
|
||||
(record: TableData) => ({
|
||||
...record,
|
||||
createUser: record.createUserName,
|
||||
handleUser: record.handleUserName,
|
||||
updateUser: record.updateUserName,
|
||||
...customFieldDataToTableData(record.customFields, customFields.value),
|
||||
})
|
||||
);
|
||||
|
||||
const detailVisible = ref(false);
|
||||
const activeDetailId = ref<string>('');
|
||||
const activeCaseIndex = ref<number>(0);
|
||||
const activeDetailTab = ref<string>('');
|
||||
|
||||
const showDetail = (id: string, rowIndex: number, tab: string) => {
|
||||
activeDetailId.value = id;
|
||||
activeCaseIndex.value = rowIndex;
|
||||
activeDetailTab.value = tab;
|
||||
detailVisible.value = true;
|
||||
};
|
||||
|
||||
function handleShowDetail(id: number) {
|
||||
openNewPage(BugManagementRouteEnum.BUG_MANAGEMENT_DETAIL, { id, pId: props.project });
|
||||
}
|
||||
|
||||
function goBugList() {
|
||||
openNewPage(BugManagementRouteEnum.BUG_MANAGEMENT_INDEX, {
|
||||
view: 'my_follow',
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
setLoadListParams({
|
||||
projectId: props.project,
|
||||
viewId: 'my_follow',
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,188 @@
|
|||
<template>
|
||||
<MsCard auto-height simple>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="cursor-pointer font-medium text-[var(--color-text-1)]" @click="goCaseReview">
|
||||
{{ t('ms.workbench.myFollowed.feature.CASE_REVIEW') }}
|
||||
</div>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" no-disable filter-icon-align-left v-on="propsEvent">
|
||||
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_STATUS]="{ filterContent }">
|
||||
<a-tag
|
||||
:color="reviewStatusMap[filterContent.value as ReviewStatus].color"
|
||||
:class="[reviewStatusMap[filterContent.value as ReviewStatus].class, 'px-[4px]']"
|
||||
size="small"
|
||||
>
|
||||
{{ t(reviewStatusMap[filterContent.value as ReviewStatus].label) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #passRateColumn>
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t('caseManagement.caseReview.passRate') }}
|
||||
<a-tooltip :content="t('caseManagement.caseReview.passRateTip')" position="right">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<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 #status="{ record }">
|
||||
<MsStatusTag :status="record.status" />
|
||||
</template>
|
||||
<template #reviewPassRule="{ record }">
|
||||
<a-tag
|
||||
:color="record.reviewPassRule === 'SINGLE' ? 'rgb(var(--success-2))' : 'rgb(var(--link-2))'"
|
||||
:class="record.reviewPassRule === 'SINGLE' ? '!text-[rgb(var(--success-6))]' : '!text-[rgb(var(--link-6))]'"
|
||||
>
|
||||
{{
|
||||
record.reviewPassRule === 'SINGLE'
|
||||
? t('caseManagement.caseReview.single')
|
||||
: t('caseManagement.caseReview.multi')
|
||||
}}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #passRate="{ record }">
|
||||
<div class="mr-[8px] w-[100px]">
|
||||
<passRateLine :review-detail="record" height="5px" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ `${record.passRate}%` }}
|
||||
</div>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
import passRateLine from '@/views/case-management/caseReview/components/passRateLine.vue';
|
||||
|
||||
import { getReviewList } from '@/api/modules/case-management/caseReview';
|
||||
import { reviewStatusMap } from '@/config/caseManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
|
||||
import { ReviewStatus } from '@/models/caseManagement/caseReview';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openNewPage } = useOpenNewPage();
|
||||
|
||||
const reviewStatusOptions = computed(() => {
|
||||
return Object.keys(reviewStatusMap).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: t(reviewStatusMap[key as ReviewStatus].label),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
showTooltip: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.name',
|
||||
dataIndex: 'name',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.status',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
filterConfig: {
|
||||
options: reviewStatusOptions.value,
|
||||
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_STATUS,
|
||||
},
|
||||
showDrag: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.passRate',
|
||||
slotName: 'passRate',
|
||||
titleSlotName: 'passRateColumn',
|
||||
showDrag: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.caseCount',
|
||||
dataIndex: 'caseCount',
|
||||
showDrag: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.caseReview.type',
|
||||
slotName: 'reviewPassRule',
|
||||
dataIndex: 'reviewPassRule',
|
||||
showDrag: true,
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'common.createTime',
|
||||
dataIndex: 'createTime',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getReviewList, {
|
||||
columns,
|
||||
showSetting: false,
|
||||
selectable: false,
|
||||
showSelectAll: false,
|
||||
paginationSize: 'mini',
|
||||
});
|
||||
|
||||
function openDetail(id: number) {
|
||||
openNewPage(CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL, { id });
|
||||
}
|
||||
|
||||
function goCaseReview() {
|
||||
openNewPage(CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW, {
|
||||
view: 'my_follow',
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
setLoadListParams({
|
||||
projectId: props.project,
|
||||
viewId: 'my_follow',
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,264 @@
|
|||
<template>
|
||||
<MsCard auto-height simple>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="cursor-pointer font-medium text-[var(--color-text-1)]" @click="goScenario">
|
||||
{{ t('ms.workbench.myFollowed.feature.API_SCENARIO') }}
|
||||
</div>
|
||||
</div>
|
||||
<ms-base-table
|
||||
class="mt-[16px]"
|
||||
v-bind="propsRes"
|
||||
:first-column-width="44"
|
||||
no-disable
|
||||
filter-icon-align-left
|
||||
v-on="propsEvent"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<div class="flex items-center">
|
||||
<MsButton type="text" class="float-left" style="margin-right: 4px" @click="openScenario(record.id)">
|
||||
{{ record.num }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #priority="{ record }">
|
||||
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
|
||||
</template>
|
||||
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
|
||||
<caseLevel :case-level="filterContent.value" />
|
||||
</template>
|
||||
<template #[FilterSlotNameEnum.API_TEST_CASE_API_STATUS]="{ filterContent }">
|
||||
<apiStatus :status="filterContent.value" />
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<apiStatus :status="record.status" />
|
||||
</template>
|
||||
<template #createUserName="{ record }">
|
||||
<a-tooltip :content="`${record.createName}`" position="tl">
|
||||
<div class="one-line-text">{{ characterLimit(record.createUserName) }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<!-- 报告结果筛选 -->
|
||||
<template #[FilterSlotNameEnum.API_TEST_CASE_API_REPORT_STATUS]="{ filterContent }">
|
||||
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
|
||||
</template>
|
||||
<template #lastReportStatus="{ record }">
|
||||
<ExecutionStatus
|
||||
:module-type="ReportEnum.API_SCENARIO_REPORT"
|
||||
:status="record.lastReportStatus ? record.lastReportStatus : 'PENDING'"
|
||||
:script-identifier="record.scriptIdentifier"
|
||||
:class="record.lastReportId ? 'cursor-pointer' : ''"
|
||||
@click="openScenarioReportDrawer(record)"
|
||||
/>
|
||||
</template>
|
||||
<template #stepTotal="{ record }">
|
||||
{{ record.stepTotal }}
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsCard>
|
||||
<!-- 场景报告抽屉 -->
|
||||
<caseAndScenarioReportDrawer
|
||||
v-model:visible="showScenarioReportVisible"
|
||||
is-scenario
|
||||
:report-id="tableRecord?.lastReportId || ''"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||
|
||||
import { getScenarioPage } from '@/api/modules/api-test/scenario';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import { ApiScenarioTableItem } from '@/models/apiTest/scenario';
|
||||
import { ApiScenarioStatus } from '@/enums/apiEnum';
|
||||
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openNewPage } = useOpenNewPage();
|
||||
const tableRecord = ref<ApiScenarioTableItem>();
|
||||
|
||||
const requestApiScenarioStatusOptions = computed(() => {
|
||||
return Object.values(ApiScenarioStatus).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: key,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const statusList = computed(() => {
|
||||
return Object.keys(ReportStatus).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: t(ReportStatus[key].label),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'num',
|
||||
slotName: 'num',
|
||||
sortIndex: 1,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
fixed: 'left',
|
||||
width: 140,
|
||||
showTooltip: false,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.name',
|
||||
dataIndex: 'name',
|
||||
slotName: 'name',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 134,
|
||||
showTooltip: true,
|
||||
columnSelectorDisabled: true,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.level',
|
||||
dataIndex: 'priority',
|
||||
slotName: 'priority',
|
||||
showDrag: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
filterConfig: {
|
||||
options: casePriorityOptions,
|
||||
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
|
||||
},
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.status',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
titleSlotName: 'statusFilter',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
filterConfig: {
|
||||
options: requestApiScenarioStatusOptions.value,
|
||||
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_STATUS,
|
||||
},
|
||||
showDrag: true,
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.runResult',
|
||||
dataIndex: 'lastReportStatus',
|
||||
slotName: 'lastReportStatus',
|
||||
showTooltip: false,
|
||||
showDrag: true,
|
||||
filterConfig: {
|
||||
options: statusList.value,
|
||||
filterSlotName: FilterSlotNameEnum.API_TEST_CASE_API_REPORT_STATUS,
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.scenarioEnv',
|
||||
dataIndex: 'environmentName',
|
||||
showDrag: true,
|
||||
width: 159,
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.createUser',
|
||||
dataIndex: 'createUser',
|
||||
slotName: 'createUserName',
|
||||
showInTable: false,
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiScenario.table.columns.createTime',
|
||||
dataIndex: 'createTime',
|
||||
showInTable: false,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
showDrag: true,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
|
||||
getScenarioPage,
|
||||
{
|
||||
columns,
|
||||
scroll: { x: '100%' },
|
||||
showSetting: false,
|
||||
selectable: false,
|
||||
showSelectAll: false,
|
||||
heightUsed: 282,
|
||||
paginationSize: 'mini',
|
||||
},
|
||||
(item) => ({
|
||||
...item,
|
||||
requestPassRate: item.requestPassRate ? `${item.requestPassRate}%` : '-',
|
||||
createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
})
|
||||
);
|
||||
|
||||
const showScenarioReportVisible = ref(false);
|
||||
function openScenarioReportDrawer(record: ApiScenarioTableItem) {
|
||||
if (record.lastReportId) {
|
||||
tableRecord.value = record;
|
||||
showScenarioReportVisible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
function openScenario(id: number) {
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, { id, pId: props.project });
|
||||
}
|
||||
|
||||
function goScenario() {
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
|
||||
view: 'my_follow',
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
setLoadListParams({
|
||||
projectId: props.project,
|
||||
viewId: 'my_follow',
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,216 @@
|
|||
<template>
|
||||
<MsCard auto-height simple>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="cursor-pointer font-medium text-[var(--color-text-1)]" @click="goTestCase">
|
||||
{{ t('ms.workbench.myFollowed.feature.TEST_CASE') }}
|
||||
</div>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" ref="tableRef" filter-icon-align-left class="mt-[16px]" v-on="propsEvent">
|
||||
<template #num="{ record }">
|
||||
<span type="text" class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]">
|
||||
{{ record.num }}
|
||||
</span>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<div class="one-line-text">{{ record.name }}</div>
|
||||
</template>
|
||||
<template #caseLevel="{ record }">
|
||||
<span class="text-[var(--color-text-2)]">
|
||||
<caseLevel :case-level="record.caseLevel" />
|
||||
</span>
|
||||
</template>
|
||||
<!-- 用例等级 -->
|
||||
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
|
||||
<caseLevel :case-level="filterContent.text" />
|
||||
</template>
|
||||
<!-- 执行结果 -->
|
||||
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
|
||||
<ExecuteStatusTag :execute-result="filterContent.value" />
|
||||
</template>
|
||||
<!-- 评审结果 -->
|
||||
<template #reviewStatus="{ record }">
|
||||
<MsIcon
|
||||
:type="statusIconMap[record.reviewStatus]?.icon || ''"
|
||||
class="mr-1"
|
||||
:class="[statusIconMap[record.reviewStatus].color]"
|
||||
></MsIcon>
|
||||
<span>{{ statusIconMap[record.reviewStatus]?.statusText || '' }} </span>
|
||||
</template>
|
||||
<template #lastExecuteResult="{ record }">
|
||||
<ExecuteStatusTag v-if="record.lastExecuteResult" :execute-result="record.lastExecuteResult" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import { MsTableProps } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import ExecuteStatusTag from '@/components/business/ms-case-associate/executeResult.vue';
|
||||
|
||||
import { getCaseList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
|
||||
import { CaseManagementTable } from '@/models/caseManagement/featureCase';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import {
|
||||
executionResultMap,
|
||||
getCaseLevels,
|
||||
statusIconMap,
|
||||
} from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openNewPage } = useOpenNewPage();
|
||||
|
||||
const executeResultOptions = computed(() => {
|
||||
return Object.keys(executionResultMap).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: executionResultMap[key].statusText,
|
||||
};
|
||||
});
|
||||
});
|
||||
const reviewResultOptions = computed(() => {
|
||||
return Object.keys(statusIconMap).map((key) => {
|
||||
return {
|
||||
value: key,
|
||||
label: statusIconMap[key].statusText,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
'title': 'ID',
|
||||
'dataIndex': 'num',
|
||||
'slotName': 'num',
|
||||
'sortIndex': 1,
|
||||
'fixed': 'left',
|
||||
'width': 150,
|
||||
'showTooltip': true,
|
||||
'columnSelectorDisabled': true,
|
||||
'filter-icon-align-left': true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 180,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnLevel',
|
||||
slotName: 'caseLevel',
|
||||
dataIndex: 'caseLevel',
|
||||
filterConfig: {
|
||||
options: [],
|
||||
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
|
||||
},
|
||||
showInTable: true,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnReviewResult',
|
||||
dataIndex: 'reviewStatus',
|
||||
slotName: 'reviewStatus',
|
||||
filterConfig: {
|
||||
options: reviewResultOptions.value,
|
||||
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT,
|
||||
},
|
||||
showInTable: true,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnExecutionResult',
|
||||
dataIndex: 'lastExecuteResult',
|
||||
slotName: 'lastExecuteResult',
|
||||
filterConfig: {
|
||||
options: executeResultOptions.value,
|
||||
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT,
|
||||
},
|
||||
showInTable: true,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnCreateUser',
|
||||
slotName: 'createUserName',
|
||||
dataIndex: 'createUserName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnCreateTime',
|
||||
slotName: 'createTime',
|
||||
dataIndex: 'createTime',
|
||||
showInTable: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 200,
|
||||
showDrag: true,
|
||||
},
|
||||
];
|
||||
|
||||
const tableProps = ref<Partial<MsTableProps<CaseManagementTable>>>({
|
||||
columns,
|
||||
selectable: false,
|
||||
showSetting: false,
|
||||
paginationSize: 'mini',
|
||||
});
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getCaseList, tableProps.value, (record) => {
|
||||
return {
|
||||
...record,
|
||||
tags: (record.tags || []).map((item: string, i: number) => {
|
||||
return {
|
||||
id: `${record.id}-${i}`,
|
||||
name: item,
|
||||
};
|
||||
}),
|
||||
visible: false,
|
||||
showModuleTree: false,
|
||||
caseLevel: getCaseLevels(record.customFields),
|
||||
};
|
||||
});
|
||||
|
||||
function init() {
|
||||
setLoadListParams({
|
||||
projectId: props.project,
|
||||
viewId: 'my_follow',
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
function goTestCase() {
|
||||
openNewPage(CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, {
|
||||
showType: 'list',
|
||||
view: 'my_follow',
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,300 @@
|
|||
<template>
|
||||
<MsCard auto-height simple>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="cursor-pointer font-medium text-[var(--color-text-1)]" @click="goTestPlan">
|
||||
{{ t('ms.workbench.myFollowed.feature.TEST_PLAN') }}
|
||||
</div>
|
||||
<a-radio-group v-model="showType" type="button" class="file-show-type mr-2" @change="fetchData">
|
||||
<a-radio :value="testPlanTypeEnum.ALL" class="show-type-icon p-[2px]">
|
||||
{{ t('testPlan.testPlanIndex.all') }}
|
||||
</a-radio>
|
||||
<a-radio :value="testPlanTypeEnum.TEST_PLAN" class="show-type-icon p-[2px]">
|
||||
{{ t('testPlan.testPlanIndex.plan') }}
|
||||
</a-radio>
|
||||
<a-radio :value="testPlanTypeEnum.GROUP" class="show-type-icon p-[2px]">
|
||||
{{ t('testPlan.testPlanIndex.testPlanGroup') }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<MsBaseTable
|
||||
v-bind="propsRes"
|
||||
ref="tableRef"
|
||||
class="mt-4"
|
||||
filter-icon-align-left
|
||||
:expanded-keys="expandedKeys"
|
||||
:first-column-width="32"
|
||||
v-on="propsEvent"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<div class="flex items-center">
|
||||
<PlanExpandRow
|
||||
v-model:expanded-keys="expandedKeys"
|
||||
:record="record"
|
||||
@action="openDetail(record.id)"
|
||||
@expand="expandHandler(record)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #[FilterSlotNameEnum.TEST_PLAN_STATUS_FILTER]="{ filterContent }">
|
||||
<MsStatusTag :status="filterContent.value" />
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<MsStatusTag v-if="getStatus(record.id)" :status="getStatus(record.id)" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #createTime="{ record }">
|
||||
<a-tooltip :content="`${dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss')}`" position="tl">
|
||||
<div class="one-line-text">{{ dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss') }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #passRate="{ record }">
|
||||
<div class="mr-[8px] w-[100px]">
|
||||
<StatusProgress :status-detail="defaultCountDetailMap[record.id]" height="5px" :type="record.type" />
|
||||
</div>
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ `${defaultCountDetailMap[record.id]?.passRate ? defaultCountDetailMap[record.id].passRate : '-'}%` }}
|
||||
</div>
|
||||
</template>
|
||||
<template #passRateTitleSlot="{ columnConfig }">
|
||||
<div class="flex items-center text-[var(--color-text-3)]">
|
||||
{{ t(columnConfig.title as string) }}
|
||||
<a-tooltip position="right" :content="t('testPlan.testPlanIndex.passRateTitleTip')">
|
||||
<icon-question-circle
|
||||
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<template #functionalCaseCount="{ record }">
|
||||
<a-popover position="bottom" content-class="p-[16px]" :disabled="getFunctionalCount(record.id) < 1">
|
||||
<div>{{ getFunctionalCount(record.id) }}</div>
|
||||
<template #content>
|
||||
<table class="min-w-[140px] max-w-[176px]">
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div>{{ t('testPlan.testPlanIndex.TotalCases') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ defaultCountDetailMap[record.id]?.caseTotal ?? '0' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.functionalUseCase') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ defaultCountDetailMap[record.id]?.functionalCaseCount ?? '0' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiCase') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ defaultCountDetailMap[record.id]?.apiCaseCount ?? '0' }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="popover-label-td">
|
||||
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiScenarioCase') }}</div>
|
||||
</td>
|
||||
<td class="popover-value-td">
|
||||
{{ defaultCountDetailMap[record.id]?.apiScenarioCount ?? '0' }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</template>
|
||||
</a-popover>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn, MsTableProps } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
import PlanExpandRow from '@/views/test-plan/testPlan/components/planExpandRow.vue';
|
||||
import StatusProgress from '@/views/test-plan/testPlan/components/statusProgress.vue';
|
||||
|
||||
import { getPlanPassRate, getTestPlanList } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
|
||||
import { PassRateCountDetail, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
import { TestPlanRouteEnum } from '@/enums/routeEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||
|
||||
import { planStatusOptions } from '@/views/test-plan/testPlan/config';
|
||||
|
||||
const props = defineProps<{
|
||||
project: string;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { openNewPage } = useOpenNewPage();
|
||||
|
||||
const showType = ref(testPlanTypeEnum.ALL);
|
||||
|
||||
const defaultCountDetailMap = ref<Record<string, PassRateCountDetail>>({});
|
||||
|
||||
function getFunctionalCount(id: string) {
|
||||
return defaultCountDetailMap.value[id]?.caseTotal ?? 0;
|
||||
}
|
||||
|
||||
function getStatus(id: string) {
|
||||
return defaultCountDetailMap.value[id]?.status;
|
||||
}
|
||||
|
||||
async function getStatistics(selectedPlanIds: (string | undefined)[]) {
|
||||
try {
|
||||
const result = await getPlanPassRate(selectedPlanIds);
|
||||
result.forEach((item: PassRateCountDetail) => {
|
||||
defaultCountDetailMap.value[item.id] = item;
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.ID',
|
||||
slotName: 'num',
|
||||
dataIndex: 'num',
|
||||
width: 180,
|
||||
showInTable: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.testPlanName',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 180,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'common.status',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
filterConfig: {
|
||||
options: planStatusOptions,
|
||||
filterSlotName: FilterSlotNameEnum.TEST_PLAN_STATUS_FILTER,
|
||||
},
|
||||
showInTable: true,
|
||||
showDrag: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.passRate',
|
||||
dataIndex: 'passRate',
|
||||
slotName: 'passRate',
|
||||
titleSlotName: 'passRateTitleSlot',
|
||||
showInTable: true,
|
||||
width: 200,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.useCount',
|
||||
slotName: 'functionalCaseCount',
|
||||
dataIndex: 'functionalCaseCount',
|
||||
showInTable: true,
|
||||
width: 150,
|
||||
showDrag: true,
|
||||
},
|
||||
{
|
||||
title: 'testPlan.testPlanIndex.createTime',
|
||||
slotName: 'createTime',
|
||||
dataIndex: 'createTime',
|
||||
showInTable: true,
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 200,
|
||||
showDrag: true,
|
||||
showTooltip: true,
|
||||
},
|
||||
];
|
||||
|
||||
const tableProps = ref<Partial<MsTableProps<TestPlanItem>>>({
|
||||
columns,
|
||||
selectable: false,
|
||||
showSetting: false,
|
||||
paginationSize: 'mini',
|
||||
showSelectorAll: false,
|
||||
});
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getTestPlanList, tableProps.value);
|
||||
|
||||
const planData = computed(() => {
|
||||
return propsRes.value.data;
|
||||
});
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
// 展开折叠
|
||||
function expandHandler(record: TestPlanItem) {
|
||||
if (expandedKeys.value.includes(record.id)) {
|
||||
expandedKeys.value = expandedKeys.value.filter((key) => key !== record.id);
|
||||
} else {
|
||||
expandedKeys.value = [...expandedKeys.value, record.id];
|
||||
if (record.type === 'GROUP' && record.childrenCount) {
|
||||
const testPlanId = record.children.map((item: TestPlanItem) => item.id);
|
||||
getStatistics(testPlanId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试计划详情
|
||||
function openDetail(id: string) {
|
||||
openNewPage(TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL, {
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => planData.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
const selectedPlanIds: (string | undefined)[] = propsRes.value.data.map((e) => e.id) || [];
|
||||
if (selectedPlanIds.length) {
|
||||
getStatistics(selectedPlanIds);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function fetchData() {
|
||||
setLoadListParams({
|
||||
type: showType.value,
|
||||
projectId: props.project,
|
||||
viewId: 'my_follow',
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
function goTestPlan() {
|
||||
openNewPage(TestPlanRouteEnum.TEST_PLAN_INDEX, {
|
||||
showType: showType.value,
|
||||
view: 'my_follow',
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-[16px]">
|
||||
<div class="flex items-center justify-end gap-[12px]">
|
||||
<MsProjectSelect v-model:project="currentProject" class="w-[240px]" use-default-arrow-icon>
|
||||
<template #prefix>
|
||||
{{ t('menu.projectManagementShort') }}
|
||||
</template>
|
||||
</MsProjectSelect>
|
||||
<a-select
|
||||
v-model:model-value="features"
|
||||
:options="featureOptions"
|
||||
:max-tag-count="1"
|
||||
multiple
|
||||
class="w-[240px]"
|
||||
@change="handleFeatureChange"
|
||||
>
|
||||
<template #prefix>
|
||||
{{ t('project.messageManagement.function') }}
|
||||
</template>
|
||||
<template #header>
|
||||
<a-checkbox v-model:model-value="featureAll" class="ml-[8px]" @change="handleFeatureAllChange">
|
||||
{{ t('common.all') }}
|
||||
</a-checkbox>
|
||||
</template>
|
||||
</a-select>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[10px]" @click="handleRefresh">
|
||||
<MsIcon type="icon-icon_reset_outlined" size="14" />
|
||||
</a-button>
|
||||
</div>
|
||||
<testPlanTable v-if="features.includes(FeatureEnum.TEST_PLAN)" :project="currentProject" />
|
||||
<testCaseTable v-if="features.includes(FeatureEnum.TEST_CASE)" :project="currentProject" />
|
||||
<caseReviewTable v-if="features.includes(FeatureEnum.CASE_REVIEW)" :project="currentProject" />
|
||||
<apiCaseTable v-if="features.includes(FeatureEnum.API_CASE)" :project="currentProject" />
|
||||
<scenarioCaseTable v-if="features.includes(FeatureEnum.API_SCENARIO)" :project="currentProject" />
|
||||
<bugTable v-if="features.includes(FeatureEnum.BUG)" :project="currentProject" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsProjectSelect from '@/components/business/ms-project-select/index.vue';
|
||||
import apiCaseTable from './components/apiCaseTable.vue';
|
||||
import bugTable from './components/bugTable.vue';
|
||||
import caseReviewTable from './components/caseReviewTable.vue';
|
||||
import scenarioCaseTable from './components/scenarioCaseTable.vue';
|
||||
import testCaseTable from './components/testCaseTable.vue';
|
||||
import testPlanTable from './components/testPlanTable.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { FeatureEnum } from '@/enums/workbenchEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const currentProject = ref(appStore.currentProjectId);
|
||||
const features = ref<FeatureEnum[]>(Object.values(FeatureEnum));
|
||||
const featureOptions = Object.keys(FeatureEnum).map((key) => ({
|
||||
label: t(`ms.workbench.myFollowed.feature.${key}`),
|
||||
value: key as FeatureEnum,
|
||||
}));
|
||||
const featureAll = ref(true);
|
||||
|
||||
function handleFeatureAllChange(val: boolean | (string | number | boolean)[]) {
|
||||
features.value = val ? featureOptions.map((item) => item.value) : [];
|
||||
}
|
||||
|
||||
function handleFeatureChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||
) {
|
||||
featureAll.value = (val as []).length === featureOptions.length;
|
||||
}
|
||||
|
||||
function handleRefresh() {
|
||||
console.log('refresh');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
'ms.workbench.myFollowed.feature.TEST_PLAN': 'Test Plan',
|
||||
'ms.workbench.myFollowed.feature.TEST_CASE': 'Test Case',
|
||||
'ms.workbench.myFollowed.feature.CASE_REVIEW': 'Case Review',
|
||||
'ms.workbench.myFollowed.feature.API_CASE': 'Interface Case',
|
||||
'ms.workbench.myFollowed.feature.API_SCENARIO': 'Interface Scenario',
|
||||
'ms.workbench.myFollowed.feature.BUG': 'Bug',
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
export default {
|
||||
'ms.workbench.myFollowed.feature.TEST_PLAN': '测试计划',
|
||||
'ms.workbench.myFollowed.feature.TEST_CASE': '测试用例',
|
||||
'ms.workbench.myFollowed.feature.CASE_REVIEW': '用例评审',
|
||||
'ms.workbench.myFollowed.feature.API_CASE': '接口用例',
|
||||
'ms.workbench.myFollowed.feature.API_SCENARIO': '接口场景',
|
||||
'ms.workbench.myFollowed.feature.BUG': '缺陷',
|
||||
};
|
Loading…
Reference in New Issue