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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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