feat(用例评审): 用例评审Done

This commit is contained in:
baiqi 2024-02-02 15:26:48 +08:00 committed by Craftsman
parent f83f94bac2
commit 067e7dc689
6 changed files with 174 additions and 162 deletions

View File

@ -145,25 +145,21 @@
const cardOverHeight = computed(() => {
if (isFullScreen.value) {
if (props.simple) {
//
return props.noContentPadding ? 0 : 48;
}
if (props.hideFooter) {
//
return props.noContentPadding ? 88 : 118;
return 62;
}
return 246;
return 142;
}
if (props.simple) {
//
return props.noContentPadding ? 88 + _specialHeight : 136 + _specialHeight;
return props.noContentPadding ? 76 + _specialHeight : 124 + _specialHeight;
}
if (props.hideFooter) {
//
return props.noContentPadding ? 152 + _specialHeight : 192 + _specialHeight;
return props.noContentPadding ? 140 + _specialHeight : 180 + _specialHeight;
}
return 246 + _specialHeight;
return 234 + _specialHeight;
});
const getComputedContentStyle = computed(() => {

View File

@ -517,6 +517,8 @@
:deep(.halo-rich-text-editor) {
padding: 16px 24px !important;
.editor-header {
.ms-scroll-bar();
justify-content: start !important;
}
p:first-child {

View File

@ -48,7 +48,7 @@
<template v-else>
<div class="ms-upload-main-text w-full">
<a-tooltip :content="fileList[0]?.name">
<span class="one-line-text w-[80%]"> {{ fileList[0]?.name }}</span>
<span class="one-line-text w-[80%] text-center"> {{ fileList[0]?.name }}</span>
</a-tooltip>
</div>
<div class="ms-upload-sub-text">{{ formatFileSize(fileList[0]?.file?.size || 0) }}</div>

View File

@ -166,14 +166,14 @@
</div>
<caseTabDemand
ref="caseDemandRef"
:fun-params="{ projectId: appStore.currentProjectId, caseId: route.query.caseId as string, keyword: demandKeyword }"
:fun-params="{ projectId: appStore.currentProjectId, caseId: activeCaseId, keyword: demandKeyword }"
:show-empty="false"
/>
</div>
<div v-else class="flex h-full flex-col overflow-hidden">
<div v-else class="flex flex-1 flex-col overflow-hidden">
<div class="review-history-list">
<a-spin :loading="reviewHistoryListLoading" class="h-full w-full">
<div v-for="item of reviewHistoryList" :key="item.id" class="mb-[16px]">
<div v-for="item of reviewHistoryList" :key="item.id" class="review-history-list-item">
<div class="flex items-center">
<MSAvatar :avatar="item.userLogo" />
<div class="ml-[8px] flex items-center">
@ -208,7 +208,7 @@
</div>
</div>
<div class="content-footer">
<div class="mb-[16px] flex items-center">
<div class="mb-[12px] flex items-center">
<div class="font-medium text-[var(--color-text-1)]">
{{ t('caseManagement.caseReview.startReview') }}
</div>
@ -225,10 +225,12 @@
/>
</a-tooltip>
</div>
<reviewForm ref="dialogFormRef" />
<a-button type="primary" class="mt-[16px]" :loading="submitReviewLoading" @click="submitReview">
{{ t('caseManagement.caseReview.submitReview') }}
</a-button>
<reviewForm
:review-id="reviewId"
:case-id="activeCaseId"
:review-pass-rule="reviewDetail.reviewPassRule"
@done="reviewDone"
/>
</div>
</a-spin>
</div>
@ -250,7 +252,6 @@
* @description 功能测试-用例评审-用例详情
*/
import { useRoute, useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
@ -260,7 +261,6 @@
import MsEmpty from '@/components/pure/ms-empty/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsPagination from '@/components/pure/ms-pagination/index';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
import caseTemplateDetail from '../caseManagementFeature/components/caseTemplateDetail.vue';
@ -272,7 +272,6 @@
getCaseReviewHistoryList,
getReviewDetail,
getReviewDetailCasePage,
saveCaseReviewResult,
} from '@/api/modules/case-management/caseReview';
import { getCaseDetail } from '@/api/modules/case-management/featureCase';
import { reviewDefaultDetail, reviewResultMap } from '@/config/caseManagement';
@ -283,21 +282,20 @@
import type { DetailCase } from '@/models/caseManagement/featureCase';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { Instance } from 'tippy.js';
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const { t } = useI18n();
const reviewDetail = ref<ReviewItem>({ ...reviewDefaultDetail });
const reviewId = ref(route.query.id as string);
const loading = ref(false);
//
async function initDetail() {
try {
loading.value = true;
const res = await getReviewDetail(route.query.id as string);
const res = await getReviewDetail(reviewId.value);
reviewDetail.value = res;
} catch (error) {
// eslint-disable-next-line no-console
@ -333,7 +331,7 @@
caseListLoading.value = true;
const res = await getReviewDetailCasePage({
projectId: appStore.currentProjectId,
reviewId: route.query.id as string,
reviewId: reviewId.value,
viewFlag: onlyMine.value,
keyword: keyword.value,
current: pageNation.value.current,
@ -470,7 +468,7 @@
async function initReviewHistoryList() {
try {
reviewHistoryListLoading.value = true;
const res = await getCaseReviewHistoryList(route.query.id as string, activeCaseId.value);
const res = await getCaseReviewHistoryList(reviewId.value, activeCaseId.value);
reviewHistoryList.value = res;
} catch (error) {
// eslint-disable-next-line no-console
@ -489,7 +487,6 @@
);
const autoNext = ref(false);
const dialogFormRef = ref<InstanceType<typeof reviewForm>>();
const demandKeyword = ref('');
const caseDemandRef = ref<InstanceType<typeof caseTabDemand>>();
@ -505,28 +502,7 @@
);
}
const submitReviewLoading = ref(false);
//
function submitReview() {
dialogFormRef.value?.validateForm(async (caseResultForm: Record<string, any>) => {
try {
submitReviewLoading.value = true;
const params = {
projectId: appStore.currentProjectId,
caseId: activeCaseId.value,
reviewId: route.query.id as string,
status: caseResultForm.value.result,
reviewPassRule: reviewDetail.value.reviewPassRule,
content: caseResultForm.value.reason,
notifier: '', // TODO:
};
await saveCaseReviewResult(params);
Message.success(t('caseManagement.caseReview.reviewSuccess'));
caseResultForm.value = {
result: 'PASS' as ReviewResult,
reason: '',
fileList: [] as MsFileItem[],
};
function reviewDone() {
if (autoNext.value) {
// id
const index = caseList.value.findIndex((e) => e.caseId === activeCaseId.value);
@ -543,13 +519,6 @@
initReviewHistoryList();
}
loadCaseList();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
submitReviewLoading.value = false;
}
});
}
const editCaseVisible = ref(false);
@ -596,7 +565,7 @@
moduleIds,
};
} else {
keyword.value = route.query.caseId as string;
keyword.value = route.query.reviewId as string;
}
initDetail();
loadCaseList();
@ -654,7 +623,12 @@
.review-history-list {
@apply h-full;
padding: 16px 0 16px 16px;
padding: 16px 0 0 16px;
.review-history-list-item {
&:not(:last-child) {
margin-bottom: 16px;
}
}
}
}
.content-footer {

View File

@ -158,34 +158,13 @@
class="mb-0"
>
<div class="flex w-full items-center">
<a-mention
v-model:model-value="dialogForm.reason"
type="textarea"
:auto-size="{ minRows: 1 }"
:max-length="1000"
allow-clear
class="flex flex-1 items-center"
<MsRichText
v-model:raw="dialogForm.reason"
v-model:commentIds="dialogForm.commentIds"
:upload-image="handleUploadImage"
class="w-full"
/>
<MsUpload
v-model:file-list="dialogForm.fileList"
accept="image"
size-unit="MB"
:auto-upload="false"
multiple
:limit="10"
:disabled="dialogForm.fileList.length >= 10"
>
<a-button type="outline" class="ml-[8px] p-[8px_6px]" :disabled="dialogForm.fileList.length >= 10">
<icon-file-image :size="18" />
</a-button>
</MsUpload>
</div>
<MsFileList
v-model:file-list="dialogForm.fileList"
show-mode="imageList"
:show-tab="false"
class="mt-[8px]"
/>
</a-form-item>
<a-form-item
v-if="dialogShowType === 'changeReviewer'"
@ -231,9 +210,10 @@
type="primary"
class="ml-[12px]"
:loading="dialogLoading"
:disabled="submitReviewDisabled"
@click="commitResult"
>
{{ t('caseManagement.caseReview.commitResult') }}
{{ t('caseManagement.caseReview.submitReview') }}
</a-button>
<a-button
v-if="dialogShowType === 'changeReviewer'"
@ -268,11 +248,10 @@
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsPopconfirm from '@/components/pure/ms-popconfirm/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
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 MsFileList from '@/components/pure/ms-upload/fileList.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import MsSelect from '@/components/business/ms-select';
@ -284,6 +263,7 @@
getReviewDetailCasePage,
getReviewUsers,
} from '@/api/modules/case-management/caseReview';
import { editorUploadFile } from '@/api/modules/case-management/featureCase';
import { getProjectMemberCommentOptions } from '@/api/modules/project-management/projectMember';
import { reviewResultMap } from '@/config/caseManagement';
import { useI18n } from '@/hooks/useI18n';
@ -420,14 +400,14 @@
...filter.combine,
}
: {},
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
total: propsRes.value.msPagination?.total,
};
setLoadListParams(tableParams.value);
loadList();
emit('init', {
...tableParams.value,
current: propsRes.value.msPagination?.current,
pageSize: propsRes.value.msPagination?.pageSize,
total: propsRes.value.msPagination?.total,
moduleIds: [],
});
}
@ -472,6 +452,7 @@
reviewer: [] as string[],
isAppend: false,
fileList: [] as MsFileItem[],
commentIds: [] as string[],
};
const dialogForm = ref({ ...defaultDialogForm });
const dialogFormRef = ref<FormInstance>();
@ -649,13 +630,13 @@
reviewPassRule: props.reviewPassRule,
status: dialogForm.value.result as ReviewResult,
content: dialogForm.value.reason,
notifier: '', // TODO:
notifier: dialogForm.value.commentIds.join(';'),
selectIds: batchParams.value.selectIds,
selectAll: batchParams.value.selectAll,
excludeIds: batchParams.value.excludeIds,
condition: batchParams.value.condition,
});
Message.success(t('common.updateSuccess'));
Message.success(t('caseManagement.caseReview.reviewSuccess'));
dialogVisible.value = false;
resetSelector();
emit('refresh');
@ -670,6 +651,19 @@
});
}
async function handleUploadImage(file: File) {
const { data } = await editorUploadFile({
fileList: [file],
});
return data;
}
const submitReviewDisabled = computed(
() =>
dialogForm.value.result !== 'PASS' &&
(dialogForm.value.reason === '' || dialogForm.value.reason.trim() === '<p style=""></p>')
);
const reviewersOptions = ref<SelectOptionData[]>([]);
const reviewerLoading = ref(false);

View File

@ -1,6 +1,6 @@
<template>
<a-form ref="dialogFormRef" :model="caseResultForm" layout="vertical">
<a-form-item field="reason" :label="t('caseManagement.caseReview.reviewResult')" class="mb-[8px]">
<a-form-item field="reason" class="mb-[4px]">
<a-radio-group v-model:model-value="caseResultForm.result" @change="() => dialogFormRef?.resetFields()">
<a-radio value="PASS">
<div class="inline-flex items-center">
@ -28,61 +28,66 @@
</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item
field="reason"
:label="t('caseManagement.caseReview.reason')"
:rules="
caseResultForm.result !== 'PASS'
? [{ required: true, message: t('caseManagement.caseReview.reasonRequired') }]
: []
"
asterisk-position="end"
class="mb-0"
>
<div class="flex w-full items-center">
<a-mention
v-model:model-value="caseResultForm.reason"
type="textarea"
:auto-size="{ minRows: 1 }"
:max-length="1000"
allow-clear
class="flex flex-1 items-center"
/>
<MsUpload
v-model:file-list="caseResultForm.fileList"
accept="image"
size-unit="MB"
:auto-upload="false"
:limit="10"
multiple
>
<a-button type="outline" class="ml-[8px] p-[8px_6px]">
<icon-file-image :size="18" />
<a-button type="secondary" class="p-[8px_6px]" size="small" @click="modalVisible = true">
<icon-plus class="mr-[4px]" />
{{ t('caseManagement.caseReview.reason') }}
</a-button>
</MsUpload>
</div>
<MsFileList
v-model:file-list="caseResultForm.fileList"
show-mode="imageList"
:show-tab="false"
class="mt-[8px]"
/>
</a-form-item>
</a-form>
<a-button
type="primary"
class="mt-[12px]"
:disabled="submitDisabled"
:submit-review-loading="submitReviewLoading"
@click="submitReview"
>
{{ t('caseManagement.caseReview.submitReview') }}
</a-button>
<a-modal
v-model:visible="modalVisible"
:title="t('caseManagement.caseReview.reason')"
class="p-[4px]"
title-align="start"
body-class="p-0"
:width="680"
:cancel-button-props="{ disabled: submitReviewLoading }"
:ok-button-props="{ disabled: submitDisabled }"
:ok-loading="submitReviewLoading"
:ok-text="t('caseManagement.caseReview.submitReview')"
@before-ok="submitReview"
>
<MsRichText
v-model:raw="caseResultForm.reason"
v-model:commentIds="caseResultForm.commentIds"
:upload-image="handleUploadImage"
class="w-full"
/>
</a-modal>
</template>
<script setup lang="ts">
import { FormInstance } from '@arco-design/web-vue';
import { FormInstance, Message } from '@arco-design/web-vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsFileList from '@/components/pure/ms-upload/fileList.vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import { MsFileItem } from '@/components/pure/ms-upload/types';
import { saveCaseReviewResult } from '@/api/modules/case-management/caseReview';
import { editorUploadFile } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { ReviewResult } from '@/models/caseManagement/caseReview';
import { ReviewPassRule, ReviewResult } from '@/models/caseManagement/caseReview';
const props = defineProps<{
reviewId: string;
caseId: string;
reviewPassRule: ReviewPassRule;
}>();
const emit = defineEmits(['done']);
const appStore = useAppStore();
const { t } = useI18n();
const dialogFormRef = ref<FormInstance>();
@ -90,23 +95,64 @@
result: 'PASS' as ReviewResult,
reason: '',
fileList: [] as MsFileItem[],
commentIds: [] as string[],
});
const submitReviewLoading = ref(false);
const submitDisabled = computed(
() =>
caseResultForm.value.result !== 'PASS' &&
(caseResultForm.value.reason === '' || caseResultForm.value.reason.trim() === '<p style=""></p>')
);
const modalVisible = ref(false);
function validateForm(cb: (form: Record<string, any>) => void) {
dialogFormRef.value?.validate((errors) => {
if (!errors && typeof cb === 'function') {
cb(caseResultForm.value);
async function handleUploadImage(file: File) {
const { data } = await editorUploadFile({
fileList: [file],
});
return data;
}
//
function submitReview() {
dialogFormRef.value?.validate(async (errors) => {
if (!errors) {
try {
submitReviewLoading.value = true;
const params = {
projectId: appStore.currentProjectId,
caseId: props.caseId,
reviewId: props.reviewId,
status: caseResultForm.value.result,
reviewPassRule: props.reviewPassRule,
content: caseResultForm.value.reason,
notifier: caseResultForm.value.commentIds.join(';'),
};
await saveCaseReviewResult(params);
modalVisible.value = false;
Message.success(t('caseManagement.caseReview.reviewSuccess'));
caseResultForm.value = {
result: 'PASS' as ReviewResult,
reason: '',
fileList: [] as MsFileItem[],
commentIds: [] as string[],
};
emit('done');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
submitReviewLoading.value = false;
}
}
});
}
defineExpose({
validateForm,
});
</script>
<style lang="less" scoped>
.image-preview-container {
margin-top: 8px;
:deep(.arco-form-item-label-col) {
margin-bottom: 0;
}
.arco-radio {
@apply pl-0;
}
</style>