fix(all): 修复部分 bug
This commit is contained in:
parent
c7bc5ed7f5
commit
41ccb313aa
|
@ -6,7 +6,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onBeforeMount, onMounted } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useEventListener, useWindowSize } from '@vueuse/core';
|
import { useEventListener, useWindowSize } from '@vueuse/core';
|
||||||
|
|
||||||
|
@ -75,7 +74,7 @@
|
||||||
const checkIsLogin = async () => {
|
const checkIsLogin = async () => {
|
||||||
const isLogin = await userStore.isLogin();
|
const isLogin = await userStore.isLogin();
|
||||||
const isLoginPage = route.name === 'login';
|
const isLoginPage = route.name === 'login';
|
||||||
if (isLogin && appStore.currentProjectId && appStore.currentProjectId !== 'no_such_project') {
|
if (isLogin && appStore.currentProjectId !== 'no_such_project') {
|
||||||
// 当前为登陆状态,且已经选择了项目,初始化当前项目配置
|
// 当前为登陆状态,且已经选择了项目,初始化当前项目配置
|
||||||
try {
|
try {
|
||||||
const HasProjectPermission = await getUserHasProjectPermission(appStore.currentProjectId);
|
const HasProjectPermission = await getUserHasProjectPermission(appStore.currentProjectId);
|
||||||
|
|
|
@ -387,6 +387,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.arco-form-item-message {
|
.arco-form-item-message {
|
||||||
|
margin-bottom: 16px; // 设计要求表单输入行报错信息距离下一个表单输入行间距为16px
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.hidden-item {
|
.hidden-item {
|
||||||
|
|
|
@ -47,14 +47,24 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="px-[16px]">
|
<div class="px-[16px]">
|
||||||
<div class="api-item-label">{{ t('ms.personal.desc') }}</div>
|
<div class="api-item-label">{{ t('ms.personal.desc') }}</div>
|
||||||
<a-tooltip :content="item.description" :disabled="!item.description">
|
<a-textarea
|
||||||
<div class="api-item-value one-line-text">{{ item.description || '-' }}</div>
|
v-if="item.showDescInput"
|
||||||
</a-tooltip>
|
v-model:model-value="item.description"
|
||||||
|
:placeholder="t('common.pleaseInput')"
|
||||||
|
:max-length="500"
|
||||||
|
@blur="handleDescChange(item)"
|
||||||
|
></a-textarea>
|
||||||
|
<div v-else class="desc-line api-item-value">
|
||||||
|
<a-tooltip :content="item.description" :disabled="!item.description">
|
||||||
|
<div class="one-line-text">{{ item.description || '-' }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
<MsIcon type="icon-icon_edit_outlined" class="edit-icon" @click="handleEditClick(item)" />
|
||||||
|
</div>
|
||||||
<div class="api-item-label">{{ t('ms.personal.createTime') }}</div>
|
<div class="api-item-label">{{ t('ms.personal.createTime') }}</div>
|
||||||
<div class="api-item-value">
|
<div class="api-item-value">
|
||||||
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="api-item-label">{{ t('ms.personal.expireTime') }}</div>
|
<div class="api-item-label">{{ t('ms.personal.validTime') }}</div>
|
||||||
<div class="api-item-value">
|
<div class="api-item-value">
|
||||||
{{ item.forever ? t('ms.personal.forever') : dayjs(item.expireTime).format('YYYY-MM-DD HH:mm:ss') }}
|
{{ item.forever ? t('ms.personal.forever') : dayjs(item.expireTime).format('YYYY-MM-DD HH:mm:ss') }}
|
||||||
<a-tooltip v-if="item.isExpire" :content="t('ms.personal.expiredTip')">
|
<a-tooltip v-if="item.isExpire" :content="t('ms.personal.expiredTip')">
|
||||||
|
@ -63,16 +73,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-[16px]">
|
<div class="flex items-center justify-between px-[16px]">
|
||||||
<MsTableMoreAction :list="actions" trigger="click" @select="handleMoreActionSelect($event, item)">
|
<div class="flex items-center gap-[8px]">
|
||||||
<a-button
|
<a-button
|
||||||
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE', 'SYSTEM_PERSONAL_API_KEY:READ+DELETE']"
|
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE']"
|
||||||
size="mini"
|
size="mini"
|
||||||
type="outline"
|
type="outline"
|
||||||
class="arco-btn-outline--secondary"
|
class="arco-btn-outline--secondary"
|
||||||
|
@click="handleSetValidTime(item)"
|
||||||
>
|
>
|
||||||
{{ t('common.setting') }}
|
{{ t('ms.personal.validTime') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</MsTableMoreAction>
|
<a-button
|
||||||
|
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+DELETE']"
|
||||||
|
size="mini"
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--danger"
|
||||||
|
@click="deleteApiKey(item)"
|
||||||
|
>
|
||||||
|
{{ t('common.delete') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
<a-switch
|
<a-switch
|
||||||
v-model:model-value="item.enable"
|
v-model:model-value="item.enable"
|
||||||
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE']"
|
v-permission="['SYSTEM_PERSONAL_API_KEY:READ+UPDATE']"
|
||||||
|
@ -142,8 +162,6 @@
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
|
||||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -168,6 +186,7 @@
|
||||||
interface APIKEYItem extends APIKEY {
|
interface APIKEYItem extends APIKEY {
|
||||||
isExpire: boolean;
|
isExpire: boolean;
|
||||||
desensitization: boolean;
|
desensitization: boolean;
|
||||||
|
showDescInput: boolean;
|
||||||
}
|
}
|
||||||
const apiKeyList = ref<APIKEYItem[]>([]);
|
const apiKeyList = ref<APIKEYItem[]>([]);
|
||||||
const hasCratePermission = hasAnyPermission(['SYSTEM_PERSONAL_API_KEY:READ+ADD']);
|
const hasCratePermission = hasAnyPermission(['SYSTEM_PERSONAL_API_KEY:READ+ADD']);
|
||||||
|
@ -180,6 +199,7 @@
|
||||||
...item,
|
...item,
|
||||||
isExpire: item.forever ? false : item.expireTime < Date.now(),
|
isExpire: item.forever ? false : item.expireTime < Date.now(),
|
||||||
desensitization: true,
|
desensitization: true,
|
||||||
|
showDescInput: false,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -208,23 +228,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions: ActionsItem[] = [
|
|
||||||
{
|
|
||||||
label: t('ms.personal.validTime'),
|
|
||||||
eventTag: 'time',
|
|
||||||
permission: ['SYSTEM_PERSONAL_API_KEY:READ+UPDATE'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isDivider: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('common.delete'),
|
|
||||||
danger: true,
|
|
||||||
eventTag: 'delete',
|
|
||||||
permission: ['SYSTEM_PERSONAL_API_KEY:READ+DELETE'],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
function handleCopy(val: string) {
|
function handleCopy(val: string) {
|
||||||
if (isSupported) {
|
if (isSupported) {
|
||||||
copy(val);
|
copy(val);
|
||||||
|
@ -299,6 +302,30 @@
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleEditClick(item: APIKEYItem) {
|
||||||
|
item.showDescInput = true;
|
||||||
|
nextTick(() => {
|
||||||
|
document.querySelector<HTMLInputElement>('.arco-textarea')?.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDescChange(item: APIKEYItem) {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await updateAPIKEY({
|
||||||
|
id: item.id || '',
|
||||||
|
description: item.description,
|
||||||
|
});
|
||||||
|
item.showDescInput = false;
|
||||||
|
Message.success(t('common.updateSuccess'));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const timeModalVisible = ref(false);
|
const timeModalVisible = ref(false);
|
||||||
const defaultTimeForm = {
|
const defaultTimeForm = {
|
||||||
activeTimeType: 'forever',
|
activeTimeType: 'forever',
|
||||||
|
@ -307,13 +334,14 @@
|
||||||
};
|
};
|
||||||
const timeForm = ref({ ...defaultTimeForm });
|
const timeForm = ref({ ...defaultTimeForm });
|
||||||
const timeFormRef = ref<FormInstance>();
|
const timeFormRef = ref<FormInstance>();
|
||||||
|
const activeKey = ref<APIKEYItem>();
|
||||||
|
|
||||||
function handleTimeConfirm(done: (closed: boolean) => void) {
|
function handleTimeConfirm(done: (closed: boolean) => void) {
|
||||||
timeFormRef.value?.validate(async (errors) => {
|
timeFormRef.value?.validate(async (errors) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
try {
|
try {
|
||||||
await updateAPIKEY({
|
await updateAPIKEY({
|
||||||
id: apiKeyList.value[0].id,
|
id: activeKey.value?.id || '',
|
||||||
description: timeForm.value.desc,
|
description: timeForm.value.desc,
|
||||||
expireTime: timeForm.value.activeTimeType === 'forever' ? 0 : dayjs(timeForm.value.time).valueOf(),
|
expireTime: timeForm.value.activeTimeType === 'forever' ? 0 : dayjs(timeForm.value.time).valueOf(),
|
||||||
forever: timeForm.value.activeTimeType === 'forever',
|
forever: timeForm.value.activeTimeType === 'forever',
|
||||||
|
@ -337,17 +365,14 @@
|
||||||
timeForm.value = { ...defaultTimeForm };
|
timeForm.value = { ...defaultTimeForm };
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMoreActionSelect(item: ActionsItem, apiKey: APIKEYItem) {
|
function handleSetValidTime(apiKey: APIKEYItem) {
|
||||||
if (item.eventTag === 'time') {
|
activeKey.value = apiKey;
|
||||||
timeForm.value = {
|
timeForm.value = {
|
||||||
activeTimeType: apiKey.forever ? 'forever' : 'custom',
|
activeTimeType: apiKey.forever ? 'forever' : 'custom',
|
||||||
time: apiKey.expireTime ? dayjs(apiKey.expireTime).format('YYYY-MM-DD HH:mm:ss') : '',
|
time: apiKey.expireTime ? dayjs(apiKey.expireTime).format('YYYY-MM-DD HH:mm:ss') : '',
|
||||||
desc: apiKey.description,
|
desc: apiKey.description,
|
||||||
};
|
};
|
||||||
timeModalVisible.value = true;
|
timeModalVisible.value = true;
|
||||||
} else if (item.eventTag === 'delete') {
|
|
||||||
deleteApiKey(apiKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -407,4 +432,17 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.desc-line {
|
||||||
|
gap: 4px;
|
||||||
|
&:hover {
|
||||||
|
.edit-icon {
|
||||||
|
@apply visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.edit-icon {
|
||||||
|
@apply invisible cursor-pointer;
|
||||||
|
|
||||||
|
color: rgb(var(--primary-5));
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
isValidXml.value = false;
|
isValidXml.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
isValidXml.value = true;
|
||||||
parsedXml.value = xmlDoc;
|
parsedXml.value = xmlDoc;
|
||||||
// 先将 XML 字符串格式化,然后解析转换并给每个开始标签加上复制 icon
|
// 先将 XML 字符串格式化,然后解析转换并给每个开始标签加上复制 icon
|
||||||
flattenedXml.value = new XmlBeautify({ parser: DOMParser })
|
flattenedXml.value = new XmlBeautify({ parser: DOMParser })
|
||||||
|
|
|
@ -326,28 +326,12 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderPager = () => {
|
const renderPager = () => {
|
||||||
if (props.simple) {
|
|
||||||
return (
|
|
||||||
<span class={`${prefixCls}-simple-jumper`}>
|
|
||||||
{getPageItemElement('previous', { simple: true })}
|
|
||||||
<PageJumper
|
|
||||||
disabled={props.disabled}
|
|
||||||
current={computedCurrent.value}
|
|
||||||
size={mergedSize.value}
|
|
||||||
pages={pages.value}
|
|
||||||
simple
|
|
||||||
onChange={handleClick}
|
|
||||||
/>
|
|
||||||
{getPageItemElement('next', { simple: true })}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul class={`${prefixCls}-list`}>
|
<ul class={`${prefixCls}-list`}>
|
||||||
{getPageItemElement('previous', { simple: true })}
|
{getPageItemElement('previous', { simple: true })}
|
||||||
{pageList.value}
|
{pageList.value}
|
||||||
{props.showMore &&
|
{props.showMore &&
|
||||||
|
!props.simple &&
|
||||||
getPageItemElement('more', {
|
getPageItemElement('more', {
|
||||||
key: 'more',
|
key: 'more',
|
||||||
step: props.bufferSize * 2 + 1,
|
step: props.bufferSize * 2 + 1,
|
||||||
|
@ -396,7 +380,7 @@ export default defineComponent({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={cls.value}>
|
<div class={cls.value}>
|
||||||
{props.showTotal && (
|
{props.showTotal && !props.simple && (
|
||||||
<span class={`${prefixCls}-total`}>
|
<span class={`${prefixCls}-total`}>
|
||||||
{slots.total?.({ total: props.total }) ?? t('msPagination.total', { total: props.total })}
|
{slots.total?.({ total: props.total }) ?? t('msPagination.total', { total: props.total })}
|
||||||
</span>
|
</span>
|
||||||
|
@ -412,7 +396,7 @@ export default defineComponent({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{renderPager()}
|
{renderPager()}
|
||||||
{!props.simple && props.showJumper && (
|
{!props.simple && !props.simple && props.showJumper && (
|
||||||
<PageJumper
|
<PageJumper
|
||||||
v-slots={{
|
v-slots={{
|
||||||
'jumper-prepend': slots['jumper-prepend'],
|
'jumper-prepend': slots['jumper-prepend'],
|
||||||
|
|
|
@ -232,7 +232,7 @@
|
||||||
v-if="!!attrs.showPagination"
|
v-if="!!attrs.showPagination"
|
||||||
size="small"
|
size="small"
|
||||||
v-bind="(attrs.msPagination as MsPaginationI)"
|
v-bind="(attrs.msPagination as MsPaginationI)"
|
||||||
hide-on-single-page
|
:simple="showBatchAction"
|
||||||
@change="pageChange"
|
@change="pageChange"
|
||||||
@page-size-change="pageSizeChange"
|
@page-size-change="pageSizeChange"
|
||||||
/>
|
/>
|
||||||
|
@ -469,7 +469,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const showBatchAction = computed(() => {
|
const showBatchAction = computed(() => {
|
||||||
return selectedCount.value > 0 && attrs.selectable;
|
return selectedCount.value > 0 && !!attrs.selectable;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleBatchAction = (value: BatchActionParams) => {
|
const handleBatchAction = (value: BatchActionParams) => {
|
||||||
|
@ -622,6 +622,13 @@
|
||||||
batchLeft.value = getBatchLeft();
|
batchLeft.value = getBatchLeft();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.columns,
|
||||||
|
() => {
|
||||||
|
initColumn();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
initColumn,
|
initColumn,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full">
|
<a-tooltip :content="allTagText" :disabled="innerModelValue.length === 0" :mouse-enter-delay="300">
|
||||||
<div :class="`flex w-full items-center ${props.class}`">
|
<div :class="`flex w-full items-center ${props.class}`">
|
||||||
<a-input-tag
|
<a-input-tag
|
||||||
v-model:model-value="innerModelValue"
|
v-model:model-value="innerModelValue"
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
<div v-if="isError" class="ml-[1px] flex justify-start text-[12px] text-[rgb(var(--danger-6))]">
|
<div v-if="isError" class="ml-[1px] flex justify-start text-[12px] text-[rgb(var(--danger-6))]">
|
||||||
{{ t('common.tagInputMaxLength', { number: props.maxLength }) }}
|
{{ t('common.tagInputMaxLength', { number: props.maxLength }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -85,6 +85,10 @@
|
||||||
innerInputValue.value.length > props.maxLength ||
|
innerInputValue.value.length > props.maxLength ||
|
||||||
(innerModelValue.value || []).some((item) => item.toString().length > props.maxLength)
|
(innerModelValue.value || []).some((item) => item.toString().length > props.maxLength)
|
||||||
);
|
);
|
||||||
|
const allTagText = computed(() => {
|
||||||
|
return (innerModelValue.value || []).join('、');
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { onBeforeRouteLeave } from 'vue-router';
|
||||||
|
|
||||||
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
|
|
||||||
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
import { useI18n } from './useI18n';
|
||||||
|
import useModal from './useModal';
|
||||||
|
|
||||||
|
export default function useLeaveTabUnSaveCheck(tabs: TabItem[], permissions?: string[]) {
|
||||||
|
const { openModal } = useModal();
|
||||||
|
const { t } = useI18n();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
let isLeaving = false;
|
||||||
|
onBeforeRouteLeave((to, from, next) => {
|
||||||
|
if (!isLeaving && tabs.some((tab) => tab.unSaved) && hasAnyPermission(permissions || [])) {
|
||||||
|
isLeaving = true;
|
||||||
|
// 如果有未保存的tab则提示用户
|
||||||
|
openModal({
|
||||||
|
type: 'warning',
|
||||||
|
title: t('common.tip'),
|
||||||
|
content: t('apiTestDebug.unsavedLeave'),
|
||||||
|
hideCancel: false,
|
||||||
|
cancelText: t('common.stay'),
|
||||||
|
okText: t('common.leave'),
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
isLeaving = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
|
|
||||||
const isSave = ref(false);
|
const isSave = ref(false);
|
||||||
const isRouteIntercepted = ref<boolean>(false);
|
|
||||||
|
|
||||||
// 离开页面确认提示
|
// 离开页面确认提示
|
||||||
export default function useLeaveUnSaveTip() {
|
export default function useLeaveUnSaveTip() {
|
||||||
|
|
|
@ -1,31 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="end-item">
|
<DefaultLayout
|
||||||
<DefaultLayout
|
:logo="pageConfig.logoPlatform[0]?.url || defaultPlatformLogo"
|
||||||
:logo="pageConfig.logoPlatform[0]?.url || defaultPlatformLogo"
|
:name="pageConfig.platformName"
|
||||||
:name="pageConfig.platformName"
|
class="overflow-hidden"
|
||||||
class="overflow-hidden"
|
hide-right
|
||||||
hide-right
|
>
|
||||||
>
|
<template #page>
|
||||||
<template #page>
|
<div class="page">
|
||||||
<div class="page">
|
<div class="content-wrapper">
|
||||||
<div class="content-wrapper">
|
<div class="content">
|
||||||
<div class="content">
|
<div class="icon">
|
||||||
<div class="icon">
|
<div class="icon-svg">
|
||||||
<div class="icon-svg">
|
<svg-icon width="232px" height="184px" name="no_resource" />
|
||||||
<svg-icon width="232px" height="184px" name="no_resource" />
|
|
||||||
</div>
|
|
||||||
<div class="radius-box"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="radius-box"></div>
|
||||||
<span>{{ props.isProject ? t('common.noProject') : t('common.noResource') }}</span>
|
|
||||||
</div>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
<span>{{ props.isProject ? t('common.noProject') : t('common.noResource') }}</span>
|
||||||
|
</div>
|
||||||
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</DefaultLayout>
|
</template>
|
||||||
</div>
|
</DefaultLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@ -45,15 +43,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.end-item {
|
|
||||||
:deep(.arco-menu-vertical) {
|
|
||||||
.arco-menu-inner {
|
|
||||||
justify-content: end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.page {
|
.page {
|
||||||
height: 100vh;
|
@apply h-full;
|
||||||
|
|
||||||
background-color: var(--color-text-fff);
|
background-color: var(--color-text-fff);
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -47,7 +47,8 @@
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.single-logo-layout {
|
.single-logo-layout {
|
||||||
height: 100vh;
|
@apply h-full;
|
||||||
|
|
||||||
background-color: var(--color-text-n9);
|
background-color: var(--color-text-n9);
|
||||||
.body {
|
.body {
|
||||||
margin-top: 56px;
|
margin-top: 56px;
|
||||||
|
|
|
@ -147,4 +147,8 @@ export default {
|
||||||
'common.value.notNull': 'The attribute value cannot be empty',
|
'common.value.notNull': 'The attribute value cannot be empty',
|
||||||
'common.nameNotNull': 'Name cannot be empty',
|
'common.nameNotNull': 'Name cannot be empty',
|
||||||
'common.namePlaceholder': 'Please enter the name and press Enter to save',
|
'common.namePlaceholder': 'Please enter the name and press Enter to save',
|
||||||
|
'common.unsavedLeave':
|
||||||
|
'The content of some tabs has not been saved. The unsaved content will be lost after leaving. Are you sure you want to leave?',
|
||||||
|
'common.image': 'Image',
|
||||||
|
'common.text': 'Text',
|
||||||
};
|
};
|
||||||
|
|
|
@ -148,4 +148,7 @@ export default {
|
||||||
'common.value.notNull': '属性值不能为空',
|
'common.value.notNull': '属性值不能为空',
|
||||||
'common.nameNotNull': '名称不能为空',
|
'common.nameNotNull': '名称不能为空',
|
||||||
'common.namePlaceholder': '请输入名称,按回车键保存',
|
'common.namePlaceholder': '请输入名称,按回车键保存',
|
||||||
|
'common.unsavedLeave': '有标签页的内容未保存,离开后后未保存的内容将丢失,确定要离开吗?',
|
||||||
|
'common.image': '图片',
|
||||||
|
'common.text': '文本',
|
||||||
};
|
};
|
||||||
|
|
|
@ -400,6 +400,7 @@ export interface ResponseResult {
|
||||||
transferStartTime: number;
|
transferStartTime: number;
|
||||||
vars: string;
|
vars: string;
|
||||||
assertions: any;
|
assertions: any;
|
||||||
|
imageUrl?: string; // 返回为图片时的图片地址
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestResult {
|
export interface RequestResult {
|
||||||
|
|
|
@ -36,8 +36,8 @@ export interface LocalConfig {
|
||||||
// 更新 APIKEY
|
// 更新 APIKEY
|
||||||
export interface UpdateAPIKEYParams {
|
export interface UpdateAPIKEYParams {
|
||||||
id: string;
|
id: string;
|
||||||
forever: boolean;
|
forever?: boolean;
|
||||||
expireTime: number;
|
expireTime?: number;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
// APIKEY
|
// APIKEY
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { getPackageType, getSystemVersion, getUserHasProjectPermission } from '@
|
||||||
import { getMenuList } from '@/api/modules/user';
|
import { getMenuList } from '@/api/modules/user';
|
||||||
import defaultSettings from '@/config/settings.json';
|
import defaultSettings from '@/config/settings.json';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME, WHITE_LIST } from '@/router/constants';
|
import { NO_PROJECT_ROUTE_NAME } from '@/router/constants';
|
||||||
import { watchStyle, watchTheme } from '@/utils/theme';
|
import { watchStyle, watchTheme } from '@/utils/theme';
|
||||||
|
|
||||||
import type { PageConfig, PageConfigKeys, Style, Theme } from '@/models/setting/config';
|
import type { PageConfig, PageConfigKeys, Style, Theme } from '@/models/setting/config';
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
v-if="columnConfig.inputType === 'autoComplete'"
|
v-if="columnConfig.inputType === 'autoComplete'"
|
||||||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||||
:disabled="props.disabledExceptParam"
|
:disabled="props.disabledExceptParam"
|
||||||
:data="columnConfig.autoCompleteParams?.filter((e) => e.isShow === true)"
|
:data="columnConfig.autoCompleteParams?.filter((e) => e.isShow !== false)"
|
||||||
class="ms-form-table-input"
|
class="ms-form-table-input"
|
||||||
:trigger-props="{ contentClass: 'ms-form-table-input-trigger' }"
|
:trigger-props="{ contentClass: 'ms-form-table-input-trigger' }"
|
||||||
:filter-option="false"
|
:filter-option="false"
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
layout: 'horizontal' | 'vertical';
|
layout: 'horizontal' | 'vertical';
|
||||||
response?: string; // 响应内容
|
response?: string; // 响应内容
|
||||||
isDefinition?: boolean; // 是否是定义页面
|
isDefinition?: boolean; // 是否是定义页面
|
||||||
|
isScenario?: boolean; // 是否是场景页面
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -62,6 +63,10 @@
|
||||||
if (props.isDefinition) {
|
if (props.isDefinition) {
|
||||||
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL, RequestConditionProcessor.EXTRACT];
|
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL, RequestConditionProcessor.EXTRACT];
|
||||||
}
|
}
|
||||||
|
// 接口场景
|
||||||
|
if (props.isScenario) {
|
||||||
|
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL];
|
||||||
|
}
|
||||||
return [RequestConditionProcessor.SCRIPT];
|
return [RequestConditionProcessor.SCRIPT];
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
config: ExecuteConditionConfig;
|
config: ExecuteConditionConfig;
|
||||||
isDefinition?: boolean; // 是否是定义页面
|
isDefinition?: boolean; // 是否是定义页面
|
||||||
|
isScenario?: boolean; // 是否是场景页面
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -48,9 +49,15 @@
|
||||||
const innerConfig = useVModel(props, 'config', emit);
|
const innerConfig = useVModel(props, 'config', emit);
|
||||||
|
|
||||||
const conditionTypes = computed(() => {
|
const conditionTypes = computed(() => {
|
||||||
|
// 接口定义
|
||||||
if (props.isDefinition) {
|
if (props.isDefinition) {
|
||||||
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL, RequestConditionProcessor.TIME_WAITING];
|
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL, RequestConditionProcessor.TIME_WAITING];
|
||||||
}
|
}
|
||||||
|
// 接口场景
|
||||||
|
if (props.isScenario) {
|
||||||
|
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.SQL];
|
||||||
|
}
|
||||||
|
// 接口调试
|
||||||
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.TIME_WAITING];
|
return [RequestConditionProcessor.SCRIPT, RequestConditionProcessor.TIME_WAITING];
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,10 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
|
<div v-if="showImg">
|
||||||
|
<div class="mb-[8px] flex items-center gap-[16px]">
|
||||||
|
<a-button type="outline" class="arco-btn-outline--secondary" size="mini" @click="handleDownload">
|
||||||
|
{{ t('common.download') }}
|
||||||
|
</a-button>
|
||||||
|
<a-radio-group v-model:model-value="showType" type="button" size="small">
|
||||||
|
<a-radio value="image">{{ t('common.image') }}</a-radio>
|
||||||
|
<a-radio value="text">{{ t('common.text') }}</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<a-image v-show="showType === 'image'" :src="imageUrl"></a-image>
|
||||||
|
</div>
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
|
v-show="!showImg || showType === 'text'"
|
||||||
ref="responseEditorRef"
|
ref="responseEditorRef"
|
||||||
:model-value="props.requestResult?.responseResult.body"
|
:model-value="props.requestResult?.responseResult.body"
|
||||||
:language="responseLanguage"
|
:language="responseLanguage"
|
||||||
theme="vs"
|
theme="vs"
|
||||||
height="100%"
|
:height="showImg ? 'calc(100% - 26px)' : '100%'"
|
||||||
:languages="[LanguageEnum.JSON, LanguageEnum.HTML, LanguageEnum.XML, LanguageEnum.PLAINTEXT]"
|
:languages="[LanguageEnum.JSON, LanguageEnum.HTML, LanguageEnum.XML, LanguageEnum.PLAINTEXT]"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
:show-theme-change="false"
|
:show-theme-change="false"
|
||||||
|
@ -27,6 +40,9 @@
|
||||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { downloadUrlFile } from '@/utils';
|
||||||
|
|
||||||
import { RequestResult } from '@/models/apiTest/common';
|
import { RequestResult } from '@/models/apiTest/common';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -40,6 +56,8 @@
|
||||||
(e: 'copy'): void;
|
(e: 'copy'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
// 响应体语言类型
|
// 响应体语言类型
|
||||||
const responseLanguage = computed(() => {
|
const responseLanguage = computed(() => {
|
||||||
if (props.requestResult) {
|
if (props.requestResult) {
|
||||||
|
@ -56,6 +74,27 @@
|
||||||
}
|
}
|
||||||
return LanguageEnum.PLAINTEXT;
|
return LanguageEnum.PLAINTEXT;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const showImg = computed(() => {
|
||||||
|
if (props.requestResult) {
|
||||||
|
return props.requestResult.responseResult.contentType.includes('image');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
const imageUrl = computed(() => {
|
||||||
|
if (props.requestResult) {
|
||||||
|
return `data:${props.requestResult?.responseResult.contentType};base64,${props.requestResult?.responseResult.imageUrl}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const showType = ref<'image' | 'text'>('image');
|
||||||
|
|
||||||
|
function handleDownload() {
|
||||||
|
if (imageUrl.value) {
|
||||||
|
downloadUrlFile(imageUrl.value, `response.${props.requestResult?.responseResult.contentType.split('/')[1]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
|
|
@ -122,6 +122,7 @@
|
||||||
uploadTempFile,
|
uploadTempFile,
|
||||||
} from '@/api/modules/api-test/debug';
|
} from '@/api/modules/api-test/debug';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import { parseCurlScript } from '@/utils';
|
import { parseCurlScript } from '@/utils';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
@ -346,34 +347,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
useLeaveTabUnSaveCheck(debugTabs.value, ['PROJECT_API_DEBUG:READ+ADD', 'PROJECT_API_DEBUG:READ+UPDATE']);
|
||||||
let isLeaving = false;
|
|
||||||
onBeforeRouteLeave((to, from, next) => {
|
|
||||||
if (
|
|
||||||
!isLeaving &&
|
|
||||||
debugTabs.value.some((tab) => tab.unSaved) &&
|
|
||||||
hasAnyPermission(['PROJECT_API_DEBUG:READ+ADD', 'PROJECT_API_DEBUG:READ+UPDATE'])
|
|
||||||
) {
|
|
||||||
isLeaving = true;
|
|
||||||
// 如果有未保存的调试则提示用户
|
|
||||||
openModal({
|
|
||||||
type: 'warning',
|
|
||||||
title: t('common.tip'),
|
|
||||||
content: t('apiTestDebug.unsavedLeave'),
|
|
||||||
hideCancel: false,
|
|
||||||
cancelText: t('common.stay'),
|
|
||||||
okText: t('common.leave'),
|
|
||||||
onBeforeOk: async () => {
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
isLeaving = false;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
<style lang="less" scoped></style>
|
||||||
|
|
|
@ -436,6 +436,9 @@
|
||||||
}
|
}
|
||||||
:deep(.ms-api-tab-nav) {
|
:deep(.ms-api-tab-nav) {
|
||||||
@apply h-full;
|
@apply h-full;
|
||||||
|
.arco-tabs {
|
||||||
|
@apply border-b-0;
|
||||||
|
}
|
||||||
.arco-tabs-nav {
|
.arco-tabs-nav {
|
||||||
border-bottom: 1px solid var(--color-text-n8);
|
border-bottom: 1px solid var(--color-text-n8);
|
||||||
}
|
}
|
||||||
|
|
|
@ -659,7 +659,7 @@
|
||||||
case RequestBodyFormat.FORM_DATA:
|
case RequestBodyFormat.FORM_DATA:
|
||||||
return (previewDetail.value.body.formDataBody?.formValues || []).map((e) => ({
|
return (previewDetail.value.body.formDataBody?.formValues || []).map((e) => ({
|
||||||
...e,
|
...e,
|
||||||
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('\n') : e.value,
|
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('、') : e.value,
|
||||||
}));
|
}));
|
||||||
case RequestBodyFormat.WWW_FORM:
|
case RequestBodyFormat.WWW_FORM:
|
||||||
return previewDetail.value.body.wwwFormBody?.formValues || [];
|
return previewDetail.value.body.wwwFormBody?.formValues || [];
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item field="tags" :label="t('common.tag')">
|
<a-form-item field="tags" :label="t('common.tag')">
|
||||||
<MsTagsInput v-model:model-value="detailForm.tags" />
|
<MsTagsInput v-model:model-value="detailForm.tags" :max-tag-count="1" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</div>
|
</div>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onBeforeRouteLeave } from 'vue-router';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||||
|
@ -68,6 +69,8 @@
|
||||||
// import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
// import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
||||||
import { getProtocolList } from '@/api/modules/api-test/common';
|
import { getProtocolList } from '@/api/modules/api-test/common';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
|
||||||
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
|
@ -95,6 +98,7 @@
|
||||||
}>();
|
}>();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const { openModal } = useModal();
|
||||||
|
|
||||||
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
||||||
|
|
||||||
|
@ -299,6 +303,13 @@
|
||||||
initProtocolList();
|
initProtocolList();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useLeaveTabUnSaveCheck(apiTabs.value, [
|
||||||
|
'PROJECT_API_DEFINITION:READ+ADD',
|
||||||
|
'PROJECT_API_DEFINITION:READ+UPDATE',
|
||||||
|
'PROJECT_API_DEFINITION_CASE:READ+ADD',
|
||||||
|
'PROJECT_API_DEFINITION_CASE:READ+UPDATE',
|
||||||
|
]);
|
||||||
|
|
||||||
/** 向孙组件提供属性 */
|
/** 向孙组件提供属性 */
|
||||||
provide('currentEnvConfig', readonly(currentEnvConfig));
|
provide('currentEnvConfig', readonly(currentEnvConfig));
|
||||||
provide('defaultCaseParams', readonly(defaultCaseParams));
|
provide('defaultCaseParams', readonly(defaultCaseParams));
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('apiScenario.namePlaceholder')"
|
:placeholder="t('apiScenario.namePlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
@input="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('apiScenario.belongModule')" class="mb-[16px]">
|
<a-form-item :label="t('apiScenario.belongModule')" class="mb-[16px]">
|
||||||
|
@ -25,10 +26,15 @@
|
||||||
},
|
},
|
||||||
}"
|
}"
|
||||||
allow-search
|
allow-search
|
||||||
|
@change="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('apiScenario.scenarioLevel')">
|
<a-form-item :label="t('apiScenario.scenarioLevel')">
|
||||||
<a-select v-model:model-value="scenario.priority" :placeholder="t('common.pleaseSelect')">
|
<a-select
|
||||||
|
v-model:model-value="scenario.priority"
|
||||||
|
:placeholder="t('common.pleaseSelect')"
|
||||||
|
@change="() => emit('change')"
|
||||||
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="scenario.priority" /></span>
|
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="scenario.priority" /></span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -42,6 +48,7 @@
|
||||||
v-model:model-value="scenario.status"
|
v-model:model-value="scenario.status"
|
||||||
:placeholder="t('common.pleaseSelect')"
|
:placeholder="t('common.pleaseSelect')"
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
|
@change="() => emit('change')"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<apiStatus :status="scenario.status" />
|
<apiStatus :status="scenario.status" />
|
||||||
|
@ -52,13 +59,14 @@
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('common.tag')" class="mb-[16px]">
|
<a-form-item :label="t('common.tag')" class="mb-[16px]">
|
||||||
<MsTagsInput v-model:model-value="scenario.tags" />
|
<MsTagsInput v-model:model-value="scenario.tags" @change="() => emit('change')" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item :label="t('common.desc')" class="mb-[16px]">
|
<a-form-item :label="t('common.desc')" class="mb-[16px]">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
v-model:model-value="scenario.description"
|
v-model:model-value="scenario.description"
|
||||||
:max-length="500"
|
:max-length="500"
|
||||||
:placeholder="t('apiScenario.descPlaceholder')"
|
:placeholder="t('apiScenario.descPlaceholder')"
|
||||||
|
@input="() => emit('change')"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<template v-if="props.isEdit">
|
<template v-if="props.isEdit">
|
||||||
|
@ -96,6 +104,8 @@
|
||||||
moduleTree: ModuleTreeNode[]; // 模块树
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
isEdit?: boolean;
|
isEdit?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
const emit = defineEmits(['change']);
|
||||||
|
|
||||||
const scenario = defineModel<ScenarioDetail | Scenario>('scenario', {
|
const scenario = defineModel<ScenarioDetail | Scenario>('scenario', {
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
v-model:model-value="requestVModel.customizeRequestEnvEnable"
|
||||||
class="w-[150px]"
|
class="w-[150px]"
|
||||||
:disabled="props.step?.isQuoteScenarioStep"
|
:disabled="props.step?.isQuoteScenarioStep"
|
||||||
popup-container=".customApiDrawer-title-right"
|
|
||||||
@change="handleUseEnvChange"
|
@change="handleUseEnvChange"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
|
@ -102,7 +101,7 @@
|
||||||
</a-input>
|
</a-input>
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div v-permission="[props.permissionMap?.execute]">
|
||||||
<a-dropdown-button
|
<a-dropdown-button
|
||||||
v-if="hasLocalExec"
|
v-if="hasLocalExec"
|
||||||
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
||||||
|
@ -395,8 +394,6 @@
|
||||||
detailLoading?: boolean; // 详情加载状态
|
detailLoading?: boolean; // 详情加载状态
|
||||||
permissionMap?: {
|
permissionMap?: {
|
||||||
execute: string;
|
execute: string;
|
||||||
create: string;
|
|
||||||
update: string;
|
|
||||||
};
|
};
|
||||||
stepResponses?: Record<string | number, RequestResult[]>;
|
stepResponses?: Record<string | number, RequestResult[]>;
|
||||||
fileParams?: ScenarioStepFileParams;
|
fileParams?: ScenarioStepFileParams;
|
||||||
|
@ -1115,6 +1112,7 @@
|
||||||
url: props.request.path, // 后台字段是 path
|
url: props.request.path, // 后台字段是 path
|
||||||
activeTab: contentTabList.value[0].value,
|
activeTab: contentTabList.value[0].value,
|
||||||
responseActiveTab: ResponseComposition.BODY,
|
responseActiveTab: ResponseComposition.BODY,
|
||||||
|
stepId: props.step?.uniqueId || '',
|
||||||
isNew: false,
|
isNew: false,
|
||||||
});
|
});
|
||||||
if (_stepType.value.isQuoteApi) {
|
if (_stepType.value.isQuoteApi) {
|
||||||
|
|
|
@ -60,27 +60,29 @@
|
||||||
:disabled="!isEditableApi"
|
:disabled="!isEditableApi"
|
||||||
allow-clear
|
allow-clear
|
||||||
/>
|
/>
|
||||||
<a-dropdown-button
|
<div v-permission="[props.permissionMap?.execute]">
|
||||||
v-if="hasLocalExec"
|
<a-dropdown-button
|
||||||
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
v-if="hasLocalExec"
|
||||||
class="exec-btn"
|
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
||||||
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
class="exec-btn"
|
||||||
@select="execute"
|
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||||
>
|
@select="execute"
|
||||||
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
>
|
||||||
<template #icon>
|
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||||
<icon-down />
|
<template #icon>
|
||||||
</template>
|
<icon-down />
|
||||||
<template #content>
|
</template>
|
||||||
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
|
<template #content>
|
||||||
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
|
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
|
||||||
</a-doption>
|
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
|
||||||
</template>
|
</a-doption>
|
||||||
</a-dropdown-button>
|
</template>
|
||||||
<a-button v-else-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
|
</a-dropdown-button>
|
||||||
{{ t('apiTestDebug.serverExec') }}
|
<a-button v-else-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
|
||||||
</a-button>
|
{{ t('apiTestDebug.serverExec') }}
|
||||||
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
|
</a-button>
|
||||||
|
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-[16px]">
|
<div class="px-[16px]">
|
||||||
<MsTab
|
<MsTab
|
||||||
|
@ -306,6 +308,9 @@
|
||||||
request?: RequestParam; // 请求参数集合
|
request?: RequestParam; // 请求参数集合
|
||||||
stepResponses?: Record<string | number, RequestResult[]>;
|
stepResponses?: Record<string | number, RequestResult[]>;
|
||||||
fileParams?: ScenarioStepFileParams;
|
fileParams?: ScenarioStepFileParams;
|
||||||
|
permissionMap?: {
|
||||||
|
execute: string;
|
||||||
|
};
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'applyStep', request: RequestParam): void;
|
(e: 'applyStep', request: RequestParam): void;
|
||||||
|
|
|
@ -94,12 +94,9 @@
|
||||||
const moduleCountMap = ref<Record<string, number>>({});
|
const moduleCountMap = ref<Record<string, number>>({});
|
||||||
const selectedKeys = ref<string[]>([]);
|
const selectedKeys = ref<string[]>([]);
|
||||||
const folderText = computed(() => {
|
const folderText = computed(() => {
|
||||||
if (props.type === 'api') {
|
if (props.type === 'api' || props.type === 'case') {
|
||||||
return t('apiTestManagement.allApi');
|
return t('apiTestManagement.allApi');
|
||||||
}
|
}
|
||||||
if (props.type === 'case') {
|
|
||||||
return t('apiTestManagement.allCase');
|
|
||||||
}
|
|
||||||
if (props.type === 'scenario') {
|
if (props.type === 'scenario') {
|
||||||
return t('apiScenario.tree.folder.allScenario');
|
return t('apiScenario.tree.folder.allScenario');
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,73 +124,136 @@
|
||||||
|
|
||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
|
|
||||||
const columns: MsTableColumn = [
|
|
||||||
{
|
|
||||||
title: 'ID',
|
|
||||||
dataIndex: 'num',
|
|
||||||
slotName: 'num',
|
|
||||||
sortIndex: 1,
|
|
||||||
sortable: {
|
|
||||||
sortDirections: ['ascend', 'descend'],
|
|
||||||
sorter: true,
|
|
||||||
},
|
|
||||||
fixed: 'left',
|
|
||||||
width: 120,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'apiTestManagement.apiName',
|
|
||||||
dataIndex: 'name',
|
|
||||||
showTooltip: true,
|
|
||||||
sortable: {
|
|
||||||
sortDirections: ['ascend', 'descend'],
|
|
||||||
sorter: true,
|
|
||||||
},
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'apiTestManagement.apiType',
|
|
||||||
dataIndex: 'method',
|
|
||||||
slotName: 'method',
|
|
||||||
titleSlotName: 'methodFilter',
|
|
||||||
width: 140,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'apiTestManagement.apiStatus',
|
|
||||||
dataIndex: 'status',
|
|
||||||
slotName: 'status',
|
|
||||||
titleSlotName: 'statusFilter',
|
|
||||||
width: 130,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'apiTestManagement.path',
|
|
||||||
dataIndex: 'path',
|
|
||||||
showTooltip: true,
|
|
||||||
width: 200,
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title: 'apiTestManagement.version',
|
|
||||||
// dataIndex: 'versionName',
|
|
||||||
// width: 100,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
title: 'common.tag',
|
|
||||||
dataIndex: 'tags',
|
|
||||||
isTag: true,
|
|
||||||
isStringTag: true,
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const tableConfig = {
|
const tableConfig = {
|
||||||
columns,
|
|
||||||
scroll: { x: 700 },
|
scroll: { x: 700 },
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showSelectorAll: false,
|
showSelectorAll: false,
|
||||||
heightUsed: 300,
|
heightUsed: 300,
|
||||||
};
|
};
|
||||||
// 接口定义表格
|
// 接口定义表格
|
||||||
const useApiTable = useTable(getDefinitionPage, tableConfig);
|
const useApiTable = useTable(getDefinitionPage, {
|
||||||
|
...tableConfig,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'num',
|
||||||
|
slotName: 'num',
|
||||||
|
sortIndex: 1,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
fixed: 'left',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.apiName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
showTooltip: true,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.apiType',
|
||||||
|
dataIndex: 'method',
|
||||||
|
slotName: 'method',
|
||||||
|
titleSlotName: 'methodFilter',
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.apiStatus',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
titleSlotName: 'statusFilter',
|
||||||
|
width: 130,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.path',
|
||||||
|
dataIndex: 'path',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: 'apiTestManagement.version',
|
||||||
|
// dataIndex: 'versionName',
|
||||||
|
// width: 100,
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: 'common.tag',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
isTag: true,
|
||||||
|
isStringTag: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
// 接口用例表格
|
// 接口用例表格
|
||||||
const useCaseTable = useTable(getCasePage, tableConfig);
|
const useCaseTable = useTable(getCasePage, {
|
||||||
|
...tableConfig,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'num',
|
||||||
|
slotName: 'num',
|
||||||
|
sortIndex: 1,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
fixed: 'left',
|
||||||
|
width: 130,
|
||||||
|
ellipsis: true,
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.caseName',
|
||||||
|
dataIndex: 'name',
|
||||||
|
showTooltip: true,
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 180,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'case.caseLevel',
|
||||||
|
dataIndex: 'priority',
|
||||||
|
slotName: 'caseLevel',
|
||||||
|
titleSlotName: 'caseLevelFilter',
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.apiStatus',
|
||||||
|
dataIndex: 'status',
|
||||||
|
slotName: 'status',
|
||||||
|
titleSlotName: 'statusFilter',
|
||||||
|
sortable: {
|
||||||
|
sortDirections: ['ascend', 'descend'],
|
||||||
|
sorter: true,
|
||||||
|
},
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.path',
|
||||||
|
dataIndex: 'path',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.tag',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
isTag: true,
|
||||||
|
isStringTag: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
// 接口场景表格
|
// 接口场景表格
|
||||||
const useScenarioTable = useTable(getScenarioPage, {
|
const useScenarioTable = useTable(getScenarioPage, {
|
||||||
...tableConfig,
|
...tableConfig,
|
||||||
|
|
|
@ -36,7 +36,9 @@
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.condition {
|
.condition {
|
||||||
overflow: overlay;
|
.ms-scroll-bar();
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 700px;
|
height: 700px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
|
@ -252,7 +252,11 @@
|
||||||
<template v-if="hasAnyPermission(['PROJECT_API_SCENARIO:READ+ADD'])" #empty>
|
<template v-if="hasAnyPermission(['PROJECT_API_SCENARIO:READ+ADD'])" #empty>
|
||||||
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
<div class="flex w-full items-center justify-center p-[8px] text-[var(--color-text-4)]">
|
||||||
{{ t('api_scenario.table.tableNoDataAndPlease') }}
|
{{ t('api_scenario.table.tableNoDataAndPlease') }}
|
||||||
<MsButton class="float-right ml-[8px]" @click="emit('createScenario')">
|
<MsButton
|
||||||
|
v-permission="['PROJECT_API_SCENARIO:READ+ADD']"
|
||||||
|
class="float-right ml-[8px]"
|
||||||
|
@click="emit('createScenario')"
|
||||||
|
>
|
||||||
{{ t('apiScenario.createScenario') }}
|
{{ t('apiScenario.createScenario') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,43 +1,48 @@
|
||||||
<template>
|
<template>
|
||||||
<a-dropdown
|
<div>
|
||||||
v-model:popup-visible="visible"
|
<a-dropdown
|
||||||
:position="props.position || 'bottom'"
|
v-model:popup-visible="visible"
|
||||||
:popup-translate="props.popupTranslate"
|
:position="props.position || 'bottom'"
|
||||||
class="scenario-action-dropdown"
|
:popup-translate="props.popupTranslate"
|
||||||
@select="(val) => handleCreateActionSelect(val as ScenarioAddStepActionType)"
|
class="scenario-action-dropdown"
|
||||||
>
|
@select="(val) => handleCreateActionSelect(val as ScenarioAddStepActionType)"
|
||||||
<slot></slot>
|
>
|
||||||
<template #content>
|
<slot></slot>
|
||||||
<a-dgroup :title="t('apiScenario.requestScenario')">
|
<template #content>
|
||||||
<a-doption :value="ScenarioAddStepActionType.IMPORT_SYSTEM_API">
|
<a-dgroup :title="t('apiScenario.requestScenario')">
|
||||||
{{ t('apiScenario.importSystemApi') }}
|
<a-doption
|
||||||
</a-doption>
|
v-permission="['PROJECT_API_SCENARIO:READ+IMPORT']"
|
||||||
<a-doption :value="ScenarioAddStepActionType.CUSTOM_API">
|
:value="ScenarioAddStepActionType.IMPORT_SYSTEM_API"
|
||||||
{{ t('apiScenario.customApi') }}
|
>
|
||||||
</a-doption>
|
{{ t('apiScenario.importSystemApi') }}
|
||||||
</a-dgroup>
|
</a-doption>
|
||||||
<a-dgroup :title="t('apiScenario.logicControl')">
|
<a-doption :value="ScenarioAddStepActionType.CUSTOM_API">
|
||||||
<a-doption :value="ScenarioAddStepActionType.LOOP_CONTROL">
|
{{ t('apiScenario.customApi') }}
|
||||||
<div class="flex w-full items-center justify-between">
|
</a-doption>
|
||||||
{{ t('apiScenario.loopControl') }}
|
</a-dgroup>
|
||||||
<MsButton type="text" @click="openTutorial">{{ t('apiScenario.tutorial') }}</MsButton>
|
<a-dgroup :title="t('apiScenario.logicControl')">
|
||||||
</div>
|
<a-doption :value="ScenarioAddStepActionType.LOOP_CONTROL">
|
||||||
</a-doption>
|
<div class="flex w-full items-center justify-between">
|
||||||
<a-doption :value="ScenarioAddStepActionType.CONDITION_CONTROL">
|
{{ t('apiScenario.loopControl') }}
|
||||||
{{ t('apiScenario.conditionControl') }}
|
<MsButton type="text" @click="openTutorial">{{ t('apiScenario.tutorial') }}</MsButton>
|
||||||
</a-doption>
|
</div>
|
||||||
<a-doption :value="ScenarioAddStepActionType.ONLY_ONCE_CONTROL">
|
</a-doption>
|
||||||
{{ t('apiScenario.onlyOnceControl') }}
|
<a-doption :value="ScenarioAddStepActionType.CONDITION_CONTROL">
|
||||||
</a-doption>
|
{{ t('apiScenario.conditionControl') }}
|
||||||
</a-dgroup>
|
</a-doption>
|
||||||
<a-dgroup :title="t('apiScenario.other')">
|
<a-doption :value="ScenarioAddStepActionType.ONLY_ONCE_CONTROL">
|
||||||
<a-doption :value="ScenarioAddStepActionType.SCRIPT_OPERATION">
|
{{ t('apiScenario.onlyOnceControl') }}
|
||||||
{{ t('apiScenario.scriptOperation') }}
|
</a-doption>
|
||||||
</a-doption>
|
</a-dgroup>
|
||||||
<a-doption :value="ScenarioAddStepActionType.WAIT_TIME">{{ t('apiScenario.waitTime') }}</a-doption>
|
<a-dgroup :title="t('apiScenario.other')">
|
||||||
</a-dgroup>
|
<a-doption :value="ScenarioAddStepActionType.SCRIPT_OPERATION">
|
||||||
</template>
|
{{ t('apiScenario.scriptOperation') }}
|
||||||
</a-dropdown>
|
</a-doption>
|
||||||
|
<a-doption :value="ScenarioAddStepActionType.WAIT_TIME">{{ t('apiScenario.waitTime') }}</a-doption>
|
||||||
|
</a-dgroup>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -1,64 +1,66 @@
|
||||||
<template>
|
<template>
|
||||||
<a-trigger
|
<div>
|
||||||
trigger="click"
|
<a-trigger
|
||||||
class="arco-trigger-menu absolute"
|
trigger="click"
|
||||||
content-class="w-[160px]"
|
class="arco-trigger-menu absolute"
|
||||||
position="br"
|
content-class="w-[160px]"
|
||||||
@popup-visible-change="handleActionTriggerChange"
|
position="br"
|
||||||
>
|
@popup-visible-change="handleActionTriggerChange"
|
||||||
<MsButton :id="step.uniqueId" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
|
>
|
||||||
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
|
<MsButton :id="step.uniqueId" type="icon" class="ms-tree-node-extra__btn !mr-[4px]" @click="emit('click')">
|
||||||
</MsButton>
|
<MsIcon type="icon-icon_add_outlined" size="14" class="text-[var(--color-text-4)]" />
|
||||||
<template #content>
|
</MsButton>
|
||||||
<createStepActions
|
<template #content>
|
||||||
v-model:visible="innerStep.createActionsVisible"
|
<createStepActions
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:visible="innerStep.createActionsVisible"
|
||||||
v-model:steps="steps"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:step="innerStep"
|
v-model:steps="steps"
|
||||||
:create-step-action="activeCreateAction"
|
v-model:step="innerStep"
|
||||||
position="br"
|
:create-step-action="activeCreateAction"
|
||||||
:popup-translate="[-7, -10]"
|
position="br"
|
||||||
@other-create="(type, step) => emit('otherCreate', type, step, activeCreateAction)"
|
:popup-translate="[-7, -10]"
|
||||||
@close="handleActionsClose"
|
@other-create="(type, step) => emit('otherCreate', type, step, activeCreateAction)"
|
||||||
@add-done="emit('addDone')"
|
@close="handleActionsClose"
|
||||||
>
|
@add-done="emit('addDone')"
|
||||||
<span></span>
|
|
||||||
</createStepActions>
|
|
||||||
<div class="arco-trigger-menu-inner">
|
|
||||||
<div
|
|
||||||
v-if="showAddChildStep"
|
|
||||||
:class="[
|
|
||||||
'arco-trigger-menu-item !mx-0 !w-full',
|
|
||||||
activeCreateAction === 'inside' ? 'step-tree-active-action' : '',
|
|
||||||
]"
|
|
||||||
@click="handleTriggerActionClick('inside')"
|
|
||||||
>
|
>
|
||||||
<icon-plus size="12" />
|
<span></span>
|
||||||
{{ t('apiScenario.inside') }}
|
</createStepActions>
|
||||||
|
<div class="arco-trigger-menu-inner">
|
||||||
|
<div
|
||||||
|
v-if="showAddChildStep"
|
||||||
|
:class="[
|
||||||
|
'arco-trigger-menu-item !mx-0 !w-full',
|
||||||
|
activeCreateAction === 'inside' ? 'step-tree-active-action' : '',
|
||||||
|
]"
|
||||||
|
@click="handleTriggerActionClick('inside')"
|
||||||
|
>
|
||||||
|
<icon-plus size="12" />
|
||||||
|
{{ t('apiScenario.inside') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'arco-trigger-menu-item !mx-0 !w-full',
|
||||||
|
activeCreateAction === 'before' ? 'step-tree-active-action' : '',
|
||||||
|
]"
|
||||||
|
@click="handleTriggerActionClick('before')"
|
||||||
|
>
|
||||||
|
<icon-left size="12" />
|
||||||
|
{{ t('apiScenario.before') }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'arco-trigger-menu-item !mx-0 !w-full',
|
||||||
|
activeCreateAction === 'after' ? 'step-tree-active-action' : '',
|
||||||
|
]"
|
||||||
|
@click="handleTriggerActionClick('after')"
|
||||||
|
>
|
||||||
|
<icon-left size="12" />
|
||||||
|
{{ t('apiScenario.after') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
</template>
|
||||||
:class="[
|
</a-trigger>
|
||||||
'arco-trigger-menu-item !mx-0 !w-full',
|
</div>
|
||||||
activeCreateAction === 'before' ? 'step-tree-active-action' : '',
|
|
||||||
]"
|
|
||||||
@click="handleTriggerActionClick('before')"
|
|
||||||
>
|
|
||||||
<icon-left size="12" />
|
|
||||||
{{ t('apiScenario.before') }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'arco-trigger-menu-item !mx-0 !w-full',
|
|
||||||
activeCreateAction === 'after' ? 'step-tree-active-action' : '',
|
|
||||||
]"
|
|
||||||
@click="handleTriggerActionClick('after')"
|
|
||||||
>
|
|
||||||
<icon-left size="12" />
|
|
||||||
{{ t('apiScenario.after') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-trigger>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -180,6 +180,7 @@
|
||||||
"
|
"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:steps="steps"
|
v-model:steps="steps"
|
||||||
|
v-permission="['PROJECT_API_DEBUG:READ+ADD', 'PROJECT_API_DEFINITION:READ+UPDATE']"
|
||||||
:step="step"
|
:step="step"
|
||||||
@click="setFocusNodeKey(step.uniqueId)"
|
@click="setFocusNodeKey(step.uniqueId)"
|
||||||
@other-create="handleOtherCreate"
|
@other-create="handleOtherCreate"
|
||||||
|
@ -223,6 +224,7 @@
|
||||||
<createStepActions
|
<createStepActions
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:steps="steps"
|
v-model:steps="steps"
|
||||||
|
v-permission="['PROJECT_API_DEBUG:READ+ADD', 'PROJECT_API_DEFINITION:READ+UPDATE']"
|
||||||
@add-done="handleAddStepDone"
|
@add-done="handleAddStepDone"
|
||||||
@other-create="handleOtherCreate"
|
@other-create="handleOtherCreate"
|
||||||
>
|
>
|
||||||
|
@ -239,6 +241,7 @@
|
||||||
:file-params="currentStepFileParams"
|
:file-params="currentStepFileParams"
|
||||||
:step="activeStep"
|
:step="activeStep"
|
||||||
:step-responses="scenario.stepResponses"
|
:step-responses="scenario.stepResponses"
|
||||||
|
:permission-map="permissionMap"
|
||||||
@add-step="addCustomApiStep"
|
@add-step="addCustomApiStep"
|
||||||
@apply-step="applyApiStep"
|
@apply-step="applyApiStep"
|
||||||
@stop-debug="handleStopExecute(activeStep)"
|
@stop-debug="handleStopExecute(activeStep)"
|
||||||
|
@ -250,6 +253,7 @@
|
||||||
:request="currentStepDetail as unknown as RequestParam"
|
:request="currentStepDetail as unknown as RequestParam"
|
||||||
:file-params="currentStepFileParams"
|
:file-params="currentStepFileParams"
|
||||||
:step-responses="scenario.stepResponses"
|
:step-responses="scenario.stepResponses"
|
||||||
|
:permission-map="permissionMap"
|
||||||
@apply-step="applyApiStep"
|
@apply-step="applyApiStep"
|
||||||
@delete-step="deleteCaseStep(activeStep)"
|
@delete-step="deleteCaseStep(activeStep)"
|
||||||
@stop-debug="handleStopExecute(activeStep)"
|
@stop-debug="handleStopExecute(activeStep)"
|
||||||
|
@ -489,6 +493,9 @@
|
||||||
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
|
const localExecuteUrl = inject<Ref<string>>('localExecuteUrl');
|
||||||
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
const currentEnvConfig = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||||
|
|
||||||
|
const permissionMap = {
|
||||||
|
execute: 'PROJECT_API_SCENARIO:READ+EXECUTE',
|
||||||
|
};
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const treeRef = ref<InstanceType<typeof MsTree>>();
|
const treeRef = ref<InstanceType<typeof MsTree>>();
|
||||||
const focusStepKey = ref<string | number>(''); // 聚焦的key
|
const focusStepKey = ref<string | number>(''); // 聚焦的key
|
||||||
|
@ -1375,6 +1382,10 @@
|
||||||
deleteFileIds: request.deleteFileIds,
|
deleteFileIds: request.deleteFileIds,
|
||||||
unLinkFileIds: request.unLinkFileIds,
|
unLinkFileIds: request.unLinkFileIds,
|
||||||
};
|
};
|
||||||
|
activeStep.value.config = {
|
||||||
|
...activeStep.value.config,
|
||||||
|
method: request.method,
|
||||||
|
};
|
||||||
emit('updateResource', request.uploadFileIds, request.linkFileIds);
|
emit('updateResource', request.uploadFileIds, request.linkFileIds);
|
||||||
activeStep.value = undefined;
|
activeStep.value = undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,12 @@
|
||||||
<div class="p-[16px]">
|
<div class="p-[16px]">
|
||||||
<!-- TODO:第一版没有模板 -->
|
<!-- TODO:第一版没有模板 -->
|
||||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||||
<baseInfo ref="baseInfoRef" :scenario="scenario as Scenario" :module-tree="props.moduleTree" />
|
<baseInfo
|
||||||
|
ref="baseInfoRef"
|
||||||
|
:scenario="scenario as Scenario"
|
||||||
|
:module-tree="props.moduleTree"
|
||||||
|
@change="scenario.unSaved = true"
|
||||||
|
/>
|
||||||
<!-- TODO:第一版先不做依赖 -->
|
<!-- TODO:第一版先不做依赖 -->
|
||||||
<!-- <div class="mb-[8px] flex items-center">
|
<!-- <div class="mb-[8px] flex items-center">
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
|
|
|
@ -20,11 +20,19 @@
|
||||||
<environmentSelect v-model:current-env-config="currentEnvConfig" />
|
<environmentSelect v-model:current-env-config="currentEnvConfig" />
|
||||||
<executeButton
|
<executeButton
|
||||||
ref="executeButtonRef"
|
ref="executeButtonRef"
|
||||||
|
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
|
||||||
:execute-loading="activeScenarioTab.executeLoading"
|
:execute-loading="activeScenarioTab.executeLoading"
|
||||||
@execute="handleExecute"
|
@execute="handleExecute"
|
||||||
@stop-debug="handleStopExecute"
|
@stop-debug="handleStopExecute"
|
||||||
/>
|
/>
|
||||||
<a-button type="primary" :loading="saveLoading" @click="saveScenario">
|
<a-button
|
||||||
|
v-permission="
|
||||||
|
activeScenarioTab.isNew ? ['PROJECT_API_SCENARIO:READ+ADD'] : ['PROJECT_API_SCENARIO:READ+UPDATE']
|
||||||
|
"
|
||||||
|
type="primary"
|
||||||
|
:loading="saveLoading"
|
||||||
|
@click="saveScenario"
|
||||||
|
>
|
||||||
{{ t('common.save') }}
|
{{ t('common.save') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,6 +126,7 @@
|
||||||
} from '@/api/modules/api-test/scenario';
|
} from '@/api/modules/api-test/scenario';
|
||||||
import { getSocket } from '@/api/modules/project-management/commonScript';
|
import { getSocket } from '@/api/modules/project-management/commonScript';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useLeaveTabUnSaveCheck from '@/hooks/useLeaveTabUnSaveCheck';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { filterTree, getGenerateId, mapTree } from '@/utils';
|
import { filterTree, getGenerateId, mapTree } from '@/utils';
|
||||||
|
@ -458,6 +467,25 @@
|
||||||
scenarioDetail.id = res.id;
|
scenarioDetail.id = res.id;
|
||||||
if (!scenarioDetail.steps) {
|
if (!scenarioDetail.steps) {
|
||||||
scenarioDetail.steps = [];
|
scenarioDetail.steps = [];
|
||||||
|
} else {
|
||||||
|
scenarioDetail.steps = mapTree(scenarioDetail.steps, (node) => {
|
||||||
|
if (
|
||||||
|
node.parent &&
|
||||||
|
node.parent.stepType === ScenarioStepType.API_SCENARIO &&
|
||||||
|
[ScenarioStepRefType.REF, ScenarioStepRefType.PARTIAL_REF].includes(node.parent.refType)
|
||||||
|
) {
|
||||||
|
// 如果根节点是引用场景
|
||||||
|
node.isQuoteScenarioStep = true; // 标记为引用场景下的子步骤
|
||||||
|
node.isRefScenarioStep = node.parent.refType === ScenarioStepRefType.REF; // 标记为完全引用场景
|
||||||
|
node.draggable = false; // 引用场景下的任何步骤不可拖拽
|
||||||
|
} else if (node.parent) {
|
||||||
|
// 如果有父节点
|
||||||
|
node.isQuoteScenarioStep = node.parent.isQuoteScenarioStep; // 复用父节点的引用场景标记
|
||||||
|
node.isRefScenarioStep = node.parent.isRefScenarioStep; // 复用父节点的是否完全引用场景标记
|
||||||
|
}
|
||||||
|
node.uniqueId = getGenerateId();
|
||||||
|
return node;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const index = scenarioTabs.value.findIndex((e) => e.id === activeScenarioTab.value.id);
|
const index = scenarioTabs.value.findIndex((e) => e.id === activeScenarioTab.value.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
|
@ -526,6 +554,8 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useLeaveTabUnSaveCheck(scenarioTabs.value, ['PROJECT_API_SCENARIO:READ+ADD', 'PROJECT_API_SCENARIO:READ+UPDATE']);
|
||||||
|
|
||||||
const hasLocalExec = computed(() => executeButtonRef.value?.hasLocalExec);
|
const hasLocalExec = computed(() => executeButtonRef.value?.hasLocalExec);
|
||||||
const localExecuteUrl = computed(() => executeButtonRef.value?.localExecuteUrl);
|
const localExecuteUrl = computed(() => executeButtonRef.value?.localExecuteUrl);
|
||||||
const isPriorityLocalExec = computed(() => executeButtonRef.value?.isPriorityLocalExec);
|
const isPriorityLocalExec = computed(() => executeButtonRef.value?.isPriorityLocalExec);
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
import { GetLoginLogoUrl } from '@/api/requrls/setting/config';
|
import { GetLoginLogoUrl } from '@/api/requrls/setting/config';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useLoading from '@/hooks/useLoading';
|
import useLoading from '@/hooks/useLoading';
|
||||||
import { NO_PROJECT_ROUTE_NAME } from '@/router/constants';
|
import { NO_PROJECT_ROUTE_NAME, NO_RESOURCE_ROUTE_NAME } from '@/router/constants';
|
||||||
import { useAppStore, useUserStore } from '@/store';
|
import { useAppStore, useUserStore } from '@/store';
|
||||||
import { encrypted } from '@/utils';
|
import { encrypted } from '@/utils';
|
||||||
import { setLoginExpires } from '@/utils/auth';
|
import { setLoginExpires } from '@/utils/auth';
|
||||||
|
@ -160,8 +160,18 @@
|
||||||
const { username, password } = values;
|
const { username, password } = values;
|
||||||
loginConfig.value.username = rememberPassword ? username : '';
|
loginConfig.value.username = rememberPassword ? username : '';
|
||||||
loginConfig.value.password = rememberPassword ? password : '';
|
loginConfig.value.password = rememberPassword ? password : '';
|
||||||
|
if (!appStore.currentProjectId || appStore.currentProjectId === 'no_such_project') {
|
||||||
|
// 没有项目权限(用户所在的当前项目被禁用&用户被移除出去该项目/白板用户没有项目)
|
||||||
|
router.push({
|
||||||
|
name: NO_PROJECT_ROUTE_NAME,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { redirect, ...othersQuery } = router.currentRoute.value.query;
|
const { redirect, ...othersQuery } = router.currentRoute.value.query;
|
||||||
const redirectHasPermission = redirect && routerNameHasPermission(redirect as string, router.getRoutes());
|
const redirectHasPermission =
|
||||||
|
redirect &&
|
||||||
|
![NO_RESOURCE_ROUTE_NAME, NO_PROJECT_ROUTE_NAME].includes(redirect as string) &&
|
||||||
|
routerNameHasPermission(redirect as string, router.getRoutes());
|
||||||
const currentRouteName = getFirstRouteNameByPermission(router.getRoutes());
|
const currentRouteName = getFirstRouteNameByPermission(router.getRoutes());
|
||||||
const res = await getProjectInfo(appStore.currentProjectId);
|
const res = await getProjectInfo(appStore.currentProjectId);
|
||||||
if (!res || res.deleted) {
|
if (!res || res.deleted) {
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="props.robot.platform === 'IN_SITE'"
|
v-if="props.robot.platform === 'IN_SITE'"
|
||||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
class="preview-rounded h-full w-[400px] bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
||||||
>
|
>
|
||||||
<div class="preview-rounded bg-white">
|
<div class="preview-rounded h-full overflow-scroll bg-white">
|
||||||
<div
|
<div
|
||||||
v-if="!props.isUpdatePreview"
|
v-if="!props.isUpdatePreview"
|
||||||
class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[16px]"
|
class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[16px]"
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-[12px] p-[16px]">
|
<div class="flex gap-[12px] p-[16px]">
|
||||||
<a-avatar>MS</a-avatar>
|
<a-avatar>MS</a-avatar>
|
||||||
<div class="flex flex-1 flex-col">
|
<div class="flex flex-1 flex-col overflow-hidden">
|
||||||
<div class="font-medium text-[var(--color-text-1)]" v-html="subject || '-'"></div>
|
<div class="font-medium text-[var(--color-text-1)]" v-html="subject || '-'"></div>
|
||||||
<div class="mt-[4px] text-[var(--color-text-2)]" v-html="template || '-'"></div>
|
<div class="mt-[4px] flex-1 text-[var(--color-text-2)]" v-html="template || '-'"></div>
|
||||||
<div class="text-[var(--color-text-4)]">{{ dayjs().format('YYYY-MM-DD HH:mm:ss') }}</div>
|
<div class="text-[var(--color-text-4)]">{{ dayjs().format('YYYY-MM-DD HH:mm:ss') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="props.robot.platform === 'MAIL'"
|
v-else-if="props.robot.platform === 'MAIL'"
|
||||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
class="preview-rounded h-full w-[400px] overflow-scroll bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
||||||
>
|
>
|
||||||
<div class="mb-[4px] text-[16px] font-medium leading-[24px] text-[var(--color-text-1)]" v-html="subject || '-'">
|
<div class="mb-[4px] text-[16px] font-medium leading-[24px] text-[var(--color-text-1)]" v-html="subject || '-'">
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,7 +48,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="props.robot.platform === 'WE_COM'"
|
v-else-if="props.robot.platform === 'WE_COM'"
|
||||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
class="preview-rounded h-full w-[400px] overflow-scroll bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
||||||
>
|
>
|
||||||
<div class="preview-rounded bg-white">
|
<div class="preview-rounded bg-white">
|
||||||
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[16px_16px_8px_16px]">
|
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[16px_16px_8px_16px]">
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="props.robot.platform === 'DING_TALK'"
|
v-else-if="props.robot.platform === 'DING_TALK'"
|
||||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] text-[14px]"
|
class="preview-rounded h-full w-[400px] overflow-scroll bg-[var(--color-text-n9)] text-[14px]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[12px_12px_4px_12px]">
|
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[12px_12px_4px_12px]">
|
||||||
<div class="flex items-center gap-[4px] text-[14px] font-medium text-[var(--color-text-1)]">
|
<div class="flex items-center gap-[4px] text-[14px] font-medium text-[var(--color-text-1)]">
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="props.robot.platform === 'LARK'"
|
v-else-if="props.robot.platform === 'LARK'"
|
||||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] text-[14px]"
|
class="preview-rounded h-full w-[400px] overflow-scroll bg-[var(--color-text-n9)] text-[14px]"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[12px_12px_4px_12px]">
|
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[12px_12px_4px_12px]">
|
||||||
<div class="flex items-center gap-[4px] text-[14px] font-medium text-[var(--color-text-1)]">
|
<div class="flex items-center gap-[4px] text-[14px] font-medium text-[var(--color-text-1)]">
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="props.robot.platform === 'CUSTOM'"
|
v-else-if="props.robot.platform === 'CUSTOM'"
|
||||||
class="preview-rounded w-[400px] bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
class="preview-rounded h-full w-[400px] overflow-scroll bg-[var(--color-text-n9)] p-[12px] text-[14px]"
|
||||||
>
|
>
|
||||||
<div class="preview-rounded bg-white">
|
<div class="preview-rounded bg-white">
|
||||||
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[16px_16px_8px_16px]">
|
<div class="flex items-center justify-between border-b border-[var(--color-text-n8)] p-[16px_16px_8px_16px]">
|
||||||
|
@ -232,7 +232,7 @@
|
||||||
) {
|
) {
|
||||||
previewName = `MeterSphere ${previewName}`;
|
previewName = `MeterSphere ${previewName}`;
|
||||||
}
|
}
|
||||||
return previewName
|
return previewName;
|
||||||
});
|
});
|
||||||
|
|
||||||
const template = computed(() => {
|
const template = computed(() => {
|
||||||
|
@ -249,4 +249,8 @@
|
||||||
|
|
||||||
border-radius: var(--border-radius-small);
|
border-radius: var(--border-radius-small);
|
||||||
}
|
}
|
||||||
|
.overflow-scroll {
|
||||||
|
@apply overflow-y-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -133,6 +133,7 @@
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('project.messageManagement.namePlaceholder')"
|
:placeholder="t('project.messageManagement.namePlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
class="w-[732px]"
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
@ -149,7 +150,11 @@
|
||||||
</a-radio>
|
</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<template v-if="robotForm.type === 'CUSTOM'">
|
<template v-if="robotForm.type === 'CUSTOM'">
|
||||||
<MsFormItemSub :text="t('project.messageManagement.dingTalkCustomTip')" :show-fill-icon="false">
|
<MsFormItemSub
|
||||||
|
:text="t('project.messageManagement.dingTalkCustomTip')"
|
||||||
|
:show-fill-icon="false"
|
||||||
|
class="mb-[16px]"
|
||||||
|
>
|
||||||
<MsButton
|
<MsButton
|
||||||
type="text"
|
type="text"
|
||||||
class="ml-[8px] !text-[12px] leading-[16px]"
|
class="ml-[8px] !text-[12px] leading-[16px]"
|
||||||
|
@ -159,7 +164,7 @@
|
||||||
{{ t('project.messageManagement.noticeDetail') }}
|
{{ t('project.messageManagement.noticeDetail') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</MsFormItemSub>
|
</MsFormItemSub>
|
||||||
<a-alert :title="t('project.messageManagement.dingTalkCustomTitle')">
|
<a-alert :title="t('project.messageManagement.dingTalkCustomTitle')" class="w-[732px]">
|
||||||
<div class="text-[var(--color-text-2)]">{{ t('project.messageManagement.dingTalkCustomContent1') }}</div>
|
<div class="text-[var(--color-text-2)]">{{ t('project.messageManagement.dingTalkCustomContent1') }}</div>
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
{{ t('project.messageManagement.dingTalkCustomContent2', { at: '@' }) }}
|
{{ t('project.messageManagement.dingTalkCustomContent2', { at: '@' }) }}
|
||||||
|
@ -168,7 +173,11 @@
|
||||||
</a-alert>
|
</a-alert>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<MsFormItemSub :text="t('project.messageManagement.dingTalkEnterpriseTip')" :show-fill-icon="false">
|
<MsFormItemSub
|
||||||
|
:text="t('project.messageManagement.dingTalkEnterpriseTip')"
|
||||||
|
:show-fill-icon="false"
|
||||||
|
class="mb-[16px]"
|
||||||
|
>
|
||||||
<MsButton
|
<MsButton
|
||||||
type="text"
|
type="text"
|
||||||
class="ml-[8px] !text-[12px] leading-[16px]"
|
class="ml-[8px] !text-[12px] leading-[16px]"
|
||||||
|
@ -182,7 +191,7 @@
|
||||||
{{ t('project.messageManagement.helpDoc') }}
|
{{ t('project.messageManagement.helpDoc') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</MsFormItemSub>
|
</MsFormItemSub>
|
||||||
<a-alert :title="t('project.messageManagement.dingTalkEnterpriseTitle')">
|
<a-alert :title="t('project.messageManagement.dingTalkEnterpriseTitle')" class="w-[732px]">
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
{{ t('project.messageManagement.dingTalkEnterpriseContent1', { at: '@' }) }}
|
{{ t('project.messageManagement.dingTalkEnterpriseContent1', { at: '@' }) }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -205,6 +214,7 @@
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('project.messageManagement.appKeyPlaceholder')"
|
:placeholder="t('project.messageManagement.appKeyPlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
class="w-[732px]"
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
@ -219,6 +229,7 @@
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
:placeholder="t('project.messageManagement.appSecretPlaceholder')"
|
:placeholder="t('project.messageManagement.appSecretPlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
class="w-[732px]"
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
@ -248,6 +259,7 @@
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
class="w-[732px]"
|
||||||
allow-clear
|
allow-clear
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
@ -358,7 +370,7 @@
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
name: '',
|
name: '',
|
||||||
platform: 'WE_COM',
|
platform: 'WE_COM',
|
||||||
enable: false,
|
enable: true,
|
||||||
webhook: '',
|
webhook: '',
|
||||||
type: 'CUSTOM',
|
type: 'CUSTOM',
|
||||||
appKey: '',
|
appKey: '',
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<a-select v-model:model-value="fieldType" class="my-[8px]" :options="fieldTypeOptions"></a-select>
|
<a-select v-model:model-value="fieldType" class="my-[8px]" :options="fieldTypeOptions"></a-select>
|
||||||
<a-spin class="relative h-[calc(100%-69px)] w-full" :loading="fieldLoading">
|
<a-spin class="relative h-[calc(100%-70px)] w-full" :loading="fieldLoading">
|
||||||
<div :class="`field-out-container ${containerStatusClass}`">
|
<div :class="`field-out-container ${containerStatusClass}`">
|
||||||
<div ref="fieldListRef" class="field-container">
|
<div ref="fieldListRef" class="field-container">
|
||||||
<div v-for="field of filterFields" :key="field.id" class="field-item" @click="addField(field)">
|
<div v-for="field of filterFields" :key="field.id" class="field-item" @click="addField(field)">
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<MsButton type="icon" class="field-plus">
|
<MsButton type="icon" class="field-plus" @click="addField(field)">
|
||||||
<MsIcon type="icon-icon_add_outlined" size="14"></MsIcon>
|
<MsIcon type="icon-icon_add_outlined" size="14"></MsIcon>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]">
|
<div class="flex-1 overflow-hidden rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[16px]">
|
||||||
<div class="mb-[8px] text-[var(--color-text-4)]">{{ t('project.messageManagement.title') }}</div>
|
<div class="mb-[8px] text-[var(--color-text-4)]">{{ t('project.messageManagement.title') }}</div>
|
||||||
<a-textarea
|
<a-textarea
|
||||||
ref="subjectInputRef"
|
ref="subjectInputRef"
|
||||||
|
@ -77,20 +77,22 @@
|
||||||
:auto-size="{ minRows: 3, maxRows: 3 }"
|
:auto-size="{ minRows: 3, maxRows: 3 }"
|
||||||
:max-length="1000"
|
:max-length="1000"
|
||||||
:disabled="saveLoading"
|
:disabled="saveLoading"
|
||||||
|
class="break-keep"
|
||||||
@focus="focusTarget = 'subject'"
|
@focus="focusTarget = 'subject'"
|
||||||
/>
|
/>
|
||||||
<div class="mb-[8px] mt-[16px] text-[var(--color-text-4)]">{{ t('project.messageManagement.content') }}</div>
|
<div class="mb-[8px] mt-[16px] text-[var(--color-text-4)]">{{ t('project.messageManagement.content') }}</div>
|
||||||
<a-textarea
|
<div v-if="template.length > 0 || focusTarget === 'template'" class="h-[calc(100%-156px)]">
|
||||||
v-if="template.length > 0 || focusTarget === 'template'"
|
<a-textarea
|
||||||
ref="templateInputRef"
|
ref="templateInputRef"
|
||||||
v-model:model-value="template"
|
v-model:model-value="template"
|
||||||
class="h-[calc(100%-156px)]"
|
class="h-full overflow-scroll break-keep"
|
||||||
:max-length="1000"
|
:max-length="1000"
|
||||||
auto-size
|
auto-size
|
||||||
:disabled="saveLoading"
|
:disabled="saveLoading"
|
||||||
@focus="focusTarget = 'template'"
|
@focus="focusTarget = 'template'"
|
||||||
@blur="focusTarget = null"
|
@blur="focusTarget = null"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex h-[calc(100%-156px)] flex-col items-center gap-[16px] bg-white"
|
class="flex h-[calc(100%-156px)] flex-col items-center gap-[16px] bg-white"
|
||||||
|
@ -105,18 +107,19 @@
|
||||||
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">
|
<div class="mb-[8px] font-medium text-[var(--color-text-1)]">
|
||||||
{{ t('project.messageManagement.updatePreview') }}
|
{{ t('project.messageManagement.updatePreview') }}
|
||||||
</div>
|
</div>
|
||||||
<MessagePreview
|
<div v-if="messageDetail" class="h-[calc(100%-30px)] overflow-hidden">
|
||||||
v-if="messageDetail"
|
<MessagePreview
|
||||||
:robot="{
|
:robot="{
|
||||||
...messageDetail,
|
...messageDetail,
|
||||||
template,
|
template,
|
||||||
subject,
|
subject,
|
||||||
}"
|
}"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:function-name="'接口测试'"
|
function-name=""
|
||||||
:event-name="'创建任务'"
|
event-name=""
|
||||||
is-update-preview
|
is-update-preview
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
|
@ -342,6 +345,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.overflow-scroll {
|
||||||
|
@apply overflow-y-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
.content-empty-img {
|
.content-empty-img {
|
||||||
margin-top: 100px;
|
margin-top: 100px;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
|
|
Loading…
Reference in New Issue