feat(工作台): 我关注的-页面
This commit is contained in:
parent
68f5fb6624
commit
b4990750d1
|
@ -1,10 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<a-select class="w-[260px]" :default-value="innerProject" allow-search @change="selectProject">
|
<a-select :class="props.class || 'w-[260px]'" :default-value="project" allow-search @change="selectProject">
|
||||||
<template #arrow-icon>
|
<template v-if="!props.useDefaultArrowIcon" #arrow-icon>
|
||||||
<icon-caret-down />
|
<icon-caret-down />
|
||||||
</template>
|
</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-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 }}
|
{{ item.name }}
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
@ -18,32 +21,23 @@
|
||||||
import type { ProjectListItem } from '@/models/setting/project';
|
import type { ProjectListItem } from '@/models/setting/project';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
project: string;
|
class?: string;
|
||||||
|
useDefaultArrowIcon?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:project', val: string): void;
|
|
||||||
(e: 'change', val: string): void;
|
(e: 'change', val: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const projectList = ref<ProjectListItem[]>([]);
|
const projectList = ref<ProjectListItem[]>([]);
|
||||||
const innerProject = ref(props.project || appStore.currentProjectId);
|
const project = defineModel<string>('project', {
|
||||||
|
default: () => '',
|
||||||
watch(
|
});
|
||||||
() => props.project,
|
|
||||||
(val) => {
|
|
||||||
innerProject.value = val;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => innerProject.value,
|
|
||||||
(val) => {
|
|
||||||
emit('update:project', val);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
|
if (!project.value) {
|
||||||
|
project.value = appStore.currentProjectId;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (appStore.currentOrgId) {
|
if (appStore.currentOrgId) {
|
||||||
const res = await getProjectList(appStore.getCurrentOrgId);
|
const res = await getProjectList(appStore.getCurrentOrgId);
|
||||||
|
@ -60,7 +54,6 @@
|
||||||
function selectProject(
|
function selectProject(
|
||||||
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
|
||||||
) {
|
) {
|
||||||
emit('update:project', value as string);
|
|
||||||
emit('change', value as string);
|
emit('change', value as string);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -186,6 +186,7 @@
|
||||||
count?: number;
|
count?: number;
|
||||||
notShowInputSearch?: boolean;
|
notShowInputSearch?: boolean;
|
||||||
viewType?: ViewTypeEnum;
|
viewType?: ViewTypeEnum;
|
||||||
|
viewName?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -201,7 +202,7 @@
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
const filterResult = ref<FilterResult>({ searchMode: 'AND', conditions: [] });
|
const filterResult = ref<FilterResult>({ searchMode: 'AND', conditions: [] });
|
||||||
|
|
||||||
const currentView = ref(''); // 当前视图
|
const currentView = ref(props.viewName || ''); // 当前视图
|
||||||
const internalViews = ref<ViewItem[]>([]);
|
const internalViews = ref<ViewItem[]>([]);
|
||||||
const customViews = ref<ViewItem[]>([]);
|
const customViews = ref<ViewItem[]>([]);
|
||||||
const viewListLoading = ref(false);
|
const viewListLoading = ref(false);
|
||||||
|
@ -228,11 +229,19 @@
|
||||||
value: e.id,
|
value: e.id,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isAdvancedSearchMode = ref(false);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (props.viewType) {
|
if (props.viewType) {
|
||||||
getMemberOptions();
|
getMemberOptions();
|
||||||
await getUserViewList();
|
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) => {
|
const getIsValidValue = (item: ConditionsItem) => {
|
||||||
if (typeof item.value === 'boolean') return String(item.value).length;
|
if (typeof item.value === 'boolean') return String(item.value).length;
|
||||||
if (typeof item.value === 'number') return item.value;
|
if (typeof item.value === 'number') return item.value;
|
||||||
|
|
|
@ -183,7 +183,7 @@
|
||||||
}
|
}
|
||||||
const width = props.otherWidth
|
const width = props.otherWidth
|
||||||
? `calc(100vw - ${menuWidth.value}px - ${props.otherWidth}px)`
|
? `calc(100vw - ${menuWidth.value}px - ${props.otherWidth}px)`
|
||||||
: `calc(100vw - ${menuWidth.value}px - 58px)`;
|
: `calc(100vw - ${menuWidth.value}px - 48px)`; // 48px 为卡片左右内边距 32+ 页面右侧内边距16
|
||||||
return {
|
return {
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
width: props.autoWidth ? 'auto' : width,
|
width: props.autoWidth ? 'auto' : width,
|
||||||
|
|
|
@ -106,13 +106,6 @@
|
||||||
const isExpanded = ref(true);
|
const isExpanded = ref(true);
|
||||||
const isExpandAnimating = ref(false); // 控制动画类
|
const isExpandAnimating = ref(false); // 控制动画类
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.notShowFirst,
|
|
||||||
(val) => {
|
|
||||||
innerSize.value = val ? 0 : initialSize;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.size,
|
() => props.size,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
@ -159,6 +152,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.notShowFirst,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
collapse();
|
||||||
|
} else {
|
||||||
|
expand();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
expand,
|
expand,
|
||||||
collapse,
|
collapse,
|
||||||
|
|
|
@ -57,4 +57,11 @@ export enum WorkCardEnum {
|
||||||
BUG_HANDLE_USER = 'BUG_HANDLE_USER', // 缺陷处理人统计
|
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',
|
path: 'followed',
|
||||||
name: WorkbenchRouteEnum.WORKBENCH_INDEX_FOLLOW,
|
name: WorkbenchRouteEnum.WORKBENCH_INDEX_FOLLOW,
|
||||||
component: () => import('@/views/workbench/homePage/index.vue'),
|
component: () => import('@/views/workbench/myFollowed/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
locale: 'menu.workbenchFollowSort',
|
locale: 'menu.workbenchFollowSort',
|
||||||
roles: ['*'],
|
roles: ['*'],
|
||||||
|
|
|
@ -913,6 +913,7 @@ export function customFieldToColumns(customFields: CustomFieldItem[]) {
|
||||||
width: 200,
|
width: 200,
|
||||||
options: options || JSON.parse(platformOptionJson),
|
options: options || JSON.parse(platformOptionJson),
|
||||||
type,
|
type,
|
||||||
|
internal: field.internal,
|
||||||
};
|
};
|
||||||
return column;
|
return column;
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
|
|
||||||
import { deleteDefinitionSchedule, switchDefinitionSchedule } from '@/api/modules/api-test/management';
|
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 { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import { characterLimit } from '@/utils';
|
import { characterLimit } from '@/utils';
|
||||||
|
|
|
@ -540,8 +540,7 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await initFilterColumn();
|
initFilterColumn();
|
||||||
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
|
||||||
if (props.readOnly) {
|
if (props.readOnly) {
|
||||||
columns = columns.filter(
|
columns = columns.filter(
|
||||||
(item) => !['version', 'createTime', 'updateTime', 'operation'].includes(item.dataIndex as string)
|
(item) => !['version', 'createTime', 'updateTime', 'operation'].includes(item.dataIndex as string)
|
||||||
|
@ -1137,6 +1136,12 @@
|
||||||
initFilterColumn();
|
initFilterColumn();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
isAdvancedSearchMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
await tableStore.initColumn(TableKeyEnum.API_TEST, columns, 'drawer', true);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<keep-alive :include="cacheStore.cacheViews">
|
<keep-alive :include="cacheStore.cacheViews">
|
||||||
<apiTable
|
<apiTable
|
||||||
v-if="activeApiTab.id === 'all' && currentTab === 'api'"
|
v-if="activeApiTab.id === 'all' && currentTab === 'api'"
|
||||||
|
ref="apiTableRef"
|
||||||
class="flex-1 pt-[8px]"
|
class="flex-1 pt-[8px]"
|
||||||
:active-module="props.activeModule"
|
:active-module="props.activeModule"
|
||||||
:offspring-ids="props.offspringIds"
|
:offspring-ids="props.offspringIds"
|
||||||
|
@ -577,10 +578,13 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
||||||
|
const isAdvancedSearchMode = computed(() => apiTableRef.value?.isAdvancedSearchMode);
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openApiTab,
|
openApiTab,
|
||||||
addApiTab,
|
addApiTab,
|
||||||
openApiTabAndDebugMock,
|
openApiTabAndDebugMock,
|
||||||
refreshTable,
|
refreshTable,
|
||||||
|
isAdvancedSearchMode,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
:view-type="ViewTypeEnum.API_CASE"
|
:view-type="ViewTypeEnum.API_CASE"
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:search-placeholder="t('apiTestManagement.searchPlaceholder')"
|
:search-placeholder="t('apiTestManagement.searchPlaceholder')"
|
||||||
|
:view-name="viewName"
|
||||||
@keyword-search="loadCaseList()"
|
@keyword-search="loadCaseList()"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="handleAdvSearch"
|
||||||
@refresh="loadCaseList()"
|
@refresh="loadCaseList()"
|
||||||
|
@ -309,6 +310,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
@ -391,6 +393,7 @@
|
||||||
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
|
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
|
@ -803,6 +806,9 @@
|
||||||
type: FilterType.DATE_PICKER,
|
type: FilterType.DATE_PICKER,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const viewName = ref('');
|
||||||
|
|
||||||
// 高级检索
|
// 高级检索
|
||||||
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -1171,8 +1177,16 @@
|
||||||
createAndEditCaseDrawerRef.value?.open(caseDetail.value.apiDefinitionId, caseDetail.value as RequestParam, false);
|
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({
|
defineExpose({
|
||||||
loadCaseList,
|
loadCaseList,
|
||||||
|
isAdvancedSearchMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
await tableStore.initColumn(TableKeyEnum.API_TEST_MANAGEMENT_CASE, columns, 'drawer', true);
|
await tableStore.initColumn(TableKeyEnum.API_TEST_MANAGEMENT_CASE, columns, 'drawer', true);
|
||||||
|
|
|
@ -156,8 +156,10 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isAdvancedSearchMode = computed(() => caseTableRef.value?.isAdvancedSearchMode);
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openCaseTab,
|
openCaseTab,
|
||||||
openCaseTabAndExecute,
|
openCaseTabAndExecute,
|
||||||
|
isAdvancedSearchMode,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -379,6 +379,10 @@
|
||||||
'PROJECT_API_DEFINITION_CASE:READ+UPDATE',
|
'PROJECT_API_DEFINITION_CASE:READ+UPDATE',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const isAdvancedSearchMode = computed(() =>
|
||||||
|
currentTab.value === 'api' ? apiRef.value?.isAdvancedSearchMode : caseRef.value?.isAdvancedSearchMode
|
||||||
|
);
|
||||||
|
|
||||||
/** 向孙组件提供属性 */
|
/** 向孙组件提供属性 */
|
||||||
provide('defaultCaseParams', readonly(defaultCaseParams));
|
provide('defaultCaseParams', readonly(defaultCaseParams));
|
||||||
provide('protocols', readonly(protocols));
|
provide('protocols', readonly(protocols));
|
||||||
|
@ -390,6 +394,7 @@
|
||||||
handleApiUpdateFromModuleTree,
|
handleApiUpdateFromModuleTree,
|
||||||
handleDeleteApiFromModuleTree,
|
handleDeleteApiFromModuleTree,
|
||||||
changeActiveApiTabToFirst,
|
changeActiveApiTabToFirst,
|
||||||
|
isAdvancedSearchMode,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -196,9 +196,8 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAdvancedSearchMode = ref(false);
|
const isAdvancedSearchMode = computed(() => managementRef.value?.isAdvancedSearchMode);
|
||||||
function handleAdvSearch(isStartAdvance: boolean) {
|
function handleAdvSearch() {
|
||||||
isAdvancedSearchMode.value = isStartAdvance;
|
|
||||||
moduleTreeRef.value?.setActiveFolder('all');
|
moduleTreeRef.value?.setActiveFolder('all');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
:view-type="ViewTypeEnum.API_SCENARIO"
|
:view-type="ViewTypeEnum.API_SCENARIO"
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:search-placeholder="t('api_scenario.table.searchPlaceholder')"
|
:search-placeholder="t('api_scenario.table.searchPlaceholder')"
|
||||||
|
:view-name="viewName"
|
||||||
@keyword-search="loadScenarioList(true)"
|
@keyword-search="loadScenarioList(true)"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="handleAdvSearch"
|
||||||
@refresh="loadScenarioList(true)"
|
@refresh="loadScenarioList(true)"
|
||||||
|
@ -481,6 +482,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
@ -559,6 +561,7 @@
|
||||||
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
|
(e: 'handleAdvSearch', isStartAdvance: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const cacheStore = useCacheStore();
|
const cacheStore = useCacheStore();
|
||||||
const showExportModal = ref(false);
|
const showExportModal = ref(false);
|
||||||
|
@ -1071,6 +1074,9 @@
|
||||||
type: FilterType.DATE_PICKER,
|
type: FilterType.DATE_PICKER,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const viewName = ref('');
|
||||||
|
|
||||||
// 高级检索
|
// 高级检索
|
||||||
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -1583,6 +1589,7 @@
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
loadScenarioList,
|
loadScenarioList,
|
||||||
|
isAdvancedSearchMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!props.readOnly) {
|
if (!props.readOnly) {
|
||||||
|
@ -1617,6 +1624,10 @@
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
cacheStore.clearCache();
|
cacheStore.clearCache();
|
||||||
|
if (route.query.view) {
|
||||||
|
setAdvanceFilter({}, route.query.view as string);
|
||||||
|
viewName.value = route.query.view as string;
|
||||||
|
}
|
||||||
if (!isActivated.value) {
|
if (!isActivated.value) {
|
||||||
loadScenarioList();
|
loadScenarioList();
|
||||||
cacheStore.setCache(CacheTabTypeEnum.API_SCENARIO_TABLE);
|
cacheStore.setCache(CacheTabTypeEnum.API_SCENARIO_TABLE);
|
||||||
|
|
|
@ -520,9 +520,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const scenarioModuleTreeRef = ref<InstanceType<typeof scenarioModuleTree>>();
|
const scenarioModuleTreeRef = ref<InstanceType<typeof scenarioModuleTree>>();
|
||||||
const isAdvancedSearchMode = ref(false);
|
const apiTableRef = ref<InstanceType<typeof ScenarioTable>>();
|
||||||
function handleAdvSearch(isStartAdvance: boolean) {
|
const isAdvancedSearchMode = computed(() => apiTableRef.value?.isAdvancedSearchMode);
|
||||||
isAdvancedSearchMode.value = isStartAdvance;
|
function handleAdvSearch() {
|
||||||
scenarioModuleTreeRef.value?.setActiveFolder('all');
|
scenarioModuleTreeRef.value?.setActiveFolder('all');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,7 +564,6 @@
|
||||||
|
|
||||||
const createRef = ref<InstanceType<typeof create>>();
|
const createRef = ref<InstanceType<typeof create>>();
|
||||||
const detailRef = ref<InstanceType<typeof detail>>();
|
const detailRef = ref<InstanceType<typeof detail>>();
|
||||||
const apiTableRef = ref<InstanceType<typeof ScenarioTable>>();
|
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
|
|
||||||
function handleModuleChange() {
|
function handleModuleChange() {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:custom-fields-config-list="searchCustomFields"
|
:custom-fields-config-list="searchCustomFields"
|
||||||
:search-placeholder="t('caseManagement.featureCase.searchByNameAndId')"
|
:search-placeholder="t('caseManagement.featureCase.searchByNameAndId')"
|
||||||
|
:view-name="viewName"
|
||||||
@keyword-search="fetchData()"
|
@keyword-search="fetchData()"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="handleAdvSearch"
|
||||||
@refresh="searchData()"
|
@refresh="searchData()"
|
||||||
|
@ -593,6 +594,8 @@
|
||||||
type: FilterType.DATE_PICKER,
|
type: FilterType.DATE_PICKER,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const viewName = ref('');
|
||||||
// 高级检索
|
// 高级检索
|
||||||
const handleAdvSearch = (filter: FilterResult, id: string) => {
|
const handleAdvSearch = (filter: FilterResult, id: string) => {
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -864,6 +867,10 @@
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
// 进入页面时检查当前项目轮训状态
|
// 进入页面时检查当前项目轮训状态
|
||||||
checkSyncStatus();
|
checkSyncStatus();
|
||||||
|
if (route.query.view) {
|
||||||
|
setAdvanceFilter({}, route.query.view as string);
|
||||||
|
viewName.value = route.query.view as string;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let customColumns: MsTableColumn = [];
|
let customColumns: MsTableColumn = [];
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
:search-placeholder="t('caseManagement.featureCase.searchPlaceholder')"
|
:search-placeholder="t('caseManagement.featureCase.searchPlaceholder')"
|
||||||
:count="modulesCount[props.activeFolder] || 0"
|
:count="modulesCount[props.activeFolder] || 0"
|
||||||
:name="moduleNamePath"
|
:name="moduleNamePath"
|
||||||
|
:view-name="viewName"
|
||||||
@keyword-search="fetchData"
|
@keyword-search="fetchData"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="handleAdvSearch"
|
||||||
@refresh="fetchData()"
|
@refresh="fetchData()"
|
||||||
|
@ -1838,6 +1839,7 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
const isActivated = computed(() => cacheStore.cacheViews.includes(RouteEnum.CASE_MANAGEMENT_CASE));
|
const isActivated = computed(() => cacheStore.cacheViews.includes(RouteEnum.CASE_MANAGEMENT_CASE));
|
||||||
|
const viewName = ref<string>('');
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
showDetailDrawer.value = false;
|
showDetailDrawer.value = false;
|
||||||
|
@ -1845,9 +1847,16 @@
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!isActivated.value) {
|
if (!isActivated.value) {
|
||||||
mountedLoad();
|
|
||||||
// 切换菜单默认还是列表;已经在脑图的时候,刷新浏览器,保持脑图状态
|
// 切换菜单默认还是列表;已经在脑图的时候,刷新浏览器,保持脑图状态
|
||||||
showType.value = minderStore.getShowType(MinderKeyEnum.FEATURE_CASE_MINDER);
|
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"
|
:view-type="ViewTypeEnum.CASE_REVIEW"
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:search-placeholder="t('caseManagement.caseReview.list.searchPlaceholder')"
|
:search-placeholder="t('caseManagement.caseReview.list.searchPlaceholder')"
|
||||||
|
:view-name="viewName"
|
||||||
@keyword-search="searchReview()"
|
@keyword-search="searchReview()"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="handleAdvSearch"
|
||||||
@refresh="searchReview()"
|
@refresh="searchReview()"
|
||||||
|
@ -160,7 +161,7 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onBeforeMount } from 'vue';
|
import { onBeforeMount } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
@ -224,6 +225,7 @@
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const cacheStore = useCacheStore();
|
const cacheStore = useCacheStore();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { openModal } = useModal();
|
const { openModal } = useModal();
|
||||||
|
@ -570,6 +572,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewName = ref<string>('');
|
||||||
|
|
||||||
// 高级检索
|
// 高级检索
|
||||||
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
const handleAdvSearch = async (filter: FilterResult, id: string, isStartAdvance: boolean) => {
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -774,6 +778,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
if (route.query.view) {
|
||||||
|
setAdvanceFilter({}, route.query.view as string);
|
||||||
|
viewName.value = route.query.view as string;
|
||||||
|
}
|
||||||
if (!isActivated.value) {
|
if (!isActivated.value) {
|
||||||
mountedLoad();
|
mountedLoad();
|
||||||
searchReview();
|
searchReview();
|
||||||
|
@ -789,6 +797,7 @@
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
searchReview,
|
searchReview,
|
||||||
|
isAdvancedSearchMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW, columns, 'drawer', true);
|
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_REVIEW, columns, 'drawer', true);
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW,
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
type ShowType = 'all' | 'reviewByMe' | 'createByMe';
|
type ShowType = 'all' | 'reviewByMe' | 'createByMe';
|
||||||
|
@ -103,9 +104,8 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdvancedSearchMode = ref(false);
|
const isAdvancedSearchMode = computed(() => reviewTableRef.value?.isAdvancedSearchMode);
|
||||||
function handleAdvSearch(isStartAdvance: boolean) {
|
function handleAdvSearch() {
|
||||||
isAdvancedSearchMode.value = isStartAdvance;
|
|
||||||
folderTreeRef.value?.setActiveFolder('all');
|
folderTreeRef.value?.setActiveFolder('all');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -24,8 +24,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
:view-type="ViewTypeEnum.TEST_PLAN"
|
:view-type="ViewTypeEnum.TEST_PLAN"
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
:search-placeholder="t('common.searchByIDNameTag')"
|
:search-placeholder="t('common.searchByIDNameTag')"
|
||||||
|
:view-name="viewName"
|
||||||
@keyword-search="fetchData()"
|
@keyword-search="fetchData()"
|
||||||
@adv-search="handleAdvSearch"
|
@adv-search="handleAdvSearch"
|
||||||
@refresh="fetchData()"
|
@refresh="fetchData()"
|
||||||
|
@ -1705,12 +1706,18 @@
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const isActivated = computed(() => cacheStore.cacheViews.includes(RouteEnum.TEST_PLAN_INDEX));
|
const isActivated = computed(() => cacheStore.cacheViews.includes(RouteEnum.TEST_PLAN_INDEX));
|
||||||
|
const viewName = ref('');
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
if (!isActivated.value) {
|
if (!isActivated.value) {
|
||||||
if (route.query.groupId) {
|
if (route.query.groupId) {
|
||||||
showType.value = testPlanTypeEnum.GROUP;
|
showType.value = testPlanTypeEnum.GROUP;
|
||||||
keyword.value = route.query.groupId as string;
|
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();
|
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