feat(功能用例): 详情和详情编辑显示设置页面
This commit is contained in:
parent
b4d8149181
commit
7e42c5cc3b
|
@ -1,21 +1,26 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import {
|
||||
AddDemandUrl,
|
||||
BatchAssociationDemandUrl,
|
||||
BatchCopyCaseUrl,
|
||||
BatchDeleteCaseUrl,
|
||||
BatchDeleteRecycleCaseListUrl,
|
||||
BatchEditCaseUrl,
|
||||
BatchMoveCaseUrl,
|
||||
CancelAssociationDemandUrl,
|
||||
CreateCaseModuleTreeUrl,
|
||||
CreateCaseUrl,
|
||||
DeleteCaseModuleTreeUrl,
|
||||
DeleteCaseUrl,
|
||||
DeleteRecycleCaseListUrl,
|
||||
DetailCaseUrl,
|
||||
FollowerCaseUrl,
|
||||
GetAssociatedFilePageUrl,
|
||||
GetCaseListUrl,
|
||||
GetCaseModulesCountUrl,
|
||||
GetCaseModuleTreeUrl,
|
||||
GetDefaultTemplateFieldsUrl,
|
||||
GetDemandListUrl,
|
||||
GetRecycleCaseListUrl,
|
||||
GetRecycleCaseModulesCountUrl,
|
||||
GetTrashCaseModuleTreeUrl,
|
||||
|
@ -24,6 +29,7 @@ import {
|
|||
RestoreCaseListUrl,
|
||||
UpdateCaseModuleTreeUrl,
|
||||
UpdateCaseUrl,
|
||||
UpdateDemandUrl,
|
||||
} from '@/api/requrls/case-management/featureCase';
|
||||
|
||||
import type {
|
||||
|
@ -33,8 +39,10 @@ import type {
|
|||
BatchMoveOrCopyType,
|
||||
CaseManagementTable,
|
||||
CaseModuleQueryParams,
|
||||
CreateOrUpdateDemand,
|
||||
CreateOrUpdateModule,
|
||||
DeleteCaseType,
|
||||
DemandItem,
|
||||
ModulesTreeType,
|
||||
MoveModules,
|
||||
UpdateModule,
|
||||
|
@ -86,6 +94,10 @@ export function getCaseDefaultFields(projectId: string) {
|
|||
export function getAssociatedFileListUrl(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<AssociatedList>>({ url: GetAssociatedFilePageUrl, data });
|
||||
}
|
||||
// 关注用例
|
||||
export function followerCaseRequest(data: { userId: string; functionalCaseId: string }) {
|
||||
return MSR.post({ url: FollowerCaseUrl, data });
|
||||
}
|
||||
// 创建用例
|
||||
export function createCaseRequest(data: Record<string, any>) {
|
||||
return MSR.uploadFile({ url: CreateCaseUrl }, { request: data.request, fileList: data.fileList }, '', true);
|
||||
|
@ -146,4 +158,31 @@ export function recoverRecycleCase(id: string) {
|
|||
export function deleteRecycleCaseList(id: string) {
|
||||
return MSR.get({ url: `${DeleteRecycleCaseListUrl}/${id}` });
|
||||
}
|
||||
|
||||
// 关联需求
|
||||
|
||||
// 已关联需求列表
|
||||
export function getDemandList(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<DemandItem[]>>({ url: GetDemandListUrl, data });
|
||||
}
|
||||
|
||||
// 添加需求
|
||||
export function addDemandRequest(data: CreateOrUpdateDemand) {
|
||||
return MSR.post({ url: AddDemandUrl, data });
|
||||
}
|
||||
|
||||
// 更新需求
|
||||
export function updateDemand(data: CreateOrUpdateDemand) {
|
||||
return MSR.post({ url: UpdateDemandUrl, data });
|
||||
}
|
||||
// 批量关联需求
|
||||
export function batchAssociationDemand(data: CreateOrUpdateDemand) {
|
||||
return MSR.post({ url: BatchAssociationDemandUrl, data });
|
||||
}
|
||||
|
||||
// 取消关联
|
||||
export function cancelAssociationDemand(id: string) {
|
||||
return MSR.get({ url: `${CancelAssociationDemandUrl}/${id}` });
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -59,4 +59,29 @@ export const RecoverRecycleCaseListUrl = '/functional/case/trash/recover';
|
|||
// 删除回收站单个用例
|
||||
export const DeleteRecycleCaseListUrl = '/functional/case/trash/delete';
|
||||
|
||||
// 关联需求
|
||||
|
||||
// 已关联需求列表
|
||||
export const GetDemandListUrl = '/functional/case/demand/page';
|
||||
// 添加需求
|
||||
export const AddDemandUrl = '/functional/case/demand/add';
|
||||
// 更新需求
|
||||
export const UpdateDemandUrl = '/functional/case/demand/update';
|
||||
// 批量关联需求
|
||||
export const BatchAssociationDemandUrl = '/functional/case/demand/batch/relevance';
|
||||
// 取消关联
|
||||
export const CancelAssociationDemandUrl = '/functional/case/demand/cancel';
|
||||
|
||||
// 附件管理
|
||||
// 上传文件并关联用例
|
||||
export const UploadOrAssociationFileUrl = '/attachment/upload/file';
|
||||
// 转存文件
|
||||
export const TransferFileUrl = '/attachment/transfer';
|
||||
// 预览文件
|
||||
export const PreviewFileUrl = '/attachment/preview';
|
||||
// 下载文件
|
||||
export const DownloadFileUrl = '/attachment/download';
|
||||
// 删除文件或取消关联用例文件
|
||||
export const deleteFileOrCancelAssociationUrl = '/attachment/delete/file';
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -757,3 +757,12 @@
|
|||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
/*** 徽标 ****/
|
||||
.arco-badge-number {
|
||||
width: 30px !important;
|
||||
height: 16px !important;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
background: var(--color-text-brand);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,12 @@
|
|||
>
|
||||
<template #title>
|
||||
<div class="flex w-full items-center">
|
||||
{{ props.title }}
|
||||
<a-tooltip :content="props.title" position="bottom">
|
||||
<div class="one-line-text max-w-[300px]">
|
||||
{{ props.title }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
|
||||
<MsPrevNextButton
|
||||
ref="prevNextButtonRef"
|
||||
v-model:loading="loading"
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
isLimit: boolean; // 是否限制文件大小
|
||||
draggable: boolean; // 是否支持拖拽上传
|
||||
isAllScreen?: boolean; // 是否是全屏显示拖拽上传
|
||||
cutHeight: number; // 被剪切高度
|
||||
}> & {
|
||||
accept: UploadType;
|
||||
fileList: MsFileItem[];
|
||||
|
@ -95,6 +96,7 @@
|
|||
showSubText: true,
|
||||
isLimit: true,
|
||||
isAllScreen: false,
|
||||
cutHeight: 110,
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:fileList', 'change']);
|
||||
|
@ -144,7 +146,7 @@
|
|||
(val) => {
|
||||
if (val) {
|
||||
total.value = '100vh';
|
||||
other.value = '110px';
|
||||
other.value = `${props.cutHeight}px`;
|
||||
showDropArea.value = false;
|
||||
} else {
|
||||
total.value = '154px';
|
||||
|
|
|
@ -9,6 +9,8 @@ export enum FormCreateKeyEnum {
|
|||
CASE_MANAGEMENT_FIELD = 'caseManagementFields',
|
||||
// 自定义属性
|
||||
CASE_CUSTOM_ATTRS = 'caseCustomAttributes',
|
||||
// 用例tab详情字段
|
||||
CASE_CUSTOM_ATTRS_DETAIL = 'caseCustomAttributesDetail',
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -33,6 +33,7 @@ export enum TableKeyEnum {
|
|||
CASE_MANAGEMENT_DETAIL_TABLE = 'caseManagementDetailTable',
|
||||
CASE_MANAGEMENT_ASSOCIATED_TABLE = 'caseManagementAssociatedFileTable',
|
||||
BUG_MANAGEMENT = 'bugManagement',
|
||||
CASE_MANAGEMENT_DEMAND = 'caseManagementDemand',
|
||||
CASE_MANAGEMENT_REVIEW = 'caseManagementReview',
|
||||
CASE_MANAGEMENT_REVIEW_CASE = 'caseManagementReviewCase',
|
||||
}
|
||||
|
|
|
@ -176,9 +176,41 @@ export interface CaseModuleQueryParams extends TableQueryParams {
|
|||
projectId: string;
|
||||
}
|
||||
|
||||
// export interface BatchParams extends BatchMoveOrCopyType {
|
||||
// selectIds: string[];
|
||||
// selectAll: boolean;
|
||||
// moduleIds: string[];
|
||||
// projectId: string;
|
||||
// }
|
||||
export interface TabItemType {
|
||||
key: string;
|
||||
title: string;
|
||||
enable: boolean;
|
||||
}
|
||||
|
||||
// 需求
|
||||
export interface DemandItem {
|
||||
id: string;
|
||||
caseId: string; // 功能用例ID
|
||||
demandId: string; // 需求ID
|
||||
demandName: string; // 需求标题
|
||||
demandUrl: string; // 需求地址
|
||||
demandPlatform: string; // 需求所属平台
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
createUser: string;
|
||||
updateUser: string;
|
||||
children: DemandItem[]; // 平台下对应的需求
|
||||
}
|
||||
|
||||
// 平台需求列表
|
||||
export interface DemandFormList {
|
||||
demandId: string;
|
||||
demandName: string;
|
||||
demandUrl: string;
|
||||
}
|
||||
|
||||
// 创建需求&编辑需求
|
||||
export interface CreateOrUpdateDemand {
|
||||
id?: string;
|
||||
caseId: string;
|
||||
demandPlatform: string;
|
||||
demandList?: DemandFormList[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type DemandList = DemandItem[];
|
||||
|
|
|
@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
|
|||
|
||||
import { getCaseModulesCounts, getRecycleModulesCounts } from '@/api/modules/case-management/featureCase';
|
||||
|
||||
import type { CaseModuleQueryParams } from '@/models/caseManagement/featureCase';
|
||||
import type { CaseModuleQueryParams, TabItemType } from '@/models/caseManagement/featureCase';
|
||||
import { ModuleTreeNode } from '@/models/projectManagement/file';
|
||||
|
||||
const useFeatureCaseStore = defineStore('featureCase', {
|
||||
|
@ -14,6 +14,7 @@ const useFeatureCaseStore = defineStore('featureCase', {
|
|||
modulesCount: Record<string, any>; // 用例树模块数量
|
||||
recycleModulesCount: Record<string, any>; // 回收站模块数量
|
||||
operatingState: boolean; // 操作状态
|
||||
tabSettingList: TabItemType[]; // 详情tab
|
||||
} => ({
|
||||
moduleId: [],
|
||||
allModuleId: [],
|
||||
|
@ -21,6 +22,7 @@ const useFeatureCaseStore = defineStore('featureCase', {
|
|||
modulesCount: {},
|
||||
recycleModulesCount: {},
|
||||
operatingState: false,
|
||||
tabSettingList: [],
|
||||
}),
|
||||
actions: {
|
||||
// 设置选择moduleId
|
||||
|
@ -37,6 +39,7 @@ const useFeatureCaseStore = defineStore('featureCase', {
|
|||
// 获取模块数量
|
||||
async getCaseModulesCountCount(params: CaseModuleQueryParams) {
|
||||
try {
|
||||
this.modulesCount = {};
|
||||
this.modulesCount = await getCaseModulesCounts(params);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@ -45,6 +48,7 @@ const useFeatureCaseStore = defineStore('featureCase', {
|
|||
// 获取模块数量
|
||||
async getRecycleMModulesCountCount(params: CaseModuleQueryParams) {
|
||||
try {
|
||||
this.recycleModulesCount = {};
|
||||
this.recycleModulesCount = await getRecycleModulesCounts(params);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@ -54,6 +58,14 @@ const useFeatureCaseStore = defineStore('featureCase', {
|
|||
setIsAlreadySuccess(state: boolean) {
|
||||
this.operatingState = state;
|
||||
},
|
||||
// 设置菜单
|
||||
setTab(list: TabItemType[]) {
|
||||
this.tabSettingList = list;
|
||||
},
|
||||
// 获取显示的tab
|
||||
getTab() {
|
||||
return this.tabSettingList.filter((item) => item.enable);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<a-modal
|
||||
v-model:visible="showModal"
|
||||
title-align="start"
|
||||
class="ms-modal-form ms-modal-medium"
|
||||
:cancel-text="t('common.cancel')"
|
||||
unmount-on-close
|
||||
@close="handleCancel"
|
||||
>
|
||||
<template #title>
|
||||
{{ title }}
|
||||
</template>
|
||||
<div class="form">
|
||||
<a-form ref="demandFormRef" :model="modelForm" size="large" layout="vertical">
|
||||
<a-form-item :label="t('caseManagement.featureCase.tableColumnID')" asterisk-position="end" field="demandId">
|
||||
<a-input
|
||||
v-model="modelForm.demandId"
|
||||
:max-length="20"
|
||||
:placeholder="t('caseManagement.featureCase.pleaseEnterID')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('caseManagement.featureCase.requirementTitle')"
|
||||
asterisk-position="end"
|
||||
field="demandName"
|
||||
:rules="[{ required: true, message: t('caseManagement.featureCase.pleaseEnterTitle') }]"
|
||||
>
|
||||
<a-input
|
||||
v-model="modelForm.demandName"
|
||||
:max-length="255"
|
||||
show-word-limit
|
||||
:placeholder="t('caseManagement.featureCase.pleaseEnterTitle')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('caseManagement.featureCase.requirementUrl')" asterisk-position="end" field="demandUrl">
|
||||
<a-input
|
||||
v-model="modelForm.demandUrl"
|
||||
:max-length="255"
|
||||
:placeholder="t('caseManagement.featureCase.pleaseEnterRequirementUrl')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="secondary" @click="handleOK(true)">{{ t('ms.dialog.saveContinue') }}</a-button>
|
||||
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading" @click="handleOK(false)">
|
||||
{{ updateName ? t('common.update') : t('common.create') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance, Message, ValidatedError } from '@arco-design/web-vue';
|
||||
|
||||
import { addDemandRequest, updateDemand } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { CreateOrUpdateDemand, DemandFormList, DemandItem } from '@/models/caseManagement/featureCase';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
visible: boolean;
|
||||
form: DemandItem;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', v: boolean): void;
|
||||
(e: 'success'): void;
|
||||
}>();
|
||||
|
||||
const form = ref<CreateOrUpdateDemand>({
|
||||
id: '',
|
||||
caseId: props.caseId,
|
||||
demandPlatform: 'LOCAL',
|
||||
});
|
||||
|
||||
const updateName = ref<string>('');
|
||||
|
||||
const showModal = ref<boolean>(false);
|
||||
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
|
||||
const initModelForm: DemandFormList = {
|
||||
demandId: '',
|
||||
demandName: '',
|
||||
demandUrl: '',
|
||||
};
|
||||
|
||||
const modelForm = ref<DemandFormList>({
|
||||
...initModelForm,
|
||||
});
|
||||
|
||||
const demandFormRef = ref<FormInstance | null>(null);
|
||||
|
||||
function resetForm() {
|
||||
modelForm.value = { ...initModelForm };
|
||||
form.value.id = '';
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
demandFormRef.value?.resetFields();
|
||||
showModal.value = false;
|
||||
}
|
||||
|
||||
function handleOK(isContinue: boolean) {
|
||||
demandFormRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
|
||||
if (!errors) {
|
||||
try {
|
||||
const { demandId, demandName, demandUrl } = modelForm.value;
|
||||
confirmLoading.value = true;
|
||||
const params: CreateOrUpdateDemand = {
|
||||
...form.value,
|
||||
demandList: [{ demandId, demandName, demandUrl }],
|
||||
};
|
||||
if (form.value.id) {
|
||||
await updateDemand(params);
|
||||
Message.success(t('common.updateSuccess'));
|
||||
} else {
|
||||
await addDemandRequest(params);
|
||||
Message.success(t('common.addSuccess'));
|
||||
}
|
||||
if (!isContinue) {
|
||||
handleCancel();
|
||||
}
|
||||
resetForm();
|
||||
emit('success');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
showModal.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showModal.value,
|
||||
(val) => {
|
||||
emit('update:visible', val);
|
||||
}
|
||||
);
|
||||
|
||||
const title = ref<string>('');
|
||||
watchEffect(() => {
|
||||
title.value = form.value.id
|
||||
? t('caseManagement.featureCase.updateDemand', { name: props.form.demandName })
|
||||
: t('caseManagement.featureCase.addDemand');
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.form,
|
||||
(val) => {
|
||||
modelForm.value = { ...val };
|
||||
form.value.id = val.id;
|
||||
updateName.value = val.demandName;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,295 @@
|
|||
<template>
|
||||
<MsBaseTable v-bind="propsRes" ref="tableRef" v-on="propsEvent">
|
||||
<template #index="{ rowIndex }">
|
||||
<div class="circle text-xs font-medium"> {{ rowIndex + 1 }}</div>
|
||||
</template>
|
||||
<template #caseStep="{ record }">
|
||||
<a-textarea
|
||||
v-if="record.showStep"
|
||||
v-model="record.step"
|
||||
size="mini"
|
||||
:auto-size="true"
|
||||
class="w-max-[267px]"
|
||||
:placeholder="t('system.orgTemplate.stepTip')"
|
||||
@blur="blurHandler(record, 'step')"
|
||||
/>
|
||||
<div v-else-if="record.step && !record.showStep" class="w-full cursor-pointer" @click="edit(record, 'step')">{{
|
||||
record.step
|
||||
}}</div>
|
||||
<div
|
||||
v-else-if="!record.caseStep && !record.showStep"
|
||||
class="placeholder w-full cursor-pointer text-[var(--color-text-brand)]"
|
||||
@click="edit(record, 'step')"
|
||||
>{{ t('system.orgTemplate.stepTip') }}</div
|
||||
>
|
||||
</template>
|
||||
<template #expectedResult="{ record }">
|
||||
<a-textarea
|
||||
v-if="record.showExpected"
|
||||
v-model="record.expected"
|
||||
size="mini"
|
||||
:auto-size="true"
|
||||
class="w-max-[267px]"
|
||||
:placeholder="t('system.orgTemplate.expectationTip')"
|
||||
@blur="blurHandler(record, 'expected')"
|
||||
/>
|
||||
<div
|
||||
v-else-if="record.expected && !record.showExpected"
|
||||
class="w-full cursor-pointer"
|
||||
@click="edit(record, 'expected')"
|
||||
>{{ record.expected }}</div
|
||||
>
|
||||
<div
|
||||
v-else-if="!record.expected && !record.showExpected"
|
||||
class="placeholder w-full cursor-pointer text-[var(--color-text-brand)]"
|
||||
@click="edit(record, 'expected')"
|
||||
>{{ t('system.orgTemplate.expectationTip') }}</div
|
||||
>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsTableMoreAction
|
||||
v-if="!record.internal"
|
||||
:list="moreActions"
|
||||
@select="(item:ActionsItem) => handleMoreActionSelect(item,record)"
|
||||
/>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<a-button class="mt-2 px-0" type="text" :disabled="!props.isDisabled" @click="addStep">
|
||||
<template #icon>
|
||||
<icon-plus class="text-[14px]" />
|
||||
</template>
|
||||
{{ t('system.orgTemplate.addStep') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
||||
import type { StepList } from '@/models/caseManagement/featureCase';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
stepList: any;
|
||||
isDisabled?: boolean;
|
||||
}>(),
|
||||
{
|
||||
isDisabled: false,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits(['update:stepList']);
|
||||
|
||||
const templateFieldColumns = ref<MsTableColumn>([
|
||||
{
|
||||
title: 'system.orgTemplate.numberIndex',
|
||||
dataIndex: 'index',
|
||||
slotName: 'index',
|
||||
width: 100,
|
||||
showDrag: false,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.useCaseStep',
|
||||
slotName: 'caseStep',
|
||||
dataIndex: 'caseStep',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.expectedResult',
|
||||
dataIndex: 'expectedResult',
|
||||
slotName: 'expectedResult',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.operation',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
showDrag: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const moreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'caseManagement.featureCase.copyStep',
|
||||
eventTag: 'copyStep',
|
||||
},
|
||||
{
|
||||
label: 'caseManagement.featureCase.InsertStepsBefore',
|
||||
eventTag: 'InsertStepsBefore',
|
||||
},
|
||||
{
|
||||
label: 'caseManagement.featureCase.afterInsertingSteps',
|
||||
eventTag: 'afterInsertingSteps',
|
||||
},
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, setProps } = useTable(undefined, {
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_DETAIL_TABLE,
|
||||
columns: templateFieldColumns.value,
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
noDisable: true,
|
||||
size: 'default',
|
||||
showSetting: false,
|
||||
showPagination: false,
|
||||
enableDrag: true,
|
||||
});
|
||||
|
||||
// 步骤描述
|
||||
const stepData = ref<StepList[]>([
|
||||
{
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// 复制步骤
|
||||
function copyStep(record: StepList) {
|
||||
stepData.value.push({
|
||||
...record,
|
||||
id: getGenerateId(),
|
||||
});
|
||||
}
|
||||
|
||||
// 删除步骤
|
||||
function deleteStep(record: StepList) {
|
||||
stepData.value = stepData.value.filter((item: any) => item.id !== record.id);
|
||||
}
|
||||
|
||||
// 步骤之前插入步骤
|
||||
function insertStepsBefore(record: StepList) {
|
||||
const index = stepData.value.map((item: any) => item.id).indexOf(record.id);
|
||||
const insertItem = {
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
};
|
||||
stepData.value.splice(index, 0, insertItem);
|
||||
}
|
||||
|
||||
// 步骤之后插入步骤
|
||||
function afterInsertingSteps(record: StepList) {
|
||||
const index = stepData.value.map((item: any) => item.id).indexOf(record.id);
|
||||
const insertItem = {
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
};
|
||||
stepData.value.splice(index + 1, 0, insertItem);
|
||||
}
|
||||
|
||||
// 更多操作
|
||||
const handleMoreActionSelect = (item: ActionsItem, record: StepList) => {
|
||||
switch (item.eventTag) {
|
||||
case 'copyStep':
|
||||
copyStep(record);
|
||||
break;
|
||||
case 'InsertStepsBefore':
|
||||
insertStepsBefore(record);
|
||||
break;
|
||||
case 'afterInsertingSteps':
|
||||
afterInsertingSteps(record);
|
||||
break;
|
||||
default:
|
||||
deleteStep(record);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 添加步骤
|
||||
const addStep = () => {
|
||||
stepData.value.push({
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑步骤
|
||||
function edit(record: StepList, type: string) {
|
||||
if (!props.isDisabled) return;
|
||||
if (type === 'step') {
|
||||
record.showStep = true;
|
||||
} else {
|
||||
record.showExpected = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 失去焦点回调
|
||||
function blurHandler(record: StepList, type: string) {
|
||||
if (!props.isDisabled) return;
|
||||
if (type === 'step') {
|
||||
record.showStep = false;
|
||||
} else {
|
||||
record.showExpected = false;
|
||||
}
|
||||
}
|
||||
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
|
||||
|
||||
watchEffect(() => {
|
||||
stepData.value = props.stepList;
|
||||
setProps({ data: stepData.value });
|
||||
if (!props.isDisabled) {
|
||||
tableRef.value?.initColumn(templateFieldColumns.value.slice(0, templateFieldColumns.value.length - 1));
|
||||
} else {
|
||||
tableRef.value?.initColumn(templateFieldColumns.value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => stepData.value,
|
||||
(val) => {
|
||||
emit('update:stepList', val);
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
setProps({ data: stepData.value });
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.circle {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
color: var(--color-text-4);
|
||||
background: var(--color-text-n8);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<ms-base-table ref="tableRef" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #demandId="{ record }">
|
||||
<span class="ml-2"> {{ record.demandId }}</span>
|
||||
</template>
|
||||
<template #demandName="{ record }">
|
||||
<span class="ml-1" :class="[props.highlightName ? 'text-[rgb(var(--primary-5))]' : '']">
|
||||
{{ record.demandName }}
|
||||
<span v-if="record.children && (record.children || []).length"
|
||||
>({{ (record.children || []).length }})</span
|
||||
></span
|
||||
>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsButton v-if="record.demandPlatform === 'LOCAL'" @click="emit('update', record)">{{
|
||||
t('caseManagement.featureCase.cancelAssociation')
|
||||
}}</MsButton>
|
||||
<MsButton v-if="record.children && (record.children || []).length" @click="emit('update', record)">{{
|
||||
t('common.edit')
|
||||
}}</MsButton>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { getDemandList } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { DemandItem } from '@/models/caseManagement/featureCase';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
funParams: Record<string, any>; // 列表查询参数
|
||||
isShowOperation?: boolean; // 是否显示操作列
|
||||
highlightName?: boolean; // 是否高亮名称
|
||||
}>(),
|
||||
{
|
||||
isShowOperation: true,
|
||||
highlightName: true,
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update', record: DemandItem): void;
|
||||
}>();
|
||||
|
||||
const expandedKeys = ref<string[]>([]);
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnID',
|
||||
slotName: 'demandId',
|
||||
showInTable: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnName',
|
||||
slotName: 'demandName',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.demandPlatform',
|
||||
width: 300,
|
||||
dataIndex: 'demandPlatform',
|
||||
showInTable: true,
|
||||
showTooltip: true,
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: 'caseManagement.featureCase.tableColumnActions',
|
||||
slotName: 'operation',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: 300,
|
||||
showInTable: true,
|
||||
showDrag: false,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(getDemandList, {
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_DEMAND,
|
||||
columns,
|
||||
rowKey: 'id',
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
showSetting: false,
|
||||
});
|
||||
|
||||
const initData = async () => {
|
||||
const { keyword, caseId } = props.funParams;
|
||||
setLoadListParams({ keyword, caseId });
|
||||
await loadList();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initData();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
initData,
|
||||
});
|
||||
const tableRef = ref<InstanceType<typeof MsBaseTable> | null>(null);
|
||||
|
||||
watch(
|
||||
() => props.isShowOperation,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
tableRef.value?.initColumn(columns.slice(0, columns.length - 1));
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -0,0 +1,439 @@
|
|||
<template>
|
||||
<MsDetailDrawer
|
||||
ref="detailDrawerRef"
|
||||
v-model:visible="showDrawerVisible"
|
||||
:width="1200"
|
||||
:footer="false"
|
||||
:title="t('caseManagement.featureCase.caseDetailTitle', { id: detailInfo?.id, name: detailInfo?.name })"
|
||||
:detail-id="props.detailId"
|
||||
:detail-index="props.detailIndex"
|
||||
:get-detail-func="getCaseDetail"
|
||||
:pagination="props.pagination"
|
||||
:table-data="props.tableData"
|
||||
:page-change="props.pageChange"
|
||||
@loaded="loadedCase"
|
||||
>
|
||||
<template #titleRight="{ loading }">
|
||||
<div class="rightButtons flex items-center">
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||
:disabled="loading"
|
||||
:loading="editLoading"
|
||||
@click="updateHandler('edit')"
|
||||
>
|
||||
<MsIcon type="icon-icon_edit_outlined" class="mr-1 font-[16px]" />
|
||||
{{ t('common.edit') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||
:disabled="loading"
|
||||
:loading="shareLoading"
|
||||
@click="shareHandler"
|
||||
>
|
||||
<MsIcon type="icon-icon_share1" class="mr-1 font-[16px]" />
|
||||
{{ t('caseManagement.featureCase.share') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
type="icon"
|
||||
status="secondary"
|
||||
class="mr-4 !rounded-[var(--border-radius-small)]"
|
||||
:disabled="loading"
|
||||
:loading="followLoading"
|
||||
@click="followHandler"
|
||||
>
|
||||
<MsIcon
|
||||
:type="detailInfo.followFlag ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||
class="mr-1 font-[16px]"
|
||||
:class="[detailInfo.followFlag ? 'text-[rgb(var(--warning-6))]' : '']"
|
||||
/>
|
||||
{{ t('caseManagement.featureCase.follow') }}
|
||||
</MsButton>
|
||||
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]">
|
||||
<a-dropdown position="br" :hide-on-select="false">
|
||||
<div>
|
||||
<icon-more class="mr-1" />
|
||||
<span> {{ t('caseManagement.featureCase.more') }}</span>
|
||||
</div>
|
||||
|
||||
<template #content>
|
||||
<a-doption>
|
||||
<a-switch class="mr-1" size="small" />{{ t('caseManagement.featureCase.addToPublic') }}
|
||||
</a-doption>
|
||||
<a-doption @click="updateHandler('copy')">
|
||||
<MsIcon type="icon-icon_copy_filled" class="font-[16px]" />{{ t('common.copy') }}</a-doption
|
||||
>
|
||||
<a-doption class="error-6 text-[rgb(var(--danger-6))]" @click="deleteHandler()">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="font-[16px] text-[rgb(var(--danger-6))]" />
|
||||
{{ t('common.delete') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</MsButton>
|
||||
<MsButton type="icon" status="secondary" class="!rounded-[var(--border-radius-small)]" @click="toggle">
|
||||
<MsIcon :type="isFullscreen ? 'icon-icon_off_screen' : 'icon-icon_full_screen_one'" class="mr-1" size="16" />
|
||||
{{ t('caseManagement.featureCase.fullScreen') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<div ref="wrapperRef" class="h-full bg-white">
|
||||
<MsSplitBox ref="wrapperRef" expand-direction="right" :max="0.7" :min="0.7" :size="900">
|
||||
<template #left>
|
||||
<div class="leftWrapper h-full">
|
||||
<div class="header h-[50px]">
|
||||
<a-tabs @change="changeTabs">
|
||||
<a-tab-pane key="detail">
|
||||
<template #title> {{ t('caseManagement.featureCase.detail') }}</template>
|
||||
<TabDetail v-if="activeTab === 'detail'" :form="detailInfo" @update-success="updateSuccess" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane v-for="tab of tabSetting" :key="tab.key">
|
||||
<template #title>
|
||||
<div class="flex items-center">
|
||||
<span>{{ t(tab.title) }}</span>
|
||||
<a-badge
|
||||
class="ml-1"
|
||||
:class="activeTab === tab.key ? 'active' : ''"
|
||||
:count="1000"
|
||||
:max-count="99"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<Demand v-if="activeTab === 'requirement'" :case-id="props.detailId" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="setting">
|
||||
<template #title>
|
||||
<span @click="showMenuSetting">{{
|
||||
t('caseManagement.featureCase.detailDisplaySetting')
|
||||
}}</span></template
|
||||
>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="rightWrapper p-[24px]">
|
||||
<div class="mb-4 font-medium">{{ t('caseManagement.featureCase.basicInfo') }}</div>
|
||||
<div class="baseItem">
|
||||
<span class="label"> {{ t('caseManagement.featureCase.tableColumnModule') }}</span>
|
||||
<span>{{ moduleName }}</span>
|
||||
</div>
|
||||
<!-- 自定义字段开始 -->
|
||||
<MsFormCreate
|
||||
v-if="formRules.length"
|
||||
ref="formCreateRef"
|
||||
class="w-full"
|
||||
:option="options"
|
||||
:form-rule="formRules"
|
||||
:form-create-key="FormCreateKeyEnum.CASE_CUSTOM_ATTRS_DETAIL"
|
||||
/>
|
||||
<!-- 自定义字段结束 -->
|
||||
<div class="baseItem">
|
||||
<span class="label"> {{ t('caseManagement.featureCase.tableColumnCreateUser') }}</span>
|
||||
<span>{{ detailInfo?.createUser }}</span>
|
||||
</div>
|
||||
<div class="baseItem">
|
||||
<span class="label"> {{ t('caseManagement.featureCase.tableColumnCreateTime') }}</span>
|
||||
<span>{{ dayjs(detailInfo?.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
</template>
|
||||
</MsDetailDrawer>
|
||||
<SettingDrawer v-model:visible="showSettingDrawer" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
|
||||
import type { FormItem } from '@/components/pure/ms-form-create/types';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
||||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||
import Demand from './demand.vue';
|
||||
import SettingDrawer from './settingDrawer.vue';
|
||||
import TabDetail from './tabDetail.vue';
|
||||
|
||||
import { deleteCaseRequest, followerCaseRequest, getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { characterLimit, findNodeByKey } from '@/utils';
|
||||
|
||||
import type { CaseManagementTable, CustomAttributes, TabItemType } from '@/models/caseManagement/featureCase';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
const router = useRouter();
|
||||
const detailDrawerRef = ref<InstanceType<typeof MsDetailDrawer>>();
|
||||
const wrapperRef = ref();
|
||||
const { isFullscreen, toggle } = useFullscreen(wrapperRef);
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
detailId: string; // 详情 id
|
||||
detailIndex: number; // 详情 下标
|
||||
tableData: any[]; // 表格数据
|
||||
pagination: MsPaginationI; // 分页器对象
|
||||
pageChange: (page: number) => Promise<void>; // 分页变更函数
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:visible']);
|
||||
|
||||
const userId = computed(() => userStore.userInfo.id);
|
||||
const appStore = useAppStore();
|
||||
|
||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||
|
||||
const showDrawerVisible = ref<boolean>(false);
|
||||
|
||||
const showSettingDrawer = ref<boolean>(false);
|
||||
function showMenuSetting() {
|
||||
showSettingDrawer.value = true;
|
||||
}
|
||||
|
||||
const tabSettingList = computed(() => {
|
||||
return featureCaseStore.tabSettingList;
|
||||
});
|
||||
|
||||
const tabSetting = ref<TabItemType[]>([...tabSettingList.value]);
|
||||
const activeTab = ref<string | number>('detail');
|
||||
function changeTabs(key: string | number) {
|
||||
activeTab.value = key;
|
||||
switch (activeTab.value) {
|
||||
case 'setting':
|
||||
showMenuSetting();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const detailInfo = ref<Record<string, any>>({});
|
||||
const customFields = ref<CustomAttributes[]>([]);
|
||||
|
||||
function loadedCase(detail: CaseManagementTable) {
|
||||
detailInfo.value = { ...detail };
|
||||
customFields.value = detailInfo.value.customFields;
|
||||
}
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return findNodeByKey<Record<string, any>>(featureCaseStore.caseTree, detailInfo.value?.moduleId as string, 'id')
|
||||
?.name;
|
||||
});
|
||||
|
||||
const editLoading = ref<boolean>(false);
|
||||
|
||||
function updateSuccess() {
|
||||
detailDrawerRef.value?.initDetail();
|
||||
}
|
||||
|
||||
function updateHandler(type: string) {
|
||||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||
query: {
|
||||
id: detailInfo.value.id,
|
||||
},
|
||||
params: {
|
||||
mode: type,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const shareLoading = ref<boolean>(false);
|
||||
|
||||
function shareHandler() {}
|
||||
|
||||
const followLoading = ref<boolean>(false);
|
||||
// 关注
|
||||
async function followHandler() {
|
||||
followLoading.value = true;
|
||||
try {
|
||||
await followerCaseRequest({ userId: userId.value as string, functionalCaseId: detailInfo.value.id });
|
||||
updateSuccess();
|
||||
Message.success(
|
||||
detailInfo.value.followFlag
|
||||
? t('caseManagement.featureCase.cancelFollowSuccess')
|
||||
: t('caseManagement.featureCase.followSuccess')
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
followLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用例
|
||||
function deleteHandler() {
|
||||
const { id, name } = detailInfo.value;
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('caseManagement.featureCase.deleteCaseTitle', { name: characterLimit(name) }),
|
||||
content: t('caseManagement.featureCase.beforeDeleteCase'),
|
||||
okText: t('common.confirmDelete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
try {
|
||||
const params = {
|
||||
id,
|
||||
deleteAll: false,
|
||||
projectId: currentProjectId.value,
|
||||
};
|
||||
await deleteCaseRequest(params);
|
||||
Message.success(t('common.deleteSuccess'));
|
||||
updateSuccess();
|
||||
detailDrawerRef.value?.openPrevDetail();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
}
|
||||
|
||||
const formRules = ref<FormItem[]>([]);
|
||||
|
||||
const isDisabled = ref<boolean>(false);
|
||||
|
||||
// 表单配置项
|
||||
const options = {
|
||||
resetBtn: false, // 不展示默认配置的重置和提交
|
||||
submitBtn: false,
|
||||
on: false, // 取消绑定on事件
|
||||
form: {
|
||||
layout: 'horizontal',
|
||||
labelAlign: 'left',
|
||||
labelColProps: {
|
||||
span: 9,
|
||||
},
|
||||
wrapperColProps: {
|
||||
span: 15,
|
||||
},
|
||||
},
|
||||
// 暂时默认
|
||||
row: {
|
||||
gutter: 0,
|
||||
},
|
||||
wrap: {
|
||||
'asterisk-position': 'end',
|
||||
'validate-trigger': ['change'],
|
||||
'hide-asterisk': true,
|
||||
},
|
||||
};
|
||||
|
||||
// 初始化表单
|
||||
function initForm() {
|
||||
formRules.value = customFields.value.map((item: any) => {
|
||||
return {
|
||||
type: item.type,
|
||||
name: item.fieldId,
|
||||
label: item.fieldName,
|
||||
value: JSON.parse(item.defaultValue),
|
||||
required: item.required,
|
||||
options: item.options || [],
|
||||
props: {
|
||||
modelValue: JSON.parse(item.defaultValue),
|
||||
disabled: isDisabled.value,
|
||||
options: item.options || [],
|
||||
},
|
||||
};
|
||||
}) as FormItem[];
|
||||
}
|
||||
|
||||
watch(
|
||||
() => customFields.value,
|
||||
() => {
|
||||
initForm();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
showDrawerVisible.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showDrawerVisible.value,
|
||||
(val) => {
|
||||
emit('update:visible', val);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => tabSettingList.value,
|
||||
() => {
|
||||
tabSetting.value = featureCaseStore.getTab();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.leftWrapper {
|
||||
.header {
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid var(--color-text-n8);
|
||||
}
|
||||
}
|
||||
.rightWrapper {
|
||||
.baseItem {
|
||||
margin-bottom: 16px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
@apply flex;
|
||||
.label {
|
||||
width: 38%;
|
||||
color: var(--color-text-3);
|
||||
}
|
||||
}
|
||||
:deep(.arco-form-item-layout-horizontal) {
|
||||
margin-bottom: 16px !important;
|
||||
}
|
||||
:deep(.arco-form-item-label-col > .arco-form-item-label) {
|
||||
color: var(--color-text-3) !important;
|
||||
}
|
||||
}
|
||||
.rightButtons {
|
||||
:deep(.ms-button--secondary):hover,
|
||||
:hover > .arco-icon {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
background: var(--color-bg-3);
|
||||
.arco-icon:hover {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.error-6 {
|
||||
color: rgb(var(--danger-6));
|
||||
&:hover {
|
||||
color: rgb(var(--danger-6));
|
||||
}
|
||||
}
|
||||
:deep(.active .arco-badge-number) {
|
||||
background: rgb(var(--primary-5));
|
||||
}
|
||||
</style>
|
|
@ -3,7 +3,7 @@
|
|||
<div class="page-header mb-4 h-[34px]">
|
||||
<div class="text-[var(--color-text-1)]"
|
||||
>{{ moduleNamePath }}
|
||||
<span class="text-[var(--color-text-4)]"> ({{ props.modulesCount[props.activeFolder] }})</span></div
|
||||
<span class="text-[var(--color-text-4)]"> ({{ props.modulesCount[props.activeFolder] || 0 }})</span></div
|
||||
>
|
||||
<div class="flex w-[80%] items-center justify-end">
|
||||
<a-select class="w-[240px]" :placeholder="t('caseManagement.featureCase.versionPlaceholder')">
|
||||
|
@ -57,8 +57,8 @@
|
|||
v-on="propsEvent"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
<template #name="{ record }">
|
||||
<a-button type="text" class="px-0" @click="showCaseDetail(record.id)">{{ record.name }}</a-button>
|
||||
<template #name="{ record, rowIndex }">
|
||||
<a-button type="text" class="px-0" @click="showCaseDetail(record.id, rowIndex)">{{ record.name }}</a-button>
|
||||
</template>
|
||||
<template #reviewStatus="{ record }">
|
||||
<MsIcon
|
||||
|
@ -83,8 +83,10 @@
|
|||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsButton @click="operateCase(record, 'edit')">{{ t('common.edit') }}</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<MsButton @click="operateCase(record, 'copy')">{{ t('caseManagement.featureCase.copy') }}</MsButton>
|
||||
<MsButton class="!mr-0" @click="deleteCase(record)">{{ t('common.delete') }}</MsButton>
|
||||
<a-divider direction="vertical" :margin="8"></a-divider>
|
||||
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" />
|
||||
</template>
|
||||
</ms-base-table>
|
||||
<!-- 用例表结束 -->
|
||||
|
@ -135,6 +137,14 @@
|
|||
</a-modal>
|
||||
<ExportExcelDrawer v-model:visible="showExportExcelVisible" />
|
||||
<BatchEditModal v-model:visible="showEditModel" :batch-params="batchParams" @success="successHandler" />
|
||||
<CaseDetailDrawer
|
||||
v-model:visible="showDetailDrawer"
|
||||
:detail-id="activeDetailId"
|
||||
:detail-index="activeCaseIndex"
|
||||
:table-data="propsRes.data"
|
||||
:page-change="propsEvent.pageChange"
|
||||
:pagination="propsRes.msPagination!"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -149,9 +159,12 @@
|
|||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||
import FilterPanel from '@/components/business/ms-filter-panel/searchForm.vue';
|
||||
import BatchEditModal from './batchEditModal.vue';
|
||||
import CaseDetailDrawer from './caseDetailDrawer.vue';
|
||||
import FeatureCaseTree from './caseTree.vue';
|
||||
import ExportExcelDrawer from './exportExcelDrawer.vue';
|
||||
|
||||
|
@ -428,6 +441,10 @@
|
|||
},
|
||||
],
|
||||
moreAction: [
|
||||
{
|
||||
label: 'featureTest.featureCase.addDemand',
|
||||
eventTag: 'addDemand',
|
||||
},
|
||||
{
|
||||
label: 'caseManagement.featureCase.associatedDemand',
|
||||
eventTag: 'associatedDemand',
|
||||
|
@ -495,7 +512,7 @@
|
|||
scroll: { x: 3200 },
|
||||
selectable: true,
|
||||
showSetting: true,
|
||||
heightUsed: 340,
|
||||
heightUsed: 374,
|
||||
enableDrag: true,
|
||||
},
|
||||
(record) => ({
|
||||
|
@ -594,6 +611,20 @@
|
|||
});
|
||||
}
|
||||
|
||||
const moreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
|
||||
function handleMoreActionSelect(item: ActionsItem, record: CaseManagementTable) {
|
||||
if (item.eventTag === 'delete') {
|
||||
deleteCase(record);
|
||||
}
|
||||
}
|
||||
|
||||
const showExportExcelVisible = ref<boolean>(false);
|
||||
|
||||
// 导出Excel
|
||||
|
@ -714,20 +745,39 @@
|
|||
});
|
||||
}
|
||||
|
||||
// 添加需求
|
||||
function addDemand() {}
|
||||
// 关联需求
|
||||
function handleAssociatedDemand() {}
|
||||
|
||||
function handleTableBatch(event: BatchActionParams, params: BatchActionQueryParams) {
|
||||
batchParams.value = params;
|
||||
if (event.eventTag === 'exportExcel') {
|
||||
handleShowExportExcel();
|
||||
} else if (event.eventTag === 'batchEdit') {
|
||||
batchEdit();
|
||||
} else if (event.eventTag === 'delete') {
|
||||
batchDelete();
|
||||
} else if (event.eventTag === 'batchMoveTo') {
|
||||
batchMoveOrCopy();
|
||||
isMove.value = true;
|
||||
} else if (event.eventTag === 'batchCopyTo') {
|
||||
batchMoveOrCopy();
|
||||
isMove.value = false;
|
||||
switch (event.eventTag) {
|
||||
case 'exportExcel':
|
||||
handleShowExportExcel();
|
||||
break;
|
||||
case 'batchEdit':
|
||||
batchEdit();
|
||||
break;
|
||||
case 'delete':
|
||||
batchDelete();
|
||||
break;
|
||||
case 'batchMoveTo':
|
||||
batchMoveOrCopy();
|
||||
isMove.value = true;
|
||||
break;
|
||||
case 'batchCopyTo':
|
||||
batchMoveOrCopy();
|
||||
isMove.value = false;
|
||||
break;
|
||||
case 'addDemand':
|
||||
addDemand();
|
||||
break;
|
||||
case 'associatedDemand':
|
||||
handleAssociatedDemand();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -741,9 +791,15 @@
|
|||
emitTableParams();
|
||||
resetSelector();
|
||||
}
|
||||
|
||||
const showDetailDrawer = ref(false);
|
||||
const activeDetailId = ref<string>('');
|
||||
const activeCaseIndex = ref<number>(0);
|
||||
// 详情
|
||||
function showCaseDetail(id: string) {}
|
||||
function showCaseDetail(id: string, index: number) {
|
||||
showDetailDrawer.value = true;
|
||||
activeDetailId.value = id;
|
||||
activeCaseIndex.value = index;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => showType.value,
|
||||
|
|
|
@ -45,70 +45,7 @@
|
|||
</div>
|
||||
<!-- 步骤描述 -->
|
||||
<div v-if="form.caseEditType === 'STEP'" class="w-full">
|
||||
<MsBaseTable v-bind="propsRes" ref="stepTableRef" v-on="propsEvent">
|
||||
<template #index="{ rowIndex }">
|
||||
<div class="circle text-xs font-medium"> {{ rowIndex + 1 }}</div>
|
||||
</template>
|
||||
<template #caseStep="{ record }">
|
||||
<a-textarea
|
||||
v-if="record.showStep"
|
||||
v-model="record.step"
|
||||
size="mini"
|
||||
:auto-size="true"
|
||||
class="w-max-[267px]"
|
||||
:placeholder="t('system.orgTemplate.stepTip')"
|
||||
@blur="blurHandler(record, 'step')"
|
||||
/>
|
||||
<div
|
||||
v-else-if="record.step && !record.showStep"
|
||||
class="w-full cursor-pointer"
|
||||
@click="edit(record, 'step')"
|
||||
>{{ record.step }}</div
|
||||
>
|
||||
<div
|
||||
v-else-if="!record.caseStep && !record.showStep"
|
||||
class="placeholder w-full cursor-pointer text-[var(--color-text-brand)]"
|
||||
@click="edit(record, 'step')"
|
||||
>{{ t('system.orgTemplate.stepTip') }}</div
|
||||
>
|
||||
</template>
|
||||
<template #expectedResult="{ record }">
|
||||
<a-textarea
|
||||
v-if="record.showExpected"
|
||||
v-model="record.expected"
|
||||
size="mini"
|
||||
:auto-size="true"
|
||||
class="w-max-[267px]"
|
||||
:placeholder="t('system.orgTemplate.expectationTip')"
|
||||
@blur="blurHandler(record, 'expected')"
|
||||
/>
|
||||
<div
|
||||
v-else-if="record.expected && !record.showExpected"
|
||||
class="w-full cursor-pointer"
|
||||
@click="edit(record, 'expected')"
|
||||
>{{ record.expected }}</div
|
||||
>
|
||||
<div
|
||||
v-else-if="!record.expected && !record.showExpected"
|
||||
class="placeholder w-full cursor-pointer text-[var(--color-text-brand)]"
|
||||
@click="edit(record, 'expected')"
|
||||
>{{ t('system.orgTemplate.expectationTip') }}</div
|
||||
>
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsTableMoreAction
|
||||
v-if="!record.internal"
|
||||
:list="moreActions"
|
||||
@select="(item:ActionsItem) => handleMoreActionSelect(item,record)"
|
||||
/>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
<a-button class="mt-2 px-0" type="text" @click="addStep">
|
||||
<template #icon>
|
||||
<icon-plus class="text-[14px]" />
|
||||
</template>
|
||||
{{ t('system.orgTemplate.addStep') }}
|
||||
</a-button>
|
||||
<AddStep v-model:step-list="stepData" />
|
||||
</div>
|
||||
<!-- 文本描述 -->
|
||||
<MsRichText v-else v-model:modelValue="form.textDescription" />
|
||||
|
@ -233,7 +170,7 @@
|
|||
</div>
|
||||
<!-- 自定义字段结束 -->
|
||||
</div>
|
||||
<div class=" ">
|
||||
<div>
|
||||
<MsUpload
|
||||
v-model:file-list="fileList"
|
||||
accept="none"
|
||||
|
@ -260,14 +197,10 @@
|
|||
import MsFormCreate from '@/components/pure/ms-form-create/form-create.vue';
|
||||
import type { FormItem } from '@/components/pure/ms-form-create/types';
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
|
||||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import AddStep from './addStep.vue';
|
||||
import AssociatedFileDrawer from './associatedFileDrawer.vue';
|
||||
|
||||
import { getCaseDefaultFields, getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||
|
@ -281,8 +214,8 @@
|
|||
import type { AssociatedList, CreateCase, StepList } from '@/models/caseManagement/featureCase';
|
||||
import type { CustomField, DefinedFieldItem } from '@/models/setting/template';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
import { convertToFile } from './utils';
|
||||
import {
|
||||
getCustomDetailFields,
|
||||
getTotalFieldOptionList,
|
||||
|
@ -300,75 +233,6 @@
|
|||
|
||||
const emit = defineEmits(['update:formModeValue', 'changeFile']);
|
||||
const acceptType = ref('none'); // 模块-上传文件类型
|
||||
|
||||
const templateFieldColumns: MsTableColumn = [
|
||||
{
|
||||
title: 'system.orgTemplate.numberIndex',
|
||||
dataIndex: 'index',
|
||||
slotName: 'index',
|
||||
width: 100,
|
||||
showDrag: false,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.useCaseStep',
|
||||
slotName: 'caseStep',
|
||||
dataIndex: 'caseStep',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.expectedResult',
|
||||
dataIndex: 'expectedResult',
|
||||
slotName: 'expectedResult',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.operation',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
showInTable: true,
|
||||
showDrag: false,
|
||||
},
|
||||
];
|
||||
|
||||
const { propsRes, propsEvent, setProps } = useTable(undefined, {
|
||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_DETAIL_TABLE,
|
||||
columns: templateFieldColumns,
|
||||
scroll: { x: '800px' },
|
||||
selectable: false,
|
||||
noDisable: true,
|
||||
size: 'default',
|
||||
showSetting: false,
|
||||
showPagination: false,
|
||||
enableDrag: true,
|
||||
});
|
||||
|
||||
const moreActions: ActionsItem[] = [
|
||||
{
|
||||
label: 'caseManagement.featureCase.copyStep',
|
||||
eventTag: 'copyStep',
|
||||
},
|
||||
{
|
||||
label: 'caseManagement.featureCase.InsertStepsBefore',
|
||||
eventTag: 'InsertStepsBefore',
|
||||
},
|
||||
{
|
||||
label: 'caseManagement.featureCase.afterInsertingSteps',
|
||||
eventTag: 'afterInsertingSteps',
|
||||
},
|
||||
{
|
||||
isDivider: true,
|
||||
},
|
||||
{
|
||||
label: 'common.delete',
|
||||
danger: true,
|
||||
eventTag: 'delete',
|
||||
},
|
||||
];
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const caseFormRef = ref<FormInstance>();
|
||||
|
||||
|
@ -440,91 +304,6 @@
|
|||
form.value.caseEditType = value as string;
|
||||
};
|
||||
|
||||
// 添加步骤
|
||||
const addStep = () => {
|
||||
stepData.value.push({
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
});
|
||||
};
|
||||
|
||||
// 复制步骤
|
||||
function copyStep(record: StepList) {
|
||||
stepData.value.push({
|
||||
...record,
|
||||
id: getGenerateId(),
|
||||
});
|
||||
}
|
||||
|
||||
// 删除步骤
|
||||
function deleteStep(record: StepList) {
|
||||
stepData.value = stepData.value.filter((item: any) => item.id !== record.id);
|
||||
}
|
||||
|
||||
// 步骤之前插入步骤
|
||||
function insertStepsBefore(record: StepList) {
|
||||
const index = stepData.value.map((item: any) => item.id).indexOf(record.id);
|
||||
const insertItem = {
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
};
|
||||
stepData.value.splice(index, 0, insertItem);
|
||||
}
|
||||
|
||||
// 步骤之后插入步骤
|
||||
function afterInsertingSteps(record: StepList) {
|
||||
const index = stepData.value.map((item: any) => item.id).indexOf(record.id);
|
||||
const insertItem = {
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
};
|
||||
stepData.value.splice(index + 1, 0, insertItem);
|
||||
}
|
||||
|
||||
// 编辑步骤
|
||||
function edit(record: StepList, type: string) {
|
||||
if (type === 'step') {
|
||||
record.showStep = true;
|
||||
} else {
|
||||
record.showExpected = true;
|
||||
}
|
||||
}
|
||||
// 失去焦点回调
|
||||
function blurHandler(record: StepList, type: string) {
|
||||
if (type === 'step') {
|
||||
record.showStep = false;
|
||||
} else {
|
||||
record.showExpected = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 更多操作
|
||||
const handleMoreActionSelect = (item: ActionsItem, record: StepList) => {
|
||||
switch (item.eventTag) {
|
||||
case 'copyStep':
|
||||
copyStep(record);
|
||||
break;
|
||||
case 'InsertStepsBefore':
|
||||
insertStepsBefore(record);
|
||||
break;
|
||||
case 'afterInsertingSteps':
|
||||
afterInsertingSteps(record);
|
||||
break;
|
||||
default:
|
||||
deleteStep(record);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 总自定义字段
|
||||
const totalTemplateField = ref<DefinedFieldItem[]>([]);
|
||||
|
||||
|
@ -564,32 +343,12 @@
|
|||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// 将文件信息转换为文件格式
|
||||
function convertToFile(fileInfo: AssociatedList): MsFileItem {
|
||||
const fileName = fileInfo.fileType ? `${fileInfo.name}.${fileInfo.fileType || ''}` : `${fileInfo.name}`;
|
||||
const type = fileName.split('.')[1];
|
||||
const file = new File([new Blob()], `${fileName}`, {
|
||||
type: `application/${type}`,
|
||||
});
|
||||
Object.defineProperty(file, 'size', { value: fileInfo.size });
|
||||
return {
|
||||
enable: fileInfo.enable || false,
|
||||
file,
|
||||
name: fileName,
|
||||
percent: 0,
|
||||
status: 'done',
|
||||
uid: fileInfo.id,
|
||||
url: `http://172.16.200.18:8081/${fileInfo.filePath || ''}`,
|
||||
local: fileInfo.local,
|
||||
};
|
||||
}
|
||||
// 处理关联文件
|
||||
function saveSelectAssociatedFile(fileData: AssociatedList[]) {
|
||||
const fileResultList = fileData.map((fileInfo) => convertToFile(fileInfo));
|
||||
fileList.value.push(...fileResultList);
|
||||
}
|
||||
|
||||
const title = ref('');
|
||||
const isEditOrCopy = computed(() => !!route.query.id);
|
||||
const attachmentsList = ref([]);
|
||||
|
||||
|
@ -669,8 +428,6 @@
|
|||
.map((fileInfo: any) => {
|
||||
return convertToFile(fileInfo);
|
||||
});
|
||||
|
||||
// 处理删除本地文件id
|
||||
}
|
||||
|
||||
// 处理详情
|
||||
|
@ -750,14 +507,6 @@
|
|||
showDrawer.value = true;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => stepData.value,
|
||||
(val) => {
|
||||
setProps({ data: val });
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
|
||||
fileList.value = _fileList.map((e) => {
|
||||
return {
|
||||
|
@ -832,10 +581,6 @@
|
|||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
setProps({ data: stepData.value });
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
formRules.value = [];
|
||||
formRuleField.value = [];
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
allow-clear
|
||||
class="mb-[16px]"
|
||||
></a-input-search>
|
||||
<a-spin class="w-full" :style="{ height: `calc(100vh - 316px)` }" :loading="loading">
|
||||
<a-spin class="w-full" :style="{ height: `calc(100vh - 346px)` }" :loading="loading">
|
||||
<MsTree
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:selected-keys="props.selectedKeys"
|
||||
|
@ -328,7 +328,7 @@
|
|||
|
||||
const virtualListProps = computed(() => {
|
||||
return {
|
||||
height: 'calc(100vh - 360px)',
|
||||
height: 'calc(100vh - 366px)',
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div>
|
||||
<a-button type="primary" @click="associatedDemand">
|
||||
{{ t('caseManagement.featureCase.associatedDemand') }}</a-button
|
||||
>
|
||||
<a-button class="mx-3" type="outline" @click="addDemand">
|
||||
{{ t('caseManagement.featureCase.addDemand') }}</a-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('caseManagement.featureCase.searchByNameAndId')"
|
||||
allow-clear
|
||||
class="mx-[8px] w-[240px]"
|
||||
@search="searchList"
|
||||
@press-enter="searchList"
|
||||
></a-input-search>
|
||||
</div>
|
||||
<AssociatedDemandTable
|
||||
ref="demandRef"
|
||||
:fun-params="{ caseId: props.caseId, keyword }"
|
||||
@update="updateDemand"
|
||||
></AssociatedDemandTable>
|
||||
<AddDemandModal v-model:visible="showAddModel" :case-id="props.caseId" :form="modelForm" @success="searchList()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import AddDemandModal from './addDemandModal.vue';
|
||||
import AssociatedDemandTable from './associatedDemandTable.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { DemandItem } from '@/models/caseManagement/featureCase';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
}>();
|
||||
|
||||
const keyword = ref<string>('');
|
||||
const demandRef = ref();
|
||||
|
||||
const searchList = debounce(() => {
|
||||
demandRef.value.initData();
|
||||
}, 100);
|
||||
|
||||
const showAddModel = ref<boolean>(false);
|
||||
|
||||
function addDemand() {
|
||||
showAddModel.value = true;
|
||||
}
|
||||
const modelForm = ref<DemandItem>({
|
||||
id: '',
|
||||
caseId: '', // 功能用例ID
|
||||
demandId: '', // 需求ID
|
||||
demandName: '', // 需求标题
|
||||
demandUrl: '', // 需求地址
|
||||
demandPlatform: '', // 需求所属平台
|
||||
createTime: '',
|
||||
updateTime: '',
|
||||
createUser: '',
|
||||
updateUser: '',
|
||||
children: [], // 平台下对应的需求
|
||||
});
|
||||
|
||||
// 更新需求
|
||||
function updateDemand(record: DemandItem) {
|
||||
showAddModel.value = true;
|
||||
modelForm.value = { ...record };
|
||||
}
|
||||
// 关联需求(暂无接口)
|
||||
function associatedDemand() {}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -14,7 +14,7 @@
|
|||
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.allCase') }}</div>
|
||||
<div class="folder-count">({{ recycleModulesCount.all }})</div></div
|
||||
<div class="folder-count">({{ recycleModulesCount.all || 0 }})</div></div
|
||||
>
|
||||
<div class="ml-auto flex items-center">
|
||||
<a-tooltip
|
||||
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<a-divider class="my-[8px]" />
|
||||
<a-spin class="w-full" :loading="loading">
|
||||
<a-spin class="h-[calc(100vh-274px)] w-full" :loading="loading">
|
||||
<MsTree
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:selected-keys="selectedKeys"
|
||||
|
@ -65,7 +65,7 @@
|
|||
<div class="page-header mb-4 h-[34px]">
|
||||
<div class="text-[var(--color-text-1)]"
|
||||
>{{ currentModuleName }}
|
||||
<span class="text-[var(--color-text-4)]"> ({{ recycleModulesCount[activeFolder] }})</span></div
|
||||
<span class="text-[var(--color-text-4)]"> ({{ recycleModulesCount[activeFolder] || 0 }})</span></div
|
||||
>
|
||||
<div class="flex w-[80%] items-center justify-end">
|
||||
<a-select class="w-[240px]" :placeholder="t('caseManagement.featureCase.versionPlaceholder')">
|
||||
|
@ -129,7 +129,6 @@
|
|||
* @description 功能用例-回收站
|
||||
*/
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -408,7 +407,7 @@
|
|||
|
||||
const virtualListProps = computed(() => {
|
||||
return {
|
||||
height: 'calc(100vh - 316px)',
|
||||
height: 'calc(100vh - 270px)',
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -645,7 +644,7 @@
|
|||
<style scoped lang="less">
|
||||
.pageWrap {
|
||||
min-width: 1000px;
|
||||
height: calc(100vh - 136px);
|
||||
height: calc(100vh - 126px);
|
||||
border-radius: var(--border-radius-large);
|
||||
@apply bg-white;
|
||||
.back {
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="showSettingVisible"
|
||||
:mask="false"
|
||||
:title="t('caseManagement.featureCase.displaySetting')"
|
||||
:width="480"
|
||||
unmount-on-close
|
||||
:footer="false"
|
||||
>
|
||||
<div class="header mb-1 flex h-[22px] items-center justify-between">
|
||||
<div class="flex items-center text-[var(--color-text-4)]"
|
||||
>{{ t('caseManagement.featureCase.displaySetting') }}
|
||||
|
||||
<a-tooltip>
|
||||
<template #content>
|
||||
<div>{{ t('caseManagement.featureCase.tabShowSetting') }} </div>
|
||||
<div>{{ t('caseManagement.featureCase.closeModuleTab') }}</div>
|
||||
<div>{{ t('caseManagement.featureCase.enableModuleTab') }}</div>
|
||||
</template>
|
||||
<span class="inline-block align-middle">
|
||||
<icon-question-circle class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
||||
/></span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div class="cursor-pointer text-[rgb(var(--primary-5))]" @click="setDefault"
|
||||
>{{ t('caseManagement.featureCase.recoverDefault') }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="itemTab">
|
||||
<span>{{ t('caseManagement.featureCase.detail') }}</span>
|
||||
<a-switch v-model="detailEnable" size="small" :disabled="true" />
|
||||
</div>
|
||||
<a-divider orientation="center" class="non-sort"
|
||||
><span class="one-line-text text-xs text-[var(--color-text-4)]">{{
|
||||
t('caseManagement.featureCase.nonClosableTab')
|
||||
}}</span></a-divider
|
||||
>
|
||||
<div v-for="item of tabSettingList" :key="item.key" class="itemTab">
|
||||
<span>{{ t(item.title) }}</span>
|
||||
<a-switch v-model="item.enable" size="small" />
|
||||
</div>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
|
||||
import type { TabItemType } from '@/models/caseManagement/featureCase';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void;
|
||||
}>();
|
||||
|
||||
const showSettingVisible = ref<boolean>(false);
|
||||
const detailEnable = ref<boolean>(true);
|
||||
|
||||
const tabDefaultSettingList = ref<TabItemType[]>([
|
||||
{
|
||||
key: 'case',
|
||||
title: 'caseManagement.featureCase.case',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'requirement',
|
||||
title: 'caseManagement.featureCase.requirement',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'bug',
|
||||
title: 'caseManagement.featureCase.bug',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'dependency',
|
||||
title: 'caseManagement.featureCase.dependency',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'caseReview',
|
||||
title: 'caseManagement.featureCase.caseReview',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'testPlan',
|
||||
title: 'caseManagement.featureCase.testPlan',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'comments',
|
||||
title: 'caseManagement.featureCase.comments',
|
||||
enable: true,
|
||||
},
|
||||
{
|
||||
key: 'changeHistory',
|
||||
title: 'caseManagement.featureCase.changeHistory',
|
||||
enable: true,
|
||||
},
|
||||
]);
|
||||
|
||||
const tabList = computed(() => {
|
||||
return featureCaseStore.tabSettingList;
|
||||
});
|
||||
|
||||
const tabSettingList = ref([...tabList.value]);
|
||||
|
||||
function setDefault() {
|
||||
tabSettingList.value = tabSettingList.value.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
enable: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(val) => {
|
||||
showSettingVisible.value = val;
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => showSettingVisible.value,
|
||||
(val) => {
|
||||
emit('update:visible', val);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => tabSettingList.value,
|
||||
(val) => {
|
||||
featureCaseStore.setTab(val as TabItemType[]);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (tabList.value.length < 1) {
|
||||
featureCaseStore.setTab(tabDefaultSettingList.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.itemTab {
|
||||
height: 38px;
|
||||
@apply flex items-center justify-between p-3;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,446 @@
|
|||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="caseDetailWrapper ml-1">
|
||||
<a-form ref="caseFormRef" class="rounded-[4px]" :model="detailForm" layout="vertical">
|
||||
<a-form-item
|
||||
class="relative"
|
||||
field="precondition"
|
||||
:label="t('system.orgTemplate.precondition')"
|
||||
asterisk-position="end"
|
||||
>
|
||||
<span class="absolute right-[6px] top-0">
|
||||
<a-button v-if="props.allowEdit" type="text" class="px-0" @click="prepositionEdit">
|
||||
<MsIcon type="icon-icon_edit_outlined" class="mr-1 font-[16px] text-[rgb(var(--primary-5))]" />{{
|
||||
t('caseManagement.featureCase.contentEdit')
|
||||
}}</a-button
|
||||
></span
|
||||
>
|
||||
<MsRichText v-if="isEditPreposition" v-model:model-value="detailForm.prerequisite" class="mt-2" />
|
||||
<div v-else class="text-[var(--color-text-3)]" v-html="detailForm?.prerequisite || '-'"></div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="step"
|
||||
:label="
|
||||
detailForm.caseEditType === 'STEP'
|
||||
? t('system.orgTemplate.stepDescription')
|
||||
: t('system.orgTemplate.textDescription')
|
||||
"
|
||||
class="relative"
|
||||
>
|
||||
<div class="absolute left-16 top-0 font-normal">
|
||||
<a-divider direction="vertical" />
|
||||
<a-dropdown :popup-max-height="false" @select="handleSelectType">
|
||||
<span class="changeType text-[var(--color-text-3)]"
|
||||
>{{ t('system.orgTemplate.changeType') }} <icon-down
|
||||
/></span>
|
||||
<template #content>
|
||||
<a-doption value="STEP" :class="getSelectTypeClass('STEP')">
|
||||
{{ t('system.orgTemplate.stepDescription') }}</a-doption
|
||||
>
|
||||
<a-doption value="TEXT" :class="getSelectTypeClass('TEXT')">{{
|
||||
t('system.orgTemplate.textDescription')
|
||||
}}</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 步骤描述 -->
|
||||
<div v-if="detailForm.caseEditType === 'STEP'" class="w-full">
|
||||
<AddStep v-model:step-list="stepData" :is-disabled="isEditPreposition" />
|
||||
</div>
|
||||
<!-- 文本描述 -->
|
||||
<MsRichText
|
||||
v-if="detailForm.caseEditType === 'TEXT' && isEditPreposition"
|
||||
v-model:modelValue="detailForm.textDescription"
|
||||
/>
|
||||
<div v-if="detailForm.caseEditType === 'TEXT' && !isEditPreposition">{{
|
||||
detailForm.textDescription || '-'
|
||||
}}</div>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="detailForm.caseEditType === 'TEXT'"
|
||||
field="remark"
|
||||
:label="t('caseManagement.featureCase.expectedResult')"
|
||||
>
|
||||
<MsRichText
|
||||
v-if="detailForm.caseEditType === 'TEXT' && isEditPreposition"
|
||||
v-model:modelValue="detailForm.expectedResult"
|
||||
/>
|
||||
<div v-else class="text-[var(--color-text-3)]" v-html="detailForm.description || '-'"></div>
|
||||
</a-form-item>
|
||||
<a-form-item field="remark" :label="t('caseManagement.featureCase.remark')">
|
||||
<MsRichText v-if="isEditPreposition" v-model:modelValue="detailForm.description" />
|
||||
<div v-else class="text-[var(--color-text-3)]" v-html="detailForm.description || '-'"></div>
|
||||
</a-form-item>
|
||||
<div v-if="isEditPreposition" class="flex justify-end">
|
||||
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
|
||||
<a-button class="ml-[12px]" type="primary" :loading="confirmLoading" @click="handleOK">
|
||||
{{ t('common.save') }}
|
||||
</a-button></div
|
||||
>
|
||||
<a-form-item field="attachment" :label="t('caseManagement.featureCase.attachment')">
|
||||
<div class="flex flex-col">
|
||||
<div class="mb-1">
|
||||
<a-dropdown position="tr" trigger="hover">
|
||||
<a-button type="outline">
|
||||
<template #icon> <icon-plus class="text-[14px]" /> </template
|
||||
>{{ t('system.orgTemplate.addAttachment') }}</a-button
|
||||
>
|
||||
<template #content>
|
||||
<a-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:before-upload="beforeUpload"
|
||||
@change="handleChange"
|
||||
>
|
||||
<template #upload-button>
|
||||
<a-button type="text" class="!text-[var(--color-text-1)]">
|
||||
<icon-upload />{{ t('caseManagement.featureCase.uploadFile') }}</a-button
|
||||
>
|
||||
</template>
|
||||
</a-upload>
|
||||
<a-button type="text" class="!text-[var(--color-text-1)]" @click="associatedFile">
|
||||
<MsIcon type="icon-icon_link-copy_outlined" size="16" />{{
|
||||
t('caseManagement.featureCase.associatedFile')
|
||||
}}</a-button
|
||||
>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<div class="!hover:bg-[rgb(var(--primary-1))] !text-[var(--color-text-4)]">{{
|
||||
t('system.orgTemplate.addAttachmentTip')
|
||||
}}</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- 文件列表开始 -->
|
||||
<div class="w-[90%]">
|
||||
<MsFileList ref="fileListRef" v-model:file-list="fileList" mode="static">
|
||||
<template #actions="{ item }">
|
||||
<!-- 本地文件 -->
|
||||
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
|
||||
<MsButton type="button" status="danger" class="!mr-[4px]" @click="transferFile(item)">
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<MsButton type="button" status="primary" class="!mr-[4px]" @click="downloadFile(item)">
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<!-- 关联文件 -->
|
||||
<div v-else class="flex flex-nowrap">
|
||||
<MsButton type="button" status="primary" class="!mr-[4px]" @click="cancelAssociated(item)">
|
||||
{{ t('caseManagement.featureCase.cancelLink') }}
|
||||
</MsButton>
|
||||
<MsButton type="button" status="primary" class="!mr-[4px]" @click="downloadFile(item)">
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
</MsFileList>
|
||||
</div>
|
||||
</div>
|
||||
<MsUpload
|
||||
v-model:file-list="fileList"
|
||||
accept="none"
|
||||
:auto-upload="false"
|
||||
:sub-text="acceptType === 'jar' ? '' : t('project.fileManagement.normalFileSubText', { size: 50 })"
|
||||
multiple
|
||||
draggable
|
||||
size-unit="MB"
|
||||
:max-size="50"
|
||||
:is-all-screen="true"
|
||||
class="mb-[16px]"
|
||||
:cut-height="50"
|
||||
@change="handleChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
|
||||
import MsUpload from '@/components/pure/ms-upload/index.vue';
|
||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
import AddStep from './addStep.vue';
|
||||
|
||||
import { updateCaseRequest } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useFormCreateStore from '@/store/modules/form-create/form-create';
|
||||
import { getGenerateId } from '@/utils';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
|
||||
import type { StepList } from '@/models/caseManagement/featureCase';
|
||||
import { FormCreateKeyEnum } from '@/enums/formCreateEnum';
|
||||
|
||||
import { convertToFile } from './utils';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
|
||||
const formCreateStore = useFormCreateStore();
|
||||
|
||||
const caseFormRef = ref<FormInstance>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
form: Record<string, any>;
|
||||
allowEdit?: boolean; // 是否允许编辑
|
||||
}>(),
|
||||
{
|
||||
allowEdit: true, // 是否允许编辑
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'updateSuccess'): void;
|
||||
}>();
|
||||
|
||||
const detailForm = ref<Record<string, any>>({
|
||||
projectId: currentProjectId.value,
|
||||
templateId: '',
|
||||
name: '',
|
||||
prerequisite: '',
|
||||
caseEditType: 'STEP',
|
||||
steps: '',
|
||||
textDescription: '',
|
||||
expectedResult: '',
|
||||
description: '',
|
||||
publicCase: false,
|
||||
moduleId: '',
|
||||
versionId: '',
|
||||
tags: [],
|
||||
customFields: [],
|
||||
relateFileMetaIds: [],
|
||||
});
|
||||
|
||||
// 步骤描述
|
||||
const stepData = ref<StepList[]>([
|
||||
{
|
||||
id: getGenerateId(),
|
||||
step: '',
|
||||
expected: '',
|
||||
showStep: false,
|
||||
showExpected: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const isEditPreposition = ref<boolean>(false); // 非编辑状态
|
||||
|
||||
// 更改类型
|
||||
const handleSelectType = (value: string | number | Record<string, any> | undefined) => {
|
||||
detailForm.value.caseEditType = value as string;
|
||||
};
|
||||
|
||||
// 获取类型样式
|
||||
function getSelectTypeClass(type: string) {
|
||||
return detailForm.value.caseEditType === type
|
||||
? ['bg-[rgb(var(--primary-1))]', '!text-[rgb(var(--primary-5))]']
|
||||
: [];
|
||||
}
|
||||
|
||||
// 编辑前置条件
|
||||
function prepositionEdit() {
|
||||
isEditPreposition.value = !isEditPreposition.value;
|
||||
}
|
||||
|
||||
const fileList = ref<MsFileItem[]>([]);
|
||||
const acceptType = ref('none'); // 模块-上传文件类型
|
||||
|
||||
function beforeUpload() {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
function handleChange(_fileList: MsFileItem[], fileItem: MsFileItem) {
|
||||
fileList.value = _fileList.map((e) => {
|
||||
return {
|
||||
...e,
|
||||
enable: true, // 是否启用
|
||||
local: true, // 是否本地文件
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const showDrawer = ref<boolean>(false);
|
||||
function associatedFile() {
|
||||
showDrawer.value = true;
|
||||
}
|
||||
// 转存
|
||||
function transferFile(item: any) {}
|
||||
// 下载
|
||||
function downloadFile(item: any) {}
|
||||
// 取消关联
|
||||
function cancelAssociated(item: any) {}
|
||||
|
||||
const attachmentsList = ref([]);
|
||||
|
||||
// 后台传过来的local文件的item列表
|
||||
const oldLocalFileList = computed(() => {
|
||||
return attachmentsList.value.filter((item: any) => item.local);
|
||||
});
|
||||
|
||||
// 后台已保存本地文件
|
||||
const currentOldLocalFileList = computed(() => {
|
||||
return fileList.value.filter((item) => item.local && item.status !== 'init').map((item: any) => item.uid);
|
||||
});
|
||||
|
||||
// 已经关联过的id列表
|
||||
const associateFileIds = computed(() => {
|
||||
return attachmentsList.value.filter((item: any) => !item.local).map((item: any) => item.id);
|
||||
});
|
||||
|
||||
// 当前新增传过来的关联list
|
||||
const currentAlreadyAssociateFileList = computed(() => {
|
||||
return fileList.value
|
||||
.filter((item) => !item.local && !associateFileIds.value.includes(item.uid))
|
||||
.map((item: any) => item.uid);
|
||||
});
|
||||
|
||||
// 新增关联文件ID列表
|
||||
const newAssociateFileListIds = computed(() => {
|
||||
return fileList.value
|
||||
.filter((item: any) => !item.local && !associateFileIds.value.includes(item.uid))
|
||||
.map((item: any) => item.uid);
|
||||
});
|
||||
|
||||
// 删除本地上传的文件id
|
||||
const deleteFileMetaIds = computed(() => {
|
||||
return oldLocalFileList.value
|
||||
.filter((item: any) => !currentOldLocalFileList.value.includes(item.id))
|
||||
.map((item: any) => item.id);
|
||||
});
|
||||
|
||||
// 取消关联文件id
|
||||
const unLinkFilesIds = computed(() => {
|
||||
return associateFileIds.value.filter((id: string) => !currentAlreadyAssociateFileList.value.includes(id));
|
||||
});
|
||||
|
||||
const formRuleList = computed(() =>
|
||||
formCreateStore.formCreateRuleMap.get(FormCreateKeyEnum.CASE_CUSTOM_ATTRS_DETAIL)
|
||||
);
|
||||
|
||||
function getParams() {
|
||||
const steps = stepData.value.map((item, index) => {
|
||||
return {
|
||||
num: index,
|
||||
desc: item.step,
|
||||
result: item.expected,
|
||||
};
|
||||
});
|
||||
|
||||
const customFieldsMaps: Record<string, any> = {};
|
||||
formRuleList.value?.forEach((item: any) => {
|
||||
customFieldsMaps[item.field as string] = item.value;
|
||||
});
|
||||
|
||||
return {
|
||||
request: {
|
||||
...detailForm.value,
|
||||
steps: JSON.stringify(steps),
|
||||
deleteFileMetaIds: deleteFileMetaIds.value,
|
||||
unLinkFilesIds: unLinkFilesIds.value,
|
||||
newAssociateFileListIds: newAssociateFileListIds.value,
|
||||
tags: JSON.parse(detailForm.value.tags),
|
||||
customFields: customFieldsMaps,
|
||||
},
|
||||
fileList: fileList.value.filter((item: any) => item.status === 'init'), // 总文件列表
|
||||
};
|
||||
}
|
||||
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
|
||||
function handleOK() {
|
||||
caseFormRef.value?.validate().then(async (res: any) => {
|
||||
if (!res) {
|
||||
try {
|
||||
confirmLoading.value = true;
|
||||
await updateCaseRequest(getParams());
|
||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||
isEditPreposition.value = false;
|
||||
emit('updateSuccess');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
return scrollIntoView(document.querySelector('.arco-form-item-message'), { block: 'center' });
|
||||
});
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
isEditPreposition.value = false;
|
||||
}
|
||||
|
||||
function getDetails() {
|
||||
const { steps, attachments } = detailForm.value;
|
||||
if (steps) {
|
||||
stepData.value = JSON.parse(steps).map((item: any) => {
|
||||
return {
|
||||
step: item.desc,
|
||||
expected: item.result,
|
||||
};
|
||||
});
|
||||
}
|
||||
attachmentsList.value = attachments;
|
||||
// 处理文件列表
|
||||
fileList.value = attachments
|
||||
.map((fileInfo: any) => {
|
||||
return {
|
||||
...fileInfo,
|
||||
name: fileInfo.fileName,
|
||||
};
|
||||
})
|
||||
.map((fileInfo: any) => {
|
||||
return convertToFile(fileInfo);
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.form,
|
||||
() => {
|
||||
detailForm.value = { ...props.form };
|
||||
getDetails();
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 单独更新字段
|
||||
const updateCustomFields = debounce(() => {
|
||||
const customFieldsMaps: Record<string, any> = {};
|
||||
formRuleList.value?.forEach((item: any) => {
|
||||
customFieldsMaps[item.field as string] = item.value;
|
||||
});
|
||||
detailForm.value.customFields = customFieldsMaps as Record<string, any>;
|
||||
}, 300);
|
||||
|
||||
// 监视收集自定义字段参数
|
||||
watch(
|
||||
() => formRuleList.value,
|
||||
() => {
|
||||
const customFieldsValues = props.form.customFields.map((item: any) => JSON.parse(item.defaultValue));
|
||||
// 如果和起始值不一致更新某个字段
|
||||
const isChange = formRuleList.value?.every((item: any) => customFieldsValues.includes(item.value));
|
||||
if (!isChange) {
|
||||
updateCustomFields();
|
||||
handleOK();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-form-item-label) {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,8 @@
|
|||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import type { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import { StatusType } from '@/enums/caseEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -89,5 +92,29 @@ export function getReviewStatusClass(status: keyof typeof StatusType) {
|
|||
return 'text-[rgb(var(--link-6))]';
|
||||
}
|
||||
}
|
||||
/** *
|
||||
*
|
||||
* @description 将文件信息转换为文件格式
|
||||
* @param {stafileInfotus} 文件file
|
||||
*/
|
||||
|
||||
export function convertToFile(fileInfo: AssociatedList): MsFileItem {
|
||||
const fileName = fileInfo.fileType ? `${fileInfo.name}.${fileInfo.fileType || ''}` : `${fileInfo.name}`;
|
||||
const type = fileName.split('.')[1];
|
||||
const file = new File([new Blob()], `${fileName}`, {
|
||||
type: `application/${type}`,
|
||||
});
|
||||
Object.defineProperty(file, 'size', { value: fileInfo.size });
|
||||
return {
|
||||
enable: fileInfo.enable || false,
|
||||
file,
|
||||
name: fileName,
|
||||
percent: 0,
|
||||
status: 'done',
|
||||
uid: fileInfo.id,
|
||||
url: `http://172.16.200.18:8081/${fileInfo.filePath || ''}`,
|
||||
local: fileInfo.local,
|
||||
};
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -1,91 +1,94 @@
|
|||
<template>
|
||||
<div class="mb-[16px]">
|
||||
<a-button type="primary" @click="caseDetail">
|
||||
{{ t('caseManagement.featureCase.creatingCase') }}
|
||||
</a-button>
|
||||
<a-button class="mx-3" type="outline"> {{ t('caseManagement.featureCase.importExcel') }} </a-button>
|
||||
<a-button type="outline"> {{ t('caseManagement.featureCase.importXmind') }} </a-button>
|
||||
</div>
|
||||
<div class="pageWrap">
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<div class="p-[24px] pb-0">
|
||||
<div class="feature-case h-[100%]">
|
||||
<div class="case h-[38px]">
|
||||
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.allCase') }}</div>
|
||||
<div class="folder-count">({{ modulesCount.all }})</div></div
|
||||
>
|
||||
<div class="ml-auto flex items-center">
|
||||
<a-tooltip
|
||||
:content="
|
||||
isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')
|
||||
"
|
||||
>
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
|
||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<MsPopConfirm
|
||||
ref="confirmRef"
|
||||
v-model:visible="addSubVisible"
|
||||
:is-delete="false"
|
||||
:title="t('caseManagement.featureCase.addSubModule')"
|
||||
:all-names="rootModulesName"
|
||||
:loading="confirmLoading"
|
||||
:ok-text="t('common.confirm')"
|
||||
:field-config="{
|
||||
placeholder: t('caseManagement.featureCase.addGroupTip'),
|
||||
}"
|
||||
@confirm="confirmHandler"
|
||||
>
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_create_planarity"
|
||||
size="18"
|
||||
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
|
||||
/>
|
||||
</MsButton>
|
||||
</MsPopConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="my-[8px]" />
|
||||
<FeatureCaseTree
|
||||
ref="caseTreeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
:all-names="rootModulesName"
|
||||
:active-folder="activeFolder"
|
||||
:is-expand-all="isExpandAll"
|
||||
:modules-count="modulesCount"
|
||||
@case-node-select="caseNodeSelect"
|
||||
@init="setRootModules"
|
||||
></FeatureCaseTree>
|
||||
<div class="b-0 absolute w-[88%]">
|
||||
<a-divider class="!my-0 !mb-2" />
|
||||
<div class="rounded-2xl bg-white">
|
||||
<div class="p-[24px] pb-[16px]">
|
||||
<a-button type="primary" @click="caseDetail">
|
||||
{{ t('caseManagement.featureCase.creatingCase') }}
|
||||
</a-button>
|
||||
<a-button class="mx-3" type="outline"> {{ t('caseManagement.featureCase.importExcel') }} </a-button>
|
||||
<a-button type="outline"> {{ t('caseManagement.featureCase.importXmind') }} </a-button>
|
||||
</div>
|
||||
<a-divider class="!my-0" />
|
||||
<div class="pageWrap">
|
||||
<MsSplitBox>
|
||||
<template #left>
|
||||
<div class="p-[24px] pb-0">
|
||||
<div class="feature-case h-[100%]">
|
||||
<div class="case h-[38px]">
|
||||
<div class="flex items-center" :class="getActiveClass('recycle')" @click="setActiveFolder('recycle')">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.recycle') }}</div>
|
||||
<div class="folder-count">({{ recycleModulesCount.all }})</div></div
|
||||
<div class="flex items-center" :class="getActiveClass('all')" @click="setActiveFolder('all')">
|
||||
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.allCase') }}</div>
|
||||
<div class="folder-count">({{ modulesCount.all || 0 }})</div></div
|
||||
>
|
||||
<div class="ml-auto flex items-center">
|
||||
<a-tooltip
|
||||
:content="
|
||||
isExpandAll ? t('project.fileManagement.collapseAll') : t('project.fileManagement.expandAll')
|
||||
"
|
||||
>
|
||||
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="expandHandler">
|
||||
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<MsPopConfirm
|
||||
ref="confirmRef"
|
||||
v-model:visible="addSubVisible"
|
||||
:is-delete="false"
|
||||
:title="t('caseManagement.featureCase.addSubModule')"
|
||||
:all-names="rootModulesName"
|
||||
:loading="confirmLoading"
|
||||
:ok-text="t('common.confirm')"
|
||||
:field-config="{
|
||||
placeholder: t('caseManagement.featureCase.addGroupTip'),
|
||||
}"
|
||||
@confirm="confirmHandler"
|
||||
>
|
||||
<MsButton type="icon" class="!mr-0 p-[2px]">
|
||||
<MsIcon
|
||||
type="icon-icon_create_planarity"
|
||||
size="18"
|
||||
class="text-[rgb(var(--primary-5))] hover:text-[rgb(var(--primary-4))]"
|
||||
/>
|
||||
</MsButton>
|
||||
</MsPopConfirm>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider class="my-[8px]" />
|
||||
<FeatureCaseTree
|
||||
ref="caseTreeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
:all-names="rootModulesName"
|
||||
:active-folder="activeFolder"
|
||||
:is-expand-all="isExpandAll"
|
||||
:modules-count="modulesCount"
|
||||
@case-node-select="caseNodeSelect"
|
||||
@init="setRootModules"
|
||||
></FeatureCaseTree>
|
||||
<div class="b-0 absolute w-[88%]">
|
||||
<a-divider class="!my-0 !mb-2" />
|
||||
<div class="case h-[38px]">
|
||||
<div class="flex items-center" :class="getActiveClass('recycle')" @click="setActiveFolder('recycle')">
|
||||
<MsIcon type="icon-icon_delete-trash_outlined" class="folder-icon" />
|
||||
<div class="folder-name mx-[4px]">{{ t('caseManagement.featureCase.recycle') }}</div>
|
||||
<div class="folder-count">({{ recycleModulesCount.all || 0 }})</div></div
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="p-[24px]">
|
||||
<CaseTable
|
||||
:active-folder="activeFolder"
|
||||
:offspring-ids="offspringIds"
|
||||
:active-folder-type="activeCaseType"
|
||||
:modules-count="modulesCount"
|
||||
@init="initModulesCount"
|
||||
></CaseTable>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</template>
|
||||
<template #right>
|
||||
<div class="p-[24px]">
|
||||
<CaseTable
|
||||
:active-folder="activeFolder"
|
||||
:offspring-ids="offspringIds"
|
||||
:active-folder-type="activeCaseType"
|
||||
:modules-count="modulesCount"
|
||||
@init="initModulesCount"
|
||||
></CaseTable>
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -234,12 +237,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
function test() {
|
||||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE_CREATE_SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
// 设置默认选中状态
|
||||
router.beforeEach((to: any, from: any, next) => {
|
||||
const routeEnumValues = Object.values(CaseManagementRouteEnum);
|
||||
|
@ -260,7 +257,7 @@
|
|||
<style scoped lang="less">
|
||||
.pageWrap {
|
||||
min-width: 1000px;
|
||||
height: calc(100vh - 136px);
|
||||
height: calc(100vh - 166px);
|
||||
border-radius: var(--border-radius-large);
|
||||
@apply bg-white;
|
||||
.case {
|
||||
|
|
|
@ -119,4 +119,40 @@ export default {
|
|||
'caseManagement.featureCase.mightWantTo': 'You might want to',
|
||||
'caseManagement.featureCase.createTestPlan': 'Create a test plan',
|
||||
'caseManagement.featureCase.createCaseReview': 'Create use case reviews',
|
||||
'caseManagement.featureCase.detailDisplaySetting': 'Display setting',
|
||||
'caseManagement.featureCase.addDemand': 'Add Demand',
|
||||
'caseManagement.featureCase.updateDemand': 'Update Demand ({name})',
|
||||
'caseManagement.featureCase.updateUser': 'processor',
|
||||
'caseManagement.featureCase.displaySetting': 'displaySetting',
|
||||
'caseManagement.featureCase.tabShowSetting': 'tab display setting',
|
||||
'caseManagement.featureCase.closeModuleTab': 'Close: in the drawer not show related modules',
|
||||
'caseManagement.featureCase.enableModuleTab': 'Open: Display the relevant modules in the drawer',
|
||||
'caseManagement.featureCase.recoverDefault': 'Restore default',
|
||||
'caseManagement.featureCase.detail': 'details',
|
||||
'caseManagement.featureCase.nonClosableTab': 'These attributes cannot be turned off',
|
||||
'caseManagement.featureCase.case': 'case',
|
||||
'caseManagement.featureCase.requirement': 'demand',
|
||||
'caseManagement.featureCase.bug': 'bug',
|
||||
'caseManagement.featureCase.dependency': 'dependencies',
|
||||
'caseManagement.featureCase.caseReview': 'case review',
|
||||
'caseManagement.featureCase.testPlan': 'Test plan',
|
||||
'caseManagement.featureCase.comments': 'comments',
|
||||
'caseManagement.featureCase.changeHistory': 'Change history',
|
||||
'caseManagement.featureCase.demandPlatform': 'Platform',
|
||||
'caseManagement.featureCase.pleaseEnterID': 'Please enter ID',
|
||||
'caseManagement.featureCase.requirementTitle': 'Requirement title',
|
||||
'caseManagement.featureCase.pleaseEnterTitle': 'Please enter a requirement title',
|
||||
'caseManagement.featureCase.requirementUrl': 'Demand url',
|
||||
'caseManagement.featureCase.pleaseEnterRequirementUrl': 'Please input requirements url',
|
||||
'caseManagement.featureCase.cancelAssociation': 'Cancel Association',
|
||||
'caseManagement.featureCase.caseDetailTitle': '【{id}】{name}',
|
||||
'caseManagement.featureCase.share': 'share',
|
||||
'caseManagement.featureCase.follow': 'follow',
|
||||
'caseManagement.featureCase.fullScreen': 'Full screen',
|
||||
'caseManagement.featureCase.more': 'More',
|
||||
'caseManagement.featureCase.basicInfo': 'Basic Info',
|
||||
'caseManagement.featureCase.attachment': 'attachment',
|
||||
'caseManagement.featureCase.contentEdit': 'Content Edit',
|
||||
'caseManagement.featureCase.followSuccess': 'Followed Success',
|
||||
'caseManagement.featureCase.cancelFollowSuccess': 'Cancel success',
|
||||
};
|
||||
|
|
|
@ -117,4 +117,40 @@ export default {
|
|||
'caseManagement.featureCase.mightWantTo': '你可能还想',
|
||||
'caseManagement.featureCase.createTestPlan': '创建测试计划',
|
||||
'caseManagement.featureCase.createCaseReview': '创建用例评审',
|
||||
'caseManagement.featureCase.detailDisplaySetting': '显示设置',
|
||||
'caseManagement.featureCase.addDemand': '添加需求',
|
||||
'caseManagement.featureCase.updateDemand': '更新需求 ({name})',
|
||||
'caseManagement.featureCase.updateUser': '处理人',
|
||||
'caseManagement.featureCase.showSetting': '显示设置',
|
||||
'caseManagement.featureCase.tabShowSetting': 'tab 显示设置',
|
||||
'caseManagement.featureCase.closeModuleTab': '关闭: 在抽屉内不展示相关模块',
|
||||
'caseManagement.featureCase.enableModuleTab': '开启: 在抽屉内展示相关模块',
|
||||
'caseManagement.featureCase.recoverDefault': '恢复默认',
|
||||
'caseManagement.featureCase.detail': '详情',
|
||||
'caseManagement.featureCase.nonClosableTab': '以上属性不可关闭',
|
||||
'caseManagement.featureCase.case': '用例',
|
||||
'caseManagement.featureCase.requirement': '需求',
|
||||
'caseManagement.featureCase.bug': '缺陷',
|
||||
'caseManagement.featureCase.dependency': '依赖关系',
|
||||
'caseManagement.featureCase.caseReview': '用例评审',
|
||||
'caseManagement.featureCase.testPlan': '测试计划',
|
||||
'caseManagement.featureCase.comments': '评论',
|
||||
'caseManagement.featureCase.changeHistory': '变更历史',
|
||||
'caseManagement.featureCase.demandPlatform': '平台',
|
||||
'caseManagement.featureCase.pleaseEnterID': '请输入ID',
|
||||
'caseManagement.featureCase.requirementTitle': '需求标题',
|
||||
'caseManagement.featureCase.pleaseEnterTitle': '请输入需求标题',
|
||||
'caseManagement.featureCase.requirementUrl': '需求地址',
|
||||
'caseManagement.featureCase.pleaseEnterRequirementUrl': '请输入需求地址',
|
||||
'caseManagement.featureCase.cancelAssociation': '取消关联',
|
||||
'caseManagement.featureCase.caseDetailTitle': '【{id}】{name}',
|
||||
'caseManagement.featureCase.share': '分享',
|
||||
'caseManagement.featureCase.follow': '关注',
|
||||
'caseManagement.featureCase.fullScreen': '全屏',
|
||||
'caseManagement.featureCase.more': '更多',
|
||||
'caseManagement.featureCase.basicInfo': '基本信息',
|
||||
'caseManagement.featureCase.attachment': '附件',
|
||||
'caseManagement.featureCase.contentEdit': '内容编辑',
|
||||
'caseManagement.featureCase.followSuccess': '关注成功',
|
||||
'caseManagement.featureCase.cancelFollowSuccess': '取消成功',
|
||||
};
|
||||
|
|
|
@ -94,10 +94,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-empty class="mt-20">
|
||||
暂无数据
|
||||
<span class="cursor-pointer text-[rgb(var(--primary-5))]" @click="goPluginManagement">跳转至插件管理</span>
|
||||
</a-empty>
|
||||
<a-empty v-if="filterList.length < 1" class="mt-20"> </a-empty>
|
||||
</a-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue