fix(测试计划): 部分 bug 修复

This commit is contained in:
baiqi 2024-05-23 16:20:52 +08:00 committed by 刘瑞斌
parent 1aeccb44d5
commit 71a35083b3
14 changed files with 186 additions and 124 deletions

View File

@ -1,10 +1,5 @@
<template>
<a-spin
class="z-[100] !block"
:class="props.autoHeight ? '' : 'h-full min-h-[500px]'"
:loading="props.loading"
:size="28"
>
<a-spin class="z-[100] !block" :class="props.autoHeight ? '' : 'min-h-[500px]'" :loading="props.loading" :size="28">
<div
ref="fullRef"
:class="[

View File

@ -7,7 +7,12 @@
:indeterminate="indeterminate"
@change="handleCheckChange"
/>
<a-dropdown v-if="props.showSelectAll" :disable="props.disabled" position="bl" @select="handleSelect">
<a-dropdown
v-if="props.showSelectAll"
:disable="props.disabled"
position="bl"
@select="(v) => handleSelect(v as SelectAllEnum)"
>
<div>
<MsIcon type="icon-icon_down_outlined" class="dropdown-icon" />
</div>
@ -67,17 +72,29 @@
},
});
const indeterminate = computed(() => {
// key 0
// key 0
return (
selectAllStatus.value !== SelectAllEnum.ALL &&
props.selectedKeys.size > 0 &&
props.selectedKeys.size < props.total
(props.excludeKeys.length > 0 && selectAllStatus.value === SelectAllEnum.ALL) ||
(selectAllStatus.value !== SelectAllEnum.ALL &&
props.selectedKeys.size > 0 &&
props.selectedKeys.size < props.total)
);
});
const handleSelect = (v: string | number | Record<string, any> | undefined) => {
selectAllStatus.value = v as SelectAllEnum;
emit('change', v as SelectAllEnum);
const handleSelect = (v: SelectAllEnum) => {
if (
(selectAllStatus.value === SelectAllEnum.ALL &&
v === SelectAllEnum.NONE &&
props.excludeKeys.length < props.total) ||
(selectAllStatus.value === SelectAllEnum.ALL && v === SelectAllEnum.CURRENT)
) {
//
//
selectAllStatus.value = SelectAllEnum.ALL;
} else {
selectAllStatus.value = v;
}
emit('change', v);
};
function hasUnselectedChildren(
@ -102,6 +119,13 @@
handleSelect(SelectAllEnum.NONE);
}
};
watchEffect(() => {
//
if (props.excludeKeys.length === 0 && props.selectedKeys.size === 0) {
selectAllStatus.value = SelectAllEnum.NONE;
}
});
</script>
<style lang="less" scoped>

View File

@ -308,7 +308,11 @@ export default function useTableProps<T>(
// 取消当前页的选中项
propsRes.value.data.forEach((item) => {
propsRes.value.selectedKeys.delete(item[rowKey]);
propsRes.value.excludeKeys.delete(item[rowKey]);
if (propsRes.value.selectorStatus === SelectAllEnum.ALL) {
propsRes.value.excludeKeys.add(item[rowKey]);
} else {
propsRes.value.excludeKeys.delete(item[rowKey]);
}
});
}
};
@ -334,6 +338,7 @@ export default function useTableProps<T>(
data.forEach((item: MsTableDataItem<T>) => {
if (item[rowKey] && !propsRes.value.selectedKeys.has(item[rowKey])) {
propsRes.value.selectedKeys.add(item[rowKey]);
propsRes.value.excludeKeys.delete(item[rowKey]);
}
if (item.children) {
collectIds(item.children, rowKey);
@ -426,7 +431,19 @@ export default function useTableProps<T>(
propsRes.value.excludeKeys.clear();
collectIds(data as MsTableDataItem<T>[], rowKey);
}
propsRes.value.selectorStatus = v;
if (
(propsRes.value.selectorStatus === SelectAllEnum.ALL &&
v === SelectAllEnum.NONE &&
propsRes.value.msPagination &&
propsRes.value.excludeKeys.size < propsRes.value.msPagination.total) ||
(propsRes.value.selectorStatus === SelectAllEnum.ALL && v === SelectAllEnum.CURRENT)
) {
// 如果当前是全选所有页状态,且是取消选中当前页操作,且排除项小于总数,则保持跨页全选状态
// 如果当前是全选所有页状态,且是选中当前页操作,则保持跨页全选状态
propsRes.value.selectorStatus = SelectAllEnum.ALL;
} else {
propsRes.value.selectorStatus = v;
}
},
// 表格行的选中/取消事件

View File

@ -1,23 +1,24 @@
<template>
<div class="ms-time-selector">
<a-input-number
v-model:model-value="current.value"
v-model:model-value="numberValue"
class="w-[120px]"
:min="0"
hide-button
size="small"
:disabled="props.disabled"
@press-enter="handleEnter(false)"
@blur="handleEnter(false)"
@press-enter="changeNumber"
@blur="changeNumber"
>
<template #suffix>
<a-select
v-model:model-value="current.type"
v-model:model-value="typeValue"
size="small"
class="max-w-[64px]"
:disabled="props.disabled"
:options="option"
:trigger-props="{ autoFitPopupMinWidth: true }"
@change="(val) => changeType(val as string)"
/>
</template>
</a-input-number>
@ -29,48 +30,49 @@
const { t } = useI18n();
const props = defineProps<{ modelValue?: string; defaultValue?: string; disabled?: boolean }>();
const props = defineProps<{ defaultValue?: string; disabled?: boolean }>();
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void;
(e: 'change', value: string): void;
}>();
// enter
const isEnter = ref<boolean>(false);
const modelValue = defineModel<string>('modelValue', {
default: '',
});
function parseValue(v?: string) {
// 使
if (!v) {
return { type: 'H', value: undefined };
return { type: 'H', value: 0 };
}
const match = v.match(/^(\d+)([MYHD])$/);
const match = v.match(/^(\d+(\.\d+)?)([MYHD])$/);
if (match) {
const value = parseInt(match[1], 10); //
const type = match[2]; //
const type = match[3]; //
return { type, value };
}
//
return { type: 'H', value: undefined };
return { type: 'H', value: 0 };
}
const current = reactive(parseValue(props.modelValue || props.defaultValue));
const numberValue = ref(0);
const typeValue = ref('H');
const handleEnter = (isBlur: boolean) => {
if (isBlur) {
if (!isEnter.value) {
// Enterblur
const { value } = parseValue(props.modelValue || props.defaultValue);
current.value = value;
}
isEnter.value = false;
} else {
// Enter
const result = current.value ? `${current.value}${current.type}` : '';
emit('update:modelValue', current.value ? `${current.value}${current.type}` : '');
emit('change', result);
isEnter.value = true;
}
};
const option = computed(() => [
function initNumberAndType() {
const { value, type } = parseValue(modelValue.value);
numberValue.value = value;
typeValue.value = type;
}
function changeNumber() {
const result =
numberValue.value === undefined ? props.defaultValue || '' : `${numberValue.value}${typeValue.value}`;
modelValue.value = result;
nextTick(() => {
initNumberAndType();
});
emit('change', modelValue.value);
}
const option = [
{
label: t('msTimeSelector.hour'),
value: 'H',
@ -87,15 +89,19 @@
label: t('msTimeSelector.year'),
value: 'Y',
},
]);
watch(
() => props.modelValue,
(v) => {
const { value, type } = parseValue(v);
current.value = value;
current.type = type;
}
);
];
function changeType(val: string) {
const result = numberValue.value === undefined ? props.defaultValue || '' : `${numberValue.value}${val}`;
modelValue.value = result;
nextTick(() => {
initNumberAndType();
});
}
onBeforeMount(() => {
initNumberAndType();
});
</script>
<style lang="less" scoped>

View File

@ -183,4 +183,5 @@ export default {
'common.updateUserName': 'Update user name',
'common.updateTime': 'Update time',
'common.belongProject': 'Belong to Project',
'common.noMatchData': 'No matching data',
};

View File

@ -184,4 +184,5 @@ export default {
'common.updateUserName': '更新人',
'common.updateTime': '更新时间',
'common.belongProject': '所属项目',
'common.noMatchData': '暂无匹配数据',
};

View File

@ -254,25 +254,26 @@
return false;
}
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
reportList,
{
tableKey: TableKeyEnum.API_TEST_REPORT,
scroll: {
x: '100%',
const { propsRes, propsEvent, loadList, setLoadListParams, setPagination, resetSelector, resetFilterParams } =
useTable(
reportList,
{
tableKey: TableKeyEnum.API_TEST_REPORT,
scroll: {
x: '100%',
},
showSetting: true,
selectable: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']),
heightUsed: 256,
paginationSize: 'mini',
showSelectorAll: true,
},
showSetting: true,
selectable: hasAnyPermission(['PROJECT_API_REPORT:READ+DELETE']),
heightUsed: 256,
paginationSize: 'mini',
showSelectorAll: true,
},
(item) => ({
...item,
startTime: dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
}),
rename
);
(item) => ({
...item,
startTime: dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
}),
rename
);
const typeFilter = computed(() => {
if (showType.value === 'All') {
@ -394,6 +395,10 @@
showType.value = val as ReportShowType;
resetFilterParams();
resetSelector();
//
setPagination({
current: 1,
});
initData();
}
@ -436,6 +441,7 @@
shareTime.value = value + (translations[type] || translations.D);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}

View File

@ -6,7 +6,7 @@ export default {
'caseManagement.featureCase.importSuccess': 'Import successfully',
'caseManagement.featureCase.publicCase': 'Public of Cases',
'caseManagement.featureCase.allCase': 'All of Cases',
'caseManagement.featureCase.searchTip': 'Please enter a group name',
'caseManagement.featureCase.searchTip': 'Please enter a module name',
'caseManagement.featureCase.caseEmptyContent':
'No use case data yet, please click the button above to create or import',
'caseManagement.featureCase.addSubModule': 'Add submodules',

View File

@ -6,7 +6,7 @@ export default {
'caseManagement.featureCase.importSuccess': '导入成功',
'caseManagement.featureCase.publicCase': '公共用例库',
'caseManagement.featureCase.allCase': '全部用例',
'caseManagement.featureCase.searchTip': '请输入分组名称',
'caseManagement.featureCase.searchTip': '请输入模块名称',
'caseManagement.featureCase.caseEmptyContent': '暂无用例数据,请点击上方按钮创建或导入',
'caseManagement.featureCase.caseEmptyRecycle': '暂无用例数据',
'caseManagement.featureCase.addSubModule': '添加子模块',

View File

@ -90,8 +90,7 @@ export default {
'caseManagement.caseReview.reviewResultTip':
'When "See only mine" is turned on, you can view my review results on the list',
'caseManagement.caseReview.disassociate': 'Disassociate',
'caseManagement.caseReview.disassociateConfirmTitle':
'Are you sure you want to cancel the selected {count} test cases?',
'caseManagement.caseReview.disassociateConfirmTitle': 'Are you sure to disassociate {count} use cases?',
'caseManagement.caseReview.version': 'Version',
'caseManagement.caseReview.unReview': 'Unreviewed',
'caseManagement.caseReview.reviewPass': 'Review passed',

View File

@ -83,7 +83,7 @@ export default {
'caseManagement.caseReview.reviewResult': '评审结果',
'caseManagement.caseReview.reviewResultTip': '“只看我的”开启时,可在列表上查看我的评审结果',
'caseManagement.caseReview.disassociate': '取消关联',
'caseManagement.caseReview.disassociateConfirmTitle': '确认取消已选的 {count} 条用例吗?',
'caseManagement.caseReview.disassociateConfirmTitle': '确认取消关联 {count} 条用例吗?',
'caseManagement.caseReview.version': '版本',
'caseManagement.caseReview.unReview': '未评审',
'caseManagement.caseReview.reviewPass': '已通过',

View File

@ -29,25 +29,27 @@
<div v-if="record.type === 'TEST_PLAN_CLEAN_REPORT'">
<!-- 测试计划 报告保留时间范围 -->
<MsTimeSelectorVue
v-model="allValueMap['TEST_PLAN_CLEAN_REPORT']"
v-model:model-value="allValueMap.TEST_PLAN_CLEAN_REPORT"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_TEST_PLAN:UPDATE'])"
:default-value="defaultValueMap.TEST_PLAN_CLEAN_REPORT"
@change="(v: string) => handleMenuStatusChange('TEST_PLAN_CLEAN_REPORT',v,MenuEnum.testPlan)"
/>
</div>
<div v-if="record.type === 'TEST_PLAN_SHARE_REPORT'">
<!-- 测试计划 报告链接有效期 -->
<MsTimeSelectorVue
v-model="allValueMap['TEST_PLAN_SHARE_REPORT']"
v-model:model-value="allValueMap.TEST_PLAN_SHARE_REPORT"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_TEST_PLAN:UPDATE'])"
:default-value="defaultValueMap.TEST_PLAN_SHARE_REPORT"
@change="(v: string) => handleMenuStatusChange('TEST_PLAN_SHARE_REPORT',v,MenuEnum.testPlan)"
/>
</div>
<template v-if="record.type === 'BUG_SYNC'">
<!-- 同步缺陷 -->
<span>{{ t('project.menu.row2') }}</span>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showDefectDrawer">{{
t('project.menu.BUG_SYNC')
}}</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showDefectDrawer">
{{ t('project.menu.BUG_SYNC') }}
</div>
</template>
<div v-if="record.type === 'CASE_PUBLIC'">
<!-- 用例 公共用例库 -->
@ -56,9 +58,9 @@
<div v-if="record.type === 'CASE_RELATED'" class="flex flex-row">
<!-- 用例 关联需求 -->
<div>{{ t('project.menu.row4') }}</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showRelatedCaseDrawer">{{
t('project.menu.CASE_RELATED')
}}</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="showRelatedCaseDrawer">
{{ t('project.menu.CASE_RELATED') }}
</div>
</div>
<div v-if="record.type === 'CASE_RE_REVIEW'">
<!-- 用例 重新提审 -->
@ -72,6 +74,7 @@
<MsTimeSelectorVue
v-model="allValueMap['API_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:UPDATE'])"
:default-value="defaultValueMap.API_CLEAN_REPORT"
@change="(v: string) => handleMenuStatusChange('API_CLEAN_REPORT',v,MenuEnum.apiTest)"
/>
</div>
@ -80,6 +83,7 @@
<MsTimeSelectorVue
v-model="allValueMap['API_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_API:UPDATE'])"
:default-value="defaultValueMap.API_SHARE_REPORT"
@change="(v: string) => handleMenuStatusChange('API_SHARE_REPORT',v,MenuEnum.apiTest)"
/>
</div>
@ -142,9 +146,9 @@
</template>
</a-input-number>
</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="pushFar">{{
t('project.menu.API_ERROR_REPORT_RULE')
}}</div>
<div class="ml-[8px] cursor-pointer text-[rgb(var(--primary-7))]" @click="pushFar">
{{ t('project.menu.API_ERROR_REPORT_RULE') }}
</div>
<a-tooltip :content="t('project.menu.API_ERROR_REPORT_RULE_TIP')" position="right">
<div>
<MsIcon
@ -160,6 +164,7 @@
<MsTimeSelectorVue
v-model="allValueMap['UI_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_UI:UPDATE'])"
:default-value="defaultValueMap.UI_CLEAN_REPORT"
@change="(v: string) => handleMenuStatusChange('UI_CLEAN_REPORT',v,MenuEnum.uiTest)"
@blur="(v: string) => handleMenuStatusChange('UI_CLEAN_REPORT',v,MenuEnum.uiTest)"
/>
@ -169,6 +174,7 @@
<MsTimeSelectorVue
v-model="allValueMap['UI_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_UI:UPDATE'])"
:default-value="defaultValueMap.UI_SHARE_REPORT"
@change="(v: string) => handleMenuStatusChange('UI_SHARE_REPORT',v,MenuEnum.uiTest)"
/>
</div>
@ -203,6 +209,7 @@
<MsTimeSelectorVue
v-model="allValueMap['PERFORMANCE_TEST_CLEAN_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:UPDATE'])"
:default-value="defaultValueMap.PERFORMANCE_TEST_CLEAN_REPORT"
@change="(v: string) => handleMenuStatusChange('PERFORMANCE_TEST_CLEAN_REPORT',v,MenuEnum.loadTest)"
/>
</div>
@ -211,6 +218,7 @@
<MsTimeSelectorVue
v-model="allValueMap['PERFORMANCE_TEST_SHARE_REPORT']"
:disabled="!hasAnyPermission(['PROJECT_APPLICATION_PERFORMANCE_TEST:UPDATE'])"
:default-value="defaultValueMap.PERFORMANCE_TEST_SHARE_REPORT"
@change="(v: string) => handleMenuStatusChange('PERFORMANCE_TEST_SHARE_REPORT',v,MenuEnum.loadTest)"
/>
</div>
@ -241,9 +249,9 @@
<template #content>
<span>
{{ t('project.menu.notConfig') }}
<span class="cursor-pointer text-[rgb(var(--primary-4))]" @click="showDefectDrawer">{{
t(`project.menu.${record.type}`)
}}</span>
<span class="cursor-pointer text-[rgb(var(--primary-4))]" @click="showDefectDrawer">
{{ t(`project.menu.${record.type}`) }}
</span>
{{ t('project.menu.configure') }}
</span>
</template>
@ -389,6 +397,7 @@
*/
import { useRouter } from 'vue-router';
import { Message, TableData } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -445,7 +454,7 @@
CASE_RELATED_CASE_ENABLE: false,
};
const allValueMap = ref<MenuTableConfigItem>(defaultValueMap);
const allValueMap = ref<MenuTableConfigItem>(cloneDeep(defaultValueMap));
const hasTitleColumns = [
{

View File

@ -32,13 +32,14 @@
@batch-action="handleTableBatch"
@filter-change="filterChange"
>
<template #name="{ record, rowIndex }">
<template #name="{ record }">
<div
type="text"
class="one-line-text flex w-full text-[rgb(var(--primary-5))]"
@click="showReportDetail(record.id, rowIndex)"
>{{ characterLimit(record.name) }}</div
@click="showReportDetail(record.id)"
>
{{ characterLimit(record.name) }}
</div>
</template>
<!-- 通过率 -->
@ -82,8 +83,9 @@
v-permission="['PROJECT_TEST_PLAN_REPORT:READ+DELETE']"
class="!mr-0"
@click="handleDelete(record.id, record.name)"
>{{ t('ms.comment.delete') }}</MsButton
>
{{ t('ms.comment.delete') }}
</MsButton>
</template>
</ms-base-table>
</div>
@ -280,25 +282,26 @@
return false;
}
};
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams } = useTable(
reportList,
{
tableKey: TableKeyEnum.TEST_PLAN_REPORT_TABLE,
scroll: {
x: '100%',
const { propsRes, propsEvent, loadList, setLoadListParams, setPagination, resetSelector, resetFilterParams } =
useTable(
reportList,
{
tableKey: TableKeyEnum.TEST_PLAN_REPORT_TABLE,
scroll: {
x: '100%',
},
showSetting: true,
selectable: hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+DELETE']),
heightUsed: 242,
paginationSize: 'mini',
showSelectorAll: true,
},
showSetting: true,
selectable: hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+DELETE']),
heightUsed: 242,
paginationSize: 'mini',
showSelectorAll: true,
},
(item) => ({
...item,
startTime: dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
}),
rename
);
(item) => ({
...item,
startTime: dayjs(item.startTime).format('YYYY-MM-DD HH:mm:ss'),
}),
rename
);
function initData(dataIndex?: string, value?: string[] | (string | number | boolean)[] | undefined) {
const filterParams = {
@ -406,11 +409,11 @@
function changeShowType(val: string | number | boolean) {
showType.value = val as ReportShowType;
resetFilterParams();
resetSelector();
propsRes.value.filter = {
integrated: integratedFilters.value,
};
initData();
//
setPagination({
current: 1,
});
searchList();
}
function filterChange(dataIndex: string, value: string[] | (string | number | boolean)[] | undefined) {
@ -420,7 +423,7 @@
/**
* 报告详情 showReportDetail
*/
function showReportDetail(id: string, rowIndex: number) {
function showReportDetail(id: string) {
router.push({
name: TestPlanRouteEnum.TEST_PLAN_REPORT_DETAIL,
query: {

View File

@ -7,7 +7,7 @@
:keyword="moduleKeyword"
:node-more-actions="caseMoreActions"
:expand-all="props.isExpandAll"
:empty-text="t('testPlan.testPlanIndex.planEmptyContent')"
:empty-text="t('common.noMatchData')"
:draggable="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE'])"
:virtual-list-props="virtualListProps"
block-node
@ -36,11 +36,12 @@
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+ADD'])"
:visible="addSubVisible"
:is-delete="false"
:all-names="[]"
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
:title="t('testPlan.testPlanIndex.addSubModule')"
:ok-text="t('common.confirm')"
:field-config="{
placeholder: t('testPlan.testPlanIndex.addGroupTip'),
nameExistTipText: t('project.fileManagement.nameExist'),
}"
:loading="confirmLoading"
@confirm="addSubModule"
@ -53,7 +54,7 @@
<MsPopConfirm
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+UPDATE'])"
:title="t('testPlan.testPlanIndex.rename')"
:all-names="[]"
:all-names="(nodeData.parent? nodeData.parent.children || [] : testPlanTree).filter((e: ModuleTreeNode) => e.id !== nodeData.id).map((e: ModuleTreeNode) => e.name || '')"
:is-delete="false"
:ok-text="t('common.confirm')"
:field-config="{ field: renameCaseName }"