feat(缺陷管理): 详情编辑

This commit is contained in:
RubyLiu 2024-02-05 22:25:33 +08:00 committed by Craftsman
parent d6c1b9f55a
commit 01d8747468
7 changed files with 771 additions and 190 deletions

View File

@ -4,7 +4,7 @@ import MSR from '@/api/http/index';
import * as bugURL from '@/api/requrls/bug-management';
import { BugEditFormObject, BugExportParams, BugListItem } from '@/models/bug-management';
import { AssociatedList, OperationFile } from '@/models/caseManagement/featureCase';
import { AssociatedList, CreateOrUpdateDemand, DemandItem, OperationFile } from '@/models/caseManagement/featureCase';
import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
/**
@ -181,3 +181,30 @@ export function deleteSingleByRecycle(id: string) {
export function deleteBatchByRecycle(data: TableQueryParams) {
return MSR.post({ url: bugURL.getBatchDeleteUrl, data });
}
// ----------------------关联需求
// 已关联需求列表
export function getAssociatedList(data: TableQueryParams) {
return MSR.post<CommonList<DemandItem[]>>({ url: bugURL.getDemandListUrl, data });
}
// 缺陷管理-关联用例-未关联用例-列表分页
export function getUnAssociatedList(data: TableQueryParams) {
return MSR.post({ url: bugURL.getUnrelatedDemandListUrl, data });
}
// 未关联用例-模块树
export function getModuleTree(data: TableQueryParams) {
return MSR.post({ url: `${bugURL.getUnrelatedModuleTreeUrl}`, data });
}
// 批量关联需求
export function batchAssociation(data: TableQueryParams) {
return MSR.post({ url: bugURL.postAddDemandUrl, data });
}
// 取消关联
export function cancelAssociation(id: string) {
return MSR.get({ url: `${bugURL.getCancelDemandUrl}/${id}` });
}

View File

@ -51,3 +51,16 @@ export const getBatchRecoverUrl = '/bug/trash/batch-recover';
export const getDeleteSingleUrl = '/bug/trash/delete/';
// 批量删除
export const getBatchDeleteUrl = '/bug/trash/batch-delete';
// 获取关联的需求列表
export const getDemandListUrl = '/bug/case/page';
// 批量添加关联
export const postAddDemandUrl = '/bug/case/relate';
// 单个取消关联
export const getCancelDemandUrl = '/bug/case/un-relate/';
// 未关联的用例列表
export const getUnrelatedDemandListUrl = '/bug/case/un-relate/page';
// 未关联的模块树
export const getUnrelatedModuleTreeUrl = '/bug/case/un-relate/module/tree';
// 未关联的模块树 数量
export const getUnrelatedModuleTreeCountUrl = '/bug/case/un-relate/module/count';

View File

@ -88,20 +88,19 @@
<template #title>
{{ t('bugManagement.detail.detail') }}
</template>
<BugDetailTab />
<BugDetailTab :allow-edit="true" :detail-info="detailInfo" @update-success="updateSuccess" />
</a-tab-pane>
<a-tab-pane key="case">
<template #title>
{{ t('bugManagement.detail.case') }}
<a-badge class="relative top-1 ml-1" :count="1000" :max-count="99" />
</template>
<BugCaseTab />
<BugCaseTab :bug-id="detailInfo.id" />
</a-tab-pane>
<a-tab-pane key="comment">
<template #title>
{{ t('bugManagement.detail.comment') }}
</template>
<CommentTab ref="commentRef" bug-id="detailInfo.id" />
<CommentTab ref="commentRef" :bug-id="detailInfo.id" />
</a-tab-pane>
</a-tabs>
</div>
@ -119,6 +118,7 @@
:form-rule="formRules"
class="w-full"
:option="options"
@change="handleOK"
/>
<!-- 自定义字段结束 -->
<div class="baseItem">
@ -175,6 +175,7 @@
followBug,
getBugDetail,
getTemplateById,
updateBug,
} from '@/api/modules/bug-management/index';
import useFullScreen from '@/hooks/useFullScreen';
import { useI18n } from '@/hooks/useI18n';
@ -214,7 +215,7 @@
const activeTab = ref<string | number>('detail');
const detailInfo = ref<Record<string, any>>({}); // loadBug
const detailInfo = ref<Record<string, any>>({ match: [] }); // loadBug
const tags = ref([]);
//
@ -332,6 +333,25 @@
});
}
const handleOK = async () => {
const values = await fApi.value.validate();
if (values) {
const params = {
id: detailInfo.value.id,
projectId: currentProjectId.value,
...values,
};
try {
await updateBug(params);
Message.success(t('common.editSuccess'));
detailDrawerRef.value?.initDetail();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
};
//
const options = {
resetBtn: false, //

View File

@ -1,148 +1,286 @@
<template>
<div class="p-[16px]">
<div class="flex flex-row justify-between">
<a-dropdown trigger="hover">
<div>
<div class="flex items-center justify-between">
<a-dropdown @select="handleSelect">
<a-button type="primary"> {{ t('caseManagement.featureCase.linkCase') }} </a-button>
<template #content>
<a-doption @click="showRelatedDrawer('api')">{{ t('bugManagement.detail.apiCase') }}</a-doption>
<a-doption @click="showRelatedDrawer('scenario')">{{ t('bugManagement.detail.scenarioCase') }}</a-doption>
<a-doption @click="showRelatedDrawer('ui')">{{ t('bugManagement.detail.uiCase') }}</a-doption>
<a-doption @click="showRelatedDrawer('performance')">{{
t('bugManagement.detail.performanceCase')
<a-doption v-for="item of caseTypeOptions" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-doption>
</template>
<a-button type="primary">{{ t('bugManagement.edit.linkCase') }}</a-button>
</a-dropdown>
<a-input-search
v-model:model-value="keyword"
:placeholder="t('caseManagement.featureCase.searchByNameAndId')"
allow-clear
:placeholder="t('bugManagement.detail.searchCase')"
class="w-[230px]"
@search="searchUser"
@press-enter="searchUser"
class="mx-[8px] w-[240px]"
@search="searchCase"
@press-enter="searchCase"
></a-input-search>
</div>
<ms-base-table class="mt-[16px]" v-bind="propsRes" v-on="propsEvent">
<template #name="{ record }">
<span>{{ record.name }}</span>
<span v-if="record.adminFlag" class="ml-[4px] text-[var(--color-text-4)]">{{ `(${t('common.admin')})` }}</span>
<ms-base-table v-bind="propsRes" v-on="propsEvent">
<template #defectName="{ record }">
<span class="one-line-text max-w[300px]"> {{ record.name }}</span
><span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
</template>
<template #operation="{ record }">
<MsRemoveButton
:title="t('system.organization.removeName', { name: record.name })"
:sub-title-tip="t('system.organization.removeTip')"
@ok="handleRemove()"
/>
<MsButton @click="cancelLink(record)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton>
</template>
<template v-if="(keyword || '').trim() === ''" #empty>
<div class="flex w-full items-center justify-center">
{{ t('caseManagement.caseReview.tableNoData') }}
<a-dropdown @select="handleSelect">
<MsButton class="ml-[8px]">
{{ t('caseManagement.featureCase.linkCase') }}
</MsButton>
<template #content>
<a-doption v-for="item of caseTypeOptions" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-doption>
</template>
</a-dropdown>
</div>
</template>
</ms-base-table>
<MsCaseAssociate
v-model:visible="innerVisible"
v-model:project-id="innerProject"
v-model:currentSelectCase="currentSelectCase"
:ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getModuleTree"
:modules-params="modulesTreeParams"
:get-table-func="getUnAssociatedList"
:table-params="getTableParams"
:modules-count="modulesCount"
:module-options="caseTypeOptions"
:confirm-loading="confirmLoading"
:case-id="props.bugId"
:associated-ids="associatedIds"
:type="RequestModuleEnum.API_CASE"
@close="emit('close')"
@save="saveHandler"
>
</MsCaseAssociate>
</div>
<MsDrawer
:width="680"
:visible="relatedVisible"
unmount-on-close
:footer="false"
:mask="false"
@cancel="relatedVisible = false"
>
<template #title>
<div class="flex flex-row items-center gap-[4px]">
<div>{{ t('bugManagement.detail.relatedCase') }}</div>
<a-select>
<a-option>1</a-option>
<a-option>2</a-option>
<a-option>3</a-option>
</a-select>
</div>
</template>
</MsDrawer>
</template>
<script lang="ts" setup>
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
import { RequestModuleEnum } from '@/components/business/ms-case-associate/utils';
import { postProjectMemberByProjectId } from '@/api/modules/setting/organizationAndProject';
import {
batchAssociation,
cancelAssociation,
getAssociatedList,
getModuleTree,
getUnAssociatedList,
} from '@/api/modules/bug-management';
import { postTabletList } from '@/api/modules/project-management/menuManagement';
import { useI18n } from '@/hooks/useI18n';
import { formatPhoneNumber } from '@/utils';
import { useAppStore } from '@/store';
import useFeatureCaseStore from '@/store/modules/case/featureCase';
import type { TableQueryParams } from '@/models/common';
import { TableKeyEnum } from '@/enums/tableEnum';
import Message from '@arco-design/web-vue/es/message';
const appStore = useAppStore();
const featureCaseStore = useFeatureCaseStore();
const { t } = useI18n();
const relatedVisible = ref(false);
const relatedType = ref('api');
const currentProjectId = computed(() => appStore.currentProjectId);
const showRelatedDrawer = (type: string) => {
relatedType.value = type;
};
const props = defineProps<{
bugId: string; // id
}>();
const keyword = ref('');
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void;
(e: 'success', val: string[]): void;
(e: 'close'): void;
}>();
const keyword = ref<string>('');
const projectColumn: MsTableColumn = [
const columns: MsTableColumn = [
{
title: 'system.organization.userName',
title: 'caseManagement.featureCase.tableColumnID',
dataIndex: 'id',
width: 200,
showInTable: true,
showTooltip: true,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.tableColumnName',
slotName: 'name',
dataIndex: 'name',
showInTable: true,
showTooltip: true,
width: 200,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'system.organization.email',
dataIndex: 'email',
title: 'caseManagement.featureCase.projectName',
slotName: 'projectName',
dataIndex: 'projectName',
showInTable: true,
showTooltip: true,
width: 200,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'system.organization.phone',
dataIndex: 'phone',
title: 'caseManagement.featureCase.tableColumnVersion',
slotName: 'version',
dataIndex: 'version',
showInTable: true,
showTooltip: true,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.changeType',
slotName: 'type',
dataIndex: 'type',
showInTable: true,
showTooltip: true,
width: 300,
ellipsis: true,
showDrag: false,
},
{
title: 'caseManagement.featureCase.tableColumnActions',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'right',
width: 140,
showInTable: true,
showDrag: false,
},
{ title: 'system.organization.operation', slotName: 'operation' },
];
const { propsRes, propsEvent, loadList, setKeyword } = useTable(
postProjectMemberByProjectId,
{
heightUsed: 240,
columns: projectColumn,
scroll: { x: '100%' },
selectable: false,
noDisable: false,
pageSimple: true,
},
(record) => {
return {
...record,
phone: formatPhoneNumber(record.phone || ''),
};
}
);
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(getAssociatedList, {
columns,
scroll: { x: '100%' },
heightUsed: 340,
enableDrag: true,
});
async function searchUser() {
const innerVisible = ref(false);
const innerProject = ref(currentProjectId.value);
const associateForm = ref({
reviewers: [],
});
const associatedIds = ref<string[]>([]);
const currentSelectCase = ref<string>('');
const modulesTreeParams = ref<TableQueryParams>({});
const getTableParams = ref<TableQueryParams>({});
function handleSelect(value: string | number | Record<string, any> | undefined) {
currentSelectCase.value = value as string;
innerVisible.value = true;
}
async function getFetch() {
setLoadListParams({
keyword: keyword.value,
bugId: props.bugId,
});
await loadList();
const { msPagination } = propsRes.value;
featureCaseStore.setListCount(featureCaseStore.activeTab, msPagination?.total || 0);
}
async function cancelLink(record: any) {
try {
const { id } = record;
await cancelAssociation(id);
} catch (error) {
console.log(error);
}
}
const caseTypeOptions = ref<{ label: string; value: string }[]>([]);
const modulesCount = ref<Record<string, any>>({});
const confirmLoading = ref<boolean>(false);
async function saveHandler(params: TableQueryParams) {
try {
confirmLoading.value = true;
await batchAssociation(params);
Message.success(t('caseManagement.featureCase.AssociatedSuccess'));
innerVisible.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
confirmLoading.value = false;
}
}
const moduleMaps: Record<string, { label: string; value: string }[]> = {
apiTest: [
{
value: 'API',
label: t('caseManagement.featureCase.apiCase'),
},
{
value: 'SCENARIO',
label: t('caseManagement.featureCase.sceneCase'),
},
],
uiTest: [
{
value: 'UI',
label: t('caseManagement.featureCase.uiCase'),
},
],
loadTest: [
{
value: 'PERFORMANCE',
label: t('caseManagement.featureCase.propertyCase'),
},
],
};
async function getEnabledModules() {
const result = await postTabletList({ projectId: currentProjectId.value });
const caseArr = result.filter((item) => Object.keys(moduleMaps).includes(item.module));
caseArr.forEach((item: any) => {
const currentModule = moduleMaps[item.module];
caseTypeOptions.value.push(...currentModule);
});
currentSelectCase.value = caseTypeOptions.value[0].value;
}
async function searchCase() {
setKeyword(keyword.value);
await loadList();
}
const fetchData = async () => {
await loadList();
};
const handleRemove = async () => {
try {
Message.success(t('common.removeSuccess'));
fetchData();
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
};
onMounted(async () => {
getEnabledModules();
getFetch();
});
</script>
<style lang="less" scoped>
:deep(.custom-height) {
height: 100vh !important;
border: 1px solid red;
}
</style>
<style scoped></style>

View File

@ -2,111 +2,479 @@
<div class="p-[16px]">
<div class="header">
<div class="header-title">{{ t('bugManagement.edit.content') }}</div>
<div class="header-action">
<a-button>
<div v-if="!contentEditAble" v-permission="['PROJECT_BUG:READ+UPDATE']" class="header-action">
<a-button type="text" @click="contentEditAble = true">
<template #icon> <MsIconfont type="icon-icon_edit_outlined" /> </template>
{{ t('bugManagement.edit.contentEdit') }}
</a-button>
</div>
</div>
<div class="header">
<div class="header-title">{{ t('bugManagement.edit.content') }}</div>
</div>
<div class="mt-[8]" :class="{ 'max-h-[260px]': contentEditAble }">
<MsRichText
v-if="form.content"
v-if="contentEditAble"
v-model:raw="form.content"
v-model:filed-ids="fileIds"
:disabled="!contentEditAble"
:placeholder="t('bugManagement.edit.contentPlaceholder')"
:upload-image="handleUploadImage"
/>
<div v-else>-</div>
<div v-else v-dompurify-html="form?.content || '-'" class="text-[var(--color-text-3)]"></div>
</div>
</div>
<a-dropdown trigger="hover">
<template #content>
<MsUpload
v-model:file-list="fileList"
:auto-upload="false"
multiple
draggable
accept="unknown"
is-limit
size-unit="MB"
:max-size="500"
>
<a-doption>{{ t('bugManagement.edit.localUpload') }}</a-doption>
</MsUpload>
<a-doption @click="handleLineFile">{{ t('bugManagement.edit.linkFile') }}</a-doption>
</template>
<a-button type="outline">
<template #icon>
<icon-plus />
<div v-if="contentEditAble" class="flex justify-end">
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading">
{{ t('common.save') }}
</a-button></div
>
<div v-if="props.allowEdit">
<div class="font-medium text-[var(--color-text-1)]">
{{ t('bugManagement.edit.file') }}
</div>
<div class="mb-1">
<a-dropdown position="tr" trigger="hover">
<a-button v-permission="['PROJECT_BUG:READ+UPDATE']" type="outline">
<template #icon> <icon-plus class="text-[14px]" /> </template
>{{ t('system.orgTemplate.addAttachment') }}</a-button
>
<template #content>
<a-upload
ref="uploadRef"
v-model:file-list="fileList"
:auto-upload="false"
:show-file-list="false"
:before-upload="beforeUpload"
>
<template #upload-button>
<a-button type="text" class="!text-[var(--color-text-1)]">
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}
</a-button>
</template>
</a-upload>
<a-button type="text" class="!text-[var(--color-text-1)]" @click="associatedFile">
<MsIcon type="icon-icon_link-copy_outlined" size="16" />
{{ t('caseManagement.featureCase.associatedFile') }}
</a-button>
</template>
</a-dropdown>
</div>
</div>
<div class="mb-[8px] mt-[2px] text-[var(--color-text-4)]">{{ t('bugManagement.edit.fileExtra') }}</div>
<MsFileList
ref="fileListRef"
v-model:file-list="fileList"
:show-tab="false"
:request-params="{
bugId: bugId,
projectId: currentProjectId,
}"
:upload-func="uploadOrAssociationFile"
:handle-delete="deleteFileHandler"
:show-delete="props.allowEdit"
>
<template #actions="{ item }">
<div v-if="props.allowEdit">
<!-- 本地文件 -->
<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 type="button" status="primary" class="!mr-[4px]" @click="transferVisible = true">
{{ t('caseManagement.featureCase.storage') }}
</MsButton>
<TransferModal
v-model:visible="transferVisible"
:request-fun="transferFileRequest"
:params="{
projectId: currentProjectId,
bugId: bugId,
fileId: item.uid,
local: true,
}"
@success="emit('updateSuccess')"
/>
<MsButton
v-if="item.status === 'done'"
type="button"
status="primary"
class="!mr-[4px]"
@click="downloadFile(item)"
>
{{ t('caseManagement.featureCase.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="item.status === 'done'"
type="button"
status="primary"
class="!mr-[4px]"
@click="downloadFile(item)"
>
{{ t('caseManagement.featureCase.download') }}
</MsButton>
<MsButton
v-if="item.isUpdateFlag"
type="button"
status="primary"
class="!mr-[4px]"
@click="handleUpdateFile(item)"
>
{{ t('common.update') }}
</MsButton>
</div>
</div>
</template>
{{ t('bugManagement.edit.uploadFile') }}
</a-button>
</a-dropdown>
<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>
<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>
</MsFileList>
</div>
<RelateFileDrawer
v-model:visible="associatedDrawer"
:get-tree-request="getModules"
:get-count-request="getModulesCount"
:get-list-request="getAssociatedFileList"
:get-list-fun-params="getListFunParams"
@save="saveSelectAssociatedFile"
/>
<a-image-preview v-model:visible="previewVisible" :src="imageUrl" />
</template>
<script setup lang="ts">
import { FileItem } from '@arco-design/web-vue';
import { Message } from '@arco-design/web-vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIconfont from '@/components/pure/ms-icon-font/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import FileList from '@/components/pure/ms-upload/fileList.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import MsFileList from '@/components/pure/ms-upload/fileList.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 {
checkFileIsUpdateRequest,
deleteFileOrCancelAssociation,
downloadFileRequest,
editorUploadFile,
getAssociatedFileList,
previewFile,
transferFileRequest,
updateFile,
uploadOrAssociationFile,
} from '@/api/modules/bug-management';
import { updateCaseRequest } from '@/api/modules/case-management/featureCase';
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { downloadByteFile, sleep } from '@/utils';
import { scrollIntoView } from '@/utils/dom';
import { BugEditFormObject } from '@/models/bug-management';
import { AssociatedList, AttachFileInfo } from '@/models/caseManagement/featureCase';
import { TableQueryParams } from '@/models/common';
import { convertToFileByBug } from '@/views/bug-management/utils';
const { t } = useI18n();
const props = defineProps<{
detailInfo: BugEditFormObject;
allowEdit?: boolean; //
}>();
const emit = defineEmits<{
(e: 'updateSuccess'): void;
}>();
const appStore = useAppStore();
const transferVisible = ref<boolean>(false);
const previewVisible = ref<boolean>(false);
// id
const fileIds = ref<string[]>([]);
const imageUrl = ref<string>('');
const associatedDrawer = ref(false);
const fileListRef = ref<InstanceType<typeof MsFileList>>();
//
const contentEditAble = ref(false);
const fileList = ref<FileItem[]>([]);
const currentProjectId = computed(() => appStore.currentProjectId);
const fileList = ref<MsFileItem[]>([]);
const bugId = computed(() => props.detailInfo.id);
const attachmentsList = ref<AttachFileInfo[]>([]);
const getListFunParams = ref<TableQueryParams>({
combine: {
hiddenIds: [],
},
});
const form = ref({
content: '',
fileList: [],
deleteLocalFileIds: [] as string[],
unLinkRefIds: [] as string[],
linkFileIds: [] as string[],
});
const uploadFile = (file: File) => {
const fileItem: FileItem = {
uid: `${Date.now()}`,
name: file.name,
status: 'init',
file,
};
fileList.value.push(fileItem);
return Promise.resolve(fileItem);
};
const handlePreview = (item: FileItem) => {
const { url } = item;
window.open(url);
const handleFileFunc = async (attachments: AttachFileInfo[]) => {
if (attachments && attachments.length) {
attachmentsList.value = attachments;
//
const checkUpdateFileIds = await checkFileIsUpdateRequest(attachments.map((item: any) => item.fileId));
//
fileList.value =
attachments
.map((fileInfo: any) => {
return {
...fileInfo,
name: fileInfo.fileName,
isUpdateFlag: checkUpdateFileIds.includes(fileInfo.id),
};
})
.map((fileInfo: any) => {
return convertToFileByBug(fileInfo);
}) || [];
}
};
const deleteFile = (item: FileItem) => {
fileList.value = fileList.value.filter((e) => e.uid !== item.uid);
function handleCancel() {
contentEditAble.value = false;
}
const confirmLoading = ref<boolean>(false);
// function handleOK() {
// caseFormRef.value?.validate().then(async (res: any) => {
// if (!res) {
// try {
// confirmLoading.value = true;
// await updateCaseRequest();
// Message.success(t('caseManagement.featureCase.editSuccess'));
// handleCancel();
// emit('updateSuccess');
// } catch (error) {
// // eslint-disable-next-line no-console
// console.log(error);
// } finally {
// confirmLoading.value = false;
// }
// }
// return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
// });
// }
const initCurrentDetail = async (detail: BugEditFormObject) => {
const { attachments, content } = detail;
form.value.content = content;
handleFileFunc(attachments);
};
const reupload = (item: FileItem) => {
fileList.value = fileList.value.map((e) => {
if (e.uid === item.uid) {
return {
...e,
status: 'init',
};
}
return e;
//
async function deleteFileHandler(item: MsFileItem) {
try {
const params = {
id: item.uid,
local: item.local,
bugId,
projectId: currentProjectId.value,
};
await deleteFileOrCancelAssociation(params);
Message.success(
item.local ? t('caseManagement.featureCase.deleteSuccess') : t('caseManagement.featureCase.cancelLinkSuccess')
);
emit('updateSuccess');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
function beforeUpload(file: File) {
const _maxSize = 50 * 1024 * 1024;
if (file.size > _maxSize) {
Message.warning(t('ms.upload.overSize'));
return Promise.resolve(false);
}
return Promise.resolve(true);
}
function handleChange(_fileList: MsFileItem[]) {
fileList.value = _fileList.map((e) => {
return {
...e,
enable: true, //
local: true, //
};
});
};
}
const handleLineFile = () => {};
//
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 || '';
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
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);
}
}
function associatedFile() {
associatedDrawer.value = true;
}
//
function saveSelectAssociatedFile(fileData: AssociatedList[]) {
const fileResultList = fileData.map((fileInfo) => convertToFileByBug(fileInfo));
fileList.value.push(...fileResultList);
}
// localitem
const oldLocalFileList = computed(() => {
return attachmentsList.value.filter((item) => item.local).map((item: any) => item.uid);
});
// id
const associateFileIds = computed(() => {
return attachmentsList.value.filter((item: any) => !item.local).map((item: any) => item.id);
});
// list
const currentAlreadyAssociateFileList = computed(() => {
return fileList.value
.filter((item) => !item.local && !associateFileIds.value.includes(item.uid))
.map((item: any) => item.uid);
});
// item
const currentOldLocalFileList = computed(() => {
return fileList.value.filter((item) => item.local && item.status !== 'init').map((item: any) => item.uid);
});
// ID
const newAssociateFileListIds = computed(() => {
return fileList.value
.filter((item: any) => !item.local && !associateFileIds.value.includes(item.uid))
.map((item: any) => item.uid);
});
// id TODO
const unLinkFilesIds = computed(() => {
const deleteAssociateFileIds = fileList.value
.filter(
(item: any) =>
!currentAlreadyAssociateFileList.value.includes(item.uid) && associateFileIds.value.includes(item.uid)
)
.map((item) => item.uid);
return associateFileIds.value.filter(
(id: string) => !currentAlreadyAssociateFileList.value.includes(id) && !deleteAssociateFileIds.includes(id)
);
});
// id
const deleteFileMetaIds = computed(() => {
return oldLocalFileList.value
.filter((item: any) => !currentOldLocalFileList.value.includes(item.id))
.map((item: any) => item.id);
});
//
function getFilesParams() {
form.value.deleteLocalFileIds = deleteFileMetaIds.value;
form.value.unLinkRefIds = unLinkFilesIds.value;
form.value.linkFileIds = newAssociateFileListIds.value;
}
async function startUpload() {
await sleep(300);
fileListRef.value?.startUpload();
emit('updateSuccess');
}
//
watch(
() => fileList.value,
async (val) => {
const isNewFiles = val.filter((item) => item.status === 'init').length;
if (val && isNewFiles) {
startUpload();
}
}
);
//
async function handleUpdateFile(item: MsFileItem) {
try {
await updateFile(currentProjectId.value, item.associationId);
Message.success(t('common.updateSuccess'));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
async function handleUploadImage(file: File) {
const { data } = await editorUploadFile({
fileList: [file],
});
return data;
}
//
watch(
() => fileList.value,
(val) => {
if (val) {
getListFunParams.value.combine.hiddenIds = fileList.value.filter((item) => !item.local).map((item) => item.uid);
getFilesParams();
}
},
{ deep: true }
);
watchEffect(() => {
initCurrentDetail(props.detailInfo);
});
</script>
<style lang="less" scoped>
@ -115,6 +483,7 @@
justify-content: space-between;
align-items: center;
&-title {
font-weight: 500;
color: var(--color-text-1);
}
&-action {

View File

@ -80,20 +80,20 @@
]);
const isNextTip = ref<boolean>(false);
const countDown = ref<number>(5);
const countDown = ref<number>(15);
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;
timer.value = setInterval(() => {
if (countDown.value > 1) {
--countDown.value;
} else {
clearInterval(timer.value);
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
});
}
}, 1000);
timer.value = 15;
}
function isDoNotShowAgainChecked() {

View File

@ -218,6 +218,7 @@
} from '@/api/modules/bug-management';
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n';
import useVisit from '@/hooks/useVisit';
import router from '@/router';
import { useAppStore } from '@/store';
import { downloadByteFile } from '@/utils';
@ -274,9 +275,12 @@
const isEdit = computed(() => !!route.query.id && route.params.mode === 'edit');
const bugId = computed(() => route.query.id || '');
const isEditOrCopy = computed(() => !!bugId.value);
const isCopy = computed(() => route.params.mode === 'copy');
const imageUrl = ref('');
const previewVisible = ref<boolean>(false);
const richTextFileIds = ref<string[]>([]);
const visitedKey = 'doNotNextTipCreateBug';
const { getIsVisited } = useVisit(visitedKey);
const title = computed(() => {
return isEdit.value ? t('bugManagement.editBug') : t('bugManagement.createBug');
@ -507,10 +511,13 @@
});
});
}
const tmpObj = {
const tmpObj: BugEditFormObject = {
...form.value,
customFields,
};
if (isCopy.value) {
delete tmpObj.id;
}
//
const res = await createOrUpdateBug({ request: tmpObj, fileList: fileList.value as unknown as File[] });
if (isEdit.value) {
@ -520,6 +527,7 @@
});
} else {
Message.success(t('common.createSuccess'));
if (isContinue) {
//
const { templateId } = form.value;
@ -536,6 +544,12 @@
fileList.value = [];
} else {
//
if (getIsVisited()) {
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_INDEX,
});
return;
}
router.push({
name: BugManagementRouteEnum.BUG_MANAGEMENT_CREATE_SUCCESS,
query: {