feat(缺陷管理): 缺陷管理创建&复制&编辑接口对接

This commit is contained in:
RubyLiu 2024-02-04 18:48:39 +08:00 committed by Craftsman
parent 2ad4ca9d0d
commit f9a6582cb0
16 changed files with 568 additions and 121 deletions

View File

@ -3,8 +3,8 @@ import { CommentParams } from '@/components/business/ms-comment/types';
import MSR from '@/api/http/index';
import * as bugURL from '@/api/requrls/bug-management';
import { BugEditFormObject, BugExportParams, BugListItem, CreateOrUpdateComment } from '@/models/bug-management';
import { AssociatedList } from '@/models/caseManagement/featureCase';
import { BugEditFormObject, BugExportParams, BugListItem } from '@/models/bug-management';
import { AssociatedList, OperationFile } from '@/models/caseManagement/featureCase';
import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
/**
@ -36,8 +36,8 @@ export function updateBatchBug(data: TableQueryParams) {
* @param data
* @returns
*/
export function createBug(data: { request: BugEditFormObject; fileList: File[] }) {
return MSR.uploadFile({ url: bugURL.postCreateBugUrl }, data, '', true);
export function createOrUpdateBug(data: { request: BugEditFormObject; fileList: File[] }) {
return MSR.uploadFile({ url: data.request.id ? bugURL.postUpdateBugUrl : bugURL.postCreateBugUrl }, data, '', true);
}
/**
* bug
@ -107,3 +107,51 @@ export function getCommentList(bugId: string) {
export function deleteComment(commentId: string) {
return MSR.get({ url: `${bugURL.getDeleteCommentUrl}${commentId}` });
}
export function getCustomFieldHeader(projectId: string) {
return MSR.get({ url: `${bugURL.getCustomFieldHeaderUrl}${projectId}` });
}
// 附件
// 上传文件并关联用例
export function uploadOrAssociationFile(data: Record<string, any>) {
return MSR.uploadFile({ url: bugURL.uploadOrAssociationFileUrl }, { request: data.request, fileList: [data.file] });
}
// 转存文件
export function transferFileRequest(data: OperationFile) {
return MSR.post({ url: bugURL.transferFileUrl, data });
}
// 获取文件转存目录
export function getTransferFileTree(projectId: string) {
return MSR.get({ url: `${bugURL.getTransferTreeUrl}/${projectId}` });
}
// 预览文件
export function previewFile(data: OperationFile) {
return MSR.post({ url: bugURL.previewFileUrl, data, responseType: 'blob' }, { isTransformResponse: false });
}
// 下载文件
export function downloadFileRequest(data: OperationFile) {
return MSR.post({ url: bugURL.downloadFileUrl, data, responseType: 'blob' }, { isTransformResponse: false });
}
// 检查文件是否更新
export function checkFileIsUpdateRequest(data: string[]) {
return MSR.post({ url: bugURL.checkFileIsUpdateUrl, data });
}
// 更新文件
export function updateFile(projectId: string, id: string) {
return MSR.get({ url: `${bugURL.getFileIsUpdateUrl}/${projectId}/${id}` });
}
// 删除文件或取消关联用例文件
export function deleteFileOrCancelAssociation(data: OperationFile) {
return MSR.post({ url: bugURL.deleteFileOrCancelAssociationUrl, data });
}
// 获取文件列表
export function getAttachmentList(bugId: string) {
return MSR.get({ url: `${bugURL.getAttachmentListUrl}${bugId}` });
}

View File

@ -10,11 +10,31 @@ export const getExportConfigUrl = '/bug/export/columns/';
export const getTemplateDetailUrl = '/bug/template/detail';
export const getSyncBugOpenSourceUrl = '/bug/sync/';
export const postExportBugUrl = '/bug/export';
export const postAssociatedFileListUrl = '/bug/relate/case/page';
export const getBugDetailUrl = '/bug/detail/';
// 获取关联文件列表
export const postAssociatedFileListUrl = '/bug/attachment/file/page';
export const getBugDetailUrl = '/bug/get/';
export const getFollowBugUrl = '/bug/follow/';
export const getUnFollowBugUrl = '/bug/unfollow/';
export const postUpdateCommentUrl = '/bug/comment/update';
export const postCreateCommentUrl = '/bug/comment/add';
export const getCommentListUrl = '/bug/comment/get/';
export const getDeleteCommentUrl = '/bug/comment/delete/';
export const getCustomFieldHeaderUrl = '/bug/header/custom-field/';
// 上传or关联文件
export const uploadOrAssociationFileUrl = '/bug/attachment/upload';
// 转存文件
export const transferFileUrl = '/bug/attachment/transfer';
// 获取文件转存目录
export const getTransferTreeUrl = '/bug/attachment/transfer/options/';
// 预览文件
export const previewFileUrl = '/bug/attachment/preview';
// 下载文件
export const downloadFileUrl = '/bug/attachment/download';
// 检查文件是否更新
export const checkFileIsUpdateUrl = '/bug/attachment/check-update';
// 更新文件
export const getFileIsUpdateUrl = '/bug/attachment/update';
// 删除文件或取消关联用例文件
export const deleteFileOrCancelAssociationUrl = '/bug/attachment/delete';
// 获取附件列表
export const getAttachmentListUrl = '/bug/attachment/list/';

View File

@ -9,6 +9,7 @@ export enum BugManagementRouteEnum {
BUG_MANAGEMENT_INDEX = 'bugManagementIndex',
BUG_MANAGEMENT_DETAIL = 'bbugManagementIndexDetail',
BUG_MANAGEMENT_RECYCLE = 'bugManagementRecycle',
BUG_MANAGEMENT_CREATE_SUCCESS = 'bugManagementCreateSuccess',
}
export enum CaseManagementRouteEnum {

View File

@ -21,6 +21,7 @@ export default {
'common.operation': 'Operation',
'common.remove': 'Remove',
'common.revoked': 'Revoked',
'common.download': 'Download',
'common.createSuccess': 'Create success',
'common.createFailed': 'Create failed',
'common.updateSuccess': 'Update success',

View File

@ -22,6 +22,7 @@ export default {
'common.remove': '移除',
'common.revoked': '已撤销',
'common.filter': '筛选',
'common.download': '下载',
'common.createSuccess': '创建成功',
'common.createFailed': '创建失败',
'common.updateSuccess': '更新成功',

View File

@ -35,6 +35,7 @@ export interface BugEditCustomField {
platformOptionJson?: string; // 选项的 Json
required: boolean;
isMutiple?: boolean;
options?: any[];
}
export interface BugEditFormObject {
[key: string]: any;
@ -62,4 +63,26 @@ export interface CreateOrUpdateComment {
content: string;
event: string; // 任务事件(仅评论: COMMENT; 评论并@: AT; 回复评论/回复并@: REPLAY;)
}
export default {};
export interface CustomFieldItem {
fieldId: string;
fieldName: string;
fieldKey: string;
required: boolean;
apiFieldId: string;
defaultValue: string;
type: string;
options: string;
platformOptionJson: string;
supportSearch: boolean;
optionMethod: string;
internal: boolean;
}
export interface OperationFile {
id?: string;
projectId: string;
bugId: string;
fileId?: string; // 文件id
associated: boolean; // 是否是本地 还是关联
moduleId?: string; // 文件转存模块id
}

View File

@ -1,3 +1,5 @@
import { key } from 'localforage';
import { TableQueryParams } from '@/models/common';
import { StatusType } from '@/enums/caseEnum';
@ -261,10 +263,11 @@ export interface CreateOrUpdateDemand {
export interface OperationFile {
id?: string;
projectId: string;
caseId: string;
caseId?: string;
fileId?: string; // 文件id
local: boolean; // 是否是本地
local?: boolean; // 是否是本地
moduleId?: string; // 文件转存模块id
[key: string]: any;
}
// 评论列表项

View File

@ -29,7 +29,7 @@ const BugManagement: AppRouteRecordRaw = {
},
// 缺陷管理-编辑缺陷
{
path: 'edit',
path: 'detail/:mode?',
name: BugManagementRouteEnum.BUG_MANAGEMENT_DETAIL,
component: () => import('@/views/bug-management/edit.vue'),
meta: {
@ -42,12 +42,23 @@ const BugManagement: AppRouteRecordRaw = {
},
{
name: BugManagementRouteEnum.BUG_MANAGEMENT_DETAIL,
locale: 'bugManagement.editBug',
editLocale: 'menu.settings.organization.templateFieldSetting',
locale: 'bugManagement.addBug',
editTag: 'id',
editLocale: 'bugManagement.editBug',
},
],
},
},
// 创建用例成功
{
path: 'create-success',
name: BugManagementRouteEnum.BUG_MANAGEMENT_CREATE_SUCCESS,
component: () => import('@/views/bug-management/createSuccess.vue'),
meta: {
locale: 'bugManagement.createBugSuccess',
roles: ['PROJECT_BUG:READ+ADD', 'PROJECT_BUG:READ+UPDATE'],
},
},
// 回收站
{
path: 'recycle',

View File

@ -1,5 +1,10 @@
import { findKey } from 'lodash-es';
import JSEncrypt from 'jsencrypt';
import { MsTableColumn, MsTableColumnData } from '@/components/pure/ms-table/type';
import { CustomFieldItem } from '@/models/bug-management';
import { isObject } from './is';
type TargetContext = '_self' | '_parent' | '_blank' | '_top';
@ -485,3 +490,18 @@ export function formatPhoneNumber(phoneNumber = '') {
}
return phoneNumber;
}
export function customFieldToColumns(customFields: CustomFieldItem[]) {
return customFields.map((field) => {
const { fieldName, fieldKey, fieldId } = field;
const column: MsTableColumnData = {
title: fieldName,
dataIndex: ['handleUser', 'status'].includes(fieldId) ? fieldKey : fieldId,
showTooltip: true,
showDrag: true,
showInTable: true,
width: 200,
};
return column;
});
}

View File

@ -0,0 +1,161 @@
<template>
<MsCard simple>
<div class="h-full">
<div class="mt-8 text-center">
<div class="flex justify-center"><svg-icon :width="'60px'" :height="'60px'" :name="'success'" /></div>
<div class="mb-2 mt-6 text-[20px] font-medium"> {{ t('common.addSuccess') }} </div>
<div
><span class="mr-1 text-[rgb(var(--primary-5))]">{{ countDown }}</span
><span class="text-[var(--color-text-4)]">{{ t('bugManagement.success.countDownTip') }}</span></div
>
<div class="my-6">
<a-button type="primary" @click="goDetail"> {{ t('bugManagement.success.bugDetail') }} </a-button>
<a-button class="mx-3" type="outline" @click="continueCreate">
{{ t('bugManagement.success.addContinueCreate') }}
</a-button>
<a-button type="secondary" @click="backCaseList">
{{ t('bugManagement.success.backBugList') }}
</a-button>
</div>
<a-checkbox v-model="isNextTip" class="mb-6">{{ t('bugManagement.success.notNextTip') }}</a-checkbox>
</div>
<div>
<div class="mb-4 font-medium">{{ t('bugManagement.success.mightWantTo') }}</div>
<MsCardList
mode="static"
:card-min-width="569"
class="flex-1"
:shadow-limit="50"
:list="cardList"
:is-proportional="false"
:gap="16"
padding-bottom-space="16px"
>
<template #item="{ item }">
<div class="outerWrapper p-[3px]">
<div class="innerWrapper flex items-center justify-between">
<div class="flex items-center">
<div class="logo-img flex h-[48px] w-[48px] items-center justify-center">
<svg-icon width="36px" height="36px" :name="item.key"></svg-icon>
</div>
<div class="ml-2"> {{ item.name }} </div>
</div>
<a-button type="outline" @click="handleCaseRelated">
{{ t('bugManagement.success.caseRelated') }}
</a-button>
</div>
</div>
</template>
</MsCardList>
</div>
</div>
</MsCard>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsCardList from '@/components/business/ms-card-list/index.vue';
import { useI18n } from '@/hooks/useI18n';
import useVisit from '@/hooks/useVisit';
import { BugManagementRouteEnum, RouteEnum } from '@/enums/routeEnum';
const { t } = useI18n();
const visitedKey = 'doNotNextTipCreateBug';
const { addVisited } = useVisit(visitedKey);
const router = useRouter();
const route = useRoute();
const cardList = ref([
{
key: 'caseReview',
name: t('bugManagement.success.caseRelated'),
},
]);
const isNextTip = ref<boolean>(false);
const countDown = ref<number>(5);
const timer = ref<any>(null);
function setCountdown() {
// timer.value = setInterval(() => {
// if (countDown.value > 1) {
// --countDown.value;
// } else {
// clearInterval(timer.value);
// router.push({
// name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
// });
// }
// }, 1000);
timer.value = 5;
}
function isDoNotShowAgainChecked() {
if (isNextTip.value) {
addVisited();
}
}
//
function backCaseList() {
clearInterval(timer.value);
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
});
}
//
function continueCreate() {
clearInterval(timer.value);
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_DETAIL,
});
}
function goDetail() {
clearInterval(timer.value);
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
query: route.query,
});
}
//
function handleCaseRelated() {
router.push({
name: RouteEnum.CASE_MANAGEMENT_CASE,
query: route.query,
});
}
watch(
() => isNextTip.value,
() => {
isDoNotShowAgainChecked();
}
);
onMounted(() => {
setCountdown();
});
</script>
<style scoped lang="less">
.outerWrapper {
box-shadow: 0 6px 15px rgba(120 56 135/ 5%);
@apply rounded bg-white;
.innerWrapper {
background: var(--color-bg-3);
@apply rounded p-6;
.logo-img {
@apply mr-3 flex items-center bg-white;
}
}
}
</style>

View File

@ -6,8 +6,9 @@
has-breadcrumb
:title="title"
:loading="loading"
@save="saveHandler"
@save-and-continue="saveHandler"
:is-edit="isEdit"
@save="saveHandler(false)"
@save-and-continue="saveHandler(true)"
>
<template #headerRight>
<a-select
@ -68,30 +69,82 @@
</div>
</a-form-item>
<div class="mb-[8px] mt-[2px] text-[var(--color-text-4)]">{{ t('bugManagement.edit.fileExtra') }}</div>
<FileList
:show-tab="false"
:file-list="fileList"
:upload-func="uploadFile"
@delete-file="deleteFile"
@reupload="reupload"
@handle-preview="handlePreview"
>
<FileList mode="static" :file-list="fileList">
<template #actions="{ item }">
<!-- 本地文件 -->
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
<MsButton
v-if="item.status !== 'init'"
type="button"
status="primary"
class="!mr-[4px]"
@click="handlePreview(item)"
>
{{ t('ms.upload.preview') }}
</MsButton>
<MsButton
v-if="item.status !== 'init'"
type="button"
status="primary"
class="!mr-[4px]"
@click="transferFile"
>
{{ t('caseManagement.featureCase.storage') }}
</MsButton>
<TransferModal
v-model:visible="transferVisible"
:request-fun="transferFileRequest"
:params="{
projectId: currentProjectId,
bugId: bugId as string,
fileId: item.uid,
associated: !item.local,
}"
@success="getDetailInfo()"
/>
<MsButton
v-if="item.status !== 'init'"
type="button"
status="primary"
class="!mr-[4px]"
@click="downloadFile(item)"
>
{{ t('common.download') }}
</MsButton>
</div>
<!-- 关联文件 -->
<div v-else class="flex flex-nowrap">
<MsButton
v-if="item.status !== 'init'"
type="button"
status="primary"
class="!mr-[4px]"
@click="handlePreview(item)"
>
{{ t('ms.upload.preview') }}
</MsButton>
<MsButton v-if="bugId" type="button" status="primary" class="!mr-[4px]" @click="downloadFile(item)">
{{ t('common.download') }}
</MsButton>
<MsButton
v-if="bugId && item.isUpdateFlag"
type="button"
status="primary"
@click="handleUpdateFile(item)"
>
{{ t('common.update') }}
</MsButton>
</div>
</template>
<template #title="{ item }">
<span v-if="item.isUpdateFlag" class="ml-4 flex items-center font-normal text-[rgb(var(--warning-6))]"
><icon-exclamation-circle-fill /> <span>{{ t('caseManagement.featureCase.fileIsUpdated') }}</span>
</span>
</template>
</FileList>
</div>
<a-divider class="ml-[16px]" direction="vertical" />
<div class="right mt-[16px] max-w-[433px] grow pr-[24px]">
<!-- <a-form-item
:label="t('bugManagement.handleMan')"
field="handleMan"
:rules="[{ required: true, message: t('bugManagement.edit.handleManIsRequired') }]"
>
<MsUserSelector
v-model:model-value="form.handleMan"
:type="UserRequestTypeEnum.PROJECT_PERMISSION_MEMBER"
:load-option-params="{ projectId: appStore.currentProjectId }"
placeholder="bugManagement.edit.handleManPlaceholder"
/>
</a-form-item> -->
<MsFormCreate
v-if="formRules.length"
ref="formCreateRef"
@ -101,7 +154,7 @@
/>
<a-form-item field="tag" :label="t('bugManagement.tag')">
<MsTagsInput
v-model:model-value="form.tag"
v-model:model-value="form.tags"
:placeholder="t('bugManagement.edit.tagPlaceholder')"
allow-clear
/>
@ -133,12 +186,14 @@
:get-list-fun-params="getListFunParams"
@save="saveSelectAssociatedFile"
/>
<a-image-preview v-model:visible="previewVisible" :src="imageUrl" />
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router';
import { FileItem, Message } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue';
import { FormItem, FormRuleItem } from '@/components/pure/ms-form-create/types';
@ -148,25 +203,32 @@
import MsUpload from '@/components/pure/ms-upload/index.vue';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import RelateFileDrawer from '@/components/business/ms-link-file/associatedFileDrawer.vue';
import TransferModal from '@/views/case-management/caseManagementFeature/components/tabContent/transferModal.vue';
// import { MsUserSelector } from '@/components/business/ms-user-selector';
// import { UserRequestTypeEnum } from '@/components/business/ms-user-selector/utils';
import {
createBug,
checkFileIsUpdateRequest,
createOrUpdateBug,
downloadFileRequest,
getAssociatedFileList,
getBugDetail,
getTemplateById,
getTemplateOption,
previewFile,
transferFileRequest,
updateFile,
} from '@/api/modules/bug-management';
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n';
import router from '@/router';
import { useAppStore } from '@/store';
import { downloadByteFile } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import { BugEditCustomField, BugEditCustomFieldItem, BugEditFormObject } from '@/models/bug-management';
import { AssociatedList, AttachFileInfo } from '@/models/caseManagement/featureCase';
import { TableQueryParams } from '@/models/common';
import { SelectValue } from '@/models/projectManagement/menuManagement';
import { BugManagementRouteEnum } from '@/enums/routeEnum';
import { convertToFile } from '../case-management/caseManagementFeature/components/utils';
@ -187,7 +249,7 @@
title: '',
description: '',
templateId: '',
tag: [],
tags: [],
});
const getListFunParams = ref<TableQueryParams>({
@ -201,12 +263,16 @@
const fileList = ref<MsFileItem[]>([]);
const formRules = ref<FormItem[]>([]);
const formItem = ref<FormRuleItem[]>([]);
const fApi = ref({});
const fApi = ref<any>(null);
const currentProjectId = computed(() => appStore.currentProjectId);
const associatedDrawer = ref(false);
const loading = ref(false);
const acceptType = ref('none'); // -
const isEdit = computed(() => !!route.query.id);
const isEdit = computed(() => !!route.query.id && route.params.mode === 'edit');
const bugId = computed(() => route.query.id || '');
const imageUrl = ref('');
const previewVisible = ref<boolean>(false);
const title = computed(() => {
return isEdit.value ? t('bugManagement.editBug') : t('bugManagement.createBug');
@ -262,6 +328,11 @@
(id: string) => !currentAlreadyAssociateFileList.value.includes(id) && !deleteAssociateFileIds.includes(id)
);
});
const transferVisible = ref<boolean>(false);
//
function transferFile() {
transferVisible.value = true;
}
//
const getFormRules = (arr: BugEditCustomField[]) => {
@ -273,11 +344,11 @@
name: item.fieldId,
label: item.fieldName,
value: item.value,
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : [],
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options,
required: item.required as boolean,
props: {
modelValue: item.value,
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : [],
options: item.platformOptionJson ? JSON.parse(item.platformOptionJson) : item.options,
},
};
});
@ -316,37 +387,53 @@
}
};
const handlePreview = (item: FileItem) => {
const { url } = item;
window.open(url);
};
const deleteFile = (item: FileItem) => {
fileList.value = fileList.value.filter((e) => e.uid !== item.uid);
};
const reupload = (item: FileItem) => {
fileList.value = fileList.value.map((e) => {
if (e.uid === item.uid) {
return {
...e,
status: 'init',
};
//
async function handlePreview(item: MsFileItem) {
try {
previewVisible.value = true;
if (item.status !== 'init') {
const res = await previewFile({
projectId: currentProjectId.value,
bugId: bugId.value as string,
fileId: item.uid,
associated: !item.local,
});
const blob = new Blob([res], { type: 'image/jpeg' });
imageUrl.value = URL.createObjectURL(blob);
} else {
imageUrl.value = item.url || '';
}
return e;
});
};
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
const uploadFile = (file: File) => {
const fileItem: FileItem = {
uid: `${Date.now()}`,
name: file.name,
status: 'init',
file,
};
fileList.value.push(fileItem);
return Promise.resolve(fileItem);
};
//
async function downloadFile(item: MsFileItem) {
try {
const res = await downloadFileRequest({
projectId: currentProjectId.value,
bugId: bugId.value as string,
fileId: item.uid,
associated: !item.local,
});
downloadByteFile(res, `${item.name}`);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
async function handleUpdateFile(item: MsFileItem) {
try {
await updateFile(currentProjectId.value, item.associationId);
Message.success(t('common.updateSuccess'));
} catch (error) {
console.log(error);
}
}
function beforeUpload(file: File) {
const _maxSize = 50 * 1024 * 1024;
@ -378,10 +465,10 @@
}
//
const saveHandler = async () => {
const saveHandler = async (isContinue = false) => {
formRef.value.validate((error: any) => {
if (!error) {
formCreateRef.value.formApi.validate(async (valid: any) => {
fApi.value.validate(async (valid: any) => {
if (valid === true) {
try {
loading.value = true;
@ -398,11 +485,41 @@
}
const tmpObj = {
...form.value,
tag: form.value.tag.join(',') || '',
customFields,
};
await createBug({ request: tmpObj, fileList: fileList.value as unknown as File[] });
Message.success(t('common.createSuccess'));
//
const res = await createOrUpdateBug({ request: tmpObj, fileList: fileList.value as unknown as File[] });
if (isEdit.value) {
Message.success(t('common.updateSuccess'));
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
});
} else {
Message.success(t('common.createSuccess'));
if (isContinue) {
//
const { templateId } = form.value;
//
await templateChange(templateId);
form.value = {
projectId: appStore.currentProjectId, // id
title: '',
description: '',
templateId,
tags: [],
};
//
fileList.value = [];
} else {
//
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_CREATE_SUCCESS,
query: {
id: res.id,
},
});
}
}
} catch (err) {
// eslint-disable-next-line no-console
console.log(err);
@ -418,11 +535,45 @@
const getDetailInfo = async () => {
const id = route.query.id as string;
// TODO:
if (!id) return;
const res = await getBugDetail(id);
const { customFields, file } = res;
formRules.value = customFields;
fileList.value = file;
const { customFields, templateId, attachments } = res;
if (attachments && attachments.length) {
attachmentsList.value = attachments;
//
const checkUpdateFileIds = await checkFileIsUpdateRequest(attachments.value.map((item: any) => item.id));
//
fileList.value = attachments
.map((fileInfo: any) => {
return {
...fileInfo,
name: fileInfo.fileName,
isUpdateFlag: checkUpdateFileIds.value.includes(fileInfo.id),
};
})
.map((fileInfo: any) => {
return convertToFile(fileInfo);
});
}
// ID
await templateChange(templateId);
const tmpObj = {};
if (customFields && Array.isArray(customFields)) {
customFields.forEach((item) => {
tmpObj[item.id] = item.value;
});
}
//
fApi.value.setValue(tmpObj);
//
form.value = {
id: res.id,
title: res.title,
description: res.description,
templateId: res.templateId,
tags: res.tags,
projectId: res.projectId,
};
};
const initDefaultFields = () => {
@ -451,9 +602,13 @@
);
onBeforeMount(() => {
if (isEdit.value) {
const { mode } = route.params;
if (mode === 'edit') {
//
getDetailInfo();
} else if (mode === 'copy') {
getDetailInfo();
initDefaultFields();
} else {
initDefaultFields();
}

View File

@ -28,8 +28,9 @@
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<template #name="{ record, rowIndex }">
<a-button type="text" class="px-0" @click="handleShowDetail(record.id, rowIndex)">{{ record.title }}</a-button>
<!-- ID -->
<template #num="{ record, rowIndex }">
<a-button type="text" class="px-0" @click="handleShowDetail(record.id, rowIndex)">{{ record.num }}</a-button>
</template>
<!-- 严重程度 -->
<template #severity="{ record }">
@ -134,7 +135,7 @@
/>
</template>
<script lang="ts" setup>
<script lang="ts" async setup>
import { Message, TableData } from '@arco-design/web-vue';
import { MsAdvanceFilter, timeSelectOptions } from '@/components/pure/ms-advance-filter';
@ -158,6 +159,7 @@
deleteSingleBug,
exportBug,
getBugList,
getCustomFieldHeader,
getExportConfig,
syncBugOpenSource,
} from '@/api/modules/bug-management';
@ -167,6 +169,7 @@
import router from '@/router';
import { useAppStore, useTableStore } from '@/store';
import useLicenseStore from '@/store/modules/setting/license';
import { customFieldToColumns } from '@/utils';
import { BugEditCustomField, BugListItem } from '@/models/bug-management';
import { RouteEnum } from '@/enums/routeEnum';
@ -225,7 +228,7 @@
},
{
title: 'bugManagement.bugName',
dataIndex: 'name',
dataIndex: 'title',
type: FilterType.SELECT,
selectProps: {
mode: 'static',
@ -259,17 +262,25 @@
const heightUsed = computed(() => 286 + (filterVisible.value ? 160 + (filterRowCount.value - 1) * 60 : 0));
//
const getCustomFieldColumns = async () => {
const res = await getCustomFieldHeader(projectId.value);
customFields.value = res;
return customFieldToColumns(res);
};
const columns: MsTableColumn = [
{
title: 'bugManagement.ID',
dataIndex: 'num',
slotName: 'num',
width: 80,
},
{
title: 'bugManagement.bugName',
editType: ColumnEditTypeEnum.INPUT,
dataIndex: 'title',
slotName: 'name',
width: 300,
showTooltip: true,
},
{
@ -347,7 +358,8 @@
width: 158,
},
];
await tableStore.initColumn(TableKeyEnum.BUG_MANAGEMENT, columns, 'drawer');
const customColumns = await getCustomFieldColumns();
await tableStore.initColumn(TableKeyEnum.BUG_MANAGEMENT, columns.concat(customColumns), 'drawer');
const handleNameChange = async (record: BugListItem) => {
try {
@ -399,34 +411,6 @@
setKeyword(v);
keyword.value = v;
await loadList();
customFields.value = propsRes.value.customFields || [
{
fieldId: 'handleUser',
fieldName: '处理人',
required: true,
apiFieldId: null,
defaultValue: null,
type: 'select',
options: null,
platformOptionJson:
'[{"text":"副驾仙人","value":"728495172886530"},{"text":"社恐的程序员","value":"728495172886645"}]',
supportSearch: null,
optionMethod: null,
isMutiple: true,
},
{
fieldId: 'status',
fieldName: '状态',
required: true,
apiFieldId: null,
defaultValue: null,
type: 'select',
options: null,
platformOptionJson: '[{"text":"新建","value":"100555929702892317"}]',
supportSearch: null,
optionMethod: null,
},
];
};
const handleAdvSearch = (filter: FilterResult) => {
@ -500,6 +484,8 @@
name: RouteEnum.BUG_MANAGEMENT_DETAIL,
query: {
id: record.id,
},
params: {
mode: 'copy',
},
});
@ -510,6 +496,8 @@
name: RouteEnum.BUG_MANAGEMENT_DETAIL,
query: {
id: record.id,
},
params: {
mode: 'edit',
},
});

View File

@ -1,6 +1,7 @@
export default {
bugManagement: {
index: '缺陷管理',
addBug: '创建缺陷',
editBug: '编辑缺陷',
recycle: '回收站',
createBug: '创建缺陷',
@ -80,6 +81,15 @@ export default {
handleUser: '处理人',
tag: '标签',
},
success: {
countDownTip: '后回到缺陷列表,也可以手动回到缺陷列表',
bugDetail: '缺陷详情',
addContinueCreate: '继续创建',
backBugList: '返回缺陷列表',
notNextTip: '下次不再提醒',
mightWantTo: '你可能还想',
caseRelated: '关联用例',
},
severityO: {
fatal: '致命',
serious: '严重',

View File

@ -36,7 +36,7 @@
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import { createBug, getTemplateDetailInfo, getTemplateOption } from '@/api/modules/bug-management/index';
import { createOrUpdateBug, getTemplateDetailInfo, getTemplateOption } from '@/api/modules/bug-management/index';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
@ -89,7 +89,10 @@
if (!errors) {
drawerLoading.value = true;
try {
await createBug({ request: { ...form.value, customFields: templateCustomFields.value }, fileList: [] });
await createOrUpdateBug({
request: { ...form.value, customFields: templateCustomFields.value },
fileList: [],
});
Message.success(t('caseManagement.featureCase.quicklyCreateDefectSuccess'));
if (!isContinue) {
handleDrawerCancel();

View File

@ -135,7 +135,7 @@
const formRef = ref<FormInstance>();
const loading = ref(false);
const isEdit = computed(() => props.currentProject && props.currentProject.id);
const isEdit = computed(() => !!(props.currentProject && props.currentProject.id));
const affiliatedOrgOption = ref<SystemOrgOption[]>([]);
const appStore = useAppStore();
const currentOrgId = computed(() => appStore.currentOrgId);

View File

@ -78,6 +78,7 @@
</template>
</MsBaseTable>
<AddProjectModal
v-if="addProjectVisible"
:visible="addProjectVisible"
:current-project="currentUpdateProject"
@cancel="handleAddProjectModalCancel"
@ -252,8 +253,8 @@
];
const showAddProject = () => {
currentUpdateProject.value = undefined;
addProjectVisible.value = true;
currentUpdateProject.value = undefined;
};
const handleEnableOrDisableProject = async (record: any, isEnable = true) => {
@ -317,6 +318,7 @@
};
const handleAddProjectModalCancel = (shouldSearch: boolean) => {
addProjectVisible.value = false;
currentUpdateProject.value = undefined;
if (shouldSearch) {
fetchData();
}