feat(功能用例): 缺陷需求调整&附件联调&关联用例补充

This commit is contained in:
xinxin.wu 2023-12-20 14:23:16 +08:00 committed by 刘瑞斌
parent 81b9f083e5
commit 74026933e4
27 changed files with 785 additions and 411 deletions

View File

@ -1,7 +1,7 @@
import MSR from '@/api/http/index'; import MSR from '@/api/http/index';
import * as bugURL from '@/api/requrls/bug-management'; import * as bugURL from '@/api/requrls/bug-management';
import { BugExportParams, BugListItem } from '@/models/bug-management'; import { BugExportParams, BugListItem, DefaultTemplate } from '@/models/bug-management';
import { CommonList, TableQueryParams, TemplateOption } from '@/models/common'; import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
/** /**
@ -22,7 +22,7 @@ export function updateBatchBug(data: TableQueryParams) {
} }
export function createBug(data: TableQueryParams) { export function createBug(data: TableQueryParams) {
return MSR.post({ url: bugURL.postCreateBugUrl, data }); return MSR.uploadFile({ url: bugURL.postCreateBugUrl }, { request: data.request, fileList: data.fileList }, '');
} }
export function deleteSingleBug(data: TableQueryParams) { export function deleteSingleBug(data: TableQueryParams) {
@ -44,6 +44,10 @@ export function getTemplateById(data: TableQueryParams) {
export function getExportConfig(projectId: string) { export function getExportConfig(projectId: string) {
return MSR.get({ url: `${bugURL.getExportConfigUrl}${projectId}` }); return MSR.get({ url: `${bugURL.getExportConfigUrl}${projectId}` });
} }
// 获取模版详情
export function getTemplateDetailInfo(data: DefaultTemplate) {
return MSR.post({ url: `${bugURL.getTemplateDetailUrl}`, data });
}
// 同步缺陷 // 同步缺陷
export function syncBugOpenSource(params: { projectId: string }) { export function syncBugOpenSource(params: { projectId: string }) {

View File

@ -7,6 +7,7 @@ import {
DeleteReviewModuleUrl, DeleteReviewModuleUrl,
EditReviewUrl, EditReviewUrl,
FollowReviewUrl, FollowReviewUrl,
GetAssociatedIdsUrl,
GetReviewDetailUrl, GetReviewDetailUrl,
GetReviewListUrl, GetReviewListUrl,
GetReviewModulesUrl, GetReviewModulesUrl,
@ -107,3 +108,8 @@ export const getReviewDetail = (id: string) => {
export const getReviewUsers = (projectId: string, keyword: string) => { export const getReviewUsers = (projectId: string, keyword: string) => {
return MSR.get<ReviewUserItem[]>({ url: `${GetReviewUsersUrl}/${projectId}`, params: { keyword } }); return MSR.get<ReviewUserItem[]>({ url: `${GetReviewUsersUrl}/${projectId}`, params: { keyword } });
}; };
// 获取评审人员列表
export const getAssociatedIds = (reviewId: string) => {
return MSR.get<string[]>({ url: `${GetAssociatedIdsUrl}/${reviewId}` });
};

View File

@ -8,6 +8,7 @@ import {
BatchEditCaseUrl, BatchEditCaseUrl,
BatchMoveCaseUrl, BatchMoveCaseUrl,
CancelAssociationDemandUrl, CancelAssociationDemandUrl,
checkFileIsUpdateUrl,
CreateCaseModuleTreeUrl, CreateCaseModuleTreeUrl,
CreateCaseUrl, CreateCaseUrl,
CreateCommentItemUrl, CreateCommentItemUrl,
@ -26,6 +27,8 @@ import {
GetCommentListUrl, GetCommentListUrl,
GetDefaultTemplateFieldsUrl, GetDefaultTemplateFieldsUrl,
GetDemandListUrl, GetDemandListUrl,
GetDetailCaseReviewUrl,
GetFileIsUpdateUrl,
GetRecycleCaseListUrl, GetRecycleCaseListUrl,
GetRecycleCaseModulesCountUrl, GetRecycleCaseModulesCountUrl,
GetSearchCustomFieldsUrl, GetSearchCustomFieldsUrl,
@ -215,13 +218,22 @@ export function getTransferFileTree(projectId: string) {
// 预览文件 // 预览文件
export function previewFile(data: OperationFile) { export function previewFile(data: OperationFile) {
return MSR.post({ url: PreviewFileUrl, data }); return MSR.post({ url: PreviewFileUrl, data, responseType: 'blob' }, { isTransformResponse: false });
} }
// 下载文件 // 下载文件
export function downloadFileRequest(data: OperationFile) { export function downloadFileRequest(data: OperationFile) {
return MSR.post({ url: DownloadFileUrl, data, responseType: 'blob' }, { isTransformResponse: false }); return MSR.post({ url: DownloadFileUrl, data, responseType: 'blob' }, { isTransformResponse: false });
} }
// 检查文件是否更新
export function checkFileIsUpdateRequest(data: string[]) {
return MSR.post({ url: checkFileIsUpdateUrl, data });
}
// 更新文件
export function updateFile(projectId: string, id: string) {
return MSR.get({ url: `${GetFileIsUpdateUrl}/${projectId}/${id}` });
}
// 删除文件或取消关联用例文件 // 删除文件或取消关联用例文件
export function deleteFileOrCancelAssociation(data: OperationFile) { export function deleteFileOrCancelAssociation(data: OperationFile) {
@ -252,4 +264,9 @@ export function DeleteCommentList(commentId: string) {
return MSR.post({ url: `${DeleteCommentItemUrl}/${commentId}` }); return MSR.post({ url: `${DeleteCommentItemUrl}/${commentId}` });
} }
// 评审
export function getDetailCaseReviewPage(data: TableQueryParams) {
return MSR.post<CommonList<CaseManagementTable>>({ url: GetDetailCaseReviewUrl, data });
}
export default {}; export default {};

View File

@ -7,5 +7,6 @@ export const postBatchDeleteBugUrl = '/bug/batch-delete';
export const getTemplateUrl = '/bug/template'; export const getTemplateUrl = '/bug/template';
export const getTemplageOption = '/bug/template/option'; export const getTemplageOption = '/bug/template/option';
export const getExportConfigUrl = '/bug/export/columns/'; export const getExportConfigUrl = '/bug/export/columns/';
export const getTemplateDetailUrl = '/bug/template/detail';
export const getSyncBugOpenSourceUrl = '/bug/sync/'; export const getSyncBugOpenSourceUrl = '/bug/sync/';
export const postExportBugUrl = '/bug/export'; export const postExportBugUrl = '/bug/export';

View File

@ -13,3 +13,4 @@ export const MoveReviewModuleUrl = '/case/review/module/move'; // 移动评审
export const AddReviewModuleUrl = '/case/review/module/add'; // 新增评审模块 export const AddReviewModuleUrl = '/case/review/module/add'; // 新增评审模块
export const GetReviewModulesUrl = '/case/review/module/tree'; // 获取评审模块树 export const GetReviewModulesUrl = '/case/review/module/tree'; // 获取评审模块树
export const DeleteReviewModuleUrl = '/case/review/module/delete'; // 删除评审模块 export const DeleteReviewModuleUrl = '/case/review/module/delete'; // 删除评审模块
export const GetAssociatedIdsUrl = '/case/review/detail/get-ids'; // 获取已关联用例id集合

View File

@ -85,6 +85,10 @@ export const DownloadFileUrl = '/attachment/download';
export const deleteFileOrCancelAssociationUrl = '/attachment/delete/file'; export const deleteFileOrCancelAssociationUrl = '/attachment/delete/file';
// 获取转存目录 // 获取转存目录
export const getTransferTreeUrl = '/attachment/options'; export const getTransferTreeUrl = '/attachment/options';
// 附件是否更新
export const GetFileIsUpdateUrl = '/attachment/update';
// 检查文件是否更新
export const checkFileIsUpdateUrl = '/attachment/check-update';
// 评论列表 // 评论列表
export const GetCommentListUrl = '/functional/case/comment/get/list'; export const GetCommentListUrl = '/functional/case/comment/get/list';
@ -94,5 +98,7 @@ export const CreateCommentItemUrl = '/functional/case/comment/save';
export const UpdateCommentItemUrl = '/functional/case/comment/update'; export const UpdateCommentItemUrl = '/functional/case/comment/update';
// 删除评论 // 删除评论
export const DeleteCommentItemUrl = '/functional/case/comment/delete'; export const DeleteCommentItemUrl = '/functional/case/comment/delete';
// 获取详情用例评审
export const GetDetailCaseReviewUrl = '/functional/case/review/page';
export default {}; export default {};

View File

@ -9,11 +9,14 @@
<template #headerLeft> <template #headerLeft>
<div class="float-left"> <div class="float-left">
<a-select <a-select
v-if="props?.moduleOptions"
v-model="caseType" v-model="caseType"
class="ml-2 max-w-[100px]" class="ml-2 max-w-[100px]"
:placeholder="t('caseManagement.featureCase.PleaseSelect')" :placeholder="t('caseManagement.featureCase.PleaseSelect')"
> >
<a-option v-for="item of actionType" :key="item.value" :value="item.value">{{ item.name }}</a-option> <a-option v-for="item of props?.moduleOptions" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-option>
</a-select> </a-select>
</div> </div>
</template> </template>
@ -21,7 +24,7 @@
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]"> <div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<MsProjectSelect v-model:project="innerProject" class="mb-[16px]" /> <MsProjectSelect v-model:project="innerProject" class="mb-[16px]" />
<a-select v-if="caseType === 'API'" v-model="protocolType" class="mb-[16px] ml-2 max-w-[90px]"> <a-select v-if="caseType === 'API_CASE'" v-model="protocolType" class="mb-[16px] ml-2 max-w-[90px]">
<a-option v-for="item of protocolOptions" :key="item" :value="item">{{ item }}</a-option> <a-option v-for="item of protocolOptions" :key="item" :value="item">{{ item }}</a-option>
</a-select> </a-select>
</div> </div>
@ -34,8 +37,8 @@
<div class="folder"> <div class="folder">
<div :class="getFolderClass('all')" @click="setActiveFolder('all')"> <div :class="getFolderClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" /> <MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name">{{ t('caseManagement.caseReview.allReviews') }}</div> <div class="folder-name">{{ t('caseManagement.featureCase.allCase') }}</div>
<div class="folder-count">({{ allCaseCount }})</div> <div class="folder-count">({{ props.modulesCount['all'] }})</div>
</div> </div>
</div> </div>
<a-divider class="my-[8px]" /> <a-divider class="my-[8px]" />
@ -69,6 +72,7 @@
<MsAdvanceFilter <MsAdvanceFilter
v-model:keyword="keyword" v-model:keyword="keyword"
:filter-config-list="filterConfigList" :filter-config-list="filterConfigList"
:custom-fields-config-list="searchCustomFields"
:row-count="filterRowCount" :row-count="filterRowCount"
:search-placeholder="t('caseManagement.caseReview.searchPlaceholder')" :search-placeholder="t('caseManagement.caseReview.searchPlaceholder')"
@keyword-search="searchCase" @keyword-search="searchCase"
@ -94,7 +98,7 @@
</MsAdvanceFilter> </MsAdvanceFilter>
<ms-base-table v-bind="propsRes" no-disable class="mt-[16px]" v-on="propsEvent"> <ms-base-table v-bind="propsRes" no-disable class="mt-[16px]" v-on="propsEvent">
<template #caseLevel="{ record }"> <template #caseLevel="{ record }">
<caseLevel :case-level="record.caseLevel" /> <caseLevel :case-level="(getCaseLevel(record) as CaseLevel)" />
</template> </template>
</ms-base-table> </ms-base-table>
<div class="footer"> <div class="footer">
@ -103,13 +107,13 @@
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<slot name="footerRight"> <slot name="footerRight">
<a-button type="secondary" :disabled="loading" class="mr-[12px]" @click="cancel"> <a-button type="secondary" :disabled="props.confirmLoading" class="mr-[12px]" @click="cancel">
{{ t('common.cancel') }} {{ t('common.cancel') }}
</a-button> </a-button>
<a-button <a-button
type="primary" type="primary"
:loading="loading" :loading="props.confirmLoading"
:disabled="propsRes.selectedKeys.size === 0 || props.okButtonDisabled" :disabled="propsRes.selectedKeys.size === 0"
@click="handleConfirm" @click="handleConfirm"
> >
{{ t('ms.case.associate.associate') }} {{ t('ms.case.associate.associate') }}
@ -123,11 +127,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeMount, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter'; import { CustomTypeMaps, MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type'; import { FilterFormItem, FilterType } from '@/components/pure/ms-advance-filter/type';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -138,100 +141,52 @@
import type { MsTreeNodeData } from '@/components/business/ms-tree/types'; import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import caseLevel from './caseLevel.vue'; import caseLevel from './caseLevel.vue';
import { getCustomFieldsTable } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils'; import { mapTree } from '@/utils';
import type { CaseManagementTable, CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { CommonList, TableQueryParams } from '@/models/common';
import { ModuleTreeNode } from '@/models/projectManagement/file'; import { ModuleTreeNode } from '@/models/projectManagement/file';
import type { CaseLevel } from './types';
const appStore = useAppStore();
const { t } = useI18n();
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
project: string; project: string;
getModulesFunc: (params: any) => Promise<ModuleTreeNode[]>; getModulesFunc: (projectId: string) => Promise<ModuleTreeNode[]>; //
modulesCount?: Record<string, number>; // getTableFunc: (params: TableQueryParams) => Promise<CommonList<CaseManagementTable>>; //
tableParams?: TableQueryParams; //
modulesCount: Record<string, number>; //
okButtonDisabled?: boolean; // okButtonDisabled?: boolean; //
selectedKeys?: string[]; // id currentSelectCase: string | number | Record<string, any> | undefined; //
moduleOptions?: { label: string; value: string }[]; //
confirmLoading: boolean;
associatedIds: string[]; // id
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:visible', val: boolean): void; (e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void; (e: 'update:project', val: string): void;
(e: 'init', val: string[]): void; (e: 'update:currentSelectCase', val: string | number | Record<string, any> | undefined): void;
(e: 'folderNodeSelect', ids: (string | number)[], springIds: string[]): void; (e: 'init', val: CaseModuleQueryParams): void; //
(e: 'success', val: string[]): void;
(e: 'close'): void; (e: 'close'): void;
(e: 'save', params: TableQueryParams): void; // table
}>(); }>();
const appStore = useAppStore();
const { t } = useI18n();
const virtualListProps = computed(() => { const virtualListProps = computed(() => {
return { return {
height: 'calc(100vh - 251px)', height: 'calc(100vh - 251px)',
}; };
}); });
const innerVisible = ref(props.visible);
const innerProject = ref(props.project);
//
const protocolType = ref('HTTP');
const caseType = ref('API');
const protocolOptions = ref(['DUBBO', 'HTTP', 'TCP', 'SQL']);
const actionType = ref([
{
value: 'API',
name: t('caseManagement.featureCase.apiCase'),
},
{
value: 'SCENE',
name: t('caseManagement.featureCase.sceneCase'),
},
{
value: 'UI',
name: t('caseManagement.featureCase.uiCase'),
},
{
value: 'PERFORMANCE',
name: t('caseManagement.featureCase.propertyCase'),
},
]);
watch(
() => props.visible,
(val) => {
innerVisible.value = val;
}
);
watch(
() => innerVisible.value,
(val) => {
if (!val) {
emit('update:visible', false);
}
}
);
watch(
() => props.project,
(val) => {
innerProject.value = val;
}
);
watch(
() => innerProject.value,
(val) => {
emit('update:project', val);
}
);
const activeFolder = ref('all'); const activeFolder = ref('all');
const activeFolderName = ref(t('ms.case.associate.allCase')); const activeFolderName = ref(t('ms.case.associate.allCase'));
const allCaseCount = ref(0);
const filterRowCount = ref(0); const filterRowCount = ref(0);
const filterConfigList = ref<FilterFormItem[]>([]);
function getFolderClass(id: string) { function getFolderClass(id: string) {
return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text'; return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text';
@ -247,9 +202,14 @@
activeFolder.value = id; activeFolder.value = id;
activeFolderName.value = t('ms.case.associate.allCase'); activeFolderName.value = t('ms.case.associate.allCase');
selectedModuleKeys.value = []; selectedModuleKeys.value = [];
emit('folderNodeSelect', [id], []);
} }
const innerVisible = ref(props.visible);
const innerProject = ref(props.project);
const protocolType = ref('HTTP'); //
const protocolOptions = ref(['HTTP']);
/** /**
* 初始化模块树 * 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点 * @param isSetDefaultKey 是否设置第一个节点为选中节点
@ -257,8 +217,16 @@
async function initModules(isSetDefaultKey = false) { async function initModules(isSetDefaultKey = false) {
try { try {
moduleLoading.value = true; moduleLoading.value = true;
const res = await props.getModulesFunc(appStore.currentProjectId); const res = await props.getModulesFunc(innerProject.value);
folderTree.value = res; folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
return {
...e,
hideMoreAction: e.id === 'root',
draggable: false,
disabled: false,
count: props.modulesCount?.[e.id] || 0,
};
});
if (isSetDefaultKey) { if (isSetDefaultKey) {
selectedModuleKeys.value = [folderTree.value[0].id]; selectedModuleKeys.value = [folderTree.value[0].id];
activeFolderName.value = folderTree.value[0].name; activeFolderName.value = folderTree.value[0].name;
@ -267,13 +235,7 @@
offspringIds.push(e.id); offspringIds.push(e.id);
return e; return e;
}); });
emit('folderNodeSelect', selectedModuleKeys.value, offspringIds);
} }
emit(
'init',
folderTree.value.map((e) => e.name)
);
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(error); console.log(error);
@ -283,40 +245,31 @@
} }
/** /**
* 处理文件夹树节点选中事件 * 处理模块树节点选中事件
*/ */
const offspringIds = ref<string[]>([]);
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) { function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
selectedModuleKeys.value = _selectedKeys as string[]; selectedModuleKeys.value = _selectedKeys as string[];
activeFolder.value = node.id; activeFolder.value = node.id;
activeFolderName.value = node.name; activeFolderName.value = node.name;
const offspringIds: string[] = []; offspringIds.value = [];
mapTree(node.children || [], (e) => { mapTree(node.children || [], (e) => {
offspringIds.push(e.id); offspringIds.value.push(e.id);
return e; return e;
}); });
emit('folderNodeSelect', _selectedKeys, offspringIds);
} }
onBeforeMount(() => { //
initModules(); const caseType = computed({
get() {
return props.currentSelectCase;
},
set(val) {
emit('update:currentSelectCase', val);
},
}); });
/**
* 初始化模块资源数量
*/
watch(
() => props.modulesCount,
(obj) => {
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
return {
...node,
count: obj?.[node.id] || 0,
};
});
}
);
const keyword = ref(''); const keyword = ref('');
const version = ref(''); const version = ref('');
const versionOptions = ref([ const versionOptions = ref([
@ -343,7 +296,7 @@
sortable: { sortable: {
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
}, },
width: 90, width: 200,
}, },
{ {
title: 'ms.case.associate.caseName', title: 'ms.case.associate.caseName',
@ -352,7 +305,7 @@
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
}, },
showTooltip: true, showTooltip: true,
width: 200, width: 300,
}, },
{ {
title: 'ms.case.associate.caseLevel', title: 'ms.case.associate.caseLevel',
@ -363,137 +316,207 @@
{ {
title: 'ms.case.associate.version', title: 'ms.case.associate.version',
slotName: 'version', slotName: 'version',
width: 80, width: 200,
}, },
{ {
title: 'ms.case.associate.tags', title: 'ms.case.associate.tags',
dataIndex: 'tags', dataIndex: 'tags',
isTag: true, isTag: true,
}, },
{
title: 'caseManagement.featureCase.tableColumnCreateUser',
slotName: 'createUser',
dataIndex: 'createUser',
showInTable: true,
width: 300,
},
{
title: 'caseManagement.featureCase.tableColumnCreateTime',
slotName: 'createTime',
dataIndex: 'createTime',
showInTable: true,
width: 300,
},
]; ];
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable( const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
() => props.getTableFunc,
Promise.resolve({
list: [
{
id: 'ded3d43',
name: '测试评审1',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 0, //
caseCount: 100,
passCount: 0,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'g545hj4',
name: '测试评审2',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 1, //
caseCount: 105,
passCount: 50,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'hj65b54',
name: '测试评审3',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 2, //
caseCount: 125,
passCount: 70,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'wefwefw',
name: '测试评审4',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 3, //
caseCount: 130,
passCount: 70,
failCount: 10,
reviewCount: 0,
reviewingCount: 50,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
],
current: 1,
pageSize: 10,
total: 2,
}),
{ {
columns, columns,
scroll: {
x: '100%',
},
showSetting: false, showSetting: false,
selectable: true, selectable: true,
showSelectAll: true, showSelectAll: true,
}, },
(item) => { (record) => {
return { return {
...item, ...record,
tags: item.tags?.map((e: string) => ({ id: e, name: e })) || [], tags: (JSON.parse(record.tags) || []).map((item: string, i: number) => {
return {
id: `${record.id}-${i}`,
name: item,
};
}),
}; };
} }
); );
function searchCase() { const searchParams = ref<TableQueryParams>({
setLoadListParams({ moduleIds: [],
version: version.value, version: version.value,
keyword: keyword.value,
});
loadList();
}
onBeforeMount(() => {
searchCase();
}); });
const loading = ref(false); function getLoadListParams() {
if (activeFolder.value === 'all') {
async function handleConfirm() { searchParams.value.moduleIds = [];
try { } else {
loading.value = true; searchParams.value.moduleIds = [activeFolder.value, ...offspringIds.value];
Message.success(t('ms.case.associate.associateSuccess'));
innerVisible.value = false;
emit('success', Array.from(propsRes.value.selectedKeys));
resetSelector();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
} }
setLoadListParams({
...searchParams.value,
...props.tableParams,
keyword: keyword.value,
projectId: innerProject.value,
excludeIds: [...props.associatedIds],
});
}
const combine = ref<Record<string, any>>({});
const searchCustomFields = ref<FilterFormItem[]>([]);
const filterConfigList = ref<FilterFormItem[]>([]);
async function initFilter() {
const result = await getCustomFieldsTable(appStore.currentProjectId);
filterConfigList.value = [
{
title: 'caseManagement.featureCase.tableColumnID',
dataIndex: 'id',
type: FilterType.INPUT,
},
{
title: 'caseManagement.featureCase.tableColumnName',
dataIndex: 'name',
type: FilterType.INPUT,
},
{
title: 'caseManagement.featureCase.tableColumnModule',
dataIndex: 'moduleId',
type: FilterType.TREE_SELECT,
treeSelectData: folderTree.value,
treeSelectProps: {
fieldNames: {
title: 'name',
key: 'id',
children: 'children',
},
},
},
{
title: 'caseManagement.featureCase.tableColumnVersion',
dataIndex: 'versionId',
type: FilterType.INPUT,
},
{
title: 'caseManagement.featureCase.tableColumnCreateUser',
dataIndex: 'createUser',
type: FilterType.SELECT,
selectProps: {
mode: 'static',
options: [],
},
},
{
title: 'caseManagement.featureCase.tableColumnCreateTime',
dataIndex: 'createTime',
type: FilterType.DATE_PICKER,
},
{
title: 'bugManagement.createTime',
dataIndex: 'createTime',
type: FilterType.DATE_PICKER,
},
{
title: 'caseManagement.featureCase.tableColumnUpdateUser',
dataIndex: 'updateUser',
type: FilterType.SELECT,
selectProps: {
mode: 'static',
options: [],
},
},
{
title: 'caseManagement.featureCase.tableColumnUpdateTime',
dataIndex: 'updateTime',
type: FilterType.DATE_PICKER,
},
{
title: 'caseManagement.featureCase.tableColumnTag',
dataIndex: 'tags',
type: FilterType.TAGS_INPUT,
},
];
//
searchCustomFields.value = result.map((item: any) => {
const FilterTypeKey: keyof typeof FilterType = CustomTypeMaps[item.type].type;
const formType = FilterType[FilterTypeKey];
const formObject = CustomTypeMaps[item.type];
const { props: formProps } = formObject;
const currentItem: any = {
title: item.name,
dataIndex: item.id,
type: formType,
};
if (formObject.propsKey && formProps.options) {
formProps.options = item.options;
currentItem[formObject.propsKey] = {
...formProps,
};
}
return currentItem;
});
}
//
function initModuleCount() {
emit('init', {
keyword: keyword.value,
moduleIds: [],
projectId: innerProject.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
combine: combine.value,
});
}
function searchCase() {
getLoadListParams();
loadList();
initModuleCount();
}
//
function handleConfirm() {
const { excludeKeys, selectedKeys, selectorStatus } = propsRes.value;
const { versionId, moduleIds } = searchParams.value;
const params = {
excludeIds: [...excludeKeys],
selectIds: selectorStatus === 'all' ? [] : [...selectedKeys],
selectAll: selectorStatus === 'all',
moduleIds,
versionId,
refId: '',
projectId: innerProject.value,
};
emit('save', params);
}
//
function getCaseLevel(record: CaseManagementTable) {
const caseLevelRes = record.customFields.find((item: any) => item.name === '用例等级');
if (caseLevelRes) {
return JSON.parse(caseLevelRes.value).replaceAll('P', '') * 1;
}
return 0;
} }
function cancel() { function cancel() {
@ -502,6 +525,82 @@
emit('close'); emit('close');
} }
watch(
() => props.visible,
(val) => {
innerVisible.value = val;
if (val) {
searchCase();
initFilter();
}
}
);
watch(
() => innerVisible.value,
(val) => {
if (!val) {
emit('update:visible', false);
}
}
);
//
watch(
() => caseType.value,
(val) => {
if (val) {
initModules(true);
searchCase();
}
}
);
watch(
() => props.project,
(val) => {
if (val) {
innerProject.value = val;
}
}
);
watch(
() => innerProject.value,
(val) => {
emit('update:project', val);
resetSelector();
initModules(true);
searchCase();
}
);
watch(
() => activeFolder.value,
() => {
searchCase();
}
);
/**
* 初始化模块数量
*/
watch(
() => props.modulesCount,
(obj) => {
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
return {
...node,
count: obj?.[node.id] || 0,
};
});
}
);
onBeforeMount(() => {
innerProject.value = appStore.currentProjectId;
});
defineExpose({ defineExpose({
initModules, initModules,
}); });

View File

@ -29,9 +29,12 @@
</a-avatar> </a-avatar>
</template> </template>
<template #title> <template #title>
<a-tooltip :content="item.file.name"> <div class="flex items-center">
<div class="one-line-text max-w-[80%] font-normal">{{ item.file.name }}</div> <a-tooltip :content="item.file.name">
</a-tooltip> <div class="one-line-text max-w-[80%] font-normal">{{ item.file.name }}</div>
</a-tooltip>
<slot name="title" :item="item"></slot>
</div>
</template> </template>
<template #description> <template #description>
<div v-if="item.status === UploadStatus.init" class="text-[12px] leading-[16px] text-[var(--color-text-4)]"> <div v-if="item.status === UploadStatus.init" class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
@ -83,7 +86,13 @@
> >
{{ t('ms.upload.reUpload') }} {{ t('ms.upload.reUpload') }}
</MsButton> </MsButton>
<MsButton v-if="props.showDelete" type="button" status="danger" class="!mr-[4px]" @click="deleteFile(item)"> <MsButton
v-if="props.showDelete"
type="button"
:status="item.deleteContent ? 'primary' : 'danger'"
class="!mr-[4px]"
@click="deleteFile(item)"
>
{{ t(item.deleteContent) || t('ms.upload.delete') }} {{ t(item.deleteContent) || t('ms.upload.delete') }}
</MsButton> </MsButton>
<slot name="actions" :item="item"></slot> <slot name="actions" :item="item"></slot>

View File

@ -12,7 +12,7 @@
:disabled="props.disabled" :disabled="props.disabled"
:class="getAllScreenClass" :class="getAllScreenClass"
:style="{ :style="{
width: props.isAllScreen ? `calc(100% - ${menuWidth}px - 16px)` : '100%', width: props.isAllScreen ? `calc(100% - ${menuWidth}px - 16px)` : '',
}" }"
@change="handleChange" @change="handleChange"
@before-upload="beforeUpload" @before-upload="beforeUpload"

View File

@ -26,4 +26,11 @@ export interface BugExportParams extends BatchApiParams {
bugExportColumns: BugExportColumn[]; // 导出字段 bugExportColumns: BugExportColumn[]; // 导出字段
} }
// 获取默认模版缺陷
export interface DefaultTemplate {
id: string;
projectId: string;
fromStatusId?: string;
platformBugKey?: string;
}
export default {}; export default {};

View File

@ -71,7 +71,7 @@ export interface CaseManagementTable {
updateTime: string; updateTime: string;
deleteTime: string; deleteTime: string;
steps: string; steps: string;
customFields: CustomAttributes[]; // 自定义字段集合 customFields: customFieldsItem[]; // 自定义字段集合
[key: string]: any; [key: string]: any;
} }

View File

@ -14,7 +14,7 @@
@loaded="loadedCase" @loaded="loadedCase"
> >
<template #titleLeft> <template #titleLeft>
<div class="flex items-center"><caseLevel :case-level="(caseLevels as CaseLevel)" /></div> <div class="flex items-center"><caseLevel :case-level="caseLevels" /></div>
</template> </template>
<template #titleRight="{ loading }"> <template #titleRight="{ loading }">
<div class="rightButtons flex items-center"> <div class="rightButtons flex items-center">
@ -118,7 +118,7 @@
<TabCaseTable v-else-if="activeTab === 'case'" /> <TabCaseTable v-else-if="activeTab === 'case'" />
<TabDefect v-else-if="activeTab === 'bug'" /> <TabDefect v-else-if="activeTab === 'bug'" />
<TabDependency v-else-if="activeTab === 'dependency'" /> <TabDependency v-else-if="activeTab === 'dependency'" />
<TabCaseReview v-else-if="activeTab === 'caseReview'" /> <TabCaseReview v-else-if="activeTab === 'caseReview'" :case-id="props.detailId" />
<TabTestPlan v-else-if="activeTab === 'testPlan'" /> <TabTestPlan v-else-if="activeTab === 'testPlan'" />
<TabComment v-else-if="activeTab === 'comments'" :case-id="props.detailId" /> <TabComment v-else-if="activeTab === 'comments'" :case-id="props.detailId" />
<TabChangeHistory v-else-if="activeTab === 'changeHistory'" /> <TabChangeHistory v-else-if="activeTab === 'changeHistory'" />
@ -296,13 +296,13 @@
const detailInfo = ref<DetailCase>({ ...initDetail }); const detailInfo = ref<DetailCase>({ ...initDetail });
const customFields = ref<CustomAttributes[]>([]); const customFields = ref<CustomAttributes[]>([]);
const caseLevels = ref(0); const caseLevels = ref<CaseLevel>(0);
function loadedCase(detail: DetailCase) { function loadedCase(detail: DetailCase) {
detailInfo.value = { ...detail }; detailInfo.value = { ...detail };
customFields.value = detailInfo.value.customFields; customFields.value = detailInfo.value.customFields;
const caseLevelsValue = customFields.value.find((item) => item.fieldName === '用例等级')?.defaultValue; const caseLevelsValue = customFields.value.find((item) => item.fieldName === '用例等级')?.defaultValue;
if (caseLevelsValue) { if (caseLevelsValue) {
caseLevels.value = JSON.parse(caseLevelsValue).replaceAll('P', '') * 1; caseLevels.value = (JSON.parse(caseLevelsValue).replaceAll('P', '') * 1) as CaseLevel;
} }
} }

View File

@ -76,9 +76,9 @@
<!-- 渲染自定义字段开始 --> <!-- 渲染自定义字段开始 -->
<template v-for="item in customFieldsColumns" :key="item.slotName" #[item.slotName]="{ record }"> <template v-for="item in customFieldsColumns" :key="item.slotName" #[item.slotName]="{ record }">
<div v-if="isCaseLevel(item.slotName as string).name === '用例等级'" class="flex items-center"> <div v-if="isCaseLevel(item.slotName as string).name === '用例等级'" class="flex items-center">
<span v-if="!record.visible" class="flex items-center" @click="record.visible = true" <span v-if="!record.visible" class="flex items-center" @click="record.visible = true">
><caseLevel :case-level="getCaseLevel(record, item)" <caseLevel :case-level="getCaseLevel(record, item)" />
/></span> </span>
<TableFormChange <TableFormChange
v-model:visible="record.visible" v-model:visible="record.visible"
:default-value="record[item.slotName]" :default-value="record[item.slotName]"
@ -583,7 +583,7 @@
searchCustomFields.value = result.map((item: any) => { searchCustomFields.value = result.map((item: any) => {
const FilterTypeKey: keyof typeof FilterType = CustomTypeMaps[item.type].type; const FilterTypeKey: keyof typeof FilterType = CustomTypeMaps[item.type].type;
const formType = FilterType[FilterTypeKey]; const formType = FilterType[FilterTypeKey];
const formObject = item.type; const formObject = CustomTypeMaps[item.type];
const { props: formProps } = formObject; const { props: formProps } = formObject;
const currentItem: any = { const currentItem: any = {
title: item.name, title: item.name,
@ -960,13 +960,7 @@
} }
} }
// const searchList = debounce(() => {
// getLoadListParams();
// loadList();
// }, 100);
const fetchData = (keywordStr = '') => { const fetchData = (keywordStr = '') => {
console.log(keywordStr);
setKeyword(keywordStr); setKeyword(keywordStr);
keyword.value = keywordStr; keyword.value = keywordStr;
getLoadListParams(); getLoadListParams();
@ -1090,7 +1084,7 @@
} }
function getCaseLevel(record: CaseManagementTable, item: MsTableColumnData): CaseLevel { function getCaseLevel(record: CaseManagementTable, item: MsTableColumnData): CaseLevel {
return ((record[item.slotName as string] || '').replaceAll('P', '') * 1) as CaseLevel; return (record[item.slotName as string].replaceAll('P', '') * 1) as CaseLevel;
} }
// //

View File

@ -103,6 +103,15 @@
<template #actions="{ item }"> <template #actions="{ item }">
<!-- 本地文件 --> <!-- 本地文件 -->
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap"> <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 <MsButton
v-if="item.status !== 'init'" v-if="item.status !== 'init'"
type="button" type="button"
@ -114,6 +123,7 @@
</MsButton> </MsButton>
<TransferModal <TransferModal
v-model:visible="transferVisible" v-model:visible="transferVisible"
:request-fun="transferFileRequest"
:params="{ :params="{
projectId: currentProjectId, projectId: currentProjectId,
caseId:route.query.id as string, caseId:route.query.id as string,
@ -134,6 +144,15 @@
</div> </div>
<!-- 关联文件 --> <!-- 关联文件 -->
<div v-else class="flex flex-nowrap"> <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 <MsButton
v-if="route.query.id" v-if="route.query.id"
type="button" type="button"
@ -143,8 +162,21 @@
> >
{{ t('caseManagement.featureCase.download') }} {{ t('caseManagement.featureCase.download') }}
</MsButton> </MsButton>
<MsButton
v-if="route.query.id && item.isUpdateFlag"
type="button"
status="primary"
@click="handleUpdateFile(item)"
>
{{ t('common.update') }}
</MsButton>
</div> </div>
</template> </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>
</MsFileList> </MsFileList>
</div> </div>
<!-- 文件列表结束 --> <!-- 文件列表结束 -->
@ -217,6 +249,7 @@
:get-list-request="getAssociatedFileListUrl" :get-list-request="getAssociatedFileListUrl"
@save="saveSelectAssociatedFile" @save="saveSelectAssociatedFile"
/> />
<a-image-preview v-model:visible="previewVisible" :src="imageUrl" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -236,11 +269,14 @@
import TransferModal from './tabContent/transferModal.vue'; import TransferModal from './tabContent/transferModal.vue';
import { import {
deleteFileOrCancelAssociation, checkFileIsUpdateRequest,
downloadFileRequest, downloadFileRequest,
getAssociatedFileListUrl, getAssociatedFileListUrl,
getCaseDefaultFields, getCaseDefaultFields,
getCaseDetail, getCaseDetail,
previewFile,
transferFileRequest,
updateFile,
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement'; import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
import { getProjectFieldList } from '@/api/modules/setting/template'; import { getProjectFieldList } from '@/api/modules/setting/template';
@ -452,6 +488,32 @@
); );
}); });
const imageUrl = ref('');
const previewVisible = ref<boolean>(false);
//
async function handlePreview(item: MsFileItem) {
try {
previewVisible.value = true;
if (item.status !== 'init') {
const res = await previewFile({
projectId: currentProjectId.value,
caseId: route.query.id as string,
fileId: item.uid,
local: item.local,
});
const blob = new Blob([res], { type: 'image/jpeg' });
imageUrl.value = URL.createObjectURL(blob);
} else {
imageUrl.value = item.url as string;
}
} catch (error) {
console.log(error);
}
}
const checkUpdateFileIds = ref<string[]>([]);
// //
function getDetailData(detailResult: DetailCase) { function getDetailData(detailResult: DetailCase) {
const { customFields, attachments, steps, tags } = detailResult; const { customFields, attachments, steps, tags } = detailResult;
@ -476,13 +538,13 @@
} }
if (attachments) { if (attachments) {
attachmentsList.value = attachments; attachmentsList.value = attachments;
// //
fileList.value = attachments fileList.value = attachments
.map((fileInfo: any) => { .map((fileInfo: any) => {
return { return {
...fileInfo, ...fileInfo,
name: fileInfo.fileName, name: fileInfo.fileName,
isUpdateFlag: checkUpdateFileIds.value.includes(fileInfo.id),
}; };
}) })
.map((fileInfo: any) => { .map((fileInfo: any) => {
@ -497,6 +559,11 @@
isLoading.value = true; isLoading.value = true;
await getAllCaseFields(); await getAllCaseFields();
const detailResult: DetailCase = await getCaseDetail(route.query.id as string); const detailResult: DetailCase = await getCaseDetail(route.query.id as string);
const fileIds = (detailResult.attachments || []).map((item: any) => item.id);
if (fileIds.length) {
checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds);
}
getDetailData(detailResult); getDetailData(detailResult);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -667,6 +734,16 @@
} }
} }
//
async function handleUpdateFile(item: MsFileItem) {
try {
await updateFile(currentProjectId.value, item.associationId);
Message.success(t('common.updateSuccess'));
} catch (error) {
console.log(error);
}
}
defineExpose({ defineExpose({
caseFormRef, caseFormRef,
formRef, formRef,

View File

@ -27,7 +27,7 @@
</a-alert> </a-alert>
<MsUpload <MsUpload
v-model:file-list="fileList" v-model:file-list="fileList"
class="mb-6" class="mb-6 w-full"
:accept="props.validateType === 'Excel' ? 'excel' : 'xmind'" :accept="props.validateType === 'Excel' ? 'excel' : 'xmind'"
:max-size="100" :max-size="100"
size-unit="MB" size-unit="MB"

View File

@ -8,73 +8,39 @@
:width="800" :width="800"
unmount-on-close unmount-on-close
:show-continue="true" :show-continue="true"
@continue="handleDrawerConfirm(true)"
@confirm="handleDrawerConfirm" @confirm="handleDrawerConfirm"
@cancel="handleDrawerCancel" @cancel="handleDrawerCancel"
> >
<a-form ref="formRef" :model="form" layout="vertical"> <a-form ref="formRef" :model="form" layout="vertical">
<a-form-item <a-form-item
field="name" field="title"
:label="t('bugManagement.bugName')" :label="t('bugManagement.bugName')"
:rules="[{ required: true, message: t('bugManagement.edit.nameIsRequired') }]" :rules="[{ required: true, message: t('bugManagement.edit.nameIsRequired') }]"
:placeholder="t('bugManagement.edit.pleaseInputBugName')" :placeholder="t('bugManagement.edit.pleaseInputBugName')"
> >
<a-input v-model="form.name" :max-length="255" show-word-limit /> <a-input v-model="form.title" :max-length="255" show-word-limit />
</a-form-item> </a-form-item>
<a-form-item :label="t('bugManagement.edit.content')"> <a-form-item :label="t('bugManagement.edit.content')">
<MsRichText v-model="form.content" /> <MsRichText v-model="form.description" />
</a-form-item> </a-form-item>
<div class="mb-[8px] text-[var(--color-text-1)]">{{ t('bugManagement.edit.file') }}</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>{{ t('bugManagement.edit.linkFile') }}</a-doption>
</template>
<a-button type="outline">
<template #icon>
<icon-plus />
</template>
{{ t('bugManagement.edit.uploadFile') }}
</a-button>
</a-dropdown>
</div>
<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>
</a-form> </a-form>
</MsDrawer> </MsDrawer>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { FileItem } from '@arco-design/web-vue'; import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue'; import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.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 { createBug, getTemplageOption, getTemplateDetailInfo } from '@/api/modules/bug-management/index';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { TemplateOption } from '@/models/common';
const appStore = useAppStore(); const appStore = useAppStore();
const props = defineProps<{ const props = defineProps<{
@ -83,54 +49,20 @@
const emit = defineEmits(['update:visible']); const emit = defineEmits(['update:visible']);
const fileList = ref<FileItem[]>([]);
const { t } = useI18n(); const { t } = useI18n();
const form = ref({ const templateOptions = ref<TemplateOption[]>([]);
name: '',
content: '', // TODO
const initForm: any = {
title: '',
templateId: '', templateId: '',
handleMan: [], projectId: appStore.currentProjectId,
status: '', description: '',
severity: '', customFields: [],
tag: [],
});
//
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 form = ref({ ...initForm });
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',
};
}
return e;
});
};
//
const handlePreview = (item: FileItem) => {
const { url } = item;
window.open(url);
};
const showDrawer = computed({ const showDrawer = computed({
get() { get() {
@ -141,10 +73,49 @@
}, },
}); });
const formRef = ref<FormInstance | null>(null);
const templateCustomFields = ref([]);
function handleDrawerCancel() {
formRef.value?.resetFields();
form.value = { ...initForm };
showDrawer.value = false;
}
const drawerLoading = ref<boolean>(false); const drawerLoading = ref<boolean>(false);
function handleDrawerConfirm() {} function handleDrawerConfirm(isContinue: boolean) {
function handleDrawerCancel() {} formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
drawerLoading.value = true;
try {
await createBug({ request: { ...form.value, customFields: templateCustomFields.value }, fileList: [] });
Message.success(t('caseManagement.featureCase.quicklyCreateDefectSuccess'));
if (!isContinue) {
handleDrawerCancel();
}
form.value = { ...initForm };
} catch (error) {
console.log(error);
} finally {
drawerLoading.value = false;
}
}
});
}
onBeforeMount(async () => {
templateOptions.value = await getTemplageOption({ projectId: appStore.currentProjectId });
form.value.templateId = templateOptions.value.find((item) => item.enableDefault)?.id as string;
const result = await getTemplateDetailInfo({ id: form.value.templateId, projectId: appStore.currentProjectId });
templateCustomFields.value = result.customFields.map((item: any) => {
return {
id: item.fieldId,
name: item.fieldName,
type: item.type,
value: item.defaultValue || '',
};
});
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -135,7 +135,7 @@
} }
onMounted(() => { onMounted(() => {
getFetch(); // getFetch();
}); });
</script> </script>

View File

@ -34,9 +34,16 @@
</div> </div>
</div> </div>
<ms-base-table v-if="showType === 'link'" ref="tableRef" v-bind="linkPropsRes" v-on="linkTableEvent"> <ms-base-table v-if="showType === 'link'" ref="tableRef" v-bind="linkPropsRes" v-on="linkTableEvent">
<template #defectName="{ record }"> <template #title="{ record }">
<span class="one-line-text max-w[300px]"> {{ record.name }}</span <span class="one-line-text max-w[300px]"> {{ record.title }}</span>
><span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span> <a-popover title="" position="right">
<span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
<template #content>
<div class="min-w-[300px] text-[14px] text-[var(--color-text-1)]">
{{ record.title }}
</div>
</template>
</a-popover>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<MsButton @click="cancelLink(record)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton> <MsButton @click="cancelLink(record)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton>
@ -56,7 +63,7 @@
</ms-base-table> </ms-base-table>
<ms-base-table v-else v-bind="testPlanPropsRes" v-on="testPlanTableEvent"> <ms-base-table v-else v-bind="testPlanPropsRes" v-on="testPlanTableEvent">
<template #defectName="{ record }"> <template #defectName="{ record }">
<span class="one-line-text max-w[300px]"> {{ record.name }}</span <span class="one-line-text max-w[300px]"> {{ record.title }}</span
><span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span> ><span class="ml-1 text-[rgb(var(--primary-5))]">{{ t('caseManagement.featureCase.preview') }}</span>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
@ -90,11 +97,13 @@
import AddDefectDrawer from './addDefectDrawer.vue'; import AddDefectDrawer from './addDefectDrawer.vue';
import LinkDefectDrawer from './linkDefectDrawer.vue'; import LinkDefectDrawer from './linkDefectDrawer.vue';
import { getRecycleListRequest } from '@/api/modules/case-management/featureCase'; import { getBugList } from '@/api/modules/bug-management/index';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const showType = ref('link'); const showType = ref('link');
@ -113,10 +122,10 @@
}, },
{ {
title: 'caseManagement.featureCase.defectName', title: 'caseManagement.featureCase.defectName',
slotName: 'defectName', slotName: 'title',
dataIndex: 'defectName', dataIndex: 'title',
showInTable: true, showInTable: true,
showTooltip: true, showTooltip: false,
width: 300, width: 300,
ellipsis: true, ellipsis: true,
showDrag: false, showDrag: false,
@ -141,15 +150,6 @@
ellipsis: true, ellipsis: true,
showDrag: false, showDrag: false,
}, },
{
title: 'caseManagement.featureCase.tableColumnLevel',
dataIndex: 'level',
showInTable: true,
width: 200,
showTooltip: true,
ellipsis: true,
showDrag: true,
},
{ {
title: 'caseManagement.featureCase.tableColumnActions', title: 'caseManagement.featureCase.tableColumnActions',
slotName: 'operation', slotName: 'operation',
@ -165,7 +165,7 @@
propsEvent: linkTableEvent, propsEvent: linkTableEvent,
loadList: loadLinkList, loadList: loadLinkList,
setLoadListParams: setLinkListParams, setLoadListParams: setLinkListParams,
} = useTable(getRecycleListRequest, { } = useTable(getBugList, {
columns, columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT, tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT,
scroll: { x: '100%' }, scroll: { x: '100%' },
@ -194,7 +194,7 @@
showDrag: false, showDrag: false,
}, },
{ {
title: 'caseManagement.featureCase.testPlan', title: 'caseManagement.featureCase.planName',
slotName: 'testPlan', slotName: 'testPlan',
dataIndex: 'testPlan', dataIndex: 'testPlan',
showInTable: true, showInTable: true,
@ -229,7 +229,7 @@
propsEvent: testPlanTableEvent, propsEvent: testPlanTableEvent,
loadList: testPlanLinkList, loadList: testPlanLinkList,
setLoadListParams: setTestPlanListParams, setLoadListParams: setTestPlanListParams,
} = useTable(getRecycleListRequest, { } = useTable(getBugList, {
columns: testPlanColumns, columns: testPlanColumns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT_TEST_PLAN, tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEFECT_TEST_PLAN,
scroll: { x: '100%' }, scroll: { x: '100%' },
@ -239,10 +239,10 @@
function getFetch() { function getFetch() {
if (showType.value === 'link') { if (showType.value === 'link') {
setLinkListParams({ keyword: keyword.value }); setLinkListParams({ keyword: keyword.value, projectId: appStore.currentProjectId });
loadLinkList(); loadLinkList();
} else { } else {
setTestPlanListParams({ keyword: keyword.value }); setTestPlanListParams({ keyword: keyword.value, projectId: appStore.currentProjectId });
testPlanLinkList(); testPlanLinkList();
} }
} }
@ -259,6 +259,7 @@
function linkDefect() { function linkDefect() {
showLinkDrawer.value = true; showLinkDrawer.value = true;
} }
watch( watch(
() => showType.value, () => showType.value,
(val) => { (val) => {

View File

@ -4,7 +4,9 @@
<a-dropdown @select="handleSelect"> <a-dropdown @select="handleSelect">
<a-button type="primary"> {{ t('caseManagement.featureCase.linkCase') }} </a-button> <a-button type="primary"> {{ t('caseManagement.featureCase.linkCase') }} </a-button>
<template #content> <template #content>
<a-doption v-for="item of caseType" :key="item.value" :value="item.value">{{ item.name }}</a-doption> <a-doption v-for="item of caseTypeOptions" :key="item.value" :value="item.value">{{
t(item.label)
}}</a-doption>
</template> </template>
</a-dropdown> </a-dropdown>
<a-input-search <a-input-search
@ -26,10 +28,17 @@
<MsCaseAssociate <MsCaseAssociate
v-model:visible="innerVisible" v-model:visible="innerVisible"
v-model:project="innerProject" v-model:project="innerProject"
v-model:currentSelectCase="currentSelectCase"
:ok-button-disabled="associateForm.reviewers.length === 0" :ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getCaseModuleTree" :get-modules-func="getCaseModuleTree"
@success="writeAssociateCases" :get-table-func="getCaseList"
:modules-count="modulesCount"
:module-options="caseTypeOptions"
:confirm-loading="confirmLoading"
:associated-ids="associatedIds"
@close="emit('close')" @close="emit('close')"
@init="getModuleCount"
@save="saveHandler"
> >
</MsCaseAssociate> </MsCaseAssociate>
</div> </div>
@ -44,13 +53,27 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue'; import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
import { getCaseModuleTree, getRecycleListRequest } from '@/api/modules/case-management/featureCase'; import { getAssociatedIds } from '@/api/modules/case-management/caseReview';
import {
getCaseList,
getCaseModulesCounts,
getCaseModuleTree,
getRecycleListRequest,
} from '@/api/modules/case-management/featureCase';
import { postTabletList } from '@/api/modules/project-management/menuManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import type { CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const currentProjectId = computed(() => appStore.currentProjectId);
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:visible', val: boolean): void; (e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void; (e: 'update:project', val: string): void;
@ -135,38 +158,74 @@
reviewers: [], reviewers: [],
}); });
const associatedIds = ref<string[]>([]);
async function getLinkedIds() {
// try {
// associatedIds.value = await getAssociatedIds('1111');
// } catch (error) {
// console.log(error);
// }
}
const currentSelectCase = ref<string | number | Record<string, any> | undefined>(''); const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
function handleSelect(value: string | number | Record<string, any> | undefined) { function handleSelect(value: string | number | Record<string, any> | undefined) {
currentSelectCase.value = value; currentSelectCase.value = value;
innerVisible.value = true; innerVisible.value = true;
getLinkedIds();
} }
function cancelLink(record: any) {} function cancelLink(record: any) {}
const caseType = ref([ const caseTypeOptions = ref<{ label: string; value: string }[]>([]);
{
value: 'API',
name: '接口用例',
},
{
value: 'SCENE',
name: '接口用例',
},
{
value: 'UI',
name: 'UI用例',
},
{
value: 'PERFORMANCE',
name: '性能用例',
},
]);
const selectedKeys = ref<string[]>([]); const modulesCount = ref<Record<string, any>>({});
function writeAssociateCases(ids: string[]) { async function getModuleCount(params: CaseModuleQueryParams) {
emit('success', ids); try {
modulesCount.value = await getCaseModulesCounts(params);
} catch (error) {
console.log(error);
}
} }
const confirmLoading = ref<boolean>(false);
function saveHandler(params: TableQueryParams) {}
const moduleMaps: Record<string, { label: string; value: string }[]> = {
apiTest: [
{
value: 'API_CASE',
label: t('caseManagement.featureCase.apiCase'),
},
{
value: 'SCENE_CASE',
label: t('caseManagement.featureCase.sceneCase'),
},
],
uiTest: [
{
value: 'UI_CASE',
label: t('caseManagement.featureCase.uiCase'),
},
],
loadTest: [
{
value: 'LOAD_CASE',
label: t('caseManagement.featureCase.propertyCase'),
},
],
};
onBeforeMount(async () => {
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);
});
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -7,6 +7,8 @@
:placeholder="t('caseManagement.featureCase.searchByNameAndId')" :placeholder="t('caseManagement.featureCase.searchByNameAndId')"
allow-clear allow-clear
class="mx-[8px] w-[240px]" class="mx-[8px] w-[240px]"
@search="searchList"
@press-enter="searchList"
></a-input-search> ></a-input-search>
</div> </div>
<ms-base-table v-bind="propsRes" v-on="propsEvent"> <ms-base-table v-bind="propsRes" v-on="propsEvent">
@ -28,13 +30,19 @@
import useTable from '@/components/pure/ms-table/useTable'; import useTable from '@/components/pure/ms-table/useTable';
import statusTag from '@/views/case-management/caseReview/components/statusTag.vue'; import statusTag from '@/views/case-management/caseReview/components/statusTag.vue';
import { getRecycleListRequest } from '@/api/modules/case-management/featureCase'; import { getDetailCaseReviewPage } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import debounce from 'lodash-es/debounce';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps<{
caseId: string; // id
}>();
const keyword = ref<string>(''); const keyword = ref<string>('');
const columns: MsTableColumn = [ const columns: MsTableColumn = [
@ -74,13 +82,26 @@
}, },
]; ];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getRecycleListRequest, { const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getDetailCaseReviewPage, {
columns, columns,
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_REVIEW, tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_REVIEW,
scroll: { x: '100%' }, scroll: { x: '100%' },
heightUsed: 340, heightUsed: 340,
enableDrag: true, enableDrag: true,
}); });
function initData() {
setLoadListParams({ keyword: keyword.value, caseId: props.caseId });
loadList();
}
const searchList = debounce(() => {
initData();
}, 100);
onBeforeMount(() => {
initData();
});
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -129,17 +129,26 @@
:upload-func="uploadOrAssociationFile" :upload-func="uploadOrAssociationFile"
:handle-delete="deleteFileHandler" :handle-delete="deleteFileHandler"
:show-delete="props.allowEdit" :show-delete="props.allowEdit"
:handle-view="handlePreview"
> >
<template #actions="{ item }"> <template #actions="{ item }">
<div v-if="props.allowEdit"> <div v-if="props.allowEdit">
<!-- 本地文件 --> <!-- 本地文件 -->
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap"> <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"> <MsButton type="button" status="primary" class="!mr-[4px]" @click="transferVisible = true">
{{ t('caseManagement.featureCase.storage') }} {{ t('caseManagement.featureCase.storage') }}
</MsButton> </MsButton>
<TransferModal <TransferModal
v-model:visible="transferVisible" v-model:visible="transferVisible"
:request-fun="transferFileRequest"
:params="{ :params="{
projectId: currentProjectId, projectId: currentProjectId,
caseId: detailForm.id, caseId: detailForm.id,
@ -160,6 +169,15 @@
</div> </div>
<!-- 关联文件 --> <!-- 关联文件 -->
<div v-else class="flex flex-nowrap"> <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 <MsButton
v-if="item.status === 'done'" v-if="item.status === 'done'"
type="button" type="button"
@ -169,9 +187,23 @@
> >
{{ t('caseManagement.featureCase.download') }} {{ t('caseManagement.featureCase.download') }}
</MsButton> </MsButton>
<MsButton
v-if="item.isUpdateFlag"
type="button"
status="primary"
class="!mr-[4px]"
@click="handleUpdateFile(item)"
>
{{ t('common.update') }}
</MsButton>
</div> </div>
</div> </div>
</template> </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>
</MsFileList> </MsFileList>
</div> </div>
<LinkFileDrawer <LinkFileDrawer
@ -182,6 +214,7 @@
@save="saveSelectAssociatedFile" @save="saveSelectAssociatedFile"
/> />
</div> </div>
<a-image-preview v-model:visible="previewVisible" :src="imageUrl" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -197,11 +230,14 @@
import TransferModal from './transferModal.vue'; import TransferModal from './transferModal.vue';
import { import {
checkFileIsUpdateRequest,
deleteFileOrCancelAssociation, deleteFileOrCancelAssociation,
downloadFileRequest, downloadFileRequest,
getAssociatedFileListUrl, getAssociatedFileListUrl,
previewFile, previewFile,
transferFileRequest,
updateCaseRequest, updateCaseRequest,
updateFile,
uploadOrAssociationFile, uploadOrAssociationFile,
} from '@/api/modules/case-management/featureCase'; } from '@/api/modules/case-management/featureCase';
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement'; import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
@ -455,9 +491,19 @@
console.log(error); console.log(error);
} }
} }
const checkUpdateFileIds = ref<string[]>([]);
//
async function getCheckFileIds(fileIds: string[]) {
try {
checkUpdateFileIds.value = await checkFileIsUpdateRequest(fileIds);
} catch (error) {
console.log(error);
}
}
// //
function getDetails() { async function getDetails() {
const { steps, attachments } = detailForm.value; const { steps, attachments } = detailForm.value;
if (steps) { if (steps) {
stepData.value = JSON.parse(steps).map((item: any) => { stepData.value = JSON.parse(steps).map((item: any) => {
@ -467,6 +513,11 @@
}; };
}); });
} }
const fileIds = (attachments || []).map((item: any) => item.id);
if (fileIds.length) {
await getCheckFileIds(fileIds);
}
attachmentsList.value = attachments || []; attachmentsList.value = attachments || [];
// //
fileList.value = (attachments || []) fileList.value = (attachments || [])
@ -474,6 +525,7 @@
return { return {
...fileInfo, ...fileInfo,
name: fileInfo.fileName, name: fileInfo.fileName,
isUpdateFlag: checkUpdateFileIds.value.includes(fileInfo.id),
}; };
}) })
.map((fileInfo: any) => { .map((fileInfo: any) => {
@ -481,14 +533,23 @@
}); });
} }
// TOTO const imageUrl = ref('');
const previewVisible = ref<boolean>(false);
//
async function handlePreview(item: MsFileItem) { async function handlePreview(item: MsFileItem) {
const res = await previewFile({ try {
projectId: currentProjectId.value, previewVisible.value = true;
caseId: detailForm.value.id, const res = await previewFile({
fileId: item.uid, projectId: currentProjectId.value,
local: item.local, caseId: detailForm.value.id,
}); fileId: item.uid,
local: item.local,
});
const blob = new Blob([res], { type: 'image/jpeg' });
imageUrl.value = URL.createObjectURL(blob);
} catch (error) {
console.log(error);
}
} }
watch( watch(
@ -546,6 +607,16 @@
fileList.value.push(...fileResultList); fileList.value.push(...fileResultList);
} }
//
async function handleUpdateFile(item: MsFileItem) {
try {
await updateFile(currentProjectId.value, item.associationId);
Message.success(t('common.updateSuccess'));
} catch (error) {
console.log(error);
}
}
onMounted(() => { onMounted(() => {
detailForm.value = { ...props.form }; detailForm.value = { ...props.form };
getDetails(); getDetails();

View File

@ -1,6 +1,6 @@
<template> <template>
<a-modal v-model:visible="transferVisible" title-align="start" class="ms-modal-upload ms-modal-small"> <a-modal v-model:visible="transferVisible" title-align="start" class="ms-modal-upload ms-modal-small">
<template #title> 请选择转存目录 </template> <template #title> {{ t('caseManagement.featureCase.selectTransferDirectory') }} </template>
<a-tree-select <a-tree-select
v-model="transferId" v-model="transferId"
:data="transCategory" :data="transCategory"
@ -31,7 +31,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { getTransferFileTree, transferFileRequest } from '@/api/modules/case-management/featureCase'; import { getTransferFileTree } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
@ -45,6 +45,7 @@
const props = defineProps<{ const props = defineProps<{
visible: boolean; visible: boolean;
params: OperationFile; // params: OperationFile; //
requestFun: (params: OperationFile) => Promise<any>;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -77,7 +78,7 @@
async function handleBeforeOk() { async function handleBeforeOk() {
loading.value = true; loading.value = true;
try { try {
await transferFileRequest({ ...requestParams.value, moduleId: transferId.value }); await props.requestFun({ ...requestParams.value, moduleId: transferId.value });
Message.success(t('caseManagement.featureCase.transferFileSuccess')); Message.success(t('caseManagement.featureCase.transferFileSuccess'));
handleCancel(); handleCancel();
emit('success'); emit('success');

View File

@ -102,21 +102,23 @@ export function convertToFile(fileInfo: AssociatedList): MsFileItem {
const gatewayAddress = `${window.location.protocol}//${window.location.hostname}:${window.location.port}`; const gatewayAddress = `${window.location.protocol}//${window.location.hostname}:${window.location.port}`;
const fileName = fileInfo.fileType ? `${fileInfo.name}.${fileInfo.fileType || ''}` : `${fileInfo.name}`; const fileName = fileInfo.fileType ? `${fileInfo.name}.${fileInfo.fileType || ''}` : `${fileInfo.name}`;
const type = fileName.split('.')[1]; const type = fileName.split('.')[1];
const isImage = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg'].some((ext) => ext === type.toLowerCase());
const file = new File([new Blob()], `${fileName}`, { const file = new File([new Blob()], `${fileName}`, {
type: isImage ? `image/${type}` : `application/${type}`, type: `application/${type}`,
}); });
Object.defineProperty(file, 'size', { value: fileInfo.size }); Object.defineProperty(file, 'size', { value: fileInfo.size });
const { id, local, isUpdateFlag, associateId } = fileInfo;
return { return {
enable: fileInfo.enable || false, enable: fileInfo.enable || false,
file, file,
name: fileName, name: fileName,
percent: 0, percent: 0,
status: 'done', status: 'done',
uid: fileInfo.id, uid: id,
url: `${gatewayAddress}/${fileInfo.filePath || ''}`, url: `${gatewayAddress}/${fileInfo.filePath || ''}`,
local: fileInfo.local, local,
deleteContent: fileInfo.local ? '' : 'caseManagement.featureCase.cancelLink', deleteContent: local ? '' : 'caseManagement.featureCase.cancelLink',
isUpdateFlag,
associateId,
}; };
} }

View File

@ -136,6 +136,7 @@ export default {
'caseManagement.featureCase.dependency': 'dependencies', 'caseManagement.featureCase.dependency': 'dependencies',
'caseManagement.featureCase.caseReview': 'case review', 'caseManagement.featureCase.caseReview': 'case review',
'caseManagement.featureCase.testPlan': 'Test plan', 'caseManagement.featureCase.testPlan': 'Test plan',
'caseManagement.featureCase.planName': 'Plan name',
'caseManagement.featureCase.comments': 'comments', 'caseManagement.featureCase.comments': 'comments',
'caseManagement.featureCase.changeHistory': 'Change history', 'caseManagement.featureCase.changeHistory': 'Change history',
'caseManagement.featureCase.demandPlatform': 'Platform', 'caseManagement.featureCase.demandPlatform': 'Platform',
@ -158,7 +159,7 @@ export default {
'caseManagement.featureCase.transferFileSuccess': 'Successful transfer', 'caseManagement.featureCase.transferFileSuccess': 'Successful transfer',
'caseManagement.featureCase.defectName': 'Defect name', 'caseManagement.featureCase.defectName': 'Defect name',
'caseManagement.featureCase.defectState': 'Defect state', 'caseManagement.featureCase.defectState': 'Defect state',
'caseManagement.featureCase.createDefect': 'Create defect', 'caseManagement.featureCase.createDefect': 'Quickly Create defect',
'caseManagement.featureCase.testPlanLinkList': 'Test plan association list', 'caseManagement.featureCase.testPlanLinkList': 'Test plan association list',
'caseManagement.featureCase.directLink': 'Direct correlation', 'caseManagement.featureCase.directLink': 'Direct correlation',
'caseManagement.featureCase.linkDefect': 'Associated defect', 'caseManagement.featureCase.linkDefect': 'Associated defect',
@ -240,4 +241,7 @@ export default {
'caseManagement.featureCase.CheckSuccess': 'Check success', 'caseManagement.featureCase.CheckSuccess': 'Check success',
'caseManagement.featureCase.tableNoData': 'No data available', 'caseManagement.featureCase.tableNoData': 'No data available',
'caseManagement.featureCase.noAssociatedDefect': 'No associated defects, please', 'caseManagement.featureCase.noAssociatedDefect': 'No associated defects, please',
'caseManagement.featureCase.fileIsUpdated': 'File is updated',
'caseManagement.featureCase.selectTransferDirectory': 'Please select the transfer directory',
'caseManagement.featureCase.quicklyCreateDefectSuccess': 'Quick bug creation success',
}; };

View File

@ -134,6 +134,7 @@ export default {
'caseManagement.featureCase.dependency': '依赖关系', 'caseManagement.featureCase.dependency': '依赖关系',
'caseManagement.featureCase.caseReview': '用例评审', 'caseManagement.featureCase.caseReview': '用例评审',
'caseManagement.featureCase.testPlan': '测试计划', 'caseManagement.featureCase.testPlan': '测试计划',
'caseManagement.featureCase.planName': '计划名称',
'caseManagement.featureCase.comments': '评论', 'caseManagement.featureCase.comments': '评论',
'caseManagement.featureCase.changeHistory': '变更历史', 'caseManagement.featureCase.changeHistory': '变更历史',
'caseManagement.featureCase.demandPlatform': '平台', 'caseManagement.featureCase.demandPlatform': '平台',
@ -156,7 +157,7 @@ export default {
'caseManagement.featureCase.transferFileSuccess': '转存成功', 'caseManagement.featureCase.transferFileSuccess': '转存成功',
'caseManagement.featureCase.defectName': '缺陷名称', 'caseManagement.featureCase.defectName': '缺陷名称',
'caseManagement.featureCase.defectState': '缺陷状态', 'caseManagement.featureCase.defectState': '缺陷状态',
'caseManagement.featureCase.createDefect': '创建缺陷', 'caseManagement.featureCase.createDefect': '快速创建缺陷',
'caseManagement.featureCase.testPlanLinkList': '测试计划关联列表', 'caseManagement.featureCase.testPlanLinkList': '测试计划关联列表',
'caseManagement.featureCase.directLink': '直接关联', 'caseManagement.featureCase.directLink': '直接关联',
'caseManagement.featureCase.linkDefect': '关联缺陷', 'caseManagement.featureCase.linkDefect': '关联缺陷',
@ -234,5 +235,8 @@ export default {
'caseManagement.featureCase.CheckFailure': '校验失败', 'caseManagement.featureCase.CheckFailure': '校验失败',
'caseManagement.featureCase.CheckSuccess': '校验成功', 'caseManagement.featureCase.CheckSuccess': '校验成功',
'caseManagement.featureCase.tableNoData': '暂无数据', 'caseManagement.featureCase.tableNoData': '暂无数据',
'caseManagement.featureCase.noAssociatedDefect': '暂无可关联缺陷,请', 'caseManagement.featureCase.noAssociated': '暂无可关联缺陷,请',
'caseManagement.featureCase.fileIsUpdated': '当前文件已更新',
'caseManagement.featureCase.selectTransferDirectory': '请选择转存目录',
'caseManagement.featureCase.quicklyCreateDefectSuccess': '快速创建缺陷成功',
}; };

View File

@ -2,9 +2,15 @@
<MsCaseAssociate <MsCaseAssociate
v-model:visible="innerVisible" v-model:visible="innerVisible"
v-model:project="innerProject" v-model:project="innerProject"
v-model:currentSelectCase="currentSelectCase"
:ok-button-disabled="associateForm.reviewers.length === 0" :ok-button-disabled="associateForm.reviewers.length === 0"
:get-modules-func="getCaseModuleTree" :get-modules-func="getCaseModuleTree"
@success="writeAssociateCases" :modules-count="modulesCount"
:get-table-func="getCaseList"
:associated-ids="associatedIds"
:confirm-loading="confirmLoading"
@init="getModuleCount"
@save="saveHandler"
> >
<template #footerLeft> <template #footerLeft>
<a-form ref="associateFormRef" :model="associateForm"> <a-form ref="associateFormRef" :model="associateForm">
@ -71,11 +77,13 @@
import MsSelect from '@/components/business/ms-select'; import MsSelect from '@/components/business/ms-select';
import { getReviewUsers } from '@/api/modules/case-management/caseReview'; import { getReviewUsers } from '@/api/modules/case-management/caseReview';
import { getCaseModuleTree } from '@/api/modules/case-management/featureCase'; import { getCaseList, getCaseModulesCounts, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLocale from '@/locale/useLocale'; import useLocale from '@/locale/useLocale';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
import type { TableQueryParams } from '@/models/common';
import { ProjectManagementRouteEnum } from '@/enums/routeEnum'; import { ProjectManagementRouteEnum } from '@/enums/routeEnum';
const props = defineProps<{ const props = defineProps<{
@ -88,7 +96,6 @@
(e: 'success', val: string[]): void; (e: 'success', val: string[]): void;
(e: 'close'): void; (e: 'close'): void;
}>(); }>();
const router = useRouter(); const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
const { currentLocale } = useLocale(); const { currentLocale } = useLocale();
@ -157,10 +164,22 @@
} }
} }
function writeAssociateCases(ids: string[]) { const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
emit('success', ids); const modulesCount = ref<Record<string, any>>({});
async function getModuleCount(params: CaseModuleQueryParams) {
try {
modulesCount.value = await getCaseModulesCounts(params);
} catch (error) {
console.log(error);
}
} }
const associatedIds = ref<string[]>([]);
const confirmLoading = ref<boolean>(false);
function saveHandler(params: TableQueryParams) {}
onBeforeMount(() => { onBeforeMount(() => {
initReviewers(); initReviewers();
}); });

View File

@ -133,7 +133,7 @@ export default {
'system.orgTemplate.startState': '开始状态', 'system.orgTemplate.startState': '开始状态',
'system.orgTemplate.endState': '结束状态', 'system.orgTemplate.endState': '结束状态',
'system.orgTemplate.iconTip': '图标可调整状态顺序', 'system.orgTemplate.iconTip': '图标可调整状态顺序',
'system.orgTemplate.anyStateToAll': '任何状态可转换到状态', 'system.orgTemplate.anyStateToAll': '任何状态可转换到状态',
'system.orgTemplate.enableAnyStateToAll': '开启', 'system.orgTemplate.enableAnyStateToAll': '开启',
'system.orgTemplate.enableNotAnyStateToAll': '未开启', 'system.orgTemplate.enableNotAnyStateToAll': '未开启',
'system.orgTemplate.createFlowStep': '创建流转步骤', 'system.orgTemplate.createFlowStep': '创建流转步骤',