feat(功能用例): 功能用例详情细节完善&关联文件页面&拖拽上传组件问题修复
This commit is contained in:
parent
444a0b8c14
commit
5ddf98c414
|
@ -6,9 +6,25 @@
|
|||
:footer="false"
|
||||
no-content-padding
|
||||
>
|
||||
<template #headerLeft>
|
||||
<div class="float-left">
|
||||
<a-select
|
||||
v-model="caseType"
|
||||
class="ml-2 max-w-[100px]"
|
||||
:placeholder="t('caseManagement.featureCase.PleaseSelect')"
|
||||
>
|
||||
<a-option v-for="item of actionType" :key="item.value" :value="item.value">{{ item.name }}</a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex h-full">
|
||||
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
|
||||
<MsProjectSelect v-model:project="innerProject" class="mb-[16px]" />
|
||||
<div class="flex items-center justify-between">
|
||||
<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-option v-for="item of protocolOptions" :key="item" :value="item">{{ item }}</a-option>
|
||||
</a-select>
|
||||
</div>
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||
|
@ -157,6 +173,30 @@
|
|||
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: '接口用例',
|
||||
},
|
||||
{
|
||||
value: 'SCENE',
|
||||
name: '接口用例',
|
||||
},
|
||||
{
|
||||
value: 'UI',
|
||||
name: 'UI用例',
|
||||
},
|
||||
{
|
||||
value: 'PERFORMANCE',
|
||||
name: '性能用例',
|
||||
},
|
||||
]);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
|
|
|
@ -16,10 +16,15 @@
|
|||
>
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
<div class="flex w-full justify-between">
|
||||
{{ props.title }}
|
||||
<a-tag v-if="titleTag" :color="props.titleTagColor" class="ml-[8px] mr-auto">{{ props.titleTag }}</a-tag>
|
||||
<slot name="tbutton"></slot>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="flex items-center">
|
||||
{{ props.title }}
|
||||
<slot name="headerLeft"></slot>
|
||||
<a-tag v-if="titleTag" :color="props.titleTagColor" class="ml-[8px] mr-auto">{{
|
||||
props.titleTag
|
||||
}}</a-tag></div
|
||||
>
|
||||
<div class="flex"> <slot name="tbutton"></slot></div>
|
||||
</div>
|
||||
</slot>
|
||||
</template>
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
>
|
||||
{{ t('ms.upload.reUpload') }}
|
||||
</MsButton>
|
||||
<MsButton type="button" status="danger" class="!mr-[4px]" @click="deleteFile(item)">
|
||||
<MsButton v-if="props.showDelete" type="button" status="danger" class="!mr-[4px]" @click="deleteFile(item)">
|
||||
{{ t(item.deleteContent) || t('ms.upload.delete') }}
|
||||
</MsButton>
|
||||
<slot name="actions" :item="item"></slot>
|
||||
|
@ -128,10 +128,12 @@
|
|||
showTab?: boolean; // 是否显示tab
|
||||
handleDelete?: (item: MsFileItem) => void;
|
||||
handleReupload?: (item: MsFileItem) => void;
|
||||
showDelete?: boolean; // 是否展示删除按钮
|
||||
}>(),
|
||||
{
|
||||
mode: 'remote',
|
||||
showTab: true,
|
||||
showDelete: true,
|
||||
}
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -139,7 +139,7 @@
|
|||
|
||||
const total = ref(''); // 总高度
|
||||
const other = ref(''); // 被减去高度
|
||||
const showDropArea = ref(false);
|
||||
const showDropArea = ref(!props.isAllScreen);
|
||||
|
||||
watch(
|
||||
() => props.isAllScreen,
|
||||
|
@ -209,8 +209,10 @@
|
|||
}
|
||||
|
||||
onMounted(() => {
|
||||
disableDefaultEvents();
|
||||
init();
|
||||
if (props.isAllScreen) {
|
||||
disableDefaultEvents();
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
|
|
@ -43,6 +43,8 @@ export enum TableKeyEnum {
|
|||
CASE_MANAGEMENT_TAB_REVIEW = 'caseManagementTabCaseReview',
|
||||
CASE_MANAGEMENT_TAB_TEST_PLAN = 'caseManagementTabTestPlan',
|
||||
CASE_MANAGEMENT_TAB_CHANGE_HISTORY = 'caseManagementTabChangeHistory',
|
||||
CASE_MANAGEMENT_TAB_CASE_TABLE = 'caseManagementTabCaseTable',
|
||||
CASE_MANAGEMENT_TAB_DEMAND_PLATFORM = 'caseManagementTabDemandPlatformTable',
|
||||
}
|
||||
|
||||
// 具有特殊功能的列
|
||||
|
|
|
@ -159,7 +159,7 @@ export interface CreateCase {
|
|||
moduleId: string;
|
||||
versionId: string;
|
||||
tags: any;
|
||||
customFields: Record<string, any>; // 自定义字段集合
|
||||
customFields: CustomAttributes[] | Record<string, any>; // 自定义字段集合
|
||||
relateFileMetaIds: string[]; // 关联文件ID集合
|
||||
[key: string]: any;
|
||||
}
|
||||
|
|
|
@ -193,7 +193,7 @@
|
|||
|
||||
function loadedCase(detail: CaseManagementTable) {
|
||||
detailInfo.value = { ...detail };
|
||||
customFields.value = detailInfo.value.customFields;
|
||||
customFields.value = detailInfo.value.customFields as CustomAttributes[];
|
||||
}
|
||||
|
||||
const moduleName = computed(() => {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
/>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<a-button class="mt-2 px-0" type="text" :disabled="!props.isDisabled" @click="addStep">
|
||||
<a-button v-if="!props.isDisabled" class="mt-2 px-0" type="text" @click="addStep">
|
||||
<template #icon>
|
||||
<icon-plus class="text-[14px]" />
|
||||
</template>
|
||||
|
@ -181,6 +181,7 @@
|
|||
// 删除步骤
|
||||
function deleteStep(record: StepList) {
|
||||
stepData.value = stepData.value.filter((item: any) => item.id !== record.id);
|
||||
setProps({ data: stepData.value });
|
||||
}
|
||||
|
||||
// 步骤之前插入步骤
|
||||
|
@ -240,7 +241,7 @@
|
|||
|
||||
// 编辑步骤
|
||||
function edit(record: StepList, type: string) {
|
||||
if (!props.isDisabled) return;
|
||||
if (props.isDisabled) return;
|
||||
if (type === 'step') {
|
||||
record.showStep = true;
|
||||
} else {
|
||||
|
@ -250,7 +251,7 @@
|
|||
|
||||
// 失去焦点回调
|
||||
function blurHandler(record: StepList, type: string) {
|
||||
if (!props.isDisabled) return;
|
||||
if (props.isDisabled) return;
|
||||
if (type === 'step') {
|
||||
record.showStep = false;
|
||||
} else {
|
||||
|
@ -260,9 +261,7 @@
|
|||
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
|
||||
|
||||
watchEffect(() => {
|
||||
stepData.value = props.stepList;
|
||||
setProps({ data: stepData.value });
|
||||
if (!props.isDisabled) {
|
||||
if (props.isDisabled) {
|
||||
tableRef.value?.initColumn(templateFieldColumns.value.slice(0, templateFieldColumns.value.length - 1));
|
||||
} else {
|
||||
tableRef.value?.initColumn(templateFieldColumns.value);
|
||||
|
@ -273,10 +272,18 @@
|
|||
() => stepData.value,
|
||||
(val) => {
|
||||
emit('update:stepList', val);
|
||||
setProps({ data: stepData.value });
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.stepList,
|
||||
() => {
|
||||
stepData.value = props.stepList;
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
setProps({ data: stepData.value });
|
||||
});
|
||||
|
|
|
@ -7,14 +7,21 @@
|
|||
@save="saveHandler"
|
||||
@save-and-continue="saveHandler(true)"
|
||||
>
|
||||
<template #headerRight>
|
||||
<a-select class="w-[240px]" :placeholder="t('caseManagement.featureCase.versionPlaceholder')">
|
||||
<a-option v-for="template of versionOptions" :key="template.id" :value="template.id">{{
|
||||
template.name
|
||||
}}</a-option>
|
||||
</a-select>
|
||||
</template>
|
||||
<CaseTemplateDetail ref="caseModuleDetailRef" v-model:form-mode-value="caseDetailInfo" />
|
||||
<template #footerRight>
|
||||
<div class="flex justify-end gap-[16px]">
|
||||
<a-button type="secondary" @click="cancelHandler">{{ t('mscard.defaultCancelText') }}</a-button>
|
||||
<a-button v-if="!isFormReviewCase" type="secondary" @click="saveHandler(true)">
|
||||
{{ t('mscard.defaultSaveAndContinueText') }}
|
||||
</a-button>
|
||||
<a-button v-if="!isFormReviewCase" type="primary" @click="saveHandler(false)">
|
||||
{{ t(isEdit ? 'mscard.defaultUpdate' : 'mscard.defaultConfirm') }}
|
||||
</a-button>
|
||||
<a-button v-if="isFormReviewCase" type="primary" @click="saveHandler(false, true)">
|
||||
{{ t('caseManagement.featureCase.createAndLink') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
|
@ -49,22 +56,16 @@
|
|||
fileList: [],
|
||||
});
|
||||
|
||||
const versionOptions = ref([
|
||||
{
|
||||
id: '1001',
|
||||
name: '模板01',
|
||||
},
|
||||
]);
|
||||
|
||||
const title = ref('');
|
||||
const loading = ref(false);
|
||||
const isEdit = computed(() => !!route.query.id);
|
||||
const isFormReviewCase = computed(() => route.query.reviewId);
|
||||
|
||||
const isContinueFlag = ref(false);
|
||||
const isShowTip = ref<boolean>(true);
|
||||
const createSuccessId = ref<string>('');
|
||||
|
||||
async function save() {
|
||||
async function save(isReview: boolean) {
|
||||
try {
|
||||
loading.value = true;
|
||||
if (route.params.mode === 'edit') {
|
||||
|
@ -72,10 +73,26 @@
|
|||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||
} else {
|
||||
const res = await createCaseRequest(caseDetailInfo.value);
|
||||
if (isReview) {
|
||||
// TODO
|
||||
// 创建并关联接口
|
||||
}
|
||||
createSuccessId.value = res.data.id;
|
||||
Message.success(route.params.mode === 'copy' ? t('ms.description.copySuccess') : t('common.addSuccess'));
|
||||
}
|
||||
router.push({ name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, query: { ...route.query } });
|
||||
if (isReview) {
|
||||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL,
|
||||
query: {
|
||||
id: route.query.reviewId,
|
||||
organizationId: route.query.organizationId,
|
||||
projectId: route.query.projectId,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
router.push({ name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, query: { ...route.query } });
|
||||
}
|
||||
|
||||
featureCaseStore.setIsAlreadySuccess(true);
|
||||
isShowTip.value = !getIsVisited();
|
||||
if (isShowTip.value && !route.query.id) {
|
||||
|
@ -97,7 +114,7 @@
|
|||
const caseModuleDetailRef = ref();
|
||||
|
||||
// 保存
|
||||
function saveHandler(isContinue = false) {
|
||||
function saveHandler(isContinue = false, isReview = false) {
|
||||
const { caseFormRef, formRef, fApi } = caseModuleDetailRef.value;
|
||||
isContinueFlag.value = isContinue;
|
||||
caseFormRef?.validate().then((res: any) => {
|
||||
|
@ -106,7 +123,7 @@
|
|||
if (valid === true) {
|
||||
formRef?.validate().then((result: any) => {
|
||||
if (!result) {
|
||||
return save();
|
||||
return save(isReview);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -115,6 +132,9 @@
|
|||
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||
});
|
||||
}
|
||||
function cancelHandler() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (route.params.mode === 'edit') {
|
||||
|
@ -124,7 +144,6 @@
|
|||
} else {
|
||||
title.value = t('caseManagement.featureCase.creatingCase');
|
||||
}
|
||||
const gatewayAddress = `${window.location.protocol}//${window.location.hostname}:${window.location.port}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -107,12 +107,19 @@
|
|||
>
|
||||
</a-menu>
|
||||
<div class="mt-4">
|
||||
<TabDetail v-if="activeTab === 'detail'" :form="detailInfo" @update-success="updateSuccess" />
|
||||
<TabDetail
|
||||
v-if="activeTab === 'detail'"
|
||||
:form="detailInfo"
|
||||
:allow-edit="true"
|
||||
@update-success="updateSuccess"
|
||||
/>
|
||||
<TabDemand v-else-if="activeTab === 'requirement'" :case-id="props.detailId" />
|
||||
<TabCaseTable v-else-if="activeTab === 'case'" />
|
||||
<TabDefect v-else-if="activeTab === 'bug'" />
|
||||
<TabDependency v-else-if="activeTab === 'dependency'" />
|
||||
<TabCaseReview v-else-if="activeTab === 'caseReview'" />
|
||||
<TabTestPlan v-else-if="activeTab === 'testPlan'" />
|
||||
<TabComment v-else-if="activeTab === 'comments'" />
|
||||
<TabChangeHistory v-else-if="activeTab === 'changeHistory'" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -169,8 +176,10 @@
|
|||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||
import SettingDrawer from './tabContent/settingDrawer.vue';
|
||||
import TabDefect from './tabContent/tabBug/tabDefect.vue';
|
||||
import TabCaseTable from './tabContent/tabCase/tabCaseTable.vue';
|
||||
import TabCaseReview from './tabContent/tabCaseReview.vue';
|
||||
import TabChangeHistory from './tabContent/tabChangeHistory.vue';
|
||||
import TabComment from './tabContent/tabComment/tabCommentIndex.vue';
|
||||
import TabDemand from './tabContent/tabDemand/demand.vue';
|
||||
import TabDependency from './tabContent/tabDependency/tabDependency.vue';
|
||||
import TabDetail from './tabContent/tabDetail.vue';
|
||||
|
@ -184,7 +193,12 @@
|
|||
import useUserStore from '@/store/modules/user';
|
||||
import { characterLimit, findNodeByKey } from '@/utils';
|
||||
|
||||
import type { CaseManagementTable, CustomAttributes, TabItemType } from '@/models/caseManagement/featureCase';
|
||||
import type {
|
||||
CaseManagementTable,
|
||||
CreateCase,
|
||||
CustomAttributes,
|
||||
TabItemType,
|
||||
} from '@/models/caseManagement/featureCase';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
|
@ -237,13 +251,30 @@
|
|||
break;
|
||||
}
|
||||
}
|
||||
const initDetail: CreateCase = {
|
||||
projectId: '',
|
||||
templateId: '',
|
||||
name: '',
|
||||
prerequisite: '', // prerequisite
|
||||
caseEditType: '', // 编辑模式:步骤模式/文本模式
|
||||
steps: '',
|
||||
textDescription: '',
|
||||
expectedResult: '', // 预期结果
|
||||
description: '',
|
||||
publicCase: false, // 是否公共用例
|
||||
moduleId: '',
|
||||
versionId: '',
|
||||
tags: [],
|
||||
customFields: [], // 自定义字段集合
|
||||
relateFileMetaIds: [], // 关联文件ID集合
|
||||
};
|
||||
|
||||
const detailInfo = ref<Record<string, any>>({});
|
||||
const detailInfo = ref<CreateCase>({ ...initDetail });
|
||||
const customFields = ref<CustomAttributes[]>([]);
|
||||
const caseLevels = ref(0);
|
||||
function loadedCase(detail: CaseManagementTable) {
|
||||
function loadedCase(detail: CreateCase) {
|
||||
detailInfo.value = { ...detail };
|
||||
customFields.value = detailInfo.value.customFields;
|
||||
customFields.value = detailInfo.value.customFields as CustomAttributes[];
|
||||
const caseLevelsValue = customFields.value.find((item) => item.fieldName === '用例等级')?.defaultValue;
|
||||
if (caseLevelsValue) {
|
||||
caseLevels.value = JSON.parse(caseLevelsValue).replaceAll('P', '') * 1;
|
||||
|
|
|
@ -804,7 +804,7 @@
|
|||
activeCaseIndex.value = index;
|
||||
}
|
||||
|
||||
// 地址栏携带 id,自动打开资源池详情抽屉
|
||||
// 地址栏携带 id,自动打开用例详情抽屉
|
||||
onMounted(() => {
|
||||
if (route.query.id) {
|
||||
showCaseDetail(route.query.id as string, 0);
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
<!-- 步骤描述 -->
|
||||
<div v-if="form.caseEditType === 'STEP'" class="w-full">
|
||||
<AddStep v-model:step-list="stepData" :is-disabled="true" />
|
||||
<AddStep v-model:step-list="stepData" :is-disabled="false" />
|
||||
</div>
|
||||
<!-- 文本描述 -->
|
||||
<MsRichText v-else v-model:modelValue="form.textDescription" />
|
||||
|
@ -200,7 +200,13 @@
|
|||
@change="handleChange"
|
||||
/>
|
||||
</div>
|
||||
<AssociatedFileDrawer v-model:visible="showDrawer" @save="saveSelectAssociatedFile" />
|
||||
<LinkFileDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:get-tree-request="getModules"
|
||||
:get-count-request="getModulesCount"
|
||||
:get-list-request="getAssociatedFileListUrl"
|
||||
@save="saveSelectAssociatedFile"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -216,9 +222,14 @@
|
|||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import AddStep from './addStep.vue';
|
||||
import AssociatedFileDrawer from './associatedFileDrawer.vue';
|
||||
import LinkFileDrawer from './linkFile/associatedFileDrawer.vue';
|
||||
|
||||
import { getCaseDefaultFields, getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||
import {
|
||||
getAssociatedFileListUrl,
|
||||
getCaseDefaultFields,
|
||||
getCaseDetail,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { getProjectFieldList } from '@/api/modules/setting/template';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:mask="false"
|
||||
:title="t('caseManagement.featureCase.associatedFile')"
|
||||
:ok-text="t('caseManagement.featureCase.associated')"
|
||||
:ok-loading="drawerLoading"
|
||||
:ok-disabled="selectFile.length < 1"
|
||||
:width="1200"
|
||||
unmount-on-close
|
||||
:show-continue="false"
|
||||
@confirm="handleDrawerConfirm"
|
||||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<div class="p-[16px] pt-0">
|
||||
<div class="folder">
|
||||
<div class="folder-text">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name">{{ t('project.fileManagement.allFile') }}</div>
|
||||
<div class="folder-count">({{ allFileCount }})</div>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center">
|
||||
<a-tooltip
|
||||
:content="isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')"
|
||||
>
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
|
||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="my-[8px]" />
|
||||
<a-radio-group v-model:model-value="showType" type="button" class="file-show-type" @change="changeShowType">
|
||||
<a-radio value="Module">{{ t('project.fileManagement.module') }}</a-radio>
|
||||
<a-radio value="Storage">{{ t('project.fileManagement.storage') }}</a-radio>
|
||||
</a-radio-group>
|
||||
<div v-show="showType === 'Module'">
|
||||
<FileTree
|
||||
ref="folderTreeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
v-model:active-folder="activeFolder"
|
||||
:is-expand-all="isExpandAll"
|
||||
:modules-count="modulesCount"
|
||||
:show-type="showType"
|
||||
:get-tree-request="props.getTreeRequest"
|
||||
@init="setRootModules"
|
||||
@folder-node-select="folderNodeSelect"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="showType === 'Storage'">
|
||||
<StorageList
|
||||
v-model:drawer-visible="storageDrawerVisible"
|
||||
v-model:active-folder="activeFolder"
|
||||
:modules-count="modulesCount"
|
||||
:show-type="showType"
|
||||
@item-click="storageItemSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<LinkFileTable
|
||||
v-model:selectFile="selectFile"
|
||||
:active-folder="activeFolder"
|
||||
:active-folder-type="activeFolderType"
|
||||
:offspring-ids="offspringIds"
|
||||
:modules-count="modulesCount"
|
||||
:folder-tree="folderTree"
|
||||
:storage-list="storageList"
|
||||
:show-type="showType"
|
||||
:get-list-request="props.getListRequest"
|
||||
@init="handleModuleTableInit"
|
||||
/>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import FileTree from './fileTree.vue';
|
||||
import LinkFileTable from './linkFileTable.vue';
|
||||
import StorageList from './storageList.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import type { CommonList, TableQueryParams } from '@/models/common';
|
||||
import { FileListQueryParams, ModuleTreeNode, Repository } from '@/models/projectManagement/file';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
getTreeRequest: (params: any) => Promise<ModuleTreeNode[]>; // 获取左侧树请求
|
||||
getCountRequest: (params: any) => Promise<Record<string, any>>; // 获取左侧树模块数量请求
|
||||
getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>; // 获取表格请求
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', val: AssociatedList[]): void;
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
}>();
|
||||
const showDrawer = computed({
|
||||
get() {
|
||||
return props.visible;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:visible', val);
|
||||
},
|
||||
});
|
||||
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
|
||||
const activeFolderType = ref<'folder' | 'module' | 'storage'>('module');
|
||||
|
||||
const activeFolder = ref<string>('root');
|
||||
const selectedKeys = computed({
|
||||
get: () => [activeFolder.value],
|
||||
set: (val) => val,
|
||||
});
|
||||
const offspringIds = ref<string[]>([]);
|
||||
|
||||
const modulesCount = ref<Record<string, number>>({});
|
||||
const myFileCount = ref(0);
|
||||
const allFileCount = ref(0);
|
||||
|
||||
const isExpandAll = ref(false);
|
||||
|
||||
function changeExpand() {
|
||||
isExpandAll.value = !isExpandAll.value;
|
||||
}
|
||||
|
||||
type FileShowType = 'Module' | 'Storage';
|
||||
const showType = ref<FileShowType>('Module');
|
||||
|
||||
/**
|
||||
* 处理文件夹树节点选中事件
|
||||
*/
|
||||
function folderNodeSelect(keys: string[], _offspringIds: string[]) {
|
||||
[activeFolder.value] = keys;
|
||||
activeFolderType.value = 'module';
|
||||
offspringIds.value = [..._offspringIds];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根模块名称列表
|
||||
* @param names 根模块名称列表
|
||||
*/
|
||||
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const rootModulesName = ref<string[]>([]); // 根模块名称列表
|
||||
function setRootModules(treeNode: ModuleTreeNode[]) {
|
||||
folderTree.value = treeNode;
|
||||
rootModulesName.value = treeNode.map((e) => e.name);
|
||||
}
|
||||
|
||||
/*
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
async function initModulesCount(params: FileListQueryParams) {
|
||||
try {
|
||||
modulesCount.value = await props.getCountRequest(params);
|
||||
myFileCount.value = modulesCount.value.my || 0;
|
||||
allFileCount.value = modulesCount.value.all || 0;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const tableFilterParams = ref<FileListQueryParams>({
|
||||
moduleIds: [],
|
||||
fileType: '',
|
||||
projectId: '',
|
||||
});
|
||||
|
||||
function changeShowType(val: string | number | boolean) {
|
||||
showType.value = val as FileShowType;
|
||||
if (val === 'Storage') {
|
||||
initModulesCount({
|
||||
...tableFilterParams.value,
|
||||
combine: {
|
||||
...tableFilterParams.value.combine,
|
||||
storage: 'git',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
initModulesCount(tableFilterParams.value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 右侧表格数据刷新后,若当前展示的是模块,则刷新模块树的统计数量
|
||||
*/
|
||||
function handleModuleTableInit(params: FileListQueryParams) {
|
||||
initModulesCount(params);
|
||||
tableFilterParams.value = { ...params };
|
||||
}
|
||||
|
||||
const storageDrawerVisible = ref(false);
|
||||
|
||||
/**
|
||||
* 处理存储库列表项选中事件
|
||||
*/
|
||||
const storageList = ref<Repository[]>([]);
|
||||
function storageItemSelect(key: string, storages: Repository[]) {
|
||||
storageList.value = storages;
|
||||
activeFolder.value = key;
|
||||
activeFolderType.value = 'storage';
|
||||
}
|
||||
|
||||
const selectFile = ref<AssociatedList[]>([]);
|
||||
|
||||
function handleDrawerConfirm() {
|
||||
emit('save', selectFile.value);
|
||||
showDrawer.value = false;
|
||||
}
|
||||
|
||||
function handleDrawerCancel() {
|
||||
showDrawer.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.folder {
|
||||
@apply flex cursor-pointer items-center justify-between;
|
||||
|
||||
padding: 8px 4px;
|
||||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.folder-text {
|
||||
@apply flex cursor-pointer items-center;
|
||||
.folder-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.folder-name {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.folder-count {
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
}
|
||||
.folder-text--active {
|
||||
.folder-icon,
|
||||
.folder-name,
|
||||
.folder-count {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
.file-show-type {
|
||||
@apply grid grid-cols-2;
|
||||
|
||||
margin-bottom: 8px;
|
||||
:deep(.arco-radio-button-content) {
|
||||
@apply text-center;
|
||||
}
|
||||
}
|
||||
:deep(.arco-drawer-body) {
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<a-input
|
||||
v-model:model-value="moduleKeyword"
|
||||
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="mb-[16px]"
|
||||
></a-input>
|
||||
<a-spin class="min-h-[300px] w-full" :loading="loading">
|
||||
<MsTree
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:selected-keys="props.selectedKeys"
|
||||
:data="folderTree"
|
||||
:keyword="moduleKeyword"
|
||||
:expand-all="props.isExpandAll"
|
||||
:empty-text="t('project.fileManagement.noFolder')"
|
||||
:virtual-list-props="virtualListProps"
|
||||
:draggable="false"
|
||||
:field-names="{
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
children: 'children',
|
||||
count: 'count',
|
||||
}"
|
||||
block-node
|
||||
title-tooltip-position="left"
|
||||
@select="folderNodeSelect"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<div class="inline-flex w-full">
|
||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">
|
||||
<MsIcon type="icon-icon_folder_filled1" size="14" class="mr-1 text-[var(--color-text-4)]" />{{
|
||||
nodeData.name
|
||||
}}</div
|
||||
>
|
||||
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsTree>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { mapTree } from '@/utils';
|
||||
|
||||
import { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
isExpandAll: boolean;
|
||||
selectedKeys?: Array<string | number>; // 选中的节点 key
|
||||
isModal?: boolean; // 是否是弹窗模式
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
showType?: string; // 显示类型
|
||||
getTreeRequest: (params: any) => Promise<ModuleTreeNode[]>; // 获取模块树接口
|
||||
activeFolder: string | number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:selectedKeys', 'init', 'folderNodeSelect', 'update:activeFolder']);
|
||||
|
||||
const moduleKeyword = ref('');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const focusNodeKey = ref<string | number>('');
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const virtualListProps = computed(() => {
|
||||
return {
|
||||
height: 'calc(100vh - 350px)',
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 处理文件夹树节点选中事件
|
||||
*/
|
||||
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
|
||||
const offspringIds: string[] = [];
|
||||
mapTree(node.children || [], (e) => {
|
||||
offspringIds.push(e.id);
|
||||
return e;
|
||||
});
|
||||
|
||||
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
||||
}
|
||||
|
||||
const selectedKeys = ref(props.selectedKeys || []);
|
||||
|
||||
/**
|
||||
* 初始化模块树
|
||||
* @param isSetDefaultKey 是否设置第一个节点为选中节点
|
||||
*/
|
||||
async function initModules(isSetDefaultKey = false) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await props.getTreeRequest(appStore.currentProjectId);
|
||||
folderTree.value = mapTree<ModuleTreeNode>(res, (e) => {
|
||||
return {
|
||||
...e,
|
||||
hideMoreAction: e.id === 'root',
|
||||
draggable: false,
|
||||
disabled: false,
|
||||
count: props.modulesCount?.[e.id] || 0,
|
||||
};
|
||||
});
|
||||
if (isSetDefaultKey) {
|
||||
selectedKeys.value = [folderTree.value[0].id];
|
||||
emit('update:activeFolder', folderTree.value[0].id);
|
||||
}
|
||||
emit('init', folderTree.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.showType,
|
||||
(val) => {
|
||||
if (val === 'Module') {
|
||||
initModules(true);
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
|
||||
return {
|
||||
...node,
|
||||
count: obj?.[node.id] || 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -0,0 +1,313 @@
|
|||
<template>
|
||||
<div class="pl-4">
|
||||
<div class="header">
|
||||
<div
|
||||
><span class="one-line-text max-w-[300px]">{{ moduleInfo.name }}</span
|
||||
><span class="ml-[4px] text-[var(--color-text-4)]">({{ moduleInfo.count }})</span></div
|
||||
>
|
||||
<div class="header-right">
|
||||
<a-select v-model="tableFileType" class="w-[240px]" :loading="fileTypeLoading" @change="searchList">
|
||||
<a-option key="" value="">{{ t('common.all') }}</a-option>
|
||||
<a-option v-for="item of tableFileTypeOptions" :key="item" :value="item">
|
||||
{{ item }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="w-[240px]"
|
||||
@search="searchList"
|
||||
@press-enter="searchList"
|
||||
/></div>
|
||||
</div>
|
||||
<ms-base-table v-bind="propsRes" ref="tableRef" no-disable v-on="propsEvent">
|
||||
<template #name="{ record }">
|
||||
<MsTag
|
||||
v-if="record.fileType.toLowerCase() === 'jar'"
|
||||
theme="light"
|
||||
type="success"
|
||||
:self-style="
|
||||
record.enable
|
||||
? {}
|
||||
: {
|
||||
color: 'var(--color-text-4)',
|
||||
backgroundColor: 'var(--color-text-n9)',
|
||||
}
|
||||
"
|
||||
>
|
||||
{{ t(record.enable ? 'common.enable' : 'common.disable') }}
|
||||
</MsTag>
|
||||
<a-tooltip :content="record.name">
|
||||
<div class="one-line-text max-w-[168px]">{{ record.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #size="{ record }">
|
||||
<span>{{ formatFileSize(record.size) }}</span>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
|
||||
import { getFileTypes, getRepositoryFileTypes } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { findNodeByKey, formatFileSize } from '@/utils';
|
||||
|
||||
import type { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import type { CommonList, TableQueryParams } from '@/models/common';
|
||||
import type { FileListQueryParams, ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
import { Repository } from '@/models/projectManagement/file';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
activeFolder: string;
|
||||
activeFolderType: 'folder' | 'module' | 'storage';
|
||||
offspringIds: string[]; // 当前选中文件夹的所有子孙节点id
|
||||
modulesCount: Record<string, any>; // 模块数量
|
||||
folderTree: ModuleTreeNode[];
|
||||
selectFile: AssociatedList[]; // 表格选中项
|
||||
getListRequest: (params: TableQueryParams) => Promise<CommonList<AssociatedList>>;
|
||||
showType: 'Module' | 'Storage'; // 展示类型
|
||||
storageList: Repository[]; // 存储库列表
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'init', params: FileListQueryParams): void;
|
||||
(e: 'update:selectFile', val: AssociatedList[]): void;
|
||||
}>();
|
||||
|
||||
const tableFileTypeOptions = ref<string[]>([]);
|
||||
const tableFileType = ref(''); // 文件格式筛选
|
||||
const keyword = ref('');
|
||||
const fileTypeLoading = ref(false);
|
||||
const fileType = ref('module'); // 当前查看的文件类型,模块/存储库
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const combine = ref<Record<string, any>>({});
|
||||
const isMyOrAllFolder = computed(() => ['my', 'all'].includes(props.activeFolder)); // 是否是我的文件/全部文件
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'project.fileManagement.name',
|
||||
slotName: 'name',
|
||||
dataIndex: 'name',
|
||||
width: 270,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.type',
|
||||
dataIndex: 'fileType',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.tag',
|
||||
dataIndex: 'tags',
|
||||
isTag: true,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.creator',
|
||||
dataIndex: 'creator',
|
||||
showTooltip: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.updater',
|
||||
dataIndex: 'updateUser',
|
||||
showTooltip: true,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'project.fileManagement.updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
props.getListRequest,
|
||||
{
|
||||
columns,
|
||||
tableKey: TableKeyEnum.FILE_MANAGEMENT_FILE,
|
||||
showSetting: false,
|
||||
selectable: true,
|
||||
showSelectAll: true,
|
||||
heightUsed: 300,
|
||||
},
|
||||
(item) => {
|
||||
return {
|
||||
...item,
|
||||
tags: item.tags?.map((e: string) => ({ id: e, name: e })) || [],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
function emitTableParams() {
|
||||
emit('init', {
|
||||
keyword: keyword.value,
|
||||
fileType: tableFileType.value,
|
||||
moduleIds: [],
|
||||
projectId: appStore.currentProjectId,
|
||||
current: propsRes.value.msPagination?.current,
|
||||
pageSize: propsRes.value.msPagination?.pageSize,
|
||||
combine: combine.value,
|
||||
});
|
||||
}
|
||||
|
||||
function setTableParams() {
|
||||
if (props.activeFolder === 'my') {
|
||||
combine.value.createUser = userStore.id;
|
||||
} else {
|
||||
combine.value.createUser = '';
|
||||
}
|
||||
if (fileType.value === 'storage') {
|
||||
combine.value.storage = 'git';
|
||||
} else {
|
||||
combine.value.storage = 'minio';
|
||||
}
|
||||
let moduleIds: string[] = [props.activeFolder, ...props.offspringIds];
|
||||
|
||||
if (isMyOrAllFolder.value) {
|
||||
moduleIds = [];
|
||||
}
|
||||
setLoadListParams({
|
||||
keyword: keyword.value,
|
||||
fileType: tableFileType.value,
|
||||
moduleIds,
|
||||
projectId: appStore.currentProjectId,
|
||||
combine: combine.value,
|
||||
});
|
||||
}
|
||||
|
||||
const searchList = debounce(() => {
|
||||
setTableParams();
|
||||
loadList();
|
||||
emitTableParams();
|
||||
}, 300);
|
||||
|
||||
/**
|
||||
* 初始化文件类型筛选选项
|
||||
*/
|
||||
async function initFileTypes() {
|
||||
try {
|
||||
fileTypeLoading.value = true;
|
||||
let res = null;
|
||||
if (fileType.value === 'storage') {
|
||||
res = await getRepositoryFileTypes(appStore.currentProjectId);
|
||||
} else {
|
||||
res = await getFileTypes(appStore.currentProjectId);
|
||||
}
|
||||
tableFileType.value = '';
|
||||
tableFileTypeOptions.value = res;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
fileTypeLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.activeFolderType,
|
||||
() => {
|
||||
initFileTypes();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeFolderType,
|
||||
(val) => {
|
||||
if (val === 'folder') {
|
||||
fileType.value = 'module';
|
||||
} else {
|
||||
fileType.value = val;
|
||||
}
|
||||
setTableParams();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.activeFolder,
|
||||
() => {
|
||||
keyword.value = '';
|
||||
searchList();
|
||||
resetSelector();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const moduleInfo = computed(() => {
|
||||
if (props.showType === 'Module') {
|
||||
return {
|
||||
name: findNodeByKey<Record<string, any>>(props.folderTree, props.activeFolder, 'id')?.name,
|
||||
count: props.modulesCount[props.activeFolder],
|
||||
};
|
||||
}
|
||||
const storageItem = props.storageList.find((item) => item.id === props.activeFolder);
|
||||
return {
|
||||
name: storageItem?.name,
|
||||
count: storageItem?.count,
|
||||
};
|
||||
});
|
||||
|
||||
const tableSelected = ref<AssociatedList[]>([]);
|
||||
|
||||
const selectedIds = computed(() => {
|
||||
return [...propsRes.value.selectedKeys];
|
||||
});
|
||||
|
||||
watch(
|
||||
() => selectedIds.value,
|
||||
() => {
|
||||
tableSelected.value = propsRes.value.data.filter((item: any) => selectedIds.value.indexOf(item.id) > -1);
|
||||
emit('update:selectFile', tableSelected.value);
|
||||
}
|
||||
);
|
||||
|
||||
defineExpose({
|
||||
resetSelector,
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
@apply flex items-center justify-between;
|
||||
|
||||
margin-bottom: 16px;
|
||||
.header-right {
|
||||
@apply ml-auto flex items-center justify-end;
|
||||
|
||||
width: 70%;
|
||||
gap: 8px;
|
||||
.show-type-icon {
|
||||
:deep(.arco-radio-button-content) {
|
||||
@apply flex;
|
||||
|
||||
padding: 4px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,169 @@
|
|||
<template>
|
||||
<a-input
|
||||
v-model:model-value="storageKeyword"
|
||||
:placeholder="t('project.fileManagement.folderSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="mb-[8px]"
|
||||
></a-input>
|
||||
<a-spin class="h-full w-full" :loading="loading">
|
||||
<MsList
|
||||
v-model:focus-item-key="focusItemKey"
|
||||
:virtual-list-props="{
|
||||
height: 'calc(100vh - 325px)',
|
||||
}"
|
||||
:data="storageList"
|
||||
:bordered="false"
|
||||
:split="false"
|
||||
:empty-text="t('project.fileManagement.noStorage')"
|
||||
item-key-field="id"
|
||||
class="mr-[-6px]"
|
||||
>
|
||||
<template #title="{ item, index }">
|
||||
<div :key="index" class="storage" @click="setActiveFolder(item.id)">
|
||||
<div :class="activeStorageNode === item.id ? 'storage-text storage-text--active' : 'storage-text'">
|
||||
<MsIcon type="icon-icon_git" class="storage-icon" />
|
||||
<div class="storage-name">{{ item.name }}</div>
|
||||
<div class="storage-count">({{ item.count }})</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsList>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsList from '@/components/pure/ms-list/index.vue';
|
||||
|
||||
import { getRepositories } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import { Repository } from '@/models/projectManagement/file';
|
||||
|
||||
const props = defineProps<{
|
||||
activeFolder: string | number;
|
||||
drawerVisible: boolean;
|
||||
showType: string;
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
}>();
|
||||
const emit = defineEmits(['update:drawerVisible', 'itemClick', 'update:activeFolder']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const activeStorageNode = computed({
|
||||
get() {
|
||||
return props.activeFolder;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:activeFolder', val);
|
||||
},
|
||||
});
|
||||
|
||||
const storageKeyword = ref('');
|
||||
const originStorageList = ref<Repository[]>([]);
|
||||
const storageList = ref(originStorageList.value);
|
||||
const loading = ref(false);
|
||||
|
||||
const searchStorage = debounce(() => {
|
||||
storageList.value = originStorageList.value.filter((item) => item.name.includes(storageKeyword.value));
|
||||
}, 300);
|
||||
|
||||
watch(
|
||||
() => storageKeyword.value,
|
||||
() => {
|
||||
if (storageKeyword.value === '') {
|
||||
storageList.value = [...originStorageList.value];
|
||||
}
|
||||
searchStorage();
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 初始化存储库列表
|
||||
*/
|
||||
async function initRepositories(setDefaultKeys = false) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getRepositories(appStore.currentProjectId);
|
||||
originStorageList.value = res;
|
||||
storageList.value = originStorageList.value.map((e) => ({
|
||||
...e,
|
||||
count: props.modulesCount?.[e.id] || 0,
|
||||
}));
|
||||
if (setDefaultKeys) {
|
||||
activeStorageNode.value = storageList.value[0].id;
|
||||
emit('itemClick', storageList.value[0].id, storageList.value);
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.showType,
|
||||
(val) => {
|
||||
if (val === 'Storage') {
|
||||
initRepositories(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
storageList.value = originStorageList.value.map((e) => ({
|
||||
...e,
|
||||
count: obj?.[e.id] || 0,
|
||||
}));
|
||||
}
|
||||
);
|
||||
|
||||
const focusItemKey = ref('');
|
||||
|
||||
function setActiveFolder(id: string) {
|
||||
emit('itemClick', id, storageList.value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.storage {
|
||||
@apply flex cursor-pointer items-center justify-between;
|
||||
|
||||
border-radius: var(--border-radius-small);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
.storage-text {
|
||||
@apply flex cursor-pointer items-center;
|
||||
.storage-icon {
|
||||
margin-right: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
.storage-name {
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
.storage-count {
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
}
|
||||
.storage-text--active {
|
||||
.storage-icon,
|
||||
.storage-name,
|
||||
.storage-count {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,172 @@
|
|||
<template>
|
||||
<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 v-for="item of caseType" :key="item.value" :value="item.value">{{ item.name }}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('caseManagement.featureCase.searchByNameAndId')"
|
||||
allow-clear
|
||||
class="mx-[8px] w-[240px]"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<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 }">
|
||||
<MsButton @click="cancelLink(record)">{{ t('caseManagement.featureCase.cancelLink') }}</MsButton>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
<MsCaseAssociate
|
||||
v-model:visible="innerVisible"
|
||||
v-model:project="innerProject"
|
||||
:ok-button-disabled="associateForm.reviewers.length === 0"
|
||||
:get-modules-func="getCaseModuleTree"
|
||||
@success="writeAssociateCases"
|
||||
@close="emit('close')"
|
||||
>
|
||||
</MsCaseAssociate>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
|
||||
|
||||
import { getCaseModuleTree, getRecycleListRequest } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
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 columns: MsTableColumn = [
|
||||
{
|
||||
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: 300,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.projectName',
|
||||
slotName: 'projectName',
|
||||
dataIndex: 'projectName',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
width: 300,
|
||||
ellipsis: true,
|
||||
showDrag: false,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getRecycleListRequest, {
|
||||
columns,
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEPENDENCY_PRE_CASE,
|
||||
scroll: { x: '100%' },
|
||||
heightUsed: 340,
|
||||
enableDrag: true,
|
||||
});
|
||||
|
||||
const innerVisible = ref(false);
|
||||
const innerProject = ref('');
|
||||
|
||||
const associateForm = ref({
|
||||
reviewers: [],
|
||||
});
|
||||
|
||||
const currentSelectCase = ref<string | number | Record<string, any> | undefined>('');
|
||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||
currentSelectCase.value = value;
|
||||
innerVisible.value = true;
|
||||
}
|
||||
|
||||
function cancelLink(record: any) {}
|
||||
|
||||
const caseType = ref([
|
||||
{
|
||||
value: 'API',
|
||||
name: '接口用例',
|
||||
},
|
||||
{
|
||||
value: 'SCENE',
|
||||
name: '接口用例',
|
||||
},
|
||||
{
|
||||
value: 'UI',
|
||||
name: 'UI用例',
|
||||
},
|
||||
{
|
||||
value: 'PERFORMANCE',
|
||||
name: '性能用例',
|
||||
},
|
||||
]);
|
||||
|
||||
const selectedKeys = ref<string[]>([]);
|
||||
|
||||
function writeAssociateCases(ids: string[]) {
|
||||
emit('success', ids);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -76,6 +76,7 @@
|
|||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsFormItemSub from '@/components/business/ms-form-item-sub/index.vue';
|
||||
import MsRemoveButton from '@/components/business/ms-remove-button/MsRemoveButton.vue';
|
||||
|
||||
import { getRecycleListRequest } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="font-medium">{{ t('caseManagement.featureCase.commentList') }}</div>
|
||||
<div>
|
||||
<a-radio-group type="button">
|
||||
<a-radio value="caseComment">{{ t('caseManagement.featureCase.caseComment') }}</a-radio>
|
||||
<a-radio value="reviewComment">{{ t('caseManagement.featureCase.reviewComment') }}</a-radio>
|
||||
<a-radio value="executiveComment">{{ t('caseManagement.featureCase.executiveReview') }}</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -43,7 +43,7 @@
|
|||
</div>
|
||||
<template #footer>
|
||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="secondary" @click="handleOK(true)">{{ t('ms.dialog.saveContinue') }}</a-button>
|
||||
<a-button v-if="!form.id" type="secondary" @click="handleOK(true)">{{ t('ms.dialog.saveContinue') }}</a-button>
|
||||
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading" @click="handleOK(false)">
|
||||
{{ updateName ? t('common.update') : t('common.create') }}
|
||||
</a-button>
|
||||
|
@ -107,6 +107,7 @@
|
|||
function handleCancel() {
|
||||
demandFormRef.value?.resetFields();
|
||||
showModal.value = false;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function handleOK(isContinue: boolean) {
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
<template>
|
||||
<ms-base-table ref="tableRef" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #demandId="{ record }">
|
||||
<span class="ml-2"> {{ record.demandId }}</span>
|
||||
</template>
|
||||
<template #demandName="{ record }">
|
||||
<span class="ml-1" :class="[props.highlightName ? 'text-[rgb(var(--primary-5))]' : '']">
|
||||
{{ record.demandName }}
|
||||
<span v-if="record.children && (record.children || []).length"
|
||||
>({{ (record.children || []).length }})</span
|
||||
></span
|
||||
<span>({{ (record.children || []).length || 0 }})</span></span
|
||||
>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsButton v-if="record.demandPlatform === 'LOCAL'" @click="emit('update', record)">{{
|
||||
<MsButton v-if="record.demandPlatform !== pageConfig.platformName" @click="emit('update', record)">{{
|
||||
t('caseManagement.featureCase.cancelAssociation')
|
||||
}}</MsButton>
|
||||
<MsButton v-if="record.children && (record.children || []).length" @click="emit('update', record)">{{
|
||||
<MsButton v-if="record.demandPlatform === pageConfig.platformName" @click="emit('update', record)">{{
|
||||
t('common.edit')
|
||||
}}</MsButton>
|
||||
</template>
|
||||
|
@ -32,15 +27,22 @@
|
|||
|
||||
import { getDemandList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import type { DemandItem } from '@/models/caseManagement/featureCase';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const pageConfig = computed(() => appStore.pageConfig);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
funParams: Record<string, any>; // 列表查询参数
|
||||
funParams: {
|
||||
caseId: string;
|
||||
keyword: string;
|
||||
}; // 列表查询参数
|
||||
isShowOperation?: boolean; // 是否显示操作列
|
||||
highlightName?: boolean; // 是否高亮名称
|
||||
}>(),
|
||||
|
@ -54,17 +56,18 @@
|
|||
(e: 'update', record: DemandItem): void;
|
||||
}>();
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
slotName: 'demandId',
|
||||
dataIndex: 'demandId',
|
||||
showInTable: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnName',
|
||||
slotName: 'demandName',
|
||||
dataIndex: 'demandName',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
|
@ -98,7 +101,7 @@
|
|||
const initData = async () => {
|
||||
const { keyword, caseId } = props.funParams;
|
||||
setLoadListParams({ keyword, caseId });
|
||||
await loadList();
|
||||
loadList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
@ -25,6 +25,40 @@
|
|||
@update="updateDemand"
|
||||
></AssociatedDemandTable>
|
||||
<AddDemandModal v-model:visible="showAddModel" :case-id="props.caseId" :form="modelForm" @success="searchList()" />
|
||||
<MsDrawer
|
||||
v-model:visible="linkDemandDrawer"
|
||||
:mask="false"
|
||||
:title="t('caseManagement.featureCase.associatedFile')"
|
||||
:ok-text="t('caseManagement.featureCase.associated')"
|
||||
:ok-loading="drawerLoading"
|
||||
:ok-disabled="tableSelected.length < 1"
|
||||
:width="960"
|
||||
unmount-on-close
|
||||
:show-continue="false"
|
||||
@confirm="handleDrawerConfirm"
|
||||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div><span class="font-medium">XXXXXXXXX</span><span class="ml-1 text-[var(--color-text-4)]">(101)</span></div>
|
||||
<a-input-search
|
||||
v-model="platformKeyword"
|
||||
:max-length="250"
|
||||
:placeholder="t('project.member.searchMember')"
|
||||
allow-clear
|
||||
class="mx-[8px] w-[240px]"
|
||||
@search="searchHandler"
|
||||
@press-enter="searchHandler"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<ms-base-table ref="tableRef" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #demandName="{ record }">
|
||||
<span class="ml-1 text-[rgb(var(--primary-5))]">
|
||||
{{ record.demandName }}
|
||||
<span>({{ (record.children || []).length || 0 }})</span></span
|
||||
>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsDrawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -32,14 +66,22 @@
|
|||
import { ref } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import AddDemandModal from './addDemandModal.vue';
|
||||
import AssociatedDemandTable from './associatedDemandTable.vue';
|
||||
|
||||
import { batchAssociationDemand, getDemandList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
|
||||
import type { DemandItem } from '@/models/caseManagement/featureCase';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
|
@ -76,8 +118,118 @@
|
|||
showAddModel.value = true;
|
||||
modelForm.value = { ...record };
|
||||
}
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
slotName: 'demandId',
|
||||
dataIndex: 'demandId',
|
||||
showInTable: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnName',
|
||||
slotName: 'demandName',
|
||||
dataIndex: 'demandName',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.platformDemandState',
|
||||
width: 300,
|
||||
dataIndex: 'status',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.platformDemandHandler',
|
||||
width: 300,
|
||||
dataIndex: 'handler',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.IterationPlan',
|
||||
width: 300,
|
||||
dataIndex: 'iterationPlan',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getDemandList, {
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_TAB_DEMAND_PLATFORM,
|
||||
columns,
|
||||
rowKey: 'id',
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
showSetting: false,
|
||||
});
|
||||
|
||||
const showDrawer = ref<boolean>(false);
|
||||
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
|
||||
const tableSelected = computed(() => {
|
||||
const selectIds = [...propsRes.value.selectedKeys];
|
||||
return propsRes.value.data.filter((item: any) => selectIds.indexOf(item.id) > -1);
|
||||
});
|
||||
|
||||
async function handleDrawerConfirm() {
|
||||
const params = {
|
||||
id: '',
|
||||
caseId: props.caseId,
|
||||
demandPlatform: '',
|
||||
demandList: [
|
||||
{
|
||||
demandId: 'string',
|
||||
parent: 'string',
|
||||
demandName: 'string',
|
||||
demandUrl: 'string',
|
||||
},
|
||||
],
|
||||
};
|
||||
try {
|
||||
drawerLoading.value = true;
|
||||
await batchAssociationDemand(params);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDrawerCancel() {
|
||||
showDrawer.value = false;
|
||||
}
|
||||
|
||||
// 关联需求(暂无接口)
|
||||
function associatedDemand() {}
|
||||
const linkDemandDrawer = ref<boolean>(false);
|
||||
function associatedDemand() {
|
||||
linkDemandDrawer.value = true;
|
||||
}
|
||||
|
||||
const platformKeyword = ref<string>('');
|
||||
|
||||
const initData = async () => {
|
||||
setLoadListParams({ keyword: platformKeyword.value });
|
||||
loadList();
|
||||
};
|
||||
|
||||
const searchHandler = () => {
|
||||
initData();
|
||||
resetSelector();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initData();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:mask="false"
|
||||
:title="t('caseManagement.featureCase.associatedFile')"
|
||||
:ok-text="t('caseManagement.featureCase.associated')"
|
||||
:ok-loading="drawerLoading"
|
||||
:width="960"
|
||||
unmount-on-close
|
||||
:show-continue="false"
|
||||
@confirm="handleDrawerConfirm"
|
||||
@cancel="handleDrawerCancel"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>XXXXXX <span>(101)</span></div>
|
||||
<a-input-search
|
||||
v-model="keyword"
|
||||
:max-length="250"
|
||||
:placeholder="t('project.member.searchMember')"
|
||||
allow-clear
|
||||
@search="searchHandler"
|
||||
@press-enter="searchHandler"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<ms-base-table ref="tableRef" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #demandName="{ record }">
|
||||
<span class="ml-1 text-[rgb(var(--primary-5))]">
|
||||
{{ record.demandName }}
|
||||
<span>({{ (record.children || []).length || 0 }})</span></span
|
||||
>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { getDemandList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
slotName: 'demandId',
|
||||
dataIndex: 'demandId',
|
||||
showInTable: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnName',
|
||||
slotName: 'demandName',
|
||||
dataIndex: 'demandName',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.platformDemandState',
|
||||
width: 300,
|
||||
dataIndex: 'status',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.platformDemandHandler',
|
||||
width: 300,
|
||||
dataIndex: 'handler',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.IterationPlan',
|
||||
width: 300,
|
||||
dataIndex: 'iterationPlan',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getDemandList, {
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_DEMAND,
|
||||
columns,
|
||||
rowKey: 'id',
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
showSetting: false,
|
||||
});
|
||||
|
||||
const showDrawer = ref<boolean>(false);
|
||||
|
||||
const drawerLoading = ref<boolean>(false);
|
||||
|
||||
function handleDrawerConfirm() {
|
||||
// const selectedIds = [...propsRes.value.selectedKeys];
|
||||
// tableSelected.value = propsRes.value.data.filter((item: any) => selectedIds.indexOf(item.id) > -1);
|
||||
// emit('save', tableSelected.value);
|
||||
// showDrawer.value = false;
|
||||
// propsRes.value.selectedKeys.clear();
|
||||
}
|
||||
|
||||
function handleDrawerCancel() {
|
||||
showDrawer.value = false;
|
||||
}
|
||||
|
||||
const keyword = ref<string>('');
|
||||
|
||||
const initData = async () => {
|
||||
setLoadListParams({ keyword: keyword.value });
|
||||
loadList();
|
||||
};
|
||||
|
||||
const searchHandler = () => {
|
||||
initData();
|
||||
resetSelector();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
resetSelector();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
<!-- 步骤描述 -->
|
||||
<div v-if="detailForm.caseEditType === 'STEP'" class="w-full">
|
||||
<AddStep v-model:step-list="stepData" :is-disabled="isEditPreposition" />
|
||||
<AddStep v-model:step-list="stepData" :is-disabled="!isEditPreposition" />
|
||||
</div>
|
||||
<!-- 文本描述 -->
|
||||
<MsRichText
|
||||
|
@ -77,8 +77,11 @@
|
|||
{{ t('common.save') }}
|
||||
</a-button></div
|
||||
>
|
||||
<a-form-item field="attachment" :label="t('caseManagement.featureCase.attachment')">
|
||||
<div class="flex flex-col">
|
||||
<a-form-item
|
||||
field="attachment"
|
||||
:label="props.allowEdit ? t('caseManagement.featureCase.attachment') : '附件列表'"
|
||||
>
|
||||
<div v-if="props.allowEdit" class="flex flex-col">
|
||||
<div class="mb-1">
|
||||
<a-dropdown position="tr" trigger="hover">
|
||||
<a-button type="outline">
|
||||
|
@ -125,38 +128,48 @@
|
|||
}"
|
||||
:upload-func="uploadOrAssociationFile"
|
||||
:handle-delete="deleteFileHandler"
|
||||
:show-delete="props.allowEdit"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<!-- 本地文件 -->
|
||||
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
|
||||
<MsButton type="button" status="primary" class="!mr-[4px]" @click="transferFile(item)">
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<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 === 'done'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="downloadFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
<div v-if="props.allowEdit">
|
||||
<!-- 本地文件 -->
|
||||
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
|
||||
<MsButton type="button" status="primary" class="!mr-[4px]" @click="transferFile(item)">
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<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 === 'done'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="downloadFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsFileList>
|
||||
</div>
|
||||
<LinkFileDrawer
|
||||
v-model:visible="showDrawer"
|
||||
:get-tree-request="getModules"
|
||||
:get-count-request="getModulesCount"
|
||||
:get-list-request="getAssociatedFileListUrl"
|
||||
@save="saveSelectAssociatedFile"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -169,21 +182,24 @@
|
|||
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
|
||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import AddStep from '../addStep.vue';
|
||||
import LinkFileDrawer from '../linkFile/associatedFileDrawer.vue';
|
||||
|
||||
import {
|
||||
deleteFileOrCancelAssociation,
|
||||
downloadFileRequest,
|
||||
getAssociatedFileListUrl,
|
||||
transferFileRequest,
|
||||
updateCaseRequest,
|
||||
uploadOrAssociationFile,
|
||||
} from '@/api/modules/case-management/featureCase';
|
||||
import { getModules, getModulesCount } from '@/api/modules/project-management/fileManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useFormCreateStore from '@/store/modules/form-create/form-create';
|
||||
import { downloadByteFile, getGenerateId } from '@/utils';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
|
||||
import type { StepList } from '@/models/caseManagement/featureCase';
|
||||
import type { AssociatedList, CreateCase, StepList } from '@/models/caseManagement/featureCase';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
|
||||
import { convertToFile } from '../utils';
|
||||
|
@ -200,7 +216,7 @@
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
form: Record<string, any>;
|
||||
form: CreateCase;
|
||||
allowEdit?: boolean; // 是否允许编辑
|
||||
}>(),
|
||||
{
|
||||
|
@ -516,6 +532,12 @@
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 处理关联文件
|
||||
function saveSelectAssociatedFile(fileData: AssociatedList[]) {
|
||||
const fileResultList = fileData.map((fileInfo) => convertToFile(fileInfo));
|
||||
fileList.value.push(...fileResultList);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -191,4 +191,12 @@ export default {
|
|||
'caseManagement.featureCase.saveAsVersionTip': `After saving, add a use case with the current serial number content to the selected version`,
|
||||
'caseManagement.featureCase.testPlanList': 'Test plan list',
|
||||
'caseManagement.featureCase.reviewResult': 'review Result',
|
||||
'caseManagement.featureCase.platformDemandState': 'Status',
|
||||
'caseManagement.featureCase.platformDemandHandler': 'handler',
|
||||
'caseManagement.featureCase.createAndLink': 'Create & Associate',
|
||||
'caseManagement.featureCase.commentList': 'Comment list',
|
||||
'caseManagement.featureCase.caseComment': 'Use case comment',
|
||||
'caseManagement.featureCase.reviewComment': 'Review Comments',
|
||||
'caseManagement.featureCase.executiveReview': 'Executive review',
|
||||
'caseManagement.featureCase.linkCase': 'Associated case',
|
||||
};
|
||||
|
|
|
@ -188,4 +188,13 @@ export default {
|
|||
'caseManagement.featureCase.saveAsVersionTip': '另存后,选择的版本内,增加一条当前序号内容的用例',
|
||||
'caseManagement.featureCase.testPlanList': '测试计划列表',
|
||||
'caseManagement.featureCase.reviewResult': '评审结果',
|
||||
'caseManagement.featureCase.allFiles': '全部文件',
|
||||
'caseManagement.featureCase.platformDemandState': '状态',
|
||||
'caseManagement.featureCase.platformDemandHandler': '处理人',
|
||||
'caseManagement.featureCase.createAndLink': '创建并关联',
|
||||
'caseManagement.featureCase.commentList': '评论列表',
|
||||
'caseManagement.featureCase.caseComment': '用例评论',
|
||||
'caseManagement.featureCase.reviewComment': '评审评论',
|
||||
'caseManagement.featureCase.executiveReview': '执行评论',
|
||||
'caseManagement.featureCase.linkCase': '关联用例',
|
||||
};
|
||||
|
|
|
@ -128,7 +128,7 @@
|
|||
<div v-else-if="showTab === 'detail'" class="h-full">
|
||||
<MsSplitBox :size="0.8" direction="vertical" min="0" :max="0.99">
|
||||
<template #top>
|
||||
<caseTabDetail :form="{}" :allow-edit="false" />
|
||||
<caseTabDetail :form="detailForm" :allow-edit="false" />
|
||||
</template>
|
||||
<template #bottom>
|
||||
<div class="flex h-full flex-col overflow-hidden">
|
||||
|
@ -180,7 +180,10 @@
|
|||
@search="searchDemand"
|
||||
/>
|
||||
</div>
|
||||
<caseTabDemand ref="caseDemandRef" :fun-params="{ caseId: route.query.id, keyword: demandKeyword }" />
|
||||
<caseTabDemand
|
||||
ref="caseDemandRef"
|
||||
:fun-params="{ caseId: route.query.id as string, keyword: demandKeyword }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-footer">
|
||||
|
@ -277,6 +280,8 @@
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { CreateCase } from '@/models/caseManagement/featureCase';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -319,6 +324,25 @@
|
|||
{ label: resultMap[3].label, value: 'reReview' },
|
||||
]);
|
||||
|
||||
const initDetail: CreateCase = {
|
||||
projectId: '',
|
||||
templateId: '',
|
||||
name: '',
|
||||
prerequisite: '', // prerequisite
|
||||
caseEditType: '', // 编辑模式:步骤模式/文本模式
|
||||
steps: '',
|
||||
textDescription: '',
|
||||
expectedResult: '', // 预期结果
|
||||
description: '',
|
||||
publicCase: false, // 是否公共用例
|
||||
moduleId: '',
|
||||
versionId: '',
|
||||
tags: [],
|
||||
customFields: [], // 自定义字段集合
|
||||
relateFileMetaIds: [], // 关联文件ID集合
|
||||
};
|
||||
const detailForm = ref<CreateCase>({ ...initDetail });
|
||||
|
||||
const caseList = ref([
|
||||
{
|
||||
id: 'g4ggtrgrtg',
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty v-if="filterList.length" class="mt-20"> </a-empty>
|
||||
<a-empty v-if="!filterList.length" class="mt-20"> </a-empty>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -216,12 +216,7 @@
|
|||
loading.value = false;
|
||||
}
|
||||
};
|
||||
// 跳转到插件管理
|
||||
function goPluginManagement() {
|
||||
router.push({
|
||||
name: SettingRouteEnum.SETTING_SYSTEM_PLUGIN_MANAGEMENT,
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadList();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue