feat(测试计划): 报告-报告总结-单击聚焦编辑&保存提示

This commit is contained in:
teukkk 2024-08-05 17:34:58 +08:00 committed by 刘瑞斌
parent 366b00706e
commit 85bd5949f8
6 changed files with 59 additions and 50 deletions

View File

@ -449,6 +449,10 @@
editor.value?.destroy(); editor.value?.destroy();
}); });
function focus() {
editor.value?.chain().focus();
}
const contentStyles = computed(() => { const contentStyles = computed(() => {
return { return {
maxHeight: props.autoHeight ? '800px' : props.maxHeight || '260px', maxHeight: props.autoHeight ? '800px' : props.maxHeight || '260px',
@ -514,6 +518,10 @@
console.log(error); console.log(error);
} }
} }
defineExpose({
focus,
});
</script> </script>
<template> <template>

View File

@ -160,6 +160,7 @@ export default {
'common.nameNotNull': '名称不能为空', 'common.nameNotNull': '名称不能为空',
'common.namePlaceholder': '请输入名称,按回车键保存', 'common.namePlaceholder': '请输入名称,按回车键保存',
'common.unsavedLeave': '有标签页的内容未保存,离开后未保存的内容将丢失,确定要离开吗?', 'common.unsavedLeave': '有标签页的内容未保存,离开后未保存的内容将丢失,确定要离开吗?',
'common.editUnsavedLeave': '编辑内容未保存,离开后未保存的内容将丢失,确定要离开吗?',
'common.image': '图片', 'common.image': '图片',
'common.text': '文本', 'common.text': '文本',
'common.resourceDeleted': '资源已被删除', 'common.resourceDeleted': '资源已被删除',

View File

@ -96,6 +96,7 @@
</div> </div>
<div class="config-right-container"> <div class="config-right-container">
<ViewReport <ViewReport
ref="viewReportRef"
v-model:card-list="cardItemList" v-model:card-list="cardItemList"
:detail-info="props.detailInfo" :detail-info="props.detailInfo"
:is-drawer="props.isDrawer" :is-drawer="props.isDrawer"
@ -363,6 +364,7 @@
} }
const confirmLoading = ref<boolean>(false); const confirmLoading = ref<boolean>(false);
const viewReportRef = ref<InstanceType<typeof ViewReport>>();
// //
async function handleSave() { async function handleSave() {
if (!reportForm.value.reportName) { if (!reportForm.value.reportName) {
@ -374,6 +376,7 @@
try { try {
const params: manualReportGenParams = makeParams(); const params: manualReportGenParams = makeParams();
const reportId = await manualReportGen(params); const reportId = await manualReportGen(params);
viewReportRef.value?.setIsSave(true);
Message.success(t('report.detail.manualGenReportSuccess')); Message.success(t('report.detail.manualGenReportSuccess'));
if (reportId) { if (reportId) {
router.push({ router.push({

View File

@ -14,17 +14,20 @@
:placeholder="t('report.detail.customTitlePlaceHolder')" :placeholder="t('report.detail.customTitlePlaceHolder')"
:max-length="255" :max-length="255"
allow-clear allow-clear
@change="() => emit('handleSetSave')"
@blur="blurHandler" @blur="blurHandler"
/> />
<div :class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`"> <div :class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`">
<MsRichText <MsRichText
ref="msRichTextRef"
v-model:raw="innerTextForm.content" v-model:raw="innerTextForm.content"
v-model:filedIds="innerTextForm.richTextTmpFileIds" v-model:filedIds="innerTextForm.richTextTmpFileIds"
:upload-image="handleUploadImage" :upload-image="handleUploadImage"
:preview-url="ReportPlanPreviewImageUrl" :preview-url="ReportPlanPreviewImageUrl"
class="mt-[8px] w-full" class="mt-[8px] w-full"
:editable="props.canEdit" :editable="props.canEdit"
@click="handleClick" @click="handleRichClick"
@update="() => emit('handleSetSave')"
/> />
</div> </div>
<div <div
@ -38,12 +41,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue'; import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import { editorUploadFile } from '@/api/modules/test-plan/report'; import { editorUploadFile } from '@/api/modules/test-plan/report';
import { ReportPlanPreviewImageUrl } from '@/api/requrls/test-plan/report'; import { ReportPlanPreviewImageUrl } from '@/api/requrls/test-plan/report';
import useDoubleClick from '@/hooks/useDoubleClick';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
@ -60,7 +63,8 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'updateCustom', formValue: customValueForm): void; (e: 'updateCustom', formValue: customValueForm): void;
(e: 'dblclick'): void; (e: 'handleClick'): void;
(e: 'handleSetSave'): void;
(e: 'cancel'): void; (e: 'cancel'): void;
}>(); }>();
@ -87,9 +91,11 @@
label: innerTextForm.value.label || t('report.detail.customDefaultCardName'), label: innerTextForm.value.label || t('report.detail.customDefaultCardName'),
}); });
} }
function emitDoubleClick() { const msRichTextRef = ref<InstanceType<typeof MsRichText>>();
function handleRichClick() {
if (!props.shareId) { if (!props.shareId) {
emit('dblclick'); msRichTextRef.value?.focus();
emit('handleClick');
} }
} }
@ -100,8 +106,6 @@
}); });
} }
const { handleClick } = useDoubleClick(emitDoubleClick);
function handleCancel() { function handleCancel() {
emit('cancel'); emit('cancel');
} }

View File

@ -1,22 +1,18 @@
<template> <template>
<div :class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`"> <div :class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`">
<MsRichText <MsRichText
ref="msRichTextRef"
v-model:raw="innerSummary.content" v-model:raw="innerSummary.content"
v-model:filedIds="innerSummary.richTextTmpFileIds" v-model:filedIds="innerSummary.richTextTmpFileIds"
:upload-image="handleUploadImage" :upload-image="handleUploadImage"
:preview-url="ReportPlanPreviewImageUrl" :preview-url="ReportPlanPreviewImageUrl"
class="mt-[8px] w-full" class="mt-[8px] w-full"
:editable="props.canEdit" :editable="props.canEdit"
@click="handleClick" @click="handleRichClick"
@update="emit('handleSetSave')"
/> />
<MsFormItemSub <MsFormItemSub
v-if=" v-if="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId && props.canEdit && props.isPreview"
hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) &&
!shareId &&
props.showButton &&
props.canEdit &&
props.isPreview
"
:text="t('report.detail.oneClickSummary')" :text="t('report.detail.oneClickSummary')"
:show-fill-icon="true" :show-fill-icon="true"
@fill="handleSummary" @fill="handleSummary"
@ -24,7 +20,7 @@
</div> </div>
<div <div
v-show="props.showButton && hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId && props.canEdit" v-show="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId && props.canEdit"
class="mt-[16px] flex items-center gap-[12px]" class="mt-[16px] flex items-center gap-[12px]"
> >
<a-button type="primary" @click="handleUpdateReportDetail">{{ t('common.save') }}</a-button> <a-button type="primary" @click="handleUpdateReportDetail">{{ t('common.save') }}</a-button>
@ -33,7 +29,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue'; import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
@ -41,7 +36,6 @@
import { editorUploadFile } from '@/api/modules/test-plan/report'; import { editorUploadFile } from '@/api/modules/test-plan/report';
import { ReportPlanPreviewImageUrl } from '@/api/requrls/test-plan/report'; import { ReportPlanPreviewImageUrl } from '@/api/requrls/test-plan/report';
import useDoubleClick from '@/hooks/useDoubleClick';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { hasAnyPermission } from '@/utils/permission'; import { hasAnyPermission } from '@/utils/permission';
@ -53,7 +47,6 @@
const props = defineProps<{ const props = defineProps<{
richText: customValueForm; richText: customValueForm;
shareId?: string; shareId?: string;
showButton: boolean;
isPlanGroup: boolean; isPlanGroup: boolean;
detail: PlanReportDetail; detail: PlanReportDetail;
canEdit: boolean; canEdit: boolean;
@ -63,7 +56,8 @@
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'updateSummary', form: customValueForm): void; (e: 'updateSummary', form: customValueForm): void;
(e: 'cancel'): void; (e: 'cancel'): void;
(e: 'dblclick'): void; (e: 'handleClick'): void;
(e: 'handleSetSave'): void;
(e: 'handleSummary', content: string): void; (e: 'handleSummary', content: string): void;
}>(); }>();
@ -134,12 +128,13 @@
emit('handleSummary', summaryContent.value); emit('handleSummary', summaryContent.value);
} }
function emitDoubleClick() { const msRichTextRef = ref<InstanceType<typeof MsRichText>>();
function handleRichClick() {
if (!props.shareId) { if (!props.shareId) {
emit('dblclick'); msRichTextRef.value?.focus();
emit('handleClick');
} }
} }
const { handleClick } = useDoubleClick(emitDoubleClick);
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -124,13 +124,13 @@
</SystemTrigger> </SystemTrigger>
</div> </div>
<div :class="`${props.isPreview ? 'mt-[16px]' : 'mt-[24px]'} drag-container`"> <div class="drag-container mt-[16px]">
<VueDraggable v-model="innerCardList" :disabled="props.isPreview" group="report"> <VueDraggable v-model="innerCardList" :disabled="props.isPreview" group="report">
<div <div
v-for="(item, index) of innerCardList" v-for="(item, index) of innerCardList"
v-show="showItem(item)" v-show="showItem(item)"
:key="item.id" :key="item.id"
:class="`${props.isPreview ? 'mt-[16px]' : 'hover-card mt-[24px]'} card-item`" :class="`${props.isPreview ? '' : 'hover-card'} card-item mt-[16px]`"
> >
<div v-if="!props.isPreview" class="action"> <div v-if="!props.isPreview" class="action">
<div class="actionList"> <div class="actionList">
@ -178,13 +178,13 @@
:share-id="shareId" :share-id="shareId"
:is-preview="props.isPreview" :is-preview="props.isPreview"
:can-edit="item.enableEdit" :can-edit="item.enableEdit"
:show-button="showButton"
:is-plan-group="props.isGroup" :is-plan-group="props.isGroup"
:detail="detail" :detail="detail"
@update-summary="(formValue:customValueForm) => updateCustom(formValue, item)" @update-summary="(formValue:customValueForm) => updateCustom(formValue, item)"
@cancel="() => handleCancelCustom(item)" @cancel="() => handleCancelCustom(item)"
@handle-summary="(value:string) => handleSummary(value,item)" @handle-summary="(value:string) => handleSummary(value,item)"
@dblclick="handleDoubleClick(item)" @handle-click="handleClick(item)"
@handle-set-save="setIsSave(false)"
/> />
<BugTable <BugTable
v-else-if="item.value === ReportCardTypeEnum.BUG_DETAIL" v-else-if="item.value === ReportCardTypeEnum.BUG_DETAIL"
@ -222,8 +222,9 @@
richTextTmpFileIds: [], richTextTmpFileIds: [],
}" }"
@update-custom="(formValue:customValueForm)=>updateCustom(formValue,item)" @update-custom="(formValue:customValueForm)=>updateCustom(formValue,item)"
@dblclick="handleDoubleClick(item)" @handle-click="handleClick(item)"
@cancel="() => handleCancelCustom(item)" @cancel="() => handleCancelCustom(item)"
@handle-set-save="setIsSave(false)"
/> />
</MsCard> </MsCard>
</div> </div>
@ -234,7 +235,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useEventListener } from '@vueuse/core';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
@ -256,6 +256,7 @@
import { getReportLayout, updateReportDetail } from '@/api/modules/test-plan/report'; import { getReportLayout, updateReportDetail } from '@/api/modules/test-plan/report';
import { commonConfig, defaultCount, defaultReportDetail, seriesConfig, statusConfig } from '@/config/testPlan'; import { commonConfig, defaultCount, defaultReportDetail, seriesConfig, statusConfig } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useLeaveUnSaveTip from '@/hooks/useLeaveUnSaveTip';
import { addCommasToNumber } from '@/utils'; import { addCommasToNumber } from '@/utils';
import { UpdateReportDetailParams } from '@/models/testPlan/report'; import { UpdateReportDetailParams } from '@/models/testPlan/report';
@ -287,12 +288,17 @@
(e: 'updateCustom', item: configItem): void; (e: 'updateCustom', item: configItem): void;
}>(); }>();
const { setIsSave } = useLeaveUnSaveTip({
leaveTitle: 'common.tip',
leaveContent: 'common.editUnsavedLeave',
tipType: 'warning',
});
const innerCardList = defineModel<configItem[]>('cardList', { const innerCardList = defineModel<configItem[]>('cardList', {
default: [], default: [],
}); });
const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) }); const detail = ref<PlanReportDetail>({ ...cloneDeep(defaultReportDetail) });
const showButton = ref<boolean>(false);
const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({ const richText = ref<{ summary: string; richTextTmpFileIds?: string[] }>({
summary: '', summary: '',
@ -513,23 +519,16 @@
}; };
} }
onMounted(async () => {
nextTick(() => {
const editorContent = document.querySelector('.editor-content');
useEventListener(editorContent, 'click', () => {
showButton.value = true;
});
});
});
function handleCancelCustom(cardItem: configItem) { function handleCancelCustom(cardItem: configItem) {
const originItem = originLayoutInfo.value.find((item: configItem) => item.id === cardItem.id); const originItem = originLayoutInfo.value.find((item: configItem) => item.id === cardItem.id);
const index = originLayoutInfo.value.findIndex((e: configItem) => e.id === cardItem.id); const index = originLayoutInfo.value.findIndex((e: configItem) => e.id === cardItem.id);
if (originItem && index !== -1) { if (originItem && index !== -1) {
innerCardList.value.splice(index, 1, originItem); innerCardList.value.splice(index, 1, originItem);
} }
showButton.value = false;
cardItem.enableEdit = false; cardItem.enableEdit = false;
if (props.isPreview) {
setIsSave(true);
}
if (isDefaultLayout.value) { if (isDefaultLayout.value) {
cardItem.content = detail.value.summary; cardItem.content = detail.value.summary;
richText.value.summary = detail.value.summary; richText.value.summary = detail.value.summary;
@ -575,10 +574,12 @@
innerCardList.value.splice(moveIndex, 1); innerCardList.value.splice(moveIndex, 1);
innerCardList.value.splice(moveIndex + 1, 0, cardItem); innerCardList.value.splice(moveIndex + 1, 0, cardItem);
} }
setIsSave(false);
} }
// //
const deleteCard = (cardItem: configItem) => { const deleteCard = (cardItem: configItem) => {
innerCardList.value = innerCardList.value.filter((item) => item.id !== cardItem.id); innerCardList.value = innerCardList.value.filter((item) => item.id !== cardItem.id);
setIsSave(false);
}; };
// //
@ -588,11 +589,8 @@
} }
} }
function handleDoubleClick(cardItem: configItem) { function handleClick(cardItem: configItem) {
if (cardItem.value === ReportCardTypeEnum.SUMMARY) { cardItem.enableEdit = true;
showButton.value = true;
}
cardItem.enableEdit = !cardItem.enableEdit;
} }
async function handleUpdateReportDetail(currentItem: configItem) { async function handleUpdateReportDetail(currentItem: configItem) {
@ -605,11 +603,8 @@
}; };
await updateReportDetail(params); await updateReportDetail(params);
Message.success(t('common.updateSuccess')); Message.success(t('common.updateSuccess'));
if (currentItem.value === ReportCardTypeEnum.SUMMARY) { setIsSave(true);
showButton.value = false; currentItem.enableEdit = false;
} else {
currentItem.enableEdit = !currentItem.enableEdit;
}
emit('updateSuccess'); emit('updateSuccess');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@ -638,6 +633,9 @@
handleUpdateReportDetail(newCurrentItem); handleUpdateReportDetail(newCurrentItem);
} }
} }
defineExpose({
setIsSave,
});
</script> </script>
<style scoped lang="less"> <style scoped lang="less">