feat(缺陷管理): 回收站接口对接

This commit is contained in:
RubyLiu 2024-02-05 15:08:20 +08:00 committed by Craftsman
parent 9a140a4df2
commit 9975bc12db
9 changed files with 226 additions and 131 deletions

View File

@ -159,3 +159,25 @@ export function getAttachmentList(bugId: string) {
export function editorUploadFile(data: { fileList: File[] }) { export function editorUploadFile(data: { fileList: File[] }) {
return MSR.uploadFile({ url: bugURL.editorUploadFileUrl }, { fileList: data.fileList }, '', false); return MSR.uploadFile({ url: bugURL.editorUploadFileUrl }, { fileList: data.fileList }, '', false);
} }
// --------------------回收站
// 获取回收站列表
export function getRecycleList(data: TableQueryParams) {
return MSR.post<CommonList<BugListItem>>({ url: bugURL.getRecycleListUrl, data });
}
// 单个恢复
export function recoverSingleByRecycle(id: string) {
return MSR.get({ url: `${bugURL.getRecoverSingleUrl}${id}` });
}
// 批量恢复
export function recoverBatchByRecycle(data: TableQueryParams) {
return MSR.post({ url: bugURL.getBatchRecoverUrl, data });
}
// 删除
export function deleteSingleByRecycle(id: string) {
return MSR.get({ url: `${bugURL.getDeleteSingleUrl}${id}` });
}
// 批量删除
export function deleteBatchByRecycle(data: TableQueryParams) {
return MSR.post({ url: bugURL.getBatchDeleteUrl, data });
}

View File

@ -40,3 +40,14 @@ export const deleteFileOrCancelAssociationUrl = '/bug/attachment/delete';
export const getAttachmentListUrl = '/bug/attachment/list/'; export const getAttachmentListUrl = '/bug/attachment/list/';
// 富文本编辑器上传图片 // 富文本编辑器上传图片
export const editorUploadFileUrl = '/bug/attachment/upload/editor'; export const editorUploadFileUrl = '/bug/attachment/upload/editor';
// 获取回收站列表
export const getRecycleListUrl = '/bug/trash/page';
// 单个恢复
export const getRecoverSingleUrl = '/bug/trash/recover/';
// 批量恢复
export const getBatchRecoverUrl = '/bug/trash/batch-recover';
// 删除
export const getDeleteSingleUrl = '/bug/trash/delete/';
// 批量删除
export const getBatchDeleteUrl = '/bug/trash/batch-delete';

View File

@ -37,6 +37,7 @@ export enum TableKeyEnum {
CASE_MANAGEMENT_DETAIL_TABLE = 'caseManagementDetailTable', CASE_MANAGEMENT_DETAIL_TABLE = 'caseManagementDetailTable',
CASE_MANAGEMENT_ASSOCIATED_TABLE = 'caseManagementAssociatedFileTable', CASE_MANAGEMENT_ASSOCIATED_TABLE = 'caseManagementAssociatedFileTable',
BUG_MANAGEMENT = 'bugManagement', BUG_MANAGEMENT = 'bugManagement',
BUG_MANAGEMENT_RECYCLE = 'bugManagementRecycle',
CASE_MANAGEMENT_REVIEW = 'caseManagementReview', CASE_MANAGEMENT_REVIEW = 'caseManagementReview',
CASE_MANAGEMENT_REVIEW_CASE = 'caseManagementReviewCase', CASE_MANAGEMENT_REVIEW_CASE = 'caseManagementReviewCase',
CASE_MANAGEMENT_TAB_DEFECT = 'caseManagementTabDefect', CASE_MANAGEMENT_TAB_DEFECT = 'caseManagementTabDefect',

View File

@ -5,7 +5,7 @@ import { BatchApiParams } from './common';
export interface BugListItem { export interface BugListItem {
id: string; // 缺陷id id: string; // 缺陷id
num: string; // 缺陷编号 num: string; // 缺陷编号
name: string; // 缺陷名称 title: string; // 缺陷名称
severity: string; // 缺陷严重程度 severity: string; // 缺陷严重程度
status: string; // 缺陷状态 status: string; // 缺陷状态
handleUser: string; // 缺陷处理人 handleUser: string; // 缺陷处理人

View File

@ -65,7 +65,7 @@ const BugManagement: AppRouteRecordRaw = {
name: BugManagementRouteEnum.BUG_MANAGEMENT_RECYCLE, name: BugManagementRouteEnum.BUG_MANAGEMENT_RECYCLE,
component: () => import('@/views/bug-management/recycle.vue'), component: () => import('@/views/bug-management/recycle.vue'),
meta: { meta: {
locale: 'bugManagement.recycle', locale: 'bugManagement.recycle.recycleBin',
roles: ['PROJECT_BUG:READ'], roles: ['PROJECT_BUG:READ'],
isTopMenu: true, isTopMenu: true,
}, },

View File

@ -1,7 +1,7 @@
import { findKey } from 'lodash-es'; import { findKey } from 'lodash-es';
import JSEncrypt from 'jsencrypt'; import JSEncrypt from 'jsencrypt';
import { MsTableColumn, MsTableColumnData } from '@/components/pure/ms-table/type'; import { BatchActionQueryParams, MsTableColumn, MsTableColumnData } from '@/components/pure/ms-table/type';
import { CustomFieldItem } from '@/models/bug-management'; import { CustomFieldItem } from '@/models/bug-management';
@ -492,7 +492,7 @@ export function formatPhoneNumber(phoneNumber = '') {
} }
return phoneNumber; return phoneNumber;
} }
// 表格自定义字段转column
export function customFieldToColumns(customFields: CustomFieldItem[]) { export function customFieldToColumns(customFields: CustomFieldItem[]) {
return customFields.map((field) => { return customFields.map((field) => {
const { fieldName, fieldKey, fieldId } = field; const { fieldName, fieldKey, fieldId } = field;
@ -507,3 +507,13 @@ export function customFieldToColumns(customFields: CustomFieldItem[]) {
return column; return column;
}); });
} }
// 表格查询参数转请求参数
export function tableParamsToRequestParams(params: BatchActionQueryParams) {
const { selectedIds, selectAll, excludeIds, condition } = params;
return {
selectIds: selectedIds,
excludeIds,
selectAll,
condition,
};
}

View File

@ -110,10 +110,6 @@
<template #second> <template #second>
<div class="rightWrapper p-[24px]"> <div class="rightWrapper p-[24px]">
<div class="mb-4 font-medium">{{ t('bugManagement.detail.basicInfo') }}</div> <div class="mb-4 font-medium">{{ t('bugManagement.detail.basicInfo') }}</div>
<div class="baseItem">
<span class="label"> {{ t('bugManagement.detail.handleUser') }}</span>
<MsUserSelector v-model:model-value="detailInfo.handleUser" />
</div>
<!-- 自定义字段开始 --> <!-- 自定义字段开始 -->
<MsFormCreate <MsFormCreate
v-if="formRules.length" v-if="formRules.length"
@ -127,7 +123,7 @@
<!-- 自定义字段结束 --> <!-- 自定义字段结束 -->
<div class="baseItem"> <div class="baseItem">
<span class="label"> {{ t('bugManagement.detail.tag') }}</span> <span class="label"> {{ t('bugManagement.detail.tag') }}</span>
<MsTagsInput v-model:modelValue="detailInfo.tag"></MsTagsInput> <!-- <MsTagsInput></MsTagsInput> -->
</div> </div>
<div class="baseItem"> <div class="baseItem">
<span class="label"> {{ t('bugManagement.detail.creator') }}</span> <span class="label"> {{ t('bugManagement.detail.creator') }}</span>
@ -169,7 +165,6 @@
import { CommentInput } from '@/components/business/ms-comment'; import { CommentInput } from '@/components/business/ms-comment';
import { CommentParams } from '@/components/business/ms-comment/types'; import { CommentParams } from '@/components/business/ms-comment/types';
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue'; import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
import { MsUserSelector } from '@/components/business/ms-user-selector';
import BugCaseTab from './bugCaseTab.vue'; import BugCaseTab from './bugCaseTab.vue';
import BugDetailTab from './bugDetailTab.vue'; import BugDetailTab from './bugDetailTab.vue';
import CommentTab from './commentTab.vue'; import CommentTab from './commentTab.vue';
@ -220,13 +215,21 @@
const tabSetting = ref<TabItemType[]>([...tabSettingList.value]); const tabSetting = ref<TabItemType[]>([...tabSettingList.value]);
const activeTab = ref<string | number>('detail'); const activeTab = ref<string | number>('detail');
const detailInfo = ref<Record<string, any>>({}); const detailInfo = ref<Record<string, any>>({
tags: [],
id: '',
createUser: '',
createTime: '',
description: '',
followFlag: false,
templateId: '',
title: '',
});
const customFields = ref<CustomAttributes[]>([]); const customFields = ref<CustomAttributes[]>([]);
function loadedBug(detail: CaseManagementTable) { function loadedBug(detail: CaseManagementTable) {
detailInfo.value = { ...detail }; detailInfo.value = { ...detail };
customFields.value = detailInfo.value.customFields; customFields.value = detailInfo.value.customFields;
detailInfo.value.id = '1070838426116099';
} }
const editLoading = ref<boolean>(false); const editLoading = ref<boolean>(false);
@ -363,7 +366,7 @@
try { try {
const params = { const params = {
// TODO // TODO
bugId: detailInfo.value.id || '1070838426116099', bugId: detailInfo.value.id,
notifier: notifiers, notifier: notifiers,
replyUser: '', replyUser: '',
parentId: '', parentId: '',
@ -372,7 +375,7 @@
}; };
await createOrUpdateComment(params as CommentParams); await createOrUpdateComment(params as CommentParams);
Message.success(t('common.publishSuccessfully')); Message.success(t('common.publishSuccessfully'));
commentRef.value?.initData(detailInfo.value.id || '1070838426116099'); commentRef.value?.initData(detailInfo.value.id);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);

View File

@ -3,7 +3,6 @@ export default {
index: '缺陷管理', index: '缺陷管理',
addBug: '创建缺陷', addBug: '创建缺陷',
editBug: '编辑缺陷', editBug: '编辑缺陷',
recycle: '回收站',
createBug: '创建缺陷', createBug: '创建缺陷',
syncBug: '同步缺陷', syncBug: '同步缺陷',
ID: 'ID', ID: 'ID',
@ -90,6 +89,19 @@ export default {
mightWantTo: '你可能还想', mightWantTo: '你可能还想',
caseRelated: '关联用例', caseRelated: '关联用例',
}, },
recycle: {
recycleBin: '回收站',
recover: '恢复',
recoverSuccess: '恢复成功',
recoverError: '恢复失败',
permanentlyDelete: '彻底删除',
permanentlyDeleteTip: '是否彻底删除 {name} 缺陷?',
deleteContent: '删除后,缺陷无法恢复,请谨慎操作!',
batchDelete: '是否彻底删除{count}条缺陷?',
searchPlaceholder: '通过 ID 或名称搜索',
deleteTime: '删除时间',
deleteMan: '删除人',
},
severityO: { severityO: {
fatal: '致命', fatal: '致命',
serious: '严重', serious: '严重',

View File

@ -1,21 +1,29 @@
<template> <template>
<MsCard simple> <MsCard simple>
<MsAdvanceFilter :filter-config-list="filterConfigList" :row-count="filterRowCount"> <MsAdvanceFilter
:search-placeholder="t('bugManagement.recycle.searchPlaceholder')"
:filter-config-list="filterConfigList"
:row-count="filterRowCount"
@keyword-search="fetchData"
>
<template #left> <template #left>
<div></div> <div></div>
</template> </template>
</MsAdvanceFilter> </MsAdvanceFilter>
<MsBaseTable v-bind="propsRes" v-on="propsEvent"> <MsBaseTable
<template #numberOfCase="{ record }"> class="mt-[16px]"
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="jumpToTestPlan(record)">{{ v-bind="propsRes"
record.memberCount :action-config="tableAction"
}}</span> v-on="propsEvent"
</template> @batch-action="handleTableBatch"
>
<template #operation="{ record }"> <template #operation="{ record }">
<div class="flex flex-row flex-nowrap"> <div class="flex flex-row flex-nowrap">
<MsButton class="!mr-0" @click="handleCopy(record)">{{ t('common.copy') }}</MsButton> <MsButton class="!mr-0" @click="handleRecover(record)">{{ t('bugManagement.recycle.recover') }}</MsButton>
<a-divider direction="vertical" /> <a-divider direction="vertical" />
<MsButton class="!mr-0" @click="handleEdit(record)">{{ t('common.edit') }}</MsButton> <MsButton class="!mr-0" @click="handleDelete(record)">{{
t('bugManagement.recycle.permanentlyDelete')
}}</MsButton>
</div> </div>
</template> </template>
<template #empty> </template> <template #empty> </template>
@ -23,29 +31,35 @@
</MsCard> </MsCard>
</template> </template>
<script lang="ts" setup> <script lang="ts" async setup>
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter'; import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type'; import { FilterFormItem, 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';
import { MsTableColumn } from '@/components/pure/ms-table/type'; import { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import { updateOrAddProjectUserGroup } from '@/api/modules/project-management/usergroup'; import {
import { postProjectTableByOrg } from '@/api/modules/setting/organizationAndProject'; deleteBatchByRecycle,
deleteSingleByRecycle,
getRecycleList,
recoverBatchByRecycle,
recoverSingleByRecycle,
} from '@/api/modules/bug-management';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import router from '@/router'; import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store'; import { useAppStore, useTableStore } from '@/store';
import { characterLimit, tableParamsToRequestParams } from '@/utils';
import { BugListItem } from '@/models/bug-management'; import { BugListItem } from '@/models/bug-management';
import { OrgProjectTableItem } from '@/models/setting/system/orgAndProject'; import { TableKeyEnum } from '@/enums/tableEnum';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
const { t } = useI18n(); const { t } = useI18n();
const { openDeleteModal } = useModal();
const tableStore = useTableStore(); const tableStore = useTableStore();
const appStore = useAppStore(); const appStore = useAppStore();
const projectId = computed(() => appStore.currentProjectId); const projectId = computed(() => appStore.currentProjectId);
@ -65,15 +79,6 @@
mode: 'static', mode: 'static',
}, },
}, },
{
title: 'bugManagement.severity',
dataIndex: 'severity',
type: FilterType.SELECT,
selectProps: {
mode: 'static',
multiple: true,
},
},
{ {
title: 'bugManagement.createTime', title: 'bugManagement.createTime',
dataIndex: 'createTime', dataIndex: 'createTime',
@ -84,106 +89,96 @@
const heightUsed = computed(() => 286 + (filterVisible.value ? 160 + (filterRowCount.value - 1) * 60 : 0)); const heightUsed = computed(() => 286 + (filterVisible.value ? 160 + (filterRowCount.value - 1) * 60 : 0));
const columns: MsTableColumn = [ const columns: MsTableColumn = [
{
title: 'bugManagement.recycle.deleteTime',
dataIndex: 'deleteTime',
showDrag: true,
width: 180,
},
{
title: 'bugManagement.recycle.deleteMan',
dataIndex: 'deleteUserName',
showDrag: true,
},
{ {
title: 'bugManagement.ID', title: 'bugManagement.ID',
dataIndex: 'num', dataIndex: 'num',
showTooltip: true, showDrag: true,
}, },
{ {
title: 'bugManagement.bugName', title: 'bugManagement.bugName',
editType: ColumnEditTypeEnum.INPUT, dataIndex: 'title',
dataIndex: 'name',
showTooltip: true, showTooltip: true,
}, },
{
title: 'bugManagement.severity',
slotName: 'memberCount',
showDrag: true,
dataIndex: 'severity',
},
{
title: 'bugManagement.status',
dataIndex: 'status',
showDrag: true,
},
{
title: 'bugManagement.handleMan',
dataIndex: 'handleUser',
showTooltip: true,
showDrag: true,
},
{
title: 'bugManagement.numberOfCase',
dataIndex: 'relationCaseCount',
slotName: 'numberOfCase',
showDrag: true,
},
{
title: 'bugManagement.belongPlatform',
width: 180,
showDrag: true,
dataIndex: 'platform',
},
{
title: 'bugManagement.tag',
showDrag: true,
isStringTag: true,
dataIndex: 'tag',
},
{ {
title: 'bugManagement.creator', title: 'bugManagement.creator',
dataIndex: 'createUser', dataIndex: 'createUser',
showDrag: true, showDrag: true,
}, showTooltip: true,
{
title: 'bugManagement.updateUser',
dataIndex: 'updateUser',
showDrag: true,
}, },
{ {
title: 'bugManagement.createTime', title: 'bugManagement.createTime',
dataIndex: 'createTime', dataIndex: 'createTime',
showDrag: true, showDrag: true,
width: 180,
}, },
{ {
title: 'bugManagement.updateTime', title: 'bugManagement.status',
dataIndex: 'updateTime', dataIndex: 'statusName',
showDrag: true, showDrag: true,
}, },
{
title: 'bugManagement.handleMan',
dataIndex: 'handleUserName',
showTooltip: true,
showDrag: true,
},
{
title: 'bugManagement.tag',
showDrag: true,
isStringTag: true,
dataIndex: 'tags',
},
{ {
title: 'common.operation', title: 'common.operation',
slotName: 'operation', slotName: 'operation',
dataIndex: 'operation', dataIndex: 'operation',
fixed: 'right', fixed: 'right',
width: 230, width: 150,
}, },
]; ];
await tableStore.initColumn(TableKeyEnum.BUG_MANAGEMENT, columns, 'drawer'); await tableStore.initColumn(TableKeyEnum.BUG_MANAGEMENT_RECYCLE, columns, 'drawer');
const handleNameChange = async (record: OrgProjectTableItem) => {
try {
await updateOrAddProjectUserGroup(record);
Message.success(t('common.updateSuccess'));
return true;
} catch (error) {
return false;
}
};
const { propsRes, propsEvent, loadList, setKeyword, setLoadListParams, setProps } = useTable( const { propsRes, propsEvent, loadList, setKeyword, setLoadListParams, setProps } = useTable(
postProjectTableByOrg, getRecycleList,
{ {
tableKey: TableKeyEnum.BUG_MANAGEMENT, tableKey: TableKeyEnum.BUG_MANAGEMENT_RECYCLE,
selectable: false, selectable: true,
noDisable: false, noDisable: true,
showJumpMethod: true,
showSetting: true, showSetting: true,
scroll: { x: '1769px' }, scroll: { x: '1900px' },
}, },
undefined, (record) => {
(record) => handleNameChange(record) record.deleteTime = dayjs(record.deleteTime).format('YYYY-MM-DD HH:mm:ss');
return record;
}
); );
const tableAction = {
baseAction: [
{
eventTag: 'recover',
label: t('bugManagement.recycle.recover'),
permission: ['PROJECT_BUG:READ+UPDATE'],
},
{
eventTag: 'delete',
label: t('bugManagement.recycle.permanentlyDelete'),
permission: ['PROJECT_BUG:READ+DELETE'],
},
],
};
watchEffect(() => { watchEffect(() => {
setProps({ heightUsed: heightUsed.value }); setProps({ heightUsed: heightUsed.value });
}); });
@ -193,39 +188,80 @@
await loadList(); await loadList();
}; };
const handleCreate = () => { //
// eslint-disable-next-line no-console const handleRecover = async (record: BugListItem) => {
console.log('create'); try {
}; await recoverSingleByRecycle(record.id);
const handleSync = () => { Message.success(t('bugManagement.recycle.recoverSuccess'));
// eslint-disable-next-line no-console fetchData();
console.log('sync'); } catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}; };
const handleCopy = (record: BugListItem) => { //
// eslint-disable-next-line no-console const handleBatchRecover = async (params: BatchActionQueryParams) => {
console.log('create', record); try {
}; const tmpObj = { ...tableParamsToRequestParams(params), projectId: projectId.value };
await recoverBatchByRecycle(tmpObj);
const handleEdit = (record: BugListItem) => { Message.success(t('bugManagement.recycle.recoverSuccess'));
// eslint-disable-next-line no-console fetchData();
console.log('create', record); } catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}; };
//
const handleDelete = (record: BugListItem) => { const handleDelete = (record: BugListItem) => {
// eslint-disable-next-line no-console openDeleteModal({
console.log('create', record); title: t('bugManagement.recycle.permanentlyDeleteTip', { name: characterLimit(record.title) }),
}; content: t('bugManagement.recycle.deleteContent'),
onBeforeOk: async () => {
const jumpToTestPlan = (record: BugListItem) => { try {
router.push({ await deleteSingleByRecycle(record.id);
name: 'testPlan', Message.success(t('common.deleteSuccess'));
query: { fetchData();
bugId: record.id, } catch (error) {
projectId: projectId.value, // eslint-disable-next-line no-console
console.log(error);
}
}, },
}); });
}; };
//
const handleBatchDelete = (params: BatchActionQueryParams) => {
openDeleteModal({
title: t('bugManagement.recycle.batchDelete', { count: params.currentSelectCount }),
content: t('bugManagement.recycle.deleteContent'),
onBeforeOk: async () => {
try {
const tmpObj = { ...tableParamsToRequestParams(params), projectId: projectId.value };
await deleteBatchByRecycle(tmpObj);
Message.success(t('common.deleteSuccess'));
fetchData();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
},
});
};
//
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
switch (event.eventTag) {
case 'recover':
handleBatchRecover(params);
break;
case 'delete':
handleBatchDelete(params);
break;
default:
break;
}
}
onMounted(() => { onMounted(() => {
setLoadListParams({ projectId: projectId.value }); setLoadListParams({ projectId: projectId.value });