feat(环境管理): 断言基本页面&评论逻辑修改

This commit is contained in:
RubyLiu 2024-01-15 20:34:24 +08:00 committed by rubylliu
parent e1bfeadd2e
commit ba3d33c24e
14 changed files with 250 additions and 39 deletions

View File

@ -0,0 +1,152 @@
<template>
<div class="ms-assertion">
<a-dropdown trigger="hover" @select="handleSelect">
<a-button class="w-[84px]" type="outline">
<div class="flex flex-row items-center gap-[8px]">
<icon-plus />
<span>{{ t('ms.assertion.button') }}</span>
</div>
</a-button>
<template #content>
<a-doption v-for="item in assertOption" :key="item.value" :value="item.value">{{ item.label }}</a-doption>
</template>
</a-dropdown>
<div class="ms-assertion-body">
<article class="ms-assertion-body-left">
<div
v-for="(item, index) in activeOption"
:key="item.value"
class="ms-assertion-body-left-item"
@click="handleItemClick(item)"
>
<div class="ms-assertion-body-left-item-row">
<span class="ms-assertion-body-left-item-row-num">{{ index + 1 }}</span>
<span class="ms-assertion-body-left-item-row-title">{{ item.label }}</span>
</div>
<div class="ms-assertion-body-left-item-switch">
<a-switch type="line" size="small" />
</div>
</div>
</article>
<section class="ms-assertion-body-right"> </section>
</div>
</div>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/useI18n';
defineOptions({
name: 'MsAssertion',
});
const { t } = useI18n();
const assertOptionSource = [
{
label: t('ms.assertion.statusCode'),
value: 'statusCode',
},
{
label: t('ms.assertion.responseHeader'),
value: 'responseHeader',
},
{
label: t('ms.assertion.responseBody'),
value: 'responseBody',
},
{
label: t('ms.assertion.responseTime'),
value: 'responseTime',
},
{
label: t('ms.assertion.param'),
value: 'param',
},
{
label: t('ms.assertion.script'),
value: 'script',
},
];
const selectIds = ref<string[]>([]);
const activeKey = ref<string>('');
const assertOption = computed(() => {
return assertOptionSource.filter((item) => !selectIds.value.includes(item.value));
});
const activeOption = computed(() => {
return assertOptionSource.filter((item) => selectIds.value.includes(item.value));
});
const handleSelect = (value: string | number | Record<string, any> | undefined) => {
selectIds.value.push(value as string);
};
const handleItemClick = (item: { label: string; value: string }) => {
activeKey.value = item.value;
};
</script>
<style lang="less" scoped>
.ms-assertion {
width: 100%;
&-body {
display: flex;
margin-top: 8px;
flex-flow: row nowrap;
gap: 8px;
&-left {
display: flex;
padding: 12px;
width: 216px;
height: calc(100vh - 394px);
background-color: var(--color-text-n9);
flex-direction: column;
gap: 4px;
&-item {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
align-items: center;
padding: 4px 8px;
border-radius: 4px;
background-color: var(--color-text-fff);
cursor: pointer;
&-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
&-num {
width: 16px;
height: 16px;
font-size: 12px;
font-weight: 500;
border-radius: 50%;
text-align: center;
color: var(--color-text-4);
background-color: var(--color-text-n8);
line-height: 16px;
}
&-title {
font-size: 12px;
font-weight: 400;
color: var(--color-text-1);
line-height: 22px;
}
}
}
}
&-right {
display: flex;
flex-grow: 1;
border: 1px solid var(--color-text-n8);
border-radius: 4px;
background: var(--color-text-fff);
}
}
}
</style>

View File

@ -0,0 +1,3 @@
export default {
'ms.assertion.button': 'Assertion',
};

View File

@ -0,0 +1,9 @@
export default {
'ms.assertion.button': '断言',
'ms.assertion.statusCode': '状态码',
'ms.assertion.responseHeader': '响应头',
'ms.assertion.responseBody': '响应体',
'ms.assertion.responseTime': '响应时间',
'ms.assertion.param': '变量',
'ms.assertion.script': '脚本',
};

View File

@ -26,7 +26,7 @@
<MsIconfont type="icon-icon_edit_outlined" />
<span>{{ t('ms.comment.edit') }}</span>
</div>
<div v-if="hasEditAuth" class="comment-btn" @click="deleteClick">
<div class="comment-btn" @click="deleteClick">
<MsIconfont type="icon-icon_delete-trash_outlined" />
<span>{{ t('ms.comment.delete') }}</span>
</div>
@ -51,14 +51,19 @@
const userStore = useUserStore();
const { t } = useI18n();
defineOptions({ name: 'MsCommentItem' });
const props = defineProps<{
element: CommentItem; //
mode: 'parent' | 'child'; //
onReply?: () => void; //
onEdit?: () => void; //
onDelete?: () => void; //
}>();
//
const hasEditAuth = computed(() => {
return props.element.commentUserInfo.id === userStore.id;
return props.element.createUser === userStore.id;
});
const emit = defineEmits<{

View File

@ -16,6 +16,10 @@ export default defineComponent({
type: Array as PropType<CommentItem[]>,
default: () => [],
},
disabled: {
type: Boolean as PropType<boolean>,
default: false,
},
},
emits: {
/* eslint-disable @typescript-eslint/no-unused-vars */
@ -23,11 +27,19 @@ export default defineComponent({
delete: (value: string) => true, // 删除评论
},
setup(props, { emit }) {
const { commentList } = toRefs(props);
const currentItem = reactive<{ id: string; parentId: string }>({ id: '', parentId: '' });
const { commentList, disabled } = toRefs(props);
const currentItem = reactive<{ id: string; parentId?: string }>({
id: '',
parentId: '',
});
const { t } = useI18n();
const { openModal } = useModal();
const resetCurrentItem = () => {
currentItem.id = '';
currentItem.parentId = '';
};
const handlePublish = (content: string, item: CommentItem) => {
const params: CommentParams = {
...item,
@ -36,9 +48,10 @@ export default defineComponent({
};
emit('updateOrAdd', params, (result: boolean) => {
if (result) {
message.success(t('common.publishSuccess'));
message.success(t('common.publishSuccessfully'));
resetCurrentItem();
} else {
message.error(t('common.publishFail'));
message.error(t('common.publishFailed'));
}
});
};
@ -60,12 +73,30 @@ export default defineComponent({
});
};
const handleReply = (item: CommentItem) => {
if (item.childComments) {
// 父级评论
currentItem.id = item.id;
} else {
// 子级评论
currentItem.id = item.parentId || '';
currentItem.parentId = item.id;
}
};
const handelEdit = (item: CommentItem) => {
currentItem.id = item.id;
currentItem.parentId = item.parentId || '';
};
const renderInput = (item: CommentItem) => {
return (
<CommentInput
isShowAvatar={false}
isUseBottom={false}
onPublish={(content: string) => handlePublish(content, item)}
defaultValue={item.content || ''}
onCancel={() => resetCurrentItem()}
{...item}
/>
);
@ -79,14 +110,8 @@ export default defineComponent({
return (
<div class="flex flex-col">
<Item
onReply={() => {
currentItem.id = item.id;
currentItem.parentId = item.parentId || '';
}}
onEdit={() => {
currentItem.id = item.id;
currentItem.parentId = item.parentId || '';
}}
onReply={() => handleReply(item)}
onEdit={() => handelEdit(item)}
onDelete={() => handleDelete(item)}
mode={'child'}
element={item}
@ -100,11 +125,18 @@ export default defineComponent({
const renderParentList = (list: CommentItem[]) => {
return list.map((item) => {
return (
<Item mode={'parent'} onDelete={() => handleDelete(item)} element={item}>
<div class="rounded border border-[var(--color-text-7)] p-[16px]">
{renderChildrenList(item.childComments)}
</div>
</Item>
<>
<Item
mode={'parent'}
onReply={() => handleReply(item)}
onEdit={() => handelEdit(item)}
onDelete={() => handleDelete(item)}
element={item}
>
<div class="rounded border border-[var(--color-text-7)] p-[16px]"></div>
</Item>
{item.id === currentItem.id && renderInput(item)}
</>
);
});
};

View File

@ -22,28 +22,32 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import { useI18n } from '@/hooks/useI18n';
defineOptions({ name: 'MsCommentInput' });
const { t } = useI18n();
// const currentContent = defineModel<string>('content', { required: true });
const props = defineProps<{
content: string;
isShowAvatar: boolean; //
isUseBottom: boolean; //
defaultValue?: string; //
}>();
const currentContent = ref(props.defaultValue || '');
const emit = defineEmits<{
(event: 'update:content', value: string): void;
(event: 'publish', value: string): void;
(event: 'cancel'): void;
}>();
const isActive = ref(false);
const currentContent = useVModel(props, 'content', emit);
const publish = () => {
emit('publish', currentContent.value);
@ -53,6 +57,7 @@
const cancelClick = () => {
isActive.value = false;
currentContent.value = '';
emit('cancel');
};
</script>

View File

@ -82,6 +82,7 @@ export default {
'common.newSuccess': 'Added successfully',
'common.publish': 'Publish',
'common.publishSuccessfully': 'Published successfully',
'common.publishFailed': 'Published failed',
'common.string': 'String',
'common.number': 'Number',
'common.boolean': 'Boolean',

View File

@ -83,6 +83,7 @@ export default {
'common.newSuccess': '新增成功',
'common.publish': '发布',
'common.publishSuccessfully': '发布成功',
'common.publishFailed': '发布失败',
'common.string': '字符串',
'common.number': '数字',
'common.boolean': '布尔',

View File

@ -49,7 +49,7 @@
<script lang="ts" setup>
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import AllParamsTable from '../allParams/AllParamsTable.vue';
import AllParamsTable from './allParams/AllParamsTable.vue';
import { useI18n } from '@/hooks/useI18n';
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';

View File

@ -43,19 +43,19 @@
</template>
<script lang="ts" setup>
import AssertTab from './AssertTab.vue';
import DataBaseTab from './DatabaseTab.vue';
import DisplayTab from './DisplayTab.vue';
import EnvParamsTab from './EnvParamsTab.vue';
import HostTab from './HostTab.vue';
import HttpTab from './HttpTab.vue';
import PostTab from './PostTab.vue';
import PreTab from './PreTab.vue';
import TcpTab from './TcpTab.vue';
import AssertTab from './envParams/AssertTab.vue';
import DataBaseTab from './envParams/DatabaseTab.vue';
import DisplayTab from './envParams/DisplayTab.vue';
import EnvParamsTab from './envParams/EnvParamsTab.vue';
import HostTab from './envParams/HostTab.vue';
import HttpTab from './envParams/HttpTab.vue';
import PostTab from './envParams/PostTab.vue';
import PreTab from './envParams/PreTab.vue';
import TcpTab from './envParams/TcpTab.vue';
import { useI18n } from '@/hooks/useI18n';
const activeKey = ref('http');
const activeKey = ref('assert');
const envForm = ref();
const canSave = ref(false);
const { t } = useI18n();

View File

@ -12,7 +12,7 @@
</template>
<a-input
v-model:model-value="record.name"
:placeholder="t('ms.apiTestDebug.paramNamePlaceholder')"
:placeholder="t('project.environmental.paramNamePlaceholder')"
class="param-input"
@input="(val) => addTableLine(val)"
/>

View File

@ -1,10 +1,12 @@
<template>
<div class="p-[24px]">
<a-divider :margin="0" class="!mb-[16px]" />
<div>
<ms-assertion />
</div>
</template>
<script lang="ts" setup>
import MsAssertion from '@/components/business/ms-assertion/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();

View File

@ -188,8 +188,8 @@
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import AllParamBox from './components/AllParamBox.vue';
import EnvGroupBox from './components/envGroup/EnvGroupBox.vue';
import EnvParamBox from './components/envParams/EnvParamBox.vue';
import EnvGroupBox from './components/EnvGroupBox.vue';
import EnvParamBox from './components/EnvParamBox.vue';
import RenamePop from './components/RenamePop.vue';
import { useI18n } from '@/hooks/useI18n';

View File

@ -11,6 +11,7 @@ export default {
'project.environmental.mustContain': '必含',
'project.environmental.searchParamsHolder': '通过名称或标签搜索',
'project.environmental.paramName': '参数名称',
'project.environmental.paramNamePlaceholder': '请输入参数名称',
'project.environmental.paramType': '类型',
'project.environmental.paramTypeTooltip': 'json仅支持 UI 测试',
'project.environmental.paramValue': '参数值',