feat(功能用例): 修改表Filter和替换功能用例表格筛选过滤

This commit is contained in:
xinxin.wu 2024-05-13 14:23:58 +08:00 committed by 刘瑞斌
parent 68a79b030d
commit 01041aa309
15 changed files with 570 additions and 736 deletions

View File

@ -65,7 +65,7 @@
}
);
const currentValue = defineModel<string[] | string>({ default: [] });
const currentValue = defineModel<(string | number)[] | string>({ default: [] });
const loading = ref(true);
const loadList = async (params: Record<string, any>) => {

View File

@ -67,7 +67,10 @@
:filterable="item.filterable"
:cell-class="item.cellClass"
:header-cell-class="`${
item.headerCellClass || (item.filterConfig && isHighlightFilterBackground) ? 'header-cell-filter' : ''
item.headerCellClass ||
(item.filterConfig && isHighlightFilterBackground && activeDataIndex === item.dataIndex)
? 'header-cell-filter'
: ''
}`"
:body-cell-class="item.bodyCellClass"
:summary-cell-class="item.summaryCellClass"
@ -104,9 +107,11 @@
v-else-if="item.filterConfig"
class="ml-[4px]"
:options="item.filterConfig.options"
:data-index="item.dataIndex"
v-bind="item.filterConfig"
@handle-confirm="(v) => handleFilterConfirm(v, item.dataIndex as string, item.isCustomParam || false)"
@show="showFilter(true)"
@hide="showFilter(false)"
@show="showFilter(true, item.dataIndex)"
@hide="showFilter(false, item.dataIndex)"
>
<template #item="{ filterItem }">
<slot :name="item.filterConfig.filterSlotName" :filter-content="filterItem"> </slot>
@ -348,7 +353,12 @@
(e: 'expand', record: TableData): void | Promise<any>;
(e: 'cell-click', record: TableData, column: TableColumnData, ev: Event): void | Promise<any>;
(e: 'clearSelector'): void;
(e: 'filterChange', dataIndex: string, value: (string | number)[], isCustomParam: boolean): void;
(
e: 'filterChange',
dataIndex: string,
value: string[] | (string | number)[] | undefined,
isCustomParam: boolean
): void;
(e: 'moduleChange'): void;
(e: 'initEnd'): void;
}>();
@ -630,7 +640,11 @@
columnSelectorVisible.value = true;
};
const handleFilterConfirm = (value: (string | number)[], dataIndex: string, isCustomParam: boolean) => {
const handleFilterConfirm = (
value: string[] | (string | number)[] | undefined,
dataIndex: string,
isCustomParam: boolean
) => {
emit('filterChange', dataIndex, value, isCustomParam);
};
@ -640,7 +654,9 @@
});
const isHighlightFilterBackground = ref<boolean>(false);
function showFilter(visible: boolean) {
const activeDataIndex = ref<string | undefined>('');
function showFilter(visible: boolean, dataIndex: string | undefined) {
activeDataIndex.value = dataIndex;
isHighlightFilterBackground.value = visible;
}

View File

@ -11,8 +11,8 @@
<template #content>
<div class="arco-table-filters-content">
<div class="arco-table-filters-content-list">
<div class="max-h-[300px] overflow-y-auto px-[12px] py-[4px]">
<a-checkbox-group v-model="checkedList" size="mini" direction="vertical">
<div class="arco-table-filters-content-wrap max-h-[300px] px-[12px] py-[4px]">
<a-checkbox-group v-if="props.mode === 'static'" v-model="checkedList" size="mini" direction="vertical">
<a-checkbox
v-for="(item, index) of props.options"
:key="item[props.valueKey || 'value']"
@ -32,7 +32,31 @@
</a-checkbox>
</a-checkbox-group>
</div>
<div class="arco-table-filters-bottom">
<div v-if="props.mode === 'remote'" class="w-[200px] p-[12px] pb-[8px]">
<MsSelect
v-model:model-value="checkedList"
mode="remote"
:options="[]"
:placeholder="props.placeholderText"
multiple
:value-key="props.valueKey || 'id'"
:label-key="props.labelKey"
:filter-option="false"
allow-clear
:search-keys="['name']"
:loading="loading"
:remote-func="loadList"
:remote-extra-params="{ ...props.loadOptionParams }"
:option-label-render="optionLabelRender"
:should-calculate-max-tag="false"
>
</MsSelect>
</div>
<div
:class="`${
props.mode === 'static' ? 'justify-between' : 'justify-end'
} arco-table-filters-bottom flex h-[38px] items-center`"
>
<a-button size="mini" type="secondary" @click="handleFilterReset">
{{ t('common.reset') }}
</a-button>
@ -47,20 +71,37 @@
</template>
<script lang="ts" setup>
import { SelectOptionData } from '@arco-design/web-vue';
import MsSelect from '@/components/business/ms-select/index';
import { useI18n } from '@/hooks/useI18n';
import { FilterRemoteMethodsEnum } from '@/enums/tableFilterEnum';
import { initRemoteOptionsFunc } from './filterConfig';
const { t } = useI18n();
export interface FilterListItem {
[key: string]: any;
}
const props = defineProps<{
options?: FilterListItem[];
valueKey?: string;
labelKey?: string;
}>();
const props = withDefaults(
defineProps<{
options?: FilterListItem[];
valueKey?: string;
labelKey?: string;
mode?: 'static' | 'remote';
remoteMethod?: FilterRemoteMethodsEnum; //
loadOptionParams?: Record<string, any>; //
placeholderText?: string;
}>(),
{
mode: 'static',
}
);
const emit = defineEmits<{
(e: 'handleConfirm', value: (string | number)[]): void;
(e: 'handleConfirm', value: (string | number)[] | string[] | undefined): void;
}>();
const visible = ref(false);
@ -77,4 +118,43 @@
emit('handleConfirm', checkedList.value);
visible.value = false;
};
const loading = ref(true);
const optionLabelRender = (option: SelectOptionData) => {
if (option.email !== '') {
return `<span class='text-[var(--color-text-1)]'>${option.name}</span><span class='text-[var(--color-text-4)] ml-[4px]'>(${option.email})</span>`;
}
return `<span class='text-[var(--color-text-1)]'>${option.name}</span>`;
};
const loadList = async (params: Record<string, any>) => {
try {
loading.value = true;
const { keyword, ...rest } = params;
if (props.remoteMethod) {
const list = (await initRemoteOptionsFunc(props.remoteMethod, { keyword, ...rest })) || [];
list.forEach((item: any) => {
if (props.valueKey) {
item.id = item[props.valueKey || 'id'] as string;
}
});
return list;
}
return [];
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
return [];
} finally {
loading.value = false;
}
};
</script>
<style scoped lang="less">
.arco-table-filters-content-wrap {
@apply overflow-y-auto;
.ms-scroll-bar();
}
</style>

View File

@ -0,0 +1,14 @@
import { getProjectMemberOptions } from '@/api/modules/project-management/projectMember';
import { FilterRemoteMethodsEnum } from '@/enums/tableFilterEnum';
export function initRemoteOptionsFunc(remoteMethod: string, params: Record<string, any>) {
switch (remoteMethod) {
case FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER:
return getProjectMemberOptions(params.projectId, params.keyword);
default:
break;
}
}
export default {};

View File

@ -1,6 +1,6 @@
import type { TableQueryParams } from '@/models/common';
import { ColumnEditTypeEnum, SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import type { TableChangeExtra, TableColumnData, TableData, TableDraggable } from '@arco-design/web-vue';
@ -20,6 +20,12 @@ export interface MsTableColumnFilterConfig {
options?: Record<string, any>[]; // 筛选数据
valueKey?: string;
labelKey?: string;
mode?: 'static' | 'remote';
remoteMethod?: FilterRemoteMethodsEnum; // 加载选项的类型
loadOptionParams?: Record<string, any>; // 请求下拉的参数
placeholderText?: string;
firstLabelKey?: string;
secondLabelKey?: string;
}
export interface MsTableColumnData extends TableColumnData {

View File

@ -2,6 +2,7 @@
// hook/table-props.ts
import { ref, watchEffect } from 'vue';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
import { useAppStore, useTableStore } from '@/store';
@ -356,15 +357,12 @@ export default function useTableProps<T>(
multiple: boolean,
isCustomParma: boolean
) => {
if (filteredValues.length > 0) {
if (isCustomParma) {
filterItem.value = { [`custom_${multiple ? 'multiple' : 'single'}_${dataIndex}`]: filteredValues };
} else {
filterItem.value = { [dataIndex]: filteredValues };
}
if (isCustomParma) {
filterItem.value = { [`custom_${multiple ? 'multiple' : 'single'}_${dataIndex}`]: filteredValues };
} else {
filterItem.value = {};
filterItem.value = { ...getTableQueryParams().filter, [dataIndex]: filteredValues };
}
propsRes.value.filter = cloneDeep(filterItem.value);
loadList();
},
// 分页触发

View File

@ -1,5 +1,16 @@
export enum FilterSlotNameEnum {
TEST_PLAN_STATUS_FILTER = 'TEST_PLAN_STATUS_FILTER',
TEST_PLAN_STATUS_FILTER = 'TEST_PLAN_STATUS_FILTER', // 测试计划执行结果
CASE_MANAGEMENT_EXECUTE_RESULT = 'CASE_MANAGEMENT_EXECUTE_RESULT', // 功能用例执行结果
CASE_MANAGEMENT_REVIEW_RESULT = 'CASE_MANAGEMENT_REVIEW_RESULT', // 功能用例评审结果
CASE_MANAGEMENT_REVIEW_STATUS = 'CASE_MANAGEMENT_REVIEW_STATUS', // 用例评审评审状态
CASE_MANAGEMENT_CASE_LEVEL = 'CASE_MANAGEMENT_CASE_LEVEL', // 用例等级
CASE_MANAGEMENT_BUG_STATE = 'CASE_MANAGEMENT_BUG_STATE', // 缺陷状态
API_TEST_API_REQUEST_METHODS = 'API_TEST_API_REQUEST_METHODS', // 接口测试请求方式
API_TEST_API_REQUEST_API_STATUS = 'API_TEST_API_REQUEST_API_STATUS', // 接口测试接口状态
}
export enum FilterRemoteMethodsEnum {
PROJECT_PERMISSION_MEMBER = 'PROJECT_PERMISSION_MEMBER', // 项目下成员
}
export default {};

View File

@ -71,92 +71,22 @@
</a-option>
</a-select>
</template>
<template #caseLevelFilter="{ columnConfig }">
<TableFilter
v-model:visible="caseFilterVisible"
v-model:status-filters="caseFilters"
:title="(columnConfig.title as string)"
:list="caseLevelList"
value-key="value"
@search="initData()"
>
<template #item="{ item }">
<div class="flex"> <caseLevel :case-level="item.text" /></div>
</template>
</TableFilter>
<!-- 用例等级 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<caseLevel :case-level="filterContent.text" />
</template>
<template #executeResultFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="executeResultFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
size="mini"
@click="executeResultFilterVisible = true"
>
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="executeResultFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="arco-table-filters-content-list">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="executeResultFilters" direction="vertical" size="mini">
<a-checkbox v-for="key of Object.keys(executionResultMap)" :key="key" :value="key">
<MsIcon
:type="executionResultMap[key]?.icon || ''"
class="mr-1"
:class="[executionResultMap[key].color]"
></MsIcon>
<span>{{ executionResultMap[key]?.statusText || '' }} </span>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetExecuteResultFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</div>
</template>
</a-trigger>
<!-- 执行结果 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
<ExecuteStatusTag :status="filterContent.value" />
</template>
<template #updateUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="updateUserFilterVisible"
v-model:status-filters="updateUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
label-key="label"
@search="initData()"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
</template>
<template #createUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="createUserFilterVisible"
v-model:status-filters="createUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
label-key="label"
@search="initData()"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
<!-- 评审结果 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT]="{ filterContent }">
<MsIcon
:type="statusIconMap[filterContent.value]?.icon"
class="mr-1"
:class="[statusIconMap[filterContent.value].color]"
></MsIcon>
<span>{{ statusIconMap[filterContent.value]?.statusText }} </span>
</template>
<template #reviewStatus="{ record }">
<MsIcon
@ -166,45 +96,6 @@
></MsIcon>
<span>{{ statusIconMap[record.reviewStatus]?.statusText || '' }} </span>
</template>
<template #reviewStatusFilter="{ columnConfig }">
<a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
size="mini"
@click="statusFilterVisible = true"
>
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(statusIconMap)" :key="key" :value="key">
<MsIcon
:type="statusIconMap[key]?.icon || ''"
class="mr-1"
:class="[statusIconMap[key].color]"
></MsIcon>
<span>{{ statusIconMap[key]?.statusText || '' }} </span>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetReviewStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #lastExecuteResult="{ record }">
<executeResult :execute-result="record.lastExecuteResult" />
</template>
@ -394,6 +285,7 @@
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Message, TableChangeExtra, TableData, TreeNodeData } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import { CustomTypeMaps, MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterResult, FilterType } from '@/components/pure/ms-advance-filter/type';
@ -406,16 +298,15 @@
import useTable from '@/components/pure/ms-table/useTable';
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import type { TagType, Theme } from '@/components/pure/ms-tag/ms-tag.vue';
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import executeResult from '@/components/business/ms-case-associate/executeResult.vue';
import BatchEditModal from './batchEditModal.vue';
import CaseDetailDrawer from './caseDetailDrawer.vue';
import FeatureCaseTree from './caseTree.vue';
import ExecuteStatusTag from './excuteStatusTag.vue';
import ExportExcelDrawer from './exportExcelDrawer.vue';
import AddDemandModal from './tabContent/tabDemand/addDemandModal.vue';
import ThirdDemandDrawer from './tabContent/tabDemand/thirdDemandDrawer.vue';
import TableFilter from './tableFilter.vue';
import {
batchAssociationDemand,
@ -451,6 +342,7 @@
import { ModuleTreeNode } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { executionResultMap, getCaseLevels, getTableFields, statusIconMap } from './utils';
import { LabelValue } from '@arco-design/web-vue/es/tree-select/interface';
@ -559,7 +451,24 @@
hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE', 'FUNCTIONAL_CASE:READ+DELETE'])
);
const columns: MsTableColumn = [
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 firstStaticColumn: MsTableColumn = [
{
'title': 'caseManagement.featureCase.tableColumnID',
'slotName': 'num',
@ -592,20 +501,43 @@
showDrag: false,
columnSelectorDisabled: true,
},
];
const caseLevelColumn: MsTableColumn = [
{
title: 'caseManagement.featureCase.tableColumnLevel',
slotName: 'caseLevel',
dataIndex: 'caseLevel',
titleSlotName: 'caseLevelFilter',
filterConfig: {
options: [],
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
},
showInTable: true,
width: 150,
showDrag: true,
},
];
const operationColumn: MsTableColumn = [
{
title: hasOperationPermission.value ? 'caseManagement.featureCase.tableColumnActions' : '',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
showInTable: true,
showDrag: false,
width: hasOperationPermission.value ? 140 : 50,
},
];
const lastStaticColumn: MsTableColumn = [
{
title: 'caseManagement.featureCase.tableColumnReviewResult',
dataIndex: 'reviewStatus',
slotName: 'reviewStatus',
titleSlotName: 'reviewStatusFilter',
filterConfig: {
options: reviewResultOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT,
},
showInTable: true,
width: 150,
showDrag: true,
@ -615,6 +547,10 @@
dataIndex: 'lastExecuteResult',
slotName: 'lastExecuteResult',
titleSlotName: 'executeResultFilter',
filterConfig: {
options: executeResultOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT,
},
showInTable: true,
width: 150,
showDrag: true,
@ -650,7 +586,14 @@
slotName: 'updateUserName',
showTooltip: true,
dataIndex: 'updateUserName',
titleSlotName: 'updateUserFilter',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
width: 200,
showDrag: true,
@ -671,7 +614,14 @@
title: 'caseManagement.featureCase.tableColumnCreateUser',
slotName: 'createUserName',
dataIndex: 'createUserName',
titleSlotName: 'createUserFilter',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
showTooltip: true,
width: 200,
@ -689,16 +639,8 @@
width: 200,
showDrag: true,
},
{
title: hasOperationPermission.value ? 'caseManagement.featureCase.tableColumnActions' : '',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
showInTable: true,
showDrag: false,
width: hasOperationPermission.value ? 140 : 50,
},
];
const platformInfo = ref<Record<string, any>>({});
const tableBatchActions = {
baseAction: [
@ -766,8 +708,7 @@
const filterConfigList = ref<FilterFormItem[]>([]);
const searchCustomFields = ref<FilterFormItem[]>([]);
const memberOptions = ref<{ label: string; value: string }[]>([]);
const updateUserFilters = ref<string[]>([]);
const createUserFilters = ref<string[]>([]);
async function initFilter() {
const result = await getCustomFieldsTable(currentProjectId.value);
memberOptions.value = await getProjectOptions(appStore.currentProjectId, keyword.value);
@ -932,9 +873,6 @@
excludeIds: [],
currentSelectCount: 0,
});
const statusFilters = ref<string[]>([]);
const caseFilters = ref<string[]>([]);
const executeResultFilters = ref<string[]>([]);
const conditionParams = ref({
keyword: '',
@ -964,13 +902,6 @@
selectAll: batchParams.value.selectAll,
selectIds: batchParams.value.selectedIds as string[],
keyword: keyword.value,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
},
combine: batchParams.value.condition,
};
}
@ -994,39 +925,16 @@
return (nodeValue as ModuleTreeNode).name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
}
const searchParams = ref<TableQueryParams>({
projectId: currentProjectId.value,
moduleIds: [],
});
const caseLevelFields = ref<Record<string, any>>({});
//
const caseFilterVisible = ref(false);
const caseLevelList = computed(() => {
return caseLevelFields.value?.options || [];
});
function getExecuteResultList() {
const list: any = [];
Object.keys(executionResultMap).forEach((key) => {
list.push({
...executionResultMap[key],
});
});
return list;
}
const executeResultFilterList = ref(getExecuteResultList());
async function getLoadListParams() {
setLoadListParams(await initTableParams());
}
//
const executeResultFilterVisible = ref(false);
const updateUserFilterVisible = ref(false);
const createUserFilterVisible = ref(false);
//
async function initData() {
await getLoadListParams();
@ -1123,13 +1031,7 @@
excludeIds: batchParams.value?.excludeIds || [],
condition: {
keyword: keyword.value,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
},
filter: propsRes.value.filter,
combine: batchParams.value.condition,
},
projectId: currentProjectId.value,
@ -1210,13 +1112,7 @@
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
condition: {
keyword: keyword.value,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
},
filter: propsRes.value.filter,
combine: batchParams.value.condition,
},
selectAll,
@ -1349,23 +1245,20 @@
caseLevelFields.value = result.customFields.find(
(item: any) => item.internal && (item.fieldName === 'Case Priority' || item.fieldName === '用例等级')
);
if (caseLevelColumn[0].filterConfig?.options) {
caseLevelColumn[0].filterConfig.options = cloneDeep(unref(caseLevelFields.value?.options)) || [];
}
fullColumns = [
...columns.slice(0, columns.length - 1),
...firstStaticColumn,
...caseLevelColumn,
...lastStaticColumn,
...customFieldsColumns,
...columns.slice(columns.length - 1, columns.length),
...operationColumn,
];
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_TABLE, fullColumns, 'drawer', true);
}
//
function isCaseLevel(slotFieldId: string) {
const currentItem = initDefaultFields.value.find((item: any) => item.fieldId === slotFieldId);
return {
name: currentItem?.fieldName,
type: currentItem?.type,
options: currentItem?.options,
};
}
//
function getCustomsParams(detailResult: CaseManagementTable, record: CaseManagementTable) {
const customFieldsList = Object.keys(record).filter((item) => item.includes('rule-'));
@ -1402,28 +1295,6 @@
}
}
//
function getCaseState(caseState: string | undefined): { type: TagType; theme: Theme } {
switch (caseState) {
case '已完成':
return {
type: 'success',
theme: 'default',
};
case '进行中':
return {
type: 'link',
theme: 'default',
};
default:
return {
type: 'default',
theme: 'default',
};
}
}
//
async function handleChangeModule(
record: CaseManagementTable,
@ -1460,13 +1331,7 @@
condition: {
keyword: keyword.value,
searchMode: accordBelow,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
},
filter: propsRes.value.filter,
combine,
},
};
@ -1588,22 +1453,10 @@
moduleId: selectedModuleKeys.value[0],
demandPlatform,
demandList,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
},
filter: propsRes.value.filter,
condition: {
keyword: keyword.value,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
},
filter: propsRes.value.filter,
combine: batchParams.value.condition,
},
functionalDemandBatchRequest,
@ -1622,26 +1475,6 @@
const statusFilterVisible = ref(false);
function handleFilterHidden(val: boolean) {
if (!val) {
initData();
statusFilterVisible.value = false;
executeResultFilterVisible.value = false;
}
}
function resetReviewStatusFilter() {
statusFilters.value = [];
statusFilterVisible.value = false;
initData();
}
function resetExecuteResultFilter() {
executeResultFilters.value = [];
executeResultFilterVisible.value = false;
initData();
}
//
onBeforeMount(async () => {
try {

View File

@ -0,0 +1,62 @@
<template>
<div class="flex items-center justify-start">
<MsIcon
:type="executionResultMap[props.status]?.icon || ''"
class="mr-1"
:class="[executionResultMap[props.status].color]"
></MsIcon>
<span>{{ executionResultMap[props.status]?.statusText || '' }} </span>
</div>
</template>
<script setup lang="ts">
/**
* @desc 功能用例的执行结果状态
*/
import { ref } from 'vue';
import { useI18n } from '@/hooks/useI18n';
import { StatusType } from '@/enums/caseEnum';
const { t } = useI18n();
const props = defineProps<{
status: StatusType;
}>();
const executionResultMap: Record<string, any> = {
UN_EXECUTED: {
key: 'UN_EXECUTED',
icon: StatusType.UN_EXECUTED,
statusText: t('caseManagement.featureCase.nonExecution'),
color: 'text-[var(--color-text-brand)]',
},
PASSED: {
key: 'PASSED',
icon: StatusType.PASSED,
statusText: t('caseManagement.featureCase.passed'),
color: '',
},
/* SKIPPED: {
key: 'SKIPPED',
icon: StatusType.SKIPPED,
statusText: t('caseManagement.featureCase.skip'),
color: 'text-[rgb(var(--link-6))]',
}, */
BLOCKED: {
key: 'BLOCKED',
icon: StatusType.BLOCKED,
statusText: t('caseManagement.featureCase.chokeUp'),
color: 'text-[rgb(var(--warning-6))]',
},
FAILED: {
key: 'FAILED',
icon: StatusType.FAILED,
statusText: t('caseManagement.featureCase.failure'),
color: '',
},
};
</script>
<style scoped lang="less"></style>

View File

@ -106,73 +106,22 @@
<template #caseLevel="{ record }">
<caseLevel :case-level="(getCaseLevels(record.customFields) as CaseLevel)" />
</template>
<template #caseLevelFilter="{ columnConfig }">
<TableFilter
v-model:visible="caseFilterVisible"
v-model:status-filters="caseFilters"
:title="(columnConfig.title as string)"
:list="caseLevelList"
value-key="value"
@search="initRecycleList()"
>
<template #item="{ item }">
<div class="flex"> <caseLevel :case-level="item.text" /></div>
</template>
</TableFilter>
<!-- 用例等级 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<caseLevel :case-level="filterContent.text" />
</template>
<template #executeResultFilter="{ columnConfig }">
<TableFilter
v-model:visible="executeResultFilterVisible"
v-model:status-filters="executeResultFilters"
:title="(columnConfig.title as string)"
:list="executeResultFilterList"
value-key="key"
@search="initRecycleList()"
>
<template #item="{ item }">
<MsIcon :type="item.icon || ''" class="mr-1" :class="[item.color]"></MsIcon>
<span>{{ item.statusText || '' }}</span>
</template>
</TableFilter>
<!-- 执行结果 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
<ExecuteStatusTag :status="filterContent.value" />
</template>
<template #updateUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="updateUserFilterVisible"
v-model:status-filters="updateUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
@search="initRecycleList()"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
</template>
<template #createUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="createUserFilterVisible"
v-model:status-filters="createUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
@search="initRecycleList()"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
</template>
<template #deleteUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="deleteUserFilterVisible"
v-model:status-filters="deleteUserFilters"
:title="(columnConfig.title as string)"
:list="memberOptions"
@search="initRecycleList()"
>
<template #item="{ item }">
{{ item.label }}
</template>
</TableFilter>
<!-- 评审结果 -->
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT]="{ filterContent }">
<MsIcon
:type="statusIconMap[filterContent.value]?.icon"
class="mr-1"
:class="[statusIconMap[filterContent.value].color]"
></MsIcon>
<span>{{ statusIconMap[filterContent.value]?.statusText }} </span>
</template>
<template #reviewStatus="{ record }">
<MsIcon
@ -182,44 +131,6 @@
></MsIcon>
<span>{{ statusIconMap[record.reviewStatus]?.statusText || '' }} </span>
</template>
<template #reviewStatusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button type="text" class="arco-btn-text--secondary p-[8px_4px]" @click="statusFilterVisible = true">
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(statusIconMap)" :key="key" :value="key">
<MsIcon
:type="statusIconMap[key]?.icon || ''"
class="mr-1"
:class="[statusIconMap[key].color]"
></MsIcon>
<span>{{ statusIconMap[key]?.statusText || '' }} </span>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetReviewStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #lastExecuteResult="{ record }">
<MsIcon
:type="executionResultMap[record.lastExecuteResult]?.icon || ''"
@ -277,6 +188,7 @@
*/
import { computed, ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
import { CustomTypeMaps, MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
@ -287,12 +199,11 @@
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import type { TagType, Theme } from '@/components/pure/ms-tag/ms-tag.vue';
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import TableFilter from './tableFilter.vue';
import ExecuteStatusTag from './excuteStatusTag.vue';
import {
batchDeleteRecycleCase,
@ -315,6 +226,7 @@
import type { CaseManagementTable, CustomAttributes } from '@/models/caseManagement/featureCase';
import type { ModuleTreeNode, TableQueryParams } from '@/models/common';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { executionResultMap, getCaseLevels, getTableFields, statusIconMap } from './utils';
@ -354,8 +266,15 @@
);
const hasOperationPermission = computed(() => hasAnyPermission(['FUNCTIONAL_CASE:READ+DELETE']));
const columns: MsTableColumn = [
const reviewResultOptions = computed(() => {
return Object.keys(statusIconMap).map((key) => {
return {
value: key,
label: statusIconMap[key].statusText,
};
});
});
const firstStaticColumn: MsTableColumn = [
{
'title': 'caseManagement.featureCase.tableColumnID',
'slotName': 'num',
@ -387,19 +306,30 @@
showDrag: false,
columnSelectorDisabled: true,
},
];
const caseLevelColumn: MsTableColumn = [
{
title: 'caseManagement.featureCase.tableColumnLevel',
slotName: 'caseLevel',
titleSlotName: 'caseLevelFilter',
dataIndex: 'caseLevel',
filterConfig: {
options: [],
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL,
},
showInTable: true,
width: 200,
width: 150,
showDrag: true,
},
];
const lastStaticColumn: MsTableColumn = [
{
title: 'caseManagement.featureCase.tableColumnReviewResult',
dataIndex: 'reviewStatus',
slotName: 'reviewStatus',
titleSlotName: 'reviewStatusFilter',
filterConfig: {
options: reviewResultOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_RESULT,
},
showInTable: true,
width: 200,
showDrag: true,
@ -442,7 +372,14 @@
title: 'caseManagement.featureCase.tableColumnUpdateUser',
slotName: 'updateUserName',
dataIndex: 'updateUser',
titleSlotName: 'updateUserFilter',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
showTooltip: true,
width: 200,
@ -465,7 +402,14 @@
slotName: 'createUserName',
dataIndex: 'createUser',
showTooltip: true,
titleSlotName: 'createUserFilter',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
width: 200,
showDrag: true,
@ -485,7 +429,14 @@
{
title: 'caseManagement.featureCase.tableColumnDeleteUser',
dataIndex: 'deleteUserName',
titleSlotName: 'deleteUserFilter',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.featureCase.PleaseSelect'),
},
showInTable: true,
showTooltip: true,
width: 200,
@ -503,6 +454,8 @@
width: 200,
showDrag: true,
},
];
const operationColumn: MsTableColumn = [
{
title: hasOperationPermission.value ? 'caseManagement.featureCase.tableColumnActions' : '',
slotName: 'operation',
@ -643,29 +596,7 @@
currentSelectCount: 0,
});
//
const statusFilters = ref<string[]>([]);
const caseLevelFields = ref<Record<string, any>>({});
const caseFilterVisible = ref(false);
const caseLevelList = computed(() => {
return caseLevelFields.value?.options || [];
});
const caseFilters = ref<string[]>([]);
const executeResultFilters = ref<string[]>([]);
const updateUserFilters = ref<string[]>([]);
const createUserFilters = ref<string[]>([]);
const deleteUserFilters = ref<string[]>([]);
function getExecuteResultList() {
const list: any = [];
Object.keys(executionResultMap).forEach((key) => {
list.push({
...executionResultMap[key],
});
});
return list;
}
const executeResultFilterList = ref(getExecuteResultList());
//
function getBatchParams(): TableQueryParams {
@ -678,12 +609,7 @@
condition: {
keyword: keyword.value,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
deleteUserName: deleteUserFilters.value,
...propsRes.value.filter,
},
combine: batchParams.value.condition,
},
@ -703,14 +629,6 @@
keyword: keyword.value,
moduleIds,
projectId: currentProjectId.value,
filter: {
reviewStatus: statusFilters.value,
caseLevel: caseFilters.value,
lastExecuteResult: executeResultFilters.value,
updateUserName: updateUserFilters.value,
createUserName: createUserFilters.value,
deleteUserName: deleteUserFilters.value,
},
};
}
@ -739,12 +657,6 @@
setLoadListParams(await initTableParams());
}
//
const executeResultFilterVisible = ref(false);
const updateUserFilterVisible = ref(false);
const createUserFilterVisible = ref(false);
const deleteUserFilterVisible = ref(false);
//
async function initRecycleList() {
await getLoadListParams();
@ -901,10 +813,15 @@
};
});
caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级');
if (caseLevelColumn[0].filterConfig?.options) {
caseLevelColumn[0].filterConfig.options = cloneDeep(unref(caseLevelFields.value?.options)) || [];
}
fullColumns = [
...columns.slice(0, columns.length - 1),
...firstStaticColumn,
...caseLevelColumn,
...lastStaticColumn,
...customFieldsColumns,
...columns.slice(columns.length - 1, columns.length),
...operationColumn,
];
await tableStore.initColumn(TableKeyEnum.CASE_MANAGEMENT_RECYCLE_TABLE, fullColumns, 'drawer');
}
@ -1011,50 +928,13 @@
condition: {
keyword: keyword.value,
searchMode: accordBelow,
filter: propsRes.value.filter,
filter: { ...propsRes.value.filter },
combine,
},
};
initRecycleList();
};
//
function getCaseState(caseState: string | undefined): { type: TagType; theme: Theme } {
switch (caseState) {
case '已完成':
return {
type: 'success',
theme: 'default',
};
case '进行中':
return {
type: 'link',
theme: 'default',
};
default:
return {
type: 'default',
theme: 'default',
};
}
}
const statusFilterVisible = ref(false);
function handleFilterHidden(val: boolean) {
if (!val) {
initRecycleList();
statusFilterVisible.value = false;
}
}
function resetReviewStatusFilter() {
statusFilters.value = [];
statusFilterVisible.value = false;
initRecycleList();
}
onMounted(async () => {
await getRecycleModules();
await initFilter();

View File

@ -56,9 +56,9 @@
></a-input-search>
</div>
</div>
<ms-base-table v-if="showType === 'link'" ref="tableRef" v-bind="linkPropsRes" v-on="linkTableEvent">
<ms-base-table v-if="showType === 'link'" ref="bugTableRef" v-bind="linkPropsRes" v-on="linkTableEvent">
<template #name="{ record }">
<span class="one-line-text max-w-[300px]"> {{ record.name }}</span>
<span class="one-line-text max-w-[150px]"> {{ characterLimit(record.name) }}</span>
<a-popover title="" position="right" style="width: 480px">
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
<template #content>
@ -66,36 +66,6 @@
</template>
</a-popover>
</template>
<template #handleUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="handleUserFilterVisible"
v-model:status-filters="handleUserFilterValue"
:title="(columnConfig.title as string)"
:list="handleUserFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #statusFilter="{ columnConfig }">
<TableFilter
v-model:visible="statusFilterVisible"
v-model:status-filters="statusFilterValue"
:title="(columnConfig.title as string)"
:list="statusFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #severityFilter="{ columnConfig }">
<TableFilter
v-model:visible="severityFilterVisible"
@ -110,6 +80,14 @@
</template>
</TableFilter>
</template>
<template #statusName="{ record }">
<div class="one-line-text">{{ record.statusName }}</div>
</template>
<template #handleUserName="{ record }">
<a-tooltip :content="record.handleUserName">
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) }}</div>
</a-tooltip>
</template>
<template #operation="{ record }">
<MsButton v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" @click="cancelLink(record.id)">{{
@ -137,9 +115,9 @@
</div>
</template>
</ms-base-table>
<ms-base-table v-else v-bind="testPlanPropsRes" v-on="testPlanTableEvent">
<ms-base-table v-else v-bind="testPlanPropsRes" ref="planTableRef" v-on="testPlanTableEvent">
<template #name="{ record }">
<span class="one-line-text max-w-[300px]"> {{ record.name }}</span>
<div class="one-line-text max-w-[300px]"> {{ record.name }}</div>
<a-popover title="" position="right">
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
<template #content>
@ -149,37 +127,14 @@
</template>
</a-popover>
</template>
<template #handleUserName="{ record }">
<a-tooltip :content="record.handleUserName">
<div class="one-line-text max-w-[200px]">{{ characterLimit(record.handleUserName) }}</div>
</a-tooltip>
</template>
<template #testPlanName="{ record }">
<a-button type="text" class="px-0" @click="goToPlan(record)">{{ record.testPlanName }}</a-button>
</template>
<template #handleUserFilter="{ columnConfig }">
<TableFilter
v-model:visible="handleUserFilterVisible"
v-model:status-filters="handleUserFilterValue"
:title="(columnConfig.title as string)"
:list="handleUserFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #statusFilter="{ columnConfig }">
<TableFilter
v-model:visible="statusFilterVisible"
v-model:status-filters="statusFilterValue"
:title="(columnConfig.title as string)"
:list="statusFilterOptions"
value-key="value"
@search="searchData()"
>
<template #item="{ item }">
{{ item.text }}
</template>
</TableFilter>
</template>
<template #severityFilter="{ columnConfig }">
<TableFilter
@ -239,11 +194,13 @@
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { BugListItem, BugOptionItem } from '@/models/bug-management';
import type { TableQueryParams } from '@/models/common';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const featureCaseStore = useFeatureCaseStore();
@ -257,10 +214,8 @@
const showType = ref('link');
const keyword = ref<string>('');
const handleUserFilterVisible = ref(false);
const handleUserFilterValue = ref<string[]>([]);
const handleUserFilterOptions = ref<BugOptionItem[]>([]);
const statusFilterVisible = ref(false);
const statusFilterValue = ref<string[]>([]);
const statusFilterOptions = ref<BugOptionItem[]>([]);
const severityFilterOptions = ref<BugOptionItem[]>([]);
@ -275,10 +230,11 @@
{
title: 'caseManagement.featureCase.tableColumnID',
dataIndex: 'num',
width: 200,
width: 100,
showInTable: true,
showTooltip: true,
showDrag: false,
fixed: 'left',
},
{
title: 'caseManagement.featureCase.defectName',
@ -286,29 +242,33 @@
dataIndex: 'name',
showInTable: true,
showTooltip: false,
width: 300,
width: 200,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.defectState',
slotName: 'statusName',
dataIndex: 'statusName',
titleSlotName: 'statusFilter',
dataIndex: 'status',
filterConfig: {
options: [],
labelKey: 'text',
},
showInTable: true,
showTooltip: true,
width: 200,
width: 150,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.updateUser',
slotName: 'handleUserName',
dataIndex: 'handleUserName',
titleSlotName: 'handleUserFilter',
dataIndex: 'handleUser',
filterConfig: {
options: [],
labelKey: 'text',
},
showInTable: true,
showTooltip: true,
width: 300,
width: 200,
ellipsis: true,
},
{
@ -317,7 +277,7 @@
dataIndex: 'source',
showInTable: true,
showTooltip: true,
width: 200,
width: 100,
ellipsis: true,
showDrag: false,
},
@ -326,7 +286,7 @@
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 140,
width: 100,
showInTable: true,
showDrag: false,
},
@ -386,8 +346,11 @@
{
title: 'caseManagement.featureCase.updateUser',
slotName: 'handleUserName',
dataIndex: 'handleUserName',
titleSlotName: 'handleUserFilter',
dataIndex: 'handleUser',
filterConfig: {
options: [],
labelKey: 'text',
},
showInTable: true,
showTooltip: true,
width: 300,
@ -402,7 +365,6 @@
setLoadListParams: setTestPlanListParams,
} = useTable(getLinkedCaseBugList, {
columns: testPlanColumns,
scroll: { x: '100%' },
heightUsed: 354,
enableDrag: true,
});
@ -412,13 +374,13 @@
status: statusFilterValue.value,
handleUser: handleUserFilterValue.value,
};
// TODO
filterParams[severityColumnId.value] = severityFilterValue.value;
return {
keyword: keyword.value,
caseId: showType.value === 'link' ? props.caseId : null,
testPlanCaseId: showType.value === 'link' ? null : props.caseId,
projectId: appStore.currentProjectId,
filter: { ...filterParams },
condition: {
keyword: keyword.value,
filter: showType.value === 'link' ? linkPropsRes : 'testPlanPropsRes',
@ -436,11 +398,40 @@
}
}
const bugTableRef = ref();
const planTableRef = ref();
function makeColumns(columnData: MsTableColumn) {
const optionsMap: Record<string, any> = {
status: statusFilterOptions.value,
handleUser: handleUserFilterOptions.value,
};
return columnData.map((e) => {
if (Object.prototype.hasOwnProperty.call(optionsMap, e.dataIndex as string)) {
return {
...e,
filterConfig: {
...e.filterConfig,
options: optionsMap[e.dataIndex as string],
},
};
}
return { ...e };
});
}
async function initFilterOptions() {
if (hasAnyPermission(['PROJECT_BUG:READ'])) {
const res = await getCustomOptionHeader(appStore.currentProjectId);
handleUserFilterOptions.value = res.handleUserOption;
statusFilterOptions.value = res.statusOption;
if (showType.value === 'link') {
const columnList = makeColumns(columns);
bugTableRef.value.initColumn(columnList);
} else {
const planColumnList = makeColumns(testPlanColumns);
planTableRef.value.initColumn(planColumnList);
}
}
}
@ -522,19 +513,12 @@
() => showType.value,
(val) => {
if (val) {
initFilterOptions();
getFetch();
}
}
);
// watch(
// () => activeTab.value,
// (val) => {
// if (val === 'bug') {
// getFetch();
// }
// }
// );
const total = ref<number>(0);
async function initBugList() {
if (!hasAnyPermission(['PROJECT_BUG:READ'])) {
@ -584,9 +568,12 @@
onMounted(() => {
getFetch();
initFilterOptions();
initBugList();
});
onBeforeMount(() => {
initFilterOptions();
});
</script>
<style scoped></style>

View File

@ -19,6 +19,16 @@
<template #reviewStatus="{ record }">
<MsStatusTag :status="record.reviewStatus || 'PREPARED'" />
</template>
<!-- TODO 后台需要加 -->
<!-- <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 #status="{ record }">
<MsIcon
:type="statusIconMap[record.status]?.icon || ''"
@ -42,19 +52,21 @@
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
import { getDetailCaseReviewPage } from '@/api/modules/case-management/featureCase';
import { reviewStatusMap } from '@/config/caseManagement';
import { useI18n } from '@/hooks/useI18n';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import { ReviewCaseItem } from '@/models/caseManagement/caseReview';
import { ReviewCaseItem, ReviewStatus } from '@/models/caseManagement/caseReview';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { statusIconMap } from '../utils';
const featureCaseStore = useFeatureCaseStore();
const router = useRouter();
const route = useRoute();
// const activeTab = computed(() => featureCaseStore.activeTab);
const { t } = useI18n();
const props = defineProps<{
@ -62,6 +74,15 @@
}>();
const keyword = ref<string>('');
// TODO
const reviewStatusOptions = computed(() => {
return Object.keys(reviewStatusMap).map((key) => {
return {
value: key,
label: reviewStatusMap[key as ReviewStatus].label,
};
});
});
const columns: MsTableColumn = [
{
@ -84,8 +105,13 @@
},
{
title: 'caseManagement.caseReview.status',
dataIndex: 'reviewStatus',
dataIndex: 'status',
slotName: 'reviewStatus',
// TODO
// filterConfig: {
// options: reviewStatusOptions.value,
// filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_STATUS,
// },
width: 150,
},
{

View File

@ -19,89 +19,14 @@
<template #planStatus="{ record }">
<MsStatusTag :status="record.planStatus" />
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
@click="statusFilterVisible = true"
>
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(planStatusMap)" :key="key" :value="key">
<a-tag
:color="planStatusMap[key as planStatusType].color"
:class="[planStatusMap[key as planStatusType].class, 'px-[4px]']"
size="small"
>
{{ t(planStatusMap[key as planStatusType].label) }}
</a-tag>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
</template>
<template #lastExecResult="{ record }">
<execute-result :execute-result="record.lastExecResult" />
</template>
<template #lastExecResultFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="lastExecResultVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
>
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
@click="lastExecResultVisible = true"
>
{{ t(columnConfig.title as string) }}
<icon-down :class="lastExecResultVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="lastExecResultFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(executionResultMap)" :key="key" :value="key">
<MsIcon
:type="executionResultMap[key]?.icon || ''"
class="mr-1"
:class="[executionResultMap[key].color]"
></MsIcon>
<span>{{ executionResultMap[key]?.statusText || '' }} </span>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetLastExecuteResultFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT]="{ filterContent }">
<execute-result :execute-result="filterContent.value" />
</template>
<template #[FilterSlotNameEnum.TEST_PLAN_STATUS_FILTER]="{ filterContent }">
<MsStatusTag :status="filterContent.value" />
</template>
</ms-base-table>
</div>
@ -126,6 +51,7 @@
import { AssociateFunctionalCaseItem, planStatusType } from '@/models/testPlan/testPlan';
import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
import { planStatusMap } from '@/views/test-plan/testPlan/config';
@ -139,6 +65,23 @@
caseId: string; // id
}>();
const executeResultOptions = computed(() => {
return Object.keys(executionResultMap).map((key) => {
return {
value: key,
label: executionResultMap[key].statusText,
};
});
});
const planStatusOptions = computed(() => {
return Object.keys(planStatusMap).map((key) => {
return {
value: key,
label: planStatusMap[key as planStatusType].label,
};
});
});
const columns: MsTableColumn = [
{
title: 'ID',
@ -163,14 +106,20 @@
title: 'caseManagement.featureCase.planStatus',
slotName: 'planStatus',
dataIndex: 'planStatus',
titleSlotName: 'statusFilter',
filterConfig: {
options: planStatusOptions.value,
filterSlotName: FilterSlotNameEnum.TEST_PLAN_STATUS_FILTER,
},
width: 200,
},
{
title: 'caseManagement.featureCase.tableColumnExecutionResult',
slotName: 'lastExecResult',
dataIndex: 'lastExecResult',
titleSlotName: 'lastExecResultFilter',
filterConfig: {
options: executeResultOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_EXECUTE_RESULT,
},
width: 200,
},
{
@ -187,12 +136,6 @@
},
];
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const lastExecResultVisible = ref(false);
const lastExecResultFilters = ref<string[]>([]);
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
getLinkedCaseTestPlanList,
{
@ -214,10 +157,6 @@
setLoadListParams({
keyword: keyword.value,
caseId: props.caseId,
filter: {
planStatus: statusFilters.value,
lastExecResult: lastExecResultFilters.value,
},
});
await loadList();
}
@ -226,26 +165,6 @@
initData();
}, 100);
function handleFilterHidden(val: boolean) {
if (!val) {
statusFilterVisible.value = false;
lastExecResultVisible.value = false;
searchList();
}
}
function resetStatusFilter() {
statusFilterVisible.value = false;
statusFilters.value = [];
searchList();
}
function resetLastExecuteResultFilter() {
lastExecResultVisible.value = false;
lastExecResultFilters.value = [];
searchList();
}
//
function goToPlan(record: AssociateFunctionalCaseItem) {
router.push({

View File

@ -1,15 +1,24 @@
<template>
<!-- TODO 目前有一部分表格筛选在用 后边这个也要替换 暂时先修改了icon统一 -->
<a-trigger v-model:popup-visible="innerVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
:class="`${
innerVisible ? '!bg-[rgb(var(--primary-1))]' : ''
} arco-btn-text--secondary no-hover p-[8px_4px] text-[14px]
`"
size="mini"
@click.stop="innerVisible = true"
>
<div class="font-medium">
<div :class="`${innerVisible ? 'filter-title' : ''} flex items-center pr-[2px] font-medium`">
{{ t(props.title) }}
</div>
<icon-down :class="innerVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
<svg-icon
width="16px"
height="16px"
:name="innerVisible ? 'filter-icon-color' : 'filter-icon'"
class="text-[12px] font-medium"
/>
</a-button>
<template #content>
<div class="arco-table-filters-content">
@ -116,4 +125,13 @@
}
</script>
<style scoped></style>
<style scoped>
.filter-title {
border-radius: 4px;
color: rgb(var(--primary-5));
background: rgb(var(--primary-1)) content-box;
.filter-icon {
color: rgb(var(--primary-5)) !important;
}
}
</style>

View File

@ -32,49 +32,14 @@
@batch-action="handleTableBatch"
@module-change="searchReview"
>
<template #statusFilter="{ columnConfig }">
<a-trigger
v-model:popup-visible="statusFilterVisible"
trigger="click"
@popup-visible-change="handleFilterHidden"
<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"
>
<a-button
type="text"
class="arco-btn-text--secondary p-[8px_4px] text-[14px]"
size="mini"
@click="statusFilterVisible = true"
>
<div class="font-medium">
{{ t(columnConfig.title as string) }}
</div>
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
</a-button>
<template #content>
<div class="arco-table-filters-content">
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(reviewStatusMap)" :key="key" :value="key">
<a-tag
:color="reviewStatusMap[key as ReviewStatus].color"
:class="[reviewStatusMap[key as ReviewStatus].class, 'px-[4px]']"
size="small"
>
{{ t(reviewStatusMap[key as ReviewStatus].label) }}
</a-tag>
</a-checkbox>
</a-checkbox-group>
</div>
<div class="filter-button">
<a-button size="mini" class="mr-[8px]" @click="resetStatusFilter">
{{ t('common.reset') }}
</a-button>
<a-button type="primary" size="mini" @click="handleFilterHidden(false)">
{{ t('system.orgTemplate.confirm') }}
</a-button>
</div>
</div>
</template>
</a-trigger>
{{ t(reviewStatusMap[filterContent.value as ReviewStatus].label) }}
</a-tag>
</template>
<template #reviewersFilter="{ columnConfig }">
<TableFilter
@ -245,6 +210,7 @@
import { ModuleTreeNode } from '@/models/common';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
const props = defineProps<{
activeFolder: string;
@ -280,6 +246,15 @@
const innerShowType = useVModel(props, 'showType', emit);
const reviewStatusOptions = computed(() => {
return Object.keys(reviewStatusMap).map((key) => {
return {
value: key,
label: reviewStatusMap[key as ReviewStatus].label,
};
});
});
onBeforeMount(async () => {
try {
const [userRes, memberRes] = await Promise.all([
@ -447,7 +422,10 @@
title: 'caseManagement.caseReview.status',
dataIndex: 'status',
slotName: 'status',
titleSlotName: 'statusFilter',
filterConfig: {
options: reviewStatusOptions.value,
filterSlotName: FilterSlotNameEnum.CASE_MANAGEMENT_REVIEW_STATUS,
},
showDrag: true,
width: 150,
},
@ -469,7 +447,14 @@
title: 'caseManagement.caseReview.reviewer',
slotName: 'reviewers',
dataIndex: 'reviewers',
titleSlotName: 'reviewersFilter',
filterConfig: {
mode: 'remote',
loadOptionParams: {
projectId: appStore.currentProjectId,
},
remoteMethod: FilterRemoteMethodsEnum.PROJECT_PERMISSION_MEMBER,
placeholderText: t('caseManagement.caseReview.reviewerPlaceholder'),
},
showDrag: true,
width: 150,
},
@ -573,7 +558,6 @@
moduleIds,
createByMe: innerShowType.value === 'createByMe' ? userStore.id : undefined,
reviewByMe: innerShowType.value === 'reviewByMe' ? userStore.id : undefined,
filter: { status: statusFilters.value, reviewers: reviewersFilters.value },
combine: filter
? {
...filter.combine,
@ -712,7 +696,7 @@
currentSelectCount: batchParams.value?.currentSelectCount || 0,
condition: {
keyword: keyword.value,
filter: { status: statusFilters.value, reviewers: reviewersFilters.value },
filter: propsRes.value.filter,
combine: batchParams.value.condition,
},
};