fix(缺陷管理): 缺陷全选批量操作参数有误

--bug=1039729 --user=宋昌昌 【缺陷管理】回收站-全选所有页-筛选删除人/处理人/状态/创建人/更新人/严重程度/搜索框搜索-批量操作-把所有缺陷都操作了 https://www.tapd.cn/55049933/s/1506497
--bug=1039725 --user=宋昌昌 【缺陷管理】缺陷-全选所有页-筛选状态/处理人/创建人/更新人/严重程度/搜索框搜索-批量操作-把所有的缺陷都操作了 https://www.tapd.cn/55049933/s/1506957
--bug=1039927 --user=宋昌昌 【项目管理】项目与权限-成员-批量添加至用户组-SQL报错 https://www.tapd.cn/55049933/s/1506849
This commit is contained in:
song-cc-rock 2024-04-23 18:06:36 +08:00 committed by 刘瑞斌
parent 6805e8cf06
commit f78286eaf5
8 changed files with 156 additions and 106 deletions

View File

@ -1511,6 +1511,12 @@ public class BugService {
BugPageRequest bugPageRequest = new BugPageRequest(); BugPageRequest bugPageRequest = new BugPageRequest();
BeanUtils.copyBean(bugPageRequest, request); BeanUtils.copyBean(bugPageRequest, request);
bugPageRequest.setUseTrash(false); bugPageRequest.setUseTrash(false);
if (request.getCondition() != null) {
bugPageRequest.setCombine(request.getCondition().getCombine());
bugPageRequest.setFilter(request.getCondition().getFilter());
bugPageRequest.setSearchMode(request.getCondition().getSearchMode());
bugPageRequest.setKeyword(request.getCondition().getKeyword());
}
List<BugDTO> allBugs = extBugMapper.list(bugPageRequest, request.getSort()); List<BugDTO> allBugs = extBugMapper.list(bugPageRequest, request.getSort());
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {
allBugs.removeIf(bug -> request.getExcludeIds().contains(bug.getId())); allBugs.removeIf(bug -> request.getExcludeIds().contains(bug.getId()));
@ -1538,6 +1544,8 @@ public class BugService {
if (request.getCondition() != null) { if (request.getCondition() != null) {
bugPageRequest.setCombine(request.getCondition().getCombine()); bugPageRequest.setCombine(request.getCondition().getCombine());
bugPageRequest.setFilter(request.getCondition().getFilter()); bugPageRequest.setFilter(request.getCondition().getFilter());
bugPageRequest.setSearchMode(request.getCondition().getSearchMode());
bugPageRequest.setKeyword(request.getCondition().getKeyword());
} }
List<String> ids = extBugMapper.getIdsByPageRequest(bugPageRequest); List<String> ids = extBugMapper.getIdsByPageRequest(bugPageRequest);
if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { if (CollectionUtils.isNotEmpty(request.getExcludeIds())) {

View File

@ -2,7 +2,7 @@
<MsDrawer <MsDrawer
v-model:visible="visible" v-model:visible="visible"
:ok-text="t('common.export')" :ok-text="t('common.export')"
:ok-loading="drawerLoading" :ok-loading="exportLoading"
:width="800" :width="800"
min-width="800px" min-width="800px"
unmount-on-close unmount-on-close
@ -36,8 +36,8 @@
class="mt-[8px] w-[95px] pl-[0px]" class="mt-[8px] w-[95px] pl-[0px]"
:disabled="item.key === 'name'" :disabled="item.key === 'name'"
> >
<a-tooltip :content="item.text"> <a-tooltip :content="item.text" position="top">
<span class="one-line-text">{{ item.text }}</span> <div class="one-line-text max-w-[80px]">{{ item.text }}</div>
</a-tooltip> </a-tooltip>
</a-checkbox> </a-checkbox>
</div> </div>
@ -51,8 +51,8 @@
:value="item.key" :value="item.key"
class="mt-[8px] w-[95px] pl-[0px]" class="mt-[8px] w-[95px] pl-[0px]"
> >
<a-tooltip :content="item.text"> <a-tooltip :content="item.text" position="top">
<span class="one-line-text">{{ item.text }}</span> <div class="one-line-text max-w-[80px]">{{ item.text }}</div>
</a-tooltip> </a-tooltip>
</a-checkbox> </a-checkbox>
</div> </div>
@ -71,10 +71,8 @@
:value="item.key" :value="item.key"
class="mt-[8px] w-[95px] pl-[0px]" class="mt-[8px] w-[95px] pl-[0px]"
> >
<a-tooltip :content="item.text"> <a-tooltip :content="item.text" position="top">
<div class="one-line-text"> <div class="one-line-text max-w-[80px]">{{ item.text }}</div>
{{ item.text }}
</div>
</a-tooltip> </a-tooltip>
</a-checkbox> </a-checkbox>
</div> </div>
@ -146,6 +144,7 @@
defaultSelectedKeys?: string[]; defaultSelectedKeys?: string[];
isArrayColumn?: boolean; isArrayColumn?: boolean;
arrayColumn?: EnvListItem[]; arrayColumn?: EnvListItem[];
exportLoading: boolean;
titleProps?: { titleProps?: {
selectableTitle: string; // selectableTitle: string; //
systemTitle: string; // | systemTitle: string; // |
@ -155,6 +154,7 @@
const props = withDefaults(defineProps<MsExportDrawerProps>(), { const props = withDefaults(defineProps<MsExportDrawerProps>(), {
visible: false, visible: false,
exportLoading: false,
defaultSelectedKeys: () => ['name', 'id', 'title', 'status', 'handle_user', 'content'], defaultSelectedKeys: () => ['name', 'id', 'title', 'status', 'handle_user', 'content'],
}); });
@ -163,6 +163,7 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:visible', value: boolean): void; (e: 'update:visible', value: boolean): void;
(e: 'update:exportLoading', value: boolean): void;
(e: 'confirm', value: MsExportDrawerOption[]): void; (e: 'confirm', value: MsExportDrawerOption[]): void;
}>(); }>();
@ -175,6 +176,15 @@
}, },
}); });
const exportLoading = computed({
get() {
return props.exportLoading;
},
set(value) {
emit('update:exportLoading', value);
},
});
const systemList = computed(() => { const systemList = computed(() => {
if (props.isArrayColumn && props.arrayColumn) { if (props.isArrayColumn && props.arrayColumn) {
return props.arrayColumn.map((item) => { return props.arrayColumn.map((item) => {
@ -310,4 +320,9 @@
border: 1px dashed rgba(var(--primary-5)); border: 1px dashed rgba(var(--primary-5));
background-color: rgba(var(--primary-1)); background-color: rgba(var(--primary-1));
} }
:deep(.arco-checkbox-group .arco-checkbox) {
margin-right: 20px;
margin-top: 10px;
}
</style> </style>

View File

@ -150,7 +150,8 @@ export interface SetPaginationPrams {
export interface BatchActionQueryParams { export interface BatchActionQueryParams {
excludeIds?: string[]; // 排除的id excludeIds?: string[]; // 排除的id
selectedIds?: string[]; // 选中的id selectedIds?: string[];
selectIds?: string[]; // 选中的id
selectAll: boolean; // 是否跨页全选 selectAll: boolean; // 是否跨页全选
params?: TableQueryParams; // 查询参数 params?: TableQueryParams; // 查询参数
currentSelectCount?: number; // 当前选中的数量 currentSelectCount?: number; // 当前选中的数量

View File

@ -123,7 +123,6 @@
import { updateBatchBug } from '@/api/modules/bug-management'; import { updateBatchBug } from '@/api/modules/bug-management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { tableParamsToRequestParams } from '@/utils';
import type { BugBatchUpdateFiledType } from '@/models/bug-management'; import type { BugBatchUpdateFiledType } from '@/models/bug-management';
import { BugBatchUpdateFiledForm, BugEditCustomField } from '@/models/bug-management'; import { BugBatchUpdateFiledForm, BugEditCustomField } from '@/models/bug-management';
@ -221,7 +220,7 @@
try { try {
loading.value = true; loading.value = true;
const tmpObj = { const tmpObj = {
...tableParamsToRequestParams(props.selectParam), ...props.selectParam,
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
[form.attribute]: form.value || form.inputValue, [form.attribute]: form.value || form.inputValue,
append: form.append, append: form.append,

View File

@ -190,7 +190,12 @@
<a-date-picker v-model="syncObject.time" value-format="timestamp" show-time class="w-[304px]" /> <a-date-picker v-model="syncObject.time" value-format="timestamp" show-time class="w-[304px]" />
</div> </div>
</a-modal> </a-modal>
<MsExportDrawer v-model:visible="exportVisible" :all-data="exportOptionData" @confirm="exportConfirm"> <MsExportDrawer
v-model:visible="exportVisible"
:export-loading="exportLoading"
:all-data="exportOptionData"
@confirm="exportConfirm"
>
<template #title> <template #title>
<span class="text-[var(--color-text-1)]">{{ t('bugManagement.exportBug') }}</span> <span class="text-[var(--color-text-1)]">{{ t('bugManagement.exportBug') }}</span>
<span v-if="currentSelectParams.currentSelectCount" class="text-[var(--color-text-4)]" <span v-if="currentSelectParams.currentSelectCount" class="text-[var(--color-text-4)]"
@ -261,12 +266,7 @@
import router from '@/router'; import router from '@/router';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license'; import useLicenseStore from '@/store/modules/setting/license';
import { import { customFieldDataToTableData, customFieldToColumns, downloadByteFile } from '@/utils';
customFieldDataToTableData,
customFieldToColumns,
downloadByteFile,
tableParamsToRequestParams,
} from '@/utils';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
import { BugEditCustomField, BugListItem, BugOptionItem } from '@/models/bug-management'; import { BugEditCustomField, BugListItem, BugOptionItem } from '@/models/bug-management';
@ -287,6 +287,7 @@
const syncVisible = ref(false); const syncVisible = ref(false);
const exportVisible = ref(false); const exportVisible = ref(false);
const exportOptionData = ref<MsExportDrawerMap>({}); const exportOptionData = ref<MsExportDrawerMap>({});
const exportLoading = ref(false);
const currentPlatform = ref('Local'); const currentPlatform = ref('Local');
const detailVisible = ref(false); const detailVisible = ref(false);
const activeDetailId = ref<string>(''); const activeDetailId = ref<string>('');
@ -545,9 +546,33 @@
], ],
}; };
function initTableParams() {
const filterParams: Record<string, any> = {
status: statusFilterValue.value,
handleUser: handleUserFilterValue.value,
updateUser: updateUserFilterValue.value,
createUser: createUserFilterValue.value,
};
filterParams[severityColumnId.value] = severityFilterValue.value;
return {
keyword: keyword.value,
projectId: projectId.value,
filter: { ...filterParams },
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
};
}
function searchData() {
setLoadListParams(initTableParams());
loadList();
}
const fetchData = async (v = '') => { const fetchData = async (v = '') => {
setKeyword(v); setKeyword(v);
await loadList(); searchData();
}; };
const handleAdvSearch = (filter: FilterResult) => { const handleAdvSearch = (filter: FilterResult) => {
@ -568,14 +593,15 @@
const exportConfirm = async (option: MsExportDrawerOption[]) => { const exportConfirm = async (option: MsExportDrawerOption[]) => {
try { try {
const params = tableParamsToRequestParams(currentSelectParams.value); exportLoading.value = true;
const blob = await exportBug({ const blob = await exportBug({
...params, ...currentSelectParams.value,
exportColumns: option.map((item) => item), exportColumns: option.map((item) => item),
projectId: appStore.currentProjectId, projectId: appStore.currentProjectId,
exportSort: sort.value, exportSort: sort.value,
}); });
downloadByteFile(blob, `${t('bugManagement.exportBug')}.zip`); downloadByteFile(blob, `${t('bugManagement.exportBug')}.zip`);
exportLoading.value = false;
exportVisible.value = false; exportVisible.value = false;
resetSelector(); resetSelector();
} catch (error) { } catch (error) {
@ -729,10 +755,10 @@
}); });
}; };
const handleBatchDelete = (params: BatchActionQueryParams) => { const handleBatchDelete = () => {
let dataCount = params.selectedIds?.length; let dataCount = currentSelectParams.value.selectIds?.length;
if (params.selectAll) { if (currentSelectParams.value.selectAll) {
dataCount = params.currentSelectCount; dataCount = currentSelectParams.value.currentSelectCount;
} }
openDeleteModal({ openDeleteModal({
title: t('bugManagement.deleteCount', { count: dataCount }), title: t('bugManagement.deleteCount', { count: dataCount }),
@ -740,7 +766,7 @@
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
const tmpObj = { const tmpObj = {
...tableParamsToRequestParams(params), ...currentSelectParams.value,
projectId: projectId.value, projectId: projectId.value,
}; };
await deleteBatchBug(tmpObj); await deleteBatchBug(tmpObj);
@ -755,18 +781,8 @@
}); });
}; };
const handleBatchEdit = (params: BatchActionQueryParams) => { const handleBatchEdit = () => {
batchEditVisible.value = true; batchEditVisible.value = true;
const condition = {
keyword: keyword.value,
searchMode: filterResult.value.accordBelow,
filter: propsRes.value.filter,
combine: filterResult.value.combine,
};
currentSelectParams.value = {
...params,
condition,
};
}; };
const handleExport = () => { const handleExport = () => {
@ -810,17 +826,29 @@
} else { } else {
params.condition = { filter: { ...filterParams } }; params.condition = { filter: { ...filterParams } };
} }
currentSelectParams.value = params; const condition = {
keyword: keyword.value,
searchMode: filterResult.value.accordBelow,
filter: params.condition.filter,
combine: filterResult.value.combine,
};
currentSelectParams.value = {
excludeIds: params.excludeIds,
selectAll: params.selectAll,
selectIds: params.selectedIds,
currentSelectCount: params.currentSelectCount,
condition,
};
switch (event.eventTag) { switch (event.eventTag) {
case 'export': case 'export':
handleExport(); handleExport();
break; break;
case 'delete': case 'delete':
handleBatchDelete(params); handleBatchDelete();
break; break;
case 'edit': case 'edit':
handleBatchEdit(params); handleBatchEdit();
break; break;
default: default:
break; break;
@ -839,30 +867,6 @@
sort.value = sortObj; sort.value = sortObj;
} }
function initTableParams() {
const filterParams: Record<string, any> = {
status: statusFilterValue.value,
handleUser: handleUserFilterValue.value,
updateUser: updateUserFilterValue.value,
createUser: createUserFilterValue.value,
};
filterParams[severityColumnId.value] = severityFilterValue.value;
return {
keyword: keyword.value,
projectId: projectId.value,
filter: { ...filterParams },
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
};
}
function searchData() {
setLoadListParams(initTableParams());
loadList();
}
watchEffect(() => { watchEffect(() => {
setProps({ heightUsed: heightUsed.value }); setProps({ heightUsed: heightUsed.value });
}); });

View File

@ -112,6 +112,7 @@ export default {
recycle: { recycle: {
recycleBin: 'Recycle', recycleBin: 'Recycle',
recover: 'Recover', recover: 'Recover',
recovering: 'Recovering',
recoverSuccess: 'Recover success', recoverSuccess: 'Recover success',
recoverError: 'Recover failed', recoverError: 'Recover failed',
permanentlyDelete: 'Permanently delete', permanentlyDelete: 'Permanently delete',

View File

@ -111,6 +111,7 @@ export default {
recycle: { recycle: {
recycleBin: '回收站', recycleBin: '回收站',
recover: '恢复', recover: '恢复',
recovering: '正在恢复中',
recoverSuccess: '恢复成功', recoverSuccess: '恢复成功',
recoverError: '恢复失败', recoverError: '恢复失败',
permanentlyDelete: '彻底删除', permanentlyDelete: '彻底删除',

View File

@ -7,7 +7,8 @@
:filter-config-list="filterConfigList" :filter-config-list="filterConfigList"
:row-count="filterRowCount" :row-count="filterRowCount"
@keyword-search="fetchData" @keyword-search="fetchData"
@refresh="fetchData('')" @adv-search="handleAdvSearch"
@refresh="handleAdvSearch"
> >
<template #left> <template #left>
<div></div> <div></div>
@ -136,7 +137,7 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter'; import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { BackEndEnum, FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type'; import { BackEndEnum, FilterFormItem, FilterResult, FilterType } from '@/components/pure/ms-advance-filter/type';
import MsButton from '@/components/pure/ms-button/index.vue'; import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue'; import MsCard from '@/components/pure/ms-card/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -156,12 +157,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import { import { characterLimit, customFieldDataToTableData, customFieldToColumns } from '@/utils';
characterLimit,
customFieldDataToTableData,
customFieldToColumns,
tableParamsToRequestParams,
} from '@/utils';
import { BugEditCustomField, BugListItem, BugOptionItem } from '@/models/bug-management'; import { BugEditCustomField, BugListItem, BugOptionItem } from '@/models/bug-management';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
@ -215,6 +211,8 @@
const severityFilterVisible = ref(false); const severityFilterVisible = ref(false);
const severityFilterValue = ref<string[]>([]); const severityFilterValue = ref<string[]>([]);
const severityColumnId = ref(''); const severityColumnId = ref('');
const filterResult = ref<FilterResult>({ accordBelow: 'AND', combine: {} });
const currentSelectParams = ref<BatchActionQueryParams>({ selectAll: false, currentSelectCount: 0 });
const heightUsed = computed(() => 286 + (filterVisible.value ? 160 + (filterRowCount.value - 1) * 60 : 0)); const heightUsed = computed(() => 286 + (filterVisible.value ? 160 + (filterRowCount.value - 1) * 60 : 0));
@ -409,9 +407,39 @@
setProps({ heightUsed: heightUsed.value }); setProps({ heightUsed: heightUsed.value });
}); });
function initTableParams() {
const filterParams: Record<string, any> = {
status: statusFilterValue.value,
handleUser: handleUserFilterValue.value,
updateUser: updateUserFilterValue.value,
createUser: createUserFilterValue.value,
deleteUser: deleteUserFilterValue.value,
};
filterParams[severityColumnId.value] = severityFilterValue.value;
return {
keyword: keyword.value,
projectId: projectId.value,
filter: { ...filterParams },
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
};
}
function searchData() {
setLoadListParams(initTableParams());
loadList();
}
const fetchData = async (v = '') => { const fetchData = async (v = '') => {
setKeyword(v); setKeyword(v);
await loadList(); searchData();
};
const handleAdvSearch = (filter: FilterResult) => {
filterResult.value = filter;
fetchData();
}; };
// //
@ -427,11 +455,14 @@
}; };
// //
const handleBatchRecover = async (params: BatchActionQueryParams) => { const handleBatchRecover = async () => {
try { try {
const tmpObj = { ...tableParamsToRequestParams(params), projectId: projectId.value }; appStore.showLoading(t('bugManagement.recycle.recovering'));
const tmpObj = { ...currentSelectParams.value, projectId: projectId.value };
await recoverBatchByRecycle(tmpObj); await recoverBatchByRecycle(tmpObj);
appStore.hideLoading();
Message.success(t('bugManagement.recycle.recoverSuccess')); Message.success(t('bugManagement.recycle.recoverSuccess'));
keyword.value = '';
fetchData(); fetchData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -457,15 +488,16 @@
}); });
}; };
// //
const handleBatchDelete = (params: BatchActionQueryParams) => { const handleBatchDelete = () => {
openDeleteModal({ openDeleteModal({
title: t('bugManagement.recycle.batchDelete', { count: params.currentSelectCount }), title: t('bugManagement.recycle.batchDelete', { count: currentSelectParams.value.currentSelectCount }),
content: t('bugManagement.recycle.deleteContent'), content: t('bugManagement.recycle.deleteContent'),
onBeforeOk: async () => { onBeforeOk: async () => {
try { try {
const tmpObj = { ...tableParamsToRequestParams(params), projectId: projectId.value }; const tmpObj = { ...currentSelectParams.value, projectId: projectId.value };
await deleteBatchByRecycle(tmpObj); await deleteBatchByRecycle(tmpObj);
Message.success(t('common.deleteSuccess')); Message.success(t('common.deleteSuccess'));
keyword.value = '';
fetchData(); fetchData();
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -490,43 +522,32 @@
} else { } else {
params.condition = { filter: { ...filterParams } }; params.condition = { filter: { ...filterParams } };
} }
const condition = {
keyword: keyword.value,
searchMode: filterResult.value.accordBelow,
filter: params.condition.filter,
combine: filterResult.value.combine,
};
currentSelectParams.value = {
excludeIds: params.excludeIds,
selectAll: params.selectAll,
selectIds: params.selectedIds,
currentSelectCount: params.currentSelectCount,
condition,
};
switch (event.eventTag) { switch (event.eventTag) {
case 'recover': case 'recover':
handleBatchRecover(params); handleBatchRecover();
break; break;
case 'delete': case 'delete':
handleBatchDelete(params); handleBatchDelete();
break; break;
default: default:
break; break;
} }
} }
function initTableParams() {
const filterParams: Record<string, any> = {
status: statusFilterValue.value,
handleUser: handleUserFilterValue.value,
updateUser: updateUserFilterValue.value,
createUser: createUserFilterValue.value,
deleteUser: deleteUserFilterValue.value,
};
filterParams[severityColumnId.value] = severityFilterValue.value;
return {
keyword: keyword.value,
projectId: projectId.value,
filter: { ...filterParams },
condition: {
keyword: keyword.value,
filter: propsRes.value.filter,
},
};
}
function searchData() {
setLoadListParams(initTableParams());
loadList();
}
async function initFilterOptions() { async function initFilterOptions() {
const res = await getCustomOptionHeader(appStore.currentProjectId); const res = await getCustomOptionHeader(appStore.currentProjectId);
createUserFilterOptions.value = res.userOption; createUserFilterOptions.value = res.userOption;