feat(测试计划): 测试计划详情-功能用例详情-执行内容展示调整优化
This commit is contained in:
parent
a3f70d1f64
commit
ae1b6ddb02
|
@ -12,7 +12,6 @@
|
||||||
* import rehypeStringify from 'rehype-stringify';
|
* import rehypeStringify from 'rehype-stringify';
|
||||||
* return unified().use(rehypeParse).use(rehypeFormat).use(rehypeStringify).processSync(content.value);
|
* return unified().use(rehypeParse).use(rehypeFormat).use(rehypeStringify).processSync(content.value);
|
||||||
*/
|
*/
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { useDebounceFn, useVModel } from '@vueuse/core';
|
import { useDebounceFn, useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
import type { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
|
@ -71,7 +70,6 @@
|
||||||
import * as fastq from 'fastq';
|
import * as fastq from 'fastq';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const route = useRoute();
|
|
||||||
type Task = {
|
type Task = {
|
||||||
file: File;
|
file: File;
|
||||||
process: (permalink: string, fileId: string) => void;
|
process: (permalink: string, fileId: string) => void;
|
||||||
|
@ -91,6 +89,7 @@
|
||||||
previewUrl?: string;
|
previewUrl?: string;
|
||||||
editable?: boolean;
|
editable?: boolean;
|
||||||
limitLength?: number;
|
limitLength?: number;
|
||||||
|
autoFocus?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
raw: '',
|
raw: '',
|
||||||
|
@ -109,6 +108,8 @@
|
||||||
(event: 'update:filedIds', value: string[]): void;
|
(event: 'update:filedIds', value: string[]): void;
|
||||||
(event: 'update', value: string): void;
|
(event: 'update', value: string): void;
|
||||||
(event: 'update:commentIds', value: string): void;
|
(event: 'update:commentIds', value: string): void;
|
||||||
|
(event: 'blur', eveValue: FocusEvent): void;
|
||||||
|
(event: 'focus', eveValue: FocusEvent): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const imagesNodesIds = useVModel(props, 'filedIds', emit);
|
const imagesNodesIds = useVModel(props, 'filedIds', emit);
|
||||||
|
@ -149,6 +150,16 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.autoFocus,
|
||||||
|
(val) => {
|
||||||
|
editor.value?.setOptions({ autofocus: val });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const attachmentSelectorModal = ref(false);
|
const attachmentSelectorModal = ref(false);
|
||||||
const selectedImagesNode = ref<string>();
|
const selectedImagesNode = ref<string>();
|
||||||
const selectedCommentNode = ref<string>();
|
const selectedCommentNode = ref<string>();
|
||||||
|
@ -385,11 +396,20 @@
|
||||||
limit: props.limitLength || null,
|
limit: props.limitLength || null,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
autofocus: false,
|
autofocus: props.autoFocus,
|
||||||
editable: props.editable,
|
editable: props.editable,
|
||||||
onUpdate: () => {
|
onUpdate: () => {
|
||||||
debounceOnUpdate();
|
debounceOnUpdate();
|
||||||
},
|
},
|
||||||
|
onBlur: ({ event }) => {
|
||||||
|
// 避免移动到菜单上触发失去焦点切换视图
|
||||||
|
if (!event.relatedTarget) {
|
||||||
|
emit('blur', event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFocus: ({ event }) => {
|
||||||
|
emit('focus', event);
|
||||||
|
},
|
||||||
editorProps: {
|
editorProps: {
|
||||||
handleDrop: (view, event: DragEvent, _, moved) => {
|
handleDrop: (view, event: DragEvent, _, moved) => {
|
||||||
if (!moved && event.dataTransfer && event.dataTransfer.files) {
|
if (!moved && event.dataTransfer && event.dataTransfer.files) {
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<a-form :model="form">
|
<a-form :model="form">
|
||||||
<a-form-item field="lastExecResult" class="mb-[8px]">
|
<a-form-item field="lastExecResult" class="mb-[8px]">
|
||||||
<a-radio-group v-model:model-value="form.lastExecResult">
|
<div class="flex w-full items-center justify-between">
|
||||||
<a-radio v-for="item in executionResultList" :key="item.key" :value="item.key">
|
<a-radio-group v-model:model-value="form.lastExecResult">
|
||||||
<ExecuteResult :execute-result="item.key" />
|
<a-radio v-for="item in executionResultList" :key="item.key" :value="item.key">
|
||||||
</a-radio>
|
<ExecuteResult :execute-result="item.key" />
|
||||||
</a-radio-group>
|
</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
<slot name="headerRight"></slot>
|
||||||
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="content" asterisk-position="end" class="mb-0">
|
<a-form-item field="content" asterisk-position="end" class="mb-0">
|
||||||
<div class="flex w-full items-center">
|
<div class="flex w-full items-center">
|
||||||
|
<a-textarea
|
||||||
|
v-if="props.isDblclickPlaceholder && !achievedForm"
|
||||||
|
v-model="form.content"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('testPlan.featureCase.richTextDblclickPlaceholder')"
|
||||||
|
:auto-size="{ minRows: 1 }"
|
||||||
|
style="resize: vertical"
|
||||||
|
:max-length="1000"
|
||||||
|
@click="achievedForm = true"
|
||||||
|
/>
|
||||||
<MsRichText
|
<MsRichText
|
||||||
|
v-if="!props.isDblclickPlaceholder || achievedForm"
|
||||||
v-model:raw="form.content"
|
v-model:raw="form.content"
|
||||||
v-model:commentIds="form.commentIds"
|
v-model:commentIds="form.commentIds"
|
||||||
v-model:filedIds="form.planCommentFileIds"
|
v-model:filedIds="form.planCommentFileIds"
|
||||||
|
@ -17,12 +31,14 @@
|
||||||
:preview-url="`${PreviewEditorImageUrl}/${appStore.currentProjectId}`"
|
:preview-url="`${PreviewEditorImageUrl}/${appStore.currentProjectId}`"
|
||||||
:auto-height="false"
|
:auto-height="false"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
|
:auto-focus="true"
|
||||||
:max-height="props.richTextMaxHeight"
|
:max-height="props.richTextMaxHeight"
|
||||||
:placeholder="
|
:placeholder="
|
||||||
props.isDblclickPlaceholder
|
props.isDblclickPlaceholder
|
||||||
? t('testPlan.featureCase.richTextDblclickPlaceholder')
|
? t('testPlan.featureCase.richTextDblclickPlaceholder')
|
||||||
: t('editor.placeholder')
|
: t('editor.placeholder')
|
||||||
"
|
"
|
||||||
|
@blur="blurHandler"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -48,10 +64,18 @@
|
||||||
richTextMaxHeight?: string;
|
richTextMaxHeight?: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'dblclick'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const form = defineModel<ExecuteFeatureCaseFormParams>('form', {
|
const form = defineModel<ExecuteFeatureCaseFormParams>('form', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const achievedForm = defineModel<boolean>('achieved', {
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
|
@ -65,6 +89,21 @@
|
||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function blurHandler() {
|
||||||
|
if (props.isDblclickPlaceholder && !form.value.content) {
|
||||||
|
achievedForm.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => achievedForm.value,
|
||||||
|
(val) => {
|
||||||
|
if (val && props.isDblclickPlaceholder) {
|
||||||
|
emit('dblclick');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<ExecuteForm v-model:form="form" is-dblclick-placeholder class="execute-form" />
|
<ExecuteForm
|
||||||
|
v-model:achieved="achievedForm"
|
||||||
|
v-model:form="form"
|
||||||
|
is-dblclick-placeholder
|
||||||
|
class="execute-form"
|
||||||
|
@dblclick="dblclickHandler"
|
||||||
|
>
|
||||||
|
<template #headerRight>
|
||||||
|
<slot name="headerRight"></slot>
|
||||||
|
</template>
|
||||||
|
</ExecuteForm>
|
||||||
<a-button type="primary" class="mt-[12px]" :loading="submitLoading" @click="() => submit()">
|
<a-button type="primary" class="mt-[12px]" :loading="submitLoading" @click="() => submit()">
|
||||||
{{ t('caseManagement.caseReview.commitResult') }}
|
{{ t('caseManagement.caseReview.commitResult') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
@ -59,9 +69,8 @@
|
||||||
|
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
|
|
||||||
// 双击富文本内容打开弹窗
|
// 双击富文本内容打开弹窗
|
||||||
onMounted(() => {
|
function dblclickHandler() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
const editorContent = document.querySelector('.execute-form')?.querySelector('.editor-content');
|
const editorContent = document.querySelector('.execute-form')?.querySelector('.editor-content');
|
||||||
useEventListener(editorContent, 'dblclick', () => {
|
useEventListener(editorContent, 'dblclick', () => {
|
||||||
|
@ -69,7 +78,7 @@
|
||||||
dialogForm.value = cloneDeep(form.value);
|
dialogForm.value = cloneDeep(form.value);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.stepExecutionResult,
|
() => props.stepExecutionResult,
|
||||||
|
@ -93,12 +102,15 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const achievedForm = ref<boolean>(false);
|
||||||
|
|
||||||
function cancel(e: Event) {
|
function cancel(e: Event) {
|
||||||
// 点击取消/关闭,弹窗关闭,富文本内容都清空;点击空白处,弹窗关闭,将弹窗内容填入下面富文本内容里
|
// 点击取消/关闭,弹窗关闭,富文本内容都清空;点击空白处,弹窗关闭,将弹窗内容填入下面富文本内容里
|
||||||
if (!(e.target as any)?.classList.contains('arco-modal-wrapper')) {
|
if (!(e.target as any)?.classList.contains('arco-modal-wrapper')) {
|
||||||
dialogForm.value = { ...defaultExecuteForm };
|
dialogForm.value = { ...defaultExecuteForm };
|
||||||
}
|
}
|
||||||
form.value = cloneDeep(dialogForm.value);
|
form.value = cloneDeep(dialogForm.value);
|
||||||
|
achievedForm.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交执行
|
// 提交执行
|
||||||
|
@ -128,6 +140,7 @@
|
||||||
modalVisible.value = false;
|
modalVisible.value = false;
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
form.value = { ...defaultExecuteForm };
|
form.value = { ...defaultExecuteForm };
|
||||||
|
achievedForm.value = false;
|
||||||
emit('done', params.lastExecResult, params.content ?? '');
|
emit('done', params.lastExecResult, params.content ?? '');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|
|
@ -116,83 +116,83 @@
|
||||||
v-if="canEdit && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
v-if="canEdit && hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
||||||
class="z-[101] px-[16px] py-[8px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
class="z-[101] px-[16px] py-[8px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"
|
||||||
>
|
>
|
||||||
<div class="mb-[12px] flex items-center justify-between">
|
|
||||||
<div class="font-medium text-[var(--color-text-1)]">
|
|
||||||
{{ t('testPlan.featureCase.startExecution') }}
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<a-switch v-model:model-value="autoNext" size="small" />
|
|
||||||
<div class="mx-[8px]">{{ t('caseManagement.caseReview.autoNext') }}</div>
|
|
||||||
<a-tooltip position="top">
|
|
||||||
<template #content>
|
|
||||||
<div>{{ t('testPlan.featureCase.autoNextTip1') }}</div>
|
|
||||||
<div>{{ t('testPlan.featureCase.autoNextTip2') }}</div>
|
|
||||||
</template>
|
|
||||||
<icon-question-circle
|
|
||||||
class="text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-4))]"
|
|
||||||
size="16"
|
|
||||||
/>
|
|
||||||
</a-tooltip>
|
|
||||||
<MsTag type="danger" theme="light" size="medium" class="ml-4">
|
|
||||||
<MsIcon type="icon-icon_defect" class="!text-[14px] text-[rgb(var(--danger-6))]" size="16" />
|
|
||||||
<span class="ml-1 text-[rgb(var(--danger-6))]"> {{ t('testPlan.featureCase.bug') }}</span>
|
|
||||||
<span class="ml-1 text-[rgb(var(--danger-6))]">{{ caseDetail.bugListCount }}</span>
|
|
||||||
</MsTag>
|
|
||||||
<a-dropdown @select="handleSelect">
|
|
||||||
<a-button
|
|
||||||
v-if="hasAllPermission(['PROJECT_BUG:READ', 'PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
|
||||||
type="outline"
|
|
||||||
size="small"
|
|
||||||
class="ml-1"
|
|
||||||
>
|
|
||||||
<template #icon> <icon-plus class="text-[12px]" /> </template>
|
|
||||||
</a-button>
|
|
||||||
<template #content>
|
|
||||||
<a-doption
|
|
||||||
v-permission="['PROJECT_BUG:READ+ADD']"
|
|
||||||
:disabled="!hasAnyPermission(['PROJECT_BUG:READ+ADD'])"
|
|
||||||
value="new"
|
|
||||||
>{{ t('common.newCreate') }}</a-doption
|
|
||||||
>
|
|
||||||
<a-doption
|
|
||||||
v-if="createdBugCount > 0 && hasAnyPermission(['PROJECT_BUG:READ'])"
|
|
||||||
:disabled="!hasAnyPermission(['PROJECT_BUG:READ'])"
|
|
||||||
value="link"
|
|
||||||
>{{ t('common.associated') }}</a-doption
|
|
||||||
>
|
|
||||||
<a-popover v-else title="" position="left">
|
|
||||||
<a-doption
|
|
||||||
v-if="createdBugCount < 1 && hasAnyPermission(['PROJECT_BUG:READ'])"
|
|
||||||
:disabled="!hasAnyPermission(['PROJECT_BUG:READ'])"
|
|
||||||
value="link"
|
|
||||||
>{{ t('common.associated') }}</a-doption
|
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
<div class="flex items-center text-[14px]">
|
|
||||||
<span class="text-[var(--color-text-4)]">{{
|
|
||||||
t('testPlan.featureCase.noBugDataTooltip')
|
|
||||||
}}</span>
|
|
||||||
<MsButton
|
|
||||||
:disabled="!hasAnyPermission(['PROJECT_BUG:READ+ADD'])"
|
|
||||||
type="text"
|
|
||||||
@click="handleSelect('new')"
|
|
||||||
>
|
|
||||||
{{ t('testPlan.featureCase.noBugDataNewBug') }}
|
|
||||||
</MsButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-popover>
|
|
||||||
</template>
|
|
||||||
</a-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ExecuteSubmit
|
<ExecuteSubmit
|
||||||
:id="activeId"
|
:id="activeId"
|
||||||
:case-id="activeCaseId"
|
:case-id="activeCaseId"
|
||||||
:test-plan-id="route.query.id as string"
|
:test-plan-id="route.query.id as string"
|
||||||
:step-execution-result="stepExecutionResult"
|
:step-execution-result="stepExecutionResult"
|
||||||
@done="executeDone"
|
@done="executeDone"
|
||||||
/>
|
>
|
||||||
|
<template #headerRight>
|
||||||
|
<div class="mb-[12px] flex items-center justify-between">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a-switch v-model:model-value="autoNext" size="small" />
|
||||||
|
<div class="mx-[8px]">{{ t('caseManagement.caseReview.autoNext') }}</div>
|
||||||
|
<a-tooltip position="top">
|
||||||
|
<template #content>
|
||||||
|
<div>{{ t('testPlan.featureCase.autoNextTip1') }}</div>
|
||||||
|
<div>{{ t('testPlan.featureCase.autoNextTip2') }}</div>
|
||||||
|
</template>
|
||||||
|
<icon-question-circle
|
||||||
|
class="text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-4))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<MsTag type="danger" theme="light" size="medium" class="ml-4">
|
||||||
|
<MsIcon type="icon-icon_defect" class="!text-[14px] text-[rgb(var(--danger-6))]" size="16" />
|
||||||
|
<span class="ml-1 text-[rgb(var(--danger-6))]"> {{ t('testPlan.featureCase.bug') }}</span>
|
||||||
|
<span class="ml-1 text-[rgb(var(--danger-6))]">{{ caseDetail.bugListCount }}</span>
|
||||||
|
</MsTag>
|
||||||
|
<a-dropdown @select="handleSelect">
|
||||||
|
<a-button
|
||||||
|
v-if="hasAllPermission(['PROJECT_BUG:READ', 'PROJECT_TEST_PLAN:READ+EXECUTE'])"
|
||||||
|
type="outline"
|
||||||
|
size="small"
|
||||||
|
class="ml-1"
|
||||||
|
>
|
||||||
|
<template #icon> <icon-plus class="text-[12px]" /> </template>
|
||||||
|
</a-button>
|
||||||
|
<template #content>
|
||||||
|
<a-doption
|
||||||
|
v-permission="['PROJECT_BUG:READ+ADD']"
|
||||||
|
:disabled="!hasAnyPermission(['PROJECT_BUG:READ+ADD'])"
|
||||||
|
value="new"
|
||||||
|
>{{ t('common.newCreate') }}</a-doption
|
||||||
|
>
|
||||||
|
<a-doption
|
||||||
|
v-if="createdBugCount > 0 && hasAnyPermission(['PROJECT_BUG:READ'])"
|
||||||
|
:disabled="!hasAnyPermission(['PROJECT_BUG:READ'])"
|
||||||
|
value="link"
|
||||||
|
>{{ t('common.associated') }}</a-doption
|
||||||
|
>
|
||||||
|
<a-popover v-else title="" position="left">
|
||||||
|
<a-doption
|
||||||
|
v-if="createdBugCount < 1 && hasAnyPermission(['PROJECT_BUG:READ'])"
|
||||||
|
:disabled="!hasAnyPermission(['PROJECT_BUG:READ'])"
|
||||||
|
value="link"
|
||||||
|
>{{ t('common.associated') }}</a-doption
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center text-[14px]">
|
||||||
|
<span class="text-[var(--color-text-4)]">{{
|
||||||
|
t('testPlan.featureCase.noBugDataTooltip')
|
||||||
|
}}</span>
|
||||||
|
<MsButton
|
||||||
|
:disabled="!hasAnyPermission(['PROJECT_BUG:READ+ADD'])"
|
||||||
|
type="text"
|
||||||
|
@click="handleSelect('new')"
|
||||||
|
>
|
||||||
|
{{ t('testPlan.featureCase.noBugDataNewBug') }}
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-popover>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ExecuteSubmit>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BugList
|
<BugList
|
||||||
|
|
Loading…
Reference in New Issue