feat(功能用例): 缺陷需求调整&附件联调&关联用例补充
This commit is contained in:
parent
81b9f083e5
commit
74026933e4
|
@ -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 }) {
|
||||||
|
|
|
@ -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}` });
|
||||||
|
};
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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集合
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 {};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模块树改变回调
|
// 模块树改变回调
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getFetch();
|
// getFetch();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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': '快速创建缺陷成功',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
|
@ -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': '创建流转步骤',
|
||||||
|
|
Loading…
Reference in New Issue