fix(接口场景): 调整引用case&删除冗余代码
This commit is contained in:
parent
d7135d57f7
commit
8bb7b0c86c
|
@ -712,7 +712,6 @@
|
|||
// HTTP 协议 tabs
|
||||
if (isHttpProtocol.value) {
|
||||
if (props.isCase) {
|
||||
// 定义没有参数BODY/QUERY/REST的,用例对应tab不显示
|
||||
return httpContentTabList;
|
||||
}
|
||||
if (props.isDefinition) {
|
||||
|
|
|
@ -455,7 +455,7 @@
|
|||
slotName: 'action',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: hasOperationPermission.value ? 200 : 50,
|
||||
width: hasOperationPermission.value ? 220 : 50,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
|
|
|
@ -54,8 +54,8 @@
|
|||
<template #label>
|
||||
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
|
||||
</template>
|
||||
<a-option v-for="item of caseLevelList" :key="item.value" :value="item.value">
|
||||
<caseLevel :case-level="item.text" />
|
||||
<a-option v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
|
||||
<caseLevel :case-level="item.label as CaseLevel" />
|
||||
</a-option>
|
||||
</a-select>
|
||||
<span v-else class="text-[var(--color-text-2)]"> <caseLevel :case-level="record.priority" /></span>
|
||||
|
@ -70,8 +70,8 @@
|
|||
<div class="arco-table-filters-content">
|
||||
<div class="ml-[6px] flex items-center justify-start px-[6px] py-[2px]">
|
||||
<a-checkbox-group v-model:model-value="caseFilters" direction="vertical" size="small">
|
||||
<a-checkbox v-for="item of caseLevelList" :key="item.text" :value="item.text">
|
||||
<caseLevel :case-level="item.text" />
|
||||
<a-checkbox v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
|
||||
<caseLevel :case-level="item.label as CaseLevel" />
|
||||
</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
</div>
|
||||
|
@ -287,7 +287,7 @@
|
|||
>
|
||||
<a-select v-model="batchForm.value" :placeholder="t('common.pleaseSelect')" :disabled="batchForm.attr === ''">
|
||||
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
|
||||
{{ t(item.text) }}
|
||||
{{ t(item.label) }}
|
||||
</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
@ -359,6 +359,7 @@
|
|||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
import caseDetailDrawer from './caseDetailDrawer.vue';
|
||||
import caseReportDrawer from './caseReportDrawer.vue';
|
||||
import createAndEditCaseDrawer from './createAndEditCaseDrawer.vue';
|
||||
|
@ -377,7 +378,6 @@
|
|||
updateCasePriority,
|
||||
updateCaseStatus,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { getCaseDefaultFields } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import useTableStore from '@/hooks/useTableStore';
|
||||
|
@ -390,6 +390,7 @@
|
|||
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
|
||||
import { TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||
|
||||
|
@ -552,7 +553,7 @@
|
|||
slotName: 'operation',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: hasOperationPermission.value ? 200 : 50,
|
||||
width: hasOperationPermission.value ? 220 : 50,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
||||
|
@ -597,12 +598,8 @@
|
|||
|
||||
const statusFilterVisible = ref(false);
|
||||
const statusFilters = ref<string[]>([]);
|
||||
const caseLevelFields = ref<Record<string, any>>({});
|
||||
const caseFilterVisible = ref(false);
|
||||
const caseFilters = ref<string[]>([]);
|
||||
const caseLevelList = computed(() => {
|
||||
return caseLevelFields.value?.options || [];
|
||||
});
|
||||
const lastReportStatusFilterVisible = ref(false);
|
||||
const lastReportStatusList = computed(() => {
|
||||
return Object.keys(ReportStatus[ReportEnum.API_REPORT]);
|
||||
|
@ -643,15 +640,8 @@
|
|||
loadCaseList();
|
||||
}
|
||||
|
||||
// 获取用例等级数据
|
||||
async function getCaseLevelFields() {
|
||||
const result = await getCaseDefaultFields(appStore.currentProjectId);
|
||||
caseLevelFields.value = result.customFields.find((item: any) => item.internal && item.fieldName === '用例等级');
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadCaseList();
|
||||
getCaseLevelFields();
|
||||
});
|
||||
|
||||
function handleFilterHidden(val: boolean) {
|
||||
|
@ -842,23 +832,23 @@
|
|||
batchForm.value.value = '';
|
||||
switch (batchForm.value.attr) {
|
||||
case 'priority':
|
||||
return caseLevelList.value;
|
||||
return casePriorityOptions;
|
||||
case 'status':
|
||||
return [
|
||||
{
|
||||
text: 'apiTestManagement.processing',
|
||||
label: 'apiTestManagement.processing',
|
||||
value: RequestDefinitionStatus.PROCESSING,
|
||||
},
|
||||
{
|
||||
text: 'apiTestManagement.done',
|
||||
label: 'apiTestManagement.done',
|
||||
value: RequestDefinitionStatus.DONE,
|
||||
},
|
||||
{
|
||||
text: 'apiTestManagement.deprecate',
|
||||
label: 'apiTestManagement.deprecate',
|
||||
value: RequestDefinitionStatus.DEPRECATED,
|
||||
},
|
||||
{
|
||||
text: 'apiTestManagement.debugging',
|
||||
label: 'apiTestManagement.debugging',
|
||||
value: RequestDefinitionStatus.DEBUGGING,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -599,17 +599,12 @@
|
|||
return httpContentTabList;
|
||||
}
|
||||
if (!isEditableApi.value) {
|
||||
return [
|
||||
...pluginContentTab,
|
||||
...httpContentTabList
|
||||
.filter((e) => commonContentTabKey.includes(e.value))
|
||||
.filter(
|
||||
(item) =>
|
||||
!(!preProcessorNum.value && item.value === RequestComposition.PRECONDITION) &&
|
||||
!(!postProcessorNum.value && item.value === RequestComposition.POST_CONDITION) &&
|
||||
!(!assertionsNum.value && item.value === RequestComposition.ASSERTION)
|
||||
),
|
||||
];
|
||||
return [...pluginContentTab, ...httpContentTabList.filter((e) => commonContentTabKey.includes(e.value))].filter(
|
||||
(item) =>
|
||||
!(!preProcessorNum.value && item.value === RequestComposition.PRECONDITION) &&
|
||||
!(!postProcessorNum.value && item.value === RequestComposition.POST_CONDITION) &&
|
||||
!(!assertionsNum.value && item.value === RequestComposition.ASSERTION)
|
||||
);
|
||||
}
|
||||
return [...pluginContentTab, ...httpContentTabList.filter((e) => commonContentTabKey.includes(e.value))];
|
||||
});
|
||||
|
@ -634,7 +629,7 @@
|
|||
case RequestComposition.ASSERTION:
|
||||
return `${assertionsNum.value > 99 ? '99+' : assertionsNum.value || ''}`;
|
||||
case RequestComposition.AUTH:
|
||||
return requestVModel.value.authConfig.authType !== RequestAuthType.NONE ? '1' : '';
|
||||
return requestVModel.value.authConfig?.authType !== RequestAuthType.NONE ? '1' : '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -1045,11 +1040,8 @@
|
|||
props.request?.[type]?.forEach((item) => {
|
||||
if (!item.key.length) return;
|
||||
const index = requestVModel.value[type]?.findIndex((itemReq) => itemReq.key === item.key);
|
||||
// 相同key的进行替换值;key不同的取交集
|
||||
if (index > -1) {
|
||||
requestVModel.value[type][index].value = item.value;
|
||||
} else {
|
||||
requestVModel.value[type].push(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1059,8 +1051,6 @@
|
|||
const index = requestVModel.value.body[type].formValues.findIndex((itemReq) => itemReq.key === item.key);
|
||||
if (index > -1) {
|
||||
requestVModel.value.body[type].formValues[index].value = item.value;
|
||||
} else {
|
||||
requestVModel.value.body[type].formValues.push(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,90 +50,45 @@
|
|||
</template>
|
||||
</a-empty>
|
||||
<div v-show="!pluginError || isHttpProtocol" class="flex h-full flex-col">
|
||||
<div class="px-[18px] pt-[8px]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-[12px]">
|
||||
<div class="flex flex-1 items-center gap-[16px]">
|
||||
<a-select
|
||||
v-if="requestVModel.isNew"
|
||||
v-model:model-value="requestVModel.protocol"
|
||||
:options="protocolOptions"
|
||||
:loading="protocolLoading"
|
||||
:disabled="_stepType.isQuoteCase"
|
||||
class="w-[90px]"
|
||||
@change="(val) => handleActiveDebugProtocolChange(val as string)"
|
||||
/>
|
||||
<div v-else class="flex items-center gap-[4px]">
|
||||
<apiMethodName
|
||||
:method="(requestVModel.protocol as RequestMethods)"
|
||||
tag-background-color="rgb(var(--link-7))"
|
||||
tag-text-color="white"
|
||||
is-tag
|
||||
class="flex items-center"
|
||||
/>
|
||||
<a-tooltip v-if="!isHttpProtocol" :content="requestVModel.name" :mouse-enter-delay="500">
|
||||
<div class="one-line-text max-w-[350px]"> {{ requestVModel.name }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-input-group v-if="isHttpProtocol" class="flex-1">
|
||||
<apiMethodSelect
|
||||
v-model:model-value="requestVModel.method"
|
||||
class="w-[140px]"
|
||||
:disabled="_stepType.isQuoteCase"
|
||||
@change="handleActiveDebugChange"
|
||||
/>
|
||||
<a-input
|
||||
v-model:model-value="requestVModel.url"
|
||||
:max-length="255"
|
||||
:placeholder="t('apiTestDebug.urlPlaceholder')"
|
||||
allow-clear
|
||||
class="hover:z-10"
|
||||
:style="isUrlError ? 'border: 1px solid rgb(var(--danger-6);z-index: 10' : ''"
|
||||
:disabled="_stepType.isQuoteCase"
|
||||
@input="() => (isUrlError = false)"
|
||||
@change="handleUrlChange"
|
||||
/>
|
||||
</a-input-group>
|
||||
</div>
|
||||
<div>
|
||||
<a-dropdown-button
|
||||
v-if="hasLocalExec"
|
||||
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
||||
class="exec-btn"
|
||||
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
@select="execute"
|
||||
>
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<a-button v-else-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
|
||||
{{ t('apiTestDebug.serverExec') }}
|
||||
</a-button>
|
||||
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-[16px] p-[16px] pb-[8px]">
|
||||
<a-input
|
||||
v-if="activeStep?.stepType && (_stepType.isCopyCase || _stepType.isQuoteCase) && isHttpProtocol"
|
||||
v-if="activeStep?.stepType && (_stepType.isCopyCase || _stepType.isQuoteCase)"
|
||||
v-model:model-value="requestVModel.name"
|
||||
:max-length="255"
|
||||
:show-word-limit="isEditableApi"
|
||||
:placeholder="t('apiTestManagement.apiNamePlaceholder')"
|
||||
:disabled="!isEditableApi"
|
||||
allow-clear
|
||||
class="mt-[8px]"
|
||||
/>
|
||||
<a-dropdown-button
|
||||
v-if="hasLocalExec"
|
||||
:disabled="requestVModel.executeLoading || (isHttpProtocol && !requestVModel.url)"
|
||||
class="exec-btn"
|
||||
@click="() => execute(isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
@select="execute"
|
||||
>
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
</template>
|
||||
<template #content>
|
||||
<a-doption :value="isPriorityLocalExec ? 'serverExec' : 'localExec'">
|
||||
{{ isPriorityLocalExec ? t('apiTestDebug.serverExec') : t('apiTestDebug.localExec') }}
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown-button>
|
||||
<a-button v-else-if="!requestVModel.executeLoading" type="primary" @click="() => execute('serverExec')">
|
||||
{{ t('apiTestDebug.serverExec') }}
|
||||
</a-button>
|
||||
<a-button v-else type="primary" class="mr-[12px]" @click="stopDebug">{{ t('common.stop') }}</a-button>
|
||||
</div>
|
||||
<div class="px-[16px]">
|
||||
<MsTab
|
||||
v-model:active-key="requestVModel.activeTab"
|
||||
:content-tab-list="contentTabList"
|
||||
:get-text-func="getTabBadge"
|
||||
class="no-content relative mt-[8px] border-b"
|
||||
no-content
|
||||
class="relative mt-[8px] border-b"
|
||||
/>
|
||||
</div>
|
||||
<div ref="splitContainerRef" class="h-[calc(100%-87px)]">
|
||||
|
@ -176,6 +131,7 @@
|
|||
<httpHeader
|
||||
v-if="requestVModel.activeTab === RequestComposition.HEADER"
|
||||
v-model:params="requestVModel.headers"
|
||||
:disabled-param-value="!isEditableApi"
|
||||
:disabled-except-param="!isEditableApi"
|
||||
:layout="activeLayout"
|
||||
:second-box-height="secondBoxHeight"
|
||||
|
@ -185,6 +141,7 @@
|
|||
v-else-if="requestVModel.activeTab === RequestComposition.BODY"
|
||||
v-model:params="requestVModel.body"
|
||||
:layout="activeLayout"
|
||||
:disabled-param-value="!isEditableApi"
|
||||
:disabled-except-param="!isEditableApi"
|
||||
:second-box-height="secondBoxHeight"
|
||||
:upload-temp-file-api="uploadTempFileCase"
|
||||
|
@ -197,6 +154,7 @@
|
|||
v-else-if="requestVModel.activeTab === RequestComposition.QUERY"
|
||||
v-model:params="requestVModel.query"
|
||||
:layout="activeLayout"
|
||||
:disabled-param-value="!isEditableApi"
|
||||
:disabled-except-param="!isEditableApi"
|
||||
:second-box-height="secondBoxHeight"
|
||||
@change="handleActiveDebugChange"
|
||||
|
@ -205,6 +163,7 @@
|
|||
v-else-if="requestVModel.activeTab === RequestComposition.REST"
|
||||
v-model:params="requestVModel.rest"
|
||||
:layout="activeLayout"
|
||||
:disabled-param-value="!isEditableApi"
|
||||
:disabled-except-param="!isEditableApi"
|
||||
:second-box-height="secondBoxHeight"
|
||||
@change="handleActiveDebugChange"
|
||||
|
@ -288,8 +247,6 @@
|
|||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import assertion from '@/components/business/ms-assertion/index.vue';
|
||||
import stepType from './stepType/stepType.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiMethodSelect from '@/views/api-test/components/apiMethodSelect.vue';
|
||||
import auth from '@/views/api-test/components/requestComposition/auth.vue';
|
||||
import postcondition from '@/views/api-test/components/requestComposition/postcondition.vue';
|
||||
import precondition from '@/views/api-test/components/requestComposition/precondition.vue';
|
||||
|
@ -305,7 +262,7 @@
|
|||
uploadTempFileCase,
|
||||
} from '@/api/modules/api-test/management';
|
||||
import { useAppStore } from '@/store';
|
||||
import { characterLimit, parseQueryParams } from '@/utils';
|
||||
import { characterLimit } from '@/utils';
|
||||
import { scrollIntoView } from '@/utils/dom';
|
||||
|
||||
import { ExecuteConditionConfig, PluginConfig, RequestResult } from '@/models/apiTest/common';
|
||||
|
@ -559,17 +516,12 @@
|
|||
return httpContentTabList;
|
||||
}
|
||||
if (!isEditableApi.value) {
|
||||
return [
|
||||
...pluginContentTab,
|
||||
...httpContentTabList
|
||||
.filter((e) => commonContentTabKey.includes(e.value))
|
||||
.filter(
|
||||
(item) =>
|
||||
!(!preProcessorNum.value && item.value === RequestComposition.PRECONDITION) &&
|
||||
!(!postProcessorNum.value && item.value === RequestComposition.POST_CONDITION) &&
|
||||
!(!assertionsNum.value && item.value === RequestComposition.ASSERTION)
|
||||
),
|
||||
];
|
||||
return [...pluginContentTab, ...httpContentTabList.filter((e) => commonContentTabKey.includes(e.value))].filter(
|
||||
(item) =>
|
||||
!(!preProcessorNum.value && item.value === RequestComposition.PRECONDITION) &&
|
||||
!(!postProcessorNum.value && item.value === RequestComposition.POST_CONDITION) &&
|
||||
!(!assertionsNum.value && item.value === RequestComposition.ASSERTION)
|
||||
);
|
||||
}
|
||||
return [...pluginContentTab, ...httpContentTabList.filter((e) => commonContentTabKey.includes(e.value))];
|
||||
});
|
||||
|
@ -594,7 +546,7 @@
|
|||
case RequestComposition.ASSERTION:
|
||||
return `${assertionsNum.value > 99 ? '99+' : assertionsNum.value || ''}`;
|
||||
case RequestComposition.AUTH:
|
||||
return requestVModel.value.authConfig.authType !== RequestAuthType.NONE ? '1' : '';
|
||||
return requestVModel.value.authConfig?.authType !== RequestAuthType.NONE ? '1' : '';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
@ -744,26 +696,6 @@
|
|||
handleActiveDebugChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理url输入框变化,解析成参数表格
|
||||
*/
|
||||
function handleUrlChange(val: string) {
|
||||
const params = parseQueryParams(val.trim());
|
||||
if (params.length > 0) {
|
||||
requestVModel.value.query = [
|
||||
...params.map((e, i) => ({
|
||||
id: (new Date().getTime() + i).toString(),
|
||||
...defaultRequestParamsItem,
|
||||
...e,
|
||||
})),
|
||||
cloneDeep(defaultRequestParamsItem),
|
||||
];
|
||||
requestVModel.value.activeTab = RequestComposition.QUERY;
|
||||
[requestVModel.value.url] = val.split('?');
|
||||
}
|
||||
handleActiveDebugChange();
|
||||
}
|
||||
|
||||
const showResponse = computed(
|
||||
() => isHttpProtocol.value || requestVModel.value.response?.requestResults[0]?.responseResult.responseCode
|
||||
);
|
||||
|
@ -952,7 +884,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
const isUrlError = ref(false);
|
||||
// const showAddDependencyDrawer = ref(false);
|
||||
// const addDependencyMode = ref<'pre' | 'post'>('pre');
|
||||
|
||||
|
@ -985,6 +916,7 @@
|
|||
...parseRequestBodyResult,
|
||||
};
|
||||
nextTick(() => {
|
||||
requestVModel.value.activeTab = contentTabList.value[0].value;
|
||||
// 等待内容渲染出来再隐藏loading
|
||||
loading.value = false;
|
||||
});
|
||||
|
@ -1010,9 +942,32 @@
|
|||
});
|
||||
if (isQuote.value || isCopyNeedInit.value) {
|
||||
// 引用时,需要初始化引用的详情;复制只在第一次初始化的时候需要加载后台数据(request.request是复制请求时列表参数字段request会为 null,以此判断释放第一次初始化)
|
||||
initQuoteCaseDetail();
|
||||
await initQuoteCaseDetail();
|
||||
}
|
||||
handleActiveDebugProtocolChange(requestVModel.value.protocol);
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.exec-btn {
|
||||
:deep(.arco-btn) {
|
||||
color: white !important;
|
||||
background-color: rgb(var(--primary-5)) !important;
|
||||
.btn-base-primary-hover();
|
||||
.btn-base-primary-active();
|
||||
.btn-base-primary-disabled();
|
||||
}
|
||||
}
|
||||
.tab-pane-container {
|
||||
@apply flex-1 overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
}
|
||||
:deep(.arco-tabs-tab:first-child) {
|
||||
margin-left: 0;
|
||||
}
|
||||
:deep(.arco-tabs-tab) {
|
||||
@apply leading-none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -547,7 +547,7 @@
|
|||
slotName: 'operation',
|
||||
dataIndex: 'operation',
|
||||
fixed: 'right',
|
||||
width: 200,
|
||||
width: 220,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||
|
|
|
@ -210,6 +210,10 @@
|
|||
|
||||
const baseInfoRef = ref<InstanceType<typeof baseInfo>>();
|
||||
function validScenarioForm(cb: () => Promise<void>) {
|
||||
if (!baseInfoRef.value) {
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
baseInfoRef.value?.createFormRef?.validate(async (errors) => {
|
||||
if (errors) {
|
||||
activeKey.value = ScenarioDetailComposition.BASE_INFO;
|
||||
|
|
Loading…
Reference in New Issue